- Add local Persian fonts: Vazirmatn, Sahel, Shabnam, Lalezar, IranNastaliq - Configure @font-face in globals.css with all font weights - Set Vazirmatn as primary font, Lalezar for headings, Sahel for alt text - Fix Hero image loading with crossOrigin and error fallback - Fix SmartAdvisor disabled button contrast (WCAG compliance) - Add visible labels for form inputs in SmartAdvisor step 1 - Fix UserDashboard null safety for walletBalance - Fix announcement bar hydration mismatch (loading state guard) - Fix UserDashboard null checks for profile fields - Improve overall frontend robustness and data-loading guards
121 lines
4.4 KiB
TypeScript
121 lines
4.4 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import Header from "../components/Header";
|
||
import Footer from "../components/Footer";
|
||
import { NetworkBanner } from "../components/NetworkBanner";
|
||
import { Toaster } from "sonner";
|
||
import { usePathname, useRouter } from 'next/navigation';
|
||
|
||
import CartDrawer from "../components/CartDrawer";
|
||
import LoginModal from "../components/LoginModal";
|
||
import B2BPortal from "../components/B2BPortal";
|
||
import { useUserStore } from "../lib/store/userStore";
|
||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||
import { useUIStore } from "../lib/store/uiStore";
|
||
|
||
export default function ClientLayout({ children }: { children: React.ReactNode }) {
|
||
const {
|
||
isCartOpen, isLoginModalOpen, isB2BPortalOpen, advisorData,
|
||
setCartOpen, setLoginModalOpen, setB2BPortalOpen, clearAdvisorData
|
||
} = useUIStore();
|
||
|
||
const pathname = usePathname();
|
||
const router = useRouter();
|
||
|
||
const { fetchProfile } = useUserStore();
|
||
const fetchSettings = useSettingsStore(state => state.fetchSettings);
|
||
const isInitialized = useSettingsStore(state => state.isInitialized);
|
||
|
||
useEffect(() => {
|
||
fetchSettings();
|
||
const token = localStorage.getItem('accessToken');
|
||
if (token) {
|
||
fetchProfile().catch(e => console.error("Auth init failed:", e));
|
||
}
|
||
}, [fetchProfile, fetchSettings]);
|
||
|
||
if (!isInitialized) {
|
||
return (
|
||
<div className="min-h-screen bg-medical-gray-50 flex flex-col items-center justify-center font-sans" dir="rtl">
|
||
<div className="flex flex-col items-center gap-4 text-center">
|
||
<div className="w-12 h-12 border-4 border-canina-blue border-t-transparent rounded-full animate-spin" />
|
||
<h2 className="text-xl font-black text-medical-gray-900 font-lalezar tracking-wide">در حال بارگذاری اطلاعات سایت...</h2>
|
||
<p className="text-sm text-medical-gray-500 font-shabnam">لطفاً شکیبا باشید</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 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: any) => {
|
||
if (typeof v === 'object') {
|
||
if (v.view === 'profile') router.push('/profile');
|
||
if (v.view === 'order-tracking') router.push('/order/tracking');
|
||
if (v.view === 'home') router.push('/');
|
||
} else {
|
||
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 (
|
||
<>
|
||
<NetworkBanner />
|
||
<Header
|
||
onNavigate={handleNavigate}
|
||
onShopNavigate={navigateToShop}
|
||
currentView={currentView}
|
||
onCartOpen={() => setCartOpen(true)}
|
||
onSearch={handleSearch}
|
||
onB2BOpen={() => setB2BPortalOpen(true)}
|
||
/>
|
||
<main className="min-h-screen">
|
||
{children}
|
||
</main>
|
||
<Footer onNavigate={handleNavigate} onShopNavigate={navigateToShop} onB2BOpen={() => setB2BPortalOpen(true)} />
|
||
|
||
{isB2BPortalOpen && <B2BPortal onClose={() => setB2BPortalOpen(false)} />}
|
||
<CartDrawer
|
||
isOpen={isCartOpen}
|
||
onClose={() => setCartOpen(false)}
|
||
onCheckout={() => { setCartOpen(false); router.push('/checkout'); }}
|
||
onShopNavigate={navigateToShop}
|
||
/>
|
||
<LoginModal
|
||
isOpen={isLoginModalOpen}
|
||
onClose={() => {
|
||
setLoginModalOpen(false);
|
||
clearAdvisorData();
|
||
}}
|
||
onLogin={() => {
|
||
setLoginModalOpen(false);
|
||
}}
|
||
petName={advisorData?.name}
|
||
isAdvisorContext={!!advisorData}
|
||
/>
|
||
<Toaster position="top-center" expand={true} richColors />
|
||
</>
|
||
);
|
||
}
|