Add login error feedback and validations
This commit is contained in:
parent
23079e230a
commit
d9bb329cf7
6 changed files with 113 additions and 30 deletions
17
src/App.js
17
src/App.js
|
@ -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;
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { Box, LinearProgress } from '@mui/material';
|
import { Box, LinearProgress } from '@mui/material';
|
||||||
|
|
||||||
function LoadingIndicator() {
|
function LoadingIndicator({ isLoading }) {
|
||||||
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Box sx={box}>
|
<Box sx={box}>
|
||||||
<LinearProgress sx={linearProgress} />
|
<LinearProgress sx={linearProgress} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const box = {
|
const box = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|
35
src/components/SnackbarIndicator.js
Normal file
35
src/components/SnackbarIndicator.js
Normal 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;
|
|
@ -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,
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue