From 2164aeee33a266915492447f5fad55601cc5314d Mon Sep 17 00:00:00 2001 From: rafael Date: Thu, 20 Nov 2025 18:32:02 -0400 Subject: [PATCH] Add login functionality, authentication handling, and logout support 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. --- package-lock.json | 35 +++++++++------- src/components/Auth/Login.js | 71 +++++++++++++++++++++++++++++++++ src/components/Layout/Header.js | 11 +++++ src/index.js | 25 +++++++++++- src/styles/components.css | 46 ++++++++++++++++++++- 5 files changed, 171 insertions(+), 17 deletions(-) create mode 100644 src/components/Auth/Login.js diff --git a/package-lock.json b/package-lock.json index e83f8c8..3b37bc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/src/components/Auth/Login.js b/src/components/Auth/Login.js new file mode 100644 index 0000000..fe13b99 --- /dev/null +++ b/src/components/Auth/Login.js @@ -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 ( +
+
+
+

+ + Iniciar Sesión +

+
+
+
+ + setEmail(e.target.value)} + autoFocus + aria-invalid={!!error && (!email || !password)} + /> +
+
+ + setPassword(e.target.value)} + aria-invalid={!!error && (!email || !password)} + /> +
+ + {error &&
{error}
} + + +
+
+
+ ); +}; + +export default Login; diff --git a/src/components/Layout/Header.js b/src/components/Layout/Header.js index e976bbe..1125410 100644 --- a/src/components/Layout/Header.js +++ b/src/components/Layout/Header.js @@ -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 (
@@ -16,6 +24,9 @@ const Header = () => { +
); diff --git a/src/index.js b/src/index.js index a1ff727..9578bea 100644 --- a/src/index.js +++ b/src/index.js @@ -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 ? : ; +} const container = document.getElementById('root'); const root = createRoot(container); root.render( - + ); diff --git a/src/styles/components.css b/src/styles/components.css index 7df536c..65881e4 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -671,4 +671,48 @@ textarea { overflow-y: auto; border: 1px solid #e2e8f0; font-size: 0.9rem; -} \ No newline at end of file +} + +/* 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%; + } +}