@qlover/create-app 0.10.1 → 0.10.3

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.
Files changed (214) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/dist/configs/_common/.github/workflows/general-check.yml +1 -1
  3. package/dist/configs/_common/.github/workflows/release.yml +2 -2
  4. package/dist/index.cjs +4 -4
  5. package/dist/index.js +4 -4
  6. package/dist/templates/next-app/config/IOCIdentifier.ts +2 -2
  7. package/dist/templates/next-app/config/Identifier/common/common.ts +14 -0
  8. package/dist/templates/next-app/config/Identifier/pages/index.ts +1 -0
  9. package/dist/templates/next-app/config/Identifier/pages/page.about.ts +20 -0
  10. package/dist/templates/next-app/config/common.ts +1 -1
  11. package/dist/templates/next-app/config/cookies.ts +23 -0
  12. package/dist/templates/next-app/config/i18n/AboutI18n.ts +14 -0
  13. package/dist/templates/next-app/config/i18n/i18nConfig.ts +3 -1
  14. package/dist/templates/next-app/config/i18n/index.ts +1 -0
  15. package/dist/templates/next-app/config/i18n/loginI18n.ts +8 -0
  16. package/dist/templates/next-app/config/theme.ts +4 -0
  17. package/dist/templates/next-app/eslint.config.mjs +9 -2
  18. package/dist/templates/next-app/next.config.ts +1 -0
  19. package/dist/templates/next-app/package.json +17 -6
  20. package/dist/templates/next-app/public/locales/en.json +5 -0
  21. package/dist/templates/next-app/public/locales/zh.json +5 -0
  22. package/dist/templates/next-app/src/app/[locale]/admin/AdminI18nProvider.tsx +37 -0
  23. package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +30 -6
  24. package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +1 -1
  25. package/dist/templates/next-app/src/app/[locale]/layout.tsx +47 -10
  26. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +1 -1
  27. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +22 -10
  28. package/dist/templates/next-app/src/app/[locale]/page.tsx +23 -8
  29. package/dist/templates/next-app/src/app/[locale]/register/page.tsx +21 -9
  30. package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +7 -28
  31. package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +7 -34
  32. package/dist/templates/next-app/src/app/api/admin/locales/route.ts +12 -34
  33. package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +7 -26
  34. package/dist/templates/next-app/src/app/api/admin/users/route.ts +14 -33
  35. package/dist/templates/next-app/src/app/api/locales/json/route.ts +13 -25
  36. package/dist/templates/next-app/src/app/api/user/login/route.ts +6 -46
  37. package/dist/templates/next-app/src/app/api/user/logout/route.ts +5 -24
  38. package/dist/templates/next-app/src/app/api/user/register/route.ts +6 -45
  39. package/dist/templates/next-app/src/app/manifest.ts +16 -0
  40. package/dist/templates/next-app/src/app/robots.txt +2 -0
  41. package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +3 -3
  42. package/dist/templates/next-app/src/base/cases/AppConfig.ts +14 -13
  43. package/dist/templates/next-app/src/base/cases/Datetime.ts +3 -3
  44. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +8 -2
  45. package/dist/templates/next-app/src/base/cases/DialogHandler.ts +30 -4
  46. package/dist/templates/next-app/src/base/cases/InversifyContainer.ts +16 -4
  47. package/dist/templates/next-app/src/base/cases/NavigateBridge.ts +20 -4
  48. package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +8 -2
  49. package/dist/templates/next-app/src/base/cases/ResourceState.ts +3 -3
  50. package/dist/templates/next-app/src/base/cases/RouterService.ts +28 -10
  51. package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +14 -2
  52. package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +1 -1
  53. package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +20 -5
  54. package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +31 -8
  55. package/dist/templates/next-app/src/base/port/AdminLayoutInterface.ts +4 -1
  56. package/dist/templates/next-app/src/base/port/AppApiInterface.ts +22 -0
  57. package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +27 -7
  58. package/dist/templates/next-app/src/base/port/IOCInterface.ts +9 -0
  59. package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +1 -1
  60. package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +3 -3
  61. package/dist/templates/next-app/src/base/services/I18nService.ts +20 -10
  62. package/dist/templates/next-app/src/base/services/ResourceService.ts +32 -11
  63. package/dist/templates/next-app/src/base/services/UserService.ts +12 -3
  64. package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +9 -6
  65. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +20 -5
  66. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +21 -4
  67. package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +12 -3
  68. package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +10 -2
  69. package/dist/templates/next-app/src/base/types/{PageProps.ts → AppPageRouter.ts} +4 -1
  70. package/dist/templates/next-app/src/base/types/PagesRouter.ts +9 -0
  71. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +20 -6
  72. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +27 -9
  73. package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +6 -4
  74. package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +39 -9
  75. package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +9 -2
  76. package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +37 -10
  77. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +9 -2
  78. package/dist/templates/next-app/src/i18n/loadMessages.ts +103 -0
  79. package/dist/templates/next-app/src/i18n/request.ts +3 -22
  80. package/dist/templates/next-app/src/pages/[locale]/about.tsx +61 -0
  81. package/dist/templates/next-app/src/pages/_app.tsx +50 -0
  82. package/dist/templates/next-app/src/pages/_document.tsx +13 -0
  83. package/dist/templates/next-app/src/{middleware.ts → proxy.ts} +2 -1
  84. package/dist/templates/next-app/src/server/AppErrorApi.ts +1 -1
  85. package/dist/templates/next-app/src/server/AppPageRouteParams.ts +107 -0
  86. package/dist/templates/next-app/src/server/AppSuccessApi.ts +1 -1
  87. package/dist/templates/next-app/src/server/NextApiServer.ts +62 -0
  88. package/dist/templates/next-app/src/server/PagesRouteParams.ts +146 -0
  89. package/dist/templates/next-app/src/server/PasswordEncrypt.ts +8 -2
  90. package/dist/templates/next-app/src/server/ServerAuth.ts +20 -5
  91. package/dist/templates/next-app/src/server/{sqlBridges/SupabaseBridge.ts → SupabaseBridge.ts} +50 -8
  92. package/dist/templates/next-app/src/server/UserCredentialToken.ts +8 -2
  93. package/dist/templates/next-app/src/server/controllers/AdminLocalesController.ts +86 -0
  94. package/dist/templates/next-app/src/server/controllers/AdminUserController.ts +42 -0
  95. package/dist/templates/next-app/src/server/controllers/LocalesController.ts +36 -0
  96. package/dist/templates/next-app/src/server/controllers/UserController.ts +86 -0
  97. package/dist/templates/next-app/src/server/port/AIControllerInterface.ts +8 -0
  98. package/dist/templates/next-app/src/server/port/AdminLocalesControllerInterface.ts +21 -0
  99. package/dist/templates/next-app/src/server/port/AdminUserControllerInterface.ts +11 -0
  100. package/dist/templates/next-app/src/server/port/LocalesControllerInterface.ts +10 -0
  101. package/dist/templates/next-app/src/server/port/{ParamsHandlerInterface.ts → RouteParamsnHandlerInterface.ts} +9 -2
  102. package/dist/templates/next-app/src/server/port/ServerInterface.ts +2 -2
  103. package/dist/templates/next-app/src/server/port/UserControllerInerface.ts +8 -0
  104. package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +1 -1
  105. package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +2 -2
  106. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +31 -9
  107. package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +15 -6
  108. package/dist/templates/next-app/src/server/services/AIService.ts +3 -1
  109. package/dist/templates/next-app/src/server/services/AdminAuthPlugin.ts +5 -2
  110. package/dist/templates/next-app/src/{base → server}/services/AdminLocalesService.ts +2 -2
  111. package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +29 -14
  112. package/dist/templates/next-app/src/server/services/ApiUserService.ts +6 -3
  113. package/dist/templates/next-app/src/server/services/UserService.ts +12 -3
  114. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +24 -7
  115. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +17 -5
  116. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +17 -11
  117. package/dist/templates/next-app/src/styles/css/antd-themes/_common/_default.css +0 -44
  118. package/dist/templates/next-app/src/styles/css/antd-themes/_common/dark.css +0 -44
  119. package/dist/templates/next-app/src/styles/css/antd-themes/_common/pink.css +0 -44
  120. package/dist/templates/next-app/src/styles/css/index.css +1 -1
  121. package/dist/templates/next-app/src/styles/css/scrollbar.css +34 -0
  122. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +34 -11
  123. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +69 -39
  124. package/dist/templates/next-app/src/uikit/components/ClientRootProvider.tsx +64 -0
  125. package/dist/templates/next-app/src/uikit/components/ClinetRenderProvider.tsx +42 -0
  126. package/dist/templates/next-app/src/uikit/components/IOCProvider.tsx +34 -0
  127. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +7 -1
  128. package/dist/templates/next-app/src/uikit/components-app/AppBridge.tsx +17 -0
  129. package/dist/templates/next-app/src/uikit/components-app/AppRoutePage.tsx +112 -0
  130. package/dist/templates/next-app/src/uikit/{components → components-app}/LanguageSwitcher.tsx +15 -19
  131. package/dist/templates/next-app/src/uikit/{components → components-app}/ThemeSwitcher.tsx +53 -52
  132. package/dist/templates/next-app/src/uikit/components-pages/LanguageSwitcher.tsx +98 -0
  133. package/dist/templates/next-app/src/uikit/components-pages/PagesRoutePage.tsx +93 -0
  134. package/dist/templates/next-app/src/uikit/context/IOCContext.ts +16 -4
  135. package/dist/templates/next-app/src/uikit/hook/useStrictEffect.ts +32 -0
  136. package/dist/templates/next-app/tsconfig.json +3 -2
  137. package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +3 -1
  138. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfig.ts +19 -19
  139. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +8 -8
  140. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +9 -9
  141. package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +1 -1
  142. package/dist/templates/react-app/__tests__/__mocks__/i18nextHttpBackend.ts +5 -5
  143. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +9 -5
  144. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +8 -4
  145. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +4 -4
  146. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +5 -5
  147. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +3 -3
  148. package/dist/templates/react-app/__tests__/src/uikit/components/chatMessage/ChatRoot.test.tsx +1 -1
  149. package/dist/templates/react-app/docs/en/components/chat-message-component.md +35 -29
  150. package/dist/templates/react-app/docs/en/components/chat-message-refactor.md +18 -5
  151. package/dist/templates/react-app/docs/en/components/message-base-list-component.md +11 -12
  152. package/dist/templates/react-app/docs/en/request.md +1 -3
  153. package/dist/templates/react-app/docs/zh/components/chat-message-component.md +35 -29
  154. package/dist/templates/react-app/docs/zh/components/chat-message-refactor.md +18 -5
  155. package/dist/templates/react-app/docs/zh/components/message-base-list-component.md +11 -12
  156. package/dist/templates/react-app/docs/zh/request.md +1 -3
  157. package/dist/templates/react-app/eslint.config.mjs +10 -5
  158. package/dist/templates/react-app/src/App.tsx +1 -1
  159. package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +2 -2
  160. package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +14 -11
  161. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +10 -16
  162. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -10
  163. package/dist/templates/react-app/src/base/cases/AppConfig.ts +19 -19
  164. package/dist/templates/react-app/src/base/cases/DialogHandler.ts +28 -5
  165. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +5 -2
  166. package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +16 -4
  167. package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +1 -1
  168. package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +6 -3
  169. package/dist/templates/react-app/src/base/cases/RequestLogger.ts +17 -8
  170. package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +4 -6
  171. package/dist/templates/react-app/src/base/cases/ResourceState.ts +3 -3
  172. package/dist/templates/react-app/src/base/cases/RouterLoader.ts +3 -3
  173. package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +1 -1
  174. package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -2
  175. package/dist/templates/react-app/src/base/port/IOCInterface.ts +4 -2
  176. package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +5 -5
  177. package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +7 -7
  178. package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +8 -8
  179. package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +4 -2
  180. package/dist/templates/react-app/src/base/services/BaseLayoutService.ts +3 -3
  181. package/dist/templates/react-app/src/base/services/I18nService.ts +24 -13
  182. package/dist/templates/react-app/src/base/services/IdentifierService.ts +21 -26
  183. package/dist/templates/react-app/src/base/services/RouteService.ts +8 -8
  184. package/dist/templates/react-app/src/base/services/UserBootstrap.ts +2 -2
  185. package/dist/templates/react-app/src/base/services/UserGatewayPlugin.ts +9 -5
  186. package/dist/templates/react-app/src/base/services/UserService.ts +10 -4
  187. package/dist/templates/react-app/src/core/bootstraps/BootstrapClient.ts +3 -1
  188. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +5 -2
  189. package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +11 -4
  190. package/dist/templates/react-app/src/core/clientIoc/ClientIOCRegister.ts +5 -4
  191. package/dist/templates/react-app/src/pages/base/Layout.tsx +1 -1
  192. package/dist/templates/react-app/src/styles/css/antd-themes/_common/_default.css +0 -44
  193. package/dist/templates/react-app/src/styles/css/antd-themes/_common/dark.css +0 -44
  194. package/dist/templates/react-app/src/styles/css/antd-themes/_common/pink.css +0 -44
  195. package/dist/templates/react-app/src/styles/css/index.css +1 -1
  196. package/dist/templates/react-app/src/styles/css/scrollbar.css +34 -0
  197. package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +2 -2
  198. package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +4 -4
  199. package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +8 -2
  200. package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +6 -6
  201. package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +1 -1
  202. package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +1 -3
  203. package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +4 -1
  204. package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +39 -16
  205. package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +4 -2
  206. package/package.json +3 -3
  207. package/dist/templates/next-app/src/server/PageParams.ts +0 -66
  208. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +0 -80
  209. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +0 -65
  210. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +0 -58
  211. package/dist/templates/next-app/src/uikit/components/NextIntlProvider.tsx +0 -21
  212. package/dist/templates/react-app/makes/eslint-utils.mjs +0 -195
  213. /package/dist/templates/next-app/{src/app/[locale] → public}/favicon.ico +0 -0
  214. /package/dist/templates/next-app/src/uikit/{components → components-app}/LogoutButton.tsx +0 -0
