@shopify/hydrogen 1.4.4 → 1.6.0

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.
Files changed (42) hide show
  1. package/dist/esnext/components/CartLineProvider/tests/fixtures.d.ts +86 -0
  2. package/dist/esnext/components/CartLineProvider/tests/fixtures.js +34 -0
  3. package/dist/esnext/components/CartProvider/CartProvider.client.d.ts +20 -11
  4. package/dist/esnext/components/CartProvider/CartProvider.client.js +457 -477
  5. package/dist/esnext/components/CartProvider/cart-queries.d.ts +1 -1
  6. package/dist/esnext/components/CartProvider/cart-queries.js +4 -1
  7. package/dist/esnext/components/CartProvider/tests/fixtures.d.ts +254 -0
  8. package/dist/esnext/components/CartProvider/tests/fixtures.js +53 -0
  9. package/dist/esnext/components/Metafield/Metafield.client.js +1 -3
  10. package/dist/esnext/entry-server.js +11 -1
  11. package/dist/esnext/experimental.d.ts +0 -1
  12. package/dist/esnext/experimental.js +0 -1
  13. package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.client.js +21 -14
  14. package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.server.js +15 -9
  15. package/dist/esnext/foundation/Analytics/connectors/Shopify/const.d.ts +5 -0
  16. package/dist/esnext/foundation/Analytics/connectors/Shopify/const.js +5 -0
  17. package/dist/esnext/foundation/Analytics/connectors/Shopify/customer-events.client.d.ts +2 -0
  18. package/dist/esnext/foundation/Analytics/connectors/Shopify/customer-events.client.js +182 -0
  19. package/dist/esnext/foundation/Analytics/connectors/Shopify/utils.d.ts +3 -0
  20. package/dist/esnext/foundation/Analytics/connectors/Shopify/utils.js +69 -0
  21. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.d.ts +1 -0
  22. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.js +2 -8
  23. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-rsc.js +2 -2
  24. package/dist/esnext/hooks/useShopQuery/hooks.js +10 -6
  25. package/dist/esnext/storefront-api-types.d.ts +334 -116
  26. package/dist/esnext/storefront-api-types.js +3 -1
  27. package/dist/esnext/testing.d.ts +2 -0
  28. package/dist/esnext/testing.js +2 -0
  29. package/dist/esnext/utilities/random.d.ts +1 -0
  30. package/dist/esnext/utilities/random.js +11 -0
  31. package/dist/esnext/utilities/tests/MockedServerRequestProvider.server.d.ts +6 -0
  32. package/dist/esnext/utilities/tests/MockedServerRequestProvider.server.js +9 -0
  33. package/dist/esnext/utilities/tests/price.d.ts +5 -0
  34. package/dist/esnext/utilities/tests/price.js +8 -0
  35. package/dist/esnext/utilities/tests/provider-helpers.d.ts +31 -0
  36. package/dist/esnext/utilities/tests/provider-helpers.js +36 -0
  37. package/dist/esnext/version.d.ts +1 -1
  38. package/dist/esnext/version.js +1 -1
  39. package/dist/node/framework/plugins/vite-plugin-hydrogen-rsc.js +2 -2
  40. package/package.json +3 -1
  41. package/dist/esnext/components/CartProvider/CartProviderV2.client.d.ts +0 -50
  42. package/dist/esnext/components/CartProvider/CartProviderV2.client.js +0 -483
