@akinon/next 1.45.0 → 1.47.0-rc.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +367 -0
  2. package/api/client.ts +39 -9
  3. package/assets/styles/index.css +49 -0
  4. package/assets/styles/index.css.map +1 -0
  5. package/assets/styles/index.scss +50 -26
  6. package/components/file-input.tsx +8 -0
  7. package/components/index.ts +1 -0
  8. package/components/input.tsx +21 -7
  9. package/components/link.tsx +17 -13
  10. package/components/pagination.tsx +1 -2
  11. package/components/price.tsx +11 -4
  12. package/components/selected-payment-option-view.tsx +26 -38
  13. package/data/client/account.ts +10 -9
  14. package/data/client/address.ts +32 -8
  15. package/data/client/api.ts +2 -2
  16. package/data/client/b2b.ts +35 -2
  17. package/data/client/basket.ts +6 -5
  18. package/data/client/checkout.ts +47 -4
  19. package/data/client/wishlist.ts +70 -2
  20. package/data/server/category.ts +2 -2
  21. package/data/server/list.ts +2 -2
  22. package/data/server/product.ts +15 -13
  23. package/data/server/special-page.ts +2 -2
  24. package/data/urls.ts +17 -3
  25. package/hooks/index.ts +2 -1
  26. package/hooks/use-message-listener.ts +24 -0
  27. package/hooks/use-payment-options.ts +2 -1
  28. package/lib/cache-handler.mjs +33 -0
  29. package/lib/cache.ts +18 -6
  30. package/middlewares/default.ts +50 -2
  31. package/middlewares/locale.ts +32 -30
  32. package/middlewares/pretty-url.ts +4 -0
  33. package/middlewares/url-redirection.ts +4 -0
  34. package/package.json +5 -4
  35. package/plugins.d.ts +1 -0
  36. package/redux/middlewares/checkout.ts +70 -11
  37. package/redux/reducers/checkout.ts +24 -5
  38. package/redux/reducers/config.ts +2 -0
  39. package/routes/pretty-url.tsx +194 -0
  40. package/types/commerce/account.ts +1 -0
  41. package/types/commerce/address.ts +1 -1
  42. package/types/commerce/b2b.ts +12 -2
  43. package/types/commerce/checkout.ts +30 -0
  44. package/types/commerce/misc.ts +2 -0
  45. package/types/commerce/order.ts +12 -0
  46. package/types/index.ts +30 -2
  47. package/utils/app-fetch.ts +1 -1
  48. package/utils/generate-commerce-search-params.ts +6 -2
  49. package/utils/index.ts +27 -6
  50. package/utils/menu-generator.ts +2 -2
  51. package/utils/redirection-iframe.ts +85 -0
  52. package/utils/server-translation.ts +5 -1
  53. package/with-pz-config.js +11 -1
@@ -1,15 +1,12 @@
1
1
  import { Cache, CacheKey } from '../../lib/cache';
2
2
  import { product } from '../urls';
3
- import {
4
- BreadcrumbResultType,
5
- ProductCategoryResult,
6
- ProductResult
7
- } from '../../types';
3
+ import { ProductCategoryResult, ProductResult } from '../../types';
8
4
  import appFetch from '../../utils/app-fetch';
5
+ import logger from '../../utils/log';
9
6
 
10
7
  type GetProduct = {
11
8
  pk: number;
12
- searchParams?: URLSearchParams;
9
+ searchParams?: { [key: string]: string | string[] | undefined };
13
10
  groupProduct?: boolean;
14
11
  };
15
12
 
@@ -50,9 +47,17 @@ const getProductDataHandler = ({
50
47
  }
51
48
  );
52
49
 
53
- const breadcrumbUrl = product.breadcrumbUrl(
54
- productCategoryData.results[0].menuitemmodel
55
- );
50
+ const menuItemModel = productCategoryData?.results[0]?.menuitemmodel;
51
+
52
+ if (!menuItemModel) {
53
+ logger.warn('menuItemModel is undefined, skipping breadcrumbData fetch', {
54
+ handler: 'getProductDataHandler',
55
+ pk
56
+ });
57
+ return { data, breadcrumbData: undefined };
58
+ }
59
+
60
+ const breadcrumbUrl = product.breadcrumbUrl(menuItemModel);
56
61
 
