@akinon/next 1.59.0 → 1.60.0-rc.7

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 (64) hide show
  1. package/CHANGELOG.md +659 -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/plugin-module.tsx +8 -3
  14. package/components/price.tsx +11 -4
  15. package/components/pz-root.tsx +15 -3
  16. package/components/selected-payment-option-view.tsx +2 -1
  17. package/data/client/api.ts +1 -1
  18. package/data/client/b2b.ts +35 -2
  19. package/data/client/basket.ts +6 -5
  20. package/data/client/checkout.ts +55 -10
  21. package/data/client/user.ts +3 -2
  22. package/data/server/category.ts +43 -19
  23. package/data/server/flatpage.ts +29 -7
  24. package/data/server/form.ts +29 -11
  25. package/data/server/landingpage.ts +26 -7
  26. package/data/server/list.ts +16 -6
  27. package/data/server/menu.ts +15 -2
  28. package/data/server/product.ts +33 -13
  29. package/data/server/seo.ts +17 -24
  30. package/data/server/special-page.ts +15 -5
  31. package/data/server/widget.ts +14 -7
  32. package/data/urls.ts +8 -1
  33. package/hocs/server/with-segment-defaults.tsx +4 -1
  34. package/hooks/index.ts +2 -1
  35. package/hooks/use-message-listener.ts +24 -0
  36. package/hooks/use-pagination.ts +2 -2
  37. package/hooks/use-payment-options.ts +2 -1
  38. package/lib/cache-handler.mjs +33 -0
  39. package/lib/cache.ts +8 -6
  40. package/middlewares/currency.ts +2 -1
  41. package/middlewares/default.ts +226 -152
  42. package/middlewares/index.ts +3 -1
  43. package/middlewares/oauth-login.ts +3 -1
  44. package/middlewares/pretty-url.ts +11 -1
  45. package/middlewares/saved-card-redirection.ts +179 -0
  46. package/middlewares/url-redirection.ts +4 -0
  47. package/package.json +4 -3
  48. package/plugins.d.ts +6 -0
  49. package/plugins.js +2 -1
  50. package/redux/middlewares/checkout.ts +78 -14
  51. package/redux/reducers/checkout.ts +23 -3
  52. package/redux/reducers/index.ts +3 -1
  53. package/routes/pretty-url.tsx +192 -0
  54. package/types/commerce/address.ts +1 -1
  55. package/types/commerce/b2b.ts +12 -2
  56. package/types/commerce/checkout.ts +30 -0
  57. package/types/commerce/order.ts +1 -0
  58. package/types/index.ts +17 -2
  59. package/utils/app-fetch.ts +16 -8
  60. package/utils/generate-commerce-search-params.ts +3 -1
  61. package/utils/index.ts +27 -6
  62. package/utils/redirection-iframe.ts +85 -0
  63. package/utils/server-translation.ts +11 -1
  64. package/with-pz-config.js +13 -2
