@magento/peregrine 12.2.0-alpha.3 → 12.3.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) 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/FilterModal/helpers.js +29 -0
  32. package/lib/talons/FilterModal/useFilterModal.js +9 -2
  33. package/lib/talons/FilterSidebar/useFilterSidebar.js +4 -1
  34. package/lib/talons/ForgotPassword/useForgotPassword.js +26 -5
  35. package/lib/talons/Link/useLink.js +2 -1
  36. package/lib/talons/MiniCart/miniCartFragments.gql.js +4 -0
  37. package/lib/talons/MyAccount/useResetPassword.js +23 -5
  38. package/lib/talons/RootComponents/Category/categoryContent.gql.js +1 -0
  39. package/lib/talons/RootComponents/Category/useCategory.js +1 -1
  40. package/lib/talons/RootComponents/Product/productDetailFragment.gql.js +6 -2
  41. package/lib/talons/RootComponents/Product/useProduct.js +1 -6
  42. package/lib/talons/SearchPage/searchPage.gql.js +1 -0
  43. package/lib/talons/SearchPage/useSearchPage.js +1 -1
  44. package/lib/talons/SignIn/useSignIn.js +25 -6
  45. package/lib/talons/WishlistPage/createWishlist.gql.js +1 -0
  46. package/lib/talons/WishlistPage/useActionMenu.js +4 -4
  47. package/lib/talons/WishlistPage/useCreateWishlist.js +7 -4
  48. package/lib/talons/WishlistPage/useWishlistItem.js +3 -2
  49. package/lib/talons/WishlistPage/wishlistConfig.gql.ee.js +1 -0
  50. package/lib/talons/WishlistPage/wishlistItemFragments.gql.js +1 -0
  51. package/lib/util/configuredVariant.js +10 -6
  52. package/package.json +1 -1
  53. 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
  */
@@ -104,6 +104,35 @@ export const getFiltersFromSearch = initialValue => {
104
104
  return filters;
105
105
  };
106
106
 
107
+ /**
108
+ * Sort filters array
109
+ * @param {Array} initialArray an array containing filters data
110
+ */
111
+ export const sortFiltersArray = initialArray => {
112
+ return initialArray.sort((a, b) => {
113
+ // Place Category filter first
114
+ if (a['attribute_code'] === 'category_id') {
115
+ return -1;
116
+ }
117
+ if (b['attribute_code'] === 'category_id') {
118
+ return 1;
119
+ }
120
+
121
+ // Sort alphabetically if same position
122
+ if (a['position'] === b['position']) {
123
+ if (a['label'] < b['label']) {
124
+ return -1;
125
+ }
126
+ if (a['label'] > b['label']) {
127
+ return 1;
128
+ }
129
+ }
130
+
131
+ // Sort by position
132
+ return a['position'] - b['position'];
133
+ });
134
+ };
135
+
107
136
  export const stripHtml = html => html.replace(/(<([^>]+)>)/gi, '');
108
137
 
109
138
  /** GetFilterInput helpers below. */
@@ -6,7 +6,12 @@ import { useAppContext } from '@magento/peregrine/lib/context/app';
6
6
 
7
7
  import mergeOperations from '../../util/shallowMerge';
8
8
  import { useFilterState } from './useFilterState';
9
- import { getSearchFromState, getStateFromSearch, stripHtml } from './helpers';
9
+ import {
10
+ getSearchFromState,
11
+ getStateFromSearch,
12
+ sortFiltersArray,
13
+ stripHtml
14
+ } from './helpers';
10
15
 
11
16
  import DEFAULT_OPERATIONS from './filterModal.gql';
12
17
 
@@ -83,7 +88,9 @@ export const useFilterModal = props => {
83
88
  const keys = new Set();
84
89
  const itemsByGroup = new Map();
85
90
 
86
- for (const filter of filters) {
91
+ const sortedFilters = sortFiltersArray([...filters]);
92
+
93
+ for (const filter of sortedFilters) {
87
94
  const { options, label: name, attribute_code: group } = filter;
88
95
 
89
96
  // If this aggregation is not a possible filter, just back out.
@@ -9,6 +9,7 @@ import { useFilterState } from '../FilterModal';
9
9
  import {
10
10
  getSearchFromState,
11
11
  getStateFromSearch,
12
+ sortFiltersArray,
12
13
  stripHtml
13
14
  } from '../FilterModal/helpers';
14
15
 
@@ -77,7 +78,9 @@ export const useFilterSidebar = props => {
77
78
  const keys = new Set();
78
79
  const itemsByGroup = new Map();
79
80
 
80
- for (const filter of filters) {
81
+ const sortedFilters = sortFiltersArray([...filters]);
82
+
83
+ for (const filter of sortedFilters) {
81
84
  const { options, label: name, attribute_code: group } = filter;
82
85
 
83
86
  // If this aggregation is not a possible filter, just back out.
@@ -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
  */
@@ -13,6 +13,7 @@ export const GET_PRODUCT_FILTERS_BY_CATEGORY = gql`
13
13
  label
14
14
  value
15
15
  }
16
+ position
16
17
  }
17
18
  }
18
19
  }
@@ -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.
@@ -60,12 +60,14 @@ export const ProductDetailsFragment = gql`
60
60
  }
61
61
  data_type
62
62
  is_system
63
- is_visible_on_front
64
63
  entity_type
65
64
  ui_input {
66
65
  ui_input_type
67
66
  is_html_allowed
68
67
  }
68
+ ... on ProductAttributeMetadata {
69
+ used_in_components
70
+ }
69
71
  }
70
72
  }
71
73
  ... on ConfigurableProduct {
@@ -138,12 +140,14 @@ export const ProductDetailsFragment = gql`
138
140
  }
139
141
  data_type
140
142
  is_system
141
- is_visible_on_front
142
143
  entity_type
143
144
  ui_input {
144
145
  ui_input_type
145
146
  is_html_allowed
146
147
  }
148
+ ... on ProductAttributeMetadata {
149
+ used_in_components
150
+ }
147
151
  }
148
152
  }
149
153
  }
@@ -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, {
@@ -21,6 +21,7 @@ export const GET_PRODUCT_FILTERS_BY_SEARCH = gql`
21
21
  label
22
22
  value
23
23
  }
24
+ position
24
25
  }
25
26
  }
26
27
  }
@@ -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.