create-secra 0.1.11 → 1.0.2

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 (133) hide show
  1. package/README.md +15 -5
  2. package/README.zh-CN.md +15 -4
  3. package/antd-adapter-template/README.md +24 -0
  4. package/antd-adapter-template/index.html +16 -0
  5. package/antd-adapter-template/package-lock.json +3420 -0
  6. package/antd-adapter-template/package.json +20 -47
  7. package/antd-adapter-template/src/app/App.tsx +7 -0
  8. package/antd-adapter-template/src/app/layouts/AppLayout.tsx +163 -0
  9. package/antd-adapter-template/src/app/router.ts +26 -0
  10. package/antd-adapter-template/src/app/routes/RouteTitleSync.tsx +26 -0
  11. package/antd-adapter-template/src/app/routes/dynamic-routes.ts +52 -0
  12. package/antd-adapter-template/src/app/routes/route-modules.ts +4 -0
  13. package/antd-adapter-template/src/app/routes/static-routes.tsx +110 -0
  14. package/antd-adapter-template/src/app/routes/types.ts +9 -0
  15. package/antd-adapter-template/src/features/auth/api/auth-api.ts +61 -0
  16. package/antd-adapter-template/src/features/auth/auth-store.ts +125 -0
  17. package/antd-adapter-template/src/features/auth/auth-types.ts +29 -0
  18. package/antd-adapter-template/src/features/auth/authorization.ts +46 -0
  19. package/antd-adapter-template/src/features/auth/use-auth.ts +10 -0
  20. package/antd-adapter-template/src/main.tsx +79 -0
  21. package/antd-adapter-template/src/pages/dashboard/DashboardPage.tsx +105 -0
  22. package/antd-adapter-template/src/pages/errors/ForbiddenPage.tsx +36 -0
  23. package/antd-adapter-template/src/pages/errors/NotFoundPage.tsx +36 -0
  24. package/antd-adapter-template/src/pages/home/HomePage.tsx +129 -0
  25. package/antd-adapter-template/src/pages/login/LoginPage.tsx +128 -0
  26. package/antd-adapter-template/src/pages/permission-test/PermissionTestPage.tsx +55 -0
  27. package/antd-adapter-template/src/pages/restricted/RestrictedDemoPage.tsx +17 -0
  28. package/antd-adapter-template/src/shared/kernel/app-kernel.ts +10 -0
  29. package/antd-adapter-template/src/shared/request/client.ts +46 -0
  30. package/antd-adapter-template/src/shared/request/contracts.ts +6 -0
  31. package/antd-adapter-template/src/shared/request/kv-adapter.ts +14 -0
  32. package/antd-adapter-template/src/shared/request/kv-backend.ts +244 -0
  33. package/antd-adapter-template/src/shared/request/ky-browser-stub.ts +6 -0
  34. package/antd-adapter-template/src/shared/request/undici-browser-stub.ts +4 -0
  35. package/antd-adapter-template/src/styles/global.css +185 -0
  36. package/antd-adapter-template/src/vite-env.d.ts +2 -0
  37. package/antd-adapter-template/tsconfig.app.json +10 -13
  38. package/antd-adapter-template/tsconfig.json +7 -2
  39. package/antd-adapter-template/tsconfig.node.json +6 -16
  40. package/antd-adapter-template/vite.config.ts +24 -0
  41. package/bin/index.mjs +32 -14
  42. package/package.json +2 -2
  43. package/{antd-adapter-template/apps/core → template}/index.html +2 -3
  44. package/template/package.json +14 -46
  45. package/template/src/App.tsx +48 -0
  46. package/template/src/main.tsx +36 -0
  47. package/template/src/styles.css +163 -0
  48. package/template/src/vite-env.d.ts +1 -0
  49. package/template/tsconfig.app.json +0 -5
  50. package/template/tsconfig.node.json +0 -5
  51. package/template/vite.config.ts +6 -0
  52. package/antd-adapter-template/apps/core/package.json +0 -18
  53. package/antd-adapter-template/apps/core/public/favicon.ico +0 -1
  54. package/antd-adapter-template/apps/core/public/favicon.svg +0 -1
  55. package/antd-adapter-template/apps/core/public/logo.svg +0 -1
  56. package/antd-adapter-template/apps/core/src/api/auth.ts +0 -49
  57. package/antd-adapter-template/apps/core/src/assets/react.svg +0 -1
  58. package/antd-adapter-template/apps/core/src/components/AntdGlobalProvider.tsx +0 -87
  59. package/antd-adapter-template/apps/core/src/components/AntdRootLayout.tsx +0 -10
  60. package/antd-adapter-template/apps/core/src/components/layout.tsx +0 -387
  61. package/antd-adapter-template/apps/core/src/guards/auth-route-guard.ts +0 -45
  62. package/antd-adapter-template/apps/core/src/main.tsx +0 -65
  63. package/antd-adapter-template/apps/core/src/pages/auth/components/account-login-fields.tsx +0 -60
  64. package/antd-adapter-template/apps/core/src/pages/auth/components/phone-login-fields.tsx +0 -60
  65. package/antd-adapter-template/apps/core/src/pages/auth/login.tsx +0 -169
  66. package/antd-adapter-template/apps/core/src/pages/index.tsx +0 -156
  67. package/antd-adapter-template/apps/core/src/router.ts +0 -42
  68. package/antd-adapter-template/apps/core/src/shims/use-sync-external-store-shim.ts +0 -3
  69. package/antd-adapter-template/apps/core/src/theme/theme.css +0 -48
  70. package/antd-adapter-template/apps/core/src/types/crypto-js.d.ts +0 -5
  71. package/antd-adapter-template/apps/core/src/utils/index.ts +0 -12
  72. package/antd-adapter-template/apps/core/src/utils/md5.ts +0 -6
  73. package/antd-adapter-template/apps/core/tsconfig.app.json +0 -11
  74. package/antd-adapter-template/apps/core/tsconfig.json +0 -13
  75. package/antd-adapter-template/apps/core/tsconfig.node.json +0 -7
  76. package/antd-adapter-template/apps/core/vite.config.ts +0 -118
  77. package/antd-adapter-template/eslint.config.js +0 -23
  78. package/antd-adapter-template/packages/sdk/.swcrc +0 -18
  79. package/antd-adapter-template/packages/sdk/package.json +0 -52
  80. package/antd-adapter-template/packages/sdk/src/build/index.ts +0 -28
  81. package/antd-adapter-template/packages/sdk/src/build/plugins/auto-import.ts +0 -46
  82. package/antd-adapter-template/packages/sdk/src/build/plugins/bundle-analyzer.ts +0 -33
  83. package/antd-adapter-template/packages/sdk/src/build/plugins/remove-console.ts +0 -23
  84. package/antd-adapter-template/packages/sdk/src/build/plugins/unocss.ts +0 -202
  85. package/antd-adapter-template/packages/sdk/src/build/plugins/unplugin-icon.ts +0 -43
  86. package/antd-adapter-template/packages/sdk/src/components/i18n-switch-dropdown.tsx +0 -139
  87. package/antd-adapter-template/packages/sdk/src/components/index.ts +0 -2
  88. package/antd-adapter-template/packages/sdk/src/components/theme-switch-dropdown.tsx +0 -131
  89. package/antd-adapter-template/packages/sdk/src/hooks/auth/core.ts +0 -101
  90. package/antd-adapter-template/packages/sdk/src/hooks/auth/index.ts +0 -139
  91. package/antd-adapter-template/packages/sdk/src/hooks/auth/with-auth.tsx +0 -41
  92. package/antd-adapter-template/packages/sdk/src/hooks/index.ts +0 -1
  93. package/antd-adapter-template/packages/sdk/src/i18n/index.ts +0 -150
  94. package/antd-adapter-template/packages/sdk/src/index.ts +0 -11
  95. package/antd-adapter-template/packages/sdk/src/request/index.ts +0 -436
  96. package/antd-adapter-template/packages/sdk/src/storage/README.md +0 -30
  97. package/antd-adapter-template/packages/sdk/src/storage/index.ts +0 -57
  98. package/antd-adapter-template/packages/sdk/src/styles/reset.css +0 -111
  99. package/antd-adapter-template/packages/sdk/src/theme/index.ts +0 -466
  100. package/antd-adapter-template/packages/sdk/tsconfig.json +0 -16
  101. package/antd-adapter-template/pnpm-workspace.yaml +0 -3
  102. package/antd-adapter-template/turbo.json +0 -17
  103. package/template/apps/core/index.html +0 -13
  104. package/template/apps/core/package.json +0 -18
  105. package/template/apps/core/public/favicon.ico +0 -1
  106. package/template/apps/core/public/favicon.svg +0 -1
  107. package/template/apps/core/public/logo.svg +0 -1
  108. package/template/apps/core/src/assets/react.svg +0 -1
  109. package/template/apps/core/src/main.tsx +0 -14
  110. package/template/apps/core/src/pages/index.tsx +0 -84
  111. package/template/apps/core/src/router.ts +0 -19
  112. package/template/apps/core/src/types/auto-imports.d.ts +0 -130
  113. package/template/apps/core/tsconfig.app.json +0 -7
  114. package/template/apps/core/tsconfig.json +0 -7
  115. package/template/apps/core/tsconfig.node.json +0 -7
  116. package/template/apps/core/vite.config.ts +0 -86
  117. package/template/eslint.config.js +0 -23
  118. package/template/packages/sdk/.swcrc +0 -18
  119. package/template/packages/sdk/package-lock.json +0 -1621
  120. package/template/packages/sdk/package.json +0 -32
  121. package/template/packages/sdk/src/build/index.ts +0 -33
  122. package/template/packages/sdk/src/build/plugins/auto-import.ts +0 -47
  123. package/template/packages/sdk/src/build/plugins/bundle-analyzer.ts +0 -35
  124. package/template/packages/sdk/src/build/plugins/remove-console.ts +0 -21
  125. package/template/packages/sdk/src/build/plugins/unocss.ts +0 -113
  126. package/template/packages/sdk/src/build/plugins/unplugin-icon.ts +0 -43
  127. package/template/packages/sdk/src/index.ts +0 -1
  128. package/template/packages/sdk/src/request/index.ts +0 -341
  129. package/template/packages/sdk/src/styles/reset.css +0 -111
  130. package/template/packages/sdk/tsconfig.json +0 -16
  131. package/template/pnpm-lock.yaml +0 -8055
  132. package/template/pnpm-workspace.yaml +0 -3
  133. package/template/turbo.json +0 -17
