diff --git a/lib/api/api.js b/lib/api/api.js index 338d457..1eb304a 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -35,7 +35,6 @@ service.use('/api/jobs', authInterceptor()); service.use('/api/version', authInterceptor()); service.use('/api/listings', authInterceptor()); service.use('/api/dashboard', authInterceptor()); -service.use('/api/features', authInterceptor()); service.use('/api/user/settings', authInterceptor()); // /admin can only be accessed when user is having admin permissions diff --git a/package.json b/package.json index 8795fb7..96e77ba 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "19.0.0", + "version": "19.1.0", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", @@ -68,7 +68,7 @@ "better-sqlite3": "^12.6.2", "body-parser": "2.2.2", "chart.js": "^4.5.1", - "cheerio": "^1.1.2", + "cheerio": "^1.2.0", "cookie-session": "2.1.1", "handlebars": "4.7.8", "lodash": "4.17.23", @@ -87,8 +87,8 @@ "react-chartjs-2": "^5.3.1", "react-dom": "19.2.3", "react-range-slider-input": "^3.3.2", - "react-router": "7.12.0", - "react-router-dom": "7.12.0", + "react-router": "7.13.0", + "react-router-dom": "7.13.0", "restana": "5.1.0", "semver": "^7.7.3", "serve-static": "2.2.1", diff --git a/ui/src/App.jsx b/ui/src/App.jsx index cda8f23..a1cbc90 100644 --- a/ui/src/App.jsx +++ b/ui/src/App.jsx @@ -40,12 +40,12 @@ export default function FredyApp() { async function init() { await actions.user.getCurrentUser(); if (!needsLogin()) { - await actions.features.getFeatures(); await actions.provider.getProvider(); await actions.jobsData.getJobs(); await actions.jobsData.getSharableUserList(); await actions.notificationAdapter.getAdapter(); await actions.generalSettings.getGeneralSettings(); + await actions.userSettings.getUserSettings(); await actions.versionUpdate.getVersionUpdate(); } setLoading(false); diff --git a/ui/src/services/state/store.js b/ui/src/services/state/store.js index d77b9d2..9ab762e 100644 --- a/ui/src/services/state/store.js +++ b/ui/src/services/state/store.js @@ -63,16 +63,6 @@ export const useFredyState = create( } }, }, - features: { - async getFeatures() { - try { - const response = await xhrGet('/api/features'); - set((state) => ({ ...state.features, ...response.json })); - } catch (Exception) { - console.error('Error while trying to get resource for api/features. Error:', Exception); - } - }, - }, provider: { async getProvider() { try { @@ -228,6 +218,16 @@ export const useFredyState = create( } }, }, + userSettings: { + async getUserSettings() { + try { + const response = await xhrGet('/api/user/settings'); + set((state) => ({ userSettings: { ...state.userSettings, settings: response.json } })); + } catch (Exception) { + console.error('Error while trying to get resource for api/user/settings. Error:', Exception); + } + }, + }, }; // Initial state @@ -241,8 +241,8 @@ export const useFredyState = create( mapListings: [], maxPrice: 0, }, - features: {}, generalSettings: { settings: {} }, + userSettings: { settings: {} }, demoMode: { demoMode: false }, versionUpdate: {}, provider: [], @@ -265,9 +265,9 @@ export const useFredyState = create( versionUpdate: { ...effects.versionUpdate }, listingsData: { ...effects.listingsData }, provider: { ...effects.provider }, - features: { ...effects.features }, jobsData: { ...effects.jobsData }, user: { ...effects.user }, + userSettings: { ...effects.userSettings }, }; return { diff --git a/ui/src/views/listings/Map.jsx b/ui/src/views/listings/Map.jsx index 5ef3adb..721071d 100644 --- a/ui/src/views/listings/Map.jsx +++ b/ui/src/views/listings/Map.jsx @@ -7,6 +7,7 @@ import React, { useEffect, useRef, useState } from 'react'; import maplibregl from 'maplibre-gl'; import 'maplibre-gl/dist/maplibre-gl.css'; import { useSelector, useActions } from '../../services/state/store.js'; +import { distanceMeters, generateCircleCoords, getBoundsFromCenter } from './mapUtils.js'; import { Select, Space, Typography, Button, Popover, Divider, Switch, Banner } from '@douyinfe/semi-ui-19'; import { IconFilter } from '@douyinfe/semi-icons'; import no_image from '../../assets/no_image.jpg'; @@ -65,8 +66,10 @@ export default function MapView() { const mapContainer = useRef(null); const map = useRef(null); const markers = useRef([]); + const homeMarker = useRef(null); const actions = useActions(); const listings = useSelector((state) => state.listingsData.mapListings); + const homeAddress = useSelector((state) => state.userSettings.settings.home_address); const [style, setStyle] = useState('STANDARD'); const [show3dBuildings, setShow3dBuildings] = useState(false); @@ -74,6 +77,7 @@ export default function MapView() { const [jobId, setJobId] = useState(null); const [priceRange, setPriceRange] = useState([0, 0]); const [showFilterBar, setShowFilterBar] = useState(false); + const [distanceFilter, setDistanceFilter] = useState(0); useEffect(() => { setPriceRange([0, getMaxPrice()]); @@ -150,6 +154,7 @@ export default function MapView() { if (!map.current) return; const add3dLayer = () => { + if (!map.current || !map.current.isStyleLoaded()) return; if (show3dBuildings) { if (!map.current.getSource('openfreemap')) { map.current.addSource('openfreemap', { @@ -201,11 +206,7 @@ export default function MapView() { } }; - if (map.current.isStyleLoaded()) { - add3dLayer(); - } else { - map.current.once('styledata', add3dLayer); - } + add3dLayer(); }, [show3dBuildings, style]); const setMapStyle = (value) => { @@ -225,12 +226,94 @@ export default function MapView() { fetchListings(); }, [jobId]); + useEffect(() => { + if (!map.current || !homeAddress?.coords) return; + + // We only want to zoom/fly when distanceFilter OR homeAddress actually change, + // not on every render. useEffect dependency array handles this. + if (distanceFilter > 0) { + const bounds = getBoundsFromCenter([homeAddress.coords.lng, homeAddress.coords.lat], distanceFilter); + + map.current.fitBounds(bounds, { + padding: 20, + maxZoom: 15, + duration: 1000, + }); + } else { + map.current.flyTo({ + center: [homeAddress.coords.lng, homeAddress.coords.lat], + zoom: 12, + duration: 1000, + }); + } + }, [homeAddress?.address, distanceFilter]); + useEffect(() => { if (!map.current) return; markers.current.forEach((marker) => marker.remove()); markers.current = []; + if (homeMarker.current) { + homeMarker.current.remove(); + homeMarker.current = null; + } + + if (homeAddress?.coords) { + homeMarker.current = new maplibregl.Marker({ color: 'red' }) + .setLngLat([homeAddress.coords.lng, homeAddress.coords.lat]) + .setPopup( + new maplibregl.Popup({ offset: 25 }).setHTML( + `
${homeAddress.address}