160 lines
7.6 KiB
TypeScript
160 lines
7.6 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
import ThemeToggle from "./ThemeToggle";
|
||
import { useEffect, useState, useRef } from "react";
|
||
|
||
export default function Navbar() {
|
||
const [me, setMe] = useState<any>(null);
|
||
const [menuOpen, setMenuOpen] = useState(false);
|
||
const menuRef = useRef<HTMLDivElement>(null);
|
||
|
||
const loadUser = () => {
|
||
fetch("/api/auth/me")
|
||
.then((r) => r.json())
|
||
.then((d) => setMe(d))
|
||
.catch(() => setMe(null));
|
||
};
|
||
|
||
useEffect(() => {
|
||
loadUser();
|
||
|
||
// Listen for profile updates
|
||
const handleProfileUpdate = () => {
|
||
loadUser();
|
||
};
|
||
window.addEventListener('profile-updated', handleProfileUpdate);
|
||
|
||
return () => {
|
||
window.removeEventListener('profile-updated', handleProfileUpdate);
|
||
};
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
function handleClickOutside(event: MouseEvent) {
|
||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
||
setMenuOpen(false);
|
||
}
|
||
}
|
||
|
||
if (menuOpen) {
|
||
document.addEventListener("mousedown", handleClickOutside);
|
||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||
}
|
||
}, [menuOpen]);
|
||
|
||
const user = me?.user;
|
||
const initials = user?.firstName && user?.lastName ? `${user.firstName[0]}${user.lastName[0]}`.toUpperCase() : "U";
|
||
|
||
return (
|
||
<>
|
||
<header className="sticky top-0 z-40 border-b border-gray-200/50 dark:border-slate-700/50 bg-white/80 dark:bg-darkbg/80 backdrop-blur-md shadow-sm">
|
||
<div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
|
||
<Link href="/" className="flex items-center gap-3 hover:opacity-90 transition-opacity duration-300 group">
|
||
<div className="h-10 w-10 rounded-lg bg-gradient-to-br from-primary to-primary-dark text-white flex items-center justify-center shadow-md group-hover:shadow-lg transition-all duration-300 text-lg">
|
||
📊
|
||
</div>
|
||
<div className="leading-tight hidden sm:block">
|
||
<div className="font-bold text-sm text-gray-900 dark:text-white">
|
||
Estate Pro
|
||
</div>
|
||
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium">Planning Hub</div>
|
||
</div>
|
||
</Link>
|
||
|
||
<nav className="hidden md:flex items-center gap-8 text-sm font-medium">
|
||
<Link href="/" className="text-gray-700 dark:text-gray-300 hover:text-primary transition-colors duration-300 relative group">
|
||
Home
|
||
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-primary group-hover:w-full transition-all duration-300" />
|
||
</Link>
|
||
<Link href="/webinars" className="text-gray-700 dark:text-gray-300 hover:text-primary transition-colors duration-300 relative group">
|
||
Webinars
|
||
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-primary group-hover:w-full transition-all duration-300" />
|
||
</Link>
|
||
<Link href="/resources" className="text-gray-700 dark:text-gray-300 hover:text-primary transition-colors duration-300 relative group">
|
||
Resources
|
||
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-primary group-hover:w-full transition-all duration-300" />
|
||
</Link>
|
||
<Link href="/about" className="text-gray-700 dark:text-gray-300 hover:text-primary transition-colors duration-300 relative group">
|
||
About
|
||
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-primary group-hover:w-full transition-all duration-300" />
|
||
</Link>
|
||
<Link href="/contact" className="text-gray-700 dark:text-gray-300 hover:text-primary dark:hover:text-primary transition-colors duration-300 relative group">
|
||
Contact
|
||
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-primary group-hover:w-full transition-all duration-300" />
|
||
</Link>
|
||
</nav>
|
||
|
||
<div className="flex items-center gap-4">
|
||
<ThemeToggle />
|
||
{user ? (
|
||
<div className="relative" ref={menuRef}>
|
||
<button
|
||
className="h-11 w-11 rounded-full bg-primary text-white flex items-center justify-center shadow-glow hover:shadow-xl hover:scale-110 transition-all duration-300 active:scale-95"
|
||
onClick={() => setMenuOpen((v) => !v)}
|
||
aria-label="Account menu"
|
||
>
|
||
{user.avatarUrl ? (
|
||
// eslint-disable-next-line @next/next/no-img-element
|
||
<img src={user.avatarUrl} alt="Avatar" className="h-11 w-11 rounded-full object-cover border-2 border-white/20" />
|
||
) : (
|
||
<span className="text-sm font-bold">{initials}</span>
|
||
)}
|
||
</button>
|
||
|
||
{menuOpen && (
|
||
<div className="absolute right-0 mt-3 w-56 rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-slate-800 shadow-elevation-3 dark:shadow-elevation-4 backdrop-blur-xl p-2 text-sm z-50 animate-slideUp">
|
||
{user.role === "ADMIN" && (
|
||
<>
|
||
<div className="px-3 py-2 text-xs font-semibold text-primary uppercase tracking-wider mb-1">Admin</div>
|
||
<Link className="block px-4 py-2.5 rounded-lg hover:bg-primary/10 dark:hover:bg-primary/20 transition-colors duration-200 font-medium text-gray-700 dark:text-gray-200" href="/admin">
|
||
📊 Dashboard
|
||
</Link>
|
||
<Link className="block px-4 py-2.5 rounded-lg hover:bg-primary/10 dark:hover:bg-primary/20 transition-colors duration-200 font-medium text-gray-700 dark:text-gray-200" href="/admin/contact-messages">
|
||
📧 Messages
|
||
</Link>
|
||
<hr className="my-2 border-gray-200 dark:border-gray-700" />
|
||
</>
|
||
)}
|
||
<Link className="block px-4 py-2.5 rounded-lg hover:bg-primary/10 dark:hover:bg-primary/20 transition-colors duration-200 font-medium text-gray-700 dark:text-gray-200" href="/account/webinars">
|
||
🎓 My Webinars
|
||
</Link>
|
||
<Link className="block px-4 py-2.5 rounded-lg hover:bg-primary/10 dark:hover:bg-primary/20 transition-colors duration-200 font-medium text-gray-700 dark:text-gray-200" href="/account/settings">
|
||
⚙️ Settings
|
||
</Link>
|
||
<hr className="my-2 border-gray-200 dark:border-gray-700" />
|
||
<button
|
||
className="w-full text-left px-4 py-2.5 rounded-lg hover:bg-danger/10 dark:hover:bg-danger/20 transition-colors duration-200 font-medium text-danger"
|
||
onClick={async () => {
|
||
await fetch("/api/auth/logout", { method: "POST" });
|
||
window.location.href = "/";
|
||
}}
|
||
>
|
||
🚪 Logout
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
) : (
|
||
<>
|
||
<Link
|
||
href="/signin"
|
||
className="px-4 py-2 text-sm font-semibold text-gray-700 dark:text-gray-300 hover:text-primary transition-colors duration-300"
|
||
>
|
||
Sign In
|
||
</Link>
|
||
<Link
|
||
href="/signup"
|
||
className="btn-primary btn-sm"
|
||
>
|
||
Get Started
|
||
</Link>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</header>
|
||
</>
|
||
);
|
||
}
|