@akinon/next 1.65.0 → 1.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @akinon/next
2
2
 
3
+ ## 1.67.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bc2b411: ZERO-2825: Add attribute-based shipping options to checkout page
8
+ - 3d2212a: ZERO-2745: Add multi basket support
9
+ - 8f47cca: ZERO-2829: fix and add nested JSON support for useFormData in api/client route
10
+ - 9e25a64: ZERO-2835: Update category page layout with breadcrumb
11
+
12
+ ## 1.66.0
13
+
14
+ ### Minor Changes
15
+
16
+ - 572d2e8: ZERO-2667: Add iframe support for redirection payment methods
17
+ - 2e6104d: ZERO-2888:Edit the numbering in the pagination and the visibility of the prev and next buttons
18
+ - 7b05522: ZERO-2905: Fix resend and close button in otp package
19
+ - 29ead87: ZERO-2905: Fix resend and close button in otp package
20
+ - 12a873e: ZERO-2846:Edit the siteKey regex in the response from GetCaptcha
21
+ - f2c325c: ZERO-2838: Move file input component into @akinon/next
22
+
3
23
  ## 1.65.0
4
24
 
5
25
  ### 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: {
@@ -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,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
+ );
@@ -20,3 +20,4 @@ export * from './trans';
20
20
  export * from './link';
21
21
  export * from './pagination';
22
22
  export * from './live-commerce';
23
+ export * from './file-input';
@@ -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',
@@ -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) => ({
@@ -57,6 +57,7 @@ export interface CompleteCreditCardParams {
57
57
  card_month: string;
58
58
  card_year: string;
59
59
  use_three_d?: boolean;
60
+ save?: boolean;
60
61
  }
61
62
 
62
63
  interface GetContractResponse {
@@ -228,7 +229,8 @@ export const checkoutApi = api.injectEndpoints({
228
229
  card_number,
229
230
  card_month,
230
231
  card_year,
231
- use_three_d = true
232
+ use_three_d = true,
233
+ save = undefined
232
234
  }) => {
233
235
  const paymentOption =
234
236
  store.getState().checkout?.preOrder?.payment_option;
@@ -242,20 +244,26 @@ export const checkoutApi = api.injectEndpoints({
242
244
  };
243
245
  }
244
246
 
247
+ const body: Record<string, string> = {
248
+ agreement: '1',
249
+ use_three_d: use_three_d ? '1' : '0',
250
+ card_cvv,
251
+ card_holder,
252
+ card_month,
253
+ card_number,
254
+ card_year
255
+ };
256
+
257
+ if (save !== undefined) {
258
+ body.save = save ? '1' : '0';
259
+ }
260
+
245
261
  return {
246
262
  url: buildClientRequestUrl(checkout.completeCreditCardPayment, {
247
263
  useFormData: true
248
264
  }),
249
265
  method: 'POST',
250
- body: {
251
- agreement: '1',
252
- use_three_d: use_three_d ? '1' : '0',
253
- card_cvv,
254
- card_holder,
255
- card_month,
256
- card_number,
257
- card_year
258
- }
266
+ body
259
267
  };
260
268
  },
261
269
  async onQueryStarted(args, { dispatch, queryFulfilled }) {
@@ -697,6 +705,25 @@ export const checkoutApi = api.injectEndpoints({
697
705
  };
698
706
  }
699
707
  }),
708
+ setAttributeBasedShippingOptions: build.mutation<
709
+ CheckoutResponse,
710
+ Record<string, number>
711
+ >({
712
+ query: (options) => ({
713
+ url: buildClientRequestUrl(checkout.setAttributeBasedShippingOption, {
714
+ useFormData: true
715
+ }),
716
+ method: 'POST',
717
+ body: {
718
+ attribute_based_shipping_options: JSON.stringify(options)
719
+ }
720
+ }),
721
+ async onQueryStarted(arg, { dispatch, queryFulfilled }) {
722
+ dispatch(setShippingStepBusy(true));
723
+ await queryFulfilled;
724
+ dispatch(setShippingStepBusy(false));
725
+ }
726
+ }),
700
727
  setOrderSelectionPage: build.mutation<
701
728
  CheckoutResponse,
702
729
  { extra_field: ExtraField }
@@ -747,5 +774,6 @@ export const {
747
774
  usePayWithLoyaltyBalanceMutation,
748
775
  useSetOrderNoteMutation,
749
776
  useSetDeliveryBagsMutation,
777
+ useSetAttributeBasedShippingOptionsMutation,
750
778
  useSetOrderSelectionPageMutation
751
779
  } = 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,
package/data/urls.ts CHANGED
@@ -17,16 +17,22 @@ export const account = {
17
17
  page,
18
18
  limit,
19
19
  createdDate,
20
- endDate
20
+ endDate,
21
+ shipping_option_slug
21
22
  }: {
22
23
  page?: number;
23
24
  limit?: number;
24
25
  createdDate?: string;
25
26
  endDate?: string;
27
+ shipping_option_slug?: string;
26
28
  }) =>
27
- `/users/orders/?page=${page || 1}&limit=${limit || 12} ${
29
+ `/users/orders/?page=${page || 1}&limit=${limit || 12}${
28
30
  createdDate ? `&created_date__gte=${createdDate}` : ''
29
- } ${endDate ? `&created_date__lte=${endDate}` : ''}`,
31
+ }${endDate ? `&created_date__lte=${endDate}` : ''}${
32
+ shipping_option_slug
33
+ ? `&shipping_option_slug=${shipping_option_slug}`
34
+ : ''
35
+ }`,
30
36
  getQuotations: (page?: number, status?: string, limit?: number) =>
31
37
  `/b2b/my-quotations/?page=${page || 1}` +
32
38
  (status ? `&status=${status}` : '') +
@@ -111,6 +117,8 @@ export const checkout = {
111
117
  loyaltyMoneyUsage: '/orders/checkout/?page=LoyaltyMoneyUsagePage',
112
118
  setOrderNote: '/orders/checkout/?page=OrderNotePage',
113
119
  couponSelectionPage: '/orders/checkout/?page=CouponSelectionPage',
120
+ setAttributeBasedShippingOption:
121
+ '/orders/checkout/?page=AttributeBasedShippingOptionSelectionPage',
114
122
  deliveryBagsPage: '/orders/checkout/?page=DeliveryBagsPage',
115
123
  setOrderSelectionPage: '/orders/checkout/?page=OrderSelectionPage'
116
124
  };
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
+ };
@@ -116,7 +116,7 @@ export default function usePagination(
116
116
  urlSearchParams.set('page', (Number(state.page) - 1).toString());
117
117
  return `${pathname}?${urlSearchParams.toString()}`;
118
118
  }
