batchCooking/apps/web/src/pages/settings/settings-pages.scss
Nicolas 38fcf03417
All checks were successful
CI / lint (push) Successful in 3m55s
CI / build (push) Successful in 4m53s
CI / intent-service-test (push) Successful in 5m52s
CI / e2e (push) Successful in 10m30s
CI / test (push) Successful in 18m43s
feat(web): moteur de tutoriel guidé driver.js (#42)
Consomme le socle backend de #42/#44 (`GET /reference/tours`,
`GET/POST /profile/tours`). Deux tours : orientation générale (auto sur
`/`) et correction des métadonnées de recette (auto à l'ouverture du
premier popover de correction).

- Dépendance `driver.js` (~5 ko, MIT, zéro dépendance).
- `features/tour/tour-engine.ts` : wrapper vanilla autour de `driver.js` —
  gère ce que la lib ne fait pas seule : la navigation React Router entre
  étapes situées sur des routes différentes, et un `onFinish` unique
  (`"completed"` seulement si on atteint la fin, `"skipped"` pour la croix
  / Échap / clic sur l'overlay).
- `features/tour/TourContext.tsx` : `TourProvider` (charge scénarios +
  progression, keyed sur `user?.id` comme `ThemeContext`) et `useTour()`
  (`startTour(key)` / `isTourCompleted(key)` / `tours` / `isReady`).
  `isReady` est **calculé au render** contre l'id utilisateur chargé — pas
  stocké — pour qu'un changement de `user` le repasse à `false`
  immédiatement, avant qu'un effet enfant (l'auto-déclenchement) ne tourne
  sur des données de complétion périmées.
- `main.tsx` : `<TourProvider>` sous `ThemeProvider`.
- Attributs `data-tour-*` : liens de nav (`AppLayout`), grille / bouton `+`
  du 1er créneau / bouton Cuisiner (`PlanningPage`), page liste de courses
  (`ShoppingListPage`), description d'étape + section technique du popover
  (`StepDescription` / `TechStepCorrectionPopover`).
- Déclenchement auto : tour `general` sur `/` (`PlanningPage`, ref-gardé),
  tour `techStepMetadata` à `activeCorrection` non-null (`StepDescription`).
- Section « Revoir un tutoriel » dans `/parametres/preferences-utilisateur`
  — un `map` sur `tours`, un bouton « Revoir » par scénario.
- `tour.scss` re-skinne le popover `driver.js` sur les tokens du thème.
- i18n `tour.*` (chrome uniquement : boutons du popover, section « Revoir » ;
  le contenu des étapes vient de la base).

Tests Cypress (`tour.feature`, 5 scénarios) : auto-démarrage + élément
surligné pour un nouveau venu ; « Terminé » → complétion enregistrée
`skipped:false` ; croix → `skipped:true` ; un tour déjà vu ne se relance
pas ; « Revoir » le relance depuis les paramètres. Vérifié 5/5 en local.
`pnpm build` + `pnpm lint` verts.

Refs #42
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 06:17:06 +02:00

214 lines
5.7 KiB
SCSS

