diff --git a/src/DTOs/Auth.tsx b/src/DTOs/Auth.tsx index 781e7c3..a7f0c90 100644 --- a/src/DTOs/Auth.tsx +++ b/src/DTOs/Auth.tsx @@ -1,3 +1,4 @@ +import { StringifyOptions } from 'node:querystring'; import { ReactNode } from 'react'; export interface AuthState { @@ -27,7 +28,7 @@ export interface InviteUserCredentials { } export interface SignUpCredentials { - token: string; + email: string; name: string; password: string; password_confirmation: string; @@ -37,6 +38,15 @@ export interface SignUpCredentials { area: string; } +export interface RecoveryCredentials { + email: string; +} + +export interface ResetCredentials { + password: string; + confirmPassword: string; +} + interface InvitationData { error: string; loader: boolean; @@ -47,6 +57,16 @@ interface RegisterData { success: boolean; } +interface RecoveryPassowordData { + error: string; + loader: boolean; +} + +interface ResetPasswordData { + error: String; + loader: boolean; +} + export interface AuthContextData { user: { id: string; @@ -61,10 +81,14 @@ export interface AuthContextData { }; invitation: InvitationData; register: RegisterData; + recovery: RecoveryPassowordData; + reset: ResetPasswordData; signIn(crendentials: SignInCredentials): Promise; signUp(crendentials: SignUpCredentials): Promise; - inviteUser(crendentials: InviteUserCredentials): Promise; signOut(): void; + inviteUser(crendentials: InviteUserCredentials): Promise; + recoveryPassword(crendentials: RecoveryCredentials): Promise; + resetPassword(credentials: ResetCredentials): Promise; } export interface AuthProviderProps { children: ReactNode; diff --git a/src/assets/shield-icon.svg b/src/assets/shield-icon.svg new file mode 100644 index 0000000..319c96c --- /dev/null +++ b/src/assets/shield-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/FormUpdate/index.tsx b/src/components/FormUpdate/index.tsx index 8add678..3299960 100644 --- a/src/components/FormUpdate/index.tsx +++ b/src/components/FormUpdate/index.tsx @@ -1,10 +1,10 @@ import React from 'react'; +import { useParams } from 'react-router-dom'; import { Form } from '@unform/web'; import InputForm from 'components/InputForm'; import Button from 'components/Button/Button'; -import Input from 'components/Input/Input'; import { Container } from './styles'; @@ -26,6 +26,9 @@ interface FormUpdateProps { }; } +interface ParamsProps { + email: string; +} function FormUpdate({ handleSubmit, formRef, @@ -33,6 +36,10 @@ function FormUpdate({ hasPasswordField, user, }: FormUpdateProps) { + const params = useParams(); + + const paramsEmail = params.email; + return (
@@ -57,12 +64,11 @@ function FormUpdate({ title="Empresa" /> - diff --git a/src/contexts/auth.tsx b/src/contexts/auth.tsx index 00aa563..c4bca8a 100644 --- a/src/contexts/auth.tsx +++ b/src/contexts/auth.tsx @@ -14,6 +14,12 @@ const AuthProvider = ({ children }: AuthProviderProps) => { const [inviteError, setInviteError] = useState(''); const [inviteLoader, setInviteLoader] = useState(false); + const [recoveryError, setRecoveryError] = useState(''); + const [recoveryLoader, setRecoveryLoader] = useState(false); + + const [resetError, setResetError] = useState(''); + const [resetLoader, setResetLoader] = useState(false); + const [data, setData] = useState(() => { const token = localStorage.getItem('@Typext:token'); const user = localStorage.getItem('@Typext:user'); @@ -108,6 +114,40 @@ const AuthProvider = ({ children }: AuthProviderProps) => { setInviteLoader(false); }, []); + const recoveryPassword = useCallback(async ({ email }) => { + setRecoveryLoader(true); + + try { + setRecoveryError(''); + + await api.post('/password/forgot', { + email, + }); + } catch (err) { + setRecoveryError(err.response?.data.message); + } + + setRecoveryLoader(false); + }, []); + + const resetPassword = useCallback(async ({ password, confirmPassword }) => { + setResetLoader(true); + + try { + setRecoveryError(''); + + await api.post('/password/reset', { password, confirmPassword }); + } catch (err) { + const errorStatus = err.response?.status; + + if (errorStatus === 401) { + setResetError(err.response?.data.message); + } + + setResetLoader(false); + } + }, []); + return ( { loader: signUpLoader, success: signUpSuccess, }, + recovery: { + error: recoveryError, + loader: recoveryLoader, + }, + reset: { + error: resetError, + loader: resetLoader, + }, signIn, signUp, signOut, inviteUser, + recoveryPassword, + resetPassword, }} > {children} @@ -143,3 +193,6 @@ function useAuth(): AuthContextData { } export { AuthProvider, useAuth }; +function async(arg0: { email: any }): any { + throw new Error('Function not implemented.'); +} diff --git a/src/pages/Minute/components/MinuteViewer/data.js b/src/pages/Minute/components/MinuteViewer/data.js deleted file mode 100644 index 73bd4bc..0000000 --- a/src/pages/Minute/components/MinuteViewer/data.js +++ /dev/null @@ -1,79 +0,0 @@ -export const minuteData = { - addressAndHour: { - local: - 'Avenida Cesare Monsueto Giulio Lattes, 1350 Distrito - Eugênio de Melo, São José dos Campos - SP, 12247-014', - startDate: '27/3/21', - startHour: '12:41', - }, - projectInfo: { - projectName: - 'Incidente com tentativa de incendio por partes dos funcionarios', - members: [ - { - name: 'Luciano Huck', - role: 'Recursos Humanos', - enterprise: 'globo.tv', - phone: '40028922', - email: 'lc@gmail.com', - }, - { - name: 'Renato Aragão', - role: 'CEO', - enterprise: 'globo.tv', - phone: '12312312', - email: 'ra@gmail.com', - }, - { - name: 'Marcelo Rezende', - role: 'Delegado do DF 23', - enterprise: 'policial.com', - phone: '123010212', - email: 'mr@gmail.com', - }, - { - name: 'Maicon Cristo', - role: 'Apresentador', - enterprise: 'youtube.com', - phone: '123010212', - email: 'mc@gmail.com', - }, - ], - }, - topics: [ - { topic: 'Incendio' }, - { topic: 'Depravação' }, - { topic: 'Falta de deus' }, - ], - subjects: [ - { - subject: `Aqueça o óleo em uma panela. - Acrescente o alho picado e deixe dourar. - Em seguida, coloque a mesma quantidade de água indicada na embalagem para fazer o miojo. - Deixe ferver. - Acrescente o macarrão e em seguida o caldo de galinha. - Mexa por 1 minuto e deixe cozinhar no tempo indicado (geralmente 3 minutos)`, - responsible: 'Matheus Fields', - deadLine: '30/10/24', - }, - { - subject: - 'O tempo é uma grandeza física presente não apenas no cotidiano como também em todas as áreas e cadeiras científicas. Uma definição do mesmo em âmbito científico é por tal não apenas essencial como também, em verdade, um requisito fundamental.', - responsible: 'Siqueira Junior', - deadLine: '30/12/2028', - }, - { - subject: `You went to school to learn girl - Things you never knew before - Like "I" before "E" except after "C" - And why 2 plus 2 makes 4 - Now, now, now - I'm gonna teach you, teach you, teach you - All about love girl, all about love - Sit yourself down, take a seat - All you gotta do is repeat after me`, - responsible: 'Datena', - deadLine: '11/09/2054', - }, - ], - distributions: ['Não sei que é isso', 'Pode crer', 'Daora'], -}; diff --git a/src/pages/Recovery/components/RecoveryModal/index.tsx b/src/pages/Recovery/components/RecoveryModal/index.tsx new file mode 100644 index 0000000..38b242d --- /dev/null +++ b/src/pages/Recovery/components/RecoveryModal/index.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { useAuth } from 'contexts/auth'; + +import EmailIcon from 'assets/email.svg'; +import EmailErrorIcon from 'assets/email_error.svg'; +import Loader from 'components/Loader'; + +import DefaultModal from 'components/DefaultModal'; +import { StyledRecoveryModal, StyledRecoveryModalContent } from './styles'; + +interface IRecoveryModalProps { + onClose: Function; +} + +const RecoveryModal: React.FC = ({ + onClose, +}: IRecoveryModalProps) => { + const { + recovery: { error, loader }, + } = useAuth(); + + return ( + + + <> + {loader ? ( + + ) : error === '' ? ( + + +

E-MAIL ENVIADO!

+

CONFIRA SUA CAIXA DE E-MAIL.

+
+ ) : ( + + +

{error.toUpperCase()}

+

REVISE-O E TENTE NOVAMENTE.

+
+ )} + +
+
+ ); +}; + +export default RecoveryModal; diff --git a/src/pages/Recovery/components/RecoveryModal/styles.ts b/src/pages/Recovery/components/RecoveryModal/styles.ts new file mode 100644 index 0000000..4dce561 --- /dev/null +++ b/src/pages/Recovery/components/RecoveryModal/styles.ts @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +export const StyledRecoveryModal = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +export const StyledRecoveryModalContent = styled.div` + display: flex; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: center; + + img { + height: 3.438rem; + margin-bottom: 1rem; + } +`; diff --git a/src/pages/Recovery/index.tsx b/src/pages/Recovery/index.tsx index 5d94d27..69c1600 100644 --- a/src/pages/Recovery/index.tsx +++ b/src/pages/Recovery/index.tsx @@ -1,15 +1,44 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react'; +import { message } from 'antd'; import Button from 'components/Button/Button'; import Input from 'components/Input/Input'; import LogoIcon from 'assets/logo.svg'; +import { useAuth } from 'contexts/auth'; import StyledRecoveryPassword from './styles'; +import RecoveryModal from './components/RecoveryModal'; const RecoveryPassword = () => { + const { recoveryPassword } = useAuth(); + + const [showRecoveryModal, setShowRecoveryModal] = useState(false); + + const [userEmail, setUserEmail] = useState(''); + + const handleCloseRecoveryModal = useCallback(() => { + setShowRecoveryModal(false); + }, []); + + const handleRecoveryPassword = useCallback(() => { + if (!userEmail) { + message.error('Todos os campos devem estar preenchidos'); + return; + } + + setShowRecoveryModal(true); + recoveryPassword({ + email: userEmail, + }); + }, [recoveryPassword, userEmail]); + return ( <> + {showRecoveryModal && ( + + )} + diff --git a/src/pages/Register/index.tsx b/src/pages/Register/index.tsx index a2aff1e..3443a9c 100644 --- a/src/pages/Register/index.tsx +++ b/src/pages/Register/index.tsx @@ -1,5 +1,4 @@ import React, { useRef, useCallback, useState } from 'react'; -import { useParams } from 'react-router-dom'; import * as Yup from 'yup'; import { FormHandles } from '@unform/core'; @@ -19,6 +18,7 @@ import StyledRegisterNewUser from './styles'; interface SignUpData { name: string; + email: string; password: string; password_confirmation: string; office: string; @@ -27,12 +27,7 @@ interface SignUpData { area: string; } -interface ParamsProps { - token: string; -} - const RegisterNewUser = () => { - const params = useParams(); const formRef = useRef(null); const inviteInfo = getInviteInfo(); @@ -49,13 +44,13 @@ const RegisterNewUser = () => { await schema.validate(data, { abortEarly: false }); setOpenRegisterModal(true); - await signUp({ ...data, token: params.token }); + await signUp(data); } catch (err) { const errors = getValidationErrors(err); formRef.current?.setErrors(errors); } }, - [signUp, params], + [signUp], ); return ( diff --git a/src/pages/ResetPassword/components/ResetPasswordModal/index.tsx b/src/pages/ResetPassword/components/ResetPasswordModal/index.tsx new file mode 100644 index 0000000..eafb0ae --- /dev/null +++ b/src/pages/ResetPassword/components/ResetPasswordModal/index.tsx @@ -0,0 +1,47 @@ +import React from 'react'; + +import { useAuth } from 'contexts/auth'; + +import ShieldIcon from 'assets/shield-icon.svg'; +import WarnIcon from 'assets/warn.svg'; + +import Loader from 'components/Loader'; +import DefaultModal from 'components/DefaultModal'; + +import { StyledResetPasswordModal, StyledResetPasswordContent } from './styles'; + +interface IResetPasswordModalProps { + onClose: Function; +} + +const ResetPasswordModal: React.FC = ({ + onClose, +}: IResetPasswordModalProps) => { + const { + reset: { error, loader }, + } = useAuth(); + + return ( + + + {loader ? ( + + ) : error === '' ? ( + + +

SENHA ATUALIZADA

+

REDIRECIONANDO PARA ÁREA DE LOGIN...

+
+ ) : ( + + +

SENHA INVÁLIDA!

+

VERIFIQUE SE A DIGITOU CORRETAMENTE

+
+ )} +
+
+ ); +}; + +export default ResetPasswordModal; diff --git a/src/pages/ResetPassword/components/ResetPasswordModal/styles.ts b/src/pages/ResetPassword/components/ResetPasswordModal/styles.ts new file mode 100644 index 0000000..a4cf200 --- /dev/null +++ b/src/pages/ResetPassword/components/ResetPasswordModal/styles.ts @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +export const StyledResetPasswordModal = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +export const StyledResetPasswordContent = styled.div` + display: flex; + flex: 1; + flex-direction: column; + justify-content: center; + align-items: center; + + img { + height: 4rem; + margin-bottom: 1rem; + } +`; diff --git a/src/pages/ResetPassword/index.tsx b/src/pages/ResetPassword/index.tsx index a950d92..2360934 100644 --- a/src/pages/ResetPassword/index.tsx +++ b/src/pages/ResetPassword/index.tsx @@ -1,13 +1,44 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react'; +import { message } from 'antd'; + +import { useAuth } from 'contexts/auth'; import Input from 'components/Input/Input'; import Button from 'components/Button/Button'; import Logo from 'assets/logo.svg'; +import ResetPasswordModal from './components/ResetPasswordModal'; import StyledNewPassword from './styles'; const NewPassword = () => { - return ( + const { resetPassword } = useAuth(); + + const [showResetModal, setShowResetModal] = useState(false); + + const [userPassword, setUserPassword] = useState(''); + const [userConfirmPassword, setUserConfirmPassword] = useState(''); + + const handleCloseResetModal = useCallback(() => { + setShowResetModal(false); + }, []); + + const handleResetPassword = useCallback(() => { + if (!userPassword || !userConfirmPassword) { + message.error('Todos os campos devem estar preenchidos'); + return; + } + + setShowResetModal(true); + resetPassword({ + password: userPassword, + confirmPassword: userConfirmPassword, + }); + }, [resetPassword, userPassword, userConfirmPassword]); + + return ( + <> + {showResetModal && } + Logo Typext @@ -17,6 +48,7 @@ const NewPassword = () => { color="var(--black)" styleWidth="41.875rem" Type="text" + onChange={event => setUserPassword(event.target.value)} /> { color="var(--black)" styleWidth="41.875rem" Type="text" + onChange={event => setUserConfirmPassword(event.target.value)} /> - - - - ); + + ); }; export default NewPassword; diff --git a/src/pages/ResetPassword/styles.ts b/src/pages/ResetPassword/styles.ts index 07c5b90..70631b6 100644 --- a/src/pages/ResetPassword/styles.ts +++ b/src/pages/ResetPassword/styles.ts @@ -1,33 +1,35 @@ import styled from 'styled-components'; export const StyledNewPassword = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + margin-top: 4.375rem; + + .NewPassword { display: flex; - flex-direction: column; align-items: center; justify-content: center; + flex-direction: column; - margin-top: 4.375rem; - - .NewPassword { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; + max-width: 41.875rem; + width: 100%; - margin-top: 13.25rem; + margin-top: 13.25rem; - align-items: flex-end; + align-items: flex-end; - Input { - margin-bottom: 2rem; - } + Input { + margin-bottom: 2rem; + } - Button { - margin-top: 4rem; - width: 14.063rem; - } + Button { + margin-top: 4rem; + width: 14.063rem; } - + } `; export default StyledNewPassword; diff --git a/src/routes.tsx b/src/routes.tsx index 5f0d1e8..fc2532f 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Switch, BrowserRouter, Redirect } from 'react-router-dom'; +import { Switch, BrowserRouter } from 'react-router-dom'; import { getMode } from 'services/api'; @@ -34,17 +34,15 @@ export default function Routes() { - + {userIsAdmin && ( <> - )} - ); diff --git a/src/utils/registerSchemaValidation.ts b/src/utils/registerSchemaValidation.ts index 8490a57..0a2ab12 100644 --- a/src/utils/registerSchemaValidation.ts +++ b/src/utils/registerSchemaValidation.ts @@ -7,6 +7,7 @@ export default { phone: Yup.string().required('Telefone obrigatório'), password: Yup.string().required('Senha obrigatória'), company: Yup.string().required('Empresa obrigatória'), + email: Yup.string().email().required('Email obrigatório'), passwordConfirmation: Yup.string().oneOf( [Yup.ref('password'), null], 'Senhas devem ser iguais',