From e3198991a9396e5ac9235cc6124cca2dbac86595 Mon Sep 17 00:00:00 2001 From: DeathKaioken Date: Tue, 13 Jan 2026 17:23:01 +0100 Subject: [PATCH] refactor: NAV BARU --- src/app/components/nav/Header.tsx | 368 +++++++++++++++--------------- src/app/login/page.tsx | 55 +++-- 2 files changed, 218 insertions(+), 205 deletions(-) diff --git a/src/app/components/nav/Header.tsx b/src/app/components/nav/Header.tsx index 8fb6c37..72dc6af 100644 --- a/src/app/components/nav/Header.tsx +++ b/src/app/components/nav/Header.tsx @@ -1,8 +1,8 @@ 'use client' import { useState, useEffect, useCallback, useRef } from 'react' -import { useRouter } from 'next/navigation'; -import Image from 'next/image'; +import { useRouter } from 'next/navigation' +import Image from 'next/image' import { Dialog, DialogPanel, @@ -13,18 +13,16 @@ import { PopoverButton, PopoverGroup, PopoverPanel, - Transition + Transition, } from '@headlessui/react' import { Bars3Icon, UserCircleIcon, XMarkIcon, ArrowRightOnRectangleIcon, - MoonIcon, - SunIcon } from '@heroicons/react/24/outline' import { ChevronDownIcon } from '@heroicons/react/20/solid' -import useAuthStore from '../../store/authStore'; +import useAuthStore from '../../store/authStore' import { Avatar } from '../avatar' // 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_ABOUT_US = process.env.NEXT_PUBLIC_DISPLAY_ABOUT_US !== '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' @@ -57,26 +55,28 @@ const showShop = false export default function Header() { const [mobileMenuOpen, setMobileMenuOpen] = useState(false) - const [isDark, setIsDark] = useState(false) - const [mounted, setMounted] = useState(false) // added - 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 [mounted, setMounted] = useState(false) + const [animateIn, setAnimateIn] = useState(false) + const [loggingOut, setLoggingOut] = useState(false) // NEW + 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() - // NEW: permission flag const [hasReferralPerm, setHasReferralPerm] = useState(false) - // NEW: admin management dropdown state const [adminMgmtOpen, setAdminMgmtOpen] = useState(false) const managementRef = useRef(null) const handleLogout = async () => { try { - await logout(); - router.push('/login'); + setLoggingOut(true) // NEW: start logout transition + await logout() + setMobileMenuOpen(false) + router.push('/login') } 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'; }; - // Theme initialization & persistence + // Initial theme (dark/light) + mark mounted + start header animation useEffect(() => { 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') - setIsDark(true) } else { document.documentElement.classList.remove('dark') - setIsDark(false) } - setMounted(true) // hydration complete - }, []) - 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 - }) + setMounted(true) + setAnimateIn(true) }, []) // Fetch user permissions and set hasReferralPerm @@ -213,21 +202,18 @@ export default function Header() { ((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
; - } - return (
-
- ))} - - - - + {/* Information dropdown already removed here */} +
- {/* Stable auth slot to avoid SSR/CSR structural drift */} + {/* Auth slot */}
{userPresent ? ( @@ -382,13 +331,7 @@ export default function Header() { Profile - + {/* Logout removed from profile dropdown; still available in hamburger menu bottom */}
@@ -403,26 +346,16 @@ export default function Header() { - {/* Replace LanguageSwitcher with English-only dropdown + note */} - - - English - - - We are currently working on implementing other languages. - - + {/* Desktop hamburger (right side, next to login/profile) */}
@@ -495,7 +428,7 @@ export default function Header() { Contract Management - {DISPLAY_ABONEMMENTS && ( + {DISPLAY_ABONEMENTS && ( <> -
+ + {/* Scrollable content */} +
{!mounted ? ( + // ...existing skeleton...
) : user ? ( <> - {/* User info now FIRST under logo */} + {/* User info + basic nav */}
@@ -633,35 +568,10 @@ export default function Header() { > Profile -
- {/* Theme + Language now AFTER user info */} -
- - {/* Replace LanguageSwitcher with English-only dropdown + note for mobile */} - - - Language: English - - - We are currently working on implementing other languages. - - -
- {/* Navigation after that */} -
+ + {/* Main navigation (info + links + referral + ADMIN LAST) */} +
{/* Information disclosure */} @@ -681,6 +591,7 @@ export default function Header() { ))} + {/* Navigation Links */} {navLinks.map((link) => ( + + + {DISPLAY_MATRIX && ( + + )} + + {DISPLAY_ABONEMENTS && ( + <> + + + + )} + {DISPLAY_POOLS && ( + + )} + + {DISPLAY_NEWS && ( + + )} +
+
+
+ )}
) : ( -
+ // logged-out drawer +
{/* Information disclosure */} @@ -751,18 +748,6 @@ export default function Header() { {link.name} ))} - {/* Language (English-only) in logged-out mobile */} -
- - - Language: English - - - We are currently working on implementing other languages. - - -
- + + {/* Sticky bottom logout button with pulsating hover */} + {user && ( +
+ +
+ )} + + + {/* Logout transition overlay */} + {loggingOut && ( +
+
+
+

Logging you out...

+
+
+ )} ) } \ No newline at end of file diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 1e677c0..9b94e95 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -7,6 +7,7 @@ import PageLayout from '../components/PageLayout' import useAuthStore from '../store/authStore' import GlobalAnimatedBackground from '../background/GlobalAnimatedBackground' import { ToastProvider } from '../components/toast/toastComponent' +import PageTransitionEffect from '../components/animation/pageTransitionEffect' export default function LoginPage() { 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) if (hasHydrated && user) { return ( - - -
-
-
-

You are already logged in. Redirecting...

+ + + +
+
+
+

You are already logged in. Redirecting...

+
-
- - + + + ) } return ( - - -
-
-
- + + + +
+
+
+ +
-
- - + + + ) } \ No newline at end of file