import { apiUrl } from '../lib/apiOrigin' import { selectedSchoolYearHeaders, withSelectedSchoolYearUrl } from '../lib/schoolYearSelection' const TOKEN_KEY = 'alrahma_api_token' export function getStoredToken(): string | null { return localStorage.getItem(TOKEN_KEY) } export function setStoredToken(token: string | null): void { if (token) localStorage.setItem(TOKEN_KEY, token) else localStorage.removeItem(TOKEN_KEY) } export function decodeJwtPayload(token: string): T | null { try { const payload = token.split('.')[1] if (!payload) return null const base64 = payload.replace(/-/g, '+').replace(/_/g, '/') const json = decodeURIComponent( atob(base64) .split('') .map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, '0')}`) .join(''), ) return JSON.parse(json) as T } catch { return null } } export function applyStoredSchoolYearHeaders(headers: Headers): Headers { for (const [key, value] of Object.entries(selectedSchoolYearHeaders())) { if (!headers.has(key)) headers.set(key, value) } return headers } export function withStoredSchoolYearUrl(url: string): string { return withSelectedSchoolYearUrl(url) } export class ApiHttpError extends Error { readonly status: number readonly body: unknown constructor(message: string, status: number, body: unknown) { super(message) this.name = 'ApiHttpError' this.status = status this.body = body } } export async function apiFetch( path: string, init: RequestInit = {}, opts: { attachAuth?: boolean } = {}, ): Promise { const attachAuth = opts.attachAuth !== false const headers = new Headers(init.headers) if (!headers.has('Accept')) { headers.set('Accept', 'application/json') } const token = getStoredToken() if (attachAuth && token && !headers.has('Authorization')) { headers.set('Authorization', `Bearer ${token}`) } if (attachAuth && token) { applyStoredSchoolYearHeaders(headers) } const method = String(init.method ?? 'GET').toUpperCase() const url = apiUrl(method === 'GET' ? withStoredSchoolYearUrl(path) : path) let res: Response try { res = await fetch(url, { ...init, headers, }) } catch (e) { const hint = import.meta.env.DEV ? ' Start the API and ensure Vite can reach it. Default proxy is http://localhost:8000 unless VITE_PROXY_API or VITE_API_ORIGIN is set.' : '' const reason = e instanceof Error ? e.message : 'Failed to fetch' throw new ApiHttpError(`Network error: ${reason}.${hint}`, 0, null) } const body: unknown = await res.json().catch(() => null) if (!res.ok) { const msg = typeof body === 'object' && body !== null && 'message' in body ? String((body as { message?: unknown }).message ?? '') : '' throw new ApiHttpError(msg || `HTTP ${res.status}`, res.status, body) } return body as T }