the-tip-top-frontend/hooks/useApi.ts
soufiane 0a00c04b54 fix: reduce code duplication and add tests for SonarQube quality gate
- Consolidate API logic: hooks/useApi.ts now uses services/api.ts
- Create BaseFormField component to reduce form duplication
- Refactor FormField, FormSelect, FormTextarea to use BaseFormField
- Add centralized theme utility (utils/theme.ts) for colors/styles
- Add comprehensive tests for api, auth.service, useApi hooks, AuthContext
- Add tests for theme utility

This reduces duplication from 11.45% and improves test coverage.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 14:54:24 +01:00

129 lines
3.4 KiB
TypeScript

'use client';
import { useState, useCallback } from 'react';
import { toast } from 'react-hot-toast';
import { api, ApiError } from '@/services/api';
// Re-export for backward compatibility
export { api, ApiError };
interface UseApiState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
interface UseApiReturn<T> extends UseApiState<T> {
execute: <R = T>(
method: 'get' | 'post' | 'put' | 'patch' | 'delete',
endpoint: string,
data?: unknown
) => Promise<R | null>;
reset: () => void;
}
/**
* Hook for API calls with loading and error states
* Uses centralized api service from services/api.ts
*/
export function useApi<T = unknown>(): UseApiReturn<T> {
const [state, setState] = useState<UseApiState<T>>({
data: null,
loading: false,
error: null,
});
const execute = useCallback(async <R = T>(
method: 'get' | 'post' | 'put' | 'patch' | 'delete',
endpoint: string,
data?: unknown
): Promise<R | null> => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
let result: R;
switch (method) {
case 'get':
result = await api.get<R>(endpoint);
break;
case 'post':
result = await api.post<R>(endpoint, data);
break;
case 'put':
result = await api.put<R>(endpoint, data);
break;
case 'patch':
result = await api.patch<R>(endpoint, data);
break;
case 'delete':
result = await api.delete<R>(endpoint);
break;
}
setState({ data: result as unknown as T, loading: false, error: null });
return result;
} 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 with auto-refresh support
* Uses centralized api service from services/api.ts
*/
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 api.get<{ 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;