@@ -0,0 +1,179 @@
1
+ import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+ import { Buffer } from 'buffer';
4
+ import logger from '../utils/log';
5
+ import { getUrlPathWithLocale } from '../utils/localization';
6
+ import { PzNextRequest } from '.';
7
+
8
+ const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
9
+ if (stream) {
10
+ const chunks = [];
11
+ let result = '';
12
+
13
+ try {
14
+ for await (const chunk of stream as any) {
15
+ chunks.push(Buffer.from(chunk));
16
+ }
17
+
18
+ result = Buffer.concat(chunks).toString('utf-8');
19
+ } catch (error) {
20
+ logger.error('Error while reading body stream', {
21
+ middleware: 'saved-card-redirection',
22
+ error
23
+ });
24
+ }
25
+
26
+ return result;
27
+ }
28
+ return null;
29
+ };
30
+
31
+ const withSavedCardRedirection =
32
+ (middleware: NextMiddleware) =>
33
+ async (req: PzNextRequest, event: NextFetchEvent) => {
34
+ const url = req.nextUrl.clone();
35
+ const ip = req.headers.get('x-forwarded-for') ?? '';
36
+ const sessionId = req.cookies.get('osessionid');
37
+
38
+ if (url.search.indexOf('SavedCardThreeDSecurePage') === -1) {
39
+ return middleware(req, event);
40
+ }
41
+
42
+ const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
43
+ const requestHeaders = {
44
+ 'X-Requested-With': 'XMLHttpRequest',
45
+ 'Content-Type': 'application/x-www-form-urlencoded',
46
+ Cookie: `osessionid=${req.cookies.get('osessionid')?.value ?? ''}`,
47
+ 'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
+ 'x-forwarded-for': ip
49
+ };
50
+
51
+ try {
52
+ const body = await streamToString(req.body);
53
+
54
+ if (!sessionId) {
55
+ logger.warn(
56
+ 'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
57
+ {
58
+ middleware: 'saved-card-redirection',
59
+ ip
60
+ }
61
+ );
62
+
63
+ return NextResponse.redirect(
64
+ `${url.origin}${getUrlPathWithLocale(
65
+ '/orders/checkout/',
66
+ req.cookies.get('pz-locale')?.value
67
+ )}`,
68
+ 303
69
+ );
70
+ }
71
+
72
+ const request = await fetch(requestUrl, {
73
+ method: 'POST',
74
+ headers: requestHeaders,
75
+ body
76
+ });
77
+
78
+ logger.info('Complete 3D payment request', {
79
+ requestUrl,
80
+ status: request.status,
81
+ requestHeaders,
82
+ ip
83
+ });
84
+
85
+ const response = await request.json();
86
+
87
+ const { context_list: contextList, errors } = response;
88
+ const redirectionContext = contextList?.find(
89
+ (context) => context.page_context?.redirect_url
90
+ );
91
+ const redirectUrl = redirectionContext?.page_context?.redirect_url;
92
+
93
+ if (errors && Object.keys(errors).length) {
94
+ logger.error('Error while completing 3D payment', {
95
+ middleware: 'saved-card-redirection',
96
+ errors,
97
+ requestHeaders,
98
+ ip
99
+ });
100
+
101
+ return NextResponse.redirect(
102
+ `${url.origin}${getUrlPathWithLocale(
103
+ '/orders/checkout/',
104
+ req.cookies.get('pz-locale')?.value
105
+ )}`,
106
+ {
107
+ status: 303,
108
+ headers: {
109
+ 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
110
+ }
111
+ }
112
+ );
113
+ }
114
+
115
+ logger.info('Order success page context list', {
116
+ middleware: 'saved-card-redirection',
117
+ contextList,
118
+ ip
119
+ });
120
+
121
+ if (!redirectUrl) {
122
+ logger.warn(
123
+ 'No redirection url for order success page found in page_context. Redirecting to checkout page.',
124
+ {
125
+ middleware: 'saved-card-redirection',
126
+ requestHeaders,
127
+ response: JSON.stringify(response),
128
+ ip
129
+ }
130
+ );
131
+
132
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
133
+ '/orders/checkout/',
134
+ req.cookies.get('pz-locale')?.value
135
+ )}`;
136
+
137
+ return NextResponse.redirect(redirectUrlWithLocale, 303);
138
+ }
139
+
140
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
141
+ redirectUrl,
142
+ req.cookies.get('pz-locale')?.value
143
+ )}`;
144
+
145
+ logger.info('Redirecting to order success page', {
146
+ middleware: 'saved-card-redirection',
147
+ redirectUrlWithLocale,
148
+ ip
149
+ });
150
+
151
+ // Using POST method while redirecting causes an error,
152
+ // So we use 303 status code to change the method to GET
153
+ const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
154
+
155
+ nextResponse.headers.set(
156
+ 'Set-Cookie',
157
+ request.headers.get('set-cookie') ?? ''
158
+ );
159
+
160
+ return nextResponse;
161
+ } catch (error) {
162
+ logger.error('Error while completing 3D payment', {
163
+ middleware: 'saved-card-redirection',
164
+ error,
165
+ requestHeaders,
166
+ ip
167
+ });
168
+
169
+ return NextResponse.redirect(
170
+ `${url.origin}${getUrlPathWithLocale(
171
+ '/orders/checkout/',
172
+ req.cookies.get('pz-locale')?.value
173
+ )}`,
174
+ 303
175
+ );
176
+ }
177
+ };
178
+
179
+ export default withSavedCardRedirection;
@@ -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.59.0",
4
+ "version": "1.60.0-rc.7",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -20,16 +20,17 @@
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
+ "redis": "4.6.13",
28
29
  "semver": "7.6.2",
