@magento/peregrine 12.2.0 → 12.3.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 (47) hide show
  1. package/lib/Apollo/clearCustomerDataFromCache.js +1 -0
  2. package/lib/Apollo/policies/index.js +9 -4
  3. package/lib/hooks/useGoogleReCaptcha/googleReCaptchaConfig.gql.js +16 -0
  4. package/lib/hooks/useGoogleReCaptcha/index.js +1 -0
  5. package/lib/hooks/useGoogleReCaptcha/useGoogleReCaptcha.js +210 -0
  6. package/lib/hooks/useMediaQuery.js +83 -0
  7. package/lib/hooks/useScript.js +68 -0
  8. package/lib/hooks/useSort.js +13 -2
  9. package/lib/talons/AccountInformationPage/useAccountInformationPage.js +23 -6
  10. package/lib/talons/CartPage/PriceAdjustments/GiftOptions/giftOptions.gql.js +36 -11
  11. package/lib/talons/CartPage/PriceAdjustments/GiftOptions/giftOptionsFragments.gql.js +19 -0
  12. package/lib/talons/CartPage/PriceAdjustments/GiftOptions/useGiftOptions.js +315 -94
  13. package/lib/talons/CartPage/PriceAdjustments/giftOptionsSection.gql.js +17 -0
  14. package/lib/talons/CartPage/PriceAdjustments/useGiftOptionsSection.js +61 -0
  15. package/lib/talons/CartPage/PriceSummary/__fixtures__/priceSummary.js +7 -2
  16. package/lib/talons/CartPage/PriceSummary/priceSummaryFragments.gql.js +7 -0
  17. package/lib/talons/CartPage/PriceSummary/queries/giftOptionsSummary.ce.js +8 -0
  18. package/lib/talons/CartPage/PriceSummary/queries/giftOptionsSummary.ee.js +15 -0
  19. package/lib/talons/CartPage/PriceSummary/useDiscountSummary.js +71 -0
  20. package/lib/talons/CartPage/PriceSummary/usePriceSummary.js +3 -1
  21. package/lib/talons/CartPage/ProductListing/EditModal/__fixtures__/configurableThumbnailSource.js +8 -0
  22. package/lib/talons/CartPage/ProductListing/EditModal/productForm.gql.js +11 -0
  23. package/lib/talons/CartPage/ProductListing/EditModal/useProductForm.js +15 -1
  24. package/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js +27 -5
  25. package/lib/talons/CheckoutPage/PaymentInformation/useCreditCard.js +36 -15
  26. package/lib/talons/CheckoutPage/useCheckoutPage.js +18 -3
  27. package/lib/talons/CmsDynamicBlock/client-schema.graphql +4 -0
  28. package/lib/talons/CmsDynamicBlock/cmsDynamicBlock.gql.js +113 -0
  29. package/lib/talons/CmsDynamicBlock/useCmsDynamicBlock.js +211 -0
  30. package/lib/talons/CreateAccount/useCreateAccount.js +29 -5
  31. package/lib/talons/ForgotPassword/useForgotPassword.js +26 -5
  32. package/lib/talons/Link/useLink.js +2 -1
  33. package/lib/talons/MiniCart/miniCartFragments.gql.js +4 -0
  34. package/lib/talons/MyAccount/useResetPassword.js +23 -5
  35. package/lib/talons/RootComponents/Category/useCategory.js +1 -1
  36. package/lib/talons/RootComponents/Product/useProduct.js +1 -6
  37. package/lib/talons/SearchPage/useSearchPage.js +1 -1
  38. package/lib/talons/SignIn/useSignIn.js +25 -6
  39. package/lib/talons/WishlistPage/createWishlist.gql.js +1 -0
  40. package/lib/talons/WishlistPage/useActionMenu.js +4 -4
  41. package/lib/talons/WishlistPage/useCreateWishlist.js +7 -4
  42. package/lib/talons/WishlistPage/useWishlistItem.js +3 -2
  43. package/lib/talons/WishlistPage/wishlistConfig.gql.ee.js +1 -0
  44. package/lib/talons/WishlistPage/wishlistItemFragments.gql.js +1 -0
  45. package/lib/util/configuredVariant.js +10 -6
  46. package/package.json +1 -1
  47. package/lib/talons/CartPage/PriceAdjustments/GiftOptions/client-schema.graphql +0 -7
