From 282118b93f91eb62f02978d7da7f4a2ecd4dc055 Mon Sep 17 00:00:00 2001 From: javiermengual Date: Fri, 16 Jan 2026 17:44:16 +0100 Subject: [PATCH] Implement authentication and routing features with login and dashboard pages --- src/components/Login.js | 31 ++++++++++ src/main.js | 24 +------- src/pages/DashboardPage.js | 24 ++++++++ src/pages/LoginPage.js | 23 ++++++++ src/router.js | 34 +++++++++++ src/services/auth.js | 37 ++++++++++++ src/style.css | 113 +++++++++++++++++++++++++++++++++++++ 7 files changed, 264 insertions(+), 22 deletions(-) create mode 100644 src/components/Login.js create mode 100644 src/pages/DashboardPage.js create mode 100644 src/pages/LoginPage.js create mode 100644 src/router.js create mode 100644 src/services/auth.js diff --git a/src/components/Login.js b/src/components/Login.js new file mode 100644 index 0000000..fba9cc8 --- /dev/null +++ b/src/components/Login.js @@ -0,0 +1,31 @@ +export function renderLogin(onLogin) { + const loginContainer = document.createElement('div'); + loginContainer.className = 'login-container'; + + loginContainer.innerHTML = ` +
+ +
+
+ + +
+
+ + +
+ +
+ +
+ `; + + loginContainer.querySelector('#login-form').addEventListener('submit', (e) => { + e.preventDefault(); + const email = loginContainer.querySelector('#email').value; + const password = loginContainer.querySelector('#password').value; + onLogin(email, password); + }); + + return loginContainer; +} diff --git a/src/main.js b/src/main.js index 5fc3358..fba4a17 100644 --- a/src/main.js +++ b/src/main.js @@ -1,24 +1,4 @@ import './style.css' -import javascriptLogo from './javascript.svg' -import viteLogo from '/vite.svg' -import { setupCounter } from './counter.js' +import { initRouter } from './router.js' -document.querySelector('#app').innerHTML = ` -
- - - - - - -

Hello Vite!

-
- -
-

- Click on the Vite logo to learn more -

-
-` - -setupCounter(document.querySelector('#counter')) +initRouter() diff --git a/src/pages/DashboardPage.js b/src/pages/DashboardPage.js new file mode 100644 index 0000000..f7ec21a --- /dev/null +++ b/src/pages/DashboardPage.js @@ -0,0 +1,24 @@ +import { auth } from '../services/auth.js'; + +export function renderDashboard() { + const user = auth.getUser(); + const container = document.createElement('div'); + container.className = 'dashboard'; + + container.innerHTML = ` +
+

Bienvenido, ${user?.email || 'Usuario'}

+ +
+
+

Dashboard protegido

+
+ `; + + container.querySelector('#logout-button').addEventListener('click', () => { + auth.logout(); + window.location.hash = '#login'; + }); + + return container; +} diff --git a/src/pages/LoginPage.js b/src/pages/LoginPage.js new file mode 100644 index 0000000..d073007 --- /dev/null +++ b/src/pages/LoginPage.js @@ -0,0 +1,23 @@ +import { auth } from '../services/auth.js'; +import { renderLogin } from '../components/Login.js'; + +export function renderLoginPage(onLoginSuccess) { + const container = document.createElement('div'); + container.className = 'login-page'; + + const loginComponent = renderLogin((email, password) => { + if (auth.login(email, password)) { + if (onLoginSuccess) { + onLoginSuccess(); + } else { + window.location.hash = '#dashboard'; + } + } else { + const error = container.querySelector('#login-error'); + error.textContent = 'Credenciales inválidas'; + } + }); + + container.appendChild(loginComponent); + return container; +} diff --git a/src/router.js b/src/router.js new file mode 100644 index 0000000..a565ba9 --- /dev/null +++ b/src/router.js @@ -0,0 +1,34 @@ +import { auth } from './services/auth.js'; +import { renderLoginPage } from './pages/LoginPage.js'; +import { renderDashboard } from './pages/DashboardPage.js'; + +export function initRouter() { + const app = document.querySelector('#app'); + + function navigate() { + const hash = window.location.hash || '#login'; + + if (!auth.checkAuth() && hash !== '#login') { + window.location.hash = '#login'; + return; + } + + app.innerHTML = ''; + + switch (hash) { + case '#login': + app.appendChild(renderLoginPage(() => { + window.location.hash = '#dashboard'; + })); + break; + case '#dashboard': + app.appendChild(renderDashboard()); + break; + default: + window.location.hash = '#login'; + } + } + + window.addEventListener('hashchange', navigate); + navigate(); +} diff --git a/src/services/auth.js b/src/services/auth.js new file mode 100644 index 0000000..2395919 --- /dev/null +++ b/src/services/auth.js @@ -0,0 +1,37 @@ +export class Auth { + constructor() { + this.currentUser = null; + this.isAuthenticated = false; + } + + login(email, password) { + if (email && password) { + this.currentUser = { email }; + this.isAuthenticated = true; + localStorage.setItem('user', JSON.stringify(this.currentUser)); + return true; + } + return false; + } + + logout() { + this.currentUser = null; + this.isAuthenticated = false; + localStorage.removeItem('user'); + } + + checkAuth() { + const user = localStorage.getItem('user'); + if (user) { + this.currentUser = JSON.parse(user); + this.isAuthenticated = true; + } + return this.isAuthenticated; + } + + getUser() { + return this.currentUser; + } +} + +export const auth = new Auth(); diff --git a/src/style.css b/src/style.css index 8df73e3..a6caaaa 100644 --- a/src/style.css +++ b/src/style.css @@ -94,3 +94,116 @@ button:focus-visible { background-color: #f9f9f9; } } + +.login-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; +} + +.login-card { + background: #2a2a2a; + padding: 2.5rem; + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 400px; +} + +.login-title { + color: #fff; + margin: 0 0 2rem 0; + font-size: 1.8rem; + text-align: center; +} + +.form-group { + margin-bottom: 1.5rem; +} + +.form-group label { + display: block; + color: #fff; + margin-bottom: 0.5rem; + font-weight: 500; +} + +.form-group input { + width: 100%; + padding: 0.75rem; + border: 1px solid #444; + border-radius: 6px; + background: #1a1a1a; + color: #fff; + font-size: 1rem; + box-sizing: border-box; +} + +.form-group input:focus { + outline: none; + border-color: #646cff; +} + +.login-button { + width: 100%; + padding: 0.875rem; + background-color: #646cff; + color: white; + border: none; + border-radius: 6px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: background-color 0.25s; +} + +.login-button:hover { + background-color: #535bf2; + border-color: transparent; +} + +.login-error { + color: #ff6b6b; + text-align: center; + margin-top: 1rem; + font-size: 0.9rem; +} + +.dashboard { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; +} + +.dashboard-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} + +.dashboard-header h1 { + font-size: 2rem; + margin: 0; +} + +.dashboard-content { + background: #2a2a2a; + padding: 2rem; + border-radius: 12px; +} + +.logout-button { + background-color: #ff6b6b; + color: white; + padding: 0.5rem 1rem; + border: none; + border-radius: 6px; + cursor: pointer; +} + +.logout-button:hover { + background-color: #fa5252; + border-color: transparent; +}