29
30
  "set-cookie-parser": "2.6.0"
30
31
  },
31
32
  "devDependencies": {
32
- "@akinon/eslint-plugin-projectzero": "1.59.0",
33
+ "@akinon/eslint-plugin-projectzero": "1.60.0-rc.7",
33
34
  "@types/react-redux": "7.1.30",
34
35
  "@types/set-cookie-parser": "2.4.7",
35
36
  "@typescript-eslint/eslint-plugin": "6.7.4",
package/plugins.d.ts CHANGED
@@ -21,4 +21,10 @@ 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;
25
+ }
26
+
27
+ declare module '@akinon/pz-saved-card' {
28
+ export const savedCardReducer: any;
29
+ export const SavedCardOption: any;
24
30
  }
package/plugins.js CHANGED
@@ -12,5 +12,6 @@ module.exports = [
12
12
  'pz-b2b',
13
13
  'pz-akifast',
14
14
  'pz-multi-basket',
15
- 'pz-tabby-extension'
15
+ 'pz-tabby-extension',
16
+ 'pz-saved-card'
16
17
  ];
@@ -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,16 +19,16 @@ import {
16
19
  setPreOrder,
17
20
  setRetailStores,
18
21
  setShippingOptions,
19
- setShippingStepCompleted,
20
- setCreditPaymentOptions
22
+ setShippingStepCompleted
21
23
  } from '../../redux/reducers/checkout';
22
24
  import { RootState, TypedDispatch } from 'redux/store';
23
25
  import { checkoutApi } from '../../data/client/checkout';
24
26
  import { CheckoutContext, PreOrder } from '../../types';
