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 { 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;
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { Box, LinearProgress } from '@mui/material';
|
||||
|
||||
function LoadingIndicator() {
|
||||
function LoadingIndicator({ isLoading }) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Box sx={box}>
|
||||
<LinearProgress sx={linearProgress} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const box = {
|
||||
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';
|
||||
|
||||
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,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue