@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.
- package/lib/Apollo/clearCustomerDataFromCache.js +1 -0
- package/lib/Apollo/policies/index.js +9 -4
- package/lib/hooks/useGoogleReCaptcha/googleReCaptchaConfig.gql.js +16 -0
- package/lib/hooks/useGoogleReCaptcha/index.js +1 -0
- package/lib/hooks/useGoogleReCaptcha/useGoogleReCaptcha.js +210 -0
- package/lib/hooks/useMediaQuery.js +83 -0
- package/lib/hooks/useScript.js +68 -0
- package/lib/hooks/useSort.js +13 -2
- package/lib/talons/AccountInformationPage/useAccountInformationPage.js +23 -6
- package/lib/talons/CartPage/PriceAdjustments/GiftOptions/giftOptions.gql.js +36 -11
- package/lib/talons/CartPage/PriceAdjustments/GiftOptions/giftOptionsFragments.gql.js +19 -0
- package/lib/talons/CartPage/PriceAdjustments/GiftOptions/useGiftOptions.js +315 -94
- package/lib/talons/CartPage/PriceAdjustments/giftOptionsSection.gql.js +17 -0
- package/lib/talons/CartPage/PriceAdjustments/useGiftOptionsSection.js +61 -0
- package/lib/talons/CartPage/PriceSummary/__fixtures__/priceSummary.js +7 -2
- package/lib/talons/CartPage/PriceSummary/priceSummaryFragments.gql.js +7 -0
- package/lib/talons/CartPage/PriceSummary/queries/giftOptionsSummary.ce.js +8 -0
- package/lib/talons/CartPage/PriceSummary/queries/giftOptionsSummary.ee.js +15 -0
- package/lib/talons/CartPage/PriceSummary/useDiscountSummary.js +71 -0
- package/lib/talons/CartPage/PriceSummary/usePriceSummary.js +3 -1
- package/lib/talons/CartPage/ProductListing/EditModal/__fixtures__/configurableThumbnailSource.js +8 -0
- package/lib/talons/CartPage/ProductListing/EditModal/productForm.gql.js +11 -0
- package/lib/talons/CartPage/ProductListing/EditModal/useProductForm.js +15 -1
- package/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js +27 -5
- package/lib/talons/CheckoutPage/PaymentInformation/useCreditCard.js +36 -15
- package/lib/talons/CheckoutPage/useCheckoutPage.js +18 -3
- package/lib/talons/CmsDynamicBlock/client-schema.graphql +4 -0
- package/lib/talons/CmsDynamicBlock/cmsDynamicBlock.gql.js +113 -0
- package/lib/talons/CmsDynamicBlock/useCmsDynamicBlock.js +211 -0
- package/lib/talons/CreateAccount/useCreateAccount.js +29 -5
- package/lib/talons/FilterModal/helpers.js +29 -0
- package/lib/talons/FilterModal/useFilterModal.js +9 -2
- package/lib/talons/FilterSidebar/useFilterSidebar.js +4 -1
- package/lib/talons/ForgotPassword/useForgotPassword.js +26 -5
- package/lib/talons/Link/useLink.js +2 -1
- package/lib/talons/MiniCart/miniCartFragments.gql.js +4 -0
- package/lib/talons/MyAccount/useResetPassword.js +23 -5
- package/lib/talons/RootComponents/Category/categoryContent.gql.js +1 -0
- package/lib/talons/RootComponents/Category/useCategory.js +1 -1
- package/lib/talons/RootComponents/Product/productDetailFragment.gql.js +6 -2
- package/lib/talons/RootComponents/Product/useProduct.js +1 -6
- package/lib/talons/SearchPage/searchPage.gql.js +1 -0
- package/lib/talons/SearchPage/useSearchPage.js +1 -1
- package/lib/talons/SignIn/useSignIn.js +25 -6
- package/lib/talons/WishlistPage/createWishlist.gql.js +1 -0
- package/lib/talons/WishlistPage/useActionMenu.js +4 -4
- package/lib/talons/WishlistPage/useCreateWishlist.js +7 -4
- package/lib/talons/WishlistPage/useWishlistItem.js +3 -2
- package/lib/talons/WishlistPage/wishlistConfig.gql.ee.js +1 -0
- package/lib/talons/WishlistPage/wishlistItemFragments.gql.js +1 -0
- package/lib/util/configuredVariant.js +10 -6
- package/package.json +1 -1
- 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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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 {
|
|
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
|
: {};
|
|
@@ -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
|
|
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.
|
|
@@ -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, {
|
|
@@ -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.
|