feat: add login/register page with JWT auth flow
Login form with role-based redirect (ADMIN→demo, BODEGA→bodega, TALLER→demo). Register form for TALLER/BODEGA with admin approval required. Includes authFetch() wrapper with automatic token refresh. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
227
dashboard/login.js
Normal file
227
dashboard/login.js
Normal file
@@ -0,0 +1,227 @@
|
||||
/* ============================================================
|
||||
login.js -- Login / Register logic for Nexus Autoparts
|
||||
============================================================ */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// ---- DOM refs ----
|
||||
const loginPanel = document.getElementById('loginPanel');
|
||||
const registerPanel = document.getElementById('registerPanel');
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const alertBox = document.getElementById('alert');
|
||||
|
||||
// ---- Role-based redirect map ----
|
||||
const ROLE_REDIRECTS = {
|
||||
ADMIN: '/demo',
|
||||
OWNER: '/demo',
|
||||
BODEGA: '/bodega',
|
||||
TALLER: '/demo',
|
||||
};
|
||||
|
||||
// ---- Check existing session on load ----
|
||||
(function checkSession() {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const role = localStorage.getItem('user_role');
|
||||
if (token && role) {
|
||||
const dest = ROLE_REDIRECTS[role] || '/index.html';
|
||||
window.location.replace(dest);
|
||||
}
|
||||
})();
|
||||
|
||||
// ---- Panel toggling ----
|
||||
window.showPanel = function (panel) {
|
||||
hideAlert();
|
||||
if (panel === 'register') {
|
||||
loginPanel.classList.remove('active');
|
||||
registerPanel.classList.add('active');
|
||||
} else {
|
||||
registerPanel.classList.remove('active');
|
||||
loginPanel.classList.add('active');
|
||||
}
|
||||
};
|
||||
|
||||
// ---- Alert helpers ----
|
||||
function showAlert(msg, type) {
|
||||
alertBox.textContent = msg;
|
||||
alertBox.className = 'login-alert show ' + type;
|
||||
}
|
||||
|
||||
function hideAlert() {
|
||||
alertBox.className = 'login-alert';
|
||||
alertBox.textContent = '';
|
||||
}
|
||||
|
||||
function setLoading(btn, loading) {
|
||||
btn.classList.toggle('loading', loading);
|
||||
}
|
||||
|
||||
// ---- Login ----
|
||||
loginForm.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
hideAlert();
|
||||
|
||||
const email = document.getElementById('loginEmail').value.trim();
|
||||
const password = document.getElementById('loginPassword').value;
|
||||
const btn = loginForm.querySelector('.btn-submit');
|
||||
|
||||
if (!email || !password) {
|
||||
showAlert('Completa todos los campos.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(btn, true);
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
showAlert(data.error || data.message || 'Credenciales incorrectas.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Persist tokens & user info
|
||||
localStorage.setItem('access_token', data.access_token);
|
||||
localStorage.setItem('refresh_token', data.refresh_token || '');
|
||||
localStorage.setItem('user_role', data.role || data.user?.role || '');
|
||||
localStorage.setItem('user_name', data.name || data.user?.name || '');
|
||||
|
||||
const role = (data.role || data.user?.role || '').toUpperCase();
|
||||
const dest = ROLE_REDIRECTS[role] || '/index.html';
|
||||
window.location.replace(dest);
|
||||
|
||||
} catch (err) {
|
||||
showAlert('Error de conexion. Intenta de nuevo.', 'error');
|
||||
} finally {
|
||||
setLoading(btn, false);
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Register ----
|
||||
registerForm.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
hideAlert();
|
||||
|
||||
const name = document.getElementById('regName').value.trim();
|
||||
const email = document.getElementById('regEmail').value.trim();
|
||||
const password = document.getElementById('regPassword').value;
|
||||
const confirm = document.getElementById('regConfirm').value;
|
||||
const business_name = document.getElementById('regBusiness').value.trim();
|
||||
const phone = document.getElementById('regPhone').value.trim();
|
||||
const role = document.getElementById('regRole').value;
|
||||
const btn = registerForm.querySelector('.btn-submit');
|
||||
|
||||
// Validations
|
||||
if (!name || !email || !password || !confirm || !business_name || !phone) {
|
||||
showAlert('Completa todos los campos.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
showAlert('La contrasena debe tener al menos 8 caracteres.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (password !== confirm) {
|
||||
showAlert('Las contrasenas no coinciden.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(btn, true);
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, email, password, role, business_name, phone }),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
showAlert(data.error || data.message || 'Error al crear la cuenta.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showAlert('Cuenta creada. Pendiente de aprobacion por administrador.', 'success');
|
||||
registerForm.reset();
|
||||
|
||||
} catch (err) {
|
||||
showAlert('Error de conexion. Intenta de nuevo.', 'error');
|
||||
} finally {
|
||||
setLoading(btn, false);
|
||||
}
|
||||
});
|
||||
|
||||
// ================================================================
|
||||
// authFetch -- Authenticated fetch wrapper (exported globally)
|
||||
// ================================================================
|
||||
window.authFetch = async function authFetch(url, options = {}) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
if (!token) {
|
||||
window.location.replace('/login.html');
|
||||
return;
|
||||
}
|
||||
|
||||
const headers = Object.assign({}, options.headers || {}, {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
});
|
||||
|
||||
let res = await fetch(url, Object.assign({}, options, { headers }));
|
||||
|
||||
// If 401, try refreshing the token once
|
||||
if (res.status === 401) {
|
||||
const refreshed = await tryRefreshToken();
|
||||
if (refreshed) {
|
||||
headers['Authorization'] = 'Bearer ' + localStorage.getItem('access_token');
|
||||
res = await fetch(url, Object.assign({}, options, { headers }));
|
||||
} else {
|
||||
// Refresh failed — log out
|
||||
logout();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
async function tryRefreshToken() {
|
||||
const refreshToken = localStorage.getItem('refresh_token');
|
||||
if (!refreshToken) return false;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/refresh', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ refresh_token: refreshToken }),
|
||||
});
|
||||
|
||||
if (!res.ok) return false;
|
||||
|
||||
const data = await res.json();
|
||||
localStorage.setItem('access_token', data.access_token);
|
||||
if (data.refresh_token) {
|
||||
localStorage.setItem('refresh_token', data.refresh_token);
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
window.logout = function logout() {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
localStorage.removeItem('user_role');
|
||||
localStorage.removeItem('user_name');
|
||||
window.location.replace('/login.html');
|
||||
};
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user