@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
|
@@ -0,0 +1,203 @@
|
|
|
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: 'wallet-complete-redirection',
|
|
22
|
+
error
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const withWalletCompleteRedirection =
|
|
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('WalletCompletePage') === -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: req.headers.get('cookie') ?? '',
|
|
47
|
+
'x-currency': req.cookies.get('pz-currency')?.value ?? '',
|
|
48
|
+
'x-forwarded-for': ip
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const existingBody = await streamToString(req.body);
|
|
53
|
+
const queryParams = new URLSearchParams(url.search);
|
|
54
|
+
const bodyParams = new URLSearchParams();
|
|
55
|
+
|
|
56
|
+
if (existingBody) {
|
|
57
|
+
try {
|
|
58
|
+
const existingParams = new URLSearchParams(existingBody);
|
|
59
|
+
|
|
60
|
+
existingParams.forEach((value, key) => {
|
|
61
|
+
bodyParams.append(key, value);
|
|
62
|
+
});
|
|
63
|
+
} catch {
|
|
64
|
+
logger.error('Error parsing existing body as URL-encoded data', {
|
|
65
|
+
middleware: 'wallet-complete-redirection',
|
|
66
|
+
existingBody,
|
|
67
|
+
ip
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
queryParams.forEach((value, key) => {
|
|
73
|
+
bodyParams.append(key, value);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const body = bodyParams.toString();
|
|
77
|
+
|
|
78
|
+
if (!sessionId) {
|
|
79
|
+
logger.warn(
|
|
80
|
+
'Make sure that the SESSION_COOKIE_SAMESITE environment variable is set to None in Commerce.',
|
|
81
|
+
{
|
|
82
|
+
middleware: 'wallet-complete-redirection',
|
|
83
|
+
ip
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return NextResponse.redirect(
|
|
88
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
89
|
+
'/orders/checkout/',
|
|
90
|
+
req.cookies.get('pz-locale')?.value
|
|
91
|
+
)}`,
|
|
92
|
+
303
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const request = await fetch(requestUrl, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: requestHeaders,
|
|
99
|
+
body
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
logger.info('Complete wallet payment request', {
|
|
103
|
+
requestUrl,
|
|
104
|
+
status: request.status,
|
|
105
|
+
requestHeaders,
|
|
106
|
+
ip
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const response = await request.json();
|
|
110
|
+
|
|
111
|
+
const { context_list: contextList, errors } = response;
|
|
112
|
+
const redirectionContext = contextList?.find(
|
|
113
|
+
(context) => context.page_context?.redirect_url
|
|
114
|
+
);
|
|
115
|
+
const redirectUrl = redirectionContext?.page_context?.redirect_url;
|
|
116
|
+
|
|
117
|
+
if (errors && Object.keys(errors).length) {
|
|
118
|
+
logger.error('Error while completing wallet payment', {
|
|
119
|
+
middleware: 'wallet-complete-redirection',
|
|
120
|
+
errors,
|
|
121
|
+
requestHeaders,
|
|
122
|
+
ip
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return NextResponse.redirect(
|
|
126
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
127
|
+
'/orders/checkout/',
|
|
128
|
+
req.cookies.get('pz-locale')?.value
|
|
129
|
+
)}`,
|
|
130
|
+
{
|
|
131
|
+
status: 303,
|
|
132
|
+
headers: {
|
|
133
|
+
'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
logger.info('Order success page context list', {
|
|
140
|
+
middleware: 'wallet-complete-redirection',
|
|
141
|
+
contextList,
|
|
142
|
+
ip
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!redirectUrl) {
|
|
146
|
+
logger.warn(
|
|
147
|
+
'No redirection url for order success page found in page_context. Redirecting to checkout page.',
|
|
148
|
+
{
|
|
149
|
+
middleware: 'wallet-complete-redirection',
|
|
150
|
+
requestHeaders,
|
|
151
|
+
response: JSON.stringify(response),
|
|
152
|
+
ip
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
|
|
157
|
+
'/orders/checkout/',
|
|
158
|
+
req.cookies.get('pz-locale')?.value
|
|
159
|
+
)}`;
|
|
160
|
+
|
|
161
|
+
return NextResponse.redirect(redirectUrlWithLocale, 303);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
|
|
165
|
+
redirectUrl,
|
|
166
|
+
req.cookies.get('pz-locale')?.value
|
|
167
|
+
)}`;
|
|
168
|
+
|
|
169
|
+
logger.info('Redirecting to order success page', {
|
|
170
|
+
middleware: 'wallet-complete-redirection',
|
|
171
|
+
redirectUrlWithLocale,
|
|
172
|
+
ip
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Using POST method while redirecting causes an error,
|
|
176
|
+
// So we use 303 status code to change the method to GET
|
|
177
|
+
const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
|
|
178
|
+
|
|
179
|
+
nextResponse.headers.set(
|
|
180
|
+
'Set-Cookie',
|
|
181
|
+
request.headers.get('set-cookie') ?? ''
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
return nextResponse;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
logger.error('Error while completing wallet payment', {
|
|
187
|
+
middleware: 'wallet-complete-redirection',
|
|
188
|
+
error,
|
|
189
|
+
requestHeaders,
|
|
190
|
+
ip
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return NextResponse.redirect(
|
|
194
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
195
|
+
'/orders/checkout/',
|
|
196
|
+
req.cookies.get('pz-locale')?.value
|
|
197
|
+
)}`,
|
|
198
|
+
303
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export default withWalletCompleteRedirection;
|
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.
|
|
4
|
+
"version": "1.95.0-rc.54",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
"test": "jest"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@neshca/cache-handler": "1.5.1",
|
|
21
20
|
"@opentelemetry/exporter-trace-otlp-http": "0.46.0",
|
|
22
21
|
"@opentelemetry/resources": "1.19.0",
|
|
23
22
|
"@opentelemetry/sdk-node": "0.46.0",
|
|
24
23
|
"@opentelemetry/sdk-trace-node": "1.19.0",
|
|
25
24
|
"@opentelemetry/semantic-conventions": "1.19.0",
|
|
26
25
|
"@reduxjs/toolkit": "1.9.7",
|
|
26
|
+
"@neshca/cache-handler": "1.9.0",
|
|
27
27
|
"@sentry/nextjs": "9.5.0",
|
|
28
28
|
"cross-spawn": "7.0.3",
|
|
29
29
|
"generic-pool": "3.9.0",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"set-cookie-parser": "2.6.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@akinon/eslint-plugin-projectzero": "1.
|
|
37
|
+
"@akinon/eslint-plugin-projectzero": "1.95.0-rc.54",
|
|
38
38
|
"@babel/core": "7.26.10",
|
|
39
39
|
"@babel/preset-env": "7.26.9",
|
|
40
40
|
"@babel/preset-typescript": "7.27.0",
|
package/plugins.d.ts
CHANGED
|
@@ -36,3 +36,13 @@ declare module '@akinon/pz-iyzico-saved-card' {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
declare module '@akinon/pz-apple-pay' {}
|
|
39
|
+
|
|
40
|
+
declare module '@akinon/pz-flow-payment' {}
|
|
41
|
+
|
|
42
|
+
declare module '@akinon/pz-similar-products' {
|
|
43
|
+
export const SimilarProductsModal: any;
|
|
44
|
+
export const SimilarProductsFilterSidebar: any;
|
|
45
|
+
export const SimilarProductsResultsGrid: any;
|
|
46
|
+
export const SimilarProductsPlugin: any;
|
|
47
|
+
export const SimilarProductsButtonPlugin: any;
|
|
48
|
+
}
|
package/plugins.js
CHANGED
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
setShippingOptions,
|
|
21
21
|
setHepsipayAvailability,
|
|
22
22
|
setWalletPaymentData,
|
|
23
|
-
setPayOnDeliveryOtpModalActive
|
|
23
|
+
setPayOnDeliveryOtpModalActive,
|
|
24
|
+
setUnavailablePaymentOptions
|
|
24
25
|
} from '../../redux/reducers/checkout';
|
|
25
26
|
import { RootState, TypedDispatch } from 'redux/store';
|
|
26
27
|
import { checkoutApi } from '../../data/client/checkout';
|
|
@@ -50,7 +51,11 @@ export const errorMiddleware: Middleware = ({ dispatch }: MiddlewareParams) => {
|
|
|
50
51
|
const result: CheckoutResult = next(action);
|
|
51
52
|
const errors = result?.payload?.errors;
|
|
52
53
|
|
|
53
|
-
if (
|
|
54
|
+
if (
|
|
55
|
+
!!errors &&
|
|
56
|
+
((typeof errors === 'object' && Object.keys(errors).length > 0) ||
|
|
57
|
+
(Array.isArray(errors) && errors.length > 0))
|
|
58
|
+
) {
|
|
54
59
|
dispatch(setErrors(errors));
|
|
55
60
|
}
|
|
56
61
|
|
|
@@ -176,6 +181,14 @@ export const contextListMiddleware: Middleware = ({
|
|
|
176
181
|
dispatch(setPaymentOptions(context.page_context.payment_options));
|
|
177
182
|
}
|
|
178
183
|
|
|
184
|
+
if (context.page_context.unavailable_options) {
|
|
185
|
+
dispatch(
|
|
186
|
+
setUnavailablePaymentOptions(
|
|
187
|
+
context.page_context.unavailable_options
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
179
192
|
if (context.page_context.credit_payment_options) {
|
|
180
193
|
dispatch(
|
|
181
194
|
setCreditPaymentOptions(context.page_context.credit_payment_options)
|
|
@@ -40,6 +40,7 @@ export interface CheckoutState {
|
|
|
40
40
|
shippingOptions: ShippingOption[];
|
|
41
41
|
dataSourceShippingOptions: DataSource[];
|
|
42
42
|
paymentOptions: PaymentOption[];
|
|
43
|
+
unavailablePaymentOptions: PaymentOption[];
|
|
43
44
|
creditPaymentOptions: CheckoutCreditPaymentOption[];
|
|
44
45
|
selectedCreditPaymentPk: number;
|
|
45
46
|
paymentChoices: PaymentChoice[];
|
|
@@ -60,6 +61,8 @@ export interface CheckoutState {
|
|
|
60
61
|
countryCode: string;
|
|
61
62
|
currencyCode: string;
|
|
62
63
|
version: string;
|
|
64
|
+
public_key: string;
|
|
65
|
+
[key: string]: any;
|
|
63
66
|
};
|
|
64
67
|
detail: {
|
|
65
68
|
label: string;
|
|
@@ -94,6 +97,7 @@ const initialState: CheckoutState = {
|
|
|
94
97
|
shippingOptions: [],
|
|
95
98
|
dataSourceShippingOptions: [],
|
|
96
99
|
paymentOptions: [],
|
|
100
|
+
unavailablePaymentOptions: [],
|
|
97
101
|
creditPaymentOptions: [],
|
|
98
102
|
selectedCreditPaymentPk: null,
|
|
99
103
|
paymentChoices: [],
|
|
@@ -157,6 +161,9 @@ const checkoutSlice = createSlice({
|
|
|
157
161
|
setPaymentOptions(state, { payload }) {
|
|
158
162
|
state.paymentOptions = payload;
|
|
159
163
|
},
|
|
164
|
+
setUnavailablePaymentOptions(state, { payload }) {
|
|
165
|
+
state.unavailablePaymentOptions = payload;
|
|
166
|
+
},
|
|
160
167
|
setPaymentChoices(state, { payload }) {
|
|
161
168
|
state.paymentChoices = payload;
|
|
162
169
|
},
|
|
@@ -218,9 +225,10 @@ export const {
|
|
|
218
225
|
setShippingOptions,
|
|
219
226
|
setDataSourceShippingOptions,
|
|
220
227
|
setPaymentOptions,
|
|
228
|
+
setUnavailablePaymentOptions,
|
|
229
|
+
setPaymentChoices,
|
|
221
230
|
setCreditPaymentOptions,
|
|
222
231
|
setSelectedCreditPaymentPk,
|
|
223
|
-
setPaymentChoices,
|
|
224
232
|
setCardType,
|
|
225
233
|
setInstallmentOptions,
|
|
226
234
|
setBankAccounts,
|
package/sentry/index.ts
CHANGED
|
@@ -13,36 +13,73 @@ const ALLOWED_CLIENT_LOG_TYPES: ClientLogType[] = [
|
|
|
13
13
|
ClientLogType.CHECKOUT
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
+
const isNetworkError = (exception: unknown): boolean => {
|
|
17
|
+
if (!(exception instanceof Error)) return false;
|
|
18
|
+
|
|
19
|
+
const networkErrorPatterns = [
|
|
20
|
+
'networkerror',
|
|
21
|
+
'failed to fetch',
|
|
22
|
+
'network request failed',
|
|
23
|
+
'network error',
|
|
24
|
+
'loading chunk',
|
|
25
|
+
'chunk load failed'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
if (exception.name === 'NetworkError') return true;
|
|
29
|
+
|
|
30
|
+
if (exception.name === 'TypeError') {
|
|
31
|
+
return networkErrorPatterns.some((pattern) =>
|
|
32
|
+
exception.message.toLowerCase().includes(pattern)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return networkErrorPatterns.some((pattern) =>
|
|
37
|
+
exception.message.toLowerCase().includes(pattern)
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
16
41
|
export const initSentry = (
|
|
17
42
|
type: 'Server' | 'Client' | 'Edge',
|
|
18
43
|
options: Sentry.BrowserOptions | Sentry.NodeOptions | Sentry.EdgeOptions = {}
|
|
19
44
|
) => {
|
|
20
|
-
// TODO:
|
|
45
|
+
// TODO: Remove Zero Project DSN
|
|
21
46
|
|
|
22
|
-
|
|
47
|
+
const baseConfig = {
|
|
23
48
|
dsn:
|
|
24
|
-
options.dsn ||
|
|
25
49
|
SENTRY_DSN ||
|
|
50
|
+
options.dsn ||
|
|
26
51
|
'https://d8558ef8997543deacf376c7d8d7cf4b@o64293.ingest.sentry.io/4504338423742464',
|
|
27
52
|
initialScope: {
|
|
28
53
|
tags: {
|
|
29
54
|
APP_TYPE: 'ProjectZeroNext',
|
|
30
|
-
TYPE: type
|
|
55
|
+
TYPE: type,
|
|
56
|
+
...((options.initialScope as any)?.tags || {})
|
|
31
57
|
}
|
|
32
58
|
},
|
|
33
59
|
tracesSampleRate: 0,
|
|
34
|
-
integrations: []
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
type === 'Client' &&
|
|
38
|
-
!ALLOWED_CLIENT_LOG_TYPES.includes(
|
|
39
|
-
event.tags?.LOG_TYPE as ClientLogType
|
|
40
|
-
)
|
|
41
|
-
) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
60
|
+
integrations: []
|
|
61
|
+
};
|
|
44
62
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
})
|
|
63
|
+
if (type === 'Server' || type === 'Edge') {
|
|
64
|
+
Sentry.init(baseConfig);
|
|
65
|
+
} else if (type === 'Client') {
|
|
66
|
+
Sentry.init({
|
|
67
|
+
...baseConfig,
|
|
68
|
+
beforeSend: (event, hint) => {
|
|
69
|
+
if (
|
|
70
|
+
!ALLOWED_CLIENT_LOG_TYPES.includes(
|
|
71
|
+
event.tags?.LOG_TYPE as ClientLogType
|
|
72
|
+
)
|
|
73
|
+
) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isNetworkError(hint?.originalException)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return event;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
48
85
|
};
|
package/types/commerce/order.ts
CHANGED
package/types/index.ts
CHANGED
|
@@ -83,6 +83,12 @@ export interface Settings {
|
|
|
83
83
|
};
|
|
84
84
|
usePrettyUrlRoute?: boolean;
|
|
85
85
|
commerceUrl: string;
|
|
86
|
+
/**
|
|
87
|
+
* This option allows you to track Sentry events on the client side, in addition to server and edge environments.
|
|
88
|
+
*
|
|
89
|
+
* It overrides process.env.NEXT_PUBLIC_SENTRY_DSN and process.env.SENTRY_DSN.
|
|
90
|
+
*/
|
|
91
|
+
sentryDsn?: string;
|
|
86
92
|
redis: {
|
|
87
93
|
defaultExpirationTime: number;
|
|
88
94
|
};
|
|
@@ -218,6 +224,14 @@ export interface CacheOptions {
|
|
|
218
224
|
useProxy?: boolean;
|
|
219
225
|
}
|
|
220
226
|
|
|
227
|
+
export interface SetCookieOptions {
|
|
228
|
+
expires?: number; // days
|
|
229
|
+
path?: string;
|
|
230
|
+
domain?: string;
|
|
231
|
+
secure?: boolean;
|
|
232
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
233
|
+
}
|
|
234
|
+
|
|
221
235
|
export interface ClientRequestOptions {
|
|
222
236
|
useTrailingSlash?: boolean;
|
|
223
237
|
useFormData?: boolean;
|
|
@@ -283,7 +297,13 @@ export interface ButtonProps
|
|
|
283
297
|
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
284
298
|
}
|
|
285
299
|
|
|
286
|
-
export
|
|
300
|
+
export interface FileInputProps extends React.HTMLProps<HTMLInputElement> {
|
|
301
|
+
fileClassName?: string;
|
|
302
|
+
fileNameWrapperClassName?: string;
|
|
303
|
+
fileInputClassName?: string;
|
|
304
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
305
|
+
buttonClassName?: string;
|
|
306
|
+
}
|
|
287
307
|
|
|
288
308
|
export interface PriceProps {
|
|
289
309
|
currencyCode?: string;
|
|
@@ -304,15 +324,19 @@ export interface InputProps extends React.HTMLProps<HTMLInputElement> {
|
|
|
304
324
|
|
|
305
325
|
export interface AccordionProps {
|
|
306
326
|
isCollapse?: boolean;
|
|
327
|
+
collapseClassName?: string;
|
|
307
328
|
title?: string;
|
|
308
329
|
subTitle?: string;
|
|
309
330
|
icons?: string[];
|
|
310
331
|
iconSize?: number;
|
|
311
332
|
iconColor?: string;
|
|
312
333
|
children?: ReactNode;
|
|
334
|
+
headerClassName?: string;
|
|
313
335
|
className?: string;
|
|
314
336
|
titleClassName?: string;
|
|
337
|
+
subTitleClassName?: string;
|
|
315
338
|
dataTestId?: string;
|
|
339
|
+
contentClassName?: string;
|
|
316
340
|
}
|
|
317
341
|
|
|
318
342
|
export interface PluginModuleComponentProps {
|
|
@@ -337,3 +361,20 @@ export interface PaginationProps {
|
|
|
337
361
|
direction?: 'next' | 'prev';
|
|
338
362
|
isLoading?: boolean;
|
|
339
363
|
}
|
|
364
|
+
|
|
365
|
+
export interface ModalProps {
|
|
366
|
+
portalId: string;
|
|
367
|
+
children?: React.ReactNode;
|
|
368
|
+
open?: boolean;
|
|
369
|
+
setOpen?: (open: boolean) => void;
|
|
370
|
+
title?: React.ReactNode;
|
|
371
|
+
showCloseButton?: React.ReactNode;
|
|
372
|
+
className?: string;
|
|
373
|
+
overlayClassName?: string;
|
|
374
|
+
headerWrapperClassName?: string;
|
|
375
|
+
titleClassName?: string;
|
|
376
|
+
closeButtonClassName?: string;
|
|
377
|
+
iconName?: string;
|
|
378
|
+
iconSize?: number;
|
|
379
|
+
iconClassName?: string;
|
|
380
|
+
}
|
package/utils/app-fetch.ts
CHANGED
|
@@ -43,12 +43,12 @@ const appFetch = async <T>({
|
|
|
43
43
|
const requestURL = `${decodeURIComponent(commerceUrl)}${url}`;
|
|
44
44
|
|
|
45
45
|
init.headers = {
|
|
46
|
+
cookie: nextCookies.toString(),
|
|
46
47
|
...(init.headers ?? {}),
|
|
47
48
|
...(ServerVariables.globalHeaders ?? {}),
|
|
48
49
|
'Accept-Language': currentLocale.apiValue,
|
|
49
50
|
'x-currency': currency,
|
|
50
|
-
'x-forwarded-for': ip
|
|
51
|
-
cookie: nextCookies.toString()
|
|
51
|
+
'x-forwarded-for': ip
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
init.next = {
|
|
@@ -60,6 +60,11 @@ const appFetch = async <T>({
|
|
|
60
60
|
status = req.status;
|
|
61
61
|
logger.debug(`FETCH END ${url}`, { status: req.status, ip });
|
|
62
62
|
|
|
63
|
+
if (!req.ok) {
|
|
64
|
+
const errorMessage = `HTTP ${req.status}: ${req.statusText}`;
|
|
65
|
+
throw new Error(errorMessage);
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
if (responseType === FetchResponseType.JSON) {
|
|
64
69
|
response = (await req.json()) as T;
|
|
65
70
|
} else {
|
package/utils/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import settings from 'settings';
|
|
2
2
|
import { LocaleUrlStrategy } from '../localization';
|
|
3
|
-
import { CDNOptions, ClientRequestOptions } from '../types';
|
|
3
|
+
import { CDNOptions, ClientRequestOptions, SetCookieOptions } from '../types';
|
|
4
|
+
import getRootHostname from './get-root-hostname';
|
|
4
5
|
|
|
5
6
|
export * from './get-currency';
|
|
6
7
|
export * from './menu-generator';
|
|
@@ -20,14 +21,40 @@ export function getCookie(name: string) {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
export function setCookie(
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
export function setCookie(
|
|
25
|
+
name: string,
|
|
26
|
+
value: string,
|
|
27
|
+
options: SetCookieOptions = {}
|
|
28
|
+
) {
|
|
29
|
+
const cookieParts = [`${name}=${value}`];
|
|
30
|
+
|
|
31
|
+
if (options.expires) {
|
|
32
|
+
const date = new Date();
|
|
33
|
+
date.setTime(date.getTime() + options.expires * 24 * 60 * 60 * 1000);
|
|
34
|
+
cookieParts.push(`expires=${date.toUTCString()}`);
|
|
35
|
+
}
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
cookieParts.push(`path=${options.path ?? '/'}`);
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
if (options.secure) {
|
|
40
|
+
cookieParts.push('secure');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (options.sameSite) {
|
|
44
|
+
cookieParts.push(`sameSite=${options.sameSite}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const domain =
|
|
48
|
+
options.domain ??
|
|
49
|
+
(settings.localization.localeUrlStrategy === LocaleUrlStrategy.Subdomain
|
|
50
|
+
? getRootHostname(document.location.href)
|
|
51
|
+
: null);
|
|
52
|
+
|
|
53
|
+
if (domain) {
|
|
54
|
+
cookieParts.push(`domain=${domain}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
document.cookie = cookieParts.join('; ');
|
|
31
58
|
}
|
|
32
59
|
|
|
33
60
|
export function removeCookie(name: string) {
|
|
@@ -152,9 +179,6 @@ export function buildCDNUrl(url: string, config?: CDNOptions) {
|
|
|
152
179
|
return `${rootWithoutOptions}${options}${fileExtension}`;
|
|
153
180
|
}
|
|
154
181
|
|
|
155
|
-
const { locales, localeUrlStrategy, defaultLocaleValue } =
|
|
156
|
-
settings.localization;
|
|
157
|
-
|
|
158
182
|
export const urlLocaleMatcherRegex = new RegExp(
|
|
159
183
|
`^/(${settings.localization.locales
|
|
160
184
|
.filter((l) =>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import settings from 'settings';
|
|
2
|
+
import { getUrlPathWithLocale } from './localization';
|
|
3
|
+
|
|
4
|
+
type IgnorePath = string | RegExp;
|
|
5
|
+
|
|
6
|
+
const defaultIgnoreList: string[] = [];
|
|
7
|
+
|
|
8
|
+
const extraIgnores: IgnorePath[] = Array.isArray(
|
|
9
|
+
settings.commerceRedirectionIgnoreList
|
|
10
|
+
)
|
|
11
|
+
? settings.commerceRedirectionIgnoreList.map((path) => {
|
|
12
|
+
if (path === '/users/reset') {
|
|
13
|
+
return /^\/users\/reset\/[^/]+\/[^/]+\/$/;
|
|
14
|
+
}
|
|
15
|
+
return path;
|
|
16
|
+
})
|
|
17
|
+
: [];
|
|
18
|
+
|
|
19
|
+
export function shouldIgnoreRedirect(
|
|
20
|
+
pathname: string,
|
|
21
|
+
locale: string
|
|
22
|
+
): boolean {
|
|
23
|
+
if (!pathname) return false;
|
|
24
|
+
|
|
25
|
+
const rawIgnoreList: IgnorePath[] = [...defaultIgnoreList, ...extraIgnores];
|
|
26
|
+
|
|
27
|
+
return rawIgnoreList.some((ignorePath) => {
|
|
28
|
+
if (ignorePath instanceof RegExp) {
|
|
29
|
+
return ignorePath.test(pathname);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const localized = getUrlPathWithLocale(ignorePath, locale);
|
|
33
|
+
return localized === pathname;
|
|
34
|
+
});
|
|
35
|
+
}
|