import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, Platform, TouchableOpacity, ActivityIndicator, } from 'react-native'; import { storage } from '../../utils/storage'; import * as LocalAuthentication from 'expo-local-authentication'; import { useRouter } from 'expo-router'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { useAuth } from '../../context/AuthContext'; import { useToast } from '../../context/ToastContext'; import { useForm } from '../../hooks/useForm'; import { useAppTheme } from '../../context/ThemeContext'; import { useAppConfig } from '../../context/ConfigContext'; import { useTranslation } from '../../context/LanguageContext'; import { Image } from 'expo-image'; import { AppScreen } from '../../components/AppScreen'; import { AIInput, AIButton } from '../../components/UI'; export default function LoginScreen() { const router = useRouter(); const { signIn, isLoading } = useAuth(); const { showToast } = useToast(); const { colors, isDark } = useAppTheme(); const { config } = useAppConfig(); const { t } = useTranslation(); const { values, handleChange } = useForm({ email: '', password: '' }); const [bioCredentials, setBioCredentials] = useState<{ email: string; pass: string } | null>(null); useEffect(() => { checkBiometrics(); }, []); const checkBiometrics = async () => { if (Platform.OS === 'web') return; try { const bioEnabled = await storage.get('pref_biometrics'); if (bioEnabled === 'true') { const hasHardware = await LocalAuthentication.hasHardwareAsync(); const isEnrolled = await LocalAuthentication.isEnrolledAsync(); if (hasHardware && isEnrolled) { const email = await storage.get('saved_email'); const pass = await storage.get('saved_pass'); if (email && pass) setBioCredentials({ email, pass }); } } } catch (e) { console.warn(e); } }; const handleBiometricLogin = async () => { if (!bioCredentials) return; try { const result = await LocalAuthentication.authenticateAsync({ promptMessage: `${t('bioConfirm')} - ${config?.branding?.app_name || 'biiproject'}`, fallbackLabel: t('password'), }); if (result.success) { await signIn(bioCredentials.email, bioCredentials.pass); showToast(t('bioSuccess'), 'success'); router.replace('/(tabs)'); } } catch { showToast(t('bioFailed'), 'error'); } }; const handleLogin = async () => { if (!values.email.includes('@')) { showToast(t('invalidEmail'), 'error'); return; } try { await signIn(values.email, values.password); const bioEnabled = await storage.get('pref_biometrics'); if (bioEnabled === 'true') { await storage.save('saved_email', values.email); await storage.save('saved_pass', values.password); } showToast(`${t('welcomeBack')} ${config?.branding?.app_name || 'biiproject'}`, 'success'); router.replace('/(tabs)'); } catch (error: any) { showToast(error.message || t('loginFailed'), 'error'); } }; return ( {/* ── Header / Brand ── */} {config?.branding?.logo_url ? ( ) : ( B )} {config?.security_auth?.login_title || 'biiproject'} {config?.security_auth?.login_subtitle || t('registerSubtitle')} {/* ── Login card ── */} {t('signIn')} {/* Email */} handleChange('email', v)} keyboardType="email-address" /> {/* Password */} handleChange('password', v)} isPassword /> {/* Forgot */} router.push('/(auth)/forgot-password')} style={styles.forgotBtn}> {t('forgotPass')} {/* Actions */} {bioCredentials && ( )} {/* Social logins */} {(config?.security_auth?.oauth_google_enabled || config?.security_auth?.oauth_apple_enabled) && ( {t('orContinueWith')} {config?.security_auth?.oauth_google_enabled && ( {t('google')} )} {config?.security_auth?.oauth_apple_enabled && ( {t('apple')} )} )} {/* Register link */} {config?.features?.enable_registration && ( {t('noAccount')} router.push('/(auth)/register')}> {t('signUp')} )} ); } const styles = StyleSheet.create({ scroll: { paddingHorizontal: 24, paddingTop: 40 }, // Header header: { alignItems: 'center', marginBottom: 28 }, logoBox: { width: 80, height: 80, borderRadius: 26, alignItems: 'center', justifyContent: 'center', elevation: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.1, shadowRadius: 18, borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)', }, logoLetter: { fontSize: 48, fontFamily: 'Outfit_800ExtraBold', lineHeight: 54 }, brandName: { fontSize: 32, fontFamily: 'Outfit_800ExtraBold', marginTop: 14, letterSpacing: -0.5 }, tagline: { fontSize: 13, fontFamily: 'Outfit_400Regular', marginTop: 4 }, // Card card: { borderRadius: 28, padding: 28, borderWidth: 1 }, cardTitle: { fontSize: 24, fontFamily: 'Outfit_700Bold', marginBottom: 22 }, // Actions forgotBtn: { alignSelf: 'flex-end', marginTop: 12, marginBottom: 4 }, forgotText: { fontSize: 13, fontFamily: 'Outfit_600SemiBold' }, actionRow: { flexDirection: 'row', alignItems: 'center', marginTop: 22 }, bioBtn: { width: 56, height: 56, borderRadius: 16, alignItems: 'center', justifyContent: 'center', borderWidth: 1 }, // Social socialSection: { marginTop: 26 }, dividerRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 16 }, divider: { flex: 1, height: 1 }, dividerText: { marginHorizontal: 12, fontSize: 10, fontFamily: 'Outfit_700Bold', letterSpacing: 1 }, socialButtons: { flexDirection: 'row', gap: 10 }, socialBtn: { flex: 1, height: 52, borderRadius: 14, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', borderWidth: 1, }, socialText: { marginLeft: 8, fontSize: 14, fontFamily: 'Outfit_600SemiBold' }, // Footer footer: { flexDirection: 'row', justifyContent: 'center', marginTop: 24 }, footerText: { fontSize: 14, fontFamily: 'Outfit_400Regular' }, linkText: { fontSize: 14, fontFamily: 'Outfit_700Bold' }, });