@akinon/next 2.0.0-beta.2 → 2.0.0-beta.21
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/.eslintrc.js +12 -0
- package/CHANGELOG.md +400 -7
- package/__tests__/next-config.test.ts +83 -0
- package/__tests__/tsconfig.json +23 -0
- package/api/auth.ts +381 -60
- package/api/barcode-search.ts +59 -0
- package/api/cache.ts +41 -5
- package/api/client.ts +21 -4
- package/api/form.ts +85 -0
- package/api/image-proxy.ts +75 -0
- package/api/product-categories.ts +53 -0
- package/api/similar-product-list.ts +63 -0
- package/api/similar-products.ts +111 -0
- package/api/virtual-try-on.ts +382 -0
- package/assets/styles/index.scss +84 -0
- package/babel.config.js +6 -0
- package/bin/pz-generate-routes.js +115 -0
- package/bin/pz-install-plugins.js +1 -1
- package/bin/pz-prebuild.js +1 -0
- package/bin/pz-predev.js +1 -0
- package/bin/pz-run-tests.js +99 -0
- package/bin/run-prebuild-tests.js +46 -0
- package/components/accordion.tsx +20 -5
- package/components/button.tsx +51 -36
- package/components/client-root.tsx +138 -2
- package/components/file-input.tsx +65 -3
- package/components/index.ts +1 -0
- package/components/input.tsx +1 -1
- package/components/link.tsx +46 -16
- package/components/logger-popup.tsx +213 -0
- package/components/modal.tsx +32 -16
- package/components/plugin-module.tsx +62 -3
- package/components/price.tsx +2 -2
- package/components/select.tsx +1 -1
- package/components/selected-payment-option-view.tsx +21 -0
- package/data/client/account.ts +17 -2
- package/data/client/api.ts +2 -0
- package/data/client/basket.ts +66 -5
- package/data/client/checkout.ts +391 -99
- package/data/client/misc.ts +38 -2
- package/data/client/product.ts +19 -2
- package/data/client/user.ts +16 -8
- package/data/server/category.ts +11 -9
- package/data/server/flatpage.ts +11 -4
- package/data/server/form.ts +15 -4
- package/data/server/landingpage.ts +11 -4
- package/data/server/list.ts +5 -4
- package/data/server/menu.ts +11 -3
- package/data/server/product.ts +111 -55
- package/data/server/seo.ts +14 -4
- package/data/server/special-page.ts +5 -4
- package/data/server/widget.ts +90 -5
- package/data/urls.ts +16 -5
- package/hocs/client/with-segment-defaults.tsx +2 -2
- package/hocs/server/with-segment-defaults.tsx +65 -20
- package/hooks/index.ts +4 -0
- package/hooks/use-localization.ts +24 -10
- package/hooks/use-logger-context.tsx +114 -0
- package/hooks/use-logger.ts +92 -0
- package/hooks/use-loyalty-availability.ts +21 -0
- package/hooks/use-payment-options.ts +2 -1
- package/hooks/use-pz-params.ts +37 -0
- package/hooks/use-router.ts +51 -14
- package/hooks/use-sentry-uncaught-errors.ts +24 -0
- package/instrumentation/index.ts +10 -1
- package/instrumentation/node.ts +2 -20
- package/jest.config.js +25 -0
- package/lib/cache-handler.mjs +534 -16
- package/lib/cache.ts +272 -37
- package/localization/index.ts +2 -1
- package/localization/provider.tsx +2 -5
- package/middlewares/bfcache-headers.ts +18 -0
- package/middlewares/checkout-provider.ts +1 -1
- package/middlewares/complete-gpay.ts +32 -26
- package/middlewares/complete-masterpass.ts +33 -26
- package/middlewares/complete-wallet.ts +182 -0
- package/middlewares/default.ts +360 -215
- package/middlewares/index.ts +10 -2
- package/middlewares/locale.ts +34 -11
- package/middlewares/masterpass-rest-callback.ts +230 -0
- package/middlewares/oauth-login.ts +200 -57
- package/middlewares/pretty-url.ts +21 -8
- package/middlewares/redirection-payment.ts +32 -26
- package/middlewares/saved-card-redirection.ts +33 -26
- package/middlewares/three-d-redirection.ts +32 -26
- package/middlewares/url-redirection.ts +11 -1
- package/middlewares/wallet-complete-redirection.ts +206 -0
- package/package.json +24 -9
- package/plugins.d.ts +19 -4
- package/plugins.js +10 -1
- package/redux/actions.ts +47 -0
- package/redux/middlewares/checkout.ts +63 -138
- package/redux/middlewares/index.ts +14 -10
- package/redux/middlewares/pre-order/address.ts +7 -2
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/delivery-option.ts +7 -1
- package/redux/middlewares/pre-order/index.ts +16 -10
- package/redux/middlewares/pre-order/installment-option.ts +8 -1
- package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
- package/redux/middlewares/pre-order/payment-option.ts +7 -1
- package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
- package/redux/middlewares/pre-order/redirection.ts +8 -2
- package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
- package/redux/middlewares/pre-order/shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/shipping-step.ts +5 -1
- package/redux/reducers/checkout.ts +23 -3
- package/redux/reducers/index.ts +11 -3
- package/redux/reducers/root.ts +7 -2
- package/redux/reducers/widget.ts +80 -0
- package/sentry/index.ts +69 -13
- package/tailwind/content.js +16 -0
- package/types/commerce/account.ts +5 -1
- package/types/commerce/checkout.ts +35 -1
- package/types/commerce/widget.ts +33 -0
- package/types/index.ts +114 -6
- package/types/next-auth.d.ts +2 -2
- package/types/widget.ts +80 -0
- package/utils/app-fetch.ts +7 -2
- package/utils/generate-commerce-search-params.ts +3 -2
- package/utils/get-checkout-path.ts +3 -0
- package/utils/get-root-hostname.ts +28 -0
- package/utils/index.ts +64 -10
- package/utils/localization.ts +4 -0
- package/utils/mobile-3d-iframe.ts +8 -2
- package/utils/override-middleware.ts +7 -12
- package/utils/pz-segments.ts +92 -0
- package/utils/redirect-ignore.ts +35 -0
- package/utils/redirect.ts +9 -3
- package/utils/redirection-iframe.ts +8 -2
- package/utils/widget-styles.ts +107 -0
- package/views/error-page.tsx +93 -0
- package/with-pz-config.js +21 -7
package/types/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { LocaleUrlStrategy } from '../localization';
|
|
2
2
|
import { PzNextRequest } from '../middlewares';
|
|
3
|
+
import { NextFetchEvent } from 'next/server';
|
|
4
|
+
import { NextURL } from 'next/dist/server/web/next-url';
|
|
3
5
|
import { Control, FieldError } from 'react-hook-form';
|
|
4
6
|
import { ReactNode } from 'react';
|
|
5
7
|
import { UsePaginationType } from '../hooks/use-pagination';
|
|
@@ -203,17 +205,33 @@ export interface Settings {
|
|
|
203
205
|
useOptimizedTranslations?: boolean;
|
|
204
206
|
plugins?: Record<string, Record<string, any>>;
|
|
205
207
|
includedProxyHeaders?: string[];
|
|
208
|
+
commerceRedirectionIgnoreList?: string[];
|
|
206
209
|
/**
|
|
207
210
|
* By default, the currency will be reset when the currency is changed.
|
|
208
211
|
* If you want to keep the basket when the currency is changed, you can set this option to `false`.
|
|
209
212
|
*/
|
|
210
213
|
resetBasketOnCurrencyChange?: boolean;
|
|
214
|
+
frontendIds?: Record<string, number>;
|
|
215
|
+
usePzSegment?: boolean;
|
|
216
|
+
pzSegments?: {
|
|
217
|
+
separator?: string;
|
|
218
|
+
segments: PzSegmentDefinition[];
|
|
219
|
+
};
|
|
211
220
|
}
|
|
212
221
|
|
|
213
222
|
export interface CacheOptions {
|
|
214
223
|
cache?: boolean;
|
|
215
224
|
expire?: number;
|
|
216
225
|
useProxy?: boolean;
|
|
226
|
+
compressed?: boolean;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface SetCookieOptions {
|
|
230
|
+
expires?: number; // days
|
|
231
|
+
path?: string;
|
|
232
|
+
domain?: string;
|
|
233
|
+
secure?: boolean;
|
|
234
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
217
235
|
}
|
|
218
236
|
|
|
219
237
|
export interface ClientRequestOptions {
|
|
@@ -238,22 +256,82 @@ export type ImageOptions = CDNOptions & {
|
|
|
238
256
|
|
|
239
257
|
export type Translations = { [key: string]: object };
|
|
240
258
|
|
|
259
|
+
export interface PzSegmentResolveContext {
|
|
260
|
+
req: PzNextRequest;
|
|
261
|
+
event: NextFetchEvent;
|
|
262
|
+
url: NextURL;
|
|
263
|
+
locale: string;
|
|
264
|
+
currency: string;
|
|
265
|
+
pathname: string;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface PzSegmentDefinition {
|
|
269
|
+
name: string;
|
|
270
|
+
resolve?: (context: PzSegmentResolveContext) => string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface PzSegmentsConfig {
|
|
274
|
+
separator: string;
|
|
275
|
+
segments: PzSegmentDefinition[];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Search params type compatible with both Next.js resolved searchParams and URLSearchParams
|
|
279
|
+
export type SearchParams = Record<string, string | string[] | undefined> | URLSearchParams;
|
|
280
|
+
|
|
281
|
+
// Page/Layout props — sync params for backward compatibility with v1 brands
|
|
241
282
|
export interface PageProps<T = any> {
|
|
242
|
-
params:
|
|
243
|
-
|
|
283
|
+
params: T & {
|
|
284
|
+
pz?: string;
|
|
285
|
+
commerce?: string;
|
|
286
|
+
locale?: string;
|
|
287
|
+
currency?: string;
|
|
288
|
+
url?: string;
|
|
289
|
+
[key: string]: any;
|
|
290
|
+
};
|
|
291
|
+
searchParams: SearchParams;
|
|
244
292
|
}
|
|
245
293
|
|
|
246
294
|
export interface LayoutProps<T = any> extends PageProps<T> {
|
|
247
295
|
children: React.ReactNode;
|
|
248
296
|
}
|
|
249
297
|
|
|
250
|
-
export interface RootLayoutProps<
|
|
298
|
+
export interface RootLayoutProps<T = any> extends LayoutProps<T> {
|
|
299
|
+
translations: Translations;
|
|
300
|
+
locale: Locale & {
|
|
301
|
+
isoCode: string;
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Async versions for Next.js 16 generateMetadata and internal use
|
|
306
|
+
export interface AsyncPageProps<T = any> {
|
|
307
|
+
params: Promise<T & {
|
|
308
|
+
pz?: string;
|
|
309
|
+
commerce?: string;
|
|
310
|
+
locale?: string;
|
|
311
|
+
currency?: string;
|
|
312
|
+
url?: string;
|
|
313
|
+
[key: string]: any;
|
|
314
|
+
}>;
|
|
315
|
+
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Resolved (sync) versions for inner components after withSegmentDefaults resolves props
|
|
319
|
+
export interface ResolvedPageProps<T = any> {
|
|
320
|
+
params: T & { locale: string; currency: string };
|
|
321
|
+
searchParams: SearchParams;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export interface ResolvedLayoutProps<T = any> extends ResolvedPageProps<T> {
|
|
325
|
+
children: React.ReactNode;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export interface ResolvedRootLayoutProps<
|
|
251
329
|
T = {
|
|
252
330
|
commerce: string;
|
|
253
331
|
locale: string;
|
|
254
332
|
currency: string;
|
|
255
333
|
}
|
|
256
|
-
> extends
|
|
334
|
+
> extends ResolvedLayoutProps<T> {
|
|
257
335
|
translations: Translations;
|
|
258
336
|
locale: Locale & {
|
|
259
337
|
isoCode: string;
|
|
@@ -275,10 +353,19 @@ export interface IconProps extends React.ComponentPropsWithRef<'i'> {
|
|
|
275
353
|
|
|
276
354
|
export interface ButtonProps
|
|
277
355
|
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
278
|
-
appearance?: 'filled' | 'outlined' | 'ghost';
|
|
356
|
+
appearance?: 'filled' | 'outlined' | 'ghost' | 'link' | string;
|
|
357
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
358
|
+
href?: string;
|
|
359
|
+
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
279
360
|
}
|
|
280
361
|
|
|
281
|
-
export
|
|
362
|
+
export interface FileInputProps extends React.HTMLProps<HTMLInputElement> {
|
|
363
|
+
fileClassName?: string;
|
|
364
|
+
fileNameWrapperClassName?: string;
|
|
365
|
+
fileInputClassName?: string;
|
|
366
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
367
|
+
buttonClassName?: string;
|
|
368
|
+
}
|
|
282
369
|
|
|
283
370
|
export interface PriceProps {
|
|
284
371
|
currencyCode?: string;
|
|
@@ -299,15 +386,19 @@ export interface InputProps extends React.HTMLProps<HTMLInputElement> {
|
|
|
299
386
|
|
|
300
387
|
export interface AccordionProps {
|
|
301
388
|
isCollapse?: boolean;
|
|
389
|
+
collapseClassName?: string;
|
|
302
390
|
title?: string;
|
|
303
391
|
subTitle?: string;
|
|
304
392
|
icons?: string[];
|
|
305
393
|
iconSize?: number;
|
|
306
394
|
iconColor?: string;
|
|
307
395
|
children?: ReactNode;
|
|
396
|
+
headerClassName?: string;
|
|
308
397
|
className?: string;
|
|
309
398
|
titleClassName?: string;
|
|
399
|
+
subTitleClassName?: string;
|
|
310
400
|
dataTestId?: string;
|
|
401
|
+
contentClassName?: string;
|
|
311
402
|
}
|
|
312
403
|
|
|
313
404
|
export interface PluginModuleComponentProps {
|
|
@@ -332,3 +423,20 @@ export interface PaginationProps {
|
|
|
332
423
|
direction?: 'next' | 'prev';
|
|
333
424
|
isLoading?: boolean;
|
|
334
425
|
}
|
|
426
|
+
|
|
427
|
+
export interface ModalProps {
|
|
428
|
+
portalId: string;
|
|
429
|
+
children?: React.ReactNode;
|
|
430
|
+
open?: boolean;
|
|
431
|
+
setOpen?: (open: boolean) => void;
|
|
432
|
+
title?: React.ReactNode;
|
|
433
|
+
showCloseButton?: React.ReactNode;
|
|
434
|
+
className?: string;
|
|
435
|
+
overlayClassName?: string;
|
|
436
|
+
headerWrapperClassName?: string;
|
|
437
|
+
titleClassName?: string;
|
|
438
|
+
closeButtonClassName?: string;
|
|
439
|
+
iconName?: string;
|
|
440
|
+
iconSize?: number;
|
|
441
|
+
iconClassName?: string;
|
|
442
|
+
}
|
package/types/next-auth.d.ts
CHANGED
package/types/widget.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export type WidgetStyle = {
|
|
2
|
+
[breakpoint: string]: Record<string, string>;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export enum WidgetDataSourceType {
|
|
6
|
+
COLLECTION = 'collection',
|
|
7
|
+
API = 'api',
|
|
8
|
+
STATIC = 'static'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface WidgetDataSource {
|
|
12
|
+
type: WidgetDataSourceType;
|
|
13
|
+
id: string;
|
|
14
|
+
widgetSlug?: string;
|
|
15
|
+
details: {
|
|
16
|
+
collection?: {
|
|
17
|
+
pk: number;
|
|
18
|
+
productLimit: number;
|
|
19
|
+
};
|
|
20
|
+
api?: {
|
|
21
|
+
url: string;
|
|
22
|
+
method: string;
|
|
23
|
+
headers: Record<string, string>;
|
|
24
|
+
body: Record<string, string>;
|
|
25
|
+
};
|
|
26
|
+
static?: {
|
|
27
|
+
name: string;
|
|
28
|
+
data: Record<string, any>;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ImageSrc = {
|
|
34
|
+
[key: string]: {
|
|
35
|
+
src: string;
|
|
36
|
+
dimensions: { width: number; height: number };
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type WidgetComponentData = {
|
|
41
|
+
id: string;
|
|
42
|
+
widgetSlug: string;
|
|
43
|
+
type: string;
|
|
44
|
+
tag: string;
|
|
45
|
+
style: WidgetStyle;
|
|
46
|
+
value: {
|
|
47
|
+
text?: string;
|
|
48
|
+
src?: string;
|
|
49
|
+
alt?: string;
|
|
50
|
+
title?: string;
|
|
51
|
+
url?: string;
|
|
52
|
+
type?: string;
|
|
53
|
+
width?: string;
|
|
54
|
+
height?: string;
|
|
55
|
+
autoplay?: boolean;
|
|
56
|
+
controls?: boolean;
|
|
57
|
+
muted?: boolean;
|
|
58
|
+
loop?: boolean;
|
|
59
|
+
[key: string]: any;
|
|
60
|
+
};
|
|
61
|
+
parentId: string | null;
|
|
62
|
+
iteratingNode: string;
|
|
63
|
+
iteratorValue: string;
|
|
64
|
+
iteratingValue: string;
|
|
65
|
+
selectedDataSourceId: string;
|
|
66
|
+
dataSources: WidgetDataSource[];
|
|
67
|
+
imageSrc?: ImageSrc;
|
|
68
|
+
href?: string;
|
|
69
|
+
target?: string;
|
|
70
|
+
slider?: {
|
|
71
|
+
items?: number;
|
|
72
|
+
slidesToSlide?: number;
|
|
73
|
+
autoPlay?: boolean;
|
|
74
|
+
autoPlaySpeed?: number;
|
|
75
|
+
infinite?: boolean;
|
|
76
|
+
showDots?: boolean;
|
|
77
|
+
arrows?: boolean;
|
|
78
|
+
itemsToShow?: number;
|
|
79
|
+
};
|
|
80
|
+
};
|
package/utils/app-fetch.ts
CHANGED
|
@@ -2,6 +2,7 @@ import Settings from 'settings';
|
|
|
2
2
|
import logger from '../utils/log';
|
|
3
3
|
import { headers, cookies } from 'next/headers';
|
|
4
4
|
import { ServerVariables } from './server-variables';
|
|
5
|
+
import { notFound } from 'next/navigation';
|
|
5
6
|
|
|
6
7
|
export enum FetchResponseType {
|
|
7
8
|
JSON = 'json',
|
|
@@ -43,12 +44,12 @@ const appFetch = async <T>({
|
|
|
43
44
|
const requestURL = `${decodeURIComponent(commerceUrl)}${url}`;
|
|
44
45
|
|
|
45
46
|
init.headers = {
|
|
47
|
+
cookie: nextCookies.toString(),
|
|
46
48
|
...(init.headers ?? {}),
|
|
47
49
|
...(ServerVariables.globalHeaders ?? {}),
|
|
48
50
|
'Accept-Language': currentLocale.apiValue,
|
|
49
51
|
'x-currency': currency,
|
|
50
|
-
'x-forwarded-for': ip
|
|
51
|
-
cookie: nextCookies.toString()
|
|
52
|
+
'x-forwarded-for': ip
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
init.next = {
|
|
@@ -75,6 +76,10 @@ const appFetch = async <T>({
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
if (status === 422) {
|
|
80
|
+
notFound();
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
return response;
|
|
79
84
|
};
|
|
80
85
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { SearchParams } from '../types';
|
|
1
2
|
import logger from './log';
|
|
2
3
|
|
|
3
4
|
export const generateCommerceSearchParams = (
|
|
4
|
-
searchParams?:
|
|
5
|
+
searchParams?: SearchParams
|
|
5
6
|
) => {
|
|
6
7
|
if (!searchParams) {
|
|
7
8
|
return null;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
const urlSerchParams = new URLSearchParams(searchParams);
|
|
11
|
+
const urlSerchParams = new URLSearchParams(searchParams as any);
|
|
11
12
|
|
|
12
13
|
Object.entries(searchParams).forEach(([key, value]) => {
|
|
13
14
|
if (typeof value === 'object') {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Settings from 'settings';
|
|
2
|
+
|
|
3
|
+
export default function getRootHostname(
|
|
4
|
+
url: string | undefined
|
|
5
|
+
): string | null {
|
|
6
|
+
if (!url) return null;
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const urlObj = new URL(url);
|
|
10
|
+
const hostname = urlObj.hostname;
|
|
11
|
+
const parts = hostname.split('.');
|
|
12
|
+
|
|
13
|
+
const firstPart = parts[0];
|
|
14
|
+
|
|
15
|
+
const isLocale = Settings.localization.locales.some(
|
|
16
|
+
(locale) => locale.value === firstPart
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (isLocale) {
|
|
20
|
+
const hostnameAfterLocale = parts.slice(1).join('.');
|
|
21
|
+
return `.${hostnameAfterLocale}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return `.${hostname}`;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/utils/index.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import settings from 'settings';
|
|
2
2
|
import { LocaleUrlStrategy } from '../localization';
|
|
3
|
-
import { CDNOptions, ClientRequestOptions } from '../types';
|
|
3
|
+
import { CDNOptions, ClientRequestOptions, SetCookieOptions } from '../types';
|
|
4
|
+
import getRootHostname from './get-root-hostname';
|
|
4
5
|
|
|
5
6
|
export * from './get-currency';
|
|
6
7
|
export * from './menu-generator';
|
|
7
8
|
export * from './generate-commerce-search-params';
|
|
8
9
|
export * from './get-currency-label';
|
|
10
|
+
export * from './pz-segments';
|
|
11
|
+
export * from './get-checkout-path';
|
|
9
12
|
|
|
10
13
|
export function getCookie(name: string) {
|
|
11
14
|
if (typeof document === 'undefined') {
|
|
@@ -20,14 +23,40 @@ export function getCookie(name: string) {
|
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
export function setCookie(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
export function setCookie(
|
|
27
|
+
name: string,
|
|
28
|
+
value: string,
|
|
29
|
+
options: SetCookieOptions = {}
|
|
30
|
+
) {
|
|
31
|
+
const cookieParts = [`${name}=${value}`];
|
|
32
|
+
|
|
33
|
+
if (options.expires) {
|
|
34
|
+
const date = new Date();
|
|
35
|
+
date.setTime(date.getTime() + options.expires * 24 * 60 * 60 * 1000);
|
|
36
|
+
cookieParts.push(`expires=${date.toUTCString()}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
cookieParts.push(`path=${options.path ?? '/'}`);
|
|
40
|
+
|
|
41
|
+
if (options.secure) {
|
|
42
|
+
cookieParts.push('secure');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (options.sameSite) {
|
|
46
|
+
cookieParts.push(`sameSite=${options.sameSite}`);
|
|
47
|
+
}
|
|
26
48
|
|
|
27
|
-
|
|
49
|
+
const domain =
|
|
50
|
+
options.domain ??
|
|
51
|
+
(settings.localization.localeUrlStrategy === LocaleUrlStrategy.Subdomain
|
|
52
|
+
? getRootHostname(document.location.href)
|
|
53
|
+
: null);
|
|
28
54
|
|
|
29
|
-
|
|
30
|
-
|
|
55
|
+
if (domain) {
|
|
56
|
+
cookieParts.push(`domain=${domain}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
document.cookie = cookieParts.join('; ');
|
|
31
60
|
}
|
|
32
61
|
|
|
33
62
|
export function removeCookie(name: string) {
|
|
@@ -155,8 +184,9 @@ export function buildCDNUrl(url: string, config?: CDNOptions) {
|
|
|
155
184
|
export const urlLocaleMatcherRegex = new RegExp(
|
|
156
185
|
`^/(${settings.localization.locales
|
|
157
186
|
.filter((l) =>
|
|
158
|
-
|
|
159
|
-
|
|
187
|
+
![LocaleUrlStrategy.ShowAllLocales, LocaleUrlStrategy.Subdomain].includes(
|
|
188
|
+
settings.localization.localeUrlStrategy
|
|
189
|
+
)
|
|
160
190
|
? l.value !== settings.localization.defaultLocaleValue
|
|
161
191
|
: l
|
|
162
192
|
)
|
|
@@ -165,7 +195,14 @@ export const urlLocaleMatcherRegex = new RegExp(
|
|
|
165
195
|
);
|
|
166
196
|
|
|
167
197
|
export const getPosError = () => {
|
|
168
|
-
const
|
|
198
|
+
const cookieValue = getCookie('pz-pos-error');
|
|
199
|
+
let decoded: string;
|
|
200
|
+
try {
|
|
201
|
+
decoded = cookieValue ? decodeURIComponent(cookieValue) : '{}';
|
|
202
|
+
} catch {
|
|
203
|
+
decoded = cookieValue ?? '{}';
|
|
204
|
+
}
|
|
205
|
+
const error = JSON.parse(decoded);
|
|
169
206
|
|
|
170
207
|
// delete 'pz-pos-error' cookie when refreshing or closing page
|
|
171
208
|
window.addEventListener('beforeunload', () => {
|
|
@@ -175,6 +212,23 @@ export const getPosError = () => {
|
|
|
175
212
|
return error;
|
|
176
213
|
};
|
|
177
214
|
|
|
215
|
+
export const checkPaymentWillRedirect = (response: {
|
|
216
|
+
context_list?: Array<{
|
|
217
|
+
page_name: string;
|
|
218
|
+
page_context?: { context_data?: { redirect_url?: string } };
|
|
219
|
+
}>;
|
|
220
|
+
redirect_url?: string;
|
|
221
|
+
errors?: unknown;
|
|
222
|
+
}): boolean => {
|
|
223
|
+
if (!response) return false;
|
|
224
|
+
|
|
225
|
+
const hasThankYouPage = response.context_list?.some(
|
|
226
|
+
(c) => c.page_name === 'ThankYouPage'
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
return Boolean(hasThankYouPage || response.redirect_url);
|
|
230
|
+
};
|
|
231
|
+
|
|
178
232
|
export const urlSchemes = [
|
|
179
233
|
'http',
|
|
180
234
|
'tel:',
|
package/utils/localization.ts
CHANGED
|
@@ -11,6 +11,10 @@ export const getUrlPathWithLocale = (
|
|
|
11
11
|
currentLocale = defaultLocaleValue;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
|
|
15
|
+
return pathname;
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
if (localeUrlStrategy === LocaleUrlStrategy.HideAllLocales) {
|
|
15
19
|
return pathname;
|
|
16
20
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
const iframeURLChange = (iframe, callback) => {
|
|
2
2
|
iframe.addEventListener('load', () => {
|
|
3
3
|
setTimeout(() => {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
try {
|
|
5
|
+
if (iframe?.contentWindow?.location) {
|
|
6
|
+
const iframeLocation = iframe.contentWindow.location;
|
|
7
|
+
|
|
8
|
+
callback(iframeLocation);
|
|
9
|
+
}
|
|
10
|
+
} catch (error) {
|
|
11
|
+
// Expected: browser blocks cross-origin iframe access for security
|
|
6
12
|
}
|
|
7
13
|
}, 0);
|
|
8
14
|
});
|
|
@@ -10,22 +10,17 @@ export enum MiddlewareNames {
|
|
|
10
10
|
DataSourceShippingOptionMiddleware = 'dataSourceShippingOptionMiddleware',
|
|
11
11
|
AttributeBasedShippingOptionMiddleware = 'attributeBasedShippingOptionMiddleware',
|
|
12
12
|
PaymentOptionMiddleware = 'paymentOptionMiddleware',
|
|
13
|
+
PaymentOptionResetMiddleware = 'paymentOptionResetMiddleware',
|
|
13
14
|
InstallmentOptionMiddleware = 'installmentOptionMiddleware',
|
|
14
15
|
ShippingStepMiddleware = 'shippingStepMiddleware'
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export const overrideMiddleware = (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
):
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
middlewares.forEach((mw, index) => {
|
|
26
|
-
const updatedMiddleware = updatesMap.get(mw.name as MiddlewareNames);
|
|
27
|
-
if (updatedMiddleware) {
|
|
28
|
-
middlewares[index] = updatedMiddleware;
|
|
29
|
-
}
|
|
19
|
+
originalMiddlewares: Middleware[],
|
|
20
|
+
overrides: Record<string, Middleware>
|
|
21
|
+
): Middleware[] => {
|
|
22
|
+
return originalMiddlewares.map((middleware) => {
|
|
23
|
+
const middlewareKey = middleware.name || middleware.toString();
|
|
24
|
+
return overrides[middlewareKey] || middleware;
|
|
30
25
|
});
|
|
31
26
|
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { PzSegmentDefinition, PzSegmentsConfig } from '../types';
|
|
2
|
+
|
|
3
|
+
export function isLegacyMode(settings: any): boolean {
|
|
4
|
+
return !settings.usePzSegment;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const DEFAULT_SEPARATOR = '--';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_SEGMENTS: PzSegmentDefinition[] = [
|
|
10
|
+
{ name: 'locale' },
|
|
11
|
+
{ name: 'currency' },
|
|
12
|
+
{ name: 'url' }
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export function getPzSegmentsConfig(settings: any): PzSegmentsConfig {
|
|
16
|
+
if (settings.pzSegments) {
|
|
17
|
+
const customSegments = (settings.pzSegments.segments ?? []).filter(
|
|
18
|
+
(seg: PzSegmentDefinition) =>
|
|
19
|
+
!DEFAULT_SEGMENTS.some((d) => d.name === seg.name)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
separator: settings.pzSegments.separator ?? DEFAULT_SEPARATOR,
|
|
24
|
+
segments: [...DEFAULT_SEGMENTS, ...customSegments]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
separator: DEFAULT_SEPARATOR,
|
|
30
|
+
segments: DEFAULT_SEGMENTS
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function encodePzValue(
|
|
35
|
+
values: Record<string, string>,
|
|
36
|
+
config: PzSegmentsConfig
|
|
37
|
+
): string {
|
|
38
|
+
return config.segments
|
|
39
|
+
.map((seg) => values[seg.name] ?? '')
|
|
40
|
+
.join(config.separator);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function decodePzValue(
|
|
44
|
+
pzValue: string,
|
|
45
|
+
config: PzSegmentsConfig
|
|
46
|
+
): Record<string, string> {
|
|
47
|
+
const parts = pzValue.split(config.separator);
|
|
48
|
+
const result: Record<string, string> = {};
|
|
49
|
+
|
|
50
|
+
config.segments.forEach((seg, index) => {
|
|
51
|
+
result[seg.name] = parts[index] ?? '';
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getBuiltInSegments(
|
|
58
|
+
parsed: Record<string, string>,
|
|
59
|
+
settings: any
|
|
60
|
+
): { locale: string; currency: string; url: string } {
|
|
61
|
+
const { defaultLocaleValue, defaultCurrencyCode } = settings.localization;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
locale: parsed.locale || defaultLocaleValue,
|
|
65
|
+
currency: parsed.currency || defaultCurrencyCode,
|
|
66
|
+
url: parsed.url ? decodeURIComponent(parsed.url) : ''
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function parsePzParams(
|
|
71
|
+
params: { pz?: string; locale?: string; currency?: string; url?: string },
|
|
72
|
+
settings: any
|
|
73
|
+
): { locale: string; currency: string; url: string; [key: string]: string } {
|
|
74
|
+
if (isLegacyMode(settings)) {
|
|
75
|
+
return {
|
|
76
|
+
locale:
|
|
77
|
+
params.locale ?? settings.localization.defaultLocaleValue,
|
|
78
|
+
currency:
|
|
79
|
+
params.currency ?? settings.localization.defaultCurrencyCode,
|
|
80
|
+
url: params.url ? decodeURIComponent(params.url) : ''
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const config = getPzSegmentsConfig(settings);
|
|
85
|
+
const parsed = decodePzValue(params.pz ?? '', config);
|
|
86
|
+
const builtIn = getBuiltInSegments(parsed, settings);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
...parsed,
|
|
90
|
+
...builtIn
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import settings from 'settings';
|
|
2
|
+
import { getUrlPathWithLocale } from './localization';
|
|
3
|
+
|
|
4
|
+
type IgnorePath = string | RegExp;
|
|
5
|
+
|
|
6
|
+
const defaultIgnoreList: string[] = [];
|
|
7
|
+
|
|
8
|
+
const extraIgnores: IgnorePath[] = Array.isArray(
|
|
9
|
+
settings.commerceRedirectionIgnoreList
|
|
10
|
+
)
|
|
11
|
+
? settings.commerceRedirectionIgnoreList.map((path) => {
|
|
12
|
+
if (path === '/users/reset') {
|
|
13
|
+
return /^\/users\/reset\/[^/]+\/[^/]+\/$/;
|
|
14
|
+
}
|
|
15
|
+
return path;
|
|
16
|
+
})
|
|
17
|
+
: [];
|
|
18
|
+
|
|
19
|
+
export function shouldIgnoreRedirect(
|
|
20
|
+
pathname: string,
|
|
21
|
+
locale: string
|
|
22
|
+
): boolean {
|
|
23
|
+
if (!pathname) return false;
|
|
24
|
+
|
|
25
|
+
const rawIgnoreList: IgnorePath[] = [...defaultIgnoreList, ...extraIgnores];
|
|
26
|
+
|
|
27
|
+
return rawIgnoreList.some((ignorePath) => {
|
|
28
|
+
if (ignorePath instanceof RegExp) {
|
|
29
|
+
return ignorePath.test(pathname);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const localized = getUrlPathWithLocale(ignorePath, locale);
|
|
33
|
+
return localized === pathname;
|
|
34
|
+
});
|
|
35
|
+
}
|