refactor: NAV BARU
This commit is contained in:
parent
6943ae7880
commit
e3198991a9
@ -1,8 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation'
|
||||||
import Image from 'next/image';
|
import Image from 'next/image'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogPanel,
|
DialogPanel,
|
||||||
@ -13,18 +13,16 @@ import {
|
|||||||
PopoverButton,
|
PopoverButton,
|
||||||
PopoverGroup,
|
PopoverGroup,
|
||||||
PopoverPanel,
|
PopoverPanel,
|
||||||
Transition
|
Transition,
|
||||||
} from '@headlessui/react'
|
} from '@headlessui/react'
|
||||||
import {
|
import {
|
||||||
Bars3Icon,
|
Bars3Icon,
|
||||||
UserCircleIcon,
|
UserCircleIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
ArrowRightOnRectangleIcon,
|
ArrowRightOnRectangleIcon,
|
||||||
MoonIcon,
|
|
||||||
SunIcon
|
|
||||||
} from '@heroicons/react/24/outline'
|
} from '@heroicons/react/24/outline'
|
||||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||||
import useAuthStore from '../../store/authStore';
|
import useAuthStore from '../../store/authStore'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
|
|
||||||
// ENV-BASED FEATURE FLAGS (string envs: treat "false" as off, everything else as on)
|
// ENV-BASED FEATURE FLAGS (string envs: treat "false" as off, everything else as on)
|
||||||
@ -32,7 +30,7 @@ const DISPLAY_NEWS = process.env.NEXT_PUBLIC_DISPLAY_NEWS !== 'false'
|
|||||||
const DISPLAY_MEMBERSHIP = process.env.NEXT_PUBLIC_DISPLAY_MEMBERSHIP !== 'false'
|
const DISPLAY_MEMBERSHIP = process.env.NEXT_PUBLIC_DISPLAY_MEMBERSHIP !== 'false'
|
||||||
const DISPLAY_ABOUT_US = process.env.NEXT_PUBLIC_DISPLAY_ABOUT_US !== 'false'
|
const DISPLAY_ABOUT_US = process.env.NEXT_PUBLIC_DISPLAY_ABOUT_US !== 'false'
|
||||||
const DISPLAY_MATRIX = process.env.NEXT_PUBLIC_DISPLAY_MATRIX !== 'false'
|
const DISPLAY_MATRIX = process.env.NEXT_PUBLIC_DISPLAY_MATRIX !== 'false'
|
||||||
const DISPLAY_ABONEMMENTS = process.env.NEXT_PUBLIC_DISPLAY_ABONEMMENTS !== 'false'
|
const DISPLAY_ABONEMENTS = process.env.NEXT_PUBLIC_DISPLAY_ABONEMMENTS !== 'false'
|
||||||
const DISPLAY_POOLS = process.env.NEXT_PUBLIC_DISPLAY_POOLS !== 'false'
|
const DISPLAY_POOLS = process.env.NEXT_PUBLIC_DISPLAY_POOLS !== 'false'
|
||||||
|
|
||||||
|
|
||||||
@ -57,26 +55,28 @@ const showShop = false
|
|||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||||
const [isDark, setIsDark] = useState(false)
|
const [mounted, setMounted] = useState(false)
|
||||||
const [mounted, setMounted] = useState(false) // added
|
const [animateIn, setAnimateIn] = useState(false)
|
||||||
const user = useAuthStore((s) => s.user);
|
const [loggingOut, setLoggingOut] = useState(false) // NEW
|
||||||
const logout = useAuthStore((s) => s.logout);
|
const user = useAuthStore(s => s.user)
|
||||||
const accessToken = useAuthStore((s) => s.accessToken);
|
const logout = useAuthStore(s => s.logout)
|
||||||
const refreshAuthToken = useAuthStore((s) => s.refreshAuthToken);
|
const accessToken = useAuthStore(s => s.accessToken)
|
||||||
const router = useRouter();
|
const refreshAuthToken = useAuthStore(s => s.refreshAuthToken)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// NEW: permission flag
|
|
||||||
const [hasReferralPerm, setHasReferralPerm] = useState(false)
|
const [hasReferralPerm, setHasReferralPerm] = useState(false)
|
||||||
// NEW: admin management dropdown state
|
|
||||||
const [adminMgmtOpen, setAdminMgmtOpen] = useState(false)
|
const [adminMgmtOpen, setAdminMgmtOpen] = useState(false)
|
||||||
const managementRef = useRef<HTMLDivElement | null>(null)
|
const managementRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
await logout();
|
setLoggingOut(true) // NEW: start logout transition
|
||||||
router.push('/login');
|
await logout()
|
||||||
|
setMobileMenuOpen(false)
|
||||||
|
router.push('/login')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Logout failed:', err);
|
console.error('Logout failed:', err)
|
||||||
|
setLoggingOut(false) // reset if something goes wrong
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -95,31 +95,20 @@ export default function Header() {
|
|||||||
return 'U';
|
return 'U';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Theme initialization & persistence
|
// Initial theme (dark/light) + mark mounted + start header animation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const stored = localStorage.getItem('theme')
|
const stored = localStorage.getItem('theme')
|
||||||
if (stored === 'dark' || (!stored && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
const useDark = stored === 'dark' || (!stored && prefersDark)
|
||||||
|
|
||||||
|
if (useDark) {
|
||||||
document.documentElement.classList.add('dark')
|
document.documentElement.classList.add('dark')
|
||||||
setIsDark(true)
|
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove('dark')
|
||||||
setIsDark(false)
|
|
||||||
}
|
}
|
||||||
setMounted(true) // hydration complete
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const toggleTheme = useCallback(() => {
|
setMounted(true)
|
||||||
setIsDark(prev => {
|
setAnimateIn(true)
|
||||||
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
|
|
||||||
})
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Fetch user permissions and set hasReferralPerm
|
// Fetch user permissions and set hasReferralPerm
|
||||||
@ -213,21 +202,18 @@ export default function Header() {
|
|||||||
((user as any)?.roles?.includes?.('admin'))
|
((user as any)?.roles?.includes?.('admin'))
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fix: Only render header after hydration to avoid SSR/CSR mismatch
|
|
||||||
if (!mounted) {
|
|
||||||
// Optionally, render a skeleton or nothing
|
|
||||||
return <div style={{ minHeight: 80 }} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
// Remove bottom border when admin subheader is present to avoid a blue line under the gold bar
|
// Remove bottom border when admin subheader is present to avoid a blue line under the gold bar
|
||||||
className={`relative isolate z-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%)] ${isAdmin ? '' : 'border-b border-white/10'}`}
|
className={`relative isolate z-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%)] ${isAdmin ? '' : 'border-b border-white/10'} ${animateIn ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-4'} transition-all duration-500 ease-out`}
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(135deg, #0F1D37 0%, #0A162A 50%, #081224 100%)',
|
background: 'linear-gradient(135deg, #0F1D37 0%, #0A162A 50%, #081224 100%)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<nav aria-label="Global" className="mx-auto flex max-w-7xl items-center justify-between p-6 lg:px-8">
|
<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">
|
<div className="flex lg:flex-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push('/')}
|
onClick={() => router.push('/')}
|
||||||
@ -244,6 +230,8 @@ export default function Header() {
|
|||||||
{/* Removed flickering mobile heading (now only shown inside the sliding panel) */}
|
{/* Removed flickering mobile heading (now only shown inside the sliding panel) */}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile hamburger (only on small screens) */}
|
||||||
<div className="flex lg:hidden">
|
<div className="flex lg:hidden">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -256,6 +244,7 @@ export default function Header() {
|
|||||||
<Bars3Icon aria-hidden="true" className="size-6" />
|
<Bars3Icon aria-hidden="true" className="size-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PopoverGroup className="hidden lg:flex lg:gap-x-12">
|
<PopoverGroup className="hidden lg:flex lg:gap-x-12">
|
||||||
|
|
||||||
{/* Navigation Links */}
|
{/* Navigation Links */}
|
||||||
@ -269,16 +258,10 @@ export default function Header() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Conditional user-specific links */}
|
{/* Conditional user-specific links (top navbar) */}
|
||||||
{userPresent && hasReferralPerm && (
|
{userPresent && hasReferralPerm && (
|
||||||
<>
|
<>
|
||||||
<button
|
{/* Referral Management REMOVED from top nav */}
|
||||||
onClick={() => { console.log('🧭 Header: navigate to /referral-management'); router.push('/referral-management') }}
|
|
||||||
className="text-sm/6 font-semibold text-gray-900 dark:text-white hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
|
|
||||||
>
|
|
||||||
Referral Management
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{DISPLAY_MATRIX && (
|
{DISPLAY_MATRIX && (
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push('/personal-matrix')}
|
onClick={() => router.push('/personal-matrix')}
|
||||||
@ -288,7 +271,7 @@ export default function Header() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{DISPLAY_ABONEMMENTS && (
|
{DISPLAY_ABONEMENTS && (
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push('/coffee-abonnements')}
|
onClick={() => router.push('/coffee-abonnements')}
|
||||||
className="text-sm/6 font-semibold text-gray-900 dark:text-white hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
|
className="text-sm/6 font-semibold text-gray-900 dark:text-white hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
|
||||||
@ -299,45 +282,11 @@ export default function Header() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Information dropdown - moved to far right */}
|
{/* Information dropdown already removed here */}
|
||||||
<Popover>
|
|
||||||
<PopoverButton className="flex items-center gap-x-1 text-sm/6 font-semibold text-gray-900 dark:text-white">
|
|
||||||
Information
|
|
||||||
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none text-gray-500" />
|
|
||||||
</PopoverButton>
|
|
||||||
<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-1 md:grid-cols-3 gap-x-4 px-6 py-10 lg:px-8 xl:gap-x-8">
|
|
||||||
{informationItems.map(item => (
|
|
||||||
<div
|
|
||||||
key={item.name}
|
|
||||||
className="group relative rounded-lg p-6 text-sm/6 hover:bg-white/5 transition-colors"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onClick={() => router.push(item.href)}
|
|
||||||
className="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>
|
|
||||||
</PopoverGroup>
|
</PopoverGroup>
|
||||||
|
|
||||||
<div className="hidden lg:flex lg:flex-1 lg:justify-end lg:items-center lg:gap-x-4">
|
<div className="hidden lg:flex lg:flex-1 lg:justify-end lg:items-center lg:gap-x-4">
|
||||||
{/* Stable auth slot to avoid SSR/CSR structural drift */}
|
{/* Auth slot */}
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{userPresent ? (
|
{userPresent ? (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
@ -382,13 +331,7 @@ export default function Header() {
|
|||||||
<UserCircleIcon className="size-5 text-gray-400" />
|
<UserCircleIcon className="size-5 text-gray-400" />
|
||||||
Profile
|
Profile
|
||||||
</button>
|
</button>
|
||||||
<button
|
{/* Logout removed from profile dropdown; still available in hamburger menu bottom */}
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex items-center gap-x-2 w-full text-left p-2 text-sm text-gray-800 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-white/5 rounded-md"
|
|
||||||
>
|
|
||||||
<ArrowRightOnRectangleIcon className="size-5 text-gray-400" />
|
|
||||||
Logout
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</PopoverPanel>
|
</PopoverPanel>
|
||||||
</Popover>
|
</Popover>
|
||||||
@ -403,26 +346,16 @@ export default function Header() {
|
|||||||
<div aria-hidden="true" className="w-20 h-8 rounded-md bg-gray-200 dark:bg-gray-700/70 animate-pulse" />
|
<div aria-hidden="true" className="w-20 h-8 rounded-md bg-gray-200 dark:bg-gray-700/70 animate-pulse" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* Replace LanguageSwitcher with English-only dropdown + note */}
|
|
||||||
<Popover className="relative">
|
|
||||||
<PopoverButton 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 text-sm font-semibold">
|
|
||||||
English
|
|
||||||
<ChevronDownIcon aria-hidden="true" className="ml-1 inline h-4 w-4 text-gray-500" />
|
|
||||||
</PopoverButton>
|
|
||||||
<PopoverPanel
|
|
||||||
transition
|
|
||||||
className="absolute right-0 top-full mt-2 w-72 rounded-md bg-white dark:bg-gray-900 ring-1 ring-black/10 dark:ring-white/10 shadow-lg p-3 text-sm text-gray-800 dark:text-gray-200 data-closed:-translate-y-1 data-closed:opacity-0 data-enter:duration-200 data-leave:duration-150"
|
|
||||||
>
|
|
||||||
We are currently working on implementing other languages.
|
|
||||||
</PopoverPanel>
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
|
{/* Desktop hamburger (right side, next to login/profile) */}
|
||||||
<button
|
<button
|
||||||
onClick={toggleTheme}
|
type="button"
|
||||||
aria-label="Toggle theme"
|
onClick={() => setMobileMenuOpen(true)}
|
||||||
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"
|
aria-expanded={mobileMenuOpen}
|
||||||
|
className="inline-flex items-center justify-center rounded-md p-2.5 text-gray-300 hover:text-white hover:bg-white/10 transition-colors"
|
||||||
>
|
>
|
||||||
{isDark ? <SunIcon className="h-5 w-5" /> : <MoonIcon className="h-5 w-5" />}
|
<span className="sr-only">Open main menu</span>
|
||||||
|
<Bars3Icon aria-hidden="true" className="size-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@ -495,7 +428,7 @@ export default function Header() {
|
|||||||
Contract Management
|
Contract Management
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{DISPLAY_ABONEMMENTS && (
|
{DISPLAY_ABONEMENTS && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => { router.push('/admin/subscriptions'); setAdminMgmtOpen(false); }}
|
onClick={() => { router.push('/admin/subscriptions'); setAdminMgmtOpen(false); }}
|
||||||
@ -550,34 +483,33 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mobile dialog and rest of header */}
|
{/* Side drawer menu: mobile + desktop */}
|
||||||
<Dialog open={mobileMenuOpen} onClose={setMobileMenuOpen} className="lg:hidden">
|
<Dialog open={mobileMenuOpen} onClose={setMobileMenuOpen}>
|
||||||
<Transition
|
<Transition appear show={mobileMenuOpen}>
|
||||||
appear
|
{/* Overlay: slightly slower fade for a smoother feel */}
|
||||||
show={mobileMenuOpen}
|
|
||||||
>
|
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
enter="transition-opacity duration-300 ease-out"
|
enter="transition-opacity duration-400 ease-out"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
enterTo="opacity-100"
|
enterTo="opacity-100"
|
||||||
leave="transition-opacity duration-200 ease-in"
|
leave="transition-opacity duration-300 ease-in"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 z-40 bg-black/40 backdrop-blur-sm" />
|
<div className="fixed inset-0 z-40 bg-black/40 backdrop-blur-sm" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
{/* Sliding panel */}
|
{/* Sliding panel: slide + fade + scale for a modern, flashy effect */}
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
enter="transform transition-transform duration-300 ease-out"
|
enter="transform transition-all duration-500 ease-out"
|
||||||
enterFrom="translate-x-full"
|
enterFrom="translate-x-full opacity-0 scale-95"
|
||||||
enterTo="translate-x-0"
|
enterTo="translate-x-0 opacity-100 scale-100"
|
||||||
leave="transform transition-transform duration-250 ease-in"
|
leave="transform transition-all duration-400 ease-in"
|
||||||
leaveFrom="translate-x-0"
|
leaveFrom="translate-x-0 opacity-100 scale-100"
|
||||||
leaveTo="translate-x-full"
|
leaveTo="translate-x-full opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<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">
|
<DialogPanel className="fixed inset-y-0 right-0 z-50 w-full sm:max-w-sm h-full bg-white/95 dark:bg-gray-900/95 backdrop-blur-xl sm:ring-1 sm:ring-black/10 dark:sm:ring-gray-100/10 shadow-[0_0_40px_rgba(0,0,0,0.55)] flex flex-col">
|
||||||
<div className="flex items-center justify-between">
|
{/* Header row */}
|
||||||
|
<div className="flex items-center justify-between px-1.5 pt-1.5 pb-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push('/')}
|
onClick={() => router.push('/')}
|
||||||
className="p-1.5 flex items-center gap-3"
|
className="p-1.5 flex items-center gap-3"
|
||||||
@ -597,21 +529,24 @@ export default function Header() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
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"
|
className="rounded-md p-2.5 text-gray-400 hover:text-gray-100 hover:bg-white/10 transition-transform duration-300 hover:scale-110"
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close menu</span>
|
<span className="sr-only">Close menu</span>
|
||||||
<XMarkIcon aria-hidden="true" className="size-6" />
|
<XMarkIcon aria-hidden="true" className="size-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 flow-root">
|
|
||||||
|
{/* Scrollable content */}
|
||||||
|
<div className="mt-4 flex-1 overflow-y-auto overflow-x-hidden">
|
||||||
<div className="-my-6 divide-y divide-gray-200 dark:divide-white/10">
|
<div className="-my-6 divide-y divide-gray-200 dark:divide-white/10">
|
||||||
{!mounted ? (
|
{!mounted ? (
|
||||||
|
// ...existing skeleton...
|
||||||
<div className="py-6 px-3">
|
<div className="py-6 px-3">
|
||||||
<div className="h-28 w-full rounded-lg bg-gray-200 dark:bg-gray-700 animate-pulse" />
|
<div className="h-28 w-full rounded-lg bg-gray-200 dark:bg-gray-700 animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
) : user ? (
|
) : user ? (
|
||||||
<>
|
<>
|
||||||
{/* User info now FIRST under logo */}
|
{/* User info + basic nav */}
|
||||||
<div className="pt-6 space-y-2">
|
<div className="pt-6 space-y-2">
|
||||||
<div className="flex flex-col border-b border-white/10 pb-4 mb-4 px-3">
|
<div className="flex flex-col border-b border-white/10 pb-4 mb-4 px-3">
|
||||||
<div className="font-medium text-white">
|
<div className="font-medium text-white">
|
||||||
@ -633,35 +568,10 @@ export default function Header() {
|
|||||||
>
|
>
|
||||||
Profile
|
Profile
|
||||||
</button>
|
</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>
|
||||||
{/* Theme + Language now AFTER user info */}
|
|
||||||
<div className="py-6">
|
{/* Main navigation (info + links + referral + ADMIN LAST) */}
|
||||||
<button
|
<div className="space-y-4 py-6 px-1">
|
||||||
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>
|
|
||||||
{/* Replace LanguageSwitcher with English-only dropdown + note for mobile */}
|
|
||||||
<Disclosure as="div" className="mt-4 px-1">
|
|
||||||
<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">
|
|
||||||
Language: English
|
|
||||||
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none group-data-open:rotate-180" />
|
|
||||||
</DisclosureButton>
|
|
||||||
<DisclosurePanel className="mt-2 space-y-1 px-3 text-sm text-gray-700 dark:text-gray-300">
|
|
||||||
We are currently working on implementing other languages.
|
|
||||||
</DisclosurePanel>
|
|
||||||
</Disclosure>
|
|
||||||
</div>
|
|
||||||
{/* Navigation after that */}
|
|
||||||
<div className="space-y-2 py-6">
|
|
||||||
{/* Information disclosure */}
|
{/* Information disclosure */}
|
||||||
<Disclosure as="div">
|
<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">
|
<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">
|
||||||
@ -681,6 +591,7 @@ export default function Header() {
|
|||||||
))}
|
))}
|
||||||
</DisclosurePanel>
|
</DisclosurePanel>
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
|
|
||||||
{/* Navigation Links */}
|
{/* Navigation Links */}
|
||||||
{navLinks.map((link) => (
|
{navLinks.map((link) => (
|
||||||
<button
|
<button
|
||||||
@ -691,7 +602,8 @@ export default function Header() {
|
|||||||
{link.name}
|
{link.name}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
{/* Conditional user-specific links */}
|
|
||||||
|
{/* Referral / matrix / abonnements */}
|
||||||
{hasReferralPerm && (
|
{hasReferralPerm && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
@ -708,7 +620,7 @@ export default function Header() {
|
|||||||
Personal Matrix
|
Personal Matrix
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{DISPLAY_ABONEMMENTS && (
|
{DISPLAY_ABONEMENTS && (
|
||||||
<button
|
<button
|
||||||
onClick={() => { router.push('/coffee-abonnements'); setMobileMenuOpen(false); }}
|
onClick={() => { router.push('/coffee-abonnements'); 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"
|
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"
|
||||||
@ -718,10 +630,95 @@ export default function Header() {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Admin navigation – LAST, neutral glassy with pulsating hover */}
|
||||||
|
{isAdmin && (
|
||||||
|
<div className="group mt-2 rounded-2xl border border-white/15 bg-gradient-to-br from-white/5 via-white/10 to-white/5 bg-clip-padding backdrop-blur-md shadow-[0_18px_45px_rgba(0,0,0,0.45)] ring-1 ring-white/15 transition-transform transition-shadow duration-200 ease-out hover:-translate-y-0.5 hover:shadow-[0_22px_55px_rgba(0,0,0,0.6)]">
|
||||||
|
<div className="px-3 py-2.5 group-hover:animate-pulse">
|
||||||
|
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-gray-200 mb-1.5">
|
||||||
|
Admin Navigation
|
||||||
|
</p>
|
||||||
|
<div className="h-px w-full bg-gradient-to-r from-transparent via-white/70 to-transparent opacity-80 mb-2 transition-opacity group-hover:opacity-100" />
|
||||||
|
<div className="grid grid-cols-1 gap-1.5 text-sm">
|
||||||
|
<button
|
||||||
|
onClick={() => { router.push('/admin'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full text-left rounded-lg px-2 py-1.5 text-gray-100 hover:bg-white/15 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Dashboard
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => { router.push('/admin/user-verify'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full text-left rounded-lg px-2 py-1.5 text-gray-100 hover:bg-white/15 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
User Verify
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => { router.push('/admin/user-management'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full text-left rounded-lg px-2 py-1.5 text-gray-100 hover:bg-white/15 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
User Management
|
||||||
|
</button>
|
||||||
|
{DISPLAY_MATRIX && (
|
||||||
|
<button
|
||||||
|
onClick={() => { router.push('/admin/matrix-management'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full text-left rounded-lg px-2 py-1.5 text-gray-100 hover:bg-white/15 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Matrix Management
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => { router.push('/admin/contract-management'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full text-left rounded-lg px-2 py-1.5 text-gray-100 hover:bg-white/15 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Contract Management
|
||||||
|
</button>
|
||||||
|
{DISPLAY_ABONEMENTS && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => { router.push('/admin/subscriptions'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full text-left rounded-lg px-2 py-1.5 text-gray-100 hover:bg-white/15 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Coffee Management
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => { router.push('/admin/finance-management'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full text-left rounded-lg px-2 py-1.5 text-gray-100 hover:bg-white/15 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Finance Management
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{DISPLAY_POOLS && (
|
||||||
|
<button
|
||||||
|
onClick={() => { router.push('/admin/pool-management'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full text-left rounded-lg px-2 py-1.5 text-gray-100 hover:bg-white/15 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Pool Management
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => { router.push('/admin/affiliate-management'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full text-left rounded-lg px-2 py-1.5 text-gray-100 hover:bg-white/15 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Affiliate Management
|
||||||
|
</button>
|
||||||
|
{DISPLAY_NEWS && (
|
||||||
|
<button
|
||||||
|
onClick={() => { router.push('/admin/news-management'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full text-left rounded-lg px-2 py-1.5 text-gray-100 hover:bg-white/15 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
News Management
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="py-6 space-y-4">
|
// logged-out drawer
|
||||||
|
<div className="py-6 space-y-4 px-1">
|
||||||
{/* Information disclosure */}
|
{/* Information disclosure */}
|
||||||
<Disclosure as="div">
|
<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">
|
<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">
|
||||||
@ -751,18 +748,6 @@ export default function Header() {
|
|||||||
{link.name}
|
{link.name}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
{/* Language (English-only) in logged-out mobile */}
|
|
||||||
<div className="px-3">
|
|
||||||
<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">
|
|
||||||
Language: English
|
|
||||||
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none group-data-open:rotate-180" />
|
|
||||||
</DisclosureButton>
|
|
||||||
<DisclosurePanel className="mt-2 space-y-1 px-3 text-sm text-gray-700 dark:text-gray-300">
|
|
||||||
We are currently working on implementing other languages.
|
|
||||||
</DisclosurePanel>
|
|
||||||
</Disclosure>
|
|
||||||
</div>
|
|
||||||
<div className="px-3">
|
<div className="px-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => { router.push('/login'); setMobileMenuOpen(false); }}
|
onClick={() => { router.push('/login'); setMobileMenuOpen(false); }}
|
||||||
@ -775,10 +760,33 @@ export default function Header() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogPanel>
|
|
||||||
|
{/* Sticky bottom logout button with pulsating hover */}
|
||||||
|
{user && (
|
||||||
|
<div className="border-t border-gray-200/60 dark:border-white/10 px-4 py-3">
|
||||||
|
<button
|
||||||
|
onClick={() => { handleLogout(); setMobileMenuOpen(false); }}
|
||||||
|
className="flex w-full items-center justify-center gap-2 rounded-lg bg-red-500/90 hover:bg-red-600 text-white py-2.5 text-sm font-semibold shadow-md shadow-red-900/30 transition-transform transition-colors duration-200 hover:animate-pulse"
|
||||||
|
>
|
||||||
|
<ArrowRightOnRectangleIcon className="h-5 w-5" />
|
||||||
|
<span>Logout</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DialogPanel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Logout transition overlay */}
|
||||||
|
{loggingOut && (
|
||||||
|
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/60 backdrop-blur-sm">
|
||||||
|
<div className="flex flex-col items-center gap-3 text-white">
|
||||||
|
<div className="h-10 w-10 rounded-full border-2 border-white/30 border-t-white animate-spin" />
|
||||||
|
<p className="text-sm font-medium">Logging you out...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -7,6 +7,7 @@ import PageLayout from '../components/PageLayout'
|
|||||||
import useAuthStore from '../store/authStore'
|
import useAuthStore from '../store/authStore'
|
||||||
import GlobalAnimatedBackground from '../background/GlobalAnimatedBackground'
|
import GlobalAnimatedBackground from '../background/GlobalAnimatedBackground'
|
||||||
import { ToastProvider } from '../components/toast/toastComponent'
|
import { ToastProvider } from '../components/toast/toastComponent'
|
||||||
|
import PageTransitionEffect from '../components/animation/pageTransitionEffect'
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [showBackground, setShowBackground] = useState(false)
|
const [showBackground, setShowBackground] = useState(false)
|
||||||
@ -37,37 +38,41 @@ export default function LoginPage() {
|
|||||||
// Don't render if user is already logged in (only after hydration to avoid SSR mismatch)
|
// Don't render if user is already logged in (only after hydration to avoid SSR mismatch)
|
||||||
if (hasHydrated && user) {
|
if (hasHydrated && user) {
|
||||||
return (
|
return (
|
||||||
<ToastProvider>
|
<PageTransitionEffect>
|
||||||
<PageLayout>
|
<ToastProvider>
|
||||||
<div className="min-h-screen flex items-center justify-center">
|
<PageLayout>
|
||||||
<div className="text-center">
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#8D6B1D] mx-auto mb-4"></div>
|
<div className="text-center">
|
||||||
<p className="text-slate-700">You are already logged in. Redirecting...</p>
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#8D6B1D] mx-auto mb-4"></div>
|
||||||
|
<p className="text-slate-700">You are already logged in. Redirecting...</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PageLayout>
|
||||||
</PageLayout>
|
</ToastProvider>
|
||||||
</ToastProvider>
|
</PageTransitionEffect>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToastProvider>
|
<PageTransitionEffect>
|
||||||
<PageLayout showFooter={true}>
|
<ToastProvider>
|
||||||
<div
|
<PageLayout showFooter={true}>
|
||||||
className="relative w-full flex flex-col min-h-screen"
|
<div
|
||||||
style={{
|
className="relative w-full flex flex-col min-h-screen"
|
||||||
backgroundImage: 'url(/images/misc/marble_bluegoldwhite_BG.jpg)',
|
style={{
|
||||||
backgroundSize: 'cover',
|
backgroundImage: 'url(/images/misc/marble_bluegoldwhite_BG.jpg)',
|
||||||
backgroundPosition: 'center'
|
backgroundSize: 'cover',
|
||||||
}}
|
backgroundPosition: 'center'
|
||||||
>
|
}}
|
||||||
<div className="relative z-10 flex-1 flex items-center justify-center">
|
>
|
||||||
<div className="w-full">
|
<div className="relative z-10 flex-1 flex items-center justify-center">
|
||||||
<LoginForm />
|
<div className="w-full">
|
||||||
|
<LoginForm />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PageLayout>
|
||||||
</PageLayout>
|
</ToastProvider>
|
||||||
</ToastProvider>
|
</PageTransitionEffect>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user