profit-planet-frontend/src/app/components/nav/Header.tsx

882 lines
39 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState, useEffect, useCallback, useRef } from 'react'
import { useRouter, usePathname } from 'next/navigation'
import Image from 'next/image'
import {
Dialog,
DialogPanel,
Disclosure,
DisclosureButton,
DisclosurePanel,
PopoverGroup,
Transition,
} from '@headlessui/react'
import {
Bars3Icon,
UserCircleIcon,
XMarkIcon,
ArrowRightOnRectangleIcon,
} from '@heroicons/react/24/outline'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import useAuthStore from '../../store/authStore'
import { Avatar } from '../avatar'
// ENV-BASED FEATURE FLAGS (string envs: treat "false" as off, everything else as on)
const DISPLAY_NEWS = process.env.NEXT_PUBLIC_DISPLAY_NEWS !== '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_MATRIX = process.env.NEXT_PUBLIC_DISPLAY_MATRIX !== 'false'
const DISPLAY_ABONEMENTS = process.env.NEXT_PUBLIC_DISPLAY_ABONEMENTS !== 'false'
const DISPLAY_POOLS = process.env.NEXT_PUBLIC_DISPLAY_POOLS !== 'false'
// Information dropdown, controlled by env flags
const informationItems = [
{ name: 'Affiliate-Links', href: '/affiliate-links', description: 'Browse our partner links' },
...(DISPLAY_MEMBERSHIP
? [{ name: 'Memberships', href: '/memberships', description: 'Explore membership options' }]
: []),
...(DISPLAY_ABOUT_US
? [{ name: 'About us', href: '/about-us', description: 'Learn more about us' }]
: []),
]
// Top-level navigation links, controlled by env flags
const navLinks = [
...(DISPLAY_NEWS ? [{ name: 'News', href: '/news' }] : []),
]
// Toggle visibility of Shop navigation across header (desktop + mobile)
const showShop = false
interface HeaderProps {
setGlobalLoggingOut?: (value: boolean) => void; // NEW
}
export default function Header({ setGlobalLoggingOut }: HeaderProps) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [mounted, setMounted] = useState(false)
const [animateIn, setAnimateIn] = useState(false)
const [scrollY, setScrollY] = useState(0)
const [isMobile, setIsMobile] = useState(false)
const user = useAuthStore(s => s.user)
const logout = useAuthStore(s => s.logout)
const accessToken = useAuthStore(s => s.accessToken)
const refreshAuthToken = useAuthStore(s => s.refreshAuthToken)
const router = useRouter()
const pathname = usePathname()
const isParallaxPage =
pathname === '/' ||
pathname === '/login' ||
pathname === '/password-reset' ||
pathname === '/register'
const parallaxEnabled = isParallaxPage && !isMobile
const headerIsFixedOverlay = isParallaxPage && !isMobile
const headerPositionClass = isParallaxPage
? (isMobile ? 'sticky top-0 w-full' : 'fixed top-0 left-0 w-full')
: 'relative'
const [hasReferralPerm, setHasReferralPerm] = useState(false)
const [canSeeDashboard, setCanSeeDashboard] = useState(false)
const headerElRef = useRef<HTMLElement | null>(null)
const handleLogout = async () => {
try {
// start global logout transition
setGlobalLoggingOut?.(true)
await logout()
setMobileMenuOpen(false)
router.push('/login')
} catch (err) {
console.error('Logout failed:', err)
setGlobalLoggingOut?.(false)
}
}
// 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'
}
// Initial theme (force dark) + mark mounted + start header animation
useEffect(() => {
document.documentElement.classList.add('dark')
setMounted(true)
setAnimateIn(true)
}, [])
// Detect mobile devices (for disabling parallax)
useEffect(() => {
const mq = window.matchMedia('(max-width: 768px)')
const apply = () => setIsMobile(mq.matches)
apply()
mq.addEventListener?.('change', apply)
window.addEventListener('resize', apply, { passive: true })
return () => {
mq.removeEventListener?.('change', apply)
window.removeEventListener('resize', apply)
}
}, [])
// Home + login scroll listener: reveal header after first scroll with slight parallax
useEffect(() => {
if (!mounted) return
if (!parallaxEnabled) {
// non-parallax (and mobile): header always visible, no scroll listeners
setScrollY(100)
return
}
const handleScroll = () => {
const y = window.scrollY || window.pageYOffset || 0
setScrollY(y)
}
const handleWheel = (e: WheelEvent) => {
// virtual scroll so header can reveal even if page cannot scroll
setScrollY(prev => {
const next = prev + e.deltaY
return Math.max(0, Math.min(next, 200))
})
}
window.addEventListener('scroll', handleScroll, { passive: true })
window.addEventListener('wheel', handleWheel, { passive: true })
return () => {
window.removeEventListener('scroll', handleScroll)
window.removeEventListener('wheel', handleWheel)
}
}, [mounted, parallaxEnabled])
// Fetch user permissions and set hasReferralPerm
useEffect(() => {
let cancelled = false
const fetchPermissions = async () => {
if (!mounted) {
console.log('⏸️ Header: not mounted yet, skipping permissions fetch')
return
}
if (!user) {
console.log(' Header: no user, clearing permission flag')
if (!cancelled) setHasReferralPerm(false)
return
}
const uid = (user as any)?.id ?? (user as any)?._id ?? (user as any)?.userId
if (!uid) {
console.warn('⚠️ Header: user id missing, cannot fetch permissions', user)
if (!cancelled) setHasReferralPerm(false)
return
}
const base = process.env.NEXT_PUBLIC_API_BASE_URL || ''
const url = `${base}/api/users/${uid}/permissions`
console.log('🌐 Header: fetching permissions:', { url, uid })
// Ensure we have a token (try refresh if needed)
let tokenToUse = accessToken
try {
if (!tokenToUse && refreshAuthToken) {
const ok = await refreshAuthToken()
if (ok) tokenToUse = useAuthStore.getState().accessToken
}
} catch (e) {
console.error('❌ Header: refreshAuthToken error:', e)
}
try {
const res = await fetch(url, {
method: 'GET',
cache: 'no-store',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...(tokenToUse ? { Authorization: `Bearer ${tokenToUse}` } : {})
}
})
console.log('📡 Header: permissions status:', res.status)
const body = await res.json().catch(() => null)
console.log('📦 Header: permissions body:', body)
// Try common shapes
const permsSrc =
body?.data?.permissions ??
body?.permissions ??
body
let can = false
if (Array.isArray(permsSrc)) {
// Could be array of strings or objects
can =
permsSrc.includes?.('can_create_referrals') ||
permsSrc.some?.((p: any) => p?.name === 'can_create_referrals' || p?.key === 'can_create_referrals')
} else if (permsSrc && typeof permsSrc === 'object') {
can = !!permsSrc.can_create_referrals
}
console.log('✅ Header: can_create_referrals =', can)
if (!cancelled) setHasReferralPerm(!!can)
} catch (e) {
console.error('❌ Header: fetch permissions error:', e)
if (!cancelled) setHasReferralPerm(false)
}
}
fetchPermissions()
return () => { cancelled = true }
}, [mounted, user, accessToken, refreshAuthToken])
// NEW: fetch onboarding status to decide if dashboard should be visible
useEffect(() => {
let cancelled = false
const fetchOnboardingStatus = async () => {
if (!mounted || !user) {
if (!cancelled) setCanSeeDashboard(false)
return
}
let tokenToUse = accessToken
try {
if (!tokenToUse && refreshAuthToken) {
const ok = await refreshAuthToken()
if (ok) tokenToUse = useAuthStore.getState().accessToken
}
} catch (e) {
console.error('❌ Header: refreshAuthToken (status-progress) error:', e)
}
if (!tokenToUse) {
if (!cancelled) setCanSeeDashboard(false)
return
}
try {
const statusUrl = `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/user/status-progress`
console.log('🌐 Header: fetching status-progress:', statusUrl)
const res = await fetch(statusUrl, {
method: 'GET',
headers: {
Authorization: `Bearer ${tokenToUse}`,
'Content-Type': 'application/json',
},
credentials: 'include',
})
if (!res.ok) {
console.warn('⚠️ Header: status-progress failed with', res.status)
if (!cancelled) setCanSeeDashboard(false)
return
}
const statusData = await res.json().catch(() => null)
const progressData = statusData?.progress || statusData || {}
const steps = progressData.steps || []
const allStepsCompleted =
steps.length === 4 && steps.every((step: any) => step?.completed === true)
const isActive = progressData.status === 'active'
if (!cancelled) {
setCanSeeDashboard(allStepsCompleted && isActive)
}
} catch (e) {
console.error('❌ Header: status-progress fetch error:', e)
if (!cancelled) setCanSeeDashboard(false)
}
}
fetchOnboardingStatus()
return () => {
cancelled = true
}
}, [mounted, user, accessToken, refreshAuthToken])
const isLoggedIn = !!user
const userPresent = mounted && isLoggedIn
// NEW: detect admin role across common shapes (guarded by mount to avoid SSR/CSR mismatch)
const rawIsAdmin =
!!user &&
(
(user as any)?.role === 'admin' ||
(user as any)?.userType === 'admin' ||
(user as any)?.isAdmin === true ||
((user as any)?.roles?.includes?.('admin'))
)
const isAdmin = mounted && rawIsAdmin
const rawIsSuperAdmin =
!!user &&
(
(user as any)?.role === 'super_admin' ||
(user as any)?.userType === 'super_admin' ||
(user as any)?.isSuperAdmin === true ||
((user as any)?.roles?.includes?.('super_admin'))
)
const isSuperAdmin = mounted && rawIsSuperAdmin
const isAdminOrSuper = mounted && (rawIsAdmin || rawIsSuperAdmin)
// Only gate visibility by scroll on parallax-enabled pages
const headerVisible = parallaxEnabled ? animateIn && scrollY > 24 : animateIn
const parallaxOffset = parallaxEnabled ? Math.max(-16, -scrollY * 0.15) : 0
// When the fixed header becomes visible, expose its height as a CSS variable so
// pages (e.g. /register) can pad their content and avoid being overlapped.
useEffect(() => {
if (!mounted) return
let raf1 = 0
let raf2 = 0
const applySpacer = () => {
// Only reserve space when header is fixed/overlaying content.
if (!headerIsFixedOverlay) {
document.documentElement.style.setProperty('--pp-header-spacer', '0px')
return
}
const h = headerElRef.current?.getBoundingClientRect().height ?? 0
const spacer = headerVisible ? `${Math.ceil(h)}px` : '0px'
document.documentElement.style.setProperty('--pp-header-spacer', spacer)
}
const applyShiftFade = () => {
if (!headerVisible) {
document.documentElement.style.setProperty('--pp-page-shift-opacity', '1')
return
}
// Less noticeable dip to avoid "too translucent" look during the shift.
document.documentElement.style.setProperty('--pp-page-shift-opacity', '0.99')
raf1 = window.requestAnimationFrame(() => {
raf2 = window.requestAnimationFrame(() => {
document.documentElement.style.setProperty('--pp-page-shift-opacity', '1')
})
})
}
applySpacer()
applyShiftFade()
const onResize = () => applySpacer()
window.addEventListener('resize', onResize, { passive: true })
return () => {
window.removeEventListener('resize', onResize)
if (raf1) cancelAnimationFrame(raf1)
if (raf2) cancelAnimationFrame(raf2)
}
}, [mounted, headerVisible, headerIsFixedOverlay])
// Hard cleanup: if any scroll-lock left padding/overflow on <body>/<html>, remove it when the drawer is closed.
useEffect(() => {
if (mobileMenuOpen) return
const html = document.documentElement
const body = document.body
body.style.paddingRight = ''
html.style.paddingRight = ''
if (body.style.overflow === 'hidden') body.style.overflow = ''
if (html.style.overflow === 'hidden') html.style.overflow = ''
}, [mobileMenuOpen])
return (
<header
ref={headerElRef}
className={`${headerPositionClass} isolate z-30 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'
} ${
headerVisible ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-6 pointer-events-none'
} transition-all duration-500 ease-out`}
style={{
background: 'linear-gradient(135deg, #0F1D37 0%, #0A162A 50%, #081224 100%)',
...(parallaxEnabled ? { transform: `translateY(${parallaxOffset}px)` } : {}),
}}
>
<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>
{/* Mobile hamburger (only on small screens) */}
<div className="flex lg:hidden">
<button
type="button"
onClick={() => setMobileMenuOpen(open => !open)}
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">
{mobileMenuOpen ? 'Close main menu' : 'Open main menu'}
</span>
<span className="relative flex h-6 w-6 items-center justify-center">
<Bars3Icon
aria-hidden="true"
className={`size-6 transition-all duration-300 ease-out ${
mobileMenuOpen
? 'opacity-0 -rotate-90 scale-75'
: 'opacity-100 rotate-0 scale-100'
}`}
/>
<XMarkIcon
aria-hidden="true"
className={`pointer-events-none absolute size-6 transition-all duration-300 ease-out ${
mobileMenuOpen
? 'opacity-100 rotate-0 scale-100'
: 'opacity-0 rotate-90 scale-75'
}`}
/>
</span>
</button>
</div>
<PopoverGroup className="hidden lg:flex lg:gap-x-12">
{/* Navigation Links */}
{navLinks.map((link) => (
<button
key={link.href}
onClick={() => router.push(link.href)}
className="text-sm/6 font-semibold text-gray-900 dark:text-white hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
>
{link.name}
</button>
))}
{/* Conditional user-specific links (top navbar) */}
{userPresent && hasReferralPerm && (
<>
{/* Referral Management REMOVED from top nav */}
{DISPLAY_MATRIX && (
<button
onClick={() => router.push('/personal-matrix')}
className="text-sm/6 font-semibold text-gray-900 dark:text-white hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
>
Personal Matrix
</button>
)}
{DISPLAY_ABONEMENTS && (
<button
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"
>
Coffee Abonnements
</button>
)}
</>
)}
{/* Information dropdown already removed here */}
</PopoverGroup>
{/* CHANGED: remove profile icon/popover from header; keep login (when logged out) + hamburger */}
<div className="hidden lg:flex lg:flex-1 lg:justify-end lg:items-center lg:gap-x-3">
{!userPresent && mounted && (
<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">&rarr;</span>
</button>
)}
{/* Desktop hamburger (right side) */}
<button
type="button"
onClick={() => setMobileMenuOpen(open => !open)}
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"
>
<span className="sr-only">
{mobileMenuOpen ? 'Close main menu' : 'Open main menu'}
</span>
<span className="relative flex h-6 w-6 items-center justify-center">
<Bars3Icon
aria-hidden="true"
className={`size-6 transition-all duration-300 ease-out ${
mobileMenuOpen
? 'opacity-0 -rotate-90 scale-75'
: 'opacity-100 rotate-0 scale-100'
}`}
/>
<XMarkIcon
aria-hidden="true"
className={`pointer-events-none absolute size-6 transition-all duration-300 ease-out ${
mobileMenuOpen
? 'opacity-100 rotate-0 scale-100'
: 'opacity-0 rotate-90 scale-75'
}`}
/>
</span>
</button>
</div>
</nav>
{/* Side drawer menu: mobile + desktop */}
<Dialog open={mobileMenuOpen} onClose={setMobileMenuOpen}>
<Transition appear show={mobileMenuOpen}>
{/* Overlay: smoother, longer fade-out */}
<Transition.Child
enter="transition-opacity duration-400 ease-out"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-800 ease-out"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 z-40 bg-black/40 backdrop-blur-sm" />
</Transition.Child>
{/* Sliding panel: smoother, longer close animation */}
<Transition.Child
enter="transform transition-all duration-500 ease-out"
enterFrom="translate-x-full opacity-0 scale-95"
enterTo="translate-x-0 opacity-100 scale-100"
leave="transform transition-all duration-800 ease-in-out"
leaveFrom="translate-x-0 opacity-100 scale-100"
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 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">
{/* Header row */}
<div className="flex items-center justify-between px-1.5 pt-1.5 pb-2">
<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-100 hover:bg-white/10 transition-transform duration-300 hover:scale-110"
>
<span className="sr-only">Close menu</span>
<XMarkIcon aria-hidden="true" className="size-6" />
</button>
</div>
{/* 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">
{!mounted ? (
// ...existing skeleton...
<div className="py-6 px-3">
<div className="h-28 w-full rounded-lg bg-gray-200 dark:bg-gray-700 animate-pulse" />
</div>
) : user ? (
<>
{/* CHANGED: include profile icon INSIDE hamburger menu */}
<div className="pt-6 space-y-2">
<div className="flex items-center gap-3 border-b border-gray-200 dark:border-white/10 pb-4 mb-4 px-3">
<Avatar
src=""
initials={getUserInitials()}
className="size-10 bg-gradient-to-br from-indigo-500/40 to-indigo-600/60 text-white"
/>
<div className="min-w-0">
<div className="font-medium text-gray-900 dark:text-white truncate">
{user?.firstName && user?.lastName ? `${user.firstName} ${user.lastName}` : (user?.email || 'User')}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400 truncate">
{user?.email || 'user@example.com'}
</div>
</div>
</div>
{/* NEW: show Quick Action Dashboard link when user cannot access /dashboard yet */}
{!canSeeDashboard && (
<button
onClick={() => {
router.push('/quickaction-dashboard')
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"
>
Startup Dashboard
</button>
)}
{canSeeDashboard && (
<button
onClick={() => {
router.push('/dashboard')
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"
>
Dashboard
</button>
)}
<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>
</div>
{/* Main navigation (info + links + referral + ADMIN LAST) */}
<div className="space-y-4 py-6 px-1">
{/* Information disclosure */}
<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">
Information
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none group-data-open:rotate-180" />
</DisclosureButton>
<DisclosurePanel className="mt-2 space-y-1">
{informationItems.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>
{/* Navigation Links */}
{navLinks.map((link) => (
<button
key={link.href}
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>
))}
{/* Referral / matrix / abonnements */}
{hasReferralPerm && (
<>
<button
onClick={() => { console.log('🧭 Header Mobile: navigate to /referral-management'); router.push('/referral-management'); 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"
>
Referral Management
</button>
{DISPLAY_MATRIX && (
<button
onClick={() => { router.push('/personal-matrix'); 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"
>
Personal Matrix
</button>
)}
{DISPLAY_ABONEMENTS && (
<button
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"
>
Coffee Abonnements
</button>
)}
</>
)}
{/* Admin navigation LAST */}
{isAdmin && (
<div className="group mt-2 rounded-2xl border border-indigo-100 bg-white shadow-[0_12px_28px_rgba(15,23,42,0.12)] ring-1 ring-indigo-100/70 transition-transform transition-shadow duration-200 ease-out hover:-translate-y-0.5 hover:shadow-[0_16px_32px_rgba(15,23,42,0.18)] dark:border-indigo-500/20 dark:bg-gradient-to-br dark:from-slate-950/85 dark:via-slate-900/90 dark:to-indigo-950/80 dark:ring-white/10 dark:shadow-[0_18px_45px_rgba(0,0,0,0.45)] dark:hover:shadow-[0_22px_55px_rgba(0,0,0,0.6)]">
<div className="px-3 py-2.5 group-hover:animate-pulse">
<p className="mb-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-700 dark:text-indigo-100/80">
Admin Navigation
</p>
<div className="mb-2 h-px w-full bg-gradient-to-r from-transparent via-indigo-200/70 to-transparent opacity-80 transition-opacity group-hover:opacity-100 dark:via-indigo-200/40" />
<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-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
Dashboard
</button>
<button
onClick={() => { router.push('/admin/user-verify'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
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-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
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-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
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-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
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-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
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-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
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-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
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-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
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-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
News Management
</button>
)}
{/* ADDED: Dev Management in hamburger admin nav */}
{isAdminOrSuper && (
<button
onClick={() => { router.push('/admin/dev-management'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
Dev Management
</button>
)}
</div>
</div>
</div>
)}
</div>
</>
) : (
// logged-out drawer
<div className="py-6 space-y-4 px-1">
{/* Information disclosure */}
<Disclosure as="div">
<DisclosureButton className="group flex w-full items-center justify-between rounded-lg py-2 px-3 textbase/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5">
Information
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none group-data-open:rotate-180" />
</DisclosureButton>
<DisclosurePanel className="mt-2 space-y-1">
{informationItems.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>
{/* Navigation Links */}
{navLinks.map((link) => (
<button
key={link.href}
onClick={() => { router.push(link.href); setMobileMenuOpen(false); }}
className="block rounded-lg px-3 py-2 textbase/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 textbase/7 font-semibold text-gray-900 dark:text-white hover:bg-white/5 w-full text-left"
>
Log in
</button>
</div>
</div>
)}
</div>
</div>
{/* 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>
</Dialog>
</header>
)
}