From 9ae23cfef331070426465e6578068aad9b5a503e Mon Sep 17 00:00:00 2001 From: Sergei Date: Sat, 24 Jan 2026 20:48:18 -0800 Subject: [PATCH] feat: Add Deployment ID setting in Profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add deploymentId storage methods in api.ts (set/get/clear) - Add Settings section in Profile with Deployment ID menu item - Add modal dialog to edit deployment ID - Update chat.tsx to use custom deployment ID from settings - Priority: custom > currentBeneficiary > first beneficiary > fallback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/(tabs)/chat.tsx | 23 +++++-- app/(tabs)/profile.tsx | 150 ++++++++++++++++++++++++++++++++++++++++- services/api.ts | 17 +++++ 3 files changed, 184 insertions(+), 6 deletions(-) diff --git a/app/(tabs)/chat.tsx b/app/(tabs)/chat.tsx index cc3edc5..33cc00c 100644 --- a/app/(tabs)/chat.tsx +++ b/app/(tabs)/chat.tsx @@ -433,6 +433,18 @@ export default function ChatScreen() { const [beneficiaries, setBeneficiaries] = useState([]); const [loadingBeneficiaries, setLoadingBeneficiaries] = useState(false); + // Custom deployment ID from settings + const [customDeploymentId, setCustomDeploymentId] = useState(null); + + // Load custom deployment ID from settings + useEffect(() => { + const loadCustomDeploymentId = async () => { + const saved = await api.getDeploymentId(); + setCustomDeploymentId(saved); + }; + loadCustomDeploymentId(); + }, []); + // Load beneficiaries const loadBeneficiaries = useCallback(async () => { setLoadingBeneficiaries(true); @@ -500,8 +512,9 @@ export default function ChatScreen() { try { // Build beneficiary data for the agent + // Priority: customDeploymentId from settings > currentBeneficiary > first beneficiary > fallback const beneficiaryData: BeneficiaryData = { - deploymentId: currentBeneficiary?.id?.toString() || beneficiaries[0]?.id?.toString() || '21', + deploymentId: customDeploymentId || currentBeneficiary?.id?.toString() || beneficiaries[0]?.id?.toString() || '21', beneficiaryNamesDict: {}, }; @@ -539,7 +552,7 @@ export default function ChatScreen() { } finally { setIsConnectingVoice(false); } - }, [isConnectingVoice, isCallActive, currentBeneficiary, beneficiaries, user, clearTranscript, startCall]); + }, [isConnectingVoice, isCallActive, currentBeneficiary, beneficiaries, user, clearTranscript, startCall, customDeploymentId]); // End voice call const endVoiceCall = useCallback(() => { @@ -647,8 +660,8 @@ export default function ChatScreen() { beneficiaryNamesDict[b.id.toString()] = b.name; }); - // Get deployment_id from current beneficiary or fallback to first one - const deploymentId = currentBeneficiary?.id?.toString() || beneficiaries[0]?.id?.toString() || '21'; + // Get deployment_id: custom from settings > current beneficiary > first beneficiary > fallback + const deploymentId = customDeploymentId || currentBeneficiary?.id?.toString() || beneficiaries[0]?.id?.toString() || '21'; // Call API with EXACT same params as voice agent // SINGLE_DEPLOYMENT_MODE: sends only deployment_id (no beneficiary_names_dict) @@ -701,7 +714,7 @@ export default function ChatScreen() { } finally { setIsSending(false); } - }, [input, isSending, getWellNuoToken]); + }, [input, isSending, getWellNuoToken, customDeploymentId, currentBeneficiary, beneficiaries]); // Render message bubble const renderMessage = ({ item }: { item: Message }) => { diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx index bfd6f98..8623202 100644 --- a/app/(tabs)/profile.tsx +++ b/app/(tabs)/profile.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, @@ -6,11 +6,14 @@ import { ScrollView, TouchableOpacity, Alert, + TextInput, + Modal, } from 'react-native'; import { router } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useAuth } from '@/contexts/AuthContext'; +import { api } from '@/services/api'; import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme'; interface MenuItemProps { @@ -50,6 +53,37 @@ function MenuItem({ export default function ProfileScreen() { const { user, logout } = useAuth(); + const [deploymentId, setDeploymentId] = useState(''); + const [showDeploymentModal, setShowDeploymentModal] = useState(false); + const [tempDeploymentId, setTempDeploymentId] = useState(''); + + // Load saved deployment ID + useEffect(() => { + const loadDeploymentId = async () => { + const saved = await api.getDeploymentId(); + if (saved) { + setDeploymentId(saved); + } + }; + loadDeploymentId(); + }, []); + + const openDeploymentModal = useCallback(() => { + setTempDeploymentId(deploymentId); + setShowDeploymentModal(true); + }, [deploymentId]); + + const saveDeploymentId = useCallback(async () => { + const trimmed = tempDeploymentId.trim(); + if (trimmed) { + await api.setDeploymentId(trimmed); + setDeploymentId(trimmed); + } else { + await api.clearDeploymentId(); + setDeploymentId(''); + } + setShowDeploymentModal(false); + }, [tempDeploymentId]); const openTerms = () => { router.push('/terms'); @@ -98,6 +132,19 @@ export default function ProfileScreen() { + {/* Settings */} + + Settings + + + + + {/* Legal - Required for App Store */} Legal @@ -127,6 +174,46 @@ export default function ProfileScreen() { {/* Version */} WellNuo v1.0.0 + + {/* Deployment ID Modal */} + setShowDeploymentModal(false)} + > + + + Deployment ID + + Enter the deployment ID to connect to a specific device. Leave empty for automatic detection. + + + + setShowDeploymentModal(false)} + > + Cancel + + + Save + + + + + ); } @@ -252,4 +339,65 @@ const styles = StyleSheet.create({ color: AppColors.textMuted, paddingVertical: Spacing.xl, }, + // Modal styles + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + justifyContent: 'center', + alignItems: 'center', + padding: Spacing.lg, + }, + modalContent: { + backgroundColor: AppColors.background, + borderRadius: BorderRadius.lg, + padding: Spacing.lg, + width: '100%', + maxWidth: 400, + }, + modalTitle: { + fontSize: FontSizes.lg, + fontWeight: '600', + color: AppColors.textPrimary, + marginBottom: Spacing.sm, + }, + modalDescription: { + fontSize: FontSizes.sm, + color: AppColors.textSecondary, + marginBottom: Spacing.md, + }, + modalInput: { + backgroundColor: AppColors.surface, + borderRadius: BorderRadius.md, + paddingHorizontal: Spacing.md, + paddingVertical: Spacing.sm + 4, + fontSize: FontSizes.base, + color: AppColors.textPrimary, + borderWidth: 1, + borderColor: AppColors.border, + marginBottom: Spacing.md, + }, + modalButtons: { + flexDirection: 'row', + justifyContent: 'flex-end', + gap: Spacing.sm, + }, + modalButtonCancel: { + paddingHorizontal: Spacing.md, + paddingVertical: Spacing.sm, + }, + modalButtonCancelText: { + fontSize: FontSizes.base, + color: AppColors.textSecondary, + }, + modalButtonSave: { + backgroundColor: AppColors.primary, + paddingHorizontal: Spacing.lg, + paddingVertical: Spacing.sm, + borderRadius: BorderRadius.md, + }, + modalButtonSaveText: { + fontSize: FontSizes.base, + fontWeight: '600', + color: AppColors.white, + }, }); diff --git a/services/api.ts b/services/api.ts index 26d84f4..b1982e7 100644 --- a/services/api.ts +++ b/services/api.ts @@ -198,6 +198,23 @@ class ApiService { } } + // Deployment ID management + async setDeploymentId(deploymentId: string): Promise { + await SecureStore.setItemAsync('deploymentId', deploymentId); + } + + async getDeploymentId(): Promise { + try { + return await SecureStore.getItemAsync('deploymentId'); + } catch { + return null; + } + } + + async clearDeploymentId(): Promise { + await SecureStore.deleteItemAsync('deploymentId'); + } + // Beneficiaries (elderly people being monitored) async getBeneficiaries(): Promise> { const token = await this.getToken();