Skip to content

Commit

Permalink
[#17] Add PasswordReset page and form
Browse files Browse the repository at this point in the history
  • Loading branch information
liamstevens111 committed Mar 20, 2023
1 parent 0e35d2c commit 6a30b5a
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 3 deletions.
6 changes: 6 additions & 0 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
9 changes: 9 additions & 0 deletions src/adapters/authAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', {});
}
Expand Down
Binary file added src/assets/images/bell-notification.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: <LoginScreen />,
},
{
path: '/reset-password',
element: <ResetPasswordScreen />,
},
{
element: <PrivateRoutes />,
children: [
Expand Down
6 changes: 3 additions & 3 deletions src/screens/Login/index.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -116,9 +116,9 @@ function LoginScreen() {
/>

{/* TODO: Change to React Router Link when implement #17 */}
<a href="." className="absolute left-60 top-5 my-8 text-white opacity-50">
<Link to="/reset-password" className="absolute left-60 top-5 my-8 text-white opacity-50">
{t('login.forgot-password')}
</a>
</Link>
</div>
<Button text={t('login.sign-in')} className="h-14 w-80" type="submit" disabled={formSubmitted} />
</form>
Expand Down
101 changes: 101 additions & 0 deletions src/screens/ResetPassword/index.tsx
Original file line number Diff line number Diff line change
@@ -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<string[]>([]);
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
const [formSubmitted, setFormSubmitted] = useState(false);

const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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<HTMLFormElement>) => {
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 (
<>
<p data-test-id="login-header" className="mb-8 w-80 text-center text-white opacity-50">
{t('reset-password.header')}
</p>

<div className="errors">
{errors.length > 0 &&
errors.map((error) => {
return (
<p className="text-center text-red-700" key={error.toString()}>
{error}
</p>
);
})}
</div>

{showSuccessMessage && (
<div className="my-3 w-80 rounded-xl bg-stone-800/60 p-5">
<img className="inline-block" src={bellNotification} alt="notification-bell"></img>
<p className="inline-block p-3 font-semibold text-white">{t('reset-password.check-email')}</p>
<p className="right-10 inline-block p-3 text-white opacity-60">{t('reset-password.success-message')}</p>
</div>
)}

<form onSubmit={handleSubmit}>
<Input
name="email"
label={t('login.email')}
type="text"
value={email}
className="my-3 block h-14 w-80"
onInputChange={handleEmailChange}
/>
<Button text={t('reset-password.recovery-email')} className="h-14 w-80" type="submit" disabled={formSubmitted} />
</form>
</>
);
}

export default ResetPasswordScreen;

0 comments on commit 6a30b5a

Please sign in to comment.