From 358b583634d701d8341b945807d995c6ddf8f2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20LAGACHE?= Date: Thu, 18 Jun 2026 11:25:49 +0200 Subject: [PATCH] =?UTF-8?q?feat(onboarding):=20step-account-info=20?= =?UTF-8?q?=E2=80=94=20firstName/lastName,=20conditional=20password,=20fin?= =?UTF-8?q?dOrCreate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actions/update-account.action.ts | 22 +++ .../onboarding/steps/step-account-info.tsx | 166 ++++++++++++------ 2 files changed, 135 insertions(+), 53 deletions(-) create mode 100644 src/features/onboarding/actions/update-account.action.ts diff --git a/src/features/onboarding/actions/update-account.action.ts b/src/features/onboarding/actions/update-account.action.ts new file mode 100644 index 00000000..a34a9888 --- /dev/null +++ b/src/features/onboarding/actions/update-account.action.ts @@ -0,0 +1,22 @@ +"use server"; + +import { userAction } from "@/lib/safe-actions/actions"; +import { z } from "zod"; +import { db } from "@/db"; +import * as drizzleDb from "@/db"; +import { eq } from "drizzle-orm"; + +export const updateAccountAction = userAction + .schema(z.object({ + firstName: z.string().min(1), + lastName: z.string().min(1), + })) + .action(async ({ parsedInput, ctx }) => { + const name = `${parsedInput.firstName} ${parsedInput.lastName}`.trim(); + const [updated] = await db + .update(drizzleDb.schemas.user) + .set({ name, updatedAt: new Date() }) + .where(eq(drizzleDb.schemas.user.id, ctx.user.id)) + .returning(); + return { user: updated }; + }); diff --git a/src/features/onboarding/steps/step-account-info.tsx b/src/features/onboarding/steps/step-account-info.tsx index 4cacad85..74f36b28 100644 --- a/src/features/onboarding/steps/step-account-info.tsx +++ b/src/features/onboarding/steps/step-account-info.tsx @@ -4,94 +4,154 @@ import { useOnboarding } from "@onboardjs/react"; import { useMutation } from "@tanstack/react-query"; import { toast } from "sonner"; import { z } from "zod"; -import { useZodForm, Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form"; +import { + useZodForm, + Form, + FormField, + FormItem, + FormLabel, + FormControl, + FormMessage, +} from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { PasswordInput } from "@/components/ui/password-input"; import { Button } from "@/components/ui/button"; import { signUp } from "@/lib/auth/auth-client"; +import { updateAccountAction } from "@/features/onboarding/actions/update-account.action"; +import type { OnboardingMeta } from "@/features/onboarding/onboarding.types"; -const AccountStepSchema = z.object({ - name: z.string().min(1, "Name is required"), +const BaseSchema = z.object({ + firstName: z.string().min(1, "First name required"), + lastName: z.string().min(1, "Last name required"), email: z.string().email("Invalid email"), +}); + +const WithPasswordSchema = BaseSchema.extend({ password: z.string().min(8, "Min. 8 characters"), }); -type AccountStepType = z.infer; - export const StepAccountInfo = () => { const { next, updateContext, state } = useOnboarding(); - const form = useZodForm({ schema: AccountStepSchema }); + const meta = state?.context.flowData.meta as OnboardingMeta | undefined; + const existingAccount = state?.context.flowData.account; + const isUpdateMode = !!existingAccount; + const passkeyEnabled = meta?.passkeyEnabled ?? false; + + const schema = passkeyEnabled ? BaseSchema : WithPasswordSchema; + const form = useZodForm({ schema }) as unknown as ReturnType>; const mutation = useMutation({ - mutationFn: async (values: AccountStepType) => { - await signUp.email( - { name: values.name, email: values.email, password: values.password }, - { - onSuccess: async () => { - await updateContext({ - flowData: { ...state?.context.flowData, account: { name: values.name, email: values.email } }, - }); - await next(); + mutationFn: async (values: z.infer) => { + if (isUpdateMode) { + const result = await updateAccountAction({ + firstName: values.firstName, + lastName: values.lastName, + }); + if (!result?.data) throw new Error("Failed to update account"); + await updateContext({ + flowData: { + ...state?.context.flowData, + account: { firstName: values.firstName, lastName: values.lastName, email: values.email }, }, - onError: (error) => { - toast.error(error.error.message); - }, - } - ); + }); + await next(); + } else { + await signUp.email( + { + name: `${values.firstName} ${values.lastName}`, + email: values.email, + password: values.password ?? "", + } as any, + { + onSuccess: async () => { + await updateContext({ + flowData: { + ...state?.context.flowData, + account: { firstName: values.firstName, lastName: values.lastName, email: values.email }, + }, + }); + await next(); + }, + onError: (error) => { + toast.error(error.error.message); + }, + } + ); + } }, }); return (
-

Create your account

+

+ {isUpdateMode ? "Update your account" : "Create your account"} +

This step can't be skipped.

-
mutation.mutateAsync(values)}> - ( - - Name - - - - - - )} - /> + mutation.mutateAsync(values as any)}> +
+ ( + + First name + + + + + + )} + /> + ( + + Last name + + + + + + )} + /> +
( Email - - - - - )} - /> - ( - - Password - - + )} /> + {!passkeyEnabled && !isUpdateMode && ( + ( + + Password + + + + + + )} + /> + )}