2bec205a30
- Bell icon in icon registry (Phosphor Bell) - NotificationProvider context with polling (15s unread / 30s list) - useNotifications() hook with optimistic updates - NotificationCenter component: bell + badge + dropdown panel - NotificationItem component: severity icon, title, relative time, actions - AppShell integration: mount in header-actions, hidden on mobile - CSS styles: dropdown, items, unread/read states, empty state - formatRelativeTime utility (custom, no new deps) - 25 frontend tests (9 hook + 6 item + 10 center) Quality gates: vitest 25 passed, tsc clean, eslint clean
30 lines
720 B
TypeScript
30 lines
720 B
TypeScript
const UNITS: { label: string; seconds: number }[] = [
|
|
{ label: "y", seconds: 31536000 },
|
|
{ label: "mo", seconds: 2592000 },
|
|
{ label: "w", seconds: 604800 },
|
|
{ label: "d", seconds: 86400 },
|
|
{ label: "h", seconds: 3600 },
|
|
{ label: "m", seconds: 60 },
|
|
{ label: "s", seconds: 1 },
|
|
];
|
|
|
|
export function formatRelativeTime(dateStr: string): string {
|
|
const date = new Date(dateStr);
|
|
const now = new Date();
|
|
const diffSeconds = Math.max(
|
|
0,
|
|
Math.floor((now.getTime() - date.getTime()) / 1000),
|
|
);
|
|
|
|
if (diffSeconds < 5) return "just now";
|
|
|
|
for (const unit of UNITS) {
|
|
const count = Math.floor(diffSeconds / unit.seconds);
|
|
if (count >= 1) {
|
|
return `${count}${unit.label} ago`;
|
|
}
|
|
}
|
|
|
|
return "just now";
|
|
}
|