/** Minimal API client: cookie session + bearer token (for SSE), French error messages. */ export const API_URL = import.meta.env.VITE_API_URL || '/api/v1'; const TOKEN_KEY = 'uqo.token'; export function getToken(): string | null { return localStorage.getItem(TOKEN_KEY); } export function setToken(token: string | null): void { if (token) localStorage.setItem(TOKEN_KEY, token); else localStorage.removeItem(TOKEN_KEY); } export class ApiError extends Error { status: number; constructor(status: number, message: string) { super(message); this.status = status; } } export function authHeaders(): Record { const t = getToken(); return t ? { Authorization: `Bearer ${t}` } : {}; } export async function api(path: string, init: RequestInit = {}): Promise { const headers: Record = { ...authHeaders(), ...(init.headers as Record | undefined) }; if (init.body && !(init.body instanceof FormData)) headers['Content-Type'] = 'application/json'; const res = await fetch(`${API_URL}${path}`, { ...init, headers, credentials: 'include' }); if (res.status === 401) { setToken(null); window.dispatchEvent(new CustomEvent('uqo:unauthorized')); } if (!res.ok) { let detail = 'Une erreur est survenue.'; try { const j = await res.json(); detail = typeof j.detail === 'string' ? j.detail : JSON.stringify(j.detail); } catch { /* ignore */ } throw new ApiError(res.status, detail); } if (res.status === 204) return undefined as T; return (await res.json()) as T; } export const fileUrl = (fileId: string): string => `${API_URL}/files/${fileId}`; export async function downloadFile(fileId: string, filename: string): Promise { const { url } = await api<{ url: string }>(`/files/${fileId}/link`); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); } export async function shareFile(fileId: string, filename: string): Promise { if (!navigator.share) return false; const res = await fetch(fileUrl(fileId), { headers: authHeaders(), credentials: 'include' }); const blob = await res.blob(); const file = new File([blob], filename, { type: blob.type }); if (navigator.canShare && !navigator.canShare({ files: [file] })) return false; await navigator.share({ files: [file], title: filename }); return true; }