diff --git a/cosiap_api/users/views.py b/cosiap_api/users/views.py index 09a19f838f7644af2dfbe579c8c82efac5dc39f2..8d135e6656b11dc0f3e1c0b290569d38ac43739a 100644 --- a/cosiap_api/users/views.py +++ b/cosiap_api/users/views.py @@ -221,7 +221,7 @@ def enviar_correo_reset_password(email, uid, token): - token (token de restablecimiento de contraseña) """ subject = 'Restablecer contraseña' - message = f'Para restablecer tu contraseña, haz click en confirmar:\n\n{settings.BASE_URL}api/usuarios/nueva-password/{uid}/{token}/' + message = f'Para restablecer tu contraseña, haz click en el siguiente enlace:\n\nhttp://localhost:5173/authentication/reset_password?uid={uid}&token={token}' from_email = settings.EMAIL_HOST_USER recipient_list = [email] send_mail(subject, message, from_email, recipient_list) diff --git a/cosiap_frontend/index.html b/cosiap_frontend/index.html index 56497389d68e544c6a2c85650dff0da5cfd7923c..51bac2d797512300ea4b94bc745beead5cfeeca6 100644 --- a/cosiap_frontend/index.html +++ b/cosiap_frontend/index.html @@ -5,7 +5,7 @@ - + Sistema de apoyos COZCyT diff --git a/cosiap_frontend/src/App.jsx b/cosiap_frontend/src/App.jsx index aca4efd5c5f2ee4e2fa625b92940f68173646f75..c67632999412e9d184856c3962abaec872d37473 100644 --- a/cosiap_frontend/src/App.jsx +++ b/cosiap_frontend/src/App.jsx @@ -6,6 +6,7 @@ import "./App.css"; import PageLoader from '@/components/common/ui/PageLoader'; import { Login } from './components/users/Login/Login'; import Register from './components/users/Register/Register'; +import ResetPassword from './components/users/Password/ResetPassword'; import Inicio from "./components/users/Inicio"; import { useState } from 'react'; @@ -31,6 +32,7 @@ function App() { } /> } /> } /> + } /> {/* Rutas protegidas */} }> diff --git a/cosiap_frontend/src/api.js b/cosiap_frontend/src/api.js index 7cd0b33c8eb737675e7f593f58d0f96227ad8d7d..26b0c7236b97d6c0f7b224a4515191648975a090 100644 --- a/cosiap_frontend/src/api.js +++ b/cosiap_frontend/src/api.js @@ -52,8 +52,8 @@ const api = { delete: (id) => ax.delete(`api/usuarios/${id}`), verificarCorreo: (uidb64, token) => ax.get(`api/usuarios/verificar-correo/${uidb64}/${token}`), - restablecerPassoword: (data) => ax.post(`api/usuarios/restablecer-password`, data), - nuevaPassword: (uidb64, token, data) => ax.get(`api/usuarios/nueva-password/${uidb64}/${token}`, data), + restablecerPassword: (data) => ax.post(`api/usuarios/restablecer-password/`, data), + nuevaPassword: (uidb64, token, data) => ax.post(`api/usuarios/nueva-password/${uidb64}/${token}/`, data), administradores: { get: () => ax.get('api/usuarios/administradores'), diff --git a/cosiap_frontend/src/components/FormsValidations.jsx b/cosiap_frontend/src/components/FormsValidations.jsx index 785a51a176fffe522f23a3f05044563863d95af8..076e4e8665160b657f44ca50007b153153a1b5d5 100644 --- a/cosiap_frontend/src/components/FormsValidations.jsx +++ b/cosiap_frontend/src/components/FormsValidations.jsx @@ -44,3 +44,14 @@ export const RegisterValidationSchema = Yup.object({ password: PASSWORD_CREATION_VALIDATION, confirmar_password: CONFIRM_PASSWORD_VALIDATION }); + +export const ResetPasswordValidationSchema = Yup.object().shape({ + email: Yup.string() + .required("El correo electronico es requerido") + .email("El correo electronico no es válido") +}); + +export const NewPasswordValidationSchema = Yup.object().shape({ + password: PASSWORD_CREATION_VALIDATION, + confirmar_password: CONFIRM_PASSWORD_VALIDATION +}); diff --git a/cosiap_frontend/src/components/common/layouts/LayoutBaseAuthenticator.jsx b/cosiap_frontend/src/components/common/layouts/LayoutBaseAuthenticator.jsx index 8523ff457fc088d9274b2766576bbfc3e372029e..6ea0ecfaa857e618b3f1456fcda510882e4242b7 100644 --- a/cosiap_frontend/src/components/common/layouts/LayoutBaseAuthenticator.jsx +++ b/cosiap_frontend/src/components/common/layouts/LayoutBaseAuthenticator.jsx @@ -20,12 +20,12 @@ export default function LayoutBaseAuthenticator({title, children}) { {/* Contenido de authentication */}
-
+

¿Haz olvidado tu contraseña?

+

navigate('reset_password')}> + Reestablecer contraseña +

¿Aun no tienes cuenta?

-

+

navigate('register')}> Registrate

diff --git a/cosiap_frontend/src/components/users/Password/NewPasswordForm.jsx b/cosiap_frontend/src/components/users/Password/NewPasswordForm.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9a3b294ed737bf88292706dbcd0f4218f298ae9c --- /dev/null +++ b/cosiap_frontend/src/components/users/Password/NewPasswordForm.jsx @@ -0,0 +1,96 @@ +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { NewPasswordValidationSchema } from "@/components/FormsValidations"; +import { RegisterInputPassword } from "@/components/users/Register/RegisterInputPassword"; +import ErrorDisplay from "@/components/common/ui/ErrorDisplay"; +import { useNavigate } from "react-router-dom"; +import api from "@/api"; + +export default function NewPasswordForm ({uid, token, setViewPageLoader}){ + // Para cambiar de url + const navigate = useNavigate(); + + // Para cambiar de componente si la creación de la nueva contraseña fue exitosa + const [successChangePassword, setSuccessChangePassword] = useState(false); + // Error de la creacion de contraseña desde back + const [errorNewPassword, setErrorNewPassword] = useState(''); + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: yupResolver(NewPasswordValidationSchema), + }); + + const handleFormSubmission = async (data) => { + setViewPageLoader(true); + try { + const response = await api.usuarios.nuevaPassword(uid, token, data) + console.log('Password change successful', response.data); + setSuccessChangePassword(true); + } catch (error) { + console.error('Password change failed', error); + setErrorNewPassword(error.response.data.messages.error); + } + setViewPageLoader(false); + } + + return ( + successChangePassword ? + ( + <> +
+
+

¡CAMBIO DE CONTRASEÑA REALIZADO CON EXITO!

+
+
+

SE HA REESTABLECIDO TU CONTRASEÑA, INICIA SESIÓN CON TUS CREDENCIALES

+
+
+ +
+
+ + ) + : + ( + <> + +
+
+ +
+
+ +
+
+ +
+
+ + + ) + ); +} \ No newline at end of file diff --git a/cosiap_frontend/src/components/users/Password/ResetPassword.jsx b/cosiap_frontend/src/components/users/Password/ResetPassword.jsx new file mode 100644 index 0000000000000000000000000000000000000000..951c8a5154b1638c70a79f90634cb7d0b2c00593 --- /dev/null +++ b/cosiap_frontend/src/components/users/Password/ResetPassword.jsx @@ -0,0 +1,77 @@ +import LayoutBaseAuthenticator from "@/components/common/layouts/LayoutBaseAuthenticator"; +import ResetPasswordForm from "./ResetPasswordForm"; +import { useState, useEffect } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import NewPasswordForm from "./NewPasswordForm"; + +export default function ResetPassword({ setViewPageLoader }){ + // false se mantiene en el formulario de envio de correo + // true se establece al formulario de creacion de nueva contraseña + const [viewChangePasswordForm, setChangePasswordForm] = useState(false); + // Definimos las variables necesarios en el componente de NewPassword + const [uid, setUid] = useState(''); + const [token, setToken] = useState(''); + // Para cambiar de url + const navigate = useNavigate(); + // Informacion de la url + const location = useLocation(); + + + useEffect (() => { + //Parametros mandados de la url, con clave - valor + const queryParams = new URLSearchParams(location.search); + // isEmpty será true si queryParams no tiene ningún parámetro y false si tiene al menos uno. + const isEmpty = ![...queryParams].length; + + if (!isEmpty){ + if (queryParams.has('uid') && queryParams.has('token')) { // Si la url contiene esos dos parametros + handleParamsChangePassword(queryParams) + } + // Limpia los parámetros de la URL + // Redirige a la misma página sin parámetros + navigate(location.pathname, { replace: true }); + } + + },[location]); // Se ejecutara si location cambia + + // Funcion para procesar los parametros para cambiar la contraseña en la api + const handleParamsChangePassword = (queryParams) => { + // Obtenemos y establecemos los valores de los parametros + setUid(queryParams.get('uid')); + setToken(queryParams.get('token')); + + //Declaramos a true para confirmar que la url que se ingreso es para realizar un cambio de contraseña + setChangePasswordForm(true); + } + + return ( + +
+ { + viewChangePasswordForm ? ( + <> + + + ) : ( + <> +
+

+ Se le enviara un correo para restablecer la contraseña +

+
+ + + ) + } +
+ +
+ ); + +} \ No newline at end of file diff --git a/cosiap_frontend/src/components/users/Password/ResetPasswordForm.jsx b/cosiap_frontend/src/components/users/Password/ResetPasswordForm.jsx new file mode 100644 index 0000000000000000000000000000000000000000..cc3f253b3ffe911c776024912aabde812360e944 --- /dev/null +++ b/cosiap_frontend/src/components/users/Password/ResetPasswordForm.jsx @@ -0,0 +1,79 @@ +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { ResetPasswordValidationSchema } from "@/components/FormsValidations"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { RegisterInput } from "@/components/users/Register/RegisterInput"; +import { useNavigate } from "react-router-dom"; +import api from "@/api"; + +export default function ResetPasswordForm({ setViewPageLoader }){ + const navigate = useNavigate(); + + const [emailSent, setEmailSent] = useState(false); + const [formError, setFormError] = useState(''); + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: yupResolver(ResetPasswordValidationSchema), + }); + + const handleFormSubmission = async (data) => { + setViewPageLoader(true); + + try { + const response = await api.usuarios.restablecerPassword(data); + console.log('Password reset succesfuly: ', response.data); + setEmailSent(true); + } catch (error) { + console.error('Password reset failed: ', error); + console.log(error.response.data); + setFormError(error.response.data); + } + setViewPageLoader(false); + } + return ( + emailSent ? ( + <> +
+

+ ¡Correo enviado con exito!
Revisa tu bandeja para seguir con el proceso de restablecimiento +

+
+
+ +
+ + ) : ( + <> +
+
+ +
+
+ +
+
+

navigate('/authentication')}> + Iniciar sesión +

+ + ) + + ); +} \ No newline at end of file