Files
colibre/docs/superpowers/specs/2026-06-24-espace-compte-design.md
T
Colin Maudry ba5ad2c2ac docs(compte): design de l'espace compte multi-sections (#73)
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>
2026-06-24 18:32:17 +02:00

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()`.