diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 332ce1f..5da4f66 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -7,5 +7,11 @@ "invalid-email": "Email has invalid format", "invalid-password": "Password should be at least {{passwordMinLength}}", "generic-server-error": "There was a problem receiving a response from the server" + }, + "reset-password": { + "recovery-email": "Send Recovery Email", + "header": "Enter your email to receive instructions for resetting your password.", + "success-message": "We've emailed you instructions to reset your password", + "check-email": "Check your email" } } diff --git a/src/adapters/authAdapter.ts b/src/adapters/authAdapter.ts index b7729b7..fa6cd33 100644 --- a/src/adapters/authAdapter.ts +++ b/src/adapters/authAdapter.ts @@ -47,6 +47,15 @@ class AuthAdapter extends BaseAdapter { return this.prototype.postRequest('oauth/revoke', { data: requestParams }); } + static resetPassword(email: string) { + const requestParams = { + ...commonParams, + user: { email: email }, + }; + + return this.prototype.postRequest('passwords', { data: requestParams }); + } + static getUser() { return this.prototype.getRequest('me', {}); } diff --git a/src/assets/images/bell-notification.png b/src/assets/images/bell-notification.png new file mode 100644 index 0000000..329b93a Binary files /dev/null and b/src/assets/images/bell-notification.png differ diff --git a/src/routes/index.tsx b/src/routes/index.tsx index fd3fb3a..7689d25 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -3,12 +3,17 @@ import { RouteObject } from 'react-router-dom'; import PrivateRoutes from 'components/PrivateRoutes'; import HomeScreen from 'screens/Home'; import LoginScreen from 'screens/Login'; +import ResetPasswordScreen from 'screens/ResetPassword'; const routes: RouteObject[] = [ { path: '/login', element: , }, + { + path: '/reset-password', + element: , + }, { element: , children: [ diff --git a/src/screens/Login/index.tsx b/src/screens/Login/index.tsx index a891d12..1cbd43e 100644 --- a/src/screens/Login/index.tsx +++ b/src/screens/Login/index.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, Link } from 'react-router-dom'; import { AxiosError } from 'axios'; @@ -116,9 +116,9 @@ function LoginScreen() { /> {/* TODO: Change to React Router Link when implement #17 */} - + {t('login.forgot-password')} - + diff --git a/src/screens/ResetPassword/index.tsx b/src/screens/ResetPassword/index.tsx new file mode 100644 index 0000000..f03fcbe --- /dev/null +++ b/src/screens/ResetPassword/index.tsx @@ -0,0 +1,101 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { AxiosError } from 'axios'; + +import AuthAdapter from 'adapters/authAdapter'; +import bellNotification from 'assets/images/bell-notification.png'; +import Button from 'components/Button'; +import Input from 'components/Input'; +import { isEmailValid } from 'helpers/validators'; + +function ResetPasswordScreen() { + const { t } = useTranslation('translation'); + + const [email, setEmail] = useState(''); + const [errors, setErrors] = useState([]); + const [showSuccessMessage, setShowSuccessMessage] = useState(false); + const [formSubmitted, setFormSubmitted] = useState(false); + + const handleEmailChange = (e: React.ChangeEvent) => { + setEmail(e.target.value); + }; + + const performPasswordReset = async () => { + try { + const resetPasswordResponse = await AuthAdapter.resetPassword(email); + + console.log(resetPasswordResponse); + } catch (error) { + let errorMessage = t('login.generic-server-error'); + + if (error instanceof Error) { + errorMessage = (error as AxiosError).response?.data?.errors[0]?.detail || error.cause || errorMessage; + } + setErrors([errorMessage]); + } finally { + setFormSubmitted(false); + setShowSuccessMessage(true); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + const formErrors = []; + + if (!isEmailValid(email)) { + formErrors.push(t('login.invalid-email')); + } + + setErrors(formErrors); + + if (formErrors.length === 0) { + performPasswordReset(); + setFormSubmitted(true); + } else { + setShowSuccessMessage(false); + } + }; + + return ( + <> + + {t('reset-password.header')} + + + + {errors.length > 0 && + errors.map((error) => { + return ( + + {error} + + ); + })} + + + {showSuccessMessage && ( + + + {t('reset-password.check-email')} + {t('reset-password.success-message')} + + )} + + + + + + > + ); +} + +export default ResetPasswordScreen;
+ {t('reset-password.header')} +
+ {error} +
{t('reset-password.check-email')}
{t('reset-password.success-message')}