This commit is contained in:
Andras Schmelczer 2026-05-14 20:42:48 +01:00
parent 273d7a83ee
commit 084117cea8
48 changed files with 2283 additions and 890 deletions

View file

@ -1,7 +1,7 @@
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { PostcodeGeometry, Property } from '../../types';
import type { MapFlyToOptions, PostcodeGeometry } from '../../types';
import type { SearchedLocation } from './LocationSearch';
import { useMapData } from '../../hooks/useMapData';
import { usePOIData } from '../../hooks/usePOIData';
@ -19,6 +19,7 @@ import { useFilterCounts } from '../../hooks/useFilterCounts';
import { trackEvent } from '../../lib/analytics';
import { INITIAL_VIEW_STATE, POSTCODE_SEARCH_ZOOM } from '../../lib/consts';
import { useLicense } from '../../hooks/useLicense';
import { stateToParams } from '../../lib/url-state';
import {
AreaPane,
Filters,
@ -30,7 +31,7 @@ import { PaneFallback } from './map-page/Fallbacks';
import { DesktopMapPage } from './map-page/DesktopMapPage';
import { MobileMapPage } from './map-page/MobileMapPage';
import { ScreenshotMapPage } from './map-page/ScreenshotMapPage';
import { BookmarkToast, ExportToast } from './map-page/Toasts';
import { ExportToast } from './map-page/Toasts';
import { MobileMapLegend } from './map-page/MobileMapLegend';
import { useExportController } from './map-page/useExportController';
import {
@ -66,6 +67,7 @@ export default function MapPage({
onClearPendingInfoFeature,
onNavigateTo,
onExportStateChange,
onDashboardParamsChange,
screenshotMode,
ogMode,
isMobile = false,
@ -75,10 +77,8 @@ export default function MapPage({
user,
onLoginClick,
onRegisterClick,
onSaveProperty,
onUnsaveProperty,
isPropertySaved,
getSavedPropertyId,
onCheckoutLoginClick,
onCheckoutRegisterClick,
deferTutorial = false,
onSaveSearch,
savingSearch,
@ -92,19 +92,6 @@ export default function MapPage({
const [mobileBottomSheetHeight, setMobileBottomSheetHeight] = useState(0);
const [poiPaneOpen, setPoiPaneOpen] = useState(false);
const [currentLocation, setCurrentLocation] = useState<{ lat: number; lng: number } | null>(null);
const [showBookmarkToast, setShowBookmarkToast] = useState(false);
const bookmarkToastDismissed = useRef(localStorage.getItem('bookmark_toast_dismissed') === '1');
const handleSavePropertyWithToast = useCallback(
(property: Property) => {
onSaveProperty?.(property);
if (!bookmarkToastDismissed.current) {
setShowBookmarkToast(true);
bookmarkToastDismissed.current = true;
}
},
[onSaveProperty]
);
const {
filters,
@ -155,6 +142,22 @@ export default function MapPage({
const pendingLocationSearchFlyToRef = useRef<PendingFlyTo | null>(null);
const mobileDrawerPanelRectRef = useRef<DOMRectReadOnly | null>(null);
const getMobileMapFlyToOptions = useCallback((): MapFlyToOptions | undefined => {
if (!isMobile) return undefined;
const panelRect = mobileDrawerPanelRectRef.current;
if (mobileDrawerOpen && panelRect) {
const bottomInset = Math.max(0, window.innerHeight - panelRect.top);
if (bottomInset > 0) {
return { visibleViewportArea: { bottom: bottomInset } };
}
}
return mobileBottomSheetHeight > 0
? { visibleArea: { bottom: mobileBottomSheetHeight } }
: undefined;
}, [isMobile, mobileBottomSheetHeight, mobileDrawerOpen]);
const mapData = useMapData({
filters,
features,
@ -209,7 +212,8 @@ export default function MapPage({
mapFlyToRef.current?.(
destination.lat,
destination.lon,
mapData.currentView?.zoom ?? INITIAL_VIEW_STATE.zoom
mapData.currentView?.zoom ?? INITIAL_VIEW_STATE.zoom,
getMobileMapFlyToOptions()
);
}
} catch {
@ -220,6 +224,7 @@ export default function MapPage({
activeEntries,
fetchAiFilters,
filters,
getMobileMapFlyToOptions,
handleSetEntries,
handleSetFilters,
mapData.currentView?.zoom,
@ -251,17 +256,22 @@ export default function MapPage({
[handleDragEndNoCommit, handleTimeRangeChange]
);
const filterCounts = useFilterCounts(filters, features, mapData.bounds, entries);
const filterCounts = useFilterCounts(filters, features, mapData.bounds, entries, shareCode);
const license = useLicense();
const handleTravelTimeSetDestination = useCallback(
(index: number, slug: string, label: string, lat: number, lon: number) => {
handleSetDestination(index, slug, label);
if (slug) {
mapFlyToRef.current?.(lat, lon, mapData.currentView?.zoom ?? INITIAL_VIEW_STATE.zoom);
mapFlyToRef.current?.(
lat,
lon,
mapData.currentView?.zoom ?? INITIAL_VIEW_STATE.zoom,
getMobileMapFlyToOptions()
);
}
},
[handleSetDestination, mapData.currentView?.zoom]
[getMobileMapFlyToOptions, handleSetDestination, mapData.currentView?.zoom]
);
const journeyDest = useJourneyDestination(entries);
@ -430,9 +440,11 @@ export default function MapPage({
useScreenshotReadySignal({
screenshotMode,
loading: mapData.loading,
boundsReady: mapData.bounds != null,
dataLength: mapData.data.length,
postcodeDataLength: mapData.postcodeData.length,
usePostcodeView: mapData.usePostcodeView,
licenseRequired: mapData.licenseRequired,
});
const handleMobileHexagonClick = useCallback(
@ -462,10 +474,42 @@ export default function MapPage({
bounds: mapData.bounds,
filters,
features,
travelTimeEntries: entries,
shareCode,
t,
onExportStateChange,
});
const dashboardParams = useMemo(
() =>
stateToParams(
mapData.currentView,
filters,
features,
selectedPOICategories,
rightPaneTab,
entries,
shareCode
).toString(),
[
entries,
features,
filters,
mapData.currentView,
rightPaneTab,
selectedPOICategories,
shareCode,
]
);
const checkoutReturnPath = useMemo(
() => `/dashboard${dashboardParams ? `?${dashboardParams}` : ''}`,
[dashboardParams]
);
useEffect(() => {
onDashboardParamsChange?.(dashboardParams);
}, [dashboardParams, onDashboardParamsChange]);
useEffect(() => {
if (mapData.licenseRequired) trackEvent('Upgrade Modal Shown');
}, [mapData.licenseRequired]);
@ -501,6 +545,7 @@ export default function MapPage({
statsUseFilters={areaStatsUseFilters}
onStatsUseFiltersChange={setAreaStatsUseFilters}
travelTimeEntries={activeEntries}
shareCode={shareCode}
isGroupExpanded={isAreaGroupExpanded}
onToggleGroup={toggleAreaGroup}
/>
@ -515,10 +560,6 @@ export default function MapPage({
loading={loadingProperties}
hexagonId={selectedHexagon?.id || null}
onLoadMore={handleLoadMoreProperties}
onSaveProperty={onSaveProperty ? handleSavePropertyWithToast : undefined}
onUnsaveProperty={onUnsaveProperty}
isPropertySaved={isPropertySaved}
getSavedPropertyId={getSavedPropertyId}
/>
</Suspense>
);
@ -589,40 +630,25 @@ export default function MapPage({
}
};
const bookmarkToast = (
<BookmarkToast
show={showBookmarkToast}
onViewSaved={() => {
setShowBookmarkToast(false);
onNavigateTo('saved', 'properties');
}}
onDismissForever={() => {
setShowBookmarkToast(false);
localStorage.setItem('bookmark_toast_dismissed', '1');
}}
/>
);
const exportToast = (
<ExportToast
notice={exportNotice}
offsetForBookmark={showBookmarkToast}
closeLabel={t('common.close')}
onClose={clearExportNotice}
/>
);
const toasts = (
<>
{bookmarkToast}
{exportToast}
</>
);
const toasts = exportToast;
const upgradeModal = mapData.licenseRequired ? (
<Suspense fallback={null}>
<UpgradeModal
isLoggedIn={!!user}
onLoginClick={onLoginClick}
onRegisterClick={onRegisterClick}
onStartCheckout={() => license.startCheckout()}
onLoginClick={() =>
onCheckoutLoginClick ? onCheckoutLoginClick(checkoutReturnPath) : onLoginClick()
}
onRegisterClick={() =>
onCheckoutRegisterClick ? onCheckoutRegisterClick(checkoutReturnPath) : onRegisterClick()
}
onStartCheckout={() => license.startCheckout(checkoutReturnPath)}
onZoomToFreeZone={handleZoomToFreeZone}
isShareReturn={!!shareReturnViewRef.current}
/>