57
62
  const breadcrumbData = await appFetch<any>(breadcrumbUrl, {
58
63
  headers: {
@@ -74,10 +79,7 @@ export const getProductData = async ({
74
79
  groupProduct
75
80
  }: GetProduct) => {
76
81
  return Cache.wrap(
77
- CacheKey[groupProduct ? 'GroupProduct' : 'Product'](
78
- pk,
79
- searchParams ?? new URLSearchParams()
80
- ),
82
+ CacheKey[groupProduct ? 'GroupProduct' : 'Product'](pk, searchParams ?? {}),
81
83
  getProductDataHandler({ pk, searchParams, groupProduct }),
82
84
  {
83
85
  expire: 300
@@ -7,7 +7,7 @@ import header from '../../redux/reducers/header';
7
7
 
8
8
  const getSpecialPageDataHandler = (
9
9
  pk: number,
10
- searchParams: URLSearchParams,
10
+ searchParams: { [key: string]: string | string[] | undefined },
11
11
  headers?: Record<string, string>
12
12
  ) => {
13
13
  return async function () {
@@ -34,7 +34,7 @@ export const getSpecialPageData = async ({
34
34
  headers
35
35
  }: {
36
36
  pk: number;
37
- searchParams: URLSearchParams;
37
+ searchParams: { [key: string]: string | string[] | undefined };
38
38
  headers?: Record<string, string>;
39
39
  }) => {
40
40
  return Cache.wrap(
package/data/urls.ts CHANGED
@@ -83,6 +83,8 @@ export const checkout = {
83
83
  setDeliveryOption: '/orders/checkout/?page=DeliveryOptionSelectionPage',
84
84
  setAddresses: '/orders/checkout/?page=AddressSelectionPage',
85
85
  setShippingOption: '/orders/checkout/?page=ShippingOptionSelectionPage',
86
+ setDataSourceShippingOption:
87
+ '/orders/checkout/?page=DataSourceShippingOptionSelectionPage',
86
88
  setPaymentOption: '/orders/checkout/?page=PaymentOptionSelectionPage',
87
89
  setBinNumber: '/orders/checkout/?page=BinNumberPage',
88
90
  setMasterpassBinNumber: '/orders/checkout/?page=MasterpassBinNumberPage',
@@ -108,7 +110,9 @@ export const checkout = {
108
110
  completeLoyaltyPayment: '/orders/checkout/?page=LoyaltyPaymentSelectedPage',
109
111
  loyaltyMoneyUsage: '/orders/checkout/?page=LoyaltyMoneyUsagePage',
110
112
  setOrderNote: '/orders/checkout/?page=OrderNotePage',
111
- couponSelectionPage: '/orders/checkout/?page=CouponSelectionPage'
113
+ couponSelectionPage: '/orders/checkout/?page=CouponSelectionPage',
114
+ setAttributeBasedShippingOption:
115
+ '/orders/checkout/?page=AttributeBasedShippingOptionSelectionPage'
112
116
  };
113
117
 
114
118
  export const flatpage = {
@@ -161,7 +165,14 @@ export const wishlist = {
161
165
  page?: number;
162
166
  limit?: number;
163
167
  }) => `/wishlists/product-alerts/?limit=${limit}&page=${page}`,
164
- deleteStockAlert: (pk: number) => `/wishlists/product-alerts/${pk}/`
168
+ deleteStockAlert: (pk: number) => `/wishlists/product-alerts/${pk}/`,
169
+ setCollection: () => '/wishlists/user-collections/',
170
+ setCollectionItems: '/wishlists/user-collection-items/',
171
+ getCollections: (search?: string, product_id?: number) =>
172
+ `/wishlists/user-collections/?search=${search ? search : ''}&product_id=${
173
+ product_id ? product_id : ''
174
+ }`,
175
+ deleteCollection: (pk: number) => `/wishlists/user-collections/${pk}/`
165
176
  };
166
177
 
167
178
  export const user = {
@@ -187,7 +198,10 @@ export const b2b = {
187
198
  draftBaskets: '/b2b/basket/drafts/',
188
199
  divisions: '/b2b/my-divisions/',
189
200
  myQuotations: '/b2b/my-quotations/',
190
- loadBasket: (id) => `/b2b/basket/${id}/load/`
201
+ loadBasket: (id) => `/b2b/basket/${id}/load/`,
202
+ statusBasket: (cacheKey) => `/b2b/basket/?status_cache_key=${cacheKey}`,
203
+ basketExport: (queryString) => `/b2b/basket/?upload=true&${queryString}`,
204
+ basketImport: '/b2b/basket/bulk-import/'
191
205
  };
192
206
 
193
207
  export const widgets = {
package/hooks/index.ts CHANGED
@@ -8,4 +8,5 @@ export * from './use-media-query';
8
8
  export * from './use-on-click-outside';
9
9
  export * from './use-mobile-iframe-handler';
10
10
  export * from './use-payment-options';
11
- export * from './use-pagination';
11
+ export * from './use-pagination';
12
+ export * from './use-message-listener';
@@ -0,0 +1,24 @@
1
+ import { useEffect } from 'react';
2
+
3
+ export const useMessageListener = () => {
4
+ useEffect(() => {
5
+ const handleMessage = (event: MessageEvent) => {
6
+ if (event.origin !== window.location.origin) {
7
+ return;
8
+ }
9
+
10
+ if (event.data && typeof event.data === 'string') {
11
+ const messageData = JSON.parse(event.data);
12
+ if (messageData?.url) {
13
+ window.location.href = messageData.url;
14
+ }
15
+ }
16
+ };
17
+
18
+ window.addEventListener('message', handleMessage);
19
+
20
+ return () => {
21
+ window.removeEventListener('message', handleMessage);
22
+ };
23
+ }, []);
24
+ };
@@ -18,7 +18,8 @@ export const usePaymentOptions = () => {
18
18
  pay_on_delivery: 'pz-pay-on-delivery',
19
19
  bkm_express: 'pz-bkm',
20
20
  credit_payment: 'pz-credit-payment',
21
- masterpass: 'pz-masterpass'
21
+ masterpass: 'pz-masterpass',
22
+ gpay: 'pz-gpay'
22
23
  };
23
24
 
24
25
  const isInitialTypeIncluded = (type: string) => initialTypes.has(type);
@@ -0,0 +1,33 @@
1
+ import { CacheHandler } from '@neshca/cache-handler';
2
+ import createLruHandler from '@neshca/cache-handler/local-lru';
3
+ import createRedisHandler from '@neshca/cache-handler/redis-stack';
4
+ import { createClient } from 'redis';
5
+
6
+ CacheHandler.onCreation(async () => {
7
+ const redisUrl = `redis://${process.env.CACHE_HOST}:${
8
+ process.env.CACHE_PORT
9
+ }/${process.env.CACHE_BUCKET ?? '0'}`;
10
+
11
+ const client = createClient({
12
+ url: redisUrl
13
+ });
14
+
15
+ client.on('error', (error) => {
16
+ console.error('Redis client error', { redisUrl, error });
17
+ });
18
+
19
+ await client.connect();
20
+
21
+ const redisHandler = await createRedisHandler({
22
+ client,
23
+ timeoutMs: 5000
24
+ });
25
+
26
+ const localHandler = createLruHandler();
27
+
28
+ return {
29
+ handlers: [redisHandler, localHandler]
30
+ };
31
+ });
32
+
33
+ export default CacheHandler;
package/lib/cache.ts CHANGED
@@ -20,13 +20,16 @@ const hashCacheKey = (object?: Record<string, string>) => {
20
20
  return `_${encodeURIComponent(cacheKey)}`;
21
21
  };
22
22
  export const CacheKey = {
23
- List: (searchParams: URLSearchParams, headers?: Record<string, string>) =>
23
+ List: (
24
+ searchParams: { [key: string]: string | string[] | undefined },
25
+ headers?: Record<string, string>
26
+ ) =>
24
27
  `list_${encodeURIComponent(JSON.stringify(searchParams))}${hashCacheKey(
25
28
  headers
26
29
  )}`,
27
30
  Category: (
28
31
  pk: number,
29
- searchParams?: URLSearchParams,
32
+ searchParams?: { [key: string]: string | string[] | undefined },
30
33
  headers?: Record<string, string>
31
34
  ) =>
32
35
  `category_${pk}_${encodeURIComponent(
@@ -35,15 +38,20 @@ export const CacheKey = {
35
38
  CategorySlug: (slug: string) => `category_${slug}`,
36
39
  SpecialPage: (
37
40
  pk: number,
38
- searchParams: URLSearchParams,
41
+ searchParams: { [key: string]: string | string[] | undefined },
39
42
  headers?: Record<string, string>
40
43
  ) =>
41
44
  `special_page_${pk}_${encodeURIComponent(
42
45
  JSON.stringify(searchParams)
43
46
  )}${hashCacheKey(headers)}`,
44
- Product: (pk: number, searchParams: URLSearchParams) =>
45
- `product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
46
- GroupProduct: (pk: number, searchParams: URLSearchParams) =>
47
+ Product: (
48
+ pk: number,
49
+ searchParams: { [key: string]: string | string[] | undefined }
50
+ ) => `product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
51
+ GroupProduct: (
52
+ pk: number,
53
+ searchParams: { [key: string]: string | string[] | undefined }
54
+ ) =>
47
55
  `group_product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
48
56
  FlatPage: (pk: number) => `flat_page_${pk}`,
49
57
  LandingPage: (pk: number) => `landing_page_${pk}`,
@@ -158,6 +166,10 @@ export class Cache {
158
166
  handler: () => Promise<T>,
159
167
  options?: CacheOptions
160
168
  ): Promise<T> {
169
+ if (Settings.usePrettyUrlRoute) {
170
+ return await handler();
171
+ }
172
+
161
173
  const requiredVariables = [
162
174
  process.env.CACHE_HOST,
163
175
  process.env.CACHE_PORT,
@@ -69,6 +69,10 @@ const withPzDefault =
69
69
  return NextResponse.json({ status: 'ok' });
70
70
  }
71
71
 
72
+ if (url.pathname.startsWith('/metrics')) {
73
+ return new NextResponse(null, { status: 200 });
74
+ }
75
+
72
76
  if (req.nextUrl.pathname.startsWith('/.well-known')) {
73
77
  const url = new URL(`${Settings.commerceUrl}${req.nextUrl.pathname}`);
74
78
  const req_ = await fetch(url.toString());
@@ -84,14 +88,44 @@ const withPzDefault =
84
88
  req.nextUrl.pathname.includes('/orders/hooks/') ||
85
89
  req.nextUrl.pathname.includes('/orders/checkout-with-token/')
86
90
  ) {
87
- return NextResponse.rewrite(
91
+ const segment = url.searchParams.get('segment');
92
+ const currency = url.searchParams.get('currency')?.toLowerCase();
93
+
94
+ const headers = {};
95
+
96
+ if (segment) {
97
+ headers['X-Segment-Id'] = segment;
98
+ }
99
+
100
+ if (currency) {
101
+ headers['x-currency'] = currency;
102
+ }
103
+
104
+ const response = NextResponse.rewrite(
88
105
  new URL(
89
106
  `${Settings.commerceUrl}${req.nextUrl.pathname.replace(
90
107
  urlLocaleMatcherRegex,
91
108
  ''
92
109
  )}`
93
- )
110
+ ),
111
+ {
112
+ headers
113
+ }
94
114
  );
115
+
116
+ if (segment) {
117
+ response.cookies.set('pz-segment', segment);
118
+ }
119
+
120
+ if (currency) {
121
+ response.cookies.set('pz-currency', currency, {
122
+ sameSite: 'none',
123
+ secure: true,
124
+ expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7) // 7 days
125
+ });
126
+ }
127
+
128
+ return response;
95
129
  }
96
130
 
97
131
  if (req.nextUrl.pathname.startsWith('/orders/redirection/')) {
@@ -203,6 +237,20 @@ const withPzDefault =
203
237
  );
204
238
  }
205
239
 
240
+ if (
241
+ Settings.usePrettyUrlRoute &&
242
+ url.searchParams.toString().length > 0 &&
243
+ !Object.entries(ROUTES).find(([, value]) =>
244
+ new RegExp(`^${value}/?$`).test(
245
+ pathnameWithoutLocale
246
+ )
247
+ )
248
+ ) {
249
+ url.pathname =
250
+ url.pathname +
251
+ `searchparams|${url.searchParams.toString()}`;
252
+ }
253
+
206
254
  Settings.rewrites.forEach((rewrite) => {
207
255
  url.pathname = url.pathname.replace(
208
256
  rewrite.source,
@@ -5,22 +5,6 @@ import { LocaleUrlStrategy } from '../localization';
5
5
  import { urlLocaleMatcherRegex } from '../utils';
6
6
  import logger from '../utils/log';
7
7
 
8
- const getMatchedLocale = (pathname: string) => {
9
- let matchedLocale = pathname.match(urlLocaleMatcherRegex)?.[0] ?? '';
10
- matchedLocale = matchedLocale.replace('/', '');
11
-
12
- if (!matchedLocale.length) {
13
- if (
14
- settings.localization.localeUrlStrategy !==
15
- LocaleUrlStrategy.ShowAllLocales
16
- ) {
17
- matchedLocale = settings.localization.defaultLocaleValue;
18
- }
19
- }
20
-
21
- return matchedLocale;
22
- };
23
-
24
8
  const withLocale =
25
9
  (middleware: NextMiddleware) =>
26
10
  async (req: PzNextRequest, event: NextFetchEvent) => {
@@ -28,12 +12,12 @@ const withLocale =
28
12
 
29
13
  try {
30
14
  const url = req.nextUrl.clone();
31
- const matchedLocale = getMatchedLocale(url.pathname);
32
- let { localeUrlStrategy, defaultLocaleValue, redirectToDefaultLocale } =
33
- settings.localization;
34
-
35
- localeUrlStrategy =
36
- localeUrlStrategy ?? LocaleUrlStrategy.HideDefaultLocale;
15
+ const {
16
+ locales,
17
+ localeUrlStrategy,
18
+ defaultLocaleValue,
19
+ redirectToDefaultLocale
20
+ } = settings.localization;
37
21
 
38
22
  if (!defaultLocaleValue) {
39
23
  logger.error('Default locale value is not defined in settings.', {
@@ -45,16 +29,34 @@ const withLocale =
45
29
  }
46
30
 
47
31
  if (
48
- !matchedLocale?.length &&
49
- localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales &&
50
- redirectToDefaultLocale &&
51
- req.method === 'GET'
32
+ locales.length === 1 &&
33
+ localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales
52
34
  ) {
53
- url.pathname = `/${defaultLocaleValue}${url.pathname}`;
54
- return NextResponse.redirect(url);
55
- }
35
+ const singleLocale = locales[0].value;
36
+ const pathParts = url.pathname.split('/').filter(Boolean);
56
37
 
57
- req.middlewareParams.rewrites.locale = matchedLocale;
38
+ if (pathParts[0] === singleLocale) {
39
+ url.pathname = '/' + pathParts.slice(1).join('/');
40
+ return NextResponse.redirect(url);
41
+ }
42
+
43
+ req.middlewareParams.rewrites.locale = singleLocale;
44
+ } else {
45
+ const matchedLocale =
46
+ url.pathname.match(urlLocaleMatcherRegex)?.[0]?.replace('/', '') ||
47
+ '';
48
+ if (
49
+ !matchedLocale &&
50
+ localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales &&
51
+ redirectToDefaultLocale &&
52
+ req.method === 'GET'
53
+ ) {
54
+ url.pathname = `/${defaultLocaleValue}${url.pathname}`;
55
+ return NextResponse.redirect(url);
56
+ }
57
+ req.middlewareParams.rewrites.locale =
58
+ matchedLocale || defaultLocaleValue;
59
+ }
58
60
  } catch (error) {
59
61
  logger.error('withLocale error', {
60
62
  error,
@@ -56,6 +56,10 @@ const resolvePrettyUrl = async (pathname: string, ip: string | null) => {
56
56
  const withPrettyUrl =
57
57
  (middleware: NextMiddleware) =>
58
58
  async (req: PzNextRequest, event: NextFetchEvent) => {
59
+ if (Settings.usePrettyUrlRoute) {
60
+ return middleware(req, event);
61
+ }
62
+
59
63
  const url = req.nextUrl.clone();
60
64
  const matchedLanguagePrefix = url.pathname.match(
61
65
  urlLocaleMatcherRegex
@@ -11,6 +11,10 @@ import { ROUTES } from 'routes';
11
11
  const withUrlRedirection =
12
12
  (middleware: NextMiddleware) =>
13
13
  async (req: PzNextRequest, event: NextFetchEvent) => {
14
+ if (settings.usePrettyUrlRoute) {
15
+ return middleware(req, event);
16
+ }
17
+
14
18
  const url = req.nextUrl.clone();
15
19
  const ip = req.headers.get('x-forwarded-for') ?? '';
16
20
  const pathnameWithoutLocale = url.pathname.replace(
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.45.0",
4
+ "version": "1.47.0-rc.0",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -20,21 +20,22 @@
20
20
  "@opentelemetry/sdk-trace-node": "1.19.0",
21
21
  "@opentelemetry/semantic-conventions": "1.19.0",
22
22
  "@reduxjs/toolkit": "1.9.7",
23
+ "@neshca/cache-handler": "1.5.1",
23
24
  "cross-spawn": "7.0.3",
24
25
  "generic-pool": "3.9.0",
25
26
  "react-redux": "8.1.3",
26
27
  "react-string-replace": "1.1.1",
27
- "redis": "4.5.1",
28
- "semver": "7.5.4",
28
+ "redis": "4.6.13",
29
+ "semver": "7.6.2",
29
30
  "set-cookie-parser": "2.6.0"
30
31
  },
31
32
  "devDependencies": {
33
+ "@akinon/eslint-plugin-projectzero": "1.47.0-rc.0",
32
34
  "@types/react-redux": "7.1.30",
33
35
  "@types/set-cookie-parser": "2.4.7",
34
36
  "@typescript-eslint/eslint-plugin": "6.7.4",
35
37
  "@typescript-eslint/parser": "6.7.4",
36
38
  "eslint": "^8.14.0",
37
- "@akinon/eslint-plugin-projectzero": "1.45.0",
38
39
  "eslint-config-prettier": "8.5.0"
39
40
  }
40
41
  }
package/plugins.d.ts CHANGED
@@ -12,6 +12,7 @@ declare module '@akinon/pz-masterpass/src/utils' {
12
12
  declare module '@akinon/pz-masterpass/src/redux/reducer' {
13
13
  export const setError: any;
14
14
  export const setOtpModalVisible: any;
15
+ export const setCvcRequired: any;
15
16
  }
16
17
 
17
18
  declare module '@akinon/pz-otp' {
@@ -16,8 +16,10 @@ import {
16
16
  setPreOrder,
17
17
  setRetailStores,
18
18
  setShippingOptions,
19
+ setDataSourceShippingOptions,
19
20
  setShippingStepCompleted,
20
- setCreditPaymentOptions
21
+ setCreditPaymentOptions,
22
+ setAttributeBasedShippingOptions
21
23
  } from '../../redux/reducers/checkout';
22
24
  import { RootState, TypedDispatch } from 'redux/store';
23
25
  import { checkoutApi } from '../../data/client/checkout';
@@ -26,10 +28,11 @@ import { getCookie, setCookie } from '../../utils';
26
28
  import settings from 'settings';
27
29
  import { LocaleUrlStrategy } from '../../localization';
28
30
  import { showMobile3dIframe } from '../../utils/mobile-3d-iframe';
31
+ import { showRedirectionIframe } from '../../utils/redirection-iframe';
29
32
 
30
33
  interface CheckoutResult {
31
34
  payload: {
32
- errors?: any;
35
+ errors?: Record<string, string[]>;
33
36
  pre_order?: PreOrder;
34
37
  context_list?: CheckoutContext[];
35
38
  };
@@ -73,8 +76,10 @@ export const preOrderMiddleware: Middleware = ({
73
76
  deliveryOptions,
74
77
  addressList: addresses,
75
78
  shippingOptions,
79
+ dataSourceShippingOptions,
76
80
  paymentOptions,
77
- installmentOptions
81
+ installmentOptions,
82
+ attributeBasedShippingOptions
78
83
  } = getState().checkout;
79
84
  const { endpoints: apiEndpoints } = checkoutApi;
80
85
 
@@ -125,6 +130,40 @@ export const preOrderMiddleware: Middleware = ({
125
130
  dispatch(apiEndpoints.setShippingOption.initiate(shippingOptions[0].pk));
126
131
  }
127
132
 
133
+ if (
134
+ dataSourceShippingOptions.length > 0 &&
135
+ !preOrder.data_source_shipping_options
136
+ ) {
137
+ const selectedDataSourceShippingOptionsPks =
138
+ dataSourceShippingOptions.map(
139
+ (opt) => opt.data_source_shipping_options[0].pk
140
+ );
141
+
142
+ dispatch(
143
+ apiEndpoints.setDataSourceShippingOptions.initiate(
144
+ selectedDataSourceShippingOptionsPks
145
+ )
146
+ );
147
+ }
148
+
149
+ if (
150
+ Object.keys(attributeBasedShippingOptions).length > 0 &&
151
+ !preOrder.attribute_based_shipping_options
152
+ ) {
153
+ const initialSelectedOptions: Record<string, number> = Object.fromEntries(
154
+ Object.entries(attributeBasedShippingOptions).map(([key, options]) => [
155
+ key,
156
+ options[0].pk
157
+ ])
158
+ );
159
+
160
+ dispatch(
161
+ apiEndpoints.setAttributeBasedShippingOptions.initiate(
162
+ initialSelectedOptions
163
+ )
164
+ );
165
+ }
166
+
128
167
  if (!preOrder.payment_option && paymentOptions.length > 0) {
129
168
  dispatch(apiEndpoints.setPaymentOption.initiate(paymentOptions[0].pk));
130
169
  }
@@ -163,6 +202,7 @@ export const contextListMiddleware: Middleware = ({
163
202
  if (result?.payload?.context_list) {
164
203
  result.payload.context_list.forEach((context) => {
165
204
  const redirectUrl = context.page_context.redirect_url;
205
+ const isIframe = context.page_context.is_frame ?? false;
166
206
 
167
207
  if (redirectUrl) {
168
208
  const currentLocale = getCookie('pz-locale');
@@ -181,16 +221,19 @@ export const contextListMiddleware: Middleware = ({
181
221
  }
182
222
 
183
223
  const urlObj = new URL(url, window.location.origin);
184
- urlObj.searchParams.set('t', new Date().getTime().toString());
185
-
186
- if (
187
- (isMobileApp ||
188
- /iPad|iPhone|iPod|Android/i.test(navigator.userAgent)) &&
224
+ const isMobileDevice =
225
+ isMobileApp ||
226
+ /iPad|iPhone|iPod|Android/i.test(navigator.userAgent);
227
+ const isIframePaymentOptionExcluded =
189
228
  !settings.checkout?.iframeExcludedPaymentOptions?.includes(
190
229
  result.payload?.pre_order?.payment_option?.slug
191
- )
192
- ) {
230
+ );
231
+ urlObj.searchParams.set('t', new Date().getTime().toString());
232
+
233
+ if (isMobileDevice && !isIframePaymentOptionExcluded) {
193
234
  showMobile3dIframe(urlObj.toString());
235
+ } else if (isIframe && !isIframePaymentOptionExcluded) {
236
+ showRedirectionIframe(urlObj.toString());
194
237
  } else {
195
238
  window.location.href = urlObj.toString();
196
239
  }
@@ -220,12 +263,28 @@ export const contextListMiddleware: Middleware = ({
220
263
  dispatch(setShippingOptions(context.page_context.shipping_options));
221
264
  }
222
265
 
266
+ if (context.page_context.data_sources) {
267
+ dispatch(
268
+ setDataSourceShippingOptions(context.page_context.data_sources)
269
+ );
270
+ }
271
+
272
+ if (context.page_context.attribute_based_shipping_options) {
273
+ dispatch(
274
+ setAttributeBasedShippingOptions(
275
+ context.page_context.attribute_based_shipping_options
276
+ )
277
+ );
278
+ }
279
+
223
280
  if (context.page_context.payment_options) {
224
281
  dispatch(setPaymentOptions(context.page_context.payment_options));
225
282
  }
226
283
 
227
284
  if (context.page_context.credit_payment_options) {
228
- dispatch(setCreditPaymentOptions(context.page_context.credit_payment_options));
285
+ dispatch(
286
+ setCreditPaymentOptions(context.page_context.credit_payment_options)
287
+ );
229
288
  }
230
289
 
231
290
  if (context.page_context.payment_choices) {