/** * EasyStream Notifications Widget * Real-time notification bell with dropdown */ class NotificationWidget { constructor(containerId = 'notification-widget') { this.container = document.getElementById(containerId); if (!this.container) { console.error('Notification container not found'); return; } this.unreadCount = 0; this.notifications = []; this.dropdownOpen = false; this.pollInterval = 30000; // Poll every 30 seconds this.pollTimer = null; this.init(); } init() { this.render(); this.attachEventListeners(); this.fetchNotifications(); this.startPolling(); } render() { this.container.innerHTML = `
`; // Add styles this.injectStyles(); } injectStyles() { if (document.getElementById('notification-widget-styles')) { return; } const style = document.createElement('style'); style.id = 'notification-widget-styles'; style.textContent = ` .notification-bell { position: relative; display: inline-block; } .notification-btn { background: none; border: none; cursor: pointer; padding: 8px; border-radius: 50%; transition: background 0.2s; position: relative; } .notification-btn:hover { background: rgba(0,0,0,0.05); } .notification-badge { position: absolute; top: 4px; right: 4px; background: #dc3545; color: white; border-radius: 10px; padding: 2px 6px; font-size: 11px; font-weight: bold; min-width: 18px; text-align: center; } .notification-dropdown { position: absolute; top: 100%; right: 0; margin-top: 8px; width: 380px; max-width: 95vw; background: white; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,0.15); z-index: 1000; max-height: 500px; display: flex; flex-direction: column; } .notification-header { padding: 16px 20px; border-bottom: 1px solid #e9ecef; display: flex; justify-content: space-between; align-items: center; } .notification-header h3 { margin: 0; font-size: 18px; font-weight: 600; } .mark-all-read-btn { background: none; border: none; color: #007bff; cursor: pointer; font-size: 14px; padding: 4px 8px; } .mark-all-read-btn:hover { text-decoration: underline; } .notification-list { overflow-y: auto; max-height: 360px; } .notification-item { padding: 16px 20px; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background 0.2s; display: flex; gap: 12px; } .notification-item:hover { background: #f8f9fa; } .notification-item.unread { background: #e7f3ff; } .notification-item.unread:hover { background: #d0e9ff; } .notification-avatar { width: 40px; height: 40px; border-radius: 50%; background: #e9ecef; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 20px; } .notification-content { flex: 1; min-width: 0; } .notification-title { font-weight: 500; margin: 0 0 4px 0; font-size: 14px; color: #212529; } .notification-message { font-size: 13px; color: #666; margin: 0 0 4px 0; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .notification-time { font-size: 12px; color: #999; } .notification-loading, .notification-empty { padding: 40px 20px; text-align: center; color: #666; } .notification-footer { padding: 12px 20px; border-top: 1px solid #e9ecef; text-align: center; } .notification-footer a { color: #007bff; text-decoration: none; font-size: 14px; font-weight: 500; } .notification-footer a:hover { text-decoration: underline; } `; document.head.appendChild(style); } attachEventListeners() { const btn = document.getElementById('notificationBtn'); const dropdown = document.getElementById('notificationDropdown'); const markAllBtn = document.getElementById('markAllReadBtn'); btn.addEventListener('click', (e) => { e.stopPropagation(); this.toggleDropdown(); }); markAllBtn.addEventListener('click', () => { this.markAllAsRead(); }); // Close dropdown when clicking outside document.addEventListener('click', (e) => { if (!this.container.contains(e.target)) { this.closeDropdown(); } }); } toggleDropdown() { if (this.dropdownOpen) { this.closeDropdown(); } else { this.openDropdown(); } } openDropdown() { document.getElementById('notificationDropdown').style.display = 'block'; this.dropdownOpen = true; this.fetchNotifications(); } closeDropdown() { document.getElementById('notificationDropdown').style.display = 'none'; this.dropdownOpen = false; } async fetchNotifications() { try { const response = await fetch('/api/notifications.php?limit=20'); const data = await response.json(); if (data.success) { this.notifications = data.data.notifications; this.unreadCount = data.data.unread_count; this.updateBadge(); this.renderNotifications(); } } catch (error) { console.error('Failed to fetch notifications:', error); } } async fetchUnreadCount() { try { const response = await fetch('/api/notifications_count.php'); const data = await response.json(); if (data.success) { this.unreadCount = data.unread_count; this.updateBadge(); } } catch (error) { console.error('Failed to fetch unread count:', error); } } updateBadge() { const badge = document.getElementById('notificationBadge'); if (this.unreadCount > 0) { badge.textContent = this.unreadCount > 99 ? '99+' : this.unreadCount; badge.style.display = 'block'; } else { badge.style.display = 'none'; } } renderNotifications() { const list = document.getElementById('notificationList'); if (this.notifications.length === 0) { list.innerHTML = '
No notifications
'; return; } list.innerHTML = this.notifications.map(n => this.renderNotification(n)).join(''); // Attach click handlers list.querySelectorAll('.notification-item').forEach((item, index) => { item.addEventListener('click', () => { this.handleNotificationClick(this.notifications[index]); }); }); } renderNotification(notification) { const isUnread = !notification.is_read; const icon = this.getNotificationIcon(notification.type); const timeAgo = this.formatTimeAgo(notification.created_at); return `
${icon}
${notification.title}
${notification.message ? `
${notification.message}
` : ''}
${timeAgo}
`; } getNotificationIcon(type) { const icons = { 'comment': 'đŸ’Ŧ', 'like': 'â¤ī¸', 'subscribe': '🔔', 'video_upload': 'đŸŽŦ', 'mention': '👤', 'system': 'â„šī¸' }; return icons[type] || '🔔'; } formatTimeAgo(timestamp) { const now = new Date(); const then = new Date(timestamp); const seconds = Math.floor((now - then) / 1000); if (seconds < 60) return 'just now'; if (seconds < 3600) return Math.floor(seconds / 60) + 'm ago'; if (seconds < 86400) return Math.floor(seconds / 3600) + 'h ago'; if (seconds < 604800) return Math.floor(seconds / 86400) + 'd ago'; return Math.floor(seconds / 604800) + 'w ago'; } async handleNotificationClick(notification) { // Mark as read await this.markAsRead(notification.notification_id); // Navigate to link if (notification.link) { window.location.href = notification.link; } this.closeDropdown(); } async markAsRead(id) { try { const response = await fetch('/api/notifications.php?action=mark_read', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id }) }); const data = await response.json(); if (data.success) { this.unreadCount = data.unread_count; this.updateBadge(); // Update UI const item = document.querySelector(`.notification-item[data-id="${id}"]`); if (item) { item.classList.remove('unread'); } } } catch (error) { console.error('Failed to mark as read:', error); } } async markAllAsRead() { try { const response = await fetch('/api/notifications.php?action=mark_all_read', { method: 'POST' }); const data = await response.json(); if (data.success) { this.unreadCount = 0; this.updateBadge(); this.fetchNotifications(); } } catch (error) { console.error('Failed to mark all as read:', error); } } startPolling() { this.pollTimer = setInterval(() => { if (!this.dropdownOpen) { this.fetchUnreadCount(); } }, this.pollInterval); } stopPolling() { if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; } } destroy() { this.stopPolling(); this.container.innerHTML = ''; } } // Auto-initialize if container exists document.addEventListener('DOMContentLoaded', () => { if (document.getElementById('notification-widget')) { window.notificationWidget = new NotificationWidget(); } });