10d0769071
BREAKING: version bump to 1.0.0 New design system: - Emerald/teal color palette with semantic tokens (success, warning, destructive) - Light/dark mode toggle with localStorage persistence and anti-FOUC - CSS custom properties via Tailwind darkMode: 'class' - Zero new dependencies — all primitives hand-rolled Foundation: - 70+ inline SVG icons (no external icon library) - ThemeProvider + useTheme hook - cn() utility for conditional classes UI primitives (web/src/components/ui/): - Button (6 variants, 4 sizes) - Input, Label, Card with slots, Badge (6 variants + sizes) - Switch (animated), Checkbox, Separator - Skeleton, Spinner, EmptyState - Toast system with auto-dismiss (success/error/warning/info) - Dialog with focus trap + escape/click-outside - ConfirmDialog replacing native confirm() - DropdownMenu with keyboard support Layout & navigation: - Sidebar with icon+label nav, active indicator, responsive drawer on mobile - Topbar for <lg viewports with hamburger - PageHeader with title/description/actions pattern - UserMenu with avatar + dropdown (change password, logout) - DirtyBanner redesigned with semantic styling Pages redesigned: - Login: centered card with gradient background - Dashboard: 4 KPI tiles, disk usage bars, services grid, recent activity - Users: table with search, empty state, edit modal - Files: breadcrumbs, drag-drop upload zone, SVG file icons, rename/delete - Samba: card list with badges, edit modal with PathField - NFS: card list with client chips, client editor - Storage: tabs (Mover/SnapRAID/Jobs), real-time job log streaming - Log: timeline grouped by date with filters - Settings: watched mounts, import actions, system info - NotFound: friendly 404 page Polish: - Toast notifications replace all native alert()/confirm() - Tailwind animations (dialog-enter, toast-enter/leave, dropdown-enter) - Custom scrollbar styling - Focus-visible rings - Skeleton loading states across all pages
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Navigate, Route, Routes } from "react-router-dom";
|
|
import { api } from "./api";
|
|
import { DirtyProvider } from "./DirtyContext";
|
|
import Layout from "./components/Layout";
|
|
import Login from "./pages/Login";
|
|
import Dashboard from "./pages/Dashboard";
|
|
import Users from "./pages/Users";
|
|
import Samba from "./pages/Samba";
|
|
import Nfs from "./pages/Nfs";
|
|
import Log from "./pages/Log";
|
|
import Settings from "./pages/Settings";
|
|
import Files from "./pages/Files";
|
|
import Storage from "./pages/Storage";
|
|
import NotFound from "./pages/NotFound";
|
|
|
|
type AuthState = { loading: boolean; authenticated: boolean; username: string };
|
|
|
|
export default function App() {
|
|
const [auth, setAuth] = useState<AuthState>({ loading: true, authenticated: false, username: "" });
|
|
|
|
useEffect(() => {
|
|
api
|
|
.status()
|
|
.then((res) => setAuth({ loading: false, authenticated: res.authenticated, username: res.username }))
|
|
.catch(() => setAuth({ loading: false, authenticated: false, username: "" }));
|
|
}, []);
|
|
|
|
if (auth.loading) {
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center">
|
|
<div className="flex items-center gap-3 text-muted-foreground">
|
|
<svg className="animate-spin" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
</svg>
|
|
<span>Cargando…</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!auth.authenticated) {
|
|
return (
|
|
<Routes>
|
|
<Route
|
|
path="/login"
|
|
element={<Login onLoggedIn={(username) => setAuth({ loading: false, authenticated: true, username })} />}
|
|
/>
|
|
<Route path="*" element={<Navigate to="/login" replace />} />
|
|
</Routes>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<DirtyProvider>
|
|
<Routes>
|
|
<Route
|
|
element={
|
|
<Layout
|
|
username={auth.username}
|
|
onLogout={() => setAuth({ loading: false, authenticated: false, username: "" })}
|
|
/>
|
|
}
|
|
>
|
|
<Route path="/" element={<Dashboard />} />
|
|
<Route path="/users" element={<Users />} />
|
|
<Route path="/files" element={<Files />} />
|
|
<Route path="/samba" element={<Samba />} />
|
|
<Route path="/nfs" element={<Nfs />} />
|
|
<Route path="/storage" element={<Storage />} />
|
|
<Route path="/log" element={<Log />} />
|
|
<Route path="/settings" element={<Settings />} />
|
|
<Route path="*" element={<NotFound />} />
|
|
</Route>
|
|
</Routes>
|
|
</DirtyProvider>
|
|
);
|
|
}
|