summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2026-03-16 14:13:26 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2026-03-16 14:13:35 +0100
commitac626a9c2edc2e528eb0b39002c836a747b2fc16 (patch)
tree981a9f8b02370f9a54459a8171cdd9a32aa11c15
parent8942cf3c705bbc78a6b3317599658e9bb86dd31b (diff)
feat: enhance geolocation handling with loading state and improve button click behavior
-rw-r--r--src/frontend/app/components/PlannerOverlay.tsx8
-rw-r--r--src/frontend/app/components/shared/AppMap.tsx9
-rw-r--r--src/frontend/app/contexts/MapContext.tsx2
-rw-r--r--src/frontend/app/hooks/useGeolocation.ts17
4 files changed, 23 insertions, 13 deletions
diff --git a/src/frontend/app/components/PlannerOverlay.tsx b/src/frontend/app/components/PlannerOverlay.tsx
index d953c2e..d42bd94 100644
--- a/src/frontend/app/components/PlannerOverlay.tsx
+++ b/src/frontend/app/components/PlannerOverlay.tsx
@@ -534,7 +534,7 @@ export const PlannerOverlay: React.FC<PlannerOverlayProps> = ({
<button
type="button"
className="flex w-full items-center justify-between px-4 py-3 text-left hover:bg-slate-50 dark:hover:bg-slate-800 disabled:opacity-50 transition-colors duration-200"
- onClick={() => setOriginFromCurrentLocation}
+ onClick={() => setOriginFromCurrentLocation()}
disabled={locationLoading}
>
<div className="flex items-center gap-2">
@@ -550,8 +550,10 @@ export const PlannerOverlay: React.FC<PlannerOverlayProps> = ({
</div>
</div>
</div>
- <div className="text-lg text-slate-600 dark:text-slate-400">
- {locationLoading ? "…" : ""}
+ <div className="flex items-center">
+ {locationLoading && (
+ <div className="w-4 h-4 border-2 border-primary-500 border-t-transparent rounded-full animate-spin" />
+ )}
</div>
</button>
</li>
diff --git a/src/frontend/app/components/shared/AppMap.tsx b/src/frontend/app/components/shared/AppMap.tsx
index f4c8658..8d0aa64 100644
--- a/src/frontend/app/components/shared/AppMap.tsx
+++ b/src/frontend/app/components/shared/AppMap.tsx
@@ -8,6 +8,7 @@ import {
useRef,
useState,
} from "react";
+import { useTranslation } from "react-i18next";
import Map, {
GeolocateControl,
NavigationControl,
@@ -15,7 +16,6 @@ import Map, {
type MapRef,
type StyleSpecification,
} from "react-map-gl/maplibre";
-import { useTranslation } from "react-i18next";
import { useLocation } from "react-router";
import { useApp } from "~/AppContext";
import { APP_CONSTANTS } from "~/config/constants";
@@ -82,7 +82,6 @@ export const AppMap = forwardRef<MapRef, AppMapProps>(
mapState,
updateMapState,
setUserLocation,
- setLocationPermission,
showTraffic: settingsShowTraffic,
showCameras: settingsShowCameras,
mapPositionMode,
@@ -213,11 +212,13 @@ export const AppMap = forwardRef<MapRef, AppMapProps>(
{showGeolocate && (
<GeolocateControl
position="bottom-right"
- positionOptions={{ enableHighAccuracy: false }}
+ positionOptions={{
+ maximumAge: 1000 * 60 * 60 * 4,
+ enableHighAccuracy: false,
+ }}
onGeolocate={(e) => {
const { latitude, longitude } = e.coords;
setUserLocation([latitude, longitude]);
- setLocationPermission(true);
}}
/>
)}
diff --git a/src/frontend/app/contexts/MapContext.tsx b/src/frontend/app/contexts/MapContext.tsx
index d93fefc..2c199be 100644
--- a/src/frontend/app/contexts/MapContext.tsx
+++ b/src/frontend/app/contexts/MapContext.tsx
@@ -72,6 +72,7 @@ export const MapProvider = ({ children }: { children: ReactNode }) => {
const setLocationPermission = useCallback(
(hasLocationPermission: boolean) => {
setMapState((prev) => {
+ if (prev.hasLocationPermission === hasLocationPermission) return prev;
const newState = { ...prev, hasLocationPermission };
localStorage.setItem("mapState", JSON.stringify(newState));
return newState;
@@ -103,7 +104,6 @@ export const MapProvider = ({ children }: { children: ReactNode }) => {
(position) => {
const { latitude, longitude } = position.coords;
setUserLocation([latitude, longitude]);
- setLocationPermission(true);
},
(error) => {
if (error.code === GeolocationPositionError.PERMISSION_DENIED) {
diff --git a/src/frontend/app/hooks/useGeolocation.ts b/src/frontend/app/hooks/useGeolocation.ts
index 878420b..78e9096 100644
--- a/src/frontend/app/hooks/useGeolocation.ts
+++ b/src/frontend/app/hooks/useGeolocation.ts
@@ -1,16 +1,18 @@
-import { useCallback } from "react";
-import { useMap } from "../contexts/MapContext";
import type { LngLatLike } from "maplibre-gl";
+import { useCallback, useState } from "react";
+import { useMap } from "../contexts/MapContext";
export interface UseGeolocationResult {
userLocation: { latitude: number; longitude: number } | null;
hasLocationPermission: boolean;
+ isLoading: boolean;
requestLocation: () => void;
}
-function lngLatToCoords(
- loc: LngLatLike
-): { latitude: number; longitude: number } {
+function lngLatToCoords(loc: LngLatLike): {
+ latitude: number;
+ longitude: number;
+} {
if (Array.isArray(loc)) {
// This codebase stores location as [latitude, longitude] (not the standard
// MapLibre [lng, lat] GeoJSON order). See MapContext.tsx where arrays are
@@ -36,16 +38,20 @@ function lngLatToCoords(
*/
export function useGeolocation(): UseGeolocationResult {
const { mapState, setUserLocation, setLocationPermission } = useMap();
+ const [isLoading, setIsLoading] = useState(false);
const requestLocation = useCallback(() => {
if (typeof window === "undefined" || !("geolocation" in navigator)) return;
+ setIsLoading(true);
navigator.geolocation.getCurrentPosition(
(pos) => {
setUserLocation([pos.coords.latitude, pos.coords.longitude]);
setLocationPermission(true);
+ setIsLoading(false);
},
() => {
setLocationPermission(false);
+ setIsLoading(false);
},
{ enableHighAccuracy: false, maximumAge: 60000, timeout: 10000 }
);
@@ -57,6 +63,7 @@ export function useGeolocation(): UseGeolocationResult {
return {
userLocation,
hasLocationPermission: mapState.hasLocationPermission,
+ isLoading,
requestLocation,
};
}