@shopify/hydrogen 1.4.0 → 1.4.2
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/CartActions.client.d.ts +5 -4
- package/dist/esnext/components/CartProvider/CartActions.client.js +19 -119
- package/dist/esnext/components/CartProvider/CartProviderV2.client.d.ts +7 -1
- package/dist/esnext/components/CartProvider/CartProviderV2.client.js +323 -70
- package/dist/esnext/components/CartProvider/types.d.ts +23 -1
- package/dist/esnext/components/CartProvider/useCartAPIStateMachine.client.d.ts +8 -3
- package/dist/esnext/components/CartProvider/useCartAPIStateMachine.client.js +43 -14
- package/dist/esnext/entry-server.js +12 -9
- package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.client.js +4 -2
- package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.server.js +2 -2
- package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.js +2 -1
- package/dist/esnext/index.d.ts +1 -1
- package/dist/esnext/utilities/apiRoutes.js +1 -1
- package/dist/esnext/utilities/log/log.d.ts +1 -1
- package/dist/esnext/utilities/log/log.js +2 -2
- package/dist/esnext/utilities/storefrontApi.js +15 -14
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
cartFragment
|
|
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;
|
|
@@ -57,4 +59,3 @@ export declare function useCartActions({ numCartLines, cartFragment, }: {
|
|
|
57
59
|
}>;
|
|
58
60
|
cartFragment: string;
|
|
59
61
|
};
|
|
60
|
-
export declare const defaultCartFragment = "\nfragment CartFragment on Cart {\n id\n checkoutUrl\n totalQuantity\n buyerIdentity {\n countryCode\n customer {\n id\n email\n firstName\n lastName\n displayName\n }\n email\n phone\n }\n lines(first: $numCartLines) {\n edges {\n node {\n id\n quantity\n attributes {\n key\n value\n }\n cost {\n totalAmount {\n amount\n currencyCode\n }\n compareAtAmountPerQuantity {\n amount\n currencyCode\n }\n }\n merchandise {\n ... on ProductVariant {\n id\n availableForSale\n compareAtPriceV2 {\n ...MoneyFragment\n }\n priceV2 {\n ...MoneyFragment\n }\n requiresShipping\n title\n image {\n ...ImageFragment\n }\n product {\n handle\n title\n }\n selectedOptions {\n name\n value\n }\n }\n }\n }\n }\n }\n cost {\n subtotalAmount {\n ...MoneyFragment\n }\n totalAmount {\n ...MoneyFragment\n }\n totalDutyAmount {\n ...MoneyFragment\n }\n totalTaxAmount {\n ...MoneyFragment\n }\n }\n note\n attributes {\n key\n value\n }\n discountCodes {\n code\n }\n}\n\nfragment MoneyFragment on MoneyV2 {\n currencyCode\n amount\n}\nfragment ImageFragment on Image {\n id\n url\n altText\n width\n height\n}\n";
|
|
@@ -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 =
|
|
10
|
+
export function useCartActions({ numCartLines, cartFragment, 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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
@@ -130,103 +130,3 @@ export function useCartActions({ numCartLines, cartFragment = defaultCartFragmen
|
|
|
130
130
|
cartFragment,
|
|
131
131
|
]);
|
|
132
132
|
}
|
|
133
|
-
export const defaultCartFragment = `
|
|
134
|
-
fragment CartFragment on Cart {
|
|
135
|
-
id
|
|
136
|
-
checkoutUrl
|
|
137
|
-
totalQuantity
|
|
138
|
-
buyerIdentity {
|
|
139
|
-
countryCode
|
|
140
|
-
customer {
|
|
141
|
-
id
|
|
142
|
-
email
|
|
143
|
-
firstName
|
|
144
|
-
lastName
|
|
145
|
-
displayName
|
|
146
|
-
}
|
|
147
|
-
email
|
|
148
|
-
phone
|
|
149
|
-
}
|
|
150
|
-
lines(first: $numCartLines) {
|
|
151
|
-
edges {
|
|
152
|
-
node {
|
|
153
|
-
id
|
|
154
|
-
quantity
|
|
155
|
-
attributes {
|
|
156
|
-
key
|
|
157
|
-
value
|
|
158
|
-
}
|
|
159
|
-
cost {
|
|
160
|
-
totalAmount {
|
|
161
|
-
amount
|
|
162
|
-
currencyCode
|
|
163
|
-
}
|
|
164
|
-
compareAtAmountPerQuantity {
|
|
165
|
-
amount
|
|
166
|
-
currencyCode
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
merchandise {
|
|
170
|
-
... on ProductVariant {
|
|
171
|
-
id
|
|
172
|
-
availableForSale
|
|
173
|
-
compareAtPriceV2 {
|
|
174
|
-
...MoneyFragment
|
|
175
|
-
}
|
|
176
|
-
priceV2 {
|
|
177
|
-
...MoneyFragment
|
|
178
|
-
}
|
|
179
|
-
requiresShipping
|
|
180
|
-
title
|
|
181
|
-
image {
|
|
182
|
-
...ImageFragment
|
|
183
|
-
}
|
|
184
|
-
product {
|
|
185
|
-
handle
|
|
186
|
-
title
|
|
187
|
-
}
|
|
188
|
-
selectedOptions {
|
|
189
|
-
name
|
|
190
|
-
value
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
cost {
|
|
198
|
-
subtotalAmount {
|
|
199
|
-
...MoneyFragment
|
|
200
|
-
}
|
|
201
|
-
totalAmount {
|
|
202
|
-
...MoneyFragment
|
|
203
|
-
}
|
|
204
|
-
totalDutyAmount {
|
|
205
|
-
...MoneyFragment
|
|
206
|
-
}
|
|
207
|
-
totalTaxAmount {
|
|
208
|
-
...MoneyFragment
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
note
|
|
212
|
-
attributes {
|
|
213
|
-
key
|
|
214
|
-
value
|
|
215
|
-
}
|
|
216
|
-
discountCodes {
|
|
217
|
-
code
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
fragment MoneyFragment on MoneyV2 {
|
|
222
|
-
currencyCode
|
|
223
|
-
amount
|
|
224
|
-
}
|
|
225
|
-
fragment ImageFragment on Image {
|
|
226
|
-
id
|
|
227
|
-
url
|
|
228
|
-
altText
|
|
229
|
-
width
|
|
230
|
-
height
|
|
231
|
-
}
|
|
232
|
-
`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { CartFragmentFragment } from './graphql/CartFragment.js';
|
|
3
|
-
|
|
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: cart, 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,9 @@ 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;
|
|
50
|
+
export declare const defaultCartFragment = "\nfragment CartFragment on Cart {\n id\n checkoutUrl\n totalQuantity\n buyerIdentity {\n countryCode\n customer {\n id\n email\n firstName\n lastName\n displayName\n }\n email\n phone\n }\n lines(first: $numCartLines) {\n edges {\n node {\n id\n quantity\n attributes {\n key\n value\n }\n cost {\n totalAmount {\n amount\n currencyCode\n }\n compareAtAmountPerQuantity {\n amount\n currencyCode\n }\n }\n merchandise {\n ... on ProductVariant {\n id\n availableForSale\n compareAtPriceV2 {\n ...MoneyFragment\n }\n priceV2 {\n ...MoneyFragment\n }\n requiresShipping\n title\n image {\n ...ImageFragment\n }\n product {\n handle\n title\n }\n selectedOptions {\n name\n value\n }\n }\n }\n }\n }\n }\n cost {\n subtotalAmount {\n ...MoneyFragment\n }\n totalAmount {\n ...MoneyFragment\n }\n totalDutyAmount {\n ...MoneyFragment\n }\n totalTaxAmount {\n ...MoneyFragment\n }\n }\n note\n attributes {\n key\n value\n }\n discountCodes {\n code\n }\n}\n\nfragment MoneyFragment on MoneyV2 {\n currencyCode\n amount\n}\nfragment ImageFragment on Image {\n id\n url\n altText\n width\n height\n}\n";
|
|
@@ -1,69 +1,176 @@
|
|
|
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
|
-
import { useCartActions } from './CartActions.client.js';
|
|
4
4
|
import { useCartAPIStateMachine } from './useCartAPIStateMachine.client.js';
|
|
5
5
|
import { CART_ID_STORAGE_KEY } from './constants.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
+
}
|
|
11
19
|
const [cartState, cartSend] = useCartAPIStateMachine({
|
|
12
20
|
numCartLines,
|
|
13
21
|
cartFragment,
|
|
22
|
+
countryCode,
|
|
14
23
|
onCartActionEntry(context, event) {
|
|
24
|
+
try {
|
|
25
|
+
switch (event.type) {
|
|
26
|
+
case 'CART_CREATE':
|
|
27
|
+
return onCreate?.();
|
|
28
|
+
case 'CARTLINE_ADD':
|
|
29
|
+
return onLineAdd?.();
|
|
30
|
+
case 'CARTLINE_REMOVE':
|
|
31
|
+
return onLineRemove?.();
|
|
32
|
+
case 'CARTLINE_UPDATE':
|
|
33
|
+
return onLineUpdate?.();
|
|
34
|
+
case 'NOTE_UPDATE':
|
|
35
|
+
return onNoteUpdate?.();
|
|
36
|
+
case 'BUYER_IDENTITY_UPDATE':
|
|
37
|
+
return onBuyerIdentityUpdate?.();
|
|
38
|
+
case 'CART_ATTRIBUTES_UPDATE':
|
|
39
|
+
return onAttributesUpdate?.();
|
|
40
|
+
case 'DISCOUNT_CODES_UPDATE':
|
|
41
|
+
return onDiscountCodesUpdate?.();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error('Cart entry action failed', error);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
onCartActionOptimisticUI(context, event) {
|
|
49
|
+
if (!context?.cart)
|
|
50
|
+
return { cart: undefined };
|
|
15
51
|
switch (event.type) {
|
|
16
|
-
case 'CART_CREATE':
|
|
17
|
-
return onCreate?.();
|
|
18
|
-
case 'CARTLINE_ADD':
|
|
19
|
-
return onLineAdd?.();
|
|
20
52
|
case 'CARTLINE_REMOVE':
|
|
21
|
-
return
|
|
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
|
+
};
|
|
22
61
|
case 'CARTLINE_UPDATE':
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
};
|
|
32
79
|
}
|
|
80
|
+
return { cart: context.cart ? { ...context.cart } : undefined };
|
|
33
81
|
},
|
|
34
82
|
onCartActionComplete(context, event) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
83
|
+
const cartActionEvent = event.payload.cartActionEvent;
|
|
84
|
+
try {
|
|
85
|
+
switch (event.type) {
|
|
86
|
+
case 'RESOLVE':
|
|
87
|
+
switch (cartActionEvent.type) {
|
|
88
|
+
case 'CART_CREATE':
|
|
89
|
+
publishCreateAnalytics(context, cartActionEvent);
|
|
90
|
+
return onCreateComplete?.();
|
|
91
|
+
case 'CARTLINE_ADD':
|
|
92
|
+
publishLineAddAnalytics(context, cartActionEvent);
|
|
93
|
+
return onLineAddComplete?.();
|
|
94
|
+
case 'CARTLINE_REMOVE':
|
|
95
|
+
publishLineRemoveAnalytics(context, cartActionEvent);
|
|
96
|
+
return onLineRemoveComplete?.();
|
|
97
|
+
case 'CARTLINE_UPDATE':
|
|
98
|
+
publishLineUpdateAnalytics(context, cartActionEvent);
|
|
99
|
+
return onLineUpdateComplete?.();
|
|
100
|
+
case 'NOTE_UPDATE':
|
|
101
|
+
return onNoteUpdateComplete?.();
|
|
102
|
+
case 'BUYER_IDENTITY_UPDATE':
|
|
103
|
+
if (countryCodeNotUpdated(context, cartActionEvent)) {
|
|
104
|
+
customerOverridesCountryCode.current = true;
|
|
105
|
+
}
|
|
106
|
+
return onBuyerIdentityUpdateComplete?.();
|
|
107
|
+
case 'CART_ATTRIBUTES_UPDATE':
|
|
108
|
+
return onAttributesUpdateComplete?.();
|
|
109
|
+
case 'DISCOUNT_CODES_UPDATE':
|
|
110
|
+
publishDiscountCodesUpdateAnalytics(context, cartActionEvent);
|
|
111
|
+
return onDiscountCodesUpdateComplete?.();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.error('onCartActionComplete failed', error);
|
|
55
117
|
}
|
|
56
118
|
},
|
|
57
119
|
});
|
|
58
|
-
const
|
|
120
|
+
const cartReady = useRef(false);
|
|
59
121
|
const cartCompleted = cartState.matches('cartCompleted');
|
|
122
|
+
const countryChanged = (cartState.value === 'idle' ||
|
|
123
|
+
cartState.value === 'error' ||
|
|
124
|
+
cartState.value === 'cartCompleted') &&
|
|
125
|
+
countryCode !== cartState?.context?.cart?.buyerIdentity?.countryCode &&
|
|
126
|
+
!cartState.context.errors;
|
|
127
|
+
/**
|
|
128
|
+
* Initializes cart with priority in this order:
|
|
129
|
+
* 1. cart props
|
|
130
|
+
* 2. localStorage cartId
|
|
131
|
+
*/
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (!cartReady.current) {
|
|
134
|
+
if (cart) {
|
|
135
|
+
cartSend({ type: 'CART_SET', payload: { cart } });
|
|
136
|
+
}
|
|
137
|
+
else if (storageAvailable('localStorage')) {
|
|
138
|
+
try {
|
|
139
|
+
const cartId = window.localStorage.getItem(CART_ID_STORAGE_KEY);
|
|
140
|
+
if (cartId) {
|
|
141
|
+
cartSend({ type: 'CART_FETCH', payload: { cartId } });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.warn('error fetching cartId');
|
|
146
|
+
console.warn(error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
cartReady.current = true;
|
|
150
|
+
}
|
|
151
|
+
}, [cart, cartReady, cartSend]);
|
|
152
|
+
// Update cart country code if cart and props countryCode's as different
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (!countryChanged || customerOverridesCountryCode.current)
|
|
155
|
+
return;
|
|
156
|
+
cartSend({
|
|
157
|
+
type: 'BUYER_IDENTITY_UPDATE',
|
|
158
|
+
payload: { buyerIdentity: { countryCode, customerAccessToken } },
|
|
159
|
+
});
|
|
160
|
+
}, [
|
|
161
|
+
countryCode,
|
|
162
|
+
customerAccessToken,
|
|
163
|
+
countryChanged,
|
|
164
|
+
customerOverridesCountryCode,
|
|
165
|
+
cartSend,
|
|
166
|
+
]);
|
|
60
167
|
// send cart events when ready
|
|
61
168
|
const onCartReadySend = useCallback((cartEvent) => {
|
|
62
|
-
if (!cartReady) {
|
|
169
|
+
if (!cartReady.current) {
|
|
63
170
|
return console.warn("Cart isn't ready yet");
|
|
64
171
|
}
|
|
65
172
|
cartSend(cartEvent);
|
|
66
|
-
}, [
|
|
173
|
+
}, [cartSend]);
|
|
67
174
|
// save cart id to local storage
|
|
68
175
|
useEffect(() => {
|
|
69
176
|
if (cartState?.context?.cart?.id && storageAvailable('localStorage')) {
|
|
@@ -86,39 +193,42 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
|
|
|
86
193
|
}
|
|
87
194
|
}
|
|
88
195
|
}, [cartCompleted]);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const cartId = window.localStorage.getItem(CART_ID_STORAGE_KEY);
|
|
94
|
-
if (cartId) {
|
|
95
|
-
cartSend({ type: 'CART_FETCH', payload: { cartId } });
|
|
96
|
-
}
|
|
196
|
+
const cartCreate = useCallback((cartInput) => {
|
|
197
|
+
if (countryCode && !cartInput.buyerIdentity?.countryCode) {
|
|
198
|
+
if (cartInput.buyerIdentity == null) {
|
|
199
|
+
cartInput.buyerIdentity = {};
|
|
97
200
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
201
|
+
cartInput.buyerIdentity.countryCode = countryCode;
|
|
202
|
+
}
|
|
203
|
+
if (customerAccessToken &&
|
|
204
|
+
!cartInput.buyerIdentity?.customerAccessToken) {
|
|
205
|
+
if (cartInput.buyerIdentity == null) {
|
|
206
|
+
cartInput.buyerIdentity = {};
|
|
101
207
|
}
|
|
102
|
-
|
|
208
|
+
cartInput.buyerIdentity.customerAccessToken = customerAccessToken;
|
|
103
209
|
}
|
|
104
|
-
|
|
210
|
+
onCartReadySend({
|
|
211
|
+
type: 'CART_CREATE',
|
|
212
|
+
payload: cartInput,
|
|
213
|
+
});
|
|
214
|
+
}, [countryCode, customerAccessToken, onCartReadySend]);
|
|
105
215
|
const cartContextValue = useMemo(() => {
|
|
106
216
|
return {
|
|
107
217
|
...(cartState?.context?.cart ?? { lines: [], attributes: [] }),
|
|
108
218
|
status: transposeStatus(cartState.value),
|
|
109
219
|
error: cartState?.context?.errors,
|
|
110
220
|
totalQuantity: cartState?.context?.cart?.totalQuantity ?? 0,
|
|
111
|
-
cartCreate
|
|
112
|
-
onCartReadySend({
|
|
113
|
-
type: 'CART_CREATE',
|
|
114
|
-
payload: cartInput,
|
|
115
|
-
});
|
|
116
|
-
},
|
|
221
|
+
cartCreate,
|
|
117
222
|
linesAdd(lines) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
223
|
+
if (cartState?.context?.cart?.id) {
|
|
224
|
+
onCartReadySend({
|
|
225
|
+
type: 'CARTLINE_ADD',
|
|
226
|
+
payload: { lines },
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
cartCreate({ lines });
|
|
231
|
+
}
|
|
122
232
|
},
|
|
123
233
|
linesRemove(lines) {
|
|
124
234
|
onCartReadySend({
|
|
@@ -168,25 +278,26 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
|
|
|
168
278
|
},
|
|
169
279
|
});
|
|
170
280
|
},
|
|
171
|
-
cartFragment
|
|
281
|
+
cartFragment,
|
|
172
282
|
};
|
|
173
283
|
}, [
|
|
284
|
+
cartCreate,
|
|
285
|
+
cartFragment,
|
|
174
286
|
cartState?.context?.cart,
|
|
175
287
|
cartState?.context?.errors,
|
|
176
288
|
cartState.value,
|
|
177
289
|
onCartReadySend,
|
|
178
|
-
usedCartFragment,
|
|
179
290
|
]);
|
|
180
291
|
return (React.createElement(CartContext.Provider, { value: cartContextValue }, children));
|
|
181
292
|
}
|
|
182
293
|
function transposeStatus(status) {
|
|
183
294
|
switch (status) {
|
|
184
295
|
case 'uninitialized':
|
|
296
|
+
case 'initializationError':
|
|
185
297
|
return 'uninitialized';
|
|
186
298
|
case 'idle':
|
|
187
299
|
case 'cartCompleted':
|
|
188
300
|
case 'error':
|
|
189
|
-
case 'initializationError':
|
|
190
301
|
return 'idle';
|
|
191
302
|
case 'cartFetching':
|
|
192
303
|
return 'fetching';
|
|
@@ -230,3 +341,145 @@ function storageAvailable(type) {
|
|
|
230
341
|
storage.length !== 0);
|
|
231
342
|
}
|
|
232
343
|
}
|
|
344
|
+
function countryCodeNotUpdated(context, event) {
|
|
345
|
+
return (event.payload.buyerIdentity.countryCode &&
|
|
346
|
+
context.cart?.buyerIdentity?.countryCode !==
|
|
347
|
+
event.payload.buyerIdentity.countryCode);
|
|
348
|
+
}
|
|
349
|
+
// Cart Analytics
|
|
350
|
+
function publishCreateAnalytics(context, event) {
|
|
351
|
+
ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
|
|
352
|
+
addedCartLines: event.payload.lines,
|
|
353
|
+
cart: context.rawCartResult,
|
|
354
|
+
prevCart: null,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
function publishLineAddAnalytics(context, event) {
|
|
358
|
+
ClientAnalytics.publish(ClientAnalytics.eventNames.ADD_TO_CART, true, {
|
|
359
|
+
addedCartLines: event.payload.lines,
|
|
360
|
+
cart: context.rawCartResult,
|
|
361
|
+
prevCart: context.prevCart,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
function publishLineUpdateAnalytics(context, event) {
|
|
365
|
+
ClientAnalytics.publish(ClientAnalytics.eventNames.UPDATE_CART, true, {
|
|
366
|
+
updatedCartLines: event.payload.lines,
|
|
367
|
+
oldCart: context.prevCart,
|
|
368
|
+
cart: context.rawCartResult,
|
|
369
|
+
prevCart: context.prevCart,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function publishLineRemoveAnalytics(context, event) {
|
|
373
|
+
ClientAnalytics.publish(ClientAnalytics.eventNames.REMOVE_FROM_CART, true, {
|
|
374
|
+
removedCartLines: event.payload.lines,
|
|
375
|
+
cart: context.rawCartResult,
|
|
376
|
+
prevCart: context.prevCart,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function publishDiscountCodesUpdateAnalytics(context, event) {
|
|
380
|
+
ClientAnalytics.publish(ClientAnalytics.eventNames.DISCOUNT_CODE_UPDATED, true, {
|
|
381
|
+
updatedDiscountCodes: event.payload.discountCodes,
|
|
382
|
+
cart: context.rawCartResult,
|
|
383
|
+
prevCart: context.prevCart,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
export const defaultCartFragment = `
|
|
387
|
+
fragment CartFragment on Cart {
|
|
388
|
+
id
|
|
389
|
+
checkoutUrl
|
|
390
|
+
totalQuantity
|
|
391
|
+
buyerIdentity {
|
|
392
|
+
countryCode
|
|
393
|
+
customer {
|
|
394
|
+
id
|
|
395
|
+
email
|
|
396
|
+
firstName
|
|
397
|
+
lastName
|
|
398
|
+
displayName
|
|
399
|
+
}
|
|
400
|
+
email
|
|
401
|
+
phone
|
|
402
|
+
}
|
|
403
|
+
lines(first: $numCartLines) {
|
|
404
|
+
edges {
|
|
405
|
+
node {
|
|
406
|
+
id
|
|
407
|
+
quantity
|
|
408
|
+
attributes {
|
|
409
|
+
key
|
|
410
|
+
value
|
|
411
|
+
}
|
|
412
|
+
cost {
|
|
413
|
+
totalAmount {
|
|
414
|
+
amount
|
|
415
|
+
currencyCode
|
|
416
|
+
}
|
|
417
|
+
compareAtAmountPerQuantity {
|
|
418
|
+
amount
|
|
419
|
+
currencyCode
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
merchandise {
|
|
423
|
+
... on ProductVariant {
|
|
424
|
+
id
|
|
425
|
+
availableForSale
|
|
426
|
+
compareAtPriceV2 {
|
|
427
|
+
...MoneyFragment
|
|
428
|
+
}
|
|
429
|
+
priceV2 {
|
|
430
|
+
...MoneyFragment
|
|
431
|
+
}
|
|
432
|
+
requiresShipping
|
|
433
|
+
title
|
|
434
|
+
image {
|
|
435
|
+
...ImageFragment
|
|
436
|
+
}
|
|
437
|
+
product {
|
|
438
|
+
handle
|
|
439
|
+
title
|
|
440
|
+
}
|
|
441
|
+
selectedOptions {
|
|
442
|
+
name
|
|
443
|
+
value
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
cost {
|
|
451
|
+
subtotalAmount {
|
|
452
|
+
...MoneyFragment
|
|
453
|
+
}
|
|
454
|
+
totalAmount {
|
|
455
|
+
...MoneyFragment
|
|
456
|
+
}
|
|
457
|
+
totalDutyAmount {
|
|
458
|
+
...MoneyFragment
|
|
459
|
+
}
|
|
460
|
+
totalTaxAmount {
|
|
461
|
+
...MoneyFragment
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
note
|
|
465
|
+
attributes {
|
|
466
|
+
key
|
|
467
|
+
value
|
|
468
|
+
}
|
|
469
|
+
discountCodes {
|
|
470
|
+
code
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
fragment MoneyFragment on MoneyV2 {
|
|
475
|
+
currencyCode
|
|
476
|
+
amount
|
|
477
|
+
}
|
|
478
|
+
fragment ImageFragment on Image {
|
|
479
|
+
id
|
|
480
|
+
url
|
|
481
|
+
altText
|
|
482
|
+
width
|
|
483
|
+
height
|
|
484
|
+
}
|
|
485
|
+
`;
|
|
@@ -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 = {
|
|
@@ -117,6 +121,12 @@ export declare type CartCreateEvent = {
|
|
|
117
121
|
type: 'CART_CREATE';
|
|
118
122
|
payload: CartInput;
|
|
119
123
|
};
|
|
124
|
+
export declare type CartSetEvent = {
|
|
125
|
+
type: 'CART_SET';
|
|
126
|
+
payload: {
|
|
127
|
+
cart: CartFragmentFragment;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
120
130
|
export declare type CartLineAddEvent = {
|
|
121
131
|
type: 'CARTLINE_ADD';
|
|
122
132
|
payload: {
|
|
@@ -159,7 +169,7 @@ export declare type DiscountCodesUpdateEvent = {
|
|
|
159
169
|
discountCodes: string[];
|
|
160
170
|
};
|
|
161
171
|
};
|
|
162
|
-
export declare type CartMachineActionEvent = CartFetchEvent | CartCreateEvent | CartLineAddEvent | CartLineRemoveEvent | CartLineUpdateEvent | NoteUpdateEvent | BuyerIdentityUpdateEvent | CartAttributesUpdateEvent | DiscountCodesUpdateEvent;
|
|
172
|
+
export declare type CartMachineActionEvent = CartFetchEvent | CartCreateEvent | CartSetEvent | CartLineAddEvent | CartLineRemoveEvent | CartLineUpdateEvent | NoteUpdateEvent | BuyerIdentityUpdateEvent | CartAttributesUpdateEvent | DiscountCodesUpdateEvent;
|
|
163
173
|
export declare type CartMachineFetchResultEvent = {
|
|
164
174
|
type: 'CART_COMPLETED';
|
|
165
175
|
payload: {
|
|
@@ -170,6 +180,7 @@ export declare type CartMachineFetchResultEvent = {
|
|
|
170
180
|
payload: {
|
|
171
181
|
cartActionEvent: CartMachineActionEvent;
|
|
172
182
|
cart: Cart;
|
|
183
|
+
rawCartResult: CartFragmentFragment;
|
|
173
184
|
};
|
|
174
185
|
} | {
|
|
175
186
|
type: 'ERROR';
|
|
@@ -183,30 +194,40 @@ export declare type CartMachineTypeState = {
|
|
|
183
194
|
value: 'uninitialized';
|
|
184
195
|
context: CartMachineContext & {
|
|
185
196
|
cart: undefined;
|
|
197
|
+
lastValidCart: undefined;
|
|
198
|
+
prevCart: undefined;
|
|
186
199
|
errors?: any;
|
|
187
200
|
};
|
|
188
201
|
} | {
|
|
189
202
|
value: 'initializationError';
|
|
190
203
|
context: CartMachineContext & {
|
|
191
204
|
cart: undefined;
|
|
205
|
+
lastValidCart: undefined;
|
|
206
|
+
prevCart: undefined;
|
|
192
207
|
errors: any;
|
|
193
208
|
};
|
|
194
209
|
} | {
|
|
195
210
|
value: 'cartCompleted';
|
|
196
211
|
context: CartMachineContext & {
|
|
197
212
|
cart: undefined;
|
|
213
|
+
prevCart?: Cart;
|
|
214
|
+
lastValidCart: undefined;
|
|
198
215
|
errors: any;
|
|
199
216
|
};
|
|
200
217
|
} | {
|
|
201
218
|
value: 'idle';
|
|
202
219
|
context: CartMachineContext & {
|
|
203
220
|
cart: Cart;
|
|
221
|
+
prevCart?: Cart;
|
|
222
|
+
lastValidCart?: Cart;
|
|
204
223
|
errors?: any;
|
|
205
224
|
};
|
|
206
225
|
} | {
|
|
207
226
|
value: 'error';
|
|
208
227
|
context: CartMachineContext & {
|
|
209
228
|
cart?: Cart;
|
|
229
|
+
prevCart?: Cart;
|
|
230
|
+
lastValidCart?: Cart;
|
|
210
231
|
errors: any;
|
|
211
232
|
};
|
|
212
233
|
} | {
|
|
@@ -249,5 +270,6 @@ export declare type CartMachineActions = {
|
|
|
249
270
|
cartAttributesUpdateAction: CartMachineAction;
|
|
250
271
|
discountCodesUpdateAction: CartMachineAction;
|
|
251
272
|
onCartActionEntry?: CartMachineAction;
|
|
273
|
+
onCartActionOptimisticUI?: StateMachine.AssignActionObject<CartMachineContext, CartMachineEvent>;
|
|
252
274
|
onCartActionComplete?: CartMachineAction;
|
|
253
275
|
};
|
|
@@ -1,16 +1,21 @@
|
|
|
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
|
-
|
|
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;
|
|
11
14
|
/** An object with fields that correspond to the Storefront API's [Cart object](https://shopify.dev/api/storefront/latest/objects/cart). */
|
|
12
15
|
data?: CartFragmentFragment;
|
|
13
16
|
/** 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
|
-
cartFragment
|
|
15
|
-
|
|
17
|
+
cartFragment: string;
|
|
18
|
+
/** The ISO country code for i18n. */
|
|
19
|
+
countryCode?: CountryCode;
|
|
20
|
+
}): readonly [StateMachine.State<CartMachineContext, CartMachineEvent, CartMachineTypeState>, (event: "CART_FETCH" | "CART_CREATE" | "CART_SET" | "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
21
|
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: [
|
|
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
|
-
|
|
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:
|
|
22
|
-
|
|
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
|
-
|
|
29
|
-
|
|
39
|
+
prevCart: (_) => undefined,
|
|
40
|
+
cart: (_) => undefined,
|
|
41
|
+
lastValidCart: (_) => undefined,
|
|
42
|
+
errors: (_) => undefined,
|
|
30
43
|
}),
|
|
31
44
|
},
|
|
32
45
|
},
|
|
@@ -40,8 +53,14 @@ const INITIALIZING_CART_EVENTS = {
|
|
|
40
53
|
CART_CREATE: {
|
|
41
54
|
target: 'cartCreating',
|
|
42
55
|
},
|
|
43
|
-
|
|
44
|
-
target: '
|
|
56
|
+
CART_SET: {
|
|
57
|
+
target: 'idle',
|
|
58
|
+
actions: [
|
|
59
|
+
assign({
|
|
60
|
+
rawCartResult: (_, event) => event.payload.cart,
|
|
61
|
+
cart: (_, event) => cartFromGraphQL(event.payload.cart),
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
45
64
|
},
|
|
46
65
|
};
|
|
47
66
|
const UPDATING_CART_EVENTS = {
|
|
@@ -81,10 +100,10 @@ const cartMachine = createMachine({
|
|
|
81
100
|
on: INITIALIZING_CART_EVENTS,
|
|
82
101
|
},
|
|
83
102
|
idle: {
|
|
84
|
-
on: UPDATING_CART_EVENTS,
|
|
103
|
+
on: { ...INITIALIZING_CART_EVENTS, ...UPDATING_CART_EVENTS },
|
|
85
104
|
},
|
|
86
105
|
error: {
|
|
87
|
-
on: UPDATING_CART_EVENTS,
|
|
106
|
+
on: { ...INITIALIZING_CART_EVENTS, ...UPDATING_CART_EVENTS },
|
|
88
107
|
},
|
|
89
108
|
cartFetching: invokeCart('cartFetchAction', {
|
|
90
109
|
errorTarget: 'initializationError',
|
|
@@ -101,10 +120,11 @@ const cartMachine = createMachine({
|
|
|
101
120
|
discountCodesUpdating: invokeCart('discountCodesUpdateAction'),
|
|
102
121
|
},
|
|
103
122
|
});
|
|
104
|
-
export function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCartActionComplete, data: cart, cartFragment, }) {
|
|
123
|
+
export function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCartActionOptimisticUI, onCartActionComplete, data: cart, cartFragment, countryCode, }) {
|
|
105
124
|
const { cartFetch, cartCreate, cartLineAdd, cartLineUpdate, cartLineRemove, noteUpdate, buyerIdentityUpdate, cartAttributesUpdate, discountCodesUpdate, } = useCartActions({
|
|
106
125
|
numCartLines,
|
|
107
126
|
cartFragment,
|
|
127
|
+
countryCode,
|
|
108
128
|
});
|
|
109
129
|
const [state, send, service] = useMachine(cartMachine, {
|
|
110
130
|
actions: {
|
|
@@ -116,7 +136,7 @@ export function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCart
|
|
|
116
136
|
send(resultEvent);
|
|
117
137
|
},
|
|
118
138
|
cartCreateAction: async (_, event) => {
|
|
119
|
-
if (event.type !== 'CART_CREATE'
|
|
139
|
+
if (event.type !== 'CART_CREATE')
|
|
120
140
|
return;
|
|
121
141
|
const { data, errors } = await cartCreate(event?.payload);
|
|
122
142
|
const resultEvent = eventFromFetchResult(event, data?.cartCreate?.cart, errors);
|
|
@@ -178,6 +198,11 @@ export function useCartAPIStateMachine({ numCartLines, onCartActionEntry, onCart
|
|
|
178
198
|
}
|
|
179
199
|
},
|
|
180
200
|
}),
|
|
201
|
+
...(onCartActionOptimisticUI && {
|
|
202
|
+
onCartActionOptimisticUI: assign((context, event) => {
|
|
203
|
+
return onCartActionOptimisticUI(context, event);
|
|
204
|
+
}),
|
|
205
|
+
}),
|
|
181
206
|
...(onCartActionComplete && {
|
|
182
207
|
onCartActionComplete: (context, event) => {
|
|
183
208
|
if (isCartFetchResultEvent(event)) {
|
|
@@ -211,7 +236,11 @@ function eventFromFetchResult(cartActionEvent, cart, errors) {
|
|
|
211
236
|
}
|
|
212
237
|
return {
|
|
213
238
|
type: 'RESOLVE',
|
|
214
|
-
payload: {
|
|
239
|
+
payload: {
|
|
240
|
+
cart: cartFromGraphQL(cart),
|
|
241
|
+
rawCartResult: cart,
|
|
242
|
+
cartActionEvent,
|
|
243
|
+
},
|
|
215
244
|
};
|
|
216
245
|
}
|
|
217
246
|
function isCartActionEvent(event) {
|
|
@@ -152,9 +152,12 @@ async function processRequest(handleRequest, App, url, request, sessionApi, opti
|
|
|
152
152
|
const rsc = runRSC({ App, state, log, request, response });
|
|
153
153
|
if (isRSCRequest) {
|
|
154
154
|
const buffered = await bufferReadableStream(rsc.readable.getReader());
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
const rscDidError = !!rsc.didError();
|
|
156
|
+
postRequestTasks('rsc', rscDidError ? 500 : 200, request, response, rscDidError);
|
|
157
|
+
if (rscDidError) {
|
|
158
|
+
response.headers.set('cache-control', response.cacheControlHeader);
|
|
159
|
+
cacheResponse(response, request, [buffered], revalidate);
|
|
160
|
+
}
|
|
158
161
|
return new Response(buffered, {
|
|
159
162
|
headers: response.headers,
|
|
160
163
|
});
|
|
@@ -319,7 +322,7 @@ async function runSSR({ rsc, state, request, response, nodeResponse, nonce, dev,
|
|
|
319
322
|
// Last SSR write might be pending, delay closing the writable one tick
|
|
320
323
|
setTimeout(() => {
|
|
321
324
|
writable.close();
|
|
322
|
-
postRequestTasks('str', responseOptions.status, request, response);
|
|
325
|
+
postRequestTasks('str', responseOptions.status, request, response, !!didError());
|
|
323
326
|
response.status = responseOptions.status;
|
|
324
327
|
cacheResponse(response, request, savedChunks, revalidate);
|
|
325
328
|
}, 0);
|
|
@@ -328,7 +331,7 @@ async function runSSR({ rsc, state, request, response, nodeResponse, nonce, dev,
|
|
|
328
331
|
else {
|
|
329
332
|
// Redirects do not write body
|
|
330
333
|
writable.close();
|
|
331
|
-
postRequestTasks('str', responseOptions.status, request, response);
|
|
334
|
+
postRequestTasks('str', responseOptions.status, request, response, !!didError());
|
|
332
335
|
}
|
|
333
336
|
if (response.canStream()) {
|
|
334
337
|
return new Response(transform.readable, responseOptions);
|
|
@@ -376,7 +379,7 @@ async function runSSR({ rsc, state, request, response, nodeResponse, nonce, dev,
|
|
|
376
379
|
log.trace('node complete ssr');
|
|
377
380
|
if (!revalidate &&
|
|
378
381
|
(response.canStream() || nodeResponse.writableEnded)) {
|
|
379
|
-
postRequestTasks('str', nodeResponse.statusCode, request, response);
|
|
382
|
+
postRequestTasks('str', nodeResponse.statusCode, request, response, !!didError());
|
|
380
383
|
return;
|
|
381
384
|
}
|
|
382
385
|
writeHeadToNodeResponse(nodeResponse, response, log, didError());
|
|
@@ -396,8 +399,8 @@ async function runSSR({ rsc, state, request, response, nodeResponse, nonce, dev,
|
|
|
396
399
|
let html = template;
|
|
397
400
|
if (!error) {
|
|
398
401
|
html = assembleHtml({ ssrHtml, rscPayload, request, template });
|
|
399
|
-
postRequestTasks('ssr', nodeResponse.statusCode, request, response);
|
|
400
402
|
}
|
|
403
|
+
postRequestTasks('ssr', nodeResponse.statusCode, request, response, !!didError());
|
|
401
404
|
if (!nodeResponse.writableEnded) {
|
|
402
405
|
nodeResponse.write(html);
|
|
403
406
|
nodeResponse.end();
|
|
@@ -506,8 +509,8 @@ function isRedirect(response) {
|
|
|
506
509
|
function flightContainer(chunk) {
|
|
507
510
|
return `<meta data-flight="${htmlEncode(chunk)}" />`;
|
|
508
511
|
}
|
|
509
|
-
function postRequestTasks(type, status, request, response) {
|
|
510
|
-
logServerResponse(type, request, status);
|
|
512
|
+
function postRequestTasks(type, status, request, response, didError) {
|
|
513
|
+
logServerResponse(type, request, status, didError);
|
|
511
514
|
logCacheControlHeaders(type, request, response);
|
|
512
515
|
logQueryTimings(type, request);
|
|
513
516
|
request.savePreloadQueries();
|
|
@@ -74,7 +74,9 @@ function getCookieDomain(cookieDomain) {
|
|
|
74
74
|
function trackPageView(payload) {
|
|
75
75
|
microSessionCount += 1;
|
|
76
76
|
try {
|
|
77
|
-
|
|
77
|
+
payload &&
|
|
78
|
+
payload.shopify &&
|
|
79
|
+
sendToServer(storefrontPageViewSchema(payload));
|
|
78
80
|
}
|
|
79
81
|
catch (error) {
|
|
80
82
|
console.error(`Error Shopify analytics: ${ClientAnalytics.eventNames.PAGE_VIEW}`, error);
|
|
@@ -94,7 +96,7 @@ function buildStorefrontPageViewPayload(payload) {
|
|
|
94
96
|
const shopify = payload.shopify;
|
|
95
97
|
let formattedData = {
|
|
96
98
|
appClientId: '6167201',
|
|
97
|
-
hydrogenSubchannelId: shopify.storefrontId,
|
|
99
|
+
hydrogenSubchannelId: shopify.storefrontId || '0',
|
|
98
100
|
isPersistentCookie: shopify.isPersistentCookie,
|
|
99
101
|
uniqToken: shopify.userId,
|
|
100
102
|
visitToken: shopify.sessionId,
|
|
@@ -10,7 +10,7 @@ import { CacheLong } from '../../../Cache/strategies/index.js';
|
|
|
10
10
|
import { gql } from '../../../../utilities/graphql-tag.js';
|
|
11
11
|
import { SHOPIFY_Y, SHOPIFY_S } from '../../../../constants.js';
|
|
12
12
|
export function ShopifyAnalytics({ cookieDomain }) {
|
|
13
|
-
const { storeDomain } = useShop();
|
|
13
|
+
const { storeDomain, storefrontId } = useShop();
|
|
14
14
|
const request = useServerRequest();
|
|
15
15
|
const cookies = parse(request.headers.get('Cookie') || '');
|
|
16
16
|
const domain = cookieDomain || storeDomain;
|
|
@@ -23,7 +23,7 @@ export function ShopifyAnalytics({ cookieDomain }) {
|
|
|
23
23
|
shopify: {
|
|
24
24
|
shopId: id,
|
|
25
25
|
currency: currencyCode,
|
|
26
|
-
storefrontId
|
|
26
|
+
storefrontId,
|
|
27
27
|
acceptedLanguage: request.headers.get('Accept-Language')?.replace(/-.*/, '') || 'en',
|
|
28
28
|
isPersistentCookie: !!cookies[SHOPIFY_S] || !!cookies[SHOPIFY_Y],
|
|
29
29
|
},
|
|
@@ -16,7 +16,8 @@ export const CLIENT_CONTEXT_ALLOW_LIST = [
|
|
|
16
16
|
function makeShopifyContext(shopifyConfig) {
|
|
17
17
|
const countryCode = shopifyConfig.defaultCountryCode ?? DEFAULT_COUNTRY;
|
|
18
18
|
const languageCode = shopifyConfig.defaultLanguageCode ?? DEFAULT_LANGUAGE;
|
|
19
|
-
const storefrontId =
|
|
19
|
+
const storefrontId = shopifyConfig.storefrontId ??
|
|
20
|
+
getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
|
|
20
21
|
const shopifyProviderServerValue = {
|
|
21
22
|
defaultCountryCode: countryCode.toUpperCase(),
|
|
22
23
|
defaultLanguageCode: languageCode.toUpperCase(),
|
package/dist/esnext/index.d.ts
CHANGED
|
@@ -40,5 +40,5 @@ export { CartQuery } from './components/CartProvider/cart-queries.js';
|
|
|
40
40
|
export { fetchSync } from './foundation/fetchSync/server/fetchSync.js';
|
|
41
41
|
export { type HydrogenRequest } from './foundation/HydrogenRequest/HydrogenRequest.server.js';
|
|
42
42
|
export { type HydrogenResponse } from './foundation/HydrogenResponse/HydrogenResponse.server.js';
|
|
43
|
-
export { type HydrogenRouteProps } from './types.js';
|
|
43
|
+
export { type HydrogenRouteProps, type CachingStrategy } from './types.js';
|
|
44
44
|
export { type ResourceGetter as HydrogenApiRoute, RequestOptions as HydrogenApiRouteOptions, } from './utilities/apiRoutes.js';
|
|
@@ -161,7 +161,7 @@ export async function renderApiRoute(request, route, hydrogenConfig, { session,
|
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
163
|
if (!suppressLog) {
|
|
164
|
-
logServerResponse('api', request, response.status ?? 200);
|
|
164
|
+
logServerResponse('api', request, response.status ?? 200, false);
|
|
165
165
|
}
|
|
166
166
|
if (response instanceof Request) {
|
|
167
167
|
const url = new URL(request.url);
|
|
@@ -24,5 +24,5 @@ export declare type RenderType = 'str' | 'rsc' | 'ssr' | 'api';
|
|
|
24
24
|
export declare function getLoggerWithContext(context: Partial<HydrogenRequest>): Logger;
|
|
25
25
|
export declare const log: Logger;
|
|
26
26
|
export declare function setLogger(config?: LoggerConfig): void;
|
|
27
|
-
export declare function logServerResponse(type: RenderType, request: HydrogenRequest, responseStatus: number): void;
|
|
27
|
+
export declare function logServerResponse(type: RenderType, request: HydrogenRequest, responseStatus: number, didError: boolean): void;
|
|
28
28
|
export {};
|
|
@@ -72,7 +72,7 @@ const SERVER_RESPONSE_MAP = {
|
|
|
72
72
|
rsc: 'Server Components',
|
|
73
73
|
ssr: 'buffered SSR',
|
|
74
74
|
};
|
|
75
|
-
export function logServerResponse(type, request, responseStatus) {
|
|
75
|
+
export function logServerResponse(type, request, responseStatus, didError) {
|
|
76
76
|
const log = getLoggerWithContext(request);
|
|
77
77
|
const coloredResponseStatus = responseStatus >= 500
|
|
78
78
|
? red(responseStatus)
|
|
@@ -85,5 +85,5 @@ export function logServerResponse(type, request, responseStatus) {
|
|
|
85
85
|
const styledType = italic(fullType.padEnd(17));
|
|
86
86
|
const paddedTiming = ((getTime() - request.time).toFixed(2) + ' ms').padEnd(10);
|
|
87
87
|
const url = parseUrl(type, request.url);
|
|
88
|
-
log.debug(`${request.method} ${styledType} ${coloredResponseStatus} ${paddedTiming} ${url}`);
|
|
88
|
+
log.debug(`${request.method} ${styledType} ${coloredResponseStatus} ${didError || responseStatus >= 400 ? red('error') : green('ok ')} ${paddedTiming} ${url}`);
|
|
89
89
|
}
|
|
@@ -5,24 +5,25 @@ let secretTokenWarned = false;
|
|
|
5
5
|
let storefrontIdWarned = false;
|
|
6
6
|
export function getStorefrontApiRequestHeaders({ buyerIp, publicStorefrontToken, privateStorefrontToken, storefrontId, }) {
|
|
7
7
|
const headers = {};
|
|
8
|
-
if (!privateStorefrontToken
|
|
9
|
-
secretTokenWarned = true;
|
|
8
|
+
if (!privateStorefrontToken) {
|
|
10
9
|
privateStorefrontToken = getOxygenVariable(OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE);
|
|
11
|
-
if (!
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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: https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config');
|
|
17
|
+
}
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
|
-
if (!storefrontId
|
|
19
|
-
storefrontIdWarned = true;
|
|
20
|
+
if (!storefrontId) {
|
|
20
21
|
storefrontId = getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
|
|
21
|
-
if (!
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
if (!storefrontIdWarned) {
|
|
23
|
+
storefrontIdWarned = true;
|
|
24
|
+
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: https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config');
|
|
26
|
+
}
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
/**
|
package/dist/esnext/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LIB_VERSION = "1.4.
|
|
1
|
+
export declare const LIB_VERSION = "1.4.2";
|
package/dist/esnext/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const LIB_VERSION = '1.4.
|
|
1
|
+
export const LIB_VERSION = '1.4.2';
|