diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 6db6ff7..4d58c95 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -3,6 +3,7 @@ import { createSupabaseServerClient } from '@/lib/supabase/server'; import { getSupabaseAdmin } from '@/lib/supabase/admin'; import { ClaimForm } from './claim-form'; import { TokenReveal } from './token-reveal'; +import { formatDate } from '@/lib/format'; export const dynamic = 'force-dynamic'; @@ -92,9 +93,7 @@ export default async function DashboardPage() {
Last seen
- {tunnel.last_seen_at - ? new Date(tunnel.last_seen_at).toLocaleString() - : 'never'} + {tunnel.last_seen_at ? formatDate(tunnel.last_seen_at) : 'never'}
diff --git a/lib/format.ts b/lib/format.ts index ea11eea..3c6b594 100644 --- a/lib/format.ts +++ b/lib/format.ts @@ -11,9 +11,34 @@ export function formatBytes(n: number): string { return `${v.toFixed(2)} ${units[i]}`; } -export function formatDate(s: string | null | undefined): string { +function pad(n: number): string { + return n < 10 ? `0${n}` : `${n}`; +} + +// Deterministic, timezone-independent date+time formatter. +// Uses UTC getters so the server (UTC) and client (local TZ) render +// byte-identical text, avoiding React hydration mismatches (error #425). +// Output format: "YYYY-MM-DD HH:MM UTC". +export function formatDateTime(s: string | null | undefined): string { if (!s) return '—'; const d = new Date(s); if (Number.isNaN(d.getTime())) return '—'; - return d.toLocaleString(); + const date = `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad( + d.getUTCDate(), + )}`; + const time = `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`; + return `${date} ${time} UTC`; } + +// Date-only deterministic UTC formatter. Output format: "YYYY-MM-DD UTC". +export function formatDateOnly(s: string | null | undefined): string { + if (!s) return '—'; + const d = new Date(s); + if (Number.isNaN(d.getTime())) return '—'; + return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad( + d.getUTCDate(), + )} UTC`; +} + +// Backwards-compatible alias: existing call sites render date+time. +export const formatDate = formatDateTime;