diff --git a/src/client/App.tsx b/src/client/App.tsx index 220012e..84712d5 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -6,6 +6,7 @@ import { EventRow, type RowEvent } from "./components/EventRow.tsx"; import { NextUp } from "./components/NextUp.tsx"; import { Timeline } from "./components/Timeline.tsx"; import { Welcome } from "./components/Welcome.tsx"; +import { Colophon } from "./components/Colophon.tsx"; import { useCompletions } from "./state/useCompletions.ts"; import { usePrefs } from "./state/usePrefs.ts"; import { clockFor, DAY, endingSoonestFirst, formatRemaining } from "../shared/time.ts"; @@ -13,6 +14,28 @@ import type { GameId } from "../shared/schema.ts"; type View = "soon" | "calendar"; +/** + * Connection state. Offline is not an error here — the service worker serves + * the last feed it saw and countdowns run off the local clock — but it does + * change what the reader can trust, so it is surfaced rather than hidden. + */ +function useOnline(): boolean { + const [online, setOnline] = useState(() => + typeof navigator === "undefined" ? true : navigator.onLine, + ); + useEffect(() => { + const up = () => setOnline(true); + const down = () => setOnline(false); + window.addEventListener("online", up); + window.addEventListener("offline", down); + return () => { + window.removeEventListener("online", up); + window.removeEventListener("offline", down); + }; + }, []); + return online; +} + /** Ticks once a second so countdowns stay honest without re-fetching. */ function useNow(intervalMs = 1000): number { const [now, setNow] = useState(() => Date.now()); @@ -28,6 +51,7 @@ export function App() { const [view, setView] = useState("soon"); const [openId, setOpenId] = useState(null); const now = useNow(); + const online = useOnline(); const { prefs, update, toggleGame } = usePrefs(); const { completions, toggle, merge } = useCompletions(); @@ -122,8 +146,14 @@ export function App() {

EVENTCLOCK

-

+

{live.length} live · {upcoming.length} upcoming + {!online && ( + + + offline + + )}

@@ -219,19 +249,17 @@ export function App() { onImport={(file) => void importProgress(file, merge)} /> -
-

- Dates come from community wikis and are shown in your local time. Every - event links to its source — check there before the last hours. + {!online && ( +

+ You're offline. These are the events last downloaded + {" "} + {formatRemaining(now - Date.parse(state.feed.generatedAt))} ago, and + countdowns are still running. Anything rescheduled since then won't + show until you reconnect.

- {staleSources.length > 0 && ( -

- {staleSources.length} source - {staleSources.length > 1 ? "s have" : " has"} not refreshed in over two - days. Some end dates may have moved. -

- )} -
+ )} + + {openRow !== null && ( s.game))]; + + return ( + + ); +}