const API_BASE = import.meta.env.VITE_API_URL || '/api' interface ApiResponse { success?: boolean data?: T error?: { code: string message: string fields?: Record limit?: number used?: number } } async function fetchApi( endpoint: string, options?: RequestInit ): Promise> { try { const response = await fetch(`${API_BASE}${endpoint}`, { ...options, headers: { 'Content-Type': 'application/json', ...options?.headers, }, }) const data = await response.json() if (!response.ok || data.success === false) { return { error: data.error || { code: 'UNKNOWN', message: `HTTP ${response.status}` } } } return { success: true, data: data.data || data } } catch (error) { return { error: { code: 'NETWORK_ERROR', message: error instanceof Error ? error.message : 'Network error' } } } } export const api = { // ═══════════════════════════════════════════════════════════════════════════ // EMAIL ACCOUNTS // ═══════════════════════════════════════════════════════════════════════════ async getEmailAccounts(userId: string) { return fetchApi>(`/email/accounts?userId=${userId}`) }, async connectEmailAccount(userId: string, provider: 'gmail' | 'outlook', email: string, accessToken: string, refreshToken?: string) { return fetchApi<{ accountId: string }>('/email/connect', { method: 'POST', body: JSON.stringify({ userId, provider, email, accessToken, refreshToken }), }) }, async connectImapAccount( userId: string, params: { email: string; password: string; imapHost?: string; imapPort?: number; imapSecure?: boolean } ) { return fetchApi<{ accountId: string }>('/email/connect', { method: 'POST', body: JSON.stringify({ userId, provider: 'imap', email: params.email, accessToken: params.password, imapHost: params.imapHost, imapPort: params.imapPort, imapSecure: params.imapSecure, }), }) }, async disconnectEmailAccount(accountId: string, userId: string) { return fetchApi<{ success: boolean }>(`/email/accounts/${accountId}?userId=${userId}`, { method: 'DELETE', }) }, // ═══════════════════════════════════════════════════════════════════════════ // EMAIL STATS & SORTING // ═══════════════════════════════════════════════════════════════════════════ async getEmailStats(userId: string) { return fetchApi<{ totalSorted: number todaySorted: number weekSorted: number categories: Record timeSaved: number }>(`/email/stats?userId=${userId}`) }, async sortEmails(userId: string, accountId: string, maxEmails?: number, processAll?: boolean) { return fetchApi<{ sorted: number inboxCleared: number categories: Record timeSaved: { minutes: number; formatted: string } highlights: Array<{ type: string; count: number; message: string }> suggestions: Array<{ type: string; message: string }> provider?: string isDemo?: boolean isFirstRun?: boolean suggestedRules?: Array<{ type: string name: string description: string confidence: number action?: { name?: string } }> }>('/email/sort', { method: 'POST', body: JSON.stringify({ userId, accountId, maxEmails, processAll }), }) }, // Demo sorting without account (for quick tests) async sortDemo(count: number = 10) { return fetchApi<{ sorted: number emails: Array<{ from: string subject: string snippet: string category: string categoryName: string confidence?: number reason?: string }> categories: Record aiEnabled: boolean }>('/email/sort-demo', { method: 'POST', body: JSON.stringify({ count }), }) }, // Connect demo account async connectDemoAccount(userId: string) { return fetchApi<{ accountId: string email: string provider: string message?: string }>('/email/connect-demo', { method: 'POST', body: JSON.stringify({ userId }), }) }, // Get categories async getCategories() { return fetchApi>('/email/categories') }, // Get today's digest async getDigest(userId: string) { return fetchApi<{ date: string totalSorted: number inboxCleared: number timeSavedMinutes: number stats: Record highlights: Array<{ type: string; count: number; message: string }> suggestions: Array<{ type: string; message: string }> hasData: boolean }>(`/email/digest?userId=${userId}`) }, // Get digest history async getDigestHistory(userId: string, days: number = 7) { return fetchApi<{ days: number digests: Array<{ date: string totalSorted: number inboxCleared: number timeSavedMinutes: number stats: Record }> totals: { totalSorted: number inboxCleared: number timeSavedMinutes: number } }>(`/email/digest/history?userId=${userId}&days=${days}`) }, // ═══════════════════════════════════════════════════════════════════════════ // OAUTH // ═══════════════════════════════════════════════════════════════════════════ async getOAuthUrl(provider: 'gmail' | 'outlook', userId: string) { return fetchApi<{ url: string }>(`/oauth/${provider}/connect?userId=${userId}`) }, async getOAuthStatus() { return fetchApi<{ gmail: { enabled: boolean; scopes: string[] } outlook: { enabled: boolean; scopes: string[] } }>('/oauth/status') }, // ═══════════════════════════════════════════════════════════════════════════ // SUBSCRIPTION // ═══════════════════════════════════════════════════════════════════════════ async getSubscriptionStatus(userId: string) { return fetchApi<{ status: string plan: string isFreeTier: boolean emailsUsedThisMonth?: number emailsLimit?: number features: { emailAccounts: number emailsPerDay: number historicalSync: boolean customRules: boolean prioritySupport: boolean } currentPeriodEnd?: string cancelAtPeriodEnd?: boolean }>(`/subscription/status?userId=${userId}`) }, async createSubscriptionCheckout(plan: string, userId: string, email?: string) { return fetchApi<{ url: string; sessionId: string }>('/subscription/checkout', { method: 'POST', body: JSON.stringify({ userId, plan, email }), }) }, async createPortalSession(userId: string) { return fetchApi<{ url: string }>('/subscription/portal', { method: 'POST', body: JSON.stringify({ userId }), }) }, async cancelSubscription(userId: string) { return fetchApi<{ success: boolean }>('/subscription/cancel', { method: 'POST', body: JSON.stringify({ userId }), }) }, async reactivateSubscription(userId: string) { return fetchApi<{ success: boolean }>('/subscription/reactivate', { method: 'POST', body: JSON.stringify({ userId }), }) }, // ═══════════════════════════════════════════════════════════════════════════ // USER PREFERENCES // ═══════════════════════════════════════════════════════════════════════════ async getUserPreferences(userId: string) { return fetchApi<{ vipSenders: Array<{ email: string; name?: string }> blockedSenders: string[] customRules: Array<{ condition: string; category: string }> priorityTopics: string[] }>(`/preferences?userId=${userId}`) }, async saveUserPreferences(userId: string, preferences: { vipSenders?: Array<{ email: string; name?: string }> blockedSenders?: string[] customRules?: Array<{ condition: string; category: string }> priorityTopics?: string[] companyLabels?: Array<{ name: string; condition?: string; category: string; enabled: boolean }> }) { return fetchApi<{ success: boolean }>('/preferences', { method: 'POST', body: JSON.stringify({ userId, ...preferences }), }) }, // ═══════════════════════════════════════════════════════════════════════════ // AI CONTROL // ═══════════════════════════════════════════════════════════════════════════ async getAIControlSettings(userId: string) { return fetchApi<{ enabledCategories: string[] categoryActions: Record autoDetectCompanies: boolean cleanup?: unknown categoryAdvanced?: Record version?: number }>(`/preferences/ai-control?userId=${userId}`) }, async saveAIControlSettings(userId: string, settings: { enabledCategories?: string[] categoryActions?: Record autoDetectCompanies?: boolean cleanup?: unknown categoryAdvanced?: Record version?: number }) { return fetchApi<{ success: boolean }>('/preferences/ai-control', { method: 'POST', body: JSON.stringify({ userId, ...settings }), }) }, // Cleanup Preview - shows what would be cleaned up without actually doing it // TODO: Backend endpoint needs to be implemented // GET /api/preferences/ai-control/cleanup/preview?userId=xxx // Response: { preview: Array<{id, subject, from, date, reason}> } async getCleanupPreview(userId: string) { // TODO: Implement backend endpoint return fetchApi<{ preview: Array<{ id: string subject: string from: string date: string reason: 'read' | 'promotion' }> }>(`/preferences/ai-control/cleanup/preview?userId=${userId}`) }, // Run cleanup now - executes cleanup for the user // POST /api/preferences/ai-control/cleanup/run // Body: { userId: string } // Response: { success: boolean, data: { readItems: number, promotions: number } } async runCleanup(userId: string) { // Uses existing /api/email/cleanup endpoint return fetchApi<{ usersProcessed: number emailsProcessed: { readItems: number promotions: number } errors: Array<{ userId: string; error: string }> }>('/email/cleanup', { method: 'POST', body: JSON.stringify({ userId }), }) }, // Get cleanup status - last run info and counts // TODO: Backend endpoint needs to be implemented // GET /api/preferences/ai-control/cleanup/status?userId=xxx // Response: { lastRun?: string, lastRunCounts?: { readItems: number, promotions: number } } async getCleanupStatus(userId: string) { // TODO: Implement backend endpoint return fetchApi<{ lastRun?: string lastRunCounts?: { readItems: number promotions: number } }>(`/preferences/ai-control/cleanup/status?userId=${userId}`) }, // ═══════════════════════════════════════════════════════════════════════════ // COMPANY LABELS // ═══════════════════════════════════════════════════════════════════════════ async getCompanyLabels(userId: string) { return fetchApi>(`/preferences/company-labels?userId=${userId}`) }, async saveCompanyLabel(userId: string, companyLabel: { id?: string name: string condition: string enabled: boolean category?: string }) { return fetchApi<{ id?: string name: string condition: string enabled: boolean category?: string }>('/preferences/company-labels', { method: 'POST', body: JSON.stringify({ userId, companyLabel }), }) }, async deleteCompanyLabel(userId: string, labelId: string) { return fetchApi<{ success: boolean }>(`/preferences/company-labels/${labelId}?userId=${userId}`, { method: 'DELETE', }) }, // ═══════════════════════════════════════════════════════════════════════════ // ME / ADMIN // ═══════════════════════════════════════════════════════════════════════════ async getMe(email: string) { return fetchApi<{ isAdmin: boolean }>(`/me?email=${encodeURIComponent(email)}`) }, // ═══════════════════════════════════════════════════════════════════════════ // NAME LABELS (Workers – Admin only) // ═══════════════════════════════════════════════════════════════════════════ async getNameLabels(userId: string, email: string) { return fetchApi>(`/preferences/name-labels?userId=${userId}&email=${encodeURIComponent(email)}`) }, async saveNameLabel( userId: string, userEmail: string, nameLabel: { id?: string; name: string; email?: string; keywords?: string[]; enabled: boolean } ) { return fetchApi<{ id?: string; name: string; email?: string; keywords?: string[]; enabled: boolean }>( '/preferences/name-labels', { method: 'POST', body: JSON.stringify({ userId, email: userEmail, nameLabel }), } ) }, async deleteNameLabel(userId: string, userEmail: string, labelId: string) { return fetchApi<{ success: boolean }>( `/preferences/name-labels/${labelId}?userId=${userId}&email=${encodeURIComponent(userEmail)}`, { method: 'DELETE' } ) }, // ═══════════════════════════════════════════════════════════════════════════ // PRODUCTS & QUESTIONS (Legacy) // ═══════════════════════════════════════════════════════════════════════════ async getProducts() { return fetchApi('/products') }, async getQuestions(productSlug: string) { return fetchApi(`/questions?productSlug=${productSlug}`) }, async createSubmission(productSlug: string, answers: Record) { return fetchApi<{ submissionId: string }>('/submissions', { method: 'POST', body: JSON.stringify({ productSlug, answers }), }) }, async createCheckout(submissionId: string) { return fetchApi<{ url: string; sessionId: string }>('/checkout', { method: 'POST', body: JSON.stringify({ submissionId }), }) }, // ═══════════════════════════════════════════════════════════════════════════ // CONFIG // ═══════════════════════════════════════════════════════════════════════════ async getConfig() { return fetchApi<{ features: { gmail: boolean outlook: boolean ai: boolean } pricing: { basic: { price: number; currency: string; accounts: number } pro: { price: number; currency: string; accounts: number } business: { price: number; currency: string; accounts: number } } }>('/config') }, async healthCheck() { return fetchApi<{ status: string timestamp: string version: string environment: string uptime: number }>('/health') }, // ═══════════════════════════════════════════════════════════════════════════ // ONBOARDING // ═══════════════════════════════════════════════════════════════════════════ async getOnboardingStatus(userId: string) { return fetchApi<{ onboarding_step: string completedSteps: string[] first_value_seen_at?: string skipped_at?: string }>(`/onboarding/status?userId=${userId}`) }, async updateOnboardingStep(userId: string, step: string, completedSteps: string[] = []) { return fetchApi<{ step: string; completedSteps: string[] }>('/onboarding/step', { method: 'POST', body: JSON.stringify({ userId, step, completedSteps }), }) }, async skipOnboarding(userId: string) { return fetchApi<{ skipped: boolean }>('/onboarding/skip', { method: 'POST', body: JSON.stringify({ userId }), }) }, async resumeOnboarding(userId: string) { return fetchApi<{ onboarding_step: string completedSteps: string[] }>('/onboarding/resume', { method: 'POST', body: JSON.stringify({ userId }), }) }, // ═══════════════════════════════════════════════════════════════════════════ // ACCOUNT MANAGEMENT // ═══════════════════════════════════════════════════════════════════════════ async deleteAccount(userId: string) { return fetchApi<{ success: boolean }>('/account/delete', { method: 'DELETE', body: JSON.stringify({ userId }), }) }, // ═══════════════════════════════════════════════════════════════════════════ // REFERRALS // ═══════════════════════════════════════════════════════════════════════════ async getReferralCode(userId: string) { return fetchApi<{ referralCode: string referralCount: number }>(`/referrals/code?userId=${userId}`) }, async trackReferral(userId: string, referralCode: string) { return fetchApi<{ success: boolean }>('/referrals/track', { method: 'POST', body: JSON.stringify({ userId, referralCode }), }) }, } export default api