fix(admin): deterministic UTC timestamp formatting to remove hydration mismatch
This commit is contained in:
@@ -3,6 +3,7 @@ import { createSupabaseServerClient } from '@/lib/supabase/server';
|
|||||||
import { getSupabaseAdmin } from '@/lib/supabase/admin';
|
import { getSupabaseAdmin } from '@/lib/supabase/admin';
|
||||||
import { ClaimForm } from './claim-form';
|
import { ClaimForm } from './claim-form';
|
||||||
import { TokenReveal } from './token-reveal';
|
import { TokenReveal } from './token-reveal';
|
||||||
|
import { formatDate } from '@/lib/format';
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
@@ -92,9 +93,7 @@ export default async function DashboardPage() {
|
|||||||
|
|
||||||
<div className="k">Last seen</div>
|
<div className="k">Last seen</div>
|
||||||
<div>
|
<div>
|
||||||
{tunnel.last_seen_at
|
{tunnel.last_seen_at ? formatDate(tunnel.last_seen_at) : 'never'}
|
||||||
? new Date(tunnel.last_seen_at).toLocaleString()
|
|
||||||
: 'never'}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+27
-2
@@ -11,9 +11,34 @@ export function formatBytes(n: number): string {
|
|||||||
return `${v.toFixed(2)} ${units[i]}`;
|
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 '—';
|
if (!s) return '—';
|
||||||
const d = new Date(s);
|
const d = new Date(s);
|
||||||
if (Number.isNaN(d.getTime())) return '—';
|
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user