@akinon/next 1.110.0-snapshot-ZERO-3792-20251107013328 → 1.110.0-snapshot-ZERO-3873-20251201162154

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,35 @@
1
1
  # @akinon/next
2
2
 
3
- ## 1.110.0-snapshot-ZERO-3792-20251107013328
3
+ ## 1.110.0-snapshot-ZERO-3873-20251201162154
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - 888fdec: ZERO-3792: Virtual Try On new features are implemented and also basket support implemented.
7
+ - fc752c98: ZERO-3795: Remove unnecessary redirection logic from payment middleware
8
+ - 7a5095ac: ZERO-3873: Refactor redirection payment middleware to improve error handling and cookie management
9
+ - d2c0e759: ZERO-3684: Handle cross-origin iframe access in iframeURLChange function
10
+ - b55acb76: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
11
+ - 143be2b9: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
12
+ - 9f8cd3bc: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
13
+ - 757af4ad: ZERO-3783: Update image remote patterns for security compliance
14
+ - d99a6a7d: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
15
+ - 4de5303c: ZERO-2504: add cookie filter to api client request
16
+
17
+ ## 1.110.0-rc.13
18
+
19
+ ### Minor Changes
20
+
21
+ - 757af4ad: ZERO-3783: Update image remote patterns for security compliance
22
+
23
+ ## 1.110.0-rc.12
24
+
25
+ ### Minor Changes
26
+
27
+ - d2c0e759: ZERO-3684: Handle cross-origin iframe access in iframeURLChange function
28
+ - b55acb76: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
29
+ - 143be2b9: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
30
+ - 9f8cd3bc: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
31
+ - d99a6a7d: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
32
+ - 4de5303c: ZERO-2504: add cookie filter to api client request
8
33
 
9
34
  ## 1.109.0
10
35
 
