ba5ad2c2ac
Design validé en brainstorming : coquille account_shell (sidebar + offcanvas), routage /compte/* (une page par section), 3 niveaux d'accès avec stub d'abonnement, section Compte (email avec re-vérification, mot de passe, suppression via modale). Refs #73 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
158 lines
6.9 KiB
Markdown
158 lines
6.9 KiB
Markdown
# Espace « Mon compte » — restructuration et section Compte
|
|
|
|
Date : 2026-06-24
|
|
Statut : design validé
|
|
Issue : #73 (comptes premium — abonnement mensuel à prix libre)
|
|
|
|
## Contexte
|
|
|
|
La page `/compte` actuelle (`src/pages/compte.py`) est une page unique étroite
|
|
qui affiche l'email, un formulaire de changement de mot de passe (POST vers
|
|
`/auth/change-password`) et un bouton de déconnexion. L'authentification passe
|
|
par un Blueprint Flask `/auth/*` (`src/auth/routes.py`) avec des formulaires
|
|
HTML côté serveur ; les pages sont des `register_page` Dash.
|
|
|
|
L'objectif est de transformer `/compte` en un **espace à plusieurs sections**
|
|
(jusqu'à 6 à terme) avec une navigation latérale, et d'implémenter
|
|
intégralement la première section, **Compte** (`/compte/admin`).
|
|
|
|
### Sections cibles
|
|
|
|
| Section | URL | Statut | Accès |
|
|
| ------------ | -------------------- | -------- | ---------- |
|
|
| Compte | `/compte/admin` | ce lot | compte |
|
|
| Abonnement | `/compte/abonnement` | coquille | compte |
|
|
| Mes archives | `/compte/archives` | futur | abonnement |
|
|
| Mes filtres | `/compte/filtres` | futur | abonnement |
|
|
| Mon SIRET | `/compte/siret` | futur | abonnement |
|
|
|
|
## Niveaux d'accès
|
|
|
|
Trois niveaux :
|
|
|
|
1. **Visiteur sans compte** → redirigé vers `/connexion?next=<path>`.
|
|
2. **Compte sans abonnement valide** → accès à _Compte_ et _Abonnement_
|
|
uniquement ; toute page gated redirige vers `/compte/abonnement`.
|
|
3. **Compte avec abonnement valide** → accès à toutes les sections.
|
|
|
|
L'abonnement n'est pas encore implémenté : on introduit une abstraction
|
|
`current_user_has_subscription() -> bool` qui renvoie `False` pour l'instant.
|
|
C'est le seul point à brancher lorsque la facturation arrivera.
|
|
|
|
## Architecture
|
|
|
|
### Routage (une page par section)
|
|
|
|
- Chaque section est un `register_page` distinct sous `/compte/*`, fidèle à la
|
|
convention « une page = un fichier » du projet.
|
|
- La page `/compte` actuelle devient une **redirection** vers `/compte/admin`
|
|
(renvoie `dcc.Location(href="/compte/admin")`).
|
|
- Ce lot crée `/compte/admin` (section Compte) et la coquille
|
|
`/compte/abonnement` (page enregistrée minimale, contenu de vente à venir).
|
|
Les sections gated (`archives`, `filtres`, `siret`) ne sont **pas** créées
|
|
dans ce lot.
|
|
|
|
### Coquille partagée
|
|
|
|
Nouveau module `src/pages/_compte_shell.py` exposant :
|
|
|
|
- `account_shell(active: str, contenu) -> Component`
|
|
Construit la mise en page commune :
|
|
|
|
- **Sidebar verticale** (desktop) listant les sections **accessibles** à
|
|
l'utilisateur courant ; l'entrée `active` est surlignée.
|
|
- **Bouton « ☰ Sections »** + `dbc.Offcanvas` (mobile) reprenant la même
|
|
liste.
|
|
- Insère `contenu` dans la zone principale.
|
|
- Sections gated **masquées** tant que `current_user_has_subscription()` est
|
|
faux (Compte + Abonnement seules visibles). La vente des fonctionnalités se
|
|
fera dans le contenu de la section Abonnement, pas via des entrées
|
|
verrouillées.
|
|
- Construit avec Dash Bootstrap Components (`dbc.Row`/`dbc.Col`,
|
|
`dbc.Nav`/`dbc.NavLink`, `dbc.Offcanvas`).
|
|
|
|
- `account_guard(require_subscription: bool) -> Component | None`
|
|
Appelé en tête de chaque `layout()` :
|
|
- non authentifié → `dcc.Location(href="/connexion?next=<path>")`
|
|
- authentifié, `require_subscription` et pas d'abonnement →
|
|
`dcc.Location(href="/compte/abonnement")`
|
|
- sinon → `None` (la page se rend normalement).
|
|
|
|
La définition des sections (libellé, URL, icône, `require_subscription`) est
|
|
centralisée dans une structure unique dans `_compte_shell.py`, pour que sidebar,
|
|
offcanvas et gardes restent cohérents et que l'ajout d'une section future tienne
|
|
en une ligne.
|
|
|
|
## Section Compte (`/compte/admin`)
|
|
|
|
Agencement : sections empilées avec séparateurs, dans `account_shell`.
|
|
|
|
1. **Adresse email** — affiche l'email actuel, champ « nouvelle adresse »,
|
|
bouton « Mettre à jour l'email » → `POST /auth/change-email`.
|
|
2. **Mot de passe** — formulaire existant (mot de passe actuel + nouveau +
|
|
confirmation), inchangé → `POST /auth/change-password`.
|
|
Sa cible de redirection passe de `/compte` à `/compte/admin`.
|
|
3. **Zone danger** — encadré rouge, bouton « Supprimer mon compte » qui ouvre
|
|
une **modale `dbc.Modal`** de confirmation demandant la re-saisie du mot de
|
|
passe → `POST /auth/delete-account`.
|
|
|
|
Le bouton **Déconnexion** est conservé (POST `/auth/logout`).
|
|
|
|
Les messages de succès/erreur sont passés en query string et rendus en
|
|
`dbc.Alert`, sur le modèle actuel ; `ERROR_MESSAGES` est étendu.
|
|
|
|
## Changement d'email avec re-vérification
|
|
|
|
Le changement d'email **ne prend pas effet immédiatement** : la nouvelle adresse
|
|
doit être confirmée par email, par cohérence avec l'inscription. L'ancienne
|
|
adresse reste active tant que la nouvelle n'est pas vérifiée (évite qu'une faute
|
|
de frappe verrouille le compte).
|
|
|
|
Flux :
|
|
|
|
1. `POST /auth/change-email` (`@login_required`) :
|
|
- valide la nouvelle adresse (`validate_email`, normalisée en minuscules) ;
|
|
- vérifie l'unicité (`get_user_by_email` → erreur `email_taken`) ;
|
|
- enregistre l'adresse en attente sur l'utilisateur et envoie un lien de
|
|
vérification à cette nouvelle adresse (réutilise le mécanisme de jetons de
|
|
vérification existant) ;
|
|
- redirige `/compte/admin?email_pending=1`.
|
|
2. Clic sur le lien → la nouvelle adresse devient l'email du compte et le
|
|
`pending_email` est effacé, puis redirection vers `/compte/admin?email_changed=1`.
|
|
|
|
Implications stockage : ajout d'un champ `pending_email` à la table `users`
|
|
(migration de schéma) et nouvelle fonction DB `update_email(user_id, email)`.
|
|
Le détail exact du jeton (réutilisation de `create_email_verification_token` vs
|
|
table dédiée) est laissé au plan d'implémentation.
|
|
|
|
## Suppression de compte
|
|
|
|
`POST /auth/delete-account` (`@login_required`) :
|
|
|
|
- vérifie le mot de passe courant (`check_password_hash`) → sinon
|
|
`?error=invalid_current_password` ;
|
|
- purge les jetons (`delete_email_verification_tokens_for_user`,
|
|
`delete_password_reset_tokens_for_user`) puis `delete_user(current_user.id)` ;
|
|
- `logout_user()` ;
|
|
- redirige vers `/?account_deleted=1`.
|
|
|
|
## Tests
|
|
|
|
- **Accès (Selenium)** : les trois niveaux, avec le stub d'abonnement forcé à
|
|
`True`/`False` ; redirection des non-authentifiés ; redirection des non-abonnés
|
|
sur une page gated ; présence/masquage des entrées de sidebar.
|
|
- **Navigation (Selenium)** : `/compte` → `/compte/admin` ; surlignage de la
|
|
section active ; ouverture de l'offcanvas mobile.
|
|
- **Routes** :
|
|
- `change-email` : succès (passage en attente), email déjà pris, email
|
|
invalide.
|
|
- `delete-account` : mauvais mot de passe (refus), succès (compte supprimé +
|
|
déconnexion).
|
|
|
|
## Hors périmètre
|
|
|
|
- Contenu réel de la section Abonnement (perks, tarifs, paiement).
|
|
- Sections Archives, Filtres, Mon SIRET.
|
|
- Implémentation réelle de la facturation derrière
|
|
`current_user_has_subscription()`.
|