@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.
Files changed (46) hide show
  1. package/CHANGELOG.md +1260 -49
  2. package/__tests__/next-config.test.ts +1 -10
  3. package/__tests__/redirect.test.ts +319 -0
  4. package/api/image-proxy.ts +75 -0
  5. package/api/similar-product-list.ts +84 -0
  6. package/api/similar-products.ts +120 -0
  7. package/components/accordion.tsx +20 -5
  8. package/components/file-input.tsx +65 -3
  9. package/components/input.tsx +2 -0
  10. package/components/link.tsx +16 -12
  11. package/components/modal.tsx +32 -16
  12. package/components/plugin-module.tsx +30 -3
  13. package/data/client/checkout.ts +5 -4
  14. package/data/server/basket.ts +72 -0
  15. package/data/server/category.ts +48 -28
  16. package/data/server/flatpage.ts +16 -12
  17. package/data/server/landingpage.ts +16 -12
  18. package/data/server/list.ts +23 -13
  19. package/data/server/product.ts +66 -39
  20. package/data/server/special-page.ts +16 -12
  21. package/data/urls.ts +5 -1
  22. package/hocs/server/with-segment-defaults.tsx +5 -2
  23. package/hooks/use-localization.ts +2 -3
  24. package/jest.config.js +7 -1
  25. package/lib/cache.ts +2 -0
  26. package/middlewares/checkout-provider.ts +1 -1
  27. package/middlewares/complete-gpay.ts +2 -1
  28. package/middlewares/complete-masterpass.ts +2 -1
  29. package/middlewares/default.ts +50 -13
  30. package/middlewares/locale.ts +9 -1
  31. package/middlewares/redirection-payment.ts +2 -1
  32. package/middlewares/saved-card-redirection.ts +2 -1
  33. package/middlewares/three-d-redirection.ts +2 -1
  34. package/middlewares/url-redirection.ts +9 -15
  35. package/package.json +3 -3
  36. package/plugins.d.ts +8 -0
  37. package/plugins.js +3 -1
  38. package/redux/middlewares/checkout.ts +5 -1
  39. package/sentry/index.ts +54 -17
  40. package/types/commerce/order.ts +1 -0
  41. package/types/index.ts +42 -1
  42. package/utils/app-fetch.ts +7 -2
  43. package/utils/index.ts +34 -10
  44. package/utils/redirect-ignore.ts +35 -0
  45. package/utils/redirect.ts +31 -6
  46. 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 type FileInputProps = React.HTMLProps<HTMLInputElement>;
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
+ }
@@ -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(name: string, val: string) {
24
- const date = new Date();
25
- const value = val;
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
- date.setTime(date.getTime() + 7 * 24 * 60 * 60 * 1000);
37
+ cookieParts.push(`path=${options.path ?? '/'}`);
28
38
 
29
- document.cookie =
30
- name + '=' + value + '; expires=' + date.toUTCString() + '; path=/';
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 === ServerVariables.locale
30
+ (locale) => locale.value === currentLocaleValue
15
31
  );
16
32
 
17
- const callbackUrl = pageUrl.pathname;
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.localePath ?? currentLocale.value
45
+ currentLocale?.value
21
46
  );
22
47
 
23
48
  const redirectUrl = `${redirectUrlWithLocale}?callbackUrl=${callbackUrl}`;
package/with-pz-config.js CHANGED
@@ -16,12 +16,8 @@ const defaultConfig = {
16
16
  remotePatterns: [
17
17
  {
18
18
  protocol: 'https',
19
- hostname: '**.akinoncloud.com'
19
+ hostname: '**'
20
20
  },
21
- {
22
- protocol: 'https',
23
- hostname: '**.akinoncdn.com'
24
- }
25
21
  ]
26
22
  },
27
23
  modularizeImports: {