Files
leocrm/frontend/src/api/client.ts
T

118 lines
3.5 KiB
TypeScript
Raw Normal View History

import axios, { AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios';
export interface ApiError {
status: number;
message: string;
detail?: string;
validationErrors?: Record<string, string[]>;
}
export const apiClient = axios.create({
baseURL: '/api/v1',
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
});
let onUnauthorized: (() => void) | null = null;
let onValidationError: ((errors: Record<string, string[]>) => void) | null = null;
export function setUnauthorizedHandler(handler: () => void) {
onUnauthorized = handler;
}
export function setValidationErrorHandler(handler: (errors: Record<string, string[]>) => void) {
onValidationError = handler;
}
apiClient.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
return config;
},
(error) => Promise.reject(error)
);
apiClient.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
const status = error.response?.status || 0;
const data = error.response?.data as any;
if (status === 401) {
if (onUnauthorized) {
onUnauthorized();
}
}
if (status === 422 && data?.detail) {
const validationErrors: Record<string, string[]> = {};
if (Array.isArray(data.detail)) {
for (const item of data.detail) {
if (item.loc && item.loc.length > 1) {
const field = item.loc[item.loc.length - 1];
if (!validationErrors[field]) {
validationErrors[field] = [];
}
validationErrors[field].push(item.msg || 'Invalid value');
}
}
}
if (onValidationError) {
onValidationError(validationErrors);
}
}
const apiError: ApiError = {
status,
message: data?.detail?.message || data?.message || error.message || 'An error occurred',
detail: typeof data?.detail === 'string' ? data.detail : undefined,
validationErrors: status === 422 ? extractValidationErrors(data) : undefined,
};
return Promise.reject(apiError);
}
);
function extractValidationErrors(data: any): Record<string, string[]> | undefined {
if (!data?.detail || !Array.isArray(data.detail)) return undefined;
const errors: Record<string, string[]> = {};
for (const item of data.detail) {
if (item.loc && item.loc.length > 1) {
const field = item.loc[item.loc.length - 1];
if (!errors[field]) {
errors[field] = [];
}
errors[field].push(item.msg || 'Invalid value');
}
}
return Object.keys(errors).length > 0 ? errors : undefined;
}
export async function apiGet<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response = await apiClient.get<T>(url, config);
return response.data;
}
export async function apiPost<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
const response = await apiClient.post<T>(url, data, config);
return response.data;
}
export async function apiPut<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
const response = await apiClient.put<T>(url, data, config);
return response.data;
}
export async function apiPatch<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
const response = await apiClient.patch<T>(url, data, config);
return response.data;
}
export async function apiDelete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response = await apiClient.delete<T>(url, config);
return response.data;
}
export default apiClient;