@qlover/create-app 0.7.10 → 0.7.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +2 -2
  4. package/dist/templates/next-app/config/Identifier/api.ts +14 -0
  5. package/dist/templates/next-app/config/Identifier/common.ts +7 -0
  6. package/dist/templates/next-app/eslint.config.mjs +17 -0
  7. package/dist/templates/next-app/migrations/schema/UserSchema.ts +21 -10
  8. package/dist/templates/next-app/next.config.ts +1 -0
  9. package/dist/templates/next-app/package.json +1 -0
  10. package/dist/templates/next-app/public/locales/en.json +4 -1
  11. package/dist/templates/next-app/public/locales/zh.json +4 -1
  12. package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +4 -7
  13. package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +16 -4
  14. package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +62 -0
  15. package/dist/templates/next-app/src/app/[locale]/layout.tsx +1 -1
  16. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +1 -1
  17. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +1 -1
  18. package/dist/templates/next-app/src/app/[locale]/page.tsx +3 -3
  19. package/dist/templates/next-app/src/app/[locale]/register/RegisterForm.tsx +1 -1
  20. package/dist/templates/next-app/src/app/[locale]/register/page.tsx +1 -1
  21. package/dist/templates/next-app/src/app/api/admin/users/route.ts +39 -0
  22. package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +40 -0
  23. package/dist/templates/next-app/src/base/cases/AppConfig.ts +1 -1
  24. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +13 -2
  25. package/dist/templates/next-app/src/base/cases/PageParams.ts +1 -1
  26. package/dist/templates/next-app/src/base/cases/RequestState.ts +20 -0
  27. package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +1 -1
  28. package/dist/templates/next-app/src/base/port/AdminLayoutInterface.ts +26 -0
  29. package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +87 -0
  30. package/dist/templates/next-app/src/base/port/AppApiInterface.ts +1 -1
  31. package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +4 -4
  32. package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +7 -0
  33. package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +3 -0
  34. package/dist/templates/next-app/src/base/port/PaginationInterface.ts +6 -0
  35. package/dist/templates/next-app/src/base/services/AdminUserService.ts +45 -0
  36. package/dist/templates/next-app/src/base/services/UserService.ts +1 -1
  37. package/dist/templates/next-app/src/base/services/adminApi/AdminApiRequester.ts +21 -0
  38. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +34 -0
  39. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +2 -2
  40. package/dist/templates/next-app/src/base/services/appApi/AppApiRequester.ts +56 -0
  41. package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +30 -31
  42. package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +5 -4
  43. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +1 -1
  44. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +3 -14
  45. package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +2 -2
  46. package/dist/templates/next-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
  47. package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +1 -1
  48. package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +1 -1
  49. package/dist/templates/next-app/src/core/globals.ts +1 -1
  50. package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +1 -1
  51. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +1 -1
  52. package/dist/templates/next-app/src/server/ServerAuth.ts +13 -3
  53. package/dist/templates/next-app/src/server/SupabaseBridge.ts +37 -2
  54. package/dist/templates/next-app/src/server/UserCredentialToken.ts +1 -1
  55. package/dist/templates/next-app/src/server/port/DBTableInterface.ts +10 -0
  56. package/dist/templates/next-app/src/server/port/{UserAuthInterface.ts → ServerAuthInterface.ts} +3 -1
  57. package/dist/templates/next-app/src/server/port/UserRepositoryInterface.ts +1 -1
  58. package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +32 -1
  59. package/dist/templates/next-app/src/server/services/AdminAuthPlugin.ts +19 -0
  60. package/dist/templates/next-app/src/server/services/ApiUserService.ts +26 -0
  61. package/dist/templates/next-app/src/server/services/UserService.ts +3 -3
  62. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +48 -0
  63. package/dist/templates/next-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +74 -1
  64. package/dist/templates/next-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +73 -0
  65. package/dist/templates/next-app/src/styles/css/antd-themes/_common/index.css +3 -0
  66. package/dist/templates/next-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +70 -0
  67. package/dist/templates/next-app/src/styles/css/antd-themes/index.css +4 -3
  68. package/dist/templates/next-app/src/styles/css/antd-themes/menu/_default.css +108 -0
  69. package/dist/templates/next-app/src/styles/css/antd-themes/menu/dark.css +67 -0
  70. package/dist/templates/next-app/src/styles/css/antd-themes/menu/index.css +3 -0
  71. package/dist/templates/next-app/src/styles/css/antd-themes/menu/pink.css +67 -0
  72. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +33 -0
  73. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +32 -0
  74. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/index.css +3 -0
  75. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +35 -0
  76. package/dist/templates/next-app/src/styles/css/antd-themes/table/_default.css +44 -0
  77. package/dist/templates/next-app/src/styles/css/antd-themes/table/dark.css +43 -0
  78. package/dist/templates/next-app/src/styles/css/antd-themes/table/index.css +3 -0
  79. package/dist/templates/next-app/src/styles/css/antd-themes/table/pink.css +43 -0
  80. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +106 -0
  81. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +68 -17
  82. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +6 -1
  83. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +1 -1
  84. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +11 -3
  85. package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +1 -1
  86. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +1 -1
  87. package/dist/templates/next-app/src/uikit/hook/useIOC.ts +1 -1
  88. package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +7 -1
  89. package/package.json +1 -1
  90. package/dist/templates/next-app/src/base/cases/ServerErrorHandler.ts +0 -27
  91. package/dist/templates/next-app/src/base/port/DBTableInterface.ts +0 -3
  92. package/dist/templates/next-app/src/base/services/appApi/AppUserType.ts +0 -51
