Initial commit: Hoomat storefront with Meshkee API and static-image slots.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRef } from "react";
|
||||
|
||||
type Product = {
|
||||
title: string;
|
||||
enName: string;
|
||||
price: string;
|
||||
image: string;
|
||||
href?: string;
|
||||
};
|
||||
|
||||
type SaleBanner = {
|
||||
image?: string | null;
|
||||
linkUrl?: string | null;
|
||||
ariaLabel?: string;
|
||||
};
|
||||
|
||||
function ProductPrice({ price }: { price: string }) {
|
||||
const trimmed = price.trim();
|
||||
const match = trimmed.match(/^([\d٬,.\s]+)\s*(تومان)$/);
|
||||
|
||||
if (match) {
|
||||
return (
|
||||
<p className="price price-with-amount" dir="ltr">
|
||||
<span className="price-amount ltr-number" lang="en">
|
||||
{match[1].trim()}
|
||||
</span>
|
||||
<span className="price-unit">تومان</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <p className="price price-note">{trimmed}</p>;
|
||||
}
|
||||
|
||||
function ProductCard({ product }: { product: Product }) {
|
||||
return (
|
||||
<Link
|
||||
href={product.href || "/store"}
|
||||
className="product-card"
|
||||
aria-label={product.title}
|
||||
>
|
||||
<Image src={product.image} alt={product.title} width={560} height={560} />
|
||||
<div className="product-body">
|
||||
<p className="product-en-name">{product.enName}</p>
|
||||
<h3>{product.title}</h3>
|
||||
<ProductPrice price={product.price} />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function SaleBannerCard({ banner }: { banner?: SaleBanner | null }) {
|
||||
const style = banner?.image
|
||||
? {
|
||||
backgroundImage: `linear-gradient(90deg, rgba(136, 63, 63, 0.55), rgba(0, 35, 75, 0.5)), url(${banner.image})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
}
|
||||
: undefined;
|
||||
const className = "sale sale-promo-card";
|
||||
const label = banner?.ariaLabel || "فروش ویژه";
|
||||
|
||||
if (banner?.linkUrl) {
|
||||
return (
|
||||
<a href={banner.linkUrl} className={className} style={style} aria-label={label} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href="/store" className={className} style={style} aria-label={label} />
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductCarousel({
|
||||
products,
|
||||
ariaLabel,
|
||||
withSaleBanner = false,
|
||||
saleBanner,
|
||||
}: {
|
||||
products: Product[];
|
||||
ariaLabel: string;
|
||||
withSaleBanner?: boolean;
|
||||
saleBanner?: SaleBanner | null;
|
||||
}) {
|
||||
const carouselRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollCarousel = (direction: "next" | "previous") => {
|
||||
const amount = 300;
|
||||
carouselRef.current?.scrollBy({
|
||||
left: direction === "next" ? -amount : amount,
|
||||
behavior: "smooth",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="product-carousel-shell">
|
||||
<button
|
||||
className="carousel-chevron carousel-chevron-left"
|
||||
type="button"
|
||||
aria-label="محصولات بعدی"
|
||||
onClick={() => scrollCarousel("next")}
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<div
|
||||
ref={carouselRef}
|
||||
className={`carousel ${withSaleBanner ? "sale-carousel" : ""}`}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{withSaleBanner ? <SaleBannerCard banner={saleBanner} /> : null}
|
||||
{products.map((product) => (
|
||||
<ProductCard key={`${ariaLabel}-${product.title}`} product={product} />
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
className="carousel-chevron carousel-chevron-right"
|
||||
type="button"
|
||||
aria-label="محصولات قبلی"
|
||||
onClick={() => scrollCarousel("previous")}
|
||||
>
|
||||
›
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user