Initial commit

This commit is contained in:
Developer
2026-02-06 21:44:04 -06:00
commit f85e93c7a6
151 changed files with 22916 additions and 0 deletions

159
components/Navbar.tsx Normal file
View File

@@ -0,0 +1,159 @@
"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>
</>
);
}