Your Name
feat: UI improvements and error suppression - Enhanced dashboard and market pages with improved header buttons, logo, and currency symbol display - Stopped animated ticker - Removed pie chart legends - Added error suppressor for external service errors (SSE, Permissions-Policy warnings) - Improved header button prominence and icon appearance - Enhanced logo with glow effects and better design - Fixed currency symbol visibility in market tables
8b7b267
| /** | |
| * Global Error Handler | |
| * Comprehensive error handling and user-friendly error messages | |
| */ | |
| class ErrorHandler { | |
| constructor() { | |
| this.errors = []; | |
| this.maxErrors = 100; | |
| this.init(); | |
| } | |
| init() { | |
| // Catch all unhandled errors | |
| window.addEventListener('error', (event) => { | |
| this.handleError(event.error || event.message, 'Global Error'); | |
| event.preventDefault(); | |
| }); | |
| // Catch unhandled promise rejections | |
| window.addEventListener('unhandledrejection', (event) => { | |
| this.handleError(event.reason, 'Unhandled Promise'); | |
| event.preventDefault(); | |
| }); | |
| console.log('✅ Error Handler initialized'); | |
| } | |
| /** | |
| * Handle error with fallback | |
| */ | |
| handleError(error, context = 'Unknown') { | |
| const errorInfo = { | |
| message: this.getErrorMessage(error), | |
| context, | |
| timestamp: Date.now(), | |
| stack: error?.stack || null, | |
| url: window.location.href | |
| }; | |
| // Log error | |
| console.error(`[${context}]`, error); | |
| // Store error | |
| this.errors.push(errorInfo); | |
| if (this.errors.length > this.maxErrors) { | |
| this.errors.shift(); | |
| } | |
| // Show user-friendly message | |
| this.showUserError(errorInfo); | |
| } | |
| /** | |
| * Get user-friendly error message | |
| */ | |
| getErrorMessage(error) { | |
| if (typeof error === 'string') return error; | |
| if (error?.message) return error.message; | |
| if (error?.toString) return error.toString(); | |
| return 'An unknown error occurred'; | |
| } | |
| /** | |
| * Show error to user | |
| */ | |
| showUserError(errorInfo) { | |
| const message = this.getUserFriendlyMessage(errorInfo.message); | |
| if (window.uiManager) { | |
| window.uiManager.showToast(message, 'error', 5000); | |
| } else { | |
| // Fallback if UI Manager not loaded | |
| console.error('Error:', message); | |
| alert(message); | |
| } | |
| } | |
| /** | |
| * Convert technical error to user-friendly message | |
| */ | |
| getUserFriendlyMessage(technicalMessage) { | |
| const lowerMessage = technicalMessage.toLowerCase(); | |
| // Network errors | |
| if (lowerMessage.includes('network') || lowerMessage.includes('fetch')) { | |
| return '🌐 Network error. Please check your connection.'; | |
| } | |
| // Timeout errors | |
| if (lowerMessage.includes('timeout') || lowerMessage.includes('timed out')) { | |
| return '⏱️ Request timed out. Please try again.'; | |
| } | |
| // Not found errors | |
| if (lowerMessage.includes('404') || lowerMessage.includes('not found')) { | |
| return '🔍 Resource not found. It may have been moved or deleted.'; | |
| } | |
| // Authorization errors | |
| if (lowerMessage.includes('401') || lowerMessage.includes('unauthorized')) { | |
| return '🔒 Authentication required. Please log in.'; | |
| } | |
| // Forbidden errors | |
| if (lowerMessage.includes('403') || lowerMessage.includes('forbidden')) { | |
| return '🚫 Access denied. You don\'t have permission.'; | |
| } | |
| // Server errors | |
| if (lowerMessage.includes('500') || lowerMessage.includes('server error')) { | |
| return '⚠️ Server error. We\'re working on it!'; | |
| } | |
| // Database errors | |
| if (lowerMessage.includes('database') || lowerMessage.includes('sql')) { | |
| return '💾 Database error. Please try again later.'; | |
| } | |
| // API errors | |
| if (lowerMessage.includes('api')) { | |
| return '🔌 API error. Using fallback data.'; | |
| } | |
| // Default message | |
| return `⚠️ ${technicalMessage}`; | |
| } | |
| /** | |
| * Get error logs | |
| */ | |
| getErrors() { | |
| return this.errors; | |
| } | |
| /** | |
| * Clear error logs | |
| */ | |
| clearErrors() { | |
| this.errors = []; | |
| } | |
| /** | |
| * Export errors for debugging | |
| */ | |
| exportErrors() { | |
| const data = JSON.stringify(this.errors, null, 2); | |
| const blob = new Blob([data], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `errors-${Date.now()}.json`; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| } | |
| } | |
| // API Error Handler | |
| class APIErrorHandler { | |
| static async handleAPIError(response, fallbackData = null) { | |
| let error = { | |
| status: response?.status || 500, | |
| statusText: response?.statusText || 'Unknown', | |
| url: response?.url || 'unknown' | |
| }; | |
| try { | |
| const data = await response.json(); | |
| error.message = data.message || data.error || 'API Error'; | |
| error.details = data.details || null; | |
| } catch (e) { | |
| error.message = `HTTP ${error.status}: ${error.statusText}`; | |
| } | |
| console.error('API Error:', error); | |
| // Show user-friendly error | |
| if (window.errorHandler) { | |
| window.errorHandler.handleError(error, 'API Error'); | |
| } | |
| // Return fallback data if provided | |
| if (fallbackData) { | |
| console.warn('Using fallback data due to API error'); | |
| return { | |
| success: false, | |
| error: error.message, | |
| data: fallbackData, | |
| fallback: true | |
| }; | |
| } | |
| throw error; | |
| } | |
| static async fetchWithFallback(url, options = {}, fallbackData = null) { | |
| try { | |
| const response = await fetch(url, { | |
| ...options, | |
| signal: options.signal || AbortSignal.timeout(options.timeout || 10000) | |
| }); | |
| if (!response.ok) { | |
| return await this.handleAPIError(response, fallbackData); | |
| } | |
| const data = await response.json(); | |
| return { | |
| success: true, | |
| data, | |
| fallback: false | |
| }; | |
| } catch (error) { | |
| console.error('Fetch error:', error); | |
| if (window.errorHandler) { | |
| window.errorHandler.handleError(error, 'Fetch Error'); | |
| } | |
| if (fallbackData) { | |
| return { | |
| success: false, | |
| error: error.message, | |
| data: fallbackData, | |
| fallback: true | |
| }; | |
| } | |
| throw error; | |
| } | |
| } | |
| } | |
| // Form Validation Helper | |
| class FormValidator { | |
| static validateRequired(value, fieldName) { | |
| if (!value || (typeof value === 'string' && value.trim() === '')) { | |
| return `${fieldName} is required`; | |
| } | |
| return null; | |
| } | |
| static validateEmail(email) { | |
| const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | |
| if (!re.test(email)) { | |
| return 'Invalid email address'; | |
| } | |
| return null; | |
| } | |
| static validateURL(url) { | |
| try { | |
| new URL(url); | |
| return null; | |
| } catch { | |
| return 'Invalid URL'; | |
| } | |
| } | |
| static validateNumber(value, min = null, max = null) { | |
| const num = Number(value); | |
| if (isNaN(num)) { | |
| return 'Must be a number'; | |
| } | |
| if (min !== null && num < min) { | |
| return `Must be at least ${min}`; | |
| } | |
| if (max !== null && num > max) { | |
| return `Must be at most ${max}`; | |
| } | |
| return null; | |
| } | |
| static validateForm(formElement) { | |
| const errors = {}; | |
| const inputs = formElement.querySelectorAll('[data-validate]'); | |
| inputs.forEach(input => { | |
| const rules = input.dataset.validate.split('|'); | |
| const fieldName = input.name || input.id; | |
| rules.forEach(rule => { | |
| let error = null; | |
| if (rule === 'required') { | |
| error = this.validateRequired(input.value, fieldName); | |
| } else if (rule === 'email') { | |
| error = this.validateEmail(input.value); | |
| } else if (rule === 'url') { | |
| error = this.validateURL(input.value); | |
| } else if (rule.startsWith('number')) { | |
| const params = rule.match(/number\((\d+),(\d+)\)/); | |
| error = this.validateNumber( | |
| input.value, | |
| params ? parseInt(params[1]) : null, | |
| params ? parseInt(params[2]) : null | |
| ); | |
| } | |
| if (error) { | |
| errors[fieldName] = error; | |
| } | |
| }); | |
| }); | |
| return { | |
| valid: Object.keys(errors).length === 0, | |
| errors | |
| }; | |
| } | |
| } | |
| // Retry Helper | |
| class RetryHelper { | |
| static async retry(fn, options = {}) { | |
| const { | |
| maxAttempts = 3, | |
| delay = 1000, | |
| backoff = 2, | |
| onRetry = null | |
| } = options; | |
| let lastError; | |
| for (let attempt = 1; attempt <= maxAttempts; attempt++) { | |
| try { | |
| return await fn(); | |
| } catch (error) { | |
| lastError = error; | |
| if (attempt < maxAttempts) { | |
| const waitTime = delay * Math.pow(backoff, attempt - 1); | |
| console.warn(`Attempt ${attempt} failed, retrying in ${waitTime}ms...`); | |
| if (onRetry) { | |
| onRetry(attempt, error); | |
| } | |
| await new Promise(resolve => setTimeout(resolve, waitTime)); | |
| } | |
| } | |
| } | |
| throw lastError; | |
| } | |
| } | |
| // Create global instances | |
| const errorHandler = new ErrorHandler(); | |
| // Export | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = { | |
| ErrorHandler, | |
| APIErrorHandler, | |
| FormValidator, | |
| RetryHelper, | |
| errorHandler | |
| }; | |
| } | |
| // Make available globally | |
| window.errorHandler = errorHandler; | |
| window.APIErrorHandler = APIErrorHandler; | |
| window.FormValidator = FormValidator; | |
| window.RetryHelper = RetryHelper; | |
| console.log('✅ Error Handler loaded and ready'); | |