@@ -14,11 +14,20 @@ import { useMountedClient } from '@brain-toolkit/react-kit';
14
14
  import { Dropdown } from 'antd';
15
15
  import { clsx } from 'clsx';
16
16
  import { useTheme } from 'next-themes';
17
- import { useMemo } from 'react';
17
+ import { useEffect, useMemo } from 'react';
18
+ import {
19
+ COMMON_THEME_DARK,
20
+ COMMON_THEME_DEFAULT,
21
+ COMMON_THEME_LIGHT,
22
+ COMMON_THEME_PINK
23
+ } from '@config/Identifier';
24
+ import { I } from '@config/IOCIdentifier';
18
25
  import { type SupportedTheme, themeConfig } from '@config/theme';
26
+ import { useIOC } from '../hook/useIOC';
27
+ import { useWarnTranslations } from '../hook/useWarnTranslations';
19
28
  import type { ItemType } from 'antd/es/menu/interface';
20
29
 
21
- const { supportedThemes } = themeConfig;
30
+ const { supportedThemes, storageKey } = themeConfig;
22
31
 
23
32
  const defaultTheme = supportedThemes[0] || 'system';
24
33
  const themesList = ['system', ...supportedThemes];
@@ -31,71 +40,76 @@ const colorMap: Record<
31
40
  normalColor: string;
