178 lines
8.9 KiB
TypeScript
178 lines
8.9 KiB
TypeScript
"use client";
|
|
import { useState, useEffect, useMemo } from "react";
|
|
import { Product } from "../lib/data/products";
|
|
import { usePetStore } from "../lib/store/usePetStore";
|
|
import { productService } from "../lib/services/productService";
|
|
import { motion } from "motion/react";
|
|
import { Eye, ShoppingCart, Heart, AlertCircle, Sparkles } from "lucide-react";
|
|
import SafeImage from "./SafeImage";
|
|
import { ProductCardSkeleton } from "./Skeleton";
|
|
import Link from "next/link";
|
|
import { useRouter } from "next/navigation";
|
|
import { useSettingsStore } from "../lib/store/settingsStore";
|
|
import { useCartStore } from "../lib/store/cartStore";
|
|
import { toast } from "sonner";
|
|
|
|
function ProductCard({ product }: { product: Product }) {
|
|
const router = useRouter();
|
|
const getText = useSettingsStore((state) => state.getText);
|
|
const { getActivePet } = usePetStore();
|
|
const activePet = getActivePet();
|
|
const { addItem } = useCartStore();
|
|
|
|
const nameFa = product.nameFa || product.name;
|
|
const nameEn = product.nameEn || "";
|
|
|
|
const compatibility = useMemo(() => {
|
|
if (!activePet) return null;
|
|
const sameSpecies = product.suitableFor === activePet.type || product.suitableFor === "هر دو";
|
|
const helpsSymptom = (activePet.medicalConditions || []).some((s) => product.symptoms.includes(s));
|
|
if (!sameSpecies) return { type: "alert", text: `مخصوص ${product.suitableFor}` };
|
|
if (helpsSymptom) return { type: "success", text: `توصیه شده برای ${activePet.name}` };
|
|
return { type: "neutral", text: `مناسب برای ${activePet.name}` };
|
|
}, [product, activePet]);
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 24 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true }}
|
|
whileHover={{ y: -6 }}
|
|
transition={{ duration: 0.35, ease: "easeOut" }}
|
|
className="group bg-white/80 backdrop-blur-md rounded-3xl border border-medical-gray-200/80 overflow-hidden hover:shadow-2xl hover:shadow-canina-blue/15 hover:border-canina-blue/30 transition-all duration-500 flex flex-col cursor-pointer relative"
|
|
>
|
|
{/* Category Badge */}
|
|
<div className="absolute top-4 right-4 z-30 px-3 py-1.5 bg-white/90 backdrop-blur-md rounded-full border border-medical-gray-200/80 text-[10px] font-black text-medical-gray-700 uppercase tracking-widest shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all">
|
|
{product.category}
|
|
</div>
|
|
|
|
{/* Compatibility Tag */}
|
|
{compatibility && (
|
|
<div className={`absolute top-[3.25rem] right-4 z-10 text-[8px] font-black uppercase tracking-widest px-3 py-1 rounded-full shadow-md flex items-center gap-1 ${
|
|
compatibility.type === "alert" ? "bg-amber-100 text-amber-700" :
|
|
compatibility.type === "success" ? "bg-green-100 text-green-700" : "bg-medical-gray-100 text-medical-gray-600"
|
|
}`}>
|
|
{compatibility.type === "alert" ? <AlertCircle className="w-2.5 h-2.5" /> : compatibility.type === "success" ? <Heart className="w-2.5 h-2.5" /> : <Sparkles className="w-2.5 h-2.5" />}
|
|
{compatibility.text}
|
|
</div>
|
|
)}
|
|
|
|
{/* Image */}
|
|
<div className="relative aspect-[4/3] bg-medical-gray-50 flex items-center justify-center overflow-hidden p-5">
|
|
<SafeImage
|
|
src={product.image}
|
|
alt={nameFa}
|
|
className="w-full h-full group-hover:scale-105 transition-transform duration-500"
|
|
imgClassName="object-contain max-h-[170px] mx-auto"
|
|
/>
|
|
{/* PDP Link overlay */}
|
|
<Link href={`/shop/${product.slug || product.id}`} className="absolute inset-0 z-10" />
|
|
{/* Hover Actions */}
|
|
<div className="absolute inset-0 bg-canina-blue/60 opacity-0 group-hover:opacity-100 backdrop-blur-sm transition-all duration-300 flex items-center justify-center gap-4 z-20">
|
|
<div
|
|
onClick={(e) => { e.stopPropagation(); router.push(`/shop/${product.slug || product.id}`); }}
|
|
className="w-11 h-11 rounded-full bg-white text-canina-blue flex items-center justify-center hover:scale-110 transition-transform shadow-lg cursor-pointer"
|
|
>
|
|
<Eye className="w-5 h-5" />
|
|
</div>
|
|
<div
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
addItem(product, 1);
|
|
toast.success(`${nameFa} به سبد خرید اضافه شد.`);
|
|
}}
|
|
className="w-11 h-11 rounded-full bg-canina-blue text-white flex items-center justify-center hover:scale-110 transition-transform shadow-lg border border-white/20 cursor-pointer"
|
|
>
|
|
<ShoppingCart className="w-5 h-5" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<Link href={`/shop/${product.slug || product.id}`} className="px-5 py-4 flex flex-col flex-1 gap-3">
|
|
{/* Dual-language Name */}
|
|
<div className="flex flex-col gap-1">
|
|
<h3 dir="rtl" className="text-[14px] font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors leading-snug text-right line-clamp-2 font-vazir">
|
|
{nameFa}
|
|
</h3>
|
|
{nameEn && (
|
|
<span dir="ltr" className="text-[11px] font-semibold text-slate-400 font-sans text-left block line-clamp-1">
|
|
{nameEn}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* Short description */}
|
|
<p className="text-xs text-medical-gray-500 leading-relaxed line-clamp-2 flex-1">
|
|
{product.shortDescription || product.description}
|
|
</p>
|
|
|
|
{/* Footer: Price + CTA */}
|
|
<div className="flex items-center justify-between pt-3 border-t border-medical-gray-100">
|
|
<div className="text-base font-black text-medical-gray-900 font-vazir whitespace-nowrap">{product.price}</div>
|
|
<span className="text-[11px] font-black text-canina-blue uppercase tracking-widest border-b-2 border-canina-blue pb-0.5 hover:text-medical-gray-900 hover:border-medical-gray-900 transition-all whitespace-nowrap">
|
|
{getText("product_view_details", "مشاهده جزئیات")}
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
export default function FeaturedProducts() {
|
|
const router = useRouter();
|
|
const getText = useSettingsStore((state) => state.getText);
|
|
const [products, setProducts] = useState<Product[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
productService.getFeaturedProducts()
|
|
.then(setProducts)
|
|
.catch(() => toast.error("خطا در دریافت لیست محصولات از سرور."))
|
|
.finally(() => setIsLoading(false));
|
|
}, []);
|
|
|
|
return (
|
|
<section className="py-16 bg-medical-gray-50">
|
|
<div className="max-w-7xl mx-auto px-4">
|
|
<div className="flex items-center justify-between w-full mb-12 pb-6 border-b border-medical-gray-200">
|
|
<div>
|
|
<div className="text-canina-blue text-xs font-bold uppercase tracking-[0.2em] mb-2 font-vazir">
|
|
{getText("featured_badge", "بیشترین انتخاب توسط دامپزشکان")}
|
|
</div>
|
|
<h2 className="text-2xl md:text-3xl lg:text-4xl font-black text-medical-gray-900 font-lalezar tracking-wide">
|
|
{getText("featured_title_line1", "محصولات برگزیده و راهکارهای")}{" "}
|
|
<span className="text-canina-blue">{getText("featured_title_line2", "درمان تخصصی")}</span>
|
|
</h2>
|
|
</div>
|
|
<button
|
|
onClick={() => router.push("/shop")}
|
|
className="inline-flex items-center gap-2 text-canina-blue font-black text-sm hover:translate-x-1 transition-all group font-vazir"
|
|
>
|
|
{getText("featured_view_all", "مشاهده تمامی محصولات")}
|
|
<span className="text-lg group-hover:-translate-x-1 transition-transform">←</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{isLoading ? (
|
|
Array.from({ length: 4 }).map((_, i) => <ProductCardSkeleton key={i} />)
|
|
) : products.length > 0 ? (
|
|
products.map((p) => <ProductCard key={p.id} product={p} />)
|
|
) : (
|
|
<div className="col-span-full py-16 px-8 flex flex-col items-center justify-center text-center bg-white rounded-[2.5rem] border border-medical-gray-200 shadow-xl max-w-lg mx-auto">
|
|
<AlertCircle className="w-12 h-12 text-canina-teal mb-4" />
|
|
<h3 className="text-xl font-black text-medical-gray-900 mb-2 font-vazir">
|
|
{getText("featured_no_products_title", "هیچ محصولی یافت نشد")}
|
|
</h3>
|
|
<p className="text-sm text-medical-gray-500 font-medium font-vazir leading-relaxed">
|
|
{getText("featured_no_products_desc", "در حال حاضر هیچ محصول برجستهای برای نمایش وجود ندارد.")}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|