"use client"; let csrf: string | null = null; export class ApiError extends Error { constructor( readonly status: number, readonly code: string, message: string, readonly details?: unknown, ) { super(message); } } export async function getCsrf(force = false): Promise { if (csrf && !force) return csrf; const r = await fetch("/api/admin/auth/session", { cache: "no-store" }); if (!r.ok) throw new ApiError(401, "unauthorized", "Session expired. Please sign in again."); const j = (await r.json()) as { csrfToken: string }; csrf = j.csrfToken; return csrf; } export function setCsrf(token: string | null) { csrf = token; } /** Fetch wrapper for admin mutations: attaches the CSRF token, parses uniform errors (401/403/429). */ export async function api( path: string, init: RequestInit & { json?: unknown; form?: FormData } = {}, ): Promise { const method = init.method ?? (init.json !== undefined || init.form ? "POST" : "GET"); const headers: Record = { ...(init.headers as Record) }; if (method !== "GET" && method !== "HEAD") headers["x-csrf-token"] = await getCsrf(); let body: BodyInit | undefined = init.body as BodyInit | undefined; if (init.json !== undefined) { headers["content-type"] = "application/json"; body = JSON.stringify(init.json); } else if (init.form) body = init.form; const r = await fetch(path, { ...init, method, headers, body, cache: "no-store" }); const text = await r.text(); let j: unknown = null; try { j = text ? JSON.parse(text) : null; } catch { j = null; } if (!r.ok) { const e = (j as { error?: { code?: string; message?: string; details?: unknown } } | null)?.error; if (r.status === 401) { setCsrf(null); if (typeof window !== "undefined") window.location.href = "/admin/login"; } if (r.status === 403 && e?.message?.includes("CSRF")) setCsrf(null); throw new ApiError( r.status, e?.code ?? "error", e?.message ?? (r.status === 429 ? "Too many requests. Slow down." : `HTTP ${r.status}`), e?.details, ); } return j as T; }