@@ -1,87 +0,0 @@
1
- import { useEffect, useMemo, useState } from 'react';
2
- import type { PropsWithChildren } from 'react';
3
- import { buildAntdCompatTheme, useConfig, useTheme } from '@vlian/framework/core';
4
- import type { ThemeSnapshot } from '@vlian/framework/core';
5
- import { App as AntdApp, ConfigProvider, theme as antdTheme } from 'antd';
6
- import type { AppProps, ConfigProviderProps } from 'antd';
7
-
8
- type UIProviderShape = {
9
- antd?: Record<string, unknown>;
10
- };
11
-
12
- const defaultTheme: ThemeSnapshot = {
13
- mode: 'light',
14
- };
15
-
16
- const getSystemDark = (): boolean => {
17
- if (typeof window === 'undefined') {
18
- return false;
19
- }
20
- return window.matchMedia('(prefers-color-scheme: dark)').matches;
21
- };
22
-
23
- export function useAntdRuntimeTheme(): ConfigProviderProps['theme'] {
24
- const { theme } = useTheme();
25
- const { antdConfig } = useConfig();
26
- const providerConfig = antdConfig as UIProviderShape | undefined;
27
- const [isSystemDark, setIsSystemDark] = useState<boolean>(getSystemDark);
28
-
29
- useEffect(() => {
30
- if (typeof window === 'undefined') {
31
- return;
32
- }
33
- const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
34
- const handleChange = (event: MediaQueryListEvent) => {
35
- setIsSystemDark(event.matches);
36
- };
37
- setIsSystemDark(mediaQuery.matches);
38
-
39
- if (typeof mediaQuery.addEventListener === 'function') {
40
- mediaQuery.addEventListener('change', handleChange);
41
- return () => {
42
- mediaQuery.removeEventListener('change', handleChange);
43
- };
44
- }
45
-
46
- mediaQuery.addListener(handleChange);
47
- return () => {
48
- mediaQuery.removeListener(handleChange);
49
- };
50
- }, []);
51
-
52
- return useMemo(() => {
53
- const resolvedMode: ThemeSnapshot['mode'] =
54
- theme.mode === 'system' ? (isSystemDark ? 'dark' : 'light') : ((theme.mode || 'light') as ThemeSnapshot['mode']);
55
-
56
- const mergedTheme: ThemeSnapshot = {
57
- ...defaultTheme,
58
- mode: resolvedMode,
59
- primaryColor: theme.primaryColor || '#1677ff',
60
- compatibility: {
61
- antd: providerConfig?.antd || {},
62
- },
63
- };
64
-
65
- return {
66
- ...buildAntdCompatTheme(mergedTheme),
67
- algorithm:
68
- mergedTheme.mode === 'dark' ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
69
- } as ConfigProviderProps['theme'];
70
- }, [isSystemDark, providerConfig?.antd, theme.mode, theme.primaryColor]);
71
- }
72
-
73
- interface AntdGlobalProviderProps extends PropsWithChildren {
74
- appProps?: AppProps;
75
- }
76
-
77
- export function AntdGlobalProvider({ children, appProps }: AntdGlobalProviderProps) {
78
- const runtimeTheme = useAntdRuntimeTheme();
79
-
80
- return (
81
- <ConfigProvider theme={runtimeTheme}>
82
- <AntdApp {...appProps}>{children}</AntdApp>
83
- </ConfigProvider>
84
- );
85
- }
86
-
87
- export default AntdGlobalProvider;
@@ -1,10 +0,0 @@
1
- import { Outlet } from "react-router-dom";
2
- import { AntdGlobalProvider } from "./AntdGlobalProvider";
3
-
4
- export default function AntdRootLayout() {
5
- return (
6
- <AntdGlobalProvider>
7
- <Outlet />
8
- </AntdGlobalProvider>
9
- );
10
- }
@@ -1,387 +0,0 @@
1
- import { useMemo, useState, type ReactNode } from "react";
2
- import {
3
- AudioWaveform,
4
- BadgeCheck,
5
- Bell,
6
- BookOpen,
7
- Bot,
8
- ChevronRight,
9
- ChevronsUpDown,
10
- Command,
11
- CreditCard,
12
- Folder,
13
- Forward,
14
- Frame,
15
- GalleryVerticalEnd,
16
- LogOut,
17
- Map,
18
- MoreHorizontal,
19
- PanelLeft,
20
- PieChart,
21
- Plus,
22
- Settings2,
23
- Sparkles,
24
- SquareTerminal,
25
- Trash2,
26
- type LucideIcon,
27
- } from "lucide-react";
28
-
29
- const cx = (...values: Array<string | false | null | undefined>) => values.filter(Boolean).join(" ");
30
-
31
- type LayoutProps = {
32
- children?: ReactNode;
33
- };
34
-
35
- type Team = {
36
- name: string;
37
- logo: LucideIcon;
38
- plan: string;
39
- };
40
-
41
- type MainNavItem = {
42
- title: string;
43
- url: string;
44
- icon: LucideIcon;
45
- isActive?: boolean;
46
- items: { title: string; url: string }[];
47
- };
48
-
49
- type ProjectItem = {
50
- name: string;
51
- url: string;
52
- icon: LucideIcon;
53
- };
54
-
55
- const DATA = {
56
- user: {
57
- name: "Secra Admin",
58
- email: "m@example.com",
59
- },
60
- teams: [
61
- { name: "Acme Inc", logo: GalleryVerticalEnd, plan: "Enterprise" },
62
- { name: "Acme Corp.", logo: AudioWaveform, plan: "Startup" },
63
- { name: "Evil Corp.", logo: Command, plan: "Free" },
64
- ] as Team[],
65
- navMain: [
66
- {
67
- title: "Playground",
68
- url: "#",
69
- icon: SquareTerminal,
70
- isActive: true,
71
- items: [
72
- { title: "History", url: "#" },
73
- { title: "Starred", url: "#" },
74
- { title: "Settings", url: "#" },
75
- ],
76
- },
77
- {
78
- title: "Models",
79
- url: "#",
80
- icon: Bot,
81
- items: [
82
- { title: "Genesis", url: "#" },
83
- { title: "Explorer", url: "#" },
84
- { title: "Quantum", url: "#" },
85
- ],
86
- },
87
- {
88
- title: "Documentation",
89
- url: "#",
90
- icon: BookOpen,
91
- items: [
92
- { title: "Introduction", url: "#" },
93
- { title: "Get Started", url: "#" },
94
- { title: "Tutorials", url: "#" },
95
- { title: "Changelog", url: "#" },
96
- ],
97
- },
98
- {
99
- title: "Settings",
100
- url: "#",
101
- icon: Settings2,
102
- items: [
103
- { title: "General", url: "#" },
104
- { title: "Team", url: "#" },
105
- { title: "Billing", url: "#" },
106
- { title: "Limits", url: "#" },
107
- ],
108
- },
109
- ] as MainNavItem[],
110
- projects: [
111
- { name: "Design Engineering", url: "#", icon: Frame },
112
- { name: "Sales & Marketing", url: "#", icon: PieChart },
113
- { name: "Travel", url: "#", icon: Map },
114
- ] as ProjectItem[],
115
- };
116
-
117
- function Layout({ children }: LayoutProps) {
118
- const [collapsed, setCollapsed] = useState(false);
119
- const [activeTeam, setActiveTeam] = useState(DATA.teams[0]);
120
- const [teamMenuOpen, setTeamMenuOpen] = useState(false);
121
- const [userMenuOpen, setUserMenuOpen] = useState(false);
122
- const [projectMenuOpen, setProjectMenuOpen] = useState<string | null>(null);
123
- const [openMap, setOpenMap] = useState<Record<string, boolean>>(
124
- Object.fromEntries(DATA.navMain.map((item) => [item.title, Boolean(item.isActive)])),
125
- );
126
-
127
- const pageTitle = useMemo(() => "Data Fetching", []);
128
-
129
- return (
130
- <div className="flex min-h-screen w-full bg-[hsl(var(--background))] text-[hsl(var(--foreground))]">
131
- <aside
132
- className={cx(
133
- "relative flex shrink-0 flex-col bg-[hsl(var(--card))] transition-[width] duration-200",
134
- collapsed ? "w-14" : "w-64",
135
- )}
136
- >
137
- <div className="border-b border-[hsl(var(--border))] p-2">
138
- <div className="relative">
139
- <button
140
- type="button"
141
- onClick={() => setTeamMenuOpen((v) => !v)}
142
- className={cx(
143
- "flex h-12 w-full items-center gap-2 rounded-md px-2 text-left hover:bg-[hsl(var(--accent))]",
144
- collapsed && "justify-center px-0",
145
- )}
146
- >
147
- <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]">
148
- <activeTeam.logo className="h-4 w-4" aria-hidden />
149
- </div>
150
- {collapsed ? null : (
151
- <>
152
- <div className="grid min-w-0 flex-1 text-sm leading-tight">
153
- <span className="truncate font-medium">{activeTeam.name}</span>
154
- <span className="truncate text-xs text-[hsl(var(--muted-foreground))]">{activeTeam.plan}</span>
155
- </div>
156
- <ChevronsUpDown className="h-4 w-4" aria-hidden />
157
- </>
158
- )}
159
- </button>
160
-
161
- {!collapsed && teamMenuOpen ? (
162
- <div className="absolute top-full left-0 z-50 mt-1 w-full min-w-56 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--popover))] p-1 text-[hsl(var(--popover-foreground))] shadow-md">
163
- <div className="px-2 py-1 text-xs text-[hsl(var(--muted-foreground))]">Teams</div>
164
- {DATA.teams.map((team, index) => (
165
- <button
166
- key={team.name}
167
- type="button"
168
- onClick={() => {
169
- setActiveTeam(team);
170
- setTeamMenuOpen(false);
171
- }}
172
- className="flex w-full items-center gap-2 rounded-sm p-2 text-left text-sm hover:bg-[hsl(var(--accent))]"
173
- >
174
- <div className="flex h-6 w-6 items-center justify-center rounded-md border border-[hsl(var(--border))]">
175
- <team.logo className="h-3.5 w-3.5" aria-hidden />
176
- </div>
177
- <span className="flex-1 truncate">{team.name}</span>
178
- <span className="text-xs text-[hsl(var(--muted-foreground))]">{index + 1}</span>
179
- </button>
180
- ))}
181
- <div className="my-1 h-px bg-[hsl(var(--border))]" />
182
- <button
183
- type="button"
184
- className="flex w-full items-center gap-2 rounded-sm p-2 text-left text-sm hover:bg-[hsl(var(--accent))]"
185
- >
186
- <div className="flex h-6 w-6 items-center justify-center rounded-md border border-[hsl(var(--border))]">
187
- <Plus className="h-4 w-4" aria-hidden />
188
- </div>
189
- <span className="text-[hsl(var(--muted-foreground))]">Add team</span>
190
- </button>
191
- </div>
192
- ) : null}
193
- </div>
194
- </div>
195
-
196
- <div className="flex min-h-0 flex-1 flex-col gap-3 overflow-auto p-2">
197
- {!collapsed ? <div className="px-2 text-xs text-[hsl(var(--muted-foreground))]">Platform</div> : null}
198
- <ul className="m-0 list-none space-y-1 p-0">
199
- {DATA.navMain.map((item) => {
200
- const open = openMap[item.title];
201
-
202
- return (
203
- <li key={item.title}>
204
- <button
205
- type="button"
206
- onClick={() => setOpenMap((prev) => ({ ...prev, [item.title]: !prev[item.title] }))}
207
- className={cx(
208
- "flex h-9 w-full items-center gap-2 rounded-md px-2 text-left text-sm hover:bg-[hsl(var(--accent))]",
209
- item.isActive && "bg-[hsl(var(--accent))] text-[hsl(var(--accent-foreground))]",
210
- collapsed && "justify-center px-0",
211
- )}
212
- >
213
- <item.icon className="h-4 w-4 shrink-0" aria-hidden />
214
- {collapsed ? null : <span className="truncate">{item.title}</span>}
215
- {!collapsed ? (
216
- <ChevronRight className={cx("ml-auto h-4 w-4 transition-transform duration-200", open && "rotate-90")} aria-hidden />
217
- ) : null}
218
- </button>
219
-
220
- {!collapsed && open ? (
221
- <ul className="m-0 ml-4 mt-1 list-none space-y-1 border-l border-[hsl(var(--border))] p-0 pl-2">
222
- {item.items.map((subItem) => (
223
- <li key={subItem.title}>
224
- <a
225
- href={subItem.url}
226
- className="flex h-8 items-center rounded-md px-2 text-sm text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))] hover:text-[hsl(var(--accent-foreground))]"
227
- >
228
- {subItem.title}
229
- </a>
230
- </li>
231
- ))}
232
- </ul>
233
- ) : null}
234
- </li>
235
- );
236
- })}
237
- </ul>
238
-
239
- {!collapsed ? <div className="mt-2 px-2 text-xs text-[hsl(var(--muted-foreground))]">Projects</div> : null}
240
- <ul className="m-0 list-none space-y-1 p-0">
241
- {DATA.projects.map((item) => (
242
- <li key={item.name} className="relative">
243
- <a
244
- href={item.url}
245
- className={cx(
246
- "flex h-9 items-center gap-2 rounded-md px-2 text-sm hover:bg-[hsl(var(--accent))]",
247
- collapsed ? "justify-center px-0" : "pr-10",
248
- )}
249
- >
250
- <item.icon className="h-4 w-4 shrink-0" aria-hidden />
251
- {collapsed ? null : <span className="truncate">{item.name}</span>}
252
- </a>
253
- {!collapsed ? (
254
- <>
255
- <button
256
- type="button"
257
- onClick={() => setProjectMenuOpen((prev) => (prev === item.name ? null : item.name))}
258
- className="absolute top-1.5 right-1 inline-flex h-6 w-6 items-center justify-center rounded-md text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--accent))]"
259
- >
260
- <MoreHorizontal className="h-4 w-4" aria-hidden />
261
- <span className="sr-only">More</span>
262
- </button>
263
- {projectMenuOpen === item.name ? (
264
- <div className="absolute top-9 right-0 z-50 w-48 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--popover))] p-1 text-[hsl(var(--popover-foreground))] shadow-md">
265
- <button type="button" className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-[hsl(var(--accent))]">
266
- <Folder className="h-4 w-4 text-[hsl(var(--muted-foreground))]" aria-hidden />
267
- View Project
268
- </button>
269
- <button type="button" className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-[hsl(var(--accent))]">
270
- <Forward className="h-4 w-4 text-[hsl(var(--muted-foreground))]" aria-hidden />
271
- Share Project
272
- </button>
273
- <div className="my-1 h-px bg-[hsl(var(--border))]" />
274
- <button type="button" className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-[hsl(var(--accent))]">
275
- <Trash2 className="h-4 w-4 text-[hsl(var(--muted-foreground))]" aria-hidden />
276
- Delete Project
277
- </button>
278
- </div>
279
- ) : null}
280
- </>
281
- ) : null}
282
- </li>
283
- ))}
284
- </ul>
285
- </div>
286
-
287
- <div className="border-t border-[hsl(var(--border))] p-2">
288
- <div className="relative">
289
- <button
290
- type="button"
291
- onClick={() => setUserMenuOpen((v) => !v)}
292
- className={cx(
293
- "flex h-12 w-full items-center gap-2 rounded-md px-2 text-left hover:bg-[hsl(var(--accent))]",
294
- collapsed && "justify-center px-0",
295
- )}
296
- >
297
- <span className="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-[hsl(var(--muted))] text-xs">CN</span>
298
- {collapsed ? null : (
299
- <>
300
- <div className="grid min-w-0 flex-1 text-sm leading-tight">
301
- <span className="truncate font-medium">{DATA.user.name}</span>
302
- <span className="truncate text-xs text-[hsl(var(--muted-foreground))]">{DATA.user.email}</span>
303
- </div>
304
- <ChevronsUpDown className="h-4 w-4" aria-hidden />
305
- </>
306
- )}
307
- </button>
308
-
309
- {!collapsed && userMenuOpen ? (
310
- <div className="absolute right-0 bottom-14 z-50 min-w-56 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--popover))] p-1 text-[hsl(var(--popover-foreground))] shadow-md">
311
- <button type="button" className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-[hsl(var(--accent))]">
312
- <Sparkles className="h-4 w-4" aria-hidden />
313
- Upgrade to Pro
314
- </button>
315
- <div className="my-1 h-px bg-[hsl(var(--border))]" />
316
- <button type="button" className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-[hsl(var(--accent))]">
317
- <BadgeCheck className="h-4 w-4" aria-hidden />
318
- Account
319
- </button>
320
- <button type="button" className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-[hsl(var(--accent))]">
321
- <CreditCard className="h-4 w-4" aria-hidden />
322
- Billing
323
- </button>
324
- <button type="button" className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-[hsl(var(--accent))]">
325
- <Bell className="h-4 w-4" aria-hidden />
326
- Notifications
327
- </button>
328
- <div className="my-1 h-px bg-[hsl(var(--border))]" />
329
- <button type="button" className="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-[hsl(var(--accent))]">
330
- <LogOut className="h-4 w-4" aria-hidden />
331
- Log out
332
- </button>
333
- </div>
334
- ) : null}
335
- </div>
336
- </div>
337
- </aside>
338
- <div className="w-px shrink-0 bg-[hsl(var(--border))]" />
339
-
340
- <main className="min-w-0 flex-1">
341
- <header className="flex h-16 shrink-0 items-center gap-2 transition-[height] ease-linear">
342
- <div className="flex items-center gap-2 px-4">
343
- <button
344
- type="button"
345
- onClick={() => setCollapsed((prev) => !prev)}
346
- className="-ml-1 inline-flex h-8 w-8 items-center justify-center rounded-md border border-[hsl(var(--border))] hover:bg-[hsl(var(--accent))]"
347
- aria-label="Toggle sidebar"
348
- >
349
- <PanelLeft className="h-4 w-4" aria-hidden />
350
- </button>
351
- <div className="mr-2 h-4 w-px bg-[hsl(var(--border))]" />
352
- <nav aria-label="breadcrumb">
353
- <ol className="m-0 flex list-none items-center gap-1.5 p-0 text-sm text-[hsl(var(--muted-foreground))]">
354
- <li className="hidden md:block">
355
- <a href="#" className="transition-colors hover:text-[hsl(var(--foreground))]">
356
- Build Your Application
357
- </a>
358
- </li>
359
- <li className="hidden md:block">/</li>
360
- <li>
361
- <span className="text-[hsl(var(--foreground))]">{pageTitle}</span>
362
- </li>
363
- </ol>
364
- </nav>
365
- </div>
366
- </header>
367
- <div className="h-px w-full shrink-0 bg-[hsl(var(--border))]" />
368
-
369
- <div className="flex flex-1 flex-col gap-4 p-4 pt-0">
370
- {children ?? (
371
- <>
372
- <div className="grid auto-rows-min gap-4 md:grid-cols-3">
373
- <div className="bg-[hsl(var(--muted))]/50 aspect-video rounded-xl" />
374
- <div className="bg-[hsl(var(--muted))]/50 aspect-video rounded-xl" />
375
- <div className="bg-[hsl(var(--muted))]/50 aspect-video rounded-xl" />
376
- </div>
377
- <div className="bg-[hsl(var(--muted))]/50 min-h-[100vh] flex-1 rounded-xl md:min-h-min" />
378
- </>
379
- )}
380
- </div>
381
- </main>
382
- </div>
383
- );
384
- }
385
-
386
- export type { LayoutProps };
387
- export default Layout;
@@ -1,45 +0,0 @@
1
- import type { RouteLocation } from "@vlian/framework/core";
2
- import {
3
- ACCESS_TOKEN_KEY,
4
- getToken,
5
- REFRESH_TOKEN_KEY,
6
- } from "@vlian/sdk/hooks/auth";
7
-
8
- const LOGIN_PATH = "/auth/login";
9
-
10
- // 登录白名单:这些路由允许匿名访问
11
- const AUTH_ROUTE_WHITELIST = new Set<string>([LOGIN_PATH]);
12
-
13
- const isWhitelistedPath = (path: string): boolean => {
14
- return AUTH_ROUTE_WHITELIST.has(path);
15
- };
16
-
17
- const isLoggedIn = async (): Promise<boolean> => {
18
- const [accessToken, refreshToken] = await Promise.all([
19
- getToken({ key: ACCESS_TOKEN_KEY }),
20
- getToken({ key: REFRESH_TOKEN_KEY }),
21
- ]);
22
- return Boolean(accessToken || refreshToken);
23
- };
24
-
25
- const normalizePathname = (path: string): string => {
26
- return path.split("?")[0]?.split("#")[0] || "/";
27
- };
28
-
29
- export const getAuthRedirectPath = async (targetPath: string): Promise<string | void> => {
30
- const pathname = normalizePathname(targetPath);
31
- if (isWhitelistedPath(pathname)) {
32
- return;
33
- }
34
-
35
- if (await isLoggedIn()) {
36
- return;
37
- }
38
-
39
- const redirect = encodeURIComponent(targetPath || "/");
40
- return `${LOGIN_PATH}?redirect=${redirect}`;
41
- };
42
-
43
- export const authRouteGuard = async (to: RouteLocation): Promise<string | void> => {
44
- return getAuthRedirectPath(to.path || "/");
45
- };
@@ -1,65 +0,0 @@
1
- import "@ant-design/v5-patch-for-react-19";
2
- import "@vlian/sdk/reset.css";
3
- import "uno.css";
4
- import "./theme/theme.css";
5
- import { PreloadStrategy, getRoutePreloader, startApp } from '@vlian/framework/core';
6
- import { LogLevel } from '@vlian/framework/utils';
7
- import { ReduxAdapter } from '@vlian/framework/state';
8
- import { cache, getSdkI18nLocales, setSdkLang, type SdkLang } from "@vlian/sdk";
9
- import { authRouteGuard } from "./guards/auth-route-guard";
10
- import { getRoutes } from './router';
11
-
12
- const LANG_STORAGE_KEY = "sdk-lang";
13
-
14
- const isSdkLang = (lang: unknown): lang is SdkLang => {
15
- return lang === "zh-CN" || lang === "en-US";
16
- };
17
-
18
- const stateAdapter = new ReduxAdapter({
19
- devMode: import.meta.env.DEV,
20
- storeConfig: {
21
- devTools: import.meta.env.DEV,
22
- },
23
- });
24
-
25
- // Disable framework route preloader to avoid pulling non-current heavy routes on first paint.
26
- getRoutePreloader({
27
- strategy: PreloadStrategy.NONE,
28
- });
29
-
30
- void startApp({
31
- showSplashScreen: 'never', // 自动判断是否显示启动页
32
- loggerLevel: LogLevel.DEBUG,
33
- globalProvider: () => import('./components/AntdGlobalProvider'),
34
- locale: getSdkI18nLocales(),
35
- theme: {
36
- mode: 'system',
37
- primaryColor: '#1677ff',
38
- },
39
- configStrategy: {
40
- //加载关键配置 这些配置必须在应用渲染前加载完成
41
- loadCriticalConfig: async () => {
42
- return {}
43
- },
44
- },
45
- lifecycle: {
46
- afterInitialization: async () => {
47
- const savedLang = await cache.localstorage.get<SdkLang>(LANG_STORAGE_KEY);
48
- setSdkLang(isSdkLang(savedLang) ? savedLang : "zh-CN");
49
- },
50
- },
51
- router: {
52
- enabled: true,
53
- routes: getRoutes,
54
- mode: 'browser',
55
- hooks: {
56
- beforeEach: authRouteGuard,
57
- },
58
- },
59
- stateManager: {
60
- defaultAdapter: stateAdapter,
61
- enableRegistry: true,
62
- defaultScope: 'core-app',
63
- devMode: import.meta.env.DEV,
64
- },
65
- });
@@ -1,60 +0,0 @@
1
- import { LockOutlined, UserOutlined } from '@ant-design/icons';
2
- import { ProFormText } from '@ant-design/pro-components';
3
- import { useTranslation } from "react-i18next";
4
-
5
- export function AccountLoginFields() {
6
- const { t } = useTranslation();
7
-
8
- return (
9
- <>
10
- <ProFormText
11
- name="username"
12
- fieldProps={{
13
- size: 'large',
14
- prefix: <UserOutlined className="prefixIcon" />,
15
- }}
16
- placeholder={t("secraAdmin.login.usernamePlaceholder")}
17
- rules={[
18
- {
19
- required: true,
20
- message: t("secraAdmin.login.usernameRequired"),
21
- },
22
- ]}
23
- />
24
- <ProFormText.Password
25
- name="password"
26
- fieldProps={{
27
- size: 'large',
28
- prefix: <LockOutlined className="prefixIcon" />,
29
- strengthText: t("secraAdmin.login.passwordStrengthText"),
30
- statusRender: (value) => {
31
- const getStatus = () => {
32
- if (value && value.length > 12) {
33
- return 'ok';
34
- }
35
- if (value && value.length > 6) {
36
- return 'pass';
37
- }
38
- return 'poor';
39
- };
40
- const status = getStatus();
41
- if (status === 'pass') {
42
- return <div className="text-amber-500">{t("secraAdmin.login.passwordStrengthMedium")}</div>;
43
- }
44
- if (status === 'ok') {
45
- return <div className="text-emerald-500">{t("secraAdmin.login.passwordStrengthStrong")}</div>;
46
- }
47
- return <div className="text-rose-500">{t("secraAdmin.login.passwordStrengthWeak")}</div>;
48
- },
49
- }}
50
- placeholder={t("secraAdmin.login.passwordPlaceholder")}
51
- rules={[
52
- {
53
- required: true,
54
- message: t("secraAdmin.login.passwordRequired"),
55
- },
56
- ]}
57
- />
58
- </>
59
- );
60
- }