import axios, { AxiosResponse, AxiosError } from 'axios'; import { toast } from 'react-hot-toast'; import { Todo, TodoCreate, TodoUpdate, TodoFilter, TodosResponse, User, UserPreferences, LdapUser, LoginRequest, LoginResponse, FireEmailRequest, FireEmailQuota, ImportJob, } from '@/types'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000'; // Create axios instance const api = axios.create({ baseURL: API_BASE_URL, timeout: 30000, }); // Request interceptor to add auth token api.interceptors.request.use( (config) => { const token = localStorage.getItem('access_token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); // Response interceptor for error handling api.interceptors.response.use( (response) => response, async (error: AxiosError) => { const originalRequest = error.config as any; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { const refreshToken = localStorage.getItem('refresh_token'); if (refreshToken) { const response = await api.post('/api/auth/refresh', {}, { headers: { Authorization: `Bearer ${refreshToken}` }, }); const { access_token } = response.data; localStorage.setItem('access_token', access_token); // Retry original request (mark it to skip toast on failure) originalRequest._isRetry = true; return api(originalRequest); } } catch (refreshError) { // Refresh failed, redirect to login localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); localStorage.removeItem('user'); window.location.href = '/login'; return Promise.reject(refreshError); } } // Show error toast (skip for retry requests to avoid duplicates) if (!originalRequest._isRetry) { const errorData = (error as any).response?.data; const status = (error as any).response?.status; let errorMessage = 'An error occurred'; if (errorData?.message) { errorMessage = errorData.message; } else if (errorData?.error) { errorMessage = errorData.error; } else if ((error as any).message) { errorMessage = (error as any).message; } // Special handling for database connection errors if (status === 503) { toast.error(errorMessage, { duration: 5000, style: { backgroundColor: '#fef3c7', color: '#92400e', }, }); } else if (status === 504) { toast.error(errorMessage, { duration: 4000, style: { backgroundColor: '#fee2e2', color: '#991b1b', }, }); } else if (status !== 401) { toast.error(errorMessage); } } return Promise.reject(error); } ); // Auth API export const authApi = { login: async (credentials: LoginRequest): Promise => { const response = await api.post('/api/auth/login', credentials); return response.data; }, logout: async (): Promise => { await api.post('/api/auth/logout'); localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); localStorage.removeItem('user'); }, getCurrentUser: async (): Promise => { const response = await api.get('/api/auth/me'); return response.data; }, validateToken: async (): Promise => { try { await api.get('/api/auth/validate'); return true; } catch { return false; } }, }; // Todos API export const todosApi = { getTodos: async (filter: TodoFilter & { page?: number; per_page?: number }): Promise => { const params = new URLSearchParams(); Object.entries(filter).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { params.append(key, value.toString()); } }); const response = await api.get(`/api/todos?${params.toString()}`); return response.data; }, getTodo: async (id: string): Promise => { const response = await api.get(`/api/todos/${id}`); return response.data; }, createTodo: async (todo: TodoCreate): Promise => { const response = await api.post('/api/todos', todo); return response.data; }, updateTodo: async (id: string, updates: Partial): Promise => { const response = await api.patch(`/api/todos/${id}`, updates); return response.data; }, deleteTodo: async (id: string): Promise => { await api.delete(`/api/todos/${id}`); }, batchUpdateTodos: async (todoIds: string[], updates: Partial): Promise<{ updated: number; errors: any[] }> => { const response = await api.patch('/api/todos/batch', { todo_ids: todoIds, updates, }); return response.data; }, fireEmail: async (request: FireEmailRequest): Promise => { await api.post('/api/notifications/fire-email', { todo_id: request.todo_id, message: request.note, }); }, }; // Users API export const usersApi = { searchUsers: async (query: string): Promise => { const response = await api.get(`/api/users/search?q=${encodeURIComponent(query)}`); return response.data.users; }, getPreferences: async (): Promise => { const response = await api.get('/api/users/preferences'); return response.data; }, updatePreferences: async (preferences: Partial): Promise => { const response = await api.patch('/api/users/preferences', preferences); return response.data; }, getFireEmailQuota: async (): Promise => { const response = await api.get('/api/users/fire-email-quota'); return response.data; }, }; // Import API export const importApi = { downloadTemplate: async (): Promise => { const response = await api.get('/api/imports/template', { responseType: 'blob', }); return response.data; }, uploadFile: async (file: File): Promise => { const formData = new FormData(); formData.append('file', file); const response = await api.post('/api/imports', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); return response.data; }, getImportJob: async (jobId: string): Promise => { const response = await api.get(`/api/imports/${jobId}`); return response.data; }, downloadErrors: async (jobId: string): Promise => { const response = await api.get(`/api/imports/${jobId}/errors`, { responseType: 'blob', }); return response.data; }, }; // Admin API (if needed) export const adminApi = { getStats: async (days: number = 30): Promise => { const response = await api.get(`/api/admin/stats?days=${days}`); return response.data; }, getAuditLogs: async (params: any): Promise => { const queryParams = new URLSearchParams(params).toString(); const response = await api.get(`/api/admin/audit-logs?${queryParams}`); return response.data; }, getMailLogs: async (params: any): Promise => { const queryParams = new URLSearchParams(params).toString(); const response = await api.get(`/api/admin/mail-logs?${queryParams}`); return response.data; }, }; // Notifications API export const notificationsApi = { getSettings: async (): Promise => { const response = await api.get('/api/notifications/settings'); return response.data; }, updateSettings: async (settings: any): Promise => { const response = await api.patch('/api/notifications/settings', settings); return response.data; }, sendTestEmail: async (recipientEmail?: string): Promise => { await api.post('/api/notifications/test', recipientEmail ? { recipient_email: recipientEmail } : {}); }, sendDigest: async (type: 'weekly' | 'monthly' = 'weekly'): Promise => { await api.post('/api/notifications/digest', { type }); }, markNotificationRead: async (notificationId: string): Promise => { await api.post('/api/notifications/mark-read', { notification_id: notificationId }); }, markAllNotificationsRead: async (): Promise => { await api.post('/api/notifications/mark-all-read'); }, getNotifications: async (): Promise => { const response = await api.get('/api/notifications/'); return response.data; }, }; // Health API export const healthApi = { check: async (): Promise => { const response = await api.get('/api/health/healthz'); return response.data; }, readiness: async (): Promise => { const response = await api.get('/api/health/readiness'); return response.data; }, }; export default api;