From 001903ddacec76c8b437396a547bf667fbe346d3 Mon Sep 17 00:00:00 2001 From: Giancarlo Buomprisco Date: Mon, 3 Feb 2025 11:34:26 +0700 Subject: [PATCH] Refactor password sign-up flow and improve form usability (#131) - Extracted sign-up logic into a new `usePasswordSignUpFlow` hook - Simplified `EmailPasswordSignUpContainer` component - Added `autoComplete="new-password"` to password input for better UX - Converted `PasswordSignUpForm` props to a TypeScript interface --- .../components/password-sign-up-container.tsx | 67 +++----------- .../src/components/password-sign-up-form.tsx | 17 ++-- .../auth/src/hooks/use-sign-up-flow.ts | 91 +++++++++++++++++++ 3 files changed, 114 insertions(+), 61 deletions(-) create mode 100644 packages/features/auth/src/hooks/use-sign-up-flow.ts diff --git a/packages/features/auth/src/components/password-sign-up-container.tsx b/packages/features/auth/src/components/password-sign-up-container.tsx index 136ec97a8..5cbe21a88 100644 --- a/packages/features/auth/src/components/password-sign-up-container.tsx +++ b/packages/features/auth/src/components/password-sign-up-container.tsx @@ -1,16 +1,13 @@ 'use client'; -import { useCallback, useRef, useState } from 'react'; - import { CheckCircledIcon } from '@radix-ui/react-icons'; -import { useAppEvents } from '@kit/shared/events'; -import { useSignUpWithEmailAndPassword } from '@kit/supabase/hooks/use-sign-up-with-email-password'; import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; import { If } from '@kit/ui/if'; import { Trans } from '@kit/ui/trans'; import { useCaptchaToken } from '../captcha/client'; +import { usePasswordSignUpFlow } from '../hooks/use-sign-up-flow'; import { AuthErrorAlert } from './auth-error-alert'; import { PasswordSignUpForm } from './password-sign-up-form'; @@ -19,7 +16,6 @@ interface EmailPasswordSignUpContainerProps { defaultValues?: { email: string; }; - onSignUp?: (userId?: string) => unknown; emailRedirectTo: string; } @@ -32,54 +28,17 @@ export function EmailPasswordSignUpContainer({ }: EmailPasswordSignUpContainerProps) { const { captchaToken, resetCaptchaToken } = useCaptchaToken(); - const signUpMutation = useSignUpWithEmailAndPassword(); - const redirecting = useRef(false); - const [showVerifyEmailAlert, setShowVerifyEmailAlert] = useState(false); - const appEvents = useAppEvents(); - - const loading = signUpMutation.isPending || redirecting.current; - - const onSignupRequested = useCallback( - async (credentials: { email: string; password: string }) => { - if (loading) { - return; - } - - try { - const data = await signUpMutation.mutateAsync({ - ...credentials, - emailRedirectTo, - captchaToken, - }); - - appEvents.emit({ - type: 'user.signedUp', - payload: { - method: 'password', - }, - }); - - setShowVerifyEmailAlert(true); - - if (onSignUp) { - onSignUp(data.user?.id); - } - } catch (error) { - console.error(error); - } finally { - resetCaptchaToken(); - } - }, - [ - appEvents, - captchaToken, - emailRedirectTo, - loading, - onSignUp, - resetCaptchaToken, - signUpMutation, - ], - ); + const { + signUp: onSignupRequested, + loading, + error, + showVerifyEmailAlert, + } = usePasswordSignUpFlow({ + emailRedirectTo, + onSignUp, + captchaToken, + resetCaptchaToken, + }); return ( <> @@ -88,7 +47,7 @@ export function EmailPasswordSignUpContainer({ - + unknown; loading: boolean; -}) { +} + +export function PasswordSignUpForm({ + defaultValues, + displayTermsCheckbox, + onSubmit, + loading, +}: PasswordSignUpFormProps) { const { t } = useTranslation(); const form = useForm({ @@ -96,6 +98,7 @@ export function PasswordSignUpForm({ required data-test={'password-input'} type="password" + autoComplete="new-password" placeholder={''} {...field} /> diff --git a/packages/features/auth/src/hooks/use-sign-up-flow.ts b/packages/features/auth/src/hooks/use-sign-up-flow.ts new file mode 100644 index 000000000..a6ba52e78 --- /dev/null +++ b/packages/features/auth/src/hooks/use-sign-up-flow.ts @@ -0,0 +1,91 @@ +'use client'; + +import { useCallback } from 'react'; + +import { useRouter } from 'next/navigation'; + +import { useAppEvents } from '@kit/shared/events'; +import { useSignUpWithEmailAndPassword } from '@kit/supabase/hooks/use-sign-up-with-email-password'; + +type SignUpCredentials = { + email: string; + password: string; +}; + +type UseSignUpFlowProps = { + emailRedirectTo: string; + onSignUp?: (userId?: string) => unknown; + captchaToken?: string; + resetCaptchaToken?: () => void; +}; + +/** + * @name usePasswordSignUpFlow + * @description + * This hook is used to handle the sign up flow using the email and password method. + */ +export function usePasswordSignUpFlow({ + emailRedirectTo, + onSignUp, + captchaToken, + resetCaptchaToken, +}: UseSignUpFlowProps) { + const router = useRouter(); + const signUpMutation = useSignUpWithEmailAndPassword(); + const appEvents = useAppEvents(); + + const signUp = useCallback( + async (credentials: SignUpCredentials) => { + if (signUpMutation.isPending) { + return; + } + + try { + const data = await signUpMutation.mutateAsync({ + ...credentials, + emailRedirectTo, + captchaToken, + }); + + // emit event to track sign up + appEvents.emit({ + type: 'user.signedUp', + payload: { + method: 'password', + }, + }); + + // Update URL with success status. This is useful for password managers + // to understand that the form was submitted successfully. + const url = new URL(window.location.href); + url.searchParams.set('status', 'success'); + router.replace(url.pathname + url.search); + + if (onSignUp) { + onSignUp(data.user?.id); + } + } catch (error) { + console.error(error); + throw error; + } finally { + resetCaptchaToken?.(); + } + }, + [ + signUpMutation, + emailRedirectTo, + captchaToken, + appEvents, + router, + onSignUp, + resetCaptchaToken, + ], + ); + + return { + signUp, + loading: signUpMutation.isPending, + error: signUpMutation.error, + showVerifyEmailAlert: signUpMutation.isSuccess, + }; +}