Implement authentication and routing features with login and dashboard pages

This commit is contained in:
javiermengual 2026-01-16 17:44:16 +01:00
parent f35cd90cd6
commit 282118b93f
7 changed files with 264 additions and 22 deletions

31
src/components/Login.js Normal file
View File

@ -0,0 +1,31 @@
export function renderLogin(onLogin) {
const loginContainer = document.createElement('div');
loginContainer.className = 'login-container';
loginContainer.innerHTML = `
<div class="login-card">
<h2 class="login-title">Iniciar Sesión</h2>
<form id="login-form">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required autocomplete="email">
</div>
<div class="form-group">
<label for="password">Contraseña</label>
<input type="password" id="password" name="password" required autocomplete="current-password">
</div>
<button type="submit" class="login-button">Ingresar</button>
</form>
<p id="login-error" class="login-error"></p>
</div>
`;
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;
}

View File

@ -1,24 +1,4 @@
import './style.css' import './style.css'
import javascriptLogo from './javascript.svg' import { initRouter } from './router.js'
import viteLogo from '/vite.svg'
import { setupCounter } from './counter.js'
document.querySelector('#app').innerHTML = ` initRouter()
<div>
<a href="https://vite.dev" target="_blank">
<img src="${viteLogo}" class="logo" alt="Vite logo" />
</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
<img src="${javascriptLogo}" class="logo vanilla" alt="JavaScript logo" />
</a>
<h1>Hello Vite!</h1>
<div class="card">
<button id="counter" type="button"></button>
</div>
<p class="read-the-docs">
Click on the Vite logo to learn more
</p>
</div>
`
setupCounter(document.querySelector('#counter'))

View File

@ -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 = `
<div class="dashboard-header">
<h1>Bienvenido, ${user?.email || 'Usuario'}</h1>
<button id="logout-button" class="logout-button">Cerrar Sesión</button>
</div>
<div class="dashboard-content">
<p>Dashboard protegido</p>
</div>
`;
container.querySelector('#logout-button').addEventListener('click', () => {
auth.logout();
window.location.hash = '#login';
});
return container;
}

23
src/pages/LoginPage.js Normal file
View File

@ -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;
}

34
src/router.js Normal file
View File

@ -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();
}

37
src/services/auth.js Normal file
View File

@ -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();

View File

@ -94,3 +94,116 @@ button:focus-visible {
background-color: #f9f9f9; 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;
}