feat(auth): bouton afficher / masquer le mot de passe (#134) #141

Merged
kyuno merged 3 commits from feat/login-password-toggle into main 2026-09-09 20:44:33 +02:00
Owner

Contexte

Closes #134 (puce 7 de #3). LoginPage et SignupPage avaient un <input id="password" type="password"> nu, sans moyen de relire ce qu'on a tapé — un mot de passe mal saisi mais invisible est une cause fréquente de connexion ratée.

Changements

components/ui/PasswordInput.tsx (nouveau, + password-input.scss) — un <input> de mot de passe avec un bouton œil (lucide-react Eye/EyeOff) qui bascule le type entre "password" et "text".

  • Visibilité en useState local, toujours masquée au montage, jamais persistée (révéler un mot de passe est une action ponctuelle délibérée, pas une préférence).
  • <button type="button"> — ne soumet jamais le formulaire ; aria-label traduit (common.passwordInput.show / .hide), aria-pressed porte l'état.
  • Props passe-plat : id, value, onChange (valeur déjà déballée, comme CheckboxOption), autoComplete.
  • Rangé dans components/ui/ (pas features/auth/) : rien dedans n'est spécifique à l'auth. Le style du champ lui-même est hérité du contexte (.auth-card input), comme CheckboxOption s'appuie sur global.scss ; la feuille colocalisée ne fait que positionner le bouton et réserver la place de l'icône.

LoginPage / SignupPage — <input type="password"> remplacé par <PasswordInput> (autoComplete="current-password" / "new-password").

auth-form.scss — la règle .auth-card button est resserrée sur .auth-card button[type="submit"] : sinon le bouton œil (type="button", imbriqué) héritait du traitement plein-largeur / primaire du bouton de connexion.

i18n — common.passwordInput.show = « Afficher le mot de passe » / .hide = « Masquer le mot de passe » (namespace common, le composant est générique).

Docs — specs/frontend-architecture.md : entrée PasswordInput dans la liste des composants components/ui/.

Écart assumé vs la conception du ticket : le ticket suggérait de mettre le SCSS dans auth-form.scss. Une feuille colocalisée (password-input.scss, comme dialog.scss / tooltip.scss / bottom-sheet.scss) garde le composant components/ui/ autonome et conforme à la règle de placement des styles de dev-conventions.md.

Points du ticket tranchés

  • Périmètre : login + signup (même champ, même bénéfice) — recommandation du ticket.
  • Icône : lucide-react Eye/EyeOff (déjà dépendance directe, cf. nav-icons.tsx).
  • Réinitialisation : masqué à chaque montage, aucune persistance.

Tests

  • cypress/component/PasswordInput.cy.tsx (nouveau, composant isolé, 7 cas) : masqué au montage ; clic œil ⇒ type="text" + valeur lisible ; reclic ⇒ re-masqué ; aria-label/aria-pressed corrects ; le clic ne soumet pas un <form> parent ; contrôlé (valeur + onChange) ; re-masqué sur un nouveau montage.
  • cypress/e2e/auth-form.cy.ts (nouveau, mode layout) : l'œil est présent et bascule #password sur /login et /signup ; le clic ne déclenche pas POST /auth/login.

Vérification locale

pnpm lint ✓ · pnpm --filter ./apps/web build ✓ · Cypress : PasswordInput.cy.tsx 7/7, suite composant complète verte, auth-form.cy.ts 3/3, régression auth.feature 6/6. Vérifié visuellement dans le navigateur (positionnement de l'icône, bascule, bouton de connexion intact).

🤖 Generated with Claude Code

## Contexte `Closes #134` (puce 7 de #3). `LoginPage` et `SignupPage` avaient un `<input id="password" type="password">` nu, sans moyen de relire ce qu'on a tapé — un mot de passe mal saisi mais invisible est une cause fréquente de connexion ratée. ## Changements **`components/ui/PasswordInput.tsx`** (nouveau, + `password-input.scss`) — un `<input>` de mot de passe avec un bouton œil (`lucide-react` `Eye`/`EyeOff`) qui bascule le `type` entre `"password"` et `"text"`. - Visibilité en `useState` local, **toujours masquée au montage, jamais persistée** (révéler un mot de passe est une action ponctuelle délibérée, pas une préférence). - `<button type="button">` — ne soumet jamais le formulaire ; `aria-label` traduit (`common.passwordInput.show` / `.hide`), `aria-pressed` porte l'état. - Props passe-plat : `id`, `value`, `onChange` (valeur déjà déballée, comme `CheckboxOption`), `autoComplete`. - Rangé dans `components/ui/` (pas `features/auth/`) : rien dedans n'est spécifique à l'auth. Le style du champ lui-même est **hérité du contexte** (`.auth-card input`), comme `CheckboxOption` s'appuie sur `global.scss` ; la feuille colocalisée ne fait que positionner le bouton et réserver la place de l'icône. **`LoginPage` / `SignupPage`** — `<input type="password">` remplacé par `<PasswordInput>` (`autoComplete="current-password"` / `"new-password"`). **`auth-form.scss`** — la règle `.auth-card button` est resserrée sur `.auth-card button[type="submit"]` : sinon le bouton œil (`type="button"`, imbriqué) héritait du traitement plein-largeur / primaire du bouton de connexion. **i18n** — `common.passwordInput.show` = « Afficher le mot de passe » / `.hide` = « Masquer le mot de passe » (namespace `common`, le composant est générique). **Docs** — `specs/frontend-architecture.md` : entrée `PasswordInput` dans la liste des composants `components/ui/`. > Écart assumé vs la conception du ticket : le ticket suggérait de mettre le SCSS dans `auth-form.scss`. Une feuille colocalisée (`password-input.scss`, comme `dialog.scss` / `tooltip.scss` / `bottom-sheet.scss`) garde le composant `components/ui/` autonome et conforme à la règle de placement des styles de `dev-conventions.md`. ## Points du ticket tranchés - **Périmètre** : login **+ signup** (même champ, même bénéfice) — recommandation du ticket. - **Icône** : `lucide-react` `Eye`/`EyeOff` (déjà dépendance directe, cf. `nav-icons.tsx`). - **Réinitialisation** : masqué à chaque montage, aucune persistance. ## Tests - **`cypress/component/PasswordInput.cy.tsx`** (nouveau, composant isolé, 7 cas) : masqué au montage ; clic œil ⇒ `type="text"` + valeur lisible ; reclic ⇒ re-masqué ; `aria-label`/`aria-pressed` corrects ; le clic ne soumet pas un `<form>` parent ; contrôlé (valeur + `onChange`) ; re-masqué sur un nouveau montage. - **`cypress/e2e/auth-form.cy.ts`** (nouveau, mode layout) : l'œil est présent et bascule `#password` sur `/login` **et** `/signup` ; le clic ne déclenche pas `POST /auth/login`. ## Vérification locale `pnpm lint` ✓ · `pnpm --filter ./apps/web build` ✓ · Cypress : `PasswordInput.cy.tsx` 7/7, suite composant complète verte, `auth-form.cy.ts` 3/3, régression `auth.feature` 6/6. Vérifié visuellement dans le navigateur (positionnement de l'icône, bascule, bouton de connexion intact). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-09-09 19:34:00 +02:00
feat(auth): bouton afficher / masquer le mot de passe (#134)
Some checks failed
CI / test (push) Failing after 1m0s
CI / e2e (push) Failing after 1m43s
CI / lint (push) Successful in 2m19s
CI / build (push) Successful in 2m28s
CI / intent-service-test (push) Successful in 4m22s
CI / build-android (push) Has been skipped
24bd2239fc
Nouveau composant `components/ui/PasswordInput` : un `<input>` de mot de
passe avec un bouton œil (`lucide-react` Eye/EyeOff) qui bascule le `type`
entre "password" et "text". Visibilité en état local, toujours masquée au
montage, jamais persistée. Bouton `type="button"` (ne soumet pas le
formulaire) + `aria-label` traduit (`common.passwordInput.*`) +
`aria-pressed`.

Branché dans `LoginPage` et `SignupPage` à la place du `<input
type="password">` nu. La règle `.auth-card button` de `auth-form.scss` est
resserrée sur `button[type="submit"]` pour que le bouton œil n'hérite pas
du traitement plein-largeur du bouton de connexion.

Tests : `cypress/component/PasswordInput.cy.tsx` (7 cas, composant isolé) +
`cypress/e2e/auth-form.cy.ts` (mode layout, l'œil sur /login et /signup).

Closes #134

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno added 1 commit 2026-09-09 19:39:10 +02:00
chore(ci): relance après un flake d'infra (apt « Hash Sum mismatch » Google Chrome)
Some checks failed
CI / test (push) Failing after 26s
CI / e2e (push) Failing after 1m13s
CI / lint (push) Successful in 1m59s
CI / build (push) Successful in 2m11s
CI / intent-service-test (push) Successful in 4m5s
CI / build-android (push) Has been skipped
16ceeda031
Run 278 : `test` et `e2e` ont échoué en ~1 min sur `apt-get update` (dépôt
apt Google Chrome incohérent en amont), avant toute exécution de code.
`lint` + `build` verts. Vérif locale complète verte.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno added 1 commit 2026-09-09 20:03:07 +02:00
chore(ci): relance — dépôt apt Google Chrome de nouveau cohérent
All checks were successful
CI / lint (push) Successful in 2m38s
CI / build (push) Successful in 2m44s
CI / intent-service-test (push) Successful in 6m0s
CI / e2e (push) Successful in 11m56s
CI / test (push) Successful in 20m1s
CI / build-android (push) Has been skipped
5a6ddfc287
Le hash InRelease vs Packages.gz du dépôt apt Google Chrome (upstream)
correspond à nouveau (233e56de…) après ~21 min de désynchro CDN qui a fait
échouer runs 277-279 sur `apt-get update` (jobs test + e2e).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno merged commit 09904cffc1 into main 2026-09-09 20:44:33 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/batchCooking#141
No description provided.