canina/frontend/application/app/ClientLayout.tsx
parsa aghaei 508356b8d3 feat: add Persian fonts, fix UI bugs, improve frontend quality
- 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
2026-07-11 19:48:43 +03:30

121 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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 } 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 />
</>
);
}