feat: Add Deployment ID setting in Profile
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
664759dee9
commit
9ae23cfef3
@ -433,6 +433,18 @@ export default function ChatScreen() {
|
|||||||
const [beneficiaries, setBeneficiaries] = useState<Beneficiary[]>([]);
|
const [beneficiaries, setBeneficiaries] = useState<Beneficiary[]>([]);
|
||||||
const [loadingBeneficiaries, setLoadingBeneficiaries] = useState(false);
|
const [loadingBeneficiaries, setLoadingBeneficiaries] = useState(false);
|
||||||
|
|
||||||
|
// Custom deployment ID from settings
|
||||||
|
const [customDeploymentId, setCustomDeploymentId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Load custom deployment ID from settings
|
||||||
|
useEffect(() => {
|
||||||
|
const loadCustomDeploymentId = async () => {
|
||||||
|
const saved = await api.getDeploymentId();
|
||||||
|
setCustomDeploymentId(saved);
|
||||||
|
};
|
||||||
|
loadCustomDeploymentId();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Load beneficiaries
|
// Load beneficiaries
|
||||||
const loadBeneficiaries = useCallback(async () => {
|
const loadBeneficiaries = useCallback(async () => {
|
||||||
setLoadingBeneficiaries(true);
|
setLoadingBeneficiaries(true);
|
||||||
@ -500,8 +512,9 @@ export default function ChatScreen() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Build beneficiary data for the agent
|
// Build beneficiary data for the agent
|
||||||
|
// Priority: customDeploymentId from settings > currentBeneficiary > first beneficiary > fallback
|
||||||
const beneficiaryData: BeneficiaryData = {
|
const beneficiaryData: BeneficiaryData = {
|
||||||
deploymentId: currentBeneficiary?.id?.toString() || beneficiaries[0]?.id?.toString() || '21',
|
deploymentId: customDeploymentId || currentBeneficiary?.id?.toString() || beneficiaries[0]?.id?.toString() || '21',
|
||||||
beneficiaryNamesDict: {},
|
beneficiaryNamesDict: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -539,7 +552,7 @@ export default function ChatScreen() {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsConnectingVoice(false);
|
setIsConnectingVoice(false);
|
||||||
}
|
}
|
||||||
}, [isConnectingVoice, isCallActive, currentBeneficiary, beneficiaries, user, clearTranscript, startCall]);
|
}, [isConnectingVoice, isCallActive, currentBeneficiary, beneficiaries, user, clearTranscript, startCall, customDeploymentId]);
|
||||||
|
|
||||||
// End voice call
|
// End voice call
|
||||||
const endVoiceCall = useCallback(() => {
|
const endVoiceCall = useCallback(() => {
|
||||||
@ -647,8 +660,8 @@ export default function ChatScreen() {
|
|||||||
beneficiaryNamesDict[b.id.toString()] = b.name;
|
beneficiaryNamesDict[b.id.toString()] = b.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get deployment_id from current beneficiary or fallback to first one
|
// Get deployment_id: custom from settings > current beneficiary > first beneficiary > fallback
|
||||||
const deploymentId = currentBeneficiary?.id?.toString() || beneficiaries[0]?.id?.toString() || '21';
|
const deploymentId = customDeploymentId || currentBeneficiary?.id?.toString() || beneficiaries[0]?.id?.toString() || '21';
|
||||||
|
|
||||||
// Call API with EXACT same params as voice agent
|
// Call API with EXACT same params as voice agent
|
||||||
// SINGLE_DEPLOYMENT_MODE: sends only deployment_id (no beneficiary_names_dict)
|
// SINGLE_DEPLOYMENT_MODE: sends only deployment_id (no beneficiary_names_dict)
|
||||||
@ -701,7 +714,7 @@ export default function ChatScreen() {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsSending(false);
|
setIsSending(false);
|
||||||
}
|
}
|
||||||
}, [input, isSending, getWellNuoToken]);
|
}, [input, isSending, getWellNuoToken, customDeploymentId, currentBeneficiary, beneficiaries]);
|
||||||
|
|
||||||
// Render message bubble
|
// Render message bubble
|
||||||
const renderMessage = ({ item }: { item: Message }) => {
|
const renderMessage = ({ item }: { item: Message }) => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@ -6,11 +6,14 @@ import {
|
|||||||
ScrollView,
|
ScrollView,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Alert,
|
Alert,
|
||||||
|
TextInput,
|
||||||
|
Modal,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { router } from 'expo-router';
|
import { router } from 'expo-router';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { api } from '@/services/api';
|
||||||
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme';
|
||||||
|
|
||||||
interface MenuItemProps {
|
interface MenuItemProps {
|
||||||
@ -50,6 +53,37 @@ function MenuItem({
|
|||||||
|
|
||||||
export default function ProfileScreen() {
|
export default function ProfileScreen() {
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
|
const [deploymentId, setDeploymentId] = useState<string>('');
|
||||||
|
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 = () => {
|
const openTerms = () => {
|
||||||
router.push('/terms');
|
router.push('/terms');
|
||||||
@ -98,6 +132,19 @@ export default function ProfileScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Settings */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Settings</Text>
|
||||||
|
<View style={styles.menuCard}>
|
||||||
|
<MenuItem
|
||||||
|
icon="server-outline"
|
||||||
|
title="Deployment ID"
|
||||||
|
subtitle={deploymentId || 'Not set (auto)'}
|
||||||
|
onPress={openDeploymentModal}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Legal - Required for App Store */}
|
{/* Legal - Required for App Store */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>Legal</Text>
|
<Text style={styles.sectionTitle}>Legal</Text>
|
||||||
@ -127,6 +174,46 @@ export default function ProfileScreen() {
|
|||||||
{/* Version */}
|
{/* Version */}
|
||||||
<Text style={styles.version}>WellNuo v1.0.0</Text>
|
<Text style={styles.version}>WellNuo v1.0.0</Text>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
{/* Deployment ID Modal */}
|
||||||
|
<Modal
|
||||||
|
visible={showDeploymentModal}
|
||||||
|
transparent
|
||||||
|
animationType="fade"
|
||||||
|
onRequestClose={() => setShowDeploymentModal(false)}
|
||||||
|
>
|
||||||
|
<View style={styles.modalOverlay}>
|
||||||
|
<View style={styles.modalContent}>
|
||||||
|
<Text style={styles.modalTitle}>Deployment ID</Text>
|
||||||
|
<Text style={styles.modalDescription}>
|
||||||
|
Enter the deployment ID to connect to a specific device. Leave empty for automatic detection.
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.modalInput}
|
||||||
|
placeholder="e.g., 21"
|
||||||
|
placeholderTextColor={AppColors.textMuted}
|
||||||
|
value={tempDeploymentId}
|
||||||
|
onChangeText={setTempDeploymentId}
|
||||||
|
keyboardType="numeric"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<View style={styles.modalButtons}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.modalButtonCancel}
|
||||||
|
onPress={() => setShowDeploymentModal(false)}
|
||||||
|
>
|
||||||
|
<Text style={styles.modalButtonCancelText}>Cancel</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.modalButtonSave}
|
||||||
|
onPress={saveDeploymentId}
|
||||||
|
>
|
||||||
|
<Text style={styles.modalButtonSaveText}>Save</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -252,4 +339,65 @@ const styles = StyleSheet.create({
|
|||||||
color: AppColors.textMuted,
|
color: AppColors.textMuted,
|
||||||
paddingVertical: Spacing.xl,
|
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,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -198,6 +198,23 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deployment ID management
|
||||||
|
async setDeploymentId(deploymentId: string): Promise<void> {
|
||||||
|
await SecureStore.setItemAsync('deploymentId', deploymentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDeploymentId(): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
return await SecureStore.getItemAsync('deploymentId');
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearDeploymentId(): Promise<void> {
|
||||||
|
await SecureStore.deleteItemAsync('deploymentId');
|
||||||
|
}
|
||||||
|
|
||||||
// Beneficiaries (elderly people being monitored)
|
// Beneficiaries (elderly people being monitored)
|
||||||
async getBeneficiaries(): Promise<ApiResponse<{ beneficiaries: Beneficiary[] }>> {
|
async getBeneficiaries(): Promise<ApiResponse<{ beneficiaries: Beneficiary[] }>> {
|
||||||
const token = await this.getToken();
|
const token = await this.getToken();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user