Nous devons créer une nouvelle page de connexion, pour permettre aux utilisateurs autorisés d’accéder à notre application. Le formulaire de connexion présent sur cette page s’assurera de vérifier les identifiants saisis par l’utilisateur, et redirigera les « bons » utilisateurs sur la page avec la liste des pokémons.
Créez donc un nouveau fichier nommé login.tsx, dans le dossier pages. Je vous préviens, la quantité de code peut vous faire peur, pourtant il s’agit d’un simple formulaire de connexion avec deux champs username et password. Ne vous laissez pas impressionner donc, il n’y a pas de raisons de paniquer. Vous avez déjà le niveau pour comprendre tout ça ! 😉
import React, { FunctionComponent, useState } from 'react';
import { useHistory } from 'react-router-dom';
import AuthenticationService from '../services/authentication-service';
type Field = {
value?: any,
error?: string,
isValid?: boolean
};
type Form = {
username: Field,
password: Field
}
const Login: FunctionComponent = () => {
const history = useHistory();
const [form, setForm] = useState<Form>({
username: { value: '' },
password: { value: '' },
});
const [message, setMessage] = useState<string>('Vous êtes déconnecté. (pikachu / pikachu)');
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
const fieldName: string = e.target.name;
const fieldValue: string = e.target.value;
const newField: Field = { [fieldName]: { value: fieldValue } };
setForm({ ...form, ...newField});
}
const validateForm = () => {
let newForm: Form = form;
// Validator username
if(form.username.value.length < 3) {
const errorMsg: string = 'Votre prénom doit faire au moins 3 caractères de long.';
const newField: Field = { value: form.username.value, error: errorMsg, isValid: false };
newForm = { ...newForm, ...{ username: newField } };
} else {
const newField: Field = { value: form.username.value, error: '', isValid: true };
newForm = { ...newForm, ...{ username: newField } };
}
// Validator password
if(form.password.value.length < 6) {
const errorMsg: string = 'Votre mot de passe doit faire au moins 6 caractères de long.';
const newField: Field = {value: form.password.value, error: errorMsg, isValid: false};
newForm = { ...newForm, ...{ password: newField } };
} else {
const newField: Field = { value: form.password.value, error: '', isValid: true };
newForm = { ...newForm, ...{ password: newField } };
}
setForm(newForm);
return newForm.username.isValid && newForm.password.isValid;
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const isFormValid = validateForm();
if(isFormValid) {
setMessage('👉 Tentative de connexion en cours ...');
AuthenticationService.login(form.username.value, form.password.value).then(isAuthenticated => {
if(!isAuthenticated) {
setMessage('🔐 Identifiant ou mot de passe incorrect.');
return;
}
history.push('/pokemons');
});
}
}
return (
<form onSubmit={(e) => handleSubmit(e)}>
<div className="row">
<div className="col s12 m8 offset-m2">
<div className="card hoverable">
<div className="card-stacked">
<div className="card-content">
{/* Form message */}
{message && <div className="form-group">
<div className="card-panel grey lighten-5">
{message}
</div>
</div>}
{/* Field username */}
<div className="form-group">
<label htmlFor="username">Identifiant</label>
<input id="username" type="text" name="username" className="form-control" value={form.username.value} onChange={e => handleInputChange(e)}></input>
{/* error */}
{form.username.error &&
<div className="card-panel red accent-1">
{form.username.error}
</div>}
</div>
{/* Field password */}
<div className="form-group">
<label htmlFor="password">Mot de passe</label>
<input id="password" type="password" name="password" className="form-control" value={form.password.value} onChange={e => handleInputChange(e)}></input>
{/* error */}
{form.password.error &&
<div className="card-panel red accent-1">
{form.password.error}
</div>}
</div>
</div>
<div className="card-action center">
{/* Submit button */}
<button type="submit" className="btn">Valider</button>
</div>
</div>
</div>
</div>
</div>
</form>
);
};
export default Login;
Bien que le code de ce composant semble interminable, je vous assure qu’il s’agit simplement d’une page affichant un formulaire de connexion. Si vous prenez le temps de regarder ce code, vous verrez qu’il n’y pas de concepts nouveaux et que vous êtes déjà capable de comprendre ce code. Cependant, je vous donne tout de même quelques explications pour vous présenter comment je vois les choses :
Après s’être authentifié correctement, l’utilisateur sera redirigé vers la liste des pokémons. C’est parfait ! 👍
Pour terminer, il faut bien sûr ajouter notre nouvelle page de connexion login auprès de notre routeur React, pour qu’il soit au courant de cette nouvelle route. Ajoutez donc notre nouvelle route de connexion dans le composant racine App.tsx :
// Les autres importations.
import Login from './pages/login';
const App: FunctionComponent = () => {
return (
<Router>
...
<Switch>
<Route exact path="/" component={PokemonsList} />
<Route exact path="/login" component={Login} />
<Route exact path="/pokemons" component={PokemonsList} />
<Route exact path="/pokemon/add" component={PokemonAdd} />
<Route exact path="/pokemons/edit/:id" component={PokemonEdit} />
<Route path="/pokemons/:id" component={PokemonsDetail} />
<Route component={PageNotFound} />
</Switch>
</div>
</Router>
);
}
export default App;
Maintenant, essayer d’accéder à votre application.
Vous serez alors redirigé vers notre liste de pokémons, au lieu du formulaire de connexion … 🤔
En fait, c’est normal, car par défaut notre liste de pokémons n’est pas protégée. Donc que l’utilisateur soit connecté ou non ne change rien au fonctionnement de notre application. Pour accéder à notre formulaire de connexion, il faut saisir manuellement la route « /login » dans votre navigateur. Ce n’est pas vraiment ce que nous voulons.
Pour résumé, on a un service d’authentification fonctionnel, un formulaire de connexion qui tourne plutôt bien, mais… les autres routes de notre application ne sont pas encore protégées. Encore un peut de boulot donc !