@akinon/next 1.111.0-rc.16 → 1.111.0-snapshot-ZERO-3792-20251107072714
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 +2 -8
- package/api/virtual-try-on.ts +83 -9
- package/components/plugin-module.tsx +7 -24
- package/data/urls.ts +1 -5
- package/package.json +2 -2
- package/plugins.d.ts +0 -10
- package/plugins.js +0 -1
- package/types/index.ts +0 -6
- package/utils/mobile-3d-iframe.ts +2 -8
- package/utils/redirection-iframe.ts +2 -8
- package/api/image-proxy.ts +0 -75
- package/api/similar-product-list.ts +0 -63
- package/api/similar-products.ts +0 -111
package/CHANGELOG.md
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
-
## 1.111.0-
|
|
3
|
+
## 1.111.0-snapshot-ZERO-3792-20251107072714
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- b55acb76: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
|
|
9
|
-
- 143be2b: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
|
|
10
|
-
- 9f8cd3bc: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
|
|
11
|
-
- d99a6a7: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
|
|
12
|
-
- 4de5303c: ZERO-2504: add cookie filter to api client request
|
|
13
|
-
- 95b139d: ZERO-3795: Remove duplicate entry for SavedCard in PluginComponents map
|
|
7
|
+
- 888fdec: ZERO-3792: Virtual Try On new features are implemented and also basket support implemented.
|
|
14
8
|
|
|
15
9
|
## 1.110.0
|
|
16
10
|
|
package/api/virtual-try-on.ts
CHANGED
|
@@ -122,6 +122,55 @@ 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
|
+
});
|
|
125
174
|
}
|
|
126
175
|
|
|
127
176
|
return NextResponse.json(
|
|
@@ -141,13 +190,32 @@ export async function POST(request: NextRequest) {
|
|
|
141
190
|
const body = await request.json();
|
|
142
191
|
|
|
143
192
|
let externalUrl: string;
|
|
193
|
+
let httpMethod = 'POST';
|
|
194
|
+
|
|
144
195
|
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
|
+
}
|
|
145
208
|
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`;
|
|
146
212
|
} else {
|
|
147
|
-
|
|
213
|
+
return NextResponse.json(
|
|
214
|
+
{ error: 'Invalid endpoint specified' },
|
|
215
|
+
{ status: 400 }
|
|
216
|
+
);
|
|
148
217
|
}
|
|
149
218
|
|
|
150
|
-
const httpMethod = endpoint === 'feedback' ? 'PUT' : 'POST';
|
|
151
219
|
const clientIP = getClientIP(request);
|
|
152
220
|
const locale = getLocaleFromRequest(request);
|
|
153
221
|
|
|
@@ -163,13 +231,18 @@ export async function POST(request: NextRequest) {
|
|
|
163
231
|
})
|
|
164
232
|
};
|
|
165
233
|
|
|
166
|
-
const
|
|
234
|
+
const fetchOptions: RequestInit = {
|
|
167
235
|
method: httpMethod,
|
|
168
|
-
headers: headersToSend
|
|
169
|
-
|
|
170
|
-
|
|
236
|
+
headers: headersToSend
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
if (httpMethod !== 'GET') {
|
|
240
|
+
fetchOptions.body = JSON.stringify(body);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const response = await fetch(externalUrl, fetchOptions);
|
|
171
244
|
|
|
172
|
-
let responseData: any
|
|
245
|
+
let responseData: Record<string, any>;
|
|
173
246
|
const responseText = await response.text();
|
|
174
247
|
|
|
175
248
|
if (endpoint === 'feedback') {
|
|
@@ -273,7 +346,7 @@ export async function PUT(request: NextRequest) {
|
|
|
273
346
|
body: JSON.stringify(body)
|
|
274
347
|
});
|
|
275
348
|
|
|
276
|
-
const responseData =
|
|
349
|
+
const responseData = response?.json() || {};
|
|
277
350
|
|
|
278
351
|
return NextResponse.json(responseData, {
|
|
279
352
|
status: response.status,
|
|
@@ -287,7 +360,8 @@ export async function PUT(request: NextRequest) {
|
|
|
287
360
|
return NextResponse.json(
|
|
288
361
|
{
|
|
289
362
|
status: 'error',
|
|
290
|
-
message: 'Internal server error occurred during feedback submission'
|
|
363
|
+
message: 'Internal server error occurred during feedback submission',
|
|
364
|
+
error: (error as Error).message
|
|
291
365
|
},
|
|
292
366
|
{ status: 500 }
|
|
293
367
|
);
|
|
@@ -23,8 +23,7 @@ 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'
|
|
27
|
-
SimilarProducts = 'pz-similar-products'
|
|
26
|
+
Hepsipay = 'pz-hepsipay'
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
export enum Component {
|
|
@@ -51,13 +50,7 @@ export enum Component {
|
|
|
51
50
|
MultiBasket = 'MultiBasket',
|
|
52
51
|
SavedCard = 'SavedCardOption',
|
|
53
52
|
VirtualTryOnPlugin = 'VirtualTryOnPlugin',
|
|
54
|
-
|
|
55
|
-
SimilarProductsFilterSidebar = 'SimilarProductsFilterSidebar',
|
|
56
|
-
SimilarProductsResultsGrid = 'SimilarProductsResultsGrid',
|
|
57
|
-
SimilarProductsPlugin = 'SimilarProductsPlugin',
|
|
58
|
-
ProductImageSearchFeature = 'ProductImageSearchFeature',
|
|
59
|
-
ImageSearchButton = 'ImageSearchButton',
|
|
60
|
-
HeaderImageSearchFeature = 'HeaderImageSearchFeature',
|
|
53
|
+
BasketVirtualTryOn = 'BasketVirtualTryOn',
|
|
61
54
|
IyzicoSavedCard = 'IyzicoSavedCardOption',
|
|
62
55
|
Hepsipay = 'Hepsipay',
|
|
63
56
|
FlowPayment = 'FlowPayment'
|
|
@@ -93,21 +86,13 @@ const PluginComponents = new Map([
|
|
|
93
86
|
[Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
|
|
94
87
|
],
|
|
95
88
|
[Plugin.MultiBasket, [Component.MultiBasket]],
|
|
96
|
-
[
|
|
97
|
-
Plugin.SimilarProducts,
|
|
98
|
-
[
|
|
99
|
-
Component.SimilarProductsModal,
|
|
100
|
-
Component.SimilarProductsFilterSidebar,
|
|
101
|
-
Component.SimilarProductsResultsGrid,
|
|
102
|
-
Component.SimilarProductsPlugin,
|
|
103
|
-
Component.ProductImageSearchFeature,
|
|
104
|
-
Component.ImageSearchButton,
|
|
105
|
-
Component.HeaderImageSearchFeature
|
|
106
|
-
]
|
|
107
|
-
],
|
|
108
89
|
[Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
|
|
90
|
+
[Plugin.SavedCard, [Component.SavedCard]],
|
|
109
91
|
[Plugin.FlowPayment, [Component.FlowPayment]],
|
|
110
|
-
[
|
|
92
|
+
[
|
|
93
|
+
Plugin.VirtualTryOn,
|
|
94
|
+
[Component.VirtualTryOnPlugin, Component.BasketVirtualTryOn]
|
|
95
|
+
],
|
|
111
96
|
[Plugin.Hepsipay, [Component.Hepsipay]]
|
|
112
97
|
]);
|
|
113
98
|
|
|
@@ -173,8 +158,6 @@ export default function PluginModule({
|
|
|
173
158
|
promise = import(`${'@akinon/pz-multi-basket'}`);
|
|
174
159
|
} else if (plugin === Plugin.SavedCard) {
|
|
175
160
|
promise = import(`${'@akinon/pz-saved-card'}`);
|
|
176
|
-
} else if (plugin === Plugin.SimilarProducts) {
|
|
177
|
-
promise = import(`${'@akinon/pz-similar-products'}`);
|
|
178
161
|
} else if (plugin === Plugin.Hepsipay) {
|
|
179
162
|
promise = import(`${'@akinon/pz-hepsipay'}`);
|
|
180
163
|
} else if (plugin === Plugin.FlowPayment) {
|
package/data/urls.ts
CHANGED
|
@@ -183,11 +183,7 @@ 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}
|
|
187
|
-
similarProducts: (params?: string) =>
|
|
188
|
-
`/similar-products${params ? `?${params}` : ''}`,
|
|
189
|
-
similarProductsList: (params?: string) =>
|
|
190
|
-
`/similar-product-list${params ? `?${params}` : ''}`
|
|
186
|
+
`/bundle-product/${productPk}/?${queryString}`
|
|
191
187
|
};
|
|
192
188
|
|
|
193
189
|
export const wishlist = {
|
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.111.0-
|
|
4
|
+
"version": "1.111.0-snapshot-ZERO-3792-20251107072714",
|
|
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.111.0-
|
|
38
|
+
"@akinon/eslint-plugin-projectzero": "1.111.0-snapshot-ZERO-3792-20251107072714",
|
|
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,16 +37,6 @@ 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
|
-
|
|
50
40
|
declare module '@akinon/pz-cybersource-uc/src/redux/reducer' {
|
|
51
41
|
export default reducer as any;
|
|
52
42
|
}
|
package/plugins.js
CHANGED
package/types/index.ts
CHANGED
|
@@ -83,12 +83,6 @@ 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;
|
|
92
86
|
redis: {
|
|
93
87
|
defaultExpirationTime: number;
|
|
94
88
|
};
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
const iframeURLChange = (iframe, callback) => {
|
|
2
2
|
iframe.addEventListener('load', () => {
|
|
3
3
|
setTimeout(() => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const iframeLocation = iframe.contentWindow.location;
|
|
7
|
-
|
|
8
|
-
callback(iframeLocation);
|
|
9
|
-
}
|
|
10
|
-
} catch (error) {
|
|
11
|
-
// Expected: browser blocks cross-origin iframe access for security
|
|
4
|
+
if (iframe?.contentWindow?.location) {
|
|
5
|
+
callback(iframe.contentWindow.location);
|
|
12
6
|
}
|
|
13
7
|
}, 0);
|
|
14
8
|
});
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
const iframeURLChange = (iframe, callback) => {
|
|
2
2
|
iframe.addEventListener('load', () => {
|
|
3
3
|
setTimeout(() => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const iframeLocation = iframe.contentWindow.location;
|
|
7
|
-
|
|
8
|
-
callback(iframeLocation);
|
|
9
|
-
}
|
|
10
|
-
} catch (error) {
|
|
11
|
-
// Expected: browser blocks cross-origin iframe access for security
|
|
4
|
+
if (iframe?.contentWindow?.location) {
|
|
5
|
+
callback(iframe.contentWindow.location);
|
|
12
6
|
}
|
|
13
7
|
}, 0);
|
|
14
8
|
});
|
package/api/image-proxy.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
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
|
-
}
|
package/api/similar-products.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
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
|
-
}
|