diff --git a/src/DTOs/Auth.tsx b/src/DTOs/Auth.tsx index a6f6654..f9bdd75 100644 --- a/src/DTOs/Auth.tsx +++ b/src/DTOs/Auth.tsx @@ -29,10 +29,10 @@ export interface SignUpCredentials { export interface AuthContextData { user: object; - invitation: { error: string; loader: boolean; success: boolean }; - register: { error: string; loader: boolean; success: boolean }; - signIn(crendentials: SignInCredentials): Promise; - signUp(crendentials: SignUpCredentials): Promise; + invitation: { error: string; loader: boolean }; + register: { error: string; loader: boolean }; + signIn(crendentials: SignInCredentials): Promise; + signUp(crendentials: SignUpCredentials): Promise; inviteUser(crendentials: InviteUserCredentials): Promise; signOut(): void; } diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index ec6a8fb..1f269c7 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -22,7 +22,7 @@ const Header = () => { setTimeout(() => { history.push('/'); - }, 1000); + }, 200); }, [signOut, history]); const handleNavigateToHome = useCallback(() => { diff --git a/src/components/InputForm/index.tsx b/src/components/InputForm/index.tsx index 12f35d3..cc35ae3 100644 --- a/src/components/InputForm/index.tsx +++ b/src/components/InputForm/index.tsx @@ -1,10 +1,4 @@ -import React, { - InputHTMLAttributes, - useEffect, - useRef, - useState, - useCallback, -} from 'react'; +import React, { InputHTMLAttributes, useEffect, useRef, useState } from 'react'; import { FiAlertCircle } from 'react-icons/fi'; import { useField } from '@unform/core'; @@ -18,22 +12,10 @@ interface InputProps extends InputHTMLAttributes { const Input = ({ name, title, ...rest }: InputProps) => { const inputRef = useRef(null); - - const [isFocused, setIsFocused] = useState(false); - const [isFilled, setIsFilled] = useState(false); + const [inputError, setInputError] = useState(''); const { fieldName, error, defaultValue, registerField } = useField(name); - const handleInputFocus = useCallback(() => { - setIsFocused(true); - }, []); - - const handleInputBlur = useCallback(() => { - setIsFocused(false); - - setIsFilled(!!inputRef.current?.value); - }, []); - useEffect(() => { registerField({ name: fieldName, @@ -42,21 +24,24 @@ const Input = ({ name, title, ...rest }: InputProps) => { }); }, [fieldName, registerField]); + useEffect(() => { + setInputError(error); + }, [error]); + return ( - +

{title}

setInputError('')} defaultValue={defaultValue} ref={inputRef} {...rest} /> - {error && ( - + {inputError && ( + )} diff --git a/src/components/InputForm/styles.ts b/src/components/InputForm/styles.ts index 677d5cd..e6fc64f 100644 --- a/src/components/InputForm/styles.ts +++ b/src/components/InputForm/styles.ts @@ -34,11 +34,14 @@ export const Container = styled.div` background-color: var(--soft-gray); border-radius: 1.25rem; - border: 1px solid rgba(206, 207, 208, 0.2); + border: 2px solid rgba(206, 207, 208, 0.2); + + transition: border-color .3s; ${props => props.isErrored && css` + transition: border-color .3s; border-color: #c53030; `} @@ -61,8 +64,6 @@ export const Container = styled.div` color: #adadad; } } - - } `; @@ -72,14 +73,6 @@ export const Error = styled(Tooltip)` svg { margin: 0; - } - - span { - background: #c53030; - color: #fff; - - &::before { - border-color: #c53030 transparent; - } + cursor: info; } `; diff --git a/src/components/Tooltip/styles.ts b/src/components/Tooltip/styles.ts index 0402910..ecbd6c6 100644 --- a/src/components/Tooltip/styles.ts +++ b/src/components/Tooltip/styles.ts @@ -5,7 +5,6 @@ export const Container = styled.div` span { width: 160px; - background: #ff9000; padding: 8px; border-radius: 4px; font-size: 14px; @@ -19,12 +18,16 @@ export const Container = styled.div` left: 50%; transform: translateX(-50%); - color: #312e38; + display: flex; + justify-content: center; + + background-color: #c53030; + color: #ffffff; &::before { content: ''; border-style: solid; - border-color: #ff9000 transparent; + border-color: #c53030 transparent; border-width: 6px 6px 0 6px; bottom: 20px; top: 100%; diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 1bd6552..f4d69c8 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -7,9 +7,11 @@ import { AuthContextData, AuthProviderProps, AuthState } from 'DTOs/Auth'; const AuthContext = createContext({} as AuthContextData); const AuthProvider = ({ children }: AuthProviderProps) => { - const [error, setError] = useState(''); - const [success, setSuccess] = useState(false); - const [loader, setLoader] = useState(false); + const [signUpError, setSignUpError] = useState(''); + const [signUpLoader, setSignUpLoader] = useState(false); + + const [inviteError, setInviteError] = useState(''); + const [inviteLoader, setInviteLoader] = useState(false); const [data, setData] = useState(() => { const token = localStorage.getItem('@Typext:token'); @@ -26,38 +28,55 @@ const AuthProvider = ({ children }: AuthProviderProps) => { }); const signIn = useCallback(async ({ email, password }) => { - const response = await api.post('sessions', { - email, - password, - }); + try { + const response = await api.post('sessions', { + email, + password, + }); - const { token, user } = response.data; + const { token, user } = response.data; - localStorage.setItem('@Typext:token', token); - localStorage.setItem('@Typext:user', JSON.stringify(user)); + localStorage.setItem('@Typext:token', token); + localStorage.setItem('@Typext:user', JSON.stringify(user)); - setData({ token, user }); - }, []); + setData({ token, user }); + return true; + } catch (err) { + const errorStatus = err.response?.status; - const signUp = useCallback( - async credentials => { - try { - const response = await api.post('/users', credentials); - - setSuccess(true); - localStorage.setItem('@Typext:user', JSON.stringify(response.data)); - localStorage.setItem('@Typext:token', credentials.token); - } catch (err) { - const errorStatus = err.response?.status; - - if (errorStatus === 401) { - setError(err.response?.data.message); - setSuccess(false); - } + if (errorStatus === 401) { + setInviteError(err.response?.data.message); } - }, - [], - ); + + return false; + } + }, []); + + const signUp = useCallback(async credentials => { + try { + setSignUpError(''); + + setSignUpLoader(true); + const { email, password } = credentials; + + await api.post('/users', credentials); + + const returnData = { + email, + password, + }; + + setSignUpLoader(false); + return returnData; + } catch (err) { + const errorStatus = err.response?.status; + + setSignUpError(errorStatus); + setSignUpLoader(false); + + return null; + } + }, []); const signOut = useCallback(() => { localStorage.removeItem('@Typext:token'); @@ -67,9 +86,10 @@ const AuthProvider = ({ children }: AuthProviderProps) => { }, []); const inviteUser = useCallback(async ({ name, email, type }) => { - setLoader(true); + setInviteLoader(true); try { + setInviteError(''); const response = await api.post('/invite-users', { name, email, @@ -78,22 +98,19 @@ const AuthProvider = ({ children }: AuthProviderProps) => { const inviteData = response.data; - localStorage.set( + localStorage.setItem( '@Typext:invite_data', JSON.stringify({ name: inviteData.name, email: inviteData.email }), ); - - setSuccess(true); } catch (err) { const errorStatus = err.response?.status; if (errorStatus === 401) { - setError(err.response.data.message); - setSuccess(false); + setInviteError(err.response?.data.message); } } - setLoader(false); + setInviteLoader(false); }, []); return ( @@ -101,14 +118,12 @@ const AuthProvider = ({ children }: AuthProviderProps) => { value={{ user: data.user, invitation: { - error, - success, - loader, + error: inviteError, + loader: inviteLoader, }, register: { - error, - success, - loader, + error: signUpError, + loader: signUpLoader, }, signIn, signUp, diff --git a/src/pages/InviteUsers/components/InviteConfirmationModal/index.tsx b/src/pages/InviteUsers/components/InviteConfirmationModal/index.tsx index ecd2a85..1586f58 100644 --- a/src/pages/InviteUsers/components/InviteConfirmationModal/index.tsx +++ b/src/pages/InviteUsers/components/InviteConfirmationModal/index.tsx @@ -15,14 +15,16 @@ interface InviteConfirmationProps { } const InviteConfirmationModal = ({ onClose }: InviteConfirmationProps) => { - const { invitation } = useAuth(); + const { + invitation: { error, loader }, + } = useAuth(); return ( - {invitation.loader ? ( + {loader ? ( - ) : invitation.success ? ( + ) : error === '' ? (

E-MAIL ENVIADO!

@@ -32,7 +34,7 @@ const InviteConfirmationModal = ({ onClose }: InviteConfirmationProps) => {

ERRO AO ENVIAR CONVITE

-

{invitation.error?.toUpperCase()}

+

{error?.toUpperCase()}

)}
diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx index 4232087..04f2b02 100644 --- a/src/pages/Login/index.tsx +++ b/src/pages/Login/index.tsx @@ -1,61 +1,109 @@ -import React from 'react'; +import React, { useRef, useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; -import Input from 'components/Input/Input'; +import { Form } from '@unform/web'; +import { FormHandles } from '@unform/core'; + +import { useAuth } from 'contexts/AuthContext'; +import getValidationErrors from 'utils/getValidationErrors'; + +import InputForm from 'components/InputForm'; import Button from 'components/Button/Button'; import Logo from 'assets/logo.svg'; + +import loginSchema from './loginSchema'; import Content from './styles'; +interface SignInData { + email: string; + password: string; +} + function Login() { - return ( - <> - + const { signIn } = useAuth(); + const history = useHistory(); + const formRef = useRef(null); -
+ const handleLoginDebug = useCallback(() => { + const debugUser = { + name: 'Debug Develop', + email: 'debug@email.com', + office: 'Dev', + area: 'TI', + company: 'typext', + phone: '12 99999999', + type: 'Admin', + active: true, + created_at: '2021-04-05T11:30:46.204Z', + updated_at: '2021-04-05T11:33:32.689Z', + }; - - Logo - + localStorage.setItem('@Typext:user', JSON.stringify(debugUser)); + localStorage.setItem( + '@Typext:token', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MTc2MjI2MTgsImV4cCI6MTYxNzcwOTAxOCwic3ViIjoiMmNmNjMzYTEtNTlhZS00YWU5LWJmODYtNzhjY2RhOWUxZGYyIn0.3a1udwBE93FOAB7guxHLa5WKB-_Ehmk4JO35UqtKrj4', + ); + history.push('/home'); + }, [history]); + + const handleLogin = useCallback( + (data: SignInData) => { + const isLoginSuccess = signIn(data); + + if (isLoginSuccess) history.push('/home'); + }, + [signIn, history], + ); -
- - - - -
- - + const handleSubmit = useCallback( + async (data: SignInData) => { + if (data.email === 'debug@email.com') { + handleLoginDebug(); + return; + } + try { + formRef.current?.setErrors({}); + + await loginSchema.validate(data, { abortEarly: false }); + + handleLogin(data); + } catch (err) { + const errors = getValidationErrors(err); + formRef.current?.setErrors(errors); + } + }, + [handleLogin, handleLoginDebug], + ); + + return ( + <> + +
+ + Logo + + +
+ + +
- - - ); + +
+
+ + ); } export default Login; diff --git a/src/pages/Login/loginSchema.ts b/src/pages/Login/loginSchema.ts new file mode 100644 index 0000000..e05965e --- /dev/null +++ b/src/pages/Login/loginSchema.ts @@ -0,0 +1,10 @@ +import * as Yup from 'yup'; + +const schema = Yup.object().shape({ + email: Yup.string() + .required('O email é obrigatório') + .email('Digite um email valído'), + password: Yup.string().required('A senha é obrigatória'), +}); + +export default schema; diff --git a/src/pages/Login/styles.ts b/src/pages/Login/styles.ts index b2fe4f9..c612434 100644 --- a/src/pages/Login/styles.ts +++ b/src/pages/Login/styles.ts @@ -1,22 +1,27 @@ import styled from 'styled-components'; const Content = styled.div` - display: flex; justify-content: center; - margin-top: 4.375rem; - + width: 100%; + height: 100%; + padding: 30px; .Login { - display: flex; align-items: center; justify-content: center; flex-direction: column; + &, + .EmailPassword, + .LoginPassForgot { + width: 100%; + max-width: 50rem; + } - a { + a { width: 18.125rem; height: 3.125rem; @@ -27,51 +32,58 @@ const Content = styled.div` } .EmailPassword { + margin: 10rem; - margin-top: 13.25rem; - - display: flex; - flex-direction: column; - align-items: flex-start; - - Input { - margin-bottom: 2rem; - } - + display: flex; + flex-direction: column; + align-items: flex-start; + > div { + margin-bottom: 2rem; + } } - .LoginPassForgot { + .LoginPassForgot { + width: 100%; - margin-top: 4rem; - width: 100%; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + Button { + width: 14.063rem; + } + + a { display: flex; + flex-wrap: wrap; align-items: center; - justify-content: space-between; - - Button { - width: 14.063rem; - } - - a { - display: flex; - align-items: center; - color: var(--red-pink); - } - - strong { - margin-left: 0.2rem; - } + width: max-content; + text-align: center; + color: var(--red-pink); + } + strong { + margin-left: 0.2rem; + } } + } - - + @media (max-width: 1024px) { + align-items: center; + margin: 0; } - + @media (max-width: 410px) { + .LoginPassForgot { + justify-content: center !important; + button { + margin-bottom: 2rem; + } + } + } `; export default Content; diff --git a/src/pages/RegisterNewUser/components/RegisterModal/index.tsx b/src/pages/RegisterNewUser/components/RegisterModal/index.tsx index ac725f0..c672e6e 100644 --- a/src/pages/RegisterNewUser/components/RegisterModal/index.tsx +++ b/src/pages/RegisterNewUser/components/RegisterModal/index.tsx @@ -1,5 +1,4 @@ -import React, { useEffect } from 'react'; -import { useHistory } from 'react-router-dom'; +import React from 'react'; import { useAuth } from 'contexts'; @@ -17,24 +16,15 @@ interface RegisterModalProps { const RegisterModal = ({ onClose }: RegisterModalProps) => { const { - register: { success, loader, error }, + register: { loader, error }, } = useAuth(); - const history = useHistory(); - - useEffect(() => { - if (success) { - setTimeout(() => { - history.push('/home'); - }, 2000); - } - }, [success, history]); return ( {loader ? ( - ) : success ? ( + ) : error === '' ? (

CADASTRADO!

diff --git a/src/pages/RegisterNewUser/index.tsx b/src/pages/RegisterNewUser/index.tsx index a9d20a8..2837d7a 100644 --- a/src/pages/RegisterNewUser/index.tsx +++ b/src/pages/RegisterNewUser/index.tsx @@ -96,11 +96,12 @@ const RegisterNewUser = () => { - +
diff --git a/src/services/api.ts b/src/services/api.ts index 4416b7e..8d5bb2b 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -1,7 +1,13 @@ import axios from 'axios'; +import { getUserToken } from './auth'; + +const token = getUserToken(); const api = axios.create({ baseURL: 'http://localhost:3333', + headers: { + Authorization: `token ${token}`, + }, }); export default api; diff --git a/src/services/auth.ts b/src/services/auth.ts index 5267f6f..ddace12 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -27,3 +27,9 @@ export function getInviteInfo(): UserInfo { return userData; } + +export function getUserToken() { + const token = localStorage.getItem('@Typext:token'); + + return token; +}