@akinon/next 1.92.0 → 1.93.0-rc.46

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 (54) hide show
  1. package/CHANGELOG.md +1166 -28
  2. package/__tests__/next-config.test.ts +1 -10
  3. package/__tests__/redirect.test.ts +758 -0
  4. package/api/image-proxy.ts +75 -0
  5. package/api/similar-product-list.ts +84 -0
  6. package/api/similar-products.ts +120 -0
  7. package/bin/pz-prebuild.js +0 -1
  8. package/components/accordion.tsx +20 -5
  9. package/components/file-input.tsx +65 -3
  10. package/components/input.tsx +2 -0
  11. package/components/link.tsx +16 -12
  12. package/components/modal.tsx +32 -16
  13. package/components/plugin-module.tsx +35 -3
  14. package/components/selected-payment-option-view.tsx +11 -0
  15. package/data/client/checkout.ts +25 -4
  16. package/data/server/basket.ts +72 -0
  17. package/data/server/category.ts +48 -28
  18. package/data/server/flatpage.ts +16 -12
  19. package/data/server/landingpage.ts +16 -12
  20. package/data/server/list.ts +23 -13
  21. package/data/server/product.ts +66 -39
  22. package/data/server/special-page.ts +16 -12
  23. package/data/urls.ts +7 -2
  24. package/hocs/server/with-segment-defaults.tsx +5 -2
  25. package/hooks/use-localization.ts +2 -3
  26. package/hooks/use-loyalty-availability.ts +21 -0
  27. package/instrumentation/node.ts +15 -13
  28. package/jest.config.js +7 -1
  29. package/lib/cache-handler.mjs +225 -16
  30. package/lib/cache.ts +2 -0
  31. package/middlewares/checkout-provider.ts +1 -1
  32. package/middlewares/complete-gpay.ts +6 -2
  33. package/middlewares/complete-masterpass.ts +7 -2
  34. package/middlewares/default.ts +232 -183
  35. package/middlewares/index.ts +3 -1
  36. package/middlewares/locale.ts +9 -1
  37. package/middlewares/redirection-payment.ts +6 -2
  38. package/middlewares/saved-card-redirection.ts +7 -2
  39. package/middlewares/three-d-redirection.ts +7 -2
  40. package/middlewares/url-redirection.ts +9 -15
  41. package/middlewares/wallet-complete-redirection.ts +203 -0
  42. package/package.json +3 -3
  43. package/plugins.d.ts +10 -0
  44. package/plugins.js +4 -1
  45. package/redux/middlewares/checkout.ts +15 -2
  46. package/redux/reducers/checkout.ts +9 -1
  47. package/sentry/index.ts +54 -17
  48. package/types/commerce/order.ts +1 -0
  49. package/types/index.ts +42 -1
  50. package/utils/app-fetch.ts +7 -2
  51. package/utils/index.ts +34 -10
  52. package/utils/redirect-ignore.ts +35 -0
  53. package/utils/redirect.ts +31 -6
  54. package/with-pz-config.js +1 -5
