Skip to article
Documentation

src/components/elapsed-time.tsx

Linked repository file from this build.

"use client";
import { useEffect, useState } from "react";

export function ElapsedTime({
  at,
  label = "Last refreshed",
  compact = false,
}: {
  at: string | null;
  label?: string;
  compact?: boolean;
}) {
  // Keep the server and first client render identical; advance to wall-clock time after mount.
  const [now, setNow] = useState(() => (at ? new Date(at).getTime() : 0));
  useEffect(() => {
    if (!at) return;
    const initial = now === new Date(at).getTime();
    const elapsed = Math.max(0, Date.now() - new Date(at).getTime());
    const delay = initial ? 0 : elapsed < 60_000 ? 1_000 : 60_000;
    const timer = setTimeout(() => setNow(Date.now()), delay);
    return () => clearTimeout(timer);
  }, [at, now]);
  if (!at)
    return (
      <span className="text-xs text-muted-foreground">{compact ? "—" : "Waiting for update…"}</span>
    );
  const seconds = Math.max(0, Math.floor((now - new Date(at).getTime()) / 1000));
  const elapsed =
    seconds < 60
      ? `${seconds}s ago`
      : seconds < 3600
        ? `${Math.floor(seconds / 60)}m ago`
        : `${Math.floor(seconds / 3600)}h ago`;
  const tooltip = `${label.toLowerCase()} ${new Date(at).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: true })}`;
  return (
    <span
      tabIndex={0}
      aria-label={`${label} ${elapsed}`}
      data-tip={tooltip}
      className="elapsed-time text-xs text-muted-foreground tabular-nums"
    >
      {compact ? elapsed : `${label} ${elapsed}`}
    </span>
  );
}