Add shopping cart, store-items pages, contact form toast; skip workshops+portfolio
Neither workshops nor portfolio is in this tenant's enabledModules
(["products","blog","finance"]) - same reasoning as before, page/nav entry
withheld until either is actually enabled.
Shopping cart (new):
- CartProvider (client context): live cart state, add/update/remove/refresh,
reads AUTH_TOKEN_COOKIE, calls the real /businesses/{id}/cart* endpoints
(bearer-only, no guest cart - by design, not a gap)
- CartButton: header/bottom-nav icon with a live count badge and a popup
preview; "ادامه" hands off to the customer portal's own cart/checkout,
since this storefront has no checkout UI (address/payment) to hand off to
internally - out of scope for this pass
- ProductBuyPanel: quantity stepper, wired to CartProvider.addItem() instead
of its own ad-hoc fetch+token logic, toast on success
- Cart/CartItem types are a best-effort guess at an undocumented response
shape (OpenAPI gives no field-level schema) - unverified against real data
since store-items are still empty; read every field defensively
Store-items pages (new): /store-items (flat listing of purchasable variants,
always priced unlike /products) and /store-items/{id} (detail - no slug,
resolves the variant's productId and reuses the product's own gallery/
description/technical-info/comments/related-products, just with that
variant preselected).
Contact form: now shows a toast + inline success text and resets while
staying on screen, instead of swapping to a separate "thank you" panel.
New shared providers: ToastProvider (useToast) and CartProvider, both
mounted once in layout.tsx.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e676397bae
commit
d624ad8a32
+20
-1
@@ -1,7 +1,7 @@
|
||||
import { discountPercent } from "./format";
|
||||
import { productHref } from "./slug";
|
||||
import { DEMO_PRICING, demoPrice } from "./demoPricing";
|
||||
import type { ApiProduct, ProductCardData, StoreSpecial } from "./types";
|
||||
import type { ApiProduct, ProductCardData, StoreItem, StoreSpecial } from "./types";
|
||||
|
||||
export function productToCard(product: ApiProduct): ProductCardData {
|
||||
const base = {
|
||||
@@ -29,6 +29,25 @@ export function productToCard(product: ApiProduct): ProductCardData {
|
||||
return { ...base, price: null, oldPrice: null, percent: 0, inStock: false };
|
||||
}
|
||||
|
||||
// Unlike products, every store-item always has a real price/stock — that's
|
||||
// the whole point of the /store-items endpoint — so this never touches
|
||||
// demoPricing. Links to the item's own /store-items/{id} detail page, not
|
||||
// the parent product's page.
|
||||
export function storeItemToCard(item: StoreItem): ProductCardData {
|
||||
const price = item.discountedPrice ?? item.price;
|
||||
const oldPrice = item.discountedPrice !== null ? item.price : null;
|
||||
return {
|
||||
id: item.id,
|
||||
href: `/store-items/${item.id}`,
|
||||
title: item.productNameFa || item.productTitle,
|
||||
image: item.productImage,
|
||||
price,
|
||||
oldPrice,
|
||||
percent: price !== null && oldPrice !== null ? discountPercent(price, oldPrice) : 0,
|
||||
inStock: item.stockQuantity > 0,
|
||||
};
|
||||
}
|
||||
|
||||
// A curated store-special group carries its own variant pricing, so the
|
||||
// discount shown there is the real one rather than a product-level guess.
|
||||
export function specialToCards(special: StoreSpecial): ProductCardData[] {
|
||||
|
||||
Reference in New Issue
Block a user