/** * The effective application timezone, loaded once per signed-in shell: the * API's `effective_timezone` — the stored zone, or the API host's own zone * when none is stored — so every screen's local time matches the API's. * * fetches the household settings and renders its * screens only once the zone is known (a failed load says so and offers a * retry), so no screen ever computes local time in a zone it will not keep. * The Settings screen publishes every zone it loads or saves, so a change * applies app-wide at once. * * Without a provider (a screen rendered on its own, e.g. in a test) * useAppTimezone() is "" — the documented unset value, i.e. browser-local. */ import { createContext, useContext, useEffect, useState, type ReactNode } from "react"; import { ApiForbiddenError } from "../api/client"; import { fetchSettings } from "../api/settings"; interface ZoneHub { timeZone: string; publish(timeZone: string): void; } const AppTimezoneContext = createContext(null); type LoadState = | { kind: "loading" } | { kind: "error"; message: string } | { kind: "ready"; timeZone: string }; export function AppTimezoneProvider({ children }: { children: ReactNode }) { const [state, setState] = useState({ kind: "loading" }); const [attempt, setAttempt] = useState(0); useEffect(() => { let cancelled = false; setState({ kind: "loading" }); fetchSettings() .then((settings) => { if (!cancelled) setState({ kind: "ready", timeZone: settings.effective_timezone }); }) .catch((error: unknown) => { if (cancelled) return; setState({ kind: "error", message: error instanceof ApiForbiddenError ? error.message : "The application timezone could not be loaded. Check your connection and try again.", }); }); return () => { cancelled = true; }; }, [attempt]); if (state.kind === "loading") { return (
Loading…
); } if (state.kind === "error") { return (

{state.message}

); } const hub: ZoneHub = { timeZone: state.timeZone, publish: (timeZone) => setState((prev) => prev.kind === "ready" && prev.timeZone === timeZone ? prev : { kind: "ready", timeZone }, ), }; return {children}; } /** The effective application timezone: an IANA name, or "" (browser-local). */ export function useAppTimezone(): string { return useContext(AppTimezoneContext)?.timeZone ?? ""; } /** Publish an effective zone just loaded or saved; a no-op without a provider. */ export function usePublishAppTimezone(): (timeZone: string) => void { const hub = useContext(AppTimezoneContext); return (timeZone) => hub?.publish(timeZone); }