the-tip-top-frontend/hooks/useApi.ts
soufiane c7c2a3f56c refactor: reduce code duplication from 18.51% to ~3%
- Delete unused page-advanced.tsx and page-backup.tsx (dashboard duplicates)
- Add useApi hook for centralized API calls with auth token
- Add LoadingState, ErrorState, StatusBadge reusable components
- Create shared ProfilePage component for admin/employee profiles
- Refactor admin and employee profile pages to use shared component

This refactoring addresses SonarQube quality gate failure for duplicated lines.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 16:06:40 +01:00

135 lines
3.5 KiB
TypeScript

'use client';
import { useState, useCallback } from 'react';
import { toast } from 'react-hot-toast';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000/api';
/**
* Get authentication token from localStorage
*/
export const getAuthToken = (): string | null => {
if (typeof window === 'undefined') return null;
return localStorage.getItem('auth_token') || localStorage.getItem('token');
};
/**
* Generic fetch wrapper with authentication
*/
export const apiFetch = async <T>(
endpoint: string,
options?: RequestInit
): Promise<T> => {
const token = getAuthToken();
const url = endpoint.startsWith('http') ? endpoint : `${API_BASE_URL}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
...options?.headers,
},
});
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Erreur serveur' }));
throw new Error(error.message || `Erreur ${response.status}`);
}
return response.json();
};
interface UseApiState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
interface UseApiReturn<T> extends UseApiState<T> {
execute: (endpoint: string, options?: RequestInit) => Promise<T | null>;
reset: () => void;
}
/**
* Hook for API calls with loading and error states
*/
export function useApi<T = unknown>(): UseApiReturn<T> {
const [state, setState] = useState<UseApiState<T>>({
data: null,
loading: false,
error: null,
});
const execute = useCallback(async (endpoint: string, options?: RequestInit): Promise<T | null> => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const data = await apiFetch<T>(endpoint, options);
setState({ data, loading: false, error: null });
return data;
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Une erreur est survenue';
setState(prev => ({ ...prev, loading: false, error: message }));
toast.error(message);
return null;
}
}, []);
const reset = useCallback(() => {
setState({ data: null, loading: false, error: null });
}, []);
return { ...state, execute, reset };
}
interface UseFetchDataOptions {
onSuccess?: (data: unknown) => void;
onError?: (error: string) => void;
showErrorToast?: boolean;
}
interface UseFetchDataReturn<T> {
data: T | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
/**
* Hook for fetching data on mount with auto-refresh support
*/
export function useFetchData<T>(
endpoint: string,
options?: UseFetchDataOptions
): UseFetchDataReturn<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const result = await apiFetch<{ data: T } | T>(endpoint);
const responseData = (result as { data: T }).data ?? result as T;
setData(responseData);
options?.onSuccess?.(responseData);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Une erreur est survenue';
setError(message);
options?.onError?.(message);
if (options?.showErrorToast !== false) {
toast.error(message);
}
} finally {
setLoading(false);
}
}, [endpoint, options]);
return { data, loading, error, refetch: fetchData };
}
export default useApi;