@@ -0,0 +1,211 @@
1
+ import { useEffect, useCallback } from 'react';
2
+ import { useQuery } from '@apollo/client';
3
+ import { useLocation } from 'react-router-dom';
4
+
5
+ import { useCartContext } from '@magento/peregrine/lib/context/cart';
6
+ import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
7
+
8
+ import DEFAULT_OPERATIONS from './cmsDynamicBlock.gql';
9
+
10
+ export const flatten = cartData => {
11
+ const cartItems = cartData?.cart?.items || [];
12
+ const totalWeight = cartItems.reduce((prevItem, currentItem) => {
13
+ const { product, quantity, configured_variant } = currentItem;
14
+
15
+ const currentWeight = configured_variant
16
+ ? configured_variant.weight
17
+ : product.weight || 0;
18
+
19
+ return prevItem + currentWeight * quantity;
20
+ }, 0);
21
+ const shippingAddresses = cartData?.cart?.shipping_addresses || [];
22
+ const subtotalExcludingTax =
23
+ cartData?.cart?.prices?.subtotal_excluding_tax?.value || 0;
24
+ const subtotalIncludingTax =
25
+ cartData?.cart?.prices?.subtotal_including_tax?.value || 0;
26
+ const selectedPaymentMethod =
27
+ cartData?.cart?.selected_payment_method?.code || null;
28
+ const shippingCountryCode = shippingAddresses[0]?.country?.code || null;
29
+ const shippingPostCode = shippingAddresses[0]?.postcode || null;
30
+ const shippingRegionCode = shippingAddresses[0]?.region?.code || null;
31
+ const shippingRegionId = shippingAddresses[0]?.region?.region_id || null;
32
+ const selectedShippingMethod =
33
+ shippingAddresses[0]?.selected_shipping_method?.method_code || null;
34
+ const totalQuantity = cartData?.cart?.total_quantity || 0;
35
+
36
+ return JSON.stringify([
37
+ totalWeight,
38
+ subtotalExcludingTax,
39
+ subtotalIncludingTax,
40
+ selectedPaymentMethod,
41
+ shippingCountryCode,
42
+ shippingPostCode,
43
+ shippingRegionCode,
44
+ shippingRegionId,
45
+ selectedShippingMethod,
46
+ totalQuantity
47
+ ]);
48
+ };
49
+
50
+ /**
51
+ * This talon contains the logic for a cms dynamic block component.
52
+ * It performs effects and returns a data object containing values for rendering the component.
53
+ *
54
+ * This talon performs the following effects:
55
+ *
56
+ * - Get cms dynamic block based on type, locations and given uids
57
+ * - Get sales rules data from cart to refetch dynamic block query when conditions change
58
+ * - Update the {@link CmsDynamicBlockTalonProps} values with the data returned by the query
59
+ *
60
+ * @function
61
+ *
62
+ * @param {Array} props.locations - array of locations of cms dynamic blocks
63
+ * @param {(String|String[])} props.uids - single or multiple uids of cms dynamic blocks
64
+ * @param {String} props.type - type of cms dynamic blocks
65
+ * @param {CmsDynamicBlockOperations} [props.operations]
66
+ *
67
+ * @returns {CmsDynamicBlockTalonProps}
68
+ *
69
+ * @example <caption>Importing into your project</caption>
70
+ * import { useCmsDynamicBlock } from '@magento/peregrine/lib/talons/CmsDynamicBlock/useCmsDynamicBlock';
71
+ */
72
+ export const useCmsDynamicBlock = props => {
73
+ const { locations, uids, type } = props;
74
+ const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
75
+ const {
76
+ getCmsDynamicBlocksQuery,
77
+ getSalesRulesDataQuery,
78
+ getStoreConfigData,
79
+ getProductDetailQuery
80
+ } = operations;
81
+
82
+ const [{ cartId }] = useCartContext();
83
+ const { pathname } = useLocation();
84
+
85
+ // Get Product Data from cache
86
+ const { data: storeConfigData, loading: storeConfigLoading } = useQuery(
87
+ getStoreConfigData
88
+ );
89
+ const slug = pathname.split('/').pop();
90
+ const productUrlSuffix = storeConfigData?.storeConfig?.product_url_suffix;
91
+ const urlKey = productUrlSuffix ? slug.replace(productUrlSuffix, '') : slug;
92
+ const { data: productData, loading: productDataLoading } = useQuery(
93
+ getProductDetailQuery,
94
+ {
95
+ skip: !storeConfigData,
96
+ variables: {
97
+ urlKey
98
+ }
99
+ }
100
+ );
101
+
102
+ // @TODO: Update with uid when done in Product Root Component
103
+ const products =
104
+ productData?.products?.items && productData.products.items.length > 0
105
+ ? productData.products.items
106
+ : [];
107
+ const productUid = products.find(item => item.url_key === urlKey)?.uid;
108
+
109
+ const { client, loading, error, data, refetch } = useQuery(
110
+ getCmsDynamicBlocksQuery,
111
+ {
112
+ variables: {
113
+ cartId,
114
+ type,
115
+ locations,
116
+ uids,
117
+ ...(productUid ? { productId: productUid } : {})
118
+ },
119
+ skip: !cartId
120
+ }
121
+ );
122
+
123
+ const { loading: cartLoading, data: cartData } = useQuery(
124
+ getSalesRulesDataQuery,
125
+ {
126
+ variables: { cartId },
127
+ skip: !cartId
128
+ }
129
+ );
130
+
131
+ const currentSalesRulesData = flatten(cartData);
132
+ const isLoading =
133
+ loading || cartLoading || storeConfigLoading || productDataLoading;
134
+ const cachedSalesRulesData = data?.dynamicBlocks?.salesRulesData;
135
+
136
+ const updateSalesRulesData = useCallback(
137
+ (currentData, currentSalesRulesData) => {
138
+ client.writeQuery({
139
+ query: getCmsDynamicBlocksQuery,
140
+ data: {
141
+ dynamicBlocks: {
142
+ ...currentData,
143
+ salesRulesData: currentSalesRulesData
144
+ }
145
+ },
146
+ variables: {
147
+ cartId,
148
+ type,
149
+ locations,
150
+ uids,
151
+ ...(productUid ? { productId: productUid } : {})
152
+ },
153
+ skip: !cartId
154
+ });
155
+ },
156
+ [
157
+ cartId,
158
+ client,
159
+ getCmsDynamicBlocksQuery,
160
+ locations,
161
+ productUid,
162
+ type,
163
+ uids
164
+ ]
165
+ );
166
+
167
+ useEffect(() => {
168
+ if (data && cachedSalesRulesData !== currentSalesRulesData) {
169
+ if (!cachedSalesRulesData) {
170
+ // Save cart conditions data in cache
171
+ updateSalesRulesData(data.dynamicBlocks, currentSalesRulesData);
172
+ } else {
173
+ // Refetch cms data if there's a mismatch
174
+ refetch();
175
+ }
176
+ }
177
+ }, [
178
+ cachedSalesRulesData,
179
+ currentSalesRulesData,
180
+ data,
181
+ refetch,
182
+ updateSalesRulesData
183
+ ]);
184
+
185
+ return {
186
+ loading: isLoading,
187
+ error,
188
+ data
189
+ };
190
+ };
191
+
192
+ /** JSDocs type definitions */
193
+
194
+ /**
195
+ * Props data to use when rendering a gift options component.
196
+ *
197
+ * @typedef {Object} CmsDynamicBlockTalonProps
198
+ *
199
+ * @property {Boolean} loading Query loading indicator.
200
+ * @property {Object} error Error of the GraphQl query.
201
+ * @property {Object} data Data returned by the query.
202
+ */
203
+
204
+ /**
205
+ * This is a type used by the {@link useCmsDynamicBlock} talon.
206
+ *
207
+ * @typedef {Object} CmsDynamicBlockOperations
208
+ *
209
+ * @property {GraphQLAST} getSalesRulesDataQuery get sales rules data from cart.
210
+ * @property {GraphQLAST} getCmsDynamicBlocksQuery get cms dynamics blocks.
211
+ */
@@ -6,6 +6,7 @@ import { useUserContext } from '../../context/user';
6
6
  import { useCartContext } from '../../context/cart';
