diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-06-24 16:02:02 +0200 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-06-24 16:02:02 +0200 |
| commit | f65b4e1e0d5648038823962349279be4badc68ed (patch) | |
| tree | 402635814103fde9060c8710523bb4b11ba0a01d /src/frontend | |
| parent | dc4a7f316c1e3f3392ffd68b6a432eddd7013868 (diff) | |
Refactor navigation structure: move NavBar to its own component, implement geolocation handling, and remove unused isWithinVigo function from AppContext.
Diffstat (limited to 'src/frontend')
| -rw-r--r-- | src/frontend/app/AppContext.tsx | 40 | ||||
| -rw-r--r-- | src/frontend/app/components/NavBar.tsx | 84 | ||||
| -rw-r--r-- | src/frontend/app/root.tsx | 54 |
3 files changed, 102 insertions, 76 deletions
diff --git a/src/frontend/app/AppContext.tsx b/src/frontend/app/AppContext.tsx index 7ca85bd..d8db66d 100644 --- a/src/frontend/app/AppContext.tsx +++ b/src/frontend/app/AppContext.tsx @@ -113,46 +113,6 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { }; }); - // Helper: check if coordinates are within Vigo bounds - function isWithinVigo(lngLat: LngLatLike): boolean { - let lng: number, lat: number; - if (Array.isArray(lngLat)) { - [lng, lat] = lngLat; - } else if ('lng' in lngLat && 'lat' in lngLat) { - lng = lngLat.lng; - lat = lngLat.lat; - } else { - return false; - } - // Rough bounding box for Vigo - return lat >= 42.18 && lat <= 42.30 && lng >= -8.78 && lng <= -8.65; - } - - // On app load, if mapPositionMode is 'gps', try to get GPS and set map center - useEffect(() => { - if (mapPositionMode === 'gps') { - if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition( - (position) => { - const { latitude, longitude } = position.coords; - const coords: LngLatLike = [latitude, longitude]; - if (isWithinVigo(coords)) { - setMapState(prev => { - const newState = { ...prev, center: coords, zoom: 16, userLocation: coords }; - localStorage.setItem('mapState', JSON.stringify(newState)); - return newState; - }); - } - }, - () => { - // Ignore error, fallback to last - } - ); - } - } - // If 'last', do nothing (already loaded from localStorage) - }, [mapPositionMode]); - const setMapCenter = (center: LngLatLike) => { setMapState(prev => { const newState = { ...prev, center }; diff --git a/src/frontend/app/components/NavBar.tsx b/src/frontend/app/components/NavBar.tsx new file mode 100644 index 0000000..091cc21 --- /dev/null +++ b/src/frontend/app/components/NavBar.tsx @@ -0,0 +1,84 @@ +import { Link } from "react-router"; +import { Map, MapPin, Settings } from "lucide-react"; +import { useApp } from "../AppContext"; +import type { LngLatLike } from "maplibre-gl"; + +// Helper: check if coordinates are within Vigo bounds +function isWithinVigo(lngLat: LngLatLike): boolean { + let lng: number, lat: number; + if (Array.isArray(lngLat)) { + [lng, lat] = lngLat; + } else if ('lng' in lngLat && 'lat' in lngLat) { + lng = lngLat.lng; + lat = lngLat.lat; + } else { + return false; + } + // Rough bounding box for Vigo + return lat >= 42.18 && lat <= 42.30 && lng >= -8.78 && lng <= -8.65; +} + +export default function NavBar() { + const { mapState, updateMapState, mapPositionMode } = useApp(); + + const navItems = [ + { + name: 'Paradas', + icon: MapPin, + path: '/stops' + }, + { + name: 'Mapa', + icon: Map, + path: '/map', + callback: () => { + if (mapPositionMode !== 'gps') { + return; + } + + if (!('geolocation' in navigator)) { + return; + } + + navigator.geolocation.getCurrentPosition( + (position) => { + const { latitude, longitude } = position.coords; + const coords: LngLatLike = [latitude, longitude]; + if (isWithinVigo(coords)) { + updateMapState(coords, 16); + } + }, + () => { } + ); + } + }, + { + name: 'Ajustes', + icon: Settings, + path: '/settings' + } + ]; + + return ( + <nav className="navigation-bar"> + {navItems.map(item => { + const Icon = item.icon; + const isActive = location.pathname.startsWith(item.path); + + return ( + <Link + key={item.name} + to={item.path} + className={`navigation-bar__link ${isActive ? 'active' : ''}`} + onClick={item.callback ? item.callback : undefined} + title={item.name} + aria-label={item.name} + > + <Icon size={24} /> + <span>{item.name}</span> + </Link> + ); + })} + </nav> + ); +} diff --git a/src/frontend/app/root.tsx b/src/frontend/app/root.tsx index d90dba0..9d59ce7 100644 --- a/src/frontend/app/root.tsx +++ b/src/frontend/app/root.tsx @@ -16,9 +16,8 @@ import "./root.css"; import "maplibre-theme/icons.default.css"; import "maplibre-theme/modern.css"; import { Protocol } from "pmtiles"; -import maplibregl from "maplibre-gl"; +import maplibregl, { type LngLatLike } from "maplibre-gl"; import { AppProvider } from "./AppContext"; -import { Map, MapPin, Settings } from "lucide-react"; const pmtiles = new Protocol(); maplibregl.addProtocol("pmtiles", pmtiles.tile); //#endregion @@ -81,48 +80,31 @@ export function Layout({ children }: { children: React.ReactNode }) { ); } -export default function App() { - const navItems = [ - { - name: 'Paradas', - icon: MapPin, - path: '/stops' - }, - { - name: 'Mapa', - icon: Map, - path: '/map' - }, - { - name: 'Ajustes', - icon: Settings, - path: '/settings' + // Helper: check if coordinates are within Vigo bounds + function isWithinVigo(lngLat: LngLatLike): boolean { + let lng: number, lat: number; + if (Array.isArray(lngLat)) { + [lng, lat] = lngLat; + } else if ('lng' in lngLat && 'lat' in lngLat) { + lng = lngLat.lng; + lat = lngLat.lat; + } else { + return false; } - ]; + // Rough bounding box for Vigo + return lat >= 42.18 && lat <= 42.30 && lng >= -8.78 && lng <= -8.65; + } + +import NavBar from "./components/NavBar"; +export default function App() { return ( <AppProvider> <main className="main-content"> <Outlet /> </main> <footer> - <nav className="navigation-bar"> - {navItems.map(item => { - const Icon = item.icon; - const isActive = location.pathname.startsWith(item.path); - - return ( - <Link - key={item.name} - to={item.path} - className={`navigation-bar__link ${isActive ? 'active' : ''}`} - > - <Icon size={24} /> - <span>{item.name}</span> - </Link> - ); - })} - </nav> + <NavBar /> </footer> </AppProvider> |
