/** * API Client Component * * Frontend helper for making authenticated requests to the backend. * Uses PocketBase token from auth session. */ // Header constants (inline to avoid cross-component dependency) const HEADERS = { AUTH: 'Authorization', CONTENT_TYPE: 'Content-Type', } as const; const CONTENT_TYPES = { JSON: 'application/json', } as const; function authHeader(token: string): string { return `Bearer ${token}`; } export interface ApiResponse { ok: boolean; status: number; data?: T; error?: string; } export interface ApiClientConfig { baseUrl: string; getToken: () => string | null; } /** * Create an API client instance */ export function createApiClient(config: ApiClientConfig) { const { baseUrl, getToken } = config; async function request( method: string, path: string, body?: unknown ): Promise> { const token = getToken(); const headers: Record = { [HEADERS.CONTENT_TYPE]: CONTENT_TYPES.JSON, }; if (token) { headers[HEADERS.AUTH] = authHeader(token); } try { const response = await fetch(`${baseUrl}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, }); const data = response.headers.get('content-type')?.includes('application/json') ? await response.json() : await response.text(); if (!response.ok) { return { ok: false, status: response.status, error: typeof data === 'string' ? data : data.message || 'Request failed', }; } return { ok: true, status: response.status, data: data as T, }; } catch (err) { return { ok: false, status: 0, error: err instanceof Error ? err.message : 'Network error', }; } } return { get: (path: string) => request('GET', path), post: (path: string, body?: unknown) => request('POST', path, body), put: (path: string, body?: unknown) => request('PUT', path, body), patch: (path: string, body?: unknown) => request('PATCH', path, body), delete: (path: string) => request('DELETE', path), }; } export type ApiClient = ReturnType;