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:
2026-02-06 16:09:50 +01:00
parent 52d230628d
commit 591b5df250
26 changed files with 3489 additions and 4 deletions

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