242 lines
13 KiB
TypeScript
242 lines
13 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, Dog, Cat } 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";
|
||
|
||
import { isSuitableForDog, isSuitableForCat, isSuitableForBoth, isSpeciesCompatible, matchesMedicalSymptom } from "../lib/petCompatibility";
|
||
|
||
function ProductCard({ product, priority = false }: { product: Product; priority?: boolean }) {
|
||
const router = useRouter();
|
||
const { getText, isInitialized } = useSettingsStore();
|
||
const { getActivePet } = usePetStore();
|
||
const activePet = getActivePet();
|
||
const { addItem } = useCartStore();
|
||
|
||
const nameFa = product.nameFa || product.name;
|
||
const nameEn = product.nameEn || "";
|
||
|
||
const forDog = isSuitableForDog(product.suitableFor);
|
||
const forCat = isSuitableForCat(product.suitableFor);
|
||
const forBoth = isSuitableForBoth(product.suitableFor) || (forDog && forCat);
|
||
|
||
const compatibility = useMemo(() => {
|
||
if (!activePet) return null;
|
||
const petType = activePet.type || 'سگ';
|
||
const sameSpecies = isSpeciesCompatible(product.suitableFor, petType);
|
||
const matchedSymptom = matchesMedicalSymptom(activePet.medicalConditions, product.symptoms, product.benefits);
|
||
|
||
if (!sameSpecies) return { type: "alert", text: `مخصوص ${product.suitableFor || 'پت'}` };
|
||
if (matchedSymptom) return { type: "success", text: `توصیه شده برای ${matchedSymptom} ${activePet.name || 'پت شما'}` };
|
||
return { type: "neutral", text: `مناسب برای ${activePet.name || 'پت شما'}` };
|
||
}, [product, activePet]);
|
||
|
||
const isCatalogOnly = isInitialized ? getText('CATALOG_ONLY_MODE', 'false') === 'true' : false;
|
||
const showPreorderBtn = isCatalogOnly && (isInitialized ? getText('CATALOG_PREORDER_BTN', 'false') === 'true' : false);
|
||
const allowCart = !isCatalogOnly && (isInitialized ? getText('allow_cart_purchase', 'true') === 'true' : true);
|
||
const showPrices = !isCatalogOnly || (isInitialized ? getText('show_product_prices', 'true') === 'true' : true);
|
||
|
||
return (
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 15 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||
onClick={() => router.push(`/shop/${product.slug || product.id}`)}
|
||
className="group bg-white/90 backdrop-blur-md rounded-2xl sm:rounded-3xl border border-medical-gray-200/80 overflow-hidden hover:shadow-xl hover:shadow-canina-blue/10 hover:border-canina-blue/30 transition-all duration-300 flex flex-col cursor-pointer relative w-full"
|
||
>
|
||
{/* Category Badge */}
|
||
<div className="absolute top-2.5 right-2.5 sm:top-4 sm:right-4 z-30 px-2 py-0.5 sm:px-2.5 sm:py-1 bg-white/90 backdrop-blur-md rounded-full border border-medical-gray-200/80 text-[10px] sm:text-xs font-black text-medical-gray-700 uppercase tracking-wider shadow-xs group-hover:bg-canina-blue group-hover:text-white transition-all">
|
||
{product.category}
|
||
</div>
|
||
|
||
{/* Compatibility Tag */}
|
||
{compatibility && (
|
||
<div className={`absolute top-[2.4rem] sm:top-[3.25rem] right-2.5 sm:right-4 z-10 text-[9px] sm:text-3xs font-black uppercase tracking-widest px-2 py-0.5 sm:px-2.5 sm:py-0.5 rounded-full shadow-xs 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" ? (
|
||
forBoth ? (
|
||
<span className="flex items-center gap-0.5">
|
||
<Dog className="w-2.5 h-2.5" />
|
||
<Cat className="w-2.5 h-2.5" />
|
||
</span>
|
||
) : forCat ? (
|
||
<Cat className="w-2.5 h-2.5" />
|
||
) : forDog ? (
|
||
<Dog className="w-2.5 h-2.5" />
|
||
) : (
|
||
<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 Container */}
|
||
<div className="relative aspect-[16/11] sm:aspect-[4/3] bg-medical-gray-50 flex items-center justify-center overflow-hidden p-2 sm:p-3">
|
||
<SafeImage
|
||
src={product.image}
|
||
alt={nameFa}
|
||
priority={priority}
|
||
sizes="(max-width: 640px) 240px, (max-width: 1024px) 240px, 280px)"
|
||
className="w-full h-full group-hover:scale-105 transition-transform duration-500"
|
||
imgClassName="object-contain w-full h-full max-h-[130px] sm:max-h-[200px] mx-auto"
|
||
/>
|
||
|
||
{/* Species Badges (Dog / Cat) */}
|
||
<div className="absolute bottom-2 left-2 flex gap-1 z-10">
|
||
{forDog && (
|
||
<div className="w-5 h-5 sm:w-6 sm:h-6 bg-white/90 backdrop-blur-xs rounded-md flex items-center justify-center text-medical-gray-500 border border-medical-gray-200 shadow-2xs" title="مناسب برای سگ">
|
||
<Dog className="w-3 h-3 sm:w-3.5 sm:h-3.5 text-canina-blue" />
|
||
</div>
|
||
)}
|
||
{forCat && (
|
||
<div className="w-5 h-5 sm:w-6 sm:h-6 bg-white/90 backdrop-blur-xs rounded-md flex items-center justify-center text-medical-gray-500 border border-medical-gray-200 shadow-2xs" title="مناسب برای گربه">
|
||
<Cat className="w-3 h-3 sm:w-3.5 sm:h-3.5 text-canina-blue" />
|
||
</div>
|
||
)}
|
||
</div>
|
||
{/* Hover Actions for desktop */}
|
||
<div className="hidden sm:flex absolute inset-0 bg-canina-blue/60 opacity-0 group-hover:opacity-100 backdrop-blur-xs transition-all duration-300 items-center justify-center gap-3 z-20">
|
||
<div
|
||
onClick={(e) => { e.stopPropagation(); router.push(`/shop/${product.slug || product.id}`); }}
|
||
className="w-10 h-10 rounded-full bg-white text-canina-blue flex items-center justify-center hover:scale-110 transition-transform shadow-lg cursor-pointer"
|
||
title="مشاهده جزئیات"
|
||
>
|
||
<Eye className="w-5 h-5" />
|
||
</div>
|
||
{allowCart && (product.packageSize !== undefined && product.packageSize !== null ? Number(product.packageSize) : 100) > 0 && (
|
||
<div
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
addItem(product, 1);
|
||
toast.success(`${nameFa} به سبد خرید اضافه شد.`);
|
||
}}
|
||
className="w-10 h-10 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"
|
||
title="افزودن به سبد خرید"
|
||
>
|
||
<ShoppingCart className="w-5 h-5" />
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Content */}
|
||
<div className="p-3 sm:px-5 sm:py-4 flex flex-col flex-1 gap-1.5 sm:gap-3">
|
||
{/* Dual-language Name */}
|
||
<div className="flex flex-col gap-0.5">
|
||
<h3 dir="rtl" className="text-xs sm:text-sm font-black text-medical-gray-900 group-hover:text-canina-blue transition-colors leading-snug text-right line-clamp-1 sm:line-clamp-2 font-vazir">
|
||
{nameFa}
|
||
</h3>
|
||
{nameEn && (
|
||
<span dir="ltr" className="text-[10px] sm:text-xs-plus font-semibold text-slate-400 font-sans text-left block line-clamp-1">
|
||
{nameEn}
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
{/* Short description */}
|
||
<p className="text-[11px] sm:text-xs text-medical-gray-500 leading-relaxed line-clamp-2 flex-1">
|
||
{product.shortDescription || (product.description ? product.description.replace(/<[^>]*>/g, '') : '')}
|
||
</p>
|
||
|
||
{/* Footer: Price + CTA */}
|
||
<div className="flex items-center justify-between pt-2 sm:pt-3 border-t border-medical-gray-100 mt-auto">
|
||
<div className="text-xs sm:text-sm md:text-base font-black text-medical-gray-900 font-vazir whitespace-nowrap">
|
||
{showPrices ? (typeof product.price === 'string' || typeof product.price === 'number' ? String(product.price) : 'تماس بگیرید') : 'تماس بگیرید'}
|
||
</div>
|
||
{showPreorderBtn ? (
|
||
<span className="text-[10px] sm:text-xs-plus font-black text-amber-600 bg-amber-50 px-2 py-0.5 sm:px-2.5 sm:py-1 rounded-full border border-amber-200">
|
||
ثبت پیشخرید
|
||
</span>
|
||
) : (
|
||
<span className="text-[10px] sm:text-xs-plus font-black text-canina-blue uppercase tracking-wider border-b border-canina-blue pb-0.5 group-hover:text-medical-gray-900 group-hover:border-medical-gray-900 transition-all whitespace-nowrap">
|
||
{getText("product_view_details", "مشاهده جزئیات")}
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</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-8 sm:py-16 bg-medical-gray-50">
|
||
<div className="max-w-7xl mx-auto px-4">
|
||
<div className="flex flex-col sm:flex-row sm:items-end justify-between w-full mb-6 sm:mb-12 pb-4 sm:pb-6 border-b border-medical-gray-200 gap-3 sm:gap-6">
|
||
<div className="max-w-2xl">
|
||
<div className="text-canina-blue text-[10px] sm:text-xs font-bold uppercase tracking-[0.2em] mb-1.5 sm:mb-2 font-vazir">
|
||
{getText("featured_badge", "بیشترین انتخاب توسط دامپزشکان")}
|
||
</div>
|
||
<h2 className="text-2xl sm:text-3xl md:text-4xl font-black text-medical-gray-900 font-lalezar tracking-wide leading-tight">
|
||
{getText("featured_title_line1", "محصولات برگزیده و راهکارهای")}{" "}
|
||
<span className="text-canina-blue">{getText("featured_title_line2", "درمان تخصصی")}</span>
|
||
</h2>
|
||
</div>
|
||
<Link
|
||
href="/shop"
|
||
className="inline-flex items-center gap-1.5 sm:gap-2 text-canina-blue font-black text-xs sm:text-sm hover:translate-x-1 transition-all rtl:hover:-translate-x-1 group font-vazir self-start sm:self-auto shrink-0"
|
||
>
|
||
<span>{getText("featured_view_all", "مشاهده تمامی محصولات")}</span>
|
||
<span className="text-sm sm:text-lg group-hover:-translate-x-1 transition-transform">←</span>
|
||
</Link>
|
||
</div>
|
||
|
||
<div className="flex md:grid md:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6 overflow-x-auto md:overflow-visible pb-4 md:pb-0 micro-hscroll -mx-4 px-4 md:mx-0 md:px-0 select-none snap-x snap-mandatory md:snap-none overscroll-x-contain">
|
||
{isLoading ? (
|
||
Array.from({ length: 4 }).map((_, i) => (
|
||
<div key={i} className="w-[68vw] sm:w-[50vw] md:w-auto flex-shrink-0 snap-start">
|
||
<ProductCardSkeleton />
|
||
</div>
|
||
))
|
||
) : products.length > 0 ? (
|
||
products.map((p, i) => (
|
||
<div key={p.id} className="w-[68vw] sm:w-[50vw] md:w-auto flex-shrink-0 snap-start flex">
|
||
<ProductCard product={p} priority={i < 2} />
|
||
</div>
|
||
))
|
||
) : (
|
||
<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 w-full">
|
||
<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>
|
||
);
|
||
}
|