/* Auth state helpers shared across all pages */ const Auth = (() => { const KEY = 'bb_token'; const USER_KEY = 'bb_user'; function getToken() { return localStorage.getItem(KEY); } function saveToken(token) { localStorage.setItem(KEY, token); } function getUser() { const raw = localStorage.getItem(USER_KEY); return raw ? JSON.parse(raw) : null; } function saveUser(user) { localStorage.setItem(USER_KEY, JSON.stringify(user)); } function _decodePayload() { const token = getToken(); if (!token) return null; try { return JSON.parse(atob(token.split('.')[1])); } catch (_) { return null; } } function logout() { localStorage.removeItem(KEY); localStorage.removeItem(USER_KEY); window.location.href = '/login.html'; } function isLoggedIn() { return !!getToken(); } function requireAuth() { if (!isLoggedIn()) { window.location.href = '/login.html'; return false; } return true; } function redirectIfLoggedIn() { if (isLoggedIn()) window.location.href = '/dashboard.html'; } async function renderNav(activePage) { const navLinksEl = document.getElementById('nav-links'); const navUserEl = document.getElementById('nav-user'); if (!navLinksEl || !navUserEl) return; if (isLoggedIn()) { let user = getUser(); if (!user) { try { user = await API.users.me(); saveUser(user); } catch (_) {} } const payload = _decodePayload(); const isImpersonating = !!(payload && payload.admin_id); navLinksEl.innerHTML = ` My Bottle Log Entry `; if (isImpersonating) { navUserEl.innerHTML = ` `; } else { navUserEl.innerHTML = ` ${user?.is_admin ? 'Admin' : ''} `; _injectSettingsModal(user); } } else { navLinksEl.innerHTML = ''; navUserEl.innerHTML = `Login`; } } function _buildTimezoneOptions(selected) { let allTz; try { allTz = Intl.supportedValuesOf('timeZone'); } catch (_) { allTz = ['UTC', 'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', 'America/Anchorage', 'Pacific/Honolulu', 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Asia/Tokyo', 'Asia/Shanghai', 'Australia/Sydney']; } const groups = {}; for (const tz of allTz) { const slash = tz.indexOf('/'); const group = slash === -1 ? 'Other' : tz.slice(0, slash); (groups[group] = groups[group] || []).push(tz); } return Object.keys(groups).sort().map(group => { const opts = groups[group].map(tz => { const label = tz.slice(tz.indexOf('/') + 1).replace(/_/g, ' ').replace(/\//g, ' / '); return ``; }).join(''); return ``; }).join(''); } function _injectSettingsModal(user) { if (document.getElementById('settings-modal')) return; const tzOptions = _buildTimezoneOptions(user?.timezone || 'UTC'); document.body.insertAdjacentHTML('beforeend', `
`); document.addEventListener('click', (e) => { if (e.target.id === 'settings-modal') hideSettingsModal(); }); } function showSettingsModal() { document.getElementById('settings-modal').style.display = 'flex'; const msg = document.getElementById('settings-msg'); if (msg) msg.innerHTML = ''; document.getElementById('settings-pw-current').value = ''; document.getElementById('settings-pw-new').value = ''; document.getElementById('settings-pw-confirm').value = ''; } function hideSettingsModal() { document.getElementById('settings-modal').style.display = 'none'; } function detectTimezone() { const detected = Intl.DateTimeFormat().resolvedOptions().timeZone; const sel = document.getElementById('settings-tz'); if (sel) sel.value = detected; } async function saveProfile() { const displayName = document.getElementById('settings-display-name').value.trim(); const timezone = document.getElementById('settings-tz').value; try { const user = await API.users.update({ display_name: displayName || null, timezone }); saveUser(user); const usernameEl = document.querySelector('.nav-username'); if (usernameEl) usernameEl.textContent = user.display_name || user.email || 'Account'; _showSettingsMsg('Profile saved.', 'success'); } catch (err) { _showSettingsMsg(err.message, 'error'); } } async function submitPasswordChange() { const current = document.getElementById('settings-pw-current').value; const newPw = document.getElementById('settings-pw-new').value; const confirm = document.getElementById('settings-pw-confirm').value; if (newPw !== confirm) { _showSettingsMsg('New passwords do not match.', 'error'); return; } if (newPw.length < 8) { _showSettingsMsg('Password must be at least 8 characters.', 'error'); return; } try { await API.users.changePassword({ current_password: current, new_password: newPw }); _showSettingsMsg('Password updated!', 'success'); document.getElementById('settings-pw-current').value = ''; document.getElementById('settings-pw-new').value = ''; document.getElementById('settings-pw-confirm').value = ''; } catch (err) { _showSettingsMsg(err.message, 'error'); } } function _showSettingsMsg(text, type) { const el = document.getElementById('settings-msg'); if (!el) return; const cls = type === 'success' ? 'alert-success' : 'alert-error'; el.innerHTML = `