Connexion « Se souvenir de moi » et ajustements panneau Totaux
- Login : case à cocher « Se souvenir de moi » sous le champ email ; mémorisation de l'identifiant (email uniquement) dans le localStorage du navigateur, pré-remplissage à la visite suivante sur le même navigateur, effacement si décochée ; erreurs de stockage ignorées - Panneau Totaux : « Total du mois » en gras et « Total annuel partiel » non gras ; « Remboursement indicatif » renommé « Remboursement indicatif pour ce mois » ; « Montant prévisionnel annuel » non gras - Steering : nouvelle règle imposant les réponses en français - Mise à jour requirements.md (Req 17 + glossaire), design-requirements.md (Req 17 technique) et design.md (LoginComponent, PanneauTotaux)
This commit is contained in:
@@ -30,6 +30,11 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="champ-case">
|
||||
<input id="seSouvenir" type="checkbox" formControlName="seSouvenir" />
|
||||
<label for="seSouvenir">Se souvenir de moi</label>
|
||||
</div>
|
||||
|
||||
<div class="champ">
|
||||
<label for="motDePasse">Mot de passe</label>
|
||||
<app-password-input
|
||||
|
||||
@@ -38,6 +38,24 @@ h1 {
|
||||
border-color: #c62828;
|
||||
}
|
||||
|
||||
.champ-case {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.champ-case input {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.champ-case label {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.erreur {
|
||||
color: #c62828;
|
||||
margin-top: 0.25rem;
|
||||
|
||||
@@ -9,6 +9,9 @@ import { PasswordInputComponent } from '../../../shared/components/password-inpu
|
||||
import { NotificationService } from '../../../core/services/notification.service';
|
||||
import { LoginDto } from '../../../core/models';
|
||||
|
||||
/** Clé localStorage de l'identifiant mémorisé via « Se souvenir de moi ». */
|
||||
const CLE_EMAIL_MEMORISE = 'schotziad.login.emailMemorise';
|
||||
|
||||
/**
|
||||
* Écran de connexion (`/login`) — Etape de session de l'authentification.
|
||||
*
|
||||
@@ -18,6 +21,14 @@ import { LoginDto } from '../../../core/models';
|
||||
* pas encore configuré. Sur 401, un message générique « Identifiants invalides. »
|
||||
* est affiché (Exigence 2.2).
|
||||
*
|
||||
* Case « Se souvenir de moi » : lorsqu'elle est cochée au moment d'une connexion
|
||||
* réussie, l'identifiant (email) est stocké dans le `localStorage` du navigateur
|
||||
* et pré-rempli aux prochaines visites sur cette même machine/navigateur. Sur un
|
||||
* autre poste ou navigateur, aucun pré-remplissage n'a lieu tant qu'une connexion
|
||||
* « se souvenir » n'y a pas été faite. Si la case est décochée lors d'une
|
||||
* connexion, l'identifiant mémorisé est effacé et devra être ressaisi ensuite.
|
||||
* Le mot de passe n'est jamais stocké.
|
||||
*
|
||||
* Contrainte zoneless : tout état async est exposé via des signals
|
||||
* (`soumissionEnCours`, `erreur`, `compteExiste`).
|
||||
*
|
||||
@@ -51,9 +62,12 @@ export class LoginComponent implements OnInit {
|
||||
readonly compteExiste = signal(true);
|
||||
|
||||
ngOnInit(): void {
|
||||
const emailMemorise = this.lireEmailMemorise();
|
||||
|
||||
this.form = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
motDePasse: ['', [Validators.required, Validators.minLength(8)]]
|
||||
email: [emailMemorise ?? '', [Validators.required, Validators.email]],
|
||||
motDePasse: ['', [Validators.required, Validators.minLength(8)]],
|
||||
seSouvenir: [emailMemorise !== null]
|
||||
});
|
||||
|
||||
this.authService.compteExiste().subscribe({
|
||||
@@ -78,8 +92,9 @@ export class LoginComponent implements OnInit {
|
||||
}
|
||||
|
||||
const valeur = this.form.getRawValue();
|
||||
const email = (valeur.email ?? '').trim();
|
||||
const dto: LoginDto = {
|
||||
email: (valeur.email ?? '').trim(),
|
||||
email,
|
||||
motDePasse: valeur.motDePasse ?? ''
|
||||
};
|
||||
|
||||
@@ -88,6 +103,8 @@ export class LoginComponent implements OnInit {
|
||||
this.authService.login(dto).subscribe({
|
||||
next: () => {
|
||||
this.soumissionEnCours.set(false);
|
||||
// Mémorisation de l'identifiant selon le choix « Se souvenir de moi ».
|
||||
this.appliquerMemorisation(valeur.seSouvenir === true, email);
|
||||
// La cible protégée oriente ensuite vers l'onboarding si nécessaire.
|
||||
this.router.navigate(['/consultation']);
|
||||
},
|
||||
@@ -105,4 +122,33 @@ export class LoginComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit l'identifiant mémorisé dans le `localStorage`, ou `null` si absent ou
|
||||
* si le stockage est indisponible (mode privé, quota, etc.).
|
||||
*/
|
||||
private lireEmailMemorise(): string | null {
|
||||
try {
|
||||
const valeur = localStorage.getItem(CLE_EMAIL_MEMORISE);
|
||||
return valeur && valeur.trim() !== '' ? valeur : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stocke l'identifiant si « Se souvenir de moi » est coché, sinon l'efface.
|
||||
* Toute erreur d'accès au stockage est ignorée silencieusement.
|
||||
*/
|
||||
private appliquerMemorisation(seSouvenir: boolean, email: string): void {
|
||||
try {
|
||||
if (seSouvenir && email !== '') {
|
||||
localStorage.setItem(CLE_EMAIL_MEMORISE, email);
|
||||
} else {
|
||||
localStorage.removeItem(CLE_EMAIL_MEMORISE);
|
||||
}
|
||||
} catch {
|
||||
// Stockage indisponible : la mémorisation est simplement sans effet.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
<dt>Total du jour</dt>
|
||||
<dd>{{ (totaux()?.totalJour ?? 0) | number: formatKm() }} km</dd>
|
||||
</div>
|
||||
<div class="panneau-totaux__ligne">
|
||||
<div class="panneau-totaux__ligne panneau-totaux__ligne--fort">
|
||||
<dt>Total du mois</dt>
|
||||
<dd>{{ (totaux()?.totalMois ?? 0) | number: formatKm() }} km</dd>
|
||||
</div>
|
||||
<div class="panneau-totaux__ligne panneau-totaux__ligne--fort">
|
||||
<div class="panneau-totaux__ligne">
|
||||
<dt>Total annuel partiel</dt>
|
||||
<dd>{{ (totaux()?.totalAnnuelPartiel ?? 0) | number: formatKm() }} km</dd>
|
||||
</div>
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="panneau-totaux__previsionnel">
|
||||
<div class="panneau-totaux__ligne panneau-totaux__ligne--fort">
|
||||
<span class="panneau-totaux__previsionnel-libelle">
|
||||
Remboursement indicatif
|
||||
Remboursement indicatif pour ce mois
|
||||
<button
|
||||
type="button"
|
||||
class="panneau-totaux__aide"
|
||||
@@ -44,7 +44,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="panneau-totaux__previsionnel">
|
||||
<div class="panneau-totaux__ligne panneau-totaux__ligne--fort">
|
||||
<div class="panneau-totaux__ligne">
|
||||
<span class="panneau-totaux__previsionnel-libelle">
|
||||
Montant prévisionnel annuel
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user