@akinon/next 1.92.0-rc.8 → 1.92.0-snapshot-ZERO-3448-20250616104958

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/sentry/index.ts CHANGED
@@ -13,73 +13,36 @@ const ALLOWED_CLIENT_LOG_TYPES: ClientLogType[] = [
13
13
  ClientLogType.CHECKOUT
14
14
  ];
15
15
 
16
- const isNetworkError = (exception: unknown): boolean => {
17
- if (!(exception instanceof Error)) return false;
18
-
19
- const networkErrorPatterns = [
20
- 'networkerror',
21
- 'failed to fetch',
22
- 'network request failed',
23
- 'network error',
24
- 'loading chunk',
25
- 'chunk load failed'
26
- ];
27
-
28
- if (exception.name === 'NetworkError') return true;
29
-
30
- if (exception.name === 'TypeError') {
31
- return networkErrorPatterns.some((pattern) =>
32
- exception.message.toLowerCase().includes(pattern)
33
- );
34
- }
35
-
36
- return networkErrorPatterns.some((pattern) =>
37
- exception.message.toLowerCase().includes(pattern)
38
- );
39
- };
40
-
41
16
  export const initSentry = (
42
17
  type: 'Server' | 'Client' | 'Edge',
43
18
  options: Sentry.BrowserOptions | Sentry.NodeOptions | Sentry.EdgeOptions = {}
44
19
  ) => {
45
- // TODO: Remove Zero Project DSN
20
+ // TODO: Handle options with ESLint rules
46
21
 
47
- const baseConfig = {
22
+ Sentry.init({
48
23
  dsn:
49
- SENTRY_DSN ||
50
24
  options.dsn ||
25
+ SENTRY_DSN ||
51
26
  'https://d8558ef8997543deacf376c7d8d7cf4b@o64293.ingest.sentry.io/4504338423742464',
52
27
  initialScope: {
53
28
  tags: {
54
29
  APP_TYPE: 'ProjectZeroNext',
55
- TYPE: type,
56
- ...((options.initialScope as any)?.tags || {})
30
+ TYPE: type
57
31
  }
58
32
  },
59
33
  tracesSampleRate: 0,
60
- integrations: []
61
- };
62
-
63
- if (type === 'Server' || type === 'Edge') {
64
- Sentry.init(baseConfig);
65
- } else if (type === 'Client') {
66
- Sentry.init({
67
- ...baseConfig,
68
- beforeSend: (event, hint) => {
69
- if (
70
- !ALLOWED_CLIENT_LOG_TYPES.includes(
71
- event.tags?.LOG_TYPE as ClientLogType
72
- )
73
- ) {
74
- return null;
75
- }
76
-
77
- if (isNetworkError(hint?.originalException)) {
78
- return null;
79
- }
80
-
81
- return event;
34
+ integrations: [],
35
+ beforeSend: (event, hint) => {
36
+ if (
37
+ type === 'Client' &&
38
+ !ALLOWED_CLIENT_LOG_TYPES.includes(
39
+ event.tags?.LOG_TYPE as ClientLogType
40
+ )
41
+ ) {
42
+ return null;
82
43
  }
83
- });
84
- }
44
+
45
+ return event;
46
+ }
47
+ });
85
48
  };
@@ -114,7 +114,6 @@ export interface Order {
114
114
  pk: number;
115
115
  name: string;
116
116
  slug: string;
117
- logo: string;
118
117
  [key: string]: any;
119
118
  };
120
119
  }
package/types/index.ts CHANGED
@@ -218,6 +218,14 @@ export interface CacheOptions {
218
218
  useProxy?: boolean;
219
219
  }
220
220
 
