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>
85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
import { Check } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
/**
|
|
* Stepper horizontal pour wizards multi-étapes (onboarding).
|
|
* - Étape courante : cercle plein rubis, label rubis
|
|
* - Étape complétée : cercle rubis-glow + ✓ rubis-deep
|
|
* - Étape future : cercle vide bordure line, label muted
|
|
* - Lignes entre les cercles : rubis si la précédente est complétée
|
|
*
|
|
* Pas une UI standard "1—2—3 cliquable". Les étapes ne sont pas navigables :
|
|
* c'est un wizard linéaire (cf. /docs/produit.md).
|
|
*/
|
|
type Step = {
|
|
/** Identifiant stable pour `key`. */
|
|
id: string;
|
|
label: string;
|
|
};
|
|
|
|
type StepperProps = {
|
|
steps: ReadonlyArray<Step>;
|
|
/** Index 0-based de l'étape courante. */
|
|
currentIndex: number;
|
|
className?: string;
|
|
};
|
|
|
|
export function Stepper({ steps, currentIndex, className }: StepperProps) {
|
|
return (
|
|
<ol
|
|
className={cn("flex items-center gap-2 sm:gap-3", className)}
|
|
aria-label={`Étape ${currentIndex + 1} sur ${steps.length}`}
|
|
>
|
|
{steps.map((step, idx) => {
|
|
const status: "done" | "current" | "todo" =
|
|
idx < currentIndex ? "done" : idx === currentIndex ? "current" : "todo";
|
|
const isLast = idx === steps.length - 1;
|
|
return (
|
|
<li key={step.id} className="flex items-center gap-2 sm:gap-3 flex-1">
|
|
<div className="flex flex-col items-center gap-2">
|
|
<span
|
|
aria-current={status === "current" ? "step" : undefined}
|
|
className={cn(
|
|
"flex size-8 items-center justify-center rounded-full text-[13px] font-semibold",
|
|
"transition-[background,border-color,color] duration-200",
|
|
status === "current" &&
|
|
"bg-rubis text-white shadow-rubis tabular-nums",
|
|
status === "done" &&
|
|
"bg-rubis-glow text-rubis-deep tabular-nums",
|
|
status === "todo" &&
|
|
"border border-line bg-white text-ink-3 tabular-nums",
|
|
)}
|
|
>
|
|
{status === "done" ? (
|
|
<Check size={15} aria-hidden="true" strokeWidth={2.5} />
|
|
) : (
|
|
idx + 1
|
|
)}
|
|
</span>
|
|
<span
|
|
className={cn(
|
|
"hidden sm:block text-[11px] font-semibold uppercase tracking-[0.1em] whitespace-nowrap",
|
|
status === "current" && "text-rubis",
|
|
status === "done" && "text-ink-2",
|
|
status === "todo" && "text-ink-3",
|
|
)}
|
|
>
|
|
{step.label}
|
|
</span>
|
|
</div>
|
|
{!isLast && (
|
|
<span
|
|
aria-hidden="true"
|
|
className={cn(
|
|
"h-px flex-1 transition-colors duration-200",
|
|
status === "done" ? "bg-rubis" : "bg-line",
|
|
)}
|
|
/>
|
|
)}
|
|
</li>
|
|
);
|
|
})}
|
|
</ol>
|
|
);
|
|
}
|