@akinon/next 1.107.0-rc.86 → 1.108.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -1299
- package/__tests__/next-config.test.ts +10 -1
- package/bin/pz-prebuild.js +0 -1
- package/components/input.tsx +0 -2
- package/components/link.tsx +12 -16
- package/components/plugin-module.tsx +2 -0
- package/components/selected-payment-option-view.tsx +9 -0
- package/data/client/checkout.ts +2 -4
- package/data/server/category.ts +24 -44
- package/data/server/flatpage.ts +12 -16
- package/data/server/landingpage.ts +12 -16
- package/data/server/list.ts +13 -23
- package/data/server/special-page.ts +12 -16
- package/data/urls.ts +1 -5
- package/hocs/server/with-segment-defaults.tsx +2 -5
- package/hooks/use-localization.ts +3 -2
- package/middlewares/complete-gpay.ts +1 -2
- package/middlewares/complete-masterpass.ts +1 -2
- package/middlewares/complete-wallet.ts +179 -0
- package/middlewares/default.ts +3 -0
- package/middlewares/index.ts +2 -0
- package/middlewares/locale.ts +1 -10
- package/middlewares/redirection-payment.ts +1 -2
- package/middlewares/saved-card-redirection.ts +1 -2
- package/middlewares/three-d-redirection.ts +1 -2
- package/package.json +2 -2
- package/plugins.d.ts +11 -7
- package/plugins.js +1 -0
- package/redux/middlewares/checkout.ts +1 -5
- package/redux/middlewares/index.ts +6 -7
- package/redux/reducers/index.ts +3 -1
- package/types/commerce/order.ts +0 -1
- package/types/index.ts +0 -6
- package/utils/app-fetch.ts +2 -7
- package/utils/redirect.ts +3 -22
- package/__tests__/redirect.test.ts +0 -319
- package/api/image-proxy.ts +0 -75
- package/api/similar-product-list.ts +0 -84
- package/api/similar-products.ts +0 -120
- package/data/server/basket.ts +0 -72
package/middlewares/locale.ts
CHANGED
|
@@ -23,15 +23,7 @@ const getMatchedLocale = (pathname: string, req: PzNextRequest) => {
|
|
|
23
23
|
);
|
|
24
24
|
|
|
25
25
|
if (subDomainLocaleMatched && subDomainLocaleMatched[0]) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const isValidSubdomainLocale = settings.localization.locales.find(
|
|
29
|
-
(l) => l.value === subdomainLocale
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
if (isValidSubdomainLocale) {
|
|
33
|
-
matchedLocale = subdomainLocale;
|
|
34
|
-
}
|
|
26
|
+
matchedLocale = subDomainLocaleMatched[0].slice(1);
|
|
35
27
|
}
|
|
36
28
|
}
|
|
37
29
|
}
|
|
@@ -77,7 +69,6 @@ const withLocale =
|
|
|
77
69
|
req.method === 'GET'
|
|
78
70
|
) {
|
|
79
71
|
// Redirect to existing or default locale
|
|
80
|
-
|
|
81
72
|
url.pathname = getUrlPathWithLocale(
|
|
82
73
|
url.pathname,
|
|
83
74
|
req.cookies.get('pz-locale')?.value
|
|
@@ -149,8 +149,7 @@ const withRedirectionPayment =
|
|
|
149
149
|
logger.info('Redirecting to order success page', {
|
|
150
150
|
middleware: 'redirection-payment',
|
|
151
151
|
redirectUrlWithLocale,
|
|
152
|
-
ip
|
|
153
|
-
setCookie: request.headers.get('set-cookie')
|
|
152
|
+
ip
|
|
154
153
|
});
|
|
155
154
|
|
|
156
155
|
// Using POST method while redirecting causes an error,
|
|
@@ -149,8 +149,7 @@ const withSavedCardRedirection =
|
|
|
149
149
|
logger.info('Redirecting to order success page', {
|
|
150
150
|
middleware: 'saved-card-redirection',
|
|
151
151
|
redirectUrlWithLocale,
|
|
152
|
-
ip
|
|
153
|
-
setCookie: request.headers.get('set-cookie')
|
|
152
|
+
ip
|
|
154
153
|
});
|
|
155
154
|
|
|
156
155
|
// Using POST method while redirecting causes an error,
|
|
@@ -148,8 +148,7 @@ const withThreeDRedirection =
|
|
|
148
148
|
logger.info('Redirecting to order success page', {
|
|
149
149
|
middleware: 'three-d-redirection',
|
|
150
150
|
redirectUrlWithLocale,
|
|
151
|
-
ip
|
|
152
|
-
setCookie: request.headers.get('set-cookie')
|
|
151
|
+
ip
|
|
153
152
|
});
|
|
154
153
|
|
|
155
154
|
// Using POST method while redirecting causes an 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.
|
|
4
|
+
"version": "1.108.0-rc.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"set-cookie-parser": "2.6.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@akinon/eslint-plugin-projectzero": "1.
|
|
38
|
+
"@akinon/eslint-plugin-projectzero": "1.108.0-rc.0",
|
|
39
39
|
"@babel/core": "7.26.10",
|
|
40
40
|
"@babel/preset-env": "7.26.9",
|
|
41
41
|
"@babel/preset-typescript": "7.27.0",
|
package/plugins.d.ts
CHANGED
|
@@ -33,12 +33,16 @@ declare module '@akinon/pz-saved-card' {
|
|
|
33
33
|
|
|
34
34
|
declare module '@akinon/pz-apple-pay' {}
|
|
35
35
|
|
|
36
|
-
declare module '@akinon/pz-
|
|
36
|
+
declare module '@akinon/pz-cybersource-uc/src/redux/middleware' {
|
|
37
|
+
export default middleware as any;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
declare module '@akinon/pz-cybersource-uc/src/redux/reducer' {
|
|
41
|
+
export default reducer as any;
|
|
42
|
+
}
|
|
37
43
|
|
|
38
|
-
declare module '@akinon/pz-
|
|
39
|
-
export const
|
|
40
|
-
export const SimilarProductsFilterSidebar: any;
|
|
41
|
-
export const SimilarProductsResultsGrid: any;
|
|
42
|
-
export const SimilarProductsPlugin: any;
|
|
43
|
-
export const SimilarProductsButtonPlugin: any;
|
|
44
|
+
declare module '@akinon/pz-cybersource-uc' {
|
|
45
|
+
export const CyberSourceUcPaymentOption: any;
|
|
44
46
|
}
|
|
47
|
+
|
|
48
|
+
declare module '@akinon/pz-flow-payment' {}
|
package/plugins.js
CHANGED
|
@@ -51,11 +51,7 @@ export const errorMiddleware: Middleware = ({ dispatch }: MiddlewareParams) => {
|
|
|
51
51
|
const result: CheckoutResult = next(action);
|
|
52
52
|
const errors = result?.payload?.errors;
|
|
53
53
|
|
|
54
|
-
if (
|
|
55
|
-
!!errors &&
|
|
56
|
-
((typeof errors === 'object' && Object.keys(errors).length > 0) ||
|
|
57
|
-
(Array.isArray(errors) && errors.length > 0))
|
|
58
|
-
) {
|
|
54
|
+
if (errors) {
|
|
59
55
|
dispatch(setErrors(errors));
|
|
60
56
|
}
|
|
61
57
|
|
|
@@ -18,13 +18,11 @@ import logger from '@akinon/next/utils/log';
|
|
|
18
18
|
|
|
19
19
|
// Plugin middlewares
|
|
20
20
|
import { savedCardMiddleware } from '@akinon/pz-saved-card';
|
|
21
|
+
import cyberSourceUcMiddleware from '@akinon/pz-cybersource-uc/src/redux/middleware';
|
|
21
22
|
|
|
22
|
-
export const rtkQueryResponseHandler: Middleware =
|
|
23
|
-
(
|
|
24
|
-
|
|
25
|
-
(action) => {
|
|
26
|
-
return next(action);
|
|
27
|
-
};
|
|
23
|
+
export const rtkQueryResponseHandler: Middleware = () => (next) => (action) => {
|
|
24
|
+
return next(action);
|
|
25
|
+
};
|
|
28
26
|
|
|
29
27
|
export const rtkQueryErrorHandler: Middleware =
|
|
30
28
|
() => (next) => async (action) => {
|
|
@@ -56,7 +54,8 @@ const middlewares = [
|
|
|
56
54
|
contextListMiddleware,
|
|
57
55
|
hepsiPayMiddleware,
|
|
58
56
|
walletPaymentMiddleware,
|
|
59
|
-
savedCardMiddleware
|
|
57
|
+
savedCardMiddleware,
|
|
58
|
+
cyberSourceUcMiddleware
|
|
60
59
|
];
|
|
61
60
|
|
|
62
61
|
// Filter out any non-function middleware to ensure all middlewares are valid and executable
|
package/redux/reducers/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { api } from '../../data/client/api';
|
|
|
8
8
|
import { masterpassReducer } from '@akinon/pz-masterpass';
|
|
9
9
|
import { otpReducer } from '@akinon/pz-otp';
|
|
10
10
|
import { savedCardReducer } from '@akinon/pz-saved-card';
|
|
11
|
+
import cyberSourceUcReducer from '@akinon/pz-cybersource-uc/src/redux/reducer';
|
|
11
12
|
|
|
12
13
|
const fallbackReducer = (state = {}) => state;
|
|
13
14
|
|
|
@@ -19,7 +20,8 @@ const reducers = {
|
|
|
19
20
|
header: headerReducer,
|
|
20
21
|
masterpass: masterpassReducer || fallbackReducer,
|
|
21
22
|
otp: otpReducer || fallbackReducer,
|
|
22
|
-
savedCard: savedCardReducer || fallbackReducer
|
|
23
|
+
savedCard: savedCardReducer || fallbackReducer,
|
|
24
|
+
cybersource_uc: cyberSourceUcReducer || fallbackReducer
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export default reducers;
|
package/types/commerce/order.ts
CHANGED
package/types/index.ts
CHANGED
|
@@ -83,12 +83,6 @@ export interface Settings {
|
|
|
83
83
|
};
|
|
84
84
|
usePrettyUrlRoute?: boolean;
|
|
85
85
|
commerceUrl: string;
|
|
86
|
-
/**
|
|
87
|
-
* This option allows you to track Sentry events on the client side, in addition to server and edge environments.
|
|
88
|
-
*
|
|
89
|
-
* It overrides process.env.NEXT_PUBLIC_SENTRY_DSN and process.env.SENTRY_DSN.
|
|
90
|
-
*/
|
|
91
|
-
sentryDsn?: string;
|
|
92
86
|
redis: {
|
|
93
87
|
defaultExpirationTime: number;
|
|
94
88
|
};
|
package/utils/app-fetch.ts
CHANGED
|
@@ -43,12 +43,12 @@ const appFetch = async <T>({
|
|
|
43
43
|
const requestURL = `${decodeURIComponent(commerceUrl)}${url}`;
|
|
44
44
|
|
|
45
45
|
init.headers = {
|
|
46
|
-
cookie: nextCookies.toString(),
|
|
47
46
|
...(init.headers ?? {}),
|
|
48
47
|
...(ServerVariables.globalHeaders ?? {}),
|
|
49
48
|
'Accept-Language': currentLocale.apiValue,
|
|
50
49
|
'x-currency': currency,
|
|
51
|
-
'x-forwarded-for': ip
|
|
50
|
+
'x-forwarded-for': ip,
|
|
51
|
+
cookie: nextCookies.toString()
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
init.next = {
|
|
@@ -60,11 +60,6 @@ const appFetch = async <T>({
|
|
|
60
60
|
status = req.status;
|
|
61
61
|
logger.debug(`FETCH END ${url}`, { status: req.status, ip });
|
|
62
62
|
|
|
63
|
-
if (!req.ok) {
|
|
64
|
-
const errorMessage = `HTTP ${req.status}: ${req.statusText}`;
|
|
65
|
-
throw new Error(errorMessage);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
63
|
if (responseType === FetchResponseType.JSON) {
|
|
69
64
|
response = (await req.json()) as T;
|
|
70
65
|
} else {
|
package/utils/redirect.ts
CHANGED
|
@@ -1,39 +1,20 @@
|
|
|
1
1
|
import { redirect as nextRedirect, RedirectType } from 'next/navigation';
|
|
2
2
|
import Settings from 'settings';
|
|
3
|
-
import { headers
|
|
3
|
+
import { headers } from 'next/headers';
|
|
4
|
+
import { ServerVariables } from '@akinon/next/utils/server-variables';
|
|
4
5
|
import { getUrlPathWithLocale } from '@akinon/next/utils/localization';
|
|
5
6
|
import { urlLocaleMatcherRegex } from '@akinon/next/utils';
|
|
6
7
|
|
|
7
8
|
export const redirect = (path: string, type?: RedirectType) => {
|
|
8
9
|
const nextHeaders = headers();
|
|
9
|
-
const nextCookies = cookies();
|
|
10
10
|
const pageUrl = new URL(
|
|
11
11
|
nextHeaders.get('pz-url') ?? process.env.NEXT_PUBLIC_URL ?? ''
|
|
12
12
|
);
|
|
13
13
|
|
|
14
|
-
let currentLocaleValue = Settings.localization.defaultLocaleValue;
|
|
15
|
-
const urlLocaleMatch = pageUrl.pathname.match(urlLocaleMatcherRegex);
|
|
16
|
-
|
|
17
|
-
if (urlLocaleMatch && urlLocaleMatch[0]) {
|
|
18
|
-
currentLocaleValue = urlLocaleMatch[0].replace('/', '');
|
|
19
|
-
} else {
|
|
20
|
-
const cookieLocale = nextCookies.get('pz-locale')?.value;
|
|
21
|
-
if (
|
|
22
|
-
cookieLocale &&
|
|
23
|
-
Settings.localization.locales.find((l) => l.value === cookieLocale)
|
|
24
|
-
) {
|
|
25
|
-
currentLocaleValue = cookieLocale;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
14
|
const currentLocale = Settings.localization.locales.find(
|
|
30
|
-
(locale) => locale.value ===
|
|
15
|
+
(locale) => locale.value === ServerVariables.locale
|
|
31
16
|
);
|
|
32
17
|
|
|
33
|
-
if (!currentLocale) {
|
|
34
|
-
currentLocaleValue = Settings.localization.defaultLocaleValue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
18
|
const searchParams = new URLSearchParams(pageUrl.search);
|
|
38
19
|
|
|
39
20
|
const callbackUrl =
|
|
@@ -1,319 +0,0 @@
|
|
|
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
|
-
});
|
package/api/image-proxy.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
|
|
3
|
-
async function proxyImage(imageUrl: string) {
|
|
4
|
-
if (!imageUrl) {
|
|
5
|
-
return NextResponse.json(
|
|
6
|
-
{ success: false, error: 'Missing url parameter' },
|
|
7
|
-
{ status: 400 }
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const imageResponse = await fetch(imageUrl);
|
|
12
|
-
|
|
13
|
-
if (!imageResponse.ok) {
|
|
14
|
-
return NextResponse.json(
|
|
15
|
-
{ success: false, error: 'Failed to fetch image from URL' },
|
|
16
|
-
{ status: 400 }
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const imageBuffer = await imageResponse.arrayBuffer();
|
|
21
|
-
const base64Image = Buffer.from(imageBuffer).toString('base64');
|
|
22
|
-
const contentType = imageResponse.headers.get('content-type') || 'image/jpeg';
|
|
23
|
-
|
|
24
|
-
return { base64Image, contentType, imageBuffer };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function GET(request: NextRequest) {
|
|
28
|
-
try {
|
|
29
|
-
const { searchParams } = new URL(request.url);
|
|
30
|
-
const imageUrl = searchParams.get('url');
|
|
31
|
-
|
|
32
|
-
const result = await proxyImage(imageUrl!);
|
|
33
|
-
if (result instanceof NextResponse) {
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return new NextResponse(result.imageBuffer, {
|
|
38
|
-
status: 200,
|
|
39
|
-
headers: {
|
|
40
|
-
'Content-Type': result.contentType,
|
|
41
|
-
'Access-Control-Allow-Origin': '*',
|
|
42
|
-
'Cache-Control': 'public, max-age=3600'
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error('Image proxy error:', error);
|
|
47
|
-
return NextResponse.json(
|
|
48
|
-
{ success: false, error: 'Internal server error' },
|
|
49
|
-
{ status: 500 }
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function POST(request: NextRequest) {
|
|
55
|
-
try {
|
|
56
|
-
const body = await request.json();
|
|
57
|
-
const imageUrl = body.imageUrl;
|
|
58
|
-
|
|
59
|
-
const result = await proxyImage(imageUrl);
|
|
60
|
-
if (result instanceof NextResponse) {
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return NextResponse.json({
|
|
65
|
-
success: true,
|
|
66
|
-
base64Image: `data:${result.contentType};base64,${result.base64Image}`
|
|
67
|
-
});
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error('Image proxy POST error:', error);
|
|
70
|
-
return NextResponse.json(
|
|
71
|
-
{ success: false, error: 'Internal server error' },
|
|
72
|
-
{ status: 500 }
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
}
|