@akinon/next 1.96.0-snapshot-ZERO-35861-20250908151109 → 1.96.0-snapshot-ZERO-3620-20250915165755

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 (49) hide show
  1. package/CHANGELOG.md +1395 -45
  2. package/__tests__/next-config.test.ts +1 -10
  3. package/__tests__/redirect.test.ts +319 -0
  4. package/api/cache.ts +5 -39
  5. package/api/image-proxy.ts +75 -0
  6. package/api/similar-product-list.ts +84 -0
  7. package/api/similar-products.ts +120 -0
  8. package/components/accordion.tsx +20 -5
  9. package/components/file-input.tsx +65 -3
  10. package/components/input.tsx +2 -0
  11. package/components/link.tsx +16 -12
  12. package/components/modal.tsx +32 -16
  13. package/components/plugin-module.tsx +30 -3
  14. package/data/client/checkout.ts +5 -4
  15. package/data/server/basket.ts +72 -0
  16. package/data/server/category.ts +50 -32
  17. package/data/server/flatpage.ts +17 -16
  18. package/data/server/form.ts +1 -4
  19. package/data/server/landingpage.ts +16 -12
  20. package/data/server/list.ts +24 -15
  21. package/data/server/menu.ts +2 -5
  22. package/data/server/product.ts +67 -41
  23. package/data/server/special-page.ts +16 -12
  24. package/data/server/widget.ts +1 -4
  25. package/data/urls.ts +5 -1
  26. package/hocs/server/with-segment-defaults.tsx +5 -2
  27. package/hooks/use-localization.ts +2 -3
  28. package/jest.config.js +7 -1
  29. package/lib/cache-handler.mjs +87 -365
  30. package/lib/cache.ts +25 -252
  31. package/middlewares/complete-gpay.ts +2 -1
  32. package/middlewares/complete-masterpass.ts +2 -1
  33. package/middlewares/default.ts +50 -13
  34. package/middlewares/locale.ts +9 -1
  35. package/middlewares/pretty-url.ts +1 -2
  36. package/middlewares/redirection-payment.ts +2 -1
  37. package/middlewares/saved-card-redirection.ts +2 -1
  38. package/middlewares/three-d-redirection.ts +2 -1
  39. package/middlewares/url-redirection.ts +8 -14
  40. package/package.json +3 -4
  41. package/plugins.d.ts +8 -0
  42. package/plugins.js +3 -1
  43. package/redux/middlewares/checkout.ts +5 -1
  44. package/types/commerce/order.ts +1 -0
  45. package/types/index.ts +34 -2
  46. package/utils/app-fetch.ts +7 -2
  47. package/utils/redirect-ignore.ts +35 -0
  48. package/utils/redirect.ts +31 -6
  49. package/with-pz-config.js +1 -5
package/lib/cache.ts CHANGED
@@ -3,65 +3,6 @@ import { RedisClientType } from 'redis';
3
3
  import Settings from 'settings';
4
4
  import { CacheOptions } from '../types';
5
5
  import logger from '../utils/log';
6
- const CACHE_VERSION = 'v2';
7
-
8
- const compressData = async (data: string): Promise<Uint8Array> => {
9
- const stream = new CompressionStream('gzip');
10
- const writer = stream.writable.getWriter();
11
- const reader = stream.readable.getReader();
12
-
13
- writer.write(new TextEncoder().encode(data));
14
- writer.close();
15
-
16
- const chunks: Uint8Array[] = [];
17
- let done = false;
18
-
19
- while (!done) {
20
- const { value, done: readerDone } = await reader.read();
21
- done = readerDone;
22
- if (value) chunks.push(value);
23
- }
24
-
25
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
26
- const result = new Uint8Array(totalLength);
27
- let offset = 0;
28
-
29
- for (const chunk of chunks) {
30
- result.set(chunk, offset);
31
- offset += chunk.length;
32
- }
33
-
34
- return result;
35
- };
36
-
37
- const decompressData = async (compressed: Uint8Array): Promise<string> => {
38
- const stream = new DecompressionStream('gzip');
39
- const writer = stream.writable.getWriter();
40
- const reader = stream.readable.getReader();
41
-
42
- writer.write(compressed);
43
- writer.close();
44
-
45
- const chunks: Uint8Array[] = [];
46
- let done = false;
47
-
48
- while (!done) {
49
- const { value, done: readerDone } = await reader.read();
50
- done = readerDone;
51
- if (value) chunks.push(value);
52
- }
53
-
54
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
55
- const result = new Uint8Array(totalLength);
56
- let offset = 0;
57
-
58
- for (const chunk of chunks) {
59
- result.set(chunk, offset);
60
- offset += chunk.length;
61
- }
62
-
63
- return new TextDecoder().decode(result);
64
- };
65
6
 
