rubis/apps/web/src/routes/onboarding.tsx
ordinarthur 332bf0bcda feat(web): /signup + 3-step onboarding flow
Nouvelles routes :
- /signup : inscription (fullName + email + password) → /onboarding/compte
- /onboarding : layout avec brand + stepper, auth-guard
- /onboarding/compte : étape 1 (nom + email, prefilled depuis la session)
- /onboarding/entreprise : étape 2 (nom, SIRET optionnel, chips volume)
- /onboarding/signature : étape 3 (signature email + aperçu live)

Nouvelles primitives UI :
- <Card variant="default|flat|hero" padding="sm|md|lg">
- <Stepper> wizard horizontal (current rubis, done rubis-glow + ✓, todo line)
- <Chip selected> : pastille pill, glow + deep quand sélectionnée (le rubis
  plein reste réservé aux CTA, cf. règle "le rubis est rare")
- <Textarea> : mêmes règles a11y/focus que <Input>

MSW handlers étendus :
- PATCH /api/v1/account/profile (fullName, email, signature)
- PATCH /api/v1/organizations/me (name, siret, monthlyVolumeBucket)
- mockDb : ajout des organizations, méthodes updateUser/updateOrg

Wiring :
- /login → "Créer un compte" pointe vers /signup (avant : loop)
- /login succès → /  (au lieu de /login)
- /  → /onboarding/compte si auth, /login sinon (placeholder dashboard)
- /onboarding/signature succès → /

Bundle prod : 113.87 KB gzip core (-2 KB grâce à MSW exclu en prod via
import.meta.env.DEV). Chaque route en chunk dédié (1-2 KB gzip).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 10:22:53 +02:00

61 lines
1.9 KiB
TypeScript

import {
Outlet,
Link,
createFileRoute,
redirect,
useRouterState,
} from "@tanstack/react-router";
import { authStore } from "@/lib/auth";
import { Brand } from "@/components/brand/Brand";
import { Stepper } from "@/components/ui/Stepper";
const ONBOARDING_STEPS = [
{ id: "compte", label: "Compte", path: "/onboarding/compte" },
{ id: "entreprise", label: "Entreprise", path: "/onboarding/entreprise" },
{ id: "signature", label: "Signature", path: "/onboarding/signature" },
] as const;
export const Route = createFileRoute("/onboarding")({
beforeLoad: ({ location }) => {
if (!authStore.isAuthenticated()) {
throw redirect({ to: "/login", search: { redirect: location.href } });
}
},
component: OnboardingLayout,
});
function OnboardingLayout() {
const pathname = useRouterState({ select: (s) => s.location.pathname });
const currentIndex = Math.max(
0,
ONBOARDING_STEPS.findIndex((s) => pathname.startsWith(s.path)),
);
return (
<main className="min-h-screen bg-cream">
{/* Header minimal — pas de sidebar, pas de nav, juste la marque + le stepper. */}
<header className="border-b border-line bg-cream/85 backdrop-blur-sm">
<div className="mx-auto flex max-w-[920px] items-center justify-between px-6 py-5 lg:px-8">
<Link to="/login">
<Brand withSuffix />
</Link>
<p className="hidden text-[12.5px] text-ink-3 sm:block">
Étape {currentIndex + 1} sur {ONBOARDING_STEPS.length}
</p>
</div>
<div className="mx-auto max-w-[920px] px-6 pb-6 lg:px-8">
<Stepper
steps={ONBOARDING_STEPS.map(({ id, label }) => ({ id, label }))}
currentIndex={currentIndex}
/>
</div>
</header>
<div className="mx-auto w-full max-w-[640px] px-6 py-12 lg:px-8 lg:py-16">
<Outlet />
</div>
</main>
);
}