decent
This commit is contained in:
223
web-components/src/hooks/useApiService.ts
Normal file
223
web-components/src/hooks/useApiService.ts
Normal file
@ -0,0 +1,223 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import { ApiResponse, PaginatedResponse, FilterOptions } from '../types';
|
||||
|
||||
export interface ApiServiceConfig {
|
||||
baseURL: string;
|
||||
defaultHeaders?: Record<string, string>;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface UseApiServiceReturn<T> {
|
||||
data: T[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
total: number;
|
||||
hasMore: boolean;
|
||||
client: AxiosInstance;
|
||||
|
||||
// CRUD operations
|
||||
getAll: (filters?: FilterOptions) => Promise<T[]>;
|
||||
getById: (id: string) => Promise<T>;
|
||||
create: (data: Partial<T>) => Promise<T>;
|
||||
update: (id: string, data: Partial<T>) => Promise<T>;
|
||||
delete: (id: string) => Promise<void>;
|
||||
|
||||
// Utility methods
|
||||
clearError: () => void;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useApiService = <T extends { id: string }>(
|
||||
config: ApiServiceConfig,
|
||||
endpoint: string
|
||||
): UseApiServiceReturn<T> => {
|
||||
const [data, setData] = useState<T[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [hasMore, setHasMore] = useState(false);
|
||||
|
||||
// Create axios instance
|
||||
const client = axios.create({
|
||||
baseURL: config.baseURL,
|
||||
timeout: config.timeout || 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...config.defaultHeaders,
|
||||
},
|
||||
});
|
||||
|
||||
// Add request interceptor for common headers
|
||||
client.interceptors.request.use(
|
||||
(config) => {
|
||||
// Add user email header if available (common pattern in the codebase)
|
||||
const userEmail = 'admin@example.com'; // This could come from a context or config
|
||||
if (userEmail) {
|
||||
config.headers['X-User-Email'] = userEmail;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
// Add response interceptor for error handling
|
||||
client.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
const errorMessage = error.response?.data?.message || error.message || 'An error occurred';
|
||||
setError(errorMessage);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
const clearError = useCallback(() => {
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
const getAll = useCallback(async (filters: FilterOptions = {}): Promise<T[]> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
params.append(key, value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const response = await client.get<PaginatedResponse<T> | ApiResponse<T[]>>(`${endpoint}?${params.toString()}`);
|
||||
|
||||
// Handle both paginated and simple array responses
|
||||
if ('data' in response.data && Array.isArray(response.data.data)) {
|
||||
// Paginated response
|
||||
const paginatedData = response.data as PaginatedResponse<T>;
|
||||
setData(paginatedData.data);
|
||||
setTotal(paginatedData.total);
|
||||
setHasMore(paginatedData.has_more || false);
|
||||
return paginatedData.data;
|
||||
} else if ('data' in response.data && Array.isArray(response.data.data)) {
|
||||
// Simple array response wrapped in ApiResponse
|
||||
const apiData = response.data as ApiResponse<T[]>;
|
||||
setData(apiData.data);
|
||||
setTotal(apiData.data.length);
|
||||
setHasMore(false);
|
||||
return apiData.data;
|
||||
} else if (Array.isArray(response.data)) {
|
||||
// Direct array response
|
||||
setData(response.data);
|
||||
setTotal(response.data.length);
|
||||
setHasMore(false);
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error('Invalid response format');
|
||||
}
|
||||
} catch (err: any) {
|
||||
const errorMessage = err.response?.data?.message || err.message || 'Failed to fetch data';
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [client, endpoint]);
|
||||
|
||||
const getById = useCallback(async (id: string): Promise<T> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await client.get<ApiResponse<T> | T>(`${endpoint}/${id}`);
|
||||
const item = 'data' in response.data ? response.data.data : response.data;
|
||||
return item;
|
||||
} catch (err: any) {
|
||||
const errorMessage = err.response?.data?.message || err.message || 'Failed to fetch item';
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [client, endpoint]);
|
||||
|
||||
const create = useCallback(async (itemData: Partial<T>): Promise<T> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await client.post<ApiResponse<T> | T>(endpoint, itemData);
|
||||
const newItem = 'data' in response.data ? response.data.data : response.data;
|
||||
|
||||
// Update local data
|
||||
setData(prev => [...prev, newItem]);
|
||||
setTotal(prev => prev + 1);
|
||||
|
||||
return newItem;
|
||||
} catch (err: any) {
|
||||
const errorMessage = err.response?.data?.message || err.message || 'Failed to create item';
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [client, endpoint]);
|
||||
|
||||
const update = useCallback(async (id: string, itemData: Partial<T>): Promise<T> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await client.put<ApiResponse<T> | T>(`${endpoint}/${id}`, itemData);
|
||||
const updatedItem = 'data' in response.data ? response.data.data : response.data;
|
||||
|
||||
// Update local data
|
||||
setData(prev => prev.map(item => item.id === id ? updatedItem : item));
|
||||
|
||||
return updatedItem;
|
||||
} catch (err: any) {
|
||||
const errorMessage = err.response?.data?.message || err.message || 'Failed to update item';
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [client, endpoint]);
|
||||
|
||||
const deleteItem = useCallback(async (id: string): Promise<void> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await client.delete(`${endpoint}/${id}`);
|
||||
|
||||
// Update local data
|
||||
setData(prev => prev.filter(item => item.id !== id));
|
||||
setTotal(prev => prev - 1);
|
||||
} catch (err: any) {
|
||||
const errorMessage = err.response?.data?.message || err.message || 'Failed to delete item';
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [client, endpoint]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
await getAll();
|
||||
}, [getAll]);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
total,
|
||||
hasMore,
|
||||
client,
|
||||
getAll,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteItem,
|
||||
clearError,
|
||||
refresh,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user