Files
SPARC/frontend/src/api/client.ts
T
0xWheatyz cb7d7121c5 feat(frontend): add React dashboard with TypeScript
Add modern React frontend to replace Streamlit dashboard:
- Vite build system with TypeScript
- Tailwind CSS for styling
- Component structure in src/
- Production Dockerfile with nginx
- Development server on port 5173

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-14 13:40:52 -04:00

155 lines
4.6 KiB
TypeScript

import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
import type { TokenResponse, User, CompanyAnalysis, BatchAnalysisResult, JobStatus, Analytics } from '../types';
const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
const api = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Token management
let accessToken: string | null = localStorage.getItem('access_token');
let refreshToken: string | null = localStorage.getItem('refresh_token');
export const setTokens = (tokens: TokenResponse) => {
accessToken = tokens.access_token;
refreshToken = tokens.refresh_token;
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
};
export const clearTokens = () => {
accessToken = null;
refreshToken = null;
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
};
export const getAccessToken = () => accessToken;
// Request interceptor to add auth header
api.interceptors.request.use((config: InternalAxiosRequestConfig) => {
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
});
// Response interceptor to handle token refresh
api.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
if (error.response?.status === 401 && !originalRequest._retry && refreshToken) {
originalRequest._retry = true;
try {
const response = await axios.post<TokenResponse>(`${API_BASE_URL}/auth/refresh`, {
refresh_token: refreshToken,
});
setTokens(response.data);
originalRequest.headers.Authorization = `Bearer ${response.data.access_token}`;
return api(originalRequest);
} catch {
clearTokens();
window.location.href = '/login';
}
}
return Promise.reject(error);
}
);
// Auth API
export const authApi = {
register: async (email: string, password: string): Promise<User> => {
const response = await api.post<User>('/auth/register', { email, password });
return response.data;
},
login: async (email: string, password: string): Promise<TokenResponse> => {
const response = await api.post<TokenResponse>('/auth/login', { email, password });
setTokens(response.data);
return response.data;
},
getMe: async (): Promise<User> => {
const response = await api.get<User>('/auth/me');
return response.data;
},
logout: () => {
clearTokens();
},
};
// Analysis API
export const analysisApi = {
analyzeCompany: async (companyName: string): Promise<CompanyAnalysis> => {
const response = await api.get<CompanyAnalysis>(`/analyze/${encodeURIComponent(companyName)}`);
return response.data;
},
analyzeBatch: async (companies: string[], maxWorkers = 3): Promise<BatchAnalysisResult> => {
const response = await api.post<BatchAnalysisResult>('/analyze/batch', {
companies,
max_workers: maxWorkers,
});
return response.data;
},
analyzeBatchAsync: async (companies: string[], maxWorkers = 3): Promise<JobStatus> => {
const response = await api.post<JobStatus>('/analyze/batch/async', {
companies,
max_workers: maxWorkers,
});
return response.data;
},
getJobStatus: async (jobId: string): Promise<JobStatus> => {
const response = await api.get<JobStatus>(`/jobs/${jobId}`);
return response.data;
},
listJobs: async (status?: string, limit = 10): Promise<JobStatus[]> => {
const params = new URLSearchParams();
if (status) params.append('status', status);
params.append('limit', limit.toString());
const response = await api.get<JobStatus[]>(`/jobs?${params}`);
return response.data;
},
};
// Analytics API
export const analyticsApi = {
getAnalytics: async (days = 30): Promise<Analytics> => {
const response = await api.get<Analytics>(`/analytics?days=${days}`);
return response.data;
},
};
// Admin API
export const adminApi = {
listUsers: async (limit = 100, offset = 0): Promise<User[]> => {
const response = await api.get<User[]>(`/admin/users?limit=${limit}&offset=${offset}`);
return response.data;
},
updateUserRole: async (userId: number, role: 'admin' | 'user'): Promise<User> => {
const response = await api.patch<User>(`/admin/users/${userId}/role`, { role });
return response.data;
},
deleteUser: async (userId: number): Promise<void> => {
await api.delete(`/admin/users/${userId}`);
},
};
export default api;