@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.
- package/CHANGELOG.md +1252 -24
- package/__tests__/next-config.test.ts +1 -10
- package/__tests__/redirect.test.ts +319 -0
- package/api/image-proxy.ts +75 -0
- package/api/similar-product-list.ts +84 -0
- package/api/similar-products.ts +120 -0
- package/components/accordion.tsx +20 -5
- package/components/file-input.tsx +65 -3
- package/components/input.tsx +2 -0
- package/components/link.tsx +16 -12
- package/components/modal.tsx +32 -16
- package/components/plugin-module.tsx +35 -3
- package/components/selected-payment-option-view.tsx +11 -0
- package/data/client/checkout.ts +25 -4
- package/data/server/basket.ts +72 -0
- package/data/server/category.ts +48 -28
- package/data/server/flatpage.ts +16 -12
- package/data/server/landingpage.ts +16 -12
- package/data/server/list.ts +23 -13
- package/data/server/product.ts +66 -39
- package/data/server/special-page.ts +16 -12
- package/data/urls.ts +7 -2
- package/hocs/server/with-segment-defaults.tsx +5 -2
- package/hooks/use-localization.ts +2 -3
- package/hooks/use-loyalty-availability.ts +21 -0
- package/instrumentation/node.ts +15 -13
- package/jest.config.js +7 -1
- package/lib/cache.ts +2 -0
- package/middlewares/checkout-provider.ts +1 -1
- package/middlewares/complete-gpay.ts +6 -2
- package/middlewares/complete-masterpass.ts +7 -2
- package/middlewares/default.ts +232 -183
- package/middlewares/index.ts +3 -1
- package/middlewares/locale.ts +9 -1
- package/middlewares/redirection-payment.ts +6 -2
- package/middlewares/saved-card-redirection.ts +7 -2
- package/middlewares/three-d-redirection.ts +7 -2
- package/middlewares/url-redirection.ts +9 -15
- package/middlewares/wallet-complete-redirection.ts +203 -0
- package/package.json +3 -3
- package/plugins.d.ts +10 -0
- package/plugins.js +4 -1
- package/redux/middlewares/checkout.ts +15 -2
- package/redux/reducers/checkout.ts +9 -1
- package/sentry/index.ts +54 -17
- package/types/commerce/order.ts +1 -0
- package/types/index.ts +42 -1
- package/utils/app-fetch.ts +7 -2
- package/utils/index.ts +34 -10
- package/utils/redirect-ignore.ts +35 -0
- package/utils/redirect.ts +31 -6
- package/with-pz-config.js +1 -5
package/data/server/product.ts
CHANGED
|
@@ -35,57 +35,84 @@ const getProductDataHandler = ({
|
|
|
35
35
|
.join('&');
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
94
|
+
const breadcrumbUrl = product.breadcrumbUrl(menuItemModel);
|
|
66
95
|
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/instrumentation/node.ts
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
new
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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') ? '' :
|
|
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,
|