Add login functionality, authentication handling, and logout support
Some checks failed
continuous-integration/drone/push Build was killed
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:
35
package-lock.json
generated
35
package-lock.json
generated
@@ -12,8 +12,8 @@
|
|||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"react": "^19.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
}
|
}
|
||||||
@@ -13725,10 +13725,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "19.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -13856,15 +13859,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"loose-envify": "^1.1.0",
|
||||||
|
"scheduler": "^0.23.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^19.2.0"
|
"react": "^18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-error-overlay": {
|
"node_modules/react-error-overlay": {
|
||||||
@@ -14543,10 +14547,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
"version": "0.27.0",
|
"version": "0.23.2",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/schema-utils": {
|
"node_modules/schema-utils": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
|
|||||||
71
src/components/Auth/Login.js
Normal file
71
src/components/Auth/Login.js
Normal 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;
|
||||||
@@ -2,6 +2,14 @@ import React from 'react';
|
|||||||
import '../../styles/components.css';
|
import '../../styles/components.css';
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
|
const handleLogout = () => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem('auth', 'false');
|
||||||
|
} catch (e) {}
|
||||||
|
// Aseguramos volver al login
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<div className="page-title">
|
<div className="page-title">
|
||||||
@@ -16,6 +24,9 @@ const Header = () => {
|
|||||||
<button className="btn btn-primary">
|
<button className="btn btn-primary">
|
||||||
<i className="fas fa-plus"></i> Nueva Solicitud
|
<i className="fas fa-plus"></i> Nueva Solicitud
|
||||||
</button>
|
</button>
|
||||||
|
<button className="btn btn-outline" onClick={handleLogout} title="Cerrar sesión">
|
||||||
|
<i className="fas fa-sign-out-alt"></i> Salir
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
25
src/index.js
25
src/index.js
@@ -1,12 +1,33 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import './styles/globals.css';
|
import './styles/globals.css';
|
||||||
import App from './App';
|
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 container = document.getElementById('root');
|
||||||
const root = createRoot(container);
|
const root = createRoot(container);
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<Root />
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -671,4 +671,48 @@ textarea {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid #e2e8f0;
|
||||||
font-size: 0.9rem;
|
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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user