Loading web/src/components/admin_panel_navbar/admin_navbar.tsx +114 −7 Original line number Diff line number Diff line import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faSignOut, faUser } from "@fortawesome/free-solid-svg-icons"; import { faEye, faEyeSlash, faSignOut, faUser, faWindowClose } from "@fortawesome/free-solid-svg-icons"; import { Link } from "react-router-dom"; import './assets/styles/style.css'; import { UserRole } from "../../constants/roles"; import { useUserData } from "../../hooks/useUserData"; import { Dispatch, SetStateAction, useState } from "react"; import { useAdminChangePassword } from "../../hooks/useAdminChangePassword"; import { usePasswoordVisibility } from "../../hooks/usePasswordVisibility"; interface props{ windowActive: boolean; isWindowActive: boolean; setIsWindowActive: Dispatch<SetStateAction<boolean>>; } export const AdminPanelNavBar = ({windowActive}:props) => { export const AdminPanelNavBar = ({isWindowActive, setIsWindowActive}:props) => { const {user, handleLogout, setToggle, toggle, userData} = useUserData(); const [changePasswordWindowActive, setChangePasswordWindowActive] = useState(false); const setChangePasswordWindowVisibility = (visibility: boolean) => { setIsWindowActive(visibility); setChangePasswordWindowActive(visibility); } const {register, handleSubmit, errors, onSubmit} = useAdminChangePassword(setChangePasswordWindowVisibility); const { values: valuesPrevPassword, handleClickShowPassword: handleClickShowPrevPassword, handleMouseDownPassword: handleMouseDownPrevPassword } = usePasswoordVisibility(); const { values: valuesNewPassword, handleClickShowPassword: handleClickShowNewPassword, handleMouseDownPassword: handleMouseDownNewPassword } = usePasswoordVisibility(); const { values: valuesNewPasswordConfirm, handleClickShowPassword: handleClickShowNewPasswordConfirm, handleMouseDownPassword: handleMouseDownNewPasswordConfirm } = usePasswoordVisibility(); if(!user ){ return null; Loading @@ -25,14 +51,14 @@ export const AdminPanelNavBar = ({windowActive}:props) => { <div className="profile"> <img src={require("./assets/images/Admin-595b40b65ba036ed117d36fe.png")} className="user-pic" onClick={() => { windowActive isWindowActive ? setToggle(false) : setToggle(!toggle) }} style={ windowActive isWindowActive ? {cursor: "auto"} : Loading @@ -48,9 +74,9 @@ export const AdminPanelNavBar = ({windowActive}:props) => { </div> <hr/> <Link to="/" className="sub-menu-link"> <Link to="/" onClick={() => {setChangePasswordWindowVisibility(true); setToggle(false);}} className="sub-menu-link"> <FontAwesomeIcon icon={faUser} className="sub-menu-link-icon"/> <p>Editar cuenta</p> <p>Cambiar contraseña</p> </Link> <Link onClick={handleLogout} to="/" className="sub-menu-link"> Loading @@ -61,6 +87,87 @@ export const AdminPanelNavBar = ({windowActive}:props) => { </div> } </div> { changePasswordWindowActive && <div className="change-password-window"> <div className="header"> Cambio de contraseña <FontAwesomeIcon icon={faWindowClose} className="close_btn" onClick={() => setChangePasswordWindowVisibility(false)}/> </div> <div className="content"> <form onSubmit={handleSubmit(onSubmit)}> <div className="input_cnt"> <label>Contraseña actual</label> <div className="password_cnt"> <input type={ valuesPrevPassword.showPassword ? "text" : "password" } {...register('prevPassword')} autoComplete="off" /> <FontAwesomeIcon className="visibility-button" onClick={handleClickShowPrevPassword} onMouseDown={handleMouseDownPrevPassword} icon={valuesPrevPassword.showPassword ? faEye : faEyeSlash} /> </div> <p className="error">{errors.prevPassword?.message}</p> </div> <div className="input_cnt"> <label>Nueva contraseña</label> <div className="password_cnt"> <input type={ valuesNewPassword.showPassword ? "text" : "password" } {...register('newPassword')} autoComplete="off" /> <FontAwesomeIcon className="visibility-button" onClick={handleClickShowNewPassword} onMouseDown={handleMouseDownNewPassword} icon={valuesNewPassword.showPassword ? faEye : faEyeSlash} /> </div> <p className="error">{errors.newPassword?.message}</p> </div> <div className="input_cnt"> <label>Confirmar nueva contraseña</label> <div className="password_cnt"> <input type={ valuesNewPasswordConfirm.showPassword ? "text" : "password" } {...register('newPasswordConfirm')} autoComplete="off" /> <FontAwesomeIcon className="visibility-button" onClick={handleClickShowNewPasswordConfirm} onMouseDown={handleMouseDownNewPasswordConfirm} icon={valuesNewPasswordConfirm.showPassword ? faEye : faEyeSlash} /> </div> <p className="error">{errors.newPasswordConfirm?.message}</p> </div> <div className="input_cnt"> <input className="submit_btn" type="submit" content="Registrar"/> </div> </form> </div> </div> } </div> ); } No newline at end of file web/src/components/admin_panel_navbar/assets/styles/style.css +137 −47 Original line number Diff line number Diff line Loading @@ -89,3 +89,93 @@ padding: 8px; margin-right: 15px; } .change-password-window { position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; width: 30vw; height: 40vh; background: green; display: flex; flex-direction: column; z-index: 5; } .change-password-window .header { display: flex; width: 100%; align-items: center; justify-content: center; padding: 5px; } .change-password-window .header .close_btn{ display: inline-block; cursor: pointer; height: 5%; position: absolute; right: 5px; } .change-password-window .content { background: white; width: 100%; flex-grow: 1; display: flex; flex-direction: column; } .change-password-window .content form{ display: flex; flex-direction: column; flex-grow: 1; } .change-password-window .content form .input_cnt{ display: flex; align-items: center; justify-content: center; flex-direction: column; padding: 5px 0; } .change-password-window .content form .input_cnt input{ width: 80%; padding: 5px 20px; border: 1px solid lightgray; border-radius: 5px; } .error{ color: red; font-size: 12px; padding: 0; margin: 0; } .change-password-window .content form .input_cnt .submit_btn{ width: 30%; } .change-password-window .content form .input_cnt .password_cnt{ position: relative; width: 80%; } .change-password-window .content form .input_cnt .password_cnt input{ width: 100%; padding-left: 20px; padding-right: 30px; } .change-password-window .content form .input_cnt .password_cnt .visibility-button{ position: absolute; top: 50%; right: 5px; transform: translateY(-50%); cursor: pointer; } No newline at end of file web/src/hooks/useAdminChangePassword.tsx 0 → 100644 +88 −0 Original line number Diff line number Diff line import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form"; import { AdminDatasourceProd } from "../data/datasources/prod/admin_datasource"; import { AdminRepositoryProd } from "../data/repositories/prod/admin_repository"; import { AdminPasswordValues } from "../infraestructure/entities/admin_form_values"; import { useState } from "react"; import axios, { AxiosError } from "axios"; import { toast } from "react-toastify"; const adminDatasource = new AdminDatasourceProd(); const adminRepository = new AdminRepositoryProd(adminDatasource); const resolver: Resolver<AdminPasswordValues> = async (data) => { const errors: FieldErrors<AdminPasswordValues> = {}; if (!data.prevPassword) { errors.prevPassword = { type: "required", message: "Se requiere la contraseña actual" }; } if (!data.newPassword) { errors.newPassword = { type: "required", message: "Se requiere una nueva contraseña" }; }else if(data.newPasswordConfirm!==data.newPassword){ errors.newPasswordConfirm = { type: "validate", message: "Las contraseñas deben coincidir" }; } if (!data.newPasswordConfirm) { errors.newPasswordConfirm = { type: "required", message: "Se requiere confirmar la nueva contraseña" }; } return { values: Object.keys(errors).length > 0 ? {} : data, errors: errors, }; }; export const useAdminChangePassword = (setChangePasswordWindowVisibility: (visibility: boolean) => void) => { const { register, handleSubmit, formState: {errors}, setError } = useForm<AdminPasswordValues>({resolver}); const [errorMessage, setErrorMessage] = useState(''); const onSubmit: SubmitHandler<AdminPasswordValues> = (data: AdminPasswordValues) => { const fetch = async () => { try{ const token = localStorage.getItem('token') || ''; await adminRepository.changePassword(token, data.prevPassword, data.newPassword); setChangePasswordWindowVisibility(false); }catch(error: any){ if(axios.isAxiosError(error)){ error as AxiosError; switch(error.code){ case(axios.AxiosError.ERR_BAD_REQUEST): setErrorMessage("Acceso no autorizado"); setError('prevPassword', {type: 'validate', message: 'Contraseña incorrecta'}) break; case(axios.AxiosError.ERR_NETWORK): setErrorMessage("Conexión con el servidor fallida"); break; } } throw new Error(); } } toast.promise( fetch(),{ pending: "Actualizando contraseña...", success: "La contraseña se ha actualizado", error: errorMessage } ) } return {register, handleSubmit, errors, onSubmit}; } No newline at end of file Loading
web/src/components/admin_panel_navbar/admin_navbar.tsx +114 −7 Original line number Diff line number Diff line import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faSignOut, faUser } from "@fortawesome/free-solid-svg-icons"; import { faEye, faEyeSlash, faSignOut, faUser, faWindowClose } from "@fortawesome/free-solid-svg-icons"; import { Link } from "react-router-dom"; import './assets/styles/style.css'; import { UserRole } from "../../constants/roles"; import { useUserData } from "../../hooks/useUserData"; import { Dispatch, SetStateAction, useState } from "react"; import { useAdminChangePassword } from "../../hooks/useAdminChangePassword"; import { usePasswoordVisibility } from "../../hooks/usePasswordVisibility"; interface props{ windowActive: boolean; isWindowActive: boolean; setIsWindowActive: Dispatch<SetStateAction<boolean>>; } export const AdminPanelNavBar = ({windowActive}:props) => { export const AdminPanelNavBar = ({isWindowActive, setIsWindowActive}:props) => { const {user, handleLogout, setToggle, toggle, userData} = useUserData(); const [changePasswordWindowActive, setChangePasswordWindowActive] = useState(false); const setChangePasswordWindowVisibility = (visibility: boolean) => { setIsWindowActive(visibility); setChangePasswordWindowActive(visibility); } const {register, handleSubmit, errors, onSubmit} = useAdminChangePassword(setChangePasswordWindowVisibility); const { values: valuesPrevPassword, handleClickShowPassword: handleClickShowPrevPassword, handleMouseDownPassword: handleMouseDownPrevPassword } = usePasswoordVisibility(); const { values: valuesNewPassword, handleClickShowPassword: handleClickShowNewPassword, handleMouseDownPassword: handleMouseDownNewPassword } = usePasswoordVisibility(); const { values: valuesNewPasswordConfirm, handleClickShowPassword: handleClickShowNewPasswordConfirm, handleMouseDownPassword: handleMouseDownNewPasswordConfirm } = usePasswoordVisibility(); if(!user ){ return null; Loading @@ -25,14 +51,14 @@ export const AdminPanelNavBar = ({windowActive}:props) => { <div className="profile"> <img src={require("./assets/images/Admin-595b40b65ba036ed117d36fe.png")} className="user-pic" onClick={() => { windowActive isWindowActive ? setToggle(false) : setToggle(!toggle) }} style={ windowActive isWindowActive ? {cursor: "auto"} : Loading @@ -48,9 +74,9 @@ export const AdminPanelNavBar = ({windowActive}:props) => { </div> <hr/> <Link to="/" className="sub-menu-link"> <Link to="/" onClick={() => {setChangePasswordWindowVisibility(true); setToggle(false);}} className="sub-menu-link"> <FontAwesomeIcon icon={faUser} className="sub-menu-link-icon"/> <p>Editar cuenta</p> <p>Cambiar contraseña</p> </Link> <Link onClick={handleLogout} to="/" className="sub-menu-link"> Loading @@ -61,6 +87,87 @@ export const AdminPanelNavBar = ({windowActive}:props) => { </div> } </div> { changePasswordWindowActive && <div className="change-password-window"> <div className="header"> Cambio de contraseña <FontAwesomeIcon icon={faWindowClose} className="close_btn" onClick={() => setChangePasswordWindowVisibility(false)}/> </div> <div className="content"> <form onSubmit={handleSubmit(onSubmit)}> <div className="input_cnt"> <label>Contraseña actual</label> <div className="password_cnt"> <input type={ valuesPrevPassword.showPassword ? "text" : "password" } {...register('prevPassword')} autoComplete="off" /> <FontAwesomeIcon className="visibility-button" onClick={handleClickShowPrevPassword} onMouseDown={handleMouseDownPrevPassword} icon={valuesPrevPassword.showPassword ? faEye : faEyeSlash} /> </div> <p className="error">{errors.prevPassword?.message}</p> </div> <div className="input_cnt"> <label>Nueva contraseña</label> <div className="password_cnt"> <input type={ valuesNewPassword.showPassword ? "text" : "password" } {...register('newPassword')} autoComplete="off" /> <FontAwesomeIcon className="visibility-button" onClick={handleClickShowNewPassword} onMouseDown={handleMouseDownNewPassword} icon={valuesNewPassword.showPassword ? faEye : faEyeSlash} /> </div> <p className="error">{errors.newPassword?.message}</p> </div> <div className="input_cnt"> <label>Confirmar nueva contraseña</label> <div className="password_cnt"> <input type={ valuesNewPasswordConfirm.showPassword ? "text" : "password" } {...register('newPasswordConfirm')} autoComplete="off" /> <FontAwesomeIcon className="visibility-button" onClick={handleClickShowNewPasswordConfirm} onMouseDown={handleMouseDownNewPasswordConfirm} icon={valuesNewPasswordConfirm.showPassword ? faEye : faEyeSlash} /> </div> <p className="error">{errors.newPasswordConfirm?.message}</p> </div> <div className="input_cnt"> <input className="submit_btn" type="submit" content="Registrar"/> </div> </form> </div> </div> } </div> ); } No newline at end of file
web/src/components/admin_panel_navbar/assets/styles/style.css +137 −47 Original line number Diff line number Diff line Loading @@ -89,3 +89,93 @@ padding: 8px; margin-right: 15px; } .change-password-window { position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; width: 30vw; height: 40vh; background: green; display: flex; flex-direction: column; z-index: 5; } .change-password-window .header { display: flex; width: 100%; align-items: center; justify-content: center; padding: 5px; } .change-password-window .header .close_btn{ display: inline-block; cursor: pointer; height: 5%; position: absolute; right: 5px; } .change-password-window .content { background: white; width: 100%; flex-grow: 1; display: flex; flex-direction: column; } .change-password-window .content form{ display: flex; flex-direction: column; flex-grow: 1; } .change-password-window .content form .input_cnt{ display: flex; align-items: center; justify-content: center; flex-direction: column; padding: 5px 0; } .change-password-window .content form .input_cnt input{ width: 80%; padding: 5px 20px; border: 1px solid lightgray; border-radius: 5px; } .error{ color: red; font-size: 12px; padding: 0; margin: 0; } .change-password-window .content form .input_cnt .submit_btn{ width: 30%; } .change-password-window .content form .input_cnt .password_cnt{ position: relative; width: 80%; } .change-password-window .content form .input_cnt .password_cnt input{ width: 100%; padding-left: 20px; padding-right: 30px; } .change-password-window .content form .input_cnt .password_cnt .visibility-button{ position: absolute; top: 50%; right: 5px; transform: translateY(-50%); cursor: pointer; } No newline at end of file
web/src/hooks/useAdminChangePassword.tsx 0 → 100644 +88 −0 Original line number Diff line number Diff line import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form"; import { AdminDatasourceProd } from "../data/datasources/prod/admin_datasource"; import { AdminRepositoryProd } from "../data/repositories/prod/admin_repository"; import { AdminPasswordValues } from "../infraestructure/entities/admin_form_values"; import { useState } from "react"; import axios, { AxiosError } from "axios"; import { toast } from "react-toastify"; const adminDatasource = new AdminDatasourceProd(); const adminRepository = new AdminRepositoryProd(adminDatasource); const resolver: Resolver<AdminPasswordValues> = async (data) => { const errors: FieldErrors<AdminPasswordValues> = {}; if (!data.prevPassword) { errors.prevPassword = { type: "required", message: "Se requiere la contraseña actual" }; } if (!data.newPassword) { errors.newPassword = { type: "required", message: "Se requiere una nueva contraseña" }; }else if(data.newPasswordConfirm!==data.newPassword){ errors.newPasswordConfirm = { type: "validate", message: "Las contraseñas deben coincidir" }; } if (!data.newPasswordConfirm) { errors.newPasswordConfirm = { type: "required", message: "Se requiere confirmar la nueva contraseña" }; } return { values: Object.keys(errors).length > 0 ? {} : data, errors: errors, }; }; export const useAdminChangePassword = (setChangePasswordWindowVisibility: (visibility: boolean) => void) => { const { register, handleSubmit, formState: {errors}, setError } = useForm<AdminPasswordValues>({resolver}); const [errorMessage, setErrorMessage] = useState(''); const onSubmit: SubmitHandler<AdminPasswordValues> = (data: AdminPasswordValues) => { const fetch = async () => { try{ const token = localStorage.getItem('token') || ''; await adminRepository.changePassword(token, data.prevPassword, data.newPassword); setChangePasswordWindowVisibility(false); }catch(error: any){ if(axios.isAxiosError(error)){ error as AxiosError; switch(error.code){ case(axios.AxiosError.ERR_BAD_REQUEST): setErrorMessage("Acceso no autorizado"); setError('prevPassword', {type: 'validate', message: 'Contraseña incorrecta'}) break; case(axios.AxiosError.ERR_NETWORK): setErrorMessage("Conexión con el servidor fallida"); break; } } throw new Error(); } } toast.promise( fetch(),{ pending: "Actualizando contraseña...", success: "La contraseña se ha actualizado", error: errorMessage } ) } return {register, handleSubmit, errors, onSubmit}; } No newline at end of file