@akinon/next 2.0.0-beta.12 → 2.0.0-beta.14
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 +290 -29
- package/api/auth.ts +99 -77
- package/api/cache.ts +41 -5
- package/api/client.ts +3 -3
- package/api/form.ts +85 -0
- package/api/image-proxy.ts +75 -0
- package/api/product-categories.ts +53 -0
- package/api/similar-product-list.ts +63 -0
- package/api/similar-products.ts +111 -0
- package/api/virtual-try-on.ts +382 -0
- package/bin/pz-generate-routes.js +105 -0
- package/bin/pz-prebuild.js +1 -1
- package/bin/pz-predev.js +1 -0
- package/components/accordion.tsx +21 -6
- package/components/button.tsx +1 -1
- package/components/file-input.tsx +65 -3
- package/components/input.tsx +2 -2
- package/components/modal.tsx +32 -16
- package/components/plugin-module.tsx +61 -3
- package/components/select.tsx +2 -2
- package/components/selected-payment-option-view.tsx +21 -0
- package/data/client/checkout.ts +130 -74
- package/data/server/category.ts +11 -9
- package/data/server/flatpage.ts +4 -1
- package/data/server/form.ts +4 -1
- package/data/server/landingpage.ts +4 -1
- package/data/server/list.ts +5 -4
- package/data/server/menu.ts +4 -1
- package/data/server/product.ts +97 -52
- package/data/server/seo.ts +4 -1
- package/data/server/special-page.ts +5 -4
- package/data/server/widget.ts +4 -1
- package/data/urls.ts +3 -2
- package/hocs/client/with-segment-defaults.tsx +2 -2
- package/hocs/server/with-segment-defaults.tsx +65 -20
- package/hooks/index.ts +1 -0
- package/hooks/use-loyalty-availability.ts +21 -0
- package/hooks/use-payment-options.ts +2 -1
- package/hooks/use-pz-params.ts +37 -0
- package/instrumentation/index.ts +0 -1
- package/instrumentation/node.ts +2 -20
- package/jest.config.js +7 -1
- package/lib/cache-handler.mjs +534 -16
- package/lib/cache.ts +268 -34
- package/localization/provider.tsx +2 -5
- package/middlewares/checkout-provider.ts +1 -1
- package/middlewares/complete-gpay.ts +32 -26
- package/middlewares/complete-masterpass.ts +33 -26
- package/middlewares/complete-wallet.ts +182 -0
- package/middlewares/default.ts +346 -235
- package/middlewares/index.ts +8 -2
- package/middlewares/locale.ts +0 -1
- package/middlewares/masterpass-rest-callback.ts +230 -0
- package/middlewares/pretty-url.ts +21 -8
- package/middlewares/redirection-payment.ts +32 -26
- package/middlewares/saved-card-redirection.ts +33 -26
- package/middlewares/three-d-redirection.ts +32 -26
- package/middlewares/url-redirection.ts +9 -15
- package/middlewares/wallet-complete-redirection.ts +206 -0
- package/package.json +20 -11
- package/plugins.d.ts +19 -4
- package/plugins.js +9 -1
- package/redux/actions.ts +47 -0
- package/redux/middlewares/checkout.ts +20 -8
- package/redux/middlewares/index.ts +12 -10
- package/redux/middlewares/pre-order/address.ts +1 -1
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +1 -1
- package/redux/middlewares/pre-order/data-source-shipping-option.ts +1 -1
- package/redux/middlewares/pre-order/delivery-option.ts +1 -1
- package/redux/middlewares/pre-order/index.ts +3 -1
- package/redux/middlewares/pre-order/installment-option.ts +2 -1
- package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
- package/redux/middlewares/pre-order/payment-option.ts +1 -1
- package/redux/middlewares/pre-order/pre-order-validation.ts +4 -3
- package/redux/middlewares/pre-order/redirection.ts +2 -2
- package/redux/middlewares/pre-order/set-pre-order.ts +2 -2
- package/redux/middlewares/pre-order/shipping-option.ts +1 -1
- package/redux/middlewares/pre-order/shipping-step.ts +1 -1
- package/redux/reducers/checkout.ts +9 -1
- package/redux/reducers/index.ts +5 -1
- package/sentry/index.ts +54 -17
- package/types/commerce/checkout.ts +11 -1
- package/types/index.ts +96 -6
- package/types/next-auth.d.ts +2 -2
- package/utils/app-fetch.ts +2 -2
- package/utils/generate-commerce-search-params.ts +3 -2
- package/utils/get-checkout-path.ts +3 -0
- package/utils/index.ts +61 -11
- package/utils/override-middleware.ts +1 -0
- package/utils/pz-segments.ts +92 -0
- package/utils/redirect-ignore.ts +35 -0
- package/utils/redirect.ts +9 -3
- package/with-pz-config.js +10 -4
package/api/auth.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import CredentialProvider from 'next-auth/providers/credentials';
|
|
1
|
+
import NextAuth, { Session, CredentialsSignin } from 'next-auth';
|
|
2
|
+
import Credentials from 'next-auth/providers/credentials';
|
|
4
3
|
import { ROUTES } from 'routes';
|
|
5
4
|
import { URLS, user } from '../data/urls';
|
|
6
5
|
import Settings from 'settings';
|
|
@@ -9,9 +8,20 @@ import logger from '@akinon/next/utils/log';
|
|
|
9
8
|
import { AuthError } from '../types';
|
|
10
9
|
import getRootHostname from '../utils/get-root-hostname';
|
|
11
10
|
import { LocaleUrlStrategy } from '../localization';
|
|
11
|
+
import { cookies, headers } from 'next/headers';
|
|
12
|
+
|
|
13
|
+
import type { NextAuthConfig } from 'next-auth';
|
|
14
|
+
|
|
15
|
+
class PzCredentialsError extends CredentialsSignin {
|
|
16
|
+
code = 'credentials';
|
|
17
|
+
constructor(errors: AuthError[]) {
|
|
18
|
+
super();
|
|
19
|
+
this.code = JSON.stringify(errors);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
12
22
|
|
|
13
23
|
async function getCurrentUser(sessionId: string, currency = '') {
|
|
14
|
-
const
|
|
24
|
+
const reqHeaders = {
|
|
15
25
|
'Content-Type': 'application/json',
|
|
16
26
|
Cookie: `osessionid=${sessionId}`,
|
|
17
27
|
'x-currency': currency
|
|
@@ -19,7 +29,7 @@ async function getCurrentUser(sessionId: string, currency = '') {
|
|
|
19
29
|
|
|
20
30
|
const currentUser = await (
|
|
21
31
|
await fetch(URLS.user.currentUser, {
|
|
22
|
-
headers
|
|
32
|
+
headers: reqHeaders
|
|
23
33
|
})
|
|
24
34
|
).json();
|
|
25
35
|
|
|
@@ -42,23 +52,18 @@ async function getCurrentUser(sessionId: string, currency = '') {
|
|
|
42
52
|
};
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
type CustomNextAuthOptions = (
|
|
46
|
-
req: NextApiRequest,
|
|
47
|
-
res: NextApiResponse
|
|
48
|
-
) => Partial<NextAuthOptions>;
|
|
55
|
+
type CustomNextAuthOptions = () => Partial<NextAuthConfig>;
|
|
49
56
|
|
|
50
|
-
const
|
|
51
|
-
req: NextApiRequest,
|
|
52
|
-
res: NextApiResponse
|
|
53
|
-
): NextAuthOptions => {
|
|
57
|
+
const getDefaultAuthConfig = (): NextAuthConfig => {
|
|
54
58
|
return {
|
|
55
59
|
providers: [
|
|
56
|
-
|
|
60
|
+
Credentials({
|
|
57
61
|
id: 'oauth',
|
|
58
62
|
name: 'credentials',
|
|
59
63
|
credentials: {},
|
|
60
64
|
authorize: async (credentials) => {
|
|
61
|
-
const
|
|
65
|
+
const cookieStore = await cookies();
|
|
66
|
+
const sessionId = cookieStore.get('osessionid')?.value;
|
|
62
67
|
|
|
63
68
|
if (!sessionId) {
|
|
64
69
|
return null;
|
|
@@ -66,12 +71,12 @@ const defaultNextAuthOptions = (
|
|
|
66
71
|
|
|
67
72
|
const currentUser = await getCurrentUser(
|
|
68
73
|
sessionId,
|
|
69
|
-
|
|
74
|
+
cookieStore.get('pz-currency')?.value ?? ''
|
|
70
75
|
);
|
|
71
76
|
return currentUser;
|
|
72
77
|
}
|
|
73
78
|
}),
|
|
74
|
-
|
|
79
|
+
Credentials({
|
|
75
80
|
id: 'default',
|
|
76
81
|
name: 'credentials',
|
|
77
82
|
credentials: {
|
|
@@ -86,49 +91,58 @@ const defaultNextAuthOptions = (
|
|
|
86
91
|
captchaValidated: {}
|
|
87
92
|
},
|
|
88
93
|
authorize: async (credentials) => {
|
|
89
|
-
const
|
|
94
|
+
const cookieStore = await cookies();
|
|
95
|
+
const headerStore = await headers();
|
|
96
|
+
|
|
97
|
+
const reqHeaders: HeadersInit = new Headers();
|
|
90
98
|
const language = Settings.localization.locales.find(
|
|
91
|
-
(item) => item.value === credentials.locale
|
|
99
|
+
(item) => item.value === (credentials as any).locale
|
|
92
100
|
).apiValue;
|
|
93
|
-
const userIp =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
const userIp = headerStore.get('x-forwarded-for') ?? '';
|
|
102
|
+
|
|
103
|
+
reqHeaders.set('Content-Type', 'application/json');
|
|
104
|
+
reqHeaders.set('cookie', headerStore.get('cookie') ?? '');
|
|
105
|
+
reqHeaders.set('Accept-Language', `${language}`);
|
|
106
|
+
reqHeaders.set(
|
|
107
|
+
'x-currency',
|
|
108
|
+
cookieStore.get('pz-currency')?.value ?? ''
|
|
109
|
+
);
|
|
110
|
+
reqHeaders.set('x-forwarded-for', userIp);
|
|
111
|
+
reqHeaders.set(
|
|
101
112
|
'x-app-device',
|
|
102
|
-
|
|
113
|
+
headerStore.get('x-app-device') ?? ''
|
|
103
114
|
);
|
|
104
115
|
|
|
105
|
-
|
|
116
|
+
reqHeaders.set(
|
|
117
|
+
'x-frontend-id',
|
|
118
|
+
cookieStore.get('pz-frontend-id')?.value || ''
|
|
119
|
+
);
|
|
106
120
|
|
|
107
121
|
logger.debug('Trying to login/register', {
|
|
108
|
-
formType: credentials.formType,
|
|
122
|
+
formType: (credentials as any).formType,
|
|
109
123
|
userIp
|
|
110
124
|
});
|
|
111
125
|
|
|
112
126
|
const checkCurrentUser = await getCurrentUser(
|
|
113
|
-
|
|
114
|
-
|
|
127
|
+
cookieStore.get('osessionid')?.value ?? '',
|
|
128
|
+
cookieStore.get('pz-currency')?.value ?? ''
|
|
115
129
|
);
|
|
116
130
|
|
|
117
131
|
if (checkCurrentUser?.pk) {
|
|
118
|
-
const sessionCookie =
|
|
132
|
+
const sessionCookie = reqHeaders
|
|
119
133
|
.get('cookie')
|
|
120
134
|
?.match(/osessionid=\w+/)?.[0]
|
|
121
135
|
.replace(/osessionid=/, '');
|
|
122
136
|
if (sessionCookie) {
|
|
123
|
-
|
|
137
|
+
reqHeaders.set('cookie', sessionCookie);
|
|
124
138
|
}
|
|
125
139
|
}
|
|
126
140
|
|
|
127
141
|
const apiRequest = await fetch(
|
|
128
|
-
`${Settings.commerceUrl}${user[credentials.formType]}`,
|
|
142
|
+
`${Settings.commerceUrl}${user[(credentials as any).formType]}`,
|
|
129
143
|
{
|
|
130
144
|
method: 'POST',
|
|
131
|
-
headers,
|
|
145
|
+
headers: reqHeaders,
|
|
132
146
|
body: JSON.stringify(credentials)
|
|
133
147
|
}
|
|
134
148
|
);
|
|
@@ -163,21 +177,25 @@ const defaultNextAuthOptions = (
|
|
|
163
177
|
const { localeUrlStrategy } = Settings.localization;
|
|
164
178
|
|
|
165
179
|
const fallbackHost =
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
headerStore.get('x-forwarded-host') ||
|
|
181
|
+
headerStore.get('host');
|
|
168
182
|
const hostname =
|
|
169
183
|
process.env.NEXT_PUBLIC_URL || `https://${fallbackHost}`;
|
|
170
184
|
const rootHostname =
|
|
171
185
|
localeUrlStrategy === LocaleUrlStrategy.Subdomain
|
|
172
186
|
? getRootHostname(hostname)
|
|
173
187
|
: null;
|
|
174
|
-
const domainOption = rootHostname ? `Domain=${rootHostname};` : '';
|
|
175
|
-
const cookieOptions = `Path=/; HttpOnly; Secure; Max-Age=${maxAge}; ${domainOption}`;
|
|
176
188
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
const cookieOptions = {
|
|
190
|
+
path: '/',
|
|
191
|
+
httpOnly: true,
|
|
192
|
+
secure: true,
|
|
193
|
+
maxAge,
|
|
194
|
+
...(rootHostname ? { domain: rootHostname } : {})
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
cookieStore.set('osessionid', sessionId, cookieOptions);
|
|
198
|
+
cookieStore.set('sessionid', sessionId, cookieOptions);
|
|
181
199
|
}
|
|
182
200
|
|
|
183
201
|
if (!response.key) {
|
|
@@ -198,8 +216,8 @@ const defaultNextAuthOptions = (
|
|
|
198
216
|
});
|
|
199
217
|
|
|
200
218
|
logger.debug('Captcha required', {
|
|
201
|
-
email: credentials.email,
|
|
202
|
-
formType: credentials.formType
|
|
219
|
+
email: (credentials as any).email,
|
|
220
|
+
formType: (credentials as any).formType
|
|
203
221
|
});
|
|
204
222
|
} else if (apiRequest.status === 202) {
|
|
205
223
|
errors.push({
|
|
@@ -218,13 +236,13 @@ const defaultNextAuthOptions = (
|
|
|
218
236
|
}
|
|
219
237
|
|
|
220
238
|
if (errors.length) {
|
|
221
|
-
throw new
|
|
239
|
+
throw new PzCredentialsError(errors);
|
|
222
240
|
}
|
|
223
241
|
}
|
|
224
242
|
|
|
225
243
|
const currentUser = await getCurrentUser(
|
|
226
244
|
sessionId,
|
|
227
|
-
|
|
245
|
+
cookieStore.get('pz-currency')?.value ?? ''
|
|
228
246
|
);
|
|
229
247
|
return currentUser;
|
|
230
248
|
}
|
|
@@ -238,18 +256,20 @@ const defaultNextAuthOptions = (
|
|
|
238
256
|
|
|
239
257
|
return token;
|
|
240
258
|
},
|
|
241
|
-
async session({ session,
|
|
242
|
-
session.user = token.user as
|
|
259
|
+
async session({ session, token }) {
|
|
260
|
+
session.user = token.user as any;
|
|
243
261
|
return session;
|
|
244
262
|
},
|
|
245
263
|
async redirect({ url, baseUrl }: { url: string; baseUrl: string }) {
|
|
264
|
+
const headerStore = await headers();
|
|
246
265
|
const pathname = url.startsWith('/') ? url : url.replace(baseUrl, '');
|
|
247
266
|
const pathnameWithoutLocale = pathname.replace(
|
|
248
267
|
urlLocaleMatcherRegex,
|
|
249
268
|
''
|
|
250
269
|
);
|
|
251
270
|
|
|
252
|
-
const localeResults =
|
|
271
|
+
const localeResults = headerStore
|
|
272
|
+
.get('referer')
|
|
253
273
|
?.replace(baseUrl, '')
|
|
254
274
|
?.match(urlLocaleMatcherRegex);
|
|
255
275
|
|
|
@@ -260,11 +280,16 @@ const defaultNextAuthOptions = (
|
|
|
260
280
|
signIn: () => {
|
|
261
281
|
logger.debug('Successfully signed in');
|
|
262
282
|
},
|
|
263
|
-
signOut: () => {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
283
|
+
signOut: async () => {
|
|
284
|
+
const cookieStore = await cookies();
|
|
285
|
+
cookieStore.set('osessionid', '', {
|
|
286
|
+
path: '/',
|
|
287
|
+
maxAge: 0
|
|
288
|
+
});
|
|
289
|
+
cookieStore.set('sessionid', '', {
|
|
290
|
+
path: '/',
|
|
291
|
+
maxAge: 0
|
|
292
|
+
});
|
|
268
293
|
logger.debug('Successfully signed out');
|
|
269
294
|
}
|
|
270
295
|
},
|
|
@@ -275,36 +300,33 @@ const defaultNextAuthOptions = (
|
|
|
275
300
|
};
|
|
276
301
|
};
|
|
277
302
|
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const mergedOptions = {
|
|
287
|
-
...baseOptions,
|
|
288
|
-
...customOptionsResult,
|
|
303
|
+
export const createAuth = (customOptions?: CustomNextAuthOptions) => {
|
|
304
|
+
const baseConfig = getDefaultAuthConfig();
|
|
305
|
+
const customConfig = customOptions ? customOptions() : {};
|
|
306
|
+
|
|
307
|
+
const mergedConfig: NextAuthConfig = {
|
|
308
|
+
trustHost: true,
|
|
309
|
+
...baseConfig,
|
|
310
|
+
...customConfig,
|
|
289
311
|
providers: [
|
|
290
|
-
...
|
|
291
|
-
...(
|
|
312
|
+
...baseConfig.providers,
|
|
313
|
+
...(customConfig.providers || [])
|
|
292
314
|
],
|
|
293
315
|
callbacks: {
|
|
294
|
-
...
|
|
295
|
-
...
|
|
316
|
+
...baseConfig.callbacks,
|
|
317
|
+
...customConfig.callbacks
|
|
296
318
|
},
|
|
297
319
|
events: {
|
|
298
|
-
...
|
|
299
|
-
...
|
|
320
|
+
...baseConfig.events,
|
|
321
|
+
...customConfig.events
|
|
300
322
|
},
|
|
301
323
|
pages: {
|
|
302
|
-
...
|
|
303
|
-
...
|
|
324
|
+
...baseConfig.pages,
|
|
325
|
+
...customConfig.pages
|
|
304
326
|
}
|
|
305
327
|
};
|
|
306
328
|
|
|
307
|
-
return NextAuth(
|
|
329
|
+
return NextAuth(mergedConfig);
|
|
308
330
|
};
|
|
309
331
|
|
|
310
|
-
export default
|
|
332
|
+
export default createAuth;
|
package/api/cache.ts
CHANGED
|
@@ -21,20 +21,56 @@ async function handleRequest(...args) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const formData = await req.formData();
|
|
24
|
-
const body = {} as {
|
|
24
|
+
const body = {} as {
|
|
25
|
+
key: string;
|
|
26
|
+
value?: string;
|
|
27
|
+
expire?: number;
|
|
28
|
+
keyValuePairs?: string;
|
|
29
|
+
compressed?: string;
|
|
30
|
+
};
|
|
25
31
|
|
|
26
32
|
formData.forEach((value, key) => {
|
|
27
33
|
body[key] = value;
|
|
28
34
|
});
|
|
29
35
|
|
|
30
|
-
const { key, value, expire } = body;
|
|
31
|
-
let response:
|
|
36
|
+
const { key, value, expire, keyValuePairs, compressed } = body;
|
|
37
|
+
let response: any;
|
|
32
38
|
|
|
33
39
|
try {
|
|
34
40
|
if (req.method === 'POST') {
|
|
35
|
-
|
|
41
|
+
// GET request - check if compressed flag is set
|
|
42
|
+
if (compressed === 'true') {
|
|
43
|
+
response = await Cache.getCompressed(key);
|
|
44
|
+
} else {
|
|
45
|
+
response = await Cache.get(key);
|
|
46
|
+
}
|
|
36
47
|
} else if (req.method === 'PUT') {
|
|
37
|
-
|
|
48
|
+
if (keyValuePairs) {
|
|
49
|
+
try {
|
|
50
|
+
const parsedKeyValuePairs = JSON.parse(keyValuePairs);
|
|
51
|
+
if (
|
|
52
|
+
typeof parsedKeyValuePairs !== 'object' ||
|
|
53
|
+
parsedKeyValuePairs === null ||
|
|
54
|
+
Array.isArray(parsedKeyValuePairs)
|
|
55
|
+
) {
|
|
56
|
+
throw new Error('Invalid keyValuePairs format - must be an object');
|
|
57
|
+
}
|
|
58
|
+
response = await Cache.mset(parsedKeyValuePairs, expire);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
logger.error('Invalid keyValuePairs in mset request', { error });
|
|
61
|
+
return NextResponse.json(
|
|
62
|
+
{ error: 'Invalid keyValuePairs format' },
|
|
63
|
+
{ status: 400 }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
// SET request - check if compressed flag is set
|
|
68
|
+
if (compressed === 'true') {
|
|
69
|
+
response = await Cache.setCompressed(key, value, expire);
|
|
70
|
+
} else {
|
|
71
|
+
response = await Cache.set(key, value, expire);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
38
74
|
}
|
|
39
75
|
} catch (error) {
|
|
40
76
|
logger.error(error);
|
package/api/client.ts
CHANGED
|
@@ -15,8 +15,8 @@ interface RouteParams {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
async function proxyRequest(...args) {
|
|
18
|
-
const [req,
|
|
19
|
-
const
|
|
18
|
+
const [req, routeContext] = args as [req: Request, { params: Promise<RouteParams['params']> }];
|
|
19
|
+
const params = await routeContext.params;
|
|
20
20
|
const { searchParams } = new URL(req.url);
|
|
21
21
|
const commerceUrl = settings.commerceUrl;
|
|
22
22
|
|
|
@@ -35,7 +35,7 @@ async function proxyRequest(...args) {
|
|
|
35
35
|
responseType: 'json'
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
const slug = `${
|
|
38
|
+
const slug = `${params.slug.join('/')}/`;
|
|
39
39
|
const options_ = JSON.parse(
|
|
40
40
|
decodeURIComponent((searchParams.get('options') as string) ?? '{}')
|
|
41
41
|
);
|
package/api/form.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getFormData } from '@akinon/next/data/server';
|
|
3
|
+
|
|
4
|
+
interface FormRouteParams {
|
|
5
|
+
params: Promise<{
|
|
6
|
+
id: string[];
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface FormConfig {
|
|
11
|
+
pk: number;
|
|
12
|
+
is_active: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type SubmissionData = Record<string, string | File>;
|
|
16
|
+
|
|
17
|
+
export async function POST(request: NextRequest, { params }: FormRouteParams) {
|
|
18
|
+
try {
|
|
19
|
+
const { id } = await params;
|
|
20
|
+
const formId = id?.[0];
|
|
21
|
+
|
|
22
|
+
if (!formId || isNaN(Number(formId))) {
|
|
23
|
+
return NextResponse.json(
|
|
24
|
+
{ error: 'Valid form ID is required' },
|
|
25
|
+
{ status: 400 }
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const formConfig: FormConfig | null = await getFormData({
|
|
30
|
+
pk: Number(formId)
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!formConfig || !formConfig.is_active) {
|
|
34
|
+
return NextResponse.json(
|
|
35
|
+
{ error: 'Form not found or inactive' },
|
|
36
|
+
{ status: 404 }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const formData = await request.formData();
|
|
41
|
+
const submissionData: SubmissionData = Object.fromEntries(
|
|
42
|
+
formData.entries()
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return await processFormSubmission(formConfig, submissionData);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Form submission error:', error);
|
|
48
|
+
|
|
49
|
+
return NextResponse.json(
|
|
50
|
+
{ error: 'Internal server error' },
|
|
51
|
+
{ status: 500 }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function processFormSubmission(
|
|
57
|
+
formConfig: FormConfig,
|
|
58
|
+
data: SubmissionData
|
|
59
|
+
) {
|
|
60
|
+
const backendUrl = process.env.SERVICE_BACKEND_URL;
|
|
61
|
+
if (!backendUrl) {
|
|
62
|
+
throw new Error('SERVICE_BACKEND_URL is not defined');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const url = `${backendUrl}/forms/${formConfig.pk}/generate`;
|
|
66
|
+
|
|
67
|
+
const submissionPayload = {
|
|
68
|
+
...data
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const response = await fetch(url, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { 'Content-Type': 'application/json' },
|
|
74
|
+
body: JSON.stringify(submissionPayload)
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const body = await response.text();
|
|
78
|
+
|
|
79
|
+
return new NextResponse(body, {
|
|
80
|
+
status: response.status,
|
|
81
|
+
headers: {
|
|
82
|
+
'Content-Type': response.headers.get('content-type') ?? 'application/json'
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -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,53 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getProductData } from '@akinon/next/data/server';
|
|
3
|
+
|
|
4
|
+
export async function GET(request: NextRequest) {
|
|
5
|
+
try {
|
|
6
|
+
const { searchParams } = new URL(request.url);
|
|
7
|
+
const pksParam = searchParams.get('pks');
|
|
8
|
+
|
|
9
|
+
if (!pksParam) {
|
|
10
|
+
return NextResponse.json(
|
|
11
|
+
{ error: 'pks parameter required' },
|
|
12
|
+
{ status: 400 }
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const pks = pksParam.split(',').map(Number).filter(Boolean);
|
|
17
|
+
|
|
18
|
+
if (pks.length === 0) {
|
|
19
|
+
return NextResponse.json({ error: 'Invalid pks' }, { status: 400 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const results = await Promise.all(
|
|
23
|
+
pks.map(async (pk) => {
|
|
24
|
+
try {
|
|
25
|
+
const { breadcrumbData } = await getProductData({ pk });
|
|
26
|
+
|
|
27
|
+
const categoryIds =
|
|
28
|
+
breadcrumbData
|
|
29
|
+
?.map((item: any) => item.extra_context?.attributes?.category_id)
|
|
30
|
+
.filter(Boolean) || [];
|
|
31
|
+
|
|
32
|
+
return { pk, categoryIds };
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(`Error fetching product ${pk}:`, error);
|
|
35
|
+
return { pk, categoryIds: [] };
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const mapping: Record<string, number[]> = {};
|
|
41
|
+
results.forEach(({ pk, categoryIds }) => {
|
|
42
|
+
mapping[String(pk)] = categoryIds;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return NextResponse.json(mapping);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error in product-categories API:', error);
|
|
48
|
+
return NextResponse.json(
|
|
49
|
+
{ error: 'Internal server error' },
|
|
50
|
+
{ status: 500 }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -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
|
+
}
|