Le dépôt sortait 7 erreurs eslint au début de cette série de correctifs, dont certaines antérieures. Il n'en reste aucune. - Dashboard et Users appelaient setState de façon synchrone dans un effet, ce qui provoque des rendus en cascade. Le chargement initial passe par une fonction asynchrone qui n'écrit qu'après l'await, avec annulation au démontage. Sur le Dashboard, l'indicateur de chargement est allumé par les gestionnaires d'événement, et le bouton Actualiser relance l'effet via un compteur plutôt qu'en appelant le chargement. - L'import ExternalLink de Documentation.jsx n'était pas utilisé. - AuthContext.jsx exportait à la fois un composant, le contexte et un hook, ce qui désactivait le rafraîchissement à chaud de Vite pour ce fichier. Le contexte et le hook vivent désormais dans leurs propres modules ; les 5 fichiers concernés sont mis à jour.
118 lines
3.8 KiB
React
118 lines
3.8 KiB
React
/* Page de connexion : formulaire avec authentification JWT + captcha anti-bot */
|
|
|
|
import { useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useAuth } from '../../context/useAuth';
|
|
import styles from './Login.module.css';
|
|
|
|
/* Génère deux entiers aléatoires pour le captcha additif */
|
|
function genererCaptcha() {
|
|
return {
|
|
x: Math.floor(Math.random() * 10) + 1,
|
|
y: Math.floor(Math.random() * 10) + 1,
|
|
};
|
|
}
|
|
|
|
export default function Login() {
|
|
const [identifiant, setIdentifiant] = useState('');
|
|
const [motDePasse, setMotDePasse] = useState('');
|
|
const [captcha, setCaptcha] = useState(() => genererCaptcha());
|
|
const [reponseCaptcha, setReponseCaptcha] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [erreur, setErreur] = useState('');
|
|
|
|
const { login } = useAuth();
|
|
const navigate = useNavigate();
|
|
|
|
async function handleSubmit(e) {
|
|
e.preventDefault();
|
|
setErreur('');
|
|
|
|
/* Vérification anti-bot du captcha avant tout appel réseau */
|
|
if (parseInt(reponseCaptcha, 10) !== captcha.x + captcha.y) {
|
|
setErreur('Réponse au captcha incorrecte.');
|
|
setCaptcha(genererCaptcha());
|
|
setReponseCaptcha('');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
await login(identifiant, motDePasse);
|
|
navigate('/dashboard');
|
|
} catch (err) {
|
|
/* Message générique en cas d'identifiants invalides ou d'erreur serveur */
|
|
setErreur(err.message === 'Identifiants invalides' ? 'Identifiants invalides' : (err.message || 'Identifiants invalides'));
|
|
setCaptcha(genererCaptcha());
|
|
setReponseCaptcha('');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className={styles.page}>
|
|
<div className={styles.card}>
|
|
<div className={styles.logoArea}>
|
|
<span className={styles.logoIcon}>▶</span>
|
|
<h1 className={styles.logoText}>Data Sentinel</h1>
|
|
<p className={styles.byXefi}>by XEFI</p>
|
|
</div>
|
|
|
|
<form className={styles.form} onSubmit={handleSubmit}>
|
|
<div className={styles.field}>
|
|
<label className={styles.label} htmlFor="identifiant">Identifiant</label>
|
|
<input
|
|
id="identifiant"
|
|
type="text"
|
|
className={styles.input}
|
|
value={identifiant}
|
|
onChange={e => setIdentifiant(e.target.value)}
|
|
placeholder="Votre identifiant"
|
|
autoComplete="username"
|
|
/>
|
|
</div>
|
|
|
|
<div className={styles.field}>
|
|
<label className={styles.label} htmlFor="motDePasse">Mot de passe</label>
|
|
<input
|
|
id="motDePasse"
|
|
type="password"
|
|
className={styles.input}
|
|
value={motDePasse}
|
|
onChange={e => setMotDePasse(e.target.value)}
|
|
placeholder="••••••••"
|
|
autoComplete="current-password"
|
|
/>
|
|
</div>
|
|
|
|
<div className={styles.field}>
|
|
<label className={styles.label} htmlFor="captcha">
|
|
Vérification : combien font {captcha.x} + {captcha.y} ?
|
|
</label>
|
|
<input
|
|
id="captcha"
|
|
type="number"
|
|
className={styles.input}
|
|
value={reponseCaptcha}
|
|
onChange={e => setReponseCaptcha(e.target.value)}
|
|
placeholder="Votre réponse"
|
|
autoComplete="off"
|
|
/>
|
|
</div>
|
|
|
|
{erreur && <p className={styles.erreur}>{erreur}</p>}
|
|
|
|
<button type="submit" className={styles.btnPrimary} disabled={loading}>
|
|
{loading ? 'Connexion...' : 'Se connecter'}
|
|
</button>
|
|
</form>
|
|
|
|
<div className={styles.links}>
|
|
<a href="#" className={styles.forgotLink}>Mot de passe oublié ?</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|