- 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>
135 lines
3.5 KiB
TypeScript
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;
|