@akinon/next 2.0.0-beta.9 → 2.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/CHANGELOG.md +434 -23
- package/__tests__/next-config.test.ts +83 -0
- package/__tests__/tsconfig.json +23 -0
- package/api/auth.ts +367 -63
- 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/components/accordion.tsx +21 -6
- package/components/client-root.tsx +119 -3
- package/components/file-input.tsx +65 -3
- package/components/index.ts +1 -0
- package/components/input.tsx +2 -2
- 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 +3 -3
- package/components/selected-payment-option-view.tsx +21 -0
- package/data/client/account.ts +17 -2
- package/data/client/basket.ts +39 -0
- package/data/client/checkout.ts +336 -99
- package/data/client/misc.ts +13 -1
- package/data/server/category.ts +11 -9
- package/data/server/flatpage.ts +4 -1
- package/data/server/form.ts +4 -1
- package/data/server/landingpage.ts +4 -1
- package/data/server/list.ts +5 -4
- package/data/server/menu.ts +4 -1
- package/data/server/product.ts +97 -52
- package/data/server/seo.ts +4 -1
- package/data/server/special-page.ts +5 -4
- package/data/server/widget.ts +71 -1
- package/data/urls.ts +6 -3
- package/hocs/client/with-segment-defaults.tsx +2 -2
- package/hocs/server/with-segment-defaults.tsx +81 -20
- package/hooks/index.ts +3 -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 +53 -19
- package/instrumentation/index.ts +0 -1
- package/instrumentation/node.ts +2 -20
- package/jest.config.js +25 -0
- package/lib/cache-handler.mjs +534 -16
- package/lib/cache.ts +269 -34
- 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 +357 -203
- package/middlewares/index.ts +10 -2
- package/middlewares/locale.ts +5 -3
- 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 +9 -15
- package/middlewares/wallet-complete-redirection.ts +206 -0
- package/package.json +24 -10
- package/plugins.d.ts +19 -4
- package/plugins.js +9 -1
- package/redux/actions.ts +47 -0
- package/redux/middlewares/checkout.ts +61 -8
- package/redux/middlewares/index.ts +14 -10
- package/redux/middlewares/pre-order/address.ts +1 -1
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +1 -1
- package/redux/middlewares/pre-order/data-source-shipping-option.ts +1 -1
- package/redux/middlewares/pre-order/delivery-option.ts +1 -1
- package/redux/middlewares/pre-order/index.ts +3 -1
- package/redux/middlewares/pre-order/installment-option.ts +2 -1
- package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
- package/redux/middlewares/pre-order/payment-option.ts +1 -1
- package/redux/middlewares/pre-order/pre-order-validation.ts +4 -3
- package/redux/middlewares/pre-order/redirection.ts +2 -2
- package/redux/middlewares/pre-order/set-pre-order.ts +2 -2
- package/redux/middlewares/pre-order/shipping-option.ts +1 -1
- package/redux/middlewares/pre-order/shipping-step.ts +1 -1
- package/redux/reducers/checkout.ts +15 -1
- package/redux/reducers/index.ts +7 -1
- package/redux/reducers/widget.ts +80 -0
- package/sentry/index.ts +54 -17
- package/tailwind/content.js +16 -0
- package/types/commerce/checkout.ts +26 -1
- package/types/commerce/widget.ts +33 -0
- package/types/index.ts +114 -5
- 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 +69 -18
- package/utils/mobile-3d-iframe.ts +8 -2
- package/utils/override-middleware.ts +1 -0
- 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/with-pz-config.js +20 -7
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import settings from 'settings';
|
|
2
1
|
import { JSX } from 'react';
|
|
3
|
-
import
|
|
2
|
+
import settings from 'settings';
|
|
3
|
+
import {
|
|
4
|
+
ResolvedPageProps,
|
|
5
|
+
ResolvedLayoutProps,
|
|
6
|
+
ResolvedRootLayoutProps
|
|
7
|
+
} from '../../types';
|
|
4
8
|
import { redirect } from 'next/navigation';
|
|
5
9
|
import { ServerVariables } from '../../utils/server-variables';
|
|
6
10
|
import { ROUTES } from 'routes';
|
|
7
11
|
import logger from '../../utils/log';
|
|
12
|
+
import {
|
|
13
|
+
decodePzValue,
|
|
14
|
+
getPzSegmentsConfig,
|
|
15
|
+
getBuiltInSegments,
|
|
16
|
+
isLegacyMode
|
|
17
|
+
} from '../../utils/pz-segments';
|
|
8
18
|
|
|
9
19
|
type SegmentType = 'root-layout' | 'layout' | 'page';
|
|
10
20
|
|
|
@@ -13,27 +23,66 @@ interface SegmentDefaultsOptions {
|
|
|
13
23
|
}
|
|
14
24
|
|
|
15
25
|
export const withSegmentDefaults =
|
|
16
|
-
<T extends
|
|
26
|
+
<T extends ResolvedPageProps | ResolvedLayoutProps | ResolvedRootLayoutProps>(
|
|
17
27
|
Component: (
|
|
18
28
|
props?: T
|
|
19
29
|
) => null | JSX.Element | Promise<JSX.Element> | Promise<JSX.Element[]>,
|
|
20
30
|
options: SegmentDefaultsOptions
|
|
21
31
|
) =>
|
|
22
|
-
async (props:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
async (props: any) => {
|
|
33
|
+
const resolvedParams = await props.params;
|
|
34
|
+
const resolvedSearchParams = props.searchParams
|
|
35
|
+
? await props.searchParams
|
|
36
|
+
: undefined;
|
|
37
|
+
|
|
38
|
+
// Normalize Next 16's raw Record<string, string | string[]> shape into a
|
|
39
|
+
// URLSearchParams instance so v1 brands (which expect URLSearchParams) can
|
|
40
|
+
// call .get/.has/etc without runtime breakage.
|
|
41
|
+
const normalizedSearchParams =
|
|
42
|
+
resolvedSearchParams instanceof URLSearchParams
|
|
43
|
+
? resolvedSearchParams
|
|
44
|
+
: new URLSearchParams(
|
|
45
|
+
Object.entries(resolvedSearchParams ?? {}).flatMap(([k, v]) =>
|
|
46
|
+
Array.isArray(v)
|
|
47
|
+
? v.map((item): [string, string] => [k, String(item)])
|
|
48
|
+
: v != null
|
|
49
|
+
? ([[k, String(v)]] as [string, string][])
|
|
50
|
+
: []
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
let componentProps = {
|
|
55
|
+
...props,
|
|
56
|
+
params: resolvedParams,
|
|
57
|
+
searchParams: normalizedSearchParams,
|
|
58
|
+
...('children' in props ? { children: (props as any).children } : {})
|
|
59
|
+
} as T;
|
|
60
|
+
|
|
61
|
+
let localeValue: string;
|
|
62
|
+
let currencyValue: string;
|
|
63
|
+
|
|
64
|
+
if (isLegacyMode(settings)) {
|
|
65
|
+
localeValue = resolvedParams.locale;
|
|
66
|
+
currencyValue = resolvedParams.currency;
|
|
67
|
+
} else {
|
|
68
|
+
const pzConfig = getPzSegmentsConfig(settings);
|
|
69
|
+
const parsed = decodePzValue(resolvedParams.pz, pzConfig);
|
|
70
|
+
const builtIn = getBuiltInSegments(parsed, settings);
|
|
71
|
+
localeValue = builtIn.locale;
|
|
72
|
+
currencyValue = builtIn.currency;
|
|
73
|
+
}
|
|
26
74
|
|
|
27
75
|
if (options.segmentType === 'root-layout') {
|
|
28
76
|
componentProps = (await addRootLayoutProps(
|
|
29
|
-
componentProps as
|
|
77
|
+
componentProps as ResolvedRootLayoutProps,
|
|
78
|
+
localeValue
|
|
30
79
|
)) as T;
|
|
31
80
|
|
|
32
81
|
checkRedisVariables();
|
|
33
82
|
}
|
|
34
83
|
|
|
35
|
-
ServerVariables.locale =
|
|
36
|
-
ServerVariables.currency =
|
|
84
|
+
ServerVariables.locale = localeValue;
|
|
85
|
+
ServerVariables.currency = currencyValue;
|
|
37
86
|
|
|
38
87
|
return await (
|
|
39
88
|
<>
|
|
@@ -42,12 +91,21 @@ export const withSegmentDefaults =
|
|
|
42
91
|
);
|
|
43
92
|
};
|
|
44
93
|
|
|
45
|
-
const addRootLayoutProps = async (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
94
|
+
const addRootLayoutProps = async (
|
|
95
|
+
componentProps: ResolvedRootLayoutProps,
|
|
96
|
+
localeValue: string
|
|
97
|
+
) => {
|
|
98
|
+
if (isLegacyMode(settings)) {
|
|
99
|
+
const params = componentProps.params;
|
|
100
|
+
if (
|
|
101
|
+
params.commerce !==
|
|
102
|
+
encodeURIComponent(decodeURI(settings.commerceUrl)) ||
|
|
103
|
+
!settings.localization.locales.find((l) => l.value === localeValue)
|
|
104
|
+
) {
|
|
105
|
+
return redirect(ROUTES.HOME);
|
|
106
|
+
}
|
|
107
|
+
} else if (
|
|
108
|
+
!settings.localization.locales.find((l) => l.value === localeValue)
|
|
51
109
|
) {
|
|
52
110
|
return redirect(ROUTES.HOME);
|
|
53
111
|
}
|
|
@@ -55,12 +113,12 @@ const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
|
|
|
55
113
|
const { getTranslations } = settings.useOptimizedTranslations
|
|
56
114
|
? require('translations')
|
|
57
115
|
: require('../../utils/server-translation');
|
|
58
|
-
const translations = await getTranslations(
|
|
116
|
+
const translations = await getTranslations(localeValue);
|
|
59
117
|
|
|
60
118
|
componentProps.translations = translations;
|
|
61
119
|
|
|
62
120
|
const locale = settings.localization.locales.find(
|
|
63
|
-
(l) => l.value ===
|
|
121
|
+
(l) => l.value === localeValue
|
|
64
122
|
);
|
|
65
123
|
const [isoCode] = locale.value.split('-');
|
|
66
124
|
|
|
@@ -75,10 +133,13 @@ const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
|
|
|
75
133
|
const checkRedisVariables = () => {
|
|
76
134
|
const requiredVariableValues = [
|
|
77
135
|
process.env.CACHE_HOST,
|
|
78
|
-
process.env.CACHE_PORT
|
|
79
|
-
process.env.CACHE_SECRET
|
|
136
|
+
process.env.CACHE_PORT
|
|
80
137
|
];
|
|
81
138
|
|
|
139
|
+
if (!settings.usePrettyUrlRoute) {
|
|
140
|
+
requiredVariableValues.push(process.env.CACHE_SECRET);
|
|
141
|
+
}
|
|
142
|
+
|
|
82
143
|
if (
|
|
83
144
|
!requiredVariableValues.every((v) => v) &&
|
|
84
145
|
process.env.NODE_ENV === 'production'
|
package/hooks/index.ts
CHANGED
|
@@ -10,4 +10,7 @@ export * from './use-mobile-iframe-handler';
|
|
|
10
10
|
export * from './use-payment-options';
|
|
11
11
|
export * from './use-pagination';
|
|
12
12
|
export * from './use-message-listener';
|
|
13
|
+
export * from './use-logger';
|
|
14
|
+
export * from './use-logger-context';
|
|
13
15
|
export * from './use-sentry-uncaught-errors';
|
|
16
|
+
export * from './use-pz-params';
|
|
@@ -25,19 +25,33 @@ export const useLocalization = () => {
|
|
|
25
25
|
* @param locale Locale value defined in the settings.
|
|
26
26
|
*/
|
|
27
27
|
const setLocale = (locale: string) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
localeUrlStrategy !== LocaleUrlStrategy.ShowAllLocales
|
|
31
|
-
? ''
|
|
32
|
-
: `/${locale}`;
|
|
28
|
+
const { protocol, hostname, port, search, pathname } = location;
|
|
29
|
+
const pathnameWithoutLocale = pathname.replace(urlLocaleMatcherRegex, '');
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
let targetUrl;
|
|
32
|
+
|
|
33
|
+
if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
|
|
34
|
+
const hostParts = hostname.split('.');
|
|
35
|
+
const subDomain = hostParts[0];
|
|
36
|
+
const isSubdomainLocale = locales.some((loc) => loc.value === subDomain);
|
|
37
|
+
const baseDomain = isSubdomainLocale
|
|
38
|
+
? hostParts.slice(1).join('.')
|
|
39
|
+
: hostname;
|
|
40
|
+
|
|
41
|
+
const formattedPort = port ? `:${port}` : '';
|
|
42
|
+
|
|
43
|
+
targetUrl = `${protocol}//${locale}.${baseDomain}${formattedPort}${pathnameWithoutLocale}${search}`;
|
|
44
|
+
} else {
|
|
45
|
+
const shouldHideLocale =
|
|
46
|
+
locale === defaultLocaleValue &&
|
|
47
|
+
localeUrlStrategy !== LocaleUrlStrategy.ShowAllLocales;
|
|
48
|
+
const localePath = shouldHideLocale ? '' : `/${locale}`;
|
|
49
|
+
|
|
50
|
+
targetUrl = `${localePath}${pathnameWithoutLocale}${search}`;
|
|
51
|
+
}
|
|
38
52
|
|
|
39
53
|
// router.push is not used because reloading the page also clears the client side cache.
|
|
40
|
-
location.href =
|
|
54
|
+
location.href = targetUrl;
|
|
41
55
|
};
|
|
42
56
|
|
|
43
57
|
/**
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
ReactNode,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useEffect
|
|
10
|
+
} from 'react';
|
|
11
|
+
import { useLogger, LogEntry, LogLevel } from './use-logger';
|
|
12
|
+
|
|
13
|
+
const LOG_LEVELS: LogLevel[] = ['info', 'warn', 'error'];
|
|
14
|
+
|
|
15
|
+
interface LoggerContextType {
|
|
16
|
+
logs: LogEntry[];
|
|
17
|
+
isVisible: boolean;
|
|
18
|
+
toggleVisibility: () => void;
|
|
19
|
+
clearLogs: () => void;
|
|
20
|
+
info: (message: string, payload?: any) => string;
|
|
21
|
+
warn: (message: string, payload?: any) => string;
|
|
22
|
+
error: (message: string, payload?: any) => string;
|
|
23
|
+
isDevelopment: boolean;
|
|
24
|
+
hasError: boolean;
|
|
25
|
+
hasWarning: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const LoggerContext = createContext<LoggerContextType | undefined>(undefined);
|
|
29
|
+
|
|
30
|
+
let globalAddLogFunction:
|
|
31
|
+
| ((level: string, message: string, payload?: any) => string)
|
|
32
|
+
| null = null;
|
|
33
|
+
|
|
34
|
+
// temporary queue for logs generated before the logger is initialized
|
|
35
|
+
const pendingLogs: Array<{ level: string; message: string; payload?: any }> =
|
|
36
|
+
[];
|
|
37
|
+
|
|
38
|
+
const createLogFunction =
|
|
39
|
+
(level: LogLevel) => (message: string, payload?: any) => {
|
|
40
|
+
if (
|
|
41
|
+
typeof window !== 'undefined' &&
|
|
42
|
+
process.env.NODE_ENV === 'development'
|
|
43
|
+
) {
|
|
44
|
+
try {
|
|
45
|
+
if (globalAddLogFunction) {
|
|
46
|
+
globalAddLogFunction(level, message, payload);
|
|
47
|
+
} else {
|
|
48
|
+
pendingLogs.push({ level, message, payload });
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
// prevent errors
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return '';
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const stableLogger = LOG_LEVELS.reduce((logger, level) => {
|
|
59
|
+
logger[level] = createLogFunction(level);
|
|
60
|
+
|
|
61
|
+
return logger;
|
|
62
|
+
}, {} as Record<LogLevel, (message: string, payload?: any) => string>);
|
|
63
|
+
|
|
64
|
+
export const LoggerProvider = ({ children }: { children: ReactNode }) => {
|
|
65
|
+
const loggerHook = useLogger();
|
|
66
|
+
|
|
67
|
+
const addLogRef = useRef<
|
|
68
|
+
(level: string, message: string, payload?: any) => string
|
|
69
|
+
>((level, message, payload) => {
|
|
70
|
+
if (LOG_LEVELS.includes(level as LogLevel)) {
|
|
71
|
+
return loggerHook[level as LogLevel](message, payload);
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
globalAddLogFunction = addLogRef.current;
|
|
78
|
+
|
|
79
|
+
if (pendingLogs.length > 0) {
|
|
80
|
+
pendingLogs.forEach((log) => {
|
|
81
|
+
if (globalAddLogFunction) {
|
|
82
|
+
globalAddLogFunction(log.level, log.message, log.payload);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
pendingLogs.length = 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
globalAddLogFunction = null;
|
|
91
|
+
};
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
const contextValue = useMemo(
|
|
95
|
+
() => loggerHook,
|
|
96
|
+
[loggerHook.logs, loggerHook.isVisible, loggerHook.isDevelopment] // eslint-disable-line react-hooks/exhaustive-deps
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<LoggerContext.Provider value={contextValue}>
|
|
101
|
+
{children}
|
|
102
|
+
</LoggerContext.Provider>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const useLoggerContext = () => {
|
|
107
|
+
const context = useContext(LoggerContext);
|
|
108
|
+
if (context === undefined) {
|
|
109
|
+
throw new Error('useLoggerContext must be used within a LoggerProvider');
|
|
110
|
+
}
|
|
111
|
+
return context;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const devLogger = stableLogger;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect, useRef, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
export type LogLevel = 'info' | 'warn' | 'error';
|
|
4
|
+
|
|
5
|
+
export interface LogEntry {
|
|
6
|
+
id: string;
|
|
7
|
+
level: LogLevel;
|
|
8
|
+
message: string;
|
|
9
|
+
timestamp: Date;
|
|
10
|
+
payload?: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const LOG_LEVELS: LogEntry['level'][] = ['info', 'warn', 'error'];
|
|
14
|
+
|
|
15
|
+
export function useLogger() {
|
|
16
|
+
const [logs, setLogs] = useState<LogEntry[]>([]);
|
|
17
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
18
|
+
const logsRef = useRef<LogEntry[]>([]);
|
|
19
|
+
const [isDevelopment, setIsDevelopment] = useState(false);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setIsDevelopment(
|
|
23
|
+
process.env.NODE_ENV === 'development' ||
|
|
24
|
+
window.location.hostname === 'localhost' ||
|
|
25
|
+
window.location.hostname === '127.0.0.1'
|
|
26
|
+
);
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
logsRef.current = logs;
|
|
31
|
+
}, [logs]);
|
|
32
|
+
|
|
33
|
+
const addLog = useCallback(
|
|
34
|
+
(level: LogEntry['level'], message: string, payload?: any) => {
|
|
35
|
+
const newLog: LogEntry = {
|
|
36
|
+
id: Math.random().toString(36).substring(2, 9),
|
|
37
|
+
level,
|
|
38
|
+
message,
|
|
39
|
+
timestamp: new Date(),
|
|
40
|
+
payload
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
setLogs((prevLogs) => [newLog, ...prevLogs]);
|
|
44
|
+
|
|
45
|
+
return newLog.id;
|
|
46
|
+
},
|
|
47
|
+
[]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const clearLogs = useCallback(() => {
|
|
51
|
+
setLogs([]);
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
const toggleVisibility = useCallback(() => {
|
|
55
|
+
setIsVisible((prev) => !prev);
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
const createLogMethod = useCallback(
|
|
59
|
+
(level: LogEntry['level']) => (message: string, payload?: any) =>
|
|
60
|
+
addLog(level, message, payload),
|
|
61
|
+
[addLog]
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const logMethods = useMemo(
|
|
65
|
+
() =>
|
|
66
|
+
LOG_LEVELS.reduce((methods, level) => {
|
|
67
|
+
methods[level] = createLogMethod(level);
|
|
68
|
+
return methods;
|
|
69
|
+
}, {} as Record<LogEntry['level'], (message: string, payload?: any) => string>),
|
|
70
|
+
[createLogMethod]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const hasError = useMemo(
|
|
74
|
+
() => logs.some((log) => log.level === 'error'),
|
|
75
|
+
[logs]
|
|
76
|
+
);
|
|
77
|
+
const hasWarning = useMemo(
|
|
78
|
+
() => logs.some((log) => log.level === 'warn'),
|
|
79
|
+
[logs]
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
logs,
|
|
84
|
+
isVisible,
|
|
85
|
+
toggleVisibility,
|
|
86
|
+
clearLogs,
|
|
87
|
+
...logMethods,
|
|
88
|
+
isDevelopment,
|
|
89
|
+
hasError,
|
|
90
|
+
hasWarning
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useAppSelector } from '../redux/hooks';
|
|
2
|
+
|
|
3
|
+
export const useLoyaltyAvailability = () => {
|
|
4
|
+
const { paymentOptions, unavailablePaymentOptions } = useAppSelector(
|
|
5
|
+
(state) => state.checkout
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
const hasLoyaltyInAvailable = paymentOptions.some(
|
|
9
|
+
(option) =>
|
|
10
|
+
option.payment_type === 'loyalty_money' ||
|
|
11
|
+
option.payment_type === 'loyalty'
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const hasLoyaltyInUnavailable = unavailablePaymentOptions.some(
|
|
15
|
+
(option) =>
|
|
16
|
+
option.payment_type === 'loyalty_money' ||
|
|
17
|
+
option.payment_type === 'loyalty'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return hasLoyaltyInAvailable || hasLoyaltyInUnavailable;
|
|
21
|
+
};
|
|
@@ -21,7 +21,8 @@ export const usePaymentOptions = () => {
|
|
|
21
21
|
credit_payment: 'pz-credit-payment',
|
|
22
22
|
masterpass: 'pz-masterpass',
|
|
23
23
|
saved_card: 'pz-saved-card',
|
|
24
|
-
gpay: 'pz-gpay'
|
|
24
|
+
gpay: 'pz-gpay',
|
|
25
|
+
masterpass_rest: 'pz-masterpass-rest'
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
const isInitialTypeIncluded = (type: string) => initialTypes.has(type);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useParams } from 'next/navigation';
|
|
4
|
+
import settings from 'settings';
|
|
5
|
+
import {
|
|
6
|
+
decodePzValue,
|
|
7
|
+
getPzSegmentsConfig,
|
|
8
|
+
getBuiltInSegments,
|
|
9
|
+
isLegacyMode
|
|
10
|
+
} from '../utils/pz-segments';
|
|
11
|
+
|
|
12
|
+
export function usePzParams(): {
|
|
13
|
+
locale: string;
|
|
14
|
+
currency: string;
|
|
15
|
+
[key: string]: string;
|
|
16
|
+
} {
|
|
17
|
+
const params = useParams() as Record<string, string>;
|
|
18
|
+
|
|
19
|
+
if (isLegacyMode(settings)) {
|
|
20
|
+
return {
|
|
21
|
+
locale:
|
|
22
|
+
params.locale ?? settings.localization.defaultLocaleValue,
|
|
23
|
+
currency:
|
|
24
|
+
params.currency ?? settings.localization.defaultCurrencyCode
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const pzValue = params.pz ?? '';
|
|
29
|
+
const config = getPzSegmentsConfig(settings);
|
|
30
|
+
const parsed = decodePzValue(pzValue, config);
|
|
31
|
+
const builtIn = getBuiltInSegments(parsed, settings);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
...parsed,
|
|
35
|
+
...builtIn
|
|
36
|
+
};
|
|
37
|
+
}
|
package/hooks/use-router.ts
CHANGED
|
@@ -5,6 +5,24 @@ import { urlLocaleMatcherRegex } from '../utils';
|
|
|
5
5
|
import { useLocalization } from './use-localization';
|
|
6
6
|
import { LocaleUrlStrategy } from '../localization';
|
|
7
7
|
|
|
8
|
+
const isNavigableHref = (href: string) => {
|
|
9
|
+
if (!href || typeof href !== 'string') return false;
|
|
10
|
+
|
|
11
|
+
const trimmedHref = href.trim();
|
|
12
|
+
if (!trimmedHref) return false;
|
|
13
|
+
if (trimmedHref === '#') return true;
|
|
14
|
+
if (trimmedHref.startsWith('mailto:') || trimmedHref.startsWith('tel:')) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
new URL(trimmedHref, 'http://localhost');
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
8
26
|
export const useRouter = () => {
|
|
9
27
|
const { locale, locales, localeUrlStrategy, defaultLocaleValue } =
|
|
10
28
|
useLocalization();
|
|
@@ -16,27 +34,43 @@ export const useRouter = () => {
|
|
|
16
34
|
href: string,
|
|
17
35
|
options?
|
|
18
36
|
) => {
|
|
19
|
-
if (href
|
|
20
|
-
return fn(
|
|
37
|
+
if (!isNavigableHref(href)) {
|
|
38
|
+
return fn('#', options);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const trimmedHref = href.trim();
|
|
42
|
+
|
|
43
|
+
if (
|
|
44
|
+
trimmedHref.startsWith('http') ||
|
|
45
|
+
trimmedHref.startsWith('//') ||
|
|
46
|
+
trimmedHref.startsWith('mailto:') ||
|
|
47
|
+
trimmedHref.startsWith('tel:') ||
|
|
48
|
+
trimmedHref.startsWith('#')
|
|
49
|
+
) {
|
|
50
|
+
return fn(trimmedHref, options);
|
|
21
51
|
}
|
|
22
52
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
try {
|
|
54
|
+
const url = new URL(trimmedHref, window.location.origin);
|
|
55
|
+
const pathnameWithoutLocale = url.pathname.replace(
|
|
56
|
+
urlLocaleMatcherRegex,
|
|
57
|
+
''
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
url.pathname = `${
|
|
61
|
+
localeUrlStrategy === LocaleUrlStrategy.Subdomain ||
|
|
62
|
+
(locale === defaultLocale?.value &&
|
|
63
|
+
[LocaleUrlStrategy.HideDefaultLocale].includes(
|
|
64
|
+
localeUrlStrategy as LocaleUrlStrategy
|
|
65
|
+
))
|
|
66
|
+
? ''
|
|
67
|
+
: `/${locale}`
|
|
68
|
+
}${pathnameWithoutLocale}`;
|
|
69
|
+
|
|
70
|
+
return fn(url.href, options);
|
|
71
|
+
} catch {
|
|
72
|
+
return fn('#', options);
|
|
73
|
+
}
|
|
40
74
|
};
|
|
41
75
|
|
|
42
76
|
return {
|
package/instrumentation/index.ts
CHANGED
package/instrumentation/node.ts
CHANGED
|
@@ -1,20 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { Resource } from '@opentelemetry/resources';
|
|
4
|
-
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
|
5
|
-
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
|
|
6
|
-
|
|
7
|
-
const sdk = new NodeSDK({
|
|
8
|
-
resource: new Resource({
|
|
9
|
-
[SemanticResourceAttributes.SERVICE_NAME]: 'pz-next-app'
|
|
10
|
-
}),
|
|
11
|
-
spanProcessor: new SimpleSpanProcessor(
|
|
12
|
-
new OTLPTraceExporter({
|
|
13
|
-
url: `${
|
|
14
|
-
process.env.PZ_DASHBOARD_URL ?? 'http://localhost:3005'
|
|
15
|
-
}/api/traces`
|
|
16
|
-
})
|
|
17
|
-
)
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
sdk.start();
|
|
1
|
+
// OpenTelemetry tracing is handled by Sentry.
|
|
2
|
+
// Custom NodeSDK setup removed due to version incompatibility with Sentry 10's OpenTelemetry v2.x requirements.
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const findBaseDir = require('./utils/find-base-dir');
|
|
3
|
+
|
|
4
|
+
const baseDir = findBaseDir();
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
preset: 'ts-jest',
|
|
8
|
+
testEnvironment: 'node',
|
|
9
|
+
rootDir: path.resolve(__dirname),
|
|
10
|
+
testMatch: ['**/*.test.ts'],
|
|
11
|
+
testPathIgnorePatterns: [],
|
|
12
|
+
roots: [path.resolve(__dirname)],
|
|
13
|
+
transformIgnorePatterns: [],
|
|
14
|
+
moduleNameMapper: {
|
|
15
|
+
'^settings$': path.resolve(baseDir, 'src/settings.js')
|
|
16
|
+
},
|
|
17
|
+
transform: {
|
|
18
|
+
'^.+\\.(tsx?|jsx?|mjs?)$': [
|
|
19
|
+
'ts-jest',
|
|
20
|
+
{
|
|
21
|
+
tsconfig: path.resolve(__dirname, '__tests__/tsconfig.json')
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
};
|