diff --git a/src/features/onboarding/hooks/use-update-account.ts b/src/features/onboarding/hooks/use-update-account.ts new file mode 100644 index 00000000..d23e2322 --- /dev/null +++ b/src/features/onboarding/hooks/use-update-account.ts @@ -0,0 +1,91 @@ +"use client"; + +import { useMutation } from "@tanstack/react-query"; +import { useOnboarding } from "@onboardjs/react"; +import { toast } from "sonner"; +import { z } from "zod"; +import { authClient, signUp, passkey, signIn } from "@/lib/auth/auth-client"; +import { updateAccountAction } from "@/features/onboarding/actions/update-account.action"; +import { generatePasskeyContextAction } from "@/features/onboarding/actions/generate-passkey-context.action"; +import { WithPasswordSchema } from "@/features/onboarding/schemas/account.schema"; +import type { OnboardingMeta } from "@/features/onboarding/types"; + +type AccountInput = z.infer; + +export const useUpdateAccount = (refetchSession: () => Promise) => { + const { state, updateContext, next } = useOnboarding(); + + return useMutation({ + mutationFn: async (values: AccountInput) => { + const meta = state?.context.flowData.meta as OnboardingMeta | undefined; + const existingAccount = state?.context.flowData.account; + const isUpdateMode = !!existingAccount; + const passkeyEnabled = meta?.passkeyEnabled ?? false; + + 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 }, + }, + }); + await next(); + return; + } + + if (passkeyEnabled) { + const name = `${values.firstName} ${values.lastName}`; + const context = await generatePasskeyContextAction(name, values.email); + const result = await passkey.addPasskey({ name: values.email, context }); + if (result?.error) { + throw new Error(result.error.message ?? "Passkey registration failed"); + } + await refetchSession(); + const { data: freshSession } = await authClient.getSession(); + if (!freshSession?.user) { + const signInResult = await (signIn as any).passkey(); + if (signInResult?.error) { + throw new Error("Registration succeeded but sign-in failed. Please reload and sign in."); + } + } + await updateContext({ + flowData: { + ...state?.context.flowData, + account: { firstName: values.firstName, lastName: values.lastName, email: values.email }, + security: { method: "passkey" }, + }, + }); + await next(); + return; + } + + 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); + }, + }, + ); + }, + onError: (err: Error) => toast.error(err.message), + }); +}; diff --git a/src/features/onboarding/steps/step-account-info.tsx b/src/features/onboarding/steps/step-account-info.tsx index 00ae0694..9812a7f9 100644 --- a/src/features/onboarding/steps/step-account-info.tsx +++ b/src/features/onboarding/steps/step-account-info.tsx @@ -2,8 +2,7 @@ import { useEffect } from "react"; import { useOnboarding } from "@onboardjs/react"; -import { useMutation } from "@tanstack/react-query"; -import { toast } from "sonner"; +import { useSession } from "@/lib/auth/auth-client"; import { z } from "zod"; import { useZodForm, @@ -17,21 +16,18 @@ import { import { Input } from "@/components/ui/input"; import { PasswordInput } from "@/components/ui/password-input"; import { Button } from "@/components/ui/button"; -import { authClient, signUp, passkey, useSession, signIn } from "@/lib/auth/auth-client"; -import { updateAccountAction } from "@/features/onboarding/actions/update-account.action"; -import { generatePasskeyContextAction } from "@/features/onboarding/actions/generate-passkey-context.action"; -import type { OnboardingMeta } from "@/features/onboarding/types"; import { BaseSchema, WithPasswordSchema } from "@/features/onboarding/schemas/account.schema"; +import { useUpdateAccount } from "@/features/onboarding/hooks/use-update-account"; +import type { OnboardingMeta } from "@/features/onboarding/types"; export const StepAccountInfo = () => { - const { next, updateContext, state } = useOnboarding(); + const { next, state } = useOnboarding(); const { data: session, refetch: refetchSession } = useSession(); const meta = state?.context.flowData.meta as OnboardingMeta | undefined; const existingAccount = state?.context.flowData.account; const isUpdateMode = !!existingAccount; const passkeyEnabled = meta?.passkeyEnabled ?? false; - // Already authenticated (e.g. came from passkey welcome-back login) — skip account creation useEffect(() => { if (session?.user && !isUpdateMode) { next(); @@ -43,91 +39,7 @@ export const StepAccountInfo = () => { typeof useZodForm >; - const mutation = useMutation({ - 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, - }, - }, - }); - await next(); - return; - } - - if (passkeyEnabled) { - const name = `${values.firstName} ${values.lastName}`; - const context = await generatePasskeyContextAction(name, values.email); - const result = await passkey.addPasskey({ - name: values.email, - context, - }); - if (result?.error) { - toast.error(result.error.message ?? "Passkey registration failed"); - return; - } - // Verify session is set; if not, trigger explicit sign-in - await refetchSession(); - const { data: freshSession } = await authClient.getSession(); - if (!freshSession?.user) { - const signInResult = await (signIn as any).passkey(); - if (signInResult?.error) { - toast.error("Registration succeeded but sign-in failed. Please reload and sign in."); - return; - } - } - await updateContext({ - flowData: { - ...state?.context.flowData, - account: { - firstName: values.firstName, - lastName: values.lastName, - email: values.email, - }, - security: { method: "passkey" }, - }, - }); - await next(); - return; - } - - 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); - }, - }, - ); - }, - }); + const mutation = useUpdateAccount(refetchSession); return (
@@ -135,9 +47,7 @@ export const StepAccountInfo = () => {

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

-

- This step can't be skipped. -

+

This step can't be skipped.

{ render={({ field }) => ( First name - - - + )} @@ -166,9 +74,7 @@ export const StepAccountInfo = () => { render={({ field }) => ( Last name - - - + )} @@ -181,9 +87,7 @@ export const StepAccountInfo = () => { render={({ field }) => ( Email - - - + )} @@ -196,9 +100,7 @@ export const StepAccountInfo = () => { render={({ field }) => ( Password - - - + )}