- Configure project metadata and dependencies - Setup Vite with React and TypeScript - Add environment configuration and .gitignore - Implement initial application entry point and assets
420 lines
20 KiB
TypeScript
420 lines
20 KiB
TypeScript
/**
|
||
* @license
|
||
* SPDX-License-Identifier: Apache-2.0
|
||
*/
|
||
|
||
import { useState, useEffect, lazy, Suspense } from "react";
|
||
import Header from "./components/Header";
|
||
import Hero from "./components/Hero";
|
||
import Footer from "./components/Footer";
|
||
import { Toaster } from "sonner";
|
||
import { Product } from "./data/products";
|
||
import { useCartStore } from "./store/cartStore";
|
||
import { usePetStore } from "./store/usePetStore";
|
||
import { NetworkBanner } from './components/NetworkBanner';
|
||
import { ErrorBoundary } from './components/ErrorBoundary';
|
||
import { NotFoundPage } from './components/ErrorPages';
|
||
import { toast } from "sonner";
|
||
import labImage from "./assets/images/regenerated_image_1779109861747.png";
|
||
|
||
// Lazy Loaded Components
|
||
const ArchivePage = lazy(() => import("./components/ArchivePage"));
|
||
const ProductPage = lazy(() => import("./components/ProductPage"));
|
||
const PetProfile = lazy(() => import("./components/PetProfile"));
|
||
const UserDashboard = lazy(() => import("./components/UserDashboard"));
|
||
const SearchResultsPage = lazy(() => import("./components/SearchResultsPage"));
|
||
const IngredientWiki = lazy(() => import("./components/IngredientWiki"));
|
||
const BlogPage = lazy(() => import("./components/BlogPage"));
|
||
const VideosPage = lazy(() => import("./components/VideosPage"));
|
||
const CheckoutPage = lazy(() => import("./components/CheckoutPage"));
|
||
const OrderSuccess = lazy(() => import("./components/OrderSuccess"));
|
||
const OrderTracking = lazy(() => import("./components/OrderTracking"));
|
||
const SmartAdvisor = lazy(() => import("./components/SmartAdvisor"));
|
||
const LoginModal = lazy(() => import("./components/LoginModal"));
|
||
const CartDrawer = lazy(() => import("./components/CartDrawer"));
|
||
const B2BPortal = lazy(() => import("./components/B2BPortal"));
|
||
const FeaturedProducts = lazy(() => import("./components/FeaturedProducts"));
|
||
const VetGallery = lazy(() => import("./components/VetGallery"));
|
||
|
||
type View = "home" | "shop" | "wiki" | "product-detail" | "profile" | "checkout" | "search" | "order-success" | "order-tracking" | "blog" | "videos" | "user-dashboard";
|
||
|
||
function LoadingFallback() {
|
||
return (
|
||
<div className="h-[60vh] flex flex-col items-center justify-center bg-medical-gray-50/50 backdrop-blur-sm">
|
||
<div className="w-16 h-16 border-4 border-canina-blue/10 border-t-canina-blue rounded-full animate-spin mb-4" />
|
||
<span className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest animate-pulse font-vazir">در حال آمادهسازی لابراتوار...</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function App() {
|
||
const [currentView, setCurrentView] = useState<View>("home");
|
||
const [subView, setSubView] = useState<any>(null);
|
||
const [advisorRecommendation, setAdvisorRecommendation] = useState<string | null>(null);
|
||
const [advisorData, setAdvisorData] = useState<any>(null);
|
||
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
|
||
const [lastOrderId, setLastOrderId] = useState<string | null>(null);
|
||
const [initialCategory, setInitialCategory] = useState("all");
|
||
const [initialSearch, setInitialSearch] = useState("");
|
||
const [isCartOpen, setIsCartOpen] = useState(false);
|
||
const [showB2B, setShowB2B] = useState(false);
|
||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||
const [showLoginModal, setShowLoginModal] = useState(false);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const [searchQuery, setSearchQuery] = useState("");
|
||
|
||
useEffect(() => {
|
||
const init = async () => {
|
||
// Simulating initial data load
|
||
await new Promise(resolve => setTimeout(resolve, 800));
|
||
setIsLoading(false);
|
||
};
|
||
init();
|
||
}, []);
|
||
useEffect(() => {
|
||
const handlePopState = (event: PopStateEvent) => {
|
||
const params = new URLSearchParams(window.location.search);
|
||
const category = params.get("category") || "all";
|
||
const search = params.get("search") || "";
|
||
|
||
setInitialCategory(category);
|
||
setInitialSearch(search);
|
||
setCurrentView("shop");
|
||
};
|
||
|
||
window.addEventListener("popstate", handlePopState);
|
||
return () => window.removeEventListener("popstate", handlePopState);
|
||
}, []);
|
||
|
||
const { clearCart, addOrder, getTotal, items } = useCartStore();
|
||
const { getActivePet, pets } = usePetStore();
|
||
|
||
// Global Scroll To Top on view change
|
||
useEffect(() => {
|
||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||
}, [currentView]);
|
||
|
||
const handleProductClick = (product: Product) => {
|
||
setSelectedProduct(product);
|
||
setCurrentView("product-detail");
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const navigateToShop = (category: string = "all", search: string = "") => {
|
||
// Update URL with search params
|
||
const params = new URLSearchParams();
|
||
if (category && category !== "all") params.set("category", category);
|
||
if (search) params.set("search", search);
|
||
|
||
const newUrl = params.toString() ? `?${params.toString()}` : "/shop";
|
||
window.history.pushState({ category, search }, "", newUrl);
|
||
|
||
setInitialCategory(category);
|
||
setInitialSearch(search);
|
||
setCurrentView("shop");
|
||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||
};
|
||
|
||
const navigateToWiki = () => {
|
||
setCurrentView("wiki");
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const handleNavigate = (v: any) => {
|
||
if (typeof v === 'object') {
|
||
setCurrentView(v.view);
|
||
setSubView(v.subview || null);
|
||
} else {
|
||
setCurrentView(v);
|
||
setSubView(null);
|
||
}
|
||
};
|
||
|
||
const navigateToProfile = (subview: any = "index") => {
|
||
setCurrentView("profile");
|
||
setSubView(subview);
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const navigateToDashboard = () => {
|
||
setCurrentView("user-dashboard");
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const handleCheckout = () => {
|
||
setIsCartOpen(false);
|
||
setCurrentView("checkout");
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const handleSearch = (q: string) => {
|
||
setSearchQuery(q);
|
||
setCurrentView("search");
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const finalizeOrder = () => {
|
||
const activePet = getActivePet();
|
||
const orderId = addOrder({
|
||
items,
|
||
total: getTotal(),
|
||
petId: activePet?.id
|
||
});
|
||
setLastOrderId(orderId);
|
||
clearCart();
|
||
setCurrentView("order-success");
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const handleAdvisorComplete = (data: any) => {
|
||
setAdvisorData(data);
|
||
|
||
if (!isLoggedIn) {
|
||
setShowLoginModal(true);
|
||
} else {
|
||
processAdvisorResult(data);
|
||
}
|
||
};
|
||
|
||
const processAdvisorResult = (data?: any) => {
|
||
const d = data || advisorData;
|
||
if (!d) return;
|
||
|
||
// Map medical conditions to a broad recommendation category for the suggested items logic in PetProfile
|
||
if (d.medicalConditions.includes("جراحی مفاصل")) setAdvisorRecommendation("joints");
|
||
else if (d.medicalConditions.includes("مشکلات گوارشی")) setAdvisorRecommendation("digestion");
|
||
else if (d.medicalConditions.includes("ریزش موی شدید")) setAdvisorRecommendation("skin");
|
||
else if (d.medicalConditions.includes("بیاشتهایی") || d.medicalConditions.includes("زایمان اخیر") || d.medicalConditions.includes("بارداری")) setAdvisorRecommendation("vitamins");
|
||
|
||
// Clear recommended items and add the pet to the store
|
||
const { addPet } = usePetStore.getState();
|
||
addPet(d);
|
||
|
||
toast.success(`تحلیل سلامت ${d.name} آماده است! شناسنامه صادر شد.`);
|
||
navigateToProfile("detail");
|
||
setAdvisorData(null);
|
||
};
|
||
|
||
const handleLogin = () => {
|
||
setIsLoggedIn(true);
|
||
setShowLoginModal(false);
|
||
|
||
if (advisorData) {
|
||
toast.success(`خوش آمدید! مشخصات ${advisorData.name} در شناسنامه ثبت شد.`);
|
||
processAdvisorResult();
|
||
} else {
|
||
toast.success("خوش آمدید!");
|
||
}
|
||
};
|
||
|
||
const renderView = () => {
|
||
if (isLoading) {
|
||
return (
|
||
<div className="h-screen flex flex-col items-center justify-center bg-medical-gray-50">
|
||
<div className="w-20 h-20 border-4 border-canina-blue/20 border-t-canina-blue rounded-full animate-spin mb-6" />
|
||
<div className="flex items-center gap-2">
|
||
<div className="w-10 h-10 bg-canina-blue rounded-2xl flex items-center justify-center text-white font-bold text-xl shadow-xl shadow-canina-blue/20 italic">C</div>
|
||
<span className="text-[12px] font-black text-medical-gray-900 uppercase tracking-widest animate-pulse">Canina Pharma Laboratory</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Suspense fallback={<LoadingFallback />}>
|
||
{(() => {
|
||
switch (currentView) {
|
||
case "user-dashboard":
|
||
return <UserDashboard onBack={() => setCurrentView("home")} onNavigate={handleNavigate} />;
|
||
case "search":
|
||
return (
|
||
<SearchResultsPage
|
||
query={searchQuery}
|
||
onProductClick={handleProductClick}
|
||
onBack={() => setCurrentView("home")}
|
||
/>
|
||
);
|
||
case "checkout":
|
||
return <CheckoutPage onBack={() => setIsCartOpen(true)} onComplete={finalizeOrder} />;
|
||
case "product-detail":
|
||
return selectedProduct ? (
|
||
<ProductPage
|
||
product={selectedProduct}
|
||
onBack={() => setCurrentView("shop")}
|
||
onProductClick={handleProductClick}
|
||
onWikiNavigate={() => setCurrentView("wiki")}
|
||
onShopNavigate={navigateToShop}
|
||
/>
|
||
) : null;
|
||
case "shop":
|
||
return <ArchivePage onProductClick={handleProductClick} initialCategory={initialCategory} initialSearch={initialSearch} onBack={() => setCurrentView("home")} onShopNavigate={navigateToShop} />;
|
||
case "home":
|
||
return (
|
||
<>
|
||
<Hero onProfileClick={() => navigateToProfile("detail")} onShopNavigate={() => navigateToShop()} />
|
||
<SmartAdvisor onComplete={handleAdvisorComplete} />
|
||
<FeaturedProducts onProductClick={handleProductClick} onShopNavigate={() => navigateToShop()} />
|
||
<VetGallery onNavigate={handleNavigate} />
|
||
|
||
{/* Navigation to Sections */}
|
||
<section className="py-20 bg-medical-gray-50 border-y border-medical-gray-100">
|
||
<div className="max-w-7xl mx-auto px-4 grid md:grid-cols-2 gap-8">
|
||
<div
|
||
onClick={() => navigateToShop()}
|
||
className="bg-white p-12 rounded-[3.5rem] border border-medical-gray-200 shadow-sm hover:shadow-2xl hover:-translate-y-2 transition-all cursor-pointer group"
|
||
>
|
||
<h3 className="text-3xl font-black text-medical-gray-900 mb-4 group-hover:text-canina-blue transition-colors italic">فروشگاه تخصصی</h3>
|
||
<p className="text-medical-gray-500 mb-8 leading-relaxed">دسترسی به تمامی محصولات بر اساس علائم بالینی و نیازهای پت شما.</p>
|
||
<div className="flex items-center gap-2 text-canina-blue font-black tracking-widest uppercase text-xs">
|
||
کاتالوگ کامل محصولات
|
||
<span className="text-lg">←</span>
|
||
</div>
|
||
</div>
|
||
<div
|
||
onClick={navigateToWiki}
|
||
className="bg-medical-gray-900 p-12 rounded-[3.5rem] text-white hover:shadow-2xl hover:-translate-y-2 transition-all cursor-pointer group"
|
||
>
|
||
<h3 className="text-3xl font-black mb-4 group-hover:text-canina-blue transition-colors italic">دانشنامه علمی</h3>
|
||
<p className="text-white/60 mb-8 leading-relaxed">آشنایی با ترکیبات نایاب و استانداردهای دارویی محصولات آلمانی کانینا.</p>
|
||
<div className="flex items-center gap-2 text-canina-blue font-black tracking-widest uppercase text-xs">
|
||
مطالعه مقالات تخصصی
|
||
<span className="text-lg">←</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* About Section Teaser */}
|
||
<section className="py-24 bg-white overflow-hidden">
|
||
<div className="max-w-7xl mx-auto px-4 grid lg:grid-cols-2 gap-16 items-center">
|
||
<div className="relative">
|
||
<div className="absolute -top-10 -right-10 w-40 h-40 bg-canina-blue/5 rounded-full blur-3xl" />
|
||
<img
|
||
src={labImage}
|
||
alt="Laboratory"
|
||
className="rounded-[3rem] shadow-2xl relative z-10 grayscale-[10%]"
|
||
/>
|
||
<div className="absolute -bottom-6 -left-6 bg-canina-blue text-white p-8 rounded-3xl shadow-xl z-20">
|
||
<div className="text-4xl font-black mb-1 italic">کیفیت</div>
|
||
<div className="text-xs uppercase tracking-widest font-bold opacity-80">استانداردهای فوقدارویی</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div className="text-canina-blue text-xs font-bold uppercase tracking-[0.2em] mb-4">میراث ما از آلمان</div>
|
||
<h2 className="text-3xl lg:text-5xl font-black text-medical-gray-900 leading-[1.2] mb-8">
|
||
چرا برند آلمانی <span className="text-canina-blue italic">Canina</span> مرجع دامپزشکان است؟
|
||
</h2>
|
||
<div className="space-y-6">
|
||
{[
|
||
{ title: "مواد اولیه نایاب", desc: "استفاده از پودر صدف لبسبز اصل نیوزیلند و مواد ارگانیک با گرید دارویی." },
|
||
{ title: "فاقد مواد نگهدارنده", desc: "تمامی محصولات ۱۰۰٪ طبیعی و فاقد رنگهای مصنوعی و طعمدهندههای شیمیایی هستند." },
|
||
{ title: "تاییدیه اروپا", desc: "مطابق با سختگیرانهترین استانداردهای ایمنی مواد غذایی و دارویی در اتحادیه اروپا." }
|
||
].map((item, idx) => (
|
||
<div key={idx} className="flex gap-4">
|
||
<div className="w-12 h-12 rounded-2xl bg-medical-gray-50 flex items-center justify-center flex-shrink-0 text-canina-blue font-bold">
|
||
{idx + 1}
|
||
</div>
|
||
<div>
|
||
<h4 className="text-lg font-bold text-medical-gray-900 mb-1">{item.title}</h4>
|
||
<p className="text-sm text-medical-gray-500 leading-relaxed font-medium">{item.desc}</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</>
|
||
);
|
||
case "wiki":
|
||
return <IngredientWiki onProductClick={handleProductClick} onBack={() => setCurrentView("home")} />;
|
||
case "profile":
|
||
return <PetProfile onProductClick={handleProductClick} onBack={() => setCurrentView("home")} initialView={subView} advisorNeed={advisorRecommendation} />;
|
||
case "order-success":
|
||
return lastOrderId ? <OrderSuccess orderId={lastOrderId} onNavigate={handleNavigate} /> : null;
|
||
case "order-tracking":
|
||
return <OrderTracking onNavigate={handleNavigate} onBack={() => setCurrentView("home")} />;
|
||
case "blog":
|
||
return <BlogPage onBack={() => setCurrentView("home")} onProductClick={handleProductClick} />;
|
||
case "videos":
|
||
return <VideosPage onBack={() => setCurrentView("home")} />;
|
||
default:
|
||
return <NotFoundPage onGoHome={() => setCurrentView("home")} />;
|
||
}
|
||
})()}
|
||
</Suspense>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<ErrorBoundary>
|
||
<div className="min-h-screen bg-medical-gray-50 font-sans" dir="rtl">
|
||
<NetworkBanner />
|
||
<Header
|
||
onNavigate={handleNavigate}
|
||
onShopNavigate={navigateToShop}
|
||
currentView={currentView}
|
||
onCartOpen={() => setIsCartOpen(true)}
|
||
onSearch={handleSearch}
|
||
onB2BOpen={() => setShowB2B(true)}
|
||
/>
|
||
<main>
|
||
{renderView()}
|
||
|
||
{/* Call to Action */}
|
||
<section className="bg-canina-blue py-20 relative overflow-hidden">
|
||
<div className="absolute inset-0 opacity-10">
|
||
<div className="grid grid-cols-10 h-full w-full">
|
||
{Array.from({length: 100}).map((_, i) => (
|
||
<div key={i} className="border border-white/20" />
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div className="max-w-4xl mx-auto px-4 text-center relative z-10">
|
||
<h2 className="text-3xl lg:text-5xl font-black text-white mb-8 leading-tight">
|
||
میخواهید بدانید کدام محصول برای پت شما مناسبتر است؟
|
||
</h2>
|
||
<p className="text-white/80 text-lg mb-10 leading-relaxed">
|
||
تیم متخصص دامپزشکی کانینا ایران آماده پاسخگویی به سوالات شماست.
|
||
</p>
|
||
<div className="flex flex-wrap justify-center gap-4">
|
||
<button className="bg-white text-canina-blue px-12 py-5 rounded-full font-black text-xl hover:scale-105 transition-transform shadow-2xl shadow-black/20">
|
||
دریافت رژیم مکمل رایگان
|
||
</button>
|
||
<button
|
||
onClick={() => navigateToShop()}
|
||
className="bg-canina-blue/20 backdrop-blur-md border-2 border-white text-white px-12 py-5 rounded-full font-black text-xl hover:bg-white hover:text-canina-blue transition-all"
|
||
>
|
||
ورود به محصولات تخصصی
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<Footer onNavigate={handleNavigate} onShopNavigate={navigateToShop} onB2BOpen={() => setShowB2B(true)} />
|
||
<Suspense fallback={null}>
|
||
{showB2B && <B2BPortal onClose={() => setShowB2B(false)} />}
|
||
<CartDrawer
|
||
isOpen={isCartOpen}
|
||
onClose={() => setIsCartOpen(false)}
|
||
onCheckout={handleCheckout}
|
||
onShopNavigate={navigateToShop}
|
||
/>
|
||
<LoginModal
|
||
isOpen={showLoginModal}
|
||
onClose={() => {
|
||
setShowLoginModal(false);
|
||
setAdvisorData(null); // Clear advisor data if closed without login
|
||
}}
|
||
onLogin={handleLogin}
|
||
petName={advisorData?.name}
|
||
isAdvisorContext={!!advisorData}
|
||
/>
|
||
</Suspense>
|
||
<Toaster position="top-center" expand={true} richColors />
|
||
</div>
|
||
</ErrorBoundary>
|
||
);
|
||
}
|