@shopify/hydrogen 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { AttributeInput, CartBuyerIdentityInput, CartInput, CartLineInput, CartLineUpdateInput } from '../../storefront-api-types.js';
1
+ import { AttributeInput, CartBuyerIdentityInput, CartInput, CartLineInput, CartLineUpdateInput, CountryCode } from '../../storefront-api-types.js';
2
2
  import { CartAttributesUpdateMutation } from './graphql/CartAttributesUpdateMutation.js';
3
3
  import { CartBuyerIdentityUpdateMutation } from './graphql/CartBuyerIdentityUpdateMutation.js';
4
4
  import { CartCreateMutation } from './graphql/CartCreateMutation.js';
@@ -13,11 +13,13 @@ import { CartQueryQuery } from './graphql/CartQuery.js';
13
13
  *
14
14
  * See [cart API graphql mutations](https://shopify.dev/api/storefront/2022-07/objects/Cart)
15
15
  */
16
- export declare function useCartActions({ numCartLines, cartFragment, }: {
16
+ export declare function useCartActions({ numCartLines, cartFragment, countryCode, }: {
17
17
  /** Maximum number of cart lines to fetch. Defaults to 250 cart lines. */
18
18
  numCartLines?: number;
19
19
  /** A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/latest/objects/cart) for all queries and mutations. A default value is used if no argument is provided. */
20
20
  cartFragment?: string;
21
+ /** The ISO country code for i18n. */
22
+ countryCode?: CountryCode;
21
23
  }): {
22
24
  cartFetch: (cartId: string) => Promise<{
23
25
  data: CartQueryQuery | undefined;
@@ -7,7 +7,7 @@ import { useCartFetch } from './hooks.client.js';
7
7
  *
8
8
  * See [cart API graphql mutations](https://shopify.dev/api/storefront/2022-07/objects/Cart)
9
9
  */
10
- export function useCartActions({ numCartLines, cartFragment = defaultCartFragment, }) {
10
+ export function useCartActions({ numCartLines, cartFragment = defaultCartFragment, countryCode = CountryCode.Us, }) {
11
11
  const fetchCart = useCartFetch();
12
12
  const cartFetch = useCallback((cartId) => {
13
13
  return fetchCart({
@@ -15,20 +15,20 @@ export function useCartActions({ numCartLines, cartFragment = defaultCartFragmen
15
15
  variables: {
16
16
  id: cartId,
17
17
  numCartLines,
18
- country: CountryCode.Us,
18
+ country: countryCode,
19
19
  },
20
20
  });
21
- }, [fetchCart, cartFragment, numCartLines]);
21
+ }, [fetchCart, cartFragment, numCartLines, countryCode]);
22
22
  const cartCreate = useCallback((cart) => {
23
23
  return fetchCart({
24
24
  query: CartCreate(cartFragment),
25
25
  variables: {
26
26
  input: cart,
27
27
  numCartLines,
28
- country: CountryCode.Us,
28
+ country: countryCode,
29
29
  },
30
30
  });
31
- }, [cartFragment, fetchCart, numCartLines]);
31
+ }, [cartFragment, countryCode, fetchCart, numCartLines]);
32
32
  const cartLineAdd = useCallback((cartId, lines) => {
33
33
  return fetchCart({
34
34
  query: CartLineAdd(cartFragment),
@@ -36,10 +36,10 @@ export function useCartActions({ numCartLines, cartFragment = defaultCartFragmen
36
36
  cartId,
37
37
  lines,
38
38
  numCartLines,
39
- country: CountryCode.Us,
39
+ country: countryCode,
40
40
  },
41
41
  });
42
- }, [cartFragment, fetchCart, numCartLines]);
42
+ }, [cartFragment, countryCode, fetchCart, numCartLines]);
43
43
  const cartLineUpdate = useCallback((cartId, lines) => {
44
44
  return fetchCart({
45
45
  query: CartLineUpdate(cartFragment),
@@ -47,10 +47,10 @@ export function useCartActions({ numCartLines, cartFragment = defaultCartFragmen
47
47
  cartId,
48
48
  lines,
49
49
  numCartLines,
50
- country: CountryCode.Us,
50
+ country: countryCode,
51
51
  },
52
52
  });
53
- }, [cartFragment, fetchCart, numCartLines]);
53
+ }, [cartFragment, countryCode, fetchCart, numCartLines]);
54
54
  const cartLineRemove = useCallback((cartId, lines) => {
55
55
  return fetchCart({
56
56
  query: CartLineRemove(cartFragment),
@@ -58,10 +58,10 @@ export function useCartActions({ numCartLines, cartFragment = defaultCartFragmen
58
58
  cartId,
59
59
  lines,
60
60
  numCartLines,
61
- country: CountryCode.Us,
61
+ country: countryCode,
62
62
  },
63
63
  });
64
- }, [cartFragment, fetchCart, numCartLines]);
64
+ }, [cartFragment, countryCode, fetchCart, numCartLines]);
65
65
  const noteUpdate = useCallback((cartId, note) => {
66
66
  return fetchCart({
67
67
  query: CartNoteUpdate(cartFragment),
@@ -69,10 +69,10 @@ export function useCartActions({ numCartLines, cartFragment = defaultCartFragmen
69
69
  cartId,
70
70
  note,
71
71
  numCartLines,
72
- country: CountryCode.Us,
72
+ country: countryCode,
73
73
  },
74
74
  });
75
- }, [fetchCart, cartFragment, numCartLines]);
75
+ }, [fetchCart, cartFragment, numCartLines, countryCode]);
76
76
  const buyerIdentityUpdate = useCallback((cartId, buyerIdentity) => {
77
77
  return fetchCart({
78
78
  query: CartBuyerIdentityUpdate(cartFragment),
@@ -80,10 +80,10 @@ export function useCartActions({ numCartLines, cartFragment = defaultCartFragmen
80
80
  cartId,
81
81
  buyerIdentity,
82
82
  numCartLines,
83
- country: CountryCode.Us,
83
+ country: countryCode,
84
84
  },
85
85
  });
86
- }, [cartFragment, fetchCart, numCartLines]);
86
+ }, [cartFragment, countryCode, fetchCart, numCartLines]);
87
87
  const cartAttributesUpdate = useCallback((cartId, attributes) => {
88
88
  return fetchCart({
89
89
  query: CartAttributesUpdate(cartFragment),
@@ -91,10 +91,10 @@ export function useCartActions({ numCartLines, cartFragment = defaultCartFragmen
91
91
  cartId,
92
92
  attributes,
93
93
  numCartLines,
94
- country: CountryCode.Us,
94
+ country: countryCode,
95
95
  },
96
96
  });
97
- }, [cartFragment, fetchCart, numCartLines]);
97
+ }, [cartFragment, countryCode, fetchCart, numCartLines]);
98
98
  const discountCodesUpdate = useCallback((cartId, discountCodes) => {
99
99
  return fetchCart({
100
100
  query: CartDiscountCodesUpdate(cartFragment),
@@ -102,10 +102,10 @@ export function useCartActions({ numCartLines, cartFragment = defaultCartFragmen
102
102
  cartId,
103
103
  discountCodes,
104
104
  numCartLines,
105
- country: CountryCode.Us,
105
+ country: countryCode,
106
106
  },
107
107
  });
108
- }, [cartFragment, fetchCart, numCartLines]);
108
+ }, [cartFragment, countryCode, fetchCart, numCartLines]);
109
109
  return useMemo(() => ({
110
110
  cartFetch,
111
111
  cartCreate,
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { CartFragmentFragment } from './graphql/CartFragment.js';
3
- export declare function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, onLineRemove, onLineUpdate, onNoteUpdate, onBuyerIdentityUpdate, onAttributesUpdate, onDiscountCodesUpdate, onCreateComplete, onLineAddComplete, onLineRemoveComplete, onLineUpdateComplete, onNoteUpdateComplete, onBuyerIdentityUpdateComplete, onAttributesUpdateComplete, onDiscountCodesUpdateComplete, data, cartFragment, }: {
3
+ import { CartBuyerIdentityInput, CountryCode } from '../../storefront-api-types.js';
4
+ export declare function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, onLineRemove, onLineUpdate, onNoteUpdate, onBuyerIdentityUpdate, onAttributesUpdate, onDiscountCodesUpdate, onCreateComplete, onLineAddComplete, onLineRemoveComplete, onLineUpdateComplete, onNoteUpdateComplete, onBuyerIdentityUpdateComplete, onAttributesUpdateComplete, onDiscountCodesUpdateComplete, data, cartFragment, customerAccessToken, countryCode, }: {
4
5
  /** Any `ReactNode` elements. */
5
6
  children: React.ReactNode;
6
7
  /** Maximum number of cart lines to fetch. Defaults to 250 cart lines. */
@@ -41,4 +42,8 @@ export declare function CartProviderV2({ children, numCartLines, onCreate, onLin
41
42
  data?: CartFragmentFragment;
42
43
  /** A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/latest/objects/cart) for all queries and mutations. A default value is used if no argument is provided. */
43
44
  cartFragment?: string;
45
+ /** A customer access token that's accessible on the server if there's a customer login. */
46
+ customerAccessToken?: CartBuyerIdentityInput['customerAccessToken'];
47
+ /** The ISO country code for i18n. */
48
+ countryCode?: CountryCode;
44
49
  }): JSX.Element;
@@ -1,12 +1,26 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { CountryCode, } from '../../storefront-api-types.js';
2
3
  import { CartContext } from './context.js';
3
4
  import { useCartActions } from './CartActions.client.js';
4
5
  import { useCartAPIStateMachine } from './useCartAPIStateMachine.client.js';
5
6
  import { CART_ID_STORAGE_KEY } from './constants.js';
6
- export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, onLineRemove, onLineUpdate, onNoteUpdate, onBuyerIdentityUpdate, onAttributesUpdate, onDiscountCodesUpdate, onCreateComplete, onLineAddComplete, onLineRemoveComplete, onLineUpdateComplete, onNoteUpdateComplete, onBuyerIdentityUpdateComplete, onAttributesUpdateComplete, onDiscountCodesUpdateComplete, data: cart, cartFragment, }) {
7
+ import { ClientAnalytics } from '../../foundation/Analytics/ClientAnalytics.js';
8
+ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, onLineRemove, onLineUpdate, onNoteUpdate, onBuyerIdentityUpdate, onAttributesUpdate, onDiscountCodesUpdate, onCreateComplete, onLineAddComplete, onLineRemoveComplete, onLineUpdateComplete, onNoteUpdateComplete, onBuyerIdentityUpdateComplete, onAttributesUpdateComplete, onDiscountCodesUpdateComplete, data: cart, cartFragment, customerAccessToken, countryCode = CountryCode.Us, }) {
9
+ if (countryCode)
10
+ countryCode = countryCode.toUpperCase();
11
+ const [prevCountryCode, setPrevCountryCode] = useState(countryCode);
12
+ const [prevCustomerAccessToken, setPrevCustomerAccessToken] = useState(customerAccessToken);
13
+ const customerOverridesCountryCode = useRef(false);
14
+ if (prevCountryCode !== countryCode ||
15
+ prevCustomerAccessToken !== customerAccessToken) {
16
+ setPrevCountryCode(countryCode);
17
+ setPrevCustomerAccessToken(customerAccessToken);
18
+ customerOverridesCountryCode.current = false;
19
+ }
7
20
  const { cartFragment: usedCartFragment } = useCartActions({
8
21
  numCartLines,
9
22
  cartFragment,
23
+ countryCode,
10
24
  });
11
25
  const [cartState, cartSend] = useCartAPIStateMachine({
12
26
  numCartLines,
@@ -31,25 +45,68 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
31
45
  return onDiscountCodesUpdate?.();
32
46
  }
33
47
  },
48
+ onCartActionOptimisticUI(context, event) {
49
+ if (!context?.cart)
50
+ return { cart: undefined };
51
+ switch (event.type) {
52
+ case 'CARTLINE_REMOVE':
53
+ return {
54
+ ...context,
55
+ lastValidCart: context.cart,
56
+ cart: {
57
+ ...context.cart,
58
+ lines: context?.cart?.lines.filter(({ id }) => !event.payload.lines.includes(id)),
59
+ },
60
+ };
61
+ case 'CARTLINE_UPDATE':
62
+ return {
63
+ ...context,
64
+ lastValidCart: context.cart,
65
+ cart: {
66
+ ...context.cart,
67
+ lines: context.cart.lines.map((line) => {
68
+ const updatedLine = event.payload.lines.find(({ id }) => id === line.id);
69
+ if (updatedLine && updatedLine.quantity) {
70
+ return {
71
+ ...line,
72
+ quantity: updatedLine.quantity,
73
+ };
74
+ }
75
+ return line;
76
+ }),
77
+ },
78
+ };
79
+ }
80
+ return { cart: context.cart ? { ...context.cart } : undefined };
81
+ },
34
82
  onCartActionComplete(context, event) {
83
+ const cartActionEvent = event.payload.cartActionEvent;
35
84
  switch (event.type) {
36
85
  case 'RESOLVE':
37
- switch (event.payload.cartActionEvent.type) {
86
+ switch (cartActionEvent.type) {
38
87
  case 'CART_CREATE':
88
+ publishCreateAnalytics(context, cartActionEvent);
39
89
  return onCreateComplete?.();
40
90
  case 'CARTLINE_ADD':
91
+ publishLineAddAnalytics(context, cartActionEvent);
41
92
  return onLineAddComplete?.();
42
93
  case 'CARTLINE_REMOVE':
94
+ publishLineRemoveAnalytics(context, cartActionEvent);
43
95
  return onLineRemoveComplete?.();
44
96
  case 'CARTLINE_UPDATE':
97
+ publishLineUpdateAnalytics(context, cartActionEvent);
45
98
  return onLineUpdateComplete?.();
46
99
  case 'NOTE_UPDATE':
47
100
  return onNoteUpdateComplete?.();
48
101
  case 'BUYER_IDENTITY_UPDATE':
102
+ if (countryCodeNotUpdated(context, cartActionEvent)) {
103
+ customerOverridesCountryCode.current = true;
104
+ }
49
105
  return onBuyerIdentityUpdateComplete?.();
50
106
  case 'CART_ATTRIBUTES_UPDATE':
51
107
  return onAttributesUpdateComplete?.();
52
108
  case 'DISCOUNT_CODES_UPDATE':
109
+ publishDiscountCodesUpdateAnalytics(context, cartActionEvent);
53
110
  return onDiscountCodesUpdateComplete?.();
54
111
  }
55
112
  }
@@ -57,6 +114,25 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
57
114
  });
58
115
  const [cartReady, setCartReady] = useState(false);
59
116
  const cartCompleted = cartState.matches('cartCompleted');
117
+ const countryChanged = (cartState.value === 'idle' ||
118
+ cartState.value === 'error' ||
119
+ cartState.value === 'cartCompleted') &&
120
+ countryCode !== cartState?.context?.cart?.buyerIdentity?.countryCode &&
121
+ !cartState.context.errors;
122
+ useEffect(() => {
123
+ if (!countryChanged || customerOverridesCountryCode.current)
124
+ return;
125
+ cartSend({
126
+ type: 'BUYER_IDENTITY_UPDATE',
127
+ payload: { buyerIdentity: { countryCode, customerAccessToken } },
128
+ });
129
+ }, [
130
+ countryCode,
131
+ customerAccessToken,
132
+ countryChanged,
133
+ customerOverridesCountryCode,
134
+ cartSend,
135
+ ]);
60
136
  // send cart events when ready
61
137
  const onCartReadySend = useCallback((cartEvent) => {
62
138
  if (!cartReady) {
@@ -102,23 +178,42 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
102
178
  setCartReady(true);
103
179
  }
104
180
  }, [cartReady, cartSend]);
181
+ const cartCreate = useCallback((cartInput) => {
182
+ if (countryCode && !cartInput.buyerIdentity?.countryCode) {
183
+ if (cartInput.buyerIdentity == null) {
184
+ cartInput.buyerIdentity = {};
185
+ }
186
+ cartInput.buyerIdentity.countryCode = countryCode;
187
+ }
188
+ if (customerAccessToken &&
189
+ !cartInput.buyerIdentity?.customerAccessToken) {
190
+ if (cartInput.buyerIdentity == null) {
191
+ cartInput.buyerIdentity = {};
192
+ }
193
+ cartInput.buyerIdentity.customerAccessToken = customerAccessToken;
194
+ }
195
+ onCartReadySend({
196
+ type: 'CART_CREATE',
197
+ payload: cartInput,
198
+ });
199
+ }, [countryCode, customerAccessToken, onCartReadySend]);
105
200
  const cartContextValue = useMemo(() => {
106
201
  return {
107
202
  ...(cartState?.context?.cart ?? { lines: [], attributes: [] }),
108
203
  status: transposeStatus(cartState.value),
109
204
  error: cartState?.context?.errors,
110
205
  totalQuantity: cartState?.context?.cart?.totalQuantity ?? 0,
111
- cartCreate(cartInput) {
112
- onCartReadySend({
113
- type: 'CART_CREATE',
114
- payload: cartInput,
115
- });
116
- },
206
+ cartCreate,
117
207
  linesAdd(lines) {
118
- onCartReadySend({
119
- type: 'CARTLINE_ADD',
120
- payload: { lines },
121
- });
208
+ if (cartState?.context?.cart?.id) {
209
+ onCartReadySend({
210
+ type: 'CARTLINE_ADD',
211
+ payload: { lines },
212
+ });
213
+ }
214
+ else {
215
+ cartCreate({ lines });
216
+ }
122
217
  },
123
218
  linesRemove(lines) {
124
219
  onCartReadySend({
@@ -171,6 +266,7 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
171
266
  cartFragment: usedCartFragment,
172
267
  };
173
268
  }, [
269
+ cartCreate,
174
270
  cartState?.context?.cart,
175
271
  cartState?.context?.errors,
176
272
  cartState.value,
@@ -182,11 +278,11 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
182
278
  function transposeStatus(status) {
183
279
  switch (status) {
184
280
  case 'uninitialized':
281
+ case 'initializationError':
185
282
  return 'uninitialized';
186
283
  case 'idle':
187
284
  case 'cartCompleted':
188
285
  case 'error':
189
- case 'initializationError':
190
286
  return 'idle';
191
287
  case 'cartFetching':
192
288
  return 'fetching';
@@ -230,3 +326,45 @@ function storageAvailable(type) {
230
326
  storage.length !== 0);
231
327
  }
232
328
  }
329
+ function countryCodeNotUpdated(context, event) {
330
+ return (event.payload.buyerIdentity.countryCode &&
331
+ context.cart?.buyerIdentity?.countryCode !==
332
+ event.payload.buyerIdentity.countryCode);
333
+ }
334
+ // Cart Analytics
335
+ function publishCreateAnalytics(context, event) {
336
+ ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
337
+ addedCartLines: event.payload.lines,
338
+ cart: context.rawCartResult,
339
+ prevCart: null,
340
+ });
341
+ }
342
+ function publishLineAddAnalytics(context, event) {
343
+ ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
344
+ addedCartLines: event.payload.lines,
345
+ cart: context.rawCartResult,
346
+ prevCart: context.prevCart,
347
+ });
348
+ }
349
+ function publishLineUpdateAnalytics(context, event) {
350
+ ClientAnalytics.publish(ClientAnalytics.eventNames.UPDATE_CART, true, {
351
+ updatedCartLines: event.payload.lines,
352
+ oldCart: context.prevCart,
353
+ cart: context.rawCartResult,
354
+ prevCart: context.prevCart,
355
+ });
356
+ }
357
+ function publishLineRemoveAnalytics(context, event) {
358
+ ClientAnalytics.publish(ClientAnalytics.eventNames.REMOVE_FROM_CART, true, {
359
+ removedCartLines: event.payload.lines,
360
+ cart: context.rawCartResult,
361
+ prevCart: context.prevCart,
362
+ });
363
+ }
364
+ function publishDiscountCodesUpdateAnalytics(context, event) {
365
+ ClientAnalytics.publish(ClientAnalytics.eventNames.DISCOUNT_CODE_UPDATED, true, {
366
+ updatedDiscountCodes: event.payload.discountCodes,
367
+ cart: context.rawCartResult,
368
+ prevCart: context.prevCart,
369
+ });
370
+ }
@@ -97,6 +97,7 @@ export declare type CartAction = {
97
97
  } | {
98
98
  type: 'resolve';
99
99
  cart: Cart;
100
+ rawCartResult?: CartFragmentFragment;
100
101
  } | {
101
102
  type: 'reject';
102
103
  errors: any;
@@ -105,6 +106,9 @@ export declare type CartAction = {
105
106
  };
106
107
  export declare type CartMachineContext = {
107
108
  cart?: Cart;
109
+ lastValidCart?: Cart;
110
+ rawCartResult?: CartFragmentFragment;
111
+ prevCart?: Cart;
108
112
  errors?: any;
109
113
  };
110
114
  export declare type CartFetchEvent = {
@@ -170,6 +174,7 @@ export declare type CartMachineFetchResultEvent = {
170
174
  payload: {
171
175
  cartActionEvent: CartMachineActionEvent;
172
176
  cart: Cart;
177
+ rawCartResult: CartFragmentFragment;
173
178
  };
174
179
  } | {
175
180
  type: 'ERROR';
@@ -183,30 +188,40 @@ export declare type CartMachineTypeState = {
183
188
  value: 'uninitialized';
184
189
  context: CartMachineContext & {
185
190
  cart: undefined;
191
+ lastValidCart: undefined;
192
+ prevCart: undefined;
186
193
  errors?: any;
187
194
  };
188
195
  } | {
189
196
  value: 'initializationError';
190
197
  context: CartMachineContext & {
191
198
  cart: undefined;
199
+ lastValidCart: undefined;
200
+ prevCart: undefined;
192
201
  errors: any;
193
202
  };
194
203
  } | {
195
204
  value: 'cartCompleted';
196
205
  context: CartMachineContext & {
197
206
  cart: undefined;
207
+ prevCart?: Cart;
208
+ lastValidCart: undefined;
198
209
  errors: any;
199
210
  };
200
211
  } | {
201
212
  value: 'idle';
202
213
  context: CartMachineContext & {
203
214
  cart: Cart;
215
+ prevCart?: Cart;
216
+ lastValidCart?: Cart;
204
217
  errors?: any;
205
218
  };
206
219
  } | {
207
220
  value: 'error';
208
221
  context: CartMachineContext & {
209
222
  cart?: Cart;
223
+ prevCart?: Cart;
224
+ lastValidCart?: Cart;
210
225
  errors: any;
211
226
  };
212
227
  } | {
@@ -249,5 +264,6 @@ export declare type CartMachineActions = {
249
264
  cartAttributesUpdateAction: CartMachineAction;
250
265
  discountCodesUpdateAction: CartMachineAction;
251
266
  onCartActionEntry?: CartMachineAction;
267
+ onCartActionOptimisticUI?: StateMachine.AssignActionObject<CartMachineContext, CartMachineEvent>;
252
268
  onCartActionComplete?: CartMachineAction;
253
269
  };
@@ -1,16 +1,22 @@
1
1
  import { StateMachine } from '@xstate/fsm';
2
2
  import { CartFragmentFragment } from './graphql/CartFragment.js';
3
3
  import { Cart, CartMachineActionEvent, CartMachineContext, CartMachineEvent, CartMachineFetchResultEvent, CartMachineTypeState } from './types.js';
4
- export declare function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCartActionComplete, data, cartFragment, }: {
4
+ import { CountryCode } from '../../storefront-api-types.js';
5
+ export declare function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCartActionOptimisticUI, onCartActionComplete, data, cartFragment, countryCode, }: {
5
6
  /** Maximum number of cart lines to fetch. Defaults to 250 cart lines. */
6
7
  numCartLines?: number;
7
8
  /** A callback that is invoked just before a Cart API action executes. */
8
9
  onCartActionEntry?: (context: CartMachineContext, event: CartMachineActionEvent) => void;
10
+ /** A callback that is invoked after executing the entry actions for optimistic UI changes. */
11
+ onCartActionOptimisticUI?: (context: CartMachineContext, event: CartMachineEvent) => Partial<CartMachineContext>;
9
12
  /** A callback that is invoked after a Cart API completes. */
10
13
  onCartActionComplete?: (context: CartMachineContext, event: CartMachineFetchResultEvent) => void;
14
+ /** A callback that is invoked after a Cart API completes. */
11
15
  /** An object with fields that correspond to the Storefront API's [Cart object](https://shopify.dev/api/storefront/latest/objects/cart). */
12
16
  data?: CartFragmentFragment;
13
17
  /** A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/latest/objects/cart) for all queries and mutations. A default value is used if no argument is provided. */
14
18
  cartFragment?: string;
19
+ /** The ISO country code for i18n. */
20
+ countryCode?: CountryCode;
15
21
  }): readonly [StateMachine.State<CartMachineContext, CartMachineEvent, CartMachineTypeState>, (event: "CART_FETCH" | "CART_CREATE" | "CARTLINE_ADD" | "CARTLINE_REMOVE" | "CARTLINE_UPDATE" | "NOTE_UPDATE" | "BUYER_IDENTITY_UPDATE" | "CART_ATTRIBUTES_UPDATE" | "DISCOUNT_CODES_UPDATE" | "CART_COMPLETED" | "RESOLVE" | "ERROR" | CartMachineEvent) => void, StateMachine.Service<CartMachineContext, CartMachineEvent, CartMachineTypeState>];
16
22
  export declare function cartFromGraphQL(cart: CartFragmentFragment): Cart;
@@ -5,28 +5,41 @@ import { useCartActions } from './CartActions.client.js';
5
5
  import { useMemo } from 'react';
6
6
  function invokeCart(action, options) {
7
7
  return {
8
- entry: [...(options?.entryActions || []), 'onCartActionEntry', action],
8
+ entry: [
9
+ ...(options?.entryActions || []),
10
+ 'onCartActionEntry',
11
+ 'onCartActionOptimisticUI',
12
+ action,
13
+ ],
9
14
  on: {
10
15
  RESOLVE: {
11
16
  target: options?.resolveTarget || 'idle',
12
17
  actions: [
13
18
  assign({
19
+ prevCart: (context) => context?.cart,
14
20
  cart: (_, event) => event?.payload?.cart,
15
- errors: (_, event) => undefined,
21
+ rawCartResult: (_, event) => event?.payload?.rawCartResult,
22
+ errors: (_) => undefined,
16
23
  }),
17
24
  ],
18
25
  },
19
26
  ERROR: {
20
27
  target: options?.errorTarget || 'error',
21
- actions: assign({
22
- errors: (_, event) => event?.payload?.errors,
23
- }),
28
+ actions: [
29
+ assign({
30
+ prevCart: (context) => context?.cart,
31
+ cart: (context, _) => context?.lastValidCart,
32
+ errors: (_, event) => event?.payload?.errors,
33
+ }),
34
+ ],
24
35
  },
25
36
  CART_COMPLETED: {
26
37
  target: 'cartCompleted',
27
38
  actions: assign({
28
- cart: (_, event) => undefined,
29
- errors: (_, event) => undefined,
39
+ prevCart: (_) => undefined,
40
+ cart: (_) => undefined,
41
+ lastValidCart: (_) => undefined,
42
+ errors: (_) => undefined,
30
43
  }),
31
44
  },
32
45
  },
@@ -40,9 +53,6 @@ const INITIALIZING_CART_EVENTS = {
40
53
  CART_CREATE: {
41
54
  target: 'cartCreating',
42
55
  },
43
- CARTLINE_ADD: {
44
- target: 'cartCreating',
45
- },
46
56
  };
47
57
  const UPDATING_CART_EVENTS = {
48
58
  CARTLINE_ADD: {
@@ -101,10 +111,11 @@ const cartMachine = createMachine({
101
111
  discountCodesUpdating: invokeCart('discountCodesUpdateAction'),
102
112
  },
103
113
  });
104
- export function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCartActionComplete, data: cart, cartFragment, }) {
114
+ export function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCartActionOptimisticUI, onCartActionComplete, data: cart, cartFragment, countryCode, }) {
105
115
  const { cartFetch, cartCreate, cartLineAdd, cartLineUpdate, cartLineRemove, noteUpdate, buyerIdentityUpdate, cartAttributesUpdate, discountCodesUpdate, } = useCartActions({
106
116
  numCartLines,
107
117
  cartFragment,
118
+ countryCode,
108
119
  });
109
120
  const [state, send, service] = useMachine(cartMachine, {
110
121
  actions: {
@@ -116,7 +127,7 @@ export function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCart
116
127
  send(resultEvent);
117
128
  },
118
129
  cartCreateAction: async (_, event) => {
119
- if (event.type !== 'CART_CREATE' && event.type !== 'CARTLINE_ADD')
130
+ if (event.type !== 'CART_CREATE')
120
131
  return;
121
132
  const { data, errors } = await cartCreate(event?.payload);
122
133
  const resultEvent = eventFromFetchResult(event, data?.cartCreate?.cart, errors);
@@ -178,6 +189,11 @@ export function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCart
178
189
  }
179
190
  },
180
191
  }),
192
+ ...(onCartActionOptimisticUI && {
193
+ onCartActionOptimisticUI: assign((context, event) => {
194
+ return onCartActionOptimisticUI(context, event);
195
+ }),
196
+ }),
181
197
  ...(onCartActionComplete && {
182
198
  onCartActionComplete: (context, event) => {
183
199
  if (isCartFetchResultEvent(event)) {
@@ -211,7 +227,11 @@ function eventFromFetchResult(cartActionEvent, cart, errors) {
211
227
  }
212
228
  return {
213
229
  type: 'RESOLVE',
214
- payload: { cart: cartFromGraphQL(cart), cartActionEvent },
230
+ payload: {
231
+ cart: cartFromGraphQL(cart),
232
+ rawCartResult: cart,
233
+ cartActionEvent,
234
+ },
215
235
  };
216
236
  }
217
237
  function isCartActionEvent(event) {
@@ -5,24 +5,28 @@ let secretTokenWarned = false;
5
5
  let storefrontIdWarned = false;
6
6
  export function getStorefrontApiRequestHeaders({ buyerIp, publicStorefrontToken, privateStorefrontToken, storefrontId, }) {
7
7
  const headers = {};
8
- if (!privateStorefrontToken && !secretTokenWarned) {
9
- secretTokenWarned = true;
8
+ if (!privateStorefrontToken) {
10
9
  privateStorefrontToken = getOxygenVariable(OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE);
11
- if (!privateStorefrontToken && !__HYDROGEN_DEV__) {
12
- log.error('No secret Shopify storefront API token was defined. This means your app will be rate limited!\nSee how to add the token: ');
13
- }
14
- else if (privateStorefrontToken) {
15
- log.warn('The private shopify storefront API token was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: ');
10
+ if (!secretTokenWarned) {
11
+ secretTokenWarned = true;
12
+ if (!privateStorefrontToken && !__HYDROGEN_DEV__) {
13
+ log.error('No secret Shopify storefront API token was defined. This means your app will be rate limited!\nSee how to add the token: ');
14
+ }
15
+ else if (privateStorefrontToken) {
16
+ log.warn('The private shopify storefront API token was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: ');
17
+ }
16
18
  }
17
19
  }
18
- if (!storefrontId && !storefrontIdWarned) {
19
- storefrontIdWarned = true;
20
+ if (!storefrontId) {
20
21
  storefrontId = getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
21
- if (!storefrontId && !__HYDROGEN_DEV__) {
22
- log.warn('No storefrontId was defined. This means the analytics on your admin dashboard will be broken!\nSee how to fix it: ');
23
- }
24
- else if (storefrontId) {
25
- log.warn('The storefrontId was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: ');
22
+ if (!storefrontIdWarned) {
23
+ storefrontIdWarned = true;
24
+ if (!storefrontId && !__HYDROGEN_DEV__) {
25
+ log.warn('No storefrontId was defined. This means the analytics on your admin dashboard will be broken!\nSee how to fix it: ');
26
+ }
27
+ else if (storefrontId) {
28
+ log.warn('The storefrontId was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: ');
29
+ }
26
30
  }
27
31
  }
28
32
  /**
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "1.4.0";
1
+ export declare const LIB_VERSION = "1.4.1";
@@ -1 +1 @@
1
- export const LIB_VERSION = '1.4.0';
1
+ export const LIB_VERSION = '1.4.1';
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "engines": {
8
8
  "node": ">=14"
9
9
  },
10
- "version": "1.4.0",
10
+ "version": "1.4.1",
11
11
  "description": "Modern custom Shopify storefronts",
12
12
  "license": "MIT",
13
13
  "main": "dist/esnext/index.js",