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
This commit is contained in:
parent
9ae23cfef3
commit
51d533f133
@ -54,15 +54,23 @@ function MenuItem({
|
||||
export default function ProfileScreen() {
|
||||
const { user, logout } = useAuth();
|
||||
const [deploymentId, setDeploymentId] = useState<string>('');
|
||||
const [deploymentName, setDeploymentName] = useState<string>('');
|
||||
const [showDeploymentModal, setShowDeploymentModal] = useState(false);
|
||||
const [tempDeploymentId, setTempDeploymentId] = useState('');
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
const [validationError, setValidationError] = useState<string | null>(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) {
|
||||
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);
|
||||
}
|
||||
}, [tempDeploymentId]);
|
||||
|
||||
const openTerms = () => {
|
||||
@ -139,7 +167,7 @@ export default function ProfileScreen() {
|
||||
<MenuItem
|
||||
icon="server-outline"
|
||||
title="Deployment ID"
|
||||
subtitle={deploymentId || 'Not set (auto)'}
|
||||
subtitle={deploymentId ? `${deploymentId}${deploymentName ? ` (${deploymentName})` : ''}` : 'Not set (auto)'}
|
||||
onPress={openDeploymentModal}
|
||||
/>
|
||||
</View>
|
||||
@ -189,26 +217,37 @@ export default function ProfileScreen() {
|
||||
Enter the deployment ID to connect to a specific device. Leave empty for automatic detection.
|
||||
</Text>
|
||||
<TextInput
|
||||
style={styles.modalInput}
|
||||
style={[styles.modalInput, validationError && styles.modalInputError]}
|
||||
placeholder="e.g., 21"
|
||||
placeholderTextColor={AppColors.textMuted}
|
||||
value={tempDeploymentId}
|
||||
onChangeText={setTempDeploymentId}
|
||||
onChangeText={(text) => {
|
||||
setTempDeploymentId(text);
|
||||
setValidationError(null);
|
||||
}}
|
||||
keyboardType="numeric"
|
||||
autoFocus
|
||||
editable={!isValidating}
|
||||
/>
|
||||
{validationError && (
|
||||
<Text style={styles.errorText}>{validationError}</Text>
|
||||
)}
|
||||
<View style={styles.modalButtons}>
|
||||
<TouchableOpacity
|
||||
style={styles.modalButtonCancel}
|
||||
onPress={() => setShowDeploymentModal(false)}
|
||||
disabled={isValidating}
|
||||
>
|
||||
<Text style={styles.modalButtonCancelText}>Cancel</Text>
|
||||
<Text style={[styles.modalButtonCancelText, isValidating && styles.disabledText]}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={styles.modalButtonSave}
|
||||
style={[styles.modalButtonSave, isValidating && styles.modalButtonDisabled]}
|
||||
onPress={saveDeploymentId}
|
||||
disabled={isValidating}
|
||||
>
|
||||
<Text style={styles.modalButtonSaveText}>Save</Text>
|
||||
<Text style={styles.modalButtonSaveText}>
|
||||
{isValidating ? 'Validating...' : 'Save'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@ -215,6 +215,50 @@ class ApiService {
|
||||
await SecureStore.deleteItemAsync('deploymentId');
|
||||
}
|
||||
|
||||
async validateDeploymentId(deploymentId: string): Promise<ApiResponse<{ valid: boolean; name?: string }>> {
|
||||
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<ApiResponse<{ beneficiaries: Beneficiary[] }>> {
|
||||
const token = await this.getToken();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user