/** HQ web ↔ hq-server client; token survives reloads via localStorage['hq.token']. */ const TOKEN_KEY = 'hq.token' const NAME_KEY = 'hq.name' const ROLE_KEY = 'hq.role' export function hasSession(): boolean { return (localStorage.getItem(TOKEN_KEY) ?? '') !== '' } export function displayName(): string { return localStorage.getItem(NAME_KEY) ?? '' } /** 'owner' | 'staff' — Modules page renders read-only for staff. */ export function role(): string { return localStorage.getItem(ROLE_KEY) ?? 'staff' } export function clearSession(): void { localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(NAME_KEY) localStorage.removeItem(ROLE_KEY) } /** * Fetch `/api${path}` with the session bearer token. On 401 (outside the login * call itself) the session is cleared and the app redirects to /login. */ export async function apiFetch(path: string, opts?: RequestInit): Promise { const token = localStorage.getItem(TOKEN_KEY) ?? '' const res = await fetch(`/api${path}`, { ...opts, headers: { 'content-type': 'application/json', ...(token !== '' ? { authorization: `Bearer ${token}` } : {}), ...(opts?.headers ?? {}), }, }).catch(() => undefined) if (res === undefined) throw new Error('HQ server unreachable — is it running on port 5182?') if (res.status === 401 && !path.startsWith('/auth/')) { clearSession() window.location.hash = '#/login' throw new Error('Not signed in') } const body = (await res.json().catch(() => ({}))) as T & { error?: string } if (!res.ok) throw new Error(body.error ?? `Server error HTTP ${res.status}`) return body } export async function login(email: string, password: string): Promise<{ displayName: string; role: string }> { const out = await apiFetch<{ token: string; staff: { id: string; displayName: string; role: string } }>( '/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) }, ) localStorage.setItem(TOKEN_KEY, out.token) localStorage.setItem(NAME_KEY, out.staff.displayName) localStorage.setItem(ROLE_KEY, out.staff.role) return { displayName: out.staff.displayName, role: out.staff.role } } export interface EmailStatus { connected: boolean; address?: string; dead: boolean } export const getEmailStatus = (): Promise => apiFetch('/email/status')