import { useToast } from './useToast' import { humanizeError, extractApiErrorMessage } from '~/shared/utils/errorMessages' export interface ApiResponse { success: boolean data?: T error?: string status?: number } interface ApiCallOptions extends RequestInit { headers?: Record } export function useApi() { const { showError } = useToast() const { public: publicConfig } = useRuntimeConfig() const API_BASE_URL = (publicConfig.apiBaseUrl as string) || 'http://localhost:3000' const parsedApiTimeout = Number(publicConfig.apiTimeout ?? 30000) const API_TIMEOUT = Number.isNaN(parsedApiTimeout) ? 30000 : parsedApiTimeout const apiCall = async (endpoint: string, options: ApiCallOptions = {}): Promise> => { const url = `${API_BASE_URL}${endpoint}` const isFormData = options.body instanceof FormData const defaultOptions: ApiCallOptions = { credentials: 'include', headers: isFormData ? {} : { 'Content-Type': 'application/json' }, } // Ajouter un timeout à la requête const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT) try { const response = await fetch(url, { ...defaultOptions, ...options, headers: { ...defaultOptions.headers, ...options.headers, }, signal: controller.signal, }) clearTimeout(timeoutId) if (response.ok) { let data: T | null = null if (response.status !== 204) { const contentType = response.headers.get('content-type') || '' if (contentType.includes('application/json') || contentType.includes('application/ld+json') || contentType.includes('+json')) { const text = await response.text() data = text ? JSON.parse(text) : null } else { const text = await response.text() data = (text || null) as T | null } } return { success: true, data: data as T } } else { const contentType = response.headers.get('content-type') || '' let errorData: Record = {} if (contentType.includes('json')) { errorData = await response.json().catch(() => ({})) } else { const text = await response.text().catch(() => '') errorData = text ? { message: text } : {} } const rawMessage = response.status === 403 ? 'Permissions insuffisantes pour cette action.' : extractApiErrorMessage(errorData) || `Erreur ${response.status}: ${response.statusText}` const errorMessage = humanizeError(rawMessage) showError(errorMessage) return { success: false, error: errorMessage, status: response.status } } } catch (error) { clearTimeout(timeoutId) const err = error as Error & { name?: string } const errorMessage = err.name === 'AbortError' ? 'La requête a pris trop de temps. Veuillez réessayer.' : 'Impossible de contacter le serveur. Vérifiez votre connexion.' showError(errorMessage) return { success: false, error: errorMessage } } } const get = async (endpoint: string): Promise> => { return apiCall(endpoint, { method: 'GET' }) } const post = async (endpoint: string, data?: unknown): Promise> => { return apiCall(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/ld+json', }, body: data !== undefined ? JSON.stringify(data) : undefined, }) } const patch = async (endpoint: string, data?: unknown): Promise> => { return apiCall(endpoint, { method: 'PATCH', headers: { 'Content-Type': 'application/merge-patch+json', }, body: data !== undefined ? JSON.stringify(data) : undefined, }) } const put = async (endpoint: string, data?: unknown): Promise> => { return apiCall(endpoint, { method: 'PUT', headers: { 'Content-Type': 'application/ld+json', }, body: data !== undefined ? JSON.stringify(data) : undefined, }) } const postFormData = async (endpoint: string, formData: FormData): Promise> => { return apiCall(endpoint, { method: 'POST', body: formData, }) } const del = async (endpoint: string): Promise> => { return apiCall(endpoint, { method: 'DELETE' }) } return { apiCall, get, post, postFormData, patch, put, delete: del, } }