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 { lazy } from 'react';
import { Container } from '@mui/material';
import { useUser } from './context/user'; import { useUser } from './context/user';
const AuthenticatedApp = lazy(() => import('./AuthenticatedApp')); const AuthenticatedApp = lazy(() => import('./AuthenticatedApp'));
@ -7,7 +8,21 @@ const UnauthenticatedApp = lazy(() => import('./UnauthenticatedApp'));
function App() { function App() {
const user = useUser(); 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; export default App;

View file

@ -1,11 +1,13 @@
import { Box, LinearProgress } from '@mui/material'; import { Box, LinearProgress } from '@mui/material';
function LoadingIndicator() { function LoadingIndicator({ isLoading }) {
return ( if (isLoading) {
<Box sx={box}> return (
<LinearProgress sx={linearProgress} /> <Box sx={box}>
</Box> <LinearProgress sx={linearProgress} />
); </Box>
);
}
} }
const 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'; import { createContext, useContext, useState } from 'react';
const sleep = time => new Promise(resolve => setTimeout(resolve, time)); 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(); const AuthContext = createContext();
@ -12,20 +19,17 @@ function AuthProvider(props) {
error: null, error: null,
}); });
if (state.status === 'error' && state.error) { const login = (email, password) => {
return (
<div>
<h1>Something went wrong!</h1>
<pre>{state.error.message ?? 'Unhandled error!'}</pre>
</div>
);
}
const login = () => {
setState({ ...state, status: 'pending' }); setState({ ...state, status: 'pending' });
return getUser().then(user => let shouldFail = email !== 'leo@gmail.com' && password !== '#leo1234';
setState({ status: 'success', user: user, error: null })
); 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 = () => { const logout = () => {
@ -47,6 +51,7 @@ function useAuthState() {
return { return {
user: state.user, user: state.user,
error: state.error,
isPending, isPending,
isError, isError,
isSuccess, isSuccess,

View file

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

View file

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