Cart
Describes the shopper's cart as the cart API returns it: items, totals, coupon, free-shipping progress, and the Money price shape.
import { Cart, CartItem, Money, FreeShippingBar, CartItemOffer, CartItemAttachment, CartItemOption, CartOption } from '@salla.sa/twilight-theme-engine/types';In plain words
Cart is the shape of a shopping cart: the list of items, the totals, the applied coupon, and free_shipping_bar, which says how far the shopper is from free delivery.
Some prices can be written two ways: a plain number such as 115, or a Money object such as { amount: 115, currency: 'SAR' }. The type allows both (number | Money, where | means "or"), so your code has to check which one it got before doing any arithmetic.
Signature
interface Cart {
id: string;
count: number;
items: CartItem[];
sub_total: number; total: number | string; discount: number; tax_amount: number;
coupon?: string | null;
has_shipping: boolean; is_require_shipping?: boolean; real_shipping_cost: number;
free_shipping_bar?: FreeShippingBar | null;
options: CartOption[]; options_total?: number | null; total_discount?: number | null;
gift?: { enabled: boolean; text?: string; type?: string } | null;
loyalty?: { prize: { points: number; title: string } } | null;
should_refresh?: boolean;
}
interface CartItem {
id: string; product_id: number; product_name: string; product_image: string; url: string;
type: ProductType | string;
quantity: number; max_quantity?: number;
price: number | Money; product_price: number | Money; original_price?: number | Money;
total: number | Money; total_special_price?: number | Money;
is_available: boolean; is_hidden_quantity: boolean; is_on_sale?: boolean; has_discount?: boolean;
can_add_note: boolean; can_upload_file: boolean; notes?: string; weight_label?: string;
offer?: CartItemOffer | null; detailed_offers?: JsonObject[] | null; donation?: JsonObject | null;
attachments?: CartItemAttachment[]; options?: CartItemOption[];
has_pre_order_campaign?: boolean;
}
interface Money { amount: number; currency: string; formatted?: string }
interface FreeShippingBar { minimum_amount: number; has_free_shipping: boolean; percent: number; remaining: number }
interface CartItemOffer { discount: number; is_free: boolean; names?: string }
interface CartItemAttachment { id: number; url: string; product_id: number; item_id: number; name: string }
interface CartItemOption {
id: number; name?: string; value?: string; quantity?: number;
options?: Record<string, string | number | boolean>[];
}
interface CartOption { /* the same fields as CartItemOption */ }Try it live
cart.queries.detail() and checked against Cart. ✗ marks a value the type does not allow.Try this: with an item in the cart, switch the price field: each one is typed number | Money, so read it through a helper.import type { CartItem, Money } from '@salla.sa/twilight-theme-engine/types';
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';
/** Item prices are typed number | Money: never do math on them directly. */
function amountOf(price: number | Money): number {
return typeof price === 'number' ? price : price.amount;
}
export function ItemPrice({ item }: { item: CartItem }) {
const { format } = useMoney();
return <span className="item-price">{format(amountOf(item.price))}</span>;
}
Example
import type { CartItem, Money } from '@salla.sa/twilight-theme-engine/types';
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';
/** Cart item prices are typed number | Money: read the number either way. */
function amountOf(price: number | Money): number {
return typeof price === 'number' ? price : price.amount;
}
export function LineTotal({ item }: { item: CartItem }) {
const { format } = useMoney();
const currency = typeof item.total === 'object' ? item.total.currency : undefined;
return (
<span className="line-total">
{item.quantity} × {format(amountOf(item.price), { currency })}
{' = '}
{format(amountOf(item.total), { currency })}
</span>
);
}
How it behaves
The cart comes from
cart.get(cartId)orcart.queries.detail(cartId)(@salla.sa/twilight-theme-engine/api/cart), with the numeric id fromSalla.cart.api.getCurrentCartId()in the browser. The cart page props,CartSummary,CartItem,SeoCartWidget, the cart context anduseGtmall use this type.The API sends
loyaltyandgiftnext to the cart;cart.getmoves them inside and sets each tonullwhen absent.should_refreshis not sent by the API: the source marks it a React-only flag.The engine
CartItemcomponent reads prices the way the example does: the number itself, oramountandcurrencyfrom aMoney.FreeShippingBar.percentruns from 0 to 100 (CartSummaryuses it as a CSS width), andremainingis put into the "add :amount more" message unformatted.CartOption(an option on the whole cart) andCartItemOption(an option on one item) have the same fields.
Gotchas
price,product_price,original_price,totalandtotal_special_pricearenumber | Money.item.price * item.quantityanditem.price.amountdo not compile, and in plain JavaScript aMoneyobject in arithmetic givesNaN. Normalize first, as the example does.Cart.totalisnumber | string:cart.total + shippingjoins two strings whenever the total is text. UseNumber(cart.total).docs/07-data-types.md shows
items_count,Money.getMoney()and a requiredMoney.formatted. The code hascount, nogetMoney, and an optionalformatted:cart.items_countdoes not compile, and in plain JavaScript it isundefined.
Related
Reads a cart by its id and returns it with the loyalty prize and gift details merged into the cart object.
CartSummaryThe cart page sidebar: free-shipping bar, loyalty and gift widgets, totals, a coupon box and the Complete Order button.
CartItemOne line of the cart page: picture, name, prices, offers, a quantity input and a delete button that change the shopper's real cart.
useMoneyFormats prices in the page language (with the riyal icon for SAR), and parses or validates amounts.
CartContextProviderA React context for a cart object: wrap part of the page once, then read the cart anywhere inside it with useCartContext().