From d9bb329cf723ac56a5df25154eba9c95ae024e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Mur=C3=A7a?= Date: Tue, 31 May 2022 23:17:31 -0300 Subject: [PATCH] Add login error feedback and validations --- src/App.js | 17 +++++++++++++- src/components/LoadingIndicator.js | 14 ++++++----- src/components/SnackbarIndicator.js | 35 ++++++++++++++++++++++++++++ src/context/auth.js | 33 +++++++++++++++----------- src/index.js | 8 +++++-- src/screens/Login.js | 36 +++++++++++++++++++++++------ 6 files changed, 113 insertions(+), 30 deletions(-) create mode 100644 src/components/SnackbarIndicator.js diff --git a/src/App.js b/src/App.js index e037c55..149a753 100644 --- a/src/App.js +++ b/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
{user ? : }
; + return ( + + {user ? : } + + ); } +const container = { + height: '100vh', + margin: 0, + padding: 0, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'primary.mainBackground', +}; + export default App; diff --git a/src/components/LoadingIndicator.js b/src/components/LoadingIndicator.js index 33053a3..8d5d006 100644 --- a/src/components/LoadingIndicator.js +++ b/src/components/LoadingIndicator.js @@ -1,11 +1,13 @@ import { Box, LinearProgress } from '@mui/material'; -function LoadingIndicator() { - return ( - - - - ); +function LoadingIndicator({ isLoading }) { + if (isLoading) { + return ( + + + + ); + } } const box = { diff --git a/src/components/SnackbarIndicator.js b/src/components/SnackbarIndicator.js new file mode 100644 index 0000000..0240e9b --- /dev/null +++ b/src/components/SnackbarIndicator.js @@ -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 ( + + + {message} + + + ); +} + +const CustomAlert = forwardRef(function MuiAlert(props, ref) { + return ; +}); + +export default SnackbarIndicator; diff --git a/src/context/auth.js b/src/context/auth.js index 61f1287..c759e72 100644 --- a/src/context/auth.js +++ b/src/context/auth.js @@ -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 ( -
-

Something went wrong!

-
{state.error.message ?? 'Unhandled error!'}
-
- ); - } - - 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, diff --git a/src/index.js b/src/index.js index 742d56d..c758c5d 100644 --- a/src/index.js +++ b/src/index.js @@ -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', diff --git a/src/screens/Login.js b/src/screens/Login.js index a09e4b8..943edde 100644 --- a/src/screens/Login.js +++ b/src/screens/Login.js @@ -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 ( @@ -33,14 +44,22 @@ function Login() { label="E-mail" variant="standard" type="email" + value={email} + onChange={e => setEmail(e.target.value)} /> setPassword(e.target.value)} /> - Esqueci minha senha @@ -48,18 +67,21 @@ function Login() { - {isPending && } + + ); } const paper = { - width: '900px', + width: '950px', height: '500px', display: 'flex', justifyContent: 'center', - margin: '0 auto', - marginTop: '20vh', color: 'white', textAlign: 'center', };