@akinon/next 2.0.0-beta.12 → 2.0.0-beta.13

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 (93) hide show
  1. package/CHANGELOG.md +282 -29
  2. package/api/auth.ts +99 -77
  3. package/api/cache.ts +41 -5
  4. package/api/client.ts +3 -3
  5. package/api/form.ts +85 -0
  6. package/api/image-proxy.ts +75 -0
  7. package/api/product-categories.ts +53 -0
  8. package/api/similar-product-list.ts +63 -0
  9. package/api/similar-products.ts +111 -0
  10. package/api/virtual-try-on.ts +382 -0
  11. package/bin/pz-generate-routes.js +105 -0
  12. package/bin/pz-prebuild.js +1 -1
  13. package/bin/pz-predev.js +1 -0
  14. package/components/accordion.tsx +21 -6
  15. package/components/button.tsx +1 -1
  16. package/components/file-input.tsx +65 -3
  17. package/components/input.tsx +2 -2
  18. package/components/modal.tsx +32 -16
  19. package/components/plugin-module.tsx +61 -3
  20. package/components/select.tsx +2 -2
  21. package/components/selected-payment-option-view.tsx +21 -0
  22. package/data/client/checkout.ts +130 -74
  23. package/data/server/category.ts +11 -9
  24. package/data/server/flatpage.ts +4 -1
  25. package/data/server/form.ts +4 -1
  26. package/data/server/landingpage.ts +4 -1
  27. package/data/server/list.ts +5 -4
  28. package/data/server/menu.ts +4 -1
  29. package/data/server/product.ts +97 -52
  30. package/data/server/seo.ts +4 -1
  31. package/data/server/special-page.ts +5 -4
  32. package/data/server/widget.ts +4 -1
  33. package/data/urls.ts +3 -2
  34. package/hocs/client/with-segment-defaults.tsx +2 -2
  35. package/hocs/server/with-segment-defaults.tsx +65 -20
  36. package/hooks/index.ts +1 -0
  37. package/hooks/use-loyalty-availability.ts +21 -0
  38. package/hooks/use-payment-options.ts +2 -1
  39. package/hooks/use-pz-params.ts +37 -0
  40. package/instrumentation/index.ts +0 -1
  41. package/instrumentation/node.ts +2 -20
  42. package/jest.config.js +7 -1
  43. package/lib/cache-handler.mjs +527 -15
  44. package/lib/cache.ts +260 -31
  45. package/localization/provider.tsx +2 -5
  46. package/middlewares/checkout-provider.ts +1 -1
  47. package/middlewares/complete-gpay.ts +33 -26
  48. package/middlewares/complete-masterpass.ts +34 -26
  49. package/middlewares/complete-wallet.ts +183 -0
  50. package/middlewares/default.ts +346 -235
  51. package/middlewares/index.ts +8 -2
  52. package/middlewares/locale.ts +0 -1
  53. package/middlewares/masterpass-rest-callback.ts +220 -0
  54. package/middlewares/pretty-url.ts +21 -8
  55. package/middlewares/redirection-payment.ts +33 -26
  56. package/middlewares/saved-card-redirection.ts +34 -26
  57. package/middlewares/three-d-redirection.ts +33 -26
  58. package/middlewares/url-redirection.ts +9 -15
  59. package/middlewares/wallet-complete-redirection.ts +207 -0
  60. package/package.json +20 -11
  61. package/plugins.d.ts +19 -4
  62. package/plugins.js +9 -1
  63. package/redux/actions.ts +47 -0
  64. package/redux/middlewares/checkout.ts +20 -8
  65. package/redux/middlewares/index.ts +12 -10
  66. package/redux/middlewares/pre-order/address.ts +1 -1
  67. package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +1 -1
  68. package/redux/middlewares/pre-order/data-source-shipping-option.ts +1 -1
  69. package/redux/middlewares/pre-order/delivery-option.ts +1 -1
  70. package/redux/middlewares/pre-order/index.ts +3 -1
  71. package/redux/middlewares/pre-order/installment-option.ts +2 -1
  72. package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
  73. package/redux/middlewares/pre-order/payment-option.ts +1 -1
  74. package/redux/middlewares/pre-order/pre-order-validation.ts +4 -3
  75. package/redux/middlewares/pre-order/redirection.ts +2 -2
  76. package/redux/middlewares/pre-order/set-pre-order.ts +2 -2
  77. package/redux/middlewares/pre-order/shipping-option.ts +1 -1
  78. package/redux/middlewares/pre-order/shipping-step.ts +1 -1
  79. package/redux/reducers/checkout.ts +9 -1
  80. package/redux/reducers/index.ts +5 -1
  81. package/sentry/index.ts +54 -17
  82. package/types/commerce/checkout.ts +11 -1
  83. package/types/index.ts +96 -6
  84. package/types/next-auth.d.ts +2 -2
  85. package/utils/app-fetch.ts +2 -2
  86. package/utils/generate-commerce-search-params.ts +3 -2
  87. package/utils/get-checkout-path.ts +3 -0
  88. package/utils/index.ts +38 -11
  89. package/utils/override-middleware.ts +1 -0
  90. package/utils/pz-segments.ts +92 -0
  91. package/utils/redirect-ignore.ts +35 -0
  92. package/utils/redirect.ts +9 -3
  93. package/with-pz-config.js +10 -4
@@ -1,15 +1,16 @@
1
1
  import { Middleware } from '@reduxjs/toolkit';
2
- import { CheckoutResult } from '../../../types';
2
+ import { CheckoutResult, MiddlewareAction } from '../../../types';
3
3
 
4
4
  export const preOrderValidationMiddleware: Middleware = () => {
5
5
  return (next) => (action) => {
6
- const result: CheckoutResult = next(action);
6
+ const result = next(action) as CheckoutResult;
7
7
  const preOrder = result?.payload?.pre_order;
8
+ const act = action as MiddlewareAction;
8
9
 
9
10
  if (
10
11
  !preOrder ||
11
12
  ['guestLogin', 'getCheckoutLoyaltyBalance'].includes(
12
- action?.meta?.arg?.endpointName
13
+ act?.meta?.arg?.endpointName
13
14
  )
14
15
  ) {
15
16
  return result;
@@ -1,12 +1,12 @@
1
1
  import { Middleware } from '@reduxjs/toolkit';
2
- import { MiddlewareParams } from '../../../types';
2
+ import { CheckoutResult, MiddlewareParams } from '../../../types';
3
3
  import { checkoutApi } from '../../../data/client/checkout';
4
4
 
5
5
  export const redirectionMiddleware: Middleware = ({
6
6
  dispatch
7
7
  }: MiddlewareParams) => {
8
8
  return (next) => (action) => {
9
- const result = next(action);
9
+ const result = next(action) as CheckoutResult;
10
10
  const preOrder = result?.payload?.pre_order;
11
11
 
12
12
  if (!preOrder) {
@@ -1,12 +1,12 @@
1
1
  import { Middleware } from '@reduxjs/toolkit';
2
- import { MiddlewareParams } from '../../../types';
2
+ import { CheckoutResult, MiddlewareParams } from '../../../types';
3
3
  import { setPreOrder } from '../../../redux/reducers/checkout';
4
4
 
5
5
  export const setPreOrderMiddleware: Middleware = ({
6
6
  dispatch
7
7
  }: MiddlewareParams) => {
8
8
  return (next) => (action) => {
9
- const result = next(action);
9
+ const result = next(action) as CheckoutResult;
10
10
  const preOrder = result?.payload?.pre_order;
11
11
 
12
12
  if (preOrder) {
@@ -7,7 +7,7 @@ export const shippingOptionMiddleware: Middleware = ({
7
7
  dispatch
8
8
  }: MiddlewareParams) => {
9
9
  return (next) => (action) => {
10
- const result: CheckoutResult = next(action);
10
+ const result = next(action) as CheckoutResult;
11
11
  const preOrder = result?.payload?.pre_order;
12
12
 
13
13
  if (!preOrder) {
@@ -7,7 +7,7 @@ export const shippingStepMiddleware: Middleware = ({
7
7
  dispatch
8
8
  }: MiddlewareParams) => {
9
9
  return (next) => (action) => {
10
- const result: CheckoutResult = next(action);
10
+ const result = next(action) as CheckoutResult;
11
11
  const preOrder = result?.payload?.pre_order;
12
12
 
13
13
  if (!preOrder) {
@@ -40,6 +40,7 @@ export interface CheckoutState {
40
40
  shippingOptions: ShippingOption[];
41
41
  dataSourceShippingOptions: DataSource[];
42
42
  paymentOptions: PaymentOption[];
43
+ unavailablePaymentOptions: PaymentOption[];
43
44
  creditPaymentOptions: CheckoutCreditPaymentOption[];
44
45
  selectedCreditPaymentPk: number;
45
46
  paymentChoices: PaymentChoice[];
@@ -60,6 +61,8 @@ export interface CheckoutState {
60
61
  countryCode: string;
61
62
  currencyCode: string;
62
63
  version: string;
64
+ public_key: string;
65
+ [key: string]: any;
63
66
  };
64
67
  detail: {
65
68
  label: string;
@@ -94,6 +97,7 @@ const initialState: CheckoutState = {
94
97
  shippingOptions: [],
95
98
  dataSourceShippingOptions: [],
96
99
  paymentOptions: [],
100
+ unavailablePaymentOptions: [],
97
101
  creditPaymentOptions: [],
98
102
  selectedCreditPaymentPk: null,
99
103
  paymentChoices: [],
@@ -157,6 +161,9 @@ const checkoutSlice = createSlice({
157
161
  setPaymentOptions(state, { payload }) {
158
162
  state.paymentOptions = payload;
159
163
  },
164
+ setUnavailablePaymentOptions(state, { payload }) {
165
+ state.unavailablePaymentOptions = payload;
166
+ },
160
167
  setPaymentChoices(state, { payload }) {
161
168
  state.paymentChoices = payload;
162
169
  },
@@ -218,9 +225,10 @@ export const {
218
225
  setShippingOptions,
219
226
  setDataSourceShippingOptions,
220
227
  setPaymentOptions,
228
+ setUnavailablePaymentOptions,
229
+ setPaymentChoices,
221
230
  setCreditPaymentOptions,
222
231
  setSelectedCreditPaymentPk,
223
- setPaymentChoices,
224
232
  setCardType,
225
233
  setInstallmentOptions,
226
234
  setBankAccounts,
@@ -8,6 +8,8 @@ import { api } from '../../data/client/api';
8
8
  import { masterpassReducer } from '@akinon/pz-masterpass';
9
9
  import { otpReducer } from '@akinon/pz-otp';
10
10
  import { savedCardReducer } from '@akinon/pz-saved-card';
11
+ import cyberSourceUcReducer from '@akinon/pz-cybersource-uc/src/redux/reducer';
12
+ import { masterpassRestReducer } from '@akinon/pz-masterpass-rest';
11
13
 
12
14
  const fallbackReducer = (state = {}) => state;
13
15
 
@@ -19,7 +21,9 @@ const reducers = {
19
21
  header: headerReducer,
20
22
  masterpass: masterpassReducer || fallbackReducer,
21
23
  otp: otpReducer || fallbackReducer,
22
- savedCard: savedCardReducer || fallbackReducer
24
+ savedCard: savedCardReducer || fallbackReducer,
25
+ cybersource_uc: cyberSourceUcReducer || fallbackReducer,
26
+ masterpassRest: masterpassRestReducer || fallbackReducer
23
27
  };
24
28
 
25
29
  export default reducers;
package/sentry/index.ts CHANGED
@@ -13,36 +13,73 @@ 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
+
16
41
  export const initSentry = (
17
42
  type: 'Server' | 'Client' | 'Edge',
18
43
  options: Sentry.BrowserOptions | Sentry.NodeOptions | Sentry.EdgeOptions = {}
19
44
  ) => {
20
- // TODO: Handle options with ESLint rules
45
+ // TODO: Remove Zero Project DSN
21
46
 
22
- Sentry.init({
47
+ const baseConfig = {
23
48
  dsn:
24
- options.dsn ||
25
49
  SENTRY_DSN ||
50
+ options.dsn ||
26
51
  'https://d8558ef8997543deacf376c7d8d7cf4b@o64293.ingest.sentry.io/4504338423742464',
27
52
  initialScope: {
28
53
  tags: {
29
54
  APP_TYPE: 'ProjectZeroNext',
30
- TYPE: type
55
+ TYPE: type,
56
+ ...((options.initialScope as any)?.tags || {})
31
57
  }
32
58
  },
33
59
  tracesSampleRate: 0,
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;
43
- }
60
+ integrations: []
61
+ };
44
62
 
45
- return event;
46
- }
47
- });
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;
82
+ }
83
+ });
84
+ }
48
85
  };
@@ -1,9 +1,9 @@
1
+ import type { JSX } from 'react';
1
2
  import { Address } from './address';
2
3
  import { Basket } from './basket';
3
4
  import { RetailStore } from './misc';
4
5
  import { PaymentOption } from './order';
5
6
  import { Product } from './product';
6
- import { JSX } from 'react';
7
7
  import { RootState, TypedDispatch } from 'redux/store';
8
8
 
9
9
  export enum CheckoutStep {
@@ -109,6 +109,7 @@ export interface PreOrder {
109
109
  token?: string;
110
110
  agreement_confirmed?: boolean;
111
111
  phone_number?: string;
112
+ number?: string;
112
113
  }
113
114
 
114
115
  export type ExtraField = Record<string, any>;
@@ -202,6 +203,15 @@ export interface MiddlewareParams {
202
203
  dispatch: TypedDispatch;
203
204
  }
204
205
 
206
+ export interface MiddlewareAction {
207
+ type: string;
208
+ payload?: { status?: number; [key: string]: unknown };
209
+ meta?: {
210
+ arg?: { endpointName?: string };
211
+ baseQueryMeta?: { request?: { url?: string } };
212
+ };
213
+ }
214
+
205
215
  export type SendSmsType = {
206
216
  phone_number: string;
207
217
  };
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';
@@ -199,6 +201,7 @@ export interface Settings {
199
201
  extraPaymentTypes?: string[];
200
202
  masterpassJsUrl?: string;
201
203
  };
204
+ customNotFoundEnabled: boolean;
202
205
  useOptimizedTranslations?: boolean;
203
206
  plugins?: Record<string, Record<string, any>>;
204
207
  includedProxyHeaders?: string[];
@@ -209,13 +212,26 @@ export interface Settings {
209
212
  */
210
213
  resetBasketOnCurrencyChange?: boolean;
211
214
  frontendIds?: Record<string, number>;
212
- customNotFoundEnabled: boolean;
215
+ usePzSegment?: boolean;
216
+ pzSegments?: {
217
+ separator?: string;
218
+ segments: PzSegmentDefinition[];
219
+ };
213
220
  }
214
221
 
215
222
  export interface CacheOptions {
216
223
  cache?: boolean;
217
224
  expire?: number;
218
225
  useProxy?: boolean;
226
+ compressed?: boolean;
227
+ }
228
+
229
+ export interface SetCookieOptions {
230
+ expires?: number; // days
231
+ path?: string;
232
+ domain?: string;
233
+ secure?: boolean;
234
+ sameSite?: 'strict' | 'lax' | 'none';
219
235
  }
220
236
 
221
237
  export interface ClientRequestOptions {
@@ -240,22 +256,69 @@ export type ImageOptions = CDNOptions & {
240
256
 
241
257
  export type Translations = { [key: string]: object };
242
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
+
278
+ // Next.js 16 async page/layout props (for exported page components and generateMetadata)
243
279
  export interface PageProps<T = any> {
244
- params: Promise<T & { locale: string; currency: string }>;
245
- searchParams: Promise<URLSearchParams>;
280
+ params: Promise<T & {
281
+ pz?: string;
282
+ commerce?: string;
283
+ locale?: string;
284
+ currency?: string;
285
+ url?: string;
286
+ [key: string]: any;
287
+ }>;
288
+ searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
246
289
  }
247
290
 
248
291
  export interface LayoutProps<T = any> extends PageProps<T> {
249
292
  children: React.ReactNode;
250
293
  }
251
294
 
252
- export interface RootLayoutProps<
295
+ export interface RootLayoutProps<T = any> extends LayoutProps<T> {
296
+ translations: Translations;
297
+ locale: Locale & {
298
+ isoCode: string;
299
+ };
300
+ }
301
+
302
+ // Search params type compatible with both Next.js resolved searchParams and URLSearchParams
303
+ export type SearchParams = Record<string, string | string[] | undefined> | URLSearchParams;
304
+
305
+ // Resolved (sync) versions for inner components after withSegmentDefaults resolves props
306
+ export interface ResolvedPageProps<T = any> {
307
+ params: T & { locale: string; currency: string };
308
+ searchParams: SearchParams;
309
+ }
310
+
311
+ export interface ResolvedLayoutProps<T = any> extends ResolvedPageProps<T> {
312
+ children: React.ReactNode;
313
+ }
314
+
315
+ export interface ResolvedRootLayoutProps<
253
316
  T = {
254
317
  commerce: string;
255
318
  locale: string;
256
319
  currency: string;
257
320
  }
258
- > extends LayoutProps<T> {
321
+ > extends ResolvedLayoutProps<T> {
259
322
  translations: Translations;
260
323
  locale: Locale & {
261
324
  isoCode: string;
@@ -283,7 +346,13 @@ export interface ButtonProps
283
346
  target?: '_blank' | '_self' | '_parent' | '_top';
284
347
  }
285
348
 
286
- export type FileInputProps = React.HTMLProps<HTMLInputElement>;
349
+ export interface FileInputProps extends React.HTMLProps<HTMLInputElement> {
350
+ fileClassName?: string;
351
+ fileNameWrapperClassName?: string;
352
+ fileInputClassName?: string;
353
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
354
+ buttonClassName?: string;
355
+ }
287
356
 
288
357
  export interface PriceProps {
289
358
  currencyCode?: string;
@@ -304,15 +373,19 @@ export interface InputProps extends React.HTMLProps<HTMLInputElement> {
304
373
 
305
374
  export interface AccordionProps {
306
375
  isCollapse?: boolean;
376
+ collapseClassName?: string;
307
377
  title?: string;
308
378
  subTitle?: string;
309
379
  icons?: string[];
310
380
  iconSize?: number;
311
381
  iconColor?: string;
312
382
  children?: ReactNode;
383
+ headerClassName?: string;
313
384
  className?: string;
314
385
  titleClassName?: string;
386
+ subTitleClassName?: string;
315
387
  dataTestId?: string;
388
+ contentClassName?: string;
316
389
  }
317
390
 
318
391
  export interface PluginModuleComponentProps {
@@ -337,3 +410,20 @@ export interface PaginationProps {
337
410
  direction?: 'next' | 'prev';
338
411
  isLoading?: boolean;
339
412
  }
413
+
414
+ export interface ModalProps {
415
+ portalId: string;
416
+ children?: React.ReactNode;
417
+ open?: boolean;
418
+ setOpen?: (open: boolean) => void;
419
+ title?: React.ReactNode;
420
+ showCloseButton?: React.ReactNode;
421
+ className?: string;
422
+ overlayClassName?: string;
423
+ headerWrapperClassName?: string;
424
+ titleClassName?: string;
425
+ closeButtonClassName?: string;
426
+ iconName?: string;
427
+ iconSize?: number;
428
+ iconClassName?: string;
429
+ }
@@ -1,8 +1,8 @@
1
- import NextAuth from 'next-auth';
1
+ import 'next-auth';
2
2
 
3
3
  declare module 'next-auth' {
4
4
  interface User {
5
- id?: number;
5
+ id?: string;
6
6
  pk: number;
7
7
  firstName: string;
8
8
  lastName: string;
@@ -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 = {
@@ -1,13 +1,14 @@
1
+ import { SearchParams } from '../types';
1
2
  import logger from './log';
2
3
 
3
4
  export const generateCommerceSearchParams = (
4
- searchParams?: URLSearchParams
5
+ searchParams?: SearchParams
5
6
  ) => {
6
7
  if (!searchParams) {
7
8
  return null;
8
9
  }
9
10
 
10
- const urlSerchParams = new URLSearchParams(searchParams);
11
+ const urlSerchParams = new URLSearchParams(searchParams as any);
11
12
 
12
13
  Object.entries(searchParams).forEach(([key, value]) => {
13
14
  if (typeof value === 'object') {
@@ -0,0 +1,3 @@
1
+ export const getCheckoutPath = (isPostCheckout: boolean): string => {
2
+ return isPostCheckout ? '/orders/post-checkout/' : '/orders/checkout/';
3
+ };
package/utils/index.ts CHANGED
@@ -1,11 +1,14 @@
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';
7
8
  export * from './generate-commerce-search-params';
8
9
  export * from './get-currency-label';
10
+ export * from './pz-segments';
11
+ export * from './get-checkout-path';
9
12
 
10
13
  export function getCookie(name: string) {
11
14
  if (typeof document === 'undefined') {
@@ -20,14 +23,40 @@ export function getCookie(name: string) {
20
23
  }
21
24
  }
22
25
 
23
- export function setCookie(name: string, val: string) {
24
- const date = new Date();
25
- const value = val;
26
+ export function setCookie(
27
+ name: string,
28
+ value: string,
29
+ options: SetCookieOptions = {}
30
+ ) {
31
+ const cookieParts = [`${name}=${value}`];
32
+
33
+ if (options.expires) {
34
+ const date = new Date();
35
+ date.setTime(date.getTime() + options.expires * 24 * 60 * 60 * 1000);
36
+ cookieParts.push(`expires=${date.toUTCString()}`);
37
+ }
26
38
 
27
- date.setTime(date.getTime() + 7 * 24 * 60 * 60 * 1000);
39
+ cookieParts.push(`path=${options.path ?? '/'}`);
28
40
 
29
- document.cookie =
30
- name + '=' + value + '; expires=' + date.toUTCString() + '; path=/';
41
+ if (options.secure) {
42
+ cookieParts.push('secure');
43
+ }
44
+
45
+ if (options.sameSite) {
46
+ cookieParts.push(`sameSite=${options.sameSite}`);
47
+ }
48
+
49
+ const domain =
50
+ options.domain ??
51
+ (settings.localization.localeUrlStrategy === LocaleUrlStrategy.Subdomain
52
+ ? getRootHostname(document.location.href)
53
+ : null);
54
+
55
+ if (domain) {
56
+ cookieParts.push(`domain=${domain}`);
57
+ }
58
+
59
+ document.cookie = cookieParts.join('; ');
31
60
  }
32
61
 
33
62
  export function removeCookie(name: string) {
@@ -152,9 +181,6 @@ export function buildCDNUrl(url: string, config?: CDNOptions) {
152
181
  return `${rootWithoutOptions}${options}${fileExtension}`;
153
182
  }
154
183
 
155
- const { locales, localeUrlStrategy, defaultLocaleValue } =
156
- settings.localization;
157
-
158
184
  export const urlLocaleMatcherRegex = new RegExp(
159
185
  `^/(${settings.localization.locales
160
186
  .filter((l) =>
@@ -169,7 +195,8 @@ export const urlLocaleMatcherRegex = new RegExp(
169
195
  );
170
196
 
171
197
  export const getPosError = () => {
172
- const error = JSON.parse(getCookie('pz-pos-error') ?? '{}');
198
+ const cookieValue = getCookie('pz-pos-error');
199
+ const error = JSON.parse(cookieValue ? decodeURIComponent(cookieValue) : '{}');
173
200
 
174
201
  // delete 'pz-pos-error' cookie when refreshing or closing page
175
202
  window.addEventListener('beforeunload', () => {
@@ -10,6 +10,7 @@ export enum MiddlewareNames {
10
10
  DataSourceShippingOptionMiddleware = 'dataSourceShippingOptionMiddleware',
11
11
  AttributeBasedShippingOptionMiddleware = 'attributeBasedShippingOptionMiddleware',
12
12
  PaymentOptionMiddleware = 'paymentOptionMiddleware',
13
+ PaymentOptionResetMiddleware = 'paymentOptionResetMiddleware',
13
14
  InstallmentOptionMiddleware = 'installmentOptionMiddleware',
14
15
  ShippingStepMiddleware = 'shippingStepMiddleware'
15
16
  }
@@ -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
+ }