feat(frontend): Task 14 - Vite React TypeScript scaffolding with Tailwind CSS

Sets up the frontend SPA foundation: Vite+React+TS project at frontend/,
configures Tailwind CSS v4 via @tailwindcss/vite, dev proxy to port 4000,
and implements the API client (JWT auth, auto-redirect on 401).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 13:06:27 +02:00
co-authored by Claude Sonnet 4.6
parent 9e96ecc231
commit 49089d6d17
10 changed files with 3572 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
const TOKEN_KEY = 'display_token';
export function getToken(): string | null {
return localStorage.getItem(TOKEN_KEY);
}
export function setToken(token: string): void {
localStorage.setItem(TOKEN_KEY, token);
}
export function clearToken(): void {
localStorage.removeItem(TOKEN_KEY);
}
export function isLoggedIn(): boolean {
return !!getToken();
}
async function request<T>(method: string, path: string, body?: unknown): Promise<T> {
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
const token = getToken();
if (token) headers['Authorization'] = `Bearer ${token}`;
const res = await fetch(path, {
method,
headers,
body: body !== undefined ? JSON.stringify(body) : undefined,
});
if (res.status === 401) {
clearToken();
window.location.href = '/login';
throw new Error('Unauthorized');
}
if (res.status === 204) return undefined as T;
const data = await res.json();
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
return data as T;
}
export const api = {
get: <T>(path: string) => request<T>('GET', path),
post: <T>(path: string, body?: unknown) => request<T>('POST', path, body),
put: <T>(path: string, body?: unknown) => request<T>('PUT', path, body),
del: <T>(path: string) => request<T>('DELETE', path),
};
+1
View File
@@ -0,0 +1 @@
@import "tailwindcss";
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.tsx';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);