119
- return '#';
119
+ return null;
120
120
  }, [state.page, pathname, urlSearchParams]);
121
121
 
122
122
  const next = useMemo(() => {
@@ -124,7 +124,7 @@ export default function usePagination(
124
124
  urlSearchParams.set('page', (Number(state.page) + 1).toString());
125
125
  return `${pathname}?${urlSearchParams.toString()}`;
126
126
  }
127
- return '#';
127
+ return null;
128
128
  }, [state.page, state.last, pathname, urlSearchParams]);
129
129
 
130
130
  return {
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.65.0",
4
+ "version": "1.67.0",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -30,7 +30,7 @@
30
30
  "set-cookie-parser": "2.6.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@akinon/eslint-plugin-projectzero": "1.65.0",
33
+ "@akinon/eslint-plugin-projectzero": "1.67.0",
34
34
  "@types/react-redux": "7.1.30",
35
35
  "@types/set-cookie-parser": "2.4.7",
36
36
  "@typescript-eslint/eslint-plugin": "6.7.4",
package/plugins.d.ts CHANGED
@@ -21,6 +21,7 @@ declare module '@akinon/pz-otp' {
21
21
 
22
22
  declare module '@akinon/pz-otp/src/redux/reducer' {
23
23
  export const showPopup: any;
24
+ export const hidePopup: any;
24
25
  }
25
26
 
26
27
  declare module 'pz-saved-card' {
@@ -3,9 +3,12 @@
3
3
  import { Middleware } from '@reduxjs/toolkit';
4
4
  import {
5
5
  setAddressList,
6
+ setAttributeBasedShippingOptions,
6
7
  setBankAccounts,
7
8
  setCanGuestPurchase,
8
9
  setCardType,
10
+ setCreditPaymentOptions,
11
+ setDataSourceShippingOptions,
9
12
  setDeliveryOptions,
10
13
  setErrors,
11
14
  setHasGiftBox,
@@ -16,9 +19,7 @@ import {
16
19
  setPreOrder,
17
20
  setRetailStores,
18
21
  setShippingOptions,
19
- setDataSourceShippingOptions,
20
- setShippingStepCompleted,
21
- setCreditPaymentOptions
22
+ setShippingStepCompleted
22
23
  } from '../../redux/reducers/checkout';
23
24
  import { RootState, TypedDispatch } from 'redux/store';
24
25
  import { checkoutApi } from '../../data/client/checkout';
@@ -27,6 +28,7 @@ import { getCookie, setCookie } from '../../utils';
27
28
  import settings from 'settings';
28
29
  import { LocaleUrlStrategy } from '../../localization';
29
30
  import { showMobile3dIframe } from '../../utils/mobile-3d-iframe';
31
+ import { showRedirectionIframe } from '../../utils/redirection-iframe';
30
32
 
31
33
  interface CheckoutResult {
32
34
  payload: {
@@ -76,7 +78,8 @@ export const preOrderMiddleware: Middleware = ({
76
78
  shippingOptions,
77
79
  dataSourceShippingOptions,
78
80
  paymentOptions,
79
- installmentOptions
81
+ installmentOptions,
82
+ attributeBasedShippingOptions
80
83
  } = getState().checkout;
81
84
  const { endpoints: apiEndpoints } = checkoutApi;
82
85
 
@@ -144,11 +147,33 @@ export const preOrderMiddleware: Middleware = ({
144
147
  );
145
148
  }
146
149
 
150
+ if (
151
+ Object.keys(attributeBasedShippingOptions).length > 0 &&
152
+ !preOrder.attribute_based_shipping_options
153
+ ) {
154
+ const initialSelectedOptions: Record<string, number> = Object.fromEntries(
155
+ Object.entries(attributeBasedShippingOptions).map(([key, options]) => [
156
+ key,
157
+ options.attribute_based_shipping_options[0].pk
158
+ ])
159
+ );
160
+
161
+ dispatch(
162
+ apiEndpoints.setAttributeBasedShippingOptions.initiate(
163
+ initialSelectedOptions
164
+ )
165
+ );
166
+ }
167
+
147
168
  if (!preOrder.payment_option && paymentOptions.length > 0) {
148
169
  dispatch(apiEndpoints.setPaymentOption.initiate(paymentOptions[0].pk));
149
170
  }
150
171
 
151
- if (!preOrder.installment && installmentOptions.length > 0) {
172
+ if (
173
+ !preOrder.installment &&
174
+ preOrder.payment_option?.payment_type !== 'saved_card' &&
175
+ installmentOptions.length > 0
176
+ ) {
152
177
  dispatch(
153
178
  apiEndpoints.setInstallmentOption.initiate(installmentOptions[0].pk)
154
179
  );
@@ -182,6 +207,8 @@ export const contextListMiddleware: Middleware = ({
182
207
  if (result?.payload?.context_list) {
183
208
  result.payload.context_list.forEach((context) => {
184
209
  const redirectUrl = context.page_context.redirect_url;
210
+ const isIframe = context.page_context.is_frame ?? false;
211
+
185
212
  if (redirectUrl) {
186
213
  const currentLocale = getCookie('pz-locale');
187
214
 
@@ -199,16 +226,19 @@ export const contextListMiddleware: Middleware = ({
199
226
  }
200
227
 
201
228
  const urlObj = new URL(url, window.location.origin);
229
+ const isMobileDevice =
230
+ isMobileApp ||
231
+ /iPad|iPhone|iPod|Android/i.test(navigator.userAgent);
232
+ const isIframePaymentOptionExcluded =
233
+ settings.checkout?.iframeExcludedPaymentOptions?.includes(
234
+ result.payload?.pre_order?.payment_option?.slug
235
+ );
202
236
  urlObj.searchParams.set('t', new Date().getTime().toString());
203
237
 
204
- if (
205
- (isMobileApp ||
206
- /iPad|iPhone|iPod|Android/i.test(navigator.userAgent)) &&
207
- !settings.checkout?.iframeExcludedPaymentOptions?.includes(
208
- result.payload?.pre_order?.payment_option?.slug
209
- )
210
- ) {
238
+ if (isMobileDevice && !isIframePaymentOptionExcluded) {
211
239
  showMobile3dIframe(urlObj.toString());
240
+ } else if (isIframe && !isIframePaymentOptionExcluded) {
241
+ showRedirectionIframe(urlObj.toString());
212
242
  } else {
213
243
  window.location.href = urlObj.toString();
214
244
  }
@@ -244,6 +274,14 @@ export const contextListMiddleware: Middleware = ({
244
274
  );
245
275
  }
246
276
 
277
+ if (context.page_context.attribute_based_shipping_options) {
278
+ dispatch(
279
+ setAttributeBasedShippingOptions(
280
+ context.page_context.attribute_based_shipping_options
281
+ )
282
+ );
283
+ }
284
+
247
285
  if (context.page_context.payment_options) {
248
286
  dispatch(setPaymentOptions(context.page_context.payment_options));
249
287
  }
@@ -15,7 +15,8 @@ import {
15
15
  PreOrder,
16
16
  RetailStore,
17
17
  ShippingOption,
18
- DataSource
18
+ DataSource,
19
+ AttributeBasedShippingOption
19
20
  } from '../../types';
20
21
 
21
22
  export interface CheckoutState {
@@ -48,6 +49,8 @@ export interface CheckoutState {
48
49
  selectedBankAccountPk: number;
49
50
  loyaltyBalance?: string;
50
51
  retailStores: RetailStore[];
52
+ attributeBasedShippingOptions: AttributeBasedShippingOption[];
53
+ selectedShippingOptions: Record<string, number>;
51
54
  }
52
55
 
53
56
  const initialState: CheckoutState = {
@@ -78,7 +81,9 @@ const initialState: CheckoutState = {
78
81
  installmentOptions: [],
79
82
  bankAccounts: [],
80
83
  selectedBankAccountPk: null,
81
- retailStores: []
84
+ retailStores: [],
85
+ attributeBasedShippingOptions: [],
86
+ selectedShippingOptions: {}
82
87
  };
83
88
 
84
89
  const checkoutSlice = createSlice({
@@ -156,6 +161,12 @@ const checkoutSlice = createSlice({
156
161
  },
157
162
  setRetailStores(state, { payload }) {
158
163
  state.retailStores = payload;
164
+ },
165
+ setAttributeBasedShippingOptions(state, { payload }) {
166
+ state.attributeBasedShippingOptions = payload;
167
+ },
168
+ setSelectedShippingOptions: (state, { payload }) => {
169
+ state.selectedShippingOptions = payload;
159
170
  }
160
171
  }
161
172
  });
@@ -184,7 +195,9 @@ export const {
184
195
  setBankAccounts,
185
196
  setSelectedBankAccountPk,
186
197
  setLoyaltyBalance,
187
- setRetailStores
198
+ setRetailStores,
199
+ setAttributeBasedShippingOptions,
200
+ setSelectedShippingOptions
188
201
  } = checkoutSlice.actions;
189
202
 
190
203
  export default checkoutSlice.reducer;
@@ -86,6 +86,7 @@ export interface PreOrder {
86
86
  total_amount?: string;
87
87
  total_amount_with_interest?: string;
88
88
  unpaid_amount?: string;
89
+ notes?: string;
89
90
  user_phone_number?: string;
90
91
  loyalty_money?: string;
91
92
  currency_type_label?: string;
@@ -97,6 +98,7 @@ export interface PreOrder {
97
98
  gift_box?: GiftBox;
98
99
  credit_payment_option?: CheckoutCreditPaymentOption;
99
100
  data_source_shipping_options: any;
101
+ attribute_based_shipping_options: any;
100
102
  bags?: Product[];
101
103
  bags_fee?: string;
102
104
  extra_field?: ExtraField;
@@ -141,6 +143,7 @@ export interface CheckoutContext {
141
143
  redirect_url?: string;
142
144
  context_data?: any;
143
145
  balance?: string;
146
+ attribute_based_shipping_options?: AttributeBasedShippingOption[];
144
147
  [key: string]: any;
145
148
  };
146
149
  }
@@ -166,3 +169,13 @@ export interface BankAccount {
166
169
  pk: number;
167
170
  sort_order: number;
168
171
  }
172
+
173
+ export interface AttributeBasedShippingOption {
174
+ pk: number;
175
+ shipping_amount: string | null;
176
+ shipping_option_name: string | null;
177
+ shipping_option_logo: string | null;
178
+ attribute_value: string | null;
179
+ attribute_key: string;
180
+ [key: string]: any;
181
+ }
package/types/index.ts CHANGED
@@ -269,6 +269,8 @@ export interface ButtonProps
269
269
  appearance?: 'filled' | 'outlined' | 'ghost';
270
270
  }
271
271
 
272
+ export type FileInputProps = React.HTMLProps<HTMLInputElement>;
273
+
272
274
  export interface PriceProps {
273
275
  currencyCode?: string;
274
276
  useCurrencySymbol?: boolean;
@@ -0,0 +1,85 @@
1
+ const iframeURLChange = (iframe, callback) => {
2
+ iframe.addEventListener('load', () => {
3
+ setTimeout(() => {
4
+ if (iframe?.contentWindow?.location) {
5
+ callback(iframe.contentWindow.location);
6
+ }
7
+ }, 0);
8
+ });
9
+ };
10
+
11
+ const removeIframe = async () => {
12
+ const iframeSelector = document.querySelector(
13
+ '.checkout-payment-redirection-iframe-wrapper'
14
+ );
15
+
16
+ const redirectionPaymentWrapper = document.querySelector(
17
+ '.checkout-redirection-payment-wrapper'
18
+ );
19
+
20
+ const form = redirectionPaymentWrapper.querySelector('form') as HTMLElement;
21
+
22
+ if (!iframeSelector) {
23
+ return;
24
+ }
25
+
26
+ iframeSelector.remove();
27
+
28
+ if (form) {
29
+ form.style.display = 'block';
30
+ }
31
+
32
+ location.reload();
33
+ };
34
+
35
+ export const showRedirectionIframe = (redirectUrl: string) => {
36
+ const iframeWrapper = document.createElement('div');
37
+ const iframe = document.createElement('iframe');
38
+ const closeButton = document.createElement('div');
39
+ const redirectionPaymentWrapper = document.querySelector(
40
+ '.checkout-redirection-payment-wrapper'
41
+ );
42
+ const form = redirectionPaymentWrapper.querySelector('form') as HTMLElement;
43
+
44
+ iframeWrapper.className = 'checkout-payment-redirection-iframe-wrapper';
45
+ closeButton.className = 'close-button';
46
+
47
+ iframe.setAttribute('src', redirectUrl);
48
+ closeButton.innerHTML = '&#x2715';
49
+ closeButton.addEventListener('click', removeIframe);
50
+
51
+ iframeWrapper.append(iframe, closeButton);
52
+
53
+ if (form) {
54
+ form.style.display = 'none';
55
+ }
56
+
57
+ redirectionPaymentWrapper.appendChild(iframeWrapper);
58
+
59
+ iframeURLChange(iframe, (location) => {
60
+ if (location.origin !== window.location.origin) {
61
+ return false;
62
+ }
63
+
64
+ const searchParams = new URLSearchParams(location.search);
65
+ const isOrderCompleted = location.href.includes('/orders/completed');
66
+
67
+ if (isOrderCompleted) {
68
+ (window.parent as any)?.postMessage?.(
69
+ JSON.stringify({
70
+ url: location.pathname
71
+ })
72
+ );
73
+ }
74
+
75
+ if (
76
+ searchParams.has('success') ||
77
+ isOrderCompleted ||
78
+ location.href.includes('/orders/checkout')
79
+ ) {
80
+ setTimeout(() => {
81
+ removeIframe();
82
+ }, 0);
83
+ }
84
+ });
85
+ };