'use client'; import { ChevronDown, LogOut, Menu, Moon, Sun, User as UserIcon, X, LayoutDashboard, Shield, } from 'lucide-react'; import * as React from 'react'; import { cn } from '@/lib/utils'; /* -------------------------------------------------------------------------- */ /* Types */ /* -------------------------------------------------------------------------- */ export interface NavLink { href: string; label: string; isActive?: boolean; /** Nested links shown on hover / accordion. */ children?: NavLink[]; } export interface NavUser { fullName: string; email?: string | null; phone?: string | null; role: string; avatarUrl?: string | null; } export interface NavbarProps { /** Brand name shown in the header. */ brand: string; /** Primary navigation links. */ links: NavLink[]; /** Current user or null if unauthenticated. */ user: NavUser | null; /** Dashboard href (role-dependent). */ dashboardHref: string; /** Slot: notification bell or other widgets. */ notifications?: React.ReactNode; /** Slot: language switcher. */ languageSwitcher?: React.ReactNode; /** Light / dark theme. */ theme: 'light' | 'dark'; /** Toggle theme callback. */ onToggleTheme: () => void; /** Logout callback. */ onLogout: () => Promise | void; /** i18n labels. */ labels: { login: string; register: string; dashboard: string; admin: string; profile: string; logout: string; openMenu: string; closeMenu: string; darkMode: string; lightMode: string; mainNav: string; }; /** Login / register hrefs — rendered via renderLink. */ loginHref?: string; registerHref?: string; profileHref?: string; /** Custom link renderer for framework-specific Link component (Next/i18n). */ renderLink: (props: { href: string; children: React.ReactNode; className?: string; onClick?: () => void; }) => React.ReactNode; } /* -------------------------------------------------------------------------- */ /* Helpers */ /* -------------------------------------------------------------------------- */ const ROLE_LABELS: Record = { ADMIN: 'Quản trị viên', AGENT: 'Đại lý', SELLER: 'Người bán', BUYER: 'Người mua', }; function getInitials(fullName: string): string { const parts = fullName.trim().split(/\s+/).filter(Boolean); if (parts.length === 0) return '?'; if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase(); return (parts[0]![0]! + parts[parts.length - 1]![0]!).toUpperCase(); } /* -------------------------------------------------------------------------- */ /* Component */ /* -------------------------------------------------------------------------- */ export function Navbar({ brand, links, user, dashboardHref, notifications, languageSwitcher, theme, onToggleTheme, onLogout, labels, loginHref = '/login', registerHref = '/register', profileHref = '/dashboard/profile', renderLink, }: NavbarProps) { const [mobileOpen, setMobileOpen] = React.useState(false); const [userMenuOpen, setUserMenuOpen] = React.useState(false); const userMenuRef = React.useRef(null); const close = () => setMobileOpen(false); const handleLogout = async () => { close(); setUserMenuOpen(false); await onLogout(); }; // Close the desktop user dropdown on outside click + Escape. React.useEffect(() => { if (!userMenuOpen) return; const onDown = (e: MouseEvent) => { if (userMenuRef.current && !userMenuRef.current.contains(e.target as Node)) { setUserMenuOpen(false); } }; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setUserMenuOpen(false); }; document.addEventListener('mousedown', onDown); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('mousedown', onDown); document.removeEventListener('keydown', onKey); }; }, [userMenuOpen]); return (
{/* -------- Main bar -------- */}
{/* Brand */} {renderLink({ href: '/', className: 'mr-6 flex items-center gap-2 group', children: ( <>
G
{brand} ), })} {/* Desktop nav */} {/* Right actions */}
{languageSwitcher} {user ? ( <>
{notifications}
{/* User dropdown — pill is the trigger, menu opens on click */}
{userMenuOpen && (
{/* Header */}
{user.avatarUrl ? ( // eslint-disable-next-line @next/next/no-img-element ) : (
{getInitials(user.fullName)}
)}
{user.fullName} {(user.email || user.phone) && ( {user.email ?? user.phone} )}
{renderLink({ href: dashboardHref, onClick: () => setUserMenuOpen(false), children: ( {user.role === 'ADMIN' ? ( ) : ( )} {user.role === 'ADMIN' ? labels.admin : labels.dashboard} ), })} {renderLink({ href: profileHref, onClick: () => setUserMenuOpen(false), children: ( {labels.profile} ), })}
)}
) : ( <> {renderLink({ href: loginHref, className: 'hidden sm:inline-flex', children: ( ), })} {renderLink({ href: registerHref, className: 'hidden sm:inline-flex', children: ( ), })} )} {/* Mobile hamburger */}
{/* -------- Mobile drawer -------- */} {mobileOpen && ( )}
); }