@akinon/next 1.108.0-rc.9 → 1.108.0

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,48 +1,12 @@
1
1
  # @akinon/next
2
2
 
3
- ## 1.108.0-rc.9
3
+ ## 1.108.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - b55acb76: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
8
- - 143be2b9: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
9
- - 9f8cd3bc: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
10
- - d99a6a7d: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
11
- - 4de5303c: ZERO-2504: add cookie filter to api client request
12
-
13
- ## 1.108.0-rc.8
14
-
15
- ## 1.108.0-rc.7
16
-
17
- ### Minor Changes
18
-
19
- - d2c0e759: ZERO-3684: Handle cross-origin iframe access in iframeURLChange function
20
-
21
- ## 1.108.0-rc.6
22
-
23
- ## 1.108.0-rc.5
24
-
25
- ### Minor Changes
26
-
27
- - 31a2d35: ZERO-3640: Refactor checkout API call to include useFormData option; update FlowPayment component for improved error handling and code readability
28
-
29
- ## 1.108.0-rc.4
30
-
31
- ### Minor Changes
32
-
33
- - d8883ce: ZERO-3640: Refactor wallet completion handling to accept additional parameters; update related API calls
34
-
35
- ## 1.108.0-rc.3
36
-
37
- ## 1.108.0-rc.2
38
-
39
- ### Minor Changes
40
-
41
- - fcbbea79: ZERO-3648: Add virtual try-on feature with localization support
42
-
43
- ## 1.108.0-rc.1
44
-
45
- ## 1.108.0-rc.0
7
+ - d8883ce6: ZERO-3640: Refactor wallet completion handling to accept additional parameters; update related API calls
8
+ - 59ed7a7e: ZERO-3640: Add order number state and update FlowPayment component for wallet payment response handling
9
+ - 31a2d35a: ZERO-3640: Refactor checkout API call to include useFormData option; update FlowPayment component for improved error handling and code readability
46
10
 
47
11
  ## 1.107.0
48
12
 
@@ -21,10 +21,8 @@ enum Plugin {
21
21
  Akifast = 'pz-akifast',
22
22
  MultiBasket = 'pz-multi-basket',
23
23
  SavedCard = 'pz-saved-card',
24
- FlowPayment = 'pz-flow-payment',
25
- VirtualTryOn = 'pz-virtual-try-on',
26
24
  Hepsipay = 'pz-hepsipay',
27
- SimilarProducts = 'pz-similar-products'
25
+ FlowPayment = 'pz-flow-payment'
28
26
  }
29
27
 
