@akinon/next 1.108.0-rc.1 → 1.108.0-rc.3

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,5 +1,13 @@
1
1
  # @akinon/next
2
2
 
3
+ ## 1.108.0-rc.3
4
+
5
+ ## 1.108.0-rc.2
6
+
7
+ ### Minor Changes
8
+
9
+ - fcbbea79: ZERO-3648: Add virtual try-on feature with localization support
10
+
3
11
  ## 1.108.0-rc.1
4
12
 
5
13
  ## 1.108.0-rc.0
@@ -0,0 +1,306 @@
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
+ }
@@ -21,8 +21,9 @@ enum Plugin {
21
21
  Akifast = 'pz-akifast',
22
22
  MultiBasket = 'pz-multi-basket',
23
23
  SavedCard = 'pz-saved-card',
24
- Hepsipay = 'pz-hepsipay',
25
- FlowPayment = 'pz-flow-payment'
24
+ FlowPayment = 'pz-flow-payment',
25
+ VirtualTryOn = 'pz-virtual-try-on',
26
+ Hepsipay = 'pz-hepsipay'
26
27
  }
27
28
 
28
29
  export enum Component {
@@ -48,6 +49,7 @@ export enum Component {
48
49
  AkifastCheckoutButton = 'CheckoutButton',
49
50
  MultiBasket = 'MultiBasket',
50
51
  SavedCard = 'SavedCardOption',
52
+ VirtualTryOnPlugin = 'VirtualTryOnPlugin',
51
53
  IyzicoSavedCard = 'IyzicoSavedCardOption',
52
54
  Hepsipay = 'Hepsipay',
53
55
  FlowPayment = 'FlowPayment'
@@ -85,8 +87,9 @@ const PluginComponents = new Map([
85
87
  [Plugin.MultiBasket, [Component.MultiBasket]],
86
88
  [Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
87
89
  [Plugin.SavedCard, [Component.SavedCard]],
88
- [Plugin.Hepsipay, [Component.Hepsipay]],
89
- [Plugin.FlowPayment, [Component.FlowPayment]]
90
+ [Plugin.FlowPayment, [Component.FlowPayment]],
91
+ [Plugin.VirtualTryOn, [Component.VirtualTryOnPlugin]],
92
+ [Plugin.Hepsipay, [Component.Hepsipay]]
90
93
  ]);
91
94
 
92
95
  const getPlugin = (component: Component) => {
@@ -155,6 +158,8 @@ export default function PluginModule({
155
158
  promise = import(`${'@akinon/pz-hepsipay'}`);
156
159
  } else if (plugin === Plugin.FlowPayment) {
157
160
  promise = import(`${'@akinon/pz-flow-payment'}`);
161
+ } else if (plugin === Plugin.VirtualTryOn) {
162
+ promise = import(`${'@akinon/pz-virtual-try-on'}`);
158
163
  }
159
164
  } catch (error) {
160
165
  logger.error(error);
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.1",
4
+ "version": "1.108.0-rc.3",
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.1",
38
+ "@akinon/eslint-plugin-projectzero": "1.108.0-rc.3",
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.js CHANGED
@@ -18,5 +18,6 @@ module.exports = [
18
18
  'pz-tamara-extension',
19
19
  'pz-cybersource-uc',
20
20
  'pz-hepsipay',
21
- 'pz-flow-payment'
21
+ 'pz-flow-payment',
22
+ 'pz-virtual-try-on'
22
23
  ];