// Create a notification container if it doesn't exist function createNotificationContainer() { let container = document.getElementById('notification-container'); if (!container) { container = document.createElement('div'); container.id = 'notification-container'; container.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 9999; max-width: 400px; `; document.body.appendChild(container); } return container; } // Show error notification function showError(message) { const container = createNotificationContainer(); // Create notification element const notification = document.createElement('div'); notification.className = 'error-notification'; notification.style.cssText = ` background-color: #ff4444; color: white; padding: 15px; margin-bottom: 10px; border-radius: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); position: relative; animation: slideIn 0.3s ease-out; `; // Add close button const closeButton = document.createElement('button'); closeButton.innerHTML = '×'; closeButton.style.cssText = ` position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; color: white; font-size: 20px; cursor: pointer; padding: 0 5px; `; // Add message const messageText = document.createElement('div'); messageText.style.cssText = ` margin-right: 20px; word-wrap: break-word; `; messageText.textContent = typeof message === 'object' ? message.message : message; // Assemble notification notification.appendChild(messageText); notification.appendChild(closeButton); // Add to container container.appendChild(notification); // Add close functionality closeButton.onclick = () => { notification.style.animation = 'slideOut 0.3s ease-out'; setTimeout(() => notification.remove(), 300); }; // Auto-remove after 10 seconds setTimeout(() => { if (notification.parentNode) { notification.style.animation = 'slideOut 0.3s ease-out'; setTimeout(() => notification.remove(), 300); } }, 10000); } // Add CSS animations const style = document.createElement('style'); style.textContent = ` @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } `; document.head.appendChild(style); // Handle SSE notifications function setupErrorNotifications() { const eventSource = new EventSource('/notifications'); eventSource.onmessage = function(event) { const data = JSON.parse(event.data); if (data.type === 'error') { showError(data.message); } }; eventSource.onerror = function(error) { console.error('EventSource failed:', error); eventSource.close(); // Try to reconnect after 5 seconds setTimeout(setupErrorNotifications, 5000); }; } // Handle AJAX errors function handleAjaxError(error) { let errorMessage = 'An error occurred'; if (error.response) { try { const data = error.response.data; errorMessage = data.error || data.message || errorMessage; } catch (e) { errorMessage = error.response.statusText || errorMessage; } } else if (error.request) { errorMessage = 'No response received from server'; } else { errorMessage = error.message; } showError(errorMessage); } // Initialize error handling document.addEventListener('DOMContentLoaded', function() { setupErrorNotifications(); // Add global AJAX error handler $(document).ajaxError(function(event, jqXHR, settings, error) { handleAjaxError(error); }); });