@akinon/next 2.0.0-beta.1 → 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/.eslintrc.js +12 -0
- package/CHANGELOG.md +56 -0
- package/__tests__/next-config.test.ts +83 -0
- package/__tests__/tsconfig.json +23 -0
- package/api/auth.ts +72 -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/bin/run-prebuild-tests.js +46 -0
- package/components/accordion.tsx +1 -1
- package/components/button.tsx +51 -36
- package/components/client-root.tsx +20 -0
- package/components/input.tsx +1 -1
- package/components/modal.tsx +1 -1
- package/components/price.tsx +2 -2
- package/components/select.tsx +1 -1
- package/components/selected-payment-option-view.tsx +1 -1
- package/data/client/api.ts +2 -0
- package/data/client/basket.ts +27 -5
- package/data/client/checkout.ts +62 -7
- package/data/client/misc.ts +25 -1
- package/data/client/product.ts +19 -2
- package/data/client/user.ts +16 -8
- package/data/server/flatpage.ts +8 -4
- package/data/server/form.ts +12 -4
- package/data/server/landingpage.ts +8 -4
- package/data/server/menu.ts +7 -2
- package/data/server/product.ts +16 -5
- package/data/server/seo.ts +11 -4
- package/data/server/widget.ts +19 -4
- package/data/urls.ts +11 -3
- package/hooks/index.ts +1 -0
- package/hooks/use-localization.ts +24 -10
- package/hooks/use-router.ts +5 -2
- package/hooks/use-sentry-uncaught-errors.ts +24 -0
- package/instrumentation/index.ts +10 -0
- package/jest.config.js +19 -0
- package/lib/cache-handler.mjs +2 -2
- package/lib/cache.ts +4 -4
- package/localization/index.ts +2 -1
- package/middlewares/default.ts +31 -4
- package/middlewares/locale.ts +35 -11
- package/middlewares/url-redirection.ts +16 -0
- package/package.json +8 -4
- package/plugins.js +2 -1
- package/redux/middlewares/checkout.ts +30 -130
- package/redux/middlewares/index.ts +6 -2
- package/redux/middlewares/pre-order/address.ts +50 -0
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +46 -0
- package/redux/middlewares/pre-order/data-source-shipping-option.ts +43 -0
- package/redux/middlewares/pre-order/delivery-option.ts +40 -0
- package/redux/middlewares/pre-order/index.ts +29 -0
- package/redux/middlewares/pre-order/installment-option.ts +42 -0
- package/redux/middlewares/pre-order/payment-option.ts +34 -0
- package/redux/middlewares/pre-order/pre-order-validation.ts +24 -0
- package/redux/middlewares/pre-order/redirection.ts +40 -0
- package/redux/middlewares/pre-order/set-pre-order.ts +22 -0
- package/redux/middlewares/pre-order/shipping-option.ts +44 -0
- package/redux/middlewares/pre-order/shipping-step.ts +38 -0
- package/redux/reducers/checkout.ts +8 -2
- package/redux/reducers/index.ts +5 -3
- package/redux/reducers/root.ts +7 -2
- package/sentry/index.ts +36 -17
- package/tailwind/content.js +16 -0
- package/types/commerce/account.ts +5 -1
- package/types/commerce/checkout.ts +23 -0
- package/types/index.ts +7 -2
- package/utils/get-root-hostname.ts +28 -0
- package/utils/index.ts +6 -2
- package/utils/localization.ts +4 -0
- package/utils/override-middleware.ts +25 -0
- package/views/error-page.tsx +93 -0
- package/with-pz-config.js +4 -3
package/.eslintrc.js
CHANGED
|
@@ -24,6 +24,18 @@ module.exports = {
|
|
|
24
24
|
parserOptions: {
|
|
25
25
|
sourceType: 'script'
|
|
26
26
|
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
env: {
|
|
30
|
+
node: true
|
|
31
|
+
},
|
|
32
|
+
files: ['redux/middlewares/pre-order/index.ts'],
|
|
33
|
+
rules: {
|
|
34
|
+
'@akinon/projectzero/check-pre-order-middleware-order': 'error'
|
|
35
|
+
},
|
|
36
|
+
parserOptions: {
|
|
37
|
+
sourceType: 'script'
|
|
38
|
+
}
|
|
27
39
|
}
|
|
28
40
|
],
|
|
29
41
|
parser: '@typescript-eslint/parser',
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
+
## 2.0.0-beta.11
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ac783d6: ZERO-3482: Update tailwindcss to version 4.1.11 and enhance button cursor styles
|
|
8
|
+
|
|
9
|
+
## 2.0.0-beta.10
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 2806320: ZERO-3390: Update version tailwindcss, autoprefixer, tailwind-merge, postcss
|
|
14
|
+
|
|
15
|
+
## 2.0.0-beta.9
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- 0fe7711: ZERO-3387: Upgrade nextjs, eslint-config-next
|
|
20
|
+
|
|
21
|
+
## 2.0.0-beta.8
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- 071d0f5: ZERO-3352: Resolve Single item size exceeds maxSize error and upgrade dependencies
|
|
26
|
+
|
|
27
|
+
## 2.0.0-beta.7
|
|
28
|
+
|
|
29
|
+
## 2.0.0-beta.6
|
|
30
|
+
|
|
31
|
+
### Minor Changes
|
|
32
|
+
|
|
33
|
+
- 8f05f9b: ZERO-3250: Beta branch synchronized with Main branch
|
|
34
|
+
|
|
35
|
+
## 2.0.0-beta.5
|
|
36
|
+
|
|
37
|
+
### Minor Changes
|
|
38
|
+
|
|
39
|
+
- e791eab: ZERO-3133: Add fallbackReducer for handling missing plugin reducers
|
|
40
|
+
|
|
41
|
+
## 2.0.0-beta.4
|
|
42
|
+
|
|
43
|
+
## 2.0.0-beta.3
|
|
44
|
+
|
|
45
|
+
### Minor Changes
|
|
46
|
+
|
|
47
|
+
- 5536b80: ZERO-3104: Add optional headers parameter
|
|
48
|
+
|
|
49
|
+
## 2.0.0-beta.2
|
|
50
|
+
|
|
51
|
+
### Minor Changes
|
|
52
|
+
|
|
53
|
+
- a006015: ZERO-3116: Add not-found page and update default middleware.
|
|
54
|
+
- 999168d: ZERO-3104: Remove local cache handler from CacheHandler initialization
|
|
55
|
+
- 1eeb3d8: ZERO-3116: Add not found page
|
|
56
|
+
- 86a5a62: ZERO-3104: Add optional headers parameter to data fetching functions
|
|
57
|
+
- dd69cc6: ZERO-3079: Modularize pre-order middleware
|
|
58
|
+
|
|
3
59
|
## 2.0.0-beta.1
|
|
4
60
|
|
|
5
61
|
### Minor Changes
|
|
@@ -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({
|
|
@@ -92,11 +102,28 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
92
102
|
req.headers['x-app-device']?.toString() ?? ''
|
|
93
103
|
);
|
|
94
104
|
|
|
105
|
+
headers.set('x-frontend-id', req.cookies['pz-frontend-id'] || '');
|
|
106
|
+
|
|
95
107
|
logger.debug('Trying to login/register', {
|
|
96
108
|
formType: credentials.formType,
|
|
97
109
|
userIp
|
|
98
110
|
});
|
|
99
111
|
|
|
112
|
+
const checkCurrentUser = await getCurrentUser(
|
|
113
|
+
req.cookies['osessionid'] ?? '',
|
|
114
|
+
req.cookies['pz-currency'] ?? ''
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (checkCurrentUser?.pk) {
|
|
118
|
+
const sessionCookie = headers
|
|
119
|
+
.get('cookie')
|
|
120
|
+
?.match(/osessionid=\w+/)?.[0]
|
|
121
|
+
.replace(/osessionid=/, '');
|
|
122
|
+
if (sessionCookie) {
|
|
123
|
+
headers.set('cookie', sessionCookie);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
100
127
|
const apiRequest = await fetch(
|
|
101
128
|
`${Settings.commerceUrl}${user[credentials.formType]}`,
|
|
102
129
|
{
|
|
@@ -133,7 +160,19 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
133
160
|
|
|
134
161
|
if (sessionId) {
|
|
135
162
|
const maxAge = 30 * 24 * 60 * 60; // 30 days in seconds
|
|
136
|
-
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}`;
|
|
137
176
|
|
|
138
177
|
res.setHeader('Set-Cookie', [
|
|
139
178
|
`osessionid=${sessionId}; ${cookieOptions}`,
|
|
@@ -236,8 +275,36 @@ const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
|
|
|
236
275
|
};
|
|
237
276
|
};
|
|
238
277
|
|
|
239
|
-
const Auth = (
|
|
240
|
-
|
|
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);
|
|
241
308
|
};
|
|
242
309
|
|
|
243
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
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
|
|
4
|
+
function runPrebuildTests() {
|
|
5
|
+
const workspaceRoot = process.cwd()
|
|
6
|
+
const configPath = path.join(workspaceRoot, 'config/prebuild-tests.json')
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
|
10
|
+
|
|
11
|
+
if (config.tests && Array.isArray(config.tests)) {
|
|
12
|
+
for (const testScript of config.tests) {
|
|
13
|
+
console.log(`🧪 Running test script: ${testScript}`)
|
|
14
|
+
const result = require('child_process').spawnSync('yarn', [testScript], {
|
|
15
|
+
stdio: 'inherit',
|
|
16
|
+
shell: true
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
if (result.status !== 0) {
|
|
20
|
+
console.error(`❌ Test script '${testScript}' failed`)
|
|
21
|
+
process.exit(1)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
console.log('✅ All prebuild tests passed successfully!')
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error.code === 'ENOENT') {
|
|
28
|
+
console.log('🧪 Running default test: test:middleware')
|
|
29
|
+
const result = require('child_process').spawnSync('yarn', ['test:middleware'], {
|
|
30
|
+
stdio: 'inherit',
|
|
31
|
+
shell: true
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
if (result.status !== 0) {
|
|
35
|
+
console.error('❌ Middleware test failed')
|
|
36
|
+
process.exit(1)
|
|
37
|
+
}
|
|
38
|
+
console.log('✅ Default test passed successfully!')
|
|
39
|
+
} else {
|
|
40
|
+
console.error('❌ Error reading test configuration:', error)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = runPrebuildTests
|
package/components/accordion.tsx
CHANGED
package/components/button.tsx
CHANGED
|
@@ -1,46 +1,61 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { ButtonProps } from '../types
|
|
3
|
+
import { ButtonProps } from '../types';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { Link } from './link';
|
|
6
7
|
|
|
7
8
|
export const Button = (props: ButtonProps) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
9
|
+
const {
|
|
10
|
+
appearance = 'filled',
|
|
11
|
+
size = 'md',
|
|
12
|
+
href,
|
|
13
|
+
target,
|
|
14
|
+
children,
|
|
15
|
+
className,
|
|
16
|
+
...rest
|
|
17
|
+
} = props;
|
|
18
|
+
|
|
19
|
+
const variants = {
|
|
20
|
+
filled:
|
|
21
|
+
'bg-primary text-primary-foreground border border-primary hover:bg-white hover:border-primary hover:text-primary',
|
|
22
|
+
outlined:
|
|
23
|
+
'bg-transparent text-primary hover:bg-primary hover:text-primary-foreground',
|
|
24
|
+
ghost:
|
|
25
|
+
'bg-transparent border-transparent text-primary hover:bg-primary hover:text-primary-foreground',
|
|
26
|
+
link: 'px-0 h-auto underline underline-offset-2'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const sizes = {
|
|
30
|
+
sm: 'h-8',
|
|
31
|
+
md: 'h-10',
|
|
32
|
+
lg: 'h-12',
|
|
33
|
+
xl: 'h-14'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const buttonClasses = twMerge(
|
|
37
|
+
clsx(
|
|
38
|
+
'px-4 text-xs transition-all duration-200 cursor-pointer',
|
|
39
|
+
'inline-flex gap-2 justify-center items-center',
|
|
40
|
+
variants[appearance],
|
|
41
|
+
sizes[size],
|
|
42
|
+
className
|
|
43
|
+
),
|
|
44
|
+
className
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return props.href ? (
|
|
48
|
+
<Link
|
|
49
|
+
prefetch={false}
|
|
50
|
+
target={target}
|
|
51
|
+
href={href}
|
|
52
|
+
className={buttonClasses}
|
|
42
53
|
>
|
|
43
|
-
{
|
|
54
|
+
{children}
|
|
55
|
+
</Link>
|
|
56
|
+
) : (
|
|
57
|
+
<button {...rest} className={buttonClasses}>
|
|
58
|
+
{children}
|
|
44
59
|
</button>
|
|
45
60
|
);
|
|
46
61
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useMobileIframeHandler } from '../hooks';
|
|
4
|
+
import * as Sentry from '@sentry/nextjs';
|
|
5
|
+
import { initSentry } from '../sentry';
|
|
6
|
+
import { useEffect } from 'react';
|
|
4
7
|
|
|
5
8
|
export default function ClientRoot({
|
|
6
9
|
children,
|
|
@@ -11,6 +14,23 @@ export default function ClientRoot({
|
|
|
11
14
|
}) {
|
|
12
15
|
const { preventPageRender } = useMobileIframeHandler({ sessionId });
|
|
13
16
|
|
|
17
|
+
const initializeSentry = async () => {
|
|
18
|
+
const response = await fetch('/api/sentry', { next: { revalidate: 0 } });
|
|
19
|
+
const data = await response.json();
|
|
20
|
+
|
|
21
|
+
const options = {
|
|
22
|
+
dsn: data.dsn
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
initSentry('Client', options);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!Sentry.isInitialized()) {
|
|
30
|
+
initializeSentry();
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
14
34
|
if (preventPageRender) {
|
|
15
35
|
return null;
|
|
16
36
|
}
|
package/components/input.tsx
CHANGED
|
@@ -40,7 +40,7 @@ export const Input = forwardRef<
|
|
|
40
40
|
const inputClass = twMerge(
|
|
41
41
|
clsx(
|
|
42
42
|
'text-xs border px-2.5 h-10 placeholder:text-gray-600 peer',
|
|
43
|
-
'focus-visible:outline-
|
|
43
|
+
'focus-visible:outline-hidden', // disable outline on focus
|
|
44
44
|
error
|
|
45
45
|
? 'border-error focus:border-error'
|
|
46
46
|
: 'border-gray-500 hover:border-black focus:border-black'
|
package/components/modal.tsx
CHANGED
|
@@ -38,7 +38,7 @@ export const Modal = (props: ModalProps) => {
|
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<ReactPortal wrapperId={portalId}>
|
|
41
|
-
<div className="fixed top-0 left-0 w-screen h-screen bg-primary
|
|
41
|
+
<div className="fixed top-0 left-0 w-screen h-screen bg-primary/60 z-50" />
|
|
42
42
|
<section
|
|
43
43
|
className={twMerge(
|
|
44
44
|
'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 bg-white',
|
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
|