Wire up product variants end-to-end, add tracking-number display
Completes the frontend half of the Payload backend's variant/inventory/
tracking work (see that repo's own commit):
- **Cart**: CartItem gained an optional `variant?: string` field — every
function that used to match a line by `id` alone (addToCart/
removeFromCart/setQuantity) now matches by `(id, variant)` together via
a shared sameLine() helper, so two lines for the same product with
different variants stay separate entries. `variant` undefined on both
sides (the no-variants case) still matches by simple equality, so every
pre-existing call site keeps working unchanged.
- **Selection UI**: AddToCartInlineButton renders a <select> above the
button when given a non-empty `variants` prop (ProductGrid/
RelatedProducts pass product.variants straight through); defaults to
the first variant.
- **Pricing**: cartTotals.ts's new effectivePrice(entry, product) — a
variant's priceOverride wins over the base product price. Every cart/
checkout/order-confirmation total and per-line price display now goes
through this instead of reading product.price directly (fixes both a
wrong-price bug and a duplicate-React-key bug the old `key={product.id}`
pattern would have had the moment two variants of one product were both
in the cart).
- **Checkout**: re-validates the requested variant server-side (same
"never trust the client" reasoning as price re-derivation) — a variant
name that doesn't exist on that product fails the whole checkout.
variantName snapshots onto orders.items, shown as a parenthetical next
to the product name on the confirmation email, both invoice PDF types,
and the order-detail page.
- **Cross-device cart**: Customers.cart[].variantName (synced via
/api/account/cart) carries the selection through a login/logout cycle,
not just the current session.
Also adds tracking-number display: /konto/bestellungen/[orderNumber]
shows a clickable link when orders.trackingNumber is set, built by a new
app/lib/tracking.ts that mirrors the Payload backend's own copy
byte-for-byte close (same carrier set/URL patterns) so what a customer
sees here matches exactly what the order-shipped email already links to.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -217,7 +217,7 @@ type PayloadCustomerMe = {
|
||||
zip: string | null;
|
||||
city: string | null;
|
||||
country: string | null;
|
||||
cart: { product: number; productSlug: string; quantity: number }[] | null;
|
||||
cart: { product: number; productSlug: string; quantity: number; variantName: string | null }[] | null;
|
||||
};
|
||||
|
||||
export async function getCustomerProfile(token: string): Promise<CustomerProfile | null> {
|
||||
@@ -353,19 +353,21 @@ export async function getServerCart(token: string): Promise<CartItem[]> {
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const data: { user: PayloadCustomerMe | null } = await res.json();
|
||||
return (data.user?.cart ?? []).map((line) => ({ id: line.productSlug, qty: line.quantity }));
|
||||
return (data.user?.cart ?? []).map((line) =>
|
||||
line.variantName ? { id: line.productSlug, qty: line.quantity, variant: line.variantName } : { id: line.productSlug, qty: line.quantity },
|
||||
);
|
||||
}
|
||||
|
||||
export async function saveServerCart(
|
||||
token: string,
|
||||
customerId: number,
|
||||
cart: { productId: number; productSlug: string; quantity: number }[],
|
||||
cart: { productId: number; productSlug: string; quantity: number; variant?: string }[],
|
||||
): Promise<boolean> {
|
||||
const res = await fetch(`${PAYLOAD_URL}/api/customers/${customerId}`, {
|
||||
method: "PATCH",
|
||||
headers: { Authorization: `JWT ${token}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
cart: cart.map((line) => ({ product: line.productId, productSlug: line.productSlug, quantity: line.quantity })),
|
||||
cart: cart.map((line) => ({ product: line.productId, productSlug: line.productSlug, quantity: line.quantity, variantName: line.variant ?? null })),
|
||||
}),
|
||||
});
|
||||
return res.ok;
|
||||
@@ -428,6 +430,8 @@ export type CustomerOrderDetail = CustomerOrder & {
|
||||
invoiceIssuedAt: string | null;
|
||||
correctionInvoiceNumber: string | null;
|
||||
correctionInvoiceIssuedAt: string | null;
|
||||
carrier: string | null;
|
||||
trackingNumber: string | null;
|
||||
customerFirstName: string;
|
||||
customerLastName: string;
|
||||
customerEmail: string;
|
||||
@@ -455,6 +459,7 @@ export type CustomerOrderItem = {
|
||||
unitPrice: number;
|
||||
taxRatePercent: number;
|
||||
bundleContents: string | null;
|
||||
variantName: string | null;
|
||||
returnQuantity: number;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user