batchCooking/apps/web/src/main.tsx
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

60 lines
2.3 KiB
TypeScript

import { Capacitor } from "@capacitor/core";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter, HashRouter } from "react-router-dom";
import { App } from "./App";
import { setTokenStore } from "./api/client";
import { AuthProvider } from "./features/auth/AuthContext";
import { ThemeProvider } from "./features/theme/ThemeContext";
import { TourProvider } from "./features/tour/TourContext";
// Side-effect import: initializes the i18next instance before anything
// renders (react-i18next reads it via context under the hood). See i18n/i18n.ts.
import "./i18n/i18n";
// Global stylesheet (theme tokens + minimal reset) — the only .scss import
// that isn't colocated with a specific component/page. See styles/global.scss.
import "./styles/global.scss";
// `BrowserRouter` needs a server that rewrites deep links back to
// `index.html`; a packaged Capacitor app serves `dist/` straight off the
// WebView's local origin with no such rewrite, so a direct hit on a deep
// route (or a reload) would 404. `HashRouter` keeps the whole route in the
// fragment — zero server cooperation needed — and the URL bar is invisible
// in the native shell anyway. Web is untouched: `isNativePlatform()` is
// `false` in every browser (and in Cypress).
const isNative = Capacitor.isNativePlatform();
const Router = isNative ? HashRouter : BrowserRouter;
/**
* On native, swap the no-op token store for the `@capacitor/preferences`
* one *before* the first render — `AuthProvider` fires `GET /auth/me` on
* mount and it must carry the `Authorization` header. Lazily imported so
* `@capacitor/preferences` never enters the web bundle; web takes no
* `await` on this path, so rendering isn't deferred.
*/
async function bootstrap() {
if (isNative) {
const { capacitorTokenStore } = await import("./api/capacitor-token-store");
setTokenStore(capacitorTokenStore);
}
const rootElement = document.getElementById("root");
if (!rootElement) {
throw new Error("Root element not found");
}
createRoot(rootElement).render(
<StrictMode>
<Router>
<AuthProvider>
<ThemeProvider>
<TourProvider>
<App />
</TourProvider>
</ThemeProvider>
</AuthProvider>
</Router>
</StrictMode>,
);
}
void bootstrap();