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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
83809815c1
commit
c2552af079
@@ -30,7 +30,12 @@ export function ProductPurchasePanel({
|
||||
header: ReactNode;
|
||||
variations: ProductVariation[];
|
||||
storeVariants: ProductStoreVariant[];
|
||||
fallbackPrice: { minPrice: number | null; maxPrice: number | null };
|
||||
fallbackPrice: {
|
||||
minPrice: number | null;
|
||||
maxPrice: number | null;
|
||||
oldPrice?: number | null;
|
||||
discount?: number;
|
||||
};
|
||||
inStock: boolean;
|
||||
contactPhone?: string;
|
||||
storeName?: string;
|
||||
@@ -68,6 +73,17 @@ export function ProductPurchasePanel({
|
||||
? `${formatPrice(fallbackPrice.minPrice)} - ${formatPrice(fallbackPrice.maxPrice)}`
|
||||
: null;
|
||||
|
||||
const variantOldPrice =
|
||||
matchedVariant?.discountedPrice != null &&
|
||||
matchedVariant?.price != null &&
|
||||
matchedVariant.price !== matchedVariant.discountedPrice
|
||||
? matchedVariant.price
|
||||
: null;
|
||||
const oldPrice = hasVariations ? variantOldPrice : (fallbackPrice.oldPrice ?? null);
|
||||
const discount = hasVariations
|
||||
? (oldPrice != null && price != null ? Math.round((1 - price / oldPrice) * 100) : null)
|
||||
: (fallbackPrice.discount ?? null);
|
||||
|
||||
const stockQuantity = hasVariations ? (matchedVariant?.stockQuantity ?? 0) : null;
|
||||
const available = hasVariations ? Boolean(matchedVariant) && stockQuantity! > 0 : inStock;
|
||||
const maxQty = stockQuantity && stockQuantity > 0 ? stockQuantity : 99;
|
||||
@@ -137,10 +153,22 @@ export function ProductPurchasePanel({
|
||||
|
||||
{price != null ? (
|
||||
<>
|
||||
<p className="mb-5 text-2xl font-bold text-red-dark">
|
||||
{priceRangeLabel ?? formatPrice(price)}
|
||||
<span className="mr-1 text-sm font-normal text-muted">تومان</span>
|
||||
</p>
|
||||
<div className="mb-5 space-y-1.5">
|
||||
{!priceRangeLabel && oldPrice != null && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted line-through">{formatPrice(oldPrice)}</span>
|
||||
{discount != null && (
|
||||
<span className="rounded-md bg-green-50 px-1.5 py-0.5 text-xs font-bold text-green-700">
|
||||
{discount}٪
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<p className="text-2xl font-bold text-red-dark">
|
||||
{priceRangeLabel ?? formatPrice(price)}
|
||||
<span className="mr-1 text-sm font-normal text-muted">تومان</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<span className="text-sm font-medium text-foreground">تعداد:</span>
|
||||
|
||||
Reference in New Issue
Block a user