@shopify/hydrogen 1.5.0 → 1.6.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.
- package/dist/esnext/components/CartProvider/CartProvider.client.d.ts +20 -11
- package/dist/esnext/components/CartProvider/CartProvider.client.js +457 -477
- package/dist/esnext/components/CartProvider/cart-queries.d.ts +1 -1
- package/dist/esnext/components/CartProvider/cart-queries.js +4 -1
- package/dist/esnext/components/Seo/Seo.client.d.ts +1 -1
- package/dist/esnext/entry-server.js +12 -2
- package/dist/esnext/experimental.d.ts +0 -1
- package/dist/esnext/experimental.js +0 -1
- package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.client.js +21 -14
- package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.server.js +15 -9
- package/dist/esnext/foundation/Analytics/connectors/Shopify/const.d.ts +5 -0
- package/dist/esnext/foundation/Analytics/connectors/Shopify/const.js +5 -0
- package/dist/esnext/foundation/Analytics/connectors/Shopify/customer-events.client.d.ts +2 -0
- package/dist/esnext/foundation/Analytics/connectors/Shopify/customer-events.client.js +182 -0
- package/dist/esnext/foundation/Analytics/connectors/Shopify/utils.d.ts +3 -0
- package/dist/esnext/foundation/Analytics/connectors/Shopify/utils.js +69 -0
- package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.d.ts +1 -0
- package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.js +2 -8
- package/dist/esnext/foundation/Route/Route.server.d.ts +3 -1
- package/dist/esnext/foundation/Route/Route.server.js +2 -2
- package/dist/esnext/hooks/useShopQuery/hooks.js +10 -6
- package/dist/esnext/utilities/random.d.ts +1 -0
- package/dist/esnext/utilities/random.js +11 -0
- package/dist/esnext/utilities/tests/price.js +0 -1
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/package.json +2 -1
- package/dist/esnext/components/CartProvider/CartProviderV2.client.d.ts +0 -50
- package/dist/esnext/components/CartProvider/CartProviderV2.client.js +0 -513
|
@@ -1,118 +1,76 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import { flattenConnection } from '../../utilities/flattenConnection/index.js';
|
|
3
|
-
import { CartLineAdd, CartCreate, CartLineRemove, CartLineUpdate, CartNoteUpdate, CartBuyerIdentityUpdate, CartAttributesUpdate, CartDiscountCodesUpdate, CartQuery, defaultCartFragment, } from './cart-queries.js';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState, useTransition, } from 'react';
|
|
4
2
|
import { CountryCode, } from '../../storefront-api-types.js';
|
|
5
|
-
import { useCartFetch } from './hooks.client.js';
|
|
6
3
|
import { CartContext } from './context.js';
|
|
4
|
+
import { useCartAPIStateMachine } from './useCartAPIStateMachine.client.js';
|
|
7
5
|
import { CART_ID_STORAGE_KEY } from './constants.js';
|
|
8
6
|
import { ClientAnalytics } from '../../foundation/Analytics/ClientAnalytics.js';
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
const localStorage = (function () {
|
|
24
|
-
try {
|
|
25
|
-
return window.localStorage || getLocalStoragePolyfill();
|
|
26
|
-
}
|
|
27
|
-
catch (e) {
|
|
28
|
-
return getLocalStoragePolyfill();
|
|
7
|
+
export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLineRemove, onLineUpdate, onNoteUpdate, onBuyerIdentityUpdate, onAttributesUpdate, onDiscountCodesUpdate, onCreateComplete, onLineAddComplete, onLineRemoveComplete, onLineUpdateComplete, onNoteUpdateComplete, onBuyerIdentityUpdateComplete, onAttributesUpdateComplete, onDiscountCodesUpdateComplete, data: cart, cartFragment = defaultCartFragment, customerAccessToken, countryCode = CountryCode.Us, }) {
|
|
8
|
+
if (countryCode)
|
|
9
|
+
countryCode = countryCode.toUpperCase();
|
|
10
|
+
const [prevCountryCode, setPrevCountryCode] = useState(countryCode);
|
|
11
|
+
const [prevCustomerAccessToken, setPrevCustomerAccessToken] = useState(customerAccessToken);
|
|
12
|
+
const customerOverridesCountryCode = useRef(false);
|
|
13
|
+
if (prevCountryCode !== countryCode ||
|
|
14
|
+
prevCustomerAccessToken !== customerAccessToken) {
|
|
15
|
+
setPrevCountryCode(countryCode);
|
|
16
|
+
setPrevCustomerAccessToken(customerAccessToken);
|
|
17
|
+
customerOverridesCountryCode.current = false;
|
|
29
18
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
case 'resolve': {
|
|
50
|
-
const resolvableStatuses = ['updating', 'fetching', 'creating'];
|
|
51
|
-
if (resolvableStatuses.includes(state.status)) {
|
|
52
|
-
return {
|
|
53
|
-
status: 'idle',
|
|
54
|
-
cart: action.cart,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
case 'reject': {
|
|
60
|
-
if (action.errors) {
|
|
61
|
-
console.group('%cCart Error:', 'color:red');
|
|
62
|
-
for (const [i, error] of action.errors.entries()) {
|
|
63
|
-
console.log(`%c${i + 1}. ` + error.message, 'color:red');
|
|
64
|
-
}
|
|
65
|
-
console.groupEnd();
|
|
66
|
-
}
|
|
67
|
-
if (state.status === 'fetching' || state.status === 'creating') {
|
|
68
|
-
return { status: 'uninitialized', error: action.errors };
|
|
69
|
-
}
|
|
70
|
-
else if (state.status === 'updating') {
|
|
71
|
-
return {
|
|
72
|
-
status: 'idle',
|
|
73
|
-
cart: state.lastValidCart,
|
|
74
|
-
error: action.errors,
|
|
75
|
-
};
|
|
19
|
+
const onCartActionEntry = useCallback((context, event) => {
|
|
20
|
+
try {
|
|
21
|
+
switch (event.type) {
|
|
22
|
+
case 'CART_CREATE':
|
|
23
|
+
return onCreate?.();
|
|
24
|
+
case 'CARTLINE_ADD':
|
|
25
|
+
return onLineAdd?.();
|
|
26
|
+
case 'CARTLINE_REMOVE':
|
|
27
|
+
return onLineRemove?.();
|
|
28
|
+
case 'CARTLINE_UPDATE':
|
|
29
|
+
return onLineUpdate?.();
|
|
30
|
+
case 'NOTE_UPDATE':
|
|
31
|
+
return onNoteUpdate?.();
|
|
32
|
+
case 'BUYER_IDENTITY_UPDATE':
|
|
33
|
+
return onBuyerIdentityUpdate?.();
|
|
34
|
+
case 'CART_ATTRIBUTES_UPDATE':
|
|
35
|
+
return onAttributesUpdate?.();
|
|
36
|
+
case 'DISCOUNT_CODES_UPDATE':
|
|
37
|
+
return onDiscountCodesUpdate?.();
|
|
76
38
|
}
|
|
77
|
-
break;
|
|
78
39
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return { status: 'uninitialized' };
|
|
82
|
-
}
|
|
83
|
-
break;
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error('Cart entry action failed', error);
|
|
84
42
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
43
|
+
}, [
|
|
44
|
+
onAttributesUpdate,
|
|
45
|
+
onBuyerIdentityUpdate,
|
|
46
|
+
onCreate,
|
|
47
|
+
onDiscountCodesUpdate,
|
|
48
|
+
onLineAdd,
|
|
49
|
+
onLineRemove,
|
|
50
|
+
onLineUpdate,
|
|
51
|
+
onNoteUpdate,
|
|
52
|
+
]);
|
|
53
|
+
const onCartActionOptimisticUI = useCallback((context, event) => {
|
|
54
|
+
if (!context?.cart)
|
|
55
|
+
return { cart: undefined };
|
|
56
|
+
switch (event.type) {
|
|
57
|
+
case 'CARTLINE_REMOVE':
|
|
97
58
|
return {
|
|
98
|
-
|
|
59
|
+
...context,
|
|
60
|
+
lastValidCart: context.cart,
|
|
99
61
|
cart: {
|
|
100
|
-
...
|
|
101
|
-
lines:
|
|
62
|
+
...context.cart,
|
|
63
|
+
lines: context?.cart?.lines.filter(({ id }) => !event.payload.lines.includes(id)),
|
|
102
64
|
},
|
|
103
|
-
lastValidCart: state.cart,
|
|
104
65
|
};
|
|
105
|
-
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
case 'updateLineItem': {
|
|
109
|
-
if (state.status === 'idle') {
|
|
66
|
+
case 'CARTLINE_UPDATE':
|
|
110
67
|
return {
|
|
111
|
-
|
|
68
|
+
...context,
|
|
69
|
+
lastValidCart: context.cart,
|
|
112
70
|
cart: {
|
|
113
|
-
...
|
|
114
|
-
lines:
|
|
115
|
-
const updatedLine =
|
|
71
|
+
...context.cart,
|
|
72
|
+
lines: context.cart.lines.map((line) => {
|
|
73
|
+
const updatedLine = event.payload.lines.find(({ id }) => id === line.id);
|
|
116
74
|
if (updatedLine && updatedLine.quantity) {
|
|
117
75
|
return {
|
|
118
76
|
...line,
|
|
@@ -122,433 +80,455 @@ function cartReducer(state, action) {
|
|
|
122
80
|
return line;
|
|
123
81
|
}),
|
|
124
82
|
},
|
|
125
|
-
lastValidCart: state.cart,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
case 'noteUpdate': {
|
|
131
|
-
if (state.status === 'idle') {
|
|
132
|
-
return {
|
|
133
|
-
status: 'updating',
|
|
134
|
-
cart: state.cart,
|
|
135
|
-
lastValidCart: state.cart,
|
|
136
83
|
};
|
|
137
|
-
}
|
|
138
|
-
break;
|
|
139
84
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
85
|
+
return { cart: context.cart ? { ...context.cart } : undefined };
|
|
86
|
+
}, []);
|
|
87
|
+
const onCartActionComplete = useCallback((context, event) => {
|
|
88
|
+
const cartActionEvent = event.payload.cartActionEvent;
|
|
89
|
+
try {
|
|
90
|
+
switch (event.type) {
|
|
91
|
+
case 'RESOLVE':
|
|
92
|
+
switch (cartActionEvent.type) {
|
|
93
|
+
case 'CART_CREATE':
|
|
94
|
+
publishCreateAnalytics(context, cartActionEvent);
|
|
95
|
+
return onCreateComplete?.();
|
|
96
|
+
case 'CARTLINE_ADD':
|
|
97
|
+
publishLineAddAnalytics(context, cartActionEvent);
|
|
98
|
+
return onLineAddComplete?.();
|
|
99
|
+
case 'CARTLINE_REMOVE':
|
|
100
|
+
publishLineRemoveAnalytics(context, cartActionEvent);
|
|
101
|
+
return onLineRemoveComplete?.();
|
|
102
|
+
case 'CARTLINE_UPDATE':
|
|
103
|
+
publishLineUpdateAnalytics(context, cartActionEvent);
|
|
104
|
+
return onLineUpdateComplete?.();
|
|
105
|
+
case 'NOTE_UPDATE':
|
|
106
|
+
return onNoteUpdateComplete?.();
|
|
107
|
+
case 'BUYER_IDENTITY_UPDATE':
|
|
108
|
+
if (countryCodeNotUpdated(context, cartActionEvent)) {
|
|
109
|
+
customerOverridesCountryCode.current = true;
|
|
110
|
+
}
|
|
111
|
+
return onBuyerIdentityUpdateComplete?.();
|
|
112
|
+
case 'CART_ATTRIBUTES_UPDATE':
|
|
113
|
+
return onAttributesUpdateComplete?.();
|
|
114
|
+
case 'DISCOUNT_CODES_UPDATE':
|
|
115
|
+
publishDiscountCodesUpdateAnalytics(context, cartActionEvent);
|
|
116
|
+
return onDiscountCodesUpdateComplete?.();
|
|
117
|
+
}
|
|
147
118
|
}
|
|
148
|
-
break;
|
|
149
119
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
status: 'updating',
|
|
154
|
-
cart: state.cart,
|
|
155
|
-
lastValidCart: state.cart,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
break;
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error('onCartActionComplete failed', error);
|
|
159
122
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
123
|
+
}, [
|
|
124
|
+
onAttributesUpdateComplete,
|
|
125
|
+
onBuyerIdentityUpdateComplete,
|
|
126
|
+
onCreateComplete,
|
|
127
|
+
onDiscountCodesUpdateComplete,
|
|
128
|
+
onLineAddComplete,
|
|
129
|
+
onLineRemoveComplete,
|
|
130
|
+
onLineUpdateComplete,
|
|
131
|
+
onNoteUpdateComplete,
|
|
132
|
+
]);
|
|
133
|
+
const [cartState, cartSend] = useCartAPIStateMachine({
|
|
134
|
+
numCartLines,
|
|
135
|
+
data: cart,
|
|
136
|
+
cartFragment,
|
|
137
|
+
countryCode,
|
|
138
|
+
onCartActionEntry,
|
|
139
|
+
onCartActionOptimisticUI,
|
|
140
|
+
onCartActionComplete,
|
|
141
|
+
});
|
|
142
|
+
const cartReady = useRef(false);
|
|
143
|
+
const cartCompleted = cartState.matches('cartCompleted');
|
|
144
|
+
const countryChanged = (cartState.value === 'idle' ||
|
|
145
|
+
cartState.value === 'error' ||
|
|
146
|
+
cartState.value === 'cartCompleted') &&
|
|
147
|
+
countryCode !== cartState?.context?.cart?.buyerIdentity?.countryCode &&
|
|
148
|
+
!cartState.context.errors;
|
|
149
|
+
const fetchingFromStorage = useRef(false);
|
|
150
|
+
/**
|
|
151
|
+
* Initializes cart with priority in this order:
|
|
152
|
+
* 1. cart props
|
|
153
|
+
* 2. localStorage cartId
|
|
154
|
+
*/
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!cartReady.current && !fetchingFromStorage.current) {
|
|
157
|
+
if (!cart && storageAvailable('localStorage')) {
|
|
158
|
+
fetchingFromStorage.current = true;
|
|
159
|
+
try {
|
|
160
|
+
const cartId = window.localStorage.getItem(CART_ID_STORAGE_KEY);
|
|
161
|
+
if (cartId) {
|
|
162
|
+
cartSend({ type: 'CART_FETCH', payload: { cartId } });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.warn('error fetching cartId');
|
|
167
|
+
console.warn(error);
|
|
168
|
+
}
|
|
167
169
|
}
|
|
168
|
-
|
|
170
|
+
cartReady.current = true;
|
|
169
171
|
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
* The `CartProvider` component creates a context for using a cart. It creates a cart object and callbacks
|
|
175
|
-
* that can be accessed by any descendent component using the `useCart` hook and related hooks. It also carries out
|
|
176
|
-
* any callback props when a relevant action is performed. For example, if a `onLineAdd` callback is provided,
|
|
177
|
-
* then the callback will be called when a new line item is successfully added to the cart.
|
|
178
|
-
*
|
|
179
|
-
* The `CartProvider` component must be a descendent of the `ShopifyProvider` component.
|
|
180
|
-
* You must use this component if you want to use the `useCart` hook or related hooks, or if you would like to use the `AddToCartButton` component.
|
|
181
|
-
*/
|
|
182
|
-
export function CartProvider({ children, numCartLines, onCreate, onLineAdd, onLineRemove, onLineUpdate, onNoteUpdate, onBuyerIdentityUpdate, onAttributesUpdate, onDiscountCodesUpdate, data: cart, cartFragment = defaultCartFragment, customerAccessToken, countryCode = CountryCode.Us, }) {
|
|
183
|
-
if (countryCode)
|
|
184
|
-
countryCode = countryCode.toUpperCase();
|
|
185
|
-
const initialStatus = cart
|
|
186
|
-
? { status: 'idle', cart: cartFromGraphQL(cart) }
|
|
187
|
-
: { status: 'uninitialized' };
|
|
188
|
-
const [state, dispatch] = useReducer((state, dispatch) => cartReducer(state, dispatch), initialStatus);
|
|
189
|
-
const fetchCart = useCartFetch();
|
|
190
|
-
const countryChanged = state.status === 'idle' &&
|
|
191
|
-
countryCode !== state?.cart?.buyerIdentity?.countryCode &&
|
|
192
|
-
!state.error;
|
|
193
|
-
const cartFetch = useCallback(async (cartId) => {
|
|
194
|
-
dispatch({ type: 'cartFetch' });
|
|
195
|
-
const { data } = await fetchCart({
|
|
196
|
-
query: CartQuery(cartFragment),
|
|
197
|
-
variables: {
|
|
198
|
-
id: cartId,
|
|
199
|
-
numCartLines,
|
|
200
|
-
country: countryCode,
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
if (!data?.cart) {
|
|
204
|
-
localStorage.removeItem(CART_ID_STORAGE_KEY);
|
|
205
|
-
dispatch({ type: 'resetCart' });
|
|
172
|
+
}, [cart, cartReady, cartSend]);
|
|
173
|
+
// Update cart country code if cart and props countryCode's as different
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (!countryChanged || customerOverridesCountryCode.current)
|
|
206
176
|
return;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const cartCreate = useCallback(async (cart) => {
|
|
211
|
-
dispatch({ type: 'cartCreate' });
|
|
212
|
-
onCreate?.();
|
|
213
|
-
if (countryCode && !cart.buyerIdentity?.countryCode) {
|
|
214
|
-
if (cart.buyerIdentity == null) {
|
|
215
|
-
cart.buyerIdentity = {};
|
|
216
|
-
}
|
|
217
|
-
cart.buyerIdentity.countryCode = countryCode;
|
|
218
|
-
}
|
|
219
|
-
if (customerAccessToken && !cart.buyerIdentity?.customerAccessToken) {
|
|
220
|
-
if (cart.buyerIdentity == null) {
|
|
221
|
-
cart.buyerIdentity = {};
|
|
222
|
-
}
|
|
223
|
-
cart.buyerIdentity.customerAccessToken = customerAccessToken;
|
|
224
|
-
}
|
|
225
|
-
const { data, errors } = await fetchCart({
|
|
226
|
-
query: CartCreate(cartFragment),
|
|
227
|
-
variables: {
|
|
228
|
-
input: cart,
|
|
229
|
-
numCartLines,
|
|
230
|
-
country: countryCode,
|
|
231
|
-
},
|
|
177
|
+
cartSend({
|
|
178
|
+
type: 'BUYER_IDENTITY_UPDATE',
|
|
179
|
+
payload: { buyerIdentity: { countryCode, customerAccessToken } },
|
|
232
180
|
});
|
|
233
|
-
if (errors) {
|
|
234
|
-
dispatch({
|
|
235
|
-
type: 'reject',
|
|
236
|
-
errors,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
if (data?.cartCreate?.cart) {
|
|
240
|
-
if (cart.lines) {
|
|
241
|
-
ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
|
|
242
|
-
addedCartLines: cart.lines,
|
|
243
|
-
cart: data.cartCreate.cart,
|
|
244
|
-
prevCart: null,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
dispatch({
|
|
248
|
-
type: 'resolve',
|
|
249
|
-
cart: cartFromGraphQL(data.cartCreate.cart),
|
|
250
|
-
});
|
|
251
|
-
localStorage.setItem(CART_ID_STORAGE_KEY, data.cartCreate.cart.id);
|
|
252
|
-
}
|
|
253
181
|
}, [
|
|
254
|
-
onCreate,
|
|
255
182
|
countryCode,
|
|
256
|
-
fetchCart,
|
|
257
|
-
cartFragment,
|
|
258
|
-
numCartLines,
|
|
259
183
|
customerAccessToken,
|
|
184
|
+
countryChanged,
|
|
185
|
+
customerOverridesCountryCode,
|
|
186
|
+
cartSend,
|
|
260
187
|
]);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const { data, errors } = await fetchCart({
|
|
266
|
-
query: CartLineAdd(cartFragment),
|
|
267
|
-
variables: {
|
|
268
|
-
cartId: state.cart.id,
|
|
269
|
-
lines,
|
|
270
|
-
numCartLines,
|
|
271
|
-
country: countryCode,
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
if (errors) {
|
|
275
|
-
dispatch({
|
|
276
|
-
type: 'reject',
|
|
277
|
-
errors,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
if (data?.cartLinesAdd?.cart) {
|
|
281
|
-
ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
|
|
282
|
-
addedCartLines: lines,
|
|
283
|
-
cart: data.cartLinesAdd.cart,
|
|
284
|
-
prevCart: state.cart,
|
|
285
|
-
});
|
|
286
|
-
dispatch({
|
|
287
|
-
type: 'resolve',
|
|
288
|
-
cart: cartFromGraphQL(data.cartLinesAdd.cart),
|
|
289
|
-
});
|
|
290
|
-
}
|
|
188
|
+
// send cart events when ready
|
|
189
|
+
const onCartReadySend = useCallback((cartEvent) => {
|
|
190
|
+
if (!cartReady.current) {
|
|
191
|
+
return console.warn("Cart isn't ready yet");
|
|
291
192
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
variables: {
|
|
300
|
-
cartId: state.cart.id,
|
|
301
|
-
lines,
|
|
302
|
-
numCartLines,
|
|
303
|
-
country: countryCode,
|
|
304
|
-
},
|
|
305
|
-
});
|
|
306
|
-
if (errors) {
|
|
307
|
-
dispatch({
|
|
308
|
-
type: 'reject',
|
|
309
|
-
errors,
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
if (data?.cartLinesRemove?.cart) {
|
|
313
|
-
ClientAnalytics.publish(ClientAnalytics.eventNames.REMOVE_FROM_CART, true, {
|
|
314
|
-
removedCartLines: lines,
|
|
315
|
-
cart: data.cartLinesRemove.cart,
|
|
316
|
-
prevCart: state.cart,
|
|
317
|
-
});
|
|
318
|
-
dispatch({
|
|
319
|
-
type: 'resolve',
|
|
320
|
-
cart: cartFromGraphQL(data.cartLinesRemove.cart),
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}, [onLineRemove, fetchCart, cartFragment, numCartLines, countryCode]);
|
|
325
|
-
const updateLineItem = useCallback(async (lines, state) => {
|
|
326
|
-
if (state.status === 'idle') {
|
|
327
|
-
dispatch({ type: 'updateLineItem', lines });
|
|
328
|
-
onLineUpdate?.();
|
|
329
|
-
const { data, errors } = await fetchCart({
|
|
330
|
-
query: CartLineUpdate(cartFragment),
|
|
331
|
-
variables: {
|
|
332
|
-
cartId: state.cart.id,
|
|
333
|
-
lines,
|
|
334
|
-
numCartLines,
|
|
335
|
-
country: countryCode,
|
|
336
|
-
},
|
|
337
|
-
});
|
|
338
|
-
if (errors) {
|
|
339
|
-
dispatch({
|
|
340
|
-
type: 'reject',
|
|
341
|
-
errors,
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
if (data?.cartLinesUpdate?.cart) {
|
|
345
|
-
ClientAnalytics.publish(ClientAnalytics.eventNames.UPDATE_CART, true, {
|
|
346
|
-
updatedCartLines: lines,
|
|
347
|
-
oldCart: state.cart,
|
|
348
|
-
cart: data.cartLinesUpdate.cart,
|
|
349
|
-
prevCart: state.cart,
|
|
350
|
-
});
|
|
351
|
-
dispatch({
|
|
352
|
-
type: 'resolve',
|
|
353
|
-
cart: cartFromGraphQL(data.cartLinesUpdate.cart),
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}, [onLineUpdate, fetchCart, cartFragment, numCartLines, countryCode]);
|
|
358
|
-
const noteUpdate = useCallback(async (note, state) => {
|
|
359
|
-
if (state.status === 'idle') {
|
|
360
|
-
dispatch({ type: 'noteUpdate' });
|
|
361
|
-
onNoteUpdate?.();
|
|
362
|
-
const { data, errors } = await fetchCart({
|
|
363
|
-
query: CartNoteUpdate(cartFragment),
|
|
364
|
-
variables: {
|
|
365
|
-
cartId: state.cart.id,
|
|
366
|
-
note,
|
|
367
|
-
numCartLines,
|
|
368
|
-
country: countryCode,
|
|
369
|
-
},
|
|
370
|
-
});
|
|
371
|
-
if (errors) {
|
|
372
|
-
dispatch({
|
|
373
|
-
type: 'reject',
|
|
374
|
-
errors,
|
|
375
|
-
});
|
|
193
|
+
cartSend(cartEvent);
|
|
194
|
+
}, [cartSend]);
|
|
195
|
+
// save cart id to local storage
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (cartState?.context?.cart?.id && storageAvailable('localStorage')) {
|
|
198
|
+
try {
|
|
199
|
+
window.localStorage.setItem(CART_ID_STORAGE_KEY, cartState.context.cart?.id);
|
|
376
200
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
type: 'resolve',
|
|
380
|
-
cart: cartFromGraphQL(data.cartNoteUpdate.cart),
|
|
381
|
-
});
|
|
201
|
+
catch (error) {
|
|
202
|
+
console.warn('Failed to save cartId to localStorage', error);
|
|
382
203
|
}
|
|
383
204
|
}
|
|
384
|
-
}, [
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
query: CartBuyerIdentityUpdate(cartFragment),
|
|
391
|
-
variables: {
|
|
392
|
-
cartId: state.cart.id,
|
|
393
|
-
buyerIdentity,
|
|
394
|
-
numCartLines,
|
|
395
|
-
country: countryCode,
|
|
396
|
-
},
|
|
397
|
-
});
|
|
398
|
-
if (errors) {
|
|
399
|
-
dispatch({
|
|
400
|
-
type: 'reject',
|
|
401
|
-
errors,
|
|
402
|
-
});
|
|
205
|
+
}, [cartState?.context?.cart?.id]);
|
|
206
|
+
// delete cart from local storage if cart fetched has been completed
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (cartCompleted && storageAvailable('localStorage')) {
|
|
209
|
+
try {
|
|
210
|
+
window.localStorage.removeItem(CART_ID_STORAGE_KEY);
|
|
403
211
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
type: 'resolve',
|
|
407
|
-
cart: cartFromGraphQL(data.cartBuyerIdentityUpdate.cart),
|
|
408
|
-
});
|
|
212
|
+
catch (error) {
|
|
213
|
+
console.warn('Failed to delete cartId from localStorage', error);
|
|
409
214
|
}
|
|
410
215
|
}
|
|
411
|
-
}, [
|
|
412
|
-
const
|
|
413
|
-
if (
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const { data, errors } = await fetchCart({
|
|
417
|
-
query: CartAttributesUpdate(cartFragment),
|
|
418
|
-
variables: {
|
|
419
|
-
cartId: state.cart.id,
|
|
420
|
-
attributes,
|
|
421
|
-
numCartLines,
|
|
422
|
-
country: countryCode,
|
|
423
|
-
},
|
|
424
|
-
});
|
|
425
|
-
if (errors) {
|
|
426
|
-
dispatch({
|
|
427
|
-
type: 'reject',
|
|
428
|
-
errors,
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
if (data?.cartAttributesUpdate?.cart) {
|
|
432
|
-
dispatch({
|
|
433
|
-
type: 'resolve',
|
|
434
|
-
cart: cartFromGraphQL(data.cartAttributesUpdate.cart),
|
|
435
|
-
});
|
|
216
|
+
}, [cartCompleted]);
|
|
217
|
+
const cartCreate = useCallback((cartInput) => {
|
|
218
|
+
if (countryCode && !cartInput.buyerIdentity?.countryCode) {
|
|
219
|
+
if (cartInput.buyerIdentity == null) {
|
|
220
|
+
cartInput.buyerIdentity = {};
|
|
436
221
|
}
|
|
222
|
+
cartInput.buyerIdentity.countryCode = countryCode;
|
|
437
223
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
onDiscountCodesUpdate?.();
|
|
443
|
-
const { data, errors } = await fetchCart({
|
|
444
|
-
query: CartDiscountCodesUpdate(cartFragment),
|
|
445
|
-
variables: {
|
|
446
|
-
cartId: state.cart.id,
|
|
447
|
-
discountCodes,
|
|
448
|
-
numCartLines,
|
|
449
|
-
country: countryCode,
|
|
450
|
-
},
|
|
451
|
-
});
|
|
452
|
-
if (errors) {
|
|
453
|
-
dispatch({
|
|
454
|
-
type: 'reject',
|
|
455
|
-
errors,
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
if (data?.cartDiscountCodesUpdate?.cart) {
|
|
459
|
-
ClientAnalytics.publish(ClientAnalytics.eventNames.DISCOUNT_CODE_UPDATED, true, {
|
|
460
|
-
updatedDiscountCodes: discountCodes,
|
|
461
|
-
cart: data.cartDiscountCodesUpdate.cart,
|
|
462
|
-
prevCart: state.cart,
|
|
463
|
-
});
|
|
464
|
-
dispatch({
|
|
465
|
-
type: 'resolve',
|
|
466
|
-
cart: cartFromGraphQL(data.cartDiscountCodesUpdate.cart),
|
|
467
|
-
});
|
|
224
|
+
if (customerAccessToken &&
|
|
225
|
+
!cartInput.buyerIdentity?.customerAccessToken) {
|
|
226
|
+
if (cartInput.buyerIdentity == null) {
|
|
227
|
+
cartInput.buyerIdentity = {};
|
|
468
228
|
}
|
|
229
|
+
cartInput.buyerIdentity.customerAccessToken = customerAccessToken;
|
|
469
230
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
}, [cartFetch, state]);
|
|
480
|
-
useEffect(() => {
|
|
481
|
-
if (!countryChanged)
|
|
482
|
-
return;
|
|
483
|
-
buyerIdentityUpdate({ countryCode, customerAccessToken }, state);
|
|
484
|
-
}, [
|
|
485
|
-
state,
|
|
486
|
-
buyerIdentityUpdate,
|
|
487
|
-
countryCode,
|
|
488
|
-
customerAccessToken,
|
|
489
|
-
countryChanged,
|
|
490
|
-
]);
|
|
231
|
+
onCartReadySend({
|
|
232
|
+
type: 'CART_CREATE',
|
|
233
|
+
payload: cartInput,
|
|
234
|
+
});
|
|
235
|
+
}, [countryCode, customerAccessToken, onCartReadySend]);
|
|
236
|
+
// Delays the cart state in the context if the page is hydrating
|
|
237
|
+
// preventing suspense boundary errors.
|
|
238
|
+
const cartDisplayState = useDelayedStateUntilHydration(cartState);
|
|
491
239
|
const cartContextValue = useMemo(() => {
|
|
492
240
|
return {
|
|
493
|
-
...(
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
attributes: [],
|
|
498
|
-
...(cart ? cartFromGraphQL(cart) : {}),
|
|
499
|
-
}),
|
|
500
|
-
status: state.status,
|
|
501
|
-
error: 'error' in state ? state.error : undefined,
|
|
502
|
-
totalQuantity: 'cart' in state ? state?.cart?.totalQuantity ?? 0 : 0,
|
|
241
|
+
...(cartDisplayState?.context?.cart ?? { lines: [], attributes: [] }),
|
|
242
|
+
status: transposeStatus(cartDisplayState.value),
|
|
243
|
+
error: cartDisplayState?.context?.errors,
|
|
244
|
+
totalQuantity: cartDisplayState?.context?.cart?.totalQuantity ?? 0,
|
|
503
245
|
cartCreate,
|
|
504
246
|
linesAdd(lines) {
|
|
505
|
-
if (
|
|
506
|
-
|
|
247
|
+
if (cartDisplayState?.context?.cart?.id) {
|
|
248
|
+
onCartReadySend({
|
|
249
|
+
type: 'CARTLINE_ADD',
|
|
250
|
+
payload: { lines },
|
|
251
|
+
});
|
|
507
252
|
}
|
|
508
253
|
else {
|
|
509
254
|
cartCreate({ lines });
|
|
510
255
|
}
|
|
511
256
|
},
|
|
512
257
|
linesRemove(lines) {
|
|
513
|
-
|
|
258
|
+
onCartReadySend({
|
|
259
|
+
type: 'CARTLINE_REMOVE',
|
|
260
|
+
payload: {
|
|
261
|
+
lines,
|
|
262
|
+
},
|
|
263
|
+
});
|
|
514
264
|
},
|
|
515
265
|
linesUpdate(lines) {
|
|
516
|
-
|
|
266
|
+
onCartReadySend({
|
|
267
|
+
type: 'CARTLINE_UPDATE',
|
|
268
|
+
payload: {
|
|
269
|
+
lines,
|
|
270
|
+
},
|
|
271
|
+
});
|
|
517
272
|
},
|
|
518
273
|
noteUpdate(note) {
|
|
519
|
-
|
|
274
|
+
onCartReadySend({
|
|
275
|
+
type: 'NOTE_UPDATE',
|
|
276
|
+
payload: {
|
|
277
|
+
note,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
520
280
|
},
|
|
521
281
|
buyerIdentityUpdate(buyerIdentity) {
|
|
522
|
-
|
|
282
|
+
onCartReadySend({
|
|
283
|
+
type: 'BUYER_IDENTITY_UPDATE',
|
|
284
|
+
payload: {
|
|
285
|
+
buyerIdentity,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
523
288
|
},
|
|
524
289
|
cartAttributesUpdate(attributes) {
|
|
525
|
-
|
|
290
|
+
onCartReadySend({
|
|
291
|
+
type: 'CART_ATTRIBUTES_UPDATE',
|
|
292
|
+
payload: {
|
|
293
|
+
attributes,
|
|
294
|
+
},
|
|
295
|
+
});
|
|
526
296
|
},
|
|
527
297
|
discountCodesUpdate(discountCodes) {
|
|
528
|
-
|
|
298
|
+
onCartReadySend({
|
|
299
|
+
type: 'DISCOUNT_CODES_UPDATE',
|
|
300
|
+
payload: {
|
|
301
|
+
discountCodes,
|
|
302
|
+
},
|
|
303
|
+
});
|
|
529
304
|
},
|
|
530
305
|
cartFragment,
|
|
531
306
|
};
|
|
532
307
|
}, [
|
|
533
|
-
state,
|
|
534
|
-
cart,
|
|
535
308
|
cartCreate,
|
|
309
|
+
cartDisplayState?.context?.cart,
|
|
310
|
+
cartDisplayState?.context?.errors,
|
|
311
|
+
cartDisplayState.value,
|
|
536
312
|
cartFragment,
|
|
537
|
-
|
|
538
|
-
removeLineItem,
|
|
539
|
-
updateLineItem,
|
|
540
|
-
noteUpdate,
|
|
541
|
-
buyerIdentityUpdate,
|
|
542
|
-
cartAttributesUpdate,
|
|
543
|
-
discountCodesUpdate,
|
|
313
|
+
onCartReadySend,
|
|
544
314
|
]);
|
|
545
315
|
return (React.createElement(CartContext.Provider, { value: cartContextValue }, children));
|
|
546
316
|
}
|
|
547
|
-
function
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
317
|
+
function transposeStatus(status) {
|
|
318
|
+
switch (status) {
|
|
319
|
+
case 'uninitialized':
|
|
320
|
+
case 'initializationError':
|
|
321
|
+
return 'uninitialized';
|
|
322
|
+
case 'idle':
|
|
323
|
+
case 'cartCompleted':
|
|
324
|
+
case 'error':
|
|
325
|
+
return 'idle';
|
|
326
|
+
case 'cartFetching':
|
|
327
|
+
return 'fetching';
|
|
328
|
+
case 'cartCreating':
|
|
329
|
+
return 'creating';
|
|
330
|
+
case 'cartLineAdding':
|
|
331
|
+
case 'cartLineRemoving':
|
|
332
|
+
case 'cartLineUpdating':
|
|
333
|
+
case 'noteUpdating':
|
|
334
|
+
case 'buyerIdentityUpdating':
|
|
335
|
+
case 'cartAttributesUpdating':
|
|
336
|
+
case 'discountCodesUpdating':
|
|
337
|
+
return 'updating';
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Delays a state update until hydration finishes. Useful for preventing suspense boundaries errors when updating a context
|
|
342
|
+
* @remarks this uses startTransition and waits for it to finish.
|
|
343
|
+
*/
|
|
344
|
+
function useDelayedStateUntilHydration(state) {
|
|
345
|
+
const [isPending, startTransition] = useTransition();
|
|
346
|
+
const [delayedState, setDelayedState] = useState(state);
|
|
347
|
+
const firstTimePending = useRef(false);
|
|
348
|
+
if (isPending) {
|
|
349
|
+
firstTimePending.current = true;
|
|
350
|
+
}
|
|
351
|
+
const firstTimePendingFinished = useRef(false);
|
|
352
|
+
if (!isPending && firstTimePending.current) {
|
|
353
|
+
firstTimePendingFinished.current = true;
|
|
354
|
+
}
|
|
355
|
+
useEffect(() => {
|
|
356
|
+
startTransition(() => {
|
|
357
|
+
if (!firstTimePendingFinished.current) {
|
|
358
|
+
setDelayedState(state);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}, [state]);
|
|
362
|
+
const displayState = firstTimePendingFinished.current ? state : delayedState;
|
|
363
|
+
return displayState;
|
|
364
|
+
}
|
|
365
|
+
/** Check for storage availability funciton obtained from
|
|
366
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
|
|
367
|
+
*/
|
|
368
|
+
function storageAvailable(type) {
|
|
369
|
+
let storage;
|
|
370
|
+
try {
|
|
371
|
+
storage = window[type];
|
|
372
|
+
const x = '__storage_test__';
|
|
373
|
+
storage.setItem(x, x);
|
|
374
|
+
storage.removeItem(x);
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
catch (e) {
|
|
378
|
+
return (e instanceof DOMException &&
|
|
379
|
+
// everything except Firefox
|
|
380
|
+
(e.code === 22 ||
|
|
381
|
+
// Firefox
|
|
382
|
+
e.code === 1014 ||
|
|
383
|
+
// test name field too, because code might not be present
|
|
384
|
+
// everything except Firefox
|
|
385
|
+
e.name === 'QuotaExceededError' ||
|
|
386
|
+
// Firefox
|
|
387
|
+
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
|
|
388
|
+
// acknowledge QuotaExceededError only if there's something already stored
|
|
389
|
+
storage &&
|
|
390
|
+
storage.length !== 0);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function countryCodeNotUpdated(context, event) {
|
|
394
|
+
return (event.payload.buyerIdentity.countryCode &&
|
|
395
|
+
context.cart?.buyerIdentity?.countryCode !==
|
|
396
|
+
event.payload.buyerIdentity.countryCode);
|
|
397
|
+
}
|
|
398
|
+
// Cart Analytics
|
|
399
|
+
function publishCreateAnalytics(context, event) {
|
|
400
|
+
ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
|
|
401
|
+
addedCartLines: event.payload.lines,
|
|
402
|
+
cart: context.rawCartResult,
|
|
403
|
+
prevCart: null,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
function publishLineAddAnalytics(context, event) {
|
|
407
|
+
ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
|
|
408
|
+
addedCartLines: event.payload.lines,
|
|
409
|
+
cart: context.rawCartResult,
|
|
410
|
+
prevCart: context.prevCart,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
function publishLineUpdateAnalytics(context, event) {
|
|
414
|
+
ClientAnalytics.publish(ClientAnalytics.eventNames.UPDATE_CART, true, {
|
|
415
|
+
updatedCartLines: event.payload.lines,
|
|
416
|
+
oldCart: context.prevCart,
|
|
417
|
+
cart: context.rawCartResult,
|
|
418
|
+
prevCart: context.prevCart,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
function publishLineRemoveAnalytics(context, event) {
|
|
422
|
+
ClientAnalytics.publish(ClientAnalytics.eventNames.REMOVE_FROM_CART, true, {
|
|
423
|
+
removedCartLines: event.payload.lines,
|
|
424
|
+
cart: context.rawCartResult,
|
|
425
|
+
prevCart: context.prevCart,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function publishDiscountCodesUpdateAnalytics(context, event) {
|
|
429
|
+
ClientAnalytics.publish(ClientAnalytics.eventNames.DISCOUNT_CODE_UPDATED, true, {
|
|
430
|
+
updatedDiscountCodes: event.payload.discountCodes,
|
|
431
|
+
cart: context.rawCartResult,
|
|
432
|
+
prevCart: context.prevCart,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
export const defaultCartFragment = `
|
|
436
|
+
fragment CartFragment on Cart {
|
|
437
|
+
id
|
|
438
|
+
checkoutUrl
|
|
439
|
+
totalQuantity
|
|
440
|
+
buyerIdentity {
|
|
441
|
+
countryCode
|
|
442
|
+
customer {
|
|
443
|
+
id
|
|
444
|
+
email
|
|
445
|
+
firstName
|
|
446
|
+
lastName
|
|
447
|
+
displayName
|
|
448
|
+
}
|
|
449
|
+
email
|
|
450
|
+
phone
|
|
451
|
+
}
|
|
452
|
+
lines(first: $numCartLines) {
|
|
453
|
+
edges {
|
|
454
|
+
node {
|
|
455
|
+
id
|
|
456
|
+
quantity
|
|
457
|
+
attributes {
|
|
458
|
+
key
|
|
459
|
+
value
|
|
460
|
+
}
|
|
461
|
+
cost {
|
|
462
|
+
totalAmount {
|
|
463
|
+
amount
|
|
464
|
+
currencyCode
|
|
465
|
+
}
|
|
466
|
+
compareAtAmountPerQuantity {
|
|
467
|
+
amount
|
|
468
|
+
currencyCode
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
merchandise {
|
|
472
|
+
... on ProductVariant {
|
|
473
|
+
id
|
|
474
|
+
availableForSale
|
|
475
|
+
compareAtPriceV2 {
|
|
476
|
+
...MoneyFragment
|
|
477
|
+
}
|
|
478
|
+
priceV2 {
|
|
479
|
+
...MoneyFragment
|
|
480
|
+
}
|
|
481
|
+
requiresShipping
|
|
482
|
+
title
|
|
483
|
+
image {
|
|
484
|
+
...ImageFragment
|
|
485
|
+
}
|
|
486
|
+
product {
|
|
487
|
+
handle
|
|
488
|
+
title
|
|
489
|
+
}
|
|
490
|
+
selectedOptions {
|
|
491
|
+
name
|
|
492
|
+
value
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
cost {
|
|
500
|
+
subtotalAmount {
|
|
501
|
+
...MoneyFragment
|
|
502
|
+
}
|
|
503
|
+
totalAmount {
|
|
504
|
+
...MoneyFragment
|
|
505
|
+
}
|
|
506
|
+
totalDutyAmount {
|
|
507
|
+
...MoneyFragment
|
|
508
|
+
}
|
|
509
|
+
totalTaxAmount {
|
|
510
|
+
...MoneyFragment
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
note
|
|
514
|
+
attributes {
|
|
515
|
+
key
|
|
516
|
+
value
|
|
517
|
+
}
|
|
518
|
+
discountCodes {
|
|
519
|
+
code
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
fragment MoneyFragment on MoneyV2 {
|
|
524
|
+
currencyCode
|
|
525
|
+
amount
|
|
526
|
+
}
|
|
527
|
+
fragment ImageFragment on Image {
|
|
528
|
+
id
|
|
529
|
+
url
|
|
530
|
+
altText
|
|
531
|
+
width
|
|
532
|
+
height
|
|
554
533
|
}
|
|
534
|
+
`;
|