@@ -0,0 +1,72 @@
1
+ import { Cache, CacheKey } from '../../lib/cache';
2
+ import { basket } from '../../data/urls';
3
+ import { Basket } from '../../types';
4
+ import appFetch from '../../utils/app-fetch';
5
+ import { ServerVariables } from '../../utils/server-variables';
6
+ import logger from '../../utils/log';
7
+
8
+ type GetBasketParams = {
9
+ locale?: string;
10
+ currency?: string;
11
+ namespace?: string;
12
+ };
13
+
14
+ const getBasketDataHandler = ({
15
+ locale,
16
+ currency,
17
+ namespace
18
+ }: GetBasketParams) => {
19
+ return async function () {
20
+ try {
21
+ const url = namespace
22
+ ? basket.getBasketDetail(namespace)
23
+ : basket.getBasket;
24
+
25
+ const basketData = await appFetch<{ basket: Basket }>({
26
+ url,
27
+ locale,
28
+ currency,
29
+ init: {
30
+ headers: {
31
+ Accept: 'application/json',
32
+ 'Content-Type': 'application/json'
33
+ }
34
+ }
35
+ });
36
+
37
+ if (!basketData?.basket) {
38
+ logger.warn('Basket data is undefined', {
39
+ handler: 'getBasketDataHandler',
40
+ namespace
41
+ });
42
+ }
43
+
44
+ return basketData;
45
+ } catch (error) {
46
+ logger.error('Error fetching basket data', {
47
+ handler: 'getBasketDataHandler',
48
+ error,
49
+ namespace
50
+ });
51
+ throw error;
52
+ }
53
+ };
54
+ };
55
+
56
+ export const getBasketData = async ({
57
+ locale = ServerVariables.locale,
58
+ currency = ServerVariables.currency,
59
+ namespace
60
+ }: GetBasketParams = {}) => {
61
+ return Cache.wrap(
62
+ CacheKey.Basket(namespace),
63
+ locale,
64
+ getBasketDataHandler({ locale, currency, namespace }),
65
+ {
66
+ expire: 0,
67
+ cache: false
68
+ }
69
+ );
70
+ };
71
+
72
+
@@ -5,7 +5,6 @@ import { category, product } from '../urls';
5
5
  import { Cache, CacheKey } from '../../lib/cache';
6
6
  import { parse } from 'lossless-json';
7
7
  import logger from '../../utils/log';
8
- import { headers as nHeaders } from 'next/headers';
9
8
  import { ServerVariables } from '../../utils/server-variables';
10
9
 
