function getXsrfToken(): string { const match = document.cookie .split('; ') .find((row) => row.startsWith('XSRF-TOKEN=')); return match ? decodeURIComponent(match.split('=')[1] ?? '') : ''; } export async function apiFetch(url: string, options: RequestInit = {}): Promise { const response = await fetch(url, { ...options, headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-XSRF-TOKEN': getXsrfToken(), 'X-Requested-With': 'XMLHttpRequest', ...options.headers, }, credentials: 'same-origin', }); const contentType = response.headers.get('content-type') ?? ''; const isJson = contentType.includes('application/json'); if (!response.ok) { if (isJson) { const error = (await response.json().catch(() => ({}))) as { message?: string; errors?: Record; }; const firstValidationError = error.errors ? Object.values(error.errors).flat()[0] : undefined; throw new Error(firstValidationError ?? error.message ?? 'Permintaan gagal diproses.'); } if (response.status === 401 || response.status === 419) { throw new Error('Sesi Anda telah berakhir. Silakan muat ulang halaman dan coba lagi.'); } throw new Error(`Permintaan gagal diproses (HTTP ${response.status}).`); } if (!isJson) { throw new Error('Server mengembalikan respons yang tidak valid. Silakan muat ulang halaman.'); } return response.json() as Promise; }