158 lines
7.5 KiB
TypeScript
158 lines
7.5 KiB
TypeScript
import { Fragment } from "react";
|
|
import { Carousel } from "@/components/ui/Carousel";
|
|
import { ProductCard } from "@/components/ui/ProductCard";
|
|
import { SectionTitle } from "@/components/ui/SectionTitle";
|
|
import { ChevronLeftIcon } from "@/components/ui/icons";
|
|
import { TwoBanners } from "@/components/home/TwoBanners";
|
|
import { ThreeBanners } from "@/components/home/ThreeBanners";
|
|
import { SpecialSaleCountdown } from "@/components/home/SpecialSaleCountdown";
|
|
import {
|
|
isSpecialSaleGroup,
|
|
productHref,
|
|
type ProductGroup,
|
|
type StaticImageWithUrl,
|
|
} from "@/lib/meshkee";
|
|
|
|
// The API only gives one `title` string per group (sometimes already
|
|
// English, e.g. "Special Sale", sometimes Persian) — there's no separate
|
|
// localized English field to pull a real translation from, and inventing
|
|
// one would be fabricated data. `key` is real data and usually a readable
|
|
// English slug (e.g. "top-selling"), so it's what gets formatted into the
|
|
// English caption beside the title (same inline treatment as every other
|
|
// section heading on the site, e.g. BlogCarousel's "LATEST BLOG"). Skip it
|
|
// if the key has no letters at all (e.g. it fell back to a bare numeric id).
|
|
function englishLabelFromKey(key: string): string | undefined {
|
|
const label = key.replace(/[-_]+/g, " ").trim();
|
|
return /[a-zA-Z]/.test(label) ? label.toUpperCase() : undefined;
|
|
}
|
|
|
|
function SpecialSaleSection({ group }: { group: ProductGroup }) {
|
|
return (
|
|
<section className="bg-red py-6 md:py-8">
|
|
<div className="mx-auto flex max-w-7xl items-stretch gap-3 px-4 md:gap-5">
|
|
<aside className="my-2 flex w-[calc(50%-1rem)] shrink-0 flex-col items-center justify-center rounded-2xl bg-red-dark/35 p-3 text-center text-white md:w-[240px] md:p-6">
|
|
<svg
|
|
viewBox="0 0 32 32"
|
|
className="mb-3 h-11 w-11 md:h-14 md:w-14"
|
|
aria-hidden="true"
|
|
>
|
|
<g transform="matrix(1.05 0 0 1.05 -.8 -.8006)">
|
|
<path
|
|
fill="currentColor"
|
|
d="m30.25 14.182-.876-.876c-.36-.36-.577-1.168-.445-1.661l.32-1.197a2.54 2.54 0 0 0-.259-1.949 2.55 2.55 0 0 0-1.559-1.198l-1.196-.321c-.492-.132-1.084-.724-1.217-1.215l-.32-1.197C24.34 3.232 22.899 2.39 21.55 2.75l-1.196.321c-.459.122-1.316-.101-1.66-.445l-.877-.876c-.969-.969-2.666-.969-3.635 0l-.877.877c-.344.344-1.201.567-1.661.445l-1.195-.321c-1.349-.361-2.79.481-3.148 1.818l-.32 1.195c-.132.494-.724 1.085-1.216 1.217l-1.197.321c-.662.177-1.216.603-1.56 1.199s-.435 1.288-.257 1.947l.319 1.198c.132.493-.084 1.3-.444 1.661l-.876.876C1.267 14.667 1 15.313 1 16s.267 1.333.751 1.817l.876.876c.36.36.577 1.168.445 1.661l-.32 1.197a2.54 2.54 0 0 0 .259 1.949 2.55 2.55 0 0 0 1.559 1.198l1.195.32c.493.132 1.085.724 1.218 1.215l.319 1.197c.358 1.336 1.804 2.175 3.147 1.817l1.197-.32c.46-.122 1.317.101 1.661.445l.876.875c.484.486 1.13.753 1.818.753s1.334-.267 1.817-.75l.877-.876c.345-.344 1.205-.565 1.661-.445l1.195.32q.326.087.662.087h.001a2.575 2.575 0 0 0 2.485-1.905l.319-1.194c.133-.494.725-1.085 1.217-1.218l1.196-.32c.662-.177 1.216-.604 1.56-1.2s.435-1.288.257-1.947l-.319-1.198c-.132-.493.084-1.3.444-1.661l.876-.876c.484-.484.751-1.13.751-1.817a2.55 2.55 0 0 0-.75-1.818M9 12c0-1.654 1.346-3 3-3s3 1.346 3 3-1.346 3-3 3-3-1.346-3-3m3.707 8.707a.997.997 0 0 1-1.414 0 1 1 0 0 1 0-1.414l8-8a.999.999 0 1 1 1.414 1.414zM20 23c-1.654 0-3-1.346-3-3s1.346-3 3-3 3 1.346 3 3-1.346 3-3 3"
|
|
/>
|
|
<circle cx="20" cy="20" r="1" fill="currentColor" />
|
|
<circle cx="12" cy="12" r="1" fill="currentColor" />
|
|
</g>
|
|
</svg>
|
|
<h2 className="text-lg font-bold md:text-2xl">{group.title}</h2>
|
|
<p className="mt-1.5 mb-4 text-xs text-white/80 md:text-sm">تا پایان فروش ویژه</p>
|
|
<SpecialSaleCountdown />
|
|
<a
|
|
href={`/store-items?specialId=${group.id}`}
|
|
className="mt-4 flex shrink-0 items-center gap-1.5 rounded-lg bg-white px-4 py-2 text-sm font-medium text-black transition-colors hover:bg-white/90"
|
|
>
|
|
مشاهده همه
|
|
<ChevronLeftIcon className="h-4 w-4" />
|
|
</a>
|
|
</aside>
|
|
|
|
<div className="min-w-0 flex-1">
|
|
<Carousel
|
|
ariaLabel={`کاروسل ${group.title}`}
|
|
slideClassName="!w-full md:!w-[240px]"
|
|
>
|
|
{group.items.map((product) => (
|
|
<ProductCard
|
|
key={product.id}
|
|
name={product.name}
|
|
brand={product.brand ?? ""}
|
|
price={product.price}
|
|
oldPrice={product.oldPrice}
|
|
discount={product.discount}
|
|
image={product.image ?? "/images/placeholder.svg"}
|
|
href={productHref(product)}
|
|
variant="sale"
|
|
fluid
|
|
/>
|
|
))}
|
|
</Carousel>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
// Renders every admin-managed carousel from GET /store-specials — whatever
|
|
// groups exist there, in their configured order. Nothing here is keyed off
|
|
// a specific group id/key/title, so a new carousel created in the Meshkee
|
|
// dashboard (any name) shows up on the homepage automatically, with no code
|
|
// change. Groups with no items are skipped (honest empty state — no heading
|
|
// over nothing).
|
|
//
|
|
// One of the two banner sections is inserted between every pair of
|
|
// carousels (never before the first or after the last), cycling if there
|
|
// end up being more than two carousels.
|
|
export function StoreSpecialCarousels({
|
|
groups,
|
|
contactPhone,
|
|
twoBannerImages,
|
|
twoBannerAspectRatio,
|
|
threeBannerImages,
|
|
threeBannerAspectRatio,
|
|
}: {
|
|
groups: ProductGroup[];
|
|
contactPhone?: string;
|
|
twoBannerImages?: StaticImageWithUrl[];
|
|
twoBannerAspectRatio?: string;
|
|
threeBannerImages?: StaticImageWithUrl[];
|
|
threeBannerAspectRatio?: string;
|
|
}) {
|
|
const visibleGroups = groups.filter((group) => group.items.length > 0);
|
|
const banners = [
|
|
<TwoBanners key="two" images={twoBannerImages} aspectRatio={twoBannerAspectRatio} />,
|
|
<ThreeBanners key="three" images={threeBannerImages} aspectRatio={threeBannerAspectRatio} />,
|
|
];
|
|
|
|
return (
|
|
<>
|
|
{visibleGroups.map((group, i) => (
|
|
<Fragment key={group.id}>
|
|
{i > 0 && banners[(i - 1) % banners.length]}
|
|
{isSpecialSaleGroup(group) ? (
|
|
<SpecialSaleSection group={group} />
|
|
) : (
|
|
<section className="py-10 md:py-14">
|
|
<div className="mx-auto max-w-7xl px-4">
|
|
<SectionTitle
|
|
title={group.title}
|
|
enTitle={englishLabelFromKey(group.key)}
|
|
action="مشاهده همه"
|
|
actionHref={`/store-items?specialId=${group.id}`}
|
|
/>
|
|
<Carousel ariaLabel={`کاروسل ${group.title}`}>
|
|
{group.items.map((product) => (
|
|
<ProductCard
|
|
key={product.id}
|
|
name={product.name}
|
|
brand={product.brand ?? ""}
|
|
price={product.price}
|
|
oldPrice={product.oldPrice}
|
|
discount={product.discount}
|
|
image={product.image ?? "/images/placeholder.svg"}
|
|
href={productHref(product)}
|
|
contactPhone={contactPhone}
|
|
variantId={product.variantId}
|
|
slug={product.slug}
|
|
/>
|
|
))}
|
|
</Carousel>
|
|
</div>
|
|
</section>
|
|
)}
|
|
</Fragment>
|
|
))}
|
|
</>
|
|
);
|
|
}
|