diff --git a/src/App.js b/src/App.js index 0bc452b..3758f48 100644 --- a/src/App.js +++ b/src/App.js @@ -722,6 +722,8 @@ function App() { authorizedFetch={authorizedFetch} knownStores={stores} userLocation={preferences?.location || null} + locationLoading={preferencesLoading} + onRequestLocation={updateLocation} /> } /> diff --git a/src/components/StoreWatchPage.js b/src/components/StoreWatchPage.js index dd7fdd2..b4a3a39 100644 --- a/src/components/StoreWatchPage.js +++ b/src/components/StoreWatchPage.js @@ -90,7 +90,13 @@ function persistWatchTableState(state) { } } -const StoreWatchPage = ({ authorizedFetch, knownStores = [], userLocation }) => { +const StoreWatchPage = ({ + authorizedFetch, + knownStores = [], + userLocation, + onRequestLocation, + locationLoading = false +}) => { const [regions, setRegions] = useState([]); const [selectedRegionId, setSelectedRegionId] = useState(() => { if (typeof window === 'undefined') { @@ -114,6 +120,9 @@ const StoreWatchPage = ({ authorizedFetch, knownStores = [], userLocation }) => const initialTableState = useMemo(() => readWatchTableState(), []); const [sorting, setSorting] = useState(initialTableState.sorting); const [columnFilters, setColumnFilters] = useState(initialTableState.columnFilters); + const [locationPromptTriggered, setLocationPromptTriggered] = useState(false); + const [locationPromptPending, setLocationPromptPending] = useState(false); + const [locationPromptError, setLocationPromptError] = useState(''); useEffect(() => { if (typeof window === 'undefined') { @@ -130,6 +139,53 @@ const StoreWatchPage = ({ authorizedFetch, knownStores = [], userLocation }) => persistWatchTableState({ sorting, columnFilters }); }, [sorting, columnFilters]); + const requestLocation = useCallback(() => { + if (!onRequestLocation) { + return; + } + setLocationPromptTriggered(true); + if (typeof window === 'undefined' || typeof navigator === 'undefined' || !navigator.geolocation) { + setLocationPromptPending(false); + setLocationPromptError('Standortbestimmung wird von diesem Browser nicht unterstützt.'); + return; + } + setLocationPromptPending(true); + setLocationPromptError(''); + navigator.geolocation.getCurrentPosition( + async (position) => { + setLocationPromptPending(false); + try { + await onRequestLocation({ + lat: position.coords.latitude, + lon: position.coords.longitude + }); + } catch { + setLocationPromptError('Standort konnte nicht gespeichert werden.'); + } + }, + (geoError) => { + setLocationPromptPending(false); + setLocationPromptError( + geoError.code === geoError.PERMISSION_DENIED + ? 'Zugriff auf den Standort wurde verweigert. Bitte erlaube den Zugriff im Browser.' + : 'Standort konnte nicht automatisch ermittelt werden.' + ); + }, + { + enableHighAccuracy: true, + timeout: 10000, + maximumAge: 0 + } + ); + }, [onRequestLocation]); + + useEffect(() => { + if (userLocation || locationLoading || !onRequestLocation || locationPromptTriggered) { + return; + } + requestLocation(); + }, [userLocation, locationLoading, onRequestLocation, locationPromptTriggered, requestLocation]); + const watchedIds = useMemo( () => new Set(watchList.map((entry) => String(entry.storeId))), [watchList] @@ -808,6 +864,28 @@ const StoreWatchPage = ({ authorizedFetch, knownStores = [], userLocation }) => )} + {!userLocation && !locationLoading && onRequestLocation && ( +
+ {locationPromptPending ? ( +

Standort wird automatisch angefragt, um Entfernungen berechnen zu können...

+ ) : locationPromptError ? ( +
+

{locationPromptError}

+ +
+ ) : ( +

Bitte bestätige die Standortabfrage deines Browsers für Entfernungssortierung.

+ )} +
+ )} +