221
+ export interface SetCookieOptions {
222
+ expires?: number; // days
223
+ path?: string;
224
+ domain?: string;
225
+ secure?: boolean;
226
+ sameSite?: 'strict' | 'lax' | 'none';
227
+ }
228
+
221
229
  export interface ClientRequestOptions {
222
230
  useTrailingSlash?: boolean;
223
231
  useFormData?: boolean;
@@ -283,13 +291,7 @@ export interface ButtonProps
283
291
  target?: '_blank' | '_self' | '_parent' | '_top';
284
292
  }
285
293
 
286
- export interface FileInputProps extends React.HTMLProps<HTMLInputElement> {
287
- fileClassName?: string;
288
- fileNameWrapperClassName?: string;
289
- fileInputClassName?: string;
290
- onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
291
- buttonClassName?: string;
292
- }
294
+ export type FileInputProps = React.HTMLProps<HTMLInputElement>;
293
295
 
294
296
  export interface PriceProps {
295
297
  currencyCode?: string;
@@ -310,19 +312,15 @@ export interface InputProps extends React.HTMLProps<HTMLInputElement> {
310
312
 
311
313
  export interface AccordionProps {
312
314
  isCollapse?: boolean;
313
- collapseClassName?: string;
314
315
  title?: string;
315
316
  subTitle?: string;
316
317
  icons?: string[];
317
318
  iconSize?: number;
318
319
  iconColor?: string;
319
320
  children?: ReactNode;
320
- headerClassName?: string;
321
321
  className?: string;
322
322
  titleClassName?: string;
323
- subTitleClassName?: string;
324
323
  dataTestId?: string;
325
- contentClassName?: string;
326
324
  }
327
325
 
328
326
  export interface PluginModuleComponentProps {
@@ -347,20 +345,3 @@ export interface PaginationProps {
347
345
  direction?: 'next' | 'prev';
348
346
  isLoading?: boolean;
349
347
  }
350
-
351
- export interface ModalProps {
352
- portalId: string;
353
- children?: React.ReactNode;
354
- open?: boolean;
355
- setOpen?: (open: boolean) => void;
356
- title?: React.ReactNode;
357
- showCloseButton?: React.ReactNode;
358
- className?: string;
359
- overlayClassName?: string;
360
- headerWrapperClassName?: string;
361
- titleClassName?: string;
362
- closeButtonClassName?: string;
363
- iconName?: string;
364
- iconSize?: number;
365
- iconClassName?: string;
366
- }
@@ -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(),
47
46
  ...(init.headers ?? {}),
48
47
  ...(ServerVariables.globalHeaders ?? {}),
49
48
  'Accept-Language': currentLocale.apiValue,
50
49
  'x-currency': currency,
51
- 'x-forwarded-for': ip
50
+ 'x-forwarded-for': ip,
51
+ cookie: nextCookies.toString()
52
52
  };
53
53
 
54
54
  init.next = {
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) =>
package/utils/redirect.ts CHANGED
@@ -3,23 +3,21 @@ 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';
7
6
 
8
7
  export const redirect = (path: string, type?: RedirectType) => {
9
8
  const nextHeaders = headers();
10
9
  const pageUrl = new URL(
11
- nextHeaders.get('pz-url') ?? process.env.NEXT_PUBLIC_URL ?? ''
10
+ nextHeaders.get('pz-url') ?? process.env.NEXT_PUBLIC_URL
12
11
  );
13
12
 
14
13
  const currentLocale = Settings.localization.locales.find(
15
14
  (locale) => locale.value === ServerVariables.locale
16
15
  );
17
16
 
18
- const callbackUrl = pageUrl.pathname.replace(urlLocaleMatcherRegex, '');
19
-
17
+ const callbackUrl = pageUrl.pathname;
20
18
  const redirectUrlWithLocale = getUrlPathWithLocale(
21
19
  path,
22
- currentLocale?.value
20
+ currentLocale.localePath ?? currentLocale.value
23
21
  );
24
22
 
25
23
  const redirectUrl = `${redirectUrlWithLocale}?callbackUrl=${callbackUrl}`;
package/with-pz-config.js CHANGED
@@ -16,8 +16,12 @@ const defaultConfig = {
16
16
  remotePatterns: [
17
17
  {
18
18
  protocol: 'https',
19
- hostname: '**'
19
+ hostname: '**.akinoncloud.com'
20
20
  },
21
+ {
22
+ protocol: 'https',
23
+ hostname: '**.akinoncdn.com'
24
+ }
21
25
  ]
22
26
  },
23
27
  modularizeImports: {
@@ -1,72 +0,0 @@
1
- import { Cache, CacheKey } from '../../lib/cache';
2
- import { basket } from '../../data/urls';
3
- import { Basket } from '../../types';
4
- import appFetch from '../../utils/app-fetch';
5
- import { ServerVariables } from '../../utils/server-variables';
6
- import logger from '../../utils/log';
7
-
8
- type GetBasketParams = {
9
- locale?: string;
10
- currency?: string;
11
- namespace?: string;
12
- };
13
-
14
- const getBasketDataHandler = ({
15
- locale,
16
- currency,
17
- namespace
18
- }: GetBasketParams) => {
19
- return async function () {
20
- try {
21
- const url = namespace
22
- ? basket.getBasketDetail(namespace)
23
- : basket.getBasket;
24
-
25
- const basketData = await appFetch<{ basket: Basket }>({
26
- url,
27
- locale,
28
- currency,
29
- init: {
30
- headers: {
31
- Accept: 'application/json',
32
- 'Content-Type': 'application/json'
33
- }
34
- }
35
- });
36
-
37
- if (!basketData?.basket) {
38
- logger.warn('Basket data is undefined', {
39
- handler: 'getBasketDataHandler',
40
- namespace
41
- });
42
- }
43
-
44
- return basketData;
45
- } catch (error) {
46
- logger.error('Error fetching basket data', {
47
- handler: 'getBasketDataHandler',
48
- error,
49
- namespace
50
- });
51
- throw error;
52
- }
53
- };
54
- };
55
-
56
- export const getBasketData = async ({
57
- locale = ServerVariables.locale,
58
- currency = ServerVariables.currency,
59
- namespace
60
- }: GetBasketParams = {}) => {
61
- return Cache.wrap(
62
- CacheKey.Basket(namespace),
63
- locale,
64
- getBasketDataHandler({ locale, currency, namespace }),
65
- {
66
- expire: 0,
67
- cache: false
68
- }
69
- );
70
- };
71
-
72
-
@@ -1,21 +0,0 @@
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
- };
@@ -1,179 +0,0 @@
1
- import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
2
- import Settings from 'settings';
3
- import { Buffer } from 'buffer';
4
- import logger from '../utils/log';
5
- import { getUrlPathWithLocale } from '../utils/localization';
6
- import { PzNextRequest } from '.';
7
-
8
- const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
9
- if (stream) {
10
- const chunks = [];
11
- let result = '';
12
-
13
- try {
14
- for await (const chunk of stream as any) {
15
- chunks.push(Buffer.from(chunk));
16
- }
17
-
18
- result = Buffer.concat(chunks).toString('utf-8');
19
- } catch (error) {
20
- logger.error('Error while reading body stream', {
21
- middleware: 'wallet-complete-redirection',
22
- error
23
- });
24
- }
25
-
26
- return result;
27
- }
28
- return null;
29
- };
30
-
31
- const withWalletCompleteRedirection =
32
- (middleware: NextMiddleware) =>
33
- async (req: PzNextRequest, event: NextFetchEvent) => {
34
- const url = req.nextUrl.clone();
35
- const ip = req.headers.get('x-forwarded-for') ?? '';
36
- const sessionId = req.cookies.get('osessionid');
37
-
38
- if (url.search.indexOf('WalletCompletePage') === -1) {
39
- return middleware(req, event);
40
- }
41
-
42
- const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
43
- const requestHeaders = {
44
- 'X-Requested-With': 'XMLHttpRequest',
45
- 'Content-Type': 'application/x-www-form-urlencoded',
46
- Cookie: req.headers.get('cookie') ?? '',
47
- 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
- 'x-forwarded-for': ip
49
- };
50
-
51
- try {
52
- const body = await streamToString(req.body);
53
-
54
- if (!sessionId) {
55
- logger.warn(
56
- 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
57
- {
58
- middleware: 'wallet-complete-redirection',
59
- ip
60
- }
61
- );
62
-
63
- return NextResponse.redirect(
64
- `${url.origin}${getUrlPathWithLocale(
65
- '/orders/checkout/',
66
- req.cookies.get('pz-locale')?.value
67
- )}`,
68
- 303
69
- );
70
- }
71
-
72
- const request = await fetch(requestUrl, {
73
- method: 'POST',
74
- headers: requestHeaders,
75
- body
76
- });
77
-
78
- logger.info('Complete wallet payment request', {
79
- requestUrl,
80
- status: request.status,
81
- requestHeaders,
82
- ip
83
- });
84
-
85
- const response = await request.json();
86
-
87
- const { context_list: contextList, errors } = response;
88
- const redirectionContext = contextList?.find(
89
- (context) => context.page_context?.redirect_url
90
- );
91
- const redirectUrl = redirectionContext?.page_context?.redirect_url;
92
-
93
- if (errors && Object.keys(errors).length) {
94
- logger.error('Error while completing wallet payment', {
95
- middleware: 'wallet-complete-redirection',
96
- errors,
97
- requestHeaders,
98
- ip
99
- });
100
-
101
- return NextResponse.redirect(
102
- `${url.origin}${getUrlPathWithLocale(
103
- '/orders/checkout/',
104
- req.cookies.get('pz-locale')?.value
105
- )}`,
106
- {
107
- status: 303,
108
- headers: {
109
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
110
- }
111
- }
112
- );
113
- }
114
-
115
- logger.info('Order success page context list', {
116
- middleware: 'wallet-complete-redirection',
117
- contextList,
118
- ip
119
- });
120
-
121
- if (!redirectUrl) {
122
- logger.warn(
123
- 'No redirection url for order success page found in page_context. Redirecting to checkout page.',
124
- {
125
- middleware: 'wallet-complete-redirection',
126
- requestHeaders,
127
- response: JSON.stringify(response),
128
- ip
129
- }
130
- );
131
-
132
- const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
133
- '/orders/checkout/',
134
- req.cookies.get('pz-locale')?.value
135
- )}`;
136
-
137
- return NextResponse.redirect(redirectUrlWithLocale, 303);
138
- }
139
-
140
- const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
141
- redirectUrl,
142
- req.cookies.get('pz-locale')?.value
143
- )}`;
144
-
145
- logger.info('Redirecting to order success page', {
146
- middleware: 'wallet-complete-redirection',
147
- redirectUrlWithLocale,
148
- ip
149
- });
150
-
151
- // Using POST method while redirecting causes an error,
152
- // So we use 303 status code to change the method to GET
153
- const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
154
-
155
- nextResponse.headers.set(
156
- 'Set-Cookie',
157
- request.headers.get('set-cookie') ?? ''
158
- );
159
-
160
- return nextResponse;
161
- } catch (error) {
162
- logger.error('Error while completing wallet payment', {
163
- middleware: 'wallet-complete-redirection',
164
- error,
165
- requestHeaders,
166
- ip
167
- });
168
-
169
- return NextResponse.redirect(
170
- `${url.origin}${getUrlPathWithLocale(
171
- '/orders/checkout/',
172
- req.cookies.get('pz-locale')?.value
173
- )}`,
174
- 303
175
- );
176
- }
177
- };
178
-
179
- export default withWalletCompleteRedirection;
@@ -1,35 +0,0 @@
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
- }