@akinon/next 1.42.0 → 1.43.0-rc.1

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 (46) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/api/client.ts +33 -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/input.tsx +21 -7
  7. package/components/link.tsx +17 -13
  8. package/components/pagination.tsx +1 -2
  9. package/components/price.tsx +11 -4
  10. package/components/selected-payment-option-view.tsx +26 -38
  11. package/data/client/account.ts +10 -9
  12. package/data/client/address.ts +32 -8
  13. package/data/client/api.ts +1 -1
  14. package/data/client/checkout.ts +26 -3
  15. package/data/server/category.ts +2 -2
  16. package/data/server/list.ts +2 -2
  17. package/data/server/product.ts +15 -13
  18. package/data/server/special-page.ts +2 -2
  19. package/data/urls.ts +2 -0
  20. package/hocs/server/with-segment-defaults.tsx +2 -2
  21. package/hooks/index.ts +2 -1
  22. package/hooks/use-message-listener.ts +24 -0
  23. package/hooks/use-payment-options.ts +2 -1
  24. package/lib/cache-handler.mjs +33 -0
  25. package/lib/cache.ts +18 -6
  26. package/middlewares/default.ts +37 -2
  27. package/middlewares/pretty-url.ts +4 -0
  28. package/middlewares/url-redirection.ts +4 -0
  29. package/package.json +5 -4
  30. package/plugins.d.ts +1 -0
  31. package/redux/middlewares/checkout.ts +40 -9
  32. package/redux/reducers/checkout.ts +9 -3
  33. package/redux/reducers/config.ts +2 -0
  34. package/routes/pretty-url.tsx +194 -0
  35. package/types/commerce/account.ts +1 -0
  36. package/types/commerce/address.ts +1 -1
  37. package/types/commerce/checkout.ts +18 -0
  38. package/types/commerce/misc.ts +2 -0
  39. package/types/commerce/order.ts +12 -0
  40. package/types/index.ts +28 -2
  41. package/utils/app-fetch.ts +1 -1
  42. package/utils/generate-commerce-search-params.ts +6 -2
  43. package/utils/index.ts +27 -6
  44. package/utils/redirection-iframe.ts +85 -0
  45. package/utils/server-translation.ts +5 -1
  46. package/with-pz-config.js +11 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @akinon/next
2
2
 
3
+ ## 1.43.0-rc.1
4
+
5
+ ### Minor Changes
6
+
7
+ - eecb282: ZERO-2607: Update address-related functions to include invalidateTag option
8
+
9
+ ## 1.43.0-rc.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 90282b5: ZERO-2729: Audit packages for yarn and npm and also update app-template
14
+ - 572d2e8: ZERO-2667: Add iframe support for redirection payment methods
15
+ - a4c8d6a: ZERO-2663: Fix the image url for gif and svgs and return them without options
16
+ - fda5b92: ZERO-2725: fix invalid import
17
+ - 2d9b2b2: ZERO-2816: Add segment to headers
18
+ - c53ea3e: ZERO-2609: Reset additional form fields when selectedFormType is not company
19
+ - 8d9ac9a: ZERO-2794: Add field to order type
20
+ - e9541a1: ZERO-2816: Add headers to url
21
+ - c53ef7b: ZERO-2668: The Link component has been updated to improve the logic for handling href values. Previously, if the href was not a string or started with 'http', it would return the href as is. Now, if the href is not provided, it will default to '#' to prevent any potential errors. Additionally, if the href is a string and does not start with 'http', it will be formatted with the locale and pathname, based on the localeUrlStrategy and defaultLocaleValue. This ensures that the correct href is generated based on the localization settings.
22
+ - 0d3a913: ZERO-2725: Update decimal scale in Price component
23
+ - 1448a96: ZERO-2612: add errors type in CheckoutState
24
+ - d3474c6: ZERO-2655: Add data source shipping option
25
+ - 75080fd: ZERO-2630: Add max limit to postcode area
26
+ - 91265bb: ZERO-2551: Improve pretty url and caching performance
27
+ - bbe18b9: ZERO-2575: Fix build error
28
+ - d409996: ZERO-2781: Refactor buildClientRequestUrl function to support caching and options
29
+ - 94b6928: ZERO-2551: Add cache handler check in url-redirection middleware
30
+ - 98bb8dc: ZERO-2706: Cache getTranlations method
31
+ - 46b7aad: ZERO-2775: Add condition and logger for menuitemmodel data
32
+ - dcc8a15: ZERO-2694: added build step to RC branch pipeline
33
+ - fad2768: ZERO-2739: add gpay to payment plugin map
34
+ - dff0d59: ZERO-2659: add formData support to proxy api requests
35
+ - beb499e: ZERO-2551: Add new tsconfig paths
36
+ - 146ea39: ZERO-2774: Update imports
37
+ - c47be30: ZERO-2744: Update Order and OrderItem types
38
+ - e9a46ac: ZERO-2738: add CVC input to registered cards in Masterpass
39
+ - f046f8e: ZERO-2575: update version for react-number-format
40
+ - 86d2531: ZERO-2693: resolve dependency collision warning for eslint-config-next
41
+
3
42
  ## 1.42.0
