web/app/components/time-ago.tsxblame
View source
a011f1e1"use client";
a011f1e2
a011f1e3import { useState, useEffect } from "react";
a011f1e4
a011f1e5function computeTimeAgo(dateStr: string): string {
a011f1e6 // SQLite datetime('now') returns UTC without Z suffix — append Z if missing
a011f1e7 const normalized = dateStr.endsWith("Z") || dateStr.includes("+") || dateStr.includes("T")
a011f1e8 ? dateStr
a011f1e9 : dateStr + "Z";
a011f1e10 const seconds = Math.floor((Date.now() - new Date(normalized).getTime()) / 1000);
a011f1e11 if (seconds < 60) return "just now";
a011f1e12 if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
a011f1e13 if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
a011f1e14 if (seconds < 2592000) return `${Math.floor(seconds / 86400)}d ago`;
a011f1e15 return new Date(normalized).toLocaleDateString();
a011f1e16}
a011f1e17
a011f1e18export function TimeAgo({ date }: { date: string }) {
a011f1e19 const [text, setText] = useState(() => computeTimeAgo(date));
a011f1e20
a011f1e21 useEffect(() => {
a011f1e22 // Re-compute immediately on mount (fixes SSR mismatch)
a011f1e23 setText(computeTimeAgo(date));
a011f1e24 const interval = setInterval(() => {
a011f1e25 setText(computeTimeAgo(date));
a011f1e26 }, 10_000);
a011f1e27 return () => clearInterval(interval);
a011f1e28 }, [date]);
a011f1e29
fe3b50930 return <span suppressHydrationWarning>{text}</span>;
a011f1e31}