@akinon/next 1.92.0-rc.9 → 1.92.0-snapshot-ZERO-3449-20250618101111

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 (42) hide show
  1. package/CHANGELOG.md +33 -1180
  2. package/api/similar-product-list.ts +63 -0
  3. package/api/similar-products.ts +109 -0
  4. package/components/accordion.tsx +5 -20
  5. package/components/file-input.tsx +3 -65
  6. package/components/input.tsx +0 -2
  7. package/components/link.tsx +12 -16
  8. package/components/modal.tsx +16 -32
  9. package/components/plugin-module.tsx +3 -13
  10. package/components/selected-payment-option-view.tsx +0 -11
  11. package/data/client/similar-products.ts +122 -0
  12. package/data/urls.ts +5 -1
  13. package/hocs/server/with-segment-defaults.tsx +2 -5
  14. package/hooks/index.ts +2 -0
  15. package/hooks/use-image-cropper.ts +160 -0
  16. package/hooks/use-similar-products.ts +720 -0
  17. package/instrumentation/node.ts +13 -15
  18. package/lib/cache.ts +0 -2
  19. package/middlewares/complete-gpay.ts +1 -2
  20. package/middlewares/complete-masterpass.ts +1 -2
  21. package/middlewares/default.ts +184 -196
  22. package/middlewares/index.ts +1 -3
  23. package/middlewares/redirection-payment.ts +1 -2
  24. package/middlewares/saved-card-redirection.ts +1 -2
  25. package/middlewares/three-d-redirection.ts +1 -2
  26. package/middlewares/url-redirection.ts +14 -8
  27. package/package.json +3 -3
  28. package/plugins.d.ts +0 -2
  29. package/plugins.js +1 -3
  30. package/redux/middlewares/checkout.ts +2 -15
  31. package/redux/reducers/checkout.ts +1 -9
  32. package/sentry/index.ts +17 -54
  33. package/types/commerce/order.ts +0 -1
  34. package/types/index.ts +73 -26
  35. package/utils/app-fetch.ts +2 -2
  36. package/utils/image-validation.ts +303 -0
  37. package/utils/redirect.ts +3 -5
  38. package/with-pz-config.js +5 -1
  39. package/data/server/basket.ts +0 -72
  40. package/hooks/use-loyalty-availability.ts +0 -21
  41. package/middlewares/wallet-complete-redirection.ts +0 -179
  42. package/utils/redirect-ignore.ts +0 -35
@@ -20,8 +20,7 @@ import {
20
20
  setShippingOptions,
21
21
  setHepsipayAvailability,
22
22
  setWalletPaymentData,
23
- setPayOnDeliveryOtpModalActive,
24
- setUnavailablePaymentOptions
23
+ setPayOnDeliveryOtpModalActive
25
24
  } from '../../redux/reducers/checkout';
26
25
  import { RootState, TypedDispatch } from 'redux/store';
27
26
  import { checkoutApi } from '../../data/client/checkout';