// =============================================================================
// Styles shared by the three settings pages (AccountSettingsPage,
// PreferencesPage, HouseholdSettingsPage) — colocated under pages/settings/
// since nothing outside that folder uses these classes. Field/label/input
// styling itself still comes from features/profile/profile-forms.scss
// (shared with the onboarding wizard); this file only covers page layout —
// direct continuation of what used to be HouseholdPage.scss before the
// household/regime/allergies page was split in three.
// =============================================================================
.settings-page {
&__status {
color: var(--color-text-muted);
font-size: var(--font-size-md);
}
&__status--error {
color: var(--color-error);
}
}
// Each setting lives in its own card, same surface treatment used
// elsewhere (see .planning-table in HomePage.scss) — reads as a distinct,
// self-contained unit rather than one long form. Width comes from
// `.settings-page` above, not its own cap, now that it's the one
// establishing the centered column every section sits in.
.settings-page__section {
margin-top: var(--space-lg);
padding: var(--space-lg);
background: var(--color-surface);
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
}
.settings-page__section-title {
margin: 0 0 var(--space-sm);
font-size: var(--font-size-md);
}
.settings-page__saving,
.settings-page__saved {
margin: var(--space-sm) 0 0;
font-size: var(--font-size-sm);
font-weight: 600;
}
.settings-page__saving {
color: var(--color-text-muted);
}
.settings-page__saved {
color: var(--color-success);
}
.settings-page__actions {
display: flex;
gap: var(--space-sm);
margin-top: var(--space-sm);
}
.settings-page button {
padding: 0.5rem var(--space-md);
font-family: var(--font-body);
font-size: var(--font-size-sm);
font-weight: 600;
cursor: pointer;
border-radius: var(--radius-base);
border: 1px solid var(--color-border);
background: var(--color-surface);
color: var(--color-text);
&:hover:not(:disabled) {
background: var(--color-surface-alt);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.settings-page__hint {
color: var(--color-text-muted);
font-size: var(--font-size-sm);
}
.settings-page__invite-section {
margin-top: var(--space-md);
}
// Household member list.
.settings-page__members {
list-style: none;
margin: var(--space-sm) 0 0;
padding: 0;
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.settings-page__member {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-sm);
padding: var(--space-xs) 0;
}
.settings-page__member-badge {
margin-left: var(--space-xs);
padding: 0.1rem 0.4rem;
font-size: var(--font-size-xs);
font-weight: 600;
color: var(--color-primary);
background: var(--color-surface-alt);
border-radius: var(--radius-base);
}
// Invite code — meant to be read/copied, so it's set in a monospace font
// and never wraps mid-code.
.settings-page__invite-code {
font-family: monospace;
font-size: var(--font-size-md);
letter-spacing: 0.08em;
}
// Destructive actions (delete household/account, remove a member) get a
// visually distinct, consistent "danger" treatment wherever they appear.
.settings-page__danger-zone {
margin-top: var(--space-lg);
border: 1.5px solid var(--color-error);
background: color-mix(in srgb, var(--color-error) 8%, var(--color-surface));
}
// `button.` prefix (not just `.settings-page__danger-button`) to outrank
// the generic `.settings-page button` rule above — a class selector alone
// has lower specificity than "class + element", so without it the generic
// gray fill/border was winning and this never actually rendered red (same
// fix as `button.settings-page__link-button` below, same reason).
button.settings-page__danger-button {
color: #fff;
background: var(--color-error);
border-color: var(--color-error);
&:hover:not(:disabled) {
opacity: 0.9;
}
}
button.settings-page__link-button {
padding: 0;
font: inherit;
color: var(--color-primary);
background: none;
border: none;
cursor: pointer;
text-decoration: underline;
}
// Theme choice (UserPreferencesPage) — a plain radio group, no fieldset/
// legend styling exists elsewhere yet to reuse (AllergySelect's `.allergy-
// select` in profile-forms.scss is checkbox-grid specific). The option
// rows' own look (bordered "card", checked state, checkmark) is entirely
// global.scss's — this only resets the fieldset chrome; the generic
// `label { margin-top }` rule (profile-forms.scss) already stacks the
// rows with breathing room between them.
.theme-select {
border: none;
padding: 0;
margin: 0;
legend {
padding: 0 0 var(--space-sm);
font-weight: 600;
font-size: var(--font-size-sm);
}
}
// "Revoir un tutoriel" (UserPreferencesPage) — one row per guided-tour
// scenario, name on the left, a replay button on the right.
.tour-replay-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: var(--space-sm);
&__item {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-md);
font-size: var(--font-size-sm);
button {
flex-shrink: 0;
padding: var(--space-xs) var(--space-md);
border-radius: var(--radius-base);
border: 1px solid var(--color-border);
background: var(--color-surface);
color: var(--color-text);
font-family: var(--font-body);
font-size: var(--font-size-sm);
font-weight: 600;
cursor: pointer;
&:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
}
}
}