const API_BASE_URL = process.env['NEXT_PUBLIC_API_URL'] || 'http://localhost:3001'; export class ApiError extends Error { constructor( public status: number, message: string, ) { super(message); this.name = 'ApiError'; } } type RequestOptions = Omit & { body?: unknown; }; function getCsrfToken(): string | undefined { if (typeof document === 'undefined') return undefined; const match = document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]*)/); return match?.[1] ? decodeURIComponent(match[1]) : undefined; } const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']); async function request(endpoint: string, options: RequestOptions = {}): Promise { const { body, headers, ...rest } = options; const method = options.method?.toUpperCase() ?? 'GET'; const csrfHeaders: HeadersInit = {}; if (!SAFE_METHODS.has(method)) { const csrfToken = getCsrfToken(); if (csrfToken) { csrfHeaders['X-CSRF-Token'] = csrfToken; } } const res = await fetch(`${API_BASE_URL}${endpoint}`, { ...rest, credentials: 'include', headers: { 'Content-Type': 'application/json', ...csrfHeaders, ...headers, }, body: body ? JSON.stringify(body) : undefined, }); if (!res.ok) { const error = await res.json().catch(() => ({ message: res.statusText })); throw new ApiError(res.status, error.message || 'Request failed'); } return res.json(); } export const apiClient = { get: (endpoint: string, headers?: HeadersInit) => request(endpoint, { method: 'GET', headers }), post: (endpoint: string, body?: unknown, headers?: HeadersInit) => request(endpoint, { method: 'POST', body, headers }), patch: (endpoint: string, body?: unknown, headers?: HeadersInit) => request(endpoint, { method: 'PATCH', body, headers }), delete: (endpoint: string, headers?: HeadersInit) => request(endpoint, { method: 'DELETE', headers }), };