diff --git a/apps/desktop/src/components/ChangePassword/ChangePasswordForm.tsx b/apps/desktop/src/components/ChangePassword/ChangePasswordForm.tsx index b85e636939..804125fd5e 100644 --- a/apps/desktop/src/components/ChangePassword/ChangePasswordForm.tsx +++ b/apps/desktop/src/components/ChangePassword/ChangePasswordForm.tsx @@ -25,7 +25,7 @@ type ChangePasswordFormValues = { export const ChangePasswordForm = () => { const { onClose } = useDynamicModalContext(); - const form = useForm({ mode: "onBlur" }); + const form = useForm({ mode: "all" }); const toast = useToast(); const dispatch = useAppDispatch(); const { handleAsyncAction, isLoading } = useAsyncActionHandler(); @@ -66,6 +66,7 @@ export const ChangePasswordForm = () => { data-testid="current-password" inputName="currentPassword" label="Current Password" + minLength={0} placeholder="Enter your current password" required="Current password is required" /> @@ -78,6 +79,7 @@ export const ChangePasswordForm = () => { { data-testid="new-password-confirmation" inputName="newPasswordConfirmation" label="Confirm New Password" + minLength={0} placeholder="Confirm new password" required="Confirmation is required" validate={(val: string) => diff --git a/apps/desktop/src/components/PasswordInput.tsx b/apps/desktop/src/components/PasswordInput.tsx index 214dea37a7..8ac89488ee 100644 --- a/apps/desktop/src/components/PasswordInput.tsx +++ b/apps/desktop/src/components/PasswordInput.tsx @@ -13,7 +13,7 @@ import { type FieldValues, type Path, type RegisterOptions, useFormContext } fro import { EyeIcon, EyeSlashIcon } from "../assets/icons"; import colors from "../style/colors"; -const MIN_LENGTH = 8; +const MIN_LENGTH = 12; // is needed to be compatible with the useForm's type parameter (FormData) // > makes sure that we can pass in only valid inputName that exists in FormData @@ -24,7 +24,7 @@ type PasswordInputProps> = { checkPasswordStrength?: boolean; required?: string | boolean; minLength?: RegisterOptions["minLength"]; - validate?: RegisterOptions["validate"]; + validate?: (val: string) => string | boolean; } & InputProps; // TODO: add an error message and make it nested under FormControl @@ -42,8 +42,23 @@ export const PasswordInput = >({ const [showPassword, setShowPassword] = useState(false); const { validatePassword, PasswordStrengthBar } = usePasswordValidation({ colorScheme: colors.gray[500], + inputName, }); + const handleValidate = (val: string) => { + if (validate) { + const validateResult = validate(val); + + if (checkPasswordStrength && validateResult === true) { + return validatePassword(val); + } + + return validateResult; + } else if (checkPasswordStrength) { + return validatePassword(val); + } + }; + return ( <> {label} @@ -62,7 +77,7 @@ export const PasswordInput = >({ message: `Your password must be at least ${minLength} characters long`, } : undefined, - validate: checkPasswordStrength ? validatePassword : validate, + validate: handleValidate, })} {...rest} /> diff --git a/apps/web/src/components/Menu/ChangePasswordMenu/ChangePasswordMenu.tsx b/apps/web/src/components/Menu/ChangePasswordMenu/ChangePasswordMenu.tsx index 92a1ade60b..258213d984 100644 --- a/apps/web/src/components/Menu/ChangePasswordMenu/ChangePasswordMenu.tsx +++ b/apps/web/src/components/Menu/ChangePasswordMenu/ChangePasswordMenu.tsx @@ -12,7 +12,7 @@ type ChangePasswordMenuValues = { }; export const ChangePasswordMenu = () => { - const form = useForm({ mode: "onBlur" }); + const form = useForm({ mode: "all" }); const toast = useToast(); const dispatch = useAppDispatch(); const { handleAsyncAction, isLoading } = useAsyncActionHandler(); @@ -37,11 +37,13 @@ export const ChangePasswordMenu = () => { data-testid="current-password" inputName="currentPassword" label="Current Password" + minLength={0} placeholder="Your password" required="Current password is required" /> { data-testid="new-password-confirmation" inputName="newPasswordConfirmation" label="Confirm password" + minLength={0} placeholder="Confirm password" required="Confirmation is required" validate={(val: string) => diff --git a/apps/web/src/components/PasswordInput/PasswordInput.tsx b/apps/web/src/components/PasswordInput/PasswordInput.tsx index b686cead64..e6b97a5f42 100644 --- a/apps/web/src/components/PasswordInput/PasswordInput.tsx +++ b/apps/web/src/components/PasswordInput/PasswordInput.tsx @@ -26,7 +26,7 @@ type PasswordInputProps> = { required?: string | boolean; checkPasswordStrength?: boolean; minLength?: RegisterOptions["minLength"]; - validate?: RegisterOptions["validate"]; + validate?: (val: string) => string | boolean; } & InputProps & { "data-testid"?: string; }; @@ -46,13 +46,29 @@ export const PasswordInput = >({ formState: { errors }, } = useFormContext(); const [showPassword, setShowPassword] = useState(false); - const { validatePassword, PasswordStrengthBar } = usePasswordValidation(); + const { validatePassword, PasswordStrengthBar } = usePasswordValidation({ + inputName, + }); const color = useColor(); const error = errors[inputName]; const errorMessage = error?.message as string; + const handleValidate = (val: string) => { + if (validate) { + const validateResult = validate(val); + + if (checkPasswordStrength && validateResult === true) { + return validatePassword(val); + } + + return validateResult; + } else if (checkPasswordStrength) { + return validatePassword(val); + } + }; + return ( {label} @@ -71,7 +87,7 @@ export const PasswordInput = >({ message: `Your password must be at least ${minLength} characters long`, } : undefined, - validate: checkPasswordStrength ? validatePassword : validate, + validate: handleValidate, })} {...rest} /> diff --git a/packages/components/src/hooks/usePasswordValidation.tsx b/packages/components/src/hooks/usePasswordValidation.tsx index 568186aeb1..34830588ee 100644 --- a/packages/components/src/hooks/usePasswordValidation.tsx +++ b/packages/components/src/hooks/usePasswordValidation.tsx @@ -9,13 +9,14 @@ const DEFAULT_COLOR_SCHEME = "gray.100"; type PasswordStrengthBarProps = { score: number; colorScheme: string; + inputName: string; }; -const PasswordStrengthBar = ({ score, colorScheme }: PasswordStrengthBarProps) => { +const PasswordStrengthBar = ({ score, colorScheme, inputName }: PasswordStrengthBarProps) => { const form = useFormContext(); const colors = [colorScheme, "red.500", "yellow.500", "green.500"]; - const passwordError = form.formState.errors.password; + const passwordError = form.formState.errors[inputName]; const getColor = (index: number) => { switch (score) { @@ -76,15 +77,17 @@ const PasswordStrengthBar = ({ score, colorScheme }: PasswordStrengthBarProps) = type UsePasswordValidationProps = { colorScheme?: string; + inputName?: string; }; export const usePasswordValidation = ({ colorScheme = DEFAULT_COLOR_SCHEME, + inputName = "password", }: UsePasswordValidationProps = {}) => { const [passwordScore, setPasswordScore] = useState(DEFAULT_SCORE); const form = useFormContext(); - const passwordError = form.formState.errors.password; + const passwordError = form.formState.errors[inputName]; useEffect(() => { if (passwordError?.type === "required") { @@ -105,6 +108,8 @@ export const usePasswordValidation = ({ return { validatePassword, - PasswordStrengthBar: , + PasswordStrengthBar: ( + + ), }; };