Files
oscars-pool/public/js/app_header_boot.js
T

134 lines
4.3 KiB
JavaScript

// Runs early (defer) to populate the navbar before page-specific JS.
// Goal: avoid "late" header updates that look like flicker.
(function () {
function base64UrlToJson(b64url) {
const s = String(b64url || '')
.replace(/-/g, '+')
.replace(/_/g, '/');
const padded = s + '='.repeat((4 - (s.length % 4)) % 4);
return JSON.parse(atob(padded));
}
function decodeJwtPayload(token) {
const parts = String(token || '').split('.');
if (parts.length < 2) return null;
try { return base64UrlToJson(parts[1]); } catch (_) { return null; }
}
function isExpired(decoded) {
const exp = Number(decoded?.exp);
if (!Number.isFinite(exp) || exp <= 0) return false;
const now = Math.floor(Date.now() / 1000);
return exp < now;
}
const token = localStorage.getItem('auth_token');
if (!token) return;
const decoded = decodeJwtPayload(token);
if (!decoded) return;
// Fast-path redirect checks (match page scripts behavior).
if (decoded.mustChangePassword && !/\/change-password\.html(\?|#|$)/.test(window.location.pathname)) {
window.location.href = '/change-password.html';
return;
}
if (isExpired(decoded)) {
try { localStorage.removeItem('auth_token'); } catch (_) {}
if (!/\/index\.html$/.test(window.location.pathname) && window.location.pathname !== '/') {
window.location.href = '/';
}
return;
}
const name = decoded.name || '';
const nameTargets = document.querySelectorAll('[data-user-name]');
if (nameTargets.length) {
nameTargets.forEach((el) => {
if (el && !el.textContent) el.textContent = name;
});
} else {
const nameEl = document.getElementById('user-name');
if (nameEl && !nameEl.textContent) {
nameEl.textContent = name;
}
}
const adminLink = document.getElementById('admin-control-link');
if (adminLink && decoded.admin) {
adminLink.classList.remove('d-none');
}
// Check visibility config for picks button
const picksButton = document.getElementById('nav-link-picks');
if (picksButton) {
// Check cached value first for immediate display
const cachedConfig = localStorage.getItem('visibility_config_cache');
let cachedValue = null;
let cachedTimestamp = null;
if (cachedConfig) {
try {
const parsed = JSON.parse(cachedConfig);
cachedValue = parsed.value;
cachedTimestamp = parsed.timestamp;
} catch (e) {
// Invalid cache, ignore
}
}
// Use cached value if it's less than 5 minutes old
const cacheAge = cachedTimestamp ? Date.now() - cachedTimestamp : Infinity;
const useCache = cachedValue !== null && cacheAge < 5 * 60 * 1000;
if (useCache) {
// Show button immediately based on cached value
if (cachedValue.showPicksButton !== false) {
picksButton.style.display = '';
}
} else {
// Default to showing while we fetch
picksButton.style.display = '';
}
// Fetch fresh value from server
fetch('/api/settings/visibility-config', { cache: 'no-store' })
.then(res => res.json())
.then(config => {
// Cache the result
localStorage.setItem('visibility_config_cache', JSON.stringify({
value: config,
timestamp: Date.now()
}));
// Update display based on fresh value
if (config.showPicksButton !== false) {
picksButton.style.display = '';
} else {
picksButton.style.display = 'none';
}
})
.catch(() => {
// On error, keep current state (already set from cache or default)
});
}
// Highlight the active page in the navbar.
(function markActiveNavLink() {
const currentFile = (String(window.location.pathname || '').split('/').pop() || 'index.html').toLowerCase();
const links = document.querySelectorAll('nav.navbar .navbar-nav a.nav-link[href]');
links.forEach((a) => {
const rawHref = String(a.getAttribute('href') || '');
const hrefFile = rawHref.split('#')[0].split('?')[0].split('/').pop().toLowerCase();
const isCurrent = hrefFile && hrefFile === currentFile;
a.classList.toggle('is-current', isCurrent);
if (isCurrent) {
a.setAttribute('aria-current', 'page');
} else if (a.getAttribute('aria-current') === 'page') {
a.removeAttribute('aria-current');
}
});
})();
})();