Center brewery directory map on the user's current location

This commit is contained in:
Aaron Po
2026-08-27 00:33:39 -04:00
parent f033dc589a
commit ffbff991ab

View File

@@ -1,4 +1,4 @@
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet'; import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet';
import L, { type LatLngExpression, type LatLngTuple } from 'leaflet'; import L, { type LatLngExpression, type LatLngTuple } from 'leaflet';
import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png'; import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png';
@@ -25,20 +25,58 @@ interface BreweryMapProps {
} }
const FALLBACK_CENTER: LatLngExpression = [20, 0]; 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: '<span class="block h-3.5 w-3.5 rounded-full bg-blue-500 border-2 border-white shadow-md"></span>',
iconSize: [14, 14],
iconAnchor: [7, 7],
});
const useCurrentLocation = () => {
const [position, setPosition] = useState<LatLngTuple | null>(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 }) => (
<Marker position={position} icon={CURRENT_LOCATION_ICON}>
<Popup>Your location</Popup>
</Marker>
);
const CURRENT_LOCATION_ZOOM = 6;
const RecenterMap = ({ bounds, currentLocation }: { bounds: LatLngTuple[]; currentLocation: LatLngTuple | null }) => {
const map = useMap(); const map = useMap();
useEffect(() => { useEffect(() => {
if (bounds.length > 0) { if (currentLocation) {
map.fitBounds(bounds, { padding: [32, 32] }); 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; return null;
}; };
const BreweryMap = ({ breweries }: BreweryMapProps) => { const BreweryMap = ({ breweries }: BreweryMapProps) => {
const currentLocation = useCurrentLocation();
const bounds: LatLngTuple[] = breweries.map((brewery) => [ const bounds: LatLngTuple[] = breweries.map((brewery) => [
brewery.latitude, brewery.latitude,
brewery.longitude, brewery.longitude,
@@ -46,16 +84,21 @@ const BreweryMap = ({ breweries }: BreweryMapProps) => {
return ( return (
<MapContainer <MapContainer
center={bounds[0] ?? FALLBACK_CENTER} center={currentLocation ?? bounds[0] ?? FALLBACK_CENTER}
zoom={4} zoom={4}
scrollWheelZoom={false} scrollWheelZoom={false}
maxBounds={WORLD_BOUNDS}
maxBoundsViscosity={1.0}
worldCopyJump={false}
className="h-112 md:h-136 w-full rounded-box relative z-0" className="h-112 md:h-136 w-full rounded-box relative z-0"
> >
<TileLayer <TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
noWrap
/> />
<FitBounds bounds={bounds} /> <RecenterMap bounds={bounds} currentLocation={currentLocation} />
{currentLocation && <CurrentLocationMarker position={currentLocation} />}
{breweries.map((brewery) => ( {breweries.map((brewery) => (
<Marker key={brewery.id} position={[brewery.latitude, brewery.longitude]}> <Marker key={brewery.id} position={[brewery.latitude, brewery.longitude]}>
<Popup> <Popup>