Initial commit: SejeteralO water tarification platform
Full-stack app for participatory water pricing using Bezier curves. - Backend: FastAPI + SQLAlchemy + SQLite with JWT auth - Frontend: Nuxt 4 + TypeScript with interactive SVG editor - Math engine: cubic Bezier tarification with Cardano solver - Admin: commune management, household import, vote monitoring, CMS - Citizen: interactive curve editor, vote submission - Docker-compose deployment ready Includes fixes for: - Impact table snake_case/camelCase property mismatch - CMS content backend API + frontend editor (was stub) - Admin route protection middleware - Public content display on commune page - Vote confirmation page link fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
67
frontend/app/composables/useApi.ts
Normal file
67
frontend/app/composables/useApi.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Composable for API calls to the FastAPI backend.
|
||||
*/
|
||||
export function useApi() {
|
||||
const config = useRuntimeConfig()
|
||||
const baseURL = config.public.apiBase as string
|
||||
|
||||
function getToken(): string | null {
|
||||
if (import.meta.client) {
|
||||
return localStorage.getItem('sejeteralo_token')
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function apiFetch<T>(
|
||||
path: string,
|
||||
options: RequestInit = {},
|
||||
): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
...(options.headers as Record<string, string> || {}),
|
||||
}
|
||||
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
if (options.body && !(options.body instanceof FormData)) {
|
||||
headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
|
||||
let response: Response
|
||||
try {
|
||||
response = await fetch(`${baseURL}${path}`, {
|
||||
...options,
|
||||
headers,
|
||||
})
|
||||
} catch (err) {
|
||||
throw new Error(`Impossible de contacter le serveur (${baseURL}). Vérifiez que le backend est lancé.`)
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ detail: response.statusText }))
|
||||
throw new Error(error.detail || `Erreur API ${response.status}`)
|
||||
}
|
||||
|
||||
const text = await response.text()
|
||||
if (!text) return {} as T
|
||||
return JSON.parse(text)
|
||||
}
|
||||
|
||||
return {
|
||||
get: <T>(path: string) => apiFetch<T>(path),
|
||||
post: <T>(path: string, body?: unknown) =>
|
||||
apiFetch<T>(path, {
|
||||
method: 'POST',
|
||||
body: body instanceof FormData ? body : JSON.stringify(body),
|
||||
}),
|
||||
put: <T>(path: string, body?: unknown) =>
|
||||
apiFetch<T>(path, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
delete: <T>(path: string) =>
|
||||
apiFetch<T>(path, { method: 'DELETE' }),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user