'use client'; import Link from 'next/link'; import { useEffect, useState } from 'react'; import { Bell } from 'lucide-react'; interface SessionInfo { user: { id: string; email: string; name: string | null; handle: string | null; avatarUrl: string | null; displayCurrency: string; role: string } | null; unread?: number; } /** * Header user menu (drop-in for the site header): shows "Sign in" when anonymous, otherwise the * avatar, unread badge and a compact menu. Fetches /api/auth/session client-side so the header * stays static/cacheable. */ export function UserMenu() { const [info, setInfo] = useState(null); useEffect(() => { let alive = true; fetch('/api/auth/session', { credentials: 'same-origin' }) .then((r) => (r.ok ? r.json() : { user: null })) .then((j: SessionInfo) => { if (alive) setInfo(j); }) .catch(() => { if (alive) setInfo({ user: null }); }); return () => { alive = false; }; }, []); if (!info?.user) { return ( Sign in ); } const u = info.user; const initial = (u.name ?? u.email).slice(0, 1).toUpperCase(); return (
{info.unread ? {info.unread > 99 ? '99+' : info.unread} : null}
{u.avatarUrl ? ( // eslint-disable-next-line @next/next/no-img-element ) : ( {initial} )} {u.name ?? u.email.split('@')[0]}

{u.name ?? 'Collector'}

{u.email}

{[ ['/collections', 'Collections'], ['/watchlist', 'Watchlist'], ['/alerts', 'Alerts'], ['/deals', 'Deal Radar'], ['/account/settings', 'Settings'], ...(u.handle ? [[`/u/${u.handle}`, 'Public profile']] : []), ].map(([href, label]) => ( {label} ))}
); }