@sonic-equipment/ui 135.0.0 → 137.0.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/dist/algolia/algolia-initialization.js +2 -1
- package/dist/algolia/algolia-insights-provider.js +3 -3
- package/dist/algolia/algolia-searchclient-offline.js +3 -2
- package/dist/algolia/use-algolia-insights.js +24 -23
- package/dist/buttons/link/link.d.ts +2 -1
- package/dist/buttons/link/link.js +4 -2
- package/dist/collapsables/accordion/accordion-item.d.ts +2 -1
- package/dist/collapsables/accordion/accordion-item.js +13 -4
- package/dist/config.js +2 -2
- package/dist/country-selector/country-select/country-select.d.ts +6 -0
- package/dist/country-selector/country-select/country-select.js +10 -4
- package/dist/exports.d.ts +5 -0
- package/dist/footer/footer.d.ts +9 -0
- package/dist/footer/footer.js +14 -0
- package/dist/footer/footer.model.d.ts +18 -0
- package/dist/footer/footer.module.css.js +3 -0
- package/dist/header/header.d.ts +1 -0
- package/dist/header/header.js +9 -0
- package/dist/index.js +5 -0
- package/dist/intl/utils.js +2 -1
- package/dist/logging/logger.d.ts +11 -0
- package/dist/logging/logger.js +22 -0
- package/dist/logging/use-log-error.d.ts +1 -0
- package/dist/logging/use-log-error.js +12 -0
- package/dist/media/image/image.js +11 -4
- package/dist/notifications/announcements/announcement-provider.js +3 -6
- package/dist/pages/checkout/order-confirmation-page/order-confirmation-page-content.js +7 -1
- package/dist/pages/checkout/payment-page/components/adyen-payment.js +40 -28
- package/dist/pages/checkout/payment-page/components/payment.js +26 -16
- package/dist/pages/checkout/shipping-page/components/edit-address.d.ts +4 -2
- package/dist/pages/checkout/shipping-page/components/edit-address.js +6 -5
- package/dist/pages/checkout/shipping-page/hooks/use-patch-shipping-details.d.ts +6 -2
- package/dist/pages/checkout/shipping-page/hooks/use-patch-shipping-details.js +27 -8
- package/dist/pages/checkout/shipping-page/shipping-page-content.d.ts +2 -1
- package/dist/pages/checkout/shipping-page/shipping-page-content.js +2 -2
- package/dist/pages/checkout/shipping-page/shipping-page.js +6 -6
- package/dist/pages/product/search-result-page/search-results-page.js +1 -1
- package/dist/shared/api/shared/hooks/use-awaitable-mutation.d.ts +21 -20
- package/dist/shared/api/storefront/hooks/cart/use-patch-cart.d.ts +1 -3
- package/dist/shared/api/storefront/hooks/cart/use-patch-cart.js +4 -6
- package/dist/shared/api/storefront/hooks/cart/use-place-order.d.ts +1 -1
- package/dist/shared/api/storefront/hooks/cart/use-place-order.js +6 -5
- package/dist/shared/api/storefront/services/cart-service.js +7 -7
- package/dist/shared/ga/data-layer.js +3 -2
- package/dist/shared/model/image.d.ts +1 -2
- package/dist/shared/providers/global-state-provider.js +3 -5
- package/dist/shared/utils/debug.d.ts +2 -0
- package/dist/shared/utils/debug.js +21 -0
- package/dist/shared/utils/environment.js +4 -2
- package/dist/styles.css +105 -12
- package/package.json +1 -1
|
@@ -42,34 +42,44 @@ function AdyenPayment({ amount, cartId, countryCode, currencyCode, customerId, d
|
|
|
42
42
|
if (!state.details.redirectResult) {
|
|
43
43
|
return onError(new Error('No redirectResult'), null);
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
try {
|
|
46
|
+
const result = await getAdyenPaymentDetails({
|
|
47
|
+
redirectResult: state.details.redirectResult,
|
|
48
|
+
});
|
|
49
|
+
if (amount.toFixed(2).replaceAll(/[,.]/gi, '') !== adyenAmount)
|
|
50
|
+
return onError(new Error('Invalid amount'), result);
|
|
51
|
+
if (customerId !== adyenCustomerId)
|
|
52
|
+
return onError(new Error('Invalid customer'), result);
|
|
53
|
+
return handlePaymentResponse(result, onComplete, onError);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return onError(error, null);
|
|
57
|
+
}
|
|
53
58
|
}),
|
|
54
59
|
onSubmit: (async (state, _component) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
state.data.
|
|
58
|
-
|
|
59
|
-
const result = await postAdyenPayment({
|
|
60
|
-
currencyCode,
|
|
61
|
-
data: state.data,
|
|
62
|
-
orderAmount,
|
|
63
|
-
returnUrl,
|
|
64
|
-
webOrderNumber: adyenSession.webOrderNumber,
|
|
65
|
-
});
|
|
66
|
-
if (result.action) {
|
|
67
|
-
if (result.action.type === 'redirect') {
|
|
68
|
-
return handleRedirectPaymentAction(result);
|
|
60
|
+
try {
|
|
61
|
+
dropinDivRef.current?.classList.add(styles.loading);
|
|
62
|
+
if (state.data.paymentMethod.type === 'paybybank') {
|
|
63
|
+
state.data.countryCode = countryCode;
|
|
69
64
|
}
|
|
70
|
-
|
|
65
|
+
const result = await postAdyenPayment({
|
|
66
|
+
currencyCode,
|
|
67
|
+
data: state.data,
|
|
68
|
+
orderAmount,
|
|
69
|
+
returnUrl,
|
|
70
|
+
webOrderNumber: adyenSession.webOrderNumber,
|
|
71
|
+
});
|
|
72
|
+
if (result.action) {
|
|
73
|
+
if (result.action.type === 'redirect') {
|
|
74
|
+
return handleRedirectPaymentAction(result);
|
|
75
|
+
}
|
|
76
|
+
return onError(new Error('Invalid payment response'), result);
|
|
77
|
+
}
|
|
78
|
+
return handlePaymentResponse(result, onComplete, onError);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return onError(error, null);
|
|
71
82
|
}
|
|
72
|
-
return handlePaymentResponse(result, onComplete, onError);
|
|
73
83
|
}),
|
|
74
84
|
session: {
|
|
75
85
|
...adyenSession,
|
|
@@ -117,10 +127,12 @@ function getAndRemoveAdyenQueryParams() {
|
|
|
117
127
|
return {};
|
|
118
128
|
const params = qs.parse(window.location.search || '');
|
|
119
129
|
const { amount, customerId, redirectResult } = params;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
if (redirectResult) {
|
|
131
|
+
delete params['redirectResult'];
|
|
132
|
+
delete params['amount'];
|
|
133
|
+
delete params['customerId'];
|
|
134
|
+
history?.pushState({}, '', `${window.location.pathname}${qs.stringify(params) ? `?${qs.stringify(params)}` : ''}`);
|
|
135
|
+
}
|
|
124
136
|
return { amount, customerId, redirectResult };
|
|
125
137
|
}
|
|
126
138
|
async function handlePaymentResponse(result, onSubmit, onError) {
|
|
@@ -12,7 +12,9 @@ import { TextField } from '../../../../forms/text-field/text-field.js';
|
|
|
12
12
|
import { InfoIconTooltip } from '../../../../info-icon-tooltip/info-icon-tooltip.js';
|
|
13
13
|
import { FormattedMessage } from '../../../../intl/formatted-message.js';
|
|
14
14
|
import { useFormattedMessage } from '../../../../intl/use-formatted-message.js';
|
|
15
|
+
import { logger } from '../../../../logging/logger.js';
|
|
15
16
|
import { usePatchSession } from '../../../../shared/api/storefront/hooks/authentication/use-patch-session.js';
|
|
17
|
+
import { useInvalidateCurrentCart } from '../../../../shared/api/storefront/hooks/cart/use-invalidate-current-cart.js';
|
|
16
18
|
import { usePatchCart } from '../../../../shared/api/storefront/hooks/cart/use-patch-cart.js';
|
|
17
19
|
import { usePlaceOrder } from '../../../../shared/api/storefront/hooks/cart/use-place-order.js';
|
|
18
20
|
import { useInvalidateAdyen } from '../../../../shared/api/storefront/hooks/payment/use-invalidate-adyen.js';
|
|
@@ -31,6 +33,7 @@ function Payment({ atp, cart: _cart, form, onError: _onError, onPaymentComplete,
|
|
|
31
33
|
const { isLoading: isPatchingSession } = usePatchSession();
|
|
32
34
|
const { isLoading: isPlacingCart, mutate: placeOrder } = usePlaceOrder();
|
|
33
35
|
const { sendPurchaseEventFromPaymentPage } = useAlgoliaInsights();
|
|
36
|
+
const invalidateCurrentCart = useInvalidateCurrentCart();
|
|
34
37
|
const dropinRef = useRef(null);
|
|
35
38
|
const [paymentError, setPaymentError] = useState();
|
|
36
39
|
const [apiError, setAPIError] = useState();
|
|
@@ -151,7 +154,7 @@ function Payment({ atp, cart: _cart, form, onError: _onError, onPaymentComplete,
|
|
|
151
154
|
if (isAdyenPayment) {
|
|
152
155
|
/* Adyen Payment */
|
|
153
156
|
if (!dropinRef.current) {
|
|
154
|
-
|
|
157
|
+
logger.warn('Adyen Dropin not ready');
|
|
155
158
|
return;
|
|
156
159
|
}
|
|
157
160
|
dropinRef.current.showValidation();
|
|
@@ -168,20 +171,26 @@ function Payment({ atp, cart: _cart, form, onError: _onError, onPaymentComplete,
|
|
|
168
171
|
requestedDeliveryDate: formData.get('deliveryDate')?.toString(),
|
|
169
172
|
};
|
|
170
173
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
174
|
+
try {
|
|
175
|
+
cartRef.current = await patchCart({
|
|
176
|
+
cart: updatedCart,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
setAPIError(error);
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
dropinRef.current.submit();
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
setPaymentError(error);
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
181
190
|
}
|
|
182
191
|
catch (error) {
|
|
183
|
-
|
|
184
|
-
|
|
192
|
+
logger.error(error);
|
|
193
|
+
invalidateCurrentCart();
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
else {
|
|
@@ -196,8 +205,9 @@ function Payment({ atp, cart: _cart, form, onError: _onError, onPaymentComplete,
|
|
|
196
205
|
return onPaymentComplete({ cartId: cart.trackId });
|
|
197
206
|
}
|
|
198
207
|
catch (error) {
|
|
199
|
-
|
|
208
|
+
logger.error(error);
|
|
200
209
|
setPaymentError(error);
|
|
210
|
+
invalidateCurrentCart();
|
|
201
211
|
}
|
|
202
212
|
}
|
|
203
213
|
}
|
|
@@ -224,7 +234,7 @@ function Payment({ atp, cart: _cart, form, onError: _onError, onPaymentComplete,
|
|
|
224
234
|
return onPaymentComplete({ cartId: cart.trackId });
|
|
225
235
|
}
|
|
226
236
|
catch (error) {
|
|
227
|
-
|
|
237
|
+
logger.error(error);
|
|
228
238
|
setAPIError(error);
|
|
229
239
|
}
|
|
230
240
|
}, [onPaymentComplete, onPlaceOrderCompleted, placeOrder]);
|
|
@@ -232,7 +242,7 @@ function Payment({ atp, cart: _cart, form, onError: _onError, onPaymentComplete,
|
|
|
232
242
|
invalidateAdyen();
|
|
233
243
|
// invalidateCurrentCart()
|
|
234
244
|
setPaymentError(error);
|
|
235
|
-
|
|
245
|
+
logger.error(error);
|
|
236
246
|
_onError?.(error, result);
|
|
237
247
|
}, [_onError, invalidateAdyen]);
|
|
238
248
|
return (jsxs(Form, { className: styles['payment-form'], "data-test-selector": "paymentForm", id: form, onSubmit: e => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CountryModel } from '../../../../shared/api/storefront/model/storefront.model';
|
|
1
|
+
import { BillToModel, CountryModel } from '../../../../shared/api/storefront/model/storefront.model';
|
|
2
2
|
export declare const EDIT_ADDRESS_FORM_ID = "billToForm";
|
|
3
3
|
interface Address {
|
|
4
4
|
address1: string;
|
|
@@ -8,12 +8,14 @@ interface Address {
|
|
|
8
8
|
city: string;
|
|
9
9
|
companyName: string;
|
|
10
10
|
country: CountryModel;
|
|
11
|
+
email: string;
|
|
11
12
|
firstName: string;
|
|
12
13
|
lastName: string;
|
|
13
14
|
phone: string;
|
|
14
15
|
postalCode: string;
|
|
15
16
|
}
|
|
16
|
-
export declare function EditAddresses({ countries, isLoading, isPickup, onSubmit, }: {
|
|
17
|
+
export declare function EditAddresses({ billTo, countries, isLoading, isPickup, onSubmit, }: {
|
|
18
|
+
billTo: BillToModel | undefined | null;
|
|
17
19
|
countries: CountryModel[];
|
|
18
20
|
isLoading: boolean;
|
|
19
21
|
isPickup: boolean;
|
|
@@ -15,10 +15,10 @@ import { SonicAddress } from './sonic-address.js';
|
|
|
15
15
|
import styles from './edit-address.module.css.js';
|
|
16
16
|
|
|
17
17
|
const EDIT_ADDRESS_FORM_ID = 'billToForm';
|
|
18
|
-
function EditAddresses({ countries, isLoading, isPickup, onSubmit, }) {
|
|
18
|
+
function EditAddresses({ billTo, countries, isLoading, isPickup, onSubmit, }) {
|
|
19
19
|
const t = useFormattedMessage();
|
|
20
|
-
const [companyName, setCompanyName] = useState('');
|
|
21
|
-
const [lastName, setLastName] = useState('');
|
|
20
|
+
const [companyName, setCompanyName] = useState(billTo?.companyName || '');
|
|
21
|
+
const [lastName, setLastName] = useState(billTo?.lastName || '');
|
|
22
22
|
const { data: cart } = useFetchCurrentCart();
|
|
23
23
|
return (jsxs(Fragment, { children: [jsx(CheckoutPageSection, { title: jsx(FormattedMessage, { id: "Billing address" }), children: jsx(CheckoutPageSectionContent, { children: jsxs(Form, { className: styles.form, "data-test-selector": "billToAddressForm", id: EDIT_ADDRESS_FORM_ID, onSubmit: e => {
|
|
24
24
|
e.preventDefault();
|
|
@@ -36,6 +36,7 @@ function EditAddresses({ countries, isLoading, isPickup, onSubmit, }) {
|
|
|
36
36
|
city: formData.get('address1')?.toString() || '',
|
|
37
37
|
companyName: formData.get('companyName')?.toString() || '',
|
|
38
38
|
country,
|
|
39
|
+
email: formData.get('email')?.toString() || '',
|
|
39
40
|
firstName: formData.get('firstName')?.toString() || '',
|
|
40
41
|
lastName: formData.get('address1')?.toString() || '',
|
|
41
42
|
phone: formData.get('phone')?.toString() || '',
|
|
@@ -43,7 +44,7 @@ function EditAddresses({ countries, isLoading, isPickup, onSubmit, }) {
|
|
|
43
44
|
},
|
|
44
45
|
notes: formData.get('notes')?.toString() || '',
|
|
45
46
|
});
|
|
46
|
-
}, children: [jsx(TextField, { isDisabled: isLoading, label: t('First name'), name: "firstName", showLabel: true }), jsx(TextField, { isDisabled: isLoading, isRequired: !companyName, label: t('Last name'), minLength: 3, name: "lastName", onChange: setLastName, showLabel: true, value: lastName }, `lastname-${Boolean(companyName)}`), jsx(TextField, { isDisabled: isLoading, label: t('Company name'), name: "companyName", onChange: setCompanyName, showLabel: true, value: companyName }), jsx(TextField, { isDisabled: isLoading, label: t('Attention'), name: "attention", showLabel: true }), jsx("div", { className: styles['span-2'], children: jsx(TextField, { isRequired: true, isDisabled: isLoading, label: `${t('Address')} 1`, maxLength: 30, minLength: 3, name: "address1", showLabel: true }) }), jsx("div", { className: styles['span-2'], children: jsx(TextField, { isDisabled: isLoading, label: `${t('Address')} 2`, maxLength: 30, minLength: 3, name: "address2", showLabel: true }) }), jsx("div", { className: styles['span-2'], children: jsx(TextField, { isDisabled: isLoading, label: `${t('Address')} 3`, maxLength: 30, minLength: 3, name: "address3", showLabel: true }) }), jsx(TextField, { isRequired: true, isDisabled: isLoading, label: t('Postal Code'), maxLength: 10, minLength: 4, name: "postalCode", showLabel: true }), jsx(TextField, { isRequired: true, isDisabled: isLoading, label: t('City'), maxLength: 30, minLength: 3, name: "city", showLabel: true }), jsx("div", { className: styles['span-2'], children: jsx(CountrySelect, { isRequired: true, countries: countries, "data-test-selector": "countrySelect", isDisabled: isLoading, name: "countrySelect" }) }), jsx("div", { className: styles['span-2'], children: jsx(TextField, { isRequired: true, isDisabled: isLoading, label: t('Phone'), name: "phone", showLabel: true, validate: value => {
|
|
47
|
+
}, children: [jsx(TextField, { defaultValue: billTo?.firstName, isDisabled: isLoading, label: t('First name'), name: "firstName", showLabel: true }), jsx(TextField, { isDisabled: isLoading, isRequired: !companyName, label: t('Last name'), minLength: 3, name: "lastName", onChange: setLastName, showLabel: true, value: lastName }, `lastname-${Boolean(companyName)}`), jsx(TextField, { defaultValue: billTo?.companyName, isDisabled: isLoading, label: t('Company name'), name: "companyName", onChange: setCompanyName, showLabel: true, value: companyName }), jsx(TextField, { defaultValue: billTo?.attention, isDisabled: isLoading, label: t('Attention'), name: "attention", showLabel: true }), jsx("div", { className: styles['span-2'], children: jsx(TextField, { isRequired: true, defaultValue: billTo?.address1, isDisabled: isLoading, label: `${t('Address')} 1`, maxLength: 30, minLength: 3, name: "address1", showLabel: true }) }), jsx("div", { className: styles['span-2'], children: jsx(TextField, { defaultValue: billTo?.address2, isDisabled: isLoading, label: `${t('Address')} 2`, maxLength: 30, minLength: 3, name: "address2", showLabel: true }) }), jsx("div", { className: styles['span-2'], children: jsx(TextField, { defaultValue: billTo?.address3, isDisabled: isLoading, label: `${t('Address')} 3`, maxLength: 30, minLength: 3, name: "address3", showLabel: true }) }), jsx(TextField, { isRequired: true, defaultValue: billTo?.postalCode, isDisabled: isLoading, label: t('Postal Code'), maxLength: 10, minLength: 4, name: "postalCode", showLabel: true }), jsx(TextField, { isRequired: true, defaultValue: billTo?.city, isDisabled: isLoading, label: t('City'), maxLength: 30, minLength: 3, name: "city", showLabel: true }), jsx("div", { className: styles['span-2'], children: jsx(CountrySelect, { isRequired: true, countries: countries, "data-test-selector": "countrySelect", defaultSelectedCountry: countries.find(country => country.id === billTo?.country?.id), isDisabled: isLoading, name: "countrySelect" }) }), jsx("div", { className: styles['span-2'], children: jsx(TextField, { isRequired: true, defaultValue: billTo?.phone, isDisabled: isLoading, label: t('Phone'), name: "phone", showLabel: true, validate: value => {
|
|
47
48
|
if (!value)
|
|
48
49
|
return value;
|
|
49
50
|
return (validatePhone(value) ||
|
|
@@ -53,7 +54,7 @@ function EditAddresses({ countries, isLoading, isPickup, onSubmit, }) {
|
|
|
53
54
|
return value;
|
|
54
55
|
return (validateEmail(value) ||
|
|
55
56
|
t('Please enter a valid e-mail address'));
|
|
56
|
-
} }) }), jsx("div", { className: styles['span-2'], children: jsx(TextField, { isDisabled: isLoading, isMultiline: true, label: t('Add order notes'), name: "notes", rows: 3, showLabel: true }) })] }) }) }), jsx(CheckoutPageSection, { title: jsx(FormattedMessage, { id: isPickup ? 'Pickup address' : 'Shipping address' }), children: jsx(CheckoutPageSectionContent, { children: jsx(Fragment, { children: isPickup ? (jsx(SonicAddress, {})) : (jsxs("div", { className: styles['use-invoice-checkbox'], children: [jsx(Checkbox, { "data-test-selector": "checkboxUseBillingAddress", isDisabled: true, isSelected: true, children: jsx(FormattedMessage, { id: "Use billing address" }) }), jsx(InfoIconTooltip, { variant: "stroke", children: t('Changing your address is currently not possible. Please contact customer support to change your address.') })] })) }) }) })] }));
|
|
57
|
+
} }) }), jsx("div", { className: styles['span-2'], children: jsx(TextField, { defaultValue: cart?.notes, isDisabled: isLoading, isMultiline: true, label: t('Add order notes'), name: "notes", rows: 3, showLabel: true }) })] }) }) }), jsx(CheckoutPageSection, { title: jsx(FormattedMessage, { id: isPickup ? 'Pickup address' : 'Shipping address' }), children: jsx(CheckoutPageSectionContent, { children: jsx(Fragment, { children: isPickup ? (jsx(SonicAddress, {})) : (jsxs("div", { className: styles['use-invoice-checkbox'], children: [jsx(Checkbox, { "data-test-selector": "checkboxUseBillingAddress", isDisabled: true, isSelected: true, children: jsx(FormattedMessage, { id: "Use billing address" }) }), jsx(InfoIconTooltip, { variant: "stroke", children: t('Changing your address is currently not possible. Please contact customer support to change your address.') })] })) }) }) })] }));
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
export { EDIT_ADDRESS_FORM_ID, EditAddresses };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BillToModel, CartModel } from '../../../../shared/api/storefront/model/storefront.model';
|
|
1
|
+
import { BillToModel, CartModel, SessionModel } from '../../../../shared/api/storefront/model/storefront.model';
|
|
2
2
|
export declare function usePatchShippingDetails(): {
|
|
3
3
|
error: unknown;
|
|
4
4
|
isError: boolean;
|
|
@@ -8,5 +8,9 @@ export declare function usePatchShippingDetails(): {
|
|
|
8
8
|
billTo?: BillToModel;
|
|
9
9
|
cart: CartModel;
|
|
10
10
|
notes: string | undefined;
|
|
11
|
-
}) => Promise<
|
|
11
|
+
}) => Promise<{
|
|
12
|
+
patchedBillTo: BillToModel | undefined;
|
|
13
|
+
patchedCart: CartModel;
|
|
14
|
+
patchedSession: SessionModel | undefined;
|
|
15
|
+
}>;
|
|
12
16
|
};
|
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
import { useAwaitableMutation } from '../../../../shared/api/shared/hooks/use-awaitable-mutation.js';
|
|
2
|
+
import { patchSession } from '../../../../shared/api/storefront/services/authentication-service.js';
|
|
2
3
|
import { patchCart } from '../../../../shared/api/storefront/services/cart-service.js';
|
|
3
4
|
import { patchBillToAddress } from '../../../../shared/api/storefront/services/customer-service.js';
|
|
4
5
|
|
|
5
6
|
function usePatchShippingDetails() {
|
|
6
7
|
return useAwaitableMutation({
|
|
7
8
|
mutationFn: async ({ billTo, cart, notes, }) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const patchedBillTo = billTo
|
|
10
|
+
? await patchBillToAddress({ billTo })
|
|
11
|
+
: undefined;
|
|
12
|
+
const patchedSession = patchedBillTo
|
|
13
|
+
? await patchSession({
|
|
14
|
+
session: {
|
|
15
|
+
billTo: { id: patchedBillTo.id },
|
|
16
|
+
customerWasUpdated: true,
|
|
17
|
+
shipTo: { id: cart.shipTo?.id || patchedBillTo.id },
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
: undefined;
|
|
21
|
+
const patchedCart = await patchCart({
|
|
22
|
+
cart: { ...cart, billTo: patchedBillTo, notes },
|
|
23
|
+
});
|
|
24
|
+
return { patchedBillTo, patchedCart, patchedSession };
|
|
12
25
|
},
|
|
13
|
-
onSuccess: ({ queryClient }) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
onSuccess: ({ data: { patchedBillTo, patchedCart, patchedSession }, queryClient, }) => {
|
|
27
|
+
if (patchedBillTo)
|
|
28
|
+
queryClient.setQueryData(['customer', 'bill-to-addresses'], patchedBillTo);
|
|
29
|
+
if (patchedSession) {
|
|
30
|
+
queryClient.setQueryData(['session'], patchedSession);
|
|
31
|
+
}
|
|
32
|
+
else if (patchedBillTo) {
|
|
33
|
+
queryClient.removeQueries({ queryKey: ['session'] });
|
|
34
|
+
}
|
|
35
|
+
queryClient.setQueryData(['carts', patchedCart.id], patchedCart);
|
|
17
36
|
},
|
|
18
37
|
});
|
|
19
38
|
}
|
|
@@ -5,10 +5,11 @@ export interface ShippingPageContentProps {
|
|
|
5
5
|
editAddress: ReactNode;
|
|
6
6
|
errorPatchBillingAddress?: unknown;
|
|
7
7
|
fulfillmentMethods: string[] | undefined;
|
|
8
|
+
isGuest: boolean;
|
|
8
9
|
isLoadingFulfillmentMethods: boolean;
|
|
9
10
|
isPatching: boolean;
|
|
10
11
|
isPatchingSession: boolean;
|
|
11
12
|
onChangeFulfillmentMethod: (value: string) => void;
|
|
12
13
|
readOnlyAddress: ReactNode;
|
|
13
14
|
}
|
|
14
|
-
export declare function ShippingPageContent({ cart, editAddress, errorPatchBillingAddress, fulfillmentMethods, isLoadingFulfillmentMethods, isPatching, isPatchingSession, onChangeFulfillmentMethod, readOnlyAddress, }: ShippingPageContentProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare function ShippingPageContent({ cart, editAddress, errorPatchBillingAddress, fulfillmentMethods, isGuest, isLoadingFulfillmentMethods, isPatching, isPatchingSession, onChangeFulfillmentMethod, readOnlyAddress, }: ShippingPageContentProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -14,7 +14,7 @@ import { CheckoutPageSectionContent } from '../layouts/checkout-page-layout/comp
|
|
|
14
14
|
import { EDIT_ADDRESS_FORM_ID } from './components/edit-address.js';
|
|
15
15
|
import styles from './shipping-page.module.css.js';
|
|
16
16
|
|
|
17
|
-
function ShippingPageContent({ cart, editAddress, errorPatchBillingAddress, fulfillmentMethods, isLoadingFulfillmentMethods, isPatching, isPatchingSession, onChangeFulfillmentMethod, readOnlyAddress, }) {
|
|
17
|
+
function ShippingPageContent({ cart, editAddress, errorPatchBillingAddress, fulfillmentMethods, isGuest, isLoadingFulfillmentMethods, isPatching, isPatchingSession, onChangeFulfillmentMethod, readOnlyAddress, }) {
|
|
18
18
|
const t = useFormattedMessage();
|
|
19
19
|
const fulfillmentMethodOptions = fulfillmentMethods?.reduce((acc, method) => ({
|
|
20
20
|
...acc,
|
|
@@ -34,7 +34,7 @@ function ShippingPageContent({ cart, editAddress, errorPatchBillingAddress, fulf
|
|
|
34
34
|
primary: (jsx(Button, { withArrow: true, "data-test-selector": "checkoutShippingCartTotalContinueButton", form: EDIT_ADDRESS_FORM_ID, isDisabled: isPatching, isLoading: isPatching || isPatchingSession ? (jsx(FormattedMessage, { id: "Updating address" })) : undefined, type: "submit", children: jsx(FormattedMessage, { id: "Continue shopping" }) })),
|
|
35
35
|
}, mobileSummary: jsx(CartTotalsSummary, { totalAmount: cart.orderGrandTotalDisplay }), overview: jsx(CartTotals, { fulfillmentMethod: fulfillmentMethods && fulfillmentMethods.length === 1
|
|
36
36
|
? cart.fulfillmentMethod
|
|
37
|
-
: undefined, shippingCost: cart.shippingAndHandlingDisplay, subtotal: cart.orderSubTotalDisplay, tax: cart.totalTaxDisplay, total: cart.orderGrandTotalDisplay, vatPercentage: cart.cartLines?.[0]?.pricing?.vatRate }), children: [jsxs(Fragment, { children: [fulfillmentMethods && fulfillmentMethods.length > 1 && (jsx(CheckoutPageSection, { hasBorder: true, title: t('Fulfillment method'), children: jsx(CheckoutPageSectionContent, { children: jsx("div", { className: styles['fulfillment-select-wrapper'], children: jsx(Select, { isRequired: true, showLabel: true, "data-test-selector": "fulfillmentMethodSelect", defaultSelectedOption: cart.fulfillmentMethod, isLoading: isLoadingFulfillmentMethods, label: t('Fulfillment method'), name: "fulfillmentMethod", onChange: onChangeFulfillmentMethod, options: fulfillmentMethodOptions || {}, variant: "solid" }) }) }) })), hasBillToAddress ? readOnlyAddress : editAddress] }), Boolean(errorPatchBillingAddress) && (jsx("div", { className: styles['error-message'], children: jsx(FormattedMessage, { id: "An unexpected error occured" }) }))] }) }));
|
|
37
|
+
: undefined, shippingCost: cart.shippingAndHandlingDisplay, subtotal: cart.orderSubTotalDisplay, tax: cart.totalTaxDisplay, total: cart.orderGrandTotalDisplay, vatPercentage: cart.cartLines?.[0]?.pricing?.vatRate }), children: [jsxs(Fragment, { children: [fulfillmentMethods && fulfillmentMethods.length > 1 && (jsx(CheckoutPageSection, { hasBorder: true, title: t('Fulfillment method'), children: jsx(CheckoutPageSectionContent, { children: jsx("div", { className: styles['fulfillment-select-wrapper'], children: jsx(Select, { isRequired: true, showLabel: true, "data-test-selector": "fulfillmentMethodSelect", defaultSelectedOption: cart.fulfillmentMethod, isLoading: isLoadingFulfillmentMethods, label: t('Fulfillment method'), name: "fulfillmentMethod", onChange: onChangeFulfillmentMethod, options: fulfillmentMethodOptions || {}, variant: "solid" }) }) }) })), hasBillToAddress && !isGuest ? readOnlyAddress : editAddress] }), Boolean(errorPatchBillingAddress) && (jsx("div", { className: styles['error-message'], children: jsx(FormattedMessage, { id: "An unexpected error occured" }) }))] }) }));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export { ShippingPageContent };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { useRef, useEffect } from 'react';
|
|
3
3
|
import { useFetchSession } from '../../../shared/api/storefront/hooks/authentication/use-fetch-session.js';
|
|
4
|
-
import { useIsAuthenticated } from '../../../shared/api/storefront/hooks/authentication/use-is-authenticated.js';
|
|
5
4
|
import { usePatchSession } from '../../../shared/api/storefront/hooks/authentication/use-patch-session.js';
|
|
6
5
|
import { useFetchCurrentCart } from '../../../shared/api/storefront/hooks/cart/use-fetch-current-cart.js';
|
|
7
6
|
import { useFetchFulfillmentMethodsForCurrentCart } from '../../../shared/api/storefront/hooks/customer/use-fetch-fulfillment-methods-for-current-cart.js';
|
|
@@ -18,17 +17,17 @@ import { usePatchShippingDetails } from './hooks/use-patch-shipping-details.js';
|
|
|
18
17
|
import { ShippingPageContent } from './shipping-page-content.js';
|
|
19
18
|
|
|
20
19
|
function ShippingPage() {
|
|
21
|
-
const isAuthenticated = useIsAuthenticated();
|
|
22
20
|
const { createEcommerceEvent, dataLayer } = useDataLayer();
|
|
23
21
|
const gaEventPushed = useRef(false);
|
|
24
22
|
const { data: cart, error: errorFetchCart, isLoading: isLoadingCart, refetch: refetchCart, } = useFetchCurrentCart();
|
|
23
|
+
const { data: session } = useFetchSession();
|
|
25
24
|
const { data: countries, isLoading: isLoadingCountries } = useFetchCountries({
|
|
26
|
-
enabled: hasNo(cart?.billTo?.address1),
|
|
25
|
+
enabled: hasNo(cart?.billTo?.address1) || Boolean(session?.isGuest),
|
|
27
26
|
});
|
|
28
|
-
const { data: session } = useFetchSession();
|
|
29
27
|
const { data: fulfillmentMethods, isLoading: isLoadingFulfillmentMethods } = useFetchFulfillmentMethodsForCurrentCart();
|
|
30
28
|
const { isLoading: isPatchingSession, mutate: patchSession } = usePatchSession();
|
|
31
29
|
const { error: errorPatchBillingAddress, isError, isLoading: isPatching, isSuccess, mutate: patchShippingDetails, } = usePatchShippingDetails();
|
|
30
|
+
const isAuthenticated = session?.isAuthenticated;
|
|
32
31
|
const isLoading = isLoadingCart || isLoadingCountries || isLoadingFulfillmentMethods;
|
|
33
32
|
const { isNavigating, navigate } = useNavigate();
|
|
34
33
|
const isPickup = cart?.fulfillmentMethod === 'PickUp';
|
|
@@ -75,13 +74,14 @@ function ShippingPage() {
|
|
|
75
74
|
if (isLoading || isNavigating || isError || isSuccess)
|
|
76
75
|
return jsx(LoadingPage, {});
|
|
77
76
|
if (!isAuthenticated ||
|
|
77
|
+
hasNo(session) ||
|
|
78
78
|
hasNo(cart) ||
|
|
79
79
|
hasNo(cart.cartLines) ||
|
|
80
80
|
cart.cartLines.length === 0 ||
|
|
81
81
|
hasNo(cart.billTo) ||
|
|
82
82
|
(hasNo(cart.billTo.address1) && hasNo(countries)))
|
|
83
83
|
return null;
|
|
84
|
-
return (jsx(ShippingPageContent, { cart: cart, editAddress: jsx(EditAddresses, { countries: countries || [], isLoading: isPatching, isPickup: isPickup, onSubmit: async ({ address, notes }) => {
|
|
84
|
+
return (jsx(ShippingPageContent, { cart: cart, editAddress: jsx(EditAddresses, { billTo: cart.billTo, countries: countries || [], isLoading: isPatching, isPickup: isPickup, onSubmit: async ({ address, notes }) => {
|
|
85
85
|
if (!cart.billTo)
|
|
86
86
|
return;
|
|
87
87
|
await patchShippingDetails({
|
|
@@ -93,7 +93,7 @@ function ShippingPage() {
|
|
|
93
93
|
cart,
|
|
94
94
|
event: { event: 'add_shipping_info' },
|
|
95
95
|
}));
|
|
96
|
-
} }), errorPatchBillingAddress: errorPatchBillingAddress, fulfillmentMethods: fulfillmentMethods, isLoadingFulfillmentMethods: isLoadingFulfillmentMethods, isPatching: isPatching, isPatchingSession: isPatchingSession, onChangeFulfillmentMethod: async (value) => {
|
|
96
|
+
} }), errorPatchBillingAddress: errorPatchBillingAddress, fulfillmentMethods: fulfillmentMethods, isGuest: session.isGuest, isLoadingFulfillmentMethods: isLoadingFulfillmentMethods, isPatching: isPatching, isPatchingSession: isPatchingSession, onChangeFulfillmentMethod: async (value) => {
|
|
97
97
|
await patchSession({
|
|
98
98
|
session: {
|
|
99
99
|
...session,
|
|
@@ -37,7 +37,7 @@ function SearchResultsPageContent({ keyword }) {
|
|
|
37
37
|
const { hits, isLoading } = useAlgoliaHits();
|
|
38
38
|
const hasHits = hits.length > 0;
|
|
39
39
|
const t = useFormattedMessage();
|
|
40
|
-
return (jsxs(Fragment, { children: [isLoading !== false && !hasHits && jsx(LoadingPage, {}), isLoading === false && !hasHits && (jsx(NoResults, { content: jsxs(
|
|
40
|
+
return (jsxs(Fragment, { children: [isLoading !== false && !hasHits && jsx(LoadingPage, {}), isLoading === false && !hasHits && (jsx(NoResults, { content: jsxs(Fragment, { children: [jsx(FormattedMessage, { id: "You could try checking the spelling of your search query" }), jsx("br", {}), jsx(FormattedMessage, { id: "Try another search" }), jsx("br", {}), jsx(FormattedMessage, { id: "Are you looking for information about our service? Please visit our customer support page" })] }), title: t("Unfortnately, We found no articles for your search '{0}'", {
|
|
41
41
|
replacementValues: { 0: keyword },
|
|
42
42
|
}) })), jsxs("div", { style: {
|
|
43
43
|
display: hasHits ? undefined : 'none',
|
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
import { QueryClient } from '@tanstack/react-query';
|
|
2
|
-
export type
|
|
3
|
-
|
|
2
|
+
export type OnError<T extends (...args: any) => Promise<unknown>> = (args: {
|
|
3
|
+
args: Parameters<T>;
|
|
4
|
+
error: unknown;
|
|
5
|
+
queryClient: QueryClient;
|
|
6
|
+
}) => void;
|
|
7
|
+
export type OnSuccess<T extends (...args: any) => Promise<TResult>, TResult = Awaited<ReturnType<T>>> = (args: {
|
|
8
|
+
args: Parameters<T>;
|
|
9
|
+
data: TResult;
|
|
10
|
+
queryClient: QueryClient;
|
|
11
|
+
}) => void;
|
|
12
|
+
export type OnComplete<T extends (...args: any) => Promise<TResult>, TResult = Awaited<ReturnType<T>>> = (args: {
|
|
13
|
+
args: Parameters<T>;
|
|
14
|
+
data: TResult | undefined;
|
|
15
|
+
error: unknown | undefined;
|
|
16
|
+
queryClient: QueryClient;
|
|
17
|
+
}) => void;
|
|
18
|
+
export interface UseAwaitableMutationArgs<T extends (...args: any) => Promise<TResult>, TResult = Awaited<ReturnType<T>>> {
|
|
4
19
|
mutationFn: T;
|
|
5
|
-
onComplete?:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
error: unknown | undefined;
|
|
9
|
-
queryClient: QueryClient;
|
|
10
|
-
}) => void;
|
|
11
|
-
onError?: (args: {
|
|
12
|
-
args: Parameters<T>;
|
|
13
|
-
error: unknown;
|
|
14
|
-
queryClient: QueryClient;
|
|
15
|
-
}) => void;
|
|
16
|
-
onSuccess?: (args: {
|
|
17
|
-
args: Parameters<T>;
|
|
18
|
-
data: TResult;
|
|
19
|
-
queryClient: QueryClient;
|
|
20
|
-
}) => void;
|
|
20
|
+
onComplete?: OnComplete<T, TResult>;
|
|
21
|
+
onError?: OnError<T>;
|
|
22
|
+
onSuccess?: OnSuccess<T, TResult>;
|
|
21
23
|
}
|
|
22
|
-
export declare function useAwaitableMutation<T extends (...args: any) => Promise<TResult>, TResult =
|
|
24
|
+
export declare function useAwaitableMutation<T extends (...args: any) => Promise<TResult>, TResult = Awaited<ReturnType<T>>>({ mutationFn, onComplete, onError, onSuccess, }: UseAwaitableMutationArgs<T, TResult>): {
|
|
23
25
|
error: unknown;
|
|
24
26
|
isError: boolean;
|
|
25
27
|
isLoading: boolean;
|
|
26
28
|
isSuccess: boolean;
|
|
27
29
|
mutate: (...args: Parameters<T>) => Promise<TResult>;
|
|
28
30
|
};
|
|
29
|
-
export {};
|
|
@@ -2,15 +2,13 @@ import { useCallback } from 'react';
|
|
|
2
2
|
import { useAwaitableMutation } from '../../../shared/hooks/use-awaitable-mutation.js';
|
|
3
3
|
import { patchCart } from '../../services/cart-service.js';
|
|
4
4
|
|
|
5
|
-
function usePatchCart(
|
|
5
|
+
function usePatchCart() {
|
|
6
6
|
const onError = useCallback(({ args: [{ cart }], queryClient }) => {
|
|
7
7
|
queryClient.invalidateQueries({ queryKey: ['carts', cart.id] });
|
|
8
8
|
}, []);
|
|
9
|
-
const onSuccess = useCallback(({ args: [{ cart }], queryClient }) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
queryClient.removeQueries({ queryKey: ['carts', cart.id] });
|
|
13
|
-
}, [skipInvalidation]);
|
|
9
|
+
const onSuccess = useCallback(({ args: [{ cart }], data, queryClient }) => {
|
|
10
|
+
queryClient.setQueryData(['carts', cart.id], data);
|
|
11
|
+
}, []);
|
|
14
12
|
return useAwaitableMutation({
|
|
15
13
|
mutationFn: patchCart,
|
|
16
14
|
onError,
|
|
@@ -3,15 +3,16 @@ import { useAwaitableMutation } from '../../../shared/hooks/use-awaitable-mutati
|
|
|
3
3
|
import { placeOrder } from '../../services/cart-service.js';
|
|
4
4
|
|
|
5
5
|
const usePlaceOrder = () => {
|
|
6
|
-
const
|
|
7
|
-
queryClient.
|
|
6
|
+
const onSuccess = useCallback(({ data: cart, queryClient }) => {
|
|
7
|
+
queryClient.invalidateQueries({ queryKey: ['carts', 'current'] });
|
|
8
|
+
queryClient.setQueryData(['carts', cart.id], cart);
|
|
8
9
|
}, []);
|
|
9
|
-
const
|
|
10
|
-
queryClient.
|
|
10
|
+
const onError = useCallback(({ queryClient }) => {
|
|
11
|
+
queryClient.invalidateQueries({ queryKey: ['carts'] });
|
|
11
12
|
}, []);
|
|
12
13
|
return useAwaitableMutation({
|
|
13
14
|
mutationFn: placeOrder,
|
|
14
|
-
|
|
15
|
+
onError,
|
|
15
16
|
onSuccess,
|
|
16
17
|
});
|
|
17
18
|
};
|
|
@@ -37,16 +37,16 @@ async function patchCartLineById({ cartLine, cartLineId, }) {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
async function patchCart({ cart, }) {
|
|
40
|
-
await request({
|
|
40
|
+
const { body } = await request({
|
|
41
41
|
body: cart,
|
|
42
42
|
credentials: 'include',
|
|
43
43
|
headers: {
|
|
44
44
|
'Content-Type': 'application/json',
|
|
45
45
|
},
|
|
46
46
|
method: 'PATCH',
|
|
47
|
-
url: `${config.
|
|
47
|
+
url: `${config.BFF_API_URL}/api/v1/carts/current`,
|
|
48
48
|
});
|
|
49
|
-
return
|
|
49
|
+
return body;
|
|
50
50
|
}
|
|
51
51
|
async function deleteCurrentCart() {
|
|
52
52
|
const { body } = await request({
|
|
@@ -103,16 +103,16 @@ async function placeOrder({ cart, isPending, }) {
|
|
|
103
103
|
: isPending
|
|
104
104
|
? 'PendingPaymentValidation'
|
|
105
105
|
: 'Submitted';
|
|
106
|
-
await request({
|
|
106
|
+
const { body } = await request({
|
|
107
107
|
body: { ...cart, status: newStatus },
|
|
108
108
|
credentials: 'include',
|
|
109
109
|
headers: {
|
|
110
110
|
'Content-Type': 'application/json',
|
|
111
111
|
},
|
|
112
112
|
method: 'PATCH',
|
|
113
|
-
url: `${config.
|
|
113
|
+
url: `${config.BFF_API_URL}/api/v1/carts/current`,
|
|
114
114
|
});
|
|
115
|
-
return
|
|
115
|
+
return body;
|
|
116
116
|
}
|
|
117
117
|
async function saveCartForLater({ cart }) {
|
|
118
118
|
// USER NEEDS TO BE LOGGED IN
|
|
@@ -123,7 +123,7 @@ async function saveCartForLater({ cart }) {
|
|
|
123
123
|
'Content-Type': 'application/json',
|
|
124
124
|
},
|
|
125
125
|
method: 'PATCH',
|
|
126
|
-
url: `${config.
|
|
126
|
+
url: `${config.BFF_API_URL}/api/v1/carts/current`,
|
|
127
127
|
});
|
|
128
128
|
return body;
|
|
129
129
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { logger } from '../../logging/logger.js';
|
|
2
|
+
|
|
1
3
|
const _dataLayer = [];
|
|
2
4
|
_dataLayer.push = (function (_push) {
|
|
3
5
|
return (...items) => {
|
|
4
|
-
|
|
5
|
-
console.log('dataLayer.push', items.length === 1 ? items[0] : items);
|
|
6
|
+
logger.info('dataLayer.push', items.length === 1 ? items[0] : items);
|
|
6
7
|
return _push(...items);
|
|
7
8
|
};
|
|
8
9
|
})(_dataLayer.push.bind(_dataLayer));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
interface DPRSrcSet {
|
|
1
|
+
export interface DPRSrcSet {
|
|
2
2
|
1: string;
|
|
3
3
|
2: string;
|
|
4
4
|
3: string;
|
|
@@ -24,4 +24,3 @@ export interface ResponsiveImageType {
|
|
|
24
24
|
sm: DPRSrcSet;
|
|
25
25
|
}
|
|
26
26
|
export declare function isResponsiveImage(image: ImageType | ResponsiveImageType | undefined): image is ResponsiveImageType;
|
|
27
|
-
export {};
|
|
@@ -73,11 +73,9 @@ function useGlobalState(key, initialState) {
|
|
|
73
73
|
state.removeEventListener('stateChanged', updateState);
|
|
74
74
|
};
|
|
75
75
|
}, [state]);
|
|
76
|
-
|
|
77
|
-
state.value
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
return [rerenderState, useCallback(updateGlobalState, [updateGlobalState])];
|
|
76
|
+
const setGlobalState = useCallback((valueOrFn) => (state.value =
|
|
77
|
+
valueOrFn instanceof Function ? valueOrFn(state.value) : valueOrFn), [state]);
|
|
78
|
+
return [rerenderState, setGlobalState];
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
export { GlobalStateProvider, GlobalStateProviderContext, useGlobalState };
|