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
+91 -9
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,12 +114,23 @@ 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) */}
{isBiometricSupported ? (
<TouchableOpacity
style={styles.biometricButton}
onPress={handleBiometricAuth}
activeOpacity={0.8}
>
<Text style={styles.biometricIcon}>👆</Text>
<Text style={styles.biometricText}>Verify with Biometrics</Text>
</TouchableOpacity>
) : (
/* Fallback to Manual Checkbox */
<TouchableOpacity <TouchableOpacity
style={styles.verificationBox} style={styles.verificationBox}
onPress={toggleCheck} onPress={toggleCheck}
@@ -87,16 +143,21 @@ const VerifyHumanScreen: React.FC<VerifyHumanScreenProps> = ({ onVerified }) =>
I&apos;m not a robot I&apos;m not a robot
</Text> </Text>
</TouchableOpacity> </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) */}
{!isBiometricSupported && (
<TouchableOpacity <TouchableOpacity
style={[styles.continueButton, !isChecked && styles.continueButtonDisabled]} style={[styles.continueButton, !isChecked && styles.continueButtonDisabled]}
onPress={handleVerify} onPress={handleManualVerify}
disabled={!isChecked} disabled={!isChecked}
activeOpacity={0.8} activeOpacity={0.8}
> >
@@ -111,6 +172,7 @@ const VerifyHumanScreen: React.FC<VerifyHumanScreenProps> = ({ onVerified }) =>
</Text> </Text>
</Animated.View> </Animated.View>
</TouchableOpacity> </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',