From 14ae5f78a6c7b28338c3377369a31abba08188b4 Mon Sep 17 00:00:00 2001 From: sahil2448 Date: Tue, 27 Jan 2026 15:23:53 +0000 Subject: [PATCH] refractor:refract registration form complexity with code refinement --- .gitignore | 2 +- .../web-ui-registration/src/RegisterForm.tsx | 311 +++++++----------- .../src/components/FormField.tsx | 25 ++ .../src/hooks/useRegistrationErrors.ts | 74 +++++ .../src/hooks/useRegistrationValidation.ts | 50 +++ 5 files changed, 274 insertions(+), 188 deletions(-) create mode 100644 packages/web-ui-registration/src/components/FormField.tsx create mode 100644 packages/web-ui-registration/src/hooks/useRegistrationErrors.ts create mode 100644 packages/web-ui-registration/src/hooks/useRegistrationValidation.ts diff --git a/.gitignore b/.gitignore index 8ee032f2d8c32..1d4d66cf16ed8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies -node_modules +node_modules/ .pnp .pnp.js diff --git a/packages/web-ui-registration/src/RegisterForm.tsx b/packages/web-ui-registration/src/RegisterForm.tsx index 9b7f3e548d3eb..f3edf21111651 100644 --- a/packages/web-ui-registration/src/RegisterForm.tsx +++ b/packages/web-ui-registration/src/RegisterForm.tsx @@ -1,11 +1,6 @@ -/* eslint-disable complexity */ import { FieldGroup, TextInput, - Field, - FieldLabel, - FieldRow, - FieldError, PasswordInput, ButtonGroup, Button, @@ -13,16 +8,19 @@ import { Callout, } from '@rocket.chat/fuselage'; import { Form, ActionLink } from '@rocket.chat/layout'; -import { CustomFieldsForm, PasswordVerifier, useValidatePassword } from '@rocket.chat/ui-client'; -import { useAccountsCustomFields, useSetting, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import { CustomFieldsForm, PasswordVerifier } from '@rocket.chat/ui-client'; +import { useAccountsCustomFields, useSetting } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import { useEffect, useId, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { Trans, useTranslation } from 'react-i18next'; import EmailConfirmationForm from './EmailConfirmationForm'; +import { FormField } from './components/FormField'; import type { DispatchLoginRouter } from './hooks/useLoginRouter'; import { useRegisterMethod } from './hooks/useRegisterMethod'; +import { useRegistrationValidation } from './hooks/useRegistrationValidation'; +import { useRegistrationErrors } from './hooks/useRegistrationErrors'; type LoginRegisterPayload = { name: string; @@ -58,8 +56,6 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo const [serverError, setServerError] = useState(undefined); - const dispatchToastMessage = useToastMessageDispatch(); - const { register, handleSubmit, @@ -72,7 +68,17 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo } = useForm({ mode: 'onBlur' }); const { password } = watch(); - const passwordIsValid = useValidatePassword(password); + const { + getNameValidation, + getEmailValidation, + getUsernameValidation, + getPasswordValidation, + getPasswordConfirmationValidation, + getReasonValidation, + passwordIsValid, + } = useRegistrationValidation(password); + + const { handleRegistrationError } = useRegistrationErrors(setError, setLoginRoute, setServerError); const registerFormRef = useRef(null); @@ -86,39 +92,7 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo registerUser.mutate( { pass: password, ...formData }, { - onError: (error: any) => { - if ([error.error, error.errorType].includes('error-invalid-email')) { - setError('email', { type: 'invalid-email', message: t('registration.component.form.invalidEmail') }); - } - if (error.errorType === 'error-user-already-exists') { - setError('username', { type: 'user-already-exists', message: t('registration.component.form.usernameAlreadyExists') }); - } - if (/Email already exists/.test(error.error)) { - setError('email', { type: 'email-already-exists', message: t('registration.component.form.emailAlreadyExists') }); - } - if (/Username is already in use/.test(error.error)) { - setError('username', { type: 'username-already-exists', message: t('registration.component.form.userAlreadyExist') }); - } - if (/The username provided is not valid/.test(error.error)) { - setError('username', { - type: 'username-contains-invalid-chars', - message: t('registration.component.form.usernameContainsInvalidChars'), - }); - } - if (/Name contains invalid characters/.test(error.error)) { - setError('name', { type: 'name-contains-invalid-chars', message: t('registration.component.form.nameContainsInvalidChars') }); - } - if (/error-too-many-requests/.test(error.error)) { - dispatchToastMessage({ type: 'error', message: error.error }); - } - if (/error-user-is-not-activated/.test(error.error)) { - dispatchToastMessage({ type: 'info', message: t('registration.page.registration.waitActivationWarning') }); - setLoginRoute('login'); - } - if (error.error === 'error-user-registration-custom-field') { - setServerError(error.message); - } - }, + onError: handleRegistrationError, }, ); }; @@ -140,156 +114,119 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo - - - {t('registration.component.form.name')} - - - - - {errors.name && ( - - {errors.name.message} - - )} - - - - {t('registration.component.form.email')} - - - - - {errors.email && ( - - {errors.email.message} - - )} - - - - {t('registration.component.form.username')} - - - - - {errors.username && ( - - {errors.username.message} - - )} - - - - {t('registration.component.form.password')} - - + + + + + + + + + + + + + + + + + + {requiresPasswordConfirmation && ( + (!passwordIsValid ? t('Password_must_meet_the_complexity_requirements') : true), - })} - error={errors.password?.message} + {...register('passwordConfirmation', getPasswordConfirmationValidation(watch('password')))} + error={errors.passwordConfirmation?.message} aria-required='true' - aria-invalid={errors.password ? 'true' : undefined} - id={passwordId} - placeholder={passwordPlaceholder || t('Create_a_password')} - aria-describedby={`${passwordVerifierId} ${passwordId}-error`} + aria-invalid={errors.passwordConfirmation ? 'true' : 'false'} + id={passwordConfirmationId} + aria-describedby={`${passwordConfirmationId}-error`} + placeholder={passwordConfirmationPlaceholder || t('Confirm_password')} + disabled={!passwordIsValid} /> - - {errors?.password && ( - - {errors.password.message} - - )} - - - {requiresPasswordConfirmation && ( - - - {t('registration.component.form.confirmPassword')} - - - (watch('password') === val ? true : t('registration.component.form.invalidConfirmPass')), - })} - error={errors.passwordConfirmation?.message} - aria-required='true' - aria-invalid={errors.passwordConfirmation ? 'true' : 'false'} - id={passwordConfirmationId} - aria-describedby={`${passwordConfirmationId}-error`} - placeholder={passwordConfirmationPlaceholder || t('Confirm_password')} - disabled={!passwordIsValid} - /> - - {errors.passwordConfirmation && ( - - {errors.passwordConfirmation.message} - - )} - + )} + {manuallyApproveNewUsersRequired && ( - - - {t('registration.component.form.reasonToJoin')} - - - - - {errors.reason && ( - - {errors.reason.message} - - )} - + + + )} + {serverError && {serverError}} diff --git a/packages/web-ui-registration/src/components/FormField.tsx b/packages/web-ui-registration/src/components/FormField.tsx new file mode 100644 index 0000000000000..62a354f80d15e --- /dev/null +++ b/packages/web-ui-registration/src/components/FormField.tsx @@ -0,0 +1,25 @@ +import { Field, FieldLabel, FieldRow, FieldError } from '@rocket.chat/fuselage'; +import type { ReactElement, ReactNode } from 'react'; + +type FormFieldProps = { + label: string; + required?: boolean; + htmlFor: string; + children: ReactNode; + error?: string; + errorId: string; +}; + +export const FormField = ({ label, required = false, htmlFor, children, error, errorId }: FormFieldProps): ReactElement => ( + + + {label} + + {children} + {error && ( + + {error} + + )} + +); diff --git a/packages/web-ui-registration/src/hooks/useRegistrationErrors.ts b/packages/web-ui-registration/src/hooks/useRegistrationErrors.ts new file mode 100644 index 0000000000000..99000a2e50ae7 --- /dev/null +++ b/packages/web-ui-registration/src/hooks/useRegistrationErrors.ts @@ -0,0 +1,74 @@ +import { useTranslation } from 'react-i18next'; +import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import type { UseFormSetError } from 'react-hook-form'; +import type { DispatchLoginRouter } from './useLoginRouter'; + +type LoginRegisterPayload = { + name: string; + passwordConfirmation: string; + username: string; + password: string; + email: string; + reason: string; +}; + +export const useRegistrationErrors = ( + setError: UseFormSetError, + setLoginRoute: DispatchLoginRouter, + setServerError: (error: string | undefined) => void, +) => { + const { t } = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + + const handleRegistrationError = (error: any) => { + if ([error.error, error.errorType].includes('error-invalid-email')) { + setError('email', { type: 'invalid-email', message: t('registration.component.form.invalidEmail') }); + return; + } + + if (error.errorType === 'error-user-already-exists') { + setError('username', { type: 'user-already-exists', message: t('registration.component.form.usernameAlreadyExists') }); + return; + } + + if (/Email already exists/.test(error.error)) { + setError('email', { type: 'email-already-exists', message: t('registration.component.form.emailAlreadyExists') }); + return; + } + + if (/Username is already in use/.test(error.error)) { + setError('username', { type: 'username-already-exists', message: t('registration.component.form.userAlreadyExist') }); + return; + } + + if (/The username provided is not valid/.test(error.error)) { + setError('username', { + type: 'username-contains-invalid-chars', + message: t('registration.component.form.usernameContainsInvalidChars'), + }); + return; + } + + if (/Name contains invalid characters/.test(error.error)) { + setError('name', { type: 'name-contains-invalid-chars', message: t('registration.component.form.nameContainsInvalidChars') }); + return; + } + + if (/error-too-many-requests/.test(error.error)) { + dispatchToastMessage({ type: 'error', message: error.error }); + return; + } + + if (/error-user-is-not-activated/.test(error.error)) { + dispatchToastMessage({ type: 'info', message: t('registration.page.registration.waitActivationWarning') }); + setLoginRoute('login'); + return; + } + + if (error.error === 'error-user-registration-custom-field') { + setServerError(error.message); + } + }; + + return { handleRegistrationError }; +}; diff --git a/packages/web-ui-registration/src/hooks/useRegistrationValidation.ts b/packages/web-ui-registration/src/hooks/useRegistrationValidation.ts new file mode 100644 index 0000000000000..71db0e5704ace --- /dev/null +++ b/packages/web-ui-registration/src/hooks/useRegistrationValidation.ts @@ -0,0 +1,50 @@ +import { useSetting } from '@rocket.chat/ui-contexts'; +import { useValidatePassword } from '@rocket.chat/ui-client'; +import { useTranslation } from 'react-i18next'; + +export const useRegistrationValidation = (password: string) => { + const { t } = useTranslation(); + const requireNameForRegister = useSetting('Accounts_RequireNameForSignUp', true); + const passwordIsValid = useValidatePassword(password); + + const getNameValidation = () => ({ + required: requireNameForRegister ? t('Required_field', { field: t('registration.component.form.name') }) : false, + }); + + const getEmailValidation = () => ({ + required: t('Required_field', { field: t('registration.component.form.email') }), + pattern: { + value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, + message: t('registration.component.form.invalidEmail'), + }, + }); + + const getUsernameValidation = () => ({ + required: t('Required_field', { field: t('registration.component.form.username') }), + }); + + const getPasswordValidation = () => ({ + required: t('Required_field', { field: t('registration.component.form.password') }), + validate: () => (!passwordIsValid ? t('Password_must_meet_the_complexity_requirements') : true), + }); + + const getPasswordConfirmationValidation = (watchPassword: string) => ({ + required: t('Required_field', { field: t('registration.component.form.confirmPassword') }), + deps: ['password'], + validate: (val: string) => (watchPassword === val ? true : t('registration.component.form.invalidConfirmPass')), + }); + + const getReasonValidation = () => ({ + required: t('Required_field', { field: t('registration.component.form.reasonToJoin') }), + }); + + return { + getNameValidation, + getEmailValidation, + getUsernameValidation, + getPasswordValidation, + getPasswordConfirmationValidation, + getReasonValidation, + passwordIsValid, + }; +};