@akinon/next 1.107.0 → 1.108.0-rc.87
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 +1304 -37
- package/__tests__/next-config.test.ts +1 -10
- package/__tests__/redirect.test.ts +319 -0
- package/api/image-proxy.ts +75 -0
- package/api/similar-product-list.ts +84 -0
- package/api/similar-products.ts +120 -0
- package/bin/pz-prebuild.js +1 -0
- package/components/input.tsx +2 -0
- package/components/link.tsx +16 -12
- package/data/client/checkout.ts +4 -2
- package/data/server/basket.ts +72 -0
- package/data/server/category.ts +44 -24
- package/data/server/flatpage.ts +16 -12
- package/data/server/landingpage.ts +16 -12
- package/data/server/list.ts +23 -13
- package/data/server/special-page.ts +16 -12
- package/data/urls.ts +5 -1
- package/hocs/server/with-segment-defaults.tsx +5 -2
- package/hooks/use-localization.ts +2 -3
- package/middlewares/complete-gpay.ts +2 -1
- package/middlewares/complete-masterpass.ts +2 -1
- package/middlewares/locale.ts +9 -1
- package/middlewares/redirection-payment.ts +2 -1
- package/middlewares/saved-card-redirection.ts +2 -1
- package/middlewares/three-d-redirection.ts +2 -1
- package/package.json +2 -2
- package/plugins.d.ts +8 -0
- package/redux/middlewares/checkout.ts +5 -1
- package/types/commerce/order.ts +1 -0
- package/types/index.ts +6 -0
- package/utils/app-fetch.ts +7 -2
- package/utils/redirect.ts +18 -3
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'path';
|
|
2
2
|
import type { NextConfig } from 'next';
|
|
3
|
-
|
|
4
|
-
function findBaseDir() {
|
|
5
|
-
const insideNodeModules = __dirname.includes('node_modules');
|
|
6
|
-
|
|
7
|
-
if (insideNodeModules) {
|
|
8
|
-
return resolve(__dirname, '../../../../');
|
|
9
|
-
} else {
|
|
10
|
-
return resolve(__dirname, '../../../apps/projectzeronext');
|
|
11
|
-
}
|
|
12
|
-
}
|
|
3
|
+
const findBaseDir = require('../utils/find-base-dir');
|
|
13
4
|
|
|
14
5
|
const baseDir = findBaseDir();
|
|
15
6
|
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
jest.mock(
|
|
5
|
+
'countries',
|
|
6
|
+
() => ({
|
|
7
|
+
countries: [
|
|
8
|
+
{ value: 'ae', localizable: true },
|
|
9
|
+
{ value: 'qa', localizable: true },
|
|
10
|
+
{ value: 'sa', localizable: true }
|
|
11
|
+
],
|
|
12
|
+
countryCurrencyMap: { ae: 'aed', qa: 'qar', sa: 'sar' },
|
|
13
|
+
countryShortCodeMap: { ae: 'uae', qa: 'qtr', sa: 'ksa' }
|
|
14
|
+
}),
|
|
15
|
+
{ virtual: true }
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
jest.mock(
|
|
19
|
+
'@theme/routes',
|
|
20
|
+
() => ({
|
|
21
|
+
ROUTES: {
|
|
22
|
+
AUTH: '/auth',
|
|
23
|
+
BASKET: '/basket',
|
|
24
|
+
ACCOUNT_ORDERS: '/account/orders'
|
|
25
|
+
}
|
|
26
|
+
}),
|
|
27
|
+
{ virtual: true }
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
type Locale = {
|
|
31
|
+
value: string;
|
|
32
|
+
label?: string;
|
|
33
|
+
localePath?: string;
|
|
34
|
+
apiValue?: string;
|
|
35
|
+
rtl?: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type Settings = {
|
|
39
|
+
localization: {
|
|
40
|
+
defaultLocaleValue: string;
|
|
41
|
+
localeUrlStrategy: string;
|
|
42
|
+
locales: Locale[];
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function resolveSettingsPath(): string | null {
|
|
47
|
+
const insideNodeModules = __dirname.includes('node_modules');
|
|
48
|
+
|
|
49
|
+
const candidates: string[] = [];
|
|
50
|
+
|
|
51
|
+
if (insideNodeModules) {
|
|
52
|
+
const projectRoot = path.resolve(__dirname, '../../../../');
|
|
53
|
+
candidates.push(path.join(projectRoot, 'src/settings.js'));
|
|
54
|
+
candidates.push(
|
|
55
|
+
path.join(projectRoot, 'apps', 'projectzeronext', 'src', 'settings.js')
|
|
56
|
+
);
|
|
57
|
+
} else {
|
|
58
|
+
const repoRoot = path.resolve(__dirname, '../../..');
|
|
59
|
+
candidates.push(
|
|
60
|
+
path.join(repoRoot, 'apps', 'projectzeronext', 'src', 'settings.js')
|
|
61
|
+
);
|
|
62
|
+
candidates.push(path.join(repoRoot, 'src', 'settings.js'));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const p of candidates) {
|
|
66
|
+
if (fs.existsSync(p)) return p;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function loadProjectSettings(): Settings {
|
|
73
|
+
const settingsPath = resolveSettingsPath();
|
|
74
|
+
if (settingsPath) {
|
|
75
|
+
const settings: any = require(settingsPath);
|
|
76
|
+
return settings as Settings;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
localization: {
|
|
81
|
+
defaultLocaleValue: 'en',
|
|
82
|
+
localeUrlStrategy: 'hide-default-locale',
|
|
83
|
+
locales: [
|
|
84
|
+
{ value: 'en', label: 'EN' },
|
|
85
|
+
{ value: 'tr', label: 'TR' }
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getUrlPathWithLocaleLocal(
|
|
92
|
+
pathname: string,
|
|
93
|
+
currentLocale: string | undefined,
|
|
94
|
+
settings: Settings
|
|
95
|
+
): string {
|
|
96
|
+
const { defaultLocaleValue, localeUrlStrategy } = settings.localization;
|
|
97
|
+
const effectiveLocale = currentLocale ?? defaultLocaleValue;
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
localeUrlStrategy === 'subdomain' ||
|
|
101
|
+
localeUrlStrategy === 'hide-all-locales'
|
|
102
|
+
) {
|
|
103
|
+
return pathname;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (localeUrlStrategy === 'hide-default-locale') {
|
|
107
|
+
if (effectiveLocale === defaultLocaleValue) {
|
|
108
|
+
return pathname;
|
|
109
|
+
}
|
|
110
|
+
return `/${effectiveLocale}${pathname}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return `/${effectiveLocale}${pathname}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function computeUrlLocaleMatcherRegex(settings: Settings): RegExp {
|
|
117
|
+
const { locales, localeUrlStrategy, defaultLocaleValue } =
|
|
118
|
+
settings.localization;
|
|
119
|
+
|
|
120
|
+
const filtered =
|
|
121
|
+
localeUrlStrategy === 'show-all-locales' ||
|
|
122
|
+
localeUrlStrategy === 'subdomain'
|
|
123
|
+
? locales
|
|
124
|
+
: locales.filter((l: any) => l.value !== defaultLocaleValue);
|
|
125
|
+
|
|
126
|
+
return new RegExp(
|
|
127
|
+
`^/(${filtered.map((l: any) => l.value).join('|')})(?=/|$)`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function redirectMinimal(
|
|
132
|
+
destPath: string,
|
|
133
|
+
pageUrlInput: string,
|
|
134
|
+
settings: Settings
|
|
135
|
+
): string {
|
|
136
|
+
const regex = computeUrlLocaleMatcherRegex(settings);
|
|
137
|
+
const pageUrl = new URL(pageUrlInput);
|
|
138
|
+
let currentLocaleValue = settings.localization.defaultLocaleValue;
|
|
139
|
+
|
|
140
|
+
const match = pageUrl.pathname.match(regex);
|
|
141
|
+
if (match && match[0]) {
|
|
142
|
+
currentLocaleValue = match[0].replace('/', '');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const redirectUrlWithLocale = getUrlPathWithLocaleLocal(
|
|
146
|
+
destPath,
|
|
147
|
+
currentLocaleValue,
|
|
148
|
+
settings
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const callbackPath = pageUrl.pathname.replace(regex, '');
|
|
152
|
+
|
|
153
|
+
return `${redirectUrlWithLocale}?callbackUrl=${callbackPath}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
describe('Redirect utility functional tests', () => {
|
|
157
|
+
const settings = loadProjectSettings();
|
|
158
|
+
|
|
159
|
+
function buildPageUrl(pathAfter: string, locale: string): string {
|
|
160
|
+
const { defaultLocaleValue, localeUrlStrategy } = settings.localization;
|
|
161
|
+
const isDefault = locale === defaultLocaleValue;
|
|
162
|
+
let prefix = '';
|
|
163
|
+
if (isDefault) {
|
|
164
|
+
prefix = localeUrlStrategy === 'show-all-locales' ? `/${locale}` : '';
|
|
165
|
+
} else {
|
|
166
|
+
prefix = `/${locale}`;
|
|
167
|
+
}
|
|
168
|
+
return `https://example.com${prefix}${pathAfter}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
describe('getUrlPathWithLocale functional tests', () => {
|
|
172
|
+
it('should respect project localeUrlStrategy dynamically', () => {
|
|
173
|
+
const { defaultLocaleValue, locales, localeUrlStrategy } =
|
|
174
|
+
settings.localization;
|
|
175
|
+
|
|
176
|
+
const nonDefault = locales.find((l) => l.value !== defaultLocaleValue);
|
|
177
|
+
|
|
178
|
+
const resultDefault = getUrlPathWithLocaleLocal(
|
|
179
|
+
'/login',
|
|
180
|
+
defaultLocaleValue,
|
|
181
|
+
settings
|
|
182
|
+
);
|
|
183
|
+
const resultNonDefault = nonDefault
|
|
184
|
+
? getUrlPathWithLocaleLocal('/login', nonDefault.value, settings)
|
|
185
|
+
: null;
|
|
186
|
+
const resultUndefined = getUrlPathWithLocaleLocal(
|
|
187
|
+
'/login',
|
|
188
|
+
undefined,
|
|
189
|
+
settings
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (localeUrlStrategy === 'hide-default-locale') {
|
|
193
|
+
expect(resultDefault).toBe('/login');
|
|
194
|
+
if (resultNonDefault && nonDefault) {
|
|
195
|
+
expect(resultNonDefault).toBe(`/${nonDefault.value}/login`);
|
|
196
|
+
}
|
|
197
|
+
expect(resultUndefined).toBe('/login');
|
|
198
|
+
} else if (localeUrlStrategy === 'show-all-locales') {
|
|
199
|
+
expect(resultDefault).toBe(`/${defaultLocaleValue}/login`);
|
|
200
|
+
if (resultNonDefault && nonDefault) {
|
|
201
|
+
expect(resultNonDefault).toBe(`/${nonDefault.value}/login`);
|
|
202
|
+
}
|
|
203
|
+
expect(resultUndefined).toBe(`/${defaultLocaleValue}/login`);
|
|
204
|
+
} else if (localeUrlStrategy === 'hide-all-locales') {
|
|
205
|
+
expect(resultDefault).toBe('/login');
|
|
206
|
+
if (resultNonDefault) {
|
|
207
|
+
expect(resultNonDefault).toBe('/login');
|
|
208
|
+
}
|
|
209
|
+
expect(resultUndefined).toBe('/login');
|
|
210
|
+
} else if (localeUrlStrategy === 'subdomain') {
|
|
211
|
+
expect(resultDefault).toBe('/login');
|
|
212
|
+
if (resultNonDefault) {
|
|
213
|
+
expect(resultNonDefault).toBe('/login');
|
|
214
|
+
}
|
|
215
|
+
expect(resultUndefined).toBe('/login');
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('redirect function behavior (simulated)', () => {
|
|
221
|
+
it('should redirect with correct URL for default locale (no query preserved)', () => {
|
|
222
|
+
const { defaultLocaleValue } = settings.localization;
|
|
223
|
+
const regex = computeUrlLocaleMatcherRegex(settings);
|
|
224
|
+
const url = buildPageUrl(
|
|
225
|
+
'/profile?tab=settings&view=list#frag',
|
|
226
|
+
defaultLocaleValue
|
|
227
|
+
);
|
|
228
|
+
const matched = new URL(url).pathname.match(regex)?.[0]?.replace('/', '');
|
|
229
|
+
const expectedPath = getUrlPathWithLocaleLocal(
|
|
230
|
+
'/login',
|
|
231
|
+
matched,
|
|
232
|
+
settings
|
|
233
|
+
);
|
|
234
|
+
const out = redirectMinimal('/login', url, settings);
|
|
235
|
+
expect(out).toBe(`${expectedPath}?callbackUrl=/profile`);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should handle non-default locale from path', () => {
|
|
239
|
+
const nonDefault = settings.localization.locales.find(
|
|
240
|
+
(l) => l.value !== settings.localization.defaultLocaleValue
|
|
241
|
+
);
|
|
242
|
+
if (!nonDefault) return;
|
|
243
|
+
const url = buildPageUrl('/products/123', nonDefault.value);
|
|
244
|
+
const regex = computeUrlLocaleMatcherRegex(settings);
|
|
245
|
+
const matched = new URL(url).pathname.match(regex)?.[0]?.replace('/', '');
|
|
246
|
+
const expectedPath = getUrlPathWithLocaleLocal(
|
|
247
|
+
'/auth',
|
|
248
|
+
matched,
|
|
249
|
+
settings
|
|
250
|
+
);
|
|
251
|
+
const out = redirectMinimal('/auth', url, settings);
|
|
252
|
+
expect(out).toBe(`${expectedPath}?callbackUrl=/products/123`);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should use env fallback when header missing (no query preserved)', () => {
|
|
256
|
+
const { defaultLocaleValue } = settings.localization;
|
|
257
|
+
const url = buildPageUrl('/some/path?x=1#y', defaultLocaleValue).replace(
|
|
258
|
+
'https://example.com',
|
|
259
|
+
'https://fallback.com'
|
|
260
|
+
);
|
|
261
|
+
const regex = computeUrlLocaleMatcherRegex(settings);
|
|
262
|
+
const matched = new URL(url).pathname.match(regex)?.[0]?.replace('/', '');
|
|
263
|
+
const expectedPath = getUrlPathWithLocaleLocal(
|
|
264
|
+
'/login',
|
|
265
|
+
matched,
|
|
266
|
+
settings
|
|
267
|
+
);
|
|
268
|
+
const out = redirectMinimal('/login', url, settings);
|
|
269
|
+
expect(out).toBe(`${expectedPath}?callbackUrl=/some/path`);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should throw with invalid URL', () => {
|
|
273
|
+
expect(() => new URL('')).toThrow();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should mention RedirectType usage in implementation', () => {
|
|
277
|
+
const redirectFilePath = path.resolve(__dirname, '../utils/redirect.ts');
|
|
278
|
+
const content = fs.readFileSync(redirectFilePath, 'utf8');
|
|
279
|
+
expect(content.includes('RedirectType')).toBe(true);
|
|
280
|
+
expect(content.includes('nextRedirect(redirectUrl, type)')).toBe(true);
|
|
281
|
+
expect(content.includes('nextRedirect(redirectUrl)')).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('URL locale matcher regex behavior (simulated)', () => {
|
|
286
|
+
it('should match prefixes based on project strategy', () => {
|
|
287
|
+
const { defaultLocaleValue, locales, localeUrlStrategy } =
|
|
288
|
+
settings.localization;
|
|
289
|
+
const urlLocaleMatcherRegex = computeUrlLocaleMatcherRegex(settings);
|
|
290
|
+
|
|
291
|
+
const nonDefault = locales.find((l) => l.value !== defaultLocaleValue);
|
|
292
|
+
|
|
293
|
+
if (localeUrlStrategy === 'show-all-locales') {
|
|
294
|
+
expect(
|
|
295
|
+
urlLocaleMatcherRegex.test(`/${defaultLocaleValue}/profile`)
|
|
296
|
+
).toBe(true);
|
|
297
|
+
} else {
|
|
298
|
+
expect(
|
|
299
|
+
urlLocaleMatcherRegex.test(`/${defaultLocaleValue}/profile`)
|
|
300
|
+
).toBe(false);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (nonDefault) {
|
|
304
|
+
expect(urlLocaleMatcherRegex.test(`/${nonDefault.value}/profile`)).toBe(
|
|
305
|
+
true
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
expect(urlLocaleMatcherRegex.test('/profile')).toBe(false);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should verify redirect utility file exists and has expected structure', () => {
|
|
313
|
+
const redirectFilePath = path.resolve(__dirname, '../utils/redirect.ts');
|
|
314
|
+
const redirectFileContent = fs.readFileSync(redirectFilePath, 'utf8');
|
|
315
|
+
expect(redirectFileContent.includes('export const redirect')).toBe(true);
|
|
316
|
+
expect(redirectFileContent.includes('getUrlPathWithLocale')).toBe(true);
|
|
317
|
+
expect(redirectFileContent.includes('callbackUrl')).toBe(true);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
@@ -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,84 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { cookies } from 'next/headers';
|
|
3
|
+
import Settings from 'settings';
|
|
4
|
+
import { ServerVariables } from '../utils/server-variables';
|
|
5
|
+
|
|
6
|
+
export async function GET(request: NextRequest) {
|
|
7
|
+
try {
|
|
8
|
+
const { searchParams } = new URL(request.url);
|
|
9
|
+
const dynamicFilter = request.headers.get('x-search-dynamic-filter');
|
|
10
|
+
const dynamicExclude = request.headers.get('x-search-dynamic-exclude');
|
|
11
|
+
|
|
12
|
+
const nextCookies = cookies();
|
|
13
|
+
const currency =
|
|
14
|
+
nextCookies.get('pz-currency')?.value || ServerVariables.currency;
|
|
15
|
+
const locale =
|
|
16
|
+
nextCookies.get('pz-locale')?.value || ServerVariables.locale;
|
|
17
|
+
|
|
18
|
+
if (!dynamicFilter && !dynamicExclude) {
|
|
19
|
+
return NextResponse.json(
|
|
20
|
+
{
|
|
21
|
+
error:
|
|
22
|
+
'Missing x-search-dynamic-filter or x-search-dynamic-exclude header'
|
|
23
|
+
},
|
|
24
|
+
{ status: 400 }
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (Settings.commerceUrl === 'default') {
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ error: 'Commerce URL is not configured' },
|
|
31
|
+
{ status: 500 }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const queryString = searchParams.toString();
|
|
36
|
+
const apiUrl = `${Settings.commerceUrl}/list${
|
|
37
|
+
queryString ? `?${queryString}` : ''
|
|
38
|
+
}`;
|
|
39
|
+
|
|
40
|
+
const headers: Record<string, string> = {
|
|
41
|
+
Accept: 'application/json',
|
|
42
|
+
'Content-Type': 'application/json'
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (dynamicFilter) {
|
|
46
|
+
headers['x-search-dynamic-filter'] = dynamicFilter;
|
|
47
|
+
} else if (dynamicExclude) {
|
|
48
|
+
headers['x-search-dynamic-exclude'] = dynamicExclude;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (currency) {
|
|
52
|
+
headers['x-currency'] = currency;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (locale) {
|
|
56
|
+
const currentLocale = Settings.localization.locales.find(
|
|
57
|
+
(l) => l.value === locale
|
|
58
|
+
);
|
|
59
|
+
if (currentLocale) {
|
|
60
|
+
headers['Accept-Language'] = currentLocale.apiValue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const response = await fetch(apiUrl, {
|
|
65
|
+
method: 'GET',
|
|
66
|
+
headers
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
return NextResponse.json(
|
|
71
|
+
{ error: `API request failed with status: ${response.status}` },
|
|
72
|
+
{ status: response.status }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
return NextResponse.json(data);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return NextResponse.json(
|
|
80
|
+
{ error: (error as Error).message },
|
|
81
|
+
{ status: 500 }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
const text = searchParams.get('text');
|
|
16
|
+
|
|
17
|
+
if (!url) {
|
|
18
|
+
return errorResponse('URL parameter is required', 400);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (Settings.commerceUrl === 'default') {
|
|
22
|
+
return errorResponse('Commerce URL is not configured', 500);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const apiParams = new URLSearchParams();
|
|
26
|
+
apiParams.append('limit', limit);
|
|
27
|
+
apiParams.append('url', url);
|
|
28
|
+
|
|
29
|
+
if (excludedProductIds) {
|
|
30
|
+
apiParams.append('excluded_product_ids', excludedProductIds);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (text) {
|
|
34
|
+
apiParams.append('text', text);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(apiUrl, {
|
|
41
|
+
method: 'GET',
|
|
42
|
+
headers: {
|
|
43
|
+
Accept: 'application/json'
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const errorText = await response.text();
|
|
49
|
+
return errorResponse(
|
|
50
|
+
errorText || `API request failed with status: ${response.status}`,
|
|
51
|
+
response.status
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const responseText = await response.text();
|
|
56
|
+
|
|
57
|
+
return NextResponse.json(JSON.parse(responseText));
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return errorResponse((error as Error).message, 500);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function POST(request: Request) {
|
|
64
|
+
const { searchParams } = new URL(request.url);
|
|
65
|
+
const limit = searchParams.get('limit') || '20';
|
|
66
|
+
|
|
67
|
+
if (Settings.commerceUrl === 'default') {
|
|
68
|
+
return errorResponse('Commerce URL is not configured', 500);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let requestBody;
|
|
72
|
+
try {
|
|
73
|
+
requestBody = await request.json();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return errorResponse('Invalid JSON in request body', 400);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!requestBody.image) {
|
|
79
|
+
return errorResponse('Image data is required in request body', 400);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const apiParams = new URLSearchParams();
|
|
83
|
+
apiParams.append('limit', limit);
|
|
84
|
+
|
|
85
|
+
const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
|
|
86
|
+
|
|
87
|
+
const bodyData: any = { image: requestBody.image };
|
|
88
|
+
|
|
89
|
+
if (requestBody.excluded_product_ids) {
|
|
90
|
+
bodyData.excluded_product_ids = requestBody.excluded_product_ids;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (requestBody.text) {
|
|
94
|
+
bodyData.text = requestBody.text;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(apiUrl, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: {
|
|
101
|
+
'Content-Type': 'application/json',
|
|
102
|
+
Accept: 'application/json'
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify(bodyData)
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const errorText = await response.text();
|
|
109
|
+
return errorResponse(
|
|
110
|
+
errorText || `API request failed with status: ${response.status}`,
|
|
111
|
+
response.status
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const responseData = await response.json();
|
|
116
|
+
return NextResponse.json(responseData);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return errorResponse((error as Error).message, 500);
|
|
119
|
+
}
|
|
120
|
+
}
|
package/bin/pz-prebuild.js
CHANGED
package/components/input.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import { forwardRef, FocusEvent, useState, Ref } from 'react';
|
|
3
3
|
import { Controller } from 'react-hook-form';
|
|
4
|
+
|
|
5
|
+
// @ts-ignore
|
|
4
6
|
import { PatternFormat, PatternFormatProps } from 'react-number-format';
|
|
5
7
|
import { InputProps } from '../types';
|
|
6
8
|
import { twMerge } from 'tailwind-merge';
|
package/components/link.tsx
CHANGED
|
@@ -10,7 +10,9 @@ type LinkProps = Omit<
|
|
|
10
10
|
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
11
11
|
keyof NextLinkProps
|
|
12
12
|
> &
|
|
13
|
-
NextLinkProps
|
|
13
|
+
NextLinkProps & {
|
|
14
|
+
href: string;
|
|
15
|
+
};
|
|
14
16
|
|
|
15
17
|
export const Link = ({ children, href, ...rest }: LinkProps) => {
|
|
16
18
|
const { locale, defaultLocaleValue, localeUrlStrategy } = useLocalization();
|
|
@@ -26,19 +28,21 @@ export const Link = ({ children, href, ...rest }: LinkProps) => {
|
|
|
26
28
|
return href;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
if (typeof href === 'string' && !href.startsWith('http')) {
|
|
32
|
+
const pathnameWithoutLocale = href.replace(urlLocaleMatcherRegex, '');
|
|
33
|
+
const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
|
|
34
|
+
|
|
35
|
+
if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
|
|
36
|
+
return hrefWithLocale;
|
|
37
|
+
} else if (
|
|
38
|
+
localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
|
|
39
|
+
locale !== defaultLocaleValue
|
|
40
|
+
) {
|
|
41
|
+
return hrefWithLocale;
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
return href
|
|
45
|
+
return href;
|
|
42
46
|
}, [href, defaultLocaleValue, locale, localeUrlStrategy]);
|
|
43
47
|
|
|
44
48
|
return (
|
package/data/client/checkout.ts
CHANGED
|
@@ -35,8 +35,10 @@ import {
|
|
|
35
35
|
|
|
36
36
|
interface CheckoutResponse {
|
|
37
37
|
pre_order?: PreOrder;
|
|
38
|
-
errors
|
|
39
|
-
non_field_errors
|
|
38
|
+
errors?: {
|
|
39
|
+
non_field_errors?: string;
|
|
40
|
+
sample_products?: string[];
|
|
41
|
+
[key: string]: string | string[] | undefined;
|
|
40
42
|
};
|
|
41
43
|
context_list?: CheckoutContext[];
|
|
42
44
|
template_name?: string;
|