Add login functionality, authentication handling, and logout support
Some checks failed
continuous-integration/drone/push Build was killed

Replaced React 19 with React 18 due to compatibility issues. Introduced conditional rendering for login and app components based on authentication state. Added basic login form and error handling. Updated styles for authentication UI and enhanced header with logout functionality.
This commit is contained in:
2025-11-20 18:32:02 -04:00
parent deb38ca331
commit 2164aeee33
5 changed files with 171 additions and 17 deletions

35
package-lock.json generated
View File

@@ -12,8 +12,8 @@
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
@@ -13725,10 +13725,13 @@
}
},
"node_modules/react": {
"version": "19.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
@@ -13856,15 +13859,16 @@
}
},
"node_modules/react-dom": {
"version": "19.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^19.2.0"
"react": "^18.2.0"
}
},
"node_modules/react-error-overlay": {
@@ -14543,10 +14547,13 @@
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/schema-utils": {
"version": "4.3.3",

View File

@@ -0,0 +1,71 @@
import React, { useState } from 'react';
import '../../styles/components.css';
const Login = ({ onLogin }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
setError('');
// Validación muy básica
if (!email || !password) {
setError('Por favor, ingresa tu correo y contraseña.');
return;
}
// Aquí iría la llamada real a API. Por ahora, simulamos éxito.
try {
localStorage.setItem('auth', 'true');
onLogin?.();
} catch (err) {
setError('Ocurrió un error al iniciar sesión.');
}
};
return (
<div className="auth-wrapper">
<div className="card auth-card">
<div className="form-header">
<h3>
<i className="fas fa-lock"></i>
Iniciar Sesión
</h3>
</div>
<form onSubmit={handleSubmit} className="login-form" noValidate>
<div className="form-group">
<label htmlFor="email">Correo</label>
<input
id="email"
type="email"
placeholder="tu@correo.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
autoFocus
aria-invalid={!!error && (!email || !password)}
/>
</div>
<div className="form-group">
<label htmlFor="password">Contraseña</label>
<input
id="password"
type="password"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
aria-invalid={!!error && (!email || !password)}
/>
</div>
{error && <div className="alert alert-error" role="alert">{error}</div>}
<button type="submit" className="btn btn-primary btn-block">Entrar</button>
</form>
</div>
</div>
);
};
export default Login;

View File

@@ -2,6 +2,14 @@ import React from 'react';
import '../../styles/components.css';
const Header = () => {
const handleLogout = () => {
try {
localStorage.setItem('auth', 'false');
} catch (e) {}
// Aseguramos volver al login
window.location.reload();
};
return (
<div className="header">
<div className="page-title">
@@ -16,6 +24,9 @@ const Header = () => {
<button className="btn btn-primary">
<i className="fas fa-plus"></i> Nueva Solicitud
</button>
<button className="btn btn-outline" onClick={handleLogout} title="Cerrar sesión">
<i className="fas fa-sign-out-alt"></i> Salir
</button>
</div>
</div>
);

View File

@@ -1,12 +1,33 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import './styles/globals.css';
import App from './App';
import Login from './components/Auth/Login';
function Root() {
const [authed, setAuthed] = useState(() => localStorage.getItem('auth') === 'true');
useEffect(() => {
const onStorage = (e) => {
if (e.key === 'auth') {
setAuthed(e.newValue === 'true');
}
};
window.addEventListener('storage', onStorage);
return () => window.removeEventListener('storage', onStorage);
}, []);
const handleLogin = () => {
setAuthed(true);
};
return authed ? <App /> : <Login onLogin={handleLogin} />;
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<React.StrictMode>
<App />
<Root />
</React.StrictMode>
);

View File

@@ -671,4 +671,48 @@ textarea {
overflow-y: auto;
border: 1px solid #e2e8f0;
font-size: 0.9rem;
}
}
/* Auth / Login Styles */
.auth-wrapper {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
}
.auth-card {
width: 100%;
max-width: 420px;
}
.btn-block {
width: 100%;
display: inline-flex;
}
.alert {
padding: 12px 14px;
border-radius: 8px;
font-size: 0.95rem;
margin: 10px 0 16px;
border-left: 4px solid transparent;
}
.alert-error {
background: rgba(239, 68, 68, 0.08);
color: #991b1b;
border-left-color: var(--danger);
}
/* Improve form spacing inside auth card */
.auth-card .form-group {
margin-bottom: 18px;
}
@media (max-width: 480px) {
.auth-card {
max-width: 100%;
}
}