feat: Add biometric verification and restore onboarding flow

This commit is contained in:
2026-01-19 03:19:12 +03:00
parent c7eab6d78a
commit 28b4e1d09e
+119 -37
View File
@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { import {
View, View,
Text, Text,
@@ -7,10 +7,13 @@ import {
SafeAreaView, SafeAreaView,
StatusBar, StatusBar,
Animated, Animated,
Platform,
Alert,
} from 'react-native'; } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { KurdistanColors } from '../theme/colors'; import { KurdistanColors } from '../theme/colors';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import * as LocalAuthentication from 'expo-local-authentication';
const HUMAN_VERIFIED_KEY = '@pezkuwi_human_verified'; const HUMAN_VERIFIED_KEY = '@pezkuwi_human_verified';
@@ -20,11 +23,53 @@ interface VerifyHumanScreenProps {
const VerifyHumanScreen: React.FC<VerifyHumanScreenProps> = ({ onVerified }) => { const VerifyHumanScreen: React.FC<VerifyHumanScreenProps> = ({ onVerified }) => {
const [isChecked, setIsChecked] = useState(false); const [isChecked, setIsChecked] = useState(false);
const [isBiometricSupported, setIsBiometricSupported] = useState(false);
const [scaleValue] = useState(new Animated.Value(1)); const [scaleValue] = useState(new Animated.Value(1));
const handleVerify = async () => { // Check biometric support on mount
if (!isChecked) return; useEffect(() => {
checkBiometricSupport();
}, []);
const checkBiometricSupport = async () => {
try {
const compatible = await LocalAuthentication.hasHardwareAsync();
const enrolled = await LocalAuthentication.isEnrolledAsync();
setIsBiometricSupported(compatible && enrolled);
} catch (error) {
console.warn('Biometric check failed:', error);
}
};
const handleBiometricAuth = async () => {
try {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Verify you are human',
fallbackLabel: 'Use Passcode',
cancelLabel: 'Cancel',
disableDeviceFallback: false,
});
if (result.success) {
completeVerification();
} else {
// Only show alert if it wasn't a user cancellation
if (result.error !== 'user_cancel') {
Alert.alert('Verification Failed', 'Could not verify identity. Please try again.');
}
}
} catch (error) {
console.error('Biometric auth error:', error);
Alert.alert('Error', 'Biometric authentication unavailable');
}
};
const handleManualVerify = () => {
if (!isChecked) return;
completeVerification();
};
const completeVerification = async () => {
// Save verification status // Save verification status
try { try {
await AsyncStorage.setItem(HUMAN_VERIFIED_KEY, 'true'); await AsyncStorage.setItem(HUMAN_VERIFIED_KEY, 'true');
@@ -69,48 +114,65 @@ const VerifyHumanScreen: React.FC<VerifyHumanScreenProps> = ({ onVerified }) =>
</View> </View>
{/* Title */} {/* Title */}
<Text style={styles.title}>Security Verification</Text> <Text style={styles.title}>Identity Verification</Text>
<Text style={styles.subtitle}> <Text style={styles.subtitle}>
Please confirm you are human to continue Please confirm your identity to continue
</Text> </Text>
{/* Verification Box */} {/* Biometric Button (Primary) */}
<TouchableOpacity {isBiometricSupported ? (
style={styles.verificationBox} <TouchableOpacity
onPress={toggleCheck} style={styles.biometricButton}
activeOpacity={0.8} onPress={handleBiometricAuth}
> activeOpacity={0.8}
<View style={[styles.checkbox, isChecked && styles.checkboxChecked]}> >
{isChecked && <Text style={styles.checkmark}></Text>} <Text style={styles.biometricIcon}>👆</Text>
</View> <Text style={styles.biometricText}>Verify with Biometrics</Text>
<Text style={styles.verificationText}> </TouchableOpacity>
I&apos;m not a robot ) : (
</Text> /* Fallback to Manual Checkbox */
</TouchableOpacity> <TouchableOpacity
style={styles.verificationBox}
onPress={toggleCheck}
activeOpacity={0.8}
>
<View style={[styles.checkbox, isChecked && styles.checkboxChecked]}>
{isChecked && <Text style={styles.checkmark}></Text>}
</View>
<Text style={styles.verificationText}>
I&apos;m not a robot
</Text>
</TouchableOpacity>
)}
{/* Info Text */} {/* Info Text */}
<Text style={styles.infoText}> <Text style={styles.infoText}>
This helps protect the Pezkuwi network from automated attacks {isBiometricSupported
? "Secure hardware-backed verification"
: "This helps protect the Pezkuwi network from automated attacks"
}
</Text> </Text>
{/* Continue Button */} {/* Continue Button (Only for manual fallback) */}
<TouchableOpacity {!isBiometricSupported && (
style={[styles.continueButton, !isChecked && styles.continueButtonDisabled]} <TouchableOpacity
onPress={handleVerify} style={[styles.continueButton, !isChecked && styles.continueButtonDisabled]}
disabled={!isChecked} onPress={handleManualVerify}
activeOpacity={0.8} disabled={!isChecked}
> activeOpacity={0.8}
<Animated.View style={{ transform: [{ scale: scaleValue }] }}> >
<Text <Animated.View style={{ transform: [{ scale: scaleValue }] }}>
style={[ <Text
styles.continueButtonText, style={[
!isChecked && styles.continueButtonTextDisabled, styles.continueButtonText,
]} !isChecked && styles.continueButtonTextDisabled,
> ]}
Continue >
</Text> Continue
</Animated.View> </Text>
</TouchableOpacity> </Animated.View>
</TouchableOpacity>
)}
{/* Footer */} {/* Footer */}
<View style={styles.footer}> <View style={styles.footer}>
@@ -166,6 +228,26 @@ const styles = StyleSheet.create({
opacity: 0.9, opacity: 0.9,
marginBottom: 40, marginBottom: 40,
}, },
biometricButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: KurdistanColors.spi,
borderRadius: 16,
paddingVertical: 20,
paddingHorizontal: 32,
marginBottom: 20,
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.2)',
elevation: 8,
},
biometricIcon: {
fontSize: 24,
marginRight: 12,
},
biometricText: {
fontSize: 18,
fontWeight: 'bold',
color: KurdistanColors.kesk,
},
verificationBox: { verificationBox: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',