Frontend : squelette React + Vite + TS + Tailwind

Scaffold de base en attendant l'intégration du design Claude Design
(« Bénévoles Festival.dc.html ») : routage, plomberie i18n (t() +
locales/fr.json), client API avec proxy Vite vers le backend. Build et
lint passent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwswqSChc3Rwtu61pRXiY2
This commit is contained in:
AdamThomas
2026-09-09 23:16:38 +02:00
co-authored by Claude Sonnet 5
parent 49ac6a4726
commit 5154a3c61b
23 changed files with 4673 additions and 9 deletions
+42
View File
@@ -0,0 +1,42 @@
// Client HTTP minimal. En dev, Vite proxifie `/api` vers le backend (:8000).
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? '/api';
export class ApiError extends Error {
constructor(
public readonly status: number,
public readonly body: unknown,
) {
super(`API ${status}`);
this.name = 'ApiError';
}
}
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE_URL}${path}`, {
...init,
headers: {
'Content-Type': 'application/json',
...init?.headers,
},
credentials: 'include',
});
const isJson = res.headers.get('content-type')?.includes('application/json');
const body = isJson ? await res.json() : await res.text();
if (!res.ok) {
throw new ApiError(res.status, body);
}
return body as T;
}
export const api = {
get: <T>(path: string) => request<T>(path),
post: <T>(path: string, data?: unknown) =>
request<T>(path, { method: 'POST', body: data === undefined ? undefined : JSON.stringify(data) }),
patch: <T>(path: string, data?: unknown) =>
request<T>(path, { method: 'PATCH', body: data === undefined ? undefined : JSON.stringify(data) }),
put: <T>(path: string, data?: unknown) =>
request<T>(path, { method: 'PUT', body: data === undefined ? undefined : JSON.stringify(data) }),
delete: <T>(path: string) => request<T>(path, { method: 'DELETE' }),
};