/** * Thin fetch wrapper — always sends cookies (HttpOnly JWT). * All paths are relative so Next.js rewrites proxy them to the backend. */ async function request(path: string, init: RequestInit = {}): Promise { const res = await fetch(path, { ...init, credentials: "include", headers: { ...(init.body instanceof FormData ? {} : { "Content-Type": "application/json" }), ...init.headers, }, }); if (!res.ok) { const detail = await res.json().catch(() => ({ detail: res.statusText })); throw new Error(detail?.detail ?? "Request failed"); } if (res.status === 204) return undefined as T; return res.json(); } export const api = { // Auth me: () => request("/api/auth/me"), login: (body: { username: string; password: string }) => request("/api/auth/login", { method: "POST", body: JSON.stringify(body) }), register: (body: { username: string; email: string; password: string }) => request("/api/auth/register", { method: "POST", body: JSON.stringify(body) }), logout: () => request("/api/auth/logout", { method: "POST" }), // PDFs listPdfs: () => request("/api/pdfs"), uploadPdfs: (formData: FormData) => request("/api/pdfs/upload", { method: "POST", body: formData }), deletePdf: (id: number) => request(`/api/pdfs/${id}`, { method: "DELETE" }), // Annotations getAnnotation: (pdfId: number, page: number) => request(`/api/annotations/${pdfId}/${page}`), getAllAnnotations: (pdfId: number, page: number) => request(`/api/annotations/${pdfId}/${page}/all`), upsertAnnotation: (pdfId: number, page: number, body: { canvas_data: object }) => request(`/api/annotations/${pdfId}/${page}`, { method: "PUT", body: JSON.stringify(body), }), };