const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8080'; type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; type RequestOptions = { body?: unknown; headers?: HeadersInit; signal?: AbortSignal; skipAuthRedirect?: boolean; }; export type ApiValidationErrors = Record; export class ApiError extends Error { status: number; errors?: ApiValidationErrors; constructor(message: string, status: number, errors?: ApiValidationErrors) { super(message); this.name = 'ApiError'; this.status = status; this.errors = errors; } } const buildUrl = (path: string) => { if (path.startsWith('http://') || path.startsWith('https://')) { return path; } return `${API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`; }; const parseJsonSafely = async (response: Response) => { const contentType = response.headers.get('content-type') ?? ''; if (!contentType.includes('application/json')) { return null; } return response.json(); }; const ensureCsrfCookie = async () => { await fetch(`${API_BASE_URL}/sanctum/csrf-cookie`, { method: 'GET', credentials: 'include', headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest', }, }); }; const getCookie = (name: string) => { if (typeof document === 'undefined') { return null; } const match = document.cookie .split('; ') .find((row) => row.startsWith(`${name}=`)); return match ? decodeURIComponent(match.split('=')[1]) : null; }; const redirectToLogin = () => { if (typeof window === 'undefined') { return; } const pathname = window.location.pathname; const search = window.location.search; const isAuthPage = pathname === '/login' || pathname === '/register'; if (isAuthPage) { return; } const next = encodeURIComponent(`${pathname}${search}`); window.location.href = `/login?next=${next}`; }; const request = async ( method: HttpMethod, path: string, options: RequestOptions = {}, ): Promise => { const headers = new Headers(options.headers); const hasBody = options.body !== undefined; headers.set('Accept', 'application/json'); headers.set('X-Requested-With', 'XMLHttpRequest'); if (hasBody) { headers.set('Content-Type', 'application/json'); } const xsrfToken = getCookie('XSRF-TOKEN'); if (xsrfToken) { headers.set('X-XSRF-TOKEN', xsrfToken); } const response = await fetch(buildUrl(path), { method, headers, body: hasBody ? JSON.stringify(options.body) : undefined, signal: options.signal, credentials: 'include', }); if (!response.ok) { const payload = await parseJsonSafely(response); const message = payload && typeof payload.message === 'string' ? payload.message : 'La solicitud al servidor falló.'; if (response.status === 401 && !options.skipAuthRedirect) { redirectToLogin(); } throw new ApiError(message, response.status, payload?.errors); } if (response.status === 204) { return undefined as T; } return (await parseJsonSafely(response)) as T; }; export const apiClient = { baseUrl: API_BASE_URL, ensureCsrfCookie, get: (path: string, options?: Omit) => request('GET', path, options), post: (path: string, body?: unknown, options?: Omit) => request('POST', path, { ...options, body }), put: (path: string, body?: unknown, options?: Omit) => request('PUT', path, { ...options, body }), patch: (path: string, body?: unknown, options?: Omit) => request('PATCH', path, { ...options, body }), delete: (path: string, options?: Omit) => request('DELETE', path, options), };