@akinon/next 1.41.0-rc.1 → 1.41.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 +2 -17
- package/api/client.ts +9 -18
- package/components/input.tsx +7 -20
- package/components/link.tsx +13 -17
- package/components/plugin-module.tsx +26 -4
- package/components/price.tsx +3 -4
- package/data/client/account.ts +9 -10
- package/data/server/category.ts +2 -2
- package/data/server/list.ts +2 -2
- package/data/server/product.ts +5 -2
- package/data/server/special-page.ts +2 -2
- package/hocs/server/with-segment-defaults.tsx +2 -2
- package/lib/cache.ts +6 -18
- package/middlewares/default.ts +0 -9
- package/middlewares/pretty-url.ts +0 -4
- package/package.json +3 -4
- package/plugins.js +2 -1
- package/redux/middlewares/checkout.ts +1 -1
- package/redux/reducers/checkout.ts +2 -1
- package/redux/reducers/config.ts +0 -2
- package/types/commerce/account.ts +0 -1
- package/types/commerce/address.ts +1 -1
- package/types/commerce/misc.ts +0 -2
- package/types/index.ts +7 -2
- package/utils/app-fetch.ts +1 -1
- package/utils/generate-commerce-search-params.ts +2 -6
- package/utils/index.ts +0 -6
- package/utils/server-translation.ts +1 -5
- package/with-pz-config.js +1 -11
- package/lib/cache-handler.mjs +0 -33
- package/routes/pretty-url.tsx +0 -194
package/CHANGELOG.md
CHANGED
|
@@ -1,25 +1,10 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
-
## 1.41.0
|
|
3
|
+
## 1.41.0
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
## 1.41.0-rc.0
|
|
10
|
-
|
|
11
|
-
### Minor Changes
|
|
12
|
-
|
|
13
|
-
- a4c8d6a: ZERO-2663: Fix the image url for gif and svgs and return them without options
|
|
14
|
-
- c53ea3e: ZERO-2609: Reset additional form fields when selectedFormType is not company
|
|
15
|
-
- c53ef7b: ZERO-2668: The Link component has been updated to improve the logic for handling href values. Previously, if the href was not a string or started with 'http', it would return the href as is. Now, if the href is not provided, it will default to '#' to prevent any potential errors. Additionally, if the href is a string and does not start with 'http', it will be formatted with the locale and pathname, based on the localeUrlStrategy and defaultLocaleValue. This ensures that the correct href is generated based on the localization settings.
|
|
16
|
-
- 1448a96: ZERO-2612: add errors type in CheckoutState
|
|
17
|
-
- 75080fd: ZERO-2630: Add max limit to postcode area
|
|
18
|
-
- 91265bb: ZERO-2551: Improve pretty url and caching performance
|
|
19
|
-
- bbe18b9: ZERO-2575: Fix build error
|
|
20
|
-
- dff0d59: ZERO-2659: add formData support to proxy api requests
|
|
21
|
-
- beb499e: ZERO-2551: Add new tsconfig paths
|
|
22
|
-
- f046f8e: ZERO-2575: update version for react-number-format
|
|
7
|
+
- 8f09473: ZERO-2686: create akifast plugin
|
|
23
8
|
|
|
24
9
|
## 1.40.0
|
|
25
10
|
|
package/api/client.ts
CHANGED
|
@@ -78,13 +78,12 @@ async function proxyRequest(...args) {
|
|
|
78
78
|
fetchOptions.headers['Content-Type'] = options.contentType;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
const isMultipartFormData = req.headers.get('content-type')?.includes('multipart/form-data;');
|
|
82
|
-
|
|
83
81
|
if (req.method !== 'GET') {
|
|
84
|
-
|
|
82
|
+
const formData = new URLSearchParams();
|
|
83
|
+
let body = {};
|
|
85
84
|
|
|
86
85
|
try {
|
|
87
|
-
body =
|
|
86
|
+
body = await req.json();
|
|
88
87
|
} catch (error) {
|
|
89
88
|
logger.error(
|
|
90
89
|
`Client Proxy Request - Error while parsing request body to JSON`,
|
|
@@ -95,21 +94,13 @@ async function proxyRequest(...args) {
|
|
|
95
94
|
);
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Object.keys(body ?? {}).forEach((key) => {
|
|
104
|
-
if (body[key]) {
|
|
105
|
-
formData.append(key, body[key]);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
97
|
+
Object.keys(body ?? {}).forEach((key) => {
|
|
98
|
+
if (body[key]) {
|
|
99
|
+
formData.append(key, body[key]);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
108
102
|
|
|
109
|
-
|
|
110
|
-
? JSON.stringify(body)
|
|
111
|
-
: formData;
|
|
112
|
-
}
|
|
103
|
+
fetchOptions.body = !options.useFormData ? JSON.stringify(body) : formData;
|
|
113
104
|
}
|
|
114
105
|
|
|
115
106
|
let url = `${commerceUrl}/${slug.replace(/,/g, '/')}`;
|
package/components/input.tsx
CHANGED
|
@@ -1,29 +1,17 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import { forwardRef, FocusEvent, useState
|
|
2
|
+
import { forwardRef, FocusEvent, useState } from 'react';
|
|
3
3
|
import { Controller } from 'react-hook-form';
|
|
4
|
-
|
|
5
|
-
import { PatternFormat, PatternFormatProps } from 'react-number-format';
|
|
4
|
+
import NumberFormat, { NumberFormatProps } from 'react-number-format';
|
|
6
5
|
import { InputProps } from '../types';
|
|
7
6
|
import { twMerge } from 'tailwind-merge';
|
|
8
7
|
|
|
9
|
-
const PatternFormatWithRef = forwardRef(
|
|
10
|
-
(props: PatternFormatProps, ref: Ref<HTMLInputElement>) => {
|
|
11
|
-
return <PatternFormat {...props} getInputRef={ref} />;
|
|
12
|
-
}
|
|
13
|
-
);
|
|
14
|
-
PatternFormatWithRef.displayName = 'PatternFormatWithRef';
|
|
15
|
-
|
|
16
8
|
export const Input = forwardRef<
|
|
17
9
|
HTMLInputElement,
|
|
18
10
|
InputProps &
|
|
19
11
|
Pick<
|
|
20
|
-
|
|
21
|
-
'mask' | 'allowEmptyFormatting' | 'onValueChange'
|
|
22
|
-
>
|
|
23
|
-
format?: string;
|
|
24
|
-
defaultValue?: string;
|
|
25
|
-
type?: string;
|
|
26
|
-
}
|
|
12
|
+
NumberFormatProps,
|
|
13
|
+
'format' | 'mask' | 'allowEmptyFormatting' | 'onValueChange'
|
|
14
|
+
>
|
|
27
15
|
>((props, ref) => {
|
|
28
16
|
const [focused, setFocused] = useState(false);
|
|
29
17
|
const [hasValue, setHasValue] = useState(false);
|
|
@@ -49,7 +37,6 @@ export const Input = forwardRef<
|
|
|
49
37
|
),
|
|
50
38
|
props.className
|
|
51
39
|
);
|
|
52
|
-
|
|
53
40
|
const inputProps: any = {
|
|
54
41
|
id,
|
|
55
42
|
ref,
|
|
@@ -92,14 +79,14 @@ export const Input = forwardRef<
|
|
|
92
79
|
<Controller
|
|
93
80
|
name={props.name ?? ''}
|
|
94
81
|
control={props.control}
|
|
82
|
+
defaultValue={false}
|
|
95
83
|
render={({ field }) => (
|
|
96
|
-
<
|
|
84
|
+
<NumberFormat
|
|
97
85
|
format={format}
|
|
98
86
|
mask={mask ?? ''}
|
|
99
87
|
{...rest}
|
|
100
88
|
{...field}
|
|
101
89
|
{...inputProps}
|
|
102
|
-
type={props.type as 'text' | 'password' | 'tel'}
|
|
103
90
|
/>
|
|
104
91
|
)}
|
|
105
92
|
/>
|
package/components/link.tsx
CHANGED
|
@@ -10,32 +10,28 @@ type LinkProps = Omit<
|
|
|
10
10
|
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
11
11
|
keyof NextLinkProps
|
|
12
12
|
> &
|
|
13
|
-
NextLinkProps
|
|
14
|
-
href: string;
|
|
15
|
-
};
|
|
13
|
+
NextLinkProps;
|
|
16
14
|
|
|
17
15
|
export const Link = ({ children, href, ...rest }: LinkProps) => {
|
|
18
16
|
const { locale, defaultLocaleValue, localeUrlStrategy } = useLocalization();
|
|
19
17
|
const formattedHref = useMemo(() => {
|
|
20
|
-
if (
|
|
21
|
-
return
|
|
18
|
+
if (typeof href !== 'string' || href.startsWith('http')) {
|
|
19
|
+
return href;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
|
|
22
|
+
const pathnameWithoutLocale = href.replace(urlLocaleMatcherRegex, '');
|
|
23
|
+
const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
25
|
+
if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
|
|
26
|
+
return hrefWithLocale;
|
|
27
|
+
} else if (
|
|
28
|
+
localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
|
|
29
|
+
locale !== defaultLocaleValue
|
|
30
|
+
) {
|
|
31
|
+
return hrefWithLocale;
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
return href;
|
|
34
|
+
return href || '#';
|
|
39
35
|
}, [href, defaultLocaleValue, locale, localeUrlStrategy]);
|
|
40
36
|
|
|
41
37
|
return (
|
|
@@ -4,6 +4,7 @@ import dynamic from 'next/dynamic';
|
|
|
4
4
|
import plugins from 'plugins';
|
|
5
5
|
import logger from '../utils/log';
|
|
6
6
|
import { useMemo } from 'react';
|
|
7
|
+
import settings from 'settings';
|
|
7
8
|
|
|
8
9
|
enum Plugin {
|
|
9
10
|
BasketGiftPack = 'pz-basket-gift-pack',
|
|
@@ -16,13 +17,15 @@ enum Plugin {
|
|
|
16
17
|
BKMExpress = 'pz-bkm',
|
|
17
18
|
CreditPayment = 'pz-credit-payment',
|
|
18
19
|
Masterpass = 'pz-masterpass',
|
|
19
|
-
B2B = 'pz-b2b'
|
|
20
|
+
B2B = 'pz-b2b',
|
|
21
|
+
Akifast = 'pz-akifast'
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export enum Component {
|
|
23
25
|
BasketGiftPack = 'BasketGiftPack',
|
|
24
26
|
ClickCollect = 'ClickCollect',
|
|
25
27
|
OneClickCheckoutButtons = 'OneClickCheckoutButtons',
|
|
28
|
+
OneClickProviderButton = 'OneClickCheckoutButton',
|
|
26
29
|
PayOnDelivery = 'PayOnDelivery',
|
|
27
30
|
CheckoutGiftPack = 'CheckoutGiftPack',
|
|
28
31
|
GPay = 'GPayOption',
|
|
@@ -36,13 +39,18 @@ export enum Component {
|
|
|
36
39
|
MasterpassOtpModal = 'MasterpassOtpModal',
|
|
37
40
|
MasterpassLinkModal = 'MasterpassLinkModal',
|
|
38
41
|
MyQuotationsB2B = 'AccountMyQuotations',
|
|
39
|
-
BasketB2B = 'BasketB2b'
|
|
42
|
+
BasketB2B = 'BasketB2b',
|
|
43
|
+
AkifastQuickLoginButton = 'QuickLoginButton',
|
|
44
|
+
AkifastCheckoutButton = 'CheckoutButton'
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
const PluginComponents = new Map([
|
|
43
48
|
[Plugin.BasketGiftPack, [Component.BasketGiftPack]],
|
|
44
49
|
[Plugin.ClickCollect, [Component.ClickCollect]],
|
|
45
|
-
[
|
|
50
|
+
[
|
|
51
|
+
Plugin.OneClickCheckout,
|
|
52
|
+
[Component.OneClickCheckoutButtons, Component.OneClickProviderButton]
|
|
53
|
+
],
|
|
46
54
|
[Plugin.PayOnDelivery, [Component.PayOnDelivery]],
|
|
47
55
|
[Plugin.CheckoutGiftPack, [Component.CheckoutGiftPack]],
|
|
48
56
|
[Plugin.GPay, [Component.GPay]],
|
|
@@ -60,7 +68,11 @@ const PluginComponents = new Map([
|
|
|
60
68
|
Component.MasterpassLinkModal
|
|
61
69
|
]
|
|
62
70
|
],
|
|
63
|
-
[Plugin.B2B, [Component.MyQuotationsB2B, Component.BasketB2B]]
|
|
71
|
+
[Plugin.B2B, [Component.MyQuotationsB2B, Component.BasketB2B]],
|
|
72
|
+
[
|
|
73
|
+
Plugin.Akifast,
|
|
74
|
+
[Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
|
|
75
|
+
]
|
|
64
76
|
]);
|
|
65
77
|
|
|
66
78
|
const getPlugin = (component: Component) => {
|
|
@@ -81,6 +93,14 @@ export default function PluginModule({
|
|
|
81
93
|
children?: React.ReactNode;
|
|
82
94
|
}) {
|
|
83
95
|
const plugin = getPlugin(component);
|
|
96
|
+
const pluginSettings = settings.plugins?.[plugin];
|
|
97
|
+
|
|
98
|
+
if (pluginSettings) {
|
|
99
|
+
props = {
|
|
100
|
+
...props,
|
|
101
|
+
settings: pluginSettings
|
|
102
|
+
};
|
|
103
|
+
}
|
|
84
104
|
|
|
85
105
|
const Component = useMemo(
|
|
86
106
|
() =>
|
|
@@ -111,6 +131,8 @@ export default function PluginModule({
|
|
|
111
131
|
promise = import(`${'@akinon/pz-masterpass'}`);
|
|
112
132
|
} else if (plugin === Plugin.B2B) {
|
|
113
133
|
promise = import(`${'@akinon/pz-b2b'}`);
|
|
134
|
+
} else if (plugin === Plugin.Akifast) {
|
|
135
|
+
promise = import(`${'@akinon/pz-akifast'}`);
|
|
114
136
|
}
|
|
115
137
|
} catch (error) {
|
|
116
138
|
logger.error(error);
|
package/components/price.tsx
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
|
-
|
|
3
|
-
import { NumericFormat, NumericFormatProps } from 'react-number-format';
|
|
2
|
+
import NumberFormat, { NumberFormatProps } from 'react-number-format';
|
|
4
3
|
import { getCurrency } from '@akinon/next/utils';
|
|
5
4
|
|
|
6
5
|
import { useLocalization } from '@akinon/next/hooks';
|
|
7
6
|
import { PriceProps } from '../types';
|
|
8
7
|
|
|
9
|
-
export const Price = (props:
|
|
8
|
+
export const Price = (props: NumberFormatProps & PriceProps) => {
|
|
10
9
|
const {
|
|
11
10
|
value,
|
|
12
11
|
currencyCode,
|
|
@@ -40,7 +39,7 @@ export const Price = (props: NumericFormatProps & PriceProps) => {
|
|
|
40
39
|
);
|
|
41
40
|
|
|
42
41
|
return (
|
|
43
|
-
<
|
|
42
|
+
<NumberFormat
|
|
44
43
|
value={useNegative ? `-${useNegativeSpace}${_value}` : _value}
|
|
45
44
|
{...{
|
|
46
45
|
[useCurrencyAfterPrice ? 'suffix' : 'prefix']: currency
|
package/data/client/account.ts
CHANGED
|
@@ -123,16 +123,15 @@ const accountApi = api.injectEndpoints({
|
|
|
123
123
|
query: ({ page, status, limit }) =>
|
|
124
124
|
buildClientRequestUrl(account.getQuotations(page, status, limit))
|
|
125
125
|
}),
|
|
126
|
-
sendContact: builder.mutation<void,
|
|
127
|
-
query: (body) => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
126
|
+
sendContact: builder.mutation<void, ContactFormType>({
|
|
127
|
+
query: (body) => ({
|
|
128
|
+
url: buildClientRequestUrl(account.sendContact, {
|
|
129
|
+
contentType: 'application/json',
|
|
130
|
+
responseType: 'text'
|
|
131
|
+
}),
|
|
132
|
+
method: 'POST',
|
|
133
|
+
body
|
|
134
|
+
})
|
|
136
135
|
}),
|
|
137
136
|
cancelOrder: builder.mutation<void, AccountOrderCancellation>({
|
|
138
137
|
query: ({ id, ...body }) => ({
|
package/data/server/category.ts
CHANGED
|
@@ -8,7 +8,7 @@ import logger from '../../utils/log';
|
|
|
8
8
|
|
|
9
9
|
function getCategoryDataHandler(
|
|
10
10
|
pk: number,
|
|
11
|
-
searchParams?:
|
|
11
|
+
searchParams?: URLSearchParams,
|
|
12
12
|
headers?: Record<string, string>
|
|
13
13
|
) {
|
|
14
14
|
return async function () {
|
|
@@ -78,7 +78,7 @@ export const getCategoryData = ({
|
|
|
78
78
|
headers
|
|
79
79
|
}: {
|
|
80
80
|
pk: number;
|
|
81
|
-
searchParams?:
|
|
81
|
+
searchParams?: URLSearchParams;
|
|
82
82
|
headers?: Record<string, string>;
|
|
83
83
|
}) => {
|
|
84
84
|
return Cache.wrap(
|
package/data/server/list.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { parse } from 'lossless-json';
|
|
|
7
7
|
import logger from '../../utils/log';
|
|
8
8
|
|
|
9
9
|
const getListDataHandler = (
|
|
10
|
-
searchParams:
|
|
10
|
+
searchParams: URLSearchParams,
|
|
11
11
|
headers?: Record<string, string>
|
|
12
12
|
) => {
|
|
13
13
|
return async function () {
|
|
@@ -54,7 +54,7 @@ export const getListData = async ({
|
|
|
54
54
|
searchParams,
|
|
55
55
|
headers
|
|
56
56
|
}: {
|
|
57
|
-
searchParams:
|
|
57
|
+
searchParams: URLSearchParams;
|
|
58
58
|
headers?: Record<string, string>;
|
|
59
59
|
}) => {
|
|
60
60
|
return Cache.wrap(
|
package/data/server/product.ts
CHANGED
|
@@ -9,7 +9,7 @@ import appFetch from '../../utils/app-fetch';
|
|
|
9
9
|
|
|
10
10
|
type GetProduct = {
|
|
11
11
|
pk: number;
|
|
12
|
-
searchParams?:
|
|
12
|
+
searchParams?: URLSearchParams;
|
|
13
13
|
groupProduct?: boolean;
|
|
14
14
|
};
|
|
15
15
|
|
|
@@ -74,7 +74,10 @@ export const getProductData = async ({
|
|
|
74
74
|
groupProduct
|
|
75
75
|
}: GetProduct) => {
|
|
76
76
|
return Cache.wrap(
|
|
77
|
-
CacheKey[groupProduct ? 'GroupProduct' : 'Product'](
|
|
77
|
+
CacheKey[groupProduct ? 'GroupProduct' : 'Product'](
|
|
78
|
+
pk,
|
|
79
|
+
searchParams ?? new URLSearchParams()
|
|
80
|
+
),
|
|
78
81
|
getProductDataHandler({ pk, searchParams, groupProduct }),
|
|
79
82
|
{
|
|
80
83
|
expire: 300
|
|
@@ -7,7 +7,7 @@ import header from '../../redux/reducers/header';
|
|
|
7
7
|
|
|
8
8
|
const getSpecialPageDataHandler = (
|
|
9
9
|
pk: number,
|
|
10
|
-
searchParams:
|
|
10
|
+
searchParams: URLSearchParams,
|
|
11
11
|
headers?: Record<string, string>
|
|
12
12
|
) => {
|
|
13
13
|
return async function () {
|
|
@@ -34,7 +34,7 @@ export const getSpecialPageData = async ({
|
|
|
34
34
|
headers
|
|
35
35
|
}: {
|
|
36
36
|
pk: number;
|
|
37
|
-
searchParams:
|
|
37
|
+
searchParams: URLSearchParams;
|
|
38
38
|
headers?: Record<string, string>;
|
|
39
39
|
}) => {
|
|
40
40
|
return Cache.wrap(
|
|
@@ -2,7 +2,7 @@ import settings from 'settings';
|
|
|
2
2
|
import { LayoutProps, PageProps, RootLayoutProps } from '../../types';
|
|
3
3
|
import { redirect } from 'next/navigation';
|
|
4
4
|
import { ServerVariables } from '../../utils/server-variables';
|
|
5
|
-
import {
|
|
5
|
+
import { getTranslations } from '../../utils/server-translation';
|
|
6
6
|
import { ROUTES } from 'routes';
|
|
7
7
|
import logger from '../../utils/log';
|
|
8
8
|
|
|
@@ -50,7 +50,7 @@ const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
|
|
|
50
50
|
return redirect(ROUTES.HOME);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const translations = await
|
|
53
|
+
const translations = await getTranslations(params.locale);
|
|
54
54
|
componentProps.translations = translations;
|
|
55
55
|
|
|
56
56
|
const locale = settings.localization.locales.find(
|
package/lib/cache.ts
CHANGED
|
@@ -20,16 +20,13 @@ const hashCacheKey = (object?: Record<string, string>) => {
|
|
|
20
20
|
return `_${encodeURIComponent(cacheKey)}`;
|
|
21
21
|
};
|
|
22
22
|
export const CacheKey = {
|
|
23
|
-
List: (
|
|
24
|
-
searchParams: { [key: string]: string | string[] | undefined },
|
|
25
|
-
headers?: Record<string, string>
|
|
26
|
-
) =>
|
|
23
|
+
List: (searchParams: URLSearchParams, headers?: Record<string, string>) =>
|
|
27
24
|
`list_${encodeURIComponent(JSON.stringify(searchParams))}${hashCacheKey(
|
|
28
25
|
headers
|
|
29
26
|
)}`,
|
|
30
27
|
Category: (
|
|
31
28
|
pk: number,
|
|
32
|
-
searchParams?:
|
|
29
|
+
searchParams?: URLSearchParams,
|
|
33
30
|
headers?: Record<string, string>
|
|
34
31
|
) =>
|
|
35
32
|
`category_${pk}_${encodeURIComponent(
|
|
@@ -38,20 +35,15 @@ export const CacheKey = {
|
|
|
38
35
|
CategorySlug: (slug: string) => `category_${slug}`,
|
|
39
36
|
SpecialPage: (
|
|
40
37
|
pk: number,
|
|
41
|
-
searchParams:
|
|
38
|
+
searchParams: URLSearchParams,
|
|
42
39
|
headers?: Record<string, string>
|
|
43
40
|
) =>
|
|
44
41
|
`special_page_${pk}_${encodeURIComponent(
|
|
45
42
|
JSON.stringify(searchParams)
|
|
46
43
|
)}${hashCacheKey(headers)}`,
|
|
47
|
-
Product: (
|
|
48
|
-
pk
|
|
49
|
-
|
|
50
|
-
) => `product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
|
|
51
|
-
GroupProduct: (
|
|
52
|
-
pk: number,
|
|
53
|
-
searchParams: { [key: string]: string | string[] | undefined }
|
|
54
|
-
) =>
|
|
44
|
+
Product: (pk: number, searchParams: URLSearchParams) =>
|
|
45
|
+
`product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
|
|
46
|
+
GroupProduct: (pk: number, searchParams: URLSearchParams) =>
|
|
55
47
|
`group_product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
|
|
56
48
|
FlatPage: (pk: number) => `flat_page_${pk}`,
|
|
57
49
|
LandingPage: (pk: number) => `landing_page_${pk}`,
|
|
@@ -166,10 +158,6 @@ export class Cache {
|
|
|
166
158
|
handler: () => Promise<T>,
|
|
167
159
|
options?: CacheOptions
|
|
168
160
|
): Promise<T> {
|
|
169
|
-
if (Settings.usePrettyUrlRoute) {
|
|
170
|
-
return await handler();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
161
|
const requiredVariables = [
|
|
174
162
|
process.env.CACHE_HOST,
|
|
175
163
|
process.env.CACHE_PORT,
|
package/middlewares/default.ts
CHANGED
|
@@ -203,15 +203,6 @@ const withPzDefault =
|
|
|
203
203
|
);
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
if (
|
|
207
|
-
Settings.usePrettyUrlRoute &&
|
|
208
|
-
url.searchParams.toString().length > 0
|
|
209
|
-
) {
|
|
210
|
-
url.pathname =
|
|
211
|
-
url.pathname +
|
|
212
|
-
`searchparams|${url.searchParams.toString()}`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
206
|
Settings.rewrites.forEach((rewrite) => {
|
|
216
207
|
url.pathname = url.pathname.replace(
|
|
217
208
|
rewrite.source,
|
|
@@ -56,10 +56,6 @@ const resolvePrettyUrl = async (pathname: string, ip: string | null) => {
|
|
|
56
56
|
const withPrettyUrl =
|
|
57
57
|
(middleware: NextMiddleware) =>
|
|
58
58
|
async (req: PzNextRequest, event: NextFetchEvent) => {
|
|
59
|
-
if (Settings.usePrettyUrlRoute) {
|
|
60
|
-
return middleware(req, event);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
59
|
const url = req.nextUrl.clone();
|
|
64
60
|
const matchedLanguagePrefix = url.pathname.match(
|
|
65
61
|
urlLocaleMatcherRegex
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/next",
|
|
3
3
|
"description": "Core package for Project Zero Next",
|
|
4
|
-
"version": "1.41.0
|
|
4
|
+
"version": "1.41.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -20,12 +20,11 @@
|
|
|
20
20
|
"@opentelemetry/sdk-trace-node": "1.19.0",
|
|
21
21
|
"@opentelemetry/semantic-conventions": "1.19.0",
|
|
22
22
|
"@reduxjs/toolkit": "1.9.7",
|
|
23
|
-
"@neshca/cache-handler": "1.0.7",
|
|
24
23
|
"cross-spawn": "7.0.3",
|
|
25
24
|
"generic-pool": "3.9.0",
|
|
26
25
|
"react-redux": "8.1.3",
|
|
27
26
|
"react-string-replace": "1.1.1",
|
|
28
|
-
"redis": "4.
|
|
27
|
+
"redis": "4.5.1",
|
|
29
28
|
"semver": "7.5.4",
|
|
30
29
|
"set-cookie-parser": "2.6.0"
|
|
31
30
|
},
|
|
@@ -35,7 +34,7 @@
|
|
|
35
34
|
"@typescript-eslint/eslint-plugin": "6.7.4",
|
|
36
35
|
"@typescript-eslint/parser": "6.7.4",
|
|
37
36
|
"eslint": "^8.14.0",
|
|
38
|
-
"@akinon/eslint-plugin-projectzero": "1.41.0
|
|
37
|
+
"@akinon/eslint-plugin-projectzero": "1.41.0",
|
|
39
38
|
"eslint-config-prettier": "8.5.0"
|
|
40
39
|
}
|
|
41
40
|
}
|
package/plugins.js
CHANGED
package/redux/reducers/config.ts
CHANGED
package/types/commerce/misc.ts
CHANGED
package/types/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ declare global {
|
|
|
8
8
|
// we did it like this because declare types needs to be identical, if not it will fail
|
|
9
9
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
10
10
|
dataLayer?: Object[];
|
|
11
|
+
|
|
11
12
|
[key: string]: any;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -70,7 +71,6 @@ export interface Currency {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
export interface Settings {
|
|
73
|
-
usePrettyUrlRoute?: boolean;
|
|
74
74
|
commerceUrl: string;
|
|
75
75
|
redis: {
|
|
76
76
|
defaultExpirationTime: number;
|
|
@@ -183,6 +183,7 @@ export interface Settings {
|
|
|
183
183
|
masterpassJsUrl?: string;
|
|
184
184
|
};
|
|
185
185
|
customNotFoundEnabled: boolean;
|
|
186
|
+
plugins?: Record<string, Record<string, any>>;
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
export interface CacheOptions {
|
|
@@ -215,7 +216,7 @@ export type Translations = { [key: string]: object };
|
|
|
215
216
|
|
|
216
217
|
export interface PageProps<T = any> {
|
|
217
218
|
params: T;
|
|
218
|
-
searchParams:
|
|
219
|
+
searchParams: URLSearchParams;
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
export interface LayoutProps<T = any> extends PageProps<T> {
|
|
@@ -278,3 +279,7 @@ export interface AccordionProps {
|
|
|
278
279
|
titleClassName?: string;
|
|
279
280
|
dataTestId?: string;
|
|
280
281
|
}
|
|
282
|
+
|
|
283
|
+
export interface PluginModuleComponentProps {
|
|
284
|
+
settings?: Record<string, any>;
|
|
285
|
+
}
|
package/utils/app-fetch.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import logger from './log';
|
|
2
2
|
|
|
3
|
-
export const generateCommerceSearchParams = (searchParams?: {
|
|
4
|
-
[key: string]: string | string[] | undefined;
|
|
5
|
-
}) => {
|
|
3
|
+
export const generateCommerceSearchParams = (searchParams?: URLSearchParams) => {
|
|
6
4
|
if (!searchParams) {
|
|
7
5
|
return null;
|
|
8
6
|
}
|
|
9
7
|
|
|
10
|
-
const urlSerchParams = new URLSearchParams(
|
|
11
|
-
searchParams as Record<string, string>
|
|
12
|
-
);
|
|
8
|
+
const urlSerchParams = new URLSearchParams(searchParams);
|
|
13
9
|
|
|
14
10
|
Object.entries(searchParams).forEach(([key, value]) => {
|
|
15
11
|
if (typeof value === 'object') {
|
package/utils/index.ts
CHANGED
|
@@ -102,12 +102,6 @@ export function buildCDNUrl(url: string, config?: CDNOptions) {
|
|
|
102
102
|
''
|
|
103
103
|
);
|
|
104
104
|
|
|
105
|
-
const noOptionFileExtension = url?.split('.').pop()?.toLowerCase() ?? '';
|
|
106
|
-
|
|
107
|
-
if (noOptionFileExtension === 'svg' || noOptionFileExtension === 'gif') {
|
|
108
|
-
return url;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
105
|
let options = '';
|
|
112
106
|
|
|
113
107
|
if (config) {
|
|
@@ -5,10 +5,10 @@ import { getTranslateFn } from '../utils';
|
|
|
5
5
|
import { Translations } from '../types';
|
|
6
6
|
import settings from 'settings';
|
|
7
7
|
import logger from './log';
|
|
8
|
-
import { unstable_cache } from 'next/cache';
|
|
9
8
|
|
|
10
9
|
export const translations: Translations = {};
|
|
11
10
|
|
|
11
|
+
// TODO: Read translations from cache
|
|
12
12
|
export async function getTranslations(locale_: string) {
|
|
13
13
|
try {
|
|
14
14
|
const locale = settings.localization.locales.find(
|
|
@@ -52,10 +52,6 @@ export async function getTranslations(locale_: string) {
|
|
|
52
52
|
return translations;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export const getCachedTranslations = unstable_cache(async (locale: string) =>
|
|
56
|
-
getTranslations(locale)
|
|
57
|
-
);
|
|
58
|
-
|
|
59
55
|
export function t(path: string) {
|
|
60
56
|
return getTranslateFn(path, translations);
|
|
61
57
|
}
|
package/with-pz-config.js
CHANGED
|
@@ -8,7 +8,6 @@ const defaultConfig = {
|
|
|
8
8
|
transpilePackages: ['@akinon/next', ...pzPlugins.map((p) => `@akinon/${p}`)],
|
|
9
9
|
skipTrailingSlashRedirect: true,
|
|
10
10
|
poweredByHeader: false,
|
|
11
|
-
cacheMaxMemorySize: 0,
|
|
12
11
|
env: {
|
|
13
12
|
NEXT_PUBLIC_SENTRY_DSN: process.env.SENTRY_DSN
|
|
14
13
|
},
|
|
@@ -66,12 +65,7 @@ const defaultConfig = {
|
|
|
66
65
|
}
|
|
67
66
|
};
|
|
68
67
|
|
|
69
|
-
const withPzConfig = (
|
|
70
|
-
myNextConfig = {},
|
|
71
|
-
options = {
|
|
72
|
-
useCacheHandler: false
|
|
73
|
-
}
|
|
74
|
-
) => {
|
|
68
|
+
const withPzConfig = (myNextConfig = {}) => {
|
|
75
69
|
let extendedConfig = deepMerge({}, defaultConfig, myNextConfig);
|
|
76
70
|
|
|
77
71
|
const originalPzHeadersFunction = defaultConfig.headers;
|
|
@@ -97,10 +91,6 @@ const withPzConfig = (
|
|
|
97
91
|
return [...pzRewrites, ...myRewrites];
|
|
98
92
|
};
|
|
99
93
|
|
|
100
|
-
if (options.useCacheHandler) {
|
|
101
|
-
extendedConfig.cacheHandler = require.resolve('./lib/cache-handler.mjs');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
94
|
return extendedConfig;
|
|
105
95
|
};
|
|
106
96
|
|
package/lib/cache-handler.mjs
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { CacheHandler } from '@neshca/cache-handler';
|
|
2
|
-
import createLruHandler from '@neshca/cache-handler/local-lru';
|
|
3
|
-
import createRedisHandler from '@neshca/cache-handler/redis-stack';
|
|
4
|
-
import { createClient } from 'redis';
|
|
5
|
-
|
|
6
|
-
CacheHandler.onCreation(async () => {
|
|
7
|
-
const redisUrl = `redis://${process.env.CACHE_HOST}:${
|
|
8
|
-
process.env.CACHE_PORT
|
|
9
|
-
}/${process.env.CACHE_BUCKET ?? '0'}`;
|
|
10
|
-
|
|
11
|
-
const client = createClient({
|
|
12
|
-
url: redisUrl
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
client.on('error', (error) => {
|
|
16
|
-
console.error('Redis client error', { redisUrl, error });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
await client.connect();
|
|
20
|
-
|
|
21
|
-
const redisHandler = await createRedisHandler({
|
|
22
|
-
client,
|
|
23
|
-
timeoutMs: 5000
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const localHandler = createLruHandler();
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
handlers: [redisHandler, localHandler]
|
|
30
|
-
};
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export default CacheHandler;
|
package/routes/pretty-url.tsx
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { URLS } from '@akinon/next/data/urls';
|
|
2
|
-
import { Metadata, PageProps } from '@akinon/next/types';
|
|
3
|
-
import logger from '@akinon/next/utils/log';
|
|
4
|
-
import { notFound } from 'next/navigation';
|
|
5
|
-
|
|
6
|
-
type PrettyUrlResult = {
|
|
7
|
-
matched: boolean;
|
|
8
|
-
path?: string;
|
|
9
|
-
pk?: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const resolvePrettyUrlHandler =
|
|
13
|
-
(pathname: string, ip: string | null) => async () => {
|
|
14
|
-
let results = [] as { old_path: string }[];
|
|
15
|
-
let prettyUrlResult: PrettyUrlResult = {
|
|
16
|
-
matched: false
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const requestUrl = URLS.misc.prettyUrls(`/${pathname}/`);
|
|
21
|
-
|
|
22
|
-
logger.debug(`Resolving pretty url`, { pathname, requestUrl, ip });
|
|
23
|
-
|
|
24
|
-
const start = Date.now();
|
|
25
|
-
const apiResponse = await fetch(requestUrl, {
|
|
26
|
-
next: {
|
|
27
|
-
revalidate: 0
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
const data = await apiResponse.json();
|
|
31
|
-
({ results } = data);
|
|
32
|
-
const end = Date.now();
|
|
33
|
-
console.warn('Pretty url response time', end - start, requestUrl);
|
|
34
|
-
|
|
35
|
-
const matched = results.length > 0;
|
|
36
|
-
const [{ old_path: path } = { old_path: '' }] = results;
|
|
37
|
-
let pk;
|
|
38
|
-
|
|
39
|
-
if (matched) {
|
|
40
|
-
const pkRegex = /\/(\d+)\/$/;
|
|
41
|
-
const match = path.match(pkRegex);
|
|
42
|
-
pk = match ? parseInt(match[1]) : undefined;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
prettyUrlResult = {
|
|
46
|
-
matched,
|
|
47
|
-
path,
|
|
48
|
-
pk
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
logger.trace('Pretty url result', { prettyUrlResult, ip });
|
|
52
|
-
} catch (error) {
|
|
53
|
-
logger.error('Error resolving pretty url', { error, pathname, ip });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return prettyUrlResult;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export async function generateMetadata({ params }: PageProps) {
|
|
60
|
-
let result: Metadata = {};
|
|
61
|
-
const { prettyurl } = params;
|
|
62
|
-
const pageSlug = prettyurl
|
|
63
|
-
.filter((x) => !x.startsWith('searchparams'))
|
|
64
|
-
.join('/');
|
|
65
|
-
|
|
66
|
-
const searchParams = Object.fromEntries(
|
|
67
|
-
new URLSearchParams(
|
|
68
|
-
decodeURIComponent(
|
|
69
|
-
prettyurl
|
|
70
|
-
.find((x) => x.startsWith('searchparams'))
|
|
71
|
-
?.replace('searchparams%7C', '')
|
|
72
|
-
) ?? {}
|
|
73
|
-
).entries()
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
const prettyUrlResult = await resolvePrettyUrlHandler(pageSlug, null)();
|
|
77
|
-
|
|
78
|
-
if (!prettyUrlResult.matched) {
|
|
79
|
-
return notFound();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const commonProps = {
|
|
83
|
-
params: {
|
|
84
|
-
...params,
|
|
85
|
-
pk: prettyUrlResult.pk
|
|
86
|
-
},
|
|
87
|
-
searchParams
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
if (prettyUrlResult.path.startsWith('/product/')) {
|
|
92
|
-
await import('@product/[pk]/page').then(async (module) => {
|
|
93
|
-
result = await module['generateMetadata']?.(commonProps);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (prettyUrlResult.path.startsWith('/group-product/')) {
|
|
98
|
-
await import('@group-product/[pk]/page').then(async (module) => {
|
|
99
|
-
result = await module['generateMetadata']?.(commonProps);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (prettyUrlResult.path.startsWith('/category/')) {
|
|
104
|
-
await import('@category/[pk]/page').then(async (module) => {
|
|
105
|
-
result = await module['generateMetadata']?.(commonProps);
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (prettyUrlResult.path.startsWith('/special-page/')) {
|
|
110
|
-
await import('@special-page/[pk]/page').then(async (module) => {
|
|
111
|
-
result = await module['generateMetadata']?.(commonProps);
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (prettyUrlResult.path.startsWith('/flat-page/')) {
|
|
116
|
-
await import('@flat-page/[pk]/page').then(async (module) => {
|
|
117
|
-
result = await module['generateMetadata']?.(commonProps);
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
// eslint-disable-next-line no-empty
|
|
121
|
-
} catch (error) {}
|
|
122
|
-
|
|
123
|
-
return result;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export const dynamic = 'force-static';
|
|
127
|
-
export const revalidate = 300;
|
|
128
|
-
|
|
129
|
-
export default async function Page({ params }) {
|
|
130
|
-
const { prettyurl } = params;
|
|
131
|
-
const pageSlug = prettyurl
|
|
132
|
-
.filter((x) => !x.startsWith('searchparams'))
|
|
133
|
-
.join('/');
|
|
134
|
-
|
|
135
|
-
const urlSearchParams = new URLSearchParams(
|
|
136
|
-
decodeURIComponent(
|
|
137
|
-
prettyurl
|
|
138
|
-
.find((x) => x.startsWith('searchparams'))
|
|
139
|
-
?.replace('searchparams%7C', '')
|
|
140
|
-
) ?? {}
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const searchParams = {};
|
|
144
|
-
|
|
145
|
-
for (const [key, value] of urlSearchParams.entries() as unknown as Array<
|
|
146
|
-
[string, string]
|
|
147
|
-
>) {
|
|
148
|
-
if (!searchParams[key]) {
|
|
149
|
-
searchParams[key] = [];
|
|
150
|
-
}
|
|
151
|
-
searchParams[key].push(value);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const result = await resolvePrettyUrlHandler(pageSlug, null)();
|
|
155
|
-
|
|
156
|
-
if (!result.matched) {
|
|
157
|
-
return notFound();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const commonProps = {
|
|
161
|
-
params: {
|
|
162
|
-
...params,
|
|
163
|
-
pk: result.pk
|
|
164
|
-
},
|
|
165
|
-
searchParams
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
if (result.path.startsWith('/category/')) {
|
|
169
|
-
const CategoryPage = (await import('@category/[pk]/page')).default;
|
|
170
|
-
return <CategoryPage {...commonProps} />;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (result.path.startsWith('/product/')) {
|
|
174
|
-
const ProductPage = (await import('@product/[pk]/page')).default;
|
|
175
|
-
return <ProductPage {...commonProps} />;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (result.path.startsWith('/group-product/')) {
|
|
179
|
-
const GroupProduct = (await import('@group-product/[pk]/page')).default;
|
|
180
|
-
return <GroupProduct {...commonProps} />;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (result.path.startsWith('/special-page/')) {
|
|
184
|
-
const SpecialPage = (await import('@special-page/[pk]/page')).default;
|
|
185
|
-
return <SpecialPage {...commonProps} />;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (result.path.startsWith('/flat-page/')) {
|
|
189
|
-
const FlatPage = (await import('@flat-page/[pk]/page')).default;
|
|
190
|
-
return <FlatPage {...commonProps} />;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return null;
|
|
194
|
-
}
|