Refine location handling with Zustand store

This commit is contained in:
Bernard Ngandu
2025-10-10 15:29:39 +02:00
parent 39c441d426
commit f6354370cb
11 changed files with 168 additions and 117 deletions
+17 -56
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Layers, Loader2, Menu, PanelRightClose, PanelRightOpen } from 'lucide-react'
import { Layers, Menu, PanelRightClose, PanelRightOpen } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { AppHeader } from '@/components/layout/AppHeader'
@@ -26,34 +26,10 @@ import { cn, distanceInKm, formatCoordinate, formatRelativeTime, formatTimestamp
import type { Point } from '@/types/api'
import { Toaster } from '@/components/ui/toaster'
import { useToast } from '@/components/ui/use-toast'
import { useAppStore } from '@/store/useAppStore'
const RADIUS_KM = 1
interface LocationGateProps {
title: string
message: string
actionLabel: string
onRetry: () => void
isLoading: boolean
}
function LocationGate({ title, message, actionLabel, onRetry, isLoading }: LocationGateProps) {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-background px-6 text-center">
<div className="max-w-md space-y-6">
<div className="space-y-2">
<h1 className="text-2xl font-semibold tracking-tight text-foreground">{title}</h1>
<p className="text-sm text-muted-foreground">{message}</p>
</div>
<Button onClick={onRetry} disabled={isLoading} className="inline-flex items-center gap-2">
{isLoading && <Loader2 className="h-4 w-4 animate-spin" />}
<span>{actionLabel}</span>
</Button>
</div>
</div>
)
}
export default function App() {
const [pendingSpot, setPendingSpot] = useState<Point | null>(null)
const [isConfirmOpen, setIsConfirmOpen] = useState(false)
@@ -91,12 +67,10 @@ export default function App() {
[t],
)
const {
location: userLocation,
error: locationError,
isRequesting: isRequestingLocation,
refresh: refreshLocation,
} = useUserLocation()
const userLocation = useAppStore((state) => state.userLocation)
const locationError = useAppStore((state) => state.locationError)
const isRequestingLocation = useAppStore((state) => state.isRequestingLocation)
const { refresh: refreshLocation } = useUserLocation()
const {
status,
@@ -207,8 +181,10 @@ export default function App() {
const handleLocateUser = useCallback(() => {
if (userLocation) {
focusOn(userLocation, 14)
} else {
refreshLocation()
}
}, [focusOn, userLocation])
}, [focusOn, refreshLocation, userLocation])
const handleFocusMySignal = useCallback(() => {
if (myVisibleSignal) {
@@ -234,20 +210,21 @@ export default function App() {
lat: formatCoordinate(cell.lat, locale),
lng: formatCoordinate(cell.lng, locale),
})
const distanceLabel = userLocation
? `${distanceFormatter.format(distanceInKm(userLocation, cell))} km`
: null
return {
id: `${cell.lat}-${cell.lng}-${index}`,
title: t('hotspots.itemTitle', { index: index + 1 }),
subtitle: hasLocation
? t('hotspots.itemSubtitleWithDistance', {
distance: `${distanceFormatter.format(distanceInKm(userLocation!, cell))} km`,
coordinates,
})
: t('hotspots.itemSubtitle', { coordinates }),
subtitle:
distanceLabel !== null
? t('hotspots.itemSubtitleWithDistance', { distance: distanceLabel, coordinates })
: t('hotspots.itemSubtitle', { coordinates }),
intensity: cell.intensity,
onFocus: () => focusOn({ lat: cell.lat, lng: cell.lng }, 15),
}
}),
[visibleDensity, hasLocation, userLocation, focusOn, distanceFormatter, t, locale],
[visibleDensity, userLocation, focusOn, distanceFormatter, t, locale],
)
const recentActivity = useMemo(
@@ -288,22 +265,6 @@ export default function App() {
: 'translate-y-[calc(100%+1rem)] sm:translate-x-[calc(100%+2rem)]',
)
if (!userLocation) {
const gateTitle = t('location.gate.title')
const gateMessage = locationError ? t(locationError) : t('location.gate.description')
const gateAction = isRequestingLocation ? t('location.gate.loading') : t('location.gate.action')
return (
<LocationGate
title={gateTitle}
message={gateMessage}
actionLabel={gateAction}
onRetry={refreshLocation}
isLoading={isRequestingLocation}
/>
)
}
return (
<>
<div className="relative min-h-screen w-full overflow-hidden bg-background text-foreground">