@akinon/next 1.125.0-rc.0 → 1.125.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 CHANGED
@@ -1,22 +1,10 @@
1
1
  # @akinon/next
2
2
 
3
- ## 1.125.0-rc.0
3
+ ## 1.125.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - ZERO-4160: Enhance oauth-login middleware with improved request handling and logging
8
- - b55acb76: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
9
- - 760258c1: ZERO-4160: Enhance oauth-login middleware to handle fetch errors and improve response handling
10
- - 143be2b9: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
11
7
  - cd68a97a: ZERO-4126: Enhance error handling in appFetch and related data handlers to throw notFound on 404 and 422 errors
12
- - 9f8cd3bc: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
13
- - bfafa3f4: ZERO-4160: Refactor oauth-login middleware to use fetchCommerce for API calls and improve cookie handling
14
- - d99a6a7d: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
15
- - 591e345e: ZERO-3855: Enhance credit card payment handling in checkout middlewares
16
- - 4de5303c: ZERO-2504: add cookie filter to api client request
17
- - 95b139dc: ZERO-3795: Remove duplicate entry for SavedCard in PluginComponents map
18
- - 3909d322: Edit the duplicate Plugin.SimilarProducts in the plugin-module.
19
- - e18836b2: ZERO-4160: Restore scope in Sentry addon configuration in akinon.json
20
8
 
21
9
  ## 1.124.0
22
10
 
