Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified doc/screenshot2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified doc/screenshot3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"
/>
<meta name="google" content="notranslate" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

<title>Fredy || Real Estate Finder</title>
Expand Down
12 changes: 12 additions & 0 deletions lib/api/routes/listingsRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ listingsRouter.get('/map', async (req, res) => {
res.send();
});

listingsRouter.get('/:listingId', async (req, res) => {
const { listingId } = req.params;
const listing = listingStorage.getListingById(listingId, req.session.currentUser, isAdminFn(req));
if (!listing) {
res.statusCode = 404;
res.body = { message: 'Listing not found' };
return res.send();
}
res.body = listing;
res.send();
});

// Toggle watch state for the current user on a listing
listingsRouter.post('/watch', async (req, res) => {
try {
Expand Down
26 changes: 26 additions & 0 deletions lib/services/storage/listingsStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -566,3 +566,29 @@ export const updateListingDistance = (id, distance) => {
{ id, distance },
);
};

/**
* Return a single listing by id.
*
* @param {string} id
* @param {string} userId
* @param {boolean} isAdmin
* @returns {Object|null}
*/
export const getListingById = (id, userId = null, isAdmin = false) => {
const params = { id, userId: userId || '__NO_USER__' };
let whereScoping = '';
if (!isAdmin) {
whereScoping = `AND (j.user_id = @userId OR EXISTS (SELECT 1 FROM json_each(j.shared_with_user) AS sw WHERE sw.value = @userId))`;
}
return (
SqliteConnection.query(
`SELECT l.*, j.name AS job_name, CASE WHEN wl.id IS NOT NULL THEN 1 ELSE 0 END AS isWatched
FROM listings l
LEFT JOIN jobs j ON j.id = l.job_id
LEFT JOIN watch_list wl ON wl.listing_id = l.id AND wl.user_id = @userId
WHERE l.id = @id AND l.manually_deleted = 0 ${whereScoping}`,
params,
)[0] || null
);
};
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fredy",
"version": "19.2.2",
"version": "19.3.0",
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
"scripts": {
"prepare": "husky",
Expand Down Expand Up @@ -72,20 +72,20 @@
"cookie-session": "2.1.1",
"handlebars": "4.7.8",
"lodash": "4.17.23",
"maplibre-gl": "^5.16.0",
"maplibre-gl": "^5.17.0",
"nanoid": "5.1.6",
"node-cron": "^4.2.1",
"node-fetch": "3.3.2",
"node-mailjet": "6.0.11",
"p-throttle": "^8.1.0",
"package-up": "^5.0.0",
"puppeteer": "^24.36.0",
"puppeteer": "^24.36.1",
"puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-stealth": "^2.11.2",
"query-string": "9.3.1",
"react": "19.2.3",
"react": "19.2.4",
"react-chartjs-2": "^5.3.1",
"react-dom": "19.2.3",
"react-dom": "19.2.4",
"react-range-slider-input": "^3.3.2",
"react-router": "7.13.0",
"react-router-dom": "7.13.0",
Expand Down
127 changes: 62 additions & 65 deletions ui/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Jobs from './views/jobs/Jobs';

import './App.less';
import TrackingModal from './components/tracking/TrackingModal.jsx';
import { Banner, Divider } from '@douyinfe/semi-ui-19';
import { Banner } from '@douyinfe/semi-ui-19';
import VersionBanner from './components/version/VersionBanner.jsx';
import Listings from './views/listings/Listings.jsx';
import MapView from './views/listings/Map.jsx';
Expand All @@ -28,6 +28,7 @@ import { Layout } from '@douyinfe/semi-ui-19';
import FredyFooter from './components/footer/FredyFooter.jsx';
import WatchlistManagement from './views/listings/management/WatchlistManagement.jsx';
import Dashboard from './views/dashboard/Dashboard.jsx';
import ListingDetail from './views/listings/ListingDetail.jsx';

export default function FredyApp() {
const actions = useActions();
Expand Down Expand Up @@ -59,7 +60,7 @@ export default function FredyApp() {
};

const isAdmin = () => currentUser != null && currentUser.isAdmin;
const { Footer, Sider, Content } = Layout;
const { Sider, Content } = Layout;

return loading ? null : needsLogin() ? (
<Routes>
Expand All @@ -68,11 +69,11 @@ export default function FredyApp() {
</Routes>
) : (
<Layout className="app">
<Layout className="app">
<Sider>
<Navigation isAdmin={isAdmin()} />
</Sider>
<Content>
<Sider>
<Navigation isAdmin={isAdmin()} />
</Sider>
<Layout className="app__main">
<Content className="app__content">
{versionUpdate?.newVersion && <VersionBanner />}
{settings.demoMode && (
<>
Expand All @@ -87,68 +88,64 @@ export default function FredyApp() {
</>
)}
{settings.analyticsEnabled === null && !settings.demoMode && <TrackingModal />}
<Divider />
<div className="app__content">
<Routes>
<Route path="/403" element={<InsufficientPermission />} />
<Route path="/jobs/new" element={<JobMutation />} />
<Route path="/jobs/edit/:jobId" element={<JobMutation />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/jobs" element={<Jobs />} />
<Route path="/listings" element={<Listings />} />
<Route path="/map" element={<MapView />} />
<Route path="/watchlistManagement" element={<WatchlistManagement />} />
<Routes>
<Route path="/403" element={<InsufficientPermission />} />
<Route path="/jobs/new" element={<JobMutation />} />
<Route path="/jobs/edit/:jobId" element={<JobMutation />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/jobs" element={<Jobs />} />
<Route path="/listings" element={<Listings />} />
<Route path="/listings/listing/:listingId" element={<ListingDetail />} />
<Route path="/map" element={<MapView />} />
<Route path="/watchlistManagement" element={<WatchlistManagement />} />

{/* Permission-aware routes */}
<Route
path="/users/new"
element={
<PermissionAwareRoute currentUser={currentUser}>
<UserMutator />
</PermissionAwareRoute>
}
/>
<Route
path="/users/edit/:userId"
element={
<PermissionAwareRoute currentUser={currentUser}>
<UserMutator />
</PermissionAwareRoute>
}
/>
<Route
path="/users"
element={
<PermissionAwareRoute currentUser={currentUser}>
<Users />
</PermissionAwareRoute>
}
/>
<Route
path="/userSettings"
element={
<PermissionAwareRoute currentUser={currentUser} adminOnly={false}>
<UserSettings />
</PermissionAwareRoute>
}
/>
<Route
path="/generalSettings"
element={
<PermissionAwareRoute currentUser={currentUser}>
<GeneralSettings />
</PermissionAwareRoute>
}
/>
{/* Permission-aware routes */}
<Route
path="/users/new"
element={
<PermissionAwareRoute currentUser={currentUser}>
<UserMutator />
</PermissionAwareRoute>
}
/>
<Route
path="/users/edit/:userId"
element={
<PermissionAwareRoute currentUser={currentUser}>
<UserMutator />
</PermissionAwareRoute>
}
/>
<Route
path="/users"
element={
<PermissionAwareRoute currentUser={currentUser}>
<Users />
</PermissionAwareRoute>
}
/>
<Route
path="/userSettings"
element={
<PermissionAwareRoute currentUser={currentUser} adminOnly={false}>
<UserSettings />
</PermissionAwareRoute>
}
/>
<Route
path="/generalSettings"
element={
<PermissionAwareRoute currentUser={currentUser}>
<GeneralSettings />
</PermissionAwareRoute>
}
/>

<Route path="/" element={<Navigate to="/dashboard" replace />} />
</Routes>
</div>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
</Routes>
</Content>
</Layout>
<Footer>
<FredyFooter />
</Footer>
</Layout>
</Layout>
);
}
Expand Down
58 changes: 20 additions & 38 deletions ui/src/App.less
Original file line number Diff line number Diff line change
@@ -1,47 +1,29 @@
.app {
height: 100%;
width: 100%;
height: 100vh;
width: 100vw;

&__main {
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}

&__content {
margin: 1rem;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
position: relative;
padding: 24px;
background-color: var(--semi-color-bg-0);
box-sizing: border-box;

@media (max-width: 768px) {
padding: 12px;
}
}
}

.ui.inverted.segment {
background: #31303078 !important;
}

.ui.black.label,
.ui.black.labels .label {
background-color: #31303078 !important;
}

a:link {
color: #54a9ff;
background-color: transparent;
text-decoration: none;
}

a:visited {
color: #54a9ff;
background-color: transparent;
text-decoration: none;
}

a:hover {
color: #54a9ff;
background-color: transparent;
text-decoration: underline;
}

a:active {
color: #54a9ff;
background-color: transparent;
text-decoration: underline;
}

a {outline : none;}

.semi-icon:not(.semi-tabs-bar .semi-tabs-tab .semi-icon) {
vertical-align: middle;
}
Loading
Loading