Files
sanihome/src/components/ui/ProductCard.tsx
T
amirhosein.ashourlooandClaude Sonnet 5 c2552af079 Fix product links, equalize card heights, and switch to IRANYekan
- Resolve product cards from group endpoints (store-specials,
  brand-groups) that only return a productId, not a slug, via a new
  /products/id/[id] route backed by GET /products/by-id/{id} — clicking
  these was previously falling back to a name search instead of the
  detail page.
- Derive discount price/percent generically in normalizeProduct so any
  source with real old/new prices shows it, even without an explicit
  discount field from the API.
- Product cards and the detail page buy-box: show the discount badge
  beside the old (struck-through) price, current price alone on its
  own line, matching the reference design.
- Equalize product card heights within a carousel row (Swiper's own
  CSS doesn't stretch slides to the tallest sibling in slidesPerView:
  "auto" + freeMode mode) so the add-to-cart button lines up across
  cards regardless of whether a card has a discount line.
- Featured categories: 110px images with no border/radius, matching
  the site's other category card treatment.
- Replace Vazirmatn with the client's IRANYekan font files — light
  weight for body text, bold weight for headings only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 09:50:56 +03:30

118 lines
3.4 KiB
TypeScript

import { Placeholder } from "./Placeholder";
import { formatPrice } from "@/data/sample";
type ProductCardProps = {
name: string;
brand: string;
price: number | null;
oldPrice?: number | null;
discount?: number;
image: string;
variant?: "sale" | "default" | "brand";
href?: string;
fluid?: boolean;
contactPhone?: string;
};
export function ProductCard({
name,
brand,
price,
oldPrice,
discount,
image,
variant = "default",
href,
fluid = false,
contactPhone,
}: ProductCardProps) {
const info = (
<>
<div className="relative p-[10px]">
<Placeholder
src={image}
alt={name}
width="100%"
fit="contain"
className="aspect-square w-full rounded-lg border-0"
/>
{variant === "brand" && (
<span className="font-display absolute top-3 left-3 text-[10px] font-bold tracking-widest text-red-dark uppercase">
{brand}
</span>
)}
</div>
<div className="p-4 pb-0">
<p className="font-display mb-1 text-[11px] tracking-wide text-muted uppercase">
{brand}
</p>
<h3 className="mb-3 line-clamp-2 min-h-[2.75rem] text-sm leading-relaxed font-medium">
{name}
</h3>
<div className="space-y-1">
{oldPrice != null && (
<div className="flex items-center gap-2">
<span className="text-xs text-muted line-through">{formatPrice(oldPrice)}</span>
{discount != null && (
<span className="rounded-md bg-green-50 px-1.5 py-0.5 text-[11px] font-bold text-green-700">
{discount}٪
</span>
)}
</div>
)}
<div className="text-base font-bold text-red-dark">
{price != null ? (
<>
{formatPrice(price)}
<span className="mr-0.5 text-xs font-normal text-muted">تومان</span>
</>
) : (
<span className="text-sm font-normal text-muted">تماس با فروشگاه</span>
)}
</div>
</div>
</div>
</>
);
return (
<article
className={`group flex h-full flex-col ${fluid ? "w-full" : "w-[220px] shrink-0 md:w-[240px]"} overflow-hidden rounded-xl border border-border bg-surface transition-shadow hover:shadow-md`}
>
{href ? (
<a href={href} className="block flex-1">
{info}
</a>
) : (
<div className="flex-1">{info}</div>
)}
<div className="p-4">
{price != null ? (
<button
type="button"
className="w-full rounded-lg bg-red py-2 text-sm font-medium text-white transition-colors hover:bg-red-dark"
>
افزودن به سبد
</button>
) : contactPhone ? (
<a
href={`tel:${contactPhone}`}
className="flex w-full items-center justify-center gap-1.5 rounded-lg bg-red py-2 text-sm font-medium text-white transition-colors hover:bg-red-dark"
>
<span aria-hidden>📞</span>
تماس با فروشگاه
</a>
) : (
<button
type="button"
disabled
className="w-full cursor-not-allowed rounded-lg bg-red/60 py-2 text-sm font-medium text-white"
>
تماس با فروشگاه
</button>
)}
</div>
</article>
);
}