@@ -26,6 +26,7 @@ const generateRoutes = () => {
26
26
  const excludedDirs = ['api', 'pz-not-found'];
27
27
 
28
28
  const skipSegments = [
29
+ '[pz]',
29
30
  '[commerce]',
30
31
  '[locale]',
31
32
  '[currency]',
@@ -114,6 +114,7 @@ const PluginComponents = new Map([
114
114
  ]
115
115
  ],
116
116
  [Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
117
+ [Plugin.SavedCard, [Component.SavedCard]],
117
118
  [Plugin.FlowPayment, [Component.FlowPayment]],
118
119
  [
119
120
  Plugin.VirtualTryOn,
package/data/urls.ts CHANGED
@@ -183,11 +183,7 @@ export const product = {
183
183
  breadcrumbUrl: (menuitemmodel: string) =>
184
184
  `/menus/generate_breadcrumb/?item=${menuitemmodel}&generator_name=menu_item`,
185
185
  bundleProduct: (productPk: string, queryString: string) =>
186
- `/bundle-product/${productPk}/?${queryString}`,
187
- similarProducts: (params?: string) =>
188
- `/similar-products${params ? `?${params}` : ''}`,
189
- similarProductsList: (params?: string) =>
190
- `/similar-product-list${params ? `?${params}` : ''}`
186
+ `/bundle-product/${productPk}/?${queryString}`
191
187
  };
192
188
 
193
189
  export const wishlist = {
@@ -4,6 +4,12 @@ import { redirect } from 'next/navigation';
4
4
  import { ServerVariables } from '../../utils/server-variables';
5
5
  import { ROUTES } from 'routes';
6
6
  import logger from '../../utils/log';
7
+ import {
8
+ decodePzValue,
9
+ getPzSegmentsConfig,
10
+ getBuiltInSegments,
11
+ isLegacyMode
12
+ } from '../../utils/pz-segments';
7
13
 
8
14
  type SegmentType = 'root-layout' | 'layout' | 'page';
9
15
 
@@ -20,17 +26,31 @@ export const withSegmentDefaults =
20
26
  ) =>
21
27
  async (props: T) => {
22
28
  let componentProps = { ...props };
29
+ let localeValue: string;
30
+ let currencyValue: string;
31
+
32
+ if (isLegacyMode(settings)) {
33
+ localeValue = (props.params as any).locale;
34
+ currencyValue = (props.params as any).currency;
35
+ } else {
36
+ const pzConfig = getPzSegmentsConfig(settings);
37
+ const parsed = decodePzValue(props.params.pz, pzConfig);
38
+ const builtIn = getBuiltInSegments(parsed, settings);
39
+ localeValue = builtIn.locale;
40
+ currencyValue = builtIn.currency;
41
+ }
23
42
 
24
43
  if (options.segmentType === 'root-layout') {
25
44
  componentProps = (await addRootLayoutProps(
26
- componentProps as RootLayoutProps
45
+ componentProps as RootLayoutProps,
46
+ localeValue
27
47
  )) as T;
28
48
 
29
49
  checkRedisVariables();
30
50
  }
31
51
 
32
- ServerVariables.locale = props.params.locale;
33
- ServerVariables.currency = props.params.currency;
52
+ ServerVariables.locale = localeValue;
53
+ ServerVariables.currency = currencyValue;
34
54
 
35
55
  return await (
36
56
  <>
@@ -39,12 +59,21 @@ export const withSegmentDefaults =
39
59
  );
40
60
  };
41
61
 
42
- const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
43
- const params = componentProps.params;
44
-
45
- if (
46
- params.commerce !== encodeURIComponent(decodeURI(settings.commerceUrl)) ||
47
- !settings.localization.locales.find((l) => l.value === params.locale)
62
+ const addRootLayoutProps = async (
63
+ componentProps: RootLayoutProps,
64
+ localeValue: string
65
+ ) => {
66
+ if (isLegacyMode(settings)) {
67
+ const params = componentProps.params as any;
68
+ if (
69
+ params.commerce !==
70
+ encodeURIComponent(decodeURI(settings.commerceUrl)) ||
71
+ !settings.localization.locales.find((l) => l.value === localeValue)
72
+ ) {
73
+ return redirect(ROUTES.HOME);
74
+ }
75
+ } else if (
76
+ !settings.localization.locales.find((l) => l.value === localeValue)
48
77
  ) {
49
78
  return redirect(ROUTES.HOME);
50
79
  }
@@ -52,12 +81,12 @@ const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
52
81
  const { getTranslations } = settings.useOptimizedTranslations
53
82
  ? require('translations')
54
83
  : require('../../utils/server-translation');
55
- const translations = await getTranslations(params.locale);
84
+ const translations = await getTranslations(localeValue);
56
85
 
57
86
  componentProps.translations = translations;
58
87
 
59
88
  const locale = settings.localization.locales.find(
60
- (l) => l.value === params.locale
89
+ (l) => l.value === localeValue
61
90
  );
62
91
  const [isoCode] = locale.value.split('-');
63
92
 
package/hooks/index.ts CHANGED
@@ -13,3 +13,4 @@ export * from './use-message-listener';
13
13
  export * from './use-logger';
14
14
  export * from './use-logger-context';
15
15
  export * from './use-sentry-uncaught-errors';
16
+ export * from './use-pz-params';
@@ -0,0 +1,37 @@
1
+ 'use client';
2
+
3
+ import { useParams } from 'next/navigation';
4
+ import settings from 'settings';
5
+ import {
6
+ decodePzValue,
7
+ getPzSegmentsConfig,
8
+ getBuiltInSegments,
9
+ isLegacyMode
10
+ } from '../utils/pz-segments';
11
+
12
+ export function usePzParams(): {
13
+ locale: string;
14
+ currency: string;
15
+ [key: string]: string;
16
+ } {
17
+ const params = useParams() as Record<string, string>;
18
+
19
+ if (isLegacyMode(settings)) {
20
+ return {
21
+ locale:
22
+ params.locale ?? settings.localization.defaultLocaleValue,
23
+ currency:
24
+ params.currency ?? settings.localization.defaultCurrencyCode
25
+ };
26
+ }
27
+
28
+ const pzValue = params.pz ?? '';
29
+ const config = getPzSegmentsConfig(settings);
30
+ const parsed = decodePzValue(pzValue, config);
31
+ const builtIn = getBuiltInSegments(parsed, settings);
32
+
33
+ return {
34
+ ...parsed,
35
+ ...builtIn
36
+ };
37
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { createContext } from 'react';
4
4
  import { getTranslateFn } from '../utils';
5
- import { useParams } from 'next/navigation';
5
+ import { usePzParams } from '../hooks/use-pz-params';
6
6
  import { Currency, Locale } from '../types';
7
7
  import settings from 'settings';
8
8
 
@@ -34,10 +34,7 @@ export default function LocalizationProvider({
34
34
  localeUrlStrategy
35
35
  } = settings.localization;
36
36
 
37
- const { locale, currency } = useParams() as {
38
- locale: string;
39
- currency: string;
40
- };
37
+ const { locale, currency } = usePzParams();
41
38
 
42
39
  return (
43
40
  <LocalizationContext.Provider
@@ -18,6 +18,7 @@ import {
18
18
  withBfcacheHeaders
19
19
  } from '.';
20
20
  import { urlLocaleMatcherRegex } from '../utils';
21
+ import { getPzSegmentsConfig, encodePzValue, isLegacyMode } from '../utils/pz-segments';
21
22
  import withCurrency from './currency';
22
23
  import withLocale from './locale';
23
24
  import logger from '../utils/log';
@@ -306,12 +307,76 @@ const withPzDefault =
306
307
  );
307
308
  }
308
309
 
309
- url.basePath = `/${commerceUrl}`;
310
- url.pathname = `/${
311
- locale.length ? `${locale}/` : ''
312
- }${currency}/${customRewriteUrlDiff}${
313
- prettyUrl ?? pathnameWithoutLocale
314
- }`.replace(/\/+/g, '/');
310
+ let ordersPrefix: string;
311
+
312
+ if (isLegacyMode(Settings)) {
313
+ url.basePath = `/${commerceUrl}`;
314
+ url.pathname =
315
+ `/${locale.length ? `${locale}/` : ''}${currency}/${customRewriteUrlDiff}${
316
+ prettyUrl ?? pathnameWithoutLocale
317
+ }`.replace(/\/+/g, '/');
318
+ ordersPrefix = `/${currency}/orders`;
319
+ } else {
320
+ const pzConfig =
321
+ getPzSegmentsConfig(Settings);
322
+ const fullUrlPath =
323
+ `${customRewriteUrlDiff}${
324
+ prettyUrl ?? pathnameWithoutLocale
325
+ }`.replace(/\/+/g, '/');
326
+ const fullUrl = `${req.nextUrl.origin}${fullUrlPath}`;
327
+ const resolvedLocale =
328
+ locale?.length > 0
329
+ ? locale
330
+ : Settings.localization
331
+ .defaultLocaleValue;
332
+ const resolveContext = {
333
+ req,
334
+ event,
335
+ url,
336
+ locale: resolvedLocale,
337
+ currency,
338
+ pathname: pathnameWithoutLocale
339
+ };
340
+ const customSegments = pzConfig.segments
341
+ .filter(
342
+ (seg) =>
343
+ seg.name !== 'locale' &&
344
+ seg.name !== 'currency' &&
345
+ seg.name !== 'url'
346
+ );
347
+ const segmentValues: Record<
348
+ string,
349
+ string
350
+ > = {
351
+ locale: resolvedLocale,
352
+ currency,
353
+ url: encodeURIComponent(fullUrl),
354
+ ...Object.fromEntries(
355
+ customSegments
356
+ .map((seg) => [
357
+ seg.name,
358
+ req.middlewareParams.rewrites[
359
+ seg.name
360
+ ] ??
361
+ (seg.resolve
362
+ ? seg.resolve(resolveContext)
363
+ : '')
364
+ ])
365
+ )
366
+ };
367
+
368
+ const pzValue = encodePzValue(
369
+ segmentValues,
370
+ pzConfig
371
+ );
372
+
373
+ url.pathname =
374
+ `/${pzValue}/${fullUrlPath}`.replace(
375
+ /\/+/g,
376
+ '/'
377
+ );
378
+ ordersPrefix = `/${pzValue}/orders`;
379
+ }
315
380
 
316
381
  if (
317
382
  Settings.usePrettyUrlRoute &&
@@ -388,7 +453,7 @@ const withPzDefault =
388
453
 
389
454
  if (
390
455
  !url.pathname.startsWith(
391
- `/${currency}/orders`
456
+ ordersPrefix
392
457
  )
393
458
  ) {
394
459
  middlewareResult.cookies.set(
@@ -40,6 +40,7 @@ export interface PzNextRequest extends NextRequest {
40
40
  locale?: string;
41
41
  prettyUrl?: string;
42
42
  currency?: string;
43
+ [key: string]: string | undefined;
43
44
  };
44
45
  };
45
46
  }
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.125.0-rc.0",
4
+ "version": "1.125.0",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "set-cookie-parser": "2.6.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@akinon/eslint-plugin-projectzero": "1.125.0-rc.0",
38
+ "@akinon/eslint-plugin-projectzero": "1.125.0",
39
39
  "@babel/core": "7.26.10",
40
40
  "@babel/preset-env": "7.26.9",
41
41
  "@babel/preset-typescript": "7.27.0",
package/plugins.d.ts CHANGED
@@ -37,16 +37,6 @@ declare module '@akinon/pz-cybersource-uc/src/redux/middleware' {
37
37
  export default middleware as any;
38
38
  }
39
39
 
40
- declare module '@akinon/pz-apple-pay' {}
41
-
42
- declare module '@akinon/pz-similar-products' {
43
- export const SimilarProductsModal: any;
44
- export const SimilarProductsFilterSidebar: any;
45
- export const SimilarProductsResultsGrid: any;
46
- export const SimilarProductsPlugin: any;
47
- export const SimilarProductsButtonPlugin: any;
48
- }
49
-
50
40
  declare module '@akinon/pz-cybersource-uc/src/redux/reducer' {
51
41
  export default reducer as any;
52
42
  }
package/plugins.js CHANGED
@@ -16,7 +16,6 @@ module.exports = [
16
16
  'pz-tabby-extension',
17
17
  'pz-apple-pay',
18
18
  'pz-tamara-extension',
19
- 'pz-similar-products',
20
19
  'pz-cybersource-uc',
21
20
  'pz-hepsipay',
22
21
  'pz-flow-payment',
@@ -209,25 +209,15 @@ export const contextListMiddleware: Middleware = ({
209
209
  (ctx) => ctx.page_name === 'DeliveryOptionSelectionPage'
210
210
  )
211
211
  ) {
212
- const isCreditCardPayment =
213
- preOrder?.payment_option?.payment_type === 'credit_card' ||
214
- preOrder?.payment_option?.payment_type === 'masterpass';
215
-
216
212
  if (context.page_context.card_type) {
217
213
  dispatch(setCardType(context.page_context.card_type));
218
- } else if (isCreditCardPayment) {
219
- dispatch(setCardType(null));
220
214
  }
221
215
 
222
216
  if (
223
217
  context.page_context.installments &&
224
218
  preOrder?.payment_option?.payment_type !== 'masterpass_rest'
225
219
  ) {
226
- if (!isCreditCardPayment || context.page_context.card_type) {
227
- dispatch(
228
- setInstallmentOptions(context.page_context.installments)
229
- );
230
- }
220
+ dispatch(setInstallmentOptions(context.page_context.installments));
231
221
  }
232
222
  }
233
223
 
@@ -14,17 +14,9 @@ export const installmentOptionMiddleware: Middleware = ({
14
14
  return result;
15
15
  }
16
16
 
17
- const { installmentOptions, cardType } = getState().checkout;
17
+ const { installmentOptions } = getState().checkout;
18
18
  const { endpoints: apiEndpoints } = checkoutApi;
19
19
 
20
- const isCreditCardPayment =
21
- preOrder?.payment_option?.payment_type === 'credit_card' ||
22
- preOrder?.payment_option?.payment_type === 'masterpass';
23
-
24
- if (isCreditCardPayment && !cardType) {
25
- return result;
26
- }
27
-
28
20
  if (
29
21
  !preOrder?.installment &&
30
22
  preOrder?.payment_option?.payment_type !== 'saved_card' &&
package/types/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { LocaleUrlStrategy } from '../localization';
2
2
  import { PzNextRequest } from '../middlewares';
3
+ import { NextFetchEvent } from 'next/server';
4
+ import { NextURL } from 'next/dist/server/web/next-url';
3
5
  import { Control, FieldError } from 'react-hook-form';
4
6
  import { ReactNode } from 'react';
5
7
  import { UsePaginationType } from '../hooks/use-pagination';
@@ -83,12 +85,6 @@ export interface Settings {
83
85
  };
84
86
  usePrettyUrlRoute?: boolean;
85
87
  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;
92
88
  redis: {
93
89
  defaultExpirationTime: number;
94
90
  };
@@ -216,6 +212,11 @@ export interface Settings {
216
212
  */
217
213
  resetBasketOnCurrencyChange?: boolean;
218
214
  frontendIds?: Record<string, number>;
215
+ usePzSegment?: boolean;
216
+ pzSegments?: {
217
+ separator?: string;
218
+ segments: PzSegmentDefinition[];
219
+ };
219
220
  }
220
221
 
221
222
  export interface CacheOptions {
@@ -255,8 +256,34 @@ export type ImageOptions = CDNOptions & {
255
256
 
256
257
  export type Translations = { [key: string]: object };
257
258
 
259
+ export interface PzSegmentResolveContext {
260
+ req: PzNextRequest;
261
+ event: NextFetchEvent;
262
+ url: NextURL;
263
+ locale: string;
264
+ currency: string;
265
+ pathname: string;
266
+ }
267
+
268
+ export interface PzSegmentDefinition {
269
+ name: string;
270
+ resolve?: (context: PzSegmentResolveContext) => string;
271
+ }
272
+
273
+ export interface PzSegmentsConfig {
274
+ separator: string;
275
+ segments: PzSegmentDefinition[];
276
+ }
277
+
258
278
  export interface PageProps<T = any> {
259
- params: T & { locale: string; currency: string };
279
+ params: T & {
280
+ pz?: string;
281
+ commerce?: string;
282
+ locale?: string;
283
+ currency?: string;
284
+ url?: string;
285
+ [key: string]: any;
286
+ };
260
287
  searchParams: URLSearchParams;
261
288
  }
262
289
 
@@ -264,13 +291,7 @@ export interface LayoutProps<T = any> extends PageProps<T> {
264
291
  children: React.ReactNode;
265
292
  }
266
293
 
267
- export interface RootLayoutProps<
268
- T = {
269
- commerce: string;
270
- locale: string;
271
- currency: string;
272
- }
273
- > extends LayoutProps<T> {
294
+ export interface RootLayoutProps<T = any> extends LayoutProps<T> {
274
295
  translations: Translations;
275
296
  locale: Locale & {
276
297
  isoCode: string;
@@ -61,15 +61,13 @@ const appFetch = async <T>({
61
61
  status = req.status;
62
62
  logger.debug(`FETCH END ${url}`, { status: req.status, ip });
63
63
 
64
- if (req.ok) {
65
- if (responseType === FetchResponseType.JSON) {
66
- response = (await req.json()) as T;
67
- } else {
68
- response = (await req.text()) as unknown as T;
69
- }
70
-
71
- logger.trace(`FETCH RESPONSE`, { url, response, ip });
64
+ if (responseType === FetchResponseType.JSON) {
65
+ response = (await req.json()) as T;
66
+ } else {
67
+ response = (await req.text()) as unknown as T;
72
68
  }
69
+
70
+ logger.trace(`FETCH RESPONSE`, { url, response, ip });
73
71
  } catch (error) {
74
72
  const logType = status === 500 ? 'fatal' : 'error';
75
73
 
package/utils/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from './get-currency';
7
7
  export * from './menu-generator';
8
8
  export * from './generate-commerce-search-params';
9
9
  export * from './get-currency-label';
10
+ export * from './pz-segments';
10
11
  export * from './get-checkout-path';
11
12
 
12
13
  export function getCookie(name: string) {
@@ -0,0 +1,92 @@
1
+ import type { PzSegmentDefinition, PzSegmentsConfig } from '../types';
2
+
3
+ export function isLegacyMode(settings: any): boolean {
4
+ return !settings.usePzSegment;
5
+ }
6
+
7
+ const DEFAULT_SEPARATOR = '--';
8
+
9
+ const DEFAULT_SEGMENTS: PzSegmentDefinition[] = [
10
+ { name: 'locale' },
11
+ { name: 'currency' },
12
+ { name: 'url' }
13
+ ];
14
+
15
+ export function getPzSegmentsConfig(settings: any): PzSegmentsConfig {
16
+ if (settings.pzSegments) {
17
+ const customSegments = (settings.pzSegments.segments ?? []).filter(
18
+ (seg: PzSegmentDefinition) =>
19
+ !DEFAULT_SEGMENTS.some((d) => d.name === seg.name)
20
+ );
21
+
22
+ return {
23
+ separator: settings.pzSegments.separator ?? DEFAULT_SEPARATOR,
24
+ segments: [...DEFAULT_SEGMENTS, ...customSegments]
25
+ };
26
+ }
27
+
28
+ return {
29
+ separator: DEFAULT_SEPARATOR,
30
+ segments: DEFAULT_SEGMENTS
31
+ };
32
+ }
33
+
34
+ export function encodePzValue(
35
+ values: Record<string, string>,
36
+ config: PzSegmentsConfig
37
+ ): string {
38
+ return config.segments
39
+ .map((seg) => values[seg.name] ?? '')
40
+ .join(config.separator);
41
+ }
42
+
43
+ export function decodePzValue(
44
+ pzValue: string,
45
+ config: PzSegmentsConfig
46
+ ): Record<string, string> {
47
+ const parts = pzValue.split(config.separator);
48
+ const result: Record<string, string> = {};
49
+
50
+ config.segments.forEach((seg, index) => {
51
+ result[seg.name] = parts[index] ?? '';
52
+ });
53
+
54
+ return result;
55
+ }
56
+
57
+ export function getBuiltInSegments(
58
+ parsed: Record<string, string>,
59
+ settings: any
60
+ ): { locale: string; currency: string; url: string } {
61
+ const { defaultLocaleValue, defaultCurrencyCode } = settings.localization;
62
+
63
+ return {
64
+ locale: parsed.locale || defaultLocaleValue,
65
+ currency: parsed.currency || defaultCurrencyCode,
66
+ url: parsed.url ? decodeURIComponent(parsed.url) : ''
67
+ };
68
+ }
69
+
70
+ export function parsePzParams(
71
+ params: { pz?: string; locale?: string; currency?: string; url?: string },
72
+ settings: any
73
+ ): { locale: string; currency: string; url: string; [key: string]: string } {
74
+ if (isLegacyMode(settings)) {
75
+ return {
76
+ locale:
77
+ params.locale ?? settings.localization.defaultLocaleValue,
78
+ currency:
79
+ params.currency ?? settings.localization.defaultCurrencyCode,
80
+ url: params.url ? decodeURIComponent(params.url) : ''
81
+ };
82
+ }
83
+
84
+ const config = getPzSegmentsConfig(settings);
85
+ const parsed = decodePzValue(params.pz ?? '', config);
86
+ const builtIn = getBuiltInSegments(parsed, settings);
87
+
88
+ return {
89
+ ...parsed,
90
+ ...builtIn
91
+ };
92
+ }