Four real surfaces wired to tenant-registry (settings, settings/api-keys CRUD, audit pagination, products live entitlements), five forward-looking empty states with CTAs. 56 vitest tests + 10 Playwright canaries. lib/format.ts consolidates date helpers. Refs: M10.1
57 lines
1.5 KiB
TypeScript
57 lines
1.5 KiB
TypeScript
// Lightweight date/time helpers — shared across server components so we
|
|
// don't reinvent toLocaleString conventions per page.
|
|
|
|
export function formatRelative(iso: string, now: number = Date.now()): string {
|
|
const t = new Date(iso).getTime();
|
|
if (Number.isNaN(t)) return iso;
|
|
const diff = t - now;
|
|
const abs = Math.abs(diff);
|
|
const ago = diff < 0;
|
|
const units: [string, number][] = [
|
|
["second", 1000],
|
|
["minute", 60 * 1000],
|
|
["hour", 3600 * 1000],
|
|
["day", 24 * 3600 * 1000],
|
|
["week", 7 * 24 * 3600 * 1000],
|
|
["month", 30 * 24 * 3600 * 1000],
|
|
["year", 365 * 24 * 3600 * 1000],
|
|
];
|
|
let unit = "second";
|
|
let n = Math.round(abs / 1000);
|
|
for (let i = units.length - 1; i >= 0; i--) {
|
|
if (abs >= units[i][1]) {
|
|
unit = units[i][0];
|
|
n = Math.round(abs / units[i][1]);
|
|
break;
|
|
}
|
|
}
|
|
const suffix = n === 1 ? unit : `${unit}s`;
|
|
return ago ? `${n} ${suffix} ago` : `in ${n} ${suffix}`;
|
|
}
|
|
|
|
export function formatDateTime(iso: string): string {
|
|
const d = new Date(iso);
|
|
if (Number.isNaN(d.getTime())) return iso;
|
|
// YYYY-MM-DD HH:MM:SS — locale-stable, sortable, no surprises.
|
|
const pad = (n: number) => String(n).padStart(2, "0");
|
|
return (
|
|
d.getUTCFullYear() +
|
|
"-" +
|
|
pad(d.getUTCMonth() + 1) +
|
|
"-" +
|
|
pad(d.getUTCDate()) +
|
|
" " +
|
|
pad(d.getUTCHours()) +
|
|
":" +
|
|
pad(d.getUTCMinutes()) +
|
|
":" +
|
|
pad(d.getUTCSeconds()) +
|
|
" UTC"
|
|
);
|
|
}
|
|
|
|
export function truncate(s: string, max = 40): string {
|
|
if (s.length <= max) return s;
|
|
return s.slice(0, max - 1) + "…";
|
|
}
|