@akinon/next 2.0.0-beta.2 → 2.0.0-beta.20
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/.eslintrc.js +12 -0
- package/CHANGELOG.md +377 -7
- package/__tests__/next-config.test.ts +83 -0
- package/__tests__/tsconfig.json +23 -0
- package/api/auth.ts +133 -44
- package/api/barcode-search.ts +59 -0
- package/api/cache.ts +41 -5
- package/api/client.ts +21 -4
- 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/assets/styles/index.scss +84 -0
- package/babel.config.js +6 -0
- package/bin/pz-generate-routes.js +115 -0
- package/bin/pz-prebuild.js +1 -0
- package/bin/pz-predev.js +1 -0
- package/bin/pz-run-tests.js +99 -0
- package/bin/run-prebuild-tests.js +46 -0
- package/components/accordion.tsx +20 -5
- package/components/button.tsx +51 -36
- package/components/client-root.tsx +138 -2
- package/components/file-input.tsx +65 -3
- package/components/index.ts +1 -0
- package/components/input.tsx +1 -1
- package/components/link.tsx +46 -16
- package/components/logger-popup.tsx +213 -0
- package/components/modal.tsx +32 -16
- package/components/plugin-module.tsx +62 -3
- package/components/price.tsx +2 -2
- package/components/select.tsx +1 -1
- package/components/selected-payment-option-view.tsx +21 -0
- package/components/theme-editor/blocks/accordion-block.tsx +136 -0
- package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
- package/components/theme-editor/blocks/button-block.tsx +593 -0
- package/components/theme-editor/blocks/counter-block.tsx +348 -0
- package/components/theme-editor/blocks/divider-block.tsx +20 -0
- package/components/theme-editor/blocks/embed-block.tsx +208 -0
- package/components/theme-editor/blocks/group-block.tsx +116 -0
- package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
- package/components/theme-editor/blocks/icon-block.tsx +230 -0
- package/components/theme-editor/blocks/image-block.tsx +137 -0
- package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
- package/components/theme-editor/blocks/input-block.tsx +123 -0
- package/components/theme-editor/blocks/link-block.tsx +216 -0
- package/components/theme-editor/blocks/lottie-block.tsx +325 -0
- package/components/theme-editor/blocks/map-block.tsx +89 -0
- package/components/theme-editor/blocks/slider-block.tsx +595 -0
- package/components/theme-editor/blocks/tab-block.tsx +10 -0
- package/components/theme-editor/blocks/text-block.tsx +52 -0
- package/components/theme-editor/blocks/video-block.tsx +122 -0
- package/components/theme-editor/components/action-toolbar.tsx +305 -0
- package/components/theme-editor/components/designer-overlay.tsx +74 -0
- package/components/theme-editor/components/with-designer-features.tsx +142 -0
- package/components/theme-editor/dynamic-font-loader.tsx +79 -0
- package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
- package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
- package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
- package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
- package/components/theme-editor/placeholder-registry.ts +31 -0
- package/components/theme-editor/sections/before-after-section.tsx +245 -0
- package/components/theme-editor/sections/contact-form-section.tsx +563 -0
- package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
- package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
- package/components/theme-editor/sections/divider-section.tsx +62 -0
- package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
- package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
- package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
- package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
- package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
- package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
- package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
- package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
- package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
- package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
- package/components/theme-editor/sections/section-wrapper.tsx +135 -0
- package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
- package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
- package/components/theme-editor/sections/tabs-section.tsx +578 -0
- package/components/theme-editor/theme-block.tsx +102 -0
- package/components/theme-editor/theme-placeholder-client.tsx +218 -0
- package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
- package/components/theme-editor/theme-placeholder.tsx +288 -0
- package/components/theme-editor/theme-section.tsx +1224 -0
- package/components/theme-editor/theme-settings-context.tsx +13 -0
- package/components/theme-editor/utils/index.ts +792 -0
- package/components/theme-editor/utils/iterator-utils.ts +234 -0
- package/components/theme-editor/utils/publish-window.ts +86 -0
- package/components/theme-editor/utils/visibility-rules.ts +188 -0
- package/data/client/account.ts +17 -2
- package/data/client/api.ts +2 -0
- package/data/client/basket.ts +66 -5
- package/data/client/checkout.ts +391 -99
- package/data/client/misc.ts +38 -2
- package/data/client/product.ts +19 -2
- package/data/client/user.ts +16 -8
- package/data/server/category.ts +11 -9
- package/data/server/flatpage.ts +11 -4
- package/data/server/form.ts +15 -4
- package/data/server/landingpage.ts +11 -4
- package/data/server/list.ts +5 -4
- package/data/server/menu.ts +11 -3
- package/data/server/product.ts +111 -55
- package/data/server/seo.ts +14 -4
- package/data/server/special-page.ts +5 -4
- package/data/server/widget.ts +90 -5
- package/data/urls.ts +16 -5
- package/hocs/client/with-segment-defaults.tsx +2 -2
- package/hocs/server/with-segment-defaults.tsx +65 -20
- package/hooks/index.ts +4 -0
- package/hooks/use-localization.ts +24 -10
- package/hooks/use-logger-context.tsx +114 -0
- package/hooks/use-logger.ts +92 -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/hooks/use-router.ts +51 -14
- package/hooks/use-sentry-uncaught-errors.ts +24 -0
- package/instrumentation/index.ts +10 -1
- package/instrumentation/node.ts +2 -20
- package/jest.config.js +25 -0
- package/lib/cache-handler.mjs +534 -16
- package/lib/cache.ts +272 -37
- package/localization/index.ts +2 -1
- package/localization/provider.tsx +2 -5
- package/middlewares/bfcache-headers.ts +18 -0
- 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 +360 -215
- package/middlewares/index.ts +10 -2
- package/middlewares/locale.ts +34 -11
- package/middlewares/masterpass-rest-callback.ts +230 -0
- package/middlewares/oauth-login.ts +200 -57
- 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 +11 -1
- package/middlewares/wallet-complete-redirection.ts +206 -0
- package/package.json +25 -10
- package/plugins.d.ts +19 -4
- package/plugins.js +10 -1
- package/redux/actions.ts +47 -0
- package/redux/middlewares/checkout.ts +63 -138
- package/redux/middlewares/index.ts +14 -10
- package/redux/middlewares/pre-order/address.ts +7 -2
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/delivery-option.ts +7 -1
- package/redux/middlewares/pre-order/index.ts +16 -10
- package/redux/middlewares/pre-order/installment-option.ts +8 -1
- package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
- package/redux/middlewares/pre-order/payment-option.ts +7 -1
- package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
- package/redux/middlewares/pre-order/redirection.ts +8 -2
- package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
- package/redux/middlewares/pre-order/shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/shipping-step.ts +5 -1
- package/redux/reducers/checkout.ts +23 -3
- package/redux/reducers/index.ts +11 -3
- package/redux/reducers/root.ts +7 -2
- package/redux/reducers/widget.ts +80 -0
- package/sentry/index.ts +69 -13
- package/tailwind/content.js +16 -0
- package/types/commerce/account.ts +5 -1
- package/types/commerce/checkout.ts +35 -1
- package/types/commerce/widget.ts +33 -0
- package/types/index.ts +101 -6
- package/types/next-auth.d.ts +2 -2
- package/types/widget.ts +80 -0
- package/utils/app-fetch.ts +7 -2
- package/utils/generate-commerce-search-params.ts +3 -2
- package/utils/get-checkout-path.ts +3 -0
- package/utils/get-root-hostname.ts +28 -0
- package/utils/index.ts +64 -10
- package/utils/localization.ts +4 -0
- package/utils/mobile-3d-iframe.ts +8 -2
- package/utils/override-middleware.ts +7 -12
- package/utils/pz-segments.ts +92 -0
- package/utils/redirect-ignore.ts +35 -0
- package/utils/redirect.ts +9 -3
- package/utils/redirection-iframe.ts +8 -2
- package/utils/widget-styles.ts +107 -0
- package/views/error-page.tsx +93 -0
- package/with-pz-config.js +13 -6
package/api/auth.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
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';
|
|
7
6
|
import { urlLocaleMatcherRegex } from '../utils';
|
|
8
7
|
import logger from '@akinon/next/utils/log';
|
|
9
8
|
import { AuthError } from '../types';
|
|
9
|
+
import getRootHostname from '../utils/get-root-hostname';
|
|
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
|
+
}
|
|
10
22
|
|
|
11
23
|
async function getCurrentUser(sessionId: string, currency = '') {
|
|
12
|
-
const
|
|
24
|
+
const reqHeaders = {
|
|
13
25
|
'Content-Type': 'application/json',
|
|
14
26
|
Cookie: `osessionid=${sessionId}`,
|
|
15
27
|
'x-currency': currency
|
|
@@ -17,7 +29,7 @@ async function getCurrentUser(sessionId: string, currency = '') {
|
|
|
17
29
|
|
|
18
30
|
const currentUser = await (
|
|
19
31
|
await fetch(URLS.user.currentUser, {
|
|
20
|
-
headers
|
|
32
|
+
headers: reqHeaders
|
|
21
33
|
})
|
|
22
34
|
).json();
|
|
23
35
|
|
|
@@ -40,15 +52,18 @@ async function getCurrentUser(sessionId: string, currency = '') {
|
|
|
40
52
|
};
|
|
41
53
|
}
|
|
42
54
|
|
|
43
|
-
|
|
55
|
+
type CustomNextAuthOptions = () => Partial<NextAuthConfig>;
|
|
56
|
+
|
|
57
|
+
const getDefaultAuthConfig = (): NextAuthConfig => {
|
|
44
58
|
return {
|
|
45
59
|
providers: [
|
|
46
|
-
|
|
60
|
+
Credentials({
|
|
47
61
|
id: 'oauth',
|
|
48
62
|
name: 'credentials',
|
|
49
63
|
credentials: {},
|
|
50
64
|
authorize: async (credentials) => {
|
|
51
|
-
const
|
|
65
|
+
const cookieStore = await cookies();
|
|
66
|
+
const sessionId = cookieStore.get('osessionid')?.value;
|
|
52
67
|
|
|
53
68
|
if (!sessionId) {
|
|
54
69
|
return null;
|
|
@@ -56,12 +71,12 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
56
71
|
|
|
57
72
|
const currentUser = await getCurrentUser(
|
|
58
73
|
sessionId,
|
|
59
|
-
|
|
74
|
+
cookieStore.get('pz-currency')?.value ?? ''
|
|
60
75
|
);
|
|
61
76
|
return currentUser;
|
|
62
77
|
}
|
|
63
78
|
}),
|
|
64
|
-
|
|
79
|
+
Credentials({
|
|
65
80
|
id: 'default',
|
|
66
81
|
name: 'credentials',
|
|
67
82
|
credentials: {
|
|
@@ -76,32 +91,58 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
76
91
|
captchaValidated: {}
|
|
77
92
|
},
|
|
78
93
|
authorize: async (credentials) => {
|
|
79
|
-
const
|
|
94
|
+
const cookieStore = await cookies();
|
|
95
|
+
const headerStore = await headers();
|
|
96
|
+
|
|
97
|
+
const reqHeaders: HeadersInit = new Headers();
|
|
80
98
|
const language = Settings.localization.locales.find(
|
|
81
|
-
(item) => item.value === credentials.locale
|
|
99
|
+
(item) => item.value === (credentials as any).locale
|
|
82
100
|
).apiValue;
|
|
83
|
-
const userIp =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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(
|
|
91
112
|
'x-app-device',
|
|
92
|
-
|
|
113
|
+
headerStore.get('x-app-device') ?? ''
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
reqHeaders.set(
|
|
117
|
+
'x-frontend-id',
|
|
118
|
+
cookieStore.get('pz-frontend-id')?.value || ''
|
|
93
119
|
);
|
|
94
120
|
|
|
95
121
|
logger.debug('Trying to login/register', {
|
|
96
|
-
formType: credentials.formType,
|
|
122
|
+
formType: (credentials as any).formType,
|
|
97
123
|
userIp
|
|
98
124
|
});
|
|
99
125
|
|
|
126
|
+
const checkCurrentUser = await getCurrentUser(
|
|
127
|
+
cookieStore.get('osessionid')?.value ?? '',
|
|
128
|
+
cookieStore.get('pz-currency')?.value ?? ''
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (checkCurrentUser?.pk) {
|
|
132
|
+
const sessionCookie = reqHeaders
|
|
133
|
+
.get('cookie')
|
|
134
|
+
?.match(/osessionid=\w+/)?.[0]
|
|
135
|
+
.replace(/osessionid=/, '');
|
|
136
|
+
if (sessionCookie) {
|
|
137
|
+
reqHeaders.set('cookie', sessionCookie);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
100
141
|
const apiRequest = await fetch(
|
|
101
|
-
`${Settings.commerceUrl}${user[credentials.formType]}`,
|
|
142
|
+
`${Settings.commerceUrl}${user[(credentials as any).formType]}`,
|
|
102
143
|
{
|
|
103
144
|
method: 'POST',
|
|
104
|
-
headers,
|
|
145
|
+
headers: reqHeaders,
|
|
105
146
|
body: JSON.stringify(credentials)
|
|
106
147
|
}
|
|
107
148
|
);
|
|
@@ -133,12 +174,28 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
133
174
|
|
|
134
175
|
if (sessionId) {
|
|
135
176
|
const maxAge = 30 * 24 * 60 * 60; // 30 days in seconds
|
|
136
|
-
const
|
|
177
|
+
const { localeUrlStrategy } = Settings.localization;
|
|
137
178
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
179
|
+
const fallbackHost =
|
|
180
|
+
headerStore.get('x-forwarded-host') ||
|
|
181
|
+
headerStore.get('host');
|
|
182
|
+
const hostname =
|
|
183
|
+
process.env.NEXT_PUBLIC_URL || `https://${fallbackHost}`;
|
|
184
|
+
const rootHostname =
|
|
185
|
+
localeUrlStrategy === LocaleUrlStrategy.Subdomain
|
|
186
|
+
? getRootHostname(hostname)
|
|
187
|
+
: null;
|
|
188
|
+
|
|
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);
|
|
142
199
|
}
|
|
143
200
|
|
|
144
201
|
if (!response.key) {
|
|
@@ -159,8 +216,8 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
159
216
|
});
|
|
160
217
|
|
|
161
218
|
logger.debug('Captcha required', {
|
|
162
|
-
email: credentials.email,
|
|
163
|
-
formType: credentials.formType
|
|
219
|
+
email: (credentials as any).email,
|
|
220
|
+
formType: (credentials as any).formType
|
|
164
221
|
});
|
|
165
222
|
} else if (apiRequest.status === 202) {
|
|
166
223
|
errors.push({
|
|
@@ -179,13 +236,13 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
179
236
|
}
|
|
180
237
|
|
|
181
238
|
if (errors.length) {
|
|
182
|
-
throw new
|
|
239
|
+
throw new PzCredentialsError(errors);
|
|
183
240
|
}
|
|
184
241
|
}
|
|
185
242
|
|
|
186
243
|
const currentUser = await getCurrentUser(
|
|
187
244
|
sessionId,
|
|
188
|
-
|
|
245
|
+
cookieStore.get('pz-currency')?.value ?? ''
|
|
189
246
|
);
|
|
190
247
|
return currentUser;
|
|
191
248
|
}
|
|
@@ -199,18 +256,20 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
199
256
|
|
|
200
257
|
return token;
|
|
201
258
|
},
|
|
202
|
-
async session({ session,
|
|
203
|
-
session.user = token.user as
|
|
259
|
+
async session({ session, token }) {
|
|
260
|
+
session.user = token.user as any;
|
|
204
261
|
return session;
|
|
205
262
|
},
|
|
206
263
|
async redirect({ url, baseUrl }: { url: string; baseUrl: string }) {
|
|
264
|
+
const headerStore = await headers();
|
|
207
265
|
const pathname = url.startsWith('/') ? url : url.replace(baseUrl, '');
|
|
208
266
|
const pathnameWithoutLocale = pathname.replace(
|
|
209
267
|
urlLocaleMatcherRegex,
|
|
210
268
|
''
|
|
211
269
|
);
|
|
212
270
|
|
|
213
|
-
const localeResults =
|
|
271
|
+
const localeResults = headerStore
|
|
272
|
+
.get('referer')
|
|
214
273
|
?.replace(baseUrl, '')
|
|
215
274
|
?.match(urlLocaleMatcherRegex);
|
|
216
275
|
|
|
@@ -221,11 +280,16 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
221
280
|
signIn: () => {
|
|
222
281
|
logger.debug('Successfully signed in');
|
|
223
282
|
},
|
|
224
|
-
signOut: () => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
+
});
|
|
229
293
|
logger.debug('Successfully signed out');
|
|
230
294
|
}
|
|
231
295
|
},
|
|
@@ -236,8 +300,33 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
236
300
|
};
|
|
237
301
|
};
|
|
238
302
|
|
|
239
|
-
const
|
|
240
|
-
|
|
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,
|
|
311
|
+
providers: [
|
|
312
|
+
...baseConfig.providers,
|
|
313
|
+
...(customConfig.providers || [])
|
|
314
|
+
],
|
|
315
|
+
callbacks: {
|
|
316
|
+
...baseConfig.callbacks,
|
|
317
|
+
...customConfig.callbacks
|
|
318
|
+
},
|
|
319
|
+
events: {
|
|
320
|
+
...baseConfig.events,
|
|
321
|
+
...customConfig.events
|
|
322
|
+
},
|
|
323
|
+
pages: {
|
|
324
|
+
...baseConfig.pages,
|
|
325
|
+
...customConfig.pages
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
return NextAuth(mergedConfig);
|
|
241
330
|
};
|
|
242
331
|
|
|
243
|
-
export default
|
|
332
|
+
export default createAuth;
|
|
@@ -0,0 +1,59 @@
|
|
|
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 barcode = searchParams.get('search_text');
|
|
8
|
+
|
|
9
|
+
if (!barcode) {
|
|
10
|
+
return NextResponse.json(
|
|
11
|
+
{ error: 'Missing search_text parameter (barcode)' },
|
|
12
|
+
{ status: 400 }
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (Settings.commerceUrl === 'default') {
|
|
17
|
+
return NextResponse.json(
|
|
18
|
+
{ error: 'Commerce URL is not configured' },
|
|
19
|
+
{ status: 500 }
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const queryParams = new URLSearchParams();
|
|
24
|
+
queryParams.append('search_text', barcode);
|
|
25
|
+
|
|
26
|
+
searchParams.forEach((value, key) => {
|
|
27
|
+
if (key !== 'search_text') {
|
|
28
|
+
queryParams.append(key, value);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const apiUrl = `${Settings.commerceUrl}/list/?${queryParams.toString()}`;
|
|
33
|
+
|
|
34
|
+
const headers: Record<string, string> = {
|
|
35
|
+
Accept: 'application/json',
|
|
36
|
+
'Content-Type': 'application/json'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const response = await fetch(apiUrl, {
|
|
40
|
+
method: 'GET',
|
|
41
|
+
headers
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
return NextResponse.json(
|
|
46
|
+
{ error: `API request failed with status: ${response.status}` },
|
|
47
|
+
{ status: response.status }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const data = await response.json();
|
|
52
|
+
return NextResponse.json(data);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return NextResponse.json(
|
|
55
|
+
{ error: (error as Error).message },
|
|
56
|
+
{ status: 500 }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
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
|
@@ -5,6 +5,8 @@ import logger from '../utils/log';
|
|
|
5
5
|
import formatCookieString from '../utils/format-cookie-string';
|
|
6
6
|
import cookieParser from 'set-cookie-parser';
|
|
7
7
|
import { cookies } from 'next/headers';
|
|
8
|
+
import getRootHostname from '../utils/get-root-hostname';
|
|
9
|
+
import { LocaleUrlStrategy } from '../localization';
|
|
8
10
|
|
|
9
11
|
interface RouteParams {
|
|
10
12
|
params: {
|
|
@@ -13,8 +15,8 @@ interface RouteParams {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
async function proxyRequest(...args) {
|
|
16
|
-
const [req,
|
|
17
|
-
const
|
|
18
|
+
const [req, routeContext] = args as [req: Request, { params: Promise<RouteParams['params']> }];
|
|
19
|
+
const params = await routeContext.params;
|
|
18
20
|
const { searchParams } = new URL(req.url);
|
|
19
21
|
const commerceUrl = settings.commerceUrl;
|
|
20
22
|
|
|
@@ -33,7 +35,7 @@ async function proxyRequest(...args) {
|
|
|
33
35
|
responseType: 'json'
|
|
34
36
|
};
|
|
35
37
|
|
|
36
|
-
const slug = `${
|
|
38
|
+
const slug = `${params.slug.join('/')}/`;
|
|
37
39
|
const options_ = JSON.parse(
|
|
38
40
|
decodeURIComponent((searchParams.get('options') as string) ?? '{}')
|
|
39
41
|
);
|
|
@@ -191,8 +193,23 @@ async function proxyRequest(...args) {
|
|
|
191
193
|
const responseHeaders: any = {};
|
|
192
194
|
|
|
193
195
|
if (filteredCookies.length > 0) {
|
|
196
|
+
const { localeUrlStrategy } = settings.localization;
|
|
197
|
+
|
|
198
|
+
const fallbackHost =
|
|
199
|
+
req.headers.get('x-forwarded-host') || req.headers.get('host');
|
|
200
|
+
const hostname = process.env.NEXT_PUBLIC_URL || `https://${fallbackHost}`;
|
|
201
|
+
const rootHostname =
|
|
202
|
+
localeUrlStrategy === LocaleUrlStrategy.Subdomain
|
|
203
|
+
? getRootHostname(hostname)
|
|
204
|
+
: null;
|
|
205
|
+
|
|
194
206
|
responseHeaders['set-cookie'] = filteredCookies
|
|
195
|
-
.map(
|
|
207
|
+
.map((cookie) => {
|
|
208
|
+
if (!cookie.domain && rootHostname) {
|
|
209
|
+
cookie.domain = rootHostname;
|
|
210
|
+
}
|
|
211
|
+
return formatCookieString(cookie);
|
|
212
|
+
})
|
|
196
213
|
.join(', ');
|
|
197
214
|
}
|
|
198
215
|
|
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
|
+
}
|