@@ -1,483 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import { CountryCode, } from '../../storefront-api-types.js';
3
- import { CartContext } from './context.js';
4
- import { useCartAPIStateMachine } from './useCartAPIStateMachine.client.js';
5
- import { CART_ID_STORAGE_KEY } from './constants.js';
6
- import { ClientAnalytics } from '../../foundation/Analytics/ClientAnalytics.js';
7
- export function CartProviderV2({ 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;
18
- }
19
- const [cartState, cartSend] = useCartAPIStateMachine({
20
- numCartLines,
21
- data: cart,
22
- cartFragment,
23
- countryCode,
24
- onCartActionEntry(context, event) {
25
- try {
26
- switch (event.type) {
27
- case 'CART_CREATE':
28
- return onCreate?.();
29
- case 'CARTLINE_ADD':
30
- return onLineAdd?.();
31
- case 'CARTLINE_REMOVE':
32
- return onLineRemove?.();
33
- case 'CARTLINE_UPDATE':
34
- return onLineUpdate?.();
35
- case 'NOTE_UPDATE':
36
- return onNoteUpdate?.();
37
- case 'BUYER_IDENTITY_UPDATE':
38
- return onBuyerIdentityUpdate?.();
39
- case 'CART_ATTRIBUTES_UPDATE':
40
- return onAttributesUpdate?.();
41
- case 'DISCOUNT_CODES_UPDATE':
42
- return onDiscountCodesUpdate?.();
43
- }
44
- }
45
- catch (error) {
46
- console.error('Cart entry action failed', error);
47
- }
48
- },
49
- onCartActionOptimisticUI(context, event) {
50
- if (!context?.cart)
51
- return { cart: undefined };
52
- switch (event.type) {
53
- case 'CARTLINE_REMOVE':
54
- return {
55
- ...context,
56
- lastValidCart: context.cart,
57
- cart: {
58
- ...context.cart,
59
- lines: context?.cart?.lines.filter(({ id }) => !event.payload.lines.includes(id)),
60
- },
61
- };
62
- case 'CARTLINE_UPDATE':
63
- return {
64
- ...context,
65
- lastValidCart: context.cart,
66
- cart: {
67
- ...context.cart,
68
- lines: context.cart.lines.map((line) => {
69
- const updatedLine = event.payload.lines.find(({ id }) => id === line.id);
70
- if (updatedLine && updatedLine.quantity) {
71
- return {
72
- ...line,
73
- quantity: updatedLine.quantity,
74
- };
75
- }
76
- return line;
77
- }),
78
- },
79
- };
80
- }
81
- return { cart: context.cart ? { ...context.cart } : undefined };
82
- },
83
- onCartActionComplete(context, event) {
84
- const cartActionEvent = event.payload.cartActionEvent;
85
- try {
86
- switch (event.type) {
87
- case 'RESOLVE':
88
- switch (cartActionEvent.type) {
89
- case 'CART_CREATE':
90
- publishCreateAnalytics(context, cartActionEvent);
91
- return onCreateComplete?.();
92
- case 'CARTLINE_ADD':
93
- publishLineAddAnalytics(context, cartActionEvent);
94
- return onLineAddComplete?.();
95
- case 'CARTLINE_REMOVE':
96
- publishLineRemoveAnalytics(context, cartActionEvent);
97
- return onLineRemoveComplete?.();
98
- case 'CARTLINE_UPDATE':
99
- publishLineUpdateAnalytics(context, cartActionEvent);
100
- return onLineUpdateComplete?.();
101
- case 'NOTE_UPDATE':
102
- return onNoteUpdateComplete?.();
103
- case 'BUYER_IDENTITY_UPDATE':
104
- if (countryCodeNotUpdated(context, cartActionEvent)) {
105
- customerOverridesCountryCode.current = true;
106
- }
107
- return onBuyerIdentityUpdateComplete?.();
108
- case 'CART_ATTRIBUTES_UPDATE':
109
- return onAttributesUpdateComplete?.();
110
- case 'DISCOUNT_CODES_UPDATE':
111
- publishDiscountCodesUpdateAnalytics(context, cartActionEvent);
112
- return onDiscountCodesUpdateComplete?.();
113
- }
114
- }
115
- }
116
- catch (error) {
117
- console.error('onCartActionComplete failed', error);
118
- }
119
- },
120
- });
121
- const cartReady = useRef(false);
122
- const cartCompleted = cartState.matches('cartCompleted');
123
- const countryChanged = (cartState.value === 'idle' ||
124
- cartState.value === 'error' ||
125
- cartState.value === 'cartCompleted') &&
126
- countryCode !== cartState?.context?.cart?.buyerIdentity?.countryCode &&
127
- !cartState.context.errors;
128
- /**
129
- * Initializes cart with priority in this order:
130
- * 1. cart props
131
- * 2. localStorage cartId
132
- */
133
- useEffect(() => {
134
- if (!cartReady.current) {
135
- if (!cart && storageAvailable('localStorage')) {
136
- try {
137
- const cartId = window.localStorage.getItem(CART_ID_STORAGE_KEY);
138
- if (cartId) {
139
- cartSend({ type: 'CART_FETCH', payload: { cartId } });
140
- }
141
- }
142
- catch (error) {
143
- console.warn('error fetching cartId');
144
- console.warn(error);
145
- }
146
- }
147
- cartReady.current = true;
148
- }
149
- }, [cart, cartReady, cartSend]);
150
- // Update cart country code if cart and props countryCode's as different
151
- useEffect(() => {
152
- if (!countryChanged || customerOverridesCountryCode.current)
153
- return;
154
- cartSend({
155
- type: 'BUYER_IDENTITY_UPDATE',
156
- payload: { buyerIdentity: { countryCode, customerAccessToken } },
157
- });
158
- }, [
159
- countryCode,
160
- customerAccessToken,
161
- countryChanged,
162
- customerOverridesCountryCode,
163
- cartSend,
164
- ]);
165
- // send cart events when ready
166
- const onCartReadySend = useCallback((cartEvent) => {
167
- if (!cartReady.current) {
168
- return console.warn("Cart isn't ready yet");
169
- }
170
- cartSend(cartEvent);
171
- }, [cartSend]);
172
- // save cart id to local storage
173
- useEffect(() => {
174
- if (cartState?.context?.cart?.id && storageAvailable('localStorage')) {
175
- try {
176
- window.localStorage.setItem(CART_ID_STORAGE_KEY, cartState.context.cart?.id);
177
- }
178
- catch (error) {
179
- console.warn('Failed to save cartId to localStorage', error);
180
- }
181
- }
182
- }, [cartState?.context?.cart?.id]);
183
- // delete cart from local storage if cart fetched has been completed
184
- useEffect(() => {
185
- if (cartCompleted && storageAvailable('localStorage')) {
186
- try {
187
- window.localStorage.removeItem(CART_ID_STORAGE_KEY);
188
- }
189
- catch (error) {
190
- console.warn('Failed to delete cartId from localStorage', error);
191
- }
192
- }
193
- }, [cartCompleted]);
194
- const cartCreate = useCallback((cartInput) => {
195
- if (countryCode && !cartInput.buyerIdentity?.countryCode) {
196
- if (cartInput.buyerIdentity == null) {
197
- cartInput.buyerIdentity = {};
198
- }
199
- cartInput.buyerIdentity.countryCode = countryCode;
200
- }
201
- if (customerAccessToken &&
202
- !cartInput.buyerIdentity?.customerAccessToken) {
203
- if (cartInput.buyerIdentity == null) {
204
- cartInput.buyerIdentity = {};
205
- }
206
- cartInput.buyerIdentity.customerAccessToken = customerAccessToken;
207
- }
208
- onCartReadySend({
209
- type: 'CART_CREATE',
210
- payload: cartInput,
211
- });
212
- }, [countryCode, customerAccessToken, onCartReadySend]);
213
- const cartContextValue = useMemo(() => {
214
- return {
215
- ...(cartState?.context?.cart ?? { lines: [], attributes: [] }),
216
- status: transposeStatus(cartState.value),
217
- error: cartState?.context?.errors,
218
- totalQuantity: cartState?.context?.cart?.totalQuantity ?? 0,
219
- cartCreate,
220
- linesAdd(lines) {
221
- if (cartState?.context?.cart?.id) {
222
- onCartReadySend({
223
- type: 'CARTLINE_ADD',
224
- payload: { lines },
225
- });
226
- }
227
- else {
228
- cartCreate({ lines });
229
- }
230
- },
231
- linesRemove(lines) {
232
- onCartReadySend({
233
- type: 'CARTLINE_REMOVE',
234
- payload: {
235
- lines,
236
- },
237
- });
238
- },
239
- linesUpdate(lines) {
240
- onCartReadySend({
241
- type: 'CARTLINE_UPDATE',
242
- payload: {
243
- lines,
244
- },
245
- });
246
- },
247
- noteUpdate(note) {
248
- onCartReadySend({
249
- type: 'NOTE_UPDATE',
250
- payload: {
251
- note,
252
- },
253
- });
254
- },
255
- buyerIdentityUpdate(buyerIdentity) {
256
- onCartReadySend({
257
- type: 'BUYER_IDENTITY_UPDATE',
258
- payload: {
259
- buyerIdentity,
260
- },
261
- });
262
- },
263
- cartAttributesUpdate(attributes) {
264
- onCartReadySend({
265
- type: 'CART_ATTRIBUTES_UPDATE',
266
- payload: {
267
- attributes,
268
- },
269
- });
270
- },
271
- discountCodesUpdate(discountCodes) {
272
- onCartReadySend({
273
- type: 'DISCOUNT_CODES_UPDATE',
274
- payload: {
275
- discountCodes,
276
- },
277
- });
278
- },
279
- cartFragment,
280
- };
281
- }, [
282
- cartCreate,
283
- cartFragment,
284
- cartState?.context?.cart,
285
- cartState?.context?.errors,
286
- cartState.value,
287
- onCartReadySend,
288
- ]);
289
- return (React.createElement(CartContext.Provider, { value: cartContextValue }, children));
290
- }
291
- function transposeStatus(status) {
292
- switch (status) {
293
- case 'uninitialized':
294
- case 'initializationError':
295
- return 'uninitialized';
296
- case 'idle':
297
- case 'cartCompleted':
298
- case 'error':
299
- return 'idle';
300
- case 'cartFetching':
301
- return 'fetching';
302
- case 'cartCreating':
303
- return 'creating';
304
- case 'cartLineAdding':
305
- case 'cartLineRemoving':
306
- case 'cartLineUpdating':
307
- case 'noteUpdating':
308
- case 'buyerIdentityUpdating':
309
- case 'cartAttributesUpdating':
310
- case 'discountCodesUpdating':
311
- return 'updating';
312
- }
313
- }
314
- /** Check for storage availability funciton obtained from
315
- * https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
316
- */
317
- function storageAvailable(type) {
318
- let storage;
319
- try {
320
- storage = window[type];
321
- const x = '__storage_test__';
322
- storage.setItem(x, x);
323
- storage.removeItem(x);
324
- return true;
325
- }
326
- catch (e) {
327
- return (e instanceof DOMException &&
328
- // everything except Firefox
329
- (e.code === 22 ||
330
- // Firefox
331
- e.code === 1014 ||
332
- // test name field too, because code might not be present
333
- // everything except Firefox
334
- e.name === 'QuotaExceededError' ||
335
- // Firefox
336
- e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
337
- // acknowledge QuotaExceededError only if there's something already stored
338
- storage &&
339
- storage.length !== 0);
340
- }
341
- }
342
- function countryCodeNotUpdated(context, event) {
343
- return (event.payload.buyerIdentity.countryCode &&
344
- context.cart?.buyerIdentity?.countryCode !==
345
- event.payload.buyerIdentity.countryCode);
346
- }
347
- // Cart Analytics
348
- function publishCreateAnalytics(context, event) {
349
- ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
350
- addedCartLines: event.payload.lines,
351
- cart: context.rawCartResult,
352
- prevCart: null,
353
- });
354
- }
355
- function publishLineAddAnalytics(context, event) {
356
- ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
357
- addedCartLines: event.payload.lines,
358
- cart: context.rawCartResult,
359
- prevCart: context.prevCart,
360
- });
361
- }
362
- function publishLineUpdateAnalytics(context, event) {
363
- ClientAnalytics.publish(ClientAnalytics.eventNames.UPDATE_CART, true, {
364
- updatedCartLines: event.payload.lines,
365
- oldCart: context.prevCart,
366
- cart: context.rawCartResult,
367
- prevCart: context.prevCart,
368
- });
369
- }
370
- function publishLineRemoveAnalytics(context, event) {
371
- ClientAnalytics.publish(ClientAnalytics.eventNames.REMOVE_FROM_CART, true, {
372
- removedCartLines: event.payload.lines,
373
- cart: context.rawCartResult,
374
- prevCart: context.prevCart,
375
- });
376
- }
377
- function publishDiscountCodesUpdateAnalytics(context, event) {
378
- ClientAnalytics.publish(ClientAnalytics.eventNames.DISCOUNT_CODE_UPDATED, true, {
379
- updatedDiscountCodes: event.payload.discountCodes,
380
- cart: context.rawCartResult,
381
- prevCart: context.prevCart,
382
- });
383
- }
384
- export const defaultCartFragment = `
385
- fragment CartFragment on Cart {
386
- id
387
- checkoutUrl
388
- totalQuantity
389
- buyerIdentity {
390
- countryCode
391
- customer {
392
- id
393
- email
394
- firstName
395
- lastName
396
- displayName
397
- }
398
- email
399
- phone
400
- }
401
- lines(first: $numCartLines) {
402
- edges {
403
- node {
404
- id
405
- quantity
406
- attributes {
407
- key
408
- value
409
- }
410
- cost {
411
- totalAmount {
412
- amount
413
- currencyCode
414
- }
415
- compareAtAmountPerQuantity {
416
- amount
417
- currencyCode
418
- }
419
- }
420
- merchandise {
421
- ... on ProductVariant {
422
- id
423
- availableForSale
424
- compareAtPriceV2 {
425
- ...MoneyFragment
426
- }
427
- priceV2 {
428
- ...MoneyFragment
429
- }
430
- requiresShipping
431
- title
432
- image {
433
- ...ImageFragment
434
- }
435
- product {
436
- handle
437
- title
438
- }
439
- selectedOptions {
440
- name
441
- value
442
- }
443
- }
444
- }
445
- }
446
- }
447
- }
448
- cost {
449
- subtotalAmount {
450
- ...MoneyFragment
451
- }
452
- totalAmount {
453
- ...MoneyFragment
454
- }
455
- totalDutyAmount {
456
- ...MoneyFragment
457
- }
458
- totalTaxAmount {
459
- ...MoneyFragment
460
- }
461
- }
462
- note
463
- attributes {
464
- key
465
- value
466
- }
467
- discountCodes {
468
- code
469
- }
470
- }
471
-
472
- fragment MoneyFragment on MoneyV2 {
473
- currencyCode
474
- amount
475
- }
476
- fragment ImageFragment on Image {
477
- id
478
- url
479
- altText
480
- width
481
- height
482
- }
483
- `;