// API client configuration // Use the backend API URL from environment variable // In development, fallback to localhost const API_URL = 'https://tahasaif3-ai-taskflow-backend.hf.space'; // Import types import { Task, TaskListResponse, Project, ProjectCreate, ProjectUpdate, ProjectProgress, User } from './types'; // API client functions that work with Better Auth and httpOnly cookies // The backend handles JWT in httpOnly cookies, so we don't need to manually manage tokens interface RegisterCredentials { email: string; password: string; } interface LoginCredentials { email: string; password: string; } interface RegisterResponse { id: string; email: string; name?: string; created_at?: string; message?: string; } interface LoginResponse { access_token: string; token_type: string; user: User; } export async function register(credentials: RegisterCredentials): Promise { const response = await fetch(`${API_URL}/api/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(credentials), // Include credentials (cookies) in the request credentials: 'include', }); if (!response.ok) { // Check if response is JSON before parsing const contentType = response.headers.get('content-type'); let errorMessage = 'Registration failed'; if (contentType && contentType.includes('application/json')) { const errorData = await response.json(); errorMessage = errorData.detail || errorMessage; } else { // If not JSON, get the text response const errorText = await response.text(); console.error('Non-JSON response:', errorText); errorMessage = `Registration failed with status ${response.status}`; } throw new Error(errorMessage); } const result = await response.json(); // Store the token in localStorage for cross-origin compatibility if returned if (result.access_token) { localStorage.setItem('auth_token', result.access_token); } return result; } export async function login(credentials: LoginCredentials): Promise { const response = await fetch(`${API_URL}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(credentials), // Include credentials (cookies) in the request credentials: 'include', }); if (!response.ok) { // Check if response is JSON before parsing const contentType = response.headers.get('content-type'); let errorMessage = 'Login failed'; if (contentType && contentType.includes('application/json')) { const errorData = await response.json(); errorMessage = errorData.detail || errorMessage; } else { // If not JSON, get the text response const errorText = await response.text(); console.error('Non-JSON response:', errorText); errorMessage = `Login failed with status ${response.status}`; } throw new Error(errorMessage); } const result = await response.json(); // Store the token in localStorage for cross-origin compatibility if (result.access_token) { localStorage.setItem('auth_token', result.access_token); } return result; } // Function to logout user export async function logout(): Promise { try { // Make a request to the backend to clear the cookie await fetch(`${API_URL}/api/auth/logout`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', }); } catch (error) { console.error('Error during logout:', error); } // Clear any client-side storage // Note: The httpOnly cookie can't be cleared from JavaScript, but the backend should handle this localStorage.removeItem('user'); localStorage.removeItem('rememberMe'); localStorage.removeItem('auth_token'); } // Function to request password reset export async function forgotPassword(email: string): Promise<{ message: string }> { const response = await fetch(`${API_URL}/api/auth/forgot-password`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email }), }); if (!response.ok) { // Check if response is JSON before parsing const contentType = response.headers.get('content-type'); let errorMessage = 'Failed to send reset link'; if (contentType && contentType.includes('application/json')) { const errorData = await response.json(); errorMessage = errorData.detail || errorMessage; } else { // If not JSON, get the text response const errorText = await response.text(); console.error('Non-JSON response:', errorText); errorMessage = `Failed to send reset link with status ${response.status}`; } throw new Error(errorMessage); } return response.json(); } // Function to reset password export async function resetPassword(email: string, newPassword: string): Promise<{ message: string }> { const response = await fetch(`${API_URL}/api/auth/reset-password`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email, new_password: newPassword }), }); if (!response.ok) { // Check if response is JSON before parsing const contentType = response.headers.get('content-type'); let errorMessage = 'Failed to reset password'; if (contentType && contentType.includes('application/json')) { const errorData = await response.json(); errorMessage = errorData.detail || errorMessage; } else { // If not JSON, get the text response const errorText = await response.text(); console.error('Non-JSON response:', errorText); errorMessage = `Failed to reset password with status ${response.status}`; } throw new Error(errorMessage); } return response.json(); } // Function to check if user is authenticated export async function getCurrentUser(): Promise { try { console.log('Making request to /api/auth/me'); // Try to get stored user data first as a fallback const storedUser = localStorage.getItem('user'); // Get the stored JWT token const storedToken = localStorage.getItem('auth_token'); const response = await fetch(`${API_URL}/api/auth/me`, { method: 'GET', headers: { 'Content-Type': 'application/json', ...(storedToken ? { 'Authorization': `Bearer ${storedToken}` } : {}), }, }); console.log('Me endpoint response status:', response.status); console.log('Me endpoint response headers:', [...response.headers.entries()]); if (!response.ok) { console.log('Me endpoint response not ok:', response.status); console.log('Response text:', await response.text()); // If we have stored user data, use it as fallback if (storedUser) { console.log('Using stored user data as fallback'); return JSON.parse(storedUser); } return null; } const userData = await response.json(); console.log('Me endpoint user data:', userData); // Store user data for future use if (userData) { localStorage.setItem('user', JSON.stringify(userData)); } return userData; } catch (error) { console.error('Error checking authentication status:', error); // Try to return stored user data as a last resort const storedUser = localStorage.getItem('user'); if (storedUser) { console.log('Returning stored user data as fallback'); return JSON.parse(storedUser); } return null; } } // Function to get task statistics for a user export async function getUserTaskStats(userId: string): Promise { try { const response = await makeAuthenticatedRequest(`/api/${userId}/tasks/stats`); if (!response.ok) { const contentType = response.headers.get('content-type'); let errorMessage = 'Failed to fetch task stats'; if (contentType && contentType.includes('application/json')) { const errorData = await response.json(); errorMessage = errorData.detail || errorMessage; } else { const errorText = await response.text(); errorMessage = `Failed to fetch stats with status ${response.status}`; } throw new Error(errorMessage); } return await response.json(); } catch (error) { console.error('Error fetching task stats:', error); throw error; } } // Global error handling for authenticated requests using JWT token export async function makeAuthenticatedRequest(endpoint: string, options: RequestInit = {}) { // Get the stored JWT token const storedToken = localStorage.getItem('auth_token'); const response = await fetch(`${API_URL}${endpoint}`, { ...options, headers: { 'Content-Type': 'application/json', ...(storedToken ? { 'Authorization': `Bearer ${storedToken}` } : {}), ...options.headers, }, }); // Handle different status codes if (response.status === 401) { // Invalid session, redirect to login throw new Error('Authentication required'); } if (response.status === 404) { throw new Error('Resource not found'); } if (response.status >= 500) { throw new Error('Server error, please try again later'); } return response; } // Task-related API functions export async function getTasks(userId: string): Promise { console.log(`Fetching tasks for user ID: ${userId}`); const response = await makeAuthenticatedRequest(`/api/${userId}/tasks/`); if (!response.ok) { // Check if response is JSON before parsing const contentType = response.headers.get('content-type'); let errorMessage = 'Failed to fetch tasks'; if (contentType && contentType.includes('application/json')) { const errorData = await response.json(); errorMessage = errorData.detail || errorMessage; } else { // If not JSON, get the text response const errorText = await response.text(); console.error('Non-JSON response:', errorText); errorMessage = `Failed to fetch tasks with status ${response.status}`; } console.error('Error fetching tasks:', errorMessage); throw new Error(errorMessage); } const data: TaskListResponse = await response.json(); console.log('Tasks fetched successfully:', data); return data.tasks; } export async function getTask(userId: string, taskId: number): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/tasks/${taskId}`); if (!response.ok) { // Check if response is JSON before parsing const contentType = response.headers.get('content-type'); let errorMessage = 'Failed to fetch task'; if (contentType && contentType.includes('application/json')) { const errorData = await response.json(); errorMessage = errorData.detail || errorMessage; } else { // If not JSON, get the text response const errorText = await response.text(); console.error('Non-JSON response:', errorText); errorMessage = `Failed to fetch task with status ${response.status}`; } throw new Error(errorMessage); } return response.json(); } export async function createTask(userId: string, taskData: Omit): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/tasks`, { method: 'POST', body: JSON.stringify({ title: taskData.title, description: taskData.description, completed: taskData.completed || false, project_id: taskData.project_id, due_date: taskData.due_date }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to create task'); } return response.json(); } export async function updateTask(userId: string, taskId: number, taskData: Partial): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/tasks/${taskId}`, { method: 'PUT', body: JSON.stringify(taskData), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to update task'); } return response.json(); } // Specific function for partial updates (PATCH) export async function patchTask(userId: string, taskId: number, taskData: Partial): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/tasks/${taskId}`, { method: 'PATCH', body: JSON.stringify(taskData), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to partially update task'); } return response.json(); } export async function deleteTask(userId: string, taskId: number): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/tasks/${taskId}`, { method: 'DELETE', }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to delete task'); } } export async function toggleTaskCompletion(userId: string, taskId: number): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/tasks/${taskId}/toggle`, { method: 'PATCH', }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to toggle task completion'); } return response.json(); } // Project-related API functions export async function getProjects(userId: string): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/projects/`); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to fetch projects'); } return response.json(); } export async function getProject(userId: string, projectId: string): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/projects/${projectId}`); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to fetch project'); } return response.json(); } export async function createProject(userId: string, projectData: ProjectCreate): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/projects`, { method: 'POST', body: JSON.stringify(projectData), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to create project'); } return response.json(); } export async function updateProject(userId: string, projectId: string, projectData: ProjectUpdate): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/projects/${projectId}`, { method: 'PUT', body: JSON.stringify(projectData), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to update project'); } return response.json(); } export async function deleteProject(userId: string, projectId: string): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/projects/${projectId}`, { method: 'DELETE', }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to delete project'); } } export async function getProjectTasks(userId: string, projectId: string): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/projects/${projectId}/tasks`); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to fetch project tasks'); } // Backend returns List[Task] directly, not TaskListResponse const data: Task[] = await response.json(); return Array.isArray(data) ? data : []; } export async function getProjectProgress(userId: string, projectId: string): Promise { const response = await makeAuthenticatedRequest(`/api/${userId}/projects/${projectId}/progress`); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.detail || 'Failed to fetch project progress'); } return response.json(); }