Replaces the previous server-cart implementation (which wrongly called
the customer-dashboard-only /businesses/{id}/cart* API) with the
documented guest-cart architecture:
- src/lib/guestCart.ts: storage key/cookie name, cookie attributes,
line shape, and encodeGuestCart() copied verbatim from AI_PROMPT.md
- CartProvider/CartButton: pure client state (localStorage + mirrored
cookie), no bearer auth, no businessId
- CartButton's "ادامه" builds the exact documented Continue URL,
branching on hasCustomerSessionCookie() to either
customer.<domain>/checkout/cart?guestCart=<encoded> or
.../login?redirect=<encoded checkout path>
- Auth cookie corrected to the real documented name
(meshkee_customer_access_token, possibly chunked) via a shared
reassembleChunkedCookie() helper used by both auth.ts (server) and
authUser.ts (client)
- ProductBuyPanel/product pages switched to the documented
GET .../store-items/by-product/{id} endpoint (getStoreItemByProduct)
- isInStock() treats stockQuantity === null as unlimited stock, not
out-of-stock, per spec
- AGENTS.md updated to describe this architecture and remove the
incorrect server-cart documentation
Manually verified in-browser: badge count, popup lines/qty/remove/
total all update correctly against injected guest-cart data; tsc,
eslint, and next build all pass clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
72 lines
2.7 KiB
TypeScript
72 lines
2.7 KiB
TypeScript
import { discountPercent, isInStock } from "./format";
|
|
import { productHref } from "./slug";
|
|
import { DEMO_PRICING, demoPrice } from "./demoPricing";
|
|
import type { ApiProduct, ProductCardData, StoreItem, StoreSpecial } from "./types";
|
|
|
|
export function productToCard(product: ApiProduct): ProductCardData {
|
|
const base = {
|
|
id: product.id,
|
|
href: productHref(product.id, product.nameFa, product.title),
|
|
title: product.nameFa || product.title,
|
|
image: product.thumbnailUrl || product.image || product.thumbnail,
|
|
};
|
|
|
|
if (product.store.minPrice !== null) {
|
|
return { ...base, price: product.store.minPrice, oldPrice: null, percent: 0, inStock: product.store.inStock };
|
|
}
|
|
|
|
if (DEMO_PRICING) {
|
|
const { price, oldPrice } = demoPrice(product);
|
|
return {
|
|
...base,
|
|
price,
|
|
oldPrice,
|
|
percent: oldPrice ? discountPercent(price, oldPrice) : 0,
|
|
inStock: true,
|
|
};
|
|
}
|
|
|
|
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[] {
|
|
return special.items
|
|
.filter((item) => item.isActive)
|
|
.map((item) => {
|
|
const variant = item.variants.find((v) => v.discountedPrice !== null) ?? item.variants[0];
|
|
const price = variant?.discountedPrice ?? variant?.price ?? null;
|
|
const oldPrice = variant?.discountedPrice !== null ? variant?.price ?? null : null;
|
|
return {
|
|
id: item.id,
|
|
href: productHref(item.productId, item.productNameFa, item.productTitle),
|
|
title: item.productNameFa || item.productTitle,
|
|
image: item.productImage,
|
|
price,
|
|
oldPrice,
|
|
percent: price !== null && oldPrice !== null ? discountPercent(price, oldPrice) : 0,
|
|
inStock: isInStock(variant?.stockQuantity),
|
|
};
|
|
});
|
|
}
|