@akinon/next 2.0.0-beta.2 → 2.0.0-beta.20

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 (189) hide show
  1. package/.eslintrc.js +12 -0
  2. package/CHANGELOG.md +377 -7
  3. package/__tests__/next-config.test.ts +83 -0
  4. package/__tests__/tsconfig.json +23 -0
  5. package/api/auth.ts +133 -44
  6. package/api/barcode-search.ts +59 -0
  7. package/api/cache.ts +41 -5
  8. package/api/client.ts +21 -4
  9. package/api/form.ts +85 -0
  10. package/api/image-proxy.ts +75 -0
  11. package/api/product-categories.ts +53 -0
  12. package/api/similar-product-list.ts +63 -0
  13. package/api/similar-products.ts +111 -0
  14. package/api/virtual-try-on.ts +382 -0
  15. package/assets/styles/index.scss +84 -0
  16. package/babel.config.js +6 -0
  17. package/bin/pz-generate-routes.js +115 -0
  18. package/bin/pz-prebuild.js +1 -0
  19. package/bin/pz-predev.js +1 -0
  20. package/bin/pz-run-tests.js +99 -0
  21. package/bin/run-prebuild-tests.js +46 -0
  22. package/components/accordion.tsx +20 -5
  23. package/components/button.tsx +51 -36
  24. package/components/client-root.tsx +138 -2
  25. package/components/file-input.tsx +65 -3
  26. package/components/index.ts +1 -0
  27. package/components/input.tsx +1 -1
  28. package/components/link.tsx +46 -16
  29. package/components/logger-popup.tsx +213 -0
  30. package/components/modal.tsx +32 -16
  31. package/components/plugin-module.tsx +62 -3
  32. package/components/price.tsx +2 -2
  33. package/components/select.tsx +1 -1
  34. package/components/selected-payment-option-view.tsx +21 -0
  35. package/components/theme-editor/blocks/accordion-block.tsx +136 -0
  36. package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
  37. package/components/theme-editor/blocks/button-block.tsx +593 -0
  38. package/components/theme-editor/blocks/counter-block.tsx +348 -0
  39. package/components/theme-editor/blocks/divider-block.tsx +20 -0
  40. package/components/theme-editor/blocks/embed-block.tsx +208 -0
  41. package/components/theme-editor/blocks/group-block.tsx +116 -0
  42. package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
  43. package/components/theme-editor/blocks/icon-block.tsx +230 -0
  44. package/components/theme-editor/blocks/image-block.tsx +137 -0
  45. package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
  46. package/components/theme-editor/blocks/input-block.tsx +123 -0
  47. package/components/theme-editor/blocks/link-block.tsx +216 -0
  48. package/components/theme-editor/blocks/lottie-block.tsx +325 -0
  49. package/components/theme-editor/blocks/map-block.tsx +89 -0
  50. package/components/theme-editor/blocks/slider-block.tsx +595 -0
  51. package/components/theme-editor/blocks/tab-block.tsx +10 -0
  52. package/components/theme-editor/blocks/text-block.tsx +52 -0
  53. package/components/theme-editor/blocks/video-block.tsx +122 -0
  54. package/components/theme-editor/components/action-toolbar.tsx +305 -0
  55. package/components/theme-editor/components/designer-overlay.tsx +74 -0
  56. package/components/theme-editor/components/with-designer-features.tsx +142 -0
  57. package/components/theme-editor/dynamic-font-loader.tsx +79 -0
  58. package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
  59. package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
  60. package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
  61. package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
  62. package/components/theme-editor/placeholder-registry.ts +31 -0
  63. package/components/theme-editor/sections/before-after-section.tsx +245 -0
  64. package/components/theme-editor/sections/contact-form-section.tsx +563 -0
  65. package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
  66. package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
  67. package/components/theme-editor/sections/divider-section.tsx +62 -0
  68. package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
  69. package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
  70. package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
  71. package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
  72. package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
  73. package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
  74. package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
  75. package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
  76. package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
  77. package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
  78. package/components/theme-editor/sections/section-wrapper.tsx +135 -0
  79. package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
  80. package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
  81. package/components/theme-editor/sections/tabs-section.tsx +578 -0
  82. package/components/theme-editor/theme-block.tsx +102 -0
  83. package/components/theme-editor/theme-placeholder-client.tsx +218 -0
  84. package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
  85. package/components/theme-editor/theme-placeholder.tsx +288 -0
  86. package/components/theme-editor/theme-section.tsx +1224 -0
  87. package/components/theme-editor/theme-settings-context.tsx +13 -0
  88. package/components/theme-editor/utils/index.ts +792 -0
  89. package/components/theme-editor/utils/iterator-utils.ts +234 -0
  90. package/components/theme-editor/utils/publish-window.ts +86 -0
  91. package/components/theme-editor/utils/visibility-rules.ts +188 -0
  92. package/data/client/account.ts +17 -2
  93. package/data/client/api.ts +2 -0
  94. package/data/client/basket.ts +66 -5
  95. package/data/client/checkout.ts +391 -99
  96. package/data/client/misc.ts +38 -2
  97. package/data/client/product.ts +19 -2
  98. package/data/client/user.ts +16 -8
  99. package/data/server/category.ts +11 -9
  100. package/data/server/flatpage.ts +11 -4
  101. package/data/server/form.ts +15 -4
  102. package/data/server/landingpage.ts +11 -4
  103. package/data/server/list.ts +5 -4
  104. package/data/server/menu.ts +11 -3
  105. package/data/server/product.ts +111 -55
  106. package/data/server/seo.ts +14 -4
  107. package/data/server/special-page.ts +5 -4
  108. package/data/server/widget.ts +90 -5
  109. package/data/urls.ts +16 -5
  110. package/hocs/client/with-segment-defaults.tsx +2 -2
  111. package/hocs/server/with-segment-defaults.tsx +65 -20
  112. package/hooks/index.ts +4 -0
  113. package/hooks/use-localization.ts +24 -10
  114. package/hooks/use-logger-context.tsx +114 -0
  115. package/hooks/use-logger.ts +92 -0
  116. package/hooks/use-loyalty-availability.ts +21 -0
  117. package/hooks/use-payment-options.ts +2 -1
  118. package/hooks/use-pz-params.ts +37 -0
  119. package/hooks/use-router.ts +51 -14
  120. package/hooks/use-sentry-uncaught-errors.ts +24 -0
  121. package/instrumentation/index.ts +10 -1
  122. package/instrumentation/node.ts +2 -20
  123. package/jest.config.js +25 -0
  124. package/lib/cache-handler.mjs +534 -16
  125. package/lib/cache.ts +272 -37
  126. package/localization/index.ts +2 -1
  127. package/localization/provider.tsx +2 -5
  128. package/middlewares/bfcache-headers.ts +18 -0
  129. package/middlewares/checkout-provider.ts +1 -1
  130. package/middlewares/complete-gpay.ts +32 -26
  131. package/middlewares/complete-masterpass.ts +33 -26
  132. package/middlewares/complete-wallet.ts +182 -0
  133. package/middlewares/default.ts +360 -215
  134. package/middlewares/index.ts +10 -2
  135. package/middlewares/locale.ts +34 -11
  136. package/middlewares/masterpass-rest-callback.ts +230 -0
  137. package/middlewares/oauth-login.ts +200 -57
  138. package/middlewares/pretty-url.ts +21 -8
  139. package/middlewares/redirection-payment.ts +32 -26
  140. package/middlewares/saved-card-redirection.ts +33 -26
  141. package/middlewares/three-d-redirection.ts +32 -26
  142. package/middlewares/url-redirection.ts +11 -1
  143. package/middlewares/wallet-complete-redirection.ts +206 -0
  144. package/package.json +25 -10
  145. package/plugins.d.ts +19 -4
  146. package/plugins.js +10 -1
  147. package/redux/actions.ts +47 -0
  148. package/redux/middlewares/checkout.ts +63 -138
  149. package/redux/middlewares/index.ts +14 -10
  150. package/redux/middlewares/pre-order/address.ts +7 -2
  151. package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
  152. package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
  153. package/redux/middlewares/pre-order/delivery-option.ts +7 -1
  154. package/redux/middlewares/pre-order/index.ts +16 -10
  155. package/redux/middlewares/pre-order/installment-option.ts +8 -1
  156. package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
  157. package/redux/middlewares/pre-order/payment-option.ts +7 -1
  158. package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
  159. package/redux/middlewares/pre-order/redirection.ts +8 -2
  160. package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
  161. package/redux/middlewares/pre-order/shipping-option.ts +7 -1
  162. package/redux/middlewares/pre-order/shipping-step.ts +5 -1
  163. package/redux/reducers/checkout.ts +23 -3
  164. package/redux/reducers/index.ts +11 -3
  165. package/redux/reducers/root.ts +7 -2
  166. package/redux/reducers/widget.ts +80 -0
  167. package/sentry/index.ts +69 -13
  168. package/tailwind/content.js +16 -0
  169. package/types/commerce/account.ts +5 -1
  170. package/types/commerce/checkout.ts +35 -1
  171. package/types/commerce/widget.ts +33 -0
  172. package/types/index.ts +101 -6
  173. package/types/next-auth.d.ts +2 -2
  174. package/types/widget.ts +80 -0
  175. package/utils/app-fetch.ts +7 -2
  176. package/utils/generate-commerce-search-params.ts +3 -2
  177. package/utils/get-checkout-path.ts +3 -0
  178. package/utils/get-root-hostname.ts +28 -0
  179. package/utils/index.ts +64 -10
  180. package/utils/localization.ts +4 -0
  181. package/utils/mobile-3d-iframe.ts +8 -2
  182. package/utils/override-middleware.ts +7 -12
  183. package/utils/pz-segments.ts +92 -0
  184. package/utils/redirect-ignore.ts +35 -0
  185. package/utils/redirect.ts +9 -3
  186. package/utils/redirection-iframe.ts +8 -2
  187. package/utils/widget-styles.ts +107 -0
  188. package/views/error-page.tsx +93 -0
  189. package/with-pz-config.js +13 -6
package/lib/cache.ts CHANGED
@@ -1,8 +1,67 @@
1
1
  import { createPool, Pool } from 'generic-pool';
2
2
  import { RedisClientType } from 'redis';
3
3
  import Settings from 'settings';
4
- import { CacheOptions } from '../types';
4
+ import { CacheOptions, SearchParams } 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 as any);
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
+ };
6
65
 
7
66
  const hashCacheKey = (object?: Record<string, string>) => {
8
67
  if (!object) {
@@ -19,34 +78,37 @@ const hashCacheKey = (object?: Record<string, string>) => {
19
78
  return `_${encodeURIComponent(cacheKey)}`;
20
79
  };
21
80
  export const CacheKey = {
22
- List: (searchParams: URLSearchParams, headers?: Record<string, string>) =>
81
+ List: (searchParams: SearchParams, headers?: Record<string, string>) =>
23
82
  `list_${encodeURIComponent(JSON.stringify(searchParams))}${hashCacheKey(
24
83
  headers
25
84
  )}`,
26
85
  Category: (
27
86
  pk: number,
28
- searchParams?: URLSearchParams,
87
+ searchParams?: SearchParams,
29
88
  headers?: Record<string, string>
30
89
  ) =>
31
90
  `category_${pk}_${encodeURIComponent(
32
91
  JSON.stringify(searchParams)
33
92
  )}${hashCacheKey(headers)}`,
93
+ Basket: (namespace?: string) => `basket${namespace ? `_${namespace}` : ''}`,
94
+ AllBaskets: () => 'all_baskets',
34
95
  CategorySlug: (slug: string) => `category_${slug}`,
35
96
  SpecialPage: (
36
97
  pk: number,
37
- searchParams: URLSearchParams,
98
+ searchParams: SearchParams,
38
99
  headers?: Record<string, string>
39
100
  ) =>
40
101
  `special_page_${pk}_${encodeURIComponent(
41
102
  JSON.stringify(searchParams)
42
103
  )}${hashCacheKey(headers)}`,
43
- Product: (pk: number, searchParams: URLSearchParams) =>
104
+ Product: (pk: number, searchParams: SearchParams) =>
44
105
  `product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
45
- GroupProduct: (pk: number, searchParams: URLSearchParams) =>
106
+ GroupProduct: (pk: number, searchParams: SearchParams) =>
46
107
  `group_product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
47
108
  FlatPage: (pk: number) => `flat_page_${pk}`,
48
109
  LandingPage: (pk: number) => `landing_page_${pk}`,
49
110
  Widget: (slug: string) => `widget_${slug}`,
111
+ WidgetSchema: (widgetSlug: string) => `widget_schema_${widgetSlug}`,
50
112
  PrettyUrl: (pathname: string) => `pretty_url_${pathname}`,
51
113
  Menu: (depth: number, parent?: string) =>
52
114
  `menu_${depth}${parent ? `_${parent}` : ''}`,
@@ -58,8 +120,32 @@ export const CacheKey = {
58
120
  export class Cache {
59
121
  static PROXY_URL = `${process.env.NEXT_PUBLIC_URL}/api/cache`;
60
122
 
123
+ private static serializeValue(value: any): string {
124
+ return typeof value === 'object' ? JSON.stringify(value) : String(value);
125
+ }
126
+
127
+ private static validateKey(key: string): boolean {
128
+ return !(!key || key.trim() === '');
129
+ }
130
+
131
+ private static validateKeyValuePairs(keyValuePairs: Record<string, any>): {
132
+ isValid: boolean;
133
+ invalidKeys: string[];
134
+ } {
135
+ if (!keyValuePairs || Object.keys(keyValuePairs).length === 0) {
136
+ return { isValid: false, invalidKeys: [] };
137
+ }
138
+
139
+ const invalidKeys = Object.keys(keyValuePairs).filter(
140
+ (key) => !this.validateKey(key)
141
+ );
142
+ return { isValid: invalidKeys.length === 0, invalidKeys };
143
+ }
144
+
61
145
  static formatKey(key: string, locale: string) {
62
- return encodeURIComponent(`${Settings.commerceUrl}_${locale}_${key}`);
146
+ return encodeURIComponent(
147
+ `${CACHE_VERSION}_${Settings.commerceUrl}_${locale}_${key}`
148
+ );
63
149
  }
64
150
 
65
151
  static clientPool: Pool<RedisClientType> = createPool(
@@ -70,9 +156,14 @@ export class Cache {
70
156
  process.env.CACHE_PORT
71
157
  }/${process.env.CACHE_BUCKET ?? '0'}`;
72
158
 
73
- const client: RedisClientType = createClient({
74
- url: redisUrl
75
- });
159
+ const options = {
160
+ url: redisUrl,
161
+ ...(process.env.CACHE_PASSWORD && {
162
+ password: process.env.CACHE_PASSWORD
163
+ })
164
+ };
165
+
166
+ const client: RedisClientType = createClient(options);
76
167
 
77
168
  client.on('error', (error) => {
78
169
  logger.error('Redis client error', { redisUrl, error });
@@ -96,9 +187,9 @@ export class Cache {
96
187
  return await Cache.clientPool.acquire();
97
188
  }
98
189
 
99
- static async get(key: string) {
100
- let value;
101
- let client;
190
+ static async get(key: string): Promise<any> {
191
+ let value: any;
192
+ let client: RedisClientType | undefined;
102
193
 
103
194
  try {
104
195
  client = await Cache.getClient();
@@ -108,9 +199,7 @@ export class Cache {
108
199
  } else {
109
200
  value = null;
110
201
  }
111
- logger.debug('Redis get success', { key, value });
112
202
  } catch (error) {
113
- logger.error('Redis get error', { key, error });
114
203
  value = null;
115
204
  } finally {
116
205
  if (client) {
@@ -121,14 +210,13 @@ export class Cache {
121
210
  return value;
122
211
  }
123
212
 
124
- static async set(key: string, value: any, expire?: number) {
213
+ static async set(key: string, value: any, expire?: number): Promise<boolean> {
125
214
  let success = false;
126
- let client;
215
+ let client: RedisClientType | undefined;
127
216
 
128
217
  try {
129
218
  client = await Cache.getClient();
130
- const serializedValue =
131
- typeof value === 'object' ? JSON.stringify(value) : value;
219
+ const serializedValue = Cache.serializeValue(value);
132
220
 
133
221
  if (expire) {
134
222
  await client.set(key, serializedValue, { EX: expire });
@@ -137,9 +225,7 @@ export class Cache {
137
225
  }
138
226
 
139
227
  success = true;
140
- logger.debug('Redis set success', { key, value });
141
228
  } catch (error) {
142
- logger.error('Redis set error', { key, error });
143
229
  success = false;
144
230
  } finally {
145
231
  if (client) {
@@ -156,10 +242,6 @@ export class Cache {
156
242
  handler: () => Promise<T>,
157
243
  options?: CacheOptions
158
244
  ): Promise<T> {
159
- if (Settings.usePrettyUrlRoute) {
160
- return await handler();
161
- }
162
-
163
245
  const requiredVariables = [
164
246
  process.env.CACHE_HOST,
165
247
  process.env.CACHE_PORT,
@@ -172,27 +254,33 @@ export class Cache {
172
254
 
173
255
  const defaultOptions: CacheOptions = {
174
256
  cache: true,
175
- expire: Settings.redis.defaultExpirationTime
257
+ expire: Settings.redis.defaultExpirationTime,
258
+ compressed: process.env.CACHE_COMPRESSION_ENABLED !== 'false'
176
259
  };
177
260
 
178
261
  const _options = Object.assign(defaultOptions, options);
179
262
  const formattedKey = Cache.formatKey(key, locale);
180
263
 
181
- logger.debug('Cache wrap', { key, formattedKey, _options });
264
+ if (Settings.usePrettyUrlRoute) {
265
+ _options.expire = 120;
266
+ }
182
267
 
183
268
  if (_options.cache) {
184
- let cachedValue;
269
+ let cachedValue: any;
185
270
 
186
271
  if (_options.useProxy) {
187
272
  const body = new URLSearchParams();
188
273
 
189
274
  body.append('key', formattedKey);
275
+ if (_options.compressed) {
276
+ body.append('compressed', 'true');
277
+ }
190
278
 
191
279
  cachedValue = await Cache.proxyRequest('POST', body);
192
- logger.debug('Cache proxy request success', { key });
193
- logger.trace('Cache proxy request', { key, cachedValue });
194
280
  } else {
195
- cachedValue = await Cache.get(formattedKey);
281
+ cachedValue = _options.compressed
282
+ ? await Cache.getCompressed(formattedKey)
283
+ : await Cache.get(formattedKey);
196
284
  }
197
285
 
198
286
  if (cachedValue) {
@@ -200,8 +288,6 @@ export class Cache {
200
288
  }
201
289
  }
202
290
 
203
- logger.debug('Redis cache miss. Setting new value...', { key });
204
-
205
291
  const data = await handler();
206
292
 
207
293
  if (data && _options.cache) {
@@ -215,14 +301,19 @@ export class Cache {
215
301
  'expire',
216
302
  String(_options?.expire ?? Settings.redis.defaultExpirationTime)
217
303
  );
304
+ if (_options.compressed) {
305
+ body.append('compressed', 'true');
306
+ }
218
307
  await Cache.proxyRequest('PUT', body);
219
-
220
- logger.debug('Cache proxy request', { key, body: body.toString() });
221
308
  } catch (error) {
222
309
  logger.error('Cache proxy error', error);
223
310
  }
224
311
  } else {
225
- await Cache.set(formattedKey, JSON.stringify(data), _options?.expire);
312
+ if (_options.compressed) {
313
+ await Cache.setCompressed(formattedKey, data, _options?.expire);
314
+ } else {
315
+ await Cache.set(formattedKey, JSON.stringify(data), _options?.expire);
316
+ }
226
317
  }
227
318
  }
228
319
 
@@ -234,7 +325,7 @@ export class Cache {
234
325
  await fetch(Cache.PROXY_URL, {
235
326
  method,
236
327
  headers: {
237
- authorization: process.env.CACHE_SECRET
328
+ authorization: process.env.CACHE_SECRET || ''
238
329
  },
239
330
  body
240
331
  })
@@ -242,4 +333,148 @@ export class Cache {
242
333
 
243
334
  return response;
244
335
  }
336
+
337
+ static async mset(
338
+ keyValuePairs: Record<string, any>,
339
+ expire?: number
340
+ ): Promise<boolean> {
341
+ const validation = Cache.validateKeyValuePairs(keyValuePairs);
342
+ if (!validation.isValid) {
343
+ if (validation.invalidKeys.length > 0) {
344
+ logger.error('Invalid keys in mset', {
345
+ invalidKeys: validation.invalidKeys
346
+ });
347
+ } else {
348
+ logger.warn('mset called with empty keyValuePairs');
349
+ }
350
+ return false;
351
+ }
352
+
353
+ let success = false;
354
+ let client: RedisClientType | undefined;
355
+
356
+ try {
357
+ client = await Cache.getClient();
358
+ const pipeline = client.multi();
359
+
360
+ Object.entries(keyValuePairs).forEach(([key, value]) => {
361
+ const serializedValue = Cache.serializeValue(value);
362
+ if (expire) {
363
+ pipeline.set(key, serializedValue, { EX: expire });
364
+ } else {
365
+ pipeline.set(key, serializedValue);
366
+ }
367
+ });
368
+
369
+ const results = await pipeline.exec();
370
+
371
+ const failures =
372
+ results?.filter((result) => result instanceof Error) || [];
373
+
374
+ if (failures.length > 0) {
375
+ success = false;
376
+ } else {
377
+ success = true;
378
+ }
379
+ } catch (error) {
380
+ success = false;
381
+ } finally {
382
+ if (client) {
383
+ await Cache.clientPool.release(client);
384
+ }
385
+ }
386
+
387
+ return success;
388
+ }
389
+
390
+ static async setCompressed(
391
+ key: string,
392
+ value: any,
393
+ expire?: number
394
+ ): Promise<boolean> {
395
+ if (!Cache.validateKey(key)) {
396
+ return false;
397
+ }
398
+
399
+ let success = false;
400
+ let client: RedisClientType | undefined;
401
+
402
+ try {
403
+ client = await Cache.getClient();
404
+ const serializedValue = Cache.serializeValue(value);
405
+
406
+ try {
407
+ const compressed = await compressData(serializedValue);
408
+ const compressedBase64 = Buffer.from(compressed).toString('base64');
409
+
410
+ if (expire) {
411
+ await client.set(key, compressedBase64, { EX: expire });
412
+ } else {
413
+ await client.set(key, compressedBase64);
414
+ }
415
+
416
+ success = true;
417
+ } catch (compressionError) {
418
+ if (expire) {
419
+ await client.set(key, serializedValue, { EX: expire });
420
+ } else {
421
+ await client.set(key, serializedValue);
422
+ }
423
+
424
+ success = true;
425
+ }
426
+ } catch (error) {
427
+ success = false;
428
+ } finally {
429
+ if (client) {
430
+ await Cache.clientPool.release(client);
431
+ }
432
+ }
433
+
434
+ return success;
435
+ }
436
+
437
+ static async getCompressed(key: string): Promise<unknown> {
438
+ if (!Cache.validateKey(key)) {
439
+ return null;
440
+ }
441
+
442
+ let value: unknown;
443
+ let client: RedisClientType | undefined;
444
+
445
+ try {
446
+ client = await Cache.getClient();
447
+ const compressed = await client.get(key);
448
+
449
+ if (compressed) {
450
+ const compressedBuffer = Buffer.from(compressed, 'base64');
451
+
452
+ try {
453
+ const decompressedString = await decompressData(
454
+ new Uint8Array(compressedBuffer)
455
+ );
456
+ value = JSON.parse(decompressedString);
457
+ return value;
458
+ } catch (decompressionError) {
459
+ try {
460
+ const rawString = compressed;
461
+ const parsedData = JSON.parse(rawString);
462
+ return parsedData;
463
+ } catch (jsonError) {
464
+ return null;
465
+ }
466
+ }
467
+ } else {
468
+ value = null;
469
+ }
470
+ } catch (error) {
471
+ value = null;
472
+ } finally {
473
+ if (client) {
474
+ await Cache.clientPool.release(client);
475
+ }
476
+ }
477
+
478
+ return value;
479
+ }
245
480
  }
@@ -1,5 +1,6 @@
1
1
  export enum LocaleUrlStrategy {
2
2
  HideAllLocales = 'hide-all-locales',
3
3
  HideDefaultLocale = 'hide-default-locale',
4
- ShowAllLocales = 'show-all-locales'
4
+ ShowAllLocales = 'show-all-locales',
5
+ Subdomain = 'subdomain'
5
6
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { createContext } from 'react';
4
4
  import { getTranslateFn } from '../utils';
5
- import { useParams } from 'next/navigation';
5
+ import { usePzParams } from '../hooks/use-pz-params';
6
6
  import { Currency, Locale } from '../types';
7
7
  import settings from 'settings';
8
8
 
@@ -34,10 +34,7 @@ export default function LocalizationProvider({
34
34
  localeUrlStrategy
35
35
  } = settings.localization;
36
36
 
37
- const { locale, currency } = useParams() as {
38
- locale: string;
39
- currency: string;
40
- };
37
+ const { locale, currency } = usePzParams();
41
38
 
42
39
  return (
43
40
  <LocalizationContext.Provider
@@ -0,0 +1,18 @@
1
+ import { NextMiddleware } from 'next/server';
2
+
3
+ const withBfcacheHeaders = (middleware: NextMiddleware): NextMiddleware => {
4
+ return async (req, event) => {
5
+ const response = await middleware(req, event);
6
+
7
+ if (process.env.BF_CACHE === 'true' && response) {
8
+ response.headers.set(
9
+ 'Cache-Control',
10
+ 'private, no-cache, max-age=0, must-revalidate'
11
+ );
12
+ }
13
+
14
+ return response;
15
+ };
16
+ };
17
+
18
+ export default withBfcacheHeaders;
@@ -64,7 +64,7 @@ const withCheckoutProvider =
64
64
  const location = request.headers.get('location');
65
65
  const redirectUrl = new URL(
66
66
  request.headers.get('location'),
67
- location.startsWith('http') ? '' : process.env.NEXT_PUBLIC_URL
67
+ location.startsWith('http') ? '' : url.origin
68
68
  );
69
69
 
70
70
  redirectUrl.pathname = getUrlPathWithLocale(
@@ -4,6 +4,7 @@ import { Buffer } from 'buffer';
4
4
  import logger from '../utils/log';
5
5
  import { getUrlPathWithLocale } from '../utils/localization';
6
6
  import { PzNextRequest } from '.';
7
+ import { getCheckoutPath } from '../utils';
7
8
 
8
9
  const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
9
10
  if (stream) {
@@ -34,18 +35,22 @@ const withCompleteGpay =
34
35
  const url = req.nextUrl.clone();
35
36
  const ip = req.headers.get('x-forwarded-for') ?? '';
36
37
  const sessionId = req.cookies.get('osessionid');
38
+ const currentLocale = req.middlewareParams?.rewrites?.locale;
37
39
 
38
40
  if (url.search.indexOf('GPayCompletePage') === -1) {
39
41
  return middleware(req, event);
40
42
  }
41
43
 
42
- const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
44
+ const isPostCheckout = req.cookies.get('pz-post-checkout-flow')?.value === 'true';
45
+ const requestUrl = `${Settings.commerceUrl}${getCheckoutPath(isPostCheckout)}${url.search}`;
43
46
  const requestHeaders = {
44
47
  'X-Requested-With': 'XMLHttpRequest',
45
48
  'Content-Type': 'application/x-www-form-urlencoded',
46
49
  Cookie: req.headers.get('cookie') ?? '',
47
50
  'x-currency': req.cookies.get('pz-currency')?.value ?? '',
48
- 'x-forwarded-for': ip
51
+ 'x-forwarded-for': ip,
52
+ 'Accept-Language':
53
+ currentLocale ?? req.cookies.get('pz-locale')?.value ?? ''
49
54
  };
50
55
 
51
56
  try {
@@ -59,17 +64,9 @@ const withCompleteGpay =
59
64
  ip
60
65
  }
61
66
  );
62
-
63
- return NextResponse.redirect(
64
- `${url.origin}${getUrlPathWithLocale(
65
- '/orders/checkout/',
66
- req.cookies.get('pz-locale')?.value
67
- )}`,
68
- 303
69
- );
70
67
  }
71
68
 
72
- const request = await fetch(requestUrl, {
69
+ const fetchResponse = await fetch(requestUrl, {
73
70
  method: 'POST',
74
71
  headers: requestHeaders,
75
72
  body
@@ -77,14 +74,14 @@ const withCompleteGpay =
77
74
 
78
75
  logger.info('Complete GPay payment request', {
79
76
  requestUrl,
80
- status: request.status,
77
+ status: fetchResponse.status,
81
78
  requestHeaders,
82
79
  ip
83
80
  });
84
81
 
85
- const response = await request.json();
82
+ const responseData = await fetchResponse.json();
86
83
 
87
- const { context_list: contextList, errors } = response;
84
+ const { context_list: contextList, errors } = responseData;
88
85
  const redirectionContext = contextList?.find(
89
86
  (context) => context.page_context?.redirect_url
90
87
  );
@@ -98,18 +95,26 @@ const withCompleteGpay =
98
95
  ip
99
96
  });
100
97
 
101
- return NextResponse.redirect(
98
+ const errorResponse = NextResponse.redirect(
102
99
  `${url.origin}${getUrlPathWithLocale(
103
100
  '/orders/checkout/',
104
101
  req.cookies.get('pz-locale')?.value
105
102
  )}`,
106
- {
107
- status: 303,
108
- headers: {
109
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
110
- }
111
- }
103
+ 303
112
104
  );
105
+
106
+ // Forward set-cookie headers from the upstream response
107
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
108
+ setCookies.forEach((cookie) => {
109
+ errorResponse.headers.append('Set-Cookie', cookie);
110
+ });
111
+
112
+ // Add error cookie
113
+ errorResponse.cookies.set('pz-pos-error', JSON.stringify(errors), {
114
+ path: '/'
115
+ });
116
+
117
+ return errorResponse;
113
118
  }
114
119
 
115
120
  logger.info('Order success page context list', {
@@ -124,7 +129,7 @@ const withCompleteGpay =
124
129
  {
125
130
  middleware: 'complete-gpay',
126
131
  requestHeaders,
127
- response: JSON.stringify(response),
132
+ response: JSON.stringify(responseData),
128
133
  ip
129
134
  }
130
135
  );
@@ -152,10 +157,11 @@ const withCompleteGpay =
152
157
  // So we use 303 status code to change the method to GET
153
158
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
154
159
 
155
- nextResponse.headers.set(
156
- 'Set-Cookie',
157
- request.headers.get('set-cookie') ?? ''
158
- );
160
+ // Forward all set-cookie headers from the upstream response
161
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
162
+ setCookies.forEach((cookie) => {
163
+ nextResponse.headers.append('Set-Cookie', cookie);
164
+ });
159
165
 
160
166
  return nextResponse;
161
167
  } catch (error) {