diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 8f819dd..a9bc1d0 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1,8 +1,10 @@ { "login": { - "sign_in": "Sign in", + "sign-in": "Sign in", "email": "Email", "password": "Password", - "forgot_password": "Forgot?" + "forgot-password": "Forgot?", + "invalid-email": "Email has invalid format", + "invalid-password": "Password should be at least {{passwordMinLength}}" } } diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 502f000..43aa938 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -4,12 +4,13 @@ type ButtonProps = { text: string; type?: 'button' | 'submit' | 'reset'; className?: string; + disabled?: boolean; onButtonClick?: () => void; }; -function Button({ text, type, className, onButtonClick }: ButtonProps) { +function Button({ text, type, className, disabled, onButtonClick }: ButtonProps) { return ( - ); diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..3904c7f --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1 @@ +export const PASSWORD_MIN_LENGTH = 5; diff --git a/src/helpers/validators.ts b/src/helpers/validators.ts index 42282f8..3cf4920 100644 --- a/src/helpers/validators.ts +++ b/src/helpers/validators.ts @@ -1,3 +1,5 @@ +import { PASSWORD_MIN_LENGTH } from '../constants'; + export const isEmailValid = (email: string) => { const emailRegEx = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i; @@ -5,7 +7,6 @@ export const isEmailValid = (email: string) => { }; export const isPasswordValid = (password: string) => { - const passwordRegEx = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/; - - return passwordRegEx.test(password); + // TODO: Should match backend logic or remove completely + return password.length >= PASSWORD_MIN_LENGTH; }; diff --git a/src/screens/Home/login.tsx b/src/screens/Home/login.tsx index b9c5cfd..8a7dc73 100644 --- a/src/screens/Home/login.tsx +++ b/src/screens/Home/login.tsx @@ -1,5 +1,8 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; + +import { AxiosError } from 'axios'; import AuthAdapter from 'adapters/authAdapter'; import logo from 'assets/images/logo.svg'; @@ -8,27 +11,16 @@ import Input from 'components/Input'; import { setToken } from 'helpers/userToken'; import { isEmailValid, isPasswordValid } from 'helpers/validators'; -type InputField = 'Email' | 'Password'; - -type Errors = { - Email?: string; - Password?: string; -}; +import { PASSWORD_MIN_LENGTH } from '../../constants'; function LoginScreen() { + const navigate = useNavigate(); const { t } = useTranslation('translation'); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const [errors, setErrors] = useState({}); - - const removeError = (field: InputField) => { - const newState: Errors = { - ...errors, - }; - delete newState[field]; - setErrors(newState); - }; + const [errors, setErrors] = useState([]); + const [formSubmitted, setFormSubmitted] = useState(false); const handleEmailChange = (e: React.ChangeEvent) => { setEmail(e.target.value); @@ -39,31 +31,44 @@ function LoginScreen() { }; const performLogin = async () => { - const response = await AuthAdapter.login({ email: email, password: password }); - - const { - attributes: { access_token: accessToken, refresh_token: refreshToken }, - } = await response.data; - - setToken({ accessToken: accessToken, refreshToken: refreshToken }); + try { + const response = await AuthAdapter.login({ email: email, password: password }); + + const { + attributes: { access_token: accessToken, refresh_token: refreshToken }, + } = await response.data; + + setToken({ accessToken: accessToken, refreshToken: refreshToken }); + navigate('/'); + } catch (error) { + let errorMessage = ''; + + if (error instanceof Error) { + errorMessage = (error as AxiosError).response?.data?.errors[0]?.detail || error.message || 'Internal error'; + } + setErrors([errorMessage]); + } finally { + setFormSubmitted(false); + } }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); + const formErrors = []; + if (!isEmailValid(email)) { - setErrors({ ...errors, Email: 'Is invalid' }); - } else { - removeError('Email'); + formErrors.push(t('login.invalid-email')); } if (!isPasswordValid(password)) { - setErrors({ ...errors, Password: 'Is invalid' }); - } else { - removeError('Password'); + formErrors.push(t('login.invalid-password', { passwordMinLength: PASSWORD_MIN_LENGTH })); } - if (Object.keys(errors).length === 0) { + setErrors(formErrors); + + if (formErrors.length === 0) { + setFormSubmitted(true); performLogin(); } }; @@ -72,8 +77,20 @@ function LoginScreen() { <> logo

- {t('login.sign_in')} to Nimble + {t('login.sign-in')} to Nimble

+ +
+ {errors.length > 0 && + errors.map((error) => { + return ( +

+ {error} +

+ ); + })} +
+
handleEmailChange(e)} + onInputChange={handleEmailChange} />
handlePasswordChange(e)} + onInputChange={handlePasswordChange} /> {/* Change to React Router Link when implement #17 */} - {t('login.forgot_password')} + {t('login.forgot-password')}
-