30
28
  export enum Component {
@@ -50,14 +48,6 @@ export enum Component {
50
48
  AkifastCheckoutButton = 'CheckoutButton',
51
49
  MultiBasket = 'MultiBasket',
52
50
  SavedCard = 'SavedCardOption',
53
- VirtualTryOnPlugin = 'VirtualTryOnPlugin',
54
- SimilarProductsModal = 'SimilarProductsModal',
55
- SimilarProductsFilterSidebar = 'SimilarProductsFilterSidebar',
56
- SimilarProductsResultsGrid = 'SimilarProductsResultsGrid',
57
- SimilarProductsPlugin = 'SimilarProductsPlugin',
58
- ProductImageSearchFeature = 'ProductImageSearchFeature',
59
- ImageSearchButton = 'ImageSearchButton',
60
- HeaderImageSearchFeature = 'HeaderImageSearchFeature',
61
51
  IyzicoSavedCard = 'IyzicoSavedCardOption',
62
52
  Hepsipay = 'Hepsipay',
63
53
  FlowPayment = 'FlowPayment'
@@ -93,24 +83,10 @@ const PluginComponents = new Map([
93
83
  [Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
94
84
  ],
95
85
  [Plugin.MultiBasket, [Component.MultiBasket]],
96
- [Plugin.SavedCard, [Component.SavedCard]],
97
- [
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
- ]
108
- ],
109
86
  [Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
110
87
  [Plugin.SavedCard, [Component.SavedCard]],
111
- [Plugin.FlowPayment, [Component.FlowPayment]],
112
- [Plugin.VirtualTryOn, [Component.VirtualTryOnPlugin]],
113
- [Plugin.Hepsipay, [Component.Hepsipay]]
88
+ [Plugin.Hepsipay, [Component.Hepsipay]],
89
+ [Plugin.FlowPayment, [Component.FlowPayment]]
114
90
  ]);
115
91
 
116
92
  const getPlugin = (component: Component) => {
@@ -175,14 +151,10 @@ export default function PluginModule({
175
151
  promise = import(`${'@akinon/pz-multi-basket'}`);
176
152
  } else if (plugin === Plugin.SavedCard) {
177
153
  promise = import(`${'@akinon/pz-saved-card'}`);
178
- } else if (plugin === Plugin.SimilarProducts) {
179
- promise = import(`${'@akinon/pz-similar-products'}`);
180
154
  } else if (plugin === Plugin.Hepsipay) {
181
155
  promise = import(`${'@akinon/pz-hepsipay'}`);
182
156
  } else if (plugin === Plugin.FlowPayment) {
183
157
  promise = import(`${'@akinon/pz-flow-payment'}`);
184
- } else if (plugin === Plugin.VirtualTryOn) {
185
- promise = import(`${'@akinon/pz-virtual-try-on'}`);
186
158
  }
187
159
  } catch (error) {
188
160
  logger.error(error);
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.108.0-rc.9",
4
+ "version": "1.108.0",
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.108.0-rc.9",
38
+ "@akinon/eslint-plugin-projectzero": "1.108.0",
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
@@ -16,9 +16,7 @@ module.exports = [
16
16
  'pz-tabby-extension',
17
17
  'pz-apple-pay',
18
18
  'pz-tamara-extension',
19
- 'pz-similar-products',
20
19
  'pz-cybersource-uc',
21
20
  'pz-hepsipay',
22
- 'pz-flow-payment',
23
- 'pz-virtual-try-on'
21
+ 'pz-flow-payment'
24
22
  ];
@@ -108,6 +108,7 @@ export interface PreOrder {
108
108
  token?: string;
109
109
  agreement_confirmed?: boolean;
110
110
  phone_number?: string;
111
+ number?: string;
111
112
  }
112
113
 
113
114
  export type ExtraField = Record<string, any>;
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
- 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
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
- 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
4
+ if (iframe?.contentWindow?.location) {
5
+ callback(iframe.contentWindow.location);
12
6
  }
13
7
  }, 0);
14
8
  });
