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:
29
frontend/src/api/auth.ts
Normal file
29
frontend/src/api/auth.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import client from "./client";
|
||||
import type { User } from "../types/models";
|
||||
|
||||
interface TokenResponse {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
/** Authenticate and return the access token. */
|
||||
export async function login(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<string> {
|
||||
const params = new URLSearchParams();
|
||||
params.append("username", username);
|
||||
params.append("password", password);
|
||||
|
||||
const { data } = await client.post<TokenResponse>("/auth/login", params, {
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
});
|
||||
|
||||
return data.access_token;
|
||||
}
|
||||
|
||||
/** Fetch the currently authenticated user profile. */
|
||||
export async function getMe(): Promise<User> {
|
||||
const { data } = await client.get<User>("/auth/me");
|
||||
return data;
|
||||
}
|
||||
28
frontend/src/api/client.ts
Normal file
28
frontend/src/api/client.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import axios from "axios";
|
||||
|
||||
const client = axios.create({
|
||||
baseURL: "http://localhost:8000/api/v1",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
// Attach the JWT token on every request (if present)
|
||||
client.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// On 401, clear token so the UI can redirect to login
|
||||
client.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem("token");
|
||||
}
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
export default client;
|
||||
Reference in New Issue
Block a user