Files
baby-nas/web/src/App.tsx
T
darroyo 10d0769071 feat!: complete visual redesign → 1.0.0
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
2026-07-08 01:24:51 -04:00

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>
);
}