[REPO REFACTOR]: changed to a better git repository structure with branches

This commit is contained in:
2025-11-01 05:49:49 +01:00
parent 4d0f44e995
commit 589215b2bc
76 changed files with 3529 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
import { useAuth } from "@/hooks/useAuth.js";
const IfAuthenticated = ({ children }) => {
const { authStatus } = useAuth();
return authStatus === "authenticated" ? children : null;
};
export default IfAuthenticated;

View File

@@ -0,0 +1,8 @@
import { useAuth } from "@/hooks/useAuth.js";
const IfNotAuthenticated = ({ children }) => {
const { authStatus } = useAuth();
return authStatus === "unauthenticated" ? children : null;
};
export default IfNotAuthenticated;

View File

@@ -0,0 +1,13 @@
import { useAuth } from "@/hooks/useAuth.js";
const IfRole = ({ roles, children }) => {
const { user, authStatus } = useAuth();
if (authStatus !== "authenticated") return null;
const userRole = user?.role;
return roles.includes(userRole) ? children : null;
};
export default IfRole;

View File

@@ -0,0 +1,120 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUser } from '@fortawesome/free-solid-svg-icons';
import { Form, Button, Alert, FloatingLabel, Row, Col } from 'react-bootstrap';
import PasswordInput from '@/components/Auth/PasswordInput.jsx';
import { useContext, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { AuthContext } from "@/context/AuthContext.jsx";
import CustomContainer from '@/components/CustomContainer.jsx';
import ContentWrapper from '@/components/ContentWrapper.jsx';
import '@/css/LoginForm.css';
const LoginForm = () => {
const { login, error } = useContext(AuthContext);
const navigate = useNavigate();
const [formState, setFormState] = useState({
emailOrUserName: "",
password: "",
keepLoggedIn: false
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormState((prev) => ({ ...prev, [name]: value }));
};
const handleSubmit = async (e) => {
e.preventDefault();
const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formState.emailOrUserName);
const loginBody = {
password: formState.password,
keepLoggedIn: Boolean(formState.keepLoggedIn),
};
if (isEmail) {
loginBody.email = formState.emailOrUserName;
} else {
loginBody.userName = formState.emailOrUserName;
}
try {
await login(loginBody);
navigate("/");
} catch (err) {
console.error("Error de login:", err.message);
}
};
return (
<CustomContainer>
<ContentWrapper>
<div className="login-card card shadow p-5 rounded-5 mx-auto col-12 col-md-8 col-lg-6 col-xl-5 d-flex flex-column gap-4">
<h1 className="text-center">Inicio de sesión</h1>
<Form className="d-flex flex-column gap-5" onSubmit={handleSubmit}>
<div className="d-flex flex-column gap-3">
<FloatingLabel
controlId="floatingUsuario"
label={
<>
<FontAwesomeIcon icon={faUser} className="me-2" />
Usuario o Email
</>
}
>
<Form.Control
type="text"
placeholder=""
name="emailOrUserName"
value={formState.emailOrUserName}
onChange={handleChange}
className="rounded-4"
/>
</FloatingLabel>
<PasswordInput
value={formState.password}
onChange={handleChange}
name="password"
/>
<div className="d-flex flex-column flex-sm-row justify-content-between align-items-center gap-2">
<Form.Check
type="checkbox"
name="keepLoggedIn"
label="Mantener sesión iniciada"
className="text-secondary"
value={formState.keepLoggedIn}
onChange={(e) => { formState.keepLoggedIn = e.target.checked; setFormState({ ...formState }) }}
/>
{/*<Link disabled to="#" className="muted">
Olvidé mi contraseña
</Link>*/}
</div>
</div>
{error && (
<Alert variant="danger" className="text-center py-2 mb-0">
{error}
</Alert>
)}
<div className="text-center">
<Button type="submit" className="w-75 padding-4 rounded-4 border-0 shadow-sm login-button">
Iniciar sesión
</Button>
</div>
</Form>
</div>
</ContentWrapper>
</CustomContainer>
);
};
export default LoginForm;

View File

@@ -0,0 +1,48 @@
import { useState } from 'react';
import { Form, FloatingLabel, Button } from 'react-bootstrap';
import '@/css/PasswordInput.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faEye, faEyeSlash, faKey } from '@fortawesome/free-solid-svg-icons';
const PasswordInput = ({ value, onChange, name = "password" }) => {
const [show, setShow] = useState(false);
const toggleShow = () => setShow(prev => !prev);
return (
<div className="position-relative w-100">
<FloatingLabel
controlId="passwordInput"
label={
<>
<FontAwesomeIcon icon={faKey} className="me-2" />
Contraseña
</>
}
>
<Form.Control
type={show ? "text" : "password"}
name={name}
value={value}
placeholder=""
onChange={onChange}
className="rounded-4 pe-5"
/>
</FloatingLabel>
<Button
variant="link"
className="show-button position-absolute end-0 top-50 translate-middle-y me-2"
onClick={toggleShow}
aria-label="Mostrar contraseña"
tabIndex={-1}
style={{ zIndex: 2 }}
>
<FontAwesomeIcon icon={show ? faEyeSlash : faEye} className='fa-lg' />
</Button>
</div>
);
};
export default PasswordInput;

View File

@@ -0,0 +1,78 @@
import PropTypes from 'prop-types';
import { Modal, Button, Form } from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faLock } from '@fortawesome/free-solid-svg-icons';
import { useState } from 'react';
import PasswordInput from '@/components/Auth/PasswordInput';
import { renderErrorAlert } from '@/util/alertHelpers';
import '@/css/PasswordModal.css';
const PasswordModal = ({
show,
onClose,
onSubmit,
error = null,
loading = false
}) => {
const [password, setPassword] = useState("");
const handleSubmit = () => {
if (password.trim() === "") return;
onSubmit(password);
};
return (
<Modal show={show} onHide={onClose} centered>
<Modal.Header
style={{ backgroundColor: "var(--modal-bg)" }}
>
<Modal.Title>
<FontAwesomeIcon icon={faLock} className="me-2" />
Paste protegida
</Modal.Title>
</Modal.Header>
<Modal.Body style={{ backgroundColor: "var(--modal-body-bg)" }}>
<p className="mb-3">
Esta paste está protegida con contraseña. Introduce la clave para continuar.
</p>
{renderErrorAlert(error)}
<PasswordInput
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={onClose} className='dialog-btn'>
Cancelar
</Button>
<Button
className='dialog-btn'
variant="primary"
onClick={handleSubmit}
disabled={loading || password.trim() === ""}
style={{
backgroundColor: "var(--btn-bg)",
borderColor: "var(--btn-bg)",
color: "var(--btn-text)"
}}
>
{loading ? "Verificando..." : "Acceder"}
</Button>
</Modal.Footer>
</Modal>
);
};
PasswordModal.propTypes = {
show: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
loading: PropTypes.bool
};
export default PasswordModal;

View File

@@ -0,0 +1,18 @@
import { Navigate } from "react-router-dom";
import { useAuth } from "@/hooks/useAuth.js";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
const ProtectedRoute = ({ minimumRoles, children }) => {
const { authStatus } = useAuth();
if (authStatus === "checking") return <FontAwesomeIcon icon={faSpinner} />; // o un loader si quieres
if (authStatus === "unauthenticated") return <Navigate to="/login" replace />;
if (authStatus === "authenticated" && minimumRoles) {
const userRole = JSON.parse(localStorage.getItem("user"))?.role;
if (!minimumRoles.includes(userRole)) return <Navigate to="/" replace />;
}
return children;
};
export default ProtectedRoute;