feat: add app-level authentication foundation

This commit is contained in:
a.tolmachev
2026-03-30 23:47:09 +03:00
parent 91854a4153
commit ab2e603997
42 changed files with 1624 additions and 236 deletions
+60 -56
View File
@@ -1,94 +1,99 @@
/**
* nav.js — shared navbar logic for non-catalog pages
* Handles: auth guard, user dropdown, hamburger, logout
*/
(function () {
/* ── Auth guard ── */
var STORAGE_KEY = 'crank_user';
var user = null;
try { user = JSON.parse(localStorage.getItem(STORAGE_KEY)); } catch (e) {}
if (!user) {
var path = window.location.pathname;
var isLogin = path.endsWith('login.html') || path.endsWith('/login');
if (!isLogin) { window.location.replace((window.APP_BASE||'')+'html/login.html'); return; }
function currentUser() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY));
} catch (_error) {
return null;
}
}
/* ── Fill user info once DOM is available ── */
function fillUserInfo() {
if (!user) return;
document.querySelectorAll('.nav-avatar').forEach(function (el) {
el.textContent = user.initials || 'AT';
var user = currentUser();
if (!user) {
return;
}
document.querySelectorAll('.nav-avatar').forEach(function (element) {
element.textContent = user.initials || 'CR';
});
var nameEl = document.querySelector('.user-dropdown-name');
var roleEl = document.querySelector('.user-dropdown-role');
if (nameEl) nameEl.textContent = user.name || 'Operator';
if (roleEl) roleEl.textContent = (user.role || 'Admin') + ' · ' + (user.workspace || 'workspace');
var nameElement = document.querySelector('.user-dropdown-name');
var roleElement = document.querySelector('.user-dropdown-role');
if (nameElement) {
nameElement.textContent = user.name || 'Operator';
}
if (roleElement) {
roleElement.textContent = (user.role || 'Viewer') + ' · ' + (user.workspace || 'workspace');
}
}
/* ── Wire up nav interactions ── */
function initNav() {
fillUserInfo();
var dropdown = document.querySelector('.user-dropdown');
var avatar = document.querySelector('.nav-avatar');
var dropdown = document.querySelector('.user-dropdown');
var avatar = document.querySelector('.nav-avatar');
var hamburger = document.querySelector('.nav-hamburger');
var mobileNav = document.querySelector('.mobile-nav');
// Initially hide elements controlled by JS
if (dropdown) dropdown.style.display = 'none';
if (mobileNav) mobileNav.style.display = 'none';
if (dropdown) {
dropdown.style.display = 'none';
}
if (mobileNav) {
mobileNav.style.display = 'none';
}
// User dropdown toggle
if (avatar && dropdown) {
avatar.addEventListener('click', function (e) {
e.stopPropagation();
var showing = dropdown.style.display !== 'none';
dropdown.style.display = showing ? 'none' : 'block';
avatar.addEventListener('click', function (event) {
event.stopPropagation();
dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
});
}
// Hamburger toggle
if (hamburger && mobileNav) {
hamburger.addEventListener('click', function (e) {
e.stopPropagation();
var showing = mobileNav.style.display !== 'none';
mobileNav.style.display = showing ? 'none' : 'block';
hamburger.classList.toggle('open', !showing);
hamburger.addEventListener('click', function (event) {
event.stopPropagation();
mobileNav.style.display = mobileNav.style.display === 'none' ? 'block' : 'none';
hamburger.classList.toggle('open', mobileNav.style.display !== 'none');
});
}
// Close dropdown / mobile-nav on outside click
document.addEventListener('click', function (e) {
if (dropdown && !e.target.closest('.user-menu')) {
document.addEventListener('click', function (event) {
if (dropdown && !event.target.closest('.user-menu')) {
dropdown.style.display = 'none';
}
if (mobileNav && !e.target.closest('.navbar') && !e.target.closest('.mobile-nav')) {
if (mobileNav && !event.target.closest('.navbar') && !event.target.closest('.mobile-nav')) {
mobileNav.style.display = 'none';
if (hamburger) hamburger.classList.remove('open');
if (hamburger) {
hamburger.classList.remove('open');
}
}
});
// Action buttons inside dropdown
document.querySelectorAll('[data-action="logout"]').forEach(function (btn) {
btn.addEventListener('click', function () {
localStorage.removeItem(STORAGE_KEY);
window.location.href = (window.APP_BASE||'')+'html/login.html';
document.querySelectorAll('[data-action="logout"]').forEach(function (button) {
button.addEventListener('click', function () {
window.CrankAuth.logout();
});
});
document.querySelectorAll('[data-action="profile"]').forEach(function (btn) {
btn.addEventListener('click', function () {
if (dropdown) dropdown.style.display = 'none';
window.location.href = (window.APP_BASE||'')+'html/settings.html#profile';
document.querySelectorAll('[data-action="profile"]').forEach(function (button) {
button.addEventListener('click', function () {
if (dropdown) {
dropdown.style.display = 'none';
}
window.location.href = (window.APP_BASE || '') + 'html/settings.html#profile';
});
});
document.querySelectorAll('[data-action="settings-link"]').forEach(function (btn) {
btn.addEventListener('click', function () {
if (dropdown) dropdown.style.display = 'none';
window.location.href = (window.APP_BASE||'')+'html/settings.html';
document.querySelectorAll('[data-action="settings-link"]').forEach(function (button) {
button.addEventListener('click', function () {
if (dropdown) {
dropdown.style.display = 'none';
}
window.location.href = (window.APP_BASE || '') + 'html/settings.html';
});
});
}
@@ -98,5 +103,4 @@
} else {
initNav();
}
})();