32
41
  Icon: React.ElementType;
33
42
  SelectedIcon: React.ElementType;
34
- TriggerIcon: React.ElementType;
35
43
  }
36
44
  > = {
37
45
  system: {
38
- i18nkey: 'System',
46
+ i18nkey: COMMON_THEME_DEFAULT,
39
47
  selectedColor: 'text-text',
40
48
  normalColor: 'text-text-secondary',
41
49
  Icon: SettingOutlined,
42
- SelectedIcon: SettingFilled,
43
- TriggerIcon: SettingOutlined
50
+ SelectedIcon: SettingFilled
44
51
  },
45
52
  light: {
46
- i18nkey: 'Light',
53
+ i18nkey: COMMON_THEME_LIGHT,
47
54
  selectedColor: 'text-text',
48
55
  normalColor: 'text-text-secondary',
49
56
  Icon: SunOutlined,
50
- SelectedIcon: SunFilled,
51
- TriggerIcon: SunOutlined
57
+ SelectedIcon: SunFilled
52
58
  },
53
59
  dark: {
54
- i18nkey: 'Dark',
60
+ i18nkey: COMMON_THEME_DARK,
55
61
  selectedColor: 'text-[#9333ea]',
56
62
  normalColor: 'text-[#a855f7]',
57
63
  Icon: MoonOutlined,
58
- SelectedIcon: MoonFilled,
59
- TriggerIcon: MoonOutlined
64
+ SelectedIcon: MoonFilled
60
65
  },
61
66
  pink: {
62
- i18nkey: 'Pink',
67
+ i18nkey: COMMON_THEME_PINK,
63
68
  selectedColor: 'text-[#f472b6]',
64
69
  normalColor: 'text-[#ec4899]',
65
70
  Icon: HeartOutlined,
66
- SelectedIcon: HeartFilled,
67
- TriggerIcon: HeartOutlined
71
+ SelectedIcon: HeartFilled
68
72
  }
69
73
  };