@@ -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
- }
@@ -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
- }
@@ -1,306 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import Settings from 'settings';
3
-
4
- const VIRTUAL_TRY_ON_API_URL = process.env.NEXT_PUBLIC_VIRTUAL_TRY_ON_API_URL;
5
-
6
- let limitedCategoriesCache: {
7
- data: any;
8
- timestamp: number;
9
- } | null = null;
10
-
11
- const CACHE_TTL = 3600000;
12
-
13
- function getClientIP(request: NextRequest): string | null {
14
- const forwardedFor = request.headers.get('x-forwarded-for');
15
- if (forwardedFor) {
16
- const ip = forwardedFor.split(',')[0].trim();
17
- if (ip === '::1' || ip === '::ffff:127.0.0.1') {
18
- return '127.0.0.1';
19
- }
20
- return ip;
21
- }
22
-
23
- const realIP = request.headers.get('x-real-ip');
24
- if (realIP) {
25
- if (realIP === '::1' || realIP === '::ffff:127.0.0.1') {
26
- return '127.0.0.1';
27
- }
28
- return realIP;
29
- }
30
-
31
- if (process.env.NODE_ENV === 'development') {
32
- return '127.0.0.1';
33
- }
34
-
35
- return null;
36
- }
37
-
38
- function getLocaleFromRequest(request: NextRequest): string | null {
39
- const cookieLocale = request.cookies.get('pz-locale')?.value;
40
-
41
- if (cookieLocale) {
42
- const currentLocale = Settings.localization.locales.find(
43
- (l) => l.value === cookieLocale
44
- );
45
- return currentLocale?.apiValue || null;
46
- }
47
-
48
- return null;
49
- }
50
-
51
- export async function GET(request: NextRequest) {
52
- try {
53
- const { searchParams } = new URL(request.url);
54
- const endpoint = searchParams.get('endpoint');
55
-
56
- if (endpoint === 'limited-categories') {
57
- const now = Date.now();
58
-
59
- if (
60
- limitedCategoriesCache &&
61
- now - limitedCategoriesCache.timestamp < CACHE_TTL
62
- ) {
63
- return NextResponse.json(limitedCategoriesCache.data, {
64
- status: 200,
65
- headers: {
66
- 'Access-Control-Allow-Origin': '*',
67
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS',
68
- 'Access-Control-Allow-Headers':
69
- 'Content-Type, Accept, Authorization',
70
- 'Cache-Control':
71
- 'public, s-maxage=3600, stale-while-revalidate=7200',
72
- 'X-Virtual-Try-On-Cache-Status': 'HIT'
73
- }
74
- });
75
- }
76
-
77
- const externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/v1/limited-categories`;
78
- const clientIP = getClientIP(request);
79
- const locale = getLocaleFromRequest(request);
80
-
81
- const headersToSend = {
82
- Accept: 'application/json',
83
- ...(locale && { 'Accept-Language': locale }),
84
- ...(request.headers.get('authorization') && {
85
- Authorization: request.headers.get('authorization')!
86
- }),
87
- ...(clientIP && {
88
- 'X-Forwarded-For': clientIP
89
- })
90
- };
91
-
92
- const response = await fetch(externalUrl, {
93
- method: 'GET',
94
- headers: headersToSend
95
- });
96
-
97
- let responseData: any;
98
- const responseText = await response.text();
99
-
100
- try {
101
- responseData = responseText ? JSON.parse(responseText) : {};
102
- } catch (parseError) {
103
- responseData = { category_ids: [] };
104
- }
105
-
106
- if (!response.ok) {
107
- responseData = { category_ids: [] };
108
- }
109
-
110
- limitedCategoriesCache = {
111
- data: responseData,
112
- timestamp: Date.now()
113
- };
114
-
115
- return NextResponse.json(responseData, {
116
- status: response.ok ? response.status : 200,
117
- headers: {
118
- 'Access-Control-Allow-Origin': '*',
119
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS',
120
- 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization',
121
- 'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
122
- 'X-Virtual-Try-On-Cache-Status': 'MISS'
123
- }
124
- });
125
- }
126
-
127
- return NextResponse.json(
128
- { error: 'Invalid endpoint for GET method' },
129
- { status: 400 }
130
- );
131
- } catch (error) {
132
- return NextResponse.json({ category_ids: [] }, { status: 200 });
133
- }
134
- }
135
-
136
- export async function POST(request: NextRequest) {
137
- try {
138
- const { searchParams } = new URL(request.url);
139
- const endpoint = searchParams.get('endpoint');
140
-
141
- const body = await request.json();
142
-
143
- let externalUrl: string;
144
- if (endpoint === 'feedback') {
145
- externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/v1/feedback`;
146
- } else {
147
- externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/v1/virtual-try-on`;
148
- }
149
-
150
- const httpMethod = endpoint === 'feedback' ? 'PUT' : 'POST';
151
- const clientIP = getClientIP(request);
152
- const locale = getLocaleFromRequest(request);
153
-
154
- const headersToSend = {
155
- 'Content-Type': 'application/json',
156
- ...(locale && { 'Accept-Language': locale }),
157
- ...(httpMethod === 'POST' && { Accept: 'application/json' }),
158
- ...(request.headers.get('authorization') && {
159
- Authorization: request.headers.get('authorization')!
160
- }),
161
- ...(clientIP && {
162
- 'X-Forwarded-For': clientIP
163
- })
164
- };
165
-
166
- const response = await fetch(externalUrl, {
167
- method: httpMethod,
168
- headers: headersToSend,
169
- body: JSON.stringify(body)
170
- });
171
-
172
- let responseData: any;
173
- const responseText = await response.text();
174
-
175
- if (endpoint === 'feedback') {
176
- try {
177
- responseData = responseText ? JSON.parse(responseText) : {};
178
- } catch (parseError) {
179
- responseData = { error: 'Invalid JSON response from feedback API' };
180
- }
181
- } else {
182
- try {
183
- responseData = responseText ? JSON.parse(responseText) : {};
184
- } catch (parseError) {
185
- responseData = { error: 'Invalid JSON response' };
186
- }
187
- }
188
-
189
- if (!response.ok && responseData.error) {
190
- let userFriendlyMessage = responseData.error;
191
-
192
- if (
193
- typeof responseData.error === 'string' &&
194
- (responseData.error.includes('duplicate key value') ||
195
- responseData.error.includes('image_pk'))
196
- ) {
197
- userFriendlyMessage =
198
- 'This image has already been processed. Please try with a different image.';
199
- } else if (responseData.error.includes('bulk insert images')) {
200
- userFriendlyMessage =
201
- 'There was an issue processing your image. Please try again with a different image.';
202
- }
203
-
204
- return NextResponse.json(
205
- {
206
- ...responseData,
207
- message: userFriendlyMessage
208
- },
209
- {
210
- status: response.status,
211
- headers: {
212
- 'Access-Control-Allow-Origin': '*',
213
- 'Access-Control-Allow-Methods': 'POST, OPTIONS',
214
- 'Access-Control-Allow-Headers':
215
- 'Content-Type, Accept, Authorization'
216
- }
217
- }
218
- );
219
- }
220
-
221
- return NextResponse.json(responseData, {
222
- status: response.status,
223
- headers: {
224
- 'Access-Control-Allow-Origin': '*',
225
- 'Access-Control-Allow-Methods': 'POST, OPTIONS',
226
- 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization'
227
- }
228
- });
229
- } catch (error) {
230
- return NextResponse.json(
231
- {
232
- status: 'error',
233
- message:
234
- 'Internal server error occurred during virtual try-on processing'
235
- },
236
- { status: 500 }
237
- );
238
- }
239
- }
240
-
241
- export async function PUT(request: NextRequest) {
242
- try {
243
- const { searchParams } = new URL(request.url);
244
- const endpoint = searchParams.get('endpoint');
245
-
246
- if (endpoint !== 'feedback') {
247
- return NextResponse.json(
248
- { error: 'PUT method only supports feedback endpoint' },
249
- { status: 400 }
250
- );
251
- }
252
-
253
- const body = await request.json();
254
-
255
- const externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/v1/feedback`;
256
- const clientIP = getClientIP(request);
257
- const locale = getLocaleFromRequest(request);
258
-
259
- const headersToSend = {
260
- 'Content-Type': 'application/json',
261
- ...(locale && { 'Accept-Language': locale }),
262
- ...(request.headers.get('authorization') && {
263
- Authorization: request.headers.get('authorization')!
264
- }),
265
- ...(clientIP && {
266
- 'X-Forwarded-For': clientIP
267
- })
268
- };
269
-
270
- const response = await fetch(externalUrl, {
271
- method: 'PUT',
272
- headers: headersToSend,
273
- body: JSON.stringify(body)
274
- });
275
-
276
- const responseData = await response.json();
277
-
278
- return NextResponse.json(responseData, {
279
- status: response.status,
280
- headers: {
281
- 'Access-Control-Allow-Origin': '*',
282
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS',
283
- 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization'
284
- }
285
- });
286
- } catch (error) {
287
- return NextResponse.json(
288
- {
289
- status: 'error',
290
- message: 'Internal server error occurred during feedback submission'
291
- },
292
- { status: 500 }
293
- );
294
- }
295
- }
296
-
297
- export async function OPTIONS() {
298
- return new NextResponse(null, {
299
- status: 200,
300
- headers: {
301
- 'Access-Control-Allow-Origin': '*',
302
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS',
303
- 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization'
304
- }
305
- });
306
- }