Handle password reset (#349)

* Refactor notifications

* Handle password reset
This commit is contained in:
Piotr Rogowski 2021-12-26 16:20:02 +01:00 committed by GitHub
parent ba3833b7dd
commit 9b8eaa2dac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 230 additions and 65 deletions

View File

@ -7,7 +7,10 @@ import {
Redirect,
generatePath,
} from 'react-router-dom';
import { Layout } from 'antd';
import {
Layout,
Result,
} from 'antd';
import { connect } from 'react-redux';
import {
ReactNode,
@ -36,6 +39,7 @@ import useStorage from './hooks/useStorage';
import useConfig from './hooks/useConfig';
import Login from './components/Auth/Login';
import SignUp from './components/Auth/SignUp';
import ResetPassword from './components/Auth/ResetPassword';
const { Content } = Layout;
@ -139,7 +143,17 @@ const App = ({ ui, config }: { ui: UIState, config: ConfigType }) => {
<SignUp />
</ContentFor>
</Route>
<Route path={Routes.RESET_PASSWORD}>
<ContentFor>
<ResetPassword />
</ContentFor>
</Route>
</Switch>
<Result
status="warning"
title="Page not found"
style={{ marginTop: 50 }}
/>
</Layout>
<StatusBar />
</>

View File

@ -4,7 +4,6 @@ import {
Input,
Button,
Divider,
notification,
} from 'antd';
import {
MailOutlined,
@ -17,7 +16,12 @@ import {
import { useAuth } from '../../contexts/AuthContext';
import { Routes } from '../../routes';
import validateMessages from './validateMessages';
import emailNotVerifiedWarning from './emailNotVerifiedWarning';
import {
emailNotVerified,
logInFailed,
logInSuccessful,
} from './notifications';
import { containerStyle } from './common';
const { Item } = Form;
@ -31,34 +35,24 @@ const Login = () => {
setIsLoading(true);
try {
const userCredentials = await login(email, password);
notification.success({
message: 'Login successful',
description: 'Welcome back!',
});
logInSuccessful();
if (!userCredentials.user.emailVerified) {
emailNotVerifiedWarning();
emailNotVerified();
}
history.push(Routes.ROOT);
} catch (err) {
} catch (error) {
form.resetFields();
console.warn(err);
notification.error({
message: 'Login failed',
description: (err as Error).message,
});
console.warn(error);
logInFailed(error as Error);
setIsLoading(false);
}
};
return (
<div style={{
padding: 20,
maxWidth: 350,
margin: '0 auto',
}}>
<Divider>Login</Divider>
<div style={containerStyle}>
<Divider>Log In</Divider>
<Form
initialValues={{ remember: true }}
onFinish={onFinish}
@ -93,11 +87,11 @@ const Login = () => {
style={{ width: '100%' }}
loading={isLoading}
>
Log in
Log In
</Button>
</Item>
<Link to={Routes.SIGN_UP}>Sign Up now!</Link>
<Link to="/" style={{ float: 'right' }}>
<Link to={Routes.SIGN_UP}>Sign Up now</Link>
<Link to={Routes.RESET_PASSWORD} style={{ float: 'right' }}>
Forgot password?
</Link>
</Form>

View File

@ -0,0 +1,83 @@
import { useState } from 'react';
import {
Form,
Input,
Button,
Divider,
} from 'antd';
import { MailOutlined } from '@ant-design/icons';
import {
Link,
useHistory,
} from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
import { Routes } from '../../routes';
import validateMessages from './validateMessages';
import {
resetFailed,
resetSuccessful,
} from './notifications';
import { containerStyle } from './common';
const { Item } = Form;
const ResetPassword = () => {
const [form] = Form.useForm();
const [isLoading, setIsLoading] = useState(false);
const { resetPassword } = useAuth();
const history = useHistory();
const onFinish = async ({ email, password }: { form: any, email: string, password: string }) => {
setIsLoading(true);
try {
await resetPassword(email);
resetSuccessful();
history.push(Routes.LOGIN);
} catch (error) {
form.resetFields();
console.warn(error);
resetFailed(error as Error);
setIsLoading(false);
}
};
return (
<div style={containerStyle}>
<Divider>Reset password</Divider>
<Form
initialValues={{ remember: true }}
onFinish={onFinish}
validateMessages={validateMessages}
autoComplete="off"
form={form}
>
<Item
name="email"
rules={[{ required: true, type: 'email' }]}
hasFeedback
>
<Input
prefix={<MailOutlined />}
placeholder="Email"
/>
</Item>
<Item>
<Button
type="primary"
htmlType="submit"
style={{ width: '100%' }}
loading={isLoading}
>
Reset password
</Button>
</Item>
<Link to={Routes.SIGN_UP}>Sign Up now</Link>
<Link to={Routes.LOGIN} style={{ float: 'right' }}>
Log In
</Link>
</Form>
</div>
);
};
export default ResetPassword;

View File

@ -4,7 +4,6 @@ import {
Input,
Button,
Divider,
notification,
} from 'antd';
import {
MailOutlined,
@ -17,7 +16,12 @@ import {
import { useAuth } from '../../contexts/AuthContext';
import { Routes } from '../../routes';
import validateMessages from './validateMessages';
import emailNotVerifiedWarning from './emailNotVerifiedWarning';
import {
emailNotVerified,
signUpFailed,
signUpSuccessful,
} from './notifications';
import { containerStyle } from './common';
const { Item } = Form;
@ -33,30 +37,19 @@ const SignUp = () => {
setIsLoading(true);
try {
await signUp(email, password);
notification.success({
message: 'Sign Up successful',
description: 'Welcome on board!',
});
emailNotVerifiedWarning();
signUpSuccessful();
emailNotVerified();
history.push(Routes.ROOT);
} catch (err) {
} catch (error) {
form.resetFields();
console.warn(err);
notification.error({
message: 'Failed to create an account',
description: (err as Error).message,
});
console.warn(error);
signUpFailed(error as Error);
setIsLoading(false);
}
};
return (
<div style={{
padding: 20,
maxWidth: 350,
margin: '0 auto',
}}>
<div style={containerStyle}>
<Divider>Sign Up</Divider>
<Form
initialValues={{ remember: true }}

View File

@ -0,0 +1,10 @@
const containerStyle = {
padding: 20,
maxWidth: 370,
margin: '0 auto',
};
export {
// eslint-disable-next-line import/prefer-default-export
containerStyle,
};

View File

@ -1,8 +0,0 @@
import { notification } from 'antd';
const emailNotVerifiedWarning = () => notification.warn({
message: 'Check your email',
description: 'Your email address has to be verified before you can upload files!',
});
export default emailNotVerifiedWarning;

View File

@ -0,0 +1,76 @@
import { notification } from 'antd';
const duration = 6;
const baseOptions = {
duration,
};
const emailNotVerified = () => notification.success({
message: 'Check your email',
description: 'Your email address has to be verified before you can upload files!',
...baseOptions,
});
const signUpSuccessful = () => notification.success({
message: 'Sign Up successful',
description: 'Welcome on board!',
...baseOptions,
});
const signUpFailed = (err: Error) => notification.error({
message: 'Failed to create an account',
description: err.message,
});
const logInSuccessful = () => notification.success({
message: 'Log in successful',
description: 'Welcome back!',
...baseOptions,
});
const logInFailed = (err: Error) => notification.error({
message: 'Failed to log in',
description: err.message,
});
const restrictedPage = () => notification.error({
message: 'Restricted page',
description: 'You have to be logged in to access this page!',
...baseOptions,
});
const logOutSuccessful = () => notification.warning({
message: 'Log out successful',
description: 'See you next time!',
...baseOptions,
});
const logOutFailed = (err: Error) => notification.error({
message: 'Log out failed',
description: err.message,
});
const resetSuccessful = () => notification.success({
message: 'Password reset successful',
description: 'Check your email!',
...baseOptions,
});
const resetFailed = (err: Error) => notification.error({
message: 'Password reset failed',
description: err.message,
});
export {
emailNotVerified,
signUpSuccessful,
signUpFailed,
logInSuccessful,
logInFailed,
restrictedPage,
logOutSuccessful,
logOutFailed,
resetSuccessful,
resetFailed,
};

View File

@ -17,7 +17,6 @@ import {
Dropdown,
Typography,
Radio,
notification,
} from 'antd';
import {
UserOutlined,
@ -57,6 +56,10 @@ import {
} from '../utils/keyboard/shortcuts';
import { Routes } from '../routes';
import { useAuth } from '../contexts/AuthContext';
import {
logOutFailed,
logOutSuccessful,
} from './Auth/notifications';
const { Header } = Layout;
const { useBreakpoint } = Grid;
@ -71,16 +74,10 @@ const TopBar = () => {
const logoutClick = useCallback(async () => {
try {
await logout();
notification.warning({
message: 'Logout successful',
description: 'See you again!',
});
} catch (err) {
console.warn(err);
notification.error({
message: 'Login failed',
description: (err as Error).message,
});
logOutSuccessful();
} catch (error) {
console.warn(error);
logOutFailed(error as Error);
}
}, [logout]);

View File

@ -13,6 +13,7 @@ import {
signInWithEmailAndPassword,
sendEmailVerification,
signOut,
sendPasswordResetEmail,
} from '../firebase';
const AuthContext = createContext<any>(null);
@ -22,6 +23,7 @@ interface AuthValue {
signUp: (email: string, password: string) => Promise<UserCredential>,
login: (email: string, password: string) => Promise<UserCredential>,
logout: () => Promise<void>,
resetPassword: (email: string) => Promise<UserCredential>,
}
const useAuth = () => useContext<AuthValue>(AuthContext);
@ -37,6 +39,7 @@ const AuthProvider = (props: { children: ReactNode }) => {
.then((userCredential) => sendEmailVerification(userCredential.user)),
login: (email: string, password: string) => signInWithEmailAndPassword(auth, email, password),
logout: () => signOut(auth),
resetPassword: (email: string) => sendPasswordResetEmail(auth, email),
}), [currentUser]);
useEffect(() => {

View File

@ -5,6 +5,7 @@ import {
signInWithEmailAndPassword,
signOut,
sendEmailVerification,
sendPasswordResetEmail,
} from 'firebase/auth';
import { getAnalytics } from 'firebase/analytics';
@ -29,4 +30,5 @@ export {
signInWithEmailAndPassword,
sendEmailVerification,
signOut,
sendPasswordResetEmail,
};

View File

@ -6,8 +6,9 @@ export enum Routes {
DIALOG = '/tune/:category/:dialog',
LOG = '/log',
DIAGNOSE = '/diagnose',
LOGIN = '/login',
LOGOUT = '/logout',
SIGN_UP = '/sign-up',
FORGOT_PASSWORD = '/forgot-password',
LOGIN = '/auth/login',
LOGOUT = '/auth/logout',
SIGN_UP = '/auth/sign-up',
FORGOT_PASSWORD = '/auth/forgot-password',
RESET_PASSWORD = '/auth/reset-password',
}