66
7
  const hashCacheKey = (object?: Record<string, string>) => {
67
8
  if (!object) {
@@ -119,32 +60,8 @@ export const CacheKey = {
119
60
  export class Cache {
120
61
  static PROXY_URL = `${process.env.NEXT_PUBLIC_URL}/api/cache`;
121
62
 
122
- private static serializeValue(value: any): string {
123
- return typeof value === 'object' ? JSON.stringify(value) : String(value);
124
- }
125
-
126
- private static validateKey(key: string): boolean {
127
- return !(!key || key.trim() === '');
128
- }
129
-
130
- private static validateKeyValuePairs(keyValuePairs: Record<string, any>): {
131
- isValid: boolean;
132
- invalidKeys: string[];
133
- } {
134
- if (!keyValuePairs || Object.keys(keyValuePairs).length === 0) {
135
- return { isValid: false, invalidKeys: [] };
136
- }
137
-
138
- const invalidKeys = Object.keys(keyValuePairs).filter(
139
- (key) => !this.validateKey(key)
140
- );
141
- return { isValid: invalidKeys.length === 0, invalidKeys };
142
- }
143
-
144
63
  static formatKey(key: string, locale: string) {
145
- return encodeURIComponent(
146
- `${CACHE_VERSION}_${Settings.commerceUrl}_${locale}_${key}`
147
- );
64
+ return encodeURIComponent(`${Settings.commerceUrl}_${locale}_${key}`);
148
65
  }
149
66
 
150
67
  static clientPool: Pool<RedisClientType> = createPool(
@@ -181,9 +98,9 @@ export class Cache {
181
98
  return await Cache.clientPool.acquire();
182
99
  }
183
100
 
184
- static async get(key: string): Promise<any> {
185
- let value: any;
186
- let client: RedisClientType | undefined;
101
+ static async get(key: string) {
102
+ let value;
103
+ let client;
187
104
 
188
105
  try {
189
106
  client = await Cache.getClient();
@@ -193,7 +110,9 @@ export class Cache {
193
110
  } else {
194
111
  value = null;
195
112
  }
113
+ logger.debug('Redis get success', { key, value });
196
114
  } catch (error) {
115
+ logger.error('Redis get error', { key, error });
197
116
  value = null;
198
117
  } finally {
199
118
  if (client) {
@@ -204,13 +123,14 @@ export class Cache {
204
123
  return value;
205
124
  }
206
125
 
207
- static async set(key: string, value: any, expire?: number): Promise<boolean> {
126
+ static async set(key: string, value: any, expire?: number) {
208
127
  let success = false;
209
- let client: RedisClientType | undefined;
128
+ let client;
210
129
 
211
130
  try {
212
131
  client = await Cache.getClient();
213
- const serializedValue = Cache.serializeValue(value);
132
+ const serializedValue =
133
+ typeof value === 'object' ? JSON.stringify(value) : value;
214
134
 
215
135
  if (expire) {
216
136
  await client.set(key, serializedValue, { EX: expire });
@@ -219,7 +139,9 @@ export class Cache {
219
139
  }
220
140
 
221
141
  success = true;
142
+ logger.debug('Redis set success', { key, value });
222
143
  } catch (error) {
144
+ logger.error('Redis set error', { key, error });
223
145
  success = false;
224
146
  } finally {
225
147
  if (client) {
@@ -248,8 +170,7 @@ export class Cache {
248
170
 
249
171
  const defaultOptions: CacheOptions = {
250
172
  cache: true,
251
- expire: Settings.redis.defaultExpirationTime,
252
- compressed: process.env.CACHE_COMPRESSION_ENABLED !== 'false'
173
+ expire: Settings.redis.defaultExpirationTime
253
174
  };
254
175
 
255
176
  const _options = Object.assign(defaultOptions, options);
@@ -259,22 +180,21 @@ export class Cache {
259
180
  _options.expire = 120;
260
181
  }
261
182
 
183
+ logger.debug('Cache wrap', { key, formattedKey, _options });
184
+
262
185
  if (_options.cache) {
263
- let cachedValue: any;
186
+ let cachedValue;
264
187
 
265
188
  if (_options.useProxy) {
266
189
  const body = new URLSearchParams();
267
190
 
268
191
  body.append('key', formattedKey);
269
- if (_options.compressed) {
270
- body.append('compressed', 'true');
271
- }
272
192
 
273
193
  cachedValue = await Cache.proxyRequest('POST', body);
194
+ logger.debug('Cache proxy request success', { key });
195
+ logger.trace('Cache proxy request', { key, cachedValue });
274
196
  } else {
275
- cachedValue = _options.compressed
276
- ? await Cache.getCompressed(formattedKey)
277
- : await Cache.get(formattedKey);
197
+ cachedValue = await Cache.get(formattedKey);
278
198
  }
279
199
 
280
200
  if (cachedValue) {
@@ -282,6 +202,8 @@ export class Cache {
282
202
  }
283
203
  }
284
204
 
205
+ logger.debug('Redis cache miss. Setting new value...', { key });
206
+
285
207
  const data = await handler();
286
208
 
287
209
  if (data && _options.cache) {
@@ -295,19 +217,14 @@ export class Cache {
295
217
  'expire',
296
218
  String(_options?.expire ?? Settings.redis.defaultExpirationTime)
297
219
  );
298
- if (_options.compressed) {
299
- body.append('compressed', 'true');
300
- }
301
220
  await Cache.proxyRequest('PUT', body);
221
+
222
+ logger.debug('Cache proxy request', { key, body: body.toString() });
302
223
  } catch (error) {
303
224
  logger.error('Cache proxy error', error);
304
225
  }
305
226
  } else {
306
- if (_options.compressed) {
307
- await Cache.setCompressed(formattedKey, data, _options?.expire);
308
- } else {
309
- await Cache.set(formattedKey, JSON.stringify(data), _options?.expire);
310
- }
227
+ await Cache.set(formattedKey, JSON.stringify(data), _options?.expire);
311
228
  }
312
229
  }
313
230
 
@@ -319,7 +236,7 @@ export class Cache {
319
236
  await fetch(Cache.PROXY_URL, {
320
237
  method,
321
238
  headers: {
322
- authorization: process.env.CACHE_SECRET || ''
239
+ authorization: process.env.CACHE_SECRET
323
240
  },
324
241
  body
325
242
  })
@@ -327,148 +244,4 @@ export class Cache {
327
244
 
328
245
  return response;
329
246
  }
330
-
331
- static async mset(
332
- keyValuePairs: Record<string, any>,
333
- expire?: number
334
- ): Promise<boolean> {
335
- const validation = Cache.validateKeyValuePairs(keyValuePairs);
336
- if (!validation.isValid) {
337
- if (validation.invalidKeys.length > 0) {
338
- logger.error('Invalid keys in mset', {
339
- invalidKeys: validation.invalidKeys
340
- });
341
- } else {
342
- logger.warn('mset called with empty keyValuePairs');
343
- }
344
- return false;
345
- }
346
-
347
- let success = false;
348
- let client: RedisClientType | undefined;
349
-
350
- try {
351
- client = await Cache.getClient();
352
- const pipeline = client.multi();
353
-
354
- Object.entries(keyValuePairs).forEach(([key, value]) => {
355
- const serializedValue = Cache.serializeValue(value);
356
- if (expire) {
357
- pipeline.set(key, serializedValue, { EX: expire });
358
- } else {
359
- pipeline.set(key, serializedValue);
360
- }
361
- });
362
-
363
- const results = await pipeline.exec();
364
-
365
- const failures =
366
- results?.filter((result) => result instanceof Error) || [];
367
-
368
- if (failures.length > 0) {
369
- success = false;
370
- } else {
371
- success = true;
372
- }
373
- } catch (error) {
374
- success = false;
375
- } finally {
376
- if (client) {
377
- await Cache.clientPool.release(client);
378
- }
379
- }
380
-
381
- return success;
382
- }
383
-
384
- static async setCompressed(
385
- key: string,
386
- value: any,
387
- expire?: number
388
- ): Promise<boolean> {
389
- if (!Cache.validateKey(key)) {
390
- return false;
391
- }
392
-
393
- let success = false;
394
- let client: RedisClientType | undefined;
395
-
396
- try {
397
- client = await Cache.getClient();
398
- const serializedValue = Cache.serializeValue(value);
399
-
400
- try {
401
- const compressed = await compressData(serializedValue);
402
- const compressedBase64 = Buffer.from(compressed).toString('base64');
403
-
404
- if (expire) {
405
- await client.set(key, compressedBase64, { EX: expire });
406
- } else {
407
- await client.set(key, compressedBase64);
408
- }
409
-
410
- success = true;
411
- } catch (compressionError) {
412
- if (expire) {
413
- await client.set(key, serializedValue, { EX: expire });
414
- } else {
415
- await client.set(key, serializedValue);
416
- }
417
-
418
- success = true;
419
- }
420
- } catch (error) {
421
- success = false;
422
- } finally {
423
- if (client) {
424
- await Cache.clientPool.release(client);
425
- }
426
- }
427
-
428
- return success;
429
- }
430
-
431
- static async getCompressed(key: string): Promise<unknown> {
432
- if (!Cache.validateKey(key)) {
433
- return null;
434
- }
435
-
436
- let value: unknown;
437
- let client: RedisClientType | undefined;
438
-
439
- try {
440
- client = await Cache.getClient();
441
- const compressed = await client.get(key);
442
-
443
- if (compressed) {
444
- const compressedBuffer = Buffer.from(compressed, 'base64');
445
-
446
- try {
447
- const decompressedString = await decompressData(
448
- new Uint8Array(compressedBuffer)
449
- );
450
- value = JSON.parse(decompressedString);
451
- return value;
452
- } catch (decompressionError) {
453
- try {
454
- const rawString = compressed;
455
- const parsedData = JSON.parse(rawString);
456
- return parsedData;
457
- } catch (jsonError) {
458
- return null;
459
- }
460
- }
461
- } else {
462
- value = null;
463
- }
464
- } catch (error) {
465
- value = null;
466
- } finally {
467
- if (client) {
468
- await Cache.clientPool.release(client);
469
- }
470
- }
471
-
472
- return value;
473
- }
474
247
  }
@@ -148,7 +148,8 @@ const withCompleteGpay =
148
148
  logger.info('Redirecting to order success page', {
149
149
  middleware: 'complete-gpay',
150
150
  redirectUrlWithLocale,
151
- ip
151
+ ip,
152
+ setCookie: request.headers.get('set-cookie')
152
153
  });
153
154
 
154
155
  // Using POST method while redirecting causes an error,
@@ -149,7 +149,8 @@ const withCompleteMasterpass =
149
149
  logger.info('Redirecting to order success page', {
150
150
  middleware: 'complete-masterpass',
151
151
  redirectUrlWithLocale,
152
- ip
152
+ ip,
153
+ setCookie: request.headers.get('set-cookie')
153
154
  });
154
155
 
155
156
  // Using POST method while redirecting causes an error,
@@ -302,19 +302,6 @@ const withPzDefault =
302
302
  )}`;
303
303
  }
304
304
 
305
- if (
306
- !req.middlewareParams.found &&
307
- Settings.customNotFoundEnabled
308
- ) {
309
- const pathname = url.pathname
310
- .replace(/\/+$/, '')
311
- .split('/');
312
- url.pathname = url.pathname.replace(
313
- pathname.pop(),
314
- 'pz-not-found'
315
- );
316
- }
317
-
318
305
  Settings.rewrites.forEach((rewrite) => {
319
306
  url.pathname = url.pathname.replace(
320
307
  rewrite.source,
@@ -352,6 +339,24 @@ const withPzDefault =
352
339
  middlewareResult = NextResponse.rewrite(url);
353
340
  }
354
341
 
342
+ if (
343
+ !req.middlewareParams.found &&
344
+ Settings.customNotFoundEnabled
345
+ ) {
346
+ const pathSegments = url.pathname
347
+ .replace(/\/+$/, '')
348
+ .split('/');
349
+ if (pathSegments.length >= 3) {
350
+ url.pathname = `/${pathSegments[1]}/${pathSegments[2]}/pz-not-found`;
351
+ } else {
352
+ url.pathname = '/pz-not-found';
353
+ }
354
+
355
+ middlewareResult = NextResponse.rewrite(url, {
356
+ status: 404
357
+ });
358
+ }
359
+
355
360
  const { localeUrlStrategy } =
356
361
  Settings.localization;
357
362
 
@@ -401,6 +406,38 @@ const withPzDefault =
401
406
  }
402
407
  );
403
408
 
409
+ if (
410
+ !url.pathname.startsWith(
411
+ `/${currency}/orders`
412
+ )
413
+ ) {
414
+ const currentCookieLocale =
415
+ req.cookies.get('pz-locale')?.value;
416
+
417
+ const urlHasExplicitLocale =
418
+ url.pathname.match(urlLocaleMatcherRegex);
419
+ const shouldUpdateCookie =
420
+ !currentCookieLocale ||
421
+ urlHasExplicitLocale;
422
+
423
+ if (shouldUpdateCookie) {
424
+ middlewareResult.cookies.set(
425
+ 'pz-locale',
426
+ locale?.length > 0
427
+ ? locale
428
+ : defaultLocaleValue,
429
+ {
430
+ domain: rootHostname,
431
+ sameSite: 'none',
432
+ secure: true,
433
+ expires: new Date(
434
+ Date.now() + 1000 * 60 * 60 * 24 * 7
435
+ ) // 7 days
436
+ }
437
+ );
438
+ }
439
+ }
440
+
404
441
  if (
405
442
  req.cookies.get('pz-locale') &&
406
443
  req.cookies.get('pz-locale').value !== locale
@@ -23,7 +23,15 @@ const getMatchedLocale = (pathname: string, req: PzNextRequest) => {
23
23
  );
24
24
 
25
25
  if (subDomainLocaleMatched && subDomainLocaleMatched[0]) {
26
- matchedLocale = subDomainLocaleMatched[0].slice(1);
26
+ const subdomainLocale = subDomainLocaleMatched[0].slice(1);
27
+
28
+ const isValidSubdomainLocale = settings.localization.locales.find(
29
+ (l) => l.value === subdomainLocale
30
+ );
31
+
32
+ if (isValidSubdomainLocale) {
33
+ matchedLocale = subdomainLocale;
34
+ }
27
35
  }
28
36
  }
29
37
  }
@@ -53,8 +53,7 @@ const resolvePrettyUrl = async (
53
53
  locale,
54
54
  resolvePrettyUrlHandler(pathname, ip),
55
55
  {
56
- useProxy: true,
57
- compressed: true
56
+ useProxy: true
58
57
  }
59
58
  );
60
59
  };
@@ -149,7 +149,8 @@ const withRedirectionPayment =
149
149
  logger.info('Redirecting to order success page', {
150
150
  middleware: 'redirection-payment',
151
151
  redirectUrlWithLocale,
152
- ip
152
+ ip,
153
+ setCookie: request.headers.get('set-cookie')
153
154
  });
154
155
 
155
156
  // Using POST method while redirecting causes an error,
@@ -149,7 +149,8 @@ const withSavedCardRedirection =
149
149
  logger.info('Redirecting to order success page', {
150
150
  middleware: 'saved-card-redirection',
151
151
  redirectUrlWithLocale,
152
- ip
152
+ ip,
153
+ setCookie: request.headers.get('set-cookie')
153
154
  });
154
155
 
155
156
  // Using POST method while redirecting causes an error,
@@ -149,7 +149,8 @@ const withThreeDRedirection =
149
149
  logger.info('Redirecting to order success page', {
150
150
  middleware: 'three-d-redirection',
151
151
  redirectUrlWithLocale,
152
- ip
152
+ ip,
153
+ setCookie: request.headers.get('set-cookie')
153
154
  });
154
155
 
155
156
  // Using POST method while redirecting causes an error,
@@ -4,6 +4,7 @@ import { PzNextRequest } from '.';
4
4
  import logger from '../utils/log';
5
5
  import { urlLocaleMatcherRegex } from '../utils';
6
6
  import { getUrlPathWithLocale } from '../utils/localization';
7
+ import { shouldIgnoreRedirect } from '../utils/redirect-ignore';
7
8
  import { ROUTES } from 'routes';
8
9
 
9
10
  // This middleware is used to handle url redirections set in Omnitron
@@ -60,20 +61,13 @@ const withUrlRedirection =
60
61
 
61
62
  const setCookies = request.headers.getSetCookie();
62
63
 
63
- if (settings.commerceRedirectionIgnoreList) {
64
- const shouldIgnoreRedirect =
65
- settings.commerceRedirectionIgnoreList.some((ignorePath) =>
66
- redirectUrl.pathname.startsWith(
67
- getUrlPathWithLocale(
68
- ignorePath,
69
- req.middlewareParams.rewrites.locale
70
- )
71
- )
72
- );
73
-
74
- if (shouldIgnoreRedirect) {
75
- return middleware(req, event);
76
- }
64
+ if (
65
+ shouldIgnoreRedirect(
66
+ url.pathname,
67
+ req.middlewareParams.rewrites.locale
68
+ )
69
+ ) {
70
+ return middleware(req, event);
77
71
  }
78
72
 
79
73
  const response = NextResponse.redirect(redirectUrl.toString(), {
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.96.0-snapshot-ZERO-35861-20250908151109",
4
+ "version": "1.96.0-snapshot-ZERO-3620-20250915165755",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -17,14 +17,13 @@
17
17
  "test": "jest"
18
18
  },
19
19
  "dependencies": {
20
- "@mongodb-js/zstd": "^2.0.1",
21
- "@neshca/cache-handler": "1.9.0",
22
20
  "@opentelemetry/exporter-trace-otlp-http": "0.46.0",
23
21
  "@opentelemetry/resources": "1.19.0",
24
22
  "@opentelemetry/sdk-node": "0.46.0",
25
23
  "@opentelemetry/sdk-trace-node": "1.19.0",
26
24
  "@opentelemetry/semantic-conventions": "1.19.0",
27
25
  "@reduxjs/toolkit": "1.9.7",
26
+ "@neshca/cache-handler": "1.9.0",
28
27
  "@sentry/nextjs": "9.5.0",
29
28
  "cross-spawn": "7.0.3",
30
29
  "generic-pool": "3.9.0",
@@ -35,7 +34,7 @@
35
34
  "set-cookie-parser": "2.6.0"
36
35
  },
37
36
  "devDependencies": {
38
- "@akinon/eslint-plugin-projectzero": "1.96.0-snapshot-ZERO-35861-20250908151109",
37
+ "@akinon/eslint-plugin-projectzero": "1.96.0-snapshot-ZERO-3620-20250915165755",
39
38
  "@babel/core": "7.26.10",
40
39
  "@babel/preset-env": "7.26.9",
41
40
  "@babel/preset-typescript": "7.27.0",
package/plugins.d.ts CHANGED
@@ -38,3 +38,11 @@ declare module '@akinon/pz-iyzico-saved-card' {
38
38
  declare module '@akinon/pz-apple-pay' {}
39
39
 
40
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
@@ -16,5 +16,7 @@ module.exports = [
16
16
  'pz-tabby-extension',
17
17
  'pz-apple-pay',
18
18
  'pz-tamara-extension',
19
- 'pz-flow-payment'
19
+ 'pz-hepsipay',
20
+ 'pz-flow-payment',
21
+ 'pz-similar-products'
20
22
  ];
@@ -51,7 +51,11 @@ export const errorMiddleware: Middleware = ({ dispatch }: MiddlewareParams) => {
51
51
  const result: CheckoutResult = next(action);
52
52
  const errors = result?.payload?.errors;
53
53
 
54
- if (errors) {
54
+ if (
55
+ !!errors &&
56
+ ((typeof errors === 'object' && Object.keys(errors).length > 0) ||
57
+ (Array.isArray(errors) && errors.length > 0))
58
+ ) {
55
59
  dispatch(setErrors(errors));
56
60
  }
57
61
 
@@ -114,6 +114,7 @@ export interface Order {
114
114
  pk: number;
115
115
  name: string;
116
116
  slug: string;
117
+ logo: string;
117
118
  [key: string]: any;
118
119
  };
119
120
  }