feat: Phase 7 - Frontend scaffolding and auth (T-023, T-024, T-025)
T-023: Initialize React project - Vite + React 19 + TypeScript scaffold - Tailwind CSS v4 with @tailwindcss/vite plugin - Dependencies: react-router-dom, axios, @tanstack/react-query, lucide-react - Project structure: api/, components/, pages/, context/, types/, hooks/, lib/ T-024: API client and auth context - Axios client with JWT interceptor (auto-attach token, clear on 401) - login() and getMe() API functions - AuthContext: user state, login, logout, isAuthenticated, isLoading - Token persistence via localStorage with hydration on mount - TypeScript types for all backend models T-025: Login page and layout - LoginPage with form, error handling, redirect on success - Layout with sidebar + header + Outlet - Sidebar with role-aware navigation (System only for admin) - ProtectedRoute wrapper with role-based access control - Routes: /login, /dashboard, /techniques, /tests, /system
This commit is contained in:
64
frontend/src/components/Sidebar.tsx
Normal file
64
frontend/src/components/Sidebar.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { NavLink } from "react-router-dom";
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Shield,
|
||||
FlaskConical,
|
||||
Settings,
|
||||
} from "lucide-react";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
|
||||
const baseLinks = [
|
||||
{ to: "/dashboard", label: "Dashboard", icon: LayoutDashboard },
|
||||
{ to: "/techniques", label: "Techniques", icon: Shield },
|
||||
{ to: "/tests", label: "Tests", icon: FlaskConical },
|
||||
];
|
||||
|
||||
const adminLinks = [
|
||||
{ to: "/system", label: "System", icon: Settings },
|
||||
];
|
||||
|
||||
export default function Sidebar() {
|
||||
const { user } = useAuth();
|
||||
const isAdmin = user?.role === "admin";
|
||||
|
||||
const links = isAdmin ? [...baseLinks, ...adminLinks] : baseLinks;
|
||||
|
||||
return (
|
||||
<aside className="flex h-screen w-60 flex-col border-r border-gray-800 bg-gray-900">
|
||||
{/* Logo */}
|
||||
<div className="flex h-16 items-center gap-2 px-5">
|
||||
<Shield className="h-7 w-7 text-cyan-400" />
|
||||
<span className="text-lg font-bold tracking-wide text-white">
|
||||
Aegis
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Nav links */}
|
||||
<nav className="flex-1 space-y-1 px-3 py-4">
|
||||
{links.map(({ to, label, icon: Icon }) => (
|
||||
<NavLink
|
||||
key={to}
|
||||
to={to}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors ${
|
||||
isActive
|
||||
? "bg-cyan-500/10 text-cyan-400"
|
||||
: "text-gray-400 hover:bg-gray-800 hover:text-gray-200"
|
||||
}`
|
||||
}
|
||||
>
|
||||
<Icon className="h-5 w-5" />
|
||||
{label}
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="border-t border-gray-800 px-5 py-4">
|
||||
<p className="truncate text-xs text-gray-500">
|
||||
{user?.role ?? "—"}
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user