import React from 'react'; import { useSyncExternalStore } from 'react'; import { subscribe, getSnapshot } from './TimeTicker'; interface TimeAgoProps { time?: string | null | Date; format?: 'short' | 'long'; showAgo?: boolean; } // Returns a compact label for a time difference (UTC millisecond inputs). function formatRelative(nowMs: number, timeMs: number, fmt: 'short' | 'long' = 'short', showAgo = true): string { const diffSec = Math.max(0, Math.floor((nowMs - timeMs) / 1000)); const units = [ { threshold: 60, divisor: 1, short: 's', long: 'second' }, { threshold: 60 * 60, divisor: 60, short: 'm', long: 'minute' }, { threshold: 60 * 60 * 24, divisor: 60 * 60, short: 'h', long: 'hour' }, { threshold: 60 * 60 * 24 * 7, divisor: 60 * 60 * 24, short: 'd', long: 'day' }, { threshold: 60 * 60 * 24 * 30, divisor: 60 * 60 * 24 * 7, short: 'w', long: 'week' }, // months: fallback bucket { threshold: Infinity, divisor: 60 * 60 * 24 * 30, short: 'mo', long: 'month' }, ]; const plural = (n: number, singular: string) => (n === 1 ? `${n} ${singular}` : `${n} ${singular}s`); // find first matching unit for (const u of units) { if (diffSec < u.threshold) { if (fmt === 'short') { if (u.short === 'mo') { // for months, compute from days to ensure at least 1 month const months = Math.max(1, Math.floor(diffSec / u.divisor)); return `${months}${u.short}${showAgo ? ' ago' : ''}`; } const value = Math.floor(diffSec / u.divisor); return `${value}${u.short}${showAgo ? ' ago' : ''}`; } // long format if (diffSec < 1) return 'just now'; if (u.long === 'month') { const months = Math.max(1, Math.floor(diffSec / u.divisor)); return `${plural(months, u.long)}${showAgo ? ' ago' : ''}`; } const value = Math.floor(diffSec / u.divisor); return `${plural(value, u.long)}${showAgo ? ' ago' : ''}`; } } return 'just now'; } // TimeAgo subscribes to a single shared ticker (via useSyncExternalStore) so // many instances update efficiently without per-component timers. const TimeAgo: React.FC = ({ time, format = 'short', showAgo = true }) => { if (!time) return <>-; const now = useSyncExternalStore(subscribe, getSnapshot); const timeMs = typeof time === 'string' ? Date.parse(time) : (time instanceof Date ? time.getTime() : NaN); if (!timeMs || Number.isNaN(timeMs)) return <>-; const label = formatRelative(now, timeMs, format, showAgo); return {label}; }; export default TimeAgo;