@akinon/next 2.0.0-beta.10 → 2.0.0-beta.11
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 +6 -0
- package/__tests__/next-config.test.ts +83 -0
- package/__tests__/tsconfig.json +23 -0
- package/api/auth.ts +55 -5
- package/api/client.ts +18 -1
- package/babel.config.js +6 -0
- package/bin/pz-prebuild.js +1 -0
- package/bin/pz-run-tests.js +99 -0
- package/components/button.tsx +1 -1
- package/components/price.tsx +2 -2
- package/hooks/use-localization.ts +24 -10
- package/jest.config.js +19 -0
- package/middlewares/default.ts +37 -1
- package/middlewares/locale.ts +5 -2
- package/package.json +6 -3
- package/redux/middlewares/checkout.ts +28 -0
- package/redux/middlewares/index.ts +2 -0
- package/tailwind/content.js +16 -0
- package/types/index.ts +1 -0
- package/utils/get-root-hostname.ts +28 -0
- package/utils/index.ts +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { resolve } from 'path';
|
|
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
|
+
}
|
|
13
|
+
|
|
14
|
+
const baseDir = findBaseDir();
|
|
15
|
+
|
|
16
|
+
jest.mock('next-pwa', () => {
|
|
17
|
+
return () => (config: NextConfig) => config;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
jest.mock('@sentry/nextjs', () => ({
|
|
21
|
+
withSentryConfig: (config: NextConfig) => config
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
jest.mock('../with-pz-config.js', () => {
|
|
25
|
+
return (config: NextConfig) => {
|
|
26
|
+
const originalHeaders = config.headers;
|
|
27
|
+
|
|
28
|
+
config.headers = async () => {
|
|
29
|
+
const originalHeadersResult = (await originalHeaders?.()) ?? [];
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
source: '/(.*)',
|
|
34
|
+
headers: [
|
|
35
|
+
{
|
|
36
|
+
key: 'Content-Security-Policy',
|
|
37
|
+
value: 'https://*.akifast.com akifast.com'
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
...originalHeadersResult
|
|
42
|
+
];
|
|
43
|
+
};
|
|
44
|
+
return config;
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
interface Header {
|
|
49
|
+
key: string;
|
|
50
|
+
value: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface HeaderGroup {
|
|
54
|
+
source: string;
|
|
55
|
+
headers: Header[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const nextConfigPath = resolve(baseDir, 'next.config.mjs');
|
|
59
|
+
let nextConfig: any;
|
|
60
|
+
|
|
61
|
+
beforeAll(async () => {
|
|
62
|
+
nextConfig = await import(nextConfigPath);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('Next.js Configuration', () => {
|
|
66
|
+
it('should contain Content-Security-Policy header with akifast domain values', async () => {
|
|
67
|
+
const headers = nextConfig.default.headers;
|
|
68
|
+
expect(headers).toBeDefined();
|
|
69
|
+
|
|
70
|
+
const headersResult = await headers();
|
|
71
|
+
|
|
72
|
+
const cspHeaders = headersResult
|
|
73
|
+
.flatMap((headerGroup: HeaderGroup) => headerGroup.headers)
|
|
74
|
+
.filter((header: Header) => header.key === 'Content-Security-Policy');
|
|
75
|
+
|
|
76
|
+
expect(cspHeaders.length).toBeGreaterThan(0);
|
|
77
|
+
|
|
78
|
+
const lastCspHeader = cspHeaders[cspHeaders.length - 1];
|
|
79
|
+
|
|
80
|
+
expect(lastCspHeader.value).toContain('akifast.com');
|
|
81
|
+
expect(lastCspHeader.value).toContain('https://*.akifast.com');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"display": "Default",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"composite": false,
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"inlineSources": false,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"moduleResolution": "node",
|
|
13
|
+
"noUnusedLocals": false,
|
|
14
|
+
"noUnusedParameters": false,
|
|
15
|
+
"preserveWatchOutput": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"strict": true,
|
|
18
|
+
"jsx": "react-jsx",
|
|
19
|
+
"allowJs": true
|
|
20
|
+
},
|
|
21
|
+
"exclude": ["node_modules"],
|
|
22
|
+
"include": [".", "../."]
|
|
23
|
+
}
|
package/api/auth.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
-
import NextAuth, { Session } from 'next-auth';
|
|
2
|
+
import NextAuth, { Session, NextAuthOptions } from 'next-auth';
|
|
3
3
|
import CredentialProvider from 'next-auth/providers/credentials';
|
|
4
4
|
import { ROUTES } from 'routes';
|
|
5
5
|
import { URLS, user } from '../data/urls';
|
|
@@ -7,6 +7,8 @@ import Settings from 'settings';
|
|
|
7
7
|
import { urlLocaleMatcherRegex } from '../utils';
|
|
8
8
|
import logger from '@akinon/next/utils/log';
|
|
9
9
|
import { AuthError } from '../types';
|
|
10
|
+
import getRootHostname from '../utils/get-root-hostname';
|
|
11
|
+
import { LocaleUrlStrategy } from '../localization';
|
|
10
12
|
|
|
11
13
|
async function getCurrentUser(sessionId: string, currency = '') {
|
|
12
14
|
const headers = {
|
|
@@ -40,7 +42,15 @@ async function getCurrentUser(sessionId: string, currency = '') {
|
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
type CustomNextAuthOptions = (
|
|
46
|
+
req: NextApiRequest,
|
|
47
|
+
res: NextApiResponse
|
|
48
|
+
) => Partial<NextAuthOptions>;
|
|
49
|
+
|
|
50
|
+
const defaultNextAuthOptions = (
|
|
51
|
+
req: NextApiRequest,
|
|
52
|
+
res: NextApiResponse
|
|
53
|
+
): NextAuthOptions => {
|
|
44
54
|
return {
|
|
45
55
|
providers: [
|
|
46
56
|
CredentialProvider({
|
|
@@ -150,7 +160,19 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
150
160
|
|
|
151
161
|
if (sessionId) {
|
|
152
162
|
const maxAge = 30 * 24 * 60 * 60; // 30 days in seconds
|
|
153
|
-
const
|
|
163
|
+
const { localeUrlStrategy } = Settings.localization;
|
|
164
|
+
|
|
165
|
+
const fallbackHost =
|
|
166
|
+
req.headers['x-forwarded-host']?.toString() ||
|
|
167
|
+
req.headers.host?.toString();
|
|
168
|
+
const hostname =
|
|
169
|
+
process.env.NEXT_PUBLIC_URL || `https://${fallbackHost}`;
|
|
170
|
+
const rootHostname =
|
|
171
|
+
localeUrlStrategy === LocaleUrlStrategy.Subdomain
|
|
172
|
+
? getRootHostname(hostname)
|
|
173
|
+
: null;
|
|
174
|
+
const domainOption = rootHostname ? `Domain=${rootHostname};` : '';
|
|
175
|
+
const cookieOptions = `Path=/; HttpOnly; Secure; Max-Age=${maxAge}; ${domainOption}`;
|
|
154
176
|
|
|
155
177
|
res.setHeader('Set-Cookie', [
|
|
156
178
|
`osessionid=${sessionId}; ${cookieOptions}`,
|
|
@@ -253,8 +275,36 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
253
275
|
};
|
|
254
276
|
};
|
|
255
277
|
|
|
256
|
-
const Auth = (
|
|
257
|
-
|
|
278
|
+
const Auth = (
|
|
279
|
+
req: NextApiRequest,
|
|
280
|
+
res: NextApiResponse,
|
|
281
|
+
customOptions?: CustomNextAuthOptions
|
|
282
|
+
) => {
|
|
283
|
+
const baseOptions = defaultNextAuthOptions(req, res);
|
|
284
|
+
const customOptionsResult = customOptions ? customOptions(req, res) : {};
|
|
285
|
+
|
|
286
|
+
const mergedOptions = {
|
|
287
|
+
...baseOptions,
|
|
288
|
+
...customOptionsResult,
|
|
289
|
+
providers: [
|
|
290
|
+
...baseOptions.providers,
|
|
291
|
+
...(customOptionsResult.providers || [])
|
|
292
|
+
],
|
|
293
|
+
callbacks: {
|
|
294
|
+
...baseOptions.callbacks,
|
|
295
|
+
...customOptionsResult.callbacks
|
|
296
|
+
},
|
|
297
|
+
events: {
|
|
298
|
+
...baseOptions.events,
|
|
299
|
+
...customOptionsResult.events
|
|
300
|
+
},
|
|
301
|
+
pages: {
|
|
302
|
+
...baseOptions.pages,
|
|
303
|
+
...customOptionsResult.pages
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
return NextAuth(req, res, mergedOptions);
|
|
258
308
|
};
|
|
259
309
|
|
|
260
310
|
export default Auth;
|
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: {
|
|
@@ -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/babel.config.js
ADDED
package/bin/pz-prebuild.js
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const glob = require('glob');
|
|
7
|
+
const findBaseDir = require('../utils/find-base-dir');
|
|
8
|
+
const checkMonorepo = require('../utils/check-monorepo');
|
|
9
|
+
|
|
10
|
+
const IS_MONOREPO = checkMonorepo() !== null;
|
|
11
|
+
const BASE_DIR = findBaseDir();
|
|
12
|
+
const PLUGINS = require(path.join(BASE_DIR, 'src', 'plugins.js'));
|
|
13
|
+
|
|
14
|
+
function findPluginTestFiles(akinonNextPackagePath) {
|
|
15
|
+
const pluginsRootPath = path.join(
|
|
16
|
+
akinonNextPackagePath,
|
|
17
|
+
'..',
|
|
18
|
+
'..',
|
|
19
|
+
IS_MONOREPO ? 'packages' : '@akinon'
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(pluginsRootPath)) {
|
|
23
|
+
console.log('Plugins directory not found:', pluginsRootPath);
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return PLUGINS.reduce((testFiles, pluginName) => {
|
|
28
|
+
const pluginDirectoryPath = path.join(pluginsRootPath, pluginName);
|
|
29
|
+
if (fs.existsSync(pluginDirectoryPath)) {
|
|
30
|
+
const pluginTestFiles = glob.sync('**/*.test.ts', {
|
|
31
|
+
cwd: pluginDirectoryPath,
|
|
32
|
+
absolute: true
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return testFiles.concat(pluginTestFiles);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`Plugin directory not found: ${pluginName}`);
|
|
38
|
+
}
|
|
39
|
+
return testFiles;
|
|
40
|
+
}, []);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isJestInstalled() {
|
|
44
|
+
try {
|
|
45
|
+
const jestExecutablePath = path.join(
|
|
46
|
+
BASE_DIR,
|
|
47
|
+
'node_modules',
|
|
48
|
+
'.bin',
|
|
49
|
+
'jest'
|
|
50
|
+
);
|
|
51
|
+
return fs.existsSync(jestExecutablePath);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getAkinonNextPackagePath() {
|
|
58
|
+
if (!IS_MONOREPO) {
|
|
59
|
+
return path.resolve(__dirname, '..');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return path.resolve(__dirname, '../../../packages/akinon-next');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!isJestInstalled()) {
|
|
66
|
+
console.error('\x1b[31mError: Jest is not installed in the project!\x1b[0m');
|
|
67
|
+
console.error(
|
|
68
|
+
'Please install all dependencies by running one of the following commands:'
|
|
69
|
+
);
|
|
70
|
+
console.error(' npm install');
|
|
71
|
+
console.error(' yarn install');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const jestExecutablePath = path.join(BASE_DIR, 'node_modules', '.bin', 'jest');
|
|
76
|
+
const akinonNextPackagePath = getAkinonNextPackagePath();
|
|
77
|
+
|
|
78
|
+
const testFiles = [
|
|
79
|
+
...glob.sync('__tests__/**/*.test.ts', {
|
|
80
|
+
cwd: akinonNextPackagePath,
|
|
81
|
+
absolute: true
|
|
82
|
+
}),
|
|
83
|
+
...findPluginTestFiles(akinonNextPackagePath)
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
console.log(`Found ${testFiles.length} test files to run`);
|
|
87
|
+
|
|
88
|
+
const jestArguments = [
|
|
89
|
+
...testFiles,
|
|
90
|
+
`--config ${path.join(akinonNextPackagePath, 'jest.config.js')}`,
|
|
91
|
+
'--runTestsByPath',
|
|
92
|
+
'--passWithNoTests'
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
spawn(jestExecutablePath, jestArguments, {
|
|
96
|
+
cwd: akinonNextPackagePath,
|
|
97
|
+
stdio: 'inherit',
|
|
98
|
+
shell: true
|
|
99
|
+
});
|
package/components/button.tsx
CHANGED
|
@@ -35,7 +35,7 @@ export const Button = (props: ButtonProps) => {
|
|
|
35
35
|
|
|
36
36
|
const buttonClasses = twMerge(
|
|
37
37
|
clsx(
|
|
38
|
-
'px-4 text-xs transition-all duration-200',
|
|
38
|
+
'px-4 text-xs transition-all duration-200 cursor-pointer',
|
|
39
39
|
'inline-flex gap-2 justify-center items-center',
|
|
40
40
|
variants[appearance],
|
|
41
41
|
sizes[size],
|
package/components/price.tsx
CHANGED
|
@@ -55,8 +55,8 @@ export const Price = (props: NumericFormatProps & PriceProps) => {
|
|
|
55
55
|
: formattedValue;
|
|
56
56
|
|
|
57
57
|
const currentCurrencyDecimalScale = Settings.localization.currencies.find(
|
|
58
|
-
(currency) => currency
|
|
59
|
-
)
|
|
58
|
+
(currency) => currency?.code === currencyCode_
|
|
59
|
+
)?.decimalScale;
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
62
|
<NumericFormat
|
|
@@ -25,19 +25,33 @@ export const useLocalization = () => {
|
|
|
25
25
|
* @param locale Locale value defined in the settings.
|
|
26
26
|
*/
|
|
27
27
|
const setLocale = (locale: string) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
localeUrlStrategy !== LocaleUrlStrategy.ShowAllLocales
|
|
31
|
-
? ''
|
|
32
|
-
: `/${locale}`;
|
|
28
|
+
const { protocol, hostname, port, search, pathname } = location;
|
|
29
|
+
const pathnameWithoutLocale = pathname.replace(urlLocaleMatcherRegex, '');
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
let targetUrl;
|
|
32
|
+
|
|
33
|
+
if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
|
|
34
|
+
const hostParts = hostname.split('.');
|
|
35
|
+
const subDomain = hostParts[0];
|
|
36
|
+
const isSubdomainLocale = locales.some((loc) => loc.value === subDomain);
|
|
37
|
+
const baseDomain = isSubdomainLocale
|
|
38
|
+
? hostParts.slice(1).join('.')
|
|
39
|
+
: hostname;
|
|
40
|
+
|
|
41
|
+
const formattedPort = port ? `:${port}` : '';
|
|
42
|
+
|
|
43
|
+
targetUrl = `${protocol}//${locale}.${baseDomain}${formattedPort}${pathnameWithoutLocale}${search}`;
|
|
44
|
+
} else {
|
|
45
|
+
const shouldHideLocale =
|
|
46
|
+
locale === defaultLocaleValue &&
|
|
47
|
+
localeUrlStrategy !== LocaleUrlStrategy.ShowAllLocales;
|
|
48
|
+
const localePath = shouldHideLocale ? '' : `/${locale}`;
|
|
49
|
+
|
|
50
|
+
targetUrl = `${localePath}${pathnameWithoutLocale}${search}`;
|
|
51
|
+
}
|
|
38
52
|
|
|
39
53
|
// router.push is not used because reloading the page also clears the client side cache.
|
|
40
|
-
location.href =
|
|
54
|
+
location.href = targetUrl;
|
|
41
55
|
};
|
|
42
56
|
|
|
43
57
|
/**
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
preset: 'ts-jest',
|
|
5
|
+
testEnvironment: 'node',
|
|
6
|
+
rootDir: path.resolve(__dirname),
|
|
7
|
+
roots: [],
|
|
8
|
+
testMatch: ['**/*.test.ts'],
|
|
9
|
+
testPathIgnorePatterns: [],
|
|
10
|
+
transformIgnorePatterns: [],
|
|
11
|
+
transform: {
|
|
12
|
+
'^.+\\.(tsx?|jsx?|mjs?)$': [
|
|
13
|
+
'ts-jest',
|
|
14
|
+
{
|
|
15
|
+
tsconfig: path.resolve(__dirname, '__tests__/tsconfig.json')
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
};
|
package/middlewares/default.ts
CHANGED
|
@@ -19,6 +19,8 @@ import withLocale from './locale';
|
|
|
19
19
|
import logger from '../utils/log';
|
|
20
20
|
import { user } from '../data/urls';
|
|
21
21
|
import { getUrlPathWithLocale } from '../utils/localization';
|
|
22
|
+
import getRootHostname from '../utils/get-root-hostname';
|
|
23
|
+
import { LocaleUrlStrategy } from '../localization';
|
|
22
24
|
|
|
23
25
|
const withPzDefault =
|
|
24
26
|
(middleware: NextMiddleware) =>
|
|
@@ -292,6 +294,19 @@ const withPzDefault =
|
|
|
292
294
|
)}`;
|
|
293
295
|
}
|
|
294
296
|
|
|
297
|
+
if (
|
|
298
|
+
!req.middlewareParams.found &&
|
|
299
|
+
Settings.customNotFoundEnabled
|
|
300
|
+
) {
|
|
301
|
+
const pathname = url.pathname
|
|
302
|
+
.replace(/\/+$/, '')
|
|
303
|
+
.split('/');
|
|
304
|
+
url.pathname = url.pathname.replace(
|
|
305
|
+
pathname.pop(),
|
|
306
|
+
'pz-not-found'
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
295
310
|
Settings.rewrites.forEach((rewrite) => {
|
|
296
311
|
url.pathname = url.pathname.replace(
|
|
297
312
|
rewrite.source,
|
|
@@ -328,6 +343,21 @@ const withPzDefault =
|
|
|
328
343
|
middlewareResult = NextResponse.rewrite(url);
|
|
329
344
|
}
|
|
330
345
|
|
|
346
|
+
const { localeUrlStrategy } =
|
|
347
|
+
Settings.localization;
|
|
348
|
+
|
|
349
|
+
const fallbackHost =
|
|
350
|
+
req.headers.get('x-forwarded-host') ||
|
|
351
|
+
req.headers.get('host');
|
|
352
|
+
const hostname =
|
|
353
|
+
process.env.NEXT_PUBLIC_URL ||
|
|
354
|
+
`https://${fallbackHost}`;
|
|
355
|
+
const rootHostname =
|
|
356
|
+
localeUrlStrategy ===
|
|
357
|
+
LocaleUrlStrategy.Subdomain
|
|
358
|
+
? getRootHostname(hostname)
|
|
359
|
+
: null;
|
|
360
|
+
|
|
331
361
|
if (
|
|
332
362
|
!url.pathname.startsWith(`/${currency}/orders`)
|
|
333
363
|
) {
|
|
@@ -337,6 +367,7 @@ const withPzDefault =
|
|
|
337
367
|
? locale
|
|
338
368
|
: defaultLocaleValue,
|
|
339
369
|
{
|
|
370
|
+
domain: rootHostname,
|
|
340
371
|
sameSite: 'none',
|
|
341
372
|
secure: true,
|
|
342
373
|
expires: new Date(
|
|
@@ -345,10 +376,12 @@ const withPzDefault =
|
|
|
345
376
|
}
|
|
346
377
|
);
|
|
347
378
|
}
|
|
379
|
+
|
|
348
380
|
middlewareResult.cookies.set(
|
|
349
381
|
'pz-currency',
|
|
350
382
|
currency,
|
|
351
383
|
{
|
|
384
|
+
domain: rootHostname,
|
|
352
385
|
sameSite: 'none',
|
|
353
386
|
secure: true,
|
|
354
387
|
expires: new Date(
|
|
@@ -397,7 +430,10 @@ const withPzDefault =
|
|
|
397
430
|
).json();
|
|
398
431
|
middlewareResult.cookies.set(
|
|
399
432
|
'csrftoken',
|
|
400
|
-
csrf_token
|
|
433
|
+
csrf_token,
|
|
434
|
+
{
|
|
435
|
+
domain: rootHostname
|
|
436
|
+
}
|
|
401
437
|
);
|
|
402
438
|
}
|
|
403
439
|
} catch (error) {
|
package/middlewares/locale.ts
CHANGED
|
@@ -13,7 +13,8 @@ const getMatchedLocale = (pathname: string, req: PzNextRequest) => {
|
|
|
13
13
|
const { localeUrlStrategy, defaultLocaleValue } = settings.localization;
|
|
14
14
|
|
|
15
15
|
if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
|
|
16
|
-
const host =
|
|
16
|
+
const host =
|
|
17
|
+
req.headers.get('x-forwarded-host') || req.headers.get('host') || '';
|
|
17
18
|
|
|
18
19
|
if (host) {
|
|
19
20
|
const subDomain = host.split('.')[0] || '';
|
|
@@ -44,7 +45,9 @@ const withLocale =
|
|
|
44
45
|
try {
|
|
45
46
|
const url = req.nextUrl.clone();
|
|
46
47
|
const matchedLocale = getMatchedLocale(url.pathname, req);
|
|
47
|
-
let { localeUrlStrategy
|
|
48
|
+
let { localeUrlStrategy } = settings.localization;
|
|
49
|
+
|
|
50
|
+
const { defaultLocaleValue, redirectToDefaultLocale } =
|
|
48
51
|
settings.localization;
|
|
49
52
|
|
|
50
53
|
localeUrlStrategy =
|
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": "2.0.0-beta.
|
|
4
|
+
"version": "2.0.0-beta.11",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
"pz-predev": "bin/pz-predev.js",
|
|
14
14
|
"pz-postdev": "bin/pz-postdev.js"
|
|
15
15
|
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "jest"
|
|
18
|
+
},
|
|
16
19
|
"dependencies": {
|
|
20
|
+
"@neshca/cache-handler": "1.5.1",
|
|
17
21
|
"@opentelemetry/exporter-trace-otlp-http": "0.46.0",
|
|
18
22
|
"@opentelemetry/resources": "1.19.0",
|
|
19
23
|
"@opentelemetry/sdk-node": "0.46.0",
|
|
20
24
|
"@opentelemetry/sdk-trace-node": "1.19.0",
|
|
21
25
|
"@opentelemetry/semantic-conventions": "1.19.0",
|
|
22
26
|
"@reduxjs/toolkit": "1.9.7",
|
|
23
|
-
"@neshca/cache-handler": "1.5.1",
|
|
24
27
|
"@sentry/nextjs": "9.5.0",
|
|
25
28
|
"cross-spawn": "7.0.3",
|
|
26
29
|
"generic-pool": "3.9.0",
|
|
@@ -31,7 +34,7 @@
|
|
|
31
34
|
"set-cookie-parser": "2.6.0"
|
|
32
35
|
},
|
|
33
36
|
"devDependencies": {
|
|
34
|
-
"@akinon/eslint-plugin-projectzero": "2.0.0-beta.
|
|
37
|
+
"@akinon/eslint-plugin-projectzero": "2.0.0-beta.11",
|
|
35
38
|
"@types/react-redux": "7.1.30",
|
|
36
39
|
"@types/set-cookie-parser": "2.4.7",
|
|
37
40
|
"@typescript-eslint/eslint-plugin": "8.18.2",
|
|
@@ -36,6 +36,7 @@ interface CheckoutResult {
|
|
|
36
36
|
errors?: Record<string, string[]>;
|
|
37
37
|
pre_order?: PreOrder;
|
|
38
38
|
context_list?: CheckoutContext[];
|
|
39
|
+
redirect_url?: string;
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -57,6 +58,33 @@ export const errorMiddleware: Middleware = ({ dispatch }: MiddlewareParams) => {
|
|
|
57
58
|
};
|
|
58
59
|
};
|
|
59
60
|
|
|
61
|
+
export const redirectUrlMiddleware: Middleware = () => {
|
|
62
|
+
return (next) => (action) => {
|
|
63
|
+
const result: CheckoutResult = next(action);
|
|
64
|
+
const redirectUrl = result?.payload?.redirect_url;
|
|
65
|
+
|
|
66
|
+
if (redirectUrl) {
|
|
67
|
+
const currentLocale = getCookie('pz-locale');
|
|
68
|
+
|
|
69
|
+
let url = redirectUrl;
|
|
70
|
+
|
|
71
|
+
if (currentLocale && !redirectUrl.includes('/orders/redirection')) {
|
|
72
|
+
const { defaultLocaleValue, localeUrlStrategy } = settings.localization;
|
|
73
|
+
|
|
74
|
+
url =
|
|
75
|
+
currentLocale === defaultLocaleValue &&
|
|
76
|
+
localeUrlStrategy !== LocaleUrlStrategy.ShowAllLocales
|
|
77
|
+
? redirectUrl
|
|
78
|
+
: `/${currentLocale}${redirectUrl}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
window.location.href = url;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
60
88
|
export const contextListMiddleware: Middleware = ({
|
|
61
89
|
dispatch,
|
|
62
90
|
getState
|
|
@@ -5,6 +5,7 @@ import { ROUTES } from 'routes';
|
|
|
5
5
|
import type { Middleware } from '@reduxjs/toolkit';
|
|
6
6
|
import {
|
|
7
7
|
errorMiddleware,
|
|
8
|
+
redirectUrlMiddleware,
|
|
8
9
|
contextListMiddleware,
|
|
9
10
|
hepsiPayMiddleware,
|
|
10
11
|
walletPaymentMiddleware
|
|
@@ -50,6 +51,7 @@ const middlewares = [
|
|
|
50
51
|
rtkQueryResponseHandler,
|
|
51
52
|
rtkQueryErrorHandler,
|
|
52
53
|
errorMiddleware,
|
|
54
|
+
redirectUrlMiddleware,
|
|
53
55
|
...preOrderMiddlewares,
|
|
54
56
|
contextListMiddleware,
|
|
55
57
|
hepsiPayMiddleware,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const defaultPlugins = require('../plugins');
|
|
2
|
+
|
|
3
|
+
const getAkinonNextContent = (plugins = defaultPlugins) => {
|
|
4
|
+
return [
|
|
5
|
+
`./node_modules/@akinon/next/components/**/*.{js,ts,jsx,tsx}`,
|
|
6
|
+
`../../node_modules/@akinon/next/components/**/*.{js,ts,jsx,tsx}`,
|
|
7
|
+
...plugins
|
|
8
|
+
.map((plugin) => [
|
|
9
|
+
`./node_modules/@akinon/${plugin}/**/*.{js,ts,jsx,tsx}`,
|
|
10
|
+
`../../node_modules/@akinon/${plugin}/**/*.{js,ts,jsx,tsx}`
|
|
11
|
+
])
|
|
12
|
+
.flat()
|
|
13
|
+
];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = getAkinonNextContent;
|
package/types/index.ts
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Settings from 'settings';
|
|
2
|
+
|
|
3
|
+
export default function getRootHostname(
|
|
4
|
+
url: string | undefined
|
|
5
|
+
): string | null {
|
|
6
|
+
if (!url) return null;
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const urlObj = new URL(url);
|
|
10
|
+
const hostname = urlObj.hostname;
|
|
11
|
+
const parts = hostname.split('.');
|
|
12
|
+
|
|
13
|
+
const firstPart = parts[0];
|
|
14
|
+
|
|
15
|
+
const isLocale = Settings.localization.locales.some(
|
|
16
|
+
(locale) => locale.value === firstPart
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (isLocale) {
|
|
20
|
+
const hostnameAfterLocale = parts.slice(1).join('.');
|
|
21
|
+
return `.${hostnameAfterLocale}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return `.${hostname}`;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/utils/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import settings from 'settings';
|
|
2
2
|
import { LocaleUrlStrategy } from '../localization';
|
|
3
|
-
import { CDNOptions, ClientRequestOptions
|
|
3
|
+
import { CDNOptions, ClientRequestOptions } from '../types';
|
|
4
4
|
|
|
5
5
|
export * from './get-currency';
|
|
6
6
|
export * from './menu-generator';
|
|
@@ -155,14 +155,15 @@ export function buildCDNUrl(url: string, config?: CDNOptions) {
|
|
|
155
155
|
const { locales, localeUrlStrategy, defaultLocaleValue } =
|
|
156
156
|
settings.localization;
|
|
157
157
|
|
|
158
|
-
const isLocaleExcluded = (locale: Locale) =>
|
|
159
|
-
![LocaleUrlStrategy.ShowAllLocales, LocaleUrlStrategy.Subdomain].includes(
|
|
160
|
-
localeUrlStrategy
|
|
161
|
-
) && locale.value !== defaultLocaleValue;
|
|
162
|
-
|
|
163
158
|
export const urlLocaleMatcherRegex = new RegExp(
|
|
164
|
-
`^/(${locales
|
|
165
|
-
.filter((l) =>
|
|
159
|
+
`^/(${settings.localization.locales
|
|
160
|
+
.filter((l) =>
|
|
161
|
+
![LocaleUrlStrategy.ShowAllLocales, LocaleUrlStrategy.Subdomain].includes(
|
|
162
|
+
settings.localization.localeUrlStrategy
|
|
163
|
+
)
|
|
164
|
+
? l.value !== settings.localization.defaultLocaleValue
|
|
165
|
+
: l
|
|
166
|
+
)
|
|
166
167
|
.map((l) => l.value)
|
|
167
168
|
.join('|')})(?=/|$)`
|
|
168
169
|
);
|