@akinon/next 1.94.0 → 1.95.0-rc.54
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 +1260 -49
- package/__tests__/next-config.test.ts +1 -10
- package/__tests__/redirect.test.ts +319 -0
- package/api/image-proxy.ts +75 -0
- package/api/similar-product-list.ts +84 -0
- package/api/similar-products.ts +120 -0
- package/components/accordion.tsx +20 -5
- package/components/file-input.tsx +65 -3
- package/components/input.tsx +2 -0
- package/components/link.tsx +16 -12
- package/components/modal.tsx +32 -16
- package/components/plugin-module.tsx +30 -3
- package/data/client/checkout.ts +5 -4
- package/data/server/basket.ts +72 -0
- package/data/server/category.ts +48 -28
- package/data/server/flatpage.ts +16 -12
- package/data/server/landingpage.ts +16 -12
- package/data/server/list.ts +23 -13
- package/data/server/product.ts +66 -39
- package/data/server/special-page.ts +16 -12
- package/data/urls.ts +5 -1
- package/hocs/server/with-segment-defaults.tsx +5 -2
- package/hooks/use-localization.ts +2 -3
- package/jest.config.js +7 -1
- package/lib/cache.ts +2 -0
- package/middlewares/checkout-provider.ts +1 -1
- package/middlewares/complete-gpay.ts +2 -1
- package/middlewares/complete-masterpass.ts +2 -1
- package/middlewares/default.ts +50 -13
- package/middlewares/locale.ts +9 -1
- package/middlewares/redirection-payment.ts +2 -1
- package/middlewares/saved-card-redirection.ts +2 -1
- package/middlewares/three-d-redirection.ts +2 -1
- package/middlewares/url-redirection.ts +9 -15
- package/package.json +3 -3
- package/plugins.d.ts +8 -0
- package/plugins.js +3 -1
- package/redux/middlewares/checkout.ts +5 -1
- package/sentry/index.ts +54 -17
- package/types/commerce/order.ts +1 -0
- package/types/index.ts +42 -1
- package/utils/app-fetch.ts +7 -2
- package/utils/index.ts +34 -10
- package/utils/redirect-ignore.ts +35 -0
- package/utils/redirect.ts +31 -6
- package/with-pz-config.js +1 -5
package/types/index.ts
CHANGED
|
@@ -83,6 +83,12 @@ export interface Settings {
|
|
|
83
83
|
};
|
|
84
84
|
usePrettyUrlRoute?: boolean;
|
|
85
85
|
commerceUrl: string;
|
|
86
|
+
/**
|
|
87
|
+
* This option allows you to track Sentry events on the client side, in addition to server and edge environments.
|
|
88
|
+
*
|
|
89
|
+
* It overrides process.env.NEXT_PUBLIC_SENTRY_DSN and process.env.SENTRY_DSN.
|
|
90
|
+
*/
|
|
91
|
+
sentryDsn?: string;
|
|
86
92
|
redis: {
|
|
87
93
|
defaultExpirationTime: number;
|
|
88
94
|
};
|
|
@@ -218,6 +224,14 @@ export interface CacheOptions {
|
|
|
218
224
|
useProxy?: boolean;
|
|
219
225
|
}
|
|
220
226
|
|
|
227
|
+
export interface SetCookieOptions {
|
|
228
|
+
expires?: number; // days
|
|
229
|
+
path?: string;
|
|
230
|
+
domain?: string;
|
|
231
|
+
secure?: boolean;
|
|
232
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
233
|
+
}
|
|
234
|
+
|
|
221
235
|
export interface ClientRequestOptions {
|
|
222
236
|
useTrailingSlash?: boolean;
|
|
223
237
|
useFormData?: boolean;
|
|
@@ -283,7 +297,13 @@ export interface ButtonProps
|
|
|
283
297
|
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
284
298
|
}
|
|
285
299
|
|
|
286
|
-
export
|
|
300
|
+
export interface FileInputProps extends React.HTMLProps<HTMLInputElement> {
|
|
301
|
+
fileClassName?: string;
|
|
302
|
+
fileNameWrapperClassName?: string;
|
|
303
|
+
fileInputClassName?: string;
|
|
304
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
305
|
+
buttonClassName?: string;
|
|
306
|
+
}
|
|
287
307
|
|
|
288
308
|
export interface PriceProps {
|
|
289
309
|
currencyCode?: string;
|
|
@@ -304,15 +324,19 @@ export interface InputProps extends React.HTMLProps<HTMLInputElement> {
|
|
|
304
324
|
|
|
305
325
|
export interface AccordionProps {
|
|
306
326
|
isCollapse?: boolean;
|
|
327
|
+
collapseClassName?: string;
|
|
307
328
|
title?: string;
|
|
308
329
|
subTitle?: string;
|
|
309
330
|
icons?: string[];
|
|
310
331
|
iconSize?: number;
|
|
311
332
|
iconColor?: string;
|
|
312
333
|
children?: ReactNode;
|
|
334
|
+
headerClassName?: string;
|
|
313
335
|
className?: string;
|
|
314
336
|
titleClassName?: string;
|
|
337
|
+
subTitleClassName?: string;
|
|
315
338
|
dataTestId?: string;
|
|
339
|
+
contentClassName?: string;
|
|
316
340
|
}
|
|
317
341
|
|
|
318
342
|
export interface PluginModuleComponentProps {
|
|
@@ -337,3 +361,20 @@ export interface PaginationProps {
|
|
|
337
361
|
direction?: 'next' | 'prev';
|
|
338
362
|
isLoading?: boolean;
|
|
339
363
|
}
|
|
364
|
+
|
|
365
|
+
export interface ModalProps {
|
|
366
|
+
portalId: string;
|
|
367
|
+
children?: React.ReactNode;
|
|
368
|
+
open?: boolean;
|
|
369
|
+
setOpen?: (open: boolean) => void;
|
|
370
|
+
title?: React.ReactNode;
|
|
371
|
+
showCloseButton?: React.ReactNode;
|
|
372
|
+
className?: string;
|
|
373
|
+
overlayClassName?: string;
|
|
374
|
+
headerWrapperClassName?: string;
|
|
375
|
+
titleClassName?: string;
|
|
376
|
+
closeButtonClassName?: string;
|
|
377
|
+
iconName?: string;
|
|
378
|
+
iconSize?: number;
|
|
379
|
+
iconClassName?: string;
|
|
380
|
+
}
|
package/utils/app-fetch.ts
CHANGED
|
@@ -43,12 +43,12 @@ const appFetch = async <T>({
|
|
|
43
43
|
const requestURL = `${decodeURIComponent(commerceUrl)}${url}`;
|
|
44
44
|
|
|
45
45
|
init.headers = {
|
|
46
|
+
cookie: nextCookies.toString(),
|
|
46
47
|
...(init.headers ?? {}),
|
|
47
48
|
...(ServerVariables.globalHeaders ?? {}),
|
|
48
49
|
'Accept-Language': currentLocale.apiValue,
|
|
49
50
|
'x-currency': currency,
|
|
50
|
-
'x-forwarded-for': ip
|
|
51
|
-
cookie: nextCookies.toString()
|
|
51
|
+
'x-forwarded-for': ip
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
init.next = {
|
|
@@ -60,6 +60,11 @@ const appFetch = async <T>({
|
|
|
60
60
|
status = req.status;
|
|
61
61
|
logger.debug(`FETCH END ${url}`, { status: req.status, ip });
|
|
62
62
|
|
|
63
|
+
if (!req.ok) {
|
|
64
|
+
const errorMessage = `HTTP ${req.status}: ${req.statusText}`;
|
|
65
|
+
throw new Error(errorMessage);
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
if (responseType === FetchResponseType.JSON) {
|
|
64
69
|
response = (await req.json()) as T;
|
|
65
70
|
} else {
|
package/utils/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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';
|
|
@@ -20,14 +21,40 @@ export function getCookie(name: string) {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
export function setCookie(
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
export function setCookie(
|
|
25
|
+
name: string,
|
|
26
|
+
value: string,
|
|
27
|
+
options: SetCookieOptions = {}
|
|
28
|
+
) {
|
|
29
|
+
const cookieParts = [`${name}=${value}`];
|
|
30
|
+
|
|
31
|
+
if (options.expires) {
|
|
32
|
+
const date = new Date();
|
|
33
|
+
date.setTime(date.getTime() + options.expires * 24 * 60 * 60 * 1000);
|
|
34
|
+
cookieParts.push(`expires=${date.toUTCString()}`);
|
|
35
|
+
}
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
cookieParts.push(`path=${options.path ?? '/'}`);
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
if (options.secure) {
|
|
40
|
+
cookieParts.push('secure');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (options.sameSite) {
|
|
44
|
+
cookieParts.push(`sameSite=${options.sameSite}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const domain =
|
|
48
|
+
options.domain ??
|
|
49
|
+
(settings.localization.localeUrlStrategy === LocaleUrlStrategy.Subdomain
|
|
50
|
+
? getRootHostname(document.location.href)
|
|
51
|
+
: null);
|
|
52
|
+
|
|
53
|
+
if (domain) {
|
|
54
|
+
cookieParts.push(`domain=${domain}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
document.cookie = cookieParts.join('; ');
|
|
31
58
|
}
|
|
32
59
|
|
|
33
60
|
export function removeCookie(name: string) {
|
|
@@ -152,9 +179,6 @@ export function buildCDNUrl(url: string, config?: CDNOptions) {
|
|
|
152
179
|
return `${rootWithoutOptions}${options}${fileExtension}`;
|
|
153
180
|
}
|
|
154
181
|
|
|
155
|
-
const { locales, localeUrlStrategy, defaultLocaleValue } =
|
|
156
|
-
settings.localization;
|
|
157
|
-
|
|
158
182
|
export const urlLocaleMatcherRegex = new RegExp(
|
|
159
183
|
`^/(${settings.localization.locales
|
|
160
184
|
.filter((l) =>
|
|
@@ -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
|
@@ -1,23 +1,48 @@
|
|
|
1
1
|
import { redirect as nextRedirect, RedirectType } from 'next/navigation';
|
|
2
2
|
import Settings from 'settings';
|
|
3
|
-
import { headers } from 'next/headers';
|
|
4
|
-
import { ServerVariables } from '@akinon/next/utils/server-variables';
|
|
3
|
+
import { headers, cookies } from 'next/headers';
|
|
5
4
|
import { getUrlPathWithLocale } from '@akinon/next/utils/localization';
|
|
5
|
+
import { urlLocaleMatcherRegex } from '@akinon/next/utils';
|
|
6
6
|
|
|
7
7
|
export const redirect = (path: string, type?: RedirectType) => {
|
|
8
8
|
const nextHeaders = headers();
|
|
9
|
+
const nextCookies = cookies();
|
|
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
|
|
|
14
|
+
let currentLocaleValue = Settings.localization.defaultLocaleValue;
|
|
15
|
+
const urlLocaleMatch = pageUrl.pathname.match(urlLocaleMatcherRegex);
|
|
16
|
+
|
|
17
|
+
if (urlLocaleMatch && urlLocaleMatch[0]) {
|
|
18
|
+
currentLocaleValue = urlLocaleMatch[0].replace('/', '');
|
|
19
|
+
} else {
|
|
20
|
+
const cookieLocale = nextCookies.get('pz-locale')?.value;
|
|
21
|
+
if (
|
|
22
|
+
cookieLocale &&
|
|
23
|
+
Settings.localization.locales.find((l) => l.value === cookieLocale)
|
|
24
|
+
) {
|
|
25
|
+
currentLocaleValue = cookieLocale;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
const currentLocale = Settings.localization.locales.find(
|
|
14
|
-
(locale) => locale.value ===
|
|
30
|
+
(locale) => locale.value === currentLocaleValue
|
|
15
31
|
);
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
if (!currentLocale) {
|
|
34
|
+
currentLocaleValue = Settings.localization.defaultLocaleValue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const searchParams = new URLSearchParams(pageUrl.search);
|
|
38
|
+
|
|
39
|
+
const callbackUrl =
|
|
40
|
+
pageUrl.pathname.replace(urlLocaleMatcherRegex, '') +
|
|
41
|
+
(searchParams.toString() ? `?${searchParams.toString()}` : '');
|
|
42
|
+
|
|
18
43
|
const redirectUrlWithLocale = getUrlPathWithLocale(
|
|
19
44
|
path,
|
|
20
|
-
currentLocale
|
|
45
|
+
currentLocale?.value
|
|
21
46
|
);
|
|
22
47
|
|
|
23
48
|
const redirectUrl = `${redirectUrlWithLocale}?callbackUrl=${callbackUrl}`;
|
package/with-pz-config.js
CHANGED