deadhead-space-freight-tycoon/apps/web/src/App.tsx

68 lines
2.1 KiB
TypeScript

import { useEffect, type ReactNode } from "react";
import { Route, Routes } from "react-router";
import { useApi } from "./api/context";
import { startClockTicker, syncClock } from "./clock/clock";
import { AwaySummary } from "./components/AwaySummary";
import { formatCredits } from "./format";
import { Layout } from "./layout/Layout";
import { SCREENS } from "./routes";
import { Contracts } from "./screens/Contracts";
import { HQ } from "./screens/HQ";
import { Placeholder } from "./screens/Placeholder";
import { StartScreen } from "./screens/StartScreen";
import { usePlayerState, useRefetchOnArrival } from "./state/player";
/** Built screens by path; the rest show a placeholder. */
const SCREEN_COMPONENTS: Partial<Record<(typeof SCREENS)[number]["path"], ReactNode>> = {
"/": <HQ />,
"/contracts": <Contracts />,
};
/** Syncs the clock with the server once, then ticks it every second. */
function useServerClock() {
const api = useApi();
useEffect(() => {
syncClock(api).catch(() => {
// Offline or server down: keep the device clock until the next launch.
});
return startClockTicker();
}, [api]);
}
/** The whole client. Router, API and query providers come from the caller. */
export function App() {
useServerClock();
const state = usePlayerState();
useRefetchOnArrival(state.data ?? null, state.refetch);
if (state.isPending) {
return <p className="p-6 text-slate-400">Loading…</p>;
}
if (state.isError) {
return (
<p role="alert" className="p-6 text-red-400">
Could not reach the server.
</p>
);
}
if (state.data === null) return <StartScreen />;
return (
<>
<Routes>
<Route element={<Layout credits={formatCredits(state.data.company.credits)} />}>
{SCREENS.map((screen) => (
<Route
key={screen.path}
index={screen.path === "/"}
path={screen.path === "/" ? undefined : screen.path}
element={SCREEN_COMPONENTS[screen.path] ?? <Placeholder title={screen.title} />}
/>
))}
</Route>
</Routes>
<AwaySummary />
</>
);
}