taboola
add student metrics dashboard, save quiz answer records, save original uploaded files
25cb9ab
Raw History Blame Contribute Delete
4.47 kB
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<string, string> {
const token = getToken();
return token ? { Authorization: `Bearer ${token}` } : {};
}
async function handleResponse<T>(res: Response): Promise<T> {
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<T>(path: string): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
headers: { ...authHeaders() },
});
return handleResponse<T>(res);
}
export async function apiPost<T>(path: string, body?: unknown): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json", ...authHeaders() },
body: body ? JSON.stringify(body) : undefined,
});
return handleResponse<T>(res);
}
export async function apiPatch<T>(path: string, body: unknown): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
method: "PATCH",
headers: { "Content-Type": "application/json", ...authHeaders() },
body: JSON.stringify(body),
});
return handleResponse<T>(res);
}
export async function apiPut<T>(path: string, body: unknown): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
method: "PUT",
headers: { "Content-Type": "application/json", ...authHeaders() },
body: JSON.stringify(body),
});
return handleResponse<T>(res);
}
export async function apiDelete<T = void>(path: string): Promise<T> {
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<T>;
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 <img>/
* <iframe> src can't carry that header, so callers create an object URL from
* this instead. */
export async function apiGetBlob(path: string): Promise<Blob> {
const res = await fetch(`${API_BASE_URL}${path}`, {
headers: { ...authHeaders() },
});
if (!res.ok) throw new Error(await res.text());
return res.blob();
}
export async function apiUpload<T>(path: string, formData: FormData): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
method: "POST",
headers: { ...authHeaders() },
body: formData,
});
return handleResponse<T>(res);
}