11
10
  function getCategoryDataHandler(
@@ -18,19 +17,30 @@ function getCategoryDataHandler(
18
17
  return async function () {
19
18
  const params = generateCommerceSearchParams(searchParams);
20
19
 
21
- const rawData = await appFetch<string>({
22
- url: `${category.getCategoryByPk(pk)}${params ? params : ''}`,
23
- locale,
24
- currency,
25
- init: {
26
- headers: {
27
- Accept: 'application/json',
28
- 'Content-Type': 'application/json',
29
- ...(headers ?? {})
30
- }
31
- },
32
- responseType: FetchResponseType.TEXT
33
- });
20
+ let rawData: string;
21
+
22
+ try {
23
+ rawData = await appFetch<string>({
24
+ url: `${category.getCategoryByPk(pk)}${params ? params : ''}`,
25
+ locale,
26
+ currency,
27
+ init: {
28
+ headers: {
29
+ Accept: 'application/json',
30
+ 'Content-Type': 'application/json',
31
+ ...(headers ?? {})
32
+ }
33
+ },
34
+ responseType: FetchResponseType.TEXT
35
+ });
36
+ } catch (error) {
37
+ logger.error('Failed to fetch category data', {
38
+ handler: 'getCategoryDataHandler',
39
+ pk,
40
+ error: error.message
41
+ });
42
+ return null;
43
+ }
34
44
 
35
45
  let data: GetCategoryResponse;
36
46
 
@@ -48,8 +58,8 @@ function getCategoryDataHandler(
48
58
  logger.fatal('Error while parsing category data', {
49
59
  handler: 'getCategoryDataHandler',
50
60
  error,
51
- rawData: rawData.startsWith('<!DOCTYPE html>')
52
- ? `${rawData.substring(0, 50)}...`
61
+ rawData: rawData?.startsWith?.('<!DOCTYPE html>')
62
+ ? `${rawData?.substring(0, 50)}...`
53
63
  : rawData
54
64
  });
55
65
  }
@@ -64,17 +74,27 @@ function getCategoryDataHandler(
64
74
  return { data, breadcrumbData: undefined };
65
75
  }
66
76
 
67
- const breadcrumbData = await appFetch<any>({
68
- url: product.breadcrumbUrl(menuItemModel),
69
- locale,
70
- currency,
71
- init: {
72
- headers: {
73
- Accept: 'application/json',
74
- 'Content-Type': 'application/json'
77
+ let breadcrumbData: { menu?: unknown } = {};
78
+
79
+ try {
80
+ breadcrumbData = await appFetch<{ menu?: unknown }>({
81
+ url: product.breadcrumbUrl(menuItemModel),
82
+ locale,
83
+ currency,
84
+ init: {
85
+ headers: {
86
+ Accept: 'application/json',
87
+ 'Content-Type': 'application/json'
88
+ }
75
89
  }
76
- }
77
- });
90
+ });
91
+ } catch (error) {
92
+ logger.warn('Failed to fetch breadcrumb data', {
93
+ handler: 'getCategoryDataHandler',
94
+ pk,
95
+ error: error.message
96
+ });
97
+ }
78
98
 
79
99
  return { data, breadcrumbData: breadcrumbData?.menu };
80
100
  };
@@ -138,8 +158,8 @@ function getCategoryBySlugDataHandler(
138
158
  logger.fatal('Error while parsing category data', {
139
159
  handler: 'getCategoryBySlugDataHandler',
140
160
  error,
141
- rawData: rawData.startsWith('<!DOCTYPE html>')
142
- ? `${rawData.substring(0, 50)}...`
161
+ rawData: rawData?.startsWith?.('<!DOCTYPE html>')
162
+ ? `${rawData?.substring(0, 50)}...`
143
163
  : rawData
144
164
  });
145
165
  }
@@ -11,20 +11,24 @@ const getFlatPageDataHandler = (
11
11
  headers?: Record<string, string>
12
12
  ) => {
13
13
  return async function () {
14
- const data = await appFetch<FlatPage>({
15
- url: flatpage.getFlatPageByPk(pk),
16
- locale,
17
- currency,
18
- init: {
19
- headers: {
20
- Accept: 'application/json',
21
- 'Content-Type': 'application/json',
22
- ...(headers ?? {})
14
+ try {
15
+ const data = await appFetch<FlatPage>({
16
+ url: flatpage.getFlatPageByPk(pk),
17
+ locale,
18
+ currency,
19
+ init: {
20
+ headers: {
21
+ Accept: 'application/json',
22
+ 'Content-Type': 'application/json',
23
+ ...(headers ?? {})
24
+ }
23
25
  }
24
- }
25
- });
26
+ });
26
27
 
27
- return data;
28
+ return data;
29
+ } catch (error) {
30
+ return null;
31
+ }
28
32
  };
29
33
  };
30
34
 
@@ -11,20 +11,24 @@ const getLandingPageHandler = (
11
11
  headers?: Record<string, string>
12
12
  ) => {
13
13
  return async function () {
14
- const data = await appFetch<LandingPage>({
15
- url: landingpage.getLandingPageByPk(pk),
16
- locale,
17
- currency,
18
- init: {
19
- headers: {
20
- Accept: 'application/json',
21
- 'Content-Type': 'application/json',
22
- ...(headers ?? {})
14
+ try {
15
+ const data = await appFetch<LandingPage>({
16
+ url: landingpage.getLandingPageByPk(pk),
17
+ locale,
18
+ currency,
19
+ init: {
20
+ headers: {
21
+ Accept: 'application/json',
22
+ 'Content-Type': 'application/json',
23
+ ...(headers ?? {})
24
+ }
23
25
  }
24
- }
25
- });
26
+ });
26
27
 
27
- return data;
28
+ return data;
29
+ } catch (error) {
30
+ return null;
31
+ }
28
32
  };
29
33
  };
30
34
 
@@ -16,19 +16,29 @@ const getListDataHandler = (
16
16
  return async function () {
17
17
  const params = generateCommerceSearchParams(searchParams);
18
18
 
19
- const rawData = await appFetch<string>({
20
- url: `${category.list}${params}`,
21
- locale,
22
- currency,
23
- init: {
24
- headers: {
25
- Accept: 'application/json',
26
- 'Content-Type': 'application/json',
27
- ...(headers ?? {})
28
- }
29
- },
30
- responseType: FetchResponseType.TEXT
31
- });
19
+ let rawData: string;
20
+
21
+ try {
22
+ rawData = await appFetch<string>({
23
+ url: `${category.list}${params}`,
24
+ locale,
25
+ currency,
26
+ init: {
27
+ headers: {
28
+ Accept: 'application/json',
29
+ 'Content-Type': 'application/json',
30
+ ...(headers ?? {})
31
+ }
32
+ },
33
+ responseType: FetchResponseType.TEXT
34
+ });
35
+ } catch (error) {
36
+ logger.error('Failed to fetch list data', {
37
+ handler: 'getListDataHandler',
38
+ error: error.message
39
+ });
40
+ return null;
41
+ }
32
42
 
33
43
  let data: GetCategoryResponse;
34
44
 
@@ -35,57 +35,84 @@ const getProductDataHandler = ({
35
35
  .join('&');
36
36
  }
37
37
 
38
- const data = await appFetch<ProductResult>({
39
- url,
40
- locale,
41
- currency,
42
- init: {
43
- headers: {
44
- Accept: 'application/json',
45
- 'Content-Type': 'application/json',
46
- ...(headers ?? {})
38
+ let data: ProductResult;
39
+
40
+ try {
41
+ data = await appFetch<ProductResult>({
42
+ url,
43
+ locale,
44
+ currency,
45
+ init: {
46
+ headers: {
47
+ Accept: 'application/json',
48
+ 'Content-Type': 'application/json',
49
+ ...(headers ?? {})
50
+ }
47
51
  }
48
- }
49
- });
52
+ });
53
+ } catch (error) {
54
+ logger.error('Failed to fetch product data', {
55
+ handler: 'getProductDataHandler',
56
+ pk,
57
+ error: error.message,
58
+ url
59
+ });
60
+ return null;
61
+ }
50
62
 
51
63
  const categoryUrl = product.categoryUrl(data.product.pk);
52
64
 
53
- const productCategoryData = await appFetch<ProductCategoryResult>({
54
- url: categoryUrl,
55
- locale,
56
- currency,
57
- init: {
58
- headers: {
59
- Accept: 'application/json',
60
- 'Content-Type': 'application/json'
65
+ let productCategoryData: ProductCategoryResult;
66
+ let breadcrumbData: { menu?: unknown } = {};
67
+
68
+ try {
69
+ productCategoryData = await appFetch<ProductCategoryResult>({
70
+ url: categoryUrl,
71
+ locale,
72
+ currency,
73
+ init: {
74
+ headers: {
75
+ Accept: 'application/json',
76
+ 'Content-Type': 'application/json'
77
+ }
61
78
  }
79
+ });
80
+
81
+ const menuItemModel = productCategoryData?.results[0]?.menuitemmodel;
82
+
83
+ if (!menuItemModel) {
84
+ logger.warn(
85
+ 'menuItemModel is undefined, skipping breadcrumbData fetch',
86
+ {
87
+ handler: 'getProductDataHandler',
88
+ pk
89
+ }
90
+ );
91
+ return { data, breadcrumbData: undefined };
62
92
  }
63
- });
64
93
 
65
- const menuItemModel = productCategoryData?.results[0]?.menuitemmodel;
94
+ const breadcrumbUrl = product.breadcrumbUrl(menuItemModel);
66
95
 
67
- if (!menuItemModel) {
68
- logger.warn('menuItemModel is undefined, skipping breadcrumbData fetch', {
96
+ breadcrumbData = await appFetch<{ menu?: unknown }>({
97
+ url: breadcrumbUrl,
98
+ locale,
99
+ currency,
100
+ init: {
101
+ headers: {
102
+ Accept: 'application/json',
103
+ 'Content-Type': 'application/json'
104
+ }
105
+ }
106
+ });
107
+ } catch (error) {
108
+ logger.warn('Failed to fetch breadcrumb data', {
69
109
  handler: 'getProductDataHandler',
70
- pk
110
+ pk,
111
+ error: error.message
71
112
  });
72
- return { data, breadcrumbData: undefined };
113
+ // Continue without breadcrumb data
73
114
  }
74
115
 
75
- const breadcrumbUrl = product.breadcrumbUrl(menuItemModel);
76
-
77
- const breadcrumbData = await appFetch<any>({
78
- url: breadcrumbUrl,
79
- locale,
80
- currency,
81
- init: {
82
- headers: {
83
- Accept: 'application/json',
84
- 'Content-Type': 'application/json'
85
- }
86
- }
87
- });
88
-
89
116
  return {
90
117
  data,
91
118
  breadcrumbData: breadcrumbData?.menu
@@ -15,20 +15,24 @@ const getSpecialPageDataHandler = (
15
15
  return async function () {
16
16
  const params = generateCommerceSearchParams(searchParams);
17
17
 
18
- const data: GetCategoryResponse = await appFetch({
19
- url: `${category.getSpecialPageByPk(pk)}${params}`,
20
- locale,
21
- currency,
22
- init: {
23
- headers: {
24
- Accept: 'application/json',
25
- 'Content-Type': 'application/json',
26
- ...(headers ?? {})
18
+ try {
19
+ const data: GetCategoryResponse = await appFetch({
20
+ url: `${category.getSpecialPageByPk(pk)}${params}`,
21
+ locale,
22
+ currency,
23
+ init: {
24
+ headers: {
25
+ Accept: 'application/json',
26
+ 'Content-Type': 'application/json',
27
+ ...(headers ?? {})
28
+ }
27
29
  }
28
- }
29
- });
30
+ });
30
31
 
31
- return data;
32
+ return data;
33
+ } catch (error) {
34
+ return null;
35
+ }
32
36
  };
33
37
  };
34
38
 
package/data/urls.ts CHANGED
@@ -142,7 +142,8 @@ export const checkout = {
142
142
  setOrderSelectionPage: '/orders/checkout/?page=OrderSelectionPage',
143
143
  loyaltyCardPage: '/orders/checkout/?page=LoyaltyCardPage',
144
144
  sendSmsPage: '/orders/checkout/?page=SendSmsPage',
145
- verifySmsPage: '/orders/checkout/?page=VerifySmsPage'
145
+ verifySmsPage: '/orders/checkout/?page=VerifySmsPage',
146
+ saveSampleProducts: '/orders/checkout/?page=SampleProductPage'
146
147
  };
147
148
 
148
149
  export const flatpage = {
@@ -182,7 +183,11 @@ export const product = {
182
183
  breadcrumbUrl: (menuitemmodel: string) =>
183
184
  `/menus/generate_breadcrumb/?item=${menuitemmodel}&generator_name=menu_item`,
184
185
  bundleProduct: (productPk: string, queryString: string) =>
185
- `/bundle-product/${productPk}/?${queryString}`
186
+ `/bundle-product/${productPk}/?${queryString}`,
187
+ similarProducts: (params?: string) =>
188
+ `/similar-products${params ? `?${params}` : ''}`,
189
+ similarProductsList: (params?: string) =>
190
+ `/similar-product-list${params ? `?${params}` : ''}`
186
191
  };
187
192
 
188
193
  export const wishlist = {
@@ -72,10 +72,13 @@ const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
72
72
  const checkRedisVariables = () => {
73
73
  const requiredVariableValues = [
74
74
  process.env.CACHE_HOST,
75
- process.env.CACHE_PORT,
76
- process.env.CACHE_SECRET
75
+ process.env.CACHE_PORT
77
76
  ];
78
77
 
78
+ if (!settings.usePrettyUrlRoute) {
79
+ requiredVariableValues.push(process.env.CACHE_SECRET);
80
+ }
81
+
79
82
  if (
80
83
  !requiredVariableValues.every((v) => v) &&
81
84
  process.env.NODE_ENV === 'production'
@@ -4,7 +4,6 @@ import { LocalizationContext } from '../localization/provider';
4
4
  import { useContext } from 'react';
5
5
  import { setCookie, urlLocaleMatcherRegex } from '../utils';
6
6
  import { LocaleUrlStrategy } from '../localization';
7
- import { useRouter } from 'next/navigation';
8
7
 
9
8
  export const useLocalization = () => {
10
9
  const {
@@ -18,8 +17,6 @@ export const useLocalization = () => {
18
17
  localeUrlStrategy
19
18
  } = useContext(LocalizationContext);
20
19
 
21
- const router = useRouter();
22
-
23
20
  /**
24
21
  * Sets the locale in the URL.
25
22
  * @param locale Locale value defined in the settings.
@@ -30,6 +27,8 @@ export const useLocalization = () => {
30
27
 
31
28
  let targetUrl;
32
29
 
30
+ setCookie('pz-locale', locale);
31
+
33
32
  if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
34
33
  const hostParts = hostname.split('.');
35
34
  const subDomain = hostParts[0];
@@ -0,0 +1,21 @@
1
+ import { useAppSelector } from '../redux/hooks';
2
+
3
+ export const useLoyaltyAvailability = () => {
4
+ const { paymentOptions, unavailablePaymentOptions } = useAppSelector(
5
+ (state) => state.checkout
6
+ );
7
+
8
+ const hasLoyaltyInAvailable = paymentOptions.some(
9
+ (option) =>
10
+ option.payment_type === 'loyalty_money' ||
11
+ option.payment_type === 'loyalty'
12
+ );
13
+
14
+ const hasLoyaltyInUnavailable = unavailablePaymentOptions.some(
15
+ (option) =>
16
+ option.payment_type === 'loyalty_money' ||
17
+ option.payment_type === 'loyalty'
18
+ );
19
+
20
+ return hasLoyaltyInAvailable || hasLoyaltyInUnavailable;
21
+ };
@@ -4,17 +4,19 @@ import { Resource } from '@opentelemetry/resources';
4
4
  import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
5
5
  import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
6
6
 
7
- const sdk = new NodeSDK({
8
- resource: new Resource({
9
- [SemanticResourceAttributes.SERVICE_NAME]: 'pz-next-app'
10
- }),
11
- spanProcessor: new SimpleSpanProcessor(
12
- new OTLPTraceExporter({
13
- url: `${
14
- process.env.PZ_DASHBOARD_URL ?? 'http://localhost:3005'
15
- }/api/traces`
16
- })
17
- )
18
- });
7
+ if (process.env.NODE_ENV === 'development') {
8
+ const sdk = new NodeSDK({
9
+ resource: new Resource({
10
+ [SemanticResourceAttributes.SERVICE_NAME]: 'pz-next-app'
11
+ }),
12
+ spanProcessor: new SimpleSpanProcessor(
13
+ new OTLPTraceExporter({
14
+ url: `${
15
+ process.env.PZ_DASHBOARD_URL ?? 'http://localhost:3005'
16
+ }/api/traces`
17
+ })
18
+ )
19
+ });
19
20
 
20
- sdk.start();
21
+ sdk.start();
22
+ }
package/jest.config.js CHANGED
@@ -1,13 +1,19 @@
1
1
  const path = require('path');
2
+ const findBaseDir = require('./utils/find-base-dir');
3
+
4
+ const baseDir = findBaseDir();
2
5
 
3
6
  module.exports = {
4
7
  preset: 'ts-jest',
5
8
  testEnvironment: 'node',
6
9
  rootDir: path.resolve(__dirname),
7
- roots: [],
8
10
  testMatch: ['**/*.test.ts'],
9
11
  testPathIgnorePatterns: [],
12
+ roots: [path.resolve(__dirname)],
10
13
  transformIgnorePatterns: [],
14
+ moduleNameMapper: {
15
+ '^settings$': path.resolve(baseDir, 'src/settings.js')
16
+ },
11
17
  transform: {
12
18
  '^.+\\.(tsx?|jsx?|mjs?)$': [
13
19
  'ts-jest',