@@ -0,0 +1,44 @@
1
+ /* custom variables - for antd and custom css */
2
+ html,
3
+ .fe-theme {
4
+ /* Antd Table 组件变量 */
5
+ .ant-table-css-var {
6
+ --fe-table-header-bg: rgb(var(--color-bg-elevated));
7
+ --fe-table-header-color: rgb(var(--text-primary));
8
+ --fe-table-header-sort-active-bg: rgb(var(--color-bg-secondary));
9
+ --fe-table-header-sort-hover-bg: rgb(var(--color-bg-secondary));
10
+ --fe-table-body-sort-bg: rgb(var(--color-bg-elevated));
11
+ --fe-table-row-hover-bg: rgb(var(--color-bg-secondary));
12
+ --fe-table-row-selected-bg: rgba(var(--color-brand), 0.1);
13
+ --fe-table-row-selected-hover-bg: rgba(var(--color-brand-hover), 0.2);
14
+ --fe-table-row-expanded-bg: rgba(var(--text-primary), 0.02);
15
+ --fe-table-cell-padding-block: 16px;
16
+ --fe-table-cell-padding-inline: 16px;
17
+ --fe-table-cell-padding-block-md: 12px;
18
+ --fe-table-cell-padding-inline-md: 8px;
19
+ --fe-table-cell-padding-block-sm: 8px;
20
+ --fe-table-cell-padding-inline-sm: 8px;
21
+ --fe-table-border-color: rgb(var(--color-border));
22
+ --fe-table-header-border-radius: 8px;
23
+ --fe-table-footer-bg: rgb(var(--color-bg-elevated));
24
+ --fe-table-footer-color: rgb(var(--text-primary));
25
+ --fe-table-cell-font-size: 14px;
26
+ --fe-table-cell-font-size-md: 14px;
27
+ --fe-table-cell-font-size-sm: 14px;
28
+ --fe-table-header-split-color: rgb(var(--color-border));
29
+ --fe-table-fixed-header-sort-active-bg: rgb(var(--color-bg-secondary));
30
+ --fe-table-header-filter-hover-bg: rgba(var(--text-primary), 0.06);
31
+ --fe-table-filter-dropdown-menu-bg: rgb(var(--color-bg-base));
32
+ --fe-table-filter-dropdown-bg: rgb(var(--color-bg-base));
33
+ --fe-table-expand-icon-bg: rgb(var(--color-bg-base));
34
+ --fe-table-selection-column-width: 32px;
35
+ --fe-table-sticky-scroll-bar-bg: rgba(var(--text-primary), 0.25);
36
+ --fe-table-sticky-scroll-bar-border-radius: 100px;
37
+ --fe-table-expand-icon-margin-top: 2.5px;
38
+ --fe-table-header-icon-color: rgb(var(--text-tertiary));
39
+ --fe-table-header-icon-hover-color: rgb(var(--text-secondary));
40
+ --fe-table-expand-icon-half-inner: 7px;
41
+ --fe-table-expand-icon-size: 17px;
42
+ --fe-table-expand-icon-scale: 0.9411764705882353;
43
+ }
44
+ }
@@ -0,0 +1,43 @@
1
+ [data-theme='dark'],
2
+ [data-theme='dark'] .fe-theme {
3
+ /* Antd Table 组件变量 */
4
+ .ant-table-css-var {
5
+ --fe-table-header-bg: rgb(var(--color-bg-secondary));
6
+ --fe-table-header-color: rgb(var(--text-primary));
7
+ --fe-table-header-sort-active-bg: rgb(var(--color-bg-elevated));
8
+ --fe-table-header-sort-hover-bg: rgb(var(--color-bg-elevated));
9
+ --fe-table-body-sort-bg: rgb(var(--color-bg-secondary));
10
+ --fe-table-row-hover-bg: rgb(var(--color-bg-elevated));
11
+ --fe-table-row-selected-bg: rgba(var(--color-brand), 0.2);
12
+ --fe-table-row-selected-hover-bg: rgba(var(--color-brand-hover), 0.3);
13
+ --fe-table-row-expanded-bg: rgba(var(--text-primary), 0.02);
14
+ --fe-table-cell-padding-block: 16px;
15
+ --fe-table-cell-padding-inline: 16px;
16
+ --fe-table-cell-padding-block-md: 12px;
17
+ --fe-table-cell-padding-inline-md: 8px;
18
+ --fe-table-cell-padding-block-sm: 8px;
19
+ --fe-table-cell-padding-inline-sm: 8px;
20
+ --fe-table-border-color: rgb(var(--color-border));
21
+ --fe-table-header-border-radius: 8px;
22
+ --fe-table-footer-bg: rgb(var(--color-bg-secondary));
23
+ --fe-table-footer-color: rgb(var(--text-primary));
24
+ --fe-table-cell-font-size: 14px;
25
+ --fe-table-cell-font-size-md: 14px;
26
+ --fe-table-cell-font-size-sm: 14px;
27
+ --fe-table-header-split-color: rgb(var(--color-border));
28
+ --fe-table-fixed-header-sort-active-bg: rgb(var(--color-bg-elevated));
29
+ --fe-table-header-filter-hover-bg: rgba(var(--text-primary), 0.06);
30
+ --fe-table-filter-dropdown-menu-bg: rgb(var(--color-bg-base));
31
+ --fe-table-filter-dropdown-bg: rgb(var(--color-bg-base));
32
+ --fe-table-expand-icon-bg: rgb(var(--color-bg-base));
33
+ --fe-table-selection-column-width: 32px;
34
+ --fe-table-sticky-scroll-bar-bg: rgba(var(--text-primary), 0.25);
35
+ --fe-table-sticky-scroll-bar-border-radius: 100px;
36
+ --fe-table-expand-icon-margin-top: 2.5px;
37
+ --fe-table-header-icon-color: rgb(var(--text-tertiary));
38
+ --fe-table-header-icon-hover-color: rgb(var(--text-secondary));
39
+ --fe-table-expand-icon-half-inner: 7px;
40
+ --fe-table-expand-icon-size: 17px;
41
+ --fe-table-expand-icon-scale: 0.9411764705882353;
42
+ }
43
+ }
@@ -0,0 +1,3 @@
1
+ @import './_default.css';
2
+ @import './dark.css';
3
+ @import './pink.css';
@@ -0,0 +1,43 @@
1
+ [data-theme='pink'],
2
+ [data-theme='pink'] .fe-theme {
3
+ /* Antd Table 组件变量 */
4
+ .ant-table-css-var {
5
+ --fe-table-header-bg: var(--ant-color-bg-container);
6
+ --fe-table-header-color: var(--ant-color-text);
7
+ --fe-table-header-sort-active-bg: var(--ant-color-bg-elevated);
8
+ --fe-table-header-sort-hover-bg: var(--ant-color-bg-elevated);
9
+ --fe-table-body-sort-bg: var(--ant-color-bg-container);
10
+ --fe-table-row-hover-bg: var(--ant-color-bg-elevated);
11
+ --fe-table-row-selected-bg: var(--fe-color-primary-bg);
12
+ --fe-table-row-selected-hover-bg: rgba(var(--fe-color-primary-hover), 0.2);
13
+ --fe-table-row-expanded-bg: rgba(var(--ant-color-text), 0.02);
14
+ --fe-table-cell-padding-block: 16px;
15
+ --fe-table-cell-padding-inline: 16px;
16
+ --fe-table-cell-padding-block-md: 12px;
17
+ --fe-table-cell-padding-inline-md: 8px;
18
+ --fe-table-cell-padding-block-sm: 8px;
19
+ --fe-table-cell-padding-inline-sm: 8px;
20
+ --fe-table-border-color: var(--ant-color-border);
21
+ --fe-table-header-border-radius: 8px;
22
+ --fe-table-footer-bg: var(--ant-color-bg-container);
23
+ --fe-table-footer-color: var(--ant-color-text);
24
+ --fe-table-cell-font-size: 14px;
25
+ --fe-table-cell-font-size-md: 14px;
26
+ --fe-table-cell-font-size-sm: 14px;
27
+ --fe-table-header-split-color: var(--ant-color-border);
28
+ --fe-table-fixed-header-sort-active-bg: var(--ant-color-bg-elevated);
29
+ --fe-table-header-filter-hover-bg: rgba(var(--ant-color-text), 0.06);
30
+ --fe-table-filter-dropdown-menu-bg: var(--ant-color-bg-container);
31
+ --fe-table-filter-dropdown-bg: var(--ant-color-bg-container);
32
+ --fe-table-expand-icon-bg: var(--ant-color-bg-container);
33
+ --fe-table-selection-column-width: 32px;
34
+ --fe-table-sticky-scroll-bar-bg: rgba(var(--ant-color-text), 0.25);
35
+ --fe-table-sticky-scroll-bar-border-radius: 100px;
36
+ --fe-table-expand-icon-margin-top: 2.5px;
37
+ --fe-table-header-icon-color: var(--fe-color-icon);
38
+ --fe-table-header-icon-hover-color: var(--fe-color-icon-hover);
39
+ --fe-table-expand-icon-half-inner: 7px;
40
+ --fe-table-expand-icon-size: 17px;
41
+ --fe-table-expand-icon-scale: 0.9411764705882353;
42
+ }
43
+ }
@@ -0,0 +1,106 @@
1
+ 'use client';
2
+
3
+ import {
4
+ DashboardOutlined,
5
+ UserOutlined,
6
+ MenuFoldOutlined,
7
+ MenuUnfoldOutlined
8
+ } from '@ant-design/icons';
9
+ import { Layout, Menu } from 'antd';
10
+ import { clsx } from 'clsx';
11
+ import React, { useCallback, useMemo, type HTMLAttributes } from 'react';
12
+ import { AdminPageManager } from '@/base/cases/AdminPageManager';
13
+ import { BaseHeader } from './BaseHeader';
14
+ import { LocaleLink } from './LocaleLink';
15
+ import { useIOC } from '../hook/useIOC';
16
+ import { useStore } from '../hook/useStore';
17
+ import type { RenderLeftFunction } from './BaseHeader';
18
+ import type { ItemType } from 'antd/es/menu/interface';
19
+
20
+ const { Sider } = Layout;
21
+
22
+ const IconMap = {
23
+ dashboard: <DashboardOutlined />,
24
+ users: <UserOutlined />
25
+ };
26
+
27
+ export interface AdminLayoutProps extends HTMLAttributes<HTMLDivElement> {
28
+ mainClassName?: string;
29
+ children: React.ReactNode;
30
+ }
31
+
32
+ export function AdminLayout(props: AdminLayoutProps) {
33
+ const { children, className, mainClassName, ...rest } = props;
34
+
35
+ const page = useIOC(AdminPageManager);
36
+
37
+ const collapsedSidebar = useStore(page, page.selectors.collapsedSidebar);
38
+ const navItems = useStore(page, page.selectors.navItems);
39
+
40
+ const sidebarItems = useMemo(() => {
41
+ return navItems.map((item) => {
42
+ // TODO: use i18n
43
+ const title = item.i18nKey;
44
+ const icon = IconMap[item.key as keyof typeof IconMap];
45
+
46
+ return {
47
+ key: item.key,
48
+ label: (
49
+ <LocaleLink
50
+ href={item.pathname!}
51
+ title={title}
52
+ className="flex items-center gap-2"
53
+ >
54
+ {icon}
55
+ <span>{title}</span>
56
+ </LocaleLink>
57
+ ),
58
+ link: item.pathname
59
+ } as ItemType;
60
+ });
61
+ }, [navItems]);
62
+
63
+ const renderHeaderLeft: RenderLeftFunction = useCallback(
64
+ ({ defaultElement }) => (
65
+ <div data-testid="AdminLayoutHeader" className="flex items-center">
66
+ <span
67
+ className="text-text hover:text-text-hover cursor-pointer text-md transition-colors"
68
+ onClick={() => page.toggleSidebar()}
69
+ >
70
+ {collapsedSidebar ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
71
+ </span>
72
+
73
+ {defaultElement}
74
+ </div>
75
+ ),
76
+ [collapsedSidebar, page]
77
+ );
78
+
79
+ return (
80
+ <Layout data-testid="AdminLayout" className={className} {...rest}>
81
+ <div className="overflow-auto h-screen sticky top-0 bottom-0 scrollbar-thin scrollbar-gutter-stable">
82
+ <Sider
83
+ className="h-full"
84
+ onCollapse={() => page.toggleSidebar()}
85
+ collapsed={collapsedSidebar}
86
+ >
87
+ <div className="demo-logo-vertical" />
88
+ <Menu mode="inline" items={sidebarItems} />
89
+ </Sider>
90
+ </div>
91
+
92
+ <Layout>
93
+ <BaseHeader
94
+ href="/admin"
95
+ showLogoutButton
96
+ renderLeft={renderHeaderLeft}
97
+ />
98
+ <main
99
+ className={clsx('p-2 bg-primary text-text flex-1', mainClassName)}
100
+ >
101
+ {children}
102
+ </main>
103
+ </Layout>
104
+ </Layout>
105
+ );
106
+ }
@@ -1,16 +1,68 @@
1
1
  'use client';
2
2
 
3
- import Link from 'next/link';
4
- import { IOCIdentifier } from '@config/IOCIdentifier';
3
+ import { TeamOutlined } from '@ant-design/icons';
4
+ import { useLocale, useTranslations } from 'next-intl';
5
+ import { useMemo } from 'react';
5
6
  import { useIOC } from '@/uikit/hook/useIOC';
7
+ import { PAGE_HEAD_ADMIN_TITLE } from '@config/Identifier';
8
+ import { IOCIdentifier } from '@config/IOCIdentifier';
6
9
  import { LanguageSwitcher } from './LanguageSwitcher';
10
+ import { LocaleLink } from './LocaleLink';
7
11
  import { LogoutButton } from './LogoutButton';
8
12
  import { ThemeSwitcher } from './ThemeSwitcher';
9
13
 
10
- export function BaseHeader(props: { showLogoutButton?: boolean }) {
11
- const { showLogoutButton } = props;
14
+ export type RenderLeftFunction = (props: {
15
+ locale: string;
16
+ defaultElement: React.ReactNode;
17
+ }) => React.ReactNode;
18
+
19
+ export function BaseHeader(props: {
20
+ href?: string;
21
+ showLogoutButton?: boolean;
22
+ showAdminButton?: boolean;
23
+ renderLeft?: React.ReactNode | RenderLeftFunction;
24
+ }) {
25
+ const { href = '/', showLogoutButton, showAdminButton, renderLeft } = props;
12
26
  const appConfig = useIOC(IOCIdentifier.AppConfig);
13
27
  const i18nService = useIOC(IOCIdentifier.I18nServiceInterface);
28
+ const locale = useLocale();
29
+ const t = useTranslations();
30
+
31
+ const tt = {
32
+ title: appConfig.appName,
33
+ admin: t(PAGE_HEAD_ADMIN_TITLE)
34
+ };
35
+
36
+ const leftDefault = useMemo(
37
+ () => (
38
+ <LocaleLink
39
+ data-testid="BaseHeaderLogo"
40
+ title={tt.title}
41
+ href={href}
42
+ locale={locale}
43
+ className="flex items-center hover:opacity-80 transition-opacity"
44
+ >
45
+ <span
46
+ data-testid="base-header-app-name"
47
+ className="ml-2 text-lg font-semibold text-text"
48
+ >
49
+ {tt.title}
50
+ </span>
51
+ </LocaleLink>
52
+ ),
53
+ [href, locale, tt.title]
54
+ );
55
+
56
+ const RenderLeft = useMemo(() => {
57
+ if (!renderLeft) {
58
+ return leftDefault;
59
+ }
60
+
61
+ if (typeof renderLeft === 'function') {
62
+ return renderLeft({ locale, defaultElement: leftDefault });
63
+ }
64
+ return renderLeft;
65
+ }, [renderLeft, locale, leftDefault]);
14
66
 
15
67
  return (
16
68
  <header
@@ -18,20 +70,19 @@ export function BaseHeader(props: { showLogoutButton?: boolean }) {
18
70
  className="h-14 bg-secondary border-b border-c-border sticky top-0 z-50"
19
71
  >
20
72
  <div className="flex items-center justify-between h-full px-4 mx-auto max-w-7xl">
21
- <div className="flex items-center">
22
- <Link
23
- href="/"
24
- className="flex items-center hover:opacity-80 transition-opacity"
25
- >
26
- <span
27
- data-testid="base-header-app-name"
28
- className="ml-2 text-lg font-semibold text-text"
29
- >
30
- {appConfig.appName}
31
- </span>
32
- </Link>
33
- </div>
73
+ <div className="flex items-center">{RenderLeft}</div>
34
74
  <div className="flex items-center gap-2 md:gap-4">
75
+ {showAdminButton && (
76
+ <LocaleLink
77
+ href="/admin"
78
+ title={tt.admin}
79
+ locale={locale}
80
+ className="text-text hover:text-text-hover cursor-pointer text-lg transition-colors"
81
+ >
82
+ <TeamOutlined className="text-lg text-text" />
83
+ </LocaleLink>
84
+ )}
85
+
35
86
  <LanguageSwitcher i18nService={i18nService} />
36
87
  <ThemeSwitcher />
37
88
  {showLogoutButton && <LogoutButton />}
@@ -3,12 +3,14 @@ import type { HTMLAttributes } from 'react';
3
3
 
4
4
  export interface BaseLayoutProps extends HTMLAttributes<HTMLDivElement> {
5
5
  showLogoutButton?: boolean;
6
+ showAdminButton?: boolean;
6
7
  mainProps?: HTMLAttributes<HTMLElement>;
7
8
  }
8
9
 
9
10
  export function BaseLayout({
10
11
  children,
11
12
  showLogoutButton,
13
+ showAdminButton,
12
14
  mainProps,
13
15
  ...props
14
16
  }: BaseLayoutProps) {
@@ -18,7 +20,10 @@ export function BaseLayout({
18
20
  className="flex flex-col min-h-screen"
19
21
  {...props}
20
22
  >
21
- <BaseHeader showLogoutButton={showLogoutButton} />
23
+ <BaseHeader
24
+ showLogoutButton={showLogoutButton}
25
+ showAdminButton={showAdminButton}
26
+ />
22
27
  <main className="flex flex-1 flex-col bg-primary" {...mainProps}>
23
28
  {children}
24
29
  </main>
@@ -3,11 +3,11 @@ import '@ant-design/v5-patch-for-react-19';
3
3
  import { useRouter } from 'next/navigation';
4
4
  import { useLocale, useTranslations } from 'next-intl';
5
5
  import { useEffect } from 'react';
6
- import { I } from '@config/IOCIdentifier';
7
6
  import { NavigateBridge } from '@/base/cases/NavigateBridge';
8
7
  import type { I18nServiceLocale } from '@/base/port/I18nServiceInterface';
9
8
  import { BootstrapClient } from '@/core/bootstraps/BootstrapClient';
10
9
  import { clientIOC } from '@/core/clientIoc/ClientIOC';
10
+ import { I } from '@config/IOCIdentifier';
11
11
  import { IOCContext } from '../context/IOCContext';
12
12
 
13
13
  export function BootstrapsProvider(props: { children: React.ReactNode }) {
@@ -3,10 +3,11 @@ 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
6
  import { clientIOC } from '@/core/clientIoc/ClientIOC';
8
- import { BootstrapsProvider } from './BootstrapsProvider';
7
+ import { IOCIdentifier } from '@config/IOCIdentifier';
9
8
  import type { CommonThemeConfig } from '@config/theme';
9
+ import { BootstrapsProvider } from './BootstrapsProvider';
10
+ import { useMountedClient } from '../hook/useMountedClient';
10
11
 
11
12
  /**
12
13
  * CommonProvider is a provider for the common components
@@ -23,6 +24,13 @@ export function ComboProvider(props: {
23
24
  themeConfig: CommonThemeConfig;
24
25
  children: React.ReactNode;
25
26
  }) {
27
+ /**
28
+ * useMountedClient 会等待客户端完全初始化
29
+ * 只有在客户端准备就绪后才渲染组件内容
30
+ * 这样可以确保所有的客户端代码(包括 API 配置、插件等)都已经正确初始化
31
+ */
32
+ const mounted = useMountedClient();
33
+
26
34
  const { themeConfig, children } = props;
27
35
 
28
36
  const IOC = clientIOC.create();
@@ -42,7 +50,7 @@ export function ComboProvider(props: {
42
50
  storageKey={themeConfig.storageKey}
43
51
  >
44
52
  <BootstrapsProvider>
45
- <AntdRegistry>{children}</AntdRegistry>
53
+ <AntdRegistry>{mounted ? children : null}</AntdRegistry>
46
54
  </BootstrapsProvider>
47
55
  </ThemeProvider>
48
56
  </AntdThemeProvider>
@@ -4,12 +4,12 @@ import { TranslationOutlined } from '@ant-design/icons';
4
4
  import { Dropdown } from 'antd';
5
5
  import { useLocale } from 'next-intl';
6
6
  import { useCallback, useMemo } from 'react';
7
- import { i18nConfig } from '@config/i18n';
8
7
  import type {
9
8
  I18nServiceInterface,
10
9
  I18nServiceLocale
11
10
  } from '@/base/port/I18nServiceInterface';
12
11
  import { usePathname, useRouter } from '@/i18n/routing';
12
+ import { i18nConfig } from '@config/i18n';
13
13
  import type { LocaleType } from '@config/i18n';
14
14
  import type { ItemType } from 'antd/es/menu/interface';
15
15
 
@@ -1,6 +1,7 @@
1
1
  import { LogoutOutlined } from '@ant-design/icons';
2
2
  import { Tooltip } from 'antd';
3
3
  import { useCallback } from 'react';
4
+ import type { PageI18nInterface } from '@config/i18n';
4
5
  import {
5
6
  AUTH_LOGOUT_DIALOG_CONTENT,
6
7
  AUTH_LOGOUT_DIALOG_TITLE
@@ -8,7 +9,6 @@ import {
8
9
  import { I } from '@config/IOCIdentifier';
9
10
  import { useI18nInterface } from '../hook/useI18nInterface';
10
11
  import { useIOC } from '../hook/useIOC';
11
- import type { PageI18nInterface } from '@config/i18n';
12
12
 
13
13
  export function LogoutButton() {
14
14
  const dialogHandler = useIOC(I.DialogHandler);
@@ -1,6 +1,6 @@
1
1
  import { useContext } from 'react';
2
- import { IOCContext } from '../context/IOCContext';
3
2
  import type { IOCIdentifierMap } from '@config/IOCIdentifier';
3
+ import { IOCContext } from '../context/IOCContext';
4
4
  import type {
5
5
  IOCContainerInterface,
6
6
  IOCFunctionInterface
@@ -4,7 +4,13 @@ export const useMountedClient = () => {
4
4
  const [mounted, setMounted] = useState(false);
5
5
 
6
6
  useEffect(() => {
7
- setMounted(true);
7
+ const timer = requestAnimationFrame(() => {
8
+ setMounted(true);
9
+ });
10
+
11
+ return () => {
12
+ cancelAnimationFrame(timer);
13
+ };
8
14
  }, []);
9
15
 
10
16
  return mounted;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qlover/create-app",
3
- "version": "0.7.10",
3
+ "version": "0.7.11",
4
4
  "description": "Create a new app with a single command",
5
5
  "private": false,
6
6
  "type": "module",
@@ -1,27 +0,0 @@
1
- import { ExecutorError, type ExecutorContext } from '@qlover/fe-corekit';
2
- import { SERVER_AUTH_ERROR } from '@config/Identifier';
3
- import type { BootstrapServerContextValue } from '@/core/bootstraps/BootstrapServer';
4
- import type { BootstrapExecutorPlugin } from '@qlover/corekit-bridge';
5
-
6
- export class ServerErrorHandler implements BootstrapExecutorPlugin {
7
- pluginName = 'ServerErrorHandler';
8
-
9
- onError(
10
- context: ExecutorContext<BootstrapServerContextValue>
11
- ): ExecutorError | void {
12
- const { parameters, error } = context;
13
- const { messages } = parameters;
14
-
15
- if (error instanceof Error) {
16
- const messageId = error.message;
17
-
18
- if (messageId === SERVER_AUTH_ERROR) {
19
- const message = messages[messageId];
20
-
21
- if (message) {
22
- return new ExecutorError(messageId, message);
23
- }
24
- }
25
- }
26
- }
27
- }
@@ -1,3 +0,0 @@
1
- export interface DBTableInterface {
2
- readonly name: string;
3
- }
@@ -1,51 +0,0 @@
1
- import type { RequestEncryptPluginProps } from '@/base/cases/RequestEncryptPlugin';
2
- import type { AppApiResponse } from '@/base/port/AppApiInterface';
3
- import type {
4
- RequestAdapterConfig,
5
- RequestAdapterResponse,
6
- RequestTransactionInterface
7
- } from '@qlover/fe-corekit';
8
-
9
- export interface UserApiConfig<Request = unknown>
10
- extends RequestAdapterConfig<Request>,
11
- RequestEncryptPluginProps<Request> {}
12
-
13
- /**
14
- * UserApiResponse
15
- *
16
- * @description
17
- * UserApiResponse is the response for the UserApi.
18
- *
19
- * extends:
20
- * - RequestAdapterResponse<Request, Response>
21
- */
22
- export type UserApiResponse<
23
- Request = unknown,
24
- Response = unknown
25
- > = RequestAdapterResponse<Request, AppApiResponse<Response>>;
26
-
27
- /**
28
- * UserApi common transaction
29
- */
30
- export interface UserApiTransaction<Request = unknown, Response = unknown>
31
- extends RequestTransactionInterface<
32
- UserApiConfig<Request>,
33
- UserApiResponse<Request, Response>
34
- > {
35
- data: UserApiConfig<Request>['data'];
36
- }
37
-
38
- export type UserApiLoginTransaction = UserApiTransaction<
39
- { email: string; password: string },
40
- {
41
- token: string;
42
- }
43
- >;
44
-
45
- export type UserApiRegisterTransaction = UserApiTransaction<
46
- {
47
- email: string;
48
- password: string;
49
- },
50
- UserApiTransaction['response']['data']
51
- >;