feat: add API client with auth middleware and React Query hooks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
46
ui/src/api/client.ts
Normal file
46
ui/src/api/client.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useAuthStore } from '../auth/auth-store';
|
||||
|
||||
const API_BASE = '/api';
|
||||
|
||||
async function apiFetch<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const token = useAuthStore.getState().accessToken;
|
||||
const headers: Record<string, string> = {
|
||||
...(options.headers as Record<string, string> || {}),
|
||||
};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
if (!headers['Content-Type'] && !(options.body instanceof FormData)) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}${path}`, { ...options, headers });
|
||||
|
||||
if (response.status === 401) {
|
||||
useAuthStore.getState().logout();
|
||||
window.location.href = '/login';
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`API error ${response.status}: ${text}`);
|
||||
}
|
||||
|
||||
if (response.status === 204) return undefined as T;
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(path: string) => apiFetch<T>(path),
|
||||
post: <T>(path: string, body?: unknown) =>
|
||||
apiFetch<T>(path, {
|
||||
method: 'POST',
|
||||
body: body instanceof FormData ? body : JSON.stringify(body),
|
||||
}),
|
||||
patch: <T>(path: string, body: unknown) =>
|
||||
apiFetch<T>(path, { method: 'PATCH', body: JSON.stringify(body) }),
|
||||
put: <T>(path: string, body: FormData) =>
|
||||
apiFetch<T>(path, { method: 'PUT', body }),
|
||||
delete: <T>(path: string) => apiFetch<T>(path, { method: 'DELETE' }),
|
||||
};
|
||||
Reference in New Issue
Block a user