70
74
 
71
75
  export function ThemeSwitcher() {
72
76
  const { theme: currentTheme, resolvedTheme, setTheme } = useTheme();
73
77
  const mounted = useMountedClient();
78
+ const cookieStorage = useIOC(I.CookieStorage);
79
+ const t = useWarnTranslations();
80
+
81
+ useEffect(() => {
82
+ if (resolvedTheme) {
83
+ cookieStorage.setItem(storageKey, resolvedTheme);
84
+ }
85
+ }, [resolvedTheme, cookieStorage]);
74
86
 
75
- const themeOptions = themesList.map((themeName) => {
76
- const { i18nkey, selectedColor, normalColor, Icon, SelectedIcon } =
77
- colorMap[themeName] || colorMap.light;
87
+ const themeOptions = useMemo(() => {
88
+ return themesList.map((themeName) => {
89
+ const { i18nkey, selectedColor, normalColor, Icon, SelectedIcon } =
90
+ colorMap[themeName] || colorMap.light;
78
91
 
79
- const isCurrentTheme =
80
- currentTheme === themeName ||
81
- (themeName === resolvedTheme && currentTheme === 'system');
92
+ const isCurrentTheme =
93
+ currentTheme === themeName ||
94
+ (themeName === resolvedTheme && currentTheme === 'system');
82
95
 
83
- return {
84
- key: themeName,
85
- value: themeName,
86
- label: (
87
- <div
88
- className={clsx(
89
- 'flex items-center gap-2',
90
- isCurrentTheme ? selectedColor : normalColor
91
- )}
92
- >
93
- {isCurrentTheme ? <SelectedIcon /> : <Icon />}
94
- <span>{i18nkey}</span>
95
- </div>
96
- )
97
- } as ItemType;
98
- });
96
+ return {
97
+ key: themeName,
98
+ value: themeName,
99
+ label: (
100
+ <div
101
+ className={clsx(
102
+ 'flex items-center gap-2',
103
+ isCurrentTheme ? selectedColor : normalColor
104
+ )}
105
+ >
106
+ {isCurrentTheme ? <SelectedIcon /> : <Icon />}
107
+ <span>{t(i18nkey)}</span>
108
+ </div>
109
+ )
110
+ } as ItemType;
111
+ });
112
+ }, [currentTheme, resolvedTheme, t]);
99
113
 
100
114
  const nextTheme = useMemo(() => {
101
115
  if (!currentTheme) {
@@ -106,26 +120,13 @@ export function ThemeSwitcher() {
106
120
  return supportedThemes[targetIndex % supportedThemes.length];
107
121
  }, [currentTheme]);
108
122
 
109
- const TriggerIcon = colorMap[currentTheme || defaultTheme].TriggerIcon;
110
-
111
- if (!mounted) {
112
- return (
113
- <span
114
- data-testid="ThemeSwitcher"
115
- className="text-text hover:text-text-hover cursor-pointer text-lg transition-colors"
116
- >
117
- <SettingOutlined />
118
- </span>
119
- );
120
- }
121
-
122
123
  return (
123
124
  <Dropdown
124
125
  data-testid="ThemeSwitcherDropdown"
125
126
  trigger={['hover']}
126
127
  menu={{
127
128
  items: themeOptions,
128
- selectedKeys: [currentTheme!],
129
+ selectedKeys: mounted ? [resolvedTheme!] : undefined,
129
130
  onClick: ({ key }) => {
130
131
  setTheme(key);
131
132
  }
@@ -136,7 +137,7 @@ export function ThemeSwitcher() {
136
137
  className="text-text hover:text-text-hover cursor-pointer text-lg transition-colors"
137
138
  onClick={() => setTheme(nextTheme)}
138
139
  >
139
- <TriggerIcon />
140
+ <SunOutlined />
140
141
  </span>
141
142
  </Dropdown>
142
143
  );
@@ -0,0 +1,98 @@
1
+ 'use client';
2
+
3
+ import { TranslationOutlined } from '@ant-design/icons';
4
+ import { Dropdown } from 'antd';
5
+ import { useRouter } from 'next/router';
6
+ import { useLocale } from 'next-intl';
7
+ import { useCallback, useMemo, useState } from 'react';
8
+ import { useLocaleRoutes } from '@config/common';
9
+ import { i18nConfig } from '@config/i18n';
10
+ import type { LocaleType } from '@config/i18n';
11
+ import type { ItemType } from 'antd/es/menu/interface';
12
+
13
+ export function LanguageSwitcher() {
14
+ const router = useRouter();
15
+ const currentLocale = useLocale() as LocaleType;
16
+ const [isPending, setIsPending] = useState(false);
17
+
18
+ const options: ItemType[] = useMemo(() => {
19
+ return i18nConfig.supportedLngs.map(
20
+ (lang) =>
21
+ ({
22
+ type: 'item',
23
+ key: lang,
24
+ value: lang,
25
+ label:
26
+ i18nConfig.localeNames[lang as keyof typeof i18nConfig.localeNames]
27
+ }) as ItemType
28
+ );
29
+ }, []);
30
+
31
+ const handleLanguageChange = useCallback(
32
+ async (value: string) => {
33
+ if (isPending || value === currentLocale) return;
34
+
35
+ setIsPending(true);
36
+
37
+ try {
38
+ // Get current path
39
+ let newPath = router.asPath;
40
+
41
+ if (useLocaleRoutes) {
42
+ // Replace locale in path (e.g., /en/about -> /zh/about)
43
+ const pathWithoutLocale = newPath.replace(
44
+ new RegExp(`^/${currentLocale}(/|$)`),
45
+ '/'
46
+ );
47
+ // Remove leading slash if path is root
48
+ const cleanPath = pathWithoutLocale === '/' ? '' : pathWithoutLocale;
49
+ newPath = `/${value}${cleanPath}`;
50
+ } else {
51
+ // If not using locale routes, just update query param
52
+ newPath = router.pathname;
53
+ router.replace({
54
+ pathname: router.pathname,
55
+ query: { ...router.query, locale: value }
56
+ });
57
+ setIsPending(false);
58
+ return;
59
+ }
60
+
61
+ // Replace the route
62
+ router.replace(newPath);
63
+ } finally {
64
+ setIsPending(false);
65
+ }
66
+ },
67
+ [router, currentLocale, isPending]
68
+ );
69
+
70
+ const nextLocale = useMemo(() => {
71
+ const targetIndex = i18nConfig.supportedLngs.indexOf(currentLocale) + 1;
72
+ return i18nConfig.supportedLngs[
73
+ targetIndex % i18nConfig.supportedLngs.length
74
+ ];
75
+ }, [currentLocale]);
76
+
77
+ return (
78
+ <Dropdown
79
+ data-testid="LanguageSwitcherDropdown"
80
+ trigger={['hover']}
81
+ menu={{
82
+ selectedKeys: [currentLocale],
83
+ items: options,
84
+ onClick: ({ key }) => {
85
+ handleLanguageChange(key);
86
+ }
87
+ }}
88
+ >
89
+ <span
90
+ data-testid="LanguageSwitcher"
91
+ className="text-text hover:text-text-hover cursor-pointer text-lg transition-colors"
92
+ onClick={() => handleLanguageChange(nextLocale)}
93
+ >
94
+ <TranslationOutlined />
95
+ </span>
96
+ </Dropdown>
97
+ );
98
+ }
@@ -0,0 +1,93 @@
1
+ import { TeamOutlined } from '@ant-design/icons';
2
+ import { clsx } from 'clsx';
3
+ import { useLocale } from 'next-intl';
4
+ import { useMemo } from 'react';
5
+ import { LanguageSwitcher } from './LanguageSwitcher';
6
+ import { LocaleLink } from '../components/LocaleLink';
7
+ import { ThemeSwitcher } from '../components-app/ThemeSwitcher';
8
+ import type { AppRoutePageProps } from '../components-app/AppRoutePage';
9
+
10
+ /**
11
+ * App Route Page
12
+ *
13
+ * 主要用于 /src/app 目录下页面的基础布局,包含头部、主体内容等
14
+ *
15
+ * @description
16
+ * - /src/app/[locale]/page.tsx
17
+ * - /src/app/[locale]/login/page.tsx
18
+ *
19
+ */
20
+ export function PagesRoutePage({
21
+ children,
22
+ showAdminButton,
23
+ mainProps,
24
+ headerClassName,
25
+ tt,
26
+ headerHref = '/',
27
+ ...props
28
+ }: AppRoutePageProps) {
29
+ const locale = useLocale();
30
+ const adminTitle = tt.adminTitle;
31
+
32
+ const actions = useMemo(() => {
33
+ return [
34
+ showAdminButton && (
35
+ <LocaleLink
36
+ key="admin-button"
37
+ href="/admin"
38
+ title={adminTitle}
39
+ locale={locale}
40
+ className="text-text hover:text-text-hover cursor-pointer text-lg transition-colors"
41
+ >
42
+ <TeamOutlined className="text-lg text-text" />
43
+ </LocaleLink>
44
+ ),
45
+
46
+ <LanguageSwitcher key="language-switcher" />,
47
+
48
+ <ThemeSwitcher key="theme-switcher" />
49
+ ].filter(Boolean);
50
+ }, [adminTitle, showAdminButton, locale]);
51
+
52
+ return (
53
+ <div
54
+ data-testid="AppRoutePage"
55
+ className="flex flex-col min-h-screen"
56
+ {...props}
57
+ >
58
+ <header
59
+ data-testid="BaseHeader"
60
+ className="h-14 bg-secondary border-b border-c-border sticky top-0 z-50"
61
+ >
62
+ <div
63
+ className={clsx(
64
+ 'flex items-center justify-between h-full px-4 mx-auto max-w-7xl',
65
+ headerClassName
66
+ )}
67
+ >
68
+ <div className="flex items-center">
69
+ <LocaleLink
70
+ data-testid="BaseHeaderLogo"
71
+ title={tt.title}
72
+ href={headerHref}
73
+ locale={locale}
74
+ className="flex items-center hover:opacity-80 transition-opacity"
75
+ >
76
+ <span
77
+ data-testid="base-header-app-name"
78
+ className="text-lg font-semibold text-text"
79
+ >
80
+ {tt.title}
81
+ </span>
82
+ </LocaleLink>
83
+ </div>
84
+ <div className="flex items-center gap-2 md:gap-4">{actions}</div>
85
+ </div>
86
+ </header>
87
+
88
+ <main className="flex flex-1 flex-col bg-primary" {...mainProps}>
89
+ {children}
90
+ </main>
91
+ </div>
92
+ );
93
+ }
@@ -1,11 +1,23 @@
1
1
  'use client';
2
2
 
3
+ import {
4
+ type IOCContainerInterface,
5
+ type IOCFunctionInterface
6
+ } from '@qlover/corekit-bridge';
3
7
  import { createContext } from 'react';
8
+ import { ClientIOC } from '@/core/clientIoc/ClientIOC';
4
9
  import type { IOCIdentifierMap } from '@config/IOCIdentifier';
5
- import type {
6
- IOCContainerInterface,
7
- IOCFunctionInterface
8
- } from '@qlover/corekit-bridge';
10
+
11
+ // export const IOCInstance = new ClientIOC();
12
+
13
+ // export const IOCContext =
14
+ // createContext<IOCInterface<IOCIdentifierMap, IOCContainerInterface>>(
15
+ // IOCInstance
16
+ // );
17
+
18
+ export const clientIOC = new ClientIOC();
19
+
20
+ export const IOCInstance = clientIOC.create();
9
21
 
10
22
  export const IOCContext = createContext<IOCFunctionInterface<
11
23
  IOCIdentifierMap,
@@ -0,0 +1,32 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ /**
4
+ * In React Strict Mode, ensure that the effect is executed only once when each dependency changes
5
+ * @param effect The effect function to execute
6
+ * @param deps The dependency array
7
+ */
8
+ export const useStrictEffect = (
9
+ effect: () => void | (() => void),
10
+ deps?: React.DependencyList
11
+ ) => {
12
+ const mountedRef = useRef(false);
13
+ const depsRef = useRef(deps);
14
+
15
+ useEffect(() => {
16
+ // Check if the dependencies have changed
17
+ const depsChanged =
18
+ !deps ||
19
+ !depsRef.current ||
20
+ deps.some((dep, i) => dep !== depsRef.current![i]);
21
+
22
+ // Update the dependency reference
23
+ depsRef.current = deps;
24
+
25
+ // If it's the first mount or the dependencies have changed, execute the effect
26
+ if (!mountedRef.current || depsChanged) {
27
+ mountedRef.current = true;
28
+ return effect();
29
+ }
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ }, deps);
32
+ };
@@ -11,7 +11,7 @@
11
11
  "moduleResolution": "bundler",
12
12
  "resolveJsonModule": true,
13
13
  "isolatedModules": true,
14
- "jsx": "preserve",
14
+ "jsx": "react-jsx",
15
15
  "incremental": true,
16
16
  "experimentalDecorators": true,
17
17
  "emitDecoratorMetadata": true,
@@ -32,7 +32,8 @@
32
32
  "src/**/*.tsx",
33
33
  "migrations/**/*.ts",
34
34
  ".next/types/**/*.ts",
35
- "config/**/*.ts"
35
+ "config/**/*.ts",
36
+ ".next/dev/types/**/*.ts"
36
37
  ],
37
38
  "exclude": ["node_modules"]
38
39
  }
@@ -7,7 +7,9 @@ export interface BootstrapTestArgs extends Omit<BootstrapClientArgs, 'ioc'> {
7
7
  }
8
8
 
9
9
  export class BootstrapTest {
10
- static async main(args: BootstrapTestArgs): Promise<BootstrapClientArgs> {
10
+ public static async main(
11
+ args: BootstrapTestArgs
12
+ ): Promise<BootstrapClientArgs> {
11
13
  const result = await BootstrapClient.main({ ...args, ioc: testIOC });
12
14
  return result;
13
15
  }
@@ -2,17 +2,17 @@ import { name, version } from '../../package.json';
2
2
  import type { EnvConfigInterface } from '@qlover/corekit-bridge';
3
3
 
4
4
  export class MockAppConfig implements EnvConfigInterface {
5
- appName = name;
5
+ public appName = name;
6
6
 
7
- appVersion = version;
7
+ public appVersion = version;
8
8
 
9
- env: string = 'test';
9
+ public env: string = 'test';
10
10
 
11
- userTokenStorageKey = '__fe_user_token__';
11
+ public userTokenStorageKey = '__fe_user_token__';
12
12
 
13
- userInfoStorageKey = '__fe_user_info__';
13
+ public userInfoStorageKey = '__fe_user_info__';
14
14
 
15
- openAiModels = [
15
+ public openAiModels = [
16
16
  'gpt-4o-mini',
17
17
  'gpt-3.5-turbo',
18
18
  'gpt-3.5-turbo-2',
@@ -20,29 +20,29 @@ export class MockAppConfig implements EnvConfigInterface {
20
20
  'gpt-4-32k'
21
21
  ];
22
22
 
23
- openAiBaseUrl = '';
23
+ public openAiBaseUrl = '';
24
24
 
25
- openAiToken = '';
25
+ public openAiToken = '';
26
26
 
27
- openAiTokenPrefix = '';
27
+ public openAiTokenPrefix = '';
28
28
 
29
- openAiRequireToken = true;
29
+ public openAiRequireToken = true;
30
30
 
31
- loginUser = '';
31
+ public loginUser = '';
32
32
 
33
- loginPassword = '';
33
+ public loginPassword = '';
34
34
 
35
- feApiBaseUrl = '';
35
+ public feApiBaseUrl = '';
36
36
 
37
- userApiBaseUrl = '';
37
+ public userApiBaseUrl = '';
38
38
 
39
- aiApiBaseUrl = 'https://api.openai.com/v1';
39
+ public aiApiBaseUrl = 'https://api.openai.com/v1';
40
40
 
41
- aiApiToken = '';
41
+ public aiApiToken = '';
42
42
 
43
- aiApiTokenPrefix = 'Bearer';
43
+ public aiApiTokenPrefix = 'Bearer';
44
44
 
45
- aiApiRequireToken = true;
45
+ public aiApiRequireToken = true;
46
46
 
47
- bootHref = '';
47
+ public bootHref = '';
48
48
  }
@@ -5,12 +5,12 @@ import type { AntdStaticApiInterface } from '@brain-toolkit/antd-theme-override/
5
5
  export class MockDialogHandler
6
6
  implements InteractionHubInterface, AntdStaticApiInterface
7
7
  {
8
- setMessage = vi.fn();
9
- setModal = vi.fn();
10
- setNotification = vi.fn();
11
- success = vi.fn();
12
- error = vi.fn();
13
- info = vi.fn();
14
- warn = vi.fn();
15
- confirm = vi.fn();
8
+ public setMessage = vi.fn();
9
+ public setModal = vi.fn();
10
+ public setNotification = vi.fn();
11
+ public success = vi.fn();
12
+ public error = vi.fn();
13
+ public info = vi.fn();
14
+ public warn = vi.fn();
15
+ public confirm = vi.fn();
16
16
  }
@@ -2,13 +2,13 @@ import { vi } from 'vitest';
2
2
  import type { LoggerInterface } from '@qlover/logger';
3
3
 
4
4
  export class MockLogger implements LoggerInterface {
5
- info = vi.fn();
6
- error = vi.fn();
7
- debug = vi.fn();
8
- warn = vi.fn();
9
- trace = vi.fn();
10
- log = vi.fn();
11
- fatal = vi.fn();
12
- addAppender = vi.fn();
13
- context = vi.fn();
5
+ public info = vi.fn();
6
+ public error = vi.fn();
7
+ public debug = vi.fn();
8
+ public warn = vi.fn();
9
+ public trace = vi.fn();
10
+ public log = vi.fn();
11
+ public fatal = vi.fn();
12
+ public addAppender = vi.fn();
13
+ public context = vi.fn();
14
14
  }
@@ -41,7 +41,7 @@ export function TestBootstrapsProvider({
41
41
  });
42
42
 
43
43
  return (
44
- <IOCContext.Provider data-testid="TestBootstrapsProvider" value={IOC}>
44
+ <IOCContext.Provider value={IOC}>
45
45
  <TestRouter
46
46
  initialEntries={routerInitialEntries}
47
47
  initialIndex={routerInitialIndex}
@@ -24,13 +24,13 @@ const resources: Record<string, Record<string, Record<string, string>>> = {
24
24
  * Implements the backend interface to avoid network requests
25
25
  */
26
26
  export class MockHttpBackend {
27
- type = 'backend';
27
+ public type = 'backend';
28
28
 
29
29
  /**
30
30
  * Initialize the backend
31
31
  * This is called by i18next when .use() is called
32
32
  */
33
- init(
33
+ public init(
34
34
  _services: unknown,
35
35
  _backendOptions: unknown,
36
36
  _i18nextOptions: unknown,
@@ -60,7 +60,7 @@ export class MockHttpBackend {
60
60
  * Read translation data for a language and namespace
61
61
  * This method is called by i18next to load translations
62
62
  */
63
- read(
63
+ public read(
64
64
  language: string,
65
65
  namespace: string,
66
66
  callback: (error: Error | null, data?: Record<string, string>) => void
@@ -82,7 +82,7 @@ export class MockHttpBackend {
82
82
  * Load URL - called by i18next-http-backend to load from URL
83
83
  * We intercept this and return data from memory instead
84
84
  */
85
- loadUrl(
85
+ public loadUrl(
86
86
  _url: string,
87
87
  callback: (error: Error | null, data?: Record<string, string>) => void
88
88
  ): void {
@@ -104,7 +104,7 @@ export class MockHttpBackend {
104
104
  /**
105
105
  * Create - not used in our mock
106
106
  */
107
- create(): void {
107
+ public create(): void {
108
108
  // No-op
109
109
  }
110
110
  }
@@ -11,22 +11,26 @@ import type {
11
11
  IOCFunctionInterface
12
12
  } from '@qlover/corekit-bridge';
13
13
 
14
- export class TestIOC
15
- implements IOCInterface<IOCIdentifierMap, IOCContainerInterface>
16
- {
14
+ export class TestIOC implements IOCInterface<
15
+ IOCIdentifierMap,
16
+ IOCContainerInterface
17
+ > {
17
18
  protected ioc: IOCFunctionInterface<
18
19
  IOCIdentifierMap,
19
20
  IOCContainerInterface
20
21
  > | null = null;
21
22
 
22
- getIoc(): IOCFunctionInterface<
23
+ public getIoc(): IOCFunctionInterface<
23
24
  IOCIdentifierMap,
24
25
  IOCContainerInterface
25
26
  > | null {
26
27
  return this.ioc;
27
28
  }
28
29
 
29
- create(
30
+ /**
31
+ * @override
32
+ */
33
+ public create(
30
34
  options: IocRegisterOptions
31
35
  ): IOCFunctionInterface<IOCIdentifierMap, IOCContainerInterface> {
32
36
  if (this.ioc) {
@@ -18,9 +18,10 @@ import type {
18
18
  /**
19
19
  * TestIOCRegister - Register mock services for testing
20
20
  */
21
- export class TestIOCRegister
22
- implements IOCRegisterInterface<IOCContainerInterface, IocRegisterOptions>
23
- {
21
+ export class TestIOCRegister implements IOCRegisterInterface<
22
+ IOCContainerInterface,
23
+ IocRegisterOptions
24
+ > {
24
25
  constructor(protected options: IocRegisterOptions) {}
25
26
 
26
27
  protected registerGlobals(ioc: IOCContainerInterface): void {
@@ -39,7 +40,10 @@ export class TestIOCRegister
39
40
  ioc.bind(I.CookieStorage, globals.cookieStorage);
40
41
  }
41
42
 
42
- register(
43
+ /**
44
+ * @override
45
+ */
46
+ public register(
43
47
  ioc: IOCContainerInterface,
44
48
  _manager: IOCManagerInterface<IOCContainerInterface>
45
49
  ): void {