@akinon/next 2.0.0-beta.2 → 2.0.0-beta.20
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 +377 -7
- package/__tests__/next-config.test.ts +83 -0
- package/__tests__/tsconfig.json +23 -0
- package/api/auth.ts +133 -44
- 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-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/components/theme-editor/blocks/accordion-block.tsx +136 -0
- package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
- package/components/theme-editor/blocks/button-block.tsx +593 -0
- package/components/theme-editor/blocks/counter-block.tsx +348 -0
- package/components/theme-editor/blocks/divider-block.tsx +20 -0
- package/components/theme-editor/blocks/embed-block.tsx +208 -0
- package/components/theme-editor/blocks/group-block.tsx +116 -0
- package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
- package/components/theme-editor/blocks/icon-block.tsx +230 -0
- package/components/theme-editor/blocks/image-block.tsx +137 -0
- package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
- package/components/theme-editor/blocks/input-block.tsx +123 -0
- package/components/theme-editor/blocks/link-block.tsx +216 -0
- package/components/theme-editor/blocks/lottie-block.tsx +325 -0
- package/components/theme-editor/blocks/map-block.tsx +89 -0
- package/components/theme-editor/blocks/slider-block.tsx +595 -0
- package/components/theme-editor/blocks/tab-block.tsx +10 -0
- package/components/theme-editor/blocks/text-block.tsx +52 -0
- package/components/theme-editor/blocks/video-block.tsx +122 -0
- package/components/theme-editor/components/action-toolbar.tsx +305 -0
- package/components/theme-editor/components/designer-overlay.tsx +74 -0
- package/components/theme-editor/components/with-designer-features.tsx +142 -0
- package/components/theme-editor/dynamic-font-loader.tsx +79 -0
- package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
- package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
- package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
- package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
- package/components/theme-editor/placeholder-registry.ts +31 -0
- package/components/theme-editor/sections/before-after-section.tsx +245 -0
- package/components/theme-editor/sections/contact-form-section.tsx +563 -0
- package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
- package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
- package/components/theme-editor/sections/divider-section.tsx +62 -0
- package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
- package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
- package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
- package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
- package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
- package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
- package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
- package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
- package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
- package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
- package/components/theme-editor/sections/section-wrapper.tsx +135 -0
- package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
- package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
- package/components/theme-editor/sections/tabs-section.tsx +578 -0
- package/components/theme-editor/theme-block.tsx +102 -0
- package/components/theme-editor/theme-placeholder-client.tsx +218 -0
- package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
- package/components/theme-editor/theme-placeholder.tsx +288 -0
- package/components/theme-editor/theme-section.tsx +1224 -0
- package/components/theme-editor/theme-settings-context.tsx +13 -0
- package/components/theme-editor/utils/index.ts +792 -0
- package/components/theme-editor/utils/iterator-utils.ts +234 -0
- package/components/theme-editor/utils/publish-window.ts +86 -0
- package/components/theme-editor/utils/visibility-rules.ts +188 -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 +25 -10
- 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 +101 -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 +13 -6
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,69 @@ 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
|
+
// Next.js 16 async page/layout props (for exported page components and generateMetadata)
|
|
241
279
|
export interface PageProps<T = any> {
|
|
242
|
-
params: Promise<T & {
|
|
243
|
-
|
|
280
|
+
params: Promise<T & {
|
|
281
|
+
pz?: string;
|
|
282
|
+
commerce?: string;
|
|
283
|
+
locale?: string;
|
|
284
|
+
currency?: string;
|
|
285
|
+
url?: string;
|
|
286
|
+
[key: string]: any;
|
|
287
|
+
}>;
|
|
288
|
+
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
|
244
289
|
}
|
|
245
290
|
|
|
246
291
|
export interface LayoutProps<T = any> extends PageProps<T> {
|
|
247
292
|
children: React.ReactNode;
|
|
248
293
|
}
|
|
249
294
|
|
|
250
|
-
export interface RootLayoutProps<
|
|
295
|
+
export interface RootLayoutProps<T = any> extends LayoutProps<T> {
|
|
296
|
+
translations: Translations;
|
|
297
|
+
locale: Locale & {
|
|
298
|
+
isoCode: string;
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Search params type compatible with both Next.js resolved searchParams and URLSearchParams
|
|
303
|
+
export type SearchParams = Record<string, string | string[] | undefined> | URLSearchParams;
|
|
304
|
+
|
|
305
|
+
// Resolved (sync) versions for inner components after withSegmentDefaults resolves props
|
|
306
|
+
export interface ResolvedPageProps<T = any> {
|
|
307
|
+
params: T & { locale: string; currency: string };
|
|
308
|
+
searchParams: SearchParams;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export interface ResolvedLayoutProps<T = any> extends ResolvedPageProps<T> {
|
|
312
|
+
children: React.ReactNode;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface ResolvedRootLayoutProps<
|
|
251
316
|
T = {
|
|
252
317
|
commerce: string;
|
|
253
318
|
locale: string;
|
|
254
319
|
currency: string;
|
|
255
320
|
}
|
|
256
|
-
> extends
|
|
321
|
+
> extends ResolvedLayoutProps<T> {
|
|
257
322
|
translations: Translations;
|
|
258
323
|
locale: Locale & {
|
|
259
324
|
isoCode: string;
|
|
@@ -275,10 +340,19 @@ export interface IconProps extends React.ComponentPropsWithRef<'i'> {
|
|
|
275
340
|
|
|
276
341
|
export interface ButtonProps
|
|
277
342
|
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
278
|
-
appearance?: 'filled' | 'outlined' | 'ghost';
|
|
343
|
+
appearance?: 'filled' | 'outlined' | 'ghost' | 'link' | string;
|
|
344
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
345
|
+
href?: string;
|
|
346
|
+
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
279
347
|
}
|
|
280
348
|
|
|
281
|
-
export
|
|
349
|
+
export interface FileInputProps extends React.HTMLProps<HTMLInputElement> {
|
|
350
|
+
fileClassName?: string;
|
|
351
|
+
fileNameWrapperClassName?: string;
|
|
352
|
+
fileInputClassName?: string;
|
|
353
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
354
|
+
buttonClassName?: string;
|
|
355
|
+
}
|
|
282
356
|
|
|
283
357
|
export interface PriceProps {
|
|
284
358
|
currencyCode?: string;
|
|
@@ -299,15 +373,19 @@ export interface InputProps extends React.HTMLProps<HTMLInputElement> {
|
|
|
299
373
|
|
|
300
374
|
export interface AccordionProps {
|
|
301
375
|
isCollapse?: boolean;
|
|
376
|
+
collapseClassName?: string;
|
|
302
377
|
title?: string;
|
|
303
378
|
subTitle?: string;
|
|
304
379
|
icons?: string[];
|
|
305
380
|
iconSize?: number;
|
|
306
381
|
iconColor?: string;
|
|
307
382
|
children?: ReactNode;
|
|
383
|
+
headerClassName?: string;
|
|
308
384
|
className?: string;
|
|
309
385
|
titleClassName?: string;
|
|
386
|
+
subTitleClassName?: string;
|
|
310
387
|
dataTestId?: string;
|
|
388
|
+
contentClassName?: string;
|
|
311
389
|
}
|
|
312
390
|
|
|
313
391
|
export interface PluginModuleComponentProps {
|
|
@@ -332,3 +410,20 @@ export interface PaginationProps {
|
|
|
332
410
|
direction?: 'next' | 'prev';
|
|
333
411
|
isLoading?: boolean;
|
|
334
412
|
}
|
|
413
|
+
|
|
414
|
+
export interface ModalProps {
|
|
415
|
+
portalId: string;
|
|
416
|
+
children?: React.ReactNode;
|
|
417
|
+
open?: boolean;
|
|
418
|
+
setOpen?: (open: boolean) => void;
|
|
419
|
+
title?: React.ReactNode;
|
|
420
|
+
showCloseButton?: React.ReactNode;
|
|
421
|
+
className?: string;
|
|
422
|
+
overlayClassName?: string;
|
|
423
|
+
headerWrapperClassName?: string;
|
|
424
|
+
titleClassName?: string;
|
|
425
|
+
closeButtonClassName?: string;
|
|
426
|
+
iconName?: string;
|
|
427
|
+
iconSize?: number;
|
|
428
|
+
iconClassName?: string;
|
|
429
|
+
}
|
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
|
+
}
|
package/utils/redirect.ts
CHANGED
|
@@ -3,21 +3,27 @@ import Settings from 'settings';
|
|
|
3
3
|
import { headers } from 'next/headers';
|
|
4
4
|
import { ServerVariables } from '@akinon/next/utils/server-variables';
|
|
5
5
|
import { getUrlPathWithLocale } from '@akinon/next/utils/localization';
|
|
6
|
+
import { urlLocaleMatcherRegex } from '@akinon/next/utils';
|
|
6
7
|
|
|
7
8
|
export const redirect = async (path: string, type?: RedirectType) => {
|
|
8
9
|
const nextHeaders = await headers();
|
|
9
10
|
const pageUrl = new URL(
|
|
10
|
-
nextHeaders.get('pz-url') ?? process.env.NEXT_PUBLIC_URL
|
|
11
|
+
nextHeaders.get('pz-url') ?? process.env.NEXT_PUBLIC_URL ?? ''
|
|
11
12
|
);
|
|
12
13
|
|
|
13
14
|
const currentLocale = Settings.localization.locales.find(
|
|
14
15
|
(locale) => locale.value === ServerVariables.locale
|
|
15
16
|
);
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
+
const searchParams = new URLSearchParams(pageUrl.search);
|
|
19
|
+
|
|
20
|
+
const callbackUrl =
|
|
21
|
+
pageUrl.pathname.replace(urlLocaleMatcherRegex, '') +
|
|
22
|
+
(searchParams.toString() ? `?${searchParams.toString()}` : '');
|
|
23
|
+
|
|
18
24
|
const redirectUrlWithLocale = getUrlPathWithLocale(
|
|
19
25
|
path,
|
|
20
|
-
currentLocale
|
|
26
|
+
currentLocale?.value
|
|
21
27
|
);
|
|
22
28
|
|
|
23
29
|
const redirectUrl = `${redirectUrlWithLocale}?callbackUrl=${callbackUrl}`;
|