(function () { 'use strict'; const bell = document.querySelector('[data-notification-bell]'); if (!bell) return; const countEl = bell.querySelector('[data-unread-count]'); const dropdown = bell.querySelector('[data-notification-dropdown]'); let isOpen = false; bell.addEventListener('click', () => { isOpen = !isOpen; dropdown.hidden = !isOpen; if (isOpen) loadNotifications(); }); document.addEventListener('click', (e) => { if (!bell.contains(e.target)) { isOpen = false; dropdown.hidden = true; } }); async function updateBell() { try { const res = await fetch('/api/notifications/bell'); if (!res.ok) return; const data = await res.json(); const count = data.unreadCount || 0; countEl.textContent = count > 99 ? '99+' : String(count); countEl.hidden = count === 0; bell.classList.toggle('has-unread', count > 0); } catch (err) { console.error('bell update', err); } } async function loadNotifications() { try { const res = await fetch('/api/notifications?limit=20'); if (!res.ok) return; const data = await res.json(); renderNotifications(data.notifications || []); } catch (err) { console.error('load notifications', err); } } function renderNotifications(notifications) { const list = dropdown.querySelector('ul'); if (!list) return; list.innerHTML = ''; if (!notifications.length) { list.innerHTML = '
  • No notifications
  • '; return; } notifications.forEach(n => { const li = document.createElement('li'); li.className = n.readAt ? 'notification-item is-read' : 'notification-item'; li.innerHTML = `
    ${escapeHtml(n.message)}
    `; if (!n.readAt) { li.addEventListener('click', () => markRead(n.id)); } list.appendChild(li); }); // Add "Mark all read" button const footer = document.createElement('li'); footer.className = 'notification-footer'; const btn = document.createElement('button'); btn.textContent = 'Mark all read'; btn.addEventListener('click', markAllRead); footer.appendChild(btn); list.appendChild(footer); } async function markRead(id) { try { await fetch(`/api/notifications/${id}/read`, { method: 'POST' }); updateBell(); loadNotifications(); } catch (err) { console.error('mark read', err); } } async function markAllRead() { try { await fetch('/api/notifications/read-all', { method: 'POST' }); updateBell(); loadNotifications(); } catch (err) { console.error('mark all read', err); } } function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function formatDate(iso) { const d = new Date(iso); return d.toLocaleString(); } // Listen for WebSocket notification events if (window.__cairnquireRealtime) { window.__cairnquireRealtime.on('notification', () => { updateBell(); }); } // Poll every 60s as fallback setInterval(updateBell, 60000); updateBell(); })();