WellNuo/services/api.ts
Sergei a804a82512 Implement Auth, Patients List, and Dashboard
Features:
- Login screen with API integration (credentials endpoint)
- SecureStore for token management
- Patients list with health data display
- Patient dashboard with stats and quick actions
- AI Chat screen (voice_ask API integration)
- Profile screen with logout
- Full error handling and loading states
- WellNuo brand colors and UI components

API Integration:
- Base URL: eluxnetworks.net/function/well-api/api
- Auth: function=credentials
- Chat: function=voice_ask

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-12 12:58:15 -08:00

195 lines
5.5 KiB
TypeScript

import * as SecureStore from 'expo-secure-store';
import type { AuthResponse, ChatResponse, Patient, ApiResponse, ApiError } from '@/types';
const API_BASE_URL = 'https://eluxnetworks.net/function/well-api/api';
const CLIENT_ID = 'MA_001';
class ApiService {
private async getToken(): Promise<string | null> {
try {
return await SecureStore.getItemAsync('accessToken');
} catch {
return null;
}
}
private async getUserName(): Promise<string | null> {
try {
return await SecureStore.getItemAsync('userName');
} catch {
return null;
}
}
private generateNonce(): string {
return Math.floor(Math.random() * 1000000).toString();
}
private async makeRequest<T>(params: Record<string, string>): Promise<ApiResponse<T>> {
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();
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
async login(username: string, password: string): Promise<ApiResponse<AuthResponse>> {
const response = await this.makeRequest<AuthResponse>({
function: 'credentials',
user_name: username,
ps: password,
clientId: CLIENT_ID,
nonce: this.generateNonce(),
});
if (response.ok && response.data) {
// Save credentials to SecureStore
await SecureStore.setItemAsync('accessToken', response.data.access_token);
await SecureStore.setItemAsync('userId', response.data.user_id.toString());
await SecureStore.setItemAsync('userName', username);
await SecureStore.setItemAsync('privileges', response.data.privileges);
await SecureStore.setItemAsync('maxRole', response.data.max_role.toString());
}
return response;
}
async logout(): Promise<void> {
await SecureStore.deleteItemAsync('accessToken');
await SecureStore.deleteItemAsync('userId');
await SecureStore.deleteItemAsync('userName');
await SecureStore.deleteItemAsync('privileges');
await SecureStore.deleteItemAsync('maxRole');
}
async isAuthenticated(): Promise<boolean> {
const token = await this.getToken();
return !!token;
}
// Get stored user info
async getStoredUser() {
try {
const userId = await SecureStore.getItemAsync('userId');
const userName = await SecureStore.getItemAsync('userName');
const privileges = await SecureStore.getItemAsync('privileges');
const maxRole = await SecureStore.getItemAsync('maxRole');
if (!userId || !userName) return null;
return {
user_id: parseInt(userId, 10),
user_name: userName,
privileges: privileges || '',
max_role: parseInt(maxRole || '0', 10),
};
} catch {
return null;
}
}
// Patients
async getPatients(): Promise<ApiResponse<{ patients: Patient[] }>> {
const token = await this.getToken();
if (!token) {
return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } };
}
// Note: Using mock data since get_patients API structure is not fully documented
// Replace with actual API call when available
const mockPatients: Patient[] = [
{
id: 1,
name: 'Julia Smith',
status: 'online',
relationship: 'Mother',
last_activity: '2 min ago',
health_data: {
heart_rate: 72,
steps: 3450,
sleep_hours: 7.5,
},
},
{
id: 2,
name: 'Robert Johnson',
status: 'offline',
relationship: 'Father',
last_activity: '1 hour ago',
health_data: {
heart_rate: 68,
steps: 2100,
sleep_hours: 6.8,
},
},
];
return { data: { patients: mockPatients }, ok: true };
}
async getPatient(id: number): Promise<ApiResponse<Patient>> {
const response = await this.getPatients();
if (!response.ok || !response.data) {
return { ok: false, error: response.error };
}
const patient = response.data.patients.find((p) => p.id === id);
if (!patient) {
return { ok: false, error: { message: 'Patient not found', code: 'NOT_FOUND' } };
}
return { data: patient, ok: true };
}
// AI Chat
async sendMessage(question: string, deploymentId: string = '21'): Promise<ApiResponse<ChatResponse>> {
const token = await this.getToken();
const userName = await this.getUserName();
if (!token || !userName) {
return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } };
}
return this.makeRequest<ChatResponse>({
function: 'voice_ask',
clientId: CLIENT_ID,
user_name: userName,
token: token,
question: question,
deployment_id: deploymentId,
});
}
}
export const api = new ApiService();