Add login error feedback and validations

This commit is contained in:
Leonardo Murça 2022-05-31 23:17:31 -03:00
parent 23079e230a
commit d9bb329cf7
6 changed files with 113 additions and 30 deletions

View file

@ -1,4 +1,5 @@
import { lazy } from 'react';
import { Container } from '@mui/material';
import { useUser } from './context/user';
const AuthenticatedApp = lazy(() => import('./AuthenticatedApp'));
@ -7,7 +8,21 @@ const UnauthenticatedApp = lazy(() => import('./UnauthenticatedApp'));
function App() {
const user = useUser();
return <div>{user ? <AuthenticatedApp /> : <UnauthenticatedApp />}</div>;
return (
<Container maxWidth="false" sx={container}>
{user ? <AuthenticatedApp /> : <UnauthenticatedApp />}
</Container>
);
}
const container = {
height: '100vh',
margin: 0,
padding: 0,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'primary.mainBackground',
};
export default App;

View file

@ -1,11 +1,13 @@
import { Box, LinearProgress } from '@mui/material';
function LoadingIndicator() {
function LoadingIndicator({ isLoading }) {
if (isLoading) {
return (
<Box sx={box}>
<LinearProgress sx={linearProgress} />
</Box>
);
}
}
const box = {

View file

@ -0,0 +1,35 @@
import { useState, useEffect, forwardRef } from 'react';
import { Snackbar, Alert } from '@mui/material';
function SnackbarIndicator({ isOpen, severity, message }) {
const [open, setOpen] = useState(false);
useEffect(() => {
setOpen(isOpen);
}, [isOpen]);
const onClose = reason => {
if (reason !== 'clickaway') {
setOpen(false);
}
};
return (
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open={open}
autoHideDuration={5000}
onClose={onClose}
>
<CustomAlert onClose={onClose} severity={severity} sx={{ width: '100%' }}>
{message}
</CustomAlert>
</Snackbar>
);
}
const CustomAlert = forwardRef(function MuiAlert(props, ref) {
return <Alert elevation={6} ref={ref} variant="filled" {...props} />;
});
export default SnackbarIndicator;

View file

@ -1,7 +1,14 @@
import { createContext, useContext, useState } from 'react';
const sleep = time => new Promise(resolve => setTimeout(resolve, time));
const getUser = () => sleep(1000).then(() => ({ username: 'Leonardo' }));
const getUser = shouldFail =>
sleep(3000).then(() => {
if (shouldFail) {
return { message: 'Falha na autenticação' };
} else {
return { username: 'Leonardo' };
}
});
const AuthContext = createContext();
@ -12,20 +19,17 @@ function AuthProvider(props) {
error: null,
});
if (state.status === 'error' && state.error) {
return (
<div>
<h1>Something went wrong!</h1>
<pre>{state.error.message ?? 'Unhandled error!'}</pre>
</div>
);
}
const login = () => {
const login = (email, password) => {
setState({ ...state, status: 'pending' });
return getUser().then(user =>
setState({ status: 'success', user: user, error: null })
);
let shouldFail = email !== 'leo@gmail.com' && password !== '#leo1234';
return getUser(shouldFail).then(data => {
if (shouldFail) {
return setState({ status: 'error', user: null, error: data });
} else {
return setState({ status: 'success', user: data, error: null });
}
});
};
const logout = () => {
@ -47,6 +51,7 @@ function useAuthState() {
return {
user: state.user,
error: state.error,
isPending,
isError,
isSuccess,

View file

@ -1,10 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { createTheme } from '@mui/material';
import { ThemeProvider } from '@mui/system';
import App from './App';
import reportWebVitals from './reportWebVitals';
import AppProviders from './context';
import { createTheme } from '@mui/material';
import { ThemeProvider } from '@mui/system';
import './index.css';
const theme = createTheme({
@ -13,6 +16,7 @@ const theme = createTheme({
main: '#32A041',
black: '#1C1C1C',
lightGray: '#8C8C8C',
mainBackground: '#EEEEEE',
},
secondary: {
main: '#00420C',

View file

@ -1,4 +1,4 @@
import { Fragment } from 'react';
import { Fragment, useState } from 'react';
import {
Box,
Button,
@ -9,11 +9,22 @@ import {
TextField,
} from '@mui/material';
import { useAuthState } from '../context/auth';
import LoadingIndicator from '../components/LoadingIndicator';
import SnackbarIndicator from '../components/SnackbarIndicator';
import logoImage from '../assets/if-salas-logo.svg';
function Login() {
const { login, isPending } = useAuthState();
const { login, isPending, isError, error } = useAuthState();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const isSubmitable = email.length !== 0 && password.length !== 0;
const onTryLogin = () => {
isSubmitable && login(email, password);
};
return (
<Fragment>
@ -33,14 +44,22 @@ function Login() {
label="E-mail"
variant="standard"
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
<TextField
id="password"
label="Senha"
variant="standard"
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
<Button onClick={login} variant="contained">
<Button
disabled={!isSubmitable}
onClick={onTryLogin}
variant="contained"
>
Entrar
</Button>
<Link href="#">Esqueci minha senha</Link>
@ -48,18 +67,21 @@ function Login() {
</Stack>
</Box>
</Paper>
{isPending && <LoadingIndicator />}
<LoadingIndicator isLoading={isPending} />
<SnackbarIndicator
isOpen={isError}
severity="error"
message={error && error.message}
/>
</Fragment>
);
}
const paper = {
width: '900px',
width: '950px',
height: '500px',
display: 'flex',
justifyContent: 'center',
margin: '0 auto',
marginTop: '20vh',
color: 'white',
textAlign: 'center',
};