Tahasaif3's picture
'change'
c4da317
// 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<RegisterResponse> {
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<LoginResponse> {
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<void> {
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<User | null> {
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<any> {
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<Task[]> {
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<Task> {
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<Task, 'id' | 'user_id' | 'created_at' | 'updated_at'>): Promise<Task> {
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<Task>): Promise<Task> {
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<Task>): Promise<Task> {
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<void> {
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<Task> {
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<Project[]> {
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<Project> {
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<Project> {
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<Project> {
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<void> {
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<Task[]> {
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<ProjectProgress> {
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();
}