@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/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}`;
|
|
@@ -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
|
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const kebabize = (str: string) => {
|
|
2
|
+
return str.replace(
|
|
3
|
+
/[A-Z]+(?![a-z])|[A-Z]/g,
|
|
4
|
+
($, ofs) => (ofs ? '-' : '') + $.toLowerCase()
|
|
5
|
+
);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const formatCssValue = (value: string | number) => {
|
|
9
|
+
return /^\d+$/.test(value.toString()) ? `${value}px` : value;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
interface NodeProperties {
|
|
13
|
+
style?: Record<string, any>;
|
|
14
|
+
parent_id?: string;
|
|
15
|
+
type?: string;
|
|
16
|
+
tag?: string;
|
|
17
|
+
breakpoints?: any[];
|
|
18
|
+
dataSources?: any[];
|
|
19
|
+
is_slider?: boolean;
|
|
20
|
+
iteratingNode?: string;
|
|
21
|
+
iteratorValue?: string;
|
|
22
|
+
selectedDataSourceId?: string;
|
|
23
|
+
order?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Node {
|
|
27
|
+
key: string;
|
|
28
|
+
properties: NodeProperties;
|
|
29
|
+
label?: string;
|
|
30
|
+
data_type?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const processNode = (node: Node, selector = '', styles: string[] = []) => {
|
|
34
|
+
const { key, properties } = node;
|
|
35
|
+
const { style } = properties;
|
|
36
|
+
|
|
37
|
+
if (key && style) {
|
|
38
|
+
const baseStyles: Record<string, any> = {};
|
|
39
|
+
const breakpointStyles: Record<string, Record<string, any>> = {};
|
|
40
|
+
|
|
41
|
+
Object.entries(style).forEach(([prop, val]) => {
|
|
42
|
+
if (typeof val === 'object') {
|
|
43
|
+
breakpointStyles[prop] = val;
|
|
44
|
+
} else {
|
|
45
|
+
baseStyles[prop] = val;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const baseCssRules = Object.entries(baseStyles)
|
|
50
|
+
.map(([prop, val]) => `${kebabize(prop)}: ${formatCssValue(val)};`)
|
|
51
|
+
.join(' ');
|
|
52
|
+
|
|
53
|
+
if (baseCssRules) {
|
|
54
|
+
styles.push(`${selector} { ${baseCssRules} }`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Object.keys(breakpointStyles)
|
|
58
|
+
.map((bp) => parseInt(bp, 10))
|
|
59
|
+
.sort((a, b) => a - b)
|
|
60
|
+
.forEach((bp) => {
|
|
61
|
+
const bpCssRules = Object.entries(breakpointStyles[bp])
|
|
62
|
+
.map(([prop, val]) => `${kebabize(prop)}: ${formatCssValue(val)};`)
|
|
63
|
+
.join(' ');
|
|
64
|
+
|
|
65
|
+
if (bpCssRules) {
|
|
66
|
+
styles.push(
|
|
67
|
+
`@media only screen and (min-width: ${bp}px) { ${selector} { ${bpCssRules} } }`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return styles;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const componentToCss = (components: Record<string, Node>) => {
|
|
77
|
+
const styles: string[] = [];
|
|
78
|
+
const componentsById: Record<string, Node> = {};
|
|
79
|
+
|
|
80
|
+
Object.values(components).forEach((component) => {
|
|
81
|
+
componentsById[component.key] = component;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const addStylesForComponent = (component: Node) => {
|
|
85
|
+
const selector = `[data-id="${component.key}"]`;
|
|
86
|
+
processNode(component, selector, styles);
|
|
87
|
+
|
|
88
|
+
Object.values(componentsById)
|
|
89
|
+
.filter((child) => child.properties.parent_id === component.key)
|
|
90
|
+
.forEach(addStylesForComponent);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
Object.values(componentsById)
|
|
94
|
+
.filter((component) => !component.properties?.parent_id)
|
|
95
|
+
.forEach(addStylesForComponent);
|
|
96
|
+
|
|
97
|
+
return styles.join(' ');
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const generateWidgetStyles = (widgetSchemas: Record<string, Node>[]) => {
|
|
101
|
+
return widgetSchemas
|
|
102
|
+
.filter((schema) => Object.keys(schema).length > 0)
|
|
103
|
+
.map((data) => componentToCss(data))
|
|
104
|
+
.join('\n');
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type { Node, NodeProperties };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useLocalization } from '../hooks';
|
|
3
|
+
import { Button, Link } from '../components';
|
|
4
|
+
import { ROUTES } from 'routes';
|
|
5
|
+
|
|
6
|
+
export default function PzErrorPage({
|
|
7
|
+
error,
|
|
8
|
+
reset
|
|
9
|
+
}: {
|
|
10
|
+
error: Error & { digest?: string; isServerError?: boolean };
|
|
11
|
+
reset: () => void;
|
|
12
|
+
}) {
|
|
13
|
+
const [isServerError, setIsServerError] = useState(false);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if ('isServerError' in error) {
|
|
17
|
+
setIsServerError(true);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setIsServerError(!!error.digest);
|
|
22
|
+
}, [error]);
|
|
23
|
+
|
|
24
|
+
return isServerError ? (
|
|
25
|
+
<ServerErrorUI />
|
|
26
|
+
) : (
|
|
27
|
+
<ClientErrorUI error={error} reset={reset} />
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ClientErrorUI({
|
|
32
|
+
error,
|
|
33
|
+
reset
|
|
34
|
+
}: {
|
|
35
|
+
error: Error & { digest?: string };
|
|
36
|
+
reset: () => void;
|
|
37
|
+
}) {
|
|
38
|
+
const { t } = useLocalization();
|
|
39
|
+
|
|
40
|
+
const errorMessage = error?.message || 'Unknown error';
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<section className="text-center px-6 py-6 my-14 md:px-0 md:m-14">
|
|
44
|
+
<div className="text-4xl font-bold md:text-6xl text-red-500">500</div>
|
|
45
|
+
<h1 className="text-lg md:text-xl mt-4">
|
|
46
|
+
{t('common.client_error.title')}
|
|
47
|
+
</h1>
|
|
48
|
+
<p className="text-lg md:text-xl mt-2">
|
|
49
|
+
{t('common.client_error.description')}
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<div className="mt-4 mx-auto max-w-lg">
|
|
53
|
+
<p className="text-xs text-gray-600 font-mono bg-gray-100 p-3 rounded overflow-auto text-left">
|
|
54
|
+
<span className="font-semibold">Error:</span> {errorMessage}
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div className="mt-6 flex flex-col gap-4 items-center justify-center">
|
|
59
|
+
<Link href={ROUTES.HOME} className="text-lg underline">
|
|
60
|
+
{t('common.client_error.link_text')}
|
|
61
|
+
</Link>
|
|
62
|
+
<Button onClick={reset} className="text-lg">
|
|
63
|
+
{t('common.try_again')}
|
|
64
|
+
</Button>
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function ServerErrorUI() {
|
|
71
|
+
const { t } = useLocalization();
|
|
72
|
+
|
|
73
|
+
const reloadPage = () => {
|
|
74
|
+
window.location.reload();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<section className="text-center px-6 my-14 md:px-0 md:m-14">
|
|
79
|
+
<div className="text-7xl font-bold md:text-8xl">500</div>
|
|
80
|
+
<h1 className="text-lg md:text-xl"> {t('common.page_500.title')} </h1>
|
|
81
|
+
<p className="text-lg md:text-xl"> {t('common.page_500.description')} </p>
|
|
82
|
+
|
|
83
|
+
<div className="mt-6 flex flex-col gap-4 items-center justify-center">
|
|
84
|
+
<Link href={ROUTES.HOME} className="text-lg underline">
|
|
85
|
+
{t('common.page_500.link_text')}
|
|
86
|
+
</Link>
|
|
87
|
+
<Button onClick={reloadPage} className="text-lg">
|
|
88
|
+
{t('common.try_again')}
|
|
89
|
+
</Button>
|
|
90
|
+
</div>
|
|
91
|
+
</section>
|
|
92
|
+
);
|
|
93
|
+
}
|
package/with-pz-config.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
+
const path = require('path');
|
|
1
2
|
const pzPlugins = require('./plugins');
|
|
2
3
|
const deepMerge = require('./utils/deep-merge');
|
|
3
4
|
|
|
4
5
|
/** @type {import('next').NextConfig} */
|
|
5
6
|
const defaultConfig = {
|
|
6
|
-
experimental: { instrumentationHook: true },
|
|
7
7
|
reactStrictMode: true,
|
|
8
|
-
transpilePackages: ['@akinon/next', ...pzPlugins.map((p) => `@akinon/${p}`)],
|
|
8
|
+
transpilePackages: ['@akinon/next', '@akinon/pz-theme', ...pzPlugins.map((p) => `@akinon/${p}`)],
|
|
9
9
|
skipTrailingSlashRedirect: true,
|
|
10
10
|
poweredByHeader: false,
|
|
11
11
|
cacheMaxMemorySize: 0,
|
|
12
|
+
output: 'standalone',
|
|
13
|
+
compress: false,
|
|
12
14
|
env: {
|
|
13
15
|
NEXT_PUBLIC_SENTRY_DSN: process.env.SENTRY_DSN
|
|
14
16
|
},
|
|
15
17
|
images: {
|
|
16
18
|
remotePatterns: [
|
|
19
|
+
//It has to be like this for security reasons
|
|
17
20
|
{
|
|
18
21
|
protocol: 'https',
|
|
19
22
|
hostname: '**.akinoncloud.com'
|
|
@@ -44,14 +47,21 @@ const defaultConfig = {
|
|
|
44
47
|
value: 'max-age=63072000; includeSubDomains; preload'
|
|
45
48
|
},
|
|
46
49
|
{
|
|
47
|
-
key: '
|
|
48
|
-
value:
|
|
50
|
+
key: 'Content-Security-Policy',
|
|
51
|
+
value:
|
|
52
|
+
"frame-ancestors 'self' https://*.akifast.com akifast.com https://*.akinoncloud.com akinoncloud.com http://localhost:5174 localhost:5174"
|
|
49
53
|
}
|
|
50
54
|
]
|
|
51
55
|
}
|
|
52
56
|
];
|
|
53
57
|
},
|
|
54
58
|
webpack: (config, options) => {
|
|
59
|
+
// Alias 'redux/store' to the app's local redux/store module to prevent
|
|
60
|
+
// collision with the npm 'redux' package's exports field
|
|
61
|
+
config.resolve.alias = {
|
|
62
|
+
...config.resolve.alias,
|
|
63
|
+
'redux/store': path.resolve(process.cwd(), 'src/redux/store')
|
|
64
|
+
};
|
|
55
65
|
config.resolve.fallback = {
|
|
56
66
|
...config.resolve.fallback,
|
|
57
67
|
...pzPlugins.reduce((acc, plugin) => {
|
|
@@ -60,10 +70,14 @@ const defaultConfig = {
|
|
|
60
70
|
}, {}),
|
|
61
71
|
translations: false
|
|
62
72
|
};
|
|
73
|
+
// Ensure webpack can resolve deps from the app's node_modules when
|
|
74
|
+
// compiling transpiled packages (e.g. @akinon/next) whose imports
|
|
75
|
+
// may not be hoisted to the monorepo root.
|
|
76
|
+
const appNodeModules = path.resolve(process.cwd(), 'node_modules');
|
|
77
|
+
if (!config.resolve.modules.includes(appNodeModules)) {
|
|
78
|
+
config.resolve.modules.push(appNodeModules);
|
|
79
|
+
}
|
|
63
80
|
return config;
|
|
64
|
-
},
|
|
65
|
-
sentry: {
|
|
66
|
-
hideSourceMaps: true
|
|
67
81
|
}
|
|
68
82
|
};
|
|
69
83
|
|