canina/frontend/application/components/FeaturedProducts.tsx
parsaaghayi dc3b8f4488
All checks were successful
Deploy Canina / deploy (push) Successful in 1m32s
Merge branch 'develop'
2026-08-24 01:46:45 +03:30

199 lines
10 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. 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, 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 petType = activePet.type || 'سگ';
const sameSpecies = product.suitableFor === petType || product.suitableFor === "هر دو";
const productSymptoms = Array.isArray(product.symptoms) ? product.symptoms : [];
const petConditions = Array.isArray(activePet.medicalConditions) ? activePet.medicalConditions : [];
const helpsSymptom = petConditions.some((s) => productSymptoms.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]);
const isCatalogOnly = getText('CATALOG_ONLY_MODE', 'false') === 'true';
const showPrices = !isCatalogOnly || getText('CATALOG_SHOW_PRICES', 'true') === 'true';
const allowCart = !isCatalogOnly || getText('CATALOG_ALLOW_CART', 'false') === 'true';
const showPreorderBtn = isCatalogOnly && getText('CATALOG_PREORDER_BTN', 'false') === 'true';
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"
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-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"
title="افزودن به سبد خرید"
>
<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">
{showPrices ? (typeof product.price === 'string' || typeof product.price === 'number' ? String(product.price) : 'تماس بگیرید') : 'تماس بگیرید'}
</div>
{showPreorderBtn ? (
<span className="text-[11px] font-black text-amber-600 bg-amber-50 px-3 py-1 rounded-full border border-amber-200">
ثبت پیشخرید
</span>
) : (
<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>
);
}