import type { Profile, Client, ClientCreate, Event, EventCreate, Email, EmailGenerate, User, Invite, ActivityItem, InsightsData, ImportPreview, ImportResult, NetworkMatch, NetworkStats } from '@/types'; const API_BASE = import.meta.env.PROD ? 'https://api.thenetwork.donovankelly.xyz/api' : '/api'; const AUTH_BASE = import.meta.env.PROD ? 'https://api.thenetwork.donovankelly.xyz' : ''; const TOKEN_KEY = 'network-auth-token'; class ApiClient { private getToken(): string | null { return localStorage.getItem(TOKEN_KEY); } setToken(token: string | null) { if (token) { localStorage.setItem(TOKEN_KEY, token); } else { localStorage.removeItem(TOKEN_KEY); } } private authHeaders(): HeadersInit { const token = this.getToken(); return token ? { Authorization: `Bearer ${token}` } : {}; } private async fetch(path: string, options: RequestInit = {}): Promise { const headers: HeadersInit = { 'Content-Type': 'application/json', ...this.authHeaders(), ...options.headers, }; const response = await fetch(`${API_BASE}${path}`, { ...options, headers, credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ error: 'Unknown error' })); throw new Error(error.error || error.message || 'Request failed'); } const text = await response.text(); if (!text) return {} as T; return JSON.parse(text); } // Auth async login(email: string, password: string) { const response = await fetch(`${AUTH_BASE}/api/auth/sign-in/email`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ message: 'Login failed' })); throw new Error(error.message || 'Login failed'); } // Capture bearer token from response header const authToken = response.headers.get('set-auth-token'); if (authToken) { this.setToken(authToken); } return response.json(); } async logout() { await fetch(`${AUTH_BASE}/api/auth/sign-out`, { method: 'POST', headers: this.authHeaders(), credentials: 'include', }); this.setToken(null); } async getSession(): Promise<{ user: User } | null> { try { const response = await fetch(`${AUTH_BASE}/api/auth/get-session`, { headers: this.authHeaders(), credentials: 'include', }); if (!response.ok) return null; const data = await response.json(); if (!data || !data.user) return null; return data; } catch { return null; } } // Account (email & password changes via profile API) async changePassword(currentPassword: string, newPassword: string): Promise { return this.fetch('/profile/password', { method: 'PUT', body: JSON.stringify({ currentPassword, newPassword }), }); } async changeEmail(newEmail: string): Promise { return this.fetch('/profile/email', { method: 'PUT', body: JSON.stringify({ newEmail }), }); } // Profile async getProfile(): Promise { return this.fetch('/profile'); } async updateProfile(data: Partial): Promise { return this.fetch('/profile', { method: 'PUT', body: JSON.stringify(data), }); } // Clients async getClients(params?: { search?: string; tag?: string }): Promise { const searchParams = new URLSearchParams(); if (params?.search) searchParams.set('search', params.search); if (params?.tag) searchParams.set('tag', params.tag); const query = searchParams.toString(); return this.fetch(`/clients${query ? `?${query}` : ''}`); } async getClient(id: string): Promise { return this.fetch(`/clients/${id}`); } async createClient(data: ClientCreate): Promise { return this.fetch('/clients', { method: 'POST', body: JSON.stringify(data), }); } async updateClient(id: string, data: Partial): Promise { return this.fetch(`/clients/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } async deleteClient(id: string): Promise { await this.fetch(`/clients/${id}`, { method: 'DELETE' }); } async markContacted(id: string): Promise { return this.fetch(`/clients/${id}/contacted`, { method: 'POST' }); } // CSV Import async importPreview(file: File): Promise { const formData = new FormData(); formData.append('file', file); const token = this.getToken(); const headers: HeadersInit = token ? { Authorization: `Bearer ${token}` } : {}; const response = await fetch(`${API_BASE}/clients/import/preview`, { method: 'POST', headers, body: formData, credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ error: 'Preview failed' })); throw new Error(error.error || 'Preview failed'); } return response.json(); } async importClients(file: File, mapping: Record): Promise { const formData = new FormData(); formData.append('file', file); formData.append('mapping', JSON.stringify(mapping)); const token = this.getToken(); const headers: HeadersInit = token ? { Authorization: `Bearer ${token}` } : {}; const response = await fetch(`${API_BASE}/clients/import`, { method: 'POST', headers, body: formData, credentials: 'include', }); if (!response.ok) { const error = await response.json().catch(() => ({ error: 'Import failed' })); throw new Error(error.error || 'Import failed'); } return response.json(); } // Activity Timeline async getClientActivity(clientId: string): Promise { return this.fetch(`/clients/${clientId}/activity`); } // Insights async getInsights(): Promise { return this.fetch('/insights'); } // Events async getEvents(params?: { clientId?: string; type?: string; upcoming?: number }): Promise { const searchParams = new URLSearchParams(); if (params?.clientId) searchParams.set('clientId', params.clientId); if (params?.type) searchParams.set('type', params.type); if (params?.upcoming) searchParams.set('upcoming', String(params.upcoming)); const query = searchParams.toString(); return this.fetch(`/events${query ? `?${query}` : ''}`); } async getEvent(id: string): Promise { return this.fetch(`/events/${id}`); } async createEvent(data: EventCreate): Promise { return this.fetch('/events', { method: 'POST', body: JSON.stringify(data), }); } async updateEvent(id: string, data: Partial): Promise { return this.fetch(`/events/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } async deleteEvent(id: string): Promise { await this.fetch(`/events/${id}`, { method: 'DELETE' }); } async syncAllEvents(): Promise { await this.fetch('/events/sync-all', { method: 'POST' }); } async syncClientEvents(clientId: string): Promise { await this.fetch(`/events/sync/${clientId}`, { method: 'POST' }); } // Emails async getEmails(params?: { status?: string; clientId?: string }): Promise { const searchParams = new URLSearchParams(); if (params?.status) searchParams.set('status', params.status); if (params?.clientId) searchParams.set('clientId', params.clientId); const query = searchParams.toString(); return this.fetch(`/emails${query ? `?${query}` : ''}`); } async getEmail(id: string): Promise { return this.fetch(`/emails/${id}`); } async generateEmail(data: EmailGenerate): Promise { return this.fetch('/emails/generate', { method: 'POST', body: JSON.stringify(data), }); } async generateBirthdayEmail(clientId: string, provider?: string): Promise { return this.fetch('/emails/generate-birthday', { method: 'POST', body: JSON.stringify({ clientId, provider }), }); } async updateEmail(id: string, data: { subject?: string; content?: string }): Promise { return this.fetch(`/emails/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } async sendEmail(id: string): Promise { return this.fetch(`/emails/${id}/send`, { method: 'POST' }); } async deleteEmail(id: string): Promise { await this.fetch(`/emails/${id}`, { method: 'DELETE' }); } // Network Matching async getNetworkMatches(params?: { minScore?: number; limit?: number }): Promise<{ matches: NetworkMatch[]; total: number; clientCount: number }> { const searchParams = new URLSearchParams(); if (params?.minScore) searchParams.set('minScore', String(params.minScore)); if (params?.limit) searchParams.set('limit', String(params.limit)); const query = searchParams.toString(); return this.fetch(`/network/matches${query ? `?${query}` : ''}`); } async getClientMatches(clientId: string, minScore?: number): Promise<{ matches: NetworkMatch[]; client: { id: string; name: string } }> { const query = minScore ? `?minScore=${minScore}` : ''; return this.fetch(`/network/matches/${clientId}${query}`); } async generateIntro(clientAId: string, clientBId: string, reasons: string[], provider?: string): Promise<{ introSuggestion: string }> { return this.fetch('/network/intro', { method: 'POST', body: JSON.stringify({ clientAId, clientBId, reasons, provider }), }); } async getNetworkStats(): Promise { return this.fetch('/network/stats'); } // Admin async getUsers(): Promise { return this.fetch('/admin/users'); } async updateUserRole(userId: string, role: string): Promise { await this.fetch(`/admin/users/${userId}/role`, { method: 'PUT', body: JSON.stringify({ role }), }); } async deleteUser(userId: string): Promise { await this.fetch(`/admin/users/${userId}`, { method: 'DELETE' }); } async createInvite(data: { email: string; name: string; role?: string }): Promise { return this.fetch('/admin/invites', { method: 'POST', body: JSON.stringify(data), }); } async getInvites(): Promise { return this.fetch('/admin/invites'); } async deleteInvite(id: string): Promise { await this.fetch(`/admin/invites/${id}`, { method: 'DELETE' }); } async createPasswordReset(userId: string): Promise<{ resetUrl: string; email: string }> { return this.fetch(`/admin/users/${userId}/reset-password`, { method: 'POST' }); } // Invite acceptance (public - no auth) async validateInvite(token: string): Promise<{ id: string; email: string; name: string; role: string; expiresAt: string }> { const response = await fetch(`${AUTH_BASE}/auth/invite/${token}`); if (!response.ok) { const error = await response.json().catch(() => ({ error: 'Invalid invite' })); throw new Error(error.error || 'Invalid invite'); } return response.json(); } async requestPasswordReset(email: string): Promise<{ success: boolean; message: string; resetUrl?: string }> { const response = await fetch(`${AUTH_BASE}/auth/reset-password/request`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }), }); if (!response.ok) { const error = await response.json().catch(() => ({ error: 'Request failed' })); throw new Error(error.error || 'Request failed'); } return response.json(); } async validateResetToken(token: string): Promise<{ valid: boolean; email?: string }> { const response = await fetch(`${AUTH_BASE}/auth/reset-password/${token}`); if (!response.ok) { const error = await response.json().catch(() => ({ error: 'Invalid token' })); throw new Error(error.error || 'Invalid or expired reset link'); } return response.json(); } async resetPassword(token: string, password: string): Promise<{ success: boolean }> { const response = await fetch(`${AUTH_BASE}/auth/reset-password/${token}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }), }); if (!response.ok) { const error = await response.json().catch(() => ({ error: 'Reset failed' })); throw new Error(error.error || 'Failed to reset password'); } return response.json(); } async acceptInvite(token: string, password: string, name?: string): Promise<{ success: boolean; token?: string }> { const response = await fetch(`${AUTH_BASE}/auth/invite/${token}/accept`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password, name }), }); if (!response.ok) { const error = await response.json().catch(() => ({ error: 'Failed to accept invite' })); throw new Error(error.error || 'Failed to accept invite'); } return response.json(); } // Reports & Analytics async getReportsOverview(): Promise { return this.fetch('/reports/overview'); } async getReportsGrowth(): Promise { return this.fetch('/reports/growth'); } async getReportsIndustries(): Promise { return this.fetch('/reports/industries'); } async getReportsTags(): Promise { return this.fetch('/reports/tags'); } async getReportsEngagement(): Promise { return this.fetch('/reports/engagement'); } async getNotifications(): Promise { return this.fetch('/reports/notifications'); } async exportClientsCSV(): Promise { const token = this.getToken(); const headers: HeadersInit = token ? { Authorization: `Bearer ${token}` } : {}; const response = await fetch(`${API_BASE}/reports/export/clients`, { headers, credentials: 'include', }); if (!response.ok) throw new Error('Export failed'); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `clients-export-${new Date().toISOString().split('T')[0]}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } } export const api = new ApiClient();