@qlover/create-app 0.7.7 → 0.7.8
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 +91 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/build/generateLocales.ts +1 -1
- package/dist/templates/next-app/config/IOCIdentifier.ts +15 -2
- package/dist/templates/next-app/config/Identifier/common.error.ts +7 -0
- package/dist/templates/next-app/package.json +1 -1
- package/dist/templates/next-app/public/locales/{en/common.json → en.json} +2 -1
- package/dist/templates/next-app/public/locales/{zh/common.json → zh.json} +2 -1
- package/dist/templates/next-app/src/app/[locale]/layout.tsx +8 -20
- package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +6 -7
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +24 -11
- package/dist/templates/next-app/src/app/[locale]/page.tsx +14 -1
- package/dist/templates/next-app/src/base/cases/DialogHandler.ts +92 -0
- package/dist/templates/next-app/src/base/cases/NavigateBridge.ts +16 -0
- package/dist/templates/next-app/src/base/cases/PageParams.ts +74 -0
- package/dist/templates/next-app/src/base/cases/RouterService.ts +35 -0
- package/dist/templates/next-app/src/base/cases/ServerAuth.ts +17 -0
- package/dist/templates/next-app/src/base/cases/ServerErrorHandler.ts +27 -0
- package/dist/templates/next-app/src/base/port/IOCInterface.ts +24 -0
- package/dist/templates/next-app/src/base/port/ParamsHandlerInterface.ts +11 -0
- package/dist/templates/next-app/src/base/port/RouterInterface.ts +11 -0
- package/dist/templates/next-app/src/base/port/ServerAuthInterface.ts +3 -0
- package/dist/templates/next-app/src/base/port/ServerInterface.ts +12 -0
- package/dist/templates/next-app/src/base/types/PageProps.ts +9 -0
- package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +2 -39
- package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +78 -0
- package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +37 -0
- package/dist/templates/next-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +20 -23
- package/dist/templates/next-app/src/core/globals.ts +3 -0
- package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +52 -0
- package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +63 -0
- package/dist/templates/next-app/src/i18n/request.ts +1 -2
- package/dist/templates/next-app/src/i18n/routing.ts +3 -3
- package/dist/templates/next-app/src/middleware.ts +6 -3
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +9 -4
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +13 -1
- package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +5 -0
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +34 -0
- package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +16 -3
- package/dist/templates/next-app/src/uikit/context/IOCContext.ts +9 -2
- package/package.json +1 -1
- package/dist/templates/next-app/plugins/eslint-plugin-testid.mjs +0 -94
- package/dist/templates/next-app/plugins/generateLocalesPlugin.ts +0 -33
- package/dist/templates/next-app/src/core/IOC.ts +0 -58
- package/dist/templates/next-app/src/server/getServerI18n.ts +0 -26
|
@@ -3,6 +3,8 @@ import '@ant-design/v5-patch-for-react-19';
|
|
|
3
3
|
import { AntdRegistry } from '@ant-design/nextjs-registry';
|
|
4
4
|
import { AntdThemeProvider } from '@brain-toolkit/antd-theme-override/react';
|
|
5
5
|
import { ThemeProvider } from 'next-themes';
|
|
6
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
7
|
+
import { clientIOC } from '@/core/clientIoc/ClientIOC';
|
|
6
8
|
import { BootstrapsProvider } from './BootstrapsProvider';
|
|
7
9
|
import type { CommonThemeConfig } from '@config/theme';
|
|
8
10
|
|
|
@@ -23,10 +25,13 @@ export function ComboProvider(props: {
|
|
|
23
25
|
}) {
|
|
24
26
|
const { themeConfig, children } = props;
|
|
25
27
|
|
|
28
|
+
const IOC = clientIOC.create();
|
|
29
|
+
|
|
26
30
|
return (
|
|
27
31
|
<AntdThemeProvider
|
|
28
32
|
data-testid="ComboProvider"
|
|
29
33
|
theme={themeConfig.antdTheme}
|
|
34
|
+
staticApi={IOC(IOCIdentifier.DialogHandler)}
|
|
30
35
|
>
|
|
31
36
|
<ThemeProvider
|
|
32
37
|
themes={themeConfig.supportedThemes as unknown as string[]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Button } from 'antd';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
AUTH_LOGOUT_DIALOG_CONTENT,
|
|
5
|
+
AUTH_LOGOUT_DIALOG_TITLE
|
|
6
|
+
} from '@config/Identifier';
|
|
7
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
8
|
+
import { useI18nInterface } from '../hook/useI18nInterface';
|
|
9
|
+
import { useIOC } from '../hook/useIOC';
|
|
10
|
+
import type { PageI18nInterface } from '@config/i18n';
|
|
11
|
+
|
|
12
|
+
export function LogoutButton() {
|
|
13
|
+
const IOC = useIOC();
|
|
14
|
+
const tt = useI18nInterface({
|
|
15
|
+
title: AUTH_LOGOUT_DIALOG_TITLE,
|
|
16
|
+
content: AUTH_LOGOUT_DIALOG_CONTENT
|
|
17
|
+
} as PageI18nInterface);
|
|
18
|
+
|
|
19
|
+
const onClick = useCallback(() => {
|
|
20
|
+
IOC(IOCIdentifier.DialogHandler).confirm({
|
|
21
|
+
title: tt.title,
|
|
22
|
+
content: tt.content,
|
|
23
|
+
onOk: () => {
|
|
24
|
+
IOC(IOCIdentifier.UserServiceInterface).logout();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}, [tt, IOC]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Button data-testid="LogoutButton" danger onClick={onClick}>
|
|
31
|
+
{tt.title}
|
|
32
|
+
</Button>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -45,6 +45,21 @@ export function ThemeSwitcher() {
|
|
|
45
45
|
const { theme, resolvedTheme, setTheme } = useTheme();
|
|
46
46
|
const mounted = useMountedClient();
|
|
47
47
|
|
|
48
|
+
// 如果组件未挂载,返回空的 Select 以避免闪烁
|
|
49
|
+
if (!mounted) {
|
|
50
|
+
return (
|
|
51
|
+
<Select
|
|
52
|
+
data-testid="ThemeSwitcher"
|
|
53
|
+
loading
|
|
54
|
+
value="system"
|
|
55
|
+
options={[]}
|
|
56
|
+
style={{ width: 120 }}
|
|
57
|
+
className="min-w-40 max-w-full"
|
|
58
|
+
disabled
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
const themeOptions = ['system', ...supportedThemes!].map((themeName) => {
|
|
49
64
|
const { i18nkey, colors, icons } = colorMap[themeName] || colorMap.light;
|
|
50
65
|
const [currentColor, normalColor] = colors;
|
|
@@ -74,13 +89,11 @@ export function ThemeSwitcher() {
|
|
|
74
89
|
return (
|
|
75
90
|
<Select
|
|
76
91
|
data-testid="ThemeSwitcher"
|
|
77
|
-
|
|
78
|
-
value={mounted ? theme : themeOptions[0]?.key}
|
|
92
|
+
value={theme}
|
|
79
93
|
onChange={setTheme}
|
|
80
94
|
options={themeOptions}
|
|
81
95
|
style={{ width: 120 }}
|
|
82
96
|
className="min-w-40 max-w-full"
|
|
83
|
-
disabled={!mounted}
|
|
84
97
|
/>
|
|
85
98
|
);
|
|
86
99
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { createContext } from 'react';
|
|
4
|
-
import type {
|
|
4
|
+
import type { IOCIdentifierMap } from '@config/IOCIdentifier';
|
|
5
|
+
import type {
|
|
6
|
+
IOCContainerInterface,
|
|
7
|
+
IOCFunctionInterface
|
|
8
|
+
} from '@qlover/corekit-bridge';
|
|
5
9
|
|
|
6
|
-
export const IOCContext = createContext<
|
|
10
|
+
export const IOCContext = createContext<IOCFunctionInterface<
|
|
11
|
+
IOCIdentifierMap,
|
|
12
|
+
IOCContainerInterface
|
|
13
|
+
> | null>(null);
|
package/package.json
CHANGED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export const eslintPluginTestId = {
|
|
2
|
-
rules: {
|
|
3
|
-
'require-root-testid': {
|
|
4
|
-
meta: {
|
|
5
|
-
type: 'problem',
|
|
6
|
-
docs: {
|
|
7
|
-
description:
|
|
8
|
-
'Enforce data-testid attribute on root elements of TSX components',
|
|
9
|
-
category: 'Best Practices',
|
|
10
|
-
recommended: true
|
|
11
|
-
},
|
|
12
|
-
fixable: 'code',
|
|
13
|
-
schema: []
|
|
14
|
-
},
|
|
15
|
-
create(context) {
|
|
16
|
-
return {
|
|
17
|
-
JSXElement(node) {
|
|
18
|
-
// 检查是否是组件的根元素
|
|
19
|
-
const isRootElement = (node) => {
|
|
20
|
-
const parent = node.parent;
|
|
21
|
-
if (!parent) return false;
|
|
22
|
-
|
|
23
|
-
// 如果父节点是 return 语句或者是箭头函数的主体,说明这是根元素
|
|
24
|
-
if (
|
|
25
|
-
parent.type === 'ReturnStatement' ||
|
|
26
|
-
(parent.type === 'ArrowFunctionExpression' &&
|
|
27
|
-
parent.body === node)
|
|
28
|
-
) {
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return false;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
if (isRootElement(node)) {
|
|
36
|
-
const hasTestId = node.openingElement.attributes.some(
|
|
37
|
-
(attr) =>
|
|
38
|
-
attr.type === 'JSXAttribute' &&
|
|
39
|
-
attr.name.name === 'data-testid'
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
if (!hasTestId) {
|
|
43
|
-
context.report({
|
|
44
|
-
node: node.openingElement,
|
|
45
|
-
message:
|
|
46
|
-
'Root element of a component must have a data-testid attribute',
|
|
47
|
-
fix(fixer) {
|
|
48
|
-
// 获取组件名称作为 testid 的默认值
|
|
49
|
-
let componentName = '';
|
|
50
|
-
let current = node;
|
|
51
|
-
while (current) {
|
|
52
|
-
if (
|
|
53
|
-
current.type === 'FunctionDeclaration' ||
|
|
54
|
-
current.type === 'VariableDeclarator'
|
|
55
|
-
) {
|
|
56
|
-
componentName = current.id?.name || '';
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
current = current.parent;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const sourceCode = context.getSourceCode();
|
|
63
|
-
const openingElement = node.openingElement;
|
|
64
|
-
const tagToken = sourceCode.getFirstToken(openingElement);
|
|
65
|
-
const hasAttributes = openingElement.attributes.length > 0;
|
|
66
|
-
|
|
67
|
-
if (hasAttributes) {
|
|
68
|
-
// 如果有其他属性,在第一个属性前添加
|
|
69
|
-
const firstAttribute = openingElement.attributes[0];
|
|
70
|
-
const indent = sourceCode
|
|
71
|
-
.getText()
|
|
72
|
-
.slice(0, firstAttribute.range[0])
|
|
73
|
-
.match(/\s*$/)[0];
|
|
74
|
-
return fixer.insertTextBefore(
|
|
75
|
-
firstAttribute,
|
|
76
|
-
`data-testid='${componentName || 'component'}'${indent}`
|
|
77
|
-
);
|
|
78
|
-
} else {
|
|
79
|
-
// 如果没有其他属性,在标签名后面添加
|
|
80
|
-
return fixer.insertTextAfter(
|
|
81
|
-
tagToken,
|
|
82
|
-
` data-testid='${componentName || 'component'}'`
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { generateLocales } from '../build/generateLocales';
|
|
2
|
-
import type { NextConfig } from 'next';
|
|
3
|
-
|
|
4
|
-
export function withGenerateLocales(nextConfig: NextConfig = {}) {
|
|
5
|
-
return {
|
|
6
|
-
...nextConfig,
|
|
7
|
-
onDevelopmentStart: async () => {
|
|
8
|
-
try {
|
|
9
|
-
await generateLocales();
|
|
10
|
-
console.log('✅ Locales generated successfully');
|
|
11
|
-
} catch (error) {
|
|
12
|
-
console.error('❌ Failed to generate locales:', error);
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
webpack: (config, options) => {
|
|
16
|
-
const { dev, isServer } = options;
|
|
17
|
-
|
|
18
|
-
// 在生产构建开始时生成本地化文件
|
|
19
|
-
if (!dev && isServer) {
|
|
20
|
-
generateLocales().catch((error) => {
|
|
21
|
-
console.error('❌ Failed to generate locales:', error);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 如果原配置有 webpack 配置,则调用它
|
|
26
|
-
if (typeof nextConfig.webpack === 'function') {
|
|
27
|
-
return nextConfig.webpack(config, options);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return config;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// ! dont't import tsx, only ts file
|
|
2
|
-
import type { InversifyContainer } from '@/base/cases/InversifyContainer';
|
|
3
|
-
import { BootstrapClient } from './bootstraps/BootstrapClient';
|
|
4
|
-
import type { IOCRegisterInterface } from '@qlover/corekit-bridge';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* IOC register options
|
|
8
|
-
*/
|
|
9
|
-
export type IocRegisterOptions = {
|
|
10
|
-
/**
|
|
11
|
-
* The app config
|
|
12
|
-
*/
|
|
13
|
-
appConfig: Record<string, unknown>;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* IOC container
|
|
18
|
-
*
|
|
19
|
-
* This is a alias of IOCContainerInterface, use it without care about the implementation.
|
|
20
|
-
*
|
|
21
|
-
* Need to achieve the effect: when the implementation class on the right side of the equal sign changes, the IOCContainer will change automatically
|
|
22
|
-
*/
|
|
23
|
-
export type IOCContainer = InversifyContainer;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* IOC register interface.
|
|
27
|
-
*
|
|
28
|
-
* This is shortcut interface, implement this interface, you can use any IOC container.
|
|
29
|
-
*
|
|
30
|
-
* Need to achieve the effect: when the implementation class on the right side of the equal sign changes, the IOCContainer will change automatically
|
|
31
|
-
*/
|
|
32
|
-
export type IOCRegister = IOCRegisterInterface<
|
|
33
|
-
IOCContainer,
|
|
34
|
-
IocRegisterOptions
|
|
35
|
-
>;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* IOC function
|
|
39
|
-
*
|
|
40
|
-
* This is the only and main exported content of the file
|
|
41
|
-
*
|
|
42
|
-
* @example use A class
|
|
43
|
-
* ```ts
|
|
44
|
-
* const userService = IOC(UserService);
|
|
45
|
-
* ```
|
|
46
|
-
*
|
|
47
|
-
* @example use A string identifier
|
|
48
|
-
*
|
|
49
|
-
* string identifier is shortcut for `IOCIdentifierMap` type, string key of `IOCIdentifier`
|
|
50
|
-
*
|
|
51
|
-
* ```ts
|
|
52
|
-
* const logger = IOC('Logger'); // Logger instance
|
|
53
|
-
*
|
|
54
|
-
* // or
|
|
55
|
-
* const logger = IOC(IOCIdentifier.Logger);
|
|
56
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
export const IOC = BootstrapClient.createSingletonIOC();
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
'use server';
|
|
2
|
-
import { getTranslations } from 'next-intl/server';
|
|
3
|
-
import type { PageI18nInterface } from '@config/i18n/PageI18nInterface';
|
|
4
|
-
|
|
5
|
-
export async function getServerI18n<T extends PageI18nInterface>(params: {
|
|
6
|
-
locale: string;
|
|
7
|
-
namespace?: string;
|
|
8
|
-
i18nInterface: T;
|
|
9
|
-
}): Promise<T> {
|
|
10
|
-
// Load translation messages from the HomePage namespace
|
|
11
|
-
const t = await getTranslations({
|
|
12
|
-
locale: params.locale,
|
|
13
|
-
namespace: params.namespace
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const result = Object.fromEntries(
|
|
17
|
-
Object.entries(params.i18nInterface).map(([key, value]) => {
|
|
18
|
-
if (typeof value === 'string') {
|
|
19
|
-
return [key, t(value)];
|
|
20
|
-
}
|
|
21
|
-
return [key, value];
|
|
22
|
-
})
|
|
23
|
-
) as T;
|
|
24
|
-
|
|
25
|
-
return result;
|
|
26
|
-
}
|