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
This commit is contained in:
committed by
GitHub
parent
5622572f36
commit
001903ddac
@@ -1,16 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useCallback, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
import { CheckCircledIcon } from '@radix-ui/react-icons';
|
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 { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||||
import { If } from '@kit/ui/if';
|
import { If } from '@kit/ui/if';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
import { useCaptchaToken } from '../captcha/client';
|
import { useCaptchaToken } from '../captcha/client';
|
||||||
|
import { usePasswordSignUpFlow } from '../hooks/use-sign-up-flow';
|
||||||
import { AuthErrorAlert } from './auth-error-alert';
|
import { AuthErrorAlert } from './auth-error-alert';
|
||||||
import { PasswordSignUpForm } from './password-sign-up-form';
|
import { PasswordSignUpForm } from './password-sign-up-form';
|
||||||
|
|
||||||
@@ -19,7 +16,6 @@ interface EmailPasswordSignUpContainerProps {
|
|||||||
defaultValues?: {
|
defaultValues?: {
|
||||||
email: string;
|
email: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
onSignUp?: (userId?: string) => unknown;
|
onSignUp?: (userId?: string) => unknown;
|
||||||
emailRedirectTo: string;
|
emailRedirectTo: string;
|
||||||
}
|
}
|
||||||
@@ -32,54 +28,17 @@ export function EmailPasswordSignUpContainer({
|
|||||||
}: EmailPasswordSignUpContainerProps) {
|
}: EmailPasswordSignUpContainerProps) {
|
||||||
const { captchaToken, resetCaptchaToken } = useCaptchaToken();
|
const { captchaToken, resetCaptchaToken } = useCaptchaToken();
|
||||||
|
|
||||||
const signUpMutation = useSignUpWithEmailAndPassword();
|
const {
|
||||||
const redirecting = useRef(false);
|
signUp: onSignupRequested,
|
||||||
const [showVerifyEmailAlert, setShowVerifyEmailAlert] = useState(false);
|
loading,
|
||||||
const appEvents = useAppEvents();
|
error,
|
||||||
|
showVerifyEmailAlert,
|
||||||
const loading = signUpMutation.isPending || redirecting.current;
|
} = usePasswordSignUpFlow({
|
||||||
|
emailRedirectTo,
|
||||||
const onSignupRequested = useCallback(
|
onSignUp,
|
||||||
async (credentials: { email: string; password: string }) => {
|
captchaToken,
|
||||||
if (loading) {
|
resetCaptchaToken,
|
||||||
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,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -88,7 +47,7 @@ export function EmailPasswordSignUpContainer({
|
|||||||
</If>
|
</If>
|
||||||
|
|
||||||
<If condition={!showVerifyEmailAlert}>
|
<If condition={!showVerifyEmailAlert}>
|
||||||
<AuthErrorAlert error={signUpMutation.error} />
|
<AuthErrorAlert error={error} />
|
||||||
|
|
||||||
<PasswordSignUpForm
|
<PasswordSignUpForm
|
||||||
onSubmit={onSignupRequested}
|
onSubmit={onSignupRequested}
|
||||||
|
|||||||
@@ -22,12 +22,7 @@ import { Trans } from '@kit/ui/trans';
|
|||||||
import { PasswordSignUpSchema } from '../schemas/password-sign-up.schema';
|
import { PasswordSignUpSchema } from '../schemas/password-sign-up.schema';
|
||||||
import { TermsAndConditionsFormField } from './terms-and-conditions-form-field';
|
import { TermsAndConditionsFormField } from './terms-and-conditions-form-field';
|
||||||
|
|
||||||
export function PasswordSignUpForm({
|
interface PasswordSignUpFormProps {
|
||||||
defaultValues,
|
|
||||||
displayTermsCheckbox,
|
|
||||||
onSubmit,
|
|
||||||
loading,
|
|
||||||
}: {
|
|
||||||
defaultValues?: {
|
defaultValues?: {
|
||||||
email: string;
|
email: string;
|
||||||
};
|
};
|
||||||
@@ -40,7 +35,14 @@ export function PasswordSignUpForm({
|
|||||||
repeatPassword: string;
|
repeatPassword: string;
|
||||||
}) => unknown;
|
}) => unknown;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}) {
|
}
|
||||||
|
|
||||||
|
export function PasswordSignUpForm({
|
||||||
|
defaultValues,
|
||||||
|
displayTermsCheckbox,
|
||||||
|
onSubmit,
|
||||||
|
loading,
|
||||||
|
}: PasswordSignUpFormProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
@@ -96,6 +98,7 @@ export function PasswordSignUpForm({
|
|||||||
required
|
required
|
||||||
data-test={'password-input'}
|
data-test={'password-input'}
|
||||||
type="password"
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
placeholder={''}
|
placeholder={''}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
|
|||||||
91
packages/features/auth/src/hooks/use-sign-up-flow.ts
Normal file
91
packages/features/auth/src/hooks/use-sign-up-flow.ts
Normal file
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user