import { API_BASE_URL } from "./config"; const TOKEN_KEY = "classlens_token"; // Student and teacher logins share one token slot, so we track which kind of // account it belongs to — lets the app pick the right "am I logged in" check // (student vs teacher) before the first API call comes back. const ROLE_KEY = "classlens_role"; export type UserRole = "student" | "teacher"; export function getToken(): string | null { return localStorage.getItem(TOKEN_KEY); } export function setToken(token: string, role: UserRole) { localStorage.setItem(TOKEN_KEY, token); localStorage.setItem(ROLE_KEY, role); } export function getRole(): UserRole | null { const role = localStorage.getItem(ROLE_KEY); return role === "student" || role === "teacher" ? role : null; } export function clearToken() { localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(ROLE_KEY); } function authHeaders(): Record { const token = getToken(); return token ? { Authorization: `Bearer ${token}` } : {}; } async function handleResponse(res: Response): Promise { if (res.status === 401) { // Only treat this as an expired session (clear + reload) if the request // actually carried a token. A login/register call sends no token, so a // 401 there means "wrong credentials," not "session expired" — let it // fall through so the caller can show the real error message instead of // silently reloading the login page. if (getToken()) { clearToken(); window.location.reload(); throw new Error("Unauthorized"); } } if (!res.ok) { const text = await res.text(); let msg = text; try { const j: unknown = JSON.parse(text); if (j && typeof j === "object") { const obj = j as { detail?: unknown; message?: unknown }; const detail = obj.detail ?? obj.message; if (typeof detail === "string") msg = detail; } } catch { // Response body wasn't JSON — fall back to the raw text already in msg. } throw new Error(msg); } return (await res.json()) as T; } export async function apiGet(path: string): Promise { const res = await fetch(`${API_BASE_URL}${path}`, { headers: { ...authHeaders() }, }); return handleResponse(res); } export async function apiPost(path: string, body?: unknown): Promise { const res = await fetch(`${API_BASE_URL}${path}`, { method: "POST", headers: { "Content-Type": "application/json", ...authHeaders() }, body: body ? JSON.stringify(body) : undefined, }); return handleResponse(res); } export async function apiPatch(path: string, body: unknown): Promise { const res = await fetch(`${API_BASE_URL}${path}`, { method: "PATCH", headers: { "Content-Type": "application/json", ...authHeaders() }, body: JSON.stringify(body), }); return handleResponse(res); } export async function apiPut(path: string, body: unknown): Promise { const res = await fetch(`${API_BASE_URL}${path}`, { method: "PUT", headers: { "Content-Type": "application/json", ...authHeaders() }, body: JSON.stringify(body), }); return handleResponse(res); } export async function apiDelete(path: string): Promise { const res = await fetch(`${API_BASE_URL}${path}`, { method: "DELETE", headers: { ...authHeaders() }, }); if (res.status === 401) { clearToken(); window.location.reload(); throw new Error("Unauthorized"); } if (!res.ok) { const text = await res.text(); throw new Error(text); } const ct = res.headers.get("content-type") || ""; if (ct.includes("application/json")) return res.json() as Promise; return undefined as T; } /** Fetches a binary response (e.g. a stored PDF/image) as a blob, attaching * the same bearer auth header as the JSON helpers above — a plain / *