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%; + } +}