@akinon/next 1.58.0 → 1.59.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 (55) hide show
  1. package/CHANGELOG.md +652 -0
  2. package/api/client.ts +23 -2
  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/bin/pz-generate-translations.js +41 -0
  7. package/bin/pz-prebuild.js +1 -0
  8. package/bin/pz-predev.js +1 -0
  9. package/components/file-input.tsx +8 -0
  10. package/components/index.ts +1 -0
  11. package/components/input.tsx +21 -7
  12. package/components/link.tsx +17 -13
  13. package/components/price.tsx +11 -4
  14. package/components/pz-root.tsx +15 -3
  15. package/data/client/api.ts +1 -1
  16. package/data/client/b2b.ts +35 -2
  17. package/data/client/basket.ts +6 -5
  18. package/data/client/checkout.ts +37 -0
  19. package/data/client/user.ts +3 -2
  20. package/data/server/category.ts +43 -19
  21. package/data/server/flatpage.ts +29 -7
  22. package/data/server/form.ts +29 -11
  23. package/data/server/landingpage.ts +26 -7
  24. package/data/server/list.ts +16 -6
  25. package/data/server/menu.ts +15 -2
  26. package/data/server/product.ts +33 -13
  27. package/data/server/seo.ts +17 -24
  28. package/data/server/special-page.ts +15 -5
  29. package/data/server/widget.ts +14 -7
  30. package/data/urls.ts +8 -1
  31. package/hocs/server/with-segment-defaults.tsx +4 -1
  32. package/hooks/index.ts +2 -1
  33. package/hooks/use-message-listener.ts +24 -0
  34. package/hooks/use-pagination.ts +2 -2
  35. package/lib/cache-handler.mjs +33 -0
  36. package/lib/cache.ts +8 -6
  37. package/middlewares/default.ts +46 -2
  38. package/middlewares/pretty-url.ts +11 -1
  39. package/middlewares/url-redirection.ts +4 -0
  40. package/package.json +4 -3
  41. package/plugins.d.ts +1 -0
  42. package/redux/middlewares/checkout.ts +71 -11
  43. package/redux/reducers/checkout.ts +23 -3
  44. package/routes/pretty-url.tsx +192 -0
  45. package/types/commerce/address.ts +1 -1
  46. package/types/commerce/b2b.ts +12 -2
  47. package/types/commerce/checkout.ts +30 -0
  48. package/types/commerce/order.ts +1 -0
  49. package/types/index.ts +17 -2
  50. package/utils/app-fetch.ts +16 -8
  51. package/utils/generate-commerce-search-params.ts +3 -1
  52. package/utils/index.ts +27 -6
  53. package/utils/redirection-iframe.ts +85 -0
  54. package/utils/server-translation.ts +11 -1
  55. package/with-pz-config.js +13 -2
package/api/client.ts CHANGED
@@ -4,6 +4,7 @@ import settings from 'settings';
4
4
  import logger from '../utils/log';
5
5
  import formatCookieString from '../utils/format-cookie-string';
6
6
  import cookieParser from 'set-cookie-parser';
7
+ import { cookies } from 'next/headers';
7
8
 
