aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/root.tsx
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2025-06-24 13:29:50 +0200
committerAriel Costas Guerrero <ariel@costas.dev>2025-06-24 13:29:50 +0200
commit894e67863dbb89a4819e825fcdf7117021082b2a (patch)
treefb544ef7fa99ff86489717e793595f503783bb72 /src/frontend/app/root.tsx
parent7dd9ea97a2f34a35e80c28d59d046f839eb6c60b (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.tsx161
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>
+ );
+}