create-secra 0.1.11 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/antd-adapter-template/README.md +24 -0
- package/antd-adapter-template/index.html +16 -0
- package/antd-adapter-template/package-lock.json +3420 -0
- package/antd-adapter-template/package.json +20 -47
- package/antd-adapter-template/src/app/App.tsx +7 -0
- package/antd-adapter-template/src/app/layouts/AppLayout.tsx +163 -0
- package/antd-adapter-template/src/app/router.ts +26 -0
- package/antd-adapter-template/src/app/routes/RouteTitleSync.tsx +26 -0
- package/antd-adapter-template/src/app/routes/dynamic-routes.ts +52 -0
- package/antd-adapter-template/src/app/routes/route-modules.ts +4 -0
- package/antd-adapter-template/src/app/routes/static-routes.tsx +110 -0
- package/antd-adapter-template/src/app/routes/types.ts +9 -0
- package/antd-adapter-template/src/features/auth/api/auth-api.ts +61 -0
- package/antd-adapter-template/src/features/auth/auth-store.ts +125 -0
- package/antd-adapter-template/src/features/auth/auth-types.ts +29 -0
- package/antd-adapter-template/src/features/auth/authorization.ts +46 -0
- package/antd-adapter-template/src/features/auth/use-auth.ts +10 -0
- package/antd-adapter-template/src/main.tsx +79 -0
- package/antd-adapter-template/src/pages/dashboard/DashboardPage.tsx +105 -0
- package/antd-adapter-template/src/pages/errors/ForbiddenPage.tsx +36 -0
- package/antd-adapter-template/src/pages/errors/NotFoundPage.tsx +36 -0
- package/antd-adapter-template/src/pages/home/HomePage.tsx +129 -0
- package/antd-adapter-template/src/pages/login/LoginPage.tsx +128 -0
- package/antd-adapter-template/src/pages/permission-test/PermissionTestPage.tsx +55 -0
- package/antd-adapter-template/src/pages/restricted/RestrictedDemoPage.tsx +17 -0
- package/antd-adapter-template/src/shared/kernel/app-kernel.ts +10 -0
- package/antd-adapter-template/src/shared/request/client.ts +46 -0
- package/antd-adapter-template/src/shared/request/contracts.ts +6 -0
- package/antd-adapter-template/src/shared/request/kv-adapter.ts +14 -0
- package/antd-adapter-template/src/shared/request/kv-backend.ts +244 -0
- package/antd-adapter-template/src/shared/request/ky-browser-stub.ts +6 -0
- package/antd-adapter-template/src/shared/request/undici-browser-stub.ts +4 -0
- package/antd-adapter-template/src/styles/global.css +185 -0
- package/antd-adapter-template/src/vite-env.d.ts +2 -0
- package/antd-adapter-template/tsconfig.app.json +10 -13
- package/antd-adapter-template/tsconfig.json +7 -2
- package/antd-adapter-template/tsconfig.node.json +6 -16
- package/antd-adapter-template/vite.config.ts +24 -0
- package/bin/index.mjs +29 -5
- package/package.json +2 -2
- package/template/apps/core/src/main.tsx +34 -13
- package/template/package.json +6 -2
- package/template/packages/sdk/package.json +3 -0
- package/template/packages/sdk/src/request/index.ts +1 -1
- package/template/pnpm-lock.yaml +67 -88
- package/antd-adapter-template/apps/core/index.html +0 -13
- package/antd-adapter-template/apps/core/package.json +0 -18
- package/antd-adapter-template/apps/core/public/favicon.ico +0 -1
- package/antd-adapter-template/apps/core/public/favicon.svg +0 -1
- package/antd-adapter-template/apps/core/public/logo.svg +0 -1
- package/antd-adapter-template/apps/core/src/api/auth.ts +0 -49
- package/antd-adapter-template/apps/core/src/assets/react.svg +0 -1
- package/antd-adapter-template/apps/core/src/components/AntdGlobalProvider.tsx +0 -87
- package/antd-adapter-template/apps/core/src/components/AntdRootLayout.tsx +0 -10
- package/antd-adapter-template/apps/core/src/components/layout.tsx +0 -387
- package/antd-adapter-template/apps/core/src/guards/auth-route-guard.ts +0 -45
- package/antd-adapter-template/apps/core/src/main.tsx +0 -65
- package/antd-adapter-template/apps/core/src/pages/auth/components/account-login-fields.tsx +0 -60
- package/antd-adapter-template/apps/core/src/pages/auth/components/phone-login-fields.tsx +0 -60
- package/antd-adapter-template/apps/core/src/pages/auth/login.tsx +0 -169
- package/antd-adapter-template/apps/core/src/pages/index.tsx +0 -156
- package/antd-adapter-template/apps/core/src/router.ts +0 -42
- package/antd-adapter-template/apps/core/src/shims/use-sync-external-store-shim.ts +0 -3
- package/antd-adapter-template/apps/core/src/theme/theme.css +0 -48
- package/antd-adapter-template/apps/core/src/types/crypto-js.d.ts +0 -5
- package/antd-adapter-template/apps/core/src/utils/index.ts +0 -12
- package/antd-adapter-template/apps/core/src/utils/md5.ts +0 -6
- package/antd-adapter-template/apps/core/tsconfig.app.json +0 -11
- package/antd-adapter-template/apps/core/tsconfig.json +0 -13
- package/antd-adapter-template/apps/core/tsconfig.node.json +0 -7
- package/antd-adapter-template/apps/core/vite.config.ts +0 -118
- package/antd-adapter-template/eslint.config.js +0 -23
- package/antd-adapter-template/packages/sdk/.swcrc +0 -18
- package/antd-adapter-template/packages/sdk/package.json +0 -52
- package/antd-adapter-template/packages/sdk/src/build/index.ts +0 -28
- package/antd-adapter-template/packages/sdk/src/build/plugins/auto-import.ts +0 -46
- package/antd-adapter-template/packages/sdk/src/build/plugins/bundle-analyzer.ts +0 -33
- package/antd-adapter-template/packages/sdk/src/build/plugins/remove-console.ts +0 -23
- package/antd-adapter-template/packages/sdk/src/build/plugins/unocss.ts +0 -202
- package/antd-adapter-template/packages/sdk/src/build/plugins/unplugin-icon.ts +0 -43
- package/antd-adapter-template/packages/sdk/src/components/i18n-switch-dropdown.tsx +0 -139
- package/antd-adapter-template/packages/sdk/src/components/index.ts +0 -2
- package/antd-adapter-template/packages/sdk/src/components/theme-switch-dropdown.tsx +0 -131
- package/antd-adapter-template/packages/sdk/src/hooks/auth/core.ts +0 -101
- package/antd-adapter-template/packages/sdk/src/hooks/auth/index.ts +0 -139
- package/antd-adapter-template/packages/sdk/src/hooks/auth/with-auth.tsx +0 -41
- package/antd-adapter-template/packages/sdk/src/hooks/index.ts +0 -1
- package/antd-adapter-template/packages/sdk/src/i18n/index.ts +0 -150
- package/antd-adapter-template/packages/sdk/src/index.ts +0 -11
- package/antd-adapter-template/packages/sdk/src/request/index.ts +0 -436
- package/antd-adapter-template/packages/sdk/src/storage/README.md +0 -30
- package/antd-adapter-template/packages/sdk/src/storage/index.ts +0 -57
- package/antd-adapter-template/packages/sdk/src/styles/reset.css +0 -111
- package/antd-adapter-template/packages/sdk/src/theme/index.ts +0 -466
- package/antd-adapter-template/packages/sdk/tsconfig.json +0 -16
- package/antd-adapter-template/pnpm-workspace.yaml +0 -3
- package/antd-adapter-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,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
|
-
}
|