This commit is contained in:
bipproduction
2025-10-13 17:27:10 +08:00
parent dd6ca462a9
commit 8b9abcdd03
4 changed files with 721 additions and 203 deletions

View File

@@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
import {
ActionIcon,
AppShell,
Avatar,
Badge,
Button,
Card,
Divider,
@@ -24,29 +24,32 @@ import {
IconDashboard,
IconKey,
IconLock,
IconUser,
} from "@tabler/icons-react";
import type { User } from "generated/prisma";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import {
default as clientRoute,
default as clientRoutes,
} from "@/clientRoutes";
import { default as clientRoute, default as clientRoutes } from "@/clientRoutes";
import apiFetch from "@/lib/apiFetch";
function Logout() {
return (
<Group>
<Group justify="center" mt="sm">
<Button
variant="transparent"
size="compact-xs"
variant="gradient"
gradient={{ from: "red", to: "orange", deg: 60 }}
size="xs"
radius="md"
onClick={async () => {
await apiFetch.auth.logout.delete();
localStorage.removeItem("token");
window.location.href = "/login";
}}
style={{
boxShadow: "0 0 10px rgba(255,100,100,0.25)",
transition: "transform 0.2s ease",
}}
>
Logout
Log Out
</Button>
</Group>
);
@@ -60,14 +63,24 @@ export default function DashboardLayout() {
return (
<AppShell
padding="md"
padding="lg"
navbar={{
width: 260,
breakpoint: "sm",
collapsed: { mobile: !opened, desktop: !opened },
}}
style={{
background: "radial-gradient(circle at top, #0a0a0a, #101010 70%)",
color: "#f8f9fa",
}}
>
<AppShell.Navbar>
<AppShell.Navbar
style={{
background: "linear-gradient(180deg, #141414, #1e1e1e)",
borderRight: "1px solid rgba(255,255,255,0.05)",
boxShadow: "2px 0 10px rgba(0,0,0,0.6)",
}}
>
<AppShell.Section>
<Group justify="flex-end" p="xs">
<Tooltip
@@ -78,8 +91,12 @@ export default function DashboardLayout() {
variant="light"
color="gray"
onClick={() => setOpened((v) => !v)}
aria-label="Toggle navigation"
radius="xl"
size="lg"
style={{
backgroundColor: "rgba(255,255,255,0.05)",
boxShadow: "0 0 6px rgba(0,255,200,0.2)",
}}
>
{opened ? <IconChevronLeft /> : <IconChevronRight />}
</ActionIcon>
@@ -97,8 +114,17 @@ export default function DashboardLayout() {
</AppShell.Navbar>
<AppShell.Main>
<Stack>
<Paper withBorder shadow="md" radius="lg" p="md">
<Stack gap="md">
<Paper
withBorder
radius="lg"
p="md"
style={{
background: "linear-gradient(145deg, #181818, #202020)",
border: "1px solid rgba(255,255,255,0.05)",
boxShadow: "0 0 15px rgba(0,255,200,0.08)",
}}
>
<Flex align="center" gap="md">
{!opened && (
<Tooltip label="Open navigation menu" withArrow>
@@ -106,15 +132,14 @@ export default function DashboardLayout() {
variant="light"
color="gray"
onClick={() => setOpened(true)}
aria-label="Open navigation"
radius="xl"
>
<IconChevronRight />
</ActionIcon>
</Tooltip>
)}
<Title order={3} fw={600}>
App Dashboard
<Title order={3} fw={600} c="gray.0">
Dashboard Control Panel
</Title>
</Flex>
</Paper>
@@ -125,7 +150,6 @@ export default function DashboardLayout() {
);
}
/* ----------------------- Host Info ----------------------- */
function HostView() {
const [host, setHost] = useState<User | null>(null);
@@ -138,33 +162,46 @@ function HostView() {
}, []);
return (
<Card radius="lg" withBorder shadow="sm" p="md">
<Card
radius="lg"
withBorder
shadow="sm"
p="md"
style={{
background: "linear-gradient(145deg, #181818, #212121)",
borderColor: "rgba(255,255,255,0.05)",
}}
>
{host ? (
<Stack>
<Stack gap="sm">
<Flex gap="md" align="center">
<Avatar size="md" radius="xl" color="blue">
{host.name?.[0]}
<Avatar size="md" radius="xl" color="teal" variant="filled">
{host.name?.[0]?.toUpperCase()}
</Avatar>
<Stack gap={2}>
<Text fw={600}>{host.name}</Text>
<Text fw={600} c="gray.0">
{host.name}
</Text>
<Text size="sm" c="dimmed">
{host.email}
</Text>
</Stack>
</Flex>
<Divider />
<Divider my="xs" color="rgba(255,255,255,0.1)" />
<Logout />
</Stack>
) : (
<Text size="sm" c="dimmed" ta="center">
No host information available
</Text>
<Flex align="center" justify="center" direction="column" p="md">
<IconUser size={28} color="gray" />
<Text size="sm" c="dimmed" mt={4}>
No user information available
</Text>
</Flex>
)}
</Card>
);
}
/* ----------------------- Navigation ----------------------- */
function NavigationDashboard() {
const navigate = useNavigate();
const location = useLocation();
@@ -172,31 +209,66 @@ function NavigationDashboard() {
const isActive = (path: keyof typeof clientRoute) =>
location.pathname.startsWith(clientRoute[path]);
const navItems = [
{
path: "/scr/dashboard/dashboard-home",
icon: <IconDashboard size={20} />,
label: "Dashboard Overview",
description: "Quick summary and insights",
},
{
path: "/scr/dashboard/apikey/apikey",
icon: <IconKey size={20} />,
label: "API Key Manager",
description: "Create and manage API keys",
},
{
path: "/scr/dashboard/credential/credential",
icon: <IconLock size={20} />,
label: "Credentials",
description: "Manage service credentials",
},
];
return (
<Stack gap="xs" p="sm">
<NavLink
active={isActive("/scr/dashboard/dashboard-home")}
leftSection={<IconDashboard size={20} />}
label="Dashboard Overview"
description="Quick summary and activity highlights"
onClick={() => navigate(clientRoutes["/scr/dashboard/dashboard-home"])}
/>
<NavLink
active={isActive("/scr/dashboard/apikey/apikey")}
leftSection={<IconKey size={20} />}
label="API Key"
description="API Key Management and Generation"
onClick={() => navigate(clientRoutes["/scr/dashboard/apikey/apikey"])}
/>
<NavLink
active={isActive("/scr/dashboard/credential/credential")}
leftSection={<IconLock size={20} />}
label="Credential"
description="Credential Management"
onClick={() =>
navigate(clientRoutes["/scr/dashboard/credential/credential"])
}
/>
{navItems.map((item) => (
<NavLink
key={item.path}
active={isActive(item.path as keyof typeof clientRoute)}
leftSection={item.icon}
label={
<Flex align="center" gap={6}>
<Text fw={500}>{item.label}</Text>
{isActive(item.path as keyof typeof clientRoute) && (
<Badge
variant="light"
color="teal"
radius="sm"
size="xs"
style={{ textTransform: "none" }}
>
Active
</Badge>
)}
</Flex>
}
description={item.description}
onClick={() => navigate(clientRoutes[item.path as keyof typeof clientRoute])}
style={{
backgroundColor: isActive(item.path as keyof typeof clientRoute)
? "rgba(0,255,200,0.1)"
: "transparent",
borderRadius: "8px",
transition: "all 0.2s ease",
}}
styles={{
label: { color: "white" },
description: { color: "#aaa" },
section: { color: "teal" },
}}
/>
))}
</Stack>
);
}