7
7
  import { useAwaitQuery } from '../../hooks/useAwaitQuery';
8
8
  import { retrieveCartId } from '../../store/actions/cart';
9
+ import { useGoogleReCaptcha } from '../../hooks/useGoogleReCaptcha';
9
10
 
10
11
  import DEFAULT_OPERATIONS from './createAccount.gql';
11
12
 
@@ -68,6 +69,15 @@ export const useCreateAccount = props => {
68
69
  const fetchUserDetails = useAwaitQuery(getCustomerQuery);
69
70
  const fetchCartDetails = useAwaitQuery(getCartDetailsQuery);
70
71
 
72
+ const {
73
+ generateReCaptchaData,
74
+ recaptchaLoading,
75
+ recaptchaWidgetProps
76
+ } = useGoogleReCaptcha({
77
+ currentForm: 'CUSTOMER_CREATE',
78
+ formAction: 'createAccount'
79
+ });
80
+
71
81
  const handleCancel = useCallback(() => {
72
82
  onCancel();
73
83
  }, [onCancel]);
@@ -79,6 +89,9 @@ export const useCreateAccount = props => {
79
89
  // Get source cart id (guest cart id).
80
90
  const sourceCartId = cartId;
81
91
 
92
+ // Get reCaptchaV3 Data for createAccount mutation
93
+ const recaptchaDataForCreateAccount = await generateReCaptchaData();
94
+
82
95
  // Create the account and then sign in.
83
96
  await createAccount({
84
97
  variables: {
@@ -87,13 +100,19 @@ export const useCreateAccount = props => {
87
100
  lastname: formValues.customer.lastname,
88
101
  password: formValues.password,
89
102
  is_subscribed: !!formValues.subscribe
90
- }
103
+ },
104
+ ...recaptchaDataForCreateAccount
91
105
  });
106
+
107
+ // Get reCaptchaV3 Data for signIn mutation
108
+ const recaptchaDataForSignIn = await generateReCaptchaData();
109
+
92
110
  const signInResponse = await signIn({
93
111
  variables: {
94
112
  email: formValues.customer.email,
95
113
  password: formValues.password
96
- }
114
+ },
115
+ ...recaptchaDataForSignIn
97
116
  });
98
117
  const token = signInResponse.data.generateCustomerToken.token;
99
118
  await setToken(token);
@@ -137,11 +156,12 @@ export const useCreateAccount = props => {
137
156
  },
138
157
  [
139
158
  cartId,
140
- apolloClient,
141
- removeCart,
159
+ generateReCaptchaData,
142
160
  createAccount,
143
161
  signIn,
144
162
  setToken,
163
+ apolloClient,
164
+ removeCart,
145
165
  createCart,
146
166
  fetchCartId,
147
167
  mergeCarts,
@@ -176,7 +196,8 @@ export const useCreateAccount = props => {
176
196
  handleCancel,
177
197
  handleSubmit,
178
198
  initialValues: sanitizedInitialValues,
179
- isDisabled: isSubmitting || isGettingDetails
199
+ isDisabled: isSubmitting || isGettingDetails || recaptchaLoading,
200
+ recaptchaWidgetProps
180
201
  };
181
202
  };
182
203
 
@@ -237,4 +258,7 @@ export const useCreateAccount = props => {
237
258
  * @property {Function} handleSubmit callback function to handle form submission
238
259
  * @property {SanitizedInitialValues} initialValues initial values for the create account form
239
260
  * @property {Boolean} isDisabled true if either details are being fetched or form is being submitted. False otherwise.
261
+ * @property {Object} recaptchaWidgetProps - Props for the GoogleReCaptcha component.
262
+ * @property {Function} recaptchaWidgetProps.containerElement - Container reference callback.
263
+ * @property {Boolean} recaptchaWidgetProps.shouldRender - Checks if component should be rendered.
240
264
  */
@@ -1,6 +1,8 @@
1
1
  import { useCallback, useState } from 'react';
2
2
  import { useMutation } from '@apollo/client';
3
3
 
4
+ import { useGoogleReCaptcha } from '@magento/peregrine/lib/hooks/useGoogleReCaptcha';
5
+
4
6
  /**
5
7
  * Returns props necessary to render a ForgotPassword form.
6
8
  *
@@ -25,17 +27,34 @@ export const useForgotPassword = props => {
25
27
  { error: requestResetEmailError, loading: isResettingPassword }
26
28
  ] = useMutation(mutations.requestPasswordResetEmailMutation);
27
29
 
30
+ const {
31
+ recaptchaLoading,
32
+ generateReCaptchaData,
33
+ recaptchaWidgetProps
34
+ } = useGoogleReCaptcha({
35
+ currentForm: 'CUSTOMER_FORGOT_PASSWORD',
36
+ formAction: 'forgotPassword'
37
+ });
38
+
28
39
  const handleFormSubmit = useCallback(
29
40
  async ({ email }) => {
30
41
  try {
31
- await requestResetEmail({ variables: { email } });
42
+ const reCaptchaData = await generateReCaptchaData();
43
+
44
+ await requestResetEmail({
45
+ variables: { email },
46
+ ...reCaptchaData
47
+ });
48
+
32
49
  setForgotPasswordEmail(email);
33
50
  setCompleted(true);
34
- } catch (err) {
51
+ } catch (error) {
52
+ // Error is logged by apollo link - no need to double log.
53
+
35
54
  setCompleted(false);
36
55
  }
37
56
  },
38
- [requestResetEmail]
57
+ [generateReCaptchaData, requestResetEmail]
39
58
  );
40
59
 
41
60
  const handleCancel = useCallback(() => {
@@ -48,7 +67,8 @@ export const useForgotPassword = props => {
48
67
  handleCancel,
49
68
  handleFormSubmit,
50
69
  hasCompleted,
51
- isResettingPassword
70
+ isResettingPassword: isResettingPassword || recaptchaLoading,
71
+ recaptchaWidgetProps
52
72
  };
53
73
  };
54
74
 
@@ -77,5 +97,6 @@ export const useForgotPassword = props => {
77
97
  * @property {Function} handleCancel Callback function to handle form cancellations
78
98
  * @property {Function} handleFormSubmit Callback function to handle form submission
79
99
  * @property {Boolean} hasCompleted True if password reset mutation has completed. False otherwise
80
- * @property {Boolean} isResettingPassword True if password reset mutation is in progress. False otherwise
100
+ * @property {Boolean} isResettingPassword True if form awaits events. False otherwise
101
+ * @property {Object} recaptchaWidgetProps Props for the GoogleReCaptcha component
81
102
  */
@@ -6,7 +6,8 @@ import mergeOperations from '../../util/shallowMerge';
6
6
  import DEFAULT_OPERATIONS from '../MagentoRoute/magentoRoute.gql';
7
7
 
8
8
  const useLink = (props, passedOperations = {}) => {
9
- const { prefetchType: shouldPrefetch, innerRef: originalRef, to } = props;
9
+ const { innerRef: originalRef, to } = props;
10
+ const shouldPrefetch = props.prefetchType || props.shouldPrefetch;
10
11
  const operations = shouldPrefetch
11
12
  ? mergeOperations(DEFAULT_OPERATIONS, passedOperations)
12
13
  : {};
@@ -10,6 +10,10 @@ export const MiniCartFragment = gql`
10
10
  currency
11
11
  value
12
12
  }
13
+ subtotal_including_tax {
14
+ currency
15
+ value
16
+ }
13
17
  }
14
18
  ...ProductListFragment
15
19
  }
@@ -2,6 +2,8 @@ import { useState, useMemo, useCallback } from 'react';
2
2
  import { useLocation } from 'react-router-dom';
3
3
  import { useMutation } from '@apollo/client';
4
4
 
5
+ import { useGoogleReCaptcha } from '@magento/peregrine/lib/hooks/useGoogleReCaptcha';
6
+
5
7
  /**
6
8
  * Returns props necessary to render a ResetPassword form.
7
9
  *
@@ -22,6 +24,15 @@ export const useResetPassword = props => {
22
24
  { error: resetPasswordErrors, loading }
23
25
  ] = useMutation(mutations.resetPasswordMutation);
24
26
 
27
+ const {
28
+ recaptchaLoading,
29
+ generateReCaptchaData,
30
+ recaptchaWidgetProps
31
+ } = useGoogleReCaptcha({
32
+ currentForm: 'CUSTOMER_FORGOT_PASSWORD',
33
+ formAction: 'resetPassword'
34
+ });
35
+
25
36
  const searchParams = useMemo(() => new URLSearchParams(location.search), [
26
37
  location
27
38
  ]);
@@ -31,25 +42,31 @@ export const useResetPassword = props => {
31
42
  async ({ email, newPassword }) => {
32
43
  try {
33
44
  if (email && token && newPassword) {
45
+ const reCaptchaData = await generateReCaptchaData();
46
+
34
47
  await resetPassword({
35
- variables: { email, token, newPassword }
48
+ variables: { email, token, newPassword },
49
+ ...reCaptchaData
36
50
  });
37
51
 
38
52
  setHasCompleted(true);
39
53
  }
40
54
  } catch (err) {
55
+ // Error is logged by apollo link - no need to double log.
56
+
41
57
  setHasCompleted(false);
42
58
  }
43
59
  },
44
- [resetPassword, token]
60
+ [generateReCaptchaData, resetPassword, token]
45
61
  );
46
62
 
47
63
  return {
48
64
  formErrors: [resetPasswordErrors],
49
65
  handleSubmit,
50
66
  hasCompleted,
51
- loading,
52
- token
67
+ loading: loading || recaptchaLoading,
68
+ token,
69
+ recaptchaWidgetProps
53
70
  };
54
71
  };
55
72
 
@@ -76,6 +93,7 @@ export const useResetPassword = props => {
76
93
  * @property {Array} formErrors A list of form errors
77
94
  * @property {Function} handleSubmit Callback function to handle form submission
78
95
  * @property {Boolean} hasCompleted True if password reset mutation has completed. False otherwise
79
- * @property {Boolean} loading True if password reset mutation is in progress. False otherwise
96
+ * @property {Boolean} loading True if form awaits events. False otherwise
80
97
  * @property {String} token token needed for password reset, will be sent in the mutation
98
+ * @property {Object} recaptchaWidgetProps Props for the GoogleReCaptcha component
81
99
  */
@@ -54,7 +54,7 @@ export const useCategory = props => {
54
54
  const { currentPage, totalPages } = paginationValues;
55
55
  const { setCurrentPage, setTotalPages } = paginationApi;
56
56
 
57
- const sortProps = useSort();
57
+ const sortProps = useSort({ sortFromSearch: false });
58
58
  const [currentSort] = sortProps;
59
59
 
60
60
  // Keep track of the sort criteria so we can tell when they change.
@@ -40,13 +40,8 @@ export const useProduct = props => {
40
40
  nextFetchPolicy: 'cache-first'
41
41
  });
42
42
 
43
- const productUrlSuffix = useMemo(() => {
44
- if (storeConfigData) {
45
- return storeConfigData.storeConfig.product_url_suffix;
46
- }
47
- }, [storeConfigData]);
48
-
49
43
  const slug = pathname.split('/').pop();
44
+ const productUrlSuffix = storeConfigData?.storeConfig?.product_url_suffix;
50
45
  const urlKey = productUrlSuffix ? slug.replace(productUrlSuffix, '') : slug;
51
46
 
52
47
  const { error, loading, data } = useQuery(getProductDetailQuery, {
@@ -44,7 +44,7 @@ export const useSearchPage = (props = {}) => {
44
44
 
45
45
  const pageSize = pageSizeData && pageSizeData.storeConfig.grid_per_page;
46
46
 
47
- const sortProps = useSort();
47
+ const sortProps = useSort({ sortFromSearch: true });
48
48
  const [currentSort] = sortProps;
49
49
  const { sortAttribute, sortDirection } = currentSort;
50
50
  // Keep track of the sort criteria so we can tell when they change.
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useRef, useState, useMemo } from 'react';
2
2
  import { useApolloClient, useMutation } from '@apollo/client';
3
3
 
4
+ import { useGoogleReCaptcha } from '../../hooks/useGoogleReCaptcha/useGoogleReCaptcha';
4
5
  import mergeOperations from '../../util/shallowMerge';
5
6
  import { useCartContext } from '../../context/cart';
6
7
  import { useUserContext } from '../../context/user';
@@ -42,6 +43,15 @@ export const useSignIn = props => {
42
43
  fetchPolicy: 'no-cache'
43
44
  });
44
45
 
46
+ const {
47
+ generateReCaptchaData,
48
+ recaptchaLoading,
49
+ recaptchaWidgetProps
50
+ } = useGoogleReCaptcha({
51
+ currentForm: 'CUSTOMER_LOGIN',
52
+ formAction: 'signIn'
53
+ });
54
+
45
55
  const [fetchCartId] = useMutation(createCartMutation);
46
56
  const [mergeCarts] = useMutation(mergeCartsMutation);
47
57
  const fetchUserDetails = useAwaitQuery(getCustomerQuery);
@@ -57,16 +67,23 @@ export const useSignIn = props => {
57
67
  // Get source cart id (guest cart id).
58
68
  const sourceCartId = cartId;
59
69
 
70
+ // Get recaptchaV3 data for login
71
+ const recaptchaData = await generateReCaptchaData();
72
+
60
73
  // Sign in and set the token.
61
74
  const signInResponse = await signIn({
62
- variables: { email, password }
75
+ variables: {
76
+ email,
77
+ password
78
+ },
79
+ ...recaptchaData
63
80
  });
64
81
  const token = signInResponse.data.generateCustomerToken.token;
65
82
  await setToken(token);
66
83
 
67
84
  // Clear all cart/customer data from cache and redux.
68
85
  await apolloClient.clearCacheData(apolloClient, 'cart');
69
- await apolloClient.clearCacheData(apolloClient, 'checkout');
86
+ await apolloClient.clearCacheData(apolloClient, 'customer');
70
87
  await removeCart();
71
88
 
72
89
  // Create and get the customer's cart id.
@@ -96,10 +113,11 @@ export const useSignIn = props => {
96
113
  },
97
114
  [
98
115
  cartId,
99
- apolloClient,
100
- removeCart,
116
+ generateReCaptchaData,
101
117
  signIn,
102
118
  setToken,
119
+ apolloClient,
120
+ removeCart,
103
121
  createCart,
104
122
  fetchCartId,
105
123
  mergeCarts,
@@ -144,7 +162,8 @@ export const useSignIn = props => {
144
162
  handleCreateAccount,
145
163
  handleForgotPassword,
146
164
  handleSubmit,
147
- isBusy: isGettingDetails || isSigningIn,
148
- setFormApi
165
+ isBusy: isGettingDetails || isSigningIn || recaptchaLoading,
166
+ setFormApi,
167
+ recaptchaWidgetProps
149
168
  };
150
169
  };
@@ -16,6 +16,7 @@ export const GET_MULTIPLE_WISHLISTS_ENABLED = gql`
16
16
  storeConfig {
17
17
  store_code
18
18
  enable_multiple_wishlists
19
+ maximum_number_of_wishlists
19
20
  }
20
21
  }
21
22
  `;
@@ -99,10 +99,10 @@ export const useActionMenu = (props = {}) => {
99
99
  [getCustomerWishlistQuery, id, updateWishlist]
100
100
  );
101
101
 
102
- const errors = useMemo(
103
- () => (displayError ? [updateWishlistErrors] : [false]),
104
- [updateWishlistErrors, displayError]
105
- );
102
+ const errors = useMemo(() => (displayError ? [updateWishlistErrors] : []), [
103
+ updateWishlistErrors,
104
+ displayError
105
+ ]);
106
106
 
107
107
  return {
108
108
  editFavoritesListIsOpen,
@@ -7,10 +7,12 @@ import WISHLIST_PAGE_OPERATIONS from './wishlistPage.gql';
7
7
 
8
8
  /**
9
9
  * @function
10
+ * @param {number} props.numberOfWishlists - The current number of wishlists created
10
11
  *
11
12
  * @returns {CreateWishListProps}
12
13
  */
13
- export const useCreateWishlist = (props = {}) => {
14
+ export const useCreateWishlist = (props = { numberOfWishlists: 1 }) => {
15
+ const { numberOfWishlists } = props;
14
16
  const operations = mergeOperations(
15
17
  DEFAULT_OPERATIONS,
16
18
  WISHLIST_PAGE_OPERATIONS,
@@ -39,11 +41,12 @@ export const useCreateWishlist = (props = {}) => {
39
41
  const shouldRender = useMemo(() => {
40
42
  return (
41
43
  (storeConfigData &&
42
- storeConfigData.storeConfig.enable_multiple_wishlists ===
43
- '1') ||
44
+ storeConfigData.storeConfig.enable_multiple_wishlists === '1' &&
45
+ numberOfWishlists <
46
+ storeConfigData.storeConfig.maximum_number_of_wishlists) ||
44
47
  false
45
48
  );
46
- }, [storeConfigData]);
49
+ }, [storeConfigData, numberOfWishlists]);
47
50
 
48
51
  const handleShowModal = useCallback(() => {
49
52
  setIsModalOpen(true);
@@ -80,9 +80,10 @@ export const useWishlistItem = props => {
80
80
  ) {
81
81
  const selectedOptionsArray = selectedConfigurableOptions.map(
82
82
  selectedOption => {
83
+ // TODO: Use configurable_product_option_uid for ConfigurableWishlistItem when available in 2.4.5
83
84
  const {
84
- configurable_product_option_uid: attributeId,
85
- configurable_product_option_value_uid: selectedValueId
85
+ id: attributeId,
86
+ value_id: selectedValueId
86
87
  } = selectedOption;
87
88
  const configurableOption = configurableOptions.find(
88
89
  option => option.attribute_id_v2 === attributeId
@@ -7,6 +7,7 @@ export const GET_WISHLIST_CONFIG = gql`
7
7
  store_code
8
8
  magento_wishlist_general_is_enabled
9
9
  enable_multiple_wishlists
10
+ maximum_number_of_wishlists
10
11
  }
11
12
  }
