diff --git a/app/static/css/style.css b/app/static/css/style.css index 3ffe7ac..0eecdfc 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -595,6 +595,7 @@ main { } .fab { + position: relative; /* For badge positioning */ width: 56px; height: 56px; border-radius: 50%; @@ -627,6 +628,41 @@ main { color: white; } +/* FAB Badge (base style for all badges on FAB buttons) */ +.fab-badge { + position: absolute; + top: -4px; + right: -4px; + min-width: 20px; + height: 20px; + border-radius: 10px; + padding: 2px 6px; + font-size: 0.65rem; + font-weight: bold; + line-height: 1.4; + text-align: center; + color: white; + display: none; + z-index: 1; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +/* FAB Badge for Pending Contacts (orange/amber) */ +.fab-badge-pending { + background-color: #fd7e14; /* Bootstrap orange */ +} + +/* FAB Badge for Direct Messages (green) */ +.fab-badge-dm { + background-color: #198754; /* Bootstrap success green */ +} + +/* Animation on hover */ +.fab:hover .fab-badge { + transform: scale(1.1); + transition: transform 0.2s ease; +} + /* Mobile optimization */ @media (max-width: 768px) { .fab-container { diff --git a/app/static/js/app.js b/app/static/js/app.js index 70ba4d6..ca06d8e 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -80,6 +80,9 @@ document.addEventListener('DOMContentLoaded', async function() { // Now load other data (can run in parallel) loadArchiveList(); loadMessages(); + + // Initial badge updates + updatePendingContactsBadge(); loadStatus(); // Setup auto-refresh AFTER channels are loaded @@ -609,6 +612,7 @@ function setupAutoRefresh() { await checkForUpdates(); await checkDmUpdates(); // Also check for DM updates + await updatePendingContactsBadge(); // Also check for pending contacts }, checkInterval); console.log(`Intelligent auto-refresh enabled: checking every ${checkInterval / 1000}s`); @@ -945,6 +949,35 @@ function updateNotificationBell(count) { } } +/** + * Update FAB button badge (universal function for all FAB badges) + * @param {string} fabSelector - CSS selector for FAB button (e.g., '.fab-dm', '.fab-contacts') + * @param {string} badgeClass - Badge class name (e.g., 'fab-badge-dm', 'fab-badge-pending') + * @param {number} count - Number to display (0 = hide badge) + */ +function updateFabBadge(fabSelector, badgeClass, count) { + const fabButton = document.querySelector(fabSelector); + if (!fabButton) return; + + let badge = fabButton.querySelector(`.${badgeClass}`); + + if (count > 0) { + // Show badge + if (!badge) { + badge = document.createElement('span'); + badge.className = `fab-badge ${badgeClass}`; + fabButton.appendChild(badge); + } + badge.textContent = count > 99 ? '99+' : count; + badge.style.display = 'inline-block'; + } else { + // Hide badge + if (badge) { + badge.style.display = 'none'; + } + } +} + /** * Setup emoji picker */ @@ -1367,29 +1400,58 @@ function updateDmBadges(totalUnread) { } } - // Update notification bell (secondary badge) - const bellContainer = document.getElementById('notificationBell'); - if (!bellContainer) return; + // Update FAB badge (green badge on Direct Messages button) + updateFabBadge('.fab-dm', 'fab-badge-dm', totalUnread); +} - let dmBadge = bellContainer.querySelector('.notification-badge-dm'); +/** + * Update pending contacts badge on Contact Management FAB button + * Fetches count from API using type filter from localStorage + */ +async function updatePendingContactsBadge() { + try { + // Load type filter from localStorage (uses same function as contacts.js) + const savedTypes = loadPendingTypeFilter(); - if (totalUnread > 0) { - if (!dmBadge) { - dmBadge = document.createElement('span'); - dmBadge.className = 'notification-badge-dm'; - bellContainer.appendChild(dmBadge); + // Build query string with types parameter + const params = new URLSearchParams(); + savedTypes.forEach(type => params.append('types', type)); + + // Fetch pending count with type filter + const response = await fetch(`/api/contacts/pending?${params.toString()}`); + if (!response.ok) return; + + const data = await response.json(); + + if (data.success) { + const count = data.pending?.length || 0; + // Update FAB badge (orange badge on Contact Management button) + updateFabBadge('.fab-contacts', 'fab-badge-pending', count); } - dmBadge.textContent = totalUnread > 99 ? '99+' : totalUnread; - dmBadge.style.display = 'inline-block'; - - // Animate bell - const bellIcon = bellContainer.querySelector('i'); - if (bellIcon) { - bellIcon.classList.add('bell-ring'); - setTimeout(() => bellIcon.classList.remove('bell-ring'), 1000); - } - } else if (dmBadge) { - dmBadge.style.display = 'none'; + } catch (error) { + console.error('Error updating pending contacts badge:', error); } } +/** + * Load pending contacts type filter from localStorage. + * This is a duplicate of the function in contacts.js for use in app.js + * @returns {Array} Array of contact types (default: [1] for CLI only) + */ +function loadPendingTypeFilter() { + try { + const stored = localStorage.getItem('pendingContactsTypeFilter'); + if (stored) { + const types = JSON.parse(stored); + // Validate: must be array of valid types + if (Array.isArray(types) && types.every(t => [1, 2, 3, 4].includes(t))) { + return types; + } + } + } catch (e) { + console.error('Failed to load pending type filter from localStorage:', e); + } + // Default: CLI only (most common use case) + return [1]; +} + diff --git a/app/templates/index.html b/app/templates/index.html index 4618a13..05fd5c2 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -202,34 +202,9 @@ await loadDmLastSeenTimestampsFromServer(); } - // Fetch latest DM conversations with unread counts - const response = await fetch('/api/dm/conversations'); - if (response.ok) { - const data = await response.json(); - - // Calculate total unread by summing unread counts from all conversations - let totalUnread = 0; - if (data.conversations && Array.isArray(data.conversations)) { - totalUnread = data.conversations.reduce((sum, conv) => sum + (conv.unread || 0), 0); - } - - // Update the green DM badge on notification bell - const bellContainer = document.getElementById('notificationBell'); - if (bellContainer) { - let dmBadge = bellContainer.querySelector('.notification-badge-dm'); - - if (totalUnread > 0) { - if (!dmBadge) { - dmBadge = document.createElement('span'); - dmBadge.className = 'notification-badge-dm'; - bellContainer.appendChild(dmBadge); - } - dmBadge.textContent = totalUnread > 99 ? '99+' : totalUnread; - dmBadge.style.display = 'inline-block'; - } else if (dmBadge) { - dmBadge.style.display = 'none'; - } - } + // Trigger DM badge update (now handled by app.js on FAB button) + if (typeof checkDmUpdates === 'function') { + await checkDmUpdates(); } } catch (error) { console.error('Error updating DM badges:', error); @@ -244,6 +219,18 @@ contactsFrame.src = contactsFrame.src; } }); + + // Update pending contacts badge when modal is closed + contactsModal.addEventListener('hidden.bs.modal', async function () { + try { + // Trigger pending contacts badge update (handled by app.js on FAB button) + if (typeof updatePendingContactsBadge === 'function') { + await updatePendingContactsBadge(); + } + } catch (error) { + console.error('Error updating pending contacts badge:', error); + } + }); } });