"use client"; import * as React from "react"; import { useMounted, useNow } from "@zq/kit/hooks"; /** * Kickoff time/date rendered in the USER's local timezone. Server components can't know the * viewer's TZ, so we gate on mount: the first (SSR-matching) render shows a stable placeholder, * then we fill the browser-local value. Fixes the "everything shows the server's timezone" bug. */ export function LocalTime({ iso, kind = "time", fallback = "ยท", }: { iso: string | null | undefined; kind?: "time" | "date" | "daytime" | "weekday"; fallback?: string; }) { const mounted = useMounted(); if (!iso) return <>{""}; if (!mounted) return {fallback}; const d = new Date(iso); const opts: Intl.DateTimeFormatOptions = kind === "date" ? { month: "short", day: "numeric" } : kind === "daytime" ? { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" } : kind === "weekday" ? { weekday: "long", month: "long", day: "numeric" } : { hour: "numeric", minute: "2-digit" }; return {d.toLocaleString([], opts)}; } function parts(ms: number): { d: number; h: number; m: number; s: number } { const s = Math.max(0, Math.floor(ms / 1000)); return { d: Math.floor(s / 86400), h: Math.floor((s % 86400) / 3600), m: Math.floor((s % 3600) / 60), s: s % 60 }; } /** * Countdown to an absolute instant (ISO). Ticks every second client-side. The target is an * absolute UTC instant so it's correct regardless of the viewer's clock skew/TZ. Renders nothing * once the instant has passed (the caller swaps to a live/result state). */ export function CountdownClock({ iso, className }: { iso: string; className?: string }) { const mounted = useMounted(); const target = React.useMemo(() => (iso ? new Date(iso).getTime() : 0), [iso]); const now = useNow(mounted); if (!mounted || !target) return ; const remaining = target - now; if (remaining <= 0) return ; const { d, h, m, s } = parts(remaining); const text = d > 0 ? `${d}d ${h}h ${m}m` : h > 0 ? `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}` : `${m}:${String(s).padStart(2, "0")}`; return ( {text} ); }