Offline banner: say how old the data is instead of failing silently
When the live connection drops, a slim notice under the header shows the timestamp of the last snapshot — the page keeps working from it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+16
-1
@@ -6,7 +6,7 @@ import type { LucideIcon } from 'lucide-react';
|
|||||||
import { useUiStore } from '@/stores/uiStore';
|
import { useUiStore } from '@/stores/uiStore';
|
||||||
import { useLocaleStore } from '@/stores/localeStore';
|
import { useLocaleStore } from '@/stores/localeStore';
|
||||||
import { useTournamentStore } from '@/stores/tournamentStore';
|
import { useTournamentStore } from '@/stores/tournamentStore';
|
||||||
import { useT, type Dict } from '@/lib/i18n';
|
import { fmt, useFormat, useT, type Dict } from '@/lib/i18n';
|
||||||
import { cn } from '@/lib/cn';
|
import { cn } from '@/lib/cn';
|
||||||
|
|
||||||
interface NavItem { to: string; label: (t: Dict) => string; exact: boolean; icon: LucideIcon }
|
interface NavItem { to: string; label: (t: Dict) => string; exact: boolean; icon: LucideIcon }
|
||||||
@@ -88,6 +88,19 @@ export function LanguageToggle() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Slim stale-data notice while the live connection is down. */
|
||||||
|
function OfflineBanner() {
|
||||||
|
const { t, kickoffDay, kickoffTime } = useFormat();
|
||||||
|
const status = useTournamentStore((s) => s.status);
|
||||||
|
const generatedAt = useTournamentStore((s) => s.snapshot?.generatedAt);
|
||||||
|
if (status !== 'offline' || !generatedAt) return null;
|
||||||
|
return (
|
||||||
|
<div className="border-b border-warning/30 bg-warning/10 px-4 py-1.5 text-center text-xs text-muted" role="status">
|
||||||
|
{fmt(t.common.offlineBanner, { time: `${kickoffDay(generatedAt)} ${kickoffTime(generatedAt)}` })}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function MobileNav() {
|
function MobileNav() {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const [moreOpen, setMoreOpen] = useState(false);
|
const [moreOpen, setMoreOpen] = useState(false);
|
||||||
@@ -221,6 +234,8 @@ export function RootLayout() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<OfflineBanner />
|
||||||
|
|
||||||
<main className="mx-auto w-full max-w-6xl flex-1 px-4 py-6 pb-24 sm:pb-6">
|
<main className="mx-auto w-full max-w-6xl flex-1 px-4 py-6 pb-24 sm:pb-6">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const de: Dict = {
|
|||||||
connected: "Verbunden",
|
connected: "Verbunden",
|
||||||
liveVia: "Live · {source}",
|
liveVia: "Live · {source}",
|
||||||
offlineShowingSchedule: "Offline — Spielplan wird angezeigt",
|
offlineShowingSchedule: "Offline — Spielplan wird angezeigt",
|
||||||
|
offlineBanner: "Offline — letzte Daten von {time}. Verbinde neu…",
|
||||||
switchToLightMode: "Zum hellen Modus wechseln",
|
switchToLightMode: "Zum hellen Modus wechseln",
|
||||||
switchToDarkMode: "Zum dunklen Modus wechseln",
|
switchToDarkMode: "Zum dunklen Modus wechseln",
|
||||||
draw: "Unentschieden",
|
draw: "Unentschieden",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const en = {
|
|||||||
connected: "Connected",
|
connected: "Connected",
|
||||||
liveVia: "Live · {source}",
|
liveVia: "Live · {source}",
|
||||||
offlineShowingSchedule: "Offline — showing the schedule",
|
offlineShowingSchedule: "Offline — showing the schedule",
|
||||||
|
offlineBanner: "Offline — showing the last data from {time}. Reconnecting…",
|
||||||
switchToLightMode: "Switch to light mode",
|
switchToLightMode: "Switch to light mode",
|
||||||
switchToDarkMode: "Switch to dark mode",
|
switchToDarkMode: "Switch to dark mode",
|
||||||
draw: "Draw",
|
draw: "Draw",
|
||||||
|
|||||||
Reference in New Issue
Block a user