@akinon/next 2.0.0-beta.0 → 2.0.0-beta.10
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/.eslintrc.js +12 -0
- package/CHANGELOG.md +56 -0
- package/api/auth.ts +17 -0
- package/api/client.ts +3 -2
- package/bin/run-prebuild-tests.js +46 -0
- package/components/accordion.tsx +1 -1
- package/components/button.tsx +51 -36
- package/components/client-root.tsx +20 -0
- package/components/input.tsx +1 -1
- package/components/modal.tsx +1 -1
- package/components/pz-root.tsx +1 -1
- package/components/select.tsx +1 -1
- package/components/selected-payment-option-view.tsx +1 -1
- package/data/client/api.ts +2 -0
- package/data/client/basket.ts +27 -5
- package/data/client/checkout.ts +62 -7
- package/data/client/misc.ts +25 -1
- package/data/client/product.ts +19 -2
- package/data/client/user.ts +16 -8
- package/data/server/flatpage.ts +8 -4
- package/data/server/form.ts +12 -4
- package/data/server/landingpage.ts +8 -4
- package/data/server/menu.ts +7 -2
- package/data/server/product.ts +16 -5
- package/data/server/seo.ts +11 -4
- package/data/server/widget.ts +19 -4
- package/data/urls.ts +11 -3
- package/hocs/client/with-segment-defaults.tsx +1 -1
- package/hocs/server/with-segment-defaults.tsx +6 -3
- package/hooks/index.ts +1 -0
- package/hooks/use-router.ts +5 -2
- package/hooks/use-sentry-uncaught-errors.ts +24 -0
- package/instrumentation/index.ts +10 -0
- package/lib/cache-handler.mjs +2 -2
- package/lib/cache.ts +4 -4
- package/localization/index.ts +2 -1
- package/middlewares/default.ts +6 -15
- package/middlewares/locale.ts +31 -10
- package/middlewares/url-redirection.ts +16 -0
- package/package.json +8 -7
- package/plugins.js +2 -1
- package/redux/middlewares/checkout.ts +16 -144
- package/redux/middlewares/index.ts +4 -2
- package/redux/middlewares/pre-order/address.ts +50 -0
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +46 -0
- package/redux/middlewares/pre-order/data-source-shipping-option.ts +43 -0
- package/redux/middlewares/pre-order/delivery-option.ts +40 -0
- package/redux/middlewares/pre-order/index.ts +29 -0
- package/redux/middlewares/pre-order/installment-option.ts +42 -0
- package/redux/middlewares/pre-order/payment-option.ts +34 -0
- package/redux/middlewares/pre-order/pre-order-validation.ts +24 -0
- package/redux/middlewares/pre-order/redirection.ts +40 -0
- package/redux/middlewares/pre-order/set-pre-order.ts +22 -0
- package/redux/middlewares/pre-order/shipping-option.ts +44 -0
- package/redux/middlewares/pre-order/shipping-step.ts +38 -0
- package/redux/reducers/checkout.ts +8 -2
- package/redux/reducers/index.ts +5 -3
- package/redux/reducers/root.ts +7 -2
- package/sentry/index.ts +36 -17
- package/types/commerce/account.ts +5 -1
- package/types/commerce/checkout.ts +24 -0
- package/types/index.ts +8 -4
- package/utils/app-fetch.ts +2 -2
- package/utils/index.ts +11 -8
- package/utils/localization.ts +4 -0
- package/utils/override-middleware.ts +25 -0
- package/utils/redirect.ts +2 -2
- package/views/error-page.tsx +93 -0
- package/with-pz-config.js +4 -3
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Middleware } from '@reduxjs/toolkit';
|
|
2
|
+
import { CheckoutResult, MiddlewareParams } from '../../../types';
|
|
3
|
+
import { setShippingStepCompleted } from '../../reducers/checkout';
|
|
4
|
+
|
|
5
|
+
export const shippingStepMiddleware: Middleware = ({
|
|
6
|
+
getState,
|
|
7
|
+
dispatch
|
|
8
|
+
}: MiddlewareParams) => {
|
|
9
|
+
return (next) => (action) => {
|
|
10
|
+
const result: CheckoutResult = next(action);
|
|
11
|
+
const preOrder = result?.payload?.pre_order;
|
|
12
|
+
|
|
13
|
+
if (!preOrder) {
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { addressList: addresses } = getState().checkout;
|
|
18
|
+
|
|
19
|
+
if (preOrder) {
|
|
20
|
+
const stepCompleted = [
|
|
21
|
+
preOrder.delivery_option?.delivery_option_type === 'retail_store'
|
|
22
|
+
? true
|
|
23
|
+
: preOrder.shipping_address?.pk,
|
|
24
|
+
preOrder.billing_address?.pk,
|
|
25
|
+
preOrder.shipping_option?.pk,
|
|
26
|
+
addresses.length > 0
|
|
27
|
+
].every(Boolean);
|
|
28
|
+
|
|
29
|
+
dispatch(setShippingStepCompleted(stepCompleted));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
Object.defineProperty(shippingStepMiddleware, 'name', {
|
|
37
|
+
value: 'shippingStepMiddleware'
|
|
38
|
+
});
|
|
@@ -70,6 +70,7 @@ export interface CheckoutState {
|
|
|
70
70
|
};
|
|
71
71
|
supportedMethods: string;
|
|
72
72
|
};
|
|
73
|
+
payOnDeliveryOtpModalActive: boolean;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
const initialState: CheckoutState = {
|
|
@@ -103,7 +104,8 @@ const initialState: CheckoutState = {
|
|
|
103
104
|
retailStores: [],
|
|
104
105
|
attributeBasedShippingOptions: [],
|
|
105
106
|
selectedShippingOptions: {},
|
|
106
|
-
hepsipayAvailability: false
|
|
107
|
+
hepsipayAvailability: false,
|
|
108
|
+
payOnDeliveryOtpModalActive: false
|
|
107
109
|
};
|
|
108
110
|
|
|
109
111
|
const checkoutSlice = createSlice({
|
|
@@ -193,6 +195,9 @@ const checkoutSlice = createSlice({
|
|
|
193
195
|
},
|
|
194
196
|
setWalletPaymentData(state, { payload }) {
|
|
195
197
|
state.walletPaymentData = payload;
|
|
198
|
+
},
|
|
199
|
+
setPayOnDeliveryOtpModalActive(state, { payload }) {
|
|
200
|
+
state.payOnDeliveryOtpModalActive = payload;
|
|
196
201
|
}
|
|
197
202
|
}
|
|
198
203
|
});
|
|
@@ -225,7 +230,8 @@ export const {
|
|
|
225
230
|
setAttributeBasedShippingOptions,
|
|
226
231
|
setSelectedShippingOptions,
|
|
227
232
|
setHepsipayAvailability,
|
|
228
|
-
setWalletPaymentData
|
|
233
|
+
setWalletPaymentData,
|
|
234
|
+
setPayOnDeliveryOtpModalActive
|
|
229
235
|
} = checkoutSlice.actions;
|
|
230
236
|
|
|
231
237
|
export default checkoutSlice.reducer;
|
package/redux/reducers/index.ts
CHANGED
|
@@ -9,15 +9,17 @@ import { masterpassReducer } from '@akinon/pz-masterpass';
|
|
|
9
9
|
import { otpReducer } from '@akinon/pz-otp';
|
|
10
10
|
import { savedCardReducer } from '@akinon/pz-saved-card';
|
|
11
11
|
|
|
12
|
+
const fallbackReducer = (state = {}) => state;
|
|
13
|
+
|
|
12
14
|
const reducers = {
|
|
13
15
|
[api.reducerPath]: api.reducer,
|
|
14
16
|
root: rootReducer,
|
|
15
17
|
checkout: checkoutReducer,
|
|
16
18
|
config: configReducer,
|
|
17
19
|
header: headerReducer,
|
|
18
|
-
masterpass: masterpassReducer,
|
|
19
|
-
otp: otpReducer,
|
|
20
|
-
savedCard: savedCardReducer
|
|
20
|
+
masterpass: masterpassReducer || fallbackReducer,
|
|
21
|
+
otp: otpReducer || fallbackReducer,
|
|
22
|
+
savedCard: savedCardReducer || fallbackReducer
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export default reducers;
|
package/redux/reducers/root.ts
CHANGED
|
@@ -14,7 +14,8 @@ const initialState = {
|
|
|
14
14
|
open: false,
|
|
15
15
|
title: null,
|
|
16
16
|
content: null
|
|
17
|
-
}
|
|
17
|
+
},
|
|
18
|
+
userPhoneNumber: null
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
const rootSlice = createSlice({
|
|
@@ -45,6 +46,9 @@ const rootSlice = createSlice({
|
|
|
45
46
|
state.rootModal.open = false;
|
|
46
47
|
state.rootModal.title = null;
|
|
47
48
|
state.rootModal.content = null;
|
|
49
|
+
},
|
|
50
|
+
setUserPhoneNumber(state, { payload }) {
|
|
51
|
+
state.userPhoneNumber = payload;
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
});
|
|
@@ -55,7 +59,8 @@ export const {
|
|
|
55
59
|
toggleMiniBasket,
|
|
56
60
|
setHighlightedItem,
|
|
57
61
|
openRootModal,
|
|
58
|
-
closeRootModal
|
|
62
|
+
closeRootModal,
|
|
63
|
+
setUserPhoneNumber
|
|
59
64
|
} = rootSlice.actions;
|
|
60
65
|
|
|
61
66
|
export default rootSlice.reducer;
|
package/sentry/index.ts
CHANGED
|
@@ -1,29 +1,48 @@
|
|
|
1
1
|
import * as Sentry from '@sentry/nextjs';
|
|
2
2
|
|
|
3
|
-
const SENTRY_DSN: string =
|
|
3
|
+
const SENTRY_DSN: string | undefined =
|
|
4
4
|
process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
|
5
5
|
|
|
6
|
+
export enum ClientLogType {
|
|
7
|
+
UNCAUGHT_ERROR_PAGE = 'UNCAUGHT_ERROR_PAGE',
|
|
8
|
+
CHECKOUT = 'CHECKOUT'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ALLOWED_CLIENT_LOG_TYPES: ClientLogType[] = [
|
|
12
|
+
ClientLogType.UNCAUGHT_ERROR_PAGE,
|
|
13
|
+
ClientLogType.CHECKOUT
|
|
14
|
+
];
|
|
15
|
+
|
|
6
16
|
export const initSentry = (
|
|
7
17
|
type: 'Server' | 'Client' | 'Edge',
|
|
8
18
|
options: Sentry.BrowserOptions | Sentry.NodeOptions | Sentry.EdgeOptions = {}
|
|
9
19
|
) => {
|
|
10
20
|
// TODO: Handle options with ESLint rules
|
|
11
21
|
|
|
12
|
-
|
|
22
|
+
Sentry.init({
|
|
23
|
+
dsn:
|
|
24
|
+
options.dsn ||
|
|
25
|
+
SENTRY_DSN ||
|
|
26
|
+
'https://d8558ef8997543deacf376c7d8d7cf4b@o64293.ingest.sentry.io/4504338423742464',
|
|
27
|
+
initialScope: {
|
|
28
|
+
tags: {
|
|
29
|
+
APP_TYPE: 'ProjectZeroNext',
|
|
30
|
+
TYPE: type
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
tracesSampleRate: 0,
|
|
34
|
+
integrations: [],
|
|
35
|
+
beforeSend: (event, hint) => {
|
|
36
|
+
if (
|
|
37
|
+
type === 'Client' &&
|
|
38
|
+
!ALLOWED_CLIENT_LOG_TYPES.includes(
|
|
39
|
+
event.tags?.LOG_TYPE as ClientLogType
|
|
40
|
+
)
|
|
41
|
+
) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
13
44
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
SENTRY_DSN ||
|
|
18
|
-
'https://d8558ef8997543deacf376c7d8d7cf4b@o64293.ingest.sentry.io/4504338423742464',
|
|
19
|
-
initialScope: {
|
|
20
|
-
tags: {
|
|
21
|
-
APP_TYPE: 'ProjectZeroNext',
|
|
22
|
-
TYPE: type
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
tracesSampleRate: 1.0,
|
|
26
|
-
integrations: []
|
|
27
|
-
});
|
|
28
|
-
}
|
|
45
|
+
return event;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
29
48
|
};
|
|
@@ -19,6 +19,10 @@ export type AccountOrderCancellation = {
|
|
|
19
19
|
description: string;
|
|
20
20
|
order_item: string;
|
|
21
21
|
reason: string;
|
|
22
|
+
cancellation_request_image_set?: Array<{
|
|
23
|
+
image: string;
|
|
24
|
+
description: string;
|
|
25
|
+
}>;
|
|
22
26
|
}>;
|
|
23
27
|
};
|
|
24
28
|
|
|
@@ -61,5 +65,5 @@ export type ContactFormType = {
|
|
|
61
65
|
order?: string;
|
|
62
66
|
country_code?: string;
|
|
63
67
|
order_needed?: boolean;
|
|
64
|
-
file?: FileList
|
|
68
|
+
file?: FileList;
|
|
65
69
|
};
|
|
@@ -3,6 +3,8 @@ import { Basket } from './basket';
|
|
|
3
3
|
import { RetailStore } from './misc';
|
|
4
4
|
import { PaymentOption } from './order';
|
|
5
5
|
import { Product } from './product';
|
|
6
|
+
import { JSX } from 'react';
|
|
7
|
+
import { RootState, TypedDispatch } from 'redux/store';
|
|
6
8
|
|
|
7
9
|
export enum CheckoutStep {
|
|
8
10
|
Shipping = 'shipping',
|
|
@@ -106,6 +108,7 @@ export interface PreOrder {
|
|
|
106
108
|
context_extras?: ExtraField;
|
|
107
109
|
token?: string;
|
|
108
110
|
agreement_confirmed?: boolean;
|
|
111
|
+
phone_number?: string;
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
export type ExtraField = Record<string, any>;
|
|
@@ -185,3 +188,24 @@ export interface AttributeBasedShippingOption {
|
|
|
185
188
|
attribute_key: string;
|
|
186
189
|
[key: string]: any;
|
|
187
190
|
}
|
|
191
|
+
|
|
192
|
+
export interface CheckoutResult {
|
|
193
|
+
payload: {
|
|
194
|
+
errors?: Record<string, string[]>;
|
|
195
|
+
pre_order?: PreOrder;
|
|
196
|
+
context_list?: CheckoutContext[];
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface MiddlewareParams {
|
|
201
|
+
getState: () => RootState;
|
|
202
|
+
dispatch: TypedDispatch;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export type SendSmsType = {
|
|
206
|
+
phone_number: string;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export type VerifySmsType = {
|
|
210
|
+
verify_code: string;
|
|
211
|
+
};
|
package/types/index.ts
CHANGED
|
@@ -199,15 +199,16 @@ export interface Settings {
|
|
|
199
199
|
extraPaymentTypes?: string[];
|
|
200
200
|
masterpassJsUrl?: string;
|
|
201
201
|
};
|
|
202
|
-
customNotFoundEnabled: boolean;
|
|
203
202
|
useOptimizedTranslations?: boolean;
|
|
204
203
|
plugins?: Record<string, Record<string, any>>;
|
|
205
204
|
includedProxyHeaders?: string[];
|
|
205
|
+
commerceRedirectionIgnoreList?: string[];
|
|
206
206
|
/**
|
|
207
207
|
* By default, the currency will be reset when the currency is changed.
|
|
208
208
|
* If you want to keep the basket when the currency is changed, you can set this option to `false`.
|
|
209
209
|
*/
|
|
210
210
|
resetBasketOnCurrencyChange?: boolean;
|
|
211
|
+
frontendIds?: Record<string, number>;
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
export interface CacheOptions {
|
|
@@ -239,8 +240,8 @@ export type ImageOptions = CDNOptions & {
|
|
|
239
240
|
export type Translations = { [key: string]: object };
|
|
240
241
|
|
|
241
242
|
export interface PageProps<T = any> {
|
|
242
|
-
params: T & { locale: string; currency: string }
|
|
243
|
-
searchParams: URLSearchParams
|
|
243
|
+
params: Promise<T & { locale: string; currency: string }>;
|
|
244
|
+
searchParams: Promise<URLSearchParams>;
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
export interface LayoutProps<T = any> extends PageProps<T> {
|
|
@@ -275,7 +276,10 @@ export interface IconProps extends React.ComponentPropsWithRef<'i'> {
|
|
|
275
276
|
|
|
276
277
|
export interface ButtonProps
|
|
277
278
|
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
278
|
-
appearance?: 'filled' | 'outlined' | 'ghost';
|
|
279
|
+
appearance?: 'filled' | 'outlined' | 'ghost' | 'link' | string;
|
|
280
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
281
|
+
href?: string;
|
|
282
|
+
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
export type FileInputProps = React.HTMLProps<HTMLInputElement>;
|
package/utils/app-fetch.ts
CHANGED
|
@@ -26,8 +26,8 @@ const appFetch = async <T>({
|
|
|
26
26
|
let ip = '';
|
|
27
27
|
|
|
28
28
|
try {
|
|
29
|
-
const nextHeaders = headers();
|
|
30
|
-
const nextCookies = cookies();
|
|
29
|
+
const nextHeaders = await headers();
|
|
30
|
+
const nextCookies = await cookies();
|
|
31
31
|
ip = nextHeaders.get('x-forwarded-for') ?? '';
|
|
32
32
|
|
|
33
33
|
const commerceUrl = Settings.commerceUrl;
|
package/utils/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import settings from 'settings';
|
|
2
2
|
import { LocaleUrlStrategy } from '../localization';
|
|
3
|
-
import { CDNOptions, ClientRequestOptions } from '../types';
|
|
3
|
+
import { CDNOptions, ClientRequestOptions, Locale } from '../types';
|
|
4
4
|
|
|
5
5
|
export * from './get-currency';
|
|
6
6
|
export * from './menu-generator';
|
|
@@ -152,14 +152,17 @@ export function buildCDNUrl(url: string, config?: CDNOptions) {
|
|
|
152
152
|
return `${rootWithoutOptions}${options}${fileExtension}`;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
const { locales, localeUrlStrategy, defaultLocaleValue } =
|
|
156
|
+
settings.localization;
|
|
157
|
+
|
|
158
|
+
const isLocaleExcluded = (locale: Locale) =>
|
|
159
|
+
![LocaleUrlStrategy.ShowAllLocales, LocaleUrlStrategy.Subdomain].includes(
|
|
160
|
+
localeUrlStrategy
|
|
161
|
+
) && locale.value !== defaultLocaleValue;
|
|
162
|
+
|
|
155
163
|
export const urlLocaleMatcherRegex = new RegExp(
|
|
156
|
-
`^/(${
|
|
157
|
-
.filter((l) =>
|
|
158
|
-
settings.localization.localeUrlStrategy !==
|
|
159
|
-
LocaleUrlStrategy.ShowAllLocales
|
|
160
|
-
? l.value !== settings.localization.defaultLocaleValue
|
|
161
|
-
: l
|
|
162
|
-
)
|
|
164
|
+
`^/(${locales
|
|
165
|
+
.filter((l) => !isLocaleExcluded(l))
|
|
163
166
|
.map((l) => l.value)
|
|
164
167
|
.join('|')})(?=/|$)`
|
|
165
168
|
);
|
package/utils/localization.ts
CHANGED
|
@@ -11,6 +11,10 @@ export const getUrlPathWithLocale = (
|
|
|
11
11
|
currentLocale = defaultLocaleValue;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
|
|
15
|
+
return pathname;
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
if (localeUrlStrategy === LocaleUrlStrategy.HideAllLocales) {
|
|
15
19
|
return pathname;
|
|
16
20
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Middleware } from '@reduxjs/toolkit';
|
|
2
|
+
|
|
3
|
+
export enum MiddlewareNames {
|
|
4
|
+
PreOrderValidationMiddleware = 'preOrderValidationMiddleware',
|
|
5
|
+
RedirectionMiddleware = 'redirectionMiddleware',
|
|
6
|
+
SetPreOrderMiddleware = 'setPreOrderMiddleware',
|
|
7
|
+
DeliveryOptionMiddleware = 'deliveryOptionMiddleware',
|
|
8
|
+
SetAddressMiddleware = 'setAddressMiddleware',
|
|
9
|
+
ShippingOptionMiddleware = 'shippingOptionMiddleware',
|
|
10
|
+
DataSourceShippingOptionMiddleware = 'dataSourceShippingOptionMiddleware',
|
|
11
|
+
AttributeBasedShippingOptionMiddleware = 'attributeBasedShippingOptionMiddleware',
|
|
12
|
+
PaymentOptionMiddleware = 'paymentOptionMiddleware',
|
|
13
|
+
InstallmentOptionMiddleware = 'installmentOptionMiddleware',
|
|
14
|
+
ShippingStepMiddleware = 'shippingStepMiddleware'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const overrideMiddleware = (
|
|
18
|
+
originalMiddlewares: Middleware[],
|
|
19
|
+
overrides: Record<string, Middleware>
|
|
20
|
+
): Middleware[] => {
|
|
21
|
+
return originalMiddlewares.map((middleware) => {
|
|
22
|
+
const middlewareKey = middleware.name || middleware.toString();
|
|
23
|
+
return overrides[middlewareKey] || middleware;
|
|
24
|
+
});
|
|
25
|
+
};
|
package/utils/redirect.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { headers } from 'next/headers';
|
|
|
4
4
|
import { ServerVariables } from '@akinon/next/utils/server-variables';
|
|
5
5
|
import { getUrlPathWithLocale } from '@akinon/next/utils/localization';
|
|
6
6
|
|
|
7
|
-
export const redirect = (path: string, type?: RedirectType) => {
|
|
8
|
-
const nextHeaders = headers();
|
|
7
|
+
export const redirect = async (path: string, type?: RedirectType) => {
|
|
8
|
+
const nextHeaders = await headers();
|
|
9
9
|
const pageUrl = new URL(
|
|
10
10
|
nextHeaders.get('pz-url') ?? process.env.NEXT_PUBLIC_URL
|
|
11
11
|
);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useLocalization } from '../hooks';
|
|
3
|
+
import { Button, Link } from '../components';
|
|
4
|
+
import { ROUTES } from 'routes';
|
|
5
|
+
|
|
6
|
+
export default function PzErrorPage({
|
|
7
|
+
error,
|
|
8
|
+
reset
|
|
9
|
+
}: {
|
|
10
|
+
error: Error & { digest?: string; isServerError?: boolean };
|
|
11
|
+
reset: () => void;
|
|
12
|
+
}) {
|
|
13
|
+
const [isServerError, setIsServerError] = useState(false);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if ('isServerError' in error) {
|
|
17
|
+
setIsServerError(true);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setIsServerError(!!error.digest);
|
|
22
|
+
}, [error]);
|
|
23
|
+
|
|
24
|
+
return isServerError ? (
|
|
25
|
+
<ServerErrorUI />
|
|
26
|
+
) : (
|
|
27
|
+
<ClientErrorUI error={error} reset={reset} />
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ClientErrorUI({
|
|
32
|
+
error,
|
|
33
|
+
reset
|
|
34
|
+
}: {
|
|
35
|
+
error: Error & { digest?: string };
|
|
36
|
+
reset: () => void;
|
|
37
|
+
}) {
|
|
38
|
+
const { t } = useLocalization();
|
|
39
|
+
|
|
40
|
+
const errorMessage = error?.message || 'Unknown error';
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<section className="text-center px-6 py-6 my-14 md:px-0 md:m-14">
|
|
44
|
+
<div className="text-4xl font-bold md:text-6xl text-red-500">500</div>
|
|
45
|
+
<h1 className="text-lg md:text-xl mt-4">
|
|
46
|
+
{t('common.client_error.title')}
|
|
47
|
+
</h1>
|
|
48
|
+
<p className="text-lg md:text-xl mt-2">
|
|
49
|
+
{t('common.client_error.description')}
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<div className="mt-4 mx-auto max-w-lg">
|
|
53
|
+
<p className="text-xs text-gray-600 font-mono bg-gray-100 p-3 rounded overflow-auto text-left">
|
|
54
|
+
<span className="font-semibold">Error:</span> {errorMessage}
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div className="mt-6 flex flex-col gap-4 items-center justify-center">
|
|
59
|
+
<Link href={ROUTES.HOME} className="text-lg underline">
|
|
60
|
+
{t('common.client_error.link_text')}
|
|
61
|
+
</Link>
|
|
62
|
+
<Button onClick={reset} className="text-lg">
|
|
63
|
+
{t('common.try_again')}
|
|
64
|
+
</Button>
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function ServerErrorUI() {
|
|
71
|
+
const { t } = useLocalization();
|
|
72
|
+
|
|
73
|
+
const reloadPage = () => {
|
|
74
|
+
window.location.reload();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<section className="text-center px-6 my-14 md:px-0 md:m-14">
|
|
79
|
+
<div className="text-7xl font-bold md:text-8xl">500</div>
|
|
80
|
+
<h1 className="text-lg md:text-xl"> {t('common.page_500.title')} </h1>
|
|
81
|
+
<p className="text-lg md:text-xl"> {t('common.page_500.description')} </p>
|
|
82
|
+
|
|
83
|
+
<div className="mt-6 flex flex-col gap-4 items-center justify-center">
|
|
84
|
+
<Link href={ROUTES.HOME} className="text-lg underline">
|
|
85
|
+
{t('common.page_500.link_text')}
|
|
86
|
+
</Link>
|
|
87
|
+
<Button onClick={reloadPage} className="text-lg">
|
|
88
|
+
{t('common.try_again')}
|
|
89
|
+
</Button>
|
|
90
|
+
</div>
|
|
91
|
+
</section>
|
|
92
|
+
);
|
|
93
|
+
}
|
package/with-pz-config.js
CHANGED
|
@@ -44,8 +44,9 @@ const defaultConfig = {
|
|
|
44
44
|
value: 'max-age=63072000; includeSubDomains; preload'
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
|
-
key: '
|
|
48
|
-
value:
|
|
47
|
+
key: 'Content-Security-Policy',
|
|
48
|
+
value:
|
|
49
|
+
"frame-ancestors 'self' https://*.akifast.com akifast.com https://*.akinoncloud.com akinoncloud.com"
|
|
49
50
|
}
|
|
50
51
|
]
|
|
51
52
|
}
|
|
@@ -64,7 +65,7 @@ const defaultConfig = {
|
|
|
64
65
|
},
|
|
65
66
|
sentry: {
|
|
66
67
|
hideSourceMaps: true
|
|
67
|
-
}
|
|
68
|
+
} // TODO: This section will be reviewed again in the Sentry 8 update.
|
|
68
69
|
};
|
|
69
70
|
|
|
70
71
|
const withPzConfig = (
|