'use client'; import { useCallback, useState, useEffect } from 'react'; import useSWR, { SWRConfiguration } from 'swr'; export interface ApiResponse { success: boolean; data?: T; error?: string; status: number; } export interface UseApiOptions extends SWRConfiguration { method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; body?: any; headers?: HeadersInit; onSuccess?: (data: any) => void; onError?: (error: any) => void; skip?: boolean; } const apiUrl = process.env.NEXT_PUBLIC_API_URL || ''; /** * Standardized API fetcher with error handling */ export const apiFetcher = async ( url: string, options?: { method?: string; body?: any; headers?: HeadersInit; } ): Promise => { const method = options?.method || 'GET'; const headers = { 'Content-Type': 'application/json', ...options?.headers, }; const response = await fetch(`${apiUrl}${url}`, { method, headers, body: options?.body ? JSON.stringify(options.body) : undefined, }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw new Error(error.error || `HTTP ${response.status}`); } return response.json(); }; /** * Hook for fetching data with built-in caching (GET requests) * @param url - API endpoint * @param options - SWR options * @returns { data, error, isLoading, mutate } */ export function useApiGet( url: string | null, options?: SWRConfiguration & { skip?: boolean } ) { const { skip = false, ...swrOptions } = options || {}; const { data, error, isLoading, mutate } = useSWR( skip || !url ? null : url, (url) => apiFetcher(url), { revalidateOnFocus: false, revalidateOnReconnect: false, dedupingInterval: 60000, ...swrOptions, } ); return { data, error, isLoading: isLoading && !error, mutate, }; } /** * Hook for mutations (POST, PUT, DELETE, PATCH) * @param url - API endpoint * @param options - Custom options including onSuccess, onError callbacks * @returns { mutate, isLoading, error, data } */ export function useApiMutation( url: string, options?: UseApiOptions ) { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [data, setData] = useState(null); const mutate = useCallback( async (payload?: any) => { setIsLoading(true); setError(null); try { const response = await fetch(`${apiUrl}${url}`, { method: options?.method || 'POST', headers: { 'Content-Type': 'application/json', ...options?.headers, }, body: JSON.stringify(payload || options?.body), }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || `HTTP ${response.status}`); } setData(result.data || result); options?.onSuccess?.(result.data || result); return result.data || result; } catch (err) { const message = err instanceof Error ? err.message : 'Unknown error'; setError(message); options?.onError?.(err); throw err; } finally { setIsLoading(false); } }, [url, options] ); return { mutate, isLoading, error, data }; } /** * Hook for complex queries with multiple dependencies * Useful for parallel requests */ export function useApiQueries( queries: Array<{ url: string | null; key?: string }>, options?: SWRConfiguration ) { const activeQueries = queries.filter((query) => query.url); const cacheKey = activeQueries.map((query) => query.url).join("|") || null; const { data: values, error, isLoading } = useSWR( cacheKey, () => Promise.all(activeQueries.map((query) => apiFetcher(query.url!))), options ); const data = Object.fromEntries( activeQueries.map((query, index) => [query.key || query.url, values?.[index]]) ) as Record; return { data, error, isLoading }; } /** * Imperative API call (for one-off requests) */ export const apiCall = async ( url: string, options?: { method?: string; body?: any; headers?: HeadersInit; } ): Promise => { return apiFetcher(`${apiUrl}${url}`, options); }; /** * Convenience methods */ export const apiGet = async (url: string): Promise => { return apiCall(url, { method: 'GET' }); }; export const apiPost = async (url: string, body?: any): Promise => { return apiCall(url, { method: 'POST', body }); }; export const apiPut = async (url: string, body?: any): Promise => { return apiCall(url, { method: 'PUT', body }); }; export const apiDelete = async (url: string, body?: any): Promise => { return apiCall(url, { method: 'DELETE', body }); }; export const apiPatch = async (url: string, body?: any): Promise => { return apiCall(url, { method: 'PATCH', body }); };