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:
amirhosein.ashourloo
2026-08-23 09:50:56 +03:30
co-authored by Claude Sonnet 5
parent 83809815c1
commit c2552af079
11 changed files with 158 additions and 52 deletions
+10 -10
View File
@@ -4,7 +4,6 @@ import { Swiper, SwiperSlide } from "swiper/react";
import { FreeMode } from "swiper/modules";
import "swiper/css";
import "swiper/css/free-mode";
import { Placeholder } from "@/components/ui/Placeholder";
import { SectionTitle } from "@/components/ui/SectionTitle";
import { EmptyState } from "@/components/ui/EmptyState";
import type { Category, StaticImageWithUrl } from "@/lib/meshkee";
@@ -13,9 +12,9 @@ function CategorySlotCard({ image }: { image: StaticImageWithUrl }) {
const label = image.titleFa ?? image.titleEn ?? "";
const content = (
<div className="group flex h-full flex-col items-center rounded-xl border border-border bg-surface p-3 transition-all hover:border-red hover:shadow-md">
<div className="mb-3 aspect-square w-[72px] overflow-hidden rounded-full border border-border">
<div className="mb-3 w-[110px] overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={image.url} alt={label} className="h-full w-full object-cover" />
<img src={image.url} alt={label} className="h-full w-full object-contain" />
</div>
{label && (
<span className="w-full truncate text-center text-xs font-medium leading-relaxed md:text-sm">
@@ -87,13 +86,14 @@ export function FeaturedCategories({
className="animate-slide-in group flex h-full flex-col items-center rounded-xl border border-border bg-surface p-3 transition-all hover:border-red hover:shadow-md"
style={{ animationDelay: `${i * 60}ms` }}
>
<Placeholder
src={cat.image ?? "/images/placeholder.svg"}
alt={cat.name}
width="72px"
height="72px"
className="mb-3 rounded-full transition-colors group-hover:border-red"
/>
<div className="mb-3 w-[110px] overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={cat.image ?? "/images/placeholder.svg"}
alt={cat.name}
className="h-full w-full object-contain"
/>
</div>
<span className="w-full truncate text-center text-xs font-medium leading-relaxed md:text-sm">
{cat.name}
</span>