4
43
 
5
44
  ### Minor Changes
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: {
@@ -74,16 +75,31 @@ async function proxyRequest(...args) {
74
75
  }
75
76
  } as RequestInit;
76
77
 
78
+ const nextCookies = cookies();
79
+ const segment = nextCookies.get('pz-segment')?.value;
80
+ const currency = nextCookies.get('pz-external-currency')?.value;
81
+
82
+ if (segment) {
83
+ fetchOptions.headers['X-Segment-Id'] = segment;
84
+ }
85
+
86
+ if (currency) {
87
+ fetchOptions.headers = Object.assign({}, fetchOptions.headers, {
88
+ 'x-currency': currency
89
+ });
90
+ }
91
+
77
92
  if (options.contentType) {
78
93
  fetchOptions.headers['Content-Type'] = options.contentType;
79
94
  }
80
95
 
96
+ const isMultipartFormData = req.headers.get('content-type')?.includes('multipart/form-data;');
97
+
81
98
  if (req.method !== 'GET') {
82
- const formData = new URLSearchParams();
83
- let body = {};
99
+ let body: Record<string, any> | FormData = {};
84
100
 
85
101
  try {
86
- body = await req.json();
102
+ body = isMultipartFormData ? await req.formData() : await req.json();
87
103
  } catch (error) {
88
104
  logger.error(
89
105
  `Client Proxy Request - Error while parsing request body to JSON`,
@@ -94,13 +110,21 @@ async function proxyRequest(...args) {
94
110
  );
95
111
  }
96
112
 
97
- Object.keys(body ?? {}).forEach((key) => {
98
- if (body[key]) {
99
- formData.append(key, body[key]);
100
- }
101
- });
113
+ if (isMultipartFormData) {
114
+ fetchOptions.body = body as FormData;
115
+ } else {
116
+ const formData = new FormData();
117
+
118
+ Object.keys(body ?? {}).forEach((key) => {
119
+ if (body[key]) {
120
+ formData.append(key, body[key]);
121
+ }
122
+ });
102
123
 
103
- fetchOptions.body = !options.useFormData ? JSON.stringify(body) : formData;
124
+ fetchOptions.body = !options.useFormData
125
+ ? JSON.stringify(body)
126
+ : formData;
127
+ }
104
128
  }
105
129
 
106
130
  let url = `${commerceUrl}/${slug.replace(/,/g, '/')}`;
@@ -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
+ }
@@ -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,10 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import { MouseEvent, useCallback, useEffect, useState } from 'react';
4
- import { PaginationProps } from '@theme/components/types';
4
+ import { PaginationProps } from '../types';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import clsx from 'clsx';
7
-
8
7
  import usePagination from '@akinon/next/hooks/use-pagination';
