- Real in-site login/register (password + SMS OTP) against Meshkee's /auth API, with self-healing access-token refresh (15 min lifetime) and an in-flight-refresh guard so AuthContext and CartContext don't race each other into a failed concurrent refresh. - Real cart: header cart button/badge, a cart popup, and a working "add to cart" on both product cards (where a specific variant id is known) and the product/store-item detail buy-box (always resolves a purchasable variant, even for single-SKU products with no selectable variation). - /checkout: address + cash/bank-transfer payment, posts to the real checkout API and creates an order; refreshes the cart afterward so the header badge doesn't go stale. - New /store-items and /store-items/[id] pages (variant-level browsing, separate from the product-level /products catalog), cross-referencing the parent product for images/description/technical-info. - Product gallery: click-to-open fullscreen lightbox alongside the existing hover-zoom. - Comments tab wired to the real comments API, gated to logged-in users (a product decision — the API itself doesn't require auth) with a login prompt otherwise. - Similar-products row on product/store-item detail pages. - Contact page wired to the real contact-submissions API, with a toast and inline confirmation on success, and the form resetting. - CONTEXT.md: a maintained architecture/conventions doc for this repo, now auto-loaded via CLAUDE.md, covering the Meshkee integration quirks and UI conventions established across this project. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
54 lines
1.8 KiB
TypeScript
54 lines
1.8 KiB
TypeScript
import { Carousel } from "@/components/ui/Carousel";
|
|
import { ProductCard } from "@/components/ui/ProductCard";
|
|
import { SectionTitle } from "@/components/ui/SectionTitle";
|
|
import { EmptyState } from "@/components/ui/EmptyState";
|
|
import { productHref, type ProductGroup } from "@/lib/meshkee";
|
|
|
|
export function BrandGroupCarousels({
|
|
brandGroups,
|
|
contactPhone,
|
|
}: {
|
|
brandGroups: ProductGroup[];
|
|
contactPhone?: string;
|
|
}) {
|
|
if (brandGroups.length === 0) {
|
|
return (
|
|
<section className="py-10 md:py-14">
|
|
<div className="mx-auto max-w-7xl px-4">
|
|
<SectionTitle title="محصولات برندها" enTitle="Brand Collections" />
|
|
<EmptyState message="هنوز محصولی برای برندها ثبت نشده است." />
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{brandGroups.map((group) => (
|
|
<section key={group.id} className="py-10 md:py-14">
|
|
<div className="mx-auto max-w-7xl px-4">
|
|
<SectionTitle title={group.title} enTitle={group.title} action="مشاهده همه" />
|
|
<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"}
|
|
variant="brand"
|
|
href={productHref(product)}
|
|
contactPhone={contactPhone}
|
|
variantId={product.variantId}
|
|
/>
|
|
))}
|
|
</Carousel>
|
|
</div>
|
|
</section>
|
|
))}
|
|
</>
|
|
);
|
|
}
|