"use client"; import { useState, useEffect } from "react"; import Header from "../components/Header"; import Footer from "../components/Footer"; import { NetworkBanner } from "../components/NetworkBanner"; import NavigationProgressBar from "../components/NavigationProgressBar"; import { Toaster, toast } from "sonner"; import { usePathname, useRouter } from 'next/navigation'; import dynamic from 'next/dynamic'; const CartDrawer = dynamic(() => import("../components/CartDrawer"), { ssr: false }); const LoginModal = dynamic(() => import("../components/LoginModal"), { ssr: false }); const AuthModal = dynamic(() => import("../components/AuthModal"), { ssr: false }); const B2BPortal = dynamic(() => import("../components/B2BPortal"), { ssr: false }); const MobileBottomNav = dynamic(() => import("../components/MobileBottomNav"), { ssr: false }); const PrescriptionUploadModal = dynamic(() => import("../components/PrescriptionUploadModal"), { ssr: false }); import { useUserStore } from "../lib/store/userStore"; import { useSettingsStore } from "../lib/store/settingsStore"; import { useUIStore } from "../lib/store/uiStore"; import MaintenancePage from "../components/MaintenancePage"; import { NavigationTarget } from "../lib/types"; // Module-level flag: survives across effect re-runs because it lives outside the component. // When the user presses Back/Forward, popstate fires BEFORE pathname changes. // The flag is read (and reset) in the pathname effect which runs AFTER. let _wasPopNavigation = false; export default function ClientLayout({ children }: { children: React.ReactNode }) { const isCartOpen = useUIStore((state) => state.isCartOpen); const isLoginModalOpen = useUIStore((state) => state.isLoginModalOpen); const isB2BPortalOpen = useUIStore((state) => state.isB2BPortalOpen); const advisorData = useUIStore((state) => state.advisorData); const setCartOpen = useUIStore((state) => state.setCartOpen); const setLoginModalOpen = useUIStore((state) => state.setLoginModalOpen); const setB2BPortalOpen = useUIStore((state) => state.setB2BPortalOpen); const clearAdvisorData = useUIStore((state) => state.clearAdvisorData); const [isPrescriptionModalOpen, setIsPrescriptionModalOpen] = useState(false); const pathname = usePathname(); const router = useRouter(); const fetchProfile = useUserStore((state) => state.fetchProfile); const role = useUserStore((state) => state.role); const isAuthModalOpen = useUserStore((state) => state.isAuthModalOpen); const setAuthModalOpen = useUserStore((state) => state.setAuthModalOpen); const fetchSettings = useSettingsStore((state) => state.fetchSettings); const getText = useSettingsStore((state) => state.getText); const isB2BEnabled = useSettingsStore((state) => state.getBoolean('b2bRegistrationOpen', true) && state.getBoolean('b2b_enabled', true)); useEffect(() => { fetchSettings(); const token = localStorage.getItem('accessToken'); if (token) { fetchProfile().catch(e => console.error("Auth init failed:", e)); } else { useUserStore.getState().logout(); } // Let the browser handle scroll restoration on Back/Forward navigation. // We separately implement push-navigation scroll-to-top below. if (typeof window !== 'undefined' && 'scrollRestoration' in window.history) { window.history.scrollRestoration = 'manual'; } // Permanent listener: set the flag whenever any popstate fires. // This includes modal sentinel pops - those don't change pathname so they // are harmless (the pathname effect won't run, flag stays set until the // next real back navigation that DOES change pathname). const handlePopState = () => { _wasPopNavigation = true; }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState); }, [fetchProfile, fetchSettings]); // Scroll-to-top only on PUSH navigation, not on Back/Forward. // _wasPopNavigation is set by the permanent popstate listener above // BEFORE this effect runs for the new pathname. useEffect(() => { if (typeof window === 'undefined') return; if (_wasPopNavigation) { // Back/Forward: restore the previously saved scroll position. _wasPopNavigation = false; const savedY = sessionStorage.getItem(`__scroll_${pathname}`); if (savedY) { const y = parseInt(savedY, 10); // Double rAF: first waits for React commit, second waits for paint requestAnimationFrame(() => requestAnimationFrame(() => window.scrollTo({ top: y, behavior: 'instant' }) ) ); } return; } // Push navigation (link click, router.push): scroll to top immediately. window.scrollTo({ top: 0, left: 0, behavior: 'instant' }); }, [pathname]); // Save current scroll position to sessionStorage (debounced + on cleanup) // so it can be restored on Back navigation. useEffect(() => { if (typeof window === 'undefined') return; const key = `__scroll_${pathname}`; let debounceTimer: ReturnType; const handleScroll = () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { sessionStorage.setItem(key, String(window.scrollY)); }, 100); }; window.addEventListener('scroll', handleScroll, { passive: true }); return () => { clearTimeout(debounceTimer); // Save immediately on exit (before pathname changes) sessionStorage.setItem(key, String(window.scrollY)); window.removeEventListener('scroll', handleScroll); }; }, [pathname]); // Check Maintenance Mode (Admin / Partner bypass) const isMaintenanceMode = getText('MAINTENANCE_MODE', 'false') === 'true' || getText('maintenance_mode', 'false') === 'true'; const isAdmin = role === 'User_Partner' || (typeof window !== 'undefined' && Boolean(localStorage.getItem('adminToken'))); if (isMaintenanceMode && !isAdmin) { return ; } // Derived currentView from pathname for Header let currentView = "home"; if (pathname?.includes("/shop")) currentView = "shop"; if (pathname?.includes("/wiki")) currentView = "wiki"; if (pathname?.includes("/profile")) currentView = "profile"; if (pathname?.includes("/checkout")) currentView = "checkout"; if (pathname?.includes("/dashboard")) currentView = "user-dashboard"; const handleNavigate = (v: NavigationTarget) => { if (typeof v === 'object' && v !== null) { if (v.view === 'profile') router.push('/profile'); if (v.view === 'order-tracking') router.push('/order/tracking'); if (v.view === 'home') router.push('/'); } else if (typeof v === 'string') { if (v === 'home') router.push('/'); else if (v === 'user-dashboard') router.push('/dashboard'); else router.push(`/${v}`); } }; const navigateToShop = (category: string = "all", search: string = "") => { const params = new URLSearchParams(); if (category && category !== "all") params.set("category", category); if (search) params.set("search", search); const newUrl = params.toString() ? `/shop?${params.toString()}` : "/shop"; router.push(newUrl); }; const handleSearch = (q: string) => { router.push(`/search?q=${encodeURIComponent(q)}`); }; return ( <>
setCartOpen(true)} onSearch={handleSearch} onB2BOpen={() => setB2BPortalOpen(true)} />
{children}