9
8
  import { useLocalization } from '@akinon/next/hooks';
10
9
  import { useRouter } from '@akinon/next/hooks';
@@ -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}
@@ -6,6 +6,21 @@ import dynamic from 'next/dynamic';
6
6
  import { PaymentOptionViews } from 'views/checkout/steps/payment';
7
7
  import { useMemo } from 'react';
8
8
 
9
+ const fallbackView = () => <div />;
10
+
11
+ const paymentTypeToView = {
12
+ bkm_express: 'bkm',
13
+ credit_card: 'credit-card',
14
+ credit_payment: 'credit-payment',
15
+ funds_transfer: 'funds-transfer',
16
+ gpay: 'gpay',
17
+ loyalty_money: 'loyalty',
18
+ masterpass: 'credit-card',
19
+ pay_on_delivery: 'pay-on-delivery',
20
+ redirection: 'redirection'
21
+ // Add other mappings as needed
22
+ };
23
+
9
24
  export default function SelectedPaymentOptionView() {
10
25
  const { payment_option } = useAppSelector(
11
26
  (state: RootState) => state.checkout.preOrder
@@ -14,7 +29,7 @@ export default function SelectedPaymentOptionView() {
14
29
  const Component = useMemo(
15
30
  () =>
16
31
  dynamic(
17
- () => {
32
+ async () => {
18
33
  const customOption = PaymentOptionViews.find(
19
34
  (opt) => opt.slug === payment_option.slug
20
35
  );
@@ -29,46 +44,19 @@ export default function SelectedPaymentOptionView() {
29
44
  });
30
45
  }
31
46
 
32
- let promise: any;
33
-
34
- try {
35
- if (payment_option.payment_type === 'credit_card') {
36
- promise = import(
37
- `views/checkout/steps/payment/options/credit-card`
38
- );
39
- } else if (payment_option.payment_type === 'funds_transfer') {
40
- promise = import(
41
- `views/checkout/steps/payment/options/funds-transfer`
42
- );
43
- } else if (payment_option.payment_type === 'redirection') {
44
- promise = import(
45
- `views/checkout/steps/payment/options/redirection`
46
- );
47
- } else if (payment_option.payment_type === 'pay_on_delivery') {
48
- promise = import(
49
- `views/checkout/steps/payment/options/pay-on-delivery`
50
- );
51
- } else if (payment_option.payment_type === 'loyalty_money') {
52
- promise = import(`views/checkout/steps/payment/options/loyalty`);
53
- } else if (payment_option.payment_type === 'masterpass') {
54
- promise = import(
55
- `views/checkout/steps/payment/options/credit-card`
47
+ const view = paymentTypeToView[payment_option.payment_type];
48
+ if (view) {
49
+ try {
50
+ const mod = await import(
51
+ `views/checkout/steps/payment/options/${view}`
56
52
  );
53
+ return mod.default || fallbackView;
54
+ } catch (error) {
55
+ return fallbackView;
57
56
  }
58
- // else if (payment_option.payment_type === 'credit_payment') {
59
- // promise = import(`views/checkout/steps/payment/options/credit-payment`);
60
- // }
61
- // else if (payment_option.payment_type === 'gpay') {
62
- // promise = import(`views/checkout/steps/payment/options/gpay`);
63
- // }
64
- // else if (payment_option.payment_type === 'bkm_express') {
65
- // promise = import(`views/checkout/steps/payment/options/bkm`);
66
- // }
67
- } catch (error) {}
57
+ }
68
58
 
69
- return promise
70
- ? promise.then((mod) => mod.default).catch(() => () => null)
71
- : new Promise<any>((resolve) => resolve(() => null));
59
+ return fallbackView;
72
60
  },
73
61
  { ssr: false }
74
62
  ),
@@ -123,15 +123,16 @@ const accountApi = api.injectEndpoints({
123
123
  query: ({ page, status, limit }) =>
124
124
  buildClientRequestUrl(account.getQuotations(page, status, limit))
125
125
  }),
126
- sendContact: builder.mutation<void, ContactFormType>({
127
- query: (body) => ({
128
- url: buildClientRequestUrl(account.sendContact, {
129
- contentType: 'application/json',
130
- responseType: 'text'
131
- }),
132
- method: 'POST',
133
- body
134
- })
126
+ sendContact: builder.mutation<void, FormData>({
127
+ query: (body) => {
128
+ return {
129
+ url: buildClientRequestUrl(account.sendContact, {
130
+ useFormData: true
131
+ }),
132
+ method: 'POST',
133
+ body
134
+ };
135
+ }
135
136
  }),
136
137
  cancelOrder: builder.mutation<void, AccountOrderCancellation>({
137
138
  query: ({ id, ...body }) => ({
@@ -39,7 +39,10 @@ const addressApi = api.injectEndpoints({
39
39
  query: (city) =>
40
40
  buildClientRequestUrl(address.getRetailStoreTownships(city))
41
41
  }),
42
- addAddress: builder.mutation<Address, Partial<Address>>({
42
+ addAddress: builder.mutation<
43
+ Address,
44
+ Partial<Address> & { invalidateTag?: 'Addresses' | 'Checkout' }
45
+ >({
43
46
  query: (body) => ({
44
47
  url: buildClientRequestUrl(address.base, {
45
48
  contentType: 'application/json'
@@ -50,9 +53,16 @@ const addressApi = api.injectEndpoints({
50
53
  type: body.is_corporate === 'true' ? 'corporate' : 'personal'
51
54
  }
52
55
  }),
53
- invalidatesTags: (_, error) => (error ? [] : ['Addresses', 'Checkout'])
56
+ invalidatesTags: (_, error, arg) => {
57
+ if (error) return [];
58
+ if (arg.invalidateTag) return [arg.invalidateTag];
59
+ return ['Addresses', 'Checkout'];
60
+ }
54
61
  }),
55
- editAddress: builder.mutation<Address, Partial<Address>>({
62
+ editAddress: builder.mutation<
63
+ Address,
64
+ Partial<Address> & { invalidateTag?: 'Addresses' | 'Checkout' }
65
+ >({
56
66
  query: ({ pk, ...body }) => ({
57
67
  url: buildClientRequestUrl(address.editAddress(pk), {
58
68
  contentType: 'application/json'
@@ -64,14 +74,28 @@ const addressApi = api.injectEndpoints({
64
74
  type: body.is_corporate === 'true' ? 'corporate' : 'personal'
65
75
  }
66
76
  }),
67
- invalidatesTags: (_, error) => (error ? [] : ['Addresses', 'Checkout']) // TODO: Invalidate one of these tags when necessary (e.g. Address page invalidates Addresses tag, Checkout page invalidates Checkout tag)
77
+ invalidatesTags: (_, error, arg) => {
78
+ if (error) return [];
79
+ if (arg.invalidateTag) return [arg.invalidateTag];
80
+ return ['Addresses', 'Checkout'];
81
+ }
68
82
  }),
69
- removeAddress: builder.mutation<void, number>({
70
- query: (id) => ({
71
- url: buildClientRequestUrl(address.removeAddress(id)),
83
+ removeAddress: builder.mutation<
84
+ void,
85
+ number | { id: number; invalidateTag: 'Addresses' | 'Checkout' }
86
+ >({
87
+ query: (arg) => ({
88
+ url: buildClientRequestUrl(
89
+ address.removeAddress(typeof arg === 'number' ? arg : arg.id)
90
+ ),
72
91
  method: 'DELETE'
73
92
  }),
74
- invalidatesTags: (_, error) => (error ? [] : ['Addresses', 'Checkout']) // TODO: Invalidate one of these tags when necessary (e.g. Address page invalidates Addresses tag, Checkout page invalidates Checkout tag)
93
+ invalidatesTags: (_, error, arg) => {
94
+ if (error) return [];
95
+ if (typeof arg === 'object' && arg.invalidateTag)
96
+ return [arg.invalidateTag];
97
+ return ['Addresses', 'Checkout'];
98
+ }
75
99
  }),
76
100
  setDefaultAddress: builder.mutation<Address, Partial<Address>>({
77
101
  query: ({ pk, primary }) => ({
@@ -9,7 +9,7 @@ import {
9
9
  } from '@reduxjs/toolkit/query/react';
10
10
  import settings from 'settings';
11
11
  import { getCookie } from '../../utils';
12
- import { RootState } from '@theme/redux/store';
12
+ import { RootState } from 'redux/store';
13
13
 
14
14
  interface CustomBaseQueryApi extends BaseQueryApi {
15
15
  getState: () => RootState;
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  setBankAccounts,
3
+ setCardType,
3
4
  setInstallmentOptions,
4
5
  setPaymentStepBusy,
5
6
  setSelectedBankAccountPk,
6
7
  setSelectedCreditPaymentPk,
7
- setShippingStepBusy,
8
- setCardType
8
+ setShippingStepBusy
9
9
  } from '../../redux/reducers/checkout';
10
10
  import {
11
11
  CheckoutContext,
@@ -20,6 +20,7 @@ import { AppDispatch, AppStore, store } from 'redux/store';
20
20
  import settings from 'settings';
21
21
  import { showMobile3dIframe } from '../../utils/mobile-3d-iframe';
22
22
  import {
23
+ setCvcRequired,
23
24
  setError,
24
25
  setOtpModalVisible
25
26
  } from '@akinon/pz-masterpass/src/redux/reducer';
@@ -113,7 +114,8 @@ const completeMasterpassPayment = async (
113
114
  ? await buildDirectPurchaseForm(commonFormValues, params)
114
115
  : await buildPurchaseForm({
115
116
  ...commonFormValues,
116
- selectedCard
117
+ selectedCard,
118
+ cardCvc: params?.card_cvv
117
119
  });
118
120
 
119
121
  window.MFS?.[
@@ -151,6 +153,10 @@ const completeMasterpassPayment = async (
151
153
  return;
152
154
  }
153
155
 
156
+ if (['5013', '5182'].includes(response.responseCode)) {
157
+ dispatch(setCvcRequired(true));
158
+ }
159
+
154
160
  if (
155
161
  response.token &&
156
162
  (response.responseCode == '0000' || response.responseCode == '')
@@ -373,6 +379,22 @@ export const checkoutApi = api.injectEndpoints({
373
379
  dispatch(setShippingStepBusy(false));
374
380
  }
375
381
  }),
382
+ setDataSourceShippingOptions: build.mutation<CheckoutResponse, number[]>({
383
+ query: (pks) => ({
384
+ url: buildClientRequestUrl(checkout.setDataSourceShippingOption, {
385
+ useFormData: true
386
+ }),
387
+ method: 'POST',
388
+ body: {
389
+ data_source_shipping_options: JSON.stringify(pks)
390
+ }
391
+ }),
392
+ async onQueryStarted(arg, { dispatch, queryFulfilled }) {
393
+ dispatch(setShippingStepBusy(true));
394
+ await queryFulfilled;
395
+ dispatch(setShippingStepBusy(false));
396
+ }
397
+ }),
376
398
  setRetailStore: build.mutation<CheckoutResponse, SetRetailStoreParams>({
377
399
  query: ({ retailStorePk, billingAddressPk }) => ({
378
400
  url: buildClientRequestUrl(
@@ -672,6 +694,7 @@ export const {
672
694
  useSetDeliveryOptionMutation,
673
695
  useSetAddressesMutation,
674
696
  useSetShippingOptionMutation,
697
+ useSetDataSourceShippingOptionsMutation,
675
698
  useSetPaymentOptionMutation,
676
699
  useSetBinNumberMutation,
677
700
  useSetInstallmentOptionMutation,