diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-06-24 13:29:50 +0200 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-06-24 13:29:50 +0200 |
| commit | 894e67863dbb89a4819e825fcdf7117021082b2a (patch) | |
| tree | fb544ef7fa99ff86489717e793595f503783bb72 /src/frontend/app/root.tsx | |
| parent | 7dd9ea97a2f34a35e80c28d59d046f839eb6c60b (diff) | |
Replace leaflet for maplibre, use react-router in framework mode
Diffstat (limited to 'src/frontend/app/root.tsx')
| -rw-r--r-- | src/frontend/app/root.tsx | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/src/frontend/app/root.tsx b/src/frontend/app/root.tsx new file mode 100644 index 0000000..d90dba0 --- /dev/null +++ b/src/frontend/app/root.tsx @@ -0,0 +1,161 @@ +import { + isRouteErrorResponse, + Link, + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration +} from "react-router"; + +import type { Route } from "./+types/root"; +import "@fontsource-variable/roboto"; +import "./root.css"; + +//#region Maplibre setup +import "maplibre-theme/icons.default.css"; +import "maplibre-theme/modern.css"; +import { Protocol } from "pmtiles"; +import maplibregl from "maplibre-gl"; +import { AppProvider } from "./AppContext"; +import { Map, MapPin, Settings } from "lucide-react"; +const pmtiles = new Protocol(); +maplibregl.addProtocol("pmtiles", pmtiles.tile); +//#endregion + +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/sw.js') + .then((registration) => { + console.log('Service Worker registered with scope:', registration.scope); + }) + .catch((error) => { + console.error('Service Worker registration failed:', error); + }); +} + +export const links: Route.LinksFunction = () => []; + +export function HydrateFallback() { + return "Loading..."; +} + +export function Layout({ children }: { children: React.ReactNode }) { + return ( + <html lang="es"> + <head> + <meta charSet="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + + <link rel="icon" type="image/jpg" href="/logo-512.jpg" /> + <link rel="icon" href="/favicon.ico" sizes="64x64" /> + <link rel="apple-touch-icon" href="/logo-512.jpg" sizes="512x512" /> + <meta name="theme-color" content="#007bff" /> + + <link rel="canonical" href="https://urbanovigo.costas.dev/" /> + + <meta name="description" content="Aplicación web para encontrar paradas y tiempos de llegada de los autobuses urbanos de Vigo, España." /> + <meta name="keywords" content="Vigo, autobús, urbano, parada, tiempo, llegada, transporte, público, España" /> + <meta name="author" content="Ariel Costas Guerrero" /> + + <meta property="og:title" content="UrbanoVigo Web" /> + <meta property="og:type" content="website" /> + <meta property="og:url" content="https://urbanovigo.costas.dev/" /> + <meta property="og:image" content="https://urbanovigo.costas.dev/logo-512.jpg" /> + <meta property="og:description" content="Aplicación web para encontrar paradas y tiempos de llegada de los autobuses urbanos de Vigo, España." /> + + <link rel="manifest" href="/manifest.webmanifest" /> + + <meta name="robots" content="noindex, nofollow" /> + <meta name="googlebot" content="noindex, nofollow" /> + + <title>Busurbano</title> + <Meta /> + <Links /> + </head> + <body> + {children} + <ScrollRestoration /> + <Scripts /> + </body> + </html> + ); +} + +export default function App() { + const navItems = [ + { + name: 'Paradas', + icon: MapPin, + path: '/stops' + }, + { + name: 'Mapa', + icon: Map, + path: '/map' + }, + { + name: 'Ajustes', + icon: Settings, + path: '/settings' + } + ]; + + 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> + </footer> + </AppProvider> + + + + ); +} + +export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { + let message = "Oops!"; + let details = "An unexpected error occurred."; + let stack: string | undefined; + + if (isRouteErrorResponse(error)) { + message = error.status === 404 ? "404" : "Error"; + details = + error.status === 404 + ? "The requested page could not be found." + : error.statusText || details; + } else if (import.meta.env.DEV && error && error instanceof Error) { + details = error.message; + stack = error.stack; + } + + return ( + <main> + <h1>{message}</h1> + <p>{details}</p> + {stack && ( + <pre> + <code>{stack}</code> + </pre> + )} + </main> + ); +} |
