From 28b4e1d09e498c92cfc2cb2316976203c277437a Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Mon, 19 Jan 2026 03:19:12 +0300 Subject: [PATCH] feat: Add biometric verification and restore onboarding flow --- mobile/src/screens/VerifyHumanScreen.tsx | 156 +++++++++++++++++------ 1 file changed, 119 insertions(+), 37 deletions(-) diff --git a/mobile/src/screens/VerifyHumanScreen.tsx b/mobile/src/screens/VerifyHumanScreen.tsx index 449c7b42..2daaa28a 100644 --- a/mobile/src/screens/VerifyHumanScreen.tsx +++ b/mobile/src/screens/VerifyHumanScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { View, Text, @@ -7,10 +7,13 @@ import { SafeAreaView, StatusBar, Animated, + Platform, + Alert, } from 'react-native'; import { LinearGradient } from 'expo-linear-gradient'; import { KurdistanColors } from '../theme/colors'; import AsyncStorage from '@react-native-async-storage/async-storage'; +import * as LocalAuthentication from 'expo-local-authentication'; const HUMAN_VERIFIED_KEY = '@pezkuwi_human_verified'; @@ -20,11 +23,53 @@ interface VerifyHumanScreenProps { const VerifyHumanScreen: React.FC = ({ onVerified }) => { const [isChecked, setIsChecked] = useState(false); + const [isBiometricSupported, setIsBiometricSupported] = useState(false); const [scaleValue] = useState(new Animated.Value(1)); - const handleVerify = async () => { - if (!isChecked) return; + // Check biometric support on mount + 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 try { await AsyncStorage.setItem(HUMAN_VERIFIED_KEY, 'true'); @@ -69,48 +114,65 @@ const VerifyHumanScreen: React.FC = ({ onVerified }) => {/* Title */} - Security Verification + Identity Verification - Please confirm you are human to continue + Please confirm your identity to continue - {/* Verification Box */} - - - {isChecked && } - - - I'm not a robot - - + {/* Biometric Button (Primary) */} + {isBiometricSupported ? ( + + 👆 + Verify with Biometrics + + ) : ( + /* Fallback to Manual Checkbox */ + + + {isChecked && } + + + I'm not a robot + + + )} {/* Info Text */} - This helps protect the Pezkuwi network from automated attacks + {isBiometricSupported + ? "Secure hardware-backed verification" + : "This helps protect the Pezkuwi network from automated attacks" + } - {/* Continue Button */} - - - - Continue - - - + {/* Continue Button (Only for manual fallback) */} + {!isBiometricSupported && ( + + + + Continue + + + + )} {/* Footer */} @@ -166,6 +228,26 @@ const styles = StyleSheet.create({ opacity: 0.9, 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: { flexDirection: 'row', alignItems: 'center',