8
9
  interface RouteParams {
9
10
  params: {
@@ -80,11 +81,27 @@ async function proxyRequest(...args) {
80
81
  }
81
82
  } as RequestInit;
82
83
 
84
+ const nextCookies = cookies();
85
+ const segment = nextCookies.get('pz-segment')?.value;
86
+ const currency = nextCookies.get('pz-external-currency')?.value;
87
+
88
+ if (segment) {
89
+ fetchOptions.headers['X-Segment-Id'] = segment;
90
+ }
91
+
92
+ if (currency) {
93
+ fetchOptions.headers = Object.assign({}, fetchOptions.headers, {
94
+ 'x-currency': currency
95
+ });
96
+ }
97
+
83
98
  if (options.contentType) {
84
99
  fetchOptions.headers['Content-Type'] = options.contentType;
85
100
  }
86
101
 
87
- const isMultipartFormData = req.headers.get('content-type')?.includes('multipart/form-data;');
102
+ const isMultipartFormData = req.headers
103
+ .get('content-type')
104
+ ?.includes('multipart/form-data;');
88
105
 
89
106
  if (req.method !== 'GET') {
90
107
  let body: Record<string, any> | FormData = {};
@@ -108,7 +125,11 @@ async function proxyRequest(...args) {
108
125
 
109
126
  Object.keys(body ?? {}).forEach((key) => {
110
127
  if (body[key]) {
111
- formData.append(key, body[key]);
128
+ if (typeof body[key] === 'object' && body[key] !== null) {
129
+ formData.append(key, JSON.stringify(body[key]));
130
+ } else {
131
+ formData.append(key, body[key]);
132
+ }
112
133
  }
113
134
  });
114
135
 
@@ -0,0 +1,49 @@
1
+ .checkout-payment-iframe-wrapper {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ border: none;
8
+ z-index: 1000;
9
+ background-color: white;
10
+ }
11
+ .checkout-payment-iframe-wrapper iframe {
12
+ width: 100%;
13
+ height: 100%;
14
+ border: none;
15
+ background-color: white;
16
+ }
17
+ .checkout-payment-iframe-wrapper .close-button {
18
+ position: fixed;
19
+ top: 16px;
20
+ right: 16px;
21
+ width: 32px;
22
+ height: 32px;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ z-index: 1001;
27
+ }
28
+
29
+ .checkout-payment-redirection-iframe-wrapper {
30
+ width: 100%;
31
+ position: relative;
32
+ }
33
+ .checkout-payment-redirection-iframe-wrapper iframe {
34
+ width: 100%;
35
+ height: 100%;
36
+ border: none;
37
+ background-color: white;
38
+ }
39
+ .checkout-payment-redirection-iframe-wrapper .close-button {
40
+ position: absolute;
41
+ top: 16px;
42
+ right: 16px;
43
+ width: 32px;
44
+ height: 32px;
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ z-index: 1001;
49
+ }/*# sourceMappingURL=index.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["index.scss","index.css"],"names":[],"mappings":"AAAA;EACE,eAAA;EACA,MAAA;EACA,OAAA;EACA,WAAA;EACA,YAAA;EACA,YAAA;EACA,aAAA;EACA,uBAAA;ACCF;ADCE;EACE,WAAA;EACA,YAAA;EACA,YAAA;EACA,uBAAA;ACCJ;ADEE;EACE,eAAA;EACA,SAAA;EACA,WAAA;EACA,WAAA;EACA,YAAA;EACA,aAAA;EACA,mBAAA;EACA,uBAAA;EACA,aAAA;ACAJ;;ADIA;EACE,WAAA;EACA,kBAAA;ACDF;ADGE;EACE,WAAA;EACA,YAAA;EACA,YAAA;EACA,uBAAA;ACDJ;ADIE;EACE,kBAAA;EACA,SAAA;EACA,WAAA;EACA,WAAA;EACA,YAAA;EACA,aAAA;EACA,mBAAA;EACA,uBAAA;EACA,aAAA;ACFJ","file":"index.css"}
@@ -1,29 +1,53 @@
1
1
  .checkout-payment-iframe-wrapper {
2
- position: fixed;
3
- top: 0;
4
- left: 0;
5
- width: 100%;
6
- height: 100%;
7
- border: none;
8
- z-index: 1000;
9
- background-color: white;
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ border: none;
8
+ z-index: 1000;
9
+ background-color: white;
10
10
 
11
- iframe {
12
- width: 100%;
13
- height: 100%;
14
- border: none;
15
- background-color: white;
16
- }
11
+ iframe {
12
+ width: 100%;
13
+ height: 100%;
14
+ border: none;
15
+ background-color: white;
16
+ }
17
17
 
18
- .close-button {
19
- position: fixed;
20
- top: 16px;
21
- right: 16px;
22
- width: 32px;
23
- height: 32px;
24
- display: flex;
25
- align-items: center;
26
- justify-content: center;
27
- z-index: 1001;
28
- }
29
- }
18
+ .close-button {
19
+ position: fixed;
20
+ top: 16px;
21
+ right: 16px;
22
+ width: 32px;
23
+ height: 32px;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ z-index: 1001;
28
+ }
29
+ }
30
+
31
+ .checkout-payment-redirection-iframe-wrapper {
32
+ width: 100%;
33
+ position: relative;
34
+
35
+ iframe {
36
+ width: 100%;
37
+ height: 100%;
38
+ border: none;
39
+ background-color: white;
40
+ }
41
+
42
+ .close-button {
43
+ position: absolute;
44
+ top: 16px;
45
+ right: 16px;
46
+ width: 32px;
47
+ height: 32px;
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ z-index: 1001;
52
+ }
53
+ }
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const localesPath = path.resolve(`public/locales/`);
6
+
7
+ async function generateTranslationIndex(locale_) {
8
+ let translations = {};
9
+
10
+ try {
11
+ const localeDirPath = path.resolve(`public/locales/${locale_}`);
12
+ const fileNames = fs.readdirSync(localeDirPath);
13
+
14
+ for await (const fileName of fileNames) {
15
+ if (fileName === 'index.json') {
16
+ continue;
17
+ }
18
+
19
+ const data = fs.readFileSync(
20
+ path.join(localeDirPath, `/${fileName}`),
21
+ 'utf-8'
22
+ );
23
+
24
+ translations[fileName.substring(0, fileName.lastIndexOf('.'))] =
25
+ JSON.parse(data);
26
+ }
27
+
28
+ fs.writeFileSync(
29
+ path.resolve(`public/locales/${locale_}/index.json`),
30
+ JSON.stringify(translations)
31
+ );
32
+ } catch (error) {
33
+ console.error(error);
34
+ }
35
+ }
36
+
37
+ const localePaths = fs.readdirSync(localesPath);
38
+
39
+ localePaths.forEach((localePath) => {
40
+ generateTranslationIndex(localePath);
41
+ });
@@ -4,3 +4,4 @@ const runScript = require('./run-script');
4
4
 
5
5
  runScript('pz-install-theme.js');
6
6
  runScript('pz-pre-check-dist.js');
7
+ runScript('pz-generate-translations.js');
package/bin/pz-predev.js CHANGED
@@ -5,3 +5,4 @@ const runScript = require('./run-script');
5
5
  runScript('pz-install-extensions.js');
6
6
  runScript('pz-check-env.js');
7
7
  runScript('pz-install-theme.js');
8
+ runScript('pz-generate-translations.js');
@@ -0,0 +1,8 @@
1
+ import { forwardRef } from 'react';
2
+ import { FileInputProps } from '../types/index';
3
+
4
+ export const FileInput = forwardRef<HTMLInputElement, FileInputProps>(
5
+ function fileInput(props, ref) {
6
+ return <input type="file" {...props} ref={ref} />;
7
+ }
8
+ );
@@ -19,3 +19,4 @@ export * from './trans';
19
19
  export * from './link';
20
20
  export * from './pagination';
21
21
  export * from './live-commerce';
22
+ export * from './file-input';
@@ -1,17 +1,30 @@
1
1
  import clsx from 'clsx';
2
- import { forwardRef, FocusEvent, useState } from 'react';
2
+ import { forwardRef, FocusEvent, useState, Ref } from 'react';
3
3
  import { Controller } from 'react-hook-form';
4
- import NumberFormat, { NumberFormatProps } from 'react-number-format';
4
+
5
+ // @ts-ignore
6
+ import { PatternFormat, PatternFormatProps } from 'react-number-format';
5
7
  import { InputProps } from '../types';
6
8
  import { twMerge } from 'tailwind-merge';
7
9
 
10
+ const PatternFormatWithRef = forwardRef(
11
+ (props: PatternFormatProps, ref: Ref<HTMLInputElement>) => {
12
+ return <PatternFormat {...props} getInputRef={ref} />;
13
+ }
14
+ );
15
+ PatternFormatWithRef.displayName = 'PatternFormatWithRef';
16
+
8
17
  export const Input = forwardRef<
9
18
  HTMLInputElement,
10
19
  InputProps &
11
20
  Pick<
12
- NumberFormatProps,
13
- 'format' | 'mask' | 'allowEmptyFormatting' | 'onValueChange'
14
- >
21
+ PatternFormatProps,
22
+ 'mask' | 'allowEmptyFormatting' | 'onValueChange'
23
+ > & {
24
+ format?: string;
25
+ defaultValue?: string;
26
+ type?: string;
27
+ }
15
28
  >((props, ref) => {
16
29
  const [focused, setFocused] = useState(false);
17
30
  const [hasValue, setHasValue] = useState(false);
@@ -37,6 +50,7 @@ export const Input = forwardRef<
37
50
  ),
38
51
  props.className
39
52
  );
53
+
40
54
  const inputProps: any = {
41
55
  id,
42
56
  ref,
@@ -79,14 +93,14 @@ export const Input = forwardRef<
79
93
  <Controller
80
94
  name={props.name ?? ''}
81
95
  control={props.control}
82
- defaultValue={false}
83
96
  render={({ field }) => (
84
- <NumberFormat
97
+ <PatternFormatWithRef
85
98
  format={format}
86
99
  mask={mask ?? ''}
87
100
  {...rest}
88
101
  {...field}
89
102
  {...inputProps}
103
+ type={props.type as 'text' | 'password' | 'tel'}
90
104
  />
91
105
  )}
92
106
  />
@@ -10,28 +10,32 @@ type LinkProps = Omit<
10
10
  React.AnchorHTMLAttributes<HTMLAnchorElement>,
11
11
  keyof NextLinkProps
12
12
  > &
13
- NextLinkProps;
13
+ NextLinkProps & {
14
+ href: string;
15
+ };
14
16
 
15
17
  export const Link = ({ children, href, ...rest }: LinkProps) => {
16
18
  const { locale, defaultLocaleValue, localeUrlStrategy } = useLocalization();
17
19
  const formattedHref = useMemo(() => {
18
- if (typeof href !== 'string' || href.startsWith('http')) {
19
- return href;
20
+ if (!href) {
21
+ return '#';
20
22
  }
21
23
 
22
- const pathnameWithoutLocale = href.replace(urlLocaleMatcherRegex, '');
23
- const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
24
+ if (typeof href === 'string' && !href.startsWith('http')) {
25
+ const pathnameWithoutLocale = href.replace(urlLocaleMatcherRegex, '');
26
+ const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
24
27
 
25
- if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
26
- return hrefWithLocale;
27
- } else if (
28
- localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
29
- locale !== defaultLocaleValue
30
- ) {
31
- return hrefWithLocale;
28
+ if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
29
+ return hrefWithLocale;
30
+ } else if (
31
+ localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
32
+ locale !== defaultLocaleValue
33
+ ) {
34
+ return hrefWithLocale;
35
+ }
32
36
  }
33
37
 
34
- return href || '#';
38
+ return href;
35
39
  }, [href, defaultLocaleValue, locale, localeUrlStrategy]);
36
40
 
37
41
  return (
@@ -1,11 +1,14 @@
1
1
  import { useMemo } from 'react';
2
- import NumberFormat, { NumberFormatProps } from 'react-number-format';
2
+
3
+ // @ts-ignore
4
+ import { NumericFormat, NumericFormatProps } from 'react-number-format';
3
5
  import { getCurrency } from '@akinon/next/utils';
4
6
 
5
7
  import { useLocalization } from '@akinon/next/hooks';
6
8
  import { PriceProps } from '../types';
9
+ import Settings from 'settings';
7
10
 
8
- export const Price = (props: NumberFormatProps & PriceProps) => {
11
+ export const Price = (props: NumericFormatProps & PriceProps) => {
9
12
  const {
10
13
  value,
11
14
  currencyCode,
@@ -27,6 +30,10 @@ export const Price = (props: NumberFormatProps & PriceProps) => {
27
30
  // TODO: This is very bad practice. It broke decimalScale.
28
31
  const _value = value?.toString().replace('.', ',');
29
32
 
33
+ const currentCurrencyDecimalScale = Settings.localization.currencies.find(
34
+ (currency) => currency.code === currencyCode_
35
+ ).decimalScale;
36
+
30
37
  const currency = useMemo(
31
38
  () =>
32
39
  getCurrency({
@@ -39,14 +46,14 @@ export const Price = (props: NumberFormatProps & PriceProps) => {
39
46
  );
40
47
 
41
48
  return (
42
- <NumberFormat
49
+ <NumericFormat
43
50
  value={useNegative ? `-${useNegativeSpace}${_value}` : _value}
44
51
  {...{
45
52
  [useCurrencyAfterPrice ? 'suffix' : 'prefix']: currency
46
53
  }}
47
54
  displayType={displayType}
48
55
  thousandSeparator={thousandSeparator}
49
- decimalScale={decimalScale}
56
+ decimalScale={currentCurrencyDecimalScale ?? decimalScale}
50
57
  decimalSeparator={decimalSeparator}
51
58
  fixedDecimalScale={fixedDecimalScale}
52
59
  {...rest}
@@ -3,17 +3,29 @@ import ClientRoot from './client-root';
3
3
  import { cookies } from 'next/headers';
4
4
  import PzProviders from './pz-providers';
5
5
  import { WebVitals } from './web-vitals';
6
+ import settings from 'settings';
6
7
 
7
- export default function PzRoot({
8
+ export default async function PzRoot({
8
9
  translations,
9
- children
10
+ children,
11
+ locale
10
12
  }: {
11
- translations: any;
13
+ translations?: any;
12
14
  children: React.ReactNode;
15
+ locale?: string;
13
16
  }) {
14
17
  const nextCookies = cookies();
15
18
  const sessionid = nextCookies.get('osessionid')?.value;
16
19
 
20
+ if (!translations) {
21
+ const { getTranslations } =
22
+ settings.useOptimizedTranslations && locale
23
+ ? require('translations')
24
+ : require('../utils/server-translation');
25
+
26
+ translations = await getTranslations(locale);
27
+ }
28
+
17
29
  return (
18
30
  <PzProviders translations={translations}>
19
31
  <WebVitals />
@@ -67,7 +67,7 @@ export const api = createApi({
67
67
  baseQuery: customBaseQuery,
68
68
  tagTypes: [
69
69
  'Basket',
70
- 'AllBaskets',
70
+ 'MultiBasket',
71
71
  'BasketB2b',
72
72
  'DraftsB2b',
73
73
  'Product',
@@ -12,7 +12,9 @@ import {
12
12
  SaveBasketParams,
13
13
  UpdateProductParams,
14
14
  DeleteProductParams,
15
- CreateQuotationParams
15
+ CreateQuotationParams,
16
+ BasketStatusResponse,
17
+ ExportBasketResponse
16
18
  } from '../../types';
17
19
 
18
20
  const b2bApi = api.injectEndpoints({
@@ -89,6 +91,34 @@ const b2bApi = api.injectEndpoints({
89
91
  }),
90
92
  invalidatesTags: ['BasketB2b', 'DraftsB2b']
91
93
  }),
94
+ exportBasket: build.mutation<ExportBasketResponse, string>({
95
+ query: (queryString) => {
96
+ return {
97
+ url: buildClientRequestUrl(b2b.basketExport(queryString)),
98
+ method: 'GET'
99
+ };
100
+ }
101
+ }),
102
+ getBasketStatus: build.mutation<BasketStatusResponse, string>({
103
+ query: (cacheKey) => {
104
+ return {
105
+ url: buildClientRequestUrl(b2b.statusBasket(cacheKey)),
106
+ method: 'GET'
107
+ };
108
+ }
109
+ }),
110
+ uploadFile: build.mutation<void, FormData>({
111
+ query: (body) => {
112
+ return {
113
+ url: buildClientRequestUrl(b2b.basketImport, {
114
+ useFormData: true
115
+ }),
116
+ method: 'POST',
117
+ body
118
+ };
119
+ },
120
+ invalidatesTags: ['BasketB2b']
121
+ })
92
122
  }),
93
123
  overrideExisting: true
94
124
  });
@@ -102,5 +132,8 @@ export const {
102
132
  useLoadBasketMutation,
103
133
  useUpdateProductMutation,
104
134
  useDeleteProductMutation,
105
- useCreateQuotationMutation
135
+ useCreateQuotationMutation,
136
+ useGetBasketStatusMutation,
137
+ useExportBasketMutation,
138
+ useUploadFileMutation
106
139
  } = b2bApi;
@@ -28,7 +28,7 @@ export const basketApi = api.injectEndpoints({
28
28
  query: ({ namespace }) =>
29
29
  buildClientRequestUrl(basket.getBasketDetail(namespace)),
30
30
  transformResponse: (response: { basket: Basket }) => response.basket,
31
- providesTags: ['AllBaskets']
31
+ providesTags: ['MultiBasket']
32
32
  }),
33
33
  getAllBaskets: build.query<Basket[], void>({
34
34
  query: () =>
@@ -36,7 +36,7 @@ export const basketApi = api.injectEndpoints({
36
36
  contentType: 'application/json'
37
37
  }),
38
38
  transformResponse: (response: { baskets: Basket[] }) => response.baskets,
39
- providesTags: ['AllBaskets']
39
+ providesTags: ['MultiBasket']
40
40
  }),
41
41
  removeBasket: build.mutation<Basket, { pk: number }>({
42
42
  query: ({ pk }) => ({
@@ -46,7 +46,7 @@ export const basketApi = api.injectEndpoints({
46
46
  method: 'DELETE',
47
47
  body: { pk }
48
48
  }),
49
- invalidatesTags: ['AllBaskets', 'Basket']
49
+ invalidatesTags: ['MultiBasket', 'Basket']
50
50
  }),
51
51
  selectMainBasket: build.mutation<Basket, { pk: number }>({
52
52
  query: ({ pk }) => ({
@@ -57,7 +57,7 @@ export const basketApi = api.injectEndpoints({
57
57
  body: { pk }
58
58
  }),
59
59
  transformResponse: (response: { baskets: Basket }) => response.baskets,
60
- invalidatesTags: ['AllBaskets', 'Basket']
60
+ invalidatesTags: ['MultiBasket', 'Basket']
61
61
  }),
62
62
  updateQuantity: build.mutation<
63
63
  UpdateQuantityResponse,
@@ -69,7 +69,8 @@ export const basketApi = api.injectEndpoints({
69
69
  }),
70
70
  method: 'PUT',
71
71
  body
72
- })
72
+ }),
73
+ invalidatesTags: ['MultiBasket', 'Basket']
73
74
  }),
74
75
  clearBasket: build.mutation<Basket, void>({
75
76
  query: (body) => ({
@@ -380,6 +380,22 @@ export const checkoutApi = api.injectEndpoints({
380
380
  dispatch(setShippingStepBusy(false));
381
381
  }
382
382
  }),
383
+ setDataSourceShippingOptions: build.mutation<CheckoutResponse, number[]>({
384
+ query: (pks) => ({
385
+ url: buildClientRequestUrl(checkout.setDataSourceShippingOption, {
386
+ useFormData: true
387
+ }),
388
+ method: 'POST',
389
+ body: {
390
+ data_source_shipping_options: JSON.stringify(pks)
391
+ }
392
+ }),
393
+ async onQueryStarted(arg, { dispatch, queryFulfilled }) {
394
+ dispatch(setShippingStepBusy(true));
395
+ await queryFulfilled;
396
+ dispatch(setShippingStepBusy(false));
397
+ }
398
+ }),
383
399
  setRetailStore: build.mutation<CheckoutResponse, SetRetailStoreParams>({
384
400
  query: ({ retailStorePk, billingAddressPk }) => ({
385
401
  url: buildClientRequestUrl(
@@ -681,6 +697,25 @@ export const checkoutApi = api.injectEndpoints({
681
697
  };
682
698
  }
683
699
  }),
700
+ setAttributeBasedShippingOptions: build.mutation<
701
+ CheckoutResponse,
702
+ Record<string, number>
703
+ >({
704
+ query: (options) => ({
705
+ url: buildClientRequestUrl(checkout.setAttributeBasedShippingOption, {
706
+ useFormData: true
707
+ }),
708
+ method: 'POST',
709
+ body: {
710
+ attribute_based_shipping_options: JSON.stringify(options)
711
+ }
712
+ }),
713
+ async onQueryStarted(arg, { dispatch, queryFulfilled }) {
714
+ dispatch(setShippingStepBusy(true));
715
+ await queryFulfilled;
716
+ dispatch(setShippingStepBusy(false));
717
+ }
718
+ }),
684
719
  setOrderSelectionPage: build.mutation<
685
720
  CheckoutResponse,
686
721
  { extra_field: ExtraField }
@@ -712,6 +747,7 @@ export const {
712
747
  useSetDeliveryOptionMutation,
713
748
  useSetAddressesMutation,
714
749
  useSetShippingOptionMutation,
750
+ useSetDataSourceShippingOptionsMutation,
715
751
  useSetPaymentOptionMutation,
716
752
  useSetBinNumberMutation,
717
753
  useSetInstallmentOptionMutation,
@@ -730,5 +766,6 @@ export const {
730
766
  usePayWithLoyaltyBalanceMutation,
731
767
  useSetOrderNoteMutation,
732
768
  useSetDeliveryBagsMutation,
769
+ useSetAttributeBasedShippingOptionsMutation,
733
770
  useSetOrderSelectionPageMutation
734
771
  } = checkoutApi;
@@ -22,11 +22,12 @@ const userApi = api.injectEndpoints({
22
22
  getCaptcha: build.query<GetCaptchaResponse, void>({
23
23
  query: () => buildClientRequestUrl(user.captcha),
24
24
  transformResponse: (response: { html: string }) => {
25
- const siteKeyMatch = response.html.match(/sitekey=["|'][^"']+/gi);
25
+ const siteKey = response.html.match(/data-sitekey="([^"]+)"/i)[1];
26
+
26
27
  const csrfTokenMatch = response.html.match(
27
28
  /name=['|"]csrfmiddlewaretoken['|"] value=['|"][^'"]+/gi
28
29
  );
29
- const siteKey = siteKeyMatch?.[0].replace(/sitekey=["|']/, '') || '';
30
+
30
31
  const csrfToken =
31
32
  csrfTokenMatch?.[0].replace(
32
33
  /name=['|"]csrfmiddlewaretoken['|"] value=['|"]/gi,