"use client"; import { useEffect, useState } from "react"; import { useSnap } from "@/lib/client"; const RESET_MS = 20 * 60 * 1000; /** Shown on every /app screen of the shared demo facility: what it is, when it resets, and a role switch. */ export default function DemoBanner() { const { s } = useSnap(); /* Seeded in an effect, not in useState's initialiser. * * This component renders on the server too, where Date.now() is the server's clock at render * time — so the first paint carried one countdown and hydration immediately replaced it with a * different one from the visitor's clock, which is both a visible jump and a hydration mismatch * on a value React has no way to reconcile. Null until mounted means the server and the first * client render agree on "no clock yet", and the countdown appears a tick later from the only * clock that is actually ticking. */ const [now, setNow] = useState(null); useEffect(() => { setNow(Date.now()); const t = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(t); }, []); if (!s.demo) return null; const base = s.demo.resetAt ? new Date(s.demo.resetAt).getTime() : now; const left = now === null || base === null ? null : Math.max(0, RESET_MS - ((now - base) % RESET_MS)); const mm = left === null ? null : Math.floor(left / 60000), ss = left === null ? null : Math.floor((left % 60000) / 1000); const other = s.session.role === "Admin" ? "issuer" : "admin"; return (
Working demo {s.settings.facility} is fictional and shared with other visitors.{" "} {mm === null ? `Resets every ${RESET_MS / 60000} minutes.` : <>Resets in {mm}:{String(ss).padStart(2, "0")}.} Switch to {other === "admin" ? "Admin" : "Issuer"} Create your own facility
); }