const API_BASE = '/api'; class ApiClient { private token: string | null = null; setToken(token: string | null) { this.token = token; } async request( method: string, path: string, body?: any ): Promise { const headers: Record = { 'Content-Type': 'application/json', }; if (this.token) { headers['Authorization'] = `Bearer ${this.token}`; } const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30000); try { const response = await fetch(`${API_BASE}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); const data = await response.json(); if (!response.ok) { throw new ApiError(data.error || 'Request failed', response.status, data.details); } return data; } catch (err: any) { if (err instanceof ApiError) throw err; if (err.name === 'AbortError') throw new ApiError('Request timed out', 0); throw new ApiError(err.message || 'Network error', 0); } finally { clearTimeout(timeout); } } // Auth async register(email: string, username: string, password: string) { return this.request<{ token: string; user: any }>('POST', '/auth/register', { email, username, password }); } async login(email: string, password: string) { return this.request<{ token: string; user: any }>('POST', '/auth/login', { email, password }); } async logout() { return this.request<{ message: string }>('POST', '/auth/logout'); } async forgotPassword(email: string) { return this.request<{ message: string }>('POST', '/auth/forgot-password', { email }); } async resetPassword(token: string, password: string) { return this.request<{ message: string }>('POST', '/auth/reset-password', { token, password }); } async verifyEmail(token: string) { return this.request<{ message: string }>('POST', '/auth/verify-email', { token }); } async getMe() { return this.request<{ user: any }>('GET', '/auth/me'); } // Settings async changePassword(currentPassword: string | undefined, newPassword: string, verificationCode?: string) { return this.request<{ message: string; step?: string }>('POST', '/settings/change-password', { currentPassword, newPassword, verificationCode, }); } async changeUsername(username: string) { return this.request<{ username: string }>('PUT', '/settings/username', { username }); } async uploadAvatar(file: File) { const formData = new FormData(); formData.append('avatar', file); const headers: Record = {}; if (this.token) headers['Authorization'] = `Bearer ${this.token}`; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30000); try { const response = await fetch(`${API_BASE}/settings/upload-avatar`, { method: 'POST', headers, body: formData, signal: controller.signal, }); const data = await response.json(); if (!response.ok) throw new ApiError(data.error || 'Upload failed', response.status); return data as { avatarPath: string }; } finally { clearTimeout(timeout); } } async getSessions() { return this.request<{ sessions: any[] }>('GET', '/settings/sessions'); } async getSession(sessionId: string) { return this.request('GET', `/settings/sessions/${sessionId}`); } async deleteSession(sessionId: string) { return this.request<{ message: string }>('DELETE', `/settings/sessions/${sessionId}`); } // Projects moved to WebSocket - see wsStore and projectStore } export class ApiError extends Error { constructor( message: string, public status: number, public details?: any ) { super(message); this.name = 'ApiError'; } } export const api = new ApiClient();