watcher
This commit is contained in:
@@ -722,6 +722,8 @@ function App() {
|
|||||||
authorizedFetch={authorizedFetch}
|
authorizedFetch={authorizedFetch}
|
||||||
knownStores={stores}
|
knownStores={stores}
|
||||||
userLocation={preferences?.location || null}
|
userLocation={preferences?.location || null}
|
||||||
|
locationLoading={preferencesLoading}
|
||||||
|
onRequestLocation={updateLocation}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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 [regions, setRegions] = useState([]);
|
||||||
const [selectedRegionId, setSelectedRegionId] = useState(() => {
|
const [selectedRegionId, setSelectedRegionId] = useState(() => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
@@ -114,6 +120,9 @@ const StoreWatchPage = ({ authorizedFetch, knownStores = [], userLocation }) =>
|
|||||||
const initialTableState = useMemo(() => readWatchTableState(), []);
|
const initialTableState = useMemo(() => readWatchTableState(), []);
|
||||||
const [sorting, setSorting] = useState(initialTableState.sorting);
|
const [sorting, setSorting] = useState(initialTableState.sorting);
|
||||||
const [columnFilters, setColumnFilters] = useState(initialTableState.columnFilters);
|
const [columnFilters, setColumnFilters] = useState(initialTableState.columnFilters);
|
||||||
|
const [locationPromptTriggered, setLocationPromptTriggered] = useState(false);
|
||||||
|
const [locationPromptPending, setLocationPromptPending] = useState(false);
|
||||||
|
const [locationPromptError, setLocationPromptError] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
@@ -130,6 +139,53 @@ const StoreWatchPage = ({ authorizedFetch, knownStores = [], userLocation }) =>
|
|||||||
persistWatchTableState({ sorting, columnFilters });
|
persistWatchTableState({ sorting, columnFilters });
|
||||||
}, [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(
|
const watchedIds = useMemo(
|
||||||
() => new Set(watchList.map((entry) => String(entry.storeId))),
|
() => new Set(watchList.map((entry) => String(entry.storeId))),
|
||||||
[watchList]
|
[watchList]
|
||||||
@@ -808,6 +864,28 @@ const StoreWatchPage = ({ authorizedFetch, knownStores = [], userLocation }) =>
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!userLocation && !locationLoading && onRequestLocation && (
|
||||||
|
<div className="mb-6 rounded-lg border border-blue-200 bg-blue-50 p-4 text-sm text-blue-900">
|
||||||
|
{locationPromptPending ? (
|
||||||
|
<p>Standort wird automatisch angefragt, um Entfernungen berechnen zu können...</p>
|
||||||
|
) : locationPromptError ? (
|
||||||
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<p className="flex-1">{locationPromptError}</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={requestLocation}
|
||||||
|
className="inline-flex items-center justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700 disabled:opacity-60"
|
||||||
|
disabled={locationPromptPending}
|
||||||
|
>
|
||||||
|
Erneut versuchen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p>Bitte bestätige die Standortabfrage deines Browsers für Entfernungssortierung.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mb-6 border border-gray-200 rounded-lg p-4 bg-gray-50">
|
<div className="mb-6 border border-gray-200 rounded-lg p-4 bg-gray-50">
|
||||||
<div className="flex flex-col md:flex-row md:items-end gap-3">
|
<div className="flex flex-col md:flex-row md:items-end gap-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user