@magento/peregrine 12.2.0-beta.1 → 12.3.0-beta.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/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/useCategory.js +1 -1
- package/lib/talons/RootComponents/Product/useProduct.js +1 -6
- 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
|
*/
|
|
@@ -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.
|
|
@@ -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: {
|
|
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, '
|
|
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
|
-
|
|
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
|
};
|
|
@@ -99,10 +99,10 @@ export const useActionMenu = (props = {}) => {
|
|
|
99
99
|
[getCustomerWishlistQuery, id, updateWishlist]
|
|
100
100
|
);
|
|
101
101
|
|
|
102
|
-
const errors = useMemo(
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
85
|
+
id: attributeId,
|
|
86
|
+
value_id: selectedValueId
|
|
86
87
|
} = selectedOption;
|
|
87
88
|
const configurableOption = configurableOptions.find(
|
|
88
89
|
option => option.attribute_id_v2 === attributeId
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
10
|
+
if (!configured_options || !product?.variants) return;
|
|
5
11
|
const optionUids = configured_options
|
|
6
12
|
.map(option => {
|
|
7
|
-
return
|
|
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();
|