import type { ApiError, ApiResponse, AuthResponse, Beneficiary, BeneficiaryDashboardData, ChatResponse, DashboardSingleResponse, NotificationSettings } from '@/types'; import * as Crypto from 'expo-crypto'; import * as SecureStore from 'expo-secure-store'; // Callback for handling unauthorized responses (401) let onUnauthorizedCallback: (() => void) | null = null; export function setOnUnauthorizedCallback(callback: () => void) { onUnauthorizedCallback = callback; } const API_BASE_URL = 'https://eluxnetworks.net/function/well-api/api'; const CLIENT_ID = 'MA_001'; // WellNuo Backend API (our own API for auth, OTP, etc.) // TODO: Update to production URL when deployed const WELLNUO_API_URL = 'https://wellnuo.smartlaunchhub.com/api'; // Avatar images for elderly beneficiaries - grandmothers (бабушки) const ELDERLY_AVATARS = [ 'https://images.unsplash.com/photo-1566616213894-2d4e1baee5d8?w=200&h=200&fit=crop&crop=face', // grandmother with gray hair 'https://images.unsplash.com/photo-1544027993-37dbfe43562a?w=200&h=200&fit=crop&crop=face', // elderly woman smiling 'https://images.unsplash.com/photo-1491308056676-205b7c9a7dc1?w=200&h=200&fit=crop&crop=face', // senior woman portrait 'https://images.unsplash.com/photo-1580489944761-15a19d654956?w=200&h=200&fit=crop&crop=face', // older woman glasses 'https://images.unsplash.com/photo-1548142813-c348350df52b?w=200&h=200&fit=crop&crop=face', // grandmother portrait ]; // Get consistent avatar based on deployment_id function getAvatarForBeneficiary(deploymentId: number): string { const index = deploymentId % ELDERLY_AVATARS.length; return ELDERLY_AVATARS[index]; } // Helper function to format time ago function formatTimeAgo(date: Date): string { const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); if (diffMins < 1) return 'Just now'; if (diffMins < 60) return `${diffMins} min ago`; if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; } class ApiService { // Public method to get the access token (used by AuthContext) async getToken(): Promise { try { return await SecureStore.getItemAsync('accessToken'); } catch { return null; } } // Get legacy API token (for eluxnetworks.net API - dashboard, voice_ask) private async getLegacyToken(): Promise { try { return await SecureStore.getItemAsync('legacyAccessToken'); } catch { return null; } } private generateNonce(): string { const randomBytes = Crypto.getRandomBytes(16); return Array.from(randomBytes) .map(b => b.toString(16).padStart(2, '0')) .join(''); } private async makeRequest(params: Record): Promise> { try { const formData = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { formData.append(key, value); }); const response = await fetch(API_BASE_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: formData.toString(), }); const data = await response.json(); // Handle 401 Unauthorized - trigger logout if (response.status === 401 || data.status === '401' || data.error === 'Unauthorized') { if (onUnauthorizedCallback) { onUnauthorizedCallback(); } return { ok: false, error: { message: 'Session expired. Please login again.', code: 'UNAUTHORIZED', status: 401, }, }; } if (data.status === '200 OK' || data.ok === true) { return { data: data as T, ok: true }; } return { ok: false, error: { message: data.message || data.error || 'Request failed', status: response.status, }, }; } catch (error) { const apiError: ApiError = { message: error instanceof Error ? error.message : 'Network error', code: 'NETWORK_ERROR', }; return { ok: false, error: apiError }; } } // Authentication (Legacy API - eluxnetworks.net) // Used for dev mode and dashboard access async login(username: string, password: string): Promise> { const response = await this.makeRequest({ function: 'credentials', email: username, ps: password, clientId: CLIENT_ID, nonce: this.generateNonce(), }); if (response.ok && response.data) { // Save LEGACY credentials separately (not to accessToken!) // accessToken is reserved for WellNuo API JWT tokens await SecureStore.setItemAsync('legacyAccessToken', response.data.access_token); // Keep these for backward compatibility await SecureStore.setItemAsync('userId', response.data.user_id.toString()); await SecureStore.setItemAsync('privileges', response.data.privileges); await SecureStore.setItemAsync('maxRole', response.data.max_role.toString()); } return response; } async logout(): Promise { // Clear WellNuo API auth data await SecureStore.deleteItemAsync('accessToken'); await SecureStore.deleteItemAsync('userId'); await SecureStore.deleteItemAsync('userEmail'); await SecureStore.deleteItemAsync('onboardingCompleted'); // Clear legacy API auth data await SecureStore.deleteItemAsync('legacyAccessToken'); await SecureStore.deleteItemAsync('privileges'); await SecureStore.deleteItemAsync('maxRole'); } // Save user email (for OTP auth flow) async saveEmail(email: string): Promise { await SecureStore.setItemAsync('userEmail', email); } // Get stored email async getStoredEmail(): Promise { try { return await SecureStore.getItemAsync('userEmail'); } catch { return null; } } // Onboarding completion flag - persists across app restarts async setOnboardingCompleted(completed: boolean): Promise { await SecureStore.setItemAsync('onboardingCompleted', completed ? '1' : '0'); } async isOnboardingCompleted(): Promise { try { const value = await SecureStore.getItemAsync('onboardingCompleted'); return value === '1'; } catch { return false; } } // Save mock user (for dev mode OTP flow) async saveMockUser(user: { user_id: string; email: string; max_role: string; privileges: string[] }): Promise { await SecureStore.setItemAsync('accessToken', `mock-token-${user.user_id}`); await SecureStore.setItemAsync('userId', user.user_id); await SecureStore.setItemAsync('privileges', user.privileges.join(',')); await SecureStore.setItemAsync('maxRole', user.max_role); await SecureStore.setItemAsync('userEmail', user.email); } // ==================== OTP Authentication (WellNuo Backend) ==================== // Check if email exists in database async checkEmail(email: string): Promise> { try { const response = await fetch(`${WELLNUO_API_URL}/auth/check-email`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email }), }); const data = await response.json(); if (response.ok) { return { data, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to check email' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // Request OTP code - sends email via Brevo async requestOTP(email: string): Promise> { try { const response = await fetch(`${WELLNUO_API_URL}/auth/request-otp`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email }), }); const data = await response.json(); if (response.ok) { return { data, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to send OTP' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // Verify OTP code and get JWT token async verifyOTP(email: string, code: string): Promise> { try { const payload = { email: email.trim().toLowerCase(), code }; console.log('[API] verifyOTP request:', JSON.stringify(payload)); const response = await fetch(`${WELLNUO_API_URL}/auth/verify-otp`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); const data = await response.json(); console.log('[API] verifyOTP response:', JSON.stringify(data)); if (response.ok && data.token) { // Save ONLY technical auth data (token, userId, email) // User profile data is fetched from API, NOT stored locally await SecureStore.setItemAsync('accessToken', data.token); await SecureStore.setItemAsync('userId', String(data.user.id)); await SecureStore.setItemAsync('userEmail', email); return { data, ok: true }; } return { ok: false, error: { message: data.error || data.message || 'Invalid or expired code' }, }; } catch (error) { console.error('[API] verifyOTP network error:', error); return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } async isAuthenticated(): Promise { const token = await this.getToken(); return !!token; } // Get current user profile from API (not local storage!) async getStoredUser() { try { const token = await this.getToken(); const userId = await SecureStore.getItemAsync('userId'); console.log('[API] getStoredUser: token exists =', !!token, ', userId =', userId); if (!token || !userId) { console.log('[API] getStoredUser: No token or userId, returning null'); return null; } // Fetch profile from server console.log('[API] getStoredUser: Fetching profile from server...'); const profile = await this.getProfile(); console.log('[API] getStoredUser: Profile response ok =', profile.ok, ', error =', profile.error); if (!profile.ok || !profile.data) { // If token is invalid (401), clear all tokens and return null // This will trigger re-authentication if (profile.error?.code === 'UNAUTHORIZED') { console.log('[API] getStoredUser: Token invalid (401), clearing auth data'); await this.logout(); return null; } // For network errors OR other API errors, fall back to minimal info // We don't want to log out the user just because the server is temporarily unavailable console.log('[API] getStoredUser: API error, falling back to local data'); const email = await SecureStore.getItemAsync('userEmail'); return { user_id: parseInt(userId, 10), email: email || undefined, privileges: '', max_role: 0, }; } // /auth/me returns { user: {...}, beneficiaries: [...] } // Extract user data from nested 'user' object const userData = profile.data.user || profile.data; return { user_id: userData.id, email: userData.email, firstName: userData.firstName, lastName: userData.lastName, phone: userData.phone, privileges: '', max_role: 0, }; } catch (error) { // On any unexpected error, fall back to local data instead of logging out console.log('[API] getStoredUser: Unexpected error, falling back to local data', error); const userId = await SecureStore.getItemAsync('userId'); const email = await SecureStore.getItemAsync('userEmail'); if (userId) { return { user_id: parseInt(userId, 10), email: email || undefined, privileges: '', max_role: 0, }; } return null; } } // Get user profile from WellNuo API async getProfile(): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/auth/me`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, }, }); const data = await response.json(); if (!response.ok) { return { ok: false, error: { message: data.error || 'Failed to get profile', code: response.status === 401 ? 'UNAUTHORIZED' : 'API_ERROR', } }; } return { data, ok: true }; } catch (error) { return { ok: false, error: { message: 'Network error', code: 'NETWORK_ERROR' } }; } } // Update user profile on WellNuo API async updateProfile(updates: { firstName?: string; lastName?: string; phone?: string; address?: { street?: string; city?: string; zip?: string; state?: string; country?: string; }; }): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/auth/profile`, { method: 'PATCH', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify(updates), }); const data = await response.json(); if (!response.ok) { return { ok: false, error: { message: data.error || 'Failed to update profile' } }; } return { data, ok: true }; } catch (error) { return { ok: false, error: { message: 'Network error', code: 'NETWORK_ERROR' } }; } } // Beneficiaries (elderly people being monitored) async getBeneficiaries(): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } // Note: Using mock data since API structure is not fully documented // Replace with actual API call when available const mockBeneficiaries: Beneficiary[] = [ { id: 1, name: 'Julia Smith', status: 'online', relationship: 'Mother', last_activity: '2 min ago', sensor_data: { motion_detected: true, last_motion: '2 min ago', door_status: 'closed', temperature: 22, humidity: 45, }, }, { id: 2, name: 'Robert Johnson', status: 'offline', relationship: 'Father', last_activity: '1 hour ago', sensor_data: { motion_detected: false, last_motion: '1 hour ago', door_status: 'closed', temperature: 21, humidity: 50, }, }, ]; return { data: { beneficiaries: mockBeneficiaries }, ok: true }; } async getBeneficiary(id: number): Promise> { // Use real API data via getBeneficiaryDashboard const response = await this.getBeneficiaryDashboard(id.toString()); if (!response.ok || !response.data) { return { ok: false, error: response.error || { message: 'Beneficiary not found', code: 'NOT_FOUND' } }; } const data = response.data; // Determine if beneficiary is "online" based on last_detected_time const lastDetected = data.last_detected_time ? new Date(data.last_detected_time) : null; const isRecent = lastDetected && (Date.now() - lastDetected.getTime()) < 30 * 60 * 1000; // 30 min const deploymentId = parseInt(data.deployment_id, 10); const beneficiary: Beneficiary = { id: deploymentId, name: data.name, avatar: getAvatarForBeneficiary(deploymentId), status: isRecent ? 'online' : 'offline', address: data.address, timezone: data.time_zone, wellness_score: data.wellness_score_percent, wellness_descriptor: data.wellness_descriptor, last_location: data.last_location, temperature: data.temperature, units: data.units, sleep_hours: data.sleep_hours, bedroom_temperature: data.bedroom_temperature, before_last_location: data.before_last_location, last_detected_time: data.last_detected_time, last_activity: data.last_detected_time ? formatTimeAgo(new Date(data.last_detected_time)) : undefined, }; return { data: beneficiary, ok: true }; } // Get beneficiary dashboard data by deployment_id (LEGACY API - eluxnetworks.net) async getBeneficiaryDashboard(deploymentId: string): Promise> { // Use legacy API credentials for dashboard const token = await this.getLegacyToken(); if (!token) { // Fallback to regular credentials if legacy not available const fallbackToken = await this.getToken(); if (!fallbackToken) { return { ok: false, error: { message: 'Not authenticated for dashboard access', code: 'UNAUTHORIZED' } }; } // Note: This will likely fail if using WellNuo token, but we try anyway } const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD const response = await this.makeRequest({ function: 'dashboard_single', token: token || await this.getToken() || '', deployment_id: deploymentId, date: today, nonce: this.generateNonce(), }); if (response.ok && response.data?.result_list?.[0]) { return { data: response.data.result_list[0], ok: true }; } return { ok: false, error: response.error || { message: 'Failed to get beneficiary data' }, }; } // Get all beneficiaries from WellNuo API async getAllBeneficiaries(): Promise> { const token = await this.getToken(); console.log('[API] getAllBeneficiaries - token exists:', !!token, 'length:', token?.length); if (!token) { console.log('[API] getAllBeneficiaries - No token found!'); return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { console.log('[API] getAllBeneficiaries - Fetching from:', `${WELLNUO_API_URL}/me/beneficiaries`); const response = await fetch(`${WELLNUO_API_URL}/me/beneficiaries`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, }, }); console.log('[API] getAllBeneficiaries - Response status:', response.status); const data = await response.json(); console.log('[API] getAllBeneficiaries - Data:', JSON.stringify(data).substring(0, 200)); if (!response.ok) { if (response.status === 401) { if (onUnauthorizedCallback) onUnauthorizedCallback(); return { ok: false, error: { message: 'Session expired', code: 'UNAUTHORIZED', status: 401 } }; } return { ok: false, error: { message: data.error || 'Failed to get beneficiaries' } }; } // Map API response to Beneficiary type const beneficiaries: Beneficiary[] = (data.beneficiaries || []).map((item: any) => ({ id: item.id, name: item.name || item.email, avatar: undefined, // No auto-generated avatars - only show if user uploaded one status: 'offline' as const, email: item.email, address: item.address?.street ? `${item.address.street}, ${item.address.city}` : undefined, subscription: item.subscription, // Equipment status from orders equipmentStatus: item.equipmentStatus, hasDevices: item.hasDevices || false, trackingNumber: item.trackingNumber, })); return { data: beneficiaries, ok: true }; } catch (error) { console.log('[API] getAllBeneficiaries - Catch error:', error); return { ok: false, error: { message: 'Network error', code: 'NETWORK_ERROR' } }; } } // Get single beneficiary details from WellNuo API async getWellNuoBeneficiary(id: number): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/me/beneficiaries/${id}`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, }, }); const data = await response.json(); console.log('[API] getWellNuoBeneficiary - Raw response:', JSON.stringify({ id: data.id, name: data.name, hasDevices: data.hasDevices, equipmentStatus: data.equipmentStatus, subscription: data.subscription })); if (!response.ok) { return { ok: false, error: { message: data.error || 'Failed to get beneficiary' } }; } const beneficiary: Beneficiary = { id: data.id, name: data.name || data.email, avatar: undefined, // No auto-generated avatars - only show if user uploaded one status: 'offline' as const, email: data.email, address: data.address?.street ? `${data.address.street}, ${data.address.city}` : undefined, subscription: data.subscription ? { status: data.subscription.status, plan: data.subscription.plan, endDate: data.subscription.endDate, cancelAtPeriodEnd: data.subscription.cancelAtPeriodEnd, } : undefined, // Equipment status from orders equipmentStatus: data.equipmentStatus, hasDevices: data.hasDevices || false, trackingNumber: data.trackingNumber, }; return { data: beneficiary, ok: true }; } catch (error) { return { ok: false, error: { message: 'Network error', code: 'NETWORK_ERROR' } }; } } // Update beneficiary in WellNuo API async updateWellNuoBeneficiary(id: number, updates: { name?: string; phone?: string; address?: string; }): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/me/beneficiaries/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify(updates), }); const data = await response.json(); if (!response.ok) { return { ok: false, error: { message: data.error || 'Failed to update beneficiary' } }; } const beneficiary: Beneficiary = { id: data.beneficiary.id, name: data.beneficiary.name || data.beneficiary.email, email: data.beneficiary.email, status: 'offline' as const, }; return { data: beneficiary, ok: true }; } catch (error) { return { ok: false, error: { message: 'Network error', code: 'NETWORK_ERROR' } }; } } // Create new beneficiary (grants owner access automatically) async createBeneficiary(data: { name: string; phone?: string; address?: string; }): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/me/beneficiaries`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify(data), }); const result = await response.json(); if (!response.ok) { return { ok: false, error: { message: result.error || 'Failed to create beneficiary' } }; } const beneficiary: Beneficiary = { id: result.beneficiary.id, name: result.beneficiary.name || '', status: 'offline' as const, }; return { data: beneficiary, ok: true }; } catch (error) { return { ok: false, error: { message: 'Network error', code: 'NETWORK_ERROR' } }; } } // Upload/update beneficiary avatar async updateBeneficiaryAvatar(id: number, imageUri: string | null): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { let base64Image: string | null = null; if (imageUri) { // Convert file URI to base64 const response = await fetch(imageUri); const blob = await response.blob(); // Convert blob to base64 const base64 = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result as string); reader.onerror = reject; reader.readAsDataURL(blob); }); base64Image = base64; } const apiResponse = await fetch(`${WELLNUO_API_URL}/me/beneficiaries/${id}/avatar`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ avatar: base64Image }), }); const data = await apiResponse.json(); if (!apiResponse.ok) { return { ok: false, error: { message: data.error || 'Failed to update avatar' } }; } return { data: { avatarUrl: data.beneficiary?.avatarUrl || null }, ok: true }; } catch (error) { console.error('[API] updateBeneficiaryAvatar error:', error); return { ok: false, error: { message: 'Network error', code: 'NETWORK_ERROR' } }; } } // Delete beneficiary (removes access record) async deleteBeneficiary(id: number): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/me/beneficiaries/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, }, }); const data = await response.json(); if (!response.ok) { return { ok: false, error: { message: data.error || 'Failed to delete beneficiary' } }; } return { data: { success: true }, ok: true }; } catch (error) { return { ok: false, error: { message: 'Network error', code: 'NETWORK_ERROR' } }; } } // Update beneficiary equipment status async updateBeneficiaryEquipmentStatus( id: number, status: 'none' | 'ordered' | 'shipped' | 'delivered' ): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/me/beneficiaries/${id}/equipment-status`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ status }), }); const data = await response.json(); if (!response.ok) { return { ok: false, error: { message: data.error || 'Failed to update equipment status' } }; } return { data: { success: true }, ok: true }; } catch (error) { return { ok: false, error: { message: 'Network error', code: 'NETWORK_ERROR' } }; } } // AI Chat - deploymentId is required, no default value for security (LEGACY API) async sendMessage(question: string, deploymentId: string): Promise> { if (!deploymentId) { return { ok: false, error: { message: 'Please select a beneficiary first', code: 'NO_BENEFICIARY_SELECTED' } }; } // Use legacy API credentials for voice_ask const token = await this.getLegacyToken() || await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } return this.makeRequest({ function: 'voice_ask', clientId: CLIENT_ID, token: token, question: question, deployment_id: deploymentId, }); } // ==================== Invitations API ==================== // Send invitation to share access to a beneficiary async sendInvitation(params: { beneficiaryId: string; email: string; role: 'caretaker' | 'guardian'; label?: string; }): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/invitations`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ beneficiaryId: params.beneficiaryId, email: params.email, role: params.role, // Backend expects 'caretaker' or 'guardian' label: params.label, }), }); const data = await response.json(); if (response.ok) { return { data, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to send invitation' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // Get invitations for a beneficiary async getInvitations(beneficiaryId: string): Promise }>> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/invitations/beneficiary/${beneficiaryId}`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, }, }); const data = await response.json(); if (response.ok) { return { data, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to get invitations' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // Delete invitation async deleteInvitation(invitationId: string): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/invitations/${invitationId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, }, }); const data = await response.json(); if (response.ok) { return { data, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to delete invitation' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // Activate equipment for beneficiary (saves to server) async activateBeneficiary(beneficiaryId: number, serialNumber: string): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/me/beneficiaries/${beneficiaryId}/activate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ serialNumber }), }); const data = await response.json(); if (response.ok) { return { data, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to activate equipment' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // ==================== SUBSCRIPTION MANAGEMENT ==================== // Cancel subscription at period end async cancelSubscription(beneficiaryId: number): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/stripe/cancel-subscription`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ beneficiaryId }), }); const data = await response.json(); if (response.ok) { return { data, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to cancel subscription' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // Get transaction history from Stripe async getTransactionHistory(beneficiaryId: number, limit = 10): Promise; hasMore: boolean; }>> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/stripe/transaction-history/${beneficiaryId}?limit=${limit}`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, }, }); const data = await response.json(); if (response.ok) { return { data, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to get transaction history' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // Reactivate subscription that was set to cancel async reactivateSubscription(beneficiaryId: number): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/stripe/reactivate-subscription`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ beneficiaryId }), }); const data = await response.json(); if (response.ok) { return { data, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to reactivate subscription' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // Update invitation role async updateInvitation(invitationId: string, role: 'caretaker' | 'guardian'): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/invitations/${invitationId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ role }), }); const data = await response.json(); if (response.ok) { return { data, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to update invitation' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // ==================== NOTIFICATION SETTINGS ==================== // Get notification settings for current user async getNotificationSettings(): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/notification-settings`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, }, }); const data = await response.json(); if (response.ok) { return { data: data.settings, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to get notification settings' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } // Update notification settings for current user async updateNotificationSettings(settings: Partial): Promise> { const token = await this.getToken(); if (!token) { return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; } try { const response = await fetch(`${WELLNUO_API_URL}/notification-settings`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify(settings), }); const data = await response.json(); if (response.ok) { return { data: data.settings, ok: true }; } return { ok: false, error: { message: data.error || 'Failed to update notification settings' }, }; } catch (error) { return { ok: false, error: { message: 'Network error. Please check your connection.' }, }; } } } export const api = new ApiService();