import { useState, useEffect, useCallback } from 'react' import { View, Text, TextInput, TouchableOpacity, KeyboardAvoidingView, Platform, ActivityIndicator, Alert, StyleSheet, } from 'react-native' import * as WebBrowser from 'expo-web-browser' import * as Linking from 'expo-linking' import { useAuthStore } from '@/lib/store' import { API_URL } from '@/lib/config' import { C } from '@/lib/theme' WebBrowser.maybeCompleteAuthSession() export default function LoginScreen() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [showPwd, setShowPwd] = useState(false) const [loading, setLoading] = useState(false) const [googleLoading, setGoogleLoading] = useState(false) const { login, loginWithToken } = useAuthStore() // Traitement du deep link OAuth — mémorisé pour éviter les re-créations const handleOAuthCallback = useCallback(async (url: string) => { const parsed = Linking.parse(url) // memento://auth?token=... → scheme='memento', hostname='auth', path='' if (parsed.scheme !== 'memento' || parsed.hostname !== 'auth') return const p = parsed.queryParams as Record | undefined if (p?.error) { Alert.alert('Connexion Google échouée', p.error === 'unauthorized' ? 'Non autorisé' : 'Erreur serveur') setGoogleLoading(false) return } if (p?.token && p?.id) { await loginWithToken(p.token, { id: p.id, name: p.name || null, email: p.email || '', tier: p.tier || 'FREE', }) } setGoogleLoading(false) }, [loginWithToken]) // Écoute le deep link (Android + app lancée via deep link) useEffect(() => { const sub = Linking.addEventListener('url', (event) => handleOAuthCallback(event.url)) Linking.getInitialURL().then((url) => { if (url) handleOAuthCallback(url) }) return () => sub.remove() }, [handleOAuthCallback]) const handleLogin = async () => { if (!email.trim() || !password.trim()) return setLoading(true) try { await login(email.trim().toLowerCase(), password) } catch (e: any) { Alert.alert( 'Connexion échouée', e.message?.includes('Identifiants invalides') ? 'Email ou mot de passe incorrect.\n\nSi vous vous êtes inscrit avec Google, utilisez le bouton Google ci-dessous.' : e.message ) } finally { setLoading(false) } } const handleGoogle = async () => { setGoogleLoading(true) try { const url = `${API_URL}/api/mobile/auth/google-start` const result = await WebBrowser.openAuthSessionAsync(url, 'memento://auth') if (result.type === 'success') { // iOS : le deep link est capturé dans result.url, pas via Linking.addEventListener await handleOAuthCallback(result.url) } else { // Annulé par l'utilisateur setGoogleLoading(false) } } catch (e: any) { Alert.alert('Erreur', e.message) setGoogleLoading(false) } } return ( {/* Logo */} Memento Votre espace de connaissance {/* Bouton Google */} {googleLoading ? : <> G Continuer avec Google } {/* Séparateur */} ou {/* Formulaire email/password */} Email Mot de passe setShowPwd(!showPwd)} style={s.eyeBtn}> {showPwd ? '🙈' : '👁️'} {loading ? : Se connecter} memento-note.com ) } const s = StyleSheet.create({ container: { flex: 1, backgroundColor: C.paper }, inner: { flex: 1, justifyContent: 'center', paddingHorizontal: 32 }, logoBlock: { marginBottom: 40, alignItems: 'center' }, logo: { fontSize: 36, fontStyle: 'italic', color: C.ink, fontWeight: '600' }, tagline: { fontSize: 14, color: C.concrete, marginTop: 4 }, googleBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10, backgroundColor: C.white, borderWidth: 1.5, borderColor: C.border, borderRadius: 12, paddingVertical: 14, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.06, shadowRadius: 3, }, googleG: { fontSize: 18, fontWeight: '700', color: '#4285F4' }, googleText: { fontSize: 15, fontWeight: '600', color: C.ink }, sep: { flexDirection: 'row', alignItems: 'center', marginVertical: 20, gap: 10 }, sepLine: { flex: 1, height: 1, backgroundColor: C.border }, sepText: { fontSize: 12, color: C.concrete, fontWeight: '500' }, form: {}, label: { fontSize: 11, fontWeight: '700', letterSpacing: 1.5, textTransform: 'uppercase', color: C.concrete, marginBottom: 6 }, input: { borderWidth: 1, borderColor: C.border, borderRadius: 12, paddingHorizontal: 16, paddingVertical: 13, fontSize: 15, color: C.ink, backgroundColor: C.white }, pwdRow: { flexDirection: 'row', alignItems: 'center' }, eyeBtn: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center', backgroundColor: C.white, borderWidth: 1, borderColor: C.border, borderRadius: 12 }, eyeIcon: { fontSize: 18 }, btn: { marginTop: 20, backgroundColor: C.ink, borderRadius: 12, paddingVertical: 14, alignItems: 'center' }, btnDisabled: { opacity: 0.4 }, btnText: { color: C.white, fontWeight: '600', fontSize: 15 }, footer: { textAlign: 'center', fontSize: 12, color: C.concrete, marginTop: 32 }, })