diff --git a/frontend/src/api/utils.ts b/frontend/src/api/utils.ts index 140e793e35..bd81719eee 100644 --- a/frontend/src/api/utils.ts +++ b/frontend/src/api/utils.ts @@ -66,7 +66,11 @@ export const Logout = (): void => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - window.Intercom('shutdown'); + if (window && window.Intercom) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + window.Intercom('shutdown'); + } history.push(ROUTES.LOGIN); }; diff --git a/frontend/src/components/WelcomeLeftContainer/styles.ts b/frontend/src/components/WelcomeLeftContainer/styles.ts index 70428a7f1d..be312edac1 100644 --- a/frontend/src/components/WelcomeLeftContainer/styles.ts +++ b/frontend/src/components/WelcomeLeftContainer/styles.ts @@ -13,6 +13,7 @@ export const Container = styled.div` &&& { display: flex; justify-content: center; + gap: 16px; align-items: center; min-height: 100vh; diff --git a/frontend/src/container/ResetPassword/ResetPassword.test.tsx b/frontend/src/container/ResetPassword/ResetPassword.test.tsx new file mode 100644 index 0000000000..b3345fedf3 --- /dev/null +++ b/frontend/src/container/ResetPassword/ResetPassword.test.tsx @@ -0,0 +1,72 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { act } from 'react-dom/test-utils'; + +import ResetPassword from './index'; + +jest.mock('api/user/resetPassword', () => ({ + __esModule: true, + default: jest.fn(), +})); + +jest.useFakeTimers(); + +describe('ResetPassword Component', () => { + beforeEach(() => { + userEvent.setup(); + jest.clearAllMocks(); + }); + + it('renders ResetPassword component correctly', () => { + render(); + expect(screen.getByText('Reset Your Password')).toBeInTheDocument(); + expect(screen.getByLabelText('Password')).toBeInTheDocument(); + // eslint-disable-next-line sonarjs/no-duplicate-string + expect(screen.getByLabelText('Confirm Password')).toBeInTheDocument(); + expect( + // eslint-disable-next-line sonarjs/no-duplicate-string + screen.getByRole('button', { name: 'Get Started' }), + ).toBeInTheDocument(); + }); + + it('disables the "Get Started" button when password is invalid', async () => { + render(); + + const passwordInput = screen.getByLabelText('Password'); + const confirmPasswordInput = screen.getByLabelText('Confirm Password'); + const submitButton = screen.getByRole('button', { name: 'Get Started' }); + + act(() => { + // Set invalid password + fireEvent.change(passwordInput, { target: { value: 'password' } }); + fireEvent.change(confirmPasswordInput, { target: { value: 'password' } }); + }); + + await waitFor(() => { + // Expect the "Get Started" button to be disabled + expect(submitButton).toBeDisabled(); + }); + }); + + it('enables the "Get Started" button when password is valid', async () => { + render(); + + const passwordInput = screen.getByLabelText('Password'); + const confirmPasswordInput = screen.getByLabelText('Confirm Password'); + const submitButton = screen.getByRole('button', { name: 'Get Started' }); + + act(() => { + fireEvent.change(passwordInput, { target: { value: 'newPassword' } }); + fireEvent.change(confirmPasswordInput, { target: { value: 'newPassword' } }); + }); + + act(() => { + jest.advanceTimersByTime(500); + }); + + await waitFor(() => { + // Expect the "Get Started" button to be enabled + expect(submitButton).toBeEnabled(); + }); + }); +}); diff --git a/frontend/src/container/ResetPassword/index.tsx b/frontend/src/container/ResetPassword/index.tsx index eac4b098cd..f4be5310e2 100644 --- a/frontend/src/container/ResetPassword/index.tsx +++ b/frontend/src/container/ResetPassword/index.tsx @@ -3,6 +3,7 @@ import resetPasswordApi from 'api/user/resetPassword'; import { Logout } from 'api/utils'; import WelcomeLeftContainer from 'components/WelcomeLeftContainer'; import ROUTES from 'constants/routes'; +import useDebouncedFn from 'hooks/useDebouncedFunction'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import { Label } from 'pages/SignUp/styles'; @@ -20,6 +21,8 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element { const [confirmPasswordError, setConfirmPasswordError] = useState( false, ); + + const [isValidPassword, setIsValidPassword] = useState(false); const [loading, setLoading] = useState(false); const { t } = useTranslation(['common']); const { search } = useLocation(); @@ -35,7 +38,7 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element { } }, [token]); - const handleSubmit: () => Promise = async () => { + const handleFormSubmit: () => Promise = async () => { try { setLoading(true); const { password } = form.getFieldsValue(); @@ -72,38 +75,88 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element { }); } }; - const handleValuesChange: (changedValues: FormValues) => void = ( - changedValues, - ) => { - if ('confirmPassword' in changedValues) { - const { confirmPassword } = changedValues; - const isSamePassword = form.getFieldValue('password') === confirmPassword; - setConfirmPasswordError(!isSamePassword); + const validatePassword = (): boolean => { + const { password, confirmPassword } = form.getFieldsValue(); + + if ( + password && + confirmPassword && + password.trim() && + confirmPassword.trim() && + password.length > 0 && + confirmPassword.length > 0 + ) { + return password === confirmPassword; + } + + return false; + }; + + const handleValuesChange = useDebouncedFn((): void => { + const { password, confirmPassword } = form.getFieldsValue(); + + if (!password || !confirmPassword) { + setIsValidPassword(false); + } + + if ( + password && + confirmPassword && + password.trim() && + confirmPassword.trim() + ) { + const isValid = validatePassword(); + + setIsValidPassword(isValid); + setConfirmPasswordError(!isValid); + } + }, 100); + + const handleSubmit = (): void => { + const isValid = validatePassword(); + setIsValidPassword(isValid); + + if (token) { + handleFormSubmit(); } }; return ( - + Reset Your Password
- - - - + + + +
- - - - + + + + {confirmPasswordError && ( - Passwords don’t match. Please try again + The passwords entered do not match. Please double-check and re-enter + your passwords. )}
@@ -124,13 +178,7 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element { htmlType="submit" data-attr="signup" loading={loading} - disabled={ - loading || - !form.getFieldValue('password') || - !form.getFieldValue('confirmPassword') || - confirmPasswordError || - token === null - } + disabled={!isValidPassword || loading} > Get Started diff --git a/frontend/src/container/ResetPassword/styles.ts b/frontend/src/container/ResetPassword/styles.ts index e59a453695..f71860382e 100644 --- a/frontend/src/container/ResetPassword/styles.ts +++ b/frontend/src/container/ResetPassword/styles.ts @@ -4,8 +4,12 @@ import styled from 'styled-components'; export const FormWrapper = styled(Card)` display: flex; justify-content: center; - max-width: 432px; + width: 432px; flex: 1; + + .ant-card-body { + width: 100%; + } `; export const ButtonContainer = styled.div`