From ffbff991ab285d71e38c07029b6a98aec0014aeb Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Thu, 27 Aug 2026 00:33:39 -0400 Subject: [PATCH] Center brewery directory map on the user's current location --- .../breweries/components/BreweryMap.tsx | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/web/frontend/app/features/breweries/components/BreweryMap.tsx b/web/frontend/app/features/breweries/components/BreweryMap.tsx index b2c3c390..99b3747a 100644 --- a/web/frontend/app/features/breweries/components/BreweryMap.tsx +++ b/web/frontend/app/features/breweries/components/BreweryMap.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet'; import L, { type LatLngExpression, type LatLngTuple } from 'leaflet'; import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png'; @@ -25,20 +25,58 @@ interface BreweryMapProps { } const FALLBACK_CENTER: LatLngExpression = [20, 0]; +const WORLD_BOUNDS: LatLngTuple[] = [ + [-90, -180], + [90, 180], +]; -const FitBounds = ({ bounds }: { bounds: LatLngTuple[] }) => { +const CURRENT_LOCATION_ICON = L.divIcon({ + className: 'brewery-map-current-location', + html: '', + iconSize: [14, 14], + iconAnchor: [7, 7], +}); + +const useCurrentLocation = () => { + const [position, setPosition] = useState(null); + + useEffect(() => { + if (!navigator.geolocation) return; + + navigator.geolocation.getCurrentPosition( + (pos) => setPosition([pos.coords.latitude, pos.coords.longitude]), + () => setPosition(null), + { enableHighAccuracy: true }, + ); + }, []); + + return position; +}; + +const CurrentLocationMarker = ({ position }: { position: LatLngTuple }) => ( + + Your location + +); + +const CURRENT_LOCATION_ZOOM = 6; + +const RecenterMap = ({ bounds, currentLocation }: { bounds: LatLngTuple[]; currentLocation: LatLngTuple | null }) => { const map = useMap(); useEffect(() => { - if (bounds.length > 0) { - map.fitBounds(bounds, { padding: [32, 32] }); + if (currentLocation) { + map.setView(currentLocation, CURRENT_LOCATION_ZOOM); + } else if (bounds.length > 0) { + map.fitBounds(bounds, { padding: [32, 32], maxZoom: 6 }); } - }, [map, bounds]); + }, [map, bounds, currentLocation]); return null; }; const BreweryMap = ({ breweries }: BreweryMapProps) => { + const currentLocation = useCurrentLocation(); const bounds: LatLngTuple[] = breweries.map((brewery) => [ brewery.latitude, brewery.longitude, @@ -46,16 +84,21 @@ const BreweryMap = ({ breweries }: BreweryMapProps) => { return ( - + + {currentLocation && } {breweries.map((brewery) => (