import { createResource, createSignal, Show, onCleanup, type JSX } from "solid-js"; import { A, Route, Router, useNavigate } from "@solidjs/router"; import { api, ApiError, decodePolyline, UNAUTHORIZED, type Me } from "./api"; import { createLiveStore } from "./live"; import { SessionContext } from "./session"; import MapPage from "./MapPage"; import Settings from "./Settings"; const TRAIL_SECONDS = 24 * 3600; const TRAIL_REFRESH_MS = 60_000; export default function App() { return ( ); } function NotFound() { return (

no such page

back to the map
); } /** * The router root: session, navigation, and the one polling loop. * * Signing out is not a redirect. The signed-out shell renders the login form in * place, so a deep link to /settings survives it — you land where you were * heading rather than back at the map. */ function Shell(props: { children?: JSX.Element }) { // undefined while the initial /api/me is in flight, null when signed out. const [me, setMe] = createSignal(undefined); api.me() .then(setMe) .catch(() => setMe(null)); return ( loading…}> } keyed> {(user) => ( setMe(null)}> {props.children} )} ); } function SignedIn(props: { me: Me; onSignedOut: () => void; children?: JSX.Element }) { const live = createLiveStore(props.onSignedOut); const [selected, setSelected] = createSignal(props.me.id); // The trail for whoever is selected. A 403 means the share does not grant // trail visibility, which is a normal answer, not an error to surface. const [trail, { refetch }] = createResource(selected, async (id) => { const to = live.serverTime(); try { const res = await api.track(id, to - TRAIL_SECONDS, to); return decodePolyline(res.polyline); } catch (e) { if (e instanceof ApiError && (e.status === 403 || e.status === UNAUTHORIZED)) return null; throw e; } }); const timer = setInterval(() => void refetch(), TRAIL_REFRESH_MS); onCleanup(() => clearInterval(timer)); async function signOut() { await api.logout().catch(() => {}); props.onSignedOut(); } return ( trail() ?? null, }} >
opentracker {props.me.display_name}
{(msg) => } {props.children}
); } function Login(props: { onSignedIn: (me: Me) => void }) { const [username, setUsername] = createSignal(""); const [password, setPassword] = createSignal(""); const [error, setError] = createSignal(null); const [busy, setBusy] = createSignal(false); const navigate = useNavigate(); async function submit(e: Event) { e.preventDefault(); setBusy(true); setError(null); try { const { user } = await api.login(username(), password()); // A stale bookmark to a route that no longer exists would otherwise sign // in and land on the 404 page. if (window.location.pathname === "/login") navigate("/", { replace: true }); props.onSignedIn(user); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setBusy(false); } } return ( ); }