Setting the file. One moment.
Cart Store · Wix Headless Templates · wix/skills · Skills Docs
ContentsBack to the top of the page This file
Number 16.814
Position 814 of 828
Type TypeScript
Size 4 KB
Lines 134 storefront/project/src/wix/storefront/ cart-store.ts
TypeScript · 134 lines · 4 KB
removeLine
as
apiRemove,
12 setNote as apiSetNote,
13 updateQuantity as apiUpdate,
14 checkoutUrl,
15 type AddToCartExtras,
16 } from "./cart" ;
17
18 export interface CartState {
19 cart : Cart | null ;
20 /**
21 * True while any cart operation is in flight — the DRAWER's flag (its steppers, remove, coupon
22 * and checkout controls), not a card's: a product card disables its own add control on
23 * `pendingProductId`, or every card in the grid dims for every add.
24 */
25 busy : boolean ;
26 /** The product whose add is in flight, null otherwise — what a card's add control binds to. */
27 pendingProductId : string | null ;
28 /** Last failed operation's message — render it; a new operation clears it. */
29 error : string | null ;
30 /** Cart drawer visibility (CartButton opens it, CartDrawer renders by it). */
31 open : boolean ;
32 }
33
34 const EMPTY : CartState = { cart: null , busy: false , pendingProductId: null , error: null , open: false };
35
36 let state : CartState = EMPTY ;
37 const listeners = new Set <() => void >();
38 let loaded = false ;
39
40 function setState ( patch : Partial < CartState >) : void {
41 state = { ... state, ... patch };
42 for ( const l of listeners) l ();
43 }
44
45 export function getCartState () : CartState {
46 return state;
47 }
48
49 export function subscribeCart ( listener : () => void ) : () => void {
50 listeners. add (listener);
51 // First subscriber triggers the initial load (browser only — SSR renders the empty state).
52 if ( ! loaded && typeof window !== "undefined" ) {
53 loaded = true ;
54 void refreshCart ();
55 }
56 return () => listeners. delete (listener);
57 }
58
59 async function run ( op : () => Promise < Cart >) : Promise < void > {
60 setState ({ busy: true , error: null });
61 try {
62 setState ({ cart: await op (), busy: false });
63 } catch (e) {
64 setState ({ busy: false , error: e instanceof Error ? e.message : String (e) });
65 throw e;
66 }
67 }
68
69 export async function refreshCart () : Promise < void > {
70 try {
71 setState ({ cart: await fetchCart () });
72 } catch {
73 /* no cart yet — leave the empty state */
74 }
75 }
76
77 /** Add to cart and open the drawer. Throws (and sets .error) on refusal. */
78 export async function addLine (
79 productId : string ,
80 variantId ?: string | null ,
81 quantity = 1 ,
82 extras ?: AddToCartExtras ,
83 ) : Promise < void > {
84 setState ({ pendingProductId: productId });
85 try {
86 await run (() => apiAdd (productId, variantId, quantity, extras));
87 } finally {
88 setState ({ pendingProductId: null });
89 // Open either way. On success this shows the new line; on refusal it's the only thing that
90 // makes .error visible — the drawer renders it, and a drawer that stays shut on failure
91 // turns a refused add (out of stock, a digital product with no file) into silence.
92 setState ({ open: true });
93 }
94 }
95
96 export async function updateLineQuantity ( lineItemId : string , quantity : number ) : Promise < void > {
97 await run (() => apiUpdate (lineItemId, quantity));
98 }
99
100 export async function removeCartLine ( lineItemId : string ) : Promise < void > {
101 await run (() => apiRemove (lineItemId));
102 }
103
104 /** Apply a coupon code; rejects (and sets .error with buyer copy) when the code is unknown, expired, or doesn't apply. */
105 export async function applyCartCoupon ( code : string ) : Promise < void > {
106 if ( ! code. trim ()) return ;
107 await run (() => apiApplyCoupon (code));
108 }
109
110 export async function removeCartCoupon () : Promise < void > {
111 await run (() => apiRemoveCoupon ());
112 }
113
114 /** Save the buyer's note to the merchant (commit on blur, not on every keystroke). */
115 export async function setCartNote ( note : string ) : Promise < void > {
116 if ((state.cart?.note ?? "" ) === note. trim ()) return ;
117 await run (() => apiSetNote (note));
118 }
119
120 /** Navigate to the Wix-hosted checkout. Never hand-build a checkout URL. */
121 export async function goToCheckout () : Promise < void > {
122 setState ({ busy: true , error: null });
123 try {
124 const url = await checkoutUrl ();
125 window.location.href = url;
126 } catch (e) {
127 setState ({ busy: false , error: e instanceof Error ? e.message : String (e) });
128 throw e;
129 }
130 }
131
132 export function setCartOpen ( open : boolean ) : void {
133 setState ({ open });
134 }