@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.
- 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
|
@@ -12,6 +12,7 @@ export const clearCustomerDataFromCache = async client => {
|
|
|
12
12
|
// Cached ROOT_QUERY
|
|
13
13
|
client.cache.evict({ fieldName: 'customer' });
|
|
14
14
|
client.cache.evict({ fieldName: 'customerWishlistProducts' });
|
|
15
|
+
client.cache.evict({ fieldName: 'dynamicBlocks' });
|
|
15
16
|
|
|
16
17
|
client.cache.gc();
|
|
17
18
|
|
|
@@ -177,10 +177,15 @@ const typePolicies = {
|
|
|
177
177
|
SelectedConfigurableOption: {
|
|
178
178
|
// id alone is not enough to identify a selected option as it can refer
|
|
179
179
|
// to something like "size" where value_id refers to "large".
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
// TODO: Use configurable_product_option_uid for ConfigurableWishlistItem when available in 2.4.5
|
|
181
|
+
keyFields: fields => {
|
|
182
|
+
return fields.configurable_product_option_uid
|
|
183
|
+
? [
|
|
184
|
+
'configurable_product_option_uid',
|
|
185
|
+
'configurable_product_option_value_uid'
|
|
186
|
+
]
|
|
187
|
+
: ['id', 'value_id'];
|
|
188
|
+
}
|
|
184
189
|
},
|
|
185
190
|
SelectedPaymentMethod: {
|
|
186
191
|
keyFields: ['code']
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
export const GET_RECAPTCHAV3_CONFIG = gql`
|
|
4
|
+
query GetReCaptchaV3Config {
|
|
5
|
+
recaptchaV3Config {
|
|
6
|
+
website_key
|
|
7
|
+
badge_position
|
|
8
|
+
language_code
|
|
9
|
+
forms
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
getReCaptchaV3ConfigQuery: GET_RECAPTCHAV3_CONFIG
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useGoogleReCaptcha } from './useGoogleReCaptcha';
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useQuery } from '@apollo/client';
|
|
3
|
+
|
|
4
|
+
import useScript from '@magento/peregrine/lib/hooks/useScript';
|
|
5
|
+
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
|
|
6
|
+
|
|
7
|
+
import defaultOperations from './googleReCaptchaConfig.gql';
|
|
8
|
+
|
|
9
|
+
const GOOGLE_RECAPTCHA_HEADER = 'X-ReCaptcha';
|
|
10
|
+
const GOOGLE_RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns props necessary to attach Google ReCaptcha V3 to a form.
|
|
14
|
+
*
|
|
15
|
+
* @function
|
|
16
|
+
*
|
|
17
|
+
* @param {String} props.currentForm - Form name to match GraphQl ReCaptchaFormEnum.
|
|
18
|
+
* @param {String} props.formAction - Action name to use for logging in API.
|
|
19
|
+
* @param {Object} [props.operations] - GraphQL operations to be run by the hook.
|
|
20
|
+
*
|
|
21
|
+
* @returns {GoogleReCaptchaProps}
|
|
22
|
+
*
|
|
23
|
+
* @example <caption>Importing into your project</caption>
|
|
24
|
+
* import { useGoogleReCaptcha } from '@magento/peregrine/lib/hooks/useGoogleReCaptcha';
|
|
25
|
+
*/
|
|
26
|
+
export const useGoogleReCaptcha = props => {
|
|
27
|
+
const operations = mergeOperations(defaultOperations, props.operations);
|
|
28
|
+
const { currentForm, formAction } = props;
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
data: configData,
|
|
32
|
+
error: configError,
|
|
33
|
+
loading: configLoading
|
|
34
|
+
} = useQuery(operations.getReCaptchaV3ConfigQuery, {
|
|
35
|
+
fetchPolicy: 'cache-and-network'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!globalThis['recaptchaCallbacks']) {
|
|
39
|
+
globalThis['recaptchaCallbacks'] = {};
|
|
40
|
+
}
|
|
41
|
+
const [apiIsReady, setApiIsReady] = useState(
|
|
42
|
+
globalThis.hasOwnProperty('grecaptcha')
|
|
43
|
+
);
|
|
44
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
45
|
+
const [widgetId, setWidgetId] = useState(null);
|
|
46
|
+
|
|
47
|
+
// Container Reference to be used for the GoogleReCaptcha component
|
|
48
|
+
const [inlineContainer, setInlineContainer] = useState(null);
|
|
49
|
+
|
|
50
|
+
// callback to update container element ref in case of mount/unmount
|
|
51
|
+
const updateInlineContainerRef = useCallback(node => {
|
|
52
|
+
if (node !== null) {
|
|
53
|
+
setInlineContainer(node);
|
|
54
|
+
}
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
const recaptchaBadge =
|
|
58
|
+
configData?.recaptchaV3Config?.badge_position &&
|
|
59
|
+
configData.recaptchaV3Config.badge_position.length > 0
|
|
60
|
+
? configData.recaptchaV3Config.badge_position
|
|
61
|
+
: 'bottomright';
|
|
62
|
+
const recaptchaKey = configData?.recaptchaV3Config?.website_key;
|
|
63
|
+
const recaptchaLang = configData?.recaptchaV3Config?.language_code;
|
|
64
|
+
const activeForms = configData?.recaptchaV3Config?.forms || [];
|
|
65
|
+
const isEnabled =
|
|
66
|
+
!(configError instanceof Error) &&
|
|
67
|
+
recaptchaKey &&
|
|
68
|
+
recaptchaKey.length > 0 &&
|
|
69
|
+
activeForms.includes(currentForm);
|
|
70
|
+
|
|
71
|
+
// Determine which type of badge should be loaded
|
|
72
|
+
const isInline = recaptchaBadge === 'inline';
|
|
73
|
+
|
|
74
|
+
// Construct script url with configs
|
|
75
|
+
const scriptUrl = new URL(GOOGLE_RECAPTCHA_URL);
|
|
76
|
+
|
|
77
|
+
scriptUrl.searchParams.append('badge', recaptchaBadge);
|
|
78
|
+
|
|
79
|
+
// Render separate widgets with GoogleReCaptcha component when inline
|
|
80
|
+
scriptUrl.searchParams.append(
|
|
81
|
+
'render',
|
|
82
|
+
isInline ? 'explicit' : recaptchaKey
|
|
83
|
+
);
|
|
84
|
+
scriptUrl.searchParams.append('onload', 'onloadRecaptchaCallback');
|
|
85
|
+
|
|
86
|
+
if (recaptchaLang && recaptchaLang.length > 0) {
|
|
87
|
+
scriptUrl.searchParams.append('hl', recaptchaLang);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Load Script only if the API is not already set, if the key is set
|
|
91
|
+
// and if the current form is enabled in the V3 configs
|
|
92
|
+
const status = useScript(!apiIsReady && isEnabled ? scriptUrl : null);
|
|
93
|
+
|
|
94
|
+
// Wait for config to be loaded and script to be ready
|
|
95
|
+
const isLoading =
|
|
96
|
+
configLoading || (isEnabled && !apiIsReady && status !== 'ready');
|
|
97
|
+
|
|
98
|
+
// Render inline widget manually
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
// Only render if container is set and API is available
|
|
101
|
+
if (
|
|
102
|
+
inlineContainer !== null &&
|
|
103
|
+
isInline &&
|
|
104
|
+
apiIsReady &&
|
|
105
|
+
widgetId === null
|
|
106
|
+
) {
|
|
107
|
+
// Avoid loading twice if already rendered
|
|
108
|
+
if ('widgetId' in inlineContainer.dataset) {
|
|
109
|
+
setWidgetId(inlineContainer.dataset.widgetId);
|
|
110
|
+
} else {
|
|
111
|
+
const id = globalThis.grecaptcha.render(inlineContainer, {
|
|
112
|
+
sitekey: recaptchaKey,
|
|
113
|
+
size: 'invisible'
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
setWidgetId(id);
|
|
117
|
+
inlineContainer.dataset.widgetId = id;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}, [apiIsReady, isInline, recaptchaKey, widgetId, inlineContainer]);
|
|
121
|
+
|
|
122
|
+
// Callback sets API as ready
|
|
123
|
+
if (!globalThis['recaptchaCallbacks'][formAction] && isEnabled) {
|
|
124
|
+
globalThis['recaptchaCallbacks'][formAction] = () => {
|
|
125
|
+
// Update non inline styles
|
|
126
|
+
if (!isInline) {
|
|
127
|
+
const floatingBadge = document.getElementsByClassName(
|
|
128
|
+
'grecaptcha-badge'
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (floatingBadge && floatingBadge.length > 0) {
|
|
132
|
+
floatingBadge[0].style.zIndex = 999;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setApiIsReady(true);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Callback loops through each instance and set API as ready
|
|
141
|
+
globalThis['onloadRecaptchaCallback'] = useCallback(() => {
|
|
142
|
+
for (const key in globalThis['recaptchaCallbacks']) {
|
|
143
|
+
globalThis['recaptchaCallbacks'][key]();
|
|
144
|
+
}
|
|
145
|
+
// Reset value after
|
|
146
|
+
globalThis['recaptchaCallbacks'] = {};
|
|
147
|
+
}, []);
|
|
148
|
+
|
|
149
|
+
// Generate the object that will be sent with the request
|
|
150
|
+
const generateReCaptchaData = useCallback(async () => {
|
|
151
|
+
if (apiIsReady) {
|
|
152
|
+
try {
|
|
153
|
+
setIsGenerating(true);
|
|
154
|
+
|
|
155
|
+
const token = await globalThis.grecaptcha.execute(
|
|
156
|
+
isInline ? widgetId : recaptchaKey,
|
|
157
|
+
{
|
|
158
|
+
action: formAction
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const result = {
|
|
163
|
+
// TODO: Use Apollo Link middleware when solution is found
|
|
164
|
+
context: {
|
|
165
|
+
headers: {
|
|
166
|
+
[GOOGLE_RECAPTCHA_HEADER]: token
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
setIsGenerating(false);
|
|
172
|
+
|
|
173
|
+
return result;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
// Log API error
|
|
176
|
+
console.error(error);
|
|
177
|
+
|
|
178
|
+
setIsGenerating(false);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {};
|
|
183
|
+
}, [apiIsReady, formAction, isInline, recaptchaKey, widgetId]);
|
|
184
|
+
|
|
185
|
+
const recaptchaWidgetProps = {
|
|
186
|
+
containerElement: updateInlineContainerRef,
|
|
187
|
+
shouldRender: !!(isEnabled && isInline && apiIsReady)
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
recaptchaLoading: isGenerating || isLoading,
|
|
192
|
+
generateReCaptchaData,
|
|
193
|
+
recaptchaWidgetProps
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/** JSDocs type definitions */
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Object type returned by the {@link useGoogleReCaptcha} hook.
|
|
201
|
+
* It provides props data to use when attaching Google ReCaptcha V3 to a form.
|
|
202
|
+
*
|
|
203
|
+
* @typedef {Object} GoogleReCaptchaProps
|
|
204
|
+
*
|
|
205
|
+
* @property {Boolean} recaptchaLoading - Indicates if hook is loading data or loading the script.
|
|
206
|
+
* @property {Function} generateReCaptchaData - The function to generate ReCaptcha Mutation data.
|
|
207
|
+
* @property {Object} recaptchaWidgetProps - Props for the GoogleReCaptcha component.
|
|
208
|
+
* @property {Function} recaptchaWidgetProps.containerElement - Container reference callback.
|
|
209
|
+
* @property {Boolean} recaptchaWidgetProps.shouldRender - Checks if component should be rendered.
|
|
210
|
+
*/
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useEffect, useState, useRef } from 'react';
|
|
2
|
+
import { string, shape, object, arrayOf } from 'prop-types';
|
|
3
|
+
|
|
4
|
+
const { matchMedia } = globalThis;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A hook that will return all matched styles for any given media queries using the
|
|
8
|
+
* matchMedia API (https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} props
|
|
11
|
+
* @param {Array} props.mediaQueries The array of media rules and respective styles to apply
|
|
12
|
+
*
|
|
13
|
+
* @returns {Object}
|
|
14
|
+
*/
|
|
15
|
+
export const useMediaQuery = (props = { mediaQueries: [] }) => {
|
|
16
|
+
const [styles, setStyles] = useState({});
|
|
17
|
+
|
|
18
|
+
const isMountedRef = useRef(null);
|
|
19
|
+
|
|
20
|
+
const { mediaQueries } = props;
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
isMountedRef.current = true;
|
|
24
|
+
if (!mediaQueries) return;
|
|
25
|
+
|
|
26
|
+
const mqlList = mediaQueries.map(({ media }) => matchMedia(media));
|
|
27
|
+
|
|
28
|
+
const handleMatch = (query, i) => {
|
|
29
|
+
if (!isMountedRef.current) return;
|
|
30
|
+
|
|
31
|
+
if (query.matches) {
|
|
32
|
+
setStyles(prevState => ({
|
|
33
|
+
...prevState,
|
|
34
|
+
...mediaQueries[i].style
|
|
35
|
+
}));
|
|
36
|
+
} else {
|
|
37
|
+
setStyles(prevState => {
|
|
38
|
+
const filteredState = Object.keys(prevState)
|
|
39
|
+
.filter(
|
|
40
|
+
key => mediaQueries[i].style[key] !== prevState[key]
|
|
41
|
+
)
|
|
42
|
+
.reduce((obj, key) => {
|
|
43
|
+
return {
|
|
44
|
+
...obj,
|
|
45
|
+
[key]: prevState[key]
|
|
46
|
+
};
|
|
47
|
+
}, {});
|
|
48
|
+
return filteredState;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
mqlList.forEach((mql, i) => {
|
|
54
|
+
if (mql.matches) {
|
|
55
|
+
setStyles(prevState => ({
|
|
56
|
+
...prevState,
|
|
57
|
+
...mediaQueries[i].style
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
mql.addEventListener('change', query => handleMatch(query, i));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return () => {
|
|
64
|
+
isMountedRef.current = false;
|
|
65
|
+
mqlList.forEach((mql, i) => {
|
|
66
|
+
mql.removeEventListener('change', query =>
|
|
67
|
+
handleMatch(query, i)
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
}, [mediaQueries]);
|
|
72
|
+
|
|
73
|
+
return { styles };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
useMediaQuery.propTypes = {
|
|
77
|
+
mediaQueries: arrayOf(
|
|
78
|
+
shape({
|
|
79
|
+
media: string.isRequired,
|
|
80
|
+
style: object.isRequired
|
|
81
|
+
})
|
|
82
|
+
).isRequired
|
|
83
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dynamically load an external script and know when its loaded
|
|
5
|
+
* @see https://usehooks.com/useScript/.
|
|
6
|
+
*
|
|
7
|
+
* @param {String} src - the script src.
|
|
8
|
+
*
|
|
9
|
+
* @returns {string} - returns one of the possible status: "idle", "loading", "ready" or "error"
|
|
10
|
+
*/
|
|
11
|
+
export default src => {
|
|
12
|
+
// Keep track of script status ("idle", "loading", "ready", "error")
|
|
13
|
+
const [status, setStatus] = useState(src ? 'loading' : 'idle');
|
|
14
|
+
useEffect(
|
|
15
|
+
() => {
|
|
16
|
+
// Allow falsy src value if waiting on other data needed for
|
|
17
|
+
// constructing the script URL passed to this hook.
|
|
18
|
+
if (!src) {
|
|
19
|
+
setStatus('idle');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Fetch existing script element by src
|
|
23
|
+
// It may have been added by another instance of this hook
|
|
24
|
+
let script = document.querySelector(`script[src="${src}"]`);
|
|
25
|
+
if (!script) {
|
|
26
|
+
// Create script
|
|
27
|
+
script = document.createElement('script');
|
|
28
|
+
script.src = src;
|
|
29
|
+
script.async = true;
|
|
30
|
+
script.setAttribute('data-status', 'loading');
|
|
31
|
+
// Add script to document body
|
|
32
|
+
document.body.appendChild(script);
|
|
33
|
+
// Store status in attribute on script
|
|
34
|
+
// This can be read by other instances of this hook
|
|
35
|
+
const setAttributeFromEvent = event => {
|
|
36
|
+
script.setAttribute(
|
|
37
|
+
'data-status',
|
|
38
|
+
event.type === 'load' ? 'ready' : 'error'
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
script.addEventListener('load', setAttributeFromEvent);
|
|
42
|
+
script.addEventListener('error', setAttributeFromEvent);
|
|
43
|
+
} else {
|
|
44
|
+
// Grab existing script status from attribute and set to state.
|
|
45
|
+
setStatus(script.getAttribute('data-status'));
|
|
46
|
+
}
|
|
47
|
+
// Script event handler to update status in state
|
|
48
|
+
// Note: Even if the script already exists we still need to add
|
|
49
|
+
// event handlers to update the state for *this* hook instance.
|
|
50
|
+
const setStateFromEvent = event => {
|
|
51
|
+
setStatus(event.type === 'load' ? 'ready' : 'error');
|
|
52
|
+
};
|
|
53
|
+
// Add event listeners
|
|
54
|
+
script.addEventListener('load', setStateFromEvent);
|
|
55
|
+
script.addEventListener('error', setStateFromEvent);
|
|
56
|
+
// Remove event listeners on cleanup
|
|
57
|
+
return () => {
|
|
58
|
+
if (script) {
|
|
59
|
+
script.removeEventListener('load', setStateFromEvent);
|
|
60
|
+
script.removeEventListener('error', setStateFromEvent);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
[src] // Only re-run effect if script src changes
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return status;
|
|
68
|
+
};
|
package/lib/hooks/useSort.js
CHANGED
|
@@ -8,10 +8,21 @@ const defaultSort = {
|
|
|
8
8
|
sortDirection: 'ASC'
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
const searchSort = {
|
|
12
|
+
sortText: 'Best Match',
|
|
13
|
+
sortId: 'sortItem.relevance',
|
|
14
|
+
sortAttribute: 'relevance',
|
|
15
|
+
sortDirection: 'DESC'
|
|
16
|
+
};
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
*
|
|
13
20
|
* @param props
|
|
14
21
|
* @returns {[{sortDirection: string, sortAttribute: string, sortText: string}, React.Dispatch<React.SetStateAction<{sortDirection: string, sortAttribute: string, sortText: string}>>]}
|
|
15
22
|
*/
|
|
16
|
-
export const useSort = (props = {}) =>
|
|
17
|
-
|
|
23
|
+
export const useSort = (props = {}) => {
|
|
24
|
+
const { sortFromSearch = false } = props;
|
|
25
|
+
return useState(() =>
|
|
26
|
+
Object.assign({}, sortFromSearch ? searchSort : defaultSort, props)
|
|
27
|
+
);
|
|
28
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useMemo, useState } from 'react';
|
|
2
2
|
import { useMutation, useQuery } from '@apollo/client';
|
|
3
3
|
import { useUserContext } from '../../context/user';
|
|
4
|
+
import { useGoogleReCaptcha } from '../../hooks/useGoogleReCaptcha';
|
|
4
5
|
|
|
5
6
|
export const useAccountInformationPage = props => {
|
|
6
7
|
const {
|
|
@@ -46,6 +47,15 @@ export const useAccountInformationPage = props => {
|
|
|
46
47
|
}
|
|
47
48
|
] = useMutation(changeCustomerPasswordMutation);
|
|
48
49
|
|
|
50
|
+
const {
|
|
51
|
+
generateReCaptchaData,
|
|
52
|
+
recaptchaLoading,
|
|
53
|
+
recaptchaWidgetProps
|
|
54
|
+
} = useGoogleReCaptcha({
|
|
55
|
+
currentForm: 'CUSTOMER_EDIT',
|
|
56
|
+
formAction: 'editCustomer'
|
|
57
|
+
});
|
|
58
|
+
|
|
49
59
|
const initialValues = useMemo(() => {
|
|
50
60
|
if (accountInformationData) {
|
|
51
61
|
return { customer: accountInformationData.customer };
|
|
@@ -97,11 +107,13 @@ export const useAccountInformationPage = props => {
|
|
|
97
107
|
});
|
|
98
108
|
}
|
|
99
109
|
if (password && newPassword) {
|
|
110
|
+
const recaptchaDataForChangeCustomerPassword = await generateReCaptchaData();
|
|
100
111
|
await changeCustomerPassword({
|
|
101
112
|
variables: {
|
|
102
113
|
currentPassword: password,
|
|
103
114
|
newPassword: newPassword
|
|
104
|
-
}
|
|
115
|
+
},
|
|
116
|
+
...recaptchaDataForChangeCustomerPassword
|
|
105
117
|
});
|
|
106
118
|
}
|
|
107
119
|
// After submission, close the form if there were no errors.
|
|
@@ -117,10 +129,11 @@ export const useAccountInformationPage = props => {
|
|
|
117
129
|
}
|
|
118
130
|
},
|
|
119
131
|
[
|
|
120
|
-
|
|
132
|
+
initialValues,
|
|
121
133
|
handleCancel,
|
|
122
|
-
|
|
123
|
-
|
|
134
|
+
setCustomerInformation,
|
|
135
|
+
generateReCaptchaData,
|
|
136
|
+
changeCustomerPassword
|
|
124
137
|
]
|
|
125
138
|
);
|
|
126
139
|
|
|
@@ -134,10 +147,14 @@ export const useAccountInformationPage = props => {
|
|
|
134
147
|
handleSubmit,
|
|
135
148
|
handleChangePassword,
|
|
136
149
|
initialValues,
|
|
137
|
-
isDisabled:
|
|
150
|
+
isDisabled:
|
|
151
|
+
isUpdatingCustomerInformation ||
|
|
152
|
+
isChangingCustomerPassword ||
|
|
153
|
+
recaptchaLoading,
|
|
138
154
|
isUpdateMode,
|
|
139
155
|
loadDataError,
|
|
140
156
|
shouldShowNewPassword,
|
|
141
|
-
showUpdateMode
|
|
157
|
+
showUpdateMode,
|
|
158
|
+
recaptchaWidgetProps
|
|
142
159
|
};
|
|
143
160
|
};
|
|
@@ -1,21 +1,46 @@
|
|
|
1
1
|
import { gql } from '@apollo/client';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* Once available, we can change the query to match the schema.
|
|
7
|
-
*/
|
|
3
|
+
import { CartPageFragment } from '../../cartPageFragments.gql';
|
|
4
|
+
import { GiftOptionsFragment } from './giftOptionsFragments.gql';
|
|
5
|
+
|
|
8
6
|
const GET_GIFT_OPTIONS = gql`
|
|
9
|
-
query
|
|
10
|
-
cart(cart_id: $cartId)
|
|
7
|
+
query GetGiftOptions($cartId: String!) {
|
|
8
|
+
cart(cart_id: $cartId) {
|
|
11
9
|
id
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
...GiftOptionsFragment
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
${GiftOptionsFragment}
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
// Currently Commerce only
|
|
17
|
+
const SET_GIFT_OPTIONS_ON_CART = gql`
|
|
18
|
+
mutation SetGiftOptionsOnCart(
|
|
19
|
+
$cartId: String!
|
|
20
|
+
$giftMessage: GiftMessageInput
|
|
21
|
+
$giftReceiptIncluded: Boolean!
|
|
22
|
+
$printedCardIncluded: Boolean!
|
|
23
|
+
) {
|
|
24
|
+
setGiftOptionsOnCart(
|
|
25
|
+
input: {
|
|
26
|
+
cart_id: $cartId
|
|
27
|
+
gift_message: $giftMessage
|
|
28
|
+
gift_receipt_included: $giftReceiptIncluded
|
|
29
|
+
printed_card_included: $printedCardIncluded
|
|
30
|
+
}
|
|
31
|
+
) {
|
|
32
|
+
cart {
|
|
33
|
+
id
|
|
34
|
+
...CartPageFragment
|
|
35
|
+
...GiftOptionsFragment
|
|
36
|
+
}
|
|
15
37
|
}
|
|
16
38
|
}
|
|
39
|
+
${CartPageFragment}
|
|
40
|
+
${GiftOptionsFragment}
|
|
17
41
|
`;
|
|
18
42
|
|
|
19
43
|
export default {
|
|
20
|
-
getGiftOptionsQuery: GET_GIFT_OPTIONS
|
|
44
|
+
getGiftOptionsQuery: GET_GIFT_OPTIONS,
|
|
45
|
+
setGiftOptionsOnCartMutation: SET_GIFT_OPTIONS_ON_CART
|
|
21
46
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
import { GiftOptionsSummaryFragment } from '../../PriceSummary/queries/giftOptionsSummary';
|
|
4
|
+
|
|
5
|
+
export const GiftOptionsFragment = gql`
|
|
6
|
+
fragment GiftOptionsFragment on Cart {
|
|
7
|
+
__typename
|
|
8
|
+
id
|
|
9
|
+
gift_message {
|
|
10
|
+
from
|
|
11
|
+
to
|
|
12
|
+
message
|
|
13
|
+
}
|
|
14
|
+
gift_receipt_included
|
|
15
|
+
printed_card_included
|
|
16
|
+
...GiftOptionsSummaryFragment
|
|
17
|
+
}
|
|
18
|
+
${GiftOptionsSummaryFragment}
|
|
19
|
+
`;
|