import brand from "../../../brand.config.json"; export const BRAND = brand as { name: string; slug: string }; const ADMIN_TOKEN_KEY = `${BRAND.slug}_admin_token`; /** Enkel API-klient för adminpanelen. Token hålls i minne + sessionStorage. */ const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:4000"; let accessToken: string | null = sessionStorage.getItem(ADMIN_TOKEN_KEY); export function setToken(token: string | null): void { accessToken = token; if (token) sessionStorage.setItem(ADMIN_TOKEN_KEY, token); else sessionStorage.removeItem(ADMIN_TOKEN_KEY); } export function hasToken(): boolean { return accessToken != null; } export class ApiRequestError extends Error { constructor( public readonly status: number, public readonly code: string, message: string, ) { super(message); } } export async function api( path: string, options: { method?: string; body?: unknown } = {}, ): Promise { const res = await fetch(`${API_BASE}${path}`, { method: options.method ?? "GET", headers: { "content-type": "application/json", ...(accessToken ? { authorization: `Bearer ${accessToken}` } : {}), }, body: options.body != null ? JSON.stringify(options.body) : undefined, }); const json = (await res.json().catch(() => ({}))) as { error?: { code?: string; message?: string }; } & T; if (!res.ok) { if (res.status === 401) setToken(null); throw new ApiRequestError( res.status, json.error?.code ?? "UNKNOWN", json.error?.message ?? `HTTP ${res.status}`, ); } return json as T; } export interface LoginResult { totpRequired: boolean; preAuthToken?: string; } export async function login(email: string, password: string): Promise { const result = await api<{ accessToken?: string; user?: { role: string }; totpRequired?: boolean; preAuthToken?: string; }>("/v1/auth/login", { method: "POST", body: { email, password } }); if (result.totpRequired && result.preAuthToken) { return { totpRequired: true, preAuthToken: result.preAuthToken }; } if (!result.user || !result.accessToken) { throw new ApiRequestError(500, "INTERNAL", "Oväntat svar från inloggningen."); } if (result.user.role !== "admin" && result.user.role !== "moderator") { throw new ApiRequestError(403, "FORBIDDEN", "Kontot saknar admin-behörighet."); } setToken(result.accessToken); return { totpRequired: false }; } /** Steg 2 av inloggningen: engångskoden från autentiseringsappen. */ export async function verifyTotp(preAuthToken: string, code: string): Promise { const result = await api<{ accessToken: string; user: { role: string } }>( "/v1/auth/totp-verify", { method: "POST", body: { preAuthToken, code } }, ); if (result.user.role !== "admin" && result.user.role !== "moderator") { throw new ApiRequestError(403, "FORBIDDEN", "Kontot saknar admin-behörighet."); } setToken(result.accessToken); }