/**
* @file Login.js
* @description Écran de connexion de l'application.
* Permet à l'utilisateur de s'authentifier via son adresse email et son mot de passe.
* En cas de succès, le token JWT est stocké et l'utilisateur est redirigé vers l'accueil.
*/
import React, { useState } from 'react';
import {
Text,
TextInput,
View,
StyleSheet,
TouchableOpacity,
ActivityIndicator,
KeyboardAvoidingView,
Platform,
ScrollView
} from 'react-native';
import { GlobalStyles } from "../styles/GlobalStyles";
import { useNavigation } from "@react-navigation/native";
import useAuth from "../hooks/useAuth";
import Icon from 'react-native-vector-icons/Feather';
import {EXPO_PUBLIC_API_URL} from "../config";
/**
* Écran de connexion utilisateur.
*
* @component
* @returns {React.JSX.Element} Le formulaire de connexion avec champs email, mot de passe et bouton de validation.
*/
export default function Login() {
/** @type {[string, Function]} Adresse email saisie par l'utilisateur. */
const [mail, setMail] = useState("");
/** @type {[string, Function]} Mot de passe saisi par l'utilisateur. */
const [password, setPassword] = useState("");
/** @type {[string, Function]} Message d'erreur affiché en cas d'échec de connexion. */
const [error, setError] = useState("");
/** @type {[boolean, Function]} Indicateur de chargement pendant la requête d'authentification. */
const [loading, setLoading] = useState(false);
const navigation = useNavigation();
const { login } = useAuth();
/**
* Envoie les identifiants à l'API et gère la réponse.
* En cas de succès (200), stocke le token et redirige vers l'écran Home.
* En cas d'erreur 401, affiche le message renvoyé par l'API.
*
* @async
* @returns {Promise<void>}
*/
const handleLogin = async () => {
setError(null);
setLoading(true);
try {
const abortController = new AbortController();
const response = await fetch(`${EXPO_PUBLIC_API_URL}/client/login`, {
signal: abortController.signal,
method: "POST",
headers: {"Content-Type": "application/json" },
body: JSON.stringify( { mail: mail, password: password})
});
const data = await response.json();
if (response.ok) {
const userData = { nom : data.nom };
login(data.token, userData);
setTimeout(() => {
navigation.navigate("Home");
}, 100);
} else {
if ( response.status === 401){
setError(data.message);
}
}
} catch (error) {
setError("Erreur de serveur, veuillez réssayer plus tard.");
} finally {
setLoading(false);
}
}
return (
/* Gère le clavier qui passe par-dessus les champs */
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.screen}
>
{/* Permet de scroller si l'écran est petit */}
<ScrollView contentContainerStyle={styles.scrollContainer}>
{/* En-tête */}
<View style={styles.header}>
<Text style={styles.title}>Bienvenue</Text>
<Text style={styles.subtitle}>Connectez-vous pour continuer</Text>
</View>
<View style={GlobalStyles.formWrapper}>
{/* GROUPE EMAIL (avec icône) */}
<Text style={GlobalStyles.label}>Adresse mail :</Text>
<View style={styles.inputWrapper}>
<Icon name="mail" size={20} color="#888" style={styles.inputIcon} />
<TextInput
style={styles.inputField}
placeholder={"Saisir email"}
keyboardType="email-address"
autoCapitalize="none"
value={mail}
onChangeText={(text) => setMail(text)}
/>
</View>
{/* GROUPE MOT DE PASSE (avec icône) */}
<Text style={GlobalStyles.label}>Mot de passe :</Text>
<View style={styles.inputWrapper}>
<Icon name="lock" size={20} color="#888" style={styles.inputIcon} />
<TextInput
style={styles.inputField}
placeholder={"Saisir mot de passe"}
secureTextEntry={true}
value={password}
onChangeText={(text) => setPassword(text)}
/>
</View>
{/* AFFICHAGE DE L'ERREUR */}
{error ? (
<Text style={GlobalStyles.errorText}>{error}</Text>
) : null}
{/* BOUTON — affiche un spinner pendant le chargement */}
<TouchableOpacity
style={styles.button}
onPress={handleLogin}
disabled={loading}
>
{loading ? (
<ActivityIndicator size="small" color="#ffffff" />
) : (
<Text style={styles.buttonText}>Connexion</Text>
)}
</TouchableOpacity>
</View>
</ScrollView>
</KeyboardAvoidingView>
);
}
/**
* Styles locaux de l'écran Login (thème minimaliste).
* @type {import('react-native').StyleSheet.NamedStyles<any>}
*/
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: '#FFFFFF',
},
scrollContainer: {
flexGrow: 1,
justifyContent: 'center',
padding: 30,
},
header: {
alignItems: 'flex-start',
marginBottom: 40,
},
title: {
fontSize: 42,
fontWeight: 'bold',
color: '#222222',
},
subtitle: {
fontSize: 18,
color: '#888',
marginTop: 8,
},
// --- Les champs (très minimalistes) ---
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#F6F6F6',
borderRadius: 14,
height: 60,
marginBottom: 20,
paddingHorizontal: 20,
borderWidth: 1,
borderColor: '#EFEFEF',
},
inputIcon: {
marginRight: 15,
color: '#AAAAAA',
},
inputField: {
flex: 1,
fontSize: 17,
color: '#333',
fontWeight: '500',
},
// --- Le bouton (forme pilule) ---
button: {
backgroundColor: '#2C5F2D',
height: 60,
borderRadius: 30,
alignItems: 'center',
justifyContent: 'center',
marginTop: 25,
shadowColor: '#2C5F2D',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.3,
shadowRadius: 5,
elevation: 6,
},
buttonText: {
color: '#FFFFFF',
fontSize: 18,
fontWeight: 'bold',
},
});