From 51d533f133d76c6dff3f277d3d0ebd3c9ad52176 Mon Sep 17 00:00:00 2001 From: Sergei Date: Sat, 24 Jan 2026 20:50:40 -0800 Subject: [PATCH] feat: Validate Deployment ID through API before saving - Add validateDeploymentId() method in api.ts that checks if ID exists in user's deployments list - Update profile.tsx to validate deployment ID before saving - Show validation error message if ID is invalid - Display deployment name alongside ID after validation - Add loading state during validation --- app/(tabs)/profile.tsx | 74 ++++++++++++++++++++++++++++++++++++------ services/api.ts | 44 +++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 10 deletions(-) diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx index 8623202..022faf1 100644 --- a/app/(tabs)/profile.tsx +++ b/app/(tabs)/profile.tsx @@ -54,15 +54,23 @@ function MenuItem({ export default function ProfileScreen() { const { user, logout } = useAuth(); const [deploymentId, setDeploymentId] = useState(''); + const [deploymentName, setDeploymentName] = useState(''); const [showDeploymentModal, setShowDeploymentModal] = useState(false); const [tempDeploymentId, setTempDeploymentId] = useState(''); + const [isValidating, setIsValidating] = useState(false); + const [validationError, setValidationError] = useState(null); - // Load saved deployment ID + // Load saved deployment ID and validate to get name useEffect(() => { const loadDeploymentId = async () => { const saved = await api.getDeploymentId(); if (saved) { setDeploymentId(saved); + // Validate to get the deployment name + const result = await api.validateDeploymentId(saved); + if (result.ok && result.data?.valid && result.data.name) { + setDeploymentName(result.data.name); + } } }; loadDeploymentId(); @@ -70,19 +78,39 @@ export default function ProfileScreen() { const openDeploymentModal = useCallback(() => { setTempDeploymentId(deploymentId); + setValidationError(null); setShowDeploymentModal(true); }, [deploymentId]); const saveDeploymentId = useCallback(async () => { const trimmed = tempDeploymentId.trim(); + setValidationError(null); + if (trimmed) { - await api.setDeploymentId(trimmed); - setDeploymentId(trimmed); + setIsValidating(true); + try { + const result = await api.validateDeploymentId(trimmed); + if (result.ok && result.data?.valid) { + await api.setDeploymentId(trimmed); + setDeploymentId(trimmed); + setDeploymentName(result.data.name || ''); + setShowDeploymentModal(false); + } else if (result.ok && !result.data?.valid) { + setValidationError('Invalid Deployment ID. Please check and try again.'); + } else { + setValidationError(result.error?.message || 'Failed to validate Deployment ID'); + } + } catch { + setValidationError('Network error. Please try again.'); + } finally { + setIsValidating(false); + } } else { await api.clearDeploymentId(); setDeploymentId(''); + setDeploymentName(''); + setShowDeploymentModal(false); } - setShowDeploymentModal(false); }, [tempDeploymentId]); const openTerms = () => { @@ -139,7 +167,7 @@ export default function ProfileScreen() { @@ -189,26 +217,37 @@ export default function ProfileScreen() { Enter the deployment ID to connect to a specific device. Leave empty for automatic detection. { + setTempDeploymentId(text); + setValidationError(null); + }} keyboardType="numeric" autoFocus + editable={!isValidating} /> + {validationError && ( + {validationError} + )} setShowDeploymentModal(false)} + disabled={isValidating} > - Cancel + Cancel - Save + + {isValidating ? 'Validating...' : 'Save'} + @@ -376,6 +415,15 @@ const styles = StyleSheet.create({ borderColor: AppColors.border, marginBottom: Spacing.md, }, + modalInputError: { + borderColor: AppColors.error, + marginBottom: Spacing.xs, + }, + errorText: { + color: AppColors.error, + fontSize: FontSizes.sm, + marginBottom: Spacing.md, + }, modalButtons: { flexDirection: 'row', justifyContent: 'flex-end', @@ -400,4 +448,10 @@ const styles = StyleSheet.create({ fontWeight: '600', color: AppColors.white, }, + modalButtonDisabled: { + backgroundColor: AppColors.textMuted, + }, + disabledText: { + opacity: 0.5, + }, }); diff --git a/services/api.ts b/services/api.ts index b1982e7..6d811eb 100644 --- a/services/api.ts +++ b/services/api.ts @@ -215,6 +215,50 @@ class ApiService { await SecureStore.deleteItemAsync('deploymentId'); } + async validateDeploymentId(deploymentId: string): Promise> { + const token = await this.getToken(); + const userName = await this.getUserName(); + + if (!token || !userName) { + return { ok: false, error: { message: 'Not authenticated', code: 'UNAUTHORIZED' } }; + } + + const response = await this.makeRequest<{ result_list: Array<{ + deployment_id: number; + email: string; + first_name: string; + last_name: string; + }> }>({ + function: 'deployments_list', + user_name: userName, + token: token, + first: '0', + last: '100', + }); + + if (!response.ok || !response.data?.result_list) { + return { ok: false, error: response.error || { message: 'Failed to validate deployment ID' } }; + } + + const deploymentIdNum = parseInt(deploymentId, 10); + const deployment = response.data.result_list.find(item => item.deployment_id === deploymentIdNum); + + if (deployment) { + return { + ok: true, + data: { + valid: true, + name: `${deployment.first_name} ${deployment.last_name}`.trim(), + }, + }; + } + + return { + ok: true, + data: { valid: false }, + }; + } + // Beneficiaries (elderly people being monitored) async getBeneficiaries(): Promise> { const token = await this.getToken();