25
- import { getCookie, setCookie } from '../../utils';
27
+ import { getCookie } 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: {
@@ -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
 
@@ -107,7 +112,8 @@ export const preOrderMiddleware: Middleware = ({
107
112
  if (
108
113
  (!preOrder.shipping_address || !preOrder.billing_address) &&
109
114
  addresses.length > 0 &&
110
- preOrder.delivery_option?.delivery_option_type === 'customer'
115
+ (!preOrder.delivery_option ||
116
+ preOrder.delivery_option.delivery_option_type === 'customer')
111
117
  ) {
112
118
  dispatch(
113
119
  apiEndpoints.setAddresses.initiate({
@@ -125,11 +131,49 @@ export const preOrderMiddleware: Middleware = ({
125
131
  dispatch(apiEndpoints.setShippingOption.initiate(shippingOptions[0].pk));
126
132
  }
127
133
 
134
+ if (
135
+ dataSourceShippingOptions.length > 0 &&
136
+ !preOrder.data_source_shipping_options
137
+ ) {
138
+ const selectedDataSourceShippingOptionsPks =
139
+ dataSourceShippingOptions.map(
140
+ (opt) => opt.data_source_shipping_options[0].pk
141
+ );
142
+
143
+ dispatch(
144
+ apiEndpoints.setDataSourceShippingOptions.initiate(
145
+ selectedDataSourceShippingOptionsPks
146
+ )
147
+ );
148
+ }
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[0].pk
158
+ ])
159
+ );
160
+
161
+ dispatch(
162
+ apiEndpoints.setAttributeBasedShippingOptions.initiate(
163
+ initialSelectedOptions
164
+ )
165
+ );
166
+ }
167
+
128
168
  if (!preOrder.payment_option && paymentOptions.length > 0) {
129
169
  dispatch(apiEndpoints.setPaymentOption.initiate(paymentOptions[0].pk));
130
170
  }
131
171
 
132
- if (!preOrder.installment && installmentOptions.length > 0) {
172
+ if (
173
+ !preOrder.installment &&
174
+ preOrder.payment_option.payment_type !== 'saved_card' &&
175
+ installmentOptions.length > 0
176
+ ) {
133
177
  dispatch(
134
178
  apiEndpoints.setInstallmentOption.initiate(installmentOptions[0].pk)
135
179
  );
@@ -163,6 +207,7 @@ export const contextListMiddleware: Middleware = ({
163
207
  if (result?.payload?.context_list) {
164
208
  result.payload.context_list.forEach((context) => {
165
209
  const redirectUrl = context.page_context.redirect_url;
210
+ const isIframe = context.page_context.is_frame ?? false;
166
211
 
167
212
  if (redirectUrl) {
168
213
  const currentLocale = getCookie('pz-locale');
@@ -181,16 +226,19 @@ export const contextListMiddleware: Middleware = ({
181
226
  }
182
227
 
183
228
  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)) &&
229
+ const isMobileDevice =
230
+ isMobileApp ||
231
+ /iPad|iPhone|iPod|Android/i.test(navigator.userAgent);
232
+ const isIframePaymentOptionExcluded =
189
233
  !settings.checkout?.iframeExcludedPaymentOptions?.includes(
190
234
  result.payload?.pre_order?.payment_option?.slug
191
- )
192
- ) {
235
+ );
236
+ urlObj.searchParams.set('t', new Date().getTime().toString());
237
+
238
+ if (isMobileDevice && !isIframePaymentOptionExcluded) {
193
239
  showMobile3dIframe(urlObj.toString());
240
+ } else if (isIframe && !isIframePaymentOptionExcluded) {
241
+ showRedirectionIframe(urlObj.toString());
194
242
  } else {
195
243
  window.location.href = urlObj.toString();
196
244
  }
@@ -220,12 +268,28 @@ export const contextListMiddleware: Middleware = ({
220
268
  dispatch(setShippingOptions(context.page_context.shipping_options));
221
269
  }
222
270
 
271
+ if (context.page_context.data_sources) {
272
+ dispatch(
273
+ setDataSourceShippingOptions(context.page_context.data_sources)
274
+ );
275
+ }
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
+
223
285
  if (context.page_context.payment_options) {
224
286
  dispatch(setPaymentOptions(context.page_context.payment_options));
225
287
  }
226
288
 
227
289
  if (context.page_context.credit_payment_options) {
228
- dispatch(setCreditPaymentOptions(context.page_context.credit_payment_options));
290
+ dispatch(
291
+ setCreditPaymentOptions(context.page_context.credit_payment_options)
292
+ );
229
293
  }
230
294
 
231
295
  if (context.page_context.payment_choices) {
@@ -14,7 +14,9 @@ import {
14
14
  CheckoutCreditPaymentOption,
15
15
  PreOrder,
16
16
  RetailStore,
17
- ShippingOption
17
+ ShippingOption,
18
+ DataSource,
19
+ AttributeBasedShippingOption
18
20
  } from '../../types';
19
21
 
20
22
  export interface CheckoutState {
@@ -36,6 +38,7 @@ export interface CheckoutState {
36
38
  addressList: Address[];
37
39
  deliveryOptions: DeliveryOption[];
38
40
  shippingOptions: ShippingOption[];
41
+ dataSourceShippingOptions: DataSource[];
39
42
  paymentOptions: PaymentOption[];
40
43
  creditPaymentOptions: CheckoutCreditPaymentOption[];
41
44
  selectedCreditPaymentPk: number;
@@ -46,6 +49,8 @@ export interface CheckoutState {
46
49
  selectedBankAccountPk: number;
47
50
  loyaltyBalance?: string;
48
51
  retailStores: RetailStore[];
52
+ attributeBasedShippingOptions: AttributeBasedShippingOption[];
53
+ selectedShippingOptions: Record<string, number>;
49
54
  }
50
55
 
51
56
  const initialState: CheckoutState = {
@@ -67,6 +72,7 @@ const initialState: CheckoutState = {
67
72
  addressList: [],
68
73
  deliveryOptions: [],
69
74
  shippingOptions: [],
75
+ dataSourceShippingOptions: [],
70
76
  paymentOptions: [],
71
77
  creditPaymentOptions: [],
72
78
  selectedCreditPaymentPk: null,
@@ -75,7 +81,9 @@ const initialState: CheckoutState = {
75
81
  installmentOptions: [],
76
82
  bankAccounts: [],
77
83
  selectedBankAccountPk: null,
78
- retailStores: []
84
+ retailStores: [],
85
+ attributeBasedShippingOptions: [],
86
+ selectedShippingOptions: {}
79
87
  };
80
88
 
81
89
  const checkoutSlice = createSlice({
@@ -121,6 +129,9 @@ const checkoutSlice = createSlice({
121
129
  setShippingOptions(state, { payload }) {
122
130
  state.shippingOptions = payload;
123
131
  },
132
+ setDataSourceShippingOptions(state, { payload }) {
133
+ state.dataSourceShippingOptions = payload;
134
+ },
124
135
  setPaymentOptions(state, { payload }) {
125
136
  state.paymentOptions = payload;
126
137
  },
@@ -150,6 +161,12 @@ const checkoutSlice = createSlice({
150
161
  },
151
162
  setRetailStores(state, { payload }) {
152
163
  state.retailStores = payload;
164
+ },
165
+ setAttributeBasedShippingOptions(state, { payload }) {
166
+ state.attributeBasedShippingOptions = payload;
167
+ },
168
+ setSelectedShippingOptions: (state, { payload }) => {
169
+ state.selectedShippingOptions = payload;
153
170
  }
154
171
  }
155
172
  });
@@ -168,6 +185,7 @@ export const {
168
185
  setAddressList,
169
186
  setDeliveryOptions,
170
187
  setShippingOptions,
188
+ setDataSourceShippingOptions,
171
189
  setPaymentOptions,
172
190
  setCreditPaymentOptions,
173
191
  setSelectedCreditPaymentPk,
@@ -177,7 +195,9 @@ export const {
177
195
  setBankAccounts,
178
196
  setSelectedBankAccountPk,
179
197
  setLoyaltyBalance,
180
- setRetailStores
198
+ setRetailStores,
199
+ setAttributeBasedShippingOptions,
200
+ setSelectedShippingOptions
181
201
  } = checkoutSlice.actions;
182
202
 
183
203
  export default checkoutSlice.reducer;
@@ -7,6 +7,7 @@ import { api } from '../../data/client/api';
7
7
  // Plugin reducers
8
8
  import { masterpassReducer } from '@akinon/pz-masterpass';
9
9
  import { otpReducer } from '@akinon/pz-otp';
10
+ import { savedCardReducer } from '@akinon/pz-saved-card';
10
11
 
11
12
  const reducers = {
12
13
  [api.reducerPath]: api.reducer,
@@ -15,7 +16,8 @@ const reducers = {
15
16
  config: configReducer,
16
17
  header: headerReducer,
17
18
  masterpass: masterpassReducer,
18
- otp: otpReducer
19
+ otp: otpReducer,
20
+ savedCard: savedCardReducer
19
21
  };
20
22
 
21
23
  export default reducers;