@@ -51,11 +50,7 @@ export const errorMiddleware: Middleware = ({ dispatch }: MiddlewareParams) => {
51
50
  const result: CheckoutResult = next(action);
52
51
  const errors = result?.payload?.errors;
53
52
 
54
- if (
55
- !!errors &&
56
- ((typeof errors === 'object' && Object.keys(errors).length > 0) ||
57
- (Array.isArray(errors) && errors.length > 0))
58
- ) {
53
+ if (errors) {
59
54
  dispatch(setErrors(errors));
60
55
  }
61
56
 
@@ -181,14 +176,6 @@ export const contextListMiddleware: Middleware = ({
181
176
  dispatch(setPaymentOptions(context.page_context.payment_options));
182
177
  }
183
178
 
184
- if (context.page_context.unavailable_options) {
185
- dispatch(
186
- setUnavailablePaymentOptions(
187
- context.page_context.unavailable_options
188
- )
189
- );
190
- }
191
-
192
179
  if (context.page_context.credit_payment_options) {
193
180
  dispatch(
194
181
  setCreditPaymentOptions(context.page_context.credit_payment_options)
@@ -40,7 +40,6 @@ export interface CheckoutState {
40
40
  shippingOptions: ShippingOption[];
41
41
  dataSourceShippingOptions: DataSource[];
42
42
  paymentOptions: PaymentOption[];
43
- unavailablePaymentOptions: PaymentOption[];
44
43
  creditPaymentOptions: CheckoutCreditPaymentOption[];
45
44
  selectedCreditPaymentPk: number;
46
45
  paymentChoices: PaymentChoice[];
@@ -61,8 +60,6 @@ export interface CheckoutState {
61
60
  countryCode: string;
62
61
  currencyCode: string;
63
62
  version: string;
64
- public_key: string;
65
- [key: string]: any;
66
63
  };
67
64
  detail: {
68
65
  label: string;
@@ -97,7 +94,6 @@ const initialState: CheckoutState = {
97
94
  shippingOptions: [],
98
95
  dataSourceShippingOptions: [],
99
96
  paymentOptions: [],
100
- unavailablePaymentOptions: [],
101
97
  creditPaymentOptions: [],
102
98
  selectedCreditPaymentPk: null,
103
99
  paymentChoices: [],
@@ -161,9 +157,6 @@ const checkoutSlice = createSlice({
161
157
  setPaymentOptions(state, { payload }) {
162
158
  state.paymentOptions = payload;
163
159
  },
164
- setUnavailablePaymentOptions(state, { payload }) {
165
- state.unavailablePaymentOptions = payload;
166
- },
167
160
  setPaymentChoices(state, { payload }) {
168
161
  state.paymentChoices = payload;
169
162
  },
@@ -225,10 +218,9 @@ export const {
225
218
  setShippingOptions,
226
219
  setDataSourceShippingOptions,
227
220
  setPaymentOptions,
228
- setUnavailablePaymentOptions,
229
- setPaymentChoices,
230
221
  setCreditPaymentOptions,
231
222
  setSelectedCreditPaymentPk,
223
+ setPaymentChoices,
232
224
  setCardType,
233
225
  setInstallmentOptions,
234
226
  setBankAccounts,
package/sentry/index.ts CHANGED
@@ -13,73 +13,36 @@ 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
-
41
16
  export const initSentry = (
42
17
  type: 'Server' | 'Client' | 'Edge',
43
18
  options: Sentry.BrowserOptions | Sentry.NodeOptions | Sentry.EdgeOptions = {}
44
19
  ) => {
45
- // TODO: Remove Zero Project DSN
20
+ // TODO: Handle options with ESLint rules
46
21
 
47
- const baseConfig = {
22
+ Sentry.init({
48
23
  dsn:
49
- SENTRY_DSN ||
50
24
  options.dsn ||
25
+ SENTRY_DSN ||
51
26
  'https://d8558ef8997543deacf376c7d8d7cf4b@o64293.ingest.sentry.io/4504338423742464',
52
27
  initialScope: {
53
28
  tags: {
54
29
  APP_TYPE: 'ProjectZeroNext',
55
- TYPE: type,
56
- ...((options.initialScope as any)?.tags || {})
30
+ TYPE: type
57
31
  }
58
32
  },
59
33
  tracesSampleRate: 0,
60
- integrations: []
61
- };
62
-
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;
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;
82
43
  }
83
- });
84
- }
44
+
45
+ return event;
46
+ }
47
+ });
85
48
  };
@@ -114,7 +114,6 @@ export interface Order {
114
114
  pk: number;
115
115
  name: string;
116
116
  slug: string;
117
- logo: string;
118
117
  [key: string]: any;
119
118
  };
120
119
  }
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
  };
@@ -283,13 +289,7 @@ export interface ButtonProps
283
289
  target?: '_blank' | '_self' | '_parent' | '_top';
284
290
  }
285
291
 
286
- export interface FileInputProps extends React.HTMLProps<HTMLInputElement> {
287
- fileClassName?: string;
288
- fileNameWrapperClassName?: string;
289
- fileInputClassName?: string;
290
- onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
291
- buttonClassName?: string;
292
- }
292
+ export type FileInputProps = React.HTMLProps<HTMLInputElement>;
293
293
 
294
294
  export interface PriceProps {
295
295
  currencyCode?: string;
@@ -310,19 +310,15 @@ export interface InputProps extends React.HTMLProps<HTMLInputElement> {
310
310
 
311
311
  export interface AccordionProps {
312
312
  isCollapse?: boolean;
313
- collapseClassName?: string;
314
313
  title?: string;
315
314
  subTitle?: string;
316
315
  icons?: string[];
317
316
  iconSize?: number;
318
317
  iconColor?: string;
319
318
  children?: ReactNode;
320
- headerClassName?: string;
321
319
  className?: string;
322
320
  titleClassName?: string;
323
- subTitleClassName?: string;
324
321
  dataTestId?: string;
325
- contentClassName?: string;
326
322
  }
327
323
 
328
324
  export interface PluginModuleComponentProps {
@@ -348,19 +344,70 @@ export interface PaginationProps {
348
344
  isLoading?: boolean;
349
345
  }
350
346
 
351
- export interface ModalProps {
352
- portalId: string;
353
- children?: React.ReactNode;
354
- open?: boolean;
355
- setOpen?: (open: boolean) => void;
356
- title?: React.ReactNode;
357
- showCloseButton?: React.ReactNode;
358
- className?: string;
359
- overlayClassName?: string;
360
- headerWrapperClassName?: string;
361
- titleClassName?: string;
362
- closeButtonClassName?: string;
363
- iconName?: string;
364
- iconSize?: number;
365
- iconClassName?: string;
347
+ export interface FilterSidebarProps {
348
+ isFilterMenuOpen: boolean;
349
+ setIsFilterMenuOpen: (open: boolean) => void;
350
+ searchResults: any;
351
+ isLoading: boolean;
352
+ handleFacetChange: (facetKey: string, choiceValue: string | number) => void;
353
+ removeFacetFilter: (facetKey: string, choiceValue: string | number) => void;
354
+ currentImageUrl: string;
355
+ isCropping: boolean;
356
+ imageRef: React.RefObject<HTMLImageElement>;
357
+ fileInputRef: React.RefObject<HTMLInputElement>;
358
+ crop: any;
359
+ setCrop: (crop: any) => void;
360
+ completedCrop: any;
361
+ setCompletedCrop: (crop: any) => void;
362
+ handleCropComplete: any;
363
+ toggleCropMode: () => void;
364
+ processCompletedCrop: (crop: any) => void;
365
+ resetCrop: () => void;
366
+ product: any;
367
+ activeIndex: number;
368
+ hasUploadedImage: boolean;
369
+ handleFileUpload: (event: React.ChangeEvent<HTMLInputElement>) => void;
370
+ handleResetToOriginal: () => void;
371
+ fileError: string;
372
+ showResetButton?: boolean;
373
+ }
374
+
375
+ export interface SimilarProductsModalProps {
376
+ isOpen: boolean;
377
+ onClose: () => void;
378
+ searchResults: any;
379
+ resultsKey: number;
380
+ isLoading: boolean;
381
+ isFilterMenuOpen: boolean;
382
+ setIsFilterMenuOpen: (open: boolean) => void;
383
+ handleSortChange: (value: string) => void;
384
+ handlePageChange: (page: number, sortValue?: string) => void;
385
+ handleFacetChange: (facetKey: string, choiceValue: string | number) => void;
386
+ removeFacetFilter: (facetKey: string, choiceValue: string | number) => void;
387
+ currentImageUrl: string;
388
+ isCropping: boolean;
389
+ imageRef: React.RefObject<HTMLImageElement>;
390
+ fileInputRef: React.RefObject<HTMLInputElement>;
391
+ crop: any;
392
+ setCrop: (crop: any) => void;
393
+ completedCrop: any;
394
+ setCompletedCrop: (crop: any) => void;
395
+ handleCropComplete: any;
396
+ toggleCropMode: () => void;
397
+ processCompletedCrop: (crop: any) => void;
398
+ resetCrop: () => void;
399
+ product: any;
400
+ activeIndex: number;
401
+ hasUploadedImage: boolean;
402
+ handleFileUpload: (event: React.ChangeEvent<HTMLInputElement>) => void;
403
+ handleResetToOriginal: () => void;
404
+ fileError: string;
405
+ showResetButton?: boolean;
406
+ }
407
+
408
+ export interface ResultsGridProps {
409
+ searchResults: any;
410
+ resultsKey: number;
411
+ isLoading: boolean;
412
+ handlePageChange: (page: number, sortValue?: string) => void;
366
413
  }
@@ -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(),
47
46
  ...(init.headers ?? {}),
48
47
  ...(ServerVariables.globalHeaders ?? {}),
49
48
  'Accept-Language': currentLocale.apiValue,
50
49
  'x-currency': currency,
51
- 'x-forwarded-for': ip
50
+ 'x-forwarded-for': ip,
51
+ cookie: nextCookies.toString()
52
52
  };
53
53
 
54
54
  init.next = {
@@ -0,0 +1,303 @@
1
+ export interface ImageValidationResult {
2
+ isValid: boolean;
3
+ error?: string;
4
+ }
5
+
6
+ const IMAGE_SIGNATURES = {
7
+ jpeg: [
8
+ { bytes: 'ffd8', minLength: 2, description: 'JPEG (Generic)' },
9
+ { bytes: 'ffd8ffe0', minLength: 4, description: 'JPEG/JFIF' },
10
+ { bytes: 'ffd8ffe1', minLength: 4, description: 'JPEG/EXIF' },
11
+ { bytes: 'ffd8ffe2', minLength: 4, description: 'JPEG/Canon' },
12
+ { bytes: 'ffd8ffe3', minLength: 4, description: 'JPEG/Samsung' },
13
+ { bytes: 'ffd8ffe8', minLength: 4, description: 'JPEG/SPIFF' },
14
+ { bytes: 'ffd8ffed', minLength: 4, description: 'JPEG/Photoshop' }
15
+ ],
16
+
17
+ png: [{ bytes: '89504e470d0a1a0a', minLength: 8, description: 'PNG' }],
18
+
19
+ gif: [
20
+ { bytes: '474946383761', minLength: 6, description: 'GIF87a' },
21
+ { bytes: '474946383961', minLength: 6, description: 'GIF89a' }
22
+ ],
23
+
24
+ webp: {
25
+ riff: '52494646',
26
+ webp: '57454250',
27
+ minLength: 12,
28
+ description: 'WebP'
29
+ },
30
+
31
+ avif: {
32
+ ftyp: '66747970',
33
+ avif: '61766966',
34
+ minLength: 12,
35
+ description: 'AVIF'
36
+ },
37
+
38
+ heic: [
39
+ {
40
+ ftyp: '66747970',
41
+ brand: '68656963',
42
+ minLength: 12,
43
+ description: 'HEIC'
44
+ },
45
+ {
46
+ ftyp: '66747970',
47
+ brand: '6d696631',
48
+ minLength: 12,
49
+ description: 'HEIF'
50
+ }
51
+ ],
52
+
53
+ bmp: [{ bytes: '424d', minLength: 2, description: 'BMP' }],
54
+
55
+ tiff: [
56
+ { bytes: '49492a00', minLength: 4, description: 'TIFF (Little Endian)' },
57
+ { bytes: '4d4d002a', minLength: 4, description: 'TIFF (Big Endian)' }
58
+ ]
59
+ } as const;
60
+
61
+ const SUPPORTED_MIME_TYPES = {
62
+ 'image/jpeg': 'jpeg',
63
+ 'image/jpg': 'jpeg',
64
+ 'image/png': 'png',
65
+ 'image/gif': 'gif',
66
+ 'image/webp': 'webp',
67
+ 'image/avif': 'avif',
68
+ 'image/heic': 'heic',
69
+ 'image/heif': 'heic',
70
+ 'image/bmp': 'bmp',
71
+ 'image/tiff': 'tiff',
72
+ 'image/tif': 'tiff'
73
+ } as const;
74
+
75
+ const FORMAT_NAMES = {
76
+ jpeg: 'JPEG',
77
+ png: 'PNG',
78
+ gif: 'GIF',
79
+ webp: 'WebP',
80
+ avif: 'AVIF',
81
+ heic: 'HEIC/HEIF',
82
+ bmp: 'BMP',
83
+ tiff: 'TIFF'
84
+ } as const;
85
+
86
+ export const isValidImage = (file: File): Promise<boolean> => {
87
+ return new Promise((resolve) => {
88
+ if (!(file.type in SUPPORTED_MIME_TYPES)) {
89
+ resolve(false);
90
+ return;
91
+ }
92
+
93
+ const img = new Image();
94
+ img.onload = () => resolve(img.width > 0 && img.height > 0);
95
+ img.onerror = () => resolve(false);
96
+ img.src = URL.createObjectURL(file);
97
+ });
98
+ };
99
+
100
+ export const checkImageSignature = (file: File): Promise<boolean> => {
101
+ return new Promise((resolve) => {
102
+ const formatKey =
103
+ SUPPORTED_MIME_TYPES[file.type as keyof typeof SUPPORTED_MIME_TYPES];
104
+ if (!formatKey) {
105
+ resolve(false);
106
+ return;
107
+ }
108
+
109
+ const reader = new FileReader();
110
+ reader.onloadend = (e) => {
111
+ const result = e.target?.result as ArrayBuffer;
112
+ const arr = new Uint8Array(result);
113
+ const header = Array.from(arr)
114
+ .map((b) => b.toString(16).padStart(2, '0'))
115
+ .join('');
116
+
117
+ let isValid = false;
118
+
119
+ switch (formatKey) {
120
+ case 'jpeg':
121
+ case 'png':
122
+ case 'gif':
123
+ case 'bmp':
124
+ case 'tiff':
125
+ const signatures = IMAGE_SIGNATURES[formatKey];
126
+ isValid = signatures.some((sig) => header.startsWith(sig.bytes));
127
+ break;
128
+
129
+ case 'webp':
130
+ if (header.length >= 24) {
131
+ const hasRiff = header.startsWith(IMAGE_SIGNATURES.webp.riff);
132
+ const hasWebp = header.substr(16, 8) === IMAGE_SIGNATURES.webp.webp;
133
+ isValid = hasRiff && hasWebp;
134
+ }
135
+ break;
136
+
137
+ case 'avif':
138
+ if (header.length >= 24) {
139
+ const hasFtyp = header.substr(8, 8) === IMAGE_SIGNATURES.avif.ftyp;
140
+ const hasAvif = header.indexOf(IMAGE_SIGNATURES.avif.avif) !== -1;
141
+ isValid = hasFtyp && hasAvif;
142
+ }
143
+ break;
144
+
145
+ case 'heic':
146
+ if (header.length >= 24) {
147
+ const hasFtyp =
148
+ header.substr(8, 8) === IMAGE_SIGNATURES.heic[0].ftyp;
149
+ const hasHeic = IMAGE_SIGNATURES.heic.some(
150
+ (variant) => header.indexOf(variant.brand) !== -1
151
+ );
152
+ isValid = hasFtyp && hasHeic;
153
+ }
154
+ break;
155
+ }
156
+
157
+ resolve(isValid);
158
+ };
159
+
160
+ reader.onerror = () => resolve(false);
161
+
162
+ const bytesToRead = ['webp', 'avif', 'heic'].includes(formatKey) ? 24 : 8;
163
+ reader.readAsArrayBuffer(file.slice(0, bytesToRead));
164
+ });
165
+ };
166
+
167
+ export const validateImageMetadata = (dataUrl: string): Promise<boolean> => {
168
+ return new Promise((resolve) => {
169
+ const img = new Image();
170
+ img.onload = () => {
171
+ if (img.width > 10000 || img.height > 10000) {
172
+ resolve(false);
173
+ return;
174
+ }
175
+
176
+ const ratio = img.width / img.height;
177
+ if (ratio > 5 || ratio < 0.2) {
178
+ resolve(false);
179
+ return;
180
+ }
181
+
182
+ resolve(true);
183
+ };
184
+ img.onerror = () => resolve(false);
185
+ img.src = dataUrl;
186
+ });
187
+ };
188
+
189
+ export const validateImageFile = async (
190
+ file: File,
191
+ maxSizeInMB: number = 5
192
+ ): Promise<ImageValidationResult> => {
193
+ const supportedFormats = Object.values(FORMAT_NAMES);
194
+ const supportedTypes = Object.keys(SUPPORTED_MIME_TYPES);
195
+
196
+ if (file.size > maxSizeInMB * 1024 * 1024) {
197
+ return {
198
+ isValid: false,
199
+ error: `File size should be less than ${maxSizeInMB}MB. Supported formats: ${supportedFormats.join(
200
+ ', '
201
+ )}`
202
+ };
203
+ }
204
+
205
+ if (!supportedTypes.includes(file.type)) {
206
+ return {
207
+ isValid: false,
208
+ error: `Unsupported file type. Please use: ${supportedFormats.join(', ')}`
209
+ };
210
+ }
211
+
212
+ const isImage = await isValidImage(file);
213
+ if (!isImage) {
214
+ return {
215
+ isValid: false,
216
+ error: `The selected file is not a valid image. Supported formats: ${supportedFormats.join(
217
+ ', '
218
+ )}`
219
+ };
220
+ }
221
+
222
+ const validSignature = await checkImageSignature(file);
223
+ if (!validSignature) {
224
+ return {
225
+ isValid: false,
226
+ error: `Invalid image format detected. Supported formats: ${supportedFormats.join(
227
+ ', '
228
+ )}`
229
+ };
230
+ }
231
+
232
+ return { isValid: true };
233
+ };
234
+
235
+ export const validateImageFromDataUrl = async (
236
+ dataUrl: string
237
+ ): Promise<ImageValidationResult> => {
238
+ const validMetadata = await validateImageMetadata(dataUrl);
239
+ if (!validMetadata) {
240
+ return {
241
+ isValid: false,
242
+ error: 'Invalid image properties detected (dimensions or aspect ratio)'
243
+ };
244
+ }
245
+
246
+ return { isValid: true };
247
+ };
248
+
249
+ export const debugImageSignature = async (
250
+ file: File
251
+ ): Promise<{
252
+ mimeType: string;
253
+ format: string;
254
+ expectedSignatures: string[];
255
+ actualHeader: string;
256
+ isValid: boolean;
257
+ }> => {
258
+ const formatKey =
259
+ SUPPORTED_MIME_TYPES[file.type as keyof typeof SUPPORTED_MIME_TYPES];
260
+
261
+ return new Promise((resolve) => {
262
+ const reader = new FileReader();
263
+ reader.onloadend = (e) => {
264
+ const result = e.target?.result as ArrayBuffer;
265
+ const arr = new Uint8Array(result);
266
+ const header = Array.from(arr.slice(0, 24))
267
+ .map((b) => b.toString(16).padStart(2, '0'))
268
+ .join('');
269
+
270
+ let expectedSignatures: string[] = [];
271
+
272
+ if (formatKey && formatKey in IMAGE_SIGNATURES) {
273
+ const sigs = IMAGE_SIGNATURES[formatKey];
274
+ if (Array.isArray(sigs)) {
275
+ expectedSignatures = sigs.map((s) => s.bytes);
276
+ } else if ('riff' in sigs) {
277
+ expectedSignatures = [sigs.riff + 'XXXXXXXX' + sigs.webp];
278
+ } else if ('ftyp' in sigs) {
279
+ expectedSignatures = [sigs.ftyp];
280
+ }
281
+ }
282
+
283
+ resolve({
284
+ mimeType: file.type,
285
+ format: formatKey || 'unknown',
286
+ expectedSignatures,
287
+ actualHeader: header,
288
+ isValid: formatKey ? true : false
289
+ });
290
+ };
291
+
292
+ reader.onerror = () =>
293
+ resolve({
294
+ mimeType: file.type,
295
+ format: 'error',
296
+ expectedSignatures: [],
297
+ actualHeader: '',
298
+ isValid: false
299
+ });
300
+
301
+ reader.readAsArrayBuffer(file.slice(0, 24));
302
+ });
303
+ };
package/utils/redirect.ts CHANGED
@@ -3,23 +3,21 @@ import Settings from 'settings';
3
3
  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
- import { urlLocaleMatcherRegex } from '@akinon/next/utils';
7
6
 
8
7
  export const redirect = (path: string, type?: RedirectType) => {
9
8
  const nextHeaders = headers();
10
9
  const pageUrl = new URL(
11
- nextHeaders.get('pz-url') ?? process.env.NEXT_PUBLIC_URL ?? ''
10
+ nextHeaders.get('pz-url') ?? process.env.NEXT_PUBLIC_URL
12
11
  );
13
12
 
14
13
  const currentLocale = Settings.localization.locales.find(
15
14
  (locale) => locale.value === ServerVariables.locale
16
15
  );
17
16
 
18
- const callbackUrl = pageUrl.pathname.replace(urlLocaleMatcherRegex, '');
19
-
17
+ const callbackUrl = pageUrl.pathname;
20
18
  const redirectUrlWithLocale = getUrlPathWithLocale(
21
19
  path,
22
- currentLocale?.value
20
+ currentLocale.localePath ?? currentLocale.value
23
21
  );
24
22
 
25
23
  const redirectUrl = `${redirectUrlWithLocale}?callbackUrl=${callbackUrl}`;
package/with-pz-config.js CHANGED
@@ -16,8 +16,12 @@ const defaultConfig = {
16
16
  remotePatterns: [
17
17
  {
18
18
  protocol: 'https',
19
- hostname: '**'
19
+ hostname: '**.akinoncloud.com'
20
20
  },
21
+ {
22
+ protocol: 'https',
23
+ hostname: '**.akinoncdn.com'
24
+ }
21
25
  ]
22
26
  },
23
27
  modularizeImports: {