431 lines
19 KiB
TypeScript
431 lines
19 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect, useCallback } from 'react'
|
|
import { useRouter } from 'next/navigation';
|
|
import Image from 'next/image';
|
|
import {
|
|
Dialog,
|
|
DialogPanel,
|
|
Disclosure,
|
|
DisclosureButton,
|
|
DisclosurePanel,
|
|
Popover,
|
|
PopoverButton,
|
|
PopoverGroup,
|
|
PopoverPanel,
|
|
Transition
|
|
} from '@headlessui/react'
|
|
import {
|
|
Bars3Icon,
|
|
ShoppingBagIcon,
|
|
UsersIcon,
|
|
HomeIcon,
|
|
UserCircleIcon,
|
|
XMarkIcon,
|
|
ArrowRightOnRectangleIcon,
|
|
MoonIcon,
|
|
SunIcon
|
|
} from '@heroicons/react/24/outline'
|
|
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
|
import useAuthStore from '../../store/authStore';
|
|
import { Avatar } from '../avatar';
|
|
import LanguageSwitcher from '../LanguageSwitcher';
|
|
|
|
// Replace current shopItems definition with detailed version (adds icon & description)
|
|
const shopItems = [
|
|
{ name: 'VIP', href: '/shop/vip', description: 'Exclusive VIP shop', icon: ShoppingBagIcon },
|
|
{ name: 'Public', href: '/shop/public', description: 'Open catalog for everyone', icon: UsersIcon },
|
|
];
|
|
|
|
const navLinks = [
|
|
{ name: 'Affiliate-Links', href: '/affiliate-links' },
|
|
{ name: 'Memberships', href: '/memberships' },
|
|
{ name: 'About us', href: '/about-us' },
|
|
];
|
|
|
|
export default function Header() {
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
|
const [isDark, setIsDark] = useState(false)
|
|
const user = useAuthStore((s) => s.user);
|
|
const logout = useAuthStore((s) => s.logout);
|
|
const router = useRouter();
|
|
|
|
const handleLogout = async () => {
|
|
try {
|
|
await logout();
|
|
router.push('/login');
|
|
} catch (err) {
|
|
console.error('Logout failed:', err);
|
|
}
|
|
};
|
|
|
|
// Helper to get user initials for profile icon
|
|
const getUserInitials = () => {
|
|
if (!user) return 'U';
|
|
if (user.firstName || user.lastName) {
|
|
return (
|
|
(user.firstName?.[0] || '') +
|
|
(user.lastName?.[0] || '')
|
|
).toUpperCase();
|
|
}
|
|
if (user.email) {
|
|
return user.email[0].toUpperCase();
|
|
}
|
|
return 'U';
|
|
};
|
|
|
|
// Theme initialization & persistence
|
|
useEffect(() => {
|
|
const stored = localStorage.getItem('theme')
|
|
if (stored === 'dark' || (!stored && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
document.documentElement.classList.add('dark')
|
|
setIsDark(true)
|
|
} else {
|
|
document.documentElement.classList.remove('dark')
|
|
setIsDark(false)
|
|
}
|
|
}, [])
|
|
|
|
const toggleTheme = useCallback(() => {
|
|
setIsDark(prev => {
|
|
const next = !prev
|
|
if (next) {
|
|
document.documentElement.classList.add('dark')
|
|
localStorage.setItem('theme', 'dark')
|
|
} else {
|
|
document.documentElement.classList.remove('dark')
|
|
localStorage.setItem('theme', 'light')
|
|
}
|
|
return next
|
|
})
|
|
}, [])
|
|
|
|
return (
|
|
<header
|
|
className="relative isolate z-10 border-b border-white/10 shadow-lg shadow-black/30 after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:bg-[radial-gradient(circle_at_20%_20%,rgba(56,124,255,0.18),transparent_55%),radial-gradient(circle_at_80%_35%,rgba(139,92,246,0.16),transparent_60%)]"
|
|
style={{
|
|
background: 'linear-gradient(135deg, rgba(15,29,55,0.78) 0%, rgba(10,22,42,0.72) 50%, rgba(8,18,36,0.78) 100%)',
|
|
backdropFilter: 'blur(18px) saturate(170%)',
|
|
WebkitBackdropFilter: 'blur(18px) saturate(170%)'
|
|
}}
|
|
>
|
|
<nav aria-label="Global" className="mx-auto flex max-w-7xl items-center justify-between p-6 lg:px-8">
|
|
<div className="flex lg:flex-1">
|
|
<button
|
|
onClick={() => router.push('/')}
|
|
className="p-2 flex items-center gap-3 max-w-full lg:-m-1.5 lg:gap-0"
|
|
>
|
|
<span className="sr-only">ProfitPlanet</span>
|
|
<Image
|
|
src="/images/logos/pp_logo_gold_transparent.png"
|
|
alt="ProfitPlanet Logo"
|
|
width={280}
|
|
height={84}
|
|
className="h-14 w-auto flex-shrink-0 sm:h-16 lg:h-[4.5rem]"
|
|
/>
|
|
{/* Removed flickering mobile heading (now only shown inside the sliding panel) */}
|
|
</button>
|
|
</div>
|
|
<div className="flex lg:hidden">
|
|
<button
|
|
type="button"
|
|
onClick={() => setMobileMenuOpen(true)}
|
|
aria-expanded={mobileMenuOpen}
|
|
className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-400 transition-transform duration-300 ease-out data-[open=true]:rotate-90"
|
|
data-open={mobileMenuOpen ? 'true' : 'false'}
|
|
>
|
|
<span className="sr-only">Open main menu</span>
|
|
<Bars3Icon aria-hidden="true" className="size-6" />
|
|
</button>
|
|
</div>
|
|
<PopoverGroup className="hidden lg:flex lg:gap-x-12">
|
|
<Popover>
|
|
<PopoverButton className="flex items-center gap-x-1 text-sm/6 font-semibold text-gray-900 dark:text-white">
|
|
Shop
|
|
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none text-gray-500" />
|
|
</PopoverButton>
|
|
{/* Redesigned mega menu panel */}
|
|
<PopoverPanel
|
|
transition
|
|
className="absolute left-0 right-0 top-full z-50 rounded-b-2xl shadow-xl shadow-black/40 ring-1 ring-white/10 dark:ring-white/15 overflow-hidden data-closed:-translate-y-1 data-closed:opacity-0 data-enter:duration-200 data-enter:ease-out data-leave:duration-150 data-leave:ease-in"
|
|
style={{
|
|
background: 'linear-gradient(150deg, rgba(26,46,84,0.95) 0%, rgba(18,37,70,0.92) 45%, rgba(30,56,104,0.88) 100%)',
|
|
backdropFilter: 'blur(26px) saturate(175%)',
|
|
WebkitBackdropFilter: 'blur(26px) saturate(175%)'
|
|
}}
|
|
>
|
|
<div className="relative before:absolute before:inset-0 before:pointer-events-none before:bg-[radial-gradient(circle_at_18%_30%,rgba(56,124,255,0.30),transparent_62%),radial-gradient(circle_at_82%_40%,rgba(139,92,246,0.22),transparent_65%)]">
|
|
<div className="mx-auto grid max-w-7xl grid-cols-2 md:grid-cols-3 gap-x-4 px-6 py-10 lg:px-8 xl:gap-x-8">
|
|
{shopItems.map(item => (
|
|
<div
|
|
key={item.name}
|
|
className="group relative rounded-lg p-6 text-sm/6 hover:bg-white/5 transition-colors"
|
|
>
|
|
<div className="flex size-11 items-center justify-center rounded-lg bg-white/10 backdrop-blur-md group-hover:bg-white/20 transition-colors">
|
|
<item.icon aria-hidden="true" className="size-6 text-gray-300 group-hover:text-white" />
|
|
</div>
|
|
<button
|
|
onClick={() => router.push(item.href)}
|
|
className="mt-6 block font-semibold text-white"
|
|
>
|
|
{item.name}
|
|
<span className="absolute inset-0" />
|
|
</button>
|
|
<p className="mt-1 text-gray-300">{item.description}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</PopoverPanel>
|
|
</Popover>
|
|
|
|
{navLinks.map(l => (
|
|
<button
|
|
key={l.name}
|
|
onClick={() => router.push(l.href)}
|
|
className="text-sm/6 font-semibold text-gray-900 dark:text-white hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
|
|
>
|
|
{l.name}
|
|
</button>
|
|
))}
|
|
{/* Profile Dropdown - unchanged */}
|
|
{user && (
|
|
<Popover>
|
|
<PopoverButton className="flex items-center gap-x-1 text-sm/6 font-semibold text-gray-900 dark:text-white">
|
|
<Avatar
|
|
src=""
|
|
initials={getUserInitials()}
|
|
className="size-8 bg-gradient-to-br from-indigo-500/40 to-indigo-600/60 text-white"
|
|
/>
|
|
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none text-gray-500" />
|
|
</PopoverButton>
|
|
|
|
<PopoverPanel
|
|
transition
|
|
className="absolute right-0 top-16 w-64 bg-white dark:bg-gray-900 ring-1 ring-black/5 dark:ring-white/15 transition data-closed:-translate-y-1 data-closed:opacity-0 data-enter:duration-200 data-enter:ease-out data-leave:duration-150 data-leave:ease-in"
|
|
>
|
|
<div className="p-4">
|
|
<div className="flex flex-col border-b border-white/10 pb-4 mb-4">
|
|
<div className="font-medium text-white">
|
|
{user?.firstName && user?.lastName
|
|
? `${user.firstName} ${user.lastName}`
|
|
: user?.email || 'User'
|
|
}
|
|
</div>
|
|
<div className="text-sm text-gray-400">
|
|
{user?.email || 'user@example.com'}
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => router.push('/profile')}
|
|
className="flex items-center gap-x-2 w-full text-left p-2 text-sm text-white hover:bg-white/5 rounded-md"
|
|
>
|
|
<UserCircleIcon className="size-5 text-gray-400" />
|
|
Profile
|
|
</button>
|
|
<button
|
|
onClick={handleLogout}
|
|
className="flex items-center gap-x-2 w-full text-left p-2 text-sm text-white hover:bg-white/5 rounded-md"
|
|
>
|
|
<ArrowRightOnRectangleIcon className="size-5 text-gray-400" />
|
|
Logout
|
|
</button>
|
|
</div>
|
|
</PopoverPanel>
|
|
</Popover>
|
|
)}
|
|
</PopoverGroup>
|
|
<div className="hidden lg:flex lg:flex-1 lg:justify-end lg:items-center lg:gap-x-4">
|
|
{/* Log in first (if not logged in) */}
|
|
{!user && (
|
|
<button
|
|
onClick={() => router.push('/login')}
|
|
className="text-sm/6 font-semibold text-gray-900 dark:text-white hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
|
|
>
|
|
Log in <span aria-hidden="true">→</span>
|
|
</button>
|
|
)}
|
|
{/* Language next */}
|
|
<LanguageSwitcher variant={isDark ? 'dark' : 'light'} />
|
|
{/* Theme toggle last */}
|
|
<button
|
|
onClick={toggleTheme}
|
|
aria-label="Toggle theme"
|
|
className="p-2 rounded-md border border-gray-300 dark:border-white/10 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200 transition-colors"
|
|
>
|
|
{isDark ? <SunIcon className="h-5 w-5" /> : <MoonIcon className="h-5 w-5" />}
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
<Dialog open={mobileMenuOpen} onClose={setMobileMenuOpen} className="lg:hidden">
|
|
{/* Overlay with fade */}
|
|
<Transition
|
|
appear
|
|
show={mobileMenuOpen}
|
|
>
|
|
<Transition.Child
|
|
enter="transition-opacity duration-300 ease-out"
|
|
enterFrom="opacity-0"
|
|
enterTo="opacity-100"
|
|
leave="transition-opacity duration-200 ease-in"
|
|
leaveFrom="opacity-100"
|
|
leaveTo="opacity-0"
|
|
>
|
|
<div className="fixed inset-0 z-40 bg-black/40 backdrop-blur-sm" />
|
|
</Transition.Child>
|
|
|
|
{/* Sliding panel */}
|
|
<Transition.Child
|
|
enter="transform transition-transform duration-300 ease-out"
|
|
enterFrom="translate-x-full"
|
|
enterTo="translate-x-0"
|
|
leave="transform transition-transform duration-250 ease-in"
|
|
leaveFrom="translate-x-0"
|
|
leaveTo="translate-x-full"
|
|
>
|
|
<DialogPanel className="fixed inset-y-0 right-0 z-50 w-full sm:max-w-sm h-full overflow-y-auto overflow-x-hidden bg-white dark:bg-gray-900 p-5 sm:ring-1 sm:ring-black/10 dark:sm:ring-gray-100/10">
|
|
<div className="flex items-center justify-between">
|
|
<button
|
|
onClick={() => router.push('/')}
|
|
className="p-1.5 flex items-center gap-3"
|
|
>
|
|
<span className="sr-only">ProfitPlanet</span>
|
|
<Image
|
|
src="/images/logos/pp_logo_gold_transparent.png"
|
|
alt="ProfitPlanet Logo"
|
|
width={190}
|
|
height={60}
|
|
className="h-12 w-auto flex-shrink-0"
|
|
/>
|
|
<span className="text-xl font-bold tracking-tight text-[#D4AF37]">
|
|
Profit Planet
|
|
</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setMobileMenuOpen(false)}
|
|
className="rounded-md p-2.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-transform duration-300 hover:scale-110"
|
|
>
|
|
<span className="sr-only">Close menu</span>
|
|
<XMarkIcon aria-hidden="true" className="size-6" />
|
|
</button>
|
|
</div>
|
|
<div className="mt-6 flow-root">
|
|
<div className="-my-6 divide-y divide-gray-200 dark:divide-white/10">
|
|
<div className="py-6">
|
|
<button
|
|
onClick={toggleTheme}
|
|
className="flex items-center gap-x-2 rounded-lg px-3 py-2.5 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-white/5 w-full"
|
|
>
|
|
{isDark ? <SunIcon className="h-5 w-5" /> : <MoonIcon className="h-5 w-5" />}
|
|
{isDark ? 'Light Mode' : 'Dark Mode'}
|
|
</button>
|
|
<div className="mt-4 px-1">
|
|
<LanguageSwitcher variant="dark" />
|
|
</div>
|
|
</div>
|
|
{user ? (
|
|
<>
|
|
<div className="space-y-2 py-6">
|
|
<Disclosure as="div">
|
|
<DisclosureButton className="group flex w-full items-center justify-between rounded-lg py-2 px-3 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5">
|
|
Shop
|
|
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none group-data-open:rotate-180" />
|
|
</DisclosureButton>
|
|
<DisclosurePanel className="mt-2 space-y-1">
|
|
{shopItems.map(item => (
|
|
<DisclosureButton
|
|
key={item.name}
|
|
as="button"
|
|
onClick={() => { router.push(item.href); setMobileMenuOpen(false); }}
|
|
className="block rounded-lg py-2 pl-6 pr-3 text-sm/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5 w-full text-left"
|
|
>
|
|
{item.name}
|
|
</DisclosureButton>
|
|
))}
|
|
</DisclosurePanel>
|
|
</Disclosure>
|
|
{navLinks.map(link => (
|
|
<button
|
|
key={link.name}
|
|
onClick={() => { router.push(link.href); setMobileMenuOpen(false); }}
|
|
className="block rounded-lg px-3 py-2 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5 w-full text-left"
|
|
>
|
|
{link.name}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div className="py-6 space-y-2">
|
|
<div className="flex flex-col border-b border-white/10 pb-4 mb-4 px-3">
|
|
<div className="font-medium text-white">
|
|
{user?.firstName && user?.lastName
|
|
? `${user.firstName} ${user.lastName}`
|
|
: user?.email || 'User'
|
|
}
|
|
</div>
|
|
<div className="text-sm text-gray-400">
|
|
{user?.email || 'user@example.com'}
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => { router.push('/profile'); setMobileMenuOpen(false); }}
|
|
className="block rounded-lg px-3 py-2 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5 w-full text-left"
|
|
>
|
|
Profile
|
|
</button>
|
|
<button
|
|
onClick={() => { handleLogout(); setMobileMenuOpen(false); }}
|
|
className="block rounded-lg px-3 py-2 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5 w-full text-left"
|
|
>
|
|
Logout
|
|
</button>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<div className="py-6 space-y-4">
|
|
<Disclosure as="div">
|
|
<DisclosureButton className="group flex w-full items-center justify-between rounded-lg py-2 px-3 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5">
|
|
Shop
|
|
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none group-data-open:rotate-180" />
|
|
</DisclosureButton>
|
|
<DisclosurePanel className="mt-2 space-y-1">
|
|
{shopItems.map(item => (
|
|
<DisclosureButton
|
|
key={item.name}
|
|
as="button"
|
|
onClick={() => { router.push(item.href); setMobileMenuOpen(false); }}
|
|
className="block rounded-lg py-2 pl-6 pr-3 text-sm/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5 w-full text-left"
|
|
>
|
|
{item.name}
|
|
</DisclosureButton>
|
|
))}
|
|
</DisclosurePanel>
|
|
</Disclosure>
|
|
{navLinks.map(link => (
|
|
<button
|
|
key={link.name}
|
|
onClick={() => { router.push(link.href); setMobileMenuOpen(false); }}
|
|
className="block rounded-lg px-3 py-2 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5 w-full text-left"
|
|
>
|
|
{link.name}
|
|
</button>
|
|
))}
|
|
<div className="px-3">
|
|
<button
|
|
onClick={() => { router.push('/login'); setMobileMenuOpen(false); }}
|
|
className="block rounded-lg px-3 py-2.5 text-base/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5 w-full text-left"
|
|
>
|
|
Log in
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</DialogPanel>
|
|
</Transition.Child>
|
|
</Transition>
|
|
</Dialog>
|
|
</header>
|
|
)
|
|
} |