/** * The HTTP client. One place that knows the base URL. * * Deliberately dependency-free: the scaffold installs no axios, so an `api.ts` * that imported it would fail to build. The shape (`.get(...).then(r => r.data)`) * matches what the fullstack skill's examples use. * * VITE_API_URL is the backend BASE, wired at deploy time. Endpoints live under * `/api`. Never hardcode localhost — it works in dev and breaks on deploy. */ const BASE = (import.meta.env.VITE_API_URL ?? '').replace(/\/$/, ''); export type Res = { data: T; status: number }; // Set by an auth store (the `auth` block) — every request then carries it as a // Bearer header. Nothing else in the app needs to know how auth travels. let authToken: string | null = null; export const setAuthToken = (token: string | null): void => { authToken = token; }; async function request(method: string, path: string, body?: unknown): Promise> { const headers: Record = {}; if (body !== undefined) headers['Content-Type'] = 'application/json'; if (authToken) headers.Authorization = `Bearer ${authToken}`; const res = await fetch(`${BASE}${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), }); if (!res.ok) { let detail = `${res.status} ${res.statusText}`; try { const j = (await res.json()) as { detail?: unknown }; if (typeof j.detail === 'string') detail = j.detail; } catch { /* keep the status line */ } throw new Error(detail); } // 204 has no body — reading it as JSON throws, and a delete that "fails" // because it succeeded is a confusing bug to chase. const data = res.status === 204 ? (undefined as T) : ((await res.json()) as T); return { data, status: res.status }; } export const api = { get: (path: string) => request('GET', path), post: (path: string, body?: unknown) => request('POST', path, body ?? {}), put: (path: string, body?: unknown) => request('PUT', path, body ?? {}), patch: (path: string, body?: unknown) => request('PATCH', path, body ?? {}), delete: (path: string) => request('DELETE', path), }; export const apiBaseUrl = () => BASE;