12
13
  `;
@@ -47,6 +47,7 @@ export const WishlistItemFragment = gql`
47
47
  }
48
48
  }
49
49
  }
50
+ # TODO: Use configurable_product_option_uid for ConfigurableWishlistItem when available in 2.4.5
50
51
  ... on ConfigurableWishlistItem {
51
52
  configurable_options {
52
53
  id
@@ -1,12 +1,16 @@
1
- // Resolves configured variant for configurable cart item.
2
- // This field is proposed in the https://github.com/magento/magento2/pull/30817
1
+ /**
2
+ * This function resolves configured variant for configurable cart item.
3
+ * This field is proposed in the https://github.com/magento/magento2/pull/30817
4
+ * @param {Object} configured_options An array of product variant options
5
+ * @param {Object} product A configurable product object with a variants property.
6
+ *
7
+ * @returns {Object | undefined}
8
+ */
3
9
  export default function configuredVariant(configured_options, product) {
4
- if (!configured_options || !product.variants) return;
10
+ if (!configured_options || !product?.variants) return;
5
11
  const optionUids = configured_options
6
12
  .map(option => {
7
- return Buffer.from(
8
- `configurable/${option.id}/${option.value_id}`
9
- ).toString('base64');
13
+ return option.configurable_product_option_value_uid;
10
14
  })
11
15
  .sort()
12
16
  .toString();