@riosst100/pwa-marketplace 1.0.0 → 1.0.2
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/package.json +1 -1
- package/src/Utilities/graphQL.js +1 -1
- package/src/componentOverrideMapping.js +9 -9
- package/src/components/BecomeSeller/becomeSeller.js +220 -0
- package/src/components/BecomeSeller/becomeSeller.module.css +47 -0
- package/src/components/BecomeSeller/index.js +1 -0
- package/src/components/BecomeSellerLink/becomeSellerLink.js +28 -0
- package/src/components/BecomeSellerLink/becomeSellerLink.module.css +13 -0
- package/src/components/BecomeSellerPage/becomeSellerPage.js +50 -0
- package/src/components/BecomeSellerPage/becomeSellerPage.module.css +21 -0
- package/src/components/BecomeSellerPage/index.js +1 -0
- package/src/components/Header/becomeSellerLink.js +28 -0
- package/src/components/Header/becomeSellerLink.module.css +13 -0
- package/src/components/Header/websiteSwitcher.js +1 -1
- package/src/components/WebsiteSwitcher/websiteSwitcher.js +109 -0
- package/src/components/WebsiteSwitcher/websiteSwitcher.module.css +111 -0
- package/src/components/WebsiteSwitcher/websiteSwitcher.shimmer.js +6 -0
- package/src/components/WebsiteSwitcher/websiteSwitcherItem.js +47 -0
- package/src/components/WebsiteSwitcher/websiteSwitcherItem.module.css +20 -0
- package/src/intercept.js +32 -19
- package/src/overwrites/venia-ui/lib/components/Adapter/adapter.js +1 -1
- package/src/overwrites/venia-ui/lib/components/Header/header.js +3 -1
- package/src/overwrites/venia-ui/lib/components/Header/storeSwitcher.js +2 -2
- package/src/talons/BecomeSeller/becomeSeller.gql.js +136 -0
- package/src/talons/BecomeSeller/useBecomeSeller.js +288 -0
- package/src/talons/BecomeSellerLink/useBecomeSellerLink.js +13 -0
- package/src/talons/BecomeSellerPage/useBecomeSellerPage.js +68 -0
- package/src/talons/Header/useBecomeSellerLink.js +13 -0
- package/src/talons/WebsiteSwitcher/useWebsiteSwitcher.js +218 -0
- package/src/talons/WebsiteSwitcher/websiteSwitcher.gql.js +45 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { useApolloClient, useMutation } from '@apollo/client';
|
|
3
|
+
|
|
4
|
+
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
|
|
5
|
+
import { useUserContext } from '@magento/peregrine/lib/context/user';
|
|
6
|
+
import { useCartContext } from '@magento/peregrine/lib/context/cart';
|
|
7
|
+
import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery';
|
|
8
|
+
import { retrieveCartId } from '@magento/peregrine/lib/store/actions/cart';
|
|
9
|
+
import { useGoogleReCaptcha } from '@magento/peregrine/lib/hooks/useGoogleReCaptcha';
|
|
10
|
+
|
|
11
|
+
import DEFAULT_OPERATIONS from './becomeSeller.gql';
|
|
12
|
+
import { useEventingContext } from '@magento/peregrine/lib/context/eventing';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns props necessary to render BecomeSeller component. In particular this
|
|
16
|
+
* talon handles the submission flow by first doing a pre-submisson validation
|
|
17
|
+
* and then, on success, invokes the `onSubmit` prop, which is usually the action.
|
|
18
|
+
*
|
|
19
|
+
* @param {BecomeSellerQueries} props.queries queries used by the talon
|
|
20
|
+
* @param {BecomeSellerMutations} props.mutations mutations used by the talon
|
|
21
|
+
* @param {InitialValues} props.initialValues initial values to sanitize and seed the form
|
|
22
|
+
* @param {Function} props.onSubmit the post submit callback
|
|
23
|
+
* @param {Function} props.onCancel the cancel callback
|
|
24
|
+
*
|
|
25
|
+
* @returns {BecomeSellerProps}
|
|
26
|
+
*
|
|
27
|
+
* @example <caption>Importing into your project</caption>
|
|
28
|
+
* import { useForgotPassword } from '@magento/peregrine/lib/talons/BecomeSeller/useBecomeSeller.js';
|
|
29
|
+
*/
|
|
30
|
+
export const useBecomeSeller = props => {
|
|
31
|
+
const { initialValues = {}, onSubmit, onCancel } = props;
|
|
32
|
+
|
|
33
|
+
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
|
|
34
|
+
const {
|
|
35
|
+
becomeSellerMutation,
|
|
36
|
+
createCartMutation,
|
|
37
|
+
getCartDetailsQuery,
|
|
38
|
+
getCustomerQuery,
|
|
39
|
+
mergeCartsMutation,
|
|
40
|
+
signInMutation
|
|
41
|
+
} = operations;
|
|
42
|
+
const apolloClient = useApolloClient();
|
|
43
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
44
|
+
const [
|
|
45
|
+
{ cartId },
|
|
46
|
+
{ createCart, removeCart, getCartDetails }
|
|
47
|
+
] = useCartContext();
|
|
48
|
+
const [
|
|
49
|
+
{ isGettingDetails },
|
|
50
|
+
{ getUserDetails, setToken }
|
|
51
|
+
] = useUserContext();
|
|
52
|
+
|
|
53
|
+
const [, { dispatch }] = useEventingContext();
|
|
54
|
+
|
|
55
|
+
const [fetchCartId] = useMutation(createCartMutation);
|
|
56
|
+
|
|
57
|
+
const [mergeCarts] = useMutation(mergeCartsMutation);
|
|
58
|
+
|
|
59
|
+
// For create account and sign in mutations, we don't want to cache any
|
|
60
|
+
// personally identifiable information (PII). So we set fetchPolicy to 'no-cache'.
|
|
61
|
+
const [becomeSeller, { error: becomeSellerError }] = useMutation(
|
|
62
|
+
becomeSellerMutation,
|
|
63
|
+
{
|
|
64
|
+
fetchPolicy: 'no-cache'
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const [signIn, { error: signInError }] = useMutation(signInMutation, {
|
|
69
|
+
fetchPolicy: 'no-cache'
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const fetchUserDetails = useAwaitQuery(getCustomerQuery);
|
|
73
|
+
const fetchCartDetails = useAwaitQuery(getCartDetailsQuery);
|
|
74
|
+
|
|
75
|
+
const {
|
|
76
|
+
generateReCaptchaData,
|
|
77
|
+
recaptchaLoading,
|
|
78
|
+
recaptchaWidgetProps
|
|
79
|
+
} = useGoogleReCaptcha({
|
|
80
|
+
currentForm: 'CUSTOMER_CREATE',
|
|
81
|
+
formAction: 'becomeSeller'
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const handleCancel = useCallback(() => {
|
|
85
|
+
onCancel();
|
|
86
|
+
}, [onCancel]);
|
|
87
|
+
|
|
88
|
+
const handleCancelKeyPress = useCallback(() => {
|
|
89
|
+
event => {
|
|
90
|
+
if (event.key === 'Enter') {
|
|
91
|
+
handleCancel();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}, [handleCancel]);
|
|
95
|
+
|
|
96
|
+
const handleSubmit = useCallback(
|
|
97
|
+
async formValues => {
|
|
98
|
+
setIsSubmitting(true);
|
|
99
|
+
try {
|
|
100
|
+
// Get source cart id (guest cart id).
|
|
101
|
+
const sourceCartId = cartId;
|
|
102
|
+
|
|
103
|
+
// Get reCaptchaV3 Data for becomeSeller mutation
|
|
104
|
+
const recaptchaDataForBecomeSeller = await generateReCaptchaData();
|
|
105
|
+
|
|
106
|
+
// Create the account and then sign in.
|
|
107
|
+
await becomeSeller({
|
|
108
|
+
variables: {
|
|
109
|
+
email: formValues.customer.email,
|
|
110
|
+
firstname: formValues.customer.firstname,
|
|
111
|
+
lastname: formValues.customer.lastname,
|
|
112
|
+
password: formValues.password,
|
|
113
|
+
is_subscribed: !!formValues.subscribe
|
|
114
|
+
},
|
|
115
|
+
...recaptchaDataForBecomeSeller
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
dispatch({
|
|
119
|
+
type: 'USER_CREATE_ACCOUNT',
|
|
120
|
+
payload: {
|
|
121
|
+
email: formValues.customer.email,
|
|
122
|
+
firstName: formValues.customer.firstname,
|
|
123
|
+
lastName: formValues.customer.lastname,
|
|
124
|
+
isSubscribed: !!formValues.subscribe
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Get reCaptchaV3 Data for signIn mutation
|
|
129
|
+
const recaptchaDataForSignIn = await generateReCaptchaData();
|
|
130
|
+
|
|
131
|
+
const signInResponse = await signIn({
|
|
132
|
+
variables: {
|
|
133
|
+
email: formValues.customer.email,
|
|
134
|
+
password: formValues.password
|
|
135
|
+
},
|
|
136
|
+
...recaptchaDataForSignIn
|
|
137
|
+
});
|
|
138
|
+
const token = signInResponse.data.generateCustomerToken.token;
|
|
139
|
+
await setToken(token);
|
|
140
|
+
|
|
141
|
+
// Clear all cart/customer data from cache and redux.
|
|
142
|
+
await apolloClient.clearCacheData(apolloClient, 'cart');
|
|
143
|
+
await apolloClient.clearCacheData(apolloClient, 'customer');
|
|
144
|
+
await removeCart();
|
|
145
|
+
|
|
146
|
+
// Create and get the customer's cart id.
|
|
147
|
+
await createCart({
|
|
148
|
+
fetchCartId
|
|
149
|
+
});
|
|
150
|
+
const destinationCartId = await retrieveCartId();
|
|
151
|
+
|
|
152
|
+
// Merge the guest cart into the customer cart.
|
|
153
|
+
await mergeCarts({
|
|
154
|
+
variables: {
|
|
155
|
+
destinationCartId,
|
|
156
|
+
sourceCartId
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Ensure old stores are updated with any new data.
|
|
161
|
+
await getUserDetails({ fetchUserDetails });
|
|
162
|
+
await getCartDetails({
|
|
163
|
+
fetchCartId,
|
|
164
|
+
fetchCartDetails
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Finally, invoke the post-submission callback.
|
|
168
|
+
if (onSubmit) {
|
|
169
|
+
onSubmit();
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
173
|
+
console.error(error);
|
|
174
|
+
}
|
|
175
|
+
setIsSubmitting(false);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
[
|
|
180
|
+
cartId,
|
|
181
|
+
generateReCaptchaData,
|
|
182
|
+
becomeSeller,
|
|
183
|
+
signIn,
|
|
184
|
+
setToken,
|
|
185
|
+
apolloClient,
|
|
186
|
+
removeCart,
|
|
187
|
+
createCart,
|
|
188
|
+
fetchCartId,
|
|
189
|
+
mergeCarts,
|
|
190
|
+
getUserDetails,
|
|
191
|
+
fetchUserDetails,
|
|
192
|
+
getCartDetails,
|
|
193
|
+
fetchCartDetails,
|
|
194
|
+
onSubmit,
|
|
195
|
+
dispatch
|
|
196
|
+
]
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const sanitizedInitialValues = useMemo(() => {
|
|
200
|
+
const { email, firstName, lastName, ...rest } = initialValues;
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
customer: { email, firstname: firstName, lastname: lastName },
|
|
204
|
+
...rest
|
|
205
|
+
};
|
|
206
|
+
}, [initialValues]);
|
|
207
|
+
|
|
208
|
+
const errors = useMemo(
|
|
209
|
+
() =>
|
|
210
|
+
new Map([
|
|
211
|
+
['becomeSellerQuery', becomeSellerError],
|
|
212
|
+
['signInMutation', signInError]
|
|
213
|
+
]),
|
|
214
|
+
[becomeSellerError, signInError]
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
errors,
|
|
219
|
+
handleCancel,
|
|
220
|
+
handleSubmit,
|
|
221
|
+
handleCancelKeyPress,
|
|
222
|
+
initialValues: sanitizedInitialValues,
|
|
223
|
+
isDisabled: isSubmitting || isGettingDetails || recaptchaLoading,
|
|
224
|
+
recaptchaWidgetProps
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/** JSDocs type definitions */
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* GraphQL queries for the create account form.
|
|
232
|
+
* This is a type used by the {@link useBecomeSeller} talon.
|
|
233
|
+
*
|
|
234
|
+
* @typedef {Object} BecomeSellerQueries
|
|
235
|
+
*
|
|
236
|
+
* @property {GraphQLAST} customerQuery query to fetch customer details
|
|
237
|
+
* @property {GraphQLAST} getCartDetailsQuery query to get cart details
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* GraphQL mutations for the create account form.
|
|
242
|
+
* This is a type used by the {@link useBecomeSeller} talon.
|
|
243
|
+
*
|
|
244
|
+
* @typedef {Object} BecomeSellerMutations
|
|
245
|
+
*
|
|
246
|
+
* @property {GraphQLAST} becomeSellerMutation mutation for creating new account
|
|
247
|
+
* @property {GraphQLAST} createCartMutation mutation for creating new cart
|
|
248
|
+
* @property {GraphQLAST} mergeCartsMutation mutation for merging carts
|
|
249
|
+
* @property {GraphQLAST} signInMutation mutation for signing
|
|
250
|
+
*/
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Initial values for the create account form.
|
|
254
|
+
* This is a type used by the {@link useBecomeSeller} talon.
|
|
255
|
+
*
|
|
256
|
+
* @typedef {Object} InitialValues
|
|
257
|
+
*
|
|
258
|
+
* @property {String} email email id of the user
|
|
259
|
+
* @property {String} firstName first name of the user
|
|
260
|
+
* @property {String} lastName last name of the user
|
|
261
|
+
*/
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Sanitized initial values for the create account form.
|
|
265
|
+
* This is a type used by the {@link useBecomeSeller} talon.
|
|
266
|
+
*
|
|
267
|
+
* @typedef {Object} SanitizedInitialValues
|
|
268
|
+
*
|
|
269
|
+
* @property {String} email email id of the user
|
|
270
|
+
* @property {String} firstname first name of the user
|
|
271
|
+
* @property {String} lastname last name of the user
|
|
272
|
+
*/
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Object type returned by the {@link useBecomeSeller} talon.
|
|
276
|
+
* It provides props data to use when rendering the create account form component.
|
|
277
|
+
*
|
|
278
|
+
* @typedef {Object} BecomeSellerProps
|
|
279
|
+
*
|
|
280
|
+
* @property {Map} errors a map of errors to their respective mutations
|
|
281
|
+
* @property {Function} handleCancel callback function to handle form cancellations
|
|
282
|
+
* @property {Function} handleSubmit callback function to handle form submission
|
|
283
|
+
* @property {SanitizedInitialValues} initialValues initial values for the create account form
|
|
284
|
+
* @property {Boolean} isDisabled true if either details are being fetched or form is being submitted. False otherwise.
|
|
285
|
+
* @property {Object} recaptchaWidgetProps - Props for the GoogleReCaptcha component.
|
|
286
|
+
* @property {Function} recaptchaWidgetProps.containerElement - Container reference callback.
|
|
287
|
+
* @property {Boolean} recaptchaWidgetProps.shouldRender - Checks if component should be rendered.
|
|
288
|
+
*/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useWebsiteSwitcher = (props = {}) => {
|
|
4
|
+
const handleTriggerClick = useCallback(() => {
|
|
5
|
+
// Toggle Stores Menu.
|
|
6
|
+
// setStoreMenuIsOpen(isOpen => !isOpen);
|
|
7
|
+
// globalThis.location.assign(`${newPath || ''}`);
|
|
8
|
+
}, []);
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
handleTriggerClick
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo } from 'react';
|
|
2
|
+
import { useHistory, useLocation } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
import { useUserContext } from '@magento/peregrine/lib/context/user';
|
|
5
|
+
|
|
6
|
+
const validBecomeSellerParams = ['email', 'firstName', 'lastName'];
|
|
7
|
+
|
|
8
|
+
const getBecomeSellerInitialValues = search => {
|
|
9
|
+
const params = new URLSearchParams(search);
|
|
10
|
+
|
|
11
|
+
return validBecomeSellerParams.reduce(
|
|
12
|
+
(values, param) => ({ ...values, [param]: params.get(param) }),
|
|
13
|
+
{}
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {function} useBecomeSellerPage
|
|
19
|
+
*
|
|
20
|
+
* @param {String} props.signedInRedirectUrl - Url to push when user is signed in
|
|
21
|
+
* @param {String} props.signInPageUrl - Sign In Page url
|
|
22
|
+
*
|
|
23
|
+
* @returns {{
|
|
24
|
+
* becomeSellerProps: object
|
|
25
|
+
* }}
|
|
26
|
+
*/
|
|
27
|
+
export const useBecomeSellerPage = props => {
|
|
28
|
+
const { signedInRedirectUrl, signInPageUrl } = props;
|
|
29
|
+
const history = useHistory();
|
|
30
|
+
const [{ isSignedIn }] = useUserContext();
|
|
31
|
+
const { search } = useLocation();
|
|
32
|
+
|
|
33
|
+
// Keep location state in memory when pushing history and redirect to
|
|
34
|
+
// the `from` url instead when creating an account
|
|
35
|
+
const historyState = useMemo(() => {
|
|
36
|
+
return history && history.location.state ? history.location.state : {};
|
|
37
|
+
}, [history]);
|
|
38
|
+
const fromRedirectUrl = historyState.from || null;
|
|
39
|
+
|
|
40
|
+
// Redirect if user is signed in
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (isSignedIn) {
|
|
43
|
+
if (fromRedirectUrl || signedInRedirectUrl) {
|
|
44
|
+
history.push(fromRedirectUrl || signedInRedirectUrl);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}, [fromRedirectUrl, history, isSignedIn, signedInRedirectUrl]);
|
|
48
|
+
|
|
49
|
+
const handleOnCancel = useCallback(() => {
|
|
50
|
+
if (signInPageUrl) {
|
|
51
|
+
history.push(signInPageUrl, historyState);
|
|
52
|
+
}
|
|
53
|
+
}, [history, historyState, signInPageUrl]);
|
|
54
|
+
|
|
55
|
+
const initialValues = useMemo(() => getBecomeSellerInitialValues(search), [
|
|
56
|
+
search
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
const becomeSellerProps = {
|
|
60
|
+
initialValues,
|
|
61
|
+
isCancelButtonHidden: false,
|
|
62
|
+
onCancel: handleOnCancel
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
becomeSellerProps
|
|
67
|
+
};
|
|
68
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useWebsiteSwitcher = (props = {}) => {
|
|
4
|
+
const handleTriggerClick = useCallback(() => {
|
|
5
|
+
// Toggle Stores Menu.
|
|
6
|
+
// setStoreMenuIsOpen(isOpen => !isOpen);
|
|
7
|
+
// globalThis.location.assign(`${newPath || ''}`);
|
|
8
|
+
}, []);
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
handleTriggerClick
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { useQuery } from '@apollo/client';
|
|
2
|
+
import { useCallback, useMemo } from 'react';
|
|
3
|
+
import { useLocation } from 'react-router-dom';
|
|
4
|
+
import { useDropdown } from '@magento/peregrine/lib/hooks/useDropdown';
|
|
5
|
+
import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery';
|
|
6
|
+
import { BrowserPersistence } from '@magento/peregrine/lib/util';
|
|
7
|
+
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
|
|
8
|
+
import DEFAULT_OPERATIONS from './websiteSwitcher.gql';
|
|
9
|
+
|
|
10
|
+
const storage = new BrowserPersistence();
|
|
11
|
+
|
|
12
|
+
const mapAvailableOptions = (config, stores) => {
|
|
13
|
+
const { store_code: configCode } = config;
|
|
14
|
+
|
|
15
|
+
return stores.reduce((map, store) => {
|
|
16
|
+
const {
|
|
17
|
+
default_display_currency_code: currency,
|
|
18
|
+
locale,
|
|
19
|
+
secure_base_media_url,
|
|
20
|
+
store_code: storeCode,
|
|
21
|
+
store_group_code: storeGroupCode,
|
|
22
|
+
store_group_name: storeGroupName,
|
|
23
|
+
website_name: websiteName,
|
|
24
|
+
website_code: websiteCode,
|
|
25
|
+
store_sort_order: sortOrder
|
|
26
|
+
} = store;
|
|
27
|
+
|
|
28
|
+
const isCurrent = storeCode === configCode;
|
|
29
|
+
const option = {
|
|
30
|
+
currency,
|
|
31
|
+
isCurrent,
|
|
32
|
+
locale,
|
|
33
|
+
secure_base_media_url,
|
|
34
|
+
sortOrder,
|
|
35
|
+
storeCode,
|
|
36
|
+
storeGroupCode,
|
|
37
|
+
storeGroupName,
|
|
38
|
+
websiteName,
|
|
39
|
+
websiteCode
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return map.set(storeCode, option);
|
|
43
|
+
}, new Map());
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The useWebsiteSwitcher talon complements the WebsiteSwitcher component.
|
|
48
|
+
*
|
|
49
|
+
* @param {Array<Object>} [props.availableRoutes] - Hardcoded app routes.
|
|
50
|
+
* @param {Object} [props.operations] - GraphQL operations to be run by the hook.
|
|
51
|
+
*
|
|
52
|
+
* @returns {Map} talonProps.availableStores - Details about the available store views.
|
|
53
|
+
* @returns {String} talonProps.currentWebsiteName - Name of the current store view.
|
|
54
|
+
* @returns {Boolean} talonProps.storeMenuIsOpen - Whether the menu that this trigger toggles is open or not.
|
|
55
|
+
* @returns {Ref} talonProps.storeMenuRef - A React ref to the menu that this trigger toggles.
|
|
56
|
+
* @returns {Ref} talonProps.storeMenuTriggerRef - A React ref to the trigger element itself.
|
|
57
|
+
* @returns {Function} talonProps.handleTriggerClick - A function for handling when the trigger is clicked.
|
|
58
|
+
* @returns {Function} talonProps.handleSwitchWebsite - A function for handling when the menu item is clicked.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
export const useWebsiteSwitcher = (props = {}) => {
|
|
62
|
+
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
|
|
63
|
+
const { availableRoutes = [] } = props;
|
|
64
|
+
const internalRoutes = useMemo(() => {
|
|
65
|
+
return availableRoutes.map(path => {
|
|
66
|
+
if (path.exact) {
|
|
67
|
+
return path.pattern;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}, [availableRoutes]);
|
|
71
|
+
|
|
72
|
+
const {
|
|
73
|
+
getStoreConfigData,
|
|
74
|
+
getRouteData,
|
|
75
|
+
getAvailableWebsitesData
|
|
76
|
+
} = operations;
|
|
77
|
+
const { pathname, search: searchParams } = useLocation();
|
|
78
|
+
const {
|
|
79
|
+
elementRef: storeMenuRef,
|
|
80
|
+
expanded: storeMenuIsOpen,
|
|
81
|
+
setExpanded: setStoreMenuIsOpen,
|
|
82
|
+
triggerRef: storeMenuTriggerRef
|
|
83
|
+
} = useDropdown();
|
|
84
|
+
|
|
85
|
+
const { data: storeConfigData } = useQuery(getStoreConfigData, {
|
|
86
|
+
fetchPolicy: 'cache-and-network',
|
|
87
|
+
nextFetchPolicy: 'cache-first'
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const fetchRouteData = useAwaitQuery(getRouteData);
|
|
91
|
+
|
|
92
|
+
const { data: availableStoresData } = useQuery(getAvailableWebsitesData, {
|
|
93
|
+
fetchPolicy: 'cache-and-network',
|
|
94
|
+
nextFetchPolicy: 'cache-first'
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const currentWebsiteName = useMemo(() => {
|
|
98
|
+
if (storeConfigData) {
|
|
99
|
+
return storeConfigData.storeConfig.website_name;
|
|
100
|
+
}
|
|
101
|
+
}, [storeConfigData]);
|
|
102
|
+
|
|
103
|
+
const currentGroupName = useMemo(() => {
|
|
104
|
+
if (storeConfigData) {
|
|
105
|
+
return storeConfigData.storeConfig.store_group_name;
|
|
106
|
+
}
|
|
107
|
+
}, [storeConfigData]);
|
|
108
|
+
|
|
109
|
+
// availableStores => mapped options or empty map if undefined.
|
|
110
|
+
const availableStores = useMemo(() => {
|
|
111
|
+
return (
|
|
112
|
+
(storeConfigData &&
|
|
113
|
+
availableStoresData &&
|
|
114
|
+
mapAvailableOptions(
|
|
115
|
+
storeConfigData.storeConfig,
|
|
116
|
+
availableStoresData.availableStoresByUserIp
|
|
117
|
+
)) ||
|
|
118
|
+
new Map()
|
|
119
|
+
);
|
|
120
|
+
}, [storeConfigData, availableStoresData]);
|
|
121
|
+
|
|
122
|
+
// Create a map of sorted store views for each group.
|
|
123
|
+
const storeGroups = useMemo(() => {
|
|
124
|
+
const groups = new Map();
|
|
125
|
+
|
|
126
|
+
availableStores.forEach(store => {
|
|
127
|
+
const groupCode = store.storeGroupCode;
|
|
128
|
+
if (!groups.has(groupCode)) {
|
|
129
|
+
const groupViews = [store];
|
|
130
|
+
groups.set(groupCode, groupViews);
|
|
131
|
+
} else {
|
|
132
|
+
const groupViews = groups.get(groupCode);
|
|
133
|
+
// Insert store at configured position
|
|
134
|
+
groupViews.splice(store.sortOrder, 0, store);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return groups;
|
|
139
|
+
}, [availableStores]);
|
|
140
|
+
|
|
141
|
+
const getPathname = useCallback(
|
|
142
|
+
async storeCode => {
|
|
143
|
+
if (pathname === '' || pathname === '/') return '';
|
|
144
|
+
let newPath = '';
|
|
145
|
+
if (internalRoutes.includes(pathname)) {
|
|
146
|
+
newPath = pathname;
|
|
147
|
+
} else {
|
|
148
|
+
const { data: routeData } = await fetchRouteData({
|
|
149
|
+
fetchPolicy: 'no-cache',
|
|
150
|
+
variables: {
|
|
151
|
+
url: pathname
|
|
152
|
+
},
|
|
153
|
+
context: { headers: { store: storeCode } }
|
|
154
|
+
});
|
|
155
|
+
if (routeData.route) {
|
|
156
|
+
newPath = routeData.route.relative_url;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return newPath.startsWith('/') ? newPath.substr(1) : newPath;
|
|
160
|
+
},
|
|
161
|
+
[pathname, fetchRouteData, internalRoutes]
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const handleSwitchWebsite = useCallback(
|
|
165
|
+
// Change store view code and currency to be used in Apollo link request headers
|
|
166
|
+
async (storeCode, websiteCode) => {
|
|
167
|
+
// Do nothing when store view is not present in available stores
|
|
168
|
+
if (!availableStores.has(storeCode)) return;
|
|
169
|
+
|
|
170
|
+
storage.setItem('store_view_code', storeCode);
|
|
171
|
+
storage.setItem(
|
|
172
|
+
'store_view_currency',
|
|
173
|
+
availableStores.get(storeCode).currency
|
|
174
|
+
);
|
|
175
|
+
storage.setItem(
|
|
176
|
+
'store_view_secure_base_media_url',
|
|
177
|
+
availableStores.get(storeCode).secure_base_media_url
|
|
178
|
+
);
|
|
179
|
+
const pathName = await getPathname(storeCode);
|
|
180
|
+
|
|
181
|
+
let accessBaseWebsite = null;
|
|
182
|
+
let newSearchParams = searchParams;
|
|
183
|
+
let newWebsiteCode = websiteCode;
|
|
184
|
+
|
|
185
|
+
if (newWebsiteCode == process.env.WEBSITE_CODE) {
|
|
186
|
+
newWebsiteCode = null;
|
|
187
|
+
newSearchParams = searchParams ? searchParams+'&access_base_website=1' : '?access_base_website=1';
|
|
188
|
+
accessBaseWebsite = '?access_base_website=1';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const newPath = pathName ? `/${pathName}${newSearchParams}` : (accessBaseWebsite ? `/${pathName}${accessBaseWebsite}` : '');
|
|
192
|
+
|
|
193
|
+
if (newWebsiteCode) {
|
|
194
|
+
globalThis.location.assign(`/${newWebsiteCode}${newPath || ''}`);
|
|
195
|
+
} else {
|
|
196
|
+
globalThis.location.assign(`${newPath || ''}`);
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
[availableStores, getPathname, searchParams]
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const handleTriggerClick = useCallback(() => {
|
|
203
|
+
// Toggle Stores Menu.
|
|
204
|
+
setStoreMenuIsOpen(isOpen => !isOpen);
|
|
205
|
+
}, [setStoreMenuIsOpen]);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
availableStores,
|
|
209
|
+
currentGroupName,
|
|
210
|
+
currentWebsiteName,
|
|
211
|
+
storeGroups,
|
|
212
|
+
storeMenuRef,
|
|
213
|
+
storeMenuTriggerRef,
|
|
214
|
+
storeMenuIsOpen,
|
|
215
|
+
handleTriggerClick,
|
|
216
|
+
handleSwitchWebsite
|
|
217
|
+
};
|
|
218
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
export const GET_STORE_CONFIG_DATA = gql`
|
|
4
|
+
query getStoreConfigData {
|
|
5
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
6
|
+
storeConfig {
|
|
7
|
+
store_code
|
|
8
|
+
store_name
|
|
9
|
+
store_group_name
|
|
10
|
+
website_name
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export const GET_ROUTE_DATA = gql`
|
|
16
|
+
query getRouteData($url: String!) {
|
|
17
|
+
route(url: $url) {
|
|
18
|
+
relative_url
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export const GET_AVAILABLE_WEBSITES_DATA = gql`
|
|
24
|
+
query availableStoresByUserIp {
|
|
25
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
26
|
+
availableStoresByUserIp {
|
|
27
|
+
default_display_currency_code
|
|
28
|
+
locale
|
|
29
|
+
secure_base_media_url
|
|
30
|
+
store_code
|
|
31
|
+
store_group_code
|
|
32
|
+
store_group_name
|
|
33
|
+
store_name
|
|
34
|
+
store_sort_order
|
|
35
|
+
website_name
|
|
36
|
+
website_code
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
export default {
|
|
42
|
+
getStoreConfigData: GET_STORE_CONFIG_DATA,
|
|
43
|
+
getRouteData: GET_ROUTE_DATA,
|
|
44
|
+
getAvailableWebsitesData: GET_AVAILABLE_WEBSITES_DATA
|
|
45
|
+
};
|