feat(onboarding): replace tabs with single form and multiple buttons for auth choice

This commit is contained in:
Théo LAGACHE
2026-06-19 13:57:10 +02:00
parent 69f500237a
commit a10ef50bcf
2 changed files with 67 additions and 29 deletions
@@ -10,15 +10,16 @@ import { generatePasskeyContextAction } from "@/features/onboarding/actions/gene
import { WithPasswordSchema } from "@/features/onboarding/schemas/account.schema";
import type { OnboardingMeta } from "@/features/onboarding/types";
type AccountInput = z.infer<typeof WithPasswordSchema>;
type AccountInput = z.infer<typeof WithPasswordSchema> & { method?: "passkey" | "password" };
export const useUpdateAccount = (refetchSession: () => Promise<any>, selectedMethod: "passkey" | "password" = "password") => {
export const useUpdateAccount = (refetchSession: () => Promise<any>) => {
const { state, updateContext, next } = useOnboarding();
return useMutation({
mutationFn: async (values: AccountInput) => {
const existingAccount = state?.context.flowData.account;
const isUpdateMode = !!existingAccount;
const selectedMethod = values.method ?? "password";
if (isUpdateMode) {
const result = await updateAccountAction({
@@ -33,22 +33,41 @@ export const StepAccountInfo = () => {
const passkeyEnabled = meta?.passkeyEnabled ?? false;
const emailPasswordEnabled = meta?.emailPasswordEnabled ?? false;
const [selectedMethod, setSelectedMethod] = useState<"passkey" | "password">(
passkeyEnabled && !emailPasswordEnabled ? "passkey" : "password"
);
useEffect(() => {
if (session?.user && !isUpdateMode) {
next();
}
}, [session?.user?.id, next, session?.user, isUpdateMode]);
const schema = selectedMethod === "password" && !isUpdateMode ? WithPasswordSchema : BaseSchema;
const schema = isUpdateMode
? BaseSchema
: z.object({
...BaseSchema.shape,
password: z.string().optional(),
});
const form = useZodForm({ schema }) as unknown as ReturnType<
typeof useZodForm<typeof WithPasswordSchema>
>;
const mutation = useUpdateAccount(refetchSession, selectedMethod);
const mutation = useUpdateAccount(refetchSession);
const onSubmitPassword = async () => {
const valid = await form.trigger();
if (!valid) return;
const values = form.getValues();
if (!values.password || values.password.length < 8) {
form.setError("password", { message: "Min. 8 characters" });
return;
}
mutation.mutateAsync({ ...(values as any), method: "password" });
};
const onSubmitPasskey = async () => {
const valid = await form.trigger(["firstName", "lastName", "email"] as any);
if (!valid) return;
mutation.mutateAsync({ ...(form.getValues() as any), method: "passkey" });
};
return (
<div className="flex flex-col gap-4">
@@ -63,20 +82,20 @@ export const StepAccountInfo = () => {
<Form
form={form}
className="flex flex-col gap-4"
onSubmit={async (values) => mutation.mutateAsync(values as any)}
onSubmit={async (values) => {
if (isUpdateMode) {
mutation.mutateAsync({ ...(values as any) });
} else if (!emailPasswordEnabled) {
mutation.mutateAsync({ ...(values as any), method: "passkey" });
} else {
if (!values.password || (values.password as string).length < 8) {
form.setError("password", { message: "Min. 8 characters" });
return;
}
mutation.mutateAsync({ ...(values as any), method: "password" });
}
}}
>
{!isUpdateMode && passkeyEnabled && emailPasswordEnabled && (
<Tabs
value={selectedMethod}
onValueChange={(v) => setSelectedMethod(v as any)}
className="w-full"
>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="passkey">Passkey</TabsTrigger>
</TabsList>
</Tabs>
)}
<div className="grid grid-cols-2 gap-3">
<FormField
control={form.control}
@@ -121,7 +140,7 @@ export const StepAccountInfo = () => {
</FormItem>
)}
/>
{!isUpdateMode && selectedMethod === "password" && (
{!isUpdateMode && emailPasswordEnabled && (
<FormField
control={form.control}
name="password"
@@ -137,13 +156,31 @@ export const StepAccountInfo = () => {
)}
/>
)}
<Button type="submit" disabled={mutation.isPending}>
{isUpdateMode
? "Update account"
: selectedMethod === "passkey"
? "Create account with passkey"
: "Create account"}
</Button>
<div className="flex flex-col gap-2 mt-2">
{isUpdateMode ? (
<Button type="submit" disabled={mutation.isPending}>
Update account
</Button>
) : (
<>
{emailPasswordEnabled && (
<Button type="button" onClick={onSubmitPassword} disabled={mutation.isPending}>
Create account
</Button>
)}
{passkeyEnabled && (
<Button
type="button"
variant={emailPasswordEnabled ? "outline" : "default"}
onClick={onSubmitPasskey}
disabled={mutation.isPending}
>
Create account with passkey
</Button>
)}
</>
)}
</div>
</Form>
</div>
);