@@ -0,0 +1,75 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ async function proxyImage(imageUrl: string) {
4
+ if (!imageUrl) {
5
+ return NextResponse.json(
6
+ { success: false, error: 'Missing url parameter' },
7
+ { status: 400 }
8
+ );
9
+ }
10
+
11
+ const imageResponse = await fetch(imageUrl);
12
+
13
+ if (!imageResponse.ok) {
14
+ return NextResponse.json(
15
+ { success: false, error: 'Failed to fetch image from URL' },
16
+ { status: 400 }
17
+ );
18
+ }
19
+
20
+ const imageBuffer = await imageResponse.arrayBuffer();
21
+ const base64Image = Buffer.from(imageBuffer).toString('base64');
22
+ const contentType = imageResponse.headers.get('content-type') || 'image/jpeg';
23
+
24
+ return { base64Image, contentType, imageBuffer };
25
+ }
26
+
27
+ export async function GET(request: NextRequest) {
28
+ try {
29
+ const { searchParams } = new URL(request.url);
30
+ const imageUrl = searchParams.get('url');
31
+
32
+ const result = await proxyImage(imageUrl!);
33
+ if (result instanceof NextResponse) {
34
+ return result;
35
+ }
36
+
37
+ return new NextResponse(result.imageBuffer, {
38
+ status: 200,
39
+ headers: {
40
+ 'Content-Type': result.contentType,
41
+ 'Access-Control-Allow-Origin': '*',
42
+ 'Cache-Control': 'public, max-age=3600'
43
+ }
44
+ });
45
+ } catch (error) {
46
+ console.error('Image proxy error:', error);
47
+ return NextResponse.json(
48
+ { success: false, error: 'Internal server error' },
49
+ { status: 500 }
50
+ );
51
+ }
52
+ }
53
+
54
+ export async function POST(request: NextRequest) {
55
+ try {
56
+ const body = await request.json();
57
+ const imageUrl = body.imageUrl;
58
+
59
+ const result = await proxyImage(imageUrl);
60
+ if (result instanceof NextResponse) {
61
+ return result;
62
+ }
63
+
64
+ return NextResponse.json({
65
+ success: true,
66
+ base64Image: `data:${result.contentType};base64,${result.base64Image}`
67
+ });
68
+ } catch (error) {
69
+ console.error('Image proxy POST error:', error);
70
+ return NextResponse.json(
71
+ { success: false, error: 'Internal server error' },
72
+ { status: 500 }
73
+ );
74
+ }
75
+ }
@@ -0,0 +1,63 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+
4
+ export async function GET(request: NextRequest) {
5
+ try {
6
+ const { searchParams } = new URL(request.url);
7
+ const dynamicFilter = request.headers.get('x-search-dynamic-filter');
8
+ const dynamicExclude = request.headers.get('x-search-dynamic-exclude');
9
+
10
+ if (!dynamicFilter && !dynamicExclude) {
11
+ return NextResponse.json(
12
+ {
13
+ error:
14
+ 'Missing x-search-dynamic-filter or x-search-dynamic-exclude header'
15
+ },
16
+ { status: 400 }
17
+ );
18
+ }
19
+
20
+ if (Settings.commerceUrl === 'default') {
21
+ return NextResponse.json(
22
+ { error: 'Commerce URL is not configured' },
23
+ { status: 500 }
24
+ );
25
+ }
26
+
27
+ const queryString = searchParams.toString();
28
+ const apiUrl = `${Settings.commerceUrl}/list${
29
+ queryString ? `?${queryString}` : ''
30
+ }`;
31
+
32
+ const headers: Record<string, string> = {
33
+ Accept: 'application/json',
34
+ 'Content-Type': 'application/json'
35
+ };
36
+
37
+ if (dynamicFilter) {
38
+ headers['x-search-dynamic-filter'] = dynamicFilter;
39
+ } else if (dynamicExclude) {
40
+ headers['x-search-dynamic-exclude'] = dynamicExclude;
41
+ }
42
+
43
+ const response = await fetch(apiUrl, {
44
+ method: 'GET',
45
+ headers
46
+ });
47
+
48
+ if (!response.ok) {
49
+ return NextResponse.json(
50
+ { error: `API request failed with status: ${response.status}` },
51
+ { status: response.status }
52
+ );
53
+ }
54
+
55
+ const data = await response.json();
56
+ return NextResponse.json(data);
57
+ } catch (error) {
58
+ return NextResponse.json(
59
+ { error: (error as Error).message },
60
+ { status: 500 }
61
+ );
62
+ }
63
+ }
@@ -0,0 +1,111 @@
1
+ import { NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+
4
+ const IMAGE_SEARCH_API_URL = Settings.commerceUrl + '/image-search/';
5
+
6
+ const errorResponse = (message: string, status: number) => {
7
+ return NextResponse.json({ error: message }, { status });
8
+ };
9
+
10
+ export async function GET(request: Request) {
11
+ const { searchParams } = new URL(request.url);
12
+ const limit = searchParams.get('limit') || '20';
13
+ const url = searchParams.get('url');
14
+ const excludedProductIds = searchParams.get('excluded_product_ids');
15
+
16
+ if (!url) {
17
+ return errorResponse('URL parameter is required', 400);
18
+ }
19
+
20
+ if (Settings.commerceUrl === 'default') {
21
+ return errorResponse('Commerce URL is not configured', 500);
22
+ }
23
+
24
+ const apiParams = new URLSearchParams();
25
+ apiParams.append('limit', limit);
26
+ apiParams.append('url', url);
27
+
28
+ if (excludedProductIds) {
29
+ apiParams.append('excluded_product_ids', excludedProductIds);
30
+ }
31
+
32
+ const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
33
+
34
+ try {
35
+ const response = await fetch(apiUrl, {
36
+ method: 'GET',
37
+ headers: {
38
+ Accept: 'application/json'
39
+ }
40
+ });
41
+
42
+ if (!response.ok) {
43
+ const errorText = await response.text();
44
+ return errorResponse(
45
+ errorText || `API request failed with status: ${response.status}`,
46
+ response.status
47
+ );
48
+ }
49
+
50
+ const responseText = await response.text();
51
+
52
+ return NextResponse.json(JSON.parse(responseText));
53
+ } catch (error) {
54
+ return errorResponse((error as Error).message, 500);
55
+ }
56
+ }
57
+
58
+ export async function POST(request: Request) {
59
+ const { searchParams } = new URL(request.url);
60
+ const limit = searchParams.get('limit') || '20';
61
+
62
+ if (Settings.commerceUrl === 'default') {
63
+ return errorResponse('Commerce URL is not configured', 500);
64
+ }
65
+
66
+ let requestBody;
67
+ try {
68
+ requestBody = await request.json();
69
+ } catch (error) {
70
+ return errorResponse('Invalid JSON in request body', 400);
71
+ }
72
+
73
+ if (!requestBody.image) {
74
+ return errorResponse('Image data is required in request body', 400);
75
+ }
76
+
77
+ const apiParams = new URLSearchParams();
78
+ apiParams.append('limit', limit);
79
+
80
+ const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
81
+
82
+ const bodyData: any = { image: requestBody.image };
83
+
84
+ if (requestBody.excluded_product_ids) {
85
+ bodyData.excluded_product_ids = requestBody.excluded_product_ids;
86
+ }
87
+
88
+ try {
89
+ const response = await fetch(apiUrl, {
90
+ method: 'POST',
91
+ headers: {
92
+ 'Content-Type': 'application/json',
93
+ Accept: 'application/json'
94
+ },
95
+ body: JSON.stringify(bodyData)
96
+ });
97
+
98
+ if (!response.ok) {
99
+ const errorText = await response.text();
100
+ return errorResponse(
101
+ errorText || `API request failed with status: ${response.status}`,
102
+ response.status
103
+ );
104
+ }
105
+
106
+ const responseData = await response.json();
107
+ return NextResponse.json(responseData);
108
+ } catch (error) {
109
+ return errorResponse((error as Error).message, 500);
110
+ }
111
+ }
@@ -122,55 +122,6 @@ export async function GET(request: NextRequest) {
122
122
  'X-Virtual-Try-On-Cache-Status': 'MISS'
123
123
  }
124
124
  });
125
- } else if (endpoint === 'job-status') {
126
- const referenceUrl = searchParams.get('reference_url');
127
- if (!referenceUrl) {
128
- return NextResponse.json(
129
- { error: 'reference_url is required' },
130
- { status: 400 }
131
- );
132
- }
133
-
134
- const externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/async/v1/job-status?reference_url=${encodeURIComponent(referenceUrl)}`;
135
- const clientIP = getClientIP(request);
136
- const locale = getLocaleFromRequest(request);
137
-
138
- const headersToSend = {
139
- Accept: 'application/json',
140
- ...(locale && { 'Accept-Language': locale }),
141
- ...(request.headers.get('authorization') && {
142
- Authorization: request.headers.get('authorization')!
143
- }),
144
- ...(clientIP && {
145
- 'X-Forwarded-For': clientIP
146
- })
147
- };
148
-
149
- const response = await fetch(externalUrl, {
150
- method: 'GET',
151
- headers: headersToSend
152
- });
153
-
154
- let responseData: any;
155
- const responseText = await response.text();
156
-
157
- try {
158
- responseData = responseText ? JSON.parse(responseText) : {};
159
- } catch (parseError) {
160
- return NextResponse.json(
161
- { error: 'Invalid JSON response from job status API' },
162
- { status: 500 }
163
- );
164
- }
165
-
166
- return NextResponse.json(responseData, {
167
- status: response.status,
168
- headers: {
169
- 'Access-Control-Allow-Origin': '*',
170
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS',
171
- 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization'
172
- }
173
- });
174
125
  }
175
126
 
176
127
  return NextResponse.json(
@@ -190,32 +141,13 @@ export async function POST(request: NextRequest) {
190
141
  const body = await request.json();
191
142
 
192
143
  let externalUrl: string;
193
- let httpMethod = 'POST';
194
-
195
144
  if (endpoint === 'feedback') {
196
- if (!body.url || typeof body.url !== 'string' || !body.url.trim()) {
197
- return NextResponse.json(
198
- { status: 'error', message: 'URL is required for feedback' },
199
- { status: 400 }
200
- );
201
- }
202
- if (typeof body.feedback !== 'boolean') {
203
- return NextResponse.json(
204
- { status: 'error', message: 'Feedback must be a boolean value' },
205
- { status: 400 }
206
- );
207
- }
208
145
  externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/v1/feedback`;
209
- httpMethod = 'PUT';
210
- } else if (endpoint === 'async-multiple-try-on') {
211
- externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/async/v1/multiple-virtual-try-on`;
212
146
  } else {
213
- return NextResponse.json(
214
- { error: 'Invalid endpoint specified' },
215
- { status: 400 }
216
- );
147
+ externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/v1/virtual-try-on`;
217
148
  }
218
149
 
150
+ const httpMethod = endpoint === 'feedback' ? 'PUT' : 'POST';
219
151
  const clientIP = getClientIP(request);
220
152
  const locale = getLocaleFromRequest(request);
221
153
 
@@ -231,18 +163,13 @@ export async function POST(request: NextRequest) {
231
163
  })
232
164
  };
233
165
 
234
- const fetchOptions: RequestInit = {
166
+ const response = await fetch(externalUrl, {
235
167
  method: httpMethod,
236
- headers: headersToSend
237
- };
238
-
239
- if (httpMethod !== 'GET') {
240
- fetchOptions.body = JSON.stringify(body);
241
- }
242
-
243
- const response = await fetch(externalUrl, fetchOptions);
168
+ headers: headersToSend,
169
+ body: JSON.stringify(body)
170
+ });
244
171
 
245
- let responseData: Record<string, any>;
172
+ let responseData: any;
246
173
  const responseText = await response.text();
247
174
 
248
175
  if (endpoint === 'feedback') {
@@ -346,7 +273,7 @@ export async function PUT(request: NextRequest) {
346
273
  body: JSON.stringify(body)
347
274
  });
348
275
 
349
- const responseData = response?.json() || {};
276
+ const responseData = await response.json();
350
277
 
351
278
  return NextResponse.json(responseData, {
352
279
  status: response.status,
@@ -360,8 +287,7 @@ export async function PUT(request: NextRequest) {
360
287
  return NextResponse.json(
361
288
  {
362
289
  status: 'error',
363
- message: 'Internal server error occurred during feedback submission',
364
- error: (error as Error).message
290
+ message: 'Internal server error occurred during feedback submission'
365
291
  },
366
292
  { status: 500 }
367
293
  );
@@ -23,7 +23,8 @@ enum Plugin {
23
23
  SavedCard = 'pz-saved-card',
24
24
  FlowPayment = 'pz-flow-payment',
25
25
  VirtualTryOn = 'pz-virtual-try-on',
26
- Hepsipay = 'pz-hepsipay'
26
+ Hepsipay = 'pz-hepsipay',
27
+ SimilarProducts = 'pz-similar-products'
27
28
  }
28
29
 
29
30
  export enum Component {
@@ -50,7 +51,13 @@ export enum Component {
50
51
  MultiBasket = 'MultiBasket',
51
52
  SavedCard = 'SavedCardOption',
52
53
  VirtualTryOnPlugin = 'VirtualTryOnPlugin',
53
- BasketVirtualTryOn = 'BasketVirtualTryOn',
54
+ SimilarProductsModal = 'SimilarProductsModal',
55
+ SimilarProductsFilterSidebar = 'SimilarProductsFilterSidebar',
56
+ SimilarProductsResultsGrid = 'SimilarProductsResultsGrid',
57
+ SimilarProductsPlugin = 'SimilarProductsPlugin',
58
+ ProductImageSearchFeature = 'ProductImageSearchFeature',
59
+ ImageSearchButton = 'ImageSearchButton',
60
+ HeaderImageSearchFeature = 'HeaderImageSearchFeature',
54
61
  IyzicoSavedCard = 'IyzicoSavedCardOption',
55
62
  Hepsipay = 'Hepsipay',
56
63
  FlowPayment = 'FlowPayment'
@@ -86,13 +93,23 @@ const PluginComponents = new Map([
86
93
  [Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
87
94
  ],
88
95
  [Plugin.MultiBasket, [Component.MultiBasket]],
89
- [Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
90
96
  [Plugin.SavedCard, [Component.SavedCard]],
91
- [Plugin.FlowPayment, [Component.FlowPayment]],
92
97
  [
93
- Plugin.VirtualTryOn,
94
- [Component.VirtualTryOnPlugin, Component.BasketVirtualTryOn]
98
+ Plugin.SimilarProducts,
99
+ [
100
+ Component.SimilarProductsModal,
101
+ Component.SimilarProductsFilterSidebar,
102
+ Component.SimilarProductsResultsGrid,
103
+ Component.SimilarProductsPlugin,
104
+ Component.ProductImageSearchFeature,
105
+ Component.ImageSearchButton,
106
+ Component.HeaderImageSearchFeature
107
+ ]
95
108
  ],
109
+ [Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
110
+ [Plugin.SavedCard, [Component.SavedCard]],
111
+ [Plugin.FlowPayment, [Component.FlowPayment]],
112
+ [Plugin.VirtualTryOn, [Component.VirtualTryOnPlugin]],
96
113
  [Plugin.Hepsipay, [Component.Hepsipay]]
97
114
  ]);
98
115
 
@@ -158,6 +175,8 @@ export default function PluginModule({
158
175
  promise = import(`${'@akinon/pz-multi-basket'}`);
159
176
  } else if (plugin === Plugin.SavedCard) {
160
177
  promise = import(`${'@akinon/pz-saved-card'}`);
178
+ } else if (plugin === Plugin.SimilarProducts) {
179
+ promise = import(`${'@akinon/pz-similar-products'}`);
161
180
  } else if (plugin === Plugin.Hepsipay) {
162
181
  promise = import(`${'@akinon/pz-hepsipay'}`);
163
182
  } else if (plugin === Plugin.FlowPayment) {
package/data/urls.ts CHANGED
@@ -183,7 +183,11 @@ export const product = {
183
183
  breadcrumbUrl: (menuitemmodel: string) =>
184
184
  `/menus/generate_breadcrumb/?item=${menuitemmodel}&generator_name=menu_item`,
185
185
  bundleProduct: (productPk: string, queryString: string) =>
186
- `/bundle-product/${productPk}/?${queryString}`
186
+ `/bundle-product/${productPk}/?${queryString}`,
187
+ similarProducts: (params?: string) =>
188
+ `/similar-products${params ? `?${params}` : ''}`,
189
+ similarProductsList: (params?: string) =>
190
+ `/similar-product-list${params ? `?${params}` : ''}`
187
191
  };
188
192
 
189
193
  export const wishlist = {
@@ -62,14 +62,6 @@ const withCompleteGpay =
62
62
  ip
63
63
  }
64
64
  );
65
-
66
- return NextResponse.redirect(
67
- `${url.origin}${getUrlPathWithLocale(
68
- '/orders/checkout/',
69
- req.cookies.get('pz-locale')?.value
70
- )}`,
71
- 303
72
- );
73
65
  }
74
66
 
75
67
  const request = await fetch(requestUrl, {
@@ -63,14 +63,6 @@ const withCompleteMasterpass =
63
63
  ip
64
64
  }
65
65
  );
66
-
67
- return NextResponse.redirect(
68
- `${url.origin}${getUrlPathWithLocale(
69
- '/orders/checkout/',
70
- req.cookies.get('pz-locale')?.value
71
- )}`,
72
- 303
73
- );
74
66
  }
75
67
 
76
68
  const request = await fetch(requestUrl, {
@@ -59,14 +59,6 @@ const withCompleteWallet =
59
59
  ip
60
60
  }
61
61
  );
62
-
63
- return NextResponse.redirect(
64
- `${url.origin}${getUrlPathWithLocale(
65
- '/orders/checkout/',
66
- req.cookies.get('pz-locale')?.value
67
- )}`,
68
- 303
69
- );
70
62
  }
71
63
 
72
64
  const request = await fetch(requestUrl, {
@@ -63,17 +63,9 @@ const withRedirectionPayment =
63
63
  ip
64
64
  }
65
65
  );
66
-
67
- return NextResponse.redirect(
68
- `${url.origin}${getUrlPathWithLocale(
69
- '/orders/checkout/',
70
- req.cookies.get('pz-locale')?.value
71
- )}`,
72
- 303
73
- );
74
66
  }
75
67
 
76
- const request = await fetch(requestUrl, {
68
+ const fetchResponse = await fetch(requestUrl, {
77
69
  method: 'POST',
78
70
  headers: requestHeaders,
79
71
  body
@@ -81,14 +73,14 @@ const withRedirectionPayment =
81
73
 
82
74
  logger.info('Complete redirection payment request', {
83
75
  requestUrl,
84
- status: request.status,
76
+ status: fetchResponse.status,
85
77
  requestHeaders,
86
78
  ip
87
79
  });
88
80
 
89
- const response = await request.json();
81
+ const responseData = await fetchResponse.json();
90
82
 
91
- const { context_list: contextList, errors } = response;
83
+ const { context_list: contextList, errors } = responseData;
92
84
  const redirectionContext = contextList?.find(
93
85
  (context) => context.page_context?.redirect_url
94
86
  );
@@ -102,18 +94,27 @@ const withRedirectionPayment =
102
94
  ip
103
95
  });
104
96
 
105
- return NextResponse.redirect(
97
+ const errorResponse = NextResponse.redirect(
106
98
  `${url.origin}${getUrlPathWithLocale(
107
99
  '/orders/checkout/',
108
100
  req.cookies.get('pz-locale')?.value
109
101
  )}`,
110
- {
111
- status: 303,
112
- headers: {
113
- 'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
114
- }
115
- }
102
+ 303
103
+ );
104
+
105
+ // Forward set-cookie headers from the upstream response
106
+ const setCookies = fetchResponse.headers.getSetCookie?.() ?? [];
107
+ setCookies.forEach((cookie) => {
108
+ errorResponse.headers.append('Set-Cookie', cookie);
109
+ });
110
+
111
+ // Add error cookie
112
+ errorResponse.headers.append(
113
+ 'Set-Cookie',
114
+ `pz-pos-error=${JSON.stringify(errors)}; path=/;`
116
115
  );
116
+
117
+ return errorResponse;
117
118
  }
118
119
 
119
120
  logger.info('Order success page context list', {
@@ -128,7 +129,7 @@ const withRedirectionPayment =
128
129
  {
129
130
  middleware: 'redirection-payment',
130
131
  requestHeaders,
131
- response: JSON.stringify(response),
132
+ response: JSON.stringify(responseData),
132
133
  ip
133
134
  }
134
135
  );
@@ -156,10 +157,11 @@ const withRedirectionPayment =
156
157
  // So we use 303 status code to change the method to GET
157
158
  const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
158
159
 
159
- nextResponse.headers.set(
160
- 'Set-Cookie',
161
- request.headers.get('set-cookie') ?? ''
162
- );
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
+ });
163
165
 
164
166
  return nextResponse;
165
167
  } catch (error) {
@@ -63,14 +63,6 @@ const withSavedCardRedirection =
63
63
  ip
64
64
  }
65
65
  );
66
-
67
- return NextResponse.redirect(
68
- `${url.origin}${getUrlPathWithLocale(
69
- '/orders/checkout/',
70
- req.cookies.get('pz-locale')?.value
71
- )}`,
72
- 303
73
- );
74
66
  }
75
67
 
76
68
  const request = await fetch(requestUrl, {
@@ -62,14 +62,6 @@ const withThreeDRedirection =
62
62
  ip
63
63
  }
64
64
  );
65
-
66
- return NextResponse.redirect(
67
- `${url.origin}${getUrlPathWithLocale(
68
- '/orders/checkout/',
69
- req.cookies.get('pz-locale')?.value
70
- )}`,
71
- 303
72
- );
73
65
  }
74
66
 
75
67
  const request = await fetch(requestUrl, {
@@ -83,14 +83,6 @@ const withWalletCompleteRedirection =
83
83
  ip
84
84
  }
85
85
  );
86
-
87
- return NextResponse.redirect(
88
- `${url.origin}${getUrlPathWithLocale(
89
- '/orders/checkout/',
90
- req.cookies.get('pz-locale')?.value
91
- )}`,
92
- 303
93
- );
94
86
  }
95
87
 
96
88
  const request = await fetch(requestUrl, {
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.110.0-snapshot-ZERO-3792-20251107013328",
4
+ "version": "1.110.0-snapshot-ZERO-3873-20251201162154",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "set-cookie-parser": "2.6.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@akinon/eslint-plugin-projectzero": "1.110.0-snapshot-ZERO-3792-20251107013328",
38
+ "@akinon/eslint-plugin-projectzero": "1.110.0-snapshot-ZERO-3873-20251201162154",
39
39
  "@babel/core": "7.26.10",
40
40
  "@babel/preset-env": "7.26.9",
41
41
  "@babel/preset-typescript": "7.27.0",
package/plugins.d.ts CHANGED
@@ -37,6 +37,16 @@ declare module '@akinon/pz-cybersource-uc/src/redux/middleware' {
37
37
  export default middleware as any;
38
38
  }
39
39
 
40
+ declare module '@akinon/pz-apple-pay' {}
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
+ }
49
+
40
50
  declare module '@akinon/pz-cybersource-uc/src/redux/reducer' {
41
51
  export default reducer as any;
42
52
  }
package/plugins.js CHANGED
@@ -16,6 +16,7 @@ module.exports = [
16
16
  'pz-tabby-extension',
17
17
  'pz-apple-pay',
18
18
  'pz-tamara-extension',
19
+ 'pz-similar-products',
19
20
  'pz-cybersource-uc',
20
21
  'pz-hepsipay',
21
22
  'pz-flow-payment',
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
  };
@@ -1,8 +1,14 @@
1
1
  const iframeURLChange = (iframe, callback) => {
2
2
  iframe.addEventListener('load', () => {
3
3
  setTimeout(() => {
4
- if (iframe?.contentWindow?.location) {
5
- callback(iframe.contentWindow.location);
4
+ try {
5
+ if (iframe?.contentWindow?.location) {
6
+ const iframeLocation = iframe.contentWindow.location;
7
+
8
+ callback(iframeLocation);
9
+ }
10
+ } catch (error) {
11
+ // Expected: browser blocks cross-origin iframe access for security
6
12
  }
7
13
  }, 0);
8
14
  });
@@ -1,8 +1,14 @@
1
1
  const iframeURLChange = (iframe, callback) => {
2
2
  iframe.addEventListener('load', () => {
3
3
  setTimeout(() => {
4
- if (iframe?.contentWindow?.location) {
5
- callback(iframe.contentWindow.location);
4
+ try {
5
+ if (iframe?.contentWindow?.location) {
6
+ const iframeLocation = iframe.contentWindow.location;
7
+
8
+ callback(iframeLocation);
9
+ }
10
+ } catch (error) {
11
+ // Expected: browser blocks cross-origin iframe access for security
6
12
  }
7
13
  }, 0);
8
14
  });
package/with-pz-config.js CHANGED
@@ -14,10 +14,15 @@ const defaultConfig = {
14
14
  },
15
15
  images: {
16
16
  remotePatterns: [
17
+ //It has to be like this for security reasons
17
18
  {
18
19
  protocol: 'https',
19
- hostname: '**'
20
+ hostname: '**.akinoncloud.com'
20
21
  },
22
+ {
23
+ protocol: 'https',
24
+ hostname: '**.akinoncdn.com'
25
+ }
21
26
  ]
22
27
  },
23
28
  modularizeImports: {