Add customer auth, real cart/checkout, store-items pages, comments, contact form
- 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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c2552af079
commit
66f92b634e
@@ -0,0 +1,45 @@
|
||||
import { Carousel } from "@/components/ui/Carousel";
|
||||
import { ProductCard } from "@/components/ui/ProductCard";
|
||||
import { getProducts, productHref, type Product } from "@/lib/meshkee";
|
||||
|
||||
export async function SimilarProducts({
|
||||
categoryId,
|
||||
excludeProductId,
|
||||
contactPhone,
|
||||
}: {
|
||||
categoryId?: string;
|
||||
excludeProductId: string;
|
||||
contactPhone?: string;
|
||||
}) {
|
||||
if (!categoryId) return null;
|
||||
|
||||
const { items } = await getProducts({ categoryId, pageSize: 9 });
|
||||
const products: Product[] = items.filter((p) => p.id !== excludeProductId).slice(0, 8);
|
||||
|
||||
if (products.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="mt-12 border-t border-border pt-10">
|
||||
<h2 className="mb-5 flex items-center gap-2 text-lg font-bold">
|
||||
<span className="h-2 w-2 shrink-0 rounded-full bg-red" aria-hidden />
|
||||
محصولات مشابه
|
||||
</h2>
|
||||
<Carousel ariaLabel="محصولات مشابه">
|
||||
{products.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}
|
||||
/>
|
||||
))}
|
||||
</Carousel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user