Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | 4x 9x 7x 2x 3x 3x 3x 27x 9x 9x 9x 8x 1x 2x 5x 2x 2x 1x 1x 3x 2x 2x 1x 1x 2x 2x 2x 22x 22x | import type { CartAction, CartLineItem, CartState } from "@/lib/cart/cart-types";
export const initialCartState: CartState = {
isOpen: false,
items: [],
};
function normalizeQuantity(quantity: number | undefined): number {
if (!quantity || !Number.isFinite(quantity)) {
return 1;
}
return Math.max(1, Math.floor(quantity));
}
function replaceLineItem(
items: readonly CartLineItem[],
slug: string,
quantity: number,
): CartLineItem[] {
return items
.map((item) => (item.product.slug === slug ? { ...item, quantity } : item))
.filter((item) => item.quantity > 0);
}
/**
* Pure cart state reducer used by the cart context and unit tests.
*/
export function cartReducer(state: CartState, action: CartAction): CartState {
switch (action.type) {
case "add": {
const quantity = normalizeQuantity(action.quantity);
const existingItem = state.items.find((item) => item.product.slug === action.product.slug);
if (!existingItem) {
return {
...state,
isOpen: true,
items: [...state.items, { product: action.product, quantity }],
};
}
return {
...state,
isOpen: true,
items: replaceLineItem(state.items, action.product.slug, existingItem.quantity + quantity),
};
}
case "clear":
return { ...state, items: [] };
case "close":
return { ...state, isOpen: false };
case "decrement": {
const existingItem = state.items.find((item) => item.product.slug === action.slug);
if (!existingItem) {
return state;
}
return {
...state,
items: replaceLineItem(state.items, action.slug, existingItem.quantity - 1),
};
}
case "hydrate":
return { ...state, items: action.items };
case "increment": {
const existingItem = state.items.find((item) => item.product.slug === action.slug);
if (!existingItem) {
return state;
}
return {
...state,
items: replaceLineItem(state.items, action.slug, existingItem.quantity + 1),
};
}
case "open":
return { ...state, isOpen: true };
case "remove":
return {
...state,
items: state.items.filter((item) => item.product.slug !== action.slug),
};
}
}
export function getCartItemCount(items: readonly CartLineItem[]): number {
return items.reduce((total, item) => total + item.quantity, 0);
}
/**
* Calculates the shopper-facing subtotal in integer cents.
*/
export function getCartSubtotal(items: readonly CartLineItem[]): number {
return items.reduce((total, item) => total + item.product.priceCents * item.quantity, 0);
}
|