'use client'; import { useEffect, useState } from 'react'; import { cn } from '@/lib/cn'; import { DASH, fmtAgo, fmtDateTime } from '@/lib/format'; /** Green live dot with optional pulse ring. */ export function Dot({ pulse = false, className, tone = 'live' }: { pulse?: boolean; className?: string; tone?: 'live' | 'warning' | 'danger' | 'muted' }) { return ; } /** * "17 sec ago" that ticks every second for the first minutes, then every 30 s. Server renders the absolute UTC time * (identical on both sides); the relative label is applied after mount (`suppressHydrationWarning`). */ export function LiveAgo({ at, prefix = '', className, absoluteFallback = true, tick = 1000 }: { at: string | null | undefined; prefix?: string; className?: string; absoluteFallback?: boolean; tick?: number }) { const [now, setNow] = useState(null); useEffect(() => { setNow(Date.now()); const t = setInterval(() => setNow(Date.now()), tick); return () => clearInterval(t); }, [tick]); const label = now === null ? (absoluteFallback ? fmtDateTime(at) : DASH) : fmtAgo(at, now); return ( ); } /** "Live · updated 12 s ago" status line for panels fed by SSE/polling. */ export function LiveStatus({ updatedAt, connected = true, className }: { updatedAt: number | string | null; connected?: boolean; className?: string }) { const [now, setNow] = useState(null); useEffect(() => { setNow(Date.now()); const t = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(t); }, []); const ts = typeof updatedAt === 'number' ? updatedAt : updatedAt ? new Date(updatedAt).getTime() : null; const s = ts && now ? Math.max(0, Math.round((now - ts) / 1000)) : null; return ( {connected ? 'Live' : 'Paused'} {s !== null && · updated {s < 1 ? 'now' : `${s} s ago`}} ); }