593 lines
25 KiB
TypeScript
593 lines
25 KiB
TypeScript
import { useState, useMemo } from "react";
|
||
import { useParams, Link } from "react-router-dom";
|
||
import {
|
||
Heart,
|
||
ShoppingCart,
|
||
Star,
|
||
Plus,
|
||
Minus,
|
||
Check,
|
||
Truck,
|
||
Shield,
|
||
RotateCcw,
|
||
ChevronDown,
|
||
ChevronUp,
|
||
Package,
|
||
} from "lucide-react";
|
||
import { toast } from "sonner";
|
||
import { useCartStore } from "@/stores/cartStore";
|
||
import { PRODUCTS } from "@/constants/data";
|
||
import ProductCard from "@/components/features/ProductCard";
|
||
import type { ProductVariant } from "@/types";
|
||
|
||
export default function ProductDetailPage() {
|
||
const { id } = useParams<{ id: string }>();
|
||
const { addToCart, toggleWishlist, isInWishlist } = useCartStore();
|
||
const [quantity, setQuantity] = useState(1);
|
||
const [selectedImage, setSelectedImage] = useState(0);
|
||
const [selectedColor, setSelectedColor] = useState<string | null>(null);
|
||
const [selectedSize, setSelectedSize] = useState<string | null>(null);
|
||
const [showVariantsTable, setShowVariantsTable] = useState(false);
|
||
|
||
const product = PRODUCTS.find((p) => p.id === id);
|
||
|
||
if (!product) {
|
||
return (
|
||
<div className="max-w-7xl mx-auto px-4 py-20 text-center">
|
||
<div className="text-6xl mb-4">😕</div>
|
||
<h2 className="text-2xl font-black text-gray-800 mb-2">المنتج غير موجود</h2>
|
||
<Link to="/products" className="btn-primary inline-block mt-4">
|
||
العودة للمتجر
|
||
</Link>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const hasVariants = product.variants && product.variants.length > 0;
|
||
|
||
// Find matching variant based on selected color+size
|
||
const selectedVariant: ProductVariant | null = useMemo(() => {
|
||
if (!hasVariants) return null;
|
||
if (!selectedColor && !selectedSize) return null;
|
||
return (
|
||
product.variants!.find(
|
||
(v) =>
|
||
(!selectedColor || v.color === selectedColor) &&
|
||
(!selectedSize || v.size === selectedSize)
|
||
) || null
|
||
);
|
||
}, [selectedColor, selectedSize, product.variants, hasVariants]);
|
||
|
||
// Sizes available for selected color
|
||
const availableSizesForColor = useMemo(() => {
|
||
if (!hasVariants) return product.availableSizes || [];
|
||
if (!selectedColor) return product.availableSizes || [];
|
||
return [
|
||
...new Set(
|
||
product.variants!
|
||
.filter((v) => v.color === selectedColor)
|
||
.map((v) => v.size!)
|
||
.filter(Boolean)
|
||
),
|
||
];
|
||
}, [selectedColor, product.variants, hasVariants, product.availableSizes]);
|
||
|
||
// Check if a size is in stock for selected color
|
||
const isSizeInStock = (size: string) => {
|
||
if (!hasVariants) return product.inStock;
|
||
const v = product.variants!.find(
|
||
(variant) =>
|
||
variant.size === size &&
|
||
(!selectedColor || variant.color === selectedColor)
|
||
);
|
||
return v ? v.inStock : false;
|
||
};
|
||
|
||
const displayPrice = selectedVariant
|
||
? selectedVariant.price
|
||
: product.price;
|
||
|
||
const inWishlist = isInWishlist(product.id);
|
||
const images = product.images || [product.image];
|
||
const discount = product.originalPrice
|
||
? Math.round((1 - product.price / product.originalPrice) * 100)
|
||
: 0;
|
||
const related = PRODUCTS.filter(
|
||
(p) => p.categoryId === product.categoryId && p.id !== product.id
|
||
).slice(0, 4);
|
||
|
||
const handleAddToCart = () => {
|
||
if (!product.inStock) return;
|
||
if (hasVariants && !selectedVariant) {
|
||
toast.error("يرجى اختيار اللون والمقاس أولاً");
|
||
return;
|
||
}
|
||
if (selectedVariant && !selectedVariant.inStock) {
|
||
toast.error("هذا الخيار غير متوفر حالياً");
|
||
return;
|
||
}
|
||
addToCart(product, quantity, selectedVariant || undefined);
|
||
toast.success(
|
||
`تمت إضافة "${product.name}"${selectedVariant ? ` (${selectedVariant.color} - ${selectedVariant.size})` : ""} إلى السلة`
|
||
);
|
||
};
|
||
|
||
const handleToggleWishlist = () => {
|
||
toggleWishlist(product);
|
||
toast.success(inWishlist ? "تمت الإزالة من المفضلة" : "تمت الإضافة إلى المفضلة");
|
||
};
|
||
|
||
const handleColorSelect = (colorName: string) => {
|
||
setSelectedColor(colorName === selectedColor ? null : colorName);
|
||
setSelectedSize(null); // reset size when color changes
|
||
};
|
||
|
||
const handleSizeSelect = (size: string) => {
|
||
if (!isSizeInStock(size)) return;
|
||
setSelectedSize(size === selectedSize ? null : size);
|
||
};
|
||
|
||
return (
|
||
<div className="max-w-7xl mx-auto px-4 py-6">
|
||
{/* Breadcrumb */}
|
||
<div className="flex items-center gap-2 text-sm text-gray-500 mb-6 flex-wrap">
|
||
<Link to="/" className="hover:text-orange-500 transition-colors">الرئيسية</Link>
|
||
<span>›</span>
|
||
<Link to="/products" className="hover:text-orange-500 transition-colors">المنتجات</Link>
|
||
<span>›</span>
|
||
<Link
|
||
to={`/products?category=${product.categoryId}`}
|
||
className="hover:text-orange-500 transition-colors"
|
||
>
|
||
{product.category}
|
||
</Link>
|
||
<span>›</span>
|
||
<span className="text-gray-800 font-semibold line-clamp-1">{product.name}</span>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-10 mb-12">
|
||
{/* ─── Images ─── */}
|
||
<div>
|
||
<div className="bg-white rounded-2xl overflow-hidden shadow-sm border border-gray-100 mb-3">
|
||
<img
|
||
src={images[selectedImage]}
|
||
alt={product.name}
|
||
className="w-full h-80 md:h-[420px] object-cover"
|
||
/>
|
||
</div>
|
||
{images.length > 1 && (
|
||
<div className="flex gap-2 flex-wrap">
|
||
{images.map((img, i) => (
|
||
<button
|
||
key={i}
|
||
onClick={() => setSelectedImage(i)}
|
||
className={`w-20 h-20 rounded-xl overflow-hidden border-2 transition-colors ${
|
||
selectedImage === i ? "border-orange-500" : "border-gray-200 hover:border-orange-300"
|
||
}`}
|
||
>
|
||
<img src={img} alt="" className="w-full h-full object-cover" />
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* ─── Info ─── */}
|
||
<div>
|
||
{/* Badges */}
|
||
<div className="flex flex-wrap gap-2 mb-3">
|
||
<Link
|
||
to={`/products?category=${product.categoryId}`}
|
||
className="text-xs font-bold text-orange-500 bg-orange-50 px-3 py-1 rounded-full hover:bg-orange-100 transition-colors"
|
||
>
|
||
{product.category}
|
||
</Link>
|
||
{product.isNew && (
|
||
<span className="text-xs font-bold text-green-600 bg-green-50 px-3 py-1 rounded-full">
|
||
جديد
|
||
</span>
|
||
)}
|
||
{discount > 0 && (
|
||
<span className="text-xs font-bold text-red-600 bg-red-50 px-3 py-1 rounded-full">
|
||
خصم {discount}%
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
<h1 className="text-2xl md:text-3xl font-black text-gray-800 leading-tight mb-4">
|
||
{product.name}
|
||
</h1>
|
||
|
||
{/* Rating */}
|
||
<div className="flex items-center gap-3 mb-4">
|
||
<div className="flex">
|
||
{Array.from({ length: 5 }).map((_, i) => (
|
||
<Star
|
||
key={i}
|
||
size={18}
|
||
className={
|
||
i < Math.floor(product.rating) ? "text-amber-400" : "text-gray-200"
|
||
}
|
||
fill={i < Math.floor(product.rating) ? "currentColor" : "none"}
|
||
/>
|
||
))}
|
||
</div>
|
||
<span className="font-bold text-gray-700">{product.rating}</span>
|
||
<span className="text-gray-400 text-sm">({product.reviewCount} تقييم)</span>
|
||
</div>
|
||
|
||
{/* Price */}
|
||
<div className="bg-orange-50 rounded-2xl p-5 mb-5">
|
||
<div className="text-4xl font-black text-orange-500 mb-1">
|
||
{displayPrice.toLocaleString()} <span className="text-xl">ر.ي</span>
|
||
</div>
|
||
{product.originalPrice && (
|
||
<div className="flex items-center gap-3 flex-wrap">
|
||
<span className="text-gray-400 line-through text-lg">
|
||
{product.originalPrice.toLocaleString()} ر.ي
|
||
</span>
|
||
<span className="bg-red-500 text-white text-sm font-bold px-3 py-0.5 rounded-full">
|
||
وفرت {(product.originalPrice - product.price).toLocaleString()} ر.ي
|
||
</span>
|
||
</div>
|
||
)}
|
||
{selectedVariant && (
|
||
<p className="text-xs text-orange-600 font-semibold mt-2">
|
||
* السعر يختلف حسب اللون والمقاس المختار
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Description */}
|
||
<p className="text-gray-600 leading-relaxed mb-6 text-sm">{product.description}</p>
|
||
|
||
{/* ─── Color Selector ─── */}
|
||
{hasVariants && product.availableColors && product.availableColors.length > 0 && (
|
||
<div className="mb-5">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<span className="text-sm font-bold text-gray-700">اختر اللون</span>
|
||
{selectedColor && (
|
||
<span className="text-xs text-orange-500 font-semibold">{selectedColor}</span>
|
||
)}
|
||
</div>
|
||
<div className="flex gap-3 flex-wrap">
|
||
{product.availableColors.map((col) => (
|
||
<button
|
||
key={col.name}
|
||
onClick={() => handleColorSelect(col.name)}
|
||
title={col.name}
|
||
className={`w-10 h-10 rounded-full border-4 transition-all duration-200 hover:scale-110 ${
|
||
selectedColor === col.name
|
||
? "border-orange-500 scale-110 shadow-lg"
|
||
: "border-white shadow-md"
|
||
}`}
|
||
style={{ backgroundColor: col.hex }}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* ─── Size Selector ─── */}
|
||
{hasVariants && availableSizesForColor.length > 0 && (
|
||
<div className="mb-5">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<span className="text-sm font-bold text-gray-700">اختر المقاس</span>
|
||
{selectedSize && (
|
||
<span className="text-xs text-orange-500 font-semibold">{selectedSize}</span>
|
||
)}
|
||
</div>
|
||
<div className="flex gap-2 flex-wrap">
|
||
{availableSizesForColor.map((size) => {
|
||
const inStock = isSizeInStock(size);
|
||
const isSelected = selectedSize === size;
|
||
// Get price for this size+color combination
|
||
const variantForSize = product.variants!.find(
|
||
(v) => v.size === size && (!selectedColor || v.color === selectedColor)
|
||
);
|
||
return (
|
||
<button
|
||
key={size}
|
||
onClick={() => handleSizeSelect(size)}
|
||
disabled={!inStock}
|
||
className={`relative px-3 py-2 rounded-xl border-2 text-sm font-bold transition-all duration-200 min-w-[56px] text-center ${
|
||
isSelected
|
||
? "border-orange-500 bg-orange-500 text-white shadow-md"
|
||
: inStock
|
||
? "border-gray-200 text-gray-700 hover:border-orange-300 hover:bg-orange-50"
|
||
: "border-gray-100 text-gray-300 bg-gray-50 cursor-not-allowed line-through"
|
||
}`}
|
||
>
|
||
{size}
|
||
{variantForSize && variantForSize.price !== product.price && (
|
||
<span
|
||
className={`block text-xs font-normal leading-tight ${
|
||
isSelected ? "text-orange-100" : "text-orange-500"
|
||
}`}
|
||
>
|
||
{variantForSize.price.toLocaleString()}
|
||
</span>
|
||
)}
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
{!selectedColor && hasVariants && product.availableColors && (
|
||
<p className="text-xs text-gray-400 mt-2">اختر اللون أولاً لرؤية المقاسات المتاحة</p>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Variant selection hint */}
|
||
{hasVariants && selectedVariant && (
|
||
<div className="flex items-center gap-2 text-sm font-semibold text-green-600 mb-4 bg-green-50 px-4 py-2.5 rounded-xl">
|
||
<Check size={16} className="bg-green-500 text-white rounded-full p-0.5 shrink-0" />
|
||
{selectedVariant.color} · {selectedVariant.size} ·{" "}
|
||
<span className="text-orange-500">{selectedVariant.price.toLocaleString()} ر.ي</span>
|
||
</div>
|
||
)}
|
||
{hasVariants && !selectedVariant && (selectedColor || selectedSize) && (
|
||
<div className="text-xs text-amber-600 bg-amber-50 px-4 py-2.5 rounded-xl mb-4">
|
||
⚠️ هذه التوليفة غير متوفرة، جرب مقاساً أو لوناً آخر
|
||
</div>
|
||
)}
|
||
|
||
{/* Stock (non-variant) */}
|
||
{!hasVariants && (
|
||
<div
|
||
className={`flex items-center gap-2 text-sm font-semibold mb-5 ${
|
||
product.inStock ? "text-green-600" : "text-red-500"
|
||
}`}
|
||
>
|
||
{product.inStock ? (
|
||
<>
|
||
<Check size={16} className="bg-green-500 text-white rounded-full p-0.5" />
|
||
متوفر في المخزن
|
||
</>
|
||
) : (
|
||
<>
|
||
<span className="w-4 h-4 bg-red-500 rounded-full inline-block" />
|
||
نفدت الكمية
|
||
</>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Quantity */}
|
||
<div className="flex items-center gap-4 mb-5">
|
||
<span className="text-sm font-semibold text-gray-700">الكمية:</span>
|
||
<div className="flex items-center gap-2 bg-gray-100 rounded-xl p-1">
|
||
<button
|
||
onClick={() => setQuantity(Math.max(1, quantity - 1))}
|
||
className="w-9 h-9 bg-white rounded-lg flex items-center justify-center hover:bg-orange-50 hover:text-orange-500 transition-colors shadow-sm"
|
||
>
|
||
<Minus size={16} />
|
||
</button>
|
||
<span className="w-10 text-center font-black text-lg">{quantity}</span>
|
||
<button
|
||
onClick={() => setQuantity(quantity + 1)}
|
||
className="w-9 h-9 bg-white rounded-lg flex items-center justify-center hover:bg-orange-50 hover:text-orange-500 transition-colors shadow-sm"
|
||
>
|
||
<Plus size={16} />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Actions */}
|
||
<div className="flex gap-3 mb-6">
|
||
<button
|
||
onClick={handleAddToCart}
|
||
disabled={!product.inStock || (hasVariants && !selectedVariant)}
|
||
className={`flex-1 flex items-center justify-center gap-2 py-3.5 rounded-xl font-bold text-sm transition-all duration-200 ${
|
||
product.inStock && (!hasVariants || selectedVariant)
|
||
? "bg-orange-500 hover:bg-orange-600 text-white shadow-md hover:shadow-orange-200 hover:shadow-lg active:scale-95"
|
||
: "bg-gray-100 text-gray-400 cursor-not-allowed"
|
||
}`}
|
||
>
|
||
<ShoppingCart size={18} />
|
||
{!product.inStock
|
||
? "نفدت الكمية"
|
||
: hasVariants && !selectedVariant
|
||
? "اختر اللون والمقاس"
|
||
: "أضف إلى السلة"}
|
||
</button>
|
||
<button
|
||
onClick={handleToggleWishlist}
|
||
className={`w-14 h-14 rounded-xl flex items-center justify-center border-2 transition-all duration-200 ${
|
||
inWishlist
|
||
? "border-red-500 bg-red-500 text-white"
|
||
: "border-gray-200 text-gray-400 hover:border-red-300 hover:text-red-400"
|
||
}`}
|
||
>
|
||
<Heart size={20} fill={inWishlist ? "currentColor" : "none"} />
|
||
</button>
|
||
</div>
|
||
|
||
{/* Features */}
|
||
<div className="grid grid-cols-3 gap-3">
|
||
{[
|
||
{ icon: Truck, label: "توصيل سريع", sub: "24-48 ساعة" },
|
||
{ icon: Shield, label: "ضمان الجودة", sub: "أصلي 100%" },
|
||
{ icon: RotateCcw, label: "إرجاع مجاني", sub: "خلال 7 أيام" },
|
||
].map(({ icon: Icon, label, sub }) => (
|
||
<div key={label} className="bg-gray-50 rounded-xl p-3 text-center">
|
||
<Icon size={18} className="text-orange-500 mx-auto mb-1" />
|
||
<p className="text-xs font-bold text-gray-700">{label}</p>
|
||
<p className="text-xs text-gray-400">{sub}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ─── Variants Table ─── */}
|
||
{hasVariants && product.variants && (
|
||
<section className="mb-12">
|
||
<button
|
||
onClick={() => setShowVariantsTable(!showVariantsTable)}
|
||
className="w-full flex items-center justify-between bg-white border border-gray-200 rounded-2xl px-6 py-4 hover:bg-gray-50 transition-colors"
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<Package size={20} className="text-orange-500" />
|
||
<span className="font-bold text-gray-800 text-base">
|
||
خصائص الصنف ({product.variants.length} تنويعة)
|
||
</span>
|
||
</div>
|
||
{showVariantsTable ? (
|
||
<ChevronUp size={20} className="text-gray-500" />
|
||
) : (
|
||
<ChevronDown size={20} className="text-gray-500" />
|
||
)}
|
||
</button>
|
||
|
||
{showVariantsTable && (
|
||
<div className="mt-3 bg-white border border-gray-200 rounded-2xl overflow-hidden">
|
||
{/* Mobile: cards grid */}
|
||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-px bg-gray-100 sm:hidden">
|
||
{product.variants.map((v) => (
|
||
<button
|
||
key={v.id}
|
||
onClick={() => {
|
||
if (!v.inStock) return;
|
||
setSelectedColor(v.color || null);
|
||
setSelectedSize(v.size || null);
|
||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||
}}
|
||
disabled={!v.inStock}
|
||
className={`bg-white p-4 text-right transition-colors ${
|
||
selectedVariant?.id === v.id
|
||
? "bg-orange-50 border-2 border-orange-400"
|
||
: v.inStock
|
||
? "hover:bg-orange-50 cursor-pointer"
|
||
: "opacity-50 cursor-not-allowed"
|
||
}`}
|
||
>
|
||
<div className="flex items-center justify-between mb-2">
|
||
<div
|
||
className="w-5 h-5 rounded-full border border-gray-300 shrink-0"
|
||
style={{ backgroundColor: v.colorHex }}
|
||
/>
|
||
{!v.inStock && (
|
||
<span className="text-xs text-red-500 font-bold">نفد</span>
|
||
)}
|
||
{selectedVariant?.id === v.id && (
|
||
<Check size={14} className="text-orange-500" />
|
||
)}
|
||
</div>
|
||
<p className="text-xs text-gray-500 mb-0.5">
|
||
الوحدة: <span className="font-semibold text-gray-700">{product.unit || "قطعة"}</span>
|
||
</p>
|
||
{v.size && (
|
||
<p className="text-xs text-gray-500 mb-0.5">
|
||
المقاس: <span className="font-semibold text-gray-700">{v.size}</span>
|
||
</p>
|
||
)}
|
||
{v.color && (
|
||
<p className="text-xs text-gray-500 mb-1">
|
||
اللون: <span className="font-semibold text-gray-700">{v.color}</span>
|
||
</p>
|
||
)}
|
||
<p className="text-sm font-black text-orange-500">
|
||
{v.price.toLocaleString()}.00
|
||
</p>
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* Desktop: table */}
|
||
<div className="hidden sm:block overflow-x-auto">
|
||
<table className="w-full text-sm">
|
||
<thead className="bg-gray-50 border-b border-gray-200">
|
||
<tr>
|
||
<th className="px-4 py-3 text-right font-bold text-gray-600">الوحدة</th>
|
||
{product.availableSizes && <th className="px-4 py-3 text-right font-bold text-gray-600">المقاس</th>}
|
||
{product.availableColors && <th className="px-4 py-3 text-right font-bold text-gray-600">اللون</th>}
|
||
<th className="px-4 py-3 text-right font-bold text-gray-600">السعر</th>
|
||
<th className="px-4 py-3 text-right font-bold text-gray-600">الحالة</th>
|
||
<th className="px-4 py-3"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-gray-100">
|
||
{product.variants.map((v) => (
|
||
<tr
|
||
key={v.id}
|
||
className={`transition-colors ${
|
||
selectedVariant?.id === v.id
|
||
? "bg-orange-50"
|
||
: v.inStock
|
||
? "hover:bg-gray-50"
|
||
: "opacity-50"
|
||
}`}
|
||
>
|
||
<td className="px-4 py-3 font-semibold text-gray-700">
|
||
{product.unit || "قطعة"}
|
||
</td>
|
||
{product.availableSizes && (
|
||
<td className="px-4 py-3 text-gray-700">{v.size || "—"}</td>
|
||
)}
|
||
{product.availableColors && (
|
||
<td className="px-4 py-3">
|
||
<div className="flex items-center gap-2">
|
||
<div
|
||
className="w-5 h-5 rounded-full border border-gray-300 shrink-0"
|
||
style={{ backgroundColor: v.colorHex }}
|
||
/>
|
||
<span className="text-gray-700">{v.color || "—"}</span>
|
||
</div>
|
||
</td>
|
||
)}
|
||
<td className="px-4 py-3 font-black text-gray-800">
|
||
{v.price.toLocaleString()}.00
|
||
</td>
|
||
<td className="px-4 py-3">
|
||
{v.inStock ? (
|
||
<span className="text-green-600 text-xs font-bold bg-green-50 px-2 py-1 rounded-full">متوفر</span>
|
||
) : (
|
||
<span className="text-red-500 text-xs font-bold bg-red-50 px-2 py-1 rounded-full">نفد</span>
|
||
)}
|
||
</td>
|
||
<td className="px-4 py-3">
|
||
<button
|
||
onClick={() => {
|
||
if (!v.inStock) return;
|
||
setSelectedColor(v.color || null);
|
||
setSelectedSize(v.size || null);
|
||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||
}}
|
||
disabled={!v.inStock}
|
||
className={`text-xs font-bold px-3 py-1.5 rounded-lg transition-colors ${
|
||
selectedVariant?.id === v.id
|
||
? "bg-orange-500 text-white"
|
||
: v.inStock
|
||
? "bg-orange-50 text-orange-500 hover:bg-orange-100"
|
||
: "text-gray-300 cursor-not-allowed"
|
||
}`}
|
||
>
|
||
{selectedVariant?.id === v.id ? "✓ مختار" : "اختر"}
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</section>
|
||
)}
|
||
|
||
{/* Related products */}
|
||
{related.length > 0 && (
|
||
<section>
|
||
<h2 className="section-title mb-5">منتجات مشابهة</h2>
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
{related.map((p) => (
|
||
<ProductCard key={p.id} product={p} />
|
||
))}
|
||
</div>
|
||
</section>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|