- Fix seed-products.ts TS error (implicit any) and BOM handling - Re-run full seed to restore correct Persian encoding in DB - Fix productService query→search param mismatch - Fix IngredientWiki hardcoded port 4000→use settingsStore - Restore seed-products-data.json from git after accidental corruption
637 lines
33 KiB
TypeScript
637 lines
33 KiB
TypeScript
"use client";
|
||
import { useState, useMemo, useEffect } from "react";
|
||
import { motion, AnimatePresence } from "motion/react";
|
||
import {
|
||
ArrowRight,
|
||
Calculator as CalcIcon,
|
||
ChevronLeft,
|
||
ShieldCheck,
|
||
CheckCircle2,
|
||
Clock,
|
||
FlaskConical,
|
||
Stethoscope,
|
||
Info,
|
||
CalendarDays,
|
||
Dog,
|
||
Gamepad2,
|
||
X,
|
||
Plus,
|
||
Minus,
|
||
Bell,
|
||
Sparkles,
|
||
ChevronRight,
|
||
ChevronDown,
|
||
Activity,
|
||
Heart,
|
||
Zap,
|
||
Flame,
|
||
Star,
|
||
Users,
|
||
ShoppingBag
|
||
} from "lucide-react";
|
||
|
||
const ICON_MAP: Record<string, any> = {
|
||
Sparkles,
|
||
Activity,
|
||
ShieldCheck,
|
||
Heart,
|
||
Zap,
|
||
Flame,
|
||
Star,
|
||
Users
|
||
};
|
||
import { toPersian, cn } from "../lib/utils";
|
||
import { Product, PRODUCTS } from "../lib/data/products";
|
||
import { productService } from "../lib/services/productService";
|
||
import { SCIENTIFIC_TERMS } from "../lib/data/scientificTerms";
|
||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||
import { create } from "zustand";
|
||
import { useCartStore } from "../lib/store/cartStore";
|
||
import { usePetStore } from "../lib/store/usePetStore";
|
||
import { toast } from "sonner";
|
||
|
||
interface CalculatorState {
|
||
petType: "young" | "adult";
|
||
weight: number;
|
||
setPetType: (type: "young" | "adult") => void;
|
||
setWeight: (weight: number) => void;
|
||
}
|
||
|
||
const useCalculatorStore = create<CalculatorState>((set) => ({
|
||
petType: "young",
|
||
weight: 10,
|
||
setPetType: (petType) => set({ petType }),
|
||
setWeight: (weight) => set({ weight }),
|
||
}));
|
||
|
||
import Tooltip from "./Tooltip";
|
||
import SafeImage from "./SafeImage";
|
||
|
||
import { useRouter } from 'next/navigation';
|
||
|
||
export default function ProductPage({ productSlug }: { productSlug: string }) {
|
||
const router = useRouter();
|
||
const [product, setProduct] = useState<Product | null>(null);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const [activeTab, setActiveTab] = useState<"specs" | "feeding" | "notes">("specs");
|
||
const [showRefillModal, setShowRefillModal] = useState(false);
|
||
const [itemQuantity, setItemQuantity] = useState(1);
|
||
const [allProducts, setAllProducts] = useState<Product[]>([]);
|
||
|
||
useEffect(() => {
|
||
productService.getProducts()
|
||
.then((products) => {
|
||
setAllProducts(products);
|
||
const found = products.find(p => p.slug === productSlug || p.id === productSlug);
|
||
setProduct(found || null);
|
||
})
|
||
.catch(err => console.error("Error fetching products in ProductPage:", err))
|
||
.finally(() => setIsLoading(false));
|
||
}, [productSlug]);
|
||
const { pets, getActivePet } = usePetStore();
|
||
const activePet = getActivePet();
|
||
const { petType, weight, setPetType, setWeight } = useCalculatorStore();
|
||
const { addItem } = useCartStore();
|
||
const scientificTerms = useSettingsStore(state => state.scientificTerms);
|
||
const termKeys = useMemo(() => {
|
||
return Array.from(new Set([
|
||
...Object.keys(scientificTerms),
|
||
...Object.keys(SCIENTIFIC_TERMS)
|
||
]));
|
||
}, [scientificTerms]);
|
||
|
||
// Re-hydrate product to ensure methods like calculateDosage exist
|
||
const fullProduct = useMemo(() => {
|
||
if (!product) return null;
|
||
const apiProduct = allProducts.find(p => p.id === product.id) || product;
|
||
if (!apiProduct) return null;
|
||
const staticProduct = PRODUCTS.find(p => p.id === apiProduct.id || p.artNo === apiProduct.artNo);
|
||
if (staticProduct) {
|
||
return {
|
||
...apiProduct,
|
||
calculateDosage: staticProduct.calculateDosage
|
||
};
|
||
}
|
||
if (typeof apiProduct.calculateDosage !== 'function') {
|
||
return {
|
||
...apiProduct,
|
||
calculateDosage: (w: number, y: boolean) => {
|
||
return {
|
||
quantity: 1,
|
||
unit: apiProduct.unit || "قرص",
|
||
description: "مصرف روزانه بر اساس دستور پزشک"
|
||
};
|
||
}
|
||
};
|
||
}
|
||
return apiProduct;
|
||
}, [product, allProducts]);
|
||
|
||
useEffect(() => {
|
||
if (activePet) {
|
||
setWeight(activePet.weight);
|
||
setPetType(activePet.age <= 1 ? "young" : "adult");
|
||
}
|
||
}, [activePet, setWeight, setPetType]);
|
||
|
||
const calculation = useMemo(() => {
|
||
if (!fullProduct || typeof fullProduct.calculateDosage !== 'function') return null;
|
||
const result = fullProduct.calculateDosage(weight, petType === "young");
|
||
const duration = Math.floor(fullProduct.packageSize / result.quantity);
|
||
|
||
return {
|
||
dailyDose: result.quantity,
|
||
duration: duration || 0,
|
||
unit: result.unit,
|
||
description: result.description
|
||
};
|
||
}, [fullProduct, petType, weight]);
|
||
|
||
// Suggest quantity inside useEffect to avoid render-phase state update
|
||
useEffect(() => {
|
||
const duration = calculation?.duration ?? 0;
|
||
const suggestedQty = (duration < 30 && duration > 0) ? 2 : 1;
|
||
setItemQuantity(suggestedQty);
|
||
}, [calculation?.duration]);
|
||
|
||
if (isLoading) return <div className="min-h-screen flex items-center justify-center">Loading...</div>;
|
||
if (!product || !fullProduct || !calculation) return <div className="min-h-screen flex items-center justify-center">محصول یافت نشد</div>;
|
||
|
||
return (
|
||
<div className="min-h-screen bg-medical-gray-50 pt-2 pb-20 px-4 md:px-0" dir="rtl">
|
||
<div className="max-w-7xl mx-auto">
|
||
{/* Mobile Header with Back Button */}
|
||
<div className="flex items-center justify-between mb-6 md:hidden">
|
||
<button
|
||
onClick={() => router.back()}
|
||
className="p-3 bg-white border border-medical-gray-200 rounded-2xl text-medical-gray-700 shadow-sm flex items-center gap-2 font-vazir text-sm font-bold"
|
||
>
|
||
<ChevronRight className="w-5 h-5" />
|
||
بازگشت
|
||
</button>
|
||
<div className="text-[10px] font-black text-canina-blue bg-canina-blue/5 px-4 py-2 rounded-xl font-vazir whitespace-nowrap">
|
||
کانینا ایران
|
||
</div>
|
||
</div>
|
||
|
||
{/* Breadcrumbs */}
|
||
<nav className="hidden md:flex items-center gap-2 text-xs font-bold text-medical-gray-400 mb-8 overflow-x-auto whitespace-nowrap py-2">
|
||
<button
|
||
onClick={() => router.push('/')}
|
||
className="hover:text-canina-blue transition-colors font-vazir cursor-pointer"
|
||
>
|
||
خانه
|
||
</button>
|
||
<ChevronLeft className="w-3 h-3 flex-shrink-0" />
|
||
<button
|
||
onClick={() => router.push(`/shop?category=${product.categorySlug}`)}
|
||
className="hover:text-canina-blue cursor-pointer font-vazir"
|
||
>
|
||
{product.category}
|
||
</button>
|
||
<ChevronLeft className="w-3 h-3 flex-shrink-0" />
|
||
<span className="text-canina-blue font-vazir whitespace-nowrap">{product.name}</span>
|
||
</nav>
|
||
|
||
<div className="grid lg:grid-cols-12 gap-12 items-start">
|
||
{/* Main Content Area */}
|
||
<div className="lg:col-span-8 space-y-12">
|
||
<div>
|
||
<div className="flex flex-wrap items-center gap-3 mb-4">
|
||
<div className="px-3 py-1 bg-canina-blue text-white rounded-full text-[10px] font-black uppercase tracking-widest font-vazir whitespace-nowrap">
|
||
استاندارد صنعتی آلمان
|
||
</div>
|
||
{product.specialBadge && (
|
||
<div className="px-3 py-1 bg-medical-gray-900 text-white rounded-full text-[10px] font-black uppercase tracking-widest font-vazir whitespace-nowrap">
|
||
{product.specialBadge}
|
||
</div>
|
||
)}
|
||
</div>
|
||
<h1 className="text-4xl lg:text-6xl font-black text-medical-gray-900 leading-tight mb-4 italic font-vazir">
|
||
{product.name}
|
||
</h1>
|
||
<p className="text-xl text-canina-blue font-bold italic opacity-70 leading-relaxed max-w-2xl font-vazir mb-6">
|
||
{pets.length > 0 ? (
|
||
product.optimisticTemplate?.replace("[PetName]", activePet?.name || "").replace("[OnSet]", product.onSetOfAction || "به زودی")
|
||
) : (
|
||
product.scientificTagline
|
||
)}
|
||
</p>
|
||
{fullProduct.shortDescription && (
|
||
<p className="text-lg text-medical-gray-600 font-medium leading-relaxed max-w-3xl font-vazir border-r-4 border-canina-blue pr-6 py-2 bg-canina-blue/5 rounded-l-2xl">
|
||
{fullProduct.shortDescription}
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Key Benefits Section */}
|
||
{fullProduct.keyBenefits?.length > 0 && (
|
||
<section className="space-y-8">
|
||
<div className="flex items-center gap-4">
|
||
<div className="w-12 h-12 bg-canina-blue text-white rounded-2xl flex items-center justify-center shadow-lg">
|
||
<Star className="w-6 h-6" />
|
||
</div>
|
||
<h3 className="text-3xl font-black text-medical-gray-900 italic font-vazir">چرا {fullProduct.name}؟</h3>
|
||
</div>
|
||
<div className="grid md:grid-cols-3 gap-6">
|
||
{fullProduct.keyBenefits.map((benefit, i) => {
|
||
const Icon = ICON_MAP[benefit.icon] || CheckCircle2;
|
||
return (
|
||
<div key={i} className="bg-white border border-medical-gray-100 rounded-[2rem] p-8 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all group overflow-hidden relative">
|
||
<div className="absolute -top-10 -left-10 w-24 h-24 bg-canina-blue/5 rounded-full group-hover:scale-150 transition-transform duration-700" />
|
||
<div className="w-12 h-12 bg-medical-gray-50 rounded-2xl flex items-center justify-center text-canina-blue mb-6 group-hover:bg-canina-blue group-hover:text-white transition-all">
|
||
<Icon className="w-6 h-6" />
|
||
</div>
|
||
<h4 className="text-xl font-black text-medical-gray-900 mb-2 font-vazir">{benefit.title}</h4>
|
||
<p className="text-sm text-medical-gray-500 font-bold font-vazir">{benefit.description}</p>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.95 }}
|
||
animate={{ opacity: 1, scale: 1 }}
|
||
className="bg-white rounded-[3.5rem] p-12 border border-medical-gray-200 shadow-xl relative group overflow-hidden"
|
||
>
|
||
<div className="absolute top-6 left-6 flex flex-col gap-2 z-10">
|
||
<div className="px-3 py-1 bg-green-50 text-green-600 rounded-full text-[10px] font-black uppercase tracking-widest border border-green-100 flex items-center gap-1 font-vazir">
|
||
<CheckCircle2 className="w-3 h-3" />
|
||
موجود در انبار
|
||
</div>
|
||
<div className="px-3 py-1 bg-canina-blue/5 text-canina-blue rounded-full text-[10px] font-black uppercase tracking-widest border border-canina-blue/10 font-vazir whitespace-nowrap">
|
||
گرید دارویی اختصاصی
|
||
</div>
|
||
</div>
|
||
<SafeImage
|
||
src={product.image}
|
||
alt={product.name}
|
||
className="w-full max-w-md mx-auto h-auto drop-shadow-2xl group-hover:scale-105 transition-transform duration-700"
|
||
/>
|
||
</motion.div>
|
||
|
||
{/* Ingredients Section */}
|
||
<section className="space-y-8">
|
||
<div className="flex items-center gap-4">
|
||
<div className="w-12 h-12 bg-medical-gray-900 text-white rounded-2xl flex items-center justify-center shadow-lg">
|
||
<FlaskConical className="w-6 h-6" />
|
||
</div>
|
||
<h3 className="text-3xl font-black text-medical-gray-900 italic font-vazir">ترکیبات و آنالیز علمی</h3>
|
||
</div>
|
||
|
||
<div className="space-y-10">
|
||
{/* Ingredients Cards */}
|
||
<div className="bg-white rounded-[2.5rem] p-8 border border-medical-gray-200">
|
||
<h4 className="text-xs font-black text-medical-gray-400 uppercase tracking-widest mb-6 font-vazir">مواد تشکیلدهنده برتر</h4>
|
||
<div className="flex flex-wrap gap-3">
|
||
{product.main_ingredients.map((ing, idx) => {
|
||
const termKey = termKeys.find(key => ing.includes(key));
|
||
return (
|
||
<div key={idx} className="bg-medical-gray-50 border border-medical-gray-100 px-5 py-3 rounded-2xl flex items-center gap-3 group hover:border-canina-blue/30 transition-all">
|
||
<span className="text-sm font-bold text-medical-gray-700 font-vazir">
|
||
{termKey ? (
|
||
<Tooltip termKey={termKey} onWikiNavigate={(id) => router.push(`/wiki?term=${id}`)}>
|
||
{ing}
|
||
</Tooltip>
|
||
) : ing}
|
||
</span>
|
||
<Sparkles className="w-3 h-3 text-canina-blue opacity-40 group-hover:opacity-100 transition-opacity" />
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Analysis Cards Grid */}
|
||
{Object.keys(product.analysis).length > 0 && (
|
||
<div className="space-y-6">
|
||
<div className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-4 font-vazir">تخمین اتمام بسته (هوش مصنوعی)</div>
|
||
<div className="grid grid-cols-2 gap-4">
|
||
{Object.entries(product.analysis).map(([key, value]) => (
|
||
<div key={key} className="bg-canina-blue text-white p-8 rounded-[2rem] border-b-2 border-white/20 shadow-xl group hover:scale-[1.02] transition-all duration-300">
|
||
<div className="text-[10px] font-black text-white/80 uppercase tracking-widest mb-3 font-vazir whitespace-nowrap">{key}</div>
|
||
<div className="text-3xl font-black font-vazir tracking-tighter text-white">{toPersian(value)}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</section>
|
||
|
||
{/* Feeding Section */}
|
||
<section className="bg-canina-blue/5 border border-canina-blue/10 rounded-[3rem] p-10">
|
||
<div className="flex items-center gap-4 mb-8">
|
||
<div className="w-10 h-10 bg-canina-blue text-white rounded-xl flex items-center justify-center">
|
||
<CalendarDays className="w-6 h-6" />
|
||
</div>
|
||
<h3 className="text-2xl font-black text-medical-gray-900 italic font-vazir">توصیه غذایی و نحوه مصرف</h3>
|
||
</div>
|
||
<p className="text-medical-gray-700 leading-relaxed text-sm md:text-base font-medium font-vazir">
|
||
{product.feedingAdvice}
|
||
</p>
|
||
</section>
|
||
|
||
{/* Specialist Note */}
|
||
{product.specialist && (
|
||
<section className="bg-white rounded-[3rem] p-10 border border-medical-gray-200">
|
||
<div className="flex flex-col md:flex-row gap-10 items-start">
|
||
<img
|
||
src={product.specialist.image}
|
||
alt={product.specialist.name}
|
||
className="w-32 h-32 rounded-[2.5rem] object-cover border-4 border-medical-gray-50 shadow-xl"
|
||
/>
|
||
<div className="flex-1">
|
||
<div className="inline-flex items-center gap-2 px-3 py-1 bg-medical-gray-900 text-white rounded-full text-[10px] font-black uppercase tracking-widest mb-4 font-vazir whitespace-nowrap">
|
||
<Stethoscope className="w-3 h-3" />
|
||
مورد تأیید دامپزشکان
|
||
</div>
|
||
<h4 className="text-2xl font-black text-medical-gray-900 mb-1 font-vazir">{product.specialist.name}</h4>
|
||
<p className="text-sm font-bold text-canina-blue mb-6 font-vazir">{product.specialist.title}</p>
|
||
<p className="text-base text-medical-gray-600 leading-relaxed italic font-vazir">
|
||
"{product.specialist.message}"
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{/* Specialized Landing Banner */}
|
||
<section>
|
||
<div
|
||
className="relative bg-canina-blue rounded-[3rem] p-12 overflow-hidden group cursor-pointer"
|
||
onClick={() => toast.info("بزودی: صفحه فرود تخصصی این محصول در حال آمادهسازی است")}
|
||
>
|
||
<div className="absolute top-0 right-0 w-full h-full bg-[url('https://www.transparenttextures.com/patterns/cubes.png')] opacity-10" />
|
||
<div className="absolute -bottom-20 -left-20 w-80 h-80 bg-white/10 rounded-full blur-3xl group-hover:bg-white/20 transition-all duration-700" />
|
||
|
||
<div className="relative z-10 flex flex-col md:flex-row items-center justify-between gap-8">
|
||
<div className="text-center md:text-right">
|
||
<h3 className="text-3xl lg:text-4xl font-black text-white mb-4 italic">مشاهده بررسی تخصصی و نتایج درمانی</h3>
|
||
<p className="text-white/80 font-bold font-vazir max-w-lg">
|
||
گزارشهای علمی، ویدیوهای آموزشی و تجربیات واقعی دیگر صاحبان پت در لندینگپیج اختصاصی این محصول.
|
||
</p>
|
||
</div>
|
||
<div className="bg-white text-canina-blue px-8 py-4 rounded-2xl font-black shadow-2xl hover:scale-105 transition-transform flex items-center gap-3">
|
||
ورود به آزمایشگاه علمی
|
||
<ArrowRight className="w-5 h-5 rotate-180" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* FAQ Section */}
|
||
{product.faqs?.length > 0 && (
|
||
<section className="pt-10">
|
||
<div className="flex items-center gap-4 mb-10">
|
||
<div className="w-12 h-12 bg-blue-100 text-canina-blue rounded-2xl flex items-center justify-center">
|
||
<Info className="w-6 h-6" />
|
||
</div>
|
||
<h3 className="text-3xl font-black text-medical-gray-900 italic font-vazir">پاسخ به ابهامات شما</h3>
|
||
</div>
|
||
<div className="grid md:grid-cols-1 gap-4">
|
||
{product.faqs.map((faq, i) => (
|
||
<details key={i} className="group bg-white border border-medical-gray-200 rounded-3xl overflow-hidden hover:border-canina-blue/30 transition-all">
|
||
<summary className="flex items-center justify-between p-6 cursor-pointer list-none">
|
||
<span className="font-bold text-medical-gray-900 font-vazir">{faq.question}</span>
|
||
<ChevronDown className="w-5 h-5 text-canina-blue group-open:rotate-180 transition-transform" />
|
||
</summary>
|
||
<div className="px-6 pb-6 text-sm text-medical-gray-500 font-medium leading-relaxed font-vazir border-t border-medical-gray-50 pt-4">
|
||
{faq.answer}
|
||
</div>
|
||
</details>
|
||
))}
|
||
</div>
|
||
</section>
|
||
)}
|
||
</div>
|
||
|
||
{/* Sticky Sidebar */}
|
||
<div className="lg:col-span-4 lg:sticky lg:top-24 space-y-8">
|
||
<div className="bg-white rounded-[3rem] border-2 border-medical-gray-100 p-8 shadow-2xl relative overflow-hidden">
|
||
<div className="absolute top-0 right-0 w-24 h-24 bg-canina-blue/5 rounded-full -mr-12 -mt-12" />
|
||
|
||
<div className="text-4xl font-black font-vazir text-medical-gray-900 tracking-tighter mb-4">
|
||
{toPersian(product.price)}
|
||
</div>
|
||
|
||
{/* Refill Auto-Calculator */}
|
||
<div className="bg-canina-blue rounded-[2rem] p-6 text-white mb-8 border-b-8 border-white/20 shadow-2xl">
|
||
<div className="flex items-center gap-2 mb-4 opacity-90">
|
||
<Clock className="w-4 h-4 text-white" />
|
||
<span className="text-[10px] font-bold uppercase tracking-widest font-vazir text-white">هوش مصنوعی تکرار خرید</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<div className="text-2xl font-black font-vazir text-white">{toPersian(calculation.duration)} روز</div>
|
||
<div className="text-[9px] font-bold text-white/80 font-vazir whitespace-nowrap">تخمین اتمام برای {toPersian(weight)} کیلوگرم</div>
|
||
</div>
|
||
<div className="text-left">
|
||
<div className="text-xl font-black font-vazir text-white">{toPersian(calculation.dailyDose)}</div>
|
||
<div className="text-[9px] font-bold text-white/80 font-vazir whitespace-nowrap">دوز روزانه ({calculation.unit === 'g' ? 'گرم' : calculation.unit})</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Manual Calculator (Mini) */}
|
||
<div className="bg-medical-gray-50 rounded-2xl p-6 border border-medical-gray-100 mb-8 space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-[10px] font-black text-medical-gray-400 font-vazir">وزن پت (کیلوگرم)</span>
|
||
<span className="text-sm font-black text-canina-blue font-vazir">{toPersian(weight)}</span>
|
||
</div>
|
||
<input
|
||
type="range"
|
||
min="1" max="100"
|
||
value={weight}
|
||
onChange={(e) => setWeight(parseInt(e.target.value))}
|
||
className="w-full h-1.5 bg-medical-gray-200 rounded-lg appearance-none cursor-pointer accent-canina-blue"
|
||
/>
|
||
<div className="flex gap-2">
|
||
<button onClick={() => setPetType("young")} className={`flex-1 py-2 rounded-xl text-[10px] font-black transition-all ${petType === "young" ? 'bg-canina-blue text-white shadow-lg' : 'bg-white text-medical-gray-400'}`}>جوان</button>
|
||
<button onClick={() => setPetType("adult")} className={`flex-1 py-2 rounded-xl text-[10px] font-black transition-all ${petType === "adult" ? 'bg-canina-blue text-white shadow-lg' : 'bg-white text-medical-gray-400'}`}>بالغ</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-4 mb-6">
|
||
{/* Quantity Selector - 40% Width */}
|
||
<div className="flex items-center gap-4 bg-medical-gray-50 border border-medical-gray-100 rounded-2xl p-2 h-16 w-[40%]">
|
||
<button
|
||
onClick={() => setItemQuantity(Math.max(1, itemQuantity - 1))}
|
||
className="w-10 h-10 flex items-center justify-center rounded-xl bg-white text-medical-gray-900 border border-medical-gray-200 hover:bg-canina-blue hover:text-white hover:border-canina-blue transition-all shadow-sm"
|
||
>
|
||
<Minus className="w-4 h-4" />
|
||
</button>
|
||
<span className="text-xl font-black text-medical-gray-900 flex-1 text-center font-vazir">
|
||
{toPersian(itemQuantity)}
|
||
</span>
|
||
<button
|
||
onClick={() => setItemQuantity(itemQuantity + 1)}
|
||
className="w-10 h-10 flex items-center justify-center rounded-xl bg-white text-medical-gray-900 border border-medical-gray-200 hover:bg-canina-blue hover:text-white hover:border-canina-blue transition-all shadow-sm"
|
||
>
|
||
<Plus className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
|
||
{/* Add to Cart Button - 60% Width */}
|
||
<button
|
||
onClick={() => {
|
||
addItem(product, itemQuantity, { quantity: calculation.dailyDose, unit: calculation.unit });
|
||
toast.success(`${toPersian(itemQuantity)} عدد ${product.name} به سبد خرید اضافه شد`);
|
||
}}
|
||
className="w-[60%] flex items-center justify-center gap-1.5 px-4 bg-medical-gray-900 text-white rounded-2xl h-16 font-black text-sm hover:bg-canina-blue transition-all shadow-xl shadow-black/10 font-vazir whitespace-nowrap"
|
||
>
|
||
<ShoppingBag className="w-5 h-5 mb-1" />
|
||
افزودن به سبد خرید
|
||
</button>
|
||
</div>
|
||
|
||
<div className="mt-6 flex flex-col gap-3">
|
||
<div className="flex items-center gap-2 text-[10px] font-bold text-medical-gray-400 font-vazir">
|
||
<ShieldCheck className="w-4 h-4 text-green-500" />
|
||
ضمانت اصالت محصول (Made in Germany)
|
||
</div>
|
||
<div className="flex items-center gap-2 text-[10px] font-bold text-medical-gray-400 font-vazir">
|
||
<Clock className="w-4 h-4 text-canina-blue" />
|
||
آماده ارسال (تحویل حداکثر ۴۸ ساعت)
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* expected results box */}
|
||
{product.expectedResults?.length > 0 && (
|
||
<div className="bg-white rounded-[2.5rem] border border-medical-gray-200 p-8">
|
||
<h4 className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-6 font-vazir">تغییرات قابل انتظار</h4>
|
||
<div className="space-y-4">
|
||
{product.expectedResults.map((res, i) => {
|
||
const Icon = ICON_MAP[res.icon] || Sparkles;
|
||
return (
|
||
<div key={i} className="flex items-center gap-4 group">
|
||
<div className="w-10 h-10 bg-medical-gray-50 rounded-xl flex items-center justify-center text-canina-blue group-hover:bg-canina-blue group-hover:text-white transition-all">
|
||
<Icon className="w-5 h-5" />
|
||
</div>
|
||
<span className="text-xs font-bold text-medical-gray-700 font-vazir">{res.text}</span>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Smart Cross-sell: Recommended Combinations */}
|
||
{product.relatedProducts && product.relatedProducts.length > 0 && (
|
||
<section className="mt-32 pt-20 border-t border-medical-gray-200">
|
||
<div className="flex flex-col items-center text-center mb-16 px-4">
|
||
<div className="inline-flex items-center gap-2 px-4 py-2 bg-canina-blue/5 border border-canina-blue/10 rounded-full text-canina-blue text-xs font-black uppercase tracking-widest mb-6 font-vazir whitespace-nowrap">
|
||
<Sparkles className="w-4 h-4" />
|
||
فرمولاسیون ترکیبی همافزا (Synergy Blend)
|
||
</div>
|
||
<h2 className="text-3xl lg:text-5xl font-black text-medical-gray-900 leading-tight">
|
||
اثربخشی <span className="text-canina-blue italic">دوبرابر</span> با ترکیب هوشمند
|
||
</h2>
|
||
</div>
|
||
|
||
<div className="grid lg:grid-cols-2 gap-8">
|
||
{product.relatedProducts.map(relId => {
|
||
const relProduct = allProducts.find(p => p.id === relId);
|
||
if (!relProduct) return null;
|
||
return (
|
||
<motion.div
|
||
key={relId}
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
className="bg-white rounded-[3rem] p-8 border border-medical-gray-200 flex flex-col md:flex-row gap-8 items-center cursor-pointer hover:shadow-2xl transition-all"
|
||
onClick={() => router.push(`/shop/${relProduct.id}`)}
|
||
>
|
||
<div className="w-40 h-40 bg-medical-gray-50 rounded-[2rem] p-4 flex items-center justify-center">
|
||
<SafeImage src={relProduct.image} alt={relProduct.name} className="w-full h-full drop-shadow-xl" imgClassName="object-contain" />
|
||
</div>
|
||
<div className="flex-1 text-center md:text-right">
|
||
<h4 className="text-xl font-black text-medical-gray-900 mb-2">{relProduct.name}</h4>
|
||
<p className="text-sm text-medical-gray-500 mb-6">{relProduct.description}</p>
|
||
<div className="flex items-center justify-center md:justify-start gap-4">
|
||
<span className="text-lg font-black text-canina-blue">{relProduct.price}</span>
|
||
<ArrowRight className="w-4 h-4 text-medical-gray-300" />
|
||
</div>
|
||
</div>
|
||
<div className="w-px h-24 bg-medical-gray-100 hidden md:block" />
|
||
<div className="flex items-center justify-center gap-1 group">
|
||
<span className="text-xs font-black text-medical-gray-400 group-hover:text-canina-blue transition-colors">مشاهده محصول مکمل</span>
|
||
<ChevronLeft className="w-4 h-4 text-medical-gray-300 group-hover:text-canina-blue group-hover:-translate-x-1 transition-all rtl:group-hover:translate-x-1" />
|
||
</div>
|
||
</motion.div>
|
||
);
|
||
})}
|
||
</div>
|
||
</section>
|
||
)}
|
||
</div>
|
||
|
||
{/* Refill Automation Popup */}
|
||
<AnimatePresence>
|
||
{showRefillModal && (
|
||
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
|
||
<motion.div
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
onClick={() => setShowRefillModal(false)}
|
||
className="absolute inset-0 bg-medical-gray-900/60 backdrop-blur-md"
|
||
/>
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||
className="bg-white w-full max-w-lg rounded-[3rem] p-10 relative z-10 shadow-2xl overflow-hidden"
|
||
>
|
||
<div className="absolute top-0 left-0 w-full h-2 bg-gradient-to-r from-canina-blue via-blue-400 to-canina-blue" />
|
||
<button
|
||
onClick={() => setShowRefillModal(false)}
|
||
className="absolute top-6 left-6 text-medical-gray-400 hover:text-medical-gray-600 transition-colors"
|
||
>
|
||
<X className="w-6 h-6" />
|
||
</button>
|
||
|
||
<div className="text-center">
|
||
<div className="w-20 h-20 bg-canina-blue/10 rounded-[2rem] flex items-center justify-center mx-auto mb-8">
|
||
<Bell className="w-10 h-10 text-canina-blue animate-bounce" />
|
||
</div>
|
||
<h3 className="text-3xl font-black text-medical-gray-900 mb-4 leading-tight italic">سیستم یادآوری هوشمند</h3>
|
||
<div className="bg-medical-gray-50 border border-medical-gray-100 rounded-3xl p-6 mb-8 text-right">
|
||
<p className="text-medical-gray-700 leading-relaxed font-medium font-vazir">
|
||
بر اساس وزن <span className="text-canina-blue font-bold">{toPersian(weight)} کیلوگرمی</span> {activePet ? `برای ${activePet.name}` : 'سگ شما'}، این بسته {toPersian(product.packageSize)} تایی دقیقاً <span className="text-canina-blue font-bold">{toPersian(calculation.duration)} روز</span> دیگر تمام میشود.
|
||
</p>
|
||
<div className="mt-4 flex items-center gap-2 text-xs font-bold text-medical-gray-500 font-vazir">
|
||
<ShieldCheck className="w-4 h-4 text-green-500" />
|
||
آیا مایلید سیستم ۵ روز قبل از اتمام، به شما پیامک یادآوری ارسال کند؟
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex flex-col gap-3">
|
||
<button
|
||
onClick={() => setShowRefillModal(false)}
|
||
className="w-full bg-canina-blue text-white py-5 rounded-2xl font-black text-lg hover:shadow-xl hover:shadow-canina-blue/20 transition-all flex items-center justify-center gap-2"
|
||
>
|
||
<CheckCircle2 className="w-5 h-5" />
|
||
بله، پیامک یادآوری فعال شود
|
||
</button>
|
||
<button
|
||
onClick={() => setShowRefillModal(false)}
|
||
className="w-full py-4 text-medical-gray-400 font-bold text-sm hover:text-medical-gray-600 transition-colors"
|
||
>
|
||
خیر، فقط محصول را به سبد خرید اضافه کن
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
)}
|
||
</AnimatePresence>
|
||
</div>
|
||
);
|
||
}
|