@akinon/next 1.93.0 → 1.95.0-rc.54

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 (52) hide show
  1. package/CHANGELOG.md +1252 -24
  2. package/__tests__/next-config.test.ts +1 -10
  3. package/__tests__/redirect.test.ts +319 -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/components/accordion.tsx +20 -5
  8. package/components/file-input.tsx +65 -3
  9. package/components/input.tsx +2 -0
  10. package/components/link.tsx +16 -12
  11. package/components/modal.tsx +32 -16
  12. package/components/plugin-module.tsx +35 -3
  13. package/components/selected-payment-option-view.tsx +11 -0
  14. package/data/client/checkout.ts +25 -4
  15. package/data/server/basket.ts +72 -0
  16. package/data/server/category.ts +48 -28
  17. package/data/server/flatpage.ts +16 -12
  18. package/data/server/landingpage.ts +16 -12
  19. package/data/server/list.ts +23 -13
  20. package/data/server/product.ts +66 -39
  21. package/data/server/special-page.ts +16 -12
  22. package/data/urls.ts +7 -2
  23. package/hocs/server/with-segment-defaults.tsx +5 -2
  24. package/hooks/use-localization.ts +2 -3
  25. package/hooks/use-loyalty-availability.ts +21 -0
  26. package/instrumentation/node.ts +15 -13
  27. package/jest.config.js +7 -1
  28. package/lib/cache.ts +2 -0
  29. package/middlewares/checkout-provider.ts +1 -1
  30. package/middlewares/complete-gpay.ts +6 -2
  31. package/middlewares/complete-masterpass.ts +7 -2
  32. package/middlewares/default.ts +232 -183
  33. package/middlewares/index.ts +3 -1
  34. package/middlewares/locale.ts +9 -1
  35. package/middlewares/redirection-payment.ts +6 -2
  36. package/middlewares/saved-card-redirection.ts +7 -2
  37. package/middlewares/three-d-redirection.ts +7 -2
  38. package/middlewares/url-redirection.ts +9 -15
  39. package/middlewares/wallet-complete-redirection.ts +203 -0
  40. package/package.json +3 -3
  41. package/plugins.d.ts +10 -0
  42. package/plugins.js +4 -1
  43. package/redux/middlewares/checkout.ts +15 -2
  44. package/redux/reducers/checkout.ts +9 -1
  45. package/sentry/index.ts +54 -17
  46. package/types/commerce/order.ts +1 -0
  47. package/types/index.ts +42 -1
  48. package/utils/app-fetch.ts +7 -2
  49. package/utils/index.ts +34 -10
  50. package/utils/redirect-ignore.ts +35 -0
  51. package/utils/redirect.ts +31 -6
  52. package/with-pz-config.js +1 -5
@@ -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',
package/lib/cache.ts CHANGED
@@ -31,6 +31,8 @@ export const CacheKey = {
31
31
  `category_${pk}_${encodeURIComponent(
32
32
  JSON.stringify(searchParams)
33
33
  )}${hashCacheKey(headers)}`,
34
+ Basket: (namespace?: string) => `basket${namespace ? `_${namespace}` : ''}`,
35
+ AllBaskets: () => 'all_baskets',
34
36
  CategorySlug: (slug: string) => `category_${slug}`,
35
37
  SpecialPage: (
36
38
  pk: number,
@@ -64,7 +64,7 @@ const withCheckoutProvider =
64
64
  const location = request.headers.get('location');
65
65
  const redirectUrl = new URL(
66
66
  request.headers.get('location'),
67
- location.startsWith('http') ? '' : process.env.NEXT_PUBLIC_URL
67
+ location.startsWith('http') ? '' : url.origin
68
68
  );
69
69
 
70
70
  redirectUrl.pathname = getUrlPathWithLocale(
@@ -34,6 +34,7 @@ const withCompleteGpay =
34
34
  const url = req.nextUrl.clone();
35
35
  const ip = req.headers.get('x-forwarded-for') ?? '';
36
36
  const sessionId = req.cookies.get('osessionid');
37
+ const currentLocale = req.middlewareParams?.rewrites?.locale;
37
38
 
38
39
  if (url.search.indexOf('GPayCompletePage') === -1) {
39
40
  return middleware(req, event);
@@ -45,7 +46,9 @@ const withCompleteGpay =
45
46
  'Content-Type': 'application/x-www-form-urlencoded',
46
47
  Cookie: req.headers.get('cookie') ?? '',
47
48
  'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
- 'x-forwarded-for': ip
49
+ 'x-forwarded-for': ip,
50
+ 'Accept-Language':
51
+ currentLocale ?? req.cookies.get('pz-locale')?.value ?? ''
49
52
  };
50
53
 
51
54
  try {
@@ -145,7 +148,8 @@ const withCompleteGpay =
145
148
  logger.info('Redirecting to order success page', {
146
149
  middleware: 'complete-gpay',
147
150
  redirectUrlWithLocale,
148
- ip
151
+ ip,
152
+ setCookie: request.headers.get('set-cookie')
149
153
  });
150
154
 
151
155
  // Using POST method while redirecting causes an error,
@@ -4,6 +4,7 @@ import { Buffer } from 'buffer';
4
4
  import logger from '../utils/log';
5
5
  import { getUrlPathWithLocale } from '../utils/localization';
6
6
  import { PzNextRequest } from '.';
7
+ import { ServerVariables } from '../utils/server-variables';
7
8
 
8
9
  const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
9
10
  if (stream) {
@@ -34,6 +35,7 @@ const withCompleteMasterpass =
34
35
  const url = req.nextUrl.clone();
35
36
  const ip = req.headers.get('x-forwarded-for') ?? '';
36
37
  const sessionId = req.cookies.get('osessionid');
38
+ const currentLocale = req.middlewareParams?.rewrites?.locale;
37
39
 
38
40
  if (url.search.indexOf('MasterpassCompletePage') === -1) {
39
41
  return middleware(req, event);
@@ -45,7 +47,9 @@ const withCompleteMasterpass =
45
47
  'Content-Type': 'application/x-www-form-urlencoded',
46
48
  Cookie: req.headers.get('cookie') ?? '',
47
49
  'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
- 'x-forwarded-for': ip
50
+ 'x-forwarded-for': ip,
51
+ 'Accept-Language':
52
+ currentLocale ?? req.cookies.get('pz-locale')?.value ?? ''
49
53
  };
50
54
 
51
55
  try {
@@ -145,7 +149,8 @@ const withCompleteMasterpass =
145
149
  logger.info('Redirecting to order success page', {
146
150
  middleware: 'complete-masterpass',
147
151
  redirectUrlWithLocale,
148
- ip
152
+ ip,
153
+ setCookie: request.headers.get('set-cookie')
149
154
  });
150
155
 
151
156
  // Using POST method while redirecting causes an error,