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() {
|
export default function ProfileScreen() {
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
const [deploymentId, setDeploymentId] = useState<string>('');
|
const [deploymentId, setDeploymentId] = useState<string>('');
|
||||||
|
const [deploymentName, setDeploymentName] = useState<string>('');
|
||||||
const [showDeploymentModal, setShowDeploymentModal] = useState(false);
|
const [showDeploymentModal, setShowDeploymentModal] = useState(false);
|
||||||
const [tempDeploymentId, setTempDeploymentId] = useState('');
|
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(() => {
|
useEffect(() => {
|
||||||
const loadDeploymentId = async () => {
|
const loadDeploymentId = async () => {
|
||||||
const saved = await api.getDeploymentId();
|
const saved = await api.getDeploymentId();
|
||||||
if (saved) {
|
if (saved) {
|
||||||
setDeploymentId(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();
|
loadDeploymentId();
|
||||||
@ -70,19 +78,39 @@ export default function ProfileScreen() {
|
|||||||
|
|
||||||
const openDeploymentModal = useCallback(() => {
|
const openDeploymentModal = useCallback(() => {
|
||||||
setTempDeploymentId(deploymentId);
|
setTempDeploymentId(deploymentId);
|
||||||
|
setValidationError(null);
|
||||||
setShowDeploymentModal(true);
|
setShowDeploymentModal(true);
|
||||||
}, [deploymentId]);
|
}, [deploymentId]);
|
||||||
|
|
||||||
const saveDeploymentId = useCallback(async () => {
|
const saveDeploymentId = useCallback(async () => {
|
||||||
const trimmed = tempDeploymentId.trim();
|
const trimmed = tempDeploymentId.trim();
|
||||||
|
setValidationError(null);
|
||||||
|
|
||||||
if (trimmed) {
|
if (trimmed) {
|
||||||
await api.setDeploymentId(trimmed);
|
setIsValidating(true);
|
||||||
setDeploymentId(trimmed);
|
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 {
|
} else {
|
||||||
await api.clearDeploymentId();
|
await api.clearDeploymentId();
|
||||||
setDeploymentId('');
|
setDeploymentId('');
|
||||||
|
setDeploymentName('');
|
||||||
|
setShowDeploymentModal(false);
|
||||||
}
|
}
|
||||||
setShowDeploymentModal(false);
|
|
||||||
}, [tempDeploymentId]);
|
}, [tempDeploymentId]);
|
||||||
|
|
||||||
const openTerms = () => {
|
const openTerms = () => {
|
||||||
@ -139,7 +167,7 @@ export default function ProfileScreen() {
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
icon="server-outline"
|
icon="server-outline"
|
||||||
title="Deployment ID"
|
title="Deployment ID"
|
||||||
subtitle={deploymentId || 'Not set (auto)'}
|
subtitle={deploymentId ? `${deploymentId}${deploymentName ? ` (${deploymentName})` : ''}` : 'Not set (auto)'}
|
||||||
onPress={openDeploymentModal}
|
onPress={openDeploymentModal}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@ -189,26 +217,37 @@ export default function ProfileScreen() {
|
|||||||
Enter the deployment ID to connect to a specific device. Leave empty for automatic detection.
|
Enter the deployment ID to connect to a specific device. Leave empty for automatic detection.
|
||||||
</Text>
|
</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.modalInput}
|
style={[styles.modalInput, validationError && styles.modalInputError]}
|
||||||
placeholder="e.g., 21"
|
placeholder="e.g., 21"
|
||||||
placeholderTextColor={AppColors.textMuted}
|
placeholderTextColor={AppColors.textMuted}
|
||||||
value={tempDeploymentId}
|
value={tempDeploymentId}
|
||||||
onChangeText={setTempDeploymentId}
|
onChangeText={(text) => {
|
||||||
|
setTempDeploymentId(text);
|
||||||
|
setValidationError(null);
|
||||||
|
}}
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
autoFocus
|
autoFocus
|
||||||
|
editable={!isValidating}
|
||||||
/>
|
/>
|
||||||
|
{validationError && (
|
||||||
|
<Text style={styles.errorText}>{validationError}</Text>
|
||||||
|
)}
|
||||||
<View style={styles.modalButtons}>
|
<View style={styles.modalButtons}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.modalButtonCancel}
|
style={styles.modalButtonCancel}
|
||||||
onPress={() => setShowDeploymentModal(false)}
|
onPress={() => setShowDeploymentModal(false)}
|
||||||
|
disabled={isValidating}
|
||||||
>
|
>
|
||||||
<Text style={styles.modalButtonCancelText}>Cancel</Text>
|
<Text style={[styles.modalButtonCancelText, isValidating && styles.disabledText]}>Cancel</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.modalButtonSave}
|
style={[styles.modalButtonSave, isValidating && styles.modalButtonDisabled]}
|
||||||
onPress={saveDeploymentId}
|
onPress={saveDeploymentId}
|
||||||
|
disabled={isValidating}
|
||||||
>
|
>
|
||||||
<Text style={styles.modalButtonSaveText}>Save</Text>
|
<Text style={styles.modalButtonSaveText}>
|
||||||
|
{isValidating ? 'Validating...' : 'Save'}
|
||||||
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@ -376,6 +415,15 @@ const styles = StyleSheet.create({
|
|||||||
borderColor: AppColors.border,
|
borderColor: AppColors.border,
|
||||||
marginBottom: Spacing.md,
|
marginBottom: Spacing.md,
|
||||||
},
|
},
|
||||||
|
modalInputError: {
|
||||||
|
borderColor: AppColors.error,
|
||||||
|
marginBottom: Spacing.xs,
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
color: AppColors.error,
|
||||||
|
fontSize: FontSizes.sm,
|
||||||
|
marginBottom: Spacing.md,
|
||||||
|
},
|
||||||
modalButtons: {
|
modalButtons: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
@ -400,4 +448,10 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: AppColors.white,
|
color: AppColors.white,
|
||||||
},
|
},
|
||||||
|
modalButtonDisabled: {
|
||||||
|
backgroundColor: AppColors.textMuted,
|
||||||
|
},
|
||||||
|
disabledText: {
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -215,6 +215,50 @@ class ApiService {
|
|||||||
await SecureStore.deleteItemAsync('deploymentId');
|
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)
|
// 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