Sync latest changes
- Updated login screen - Updated chat screen - Updated AuthContext - Updated API service - Updated packages - Updated discussion docs and scheme 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1e2c2b9856
commit
8590202d53
@ -3,6 +3,18 @@ Discussion Points for EluxNetworks Team
|
||||
|
||||
────────────────────────────────────────────────────────────
|
||||
|
||||
CURRENT STATUS
|
||||
|
||||
I've uploaded the latest build to TestFlight. Please test on iOS.
|
||||
|
||||
Tomorrow I'll start working on Android deployment.
|
||||
|
||||
We need to schedule a call to discuss these questions in more detail.
|
||||
I've hit some bottlenecks and need clarification before moving forward.
|
||||
All questions are listed below and in the project scheme.
|
||||
|
||||
────────────────────────────────────────────────────────────
|
||||
|
||||
1. BACKEND/API DEVELOPMENT ACCESS
|
||||
|
||||
Request: Can I get access to develop backend/API endpoints myself?
|
||||
@ -19,7 +31,7 @@ Option A: Access Granted (Preferred)
|
||||
|
||||
I need:
|
||||
- Repository access (or separate repo)
|
||||
- Database documentation
|
||||
- Database access OR database dump (so I can set up my own)
|
||||
- Architecture overview
|
||||
|
||||
I will:
|
||||
@ -57,6 +69,17 @@ Hide:
|
||||
Reason: Mobile app has its own navigation and auth.
|
||||
|
||||
|
||||
────────────────────────────────────────────────────────────
|
||||
|
||||
3. ACCOUNT ACCESS NEEDED
|
||||
|
||||
Apple Developer Account
|
||||
- Need access for App Store submissions, certificates, push notifications setup
|
||||
|
||||
Stripe Account (if we're integrating payments/subscriptions)
|
||||
- Need API keys for payment integration
|
||||
- Need access to configure products, prices, webhooks
|
||||
|
||||
────────────────────────────────────────────────────────────
|
||||
|
||||
SUMMARY
|
||||
|
||||
@ -40,6 +40,8 @@ export default function LoginScreen() {
|
||||
const success = await login({ username: username.trim(), password });
|
||||
|
||||
if (success) {
|
||||
// Clear password from memory after successful login
|
||||
setPassword('');
|
||||
router.replace('/(tabs)');
|
||||
}
|
||||
}, [username, password, login, clearError]);
|
||||
|
||||
@ -30,11 +30,30 @@ export default function ChatScreen() {
|
||||
const [input, setInput] = useState('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const flatListRef = useRef<FlatList>(null);
|
||||
const lastSendTimeRef = useRef<number>(0);
|
||||
const SEND_COOLDOWN_MS = 1000; // 1 second cooldown between messages
|
||||
|
||||
const handleSend = useCallback(async () => {
|
||||
const trimmedInput = input.trim();
|
||||
if (!trimmedInput || isSending) return;
|
||||
|
||||
// Debounce: prevent rapid-fire messages
|
||||
const now = Date.now();
|
||||
if (now - lastSendTimeRef.current < SEND_COOLDOWN_MS) {
|
||||
return;
|
||||
}
|
||||
lastSendTimeRef.current = now;
|
||||
|
||||
// Security: require beneficiary to be selected
|
||||
if (!currentBeneficiary?.id) {
|
||||
Alert.alert(
|
||||
'Select Patient',
|
||||
'Please select a patient from the Patients tab before starting a conversation.',
|
||||
[{ text: 'OK' }]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const userMessage: Message = {
|
||||
id: Date.now().toString(),
|
||||
role: 'user',
|
||||
@ -53,8 +72,8 @@ export default function ChatScreen() {
|
||||
? `${beneficiaryContext} ${trimmedInput}`
|
||||
: trimmedInput;
|
||||
|
||||
// Pass deployment_id from selected beneficiary (fallback to '21' if not selected)
|
||||
const deploymentId = currentBeneficiary?.id?.toString() || '21';
|
||||
// Pass deployment_id from selected beneficiary (required, no fallback)
|
||||
const deploymentId = currentBeneficiary.id.toString();
|
||||
const response = await api.sendMessage(questionWithContext, deploymentId);
|
||||
|
||||
if (response.ok && response.data?.response) {
|
||||
@ -85,7 +104,7 @@ export default function ChatScreen() {
|
||||
} finally {
|
||||
setIsSending(false);
|
||||
}
|
||||
}, [input, isSending, getBeneficiaryContext]);
|
||||
}, [input, isSending, currentBeneficiary, getBeneficiaryContext]);
|
||||
|
||||
const renderMessage = ({ item }: { item: Message }) => {
|
||||
const isUser = item.role === 'user';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react';
|
||||
import { api } from '@/services/api';
|
||||
import React, { createContext, useContext, useState, useEffect, useCallback, useRef, type ReactNode } from 'react';
|
||||
import { api, setOnUnauthorizedCallback } from '@/services/api';
|
||||
import type { User, LoginCredentials, ApiError } from '@/types';
|
||||
|
||||
interface AuthState {
|
||||
@ -30,6 +30,20 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
checkAuth();
|
||||
}, []);
|
||||
|
||||
// Set up callback for 401 responses - auto logout
|
||||
useEffect(() => {
|
||||
setOnUnauthorizedCallback(() => {
|
||||
api.logout().then(() => {
|
||||
setState({
|
||||
user: null,
|
||||
isLoading: false,
|
||||
isAuthenticated: false,
|
||||
error: { message: 'Session expired. Please login again.' },
|
||||
});
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
const checkAuth = async () => {
|
||||
try {
|
||||
const isAuth = await api.isAuthenticated();
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@ -14,6 +14,7 @@
|
||||
"@react-navigation/native": "^7.1.8",
|
||||
"expo": "~54.0.29",
|
||||
"expo-constants": "~18.0.12",
|
||||
"expo-crypto": "~15.0.8",
|
||||
"expo-font": "~14.0.10",
|
||||
"expo-haptics": "~15.0.8",
|
||||
"expo-image": "~3.0.11",
|
||||
@ -6581,6 +6582,18 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-crypto": {
|
||||
"version": "15.0.8",
|
||||
"resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-15.0.8.tgz",
|
||||
"integrity": "sha512-aF7A914TB66WIlTJvl5J6/itejfY78O7dq3ibvFltL9vnTALJ/7LYHvLT4fwmx9yUNS6ekLBtDGWivFWnj2Fcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-file-system": {
|
||||
"version": "19.0.21",
|
||||
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-19.0.21.tgz",
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
"@react-navigation/native": "^7.1.8",
|
||||
"expo": "~54.0.29",
|
||||
"expo-constants": "~18.0.12",
|
||||
"expo-crypto": "~15.0.8",
|
||||
"expo-font": "~14.0.10",
|
||||
"expo-haptics": "~15.0.8",
|
||||
"expo-image": "~3.0.11",
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import * as Crypto from 'expo-crypto';
|
||||
import type { AuthResponse, ChatResponse, Beneficiary, ApiResponse, ApiError, DashboardSingleResponse, PatientDashboardData } from '@/types';
|
||||
|
||||
// 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';
|
||||
|
||||
@ -51,7 +59,10 @@ class ApiService {
|
||||
}
|
||||
|
||||
private generateNonce(): string {
|
||||
return Math.floor(Math.random() * 1000000).toString();
|
||||
const randomBytes = Crypto.getRandomBytes(16);
|
||||
return Array.from(randomBytes)
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
|
||||
private async makeRequest<T>(params: Record<string, string>): Promise<ApiResponse<T>> {
|
||||
@ -71,6 +82,21 @@ class ApiService {
|
||||
|
||||
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 };
|
||||
}
|
||||
@ -309,8 +335,11 @@ class ApiService {
|
||||
return { data: patients, ok: true };
|
||||
}
|
||||
|
||||
// AI Chat
|
||||
async sendMessage(question: string, deploymentId: string = '21'): Promise<ApiResponse<ChatResponse>> {
|
||||
// AI Chat - deploymentId is required, no default value for security
|
||||
async sendMessage(question: string, deploymentId: string): Promise<ApiResponse<ChatResponse>> {
|
||||
if (!deploymentId) {
|
||||
return { ok: false, error: { message: 'Please select a patient first', code: 'NO_PATIENT_SELECTED' } };
|
||||
}
|
||||
const token = await this.getToken();
|
||||
const userName = await this.getUserName();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user