// Main Application Module class MainApp { constructor() { this.init(); } async init() { // Initialize message display system window.messageDisplay = new MessageDisplay(); // Check API health on startup this.checkAPIHealth(); // Initialize postal lookup immediately (always show it first) this.postalLookup = new PostalLookup(this.updateRepresentatives.bind(this)); // Check for highlighted campaign FIRST (before campaigns grid) await this.checkHighlightedCampaign(); // Initialize campaigns grid AFTER highlighted campaign loads this.campaignsGrid = new CampaignsGrid(); await this.campaignsGrid.init(); // Add global error handling window.addEventListener('error', (e) => { // Only log and show message for actual errors, not null/undefined if (e.error) { console.error('Global error:', e.error); console.error('Error details:', { message: e.message, filename: e.filename, lineno: e.lineno, colno: e.colno, error: e.error }); window.messageDisplay?.show('An unexpected error occurred. Please refresh the page and try again.', 'error'); } else { // Just log these non-critical errors without showing popup console.log('Non-critical error event:', { message: e.message, filename: e.filename, lineno: e.lineno, colno: e.colno, type: e.type }); } }); // Add unhandled promise rejection handling window.addEventListener('unhandledrejection', (e) => { if (e.reason) { console.error('Unhandled promise rejection:', e.reason); window.messageDisplay?.show('An unexpected error occurred. Please try again.', 'error'); e.preventDefault(); } else { console.log('Non-critical promise rejection:', e); } }); } async checkAPIHealth() { try { await window.apiClient.checkHealth(); console.log('API health check passed'); } catch (error) { console.error('API health check failed:', error); window.messageDisplay.show('Connection to server failed. Please check your internet connection and try again.', 'error'); } } async checkHighlightedCampaign() { try { const response = await fetch('/api/public/highlighted-campaign'); if (!response.ok) { if (response.status === 404) { // No highlighted campaign, show normal postal code lookup return false; } throw new Error('Failed to fetch highlighted campaign'); } const data = await response.json(); if (data.success && data.campaign) { this.displayHighlightedCampaign(data.campaign); return true; } return false; } catch (error) { console.error('Error checking for highlighted campaign:', error); // Continue with normal postal code lookup if there's an error return false; } } displayHighlightedCampaign(campaign) { const highlightedSection = document.getElementById('highlighted-campaign-section'); const highlightedContainer = document.getElementById('highlighted-campaign-container'); if (!highlightedSection || !highlightedContainer) return; // Build the campaign display HTML with cover photo const coverPhotoStyle = campaign.cover_photo ? `background-image: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url('/uploads/${campaign.cover_photo}'); background-size: cover; background-position: center;` : 'background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);'; const statsHTML = []; if (campaign.show_email_count && campaign.emailCount !== null) { statsHTML.push(`
📧${campaign.emailCount} Emails Sent
`); } if (campaign.show_call_count && campaign.callCount !== null) { statsHTML.push(`
📞${campaign.callCount} Calls Made
`); } if (campaign.show_response_count && campaign.responseCount !== null) { statsHTML.push(`
${campaign.responseCount} Responses
`); } const highlightedHTML = `
${campaign.cover_photo ? `
⭐ Featured Campaign

${this.escapeHtml(campaign.title || campaign.name)}

` : `
⭐ Featured Campaign

${this.escapeHtml(campaign.title || campaign.name)}

`}
${campaign.description ? `

${this.escapeHtml(campaign.description)}

` : ''} ${statsHTML.length > 0 ? `
${statsHTML.join('')}
` : ''}
`; // Insert the HTML highlightedContainer.innerHTML = highlightedHTML; // Make section visible but collapsed highlightedSection.style.display = 'grid'; // Force a reflow to ensure the initial state is applied const height = highlightedSection.offsetHeight; console.log('Campaign section initial height:', height); // Wait a bit longer before starting animation to ensure it's visible setTimeout(() => { console.log('Starting campaign expansion animation...'); highlightedSection.classList.add('show'); // Add animation to the container after expansion starts setTimeout(() => { const container = highlightedContainer.querySelector('.highlighted-campaign-container'); if (container) { console.log('Adding visible class to container...'); container.classList.add('visible', 'fade-in-smooth'); } }, 300); }, 100); } updateRepresentatives(representatives) { } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } // Message Display System class MessageDisplay { constructor() { this.container = document.getElementById('message-display'); this.timeouts = new Map(); } show(message, type = 'info', duration = 5000) { // Clear existing timeout for this container if (this.timeouts.has(this.container)) { clearTimeout(this.timeouts.get(this.container)); } // Set message content and type this.container.innerHTML = message; this.container.className = `message-display ${type}`; this.container.style.display = 'block'; // Auto-hide after duration const timeout = setTimeout(() => { this.hide(); }, duration); this.timeouts.set(this.container, timeout); // Add click to dismiss this.container.style.cursor = 'pointer'; this.container.onclick = () => this.hide(); } hide() { this.container.style.display = 'none'; this.container.onclick = null; // Clear timeout if (this.timeouts.has(this.container)) { clearTimeout(this.timeouts.get(this.container)); this.timeouts.delete(this.container); } } } // Utility functions const Utils = { // Format postal code consistently formatPostalCode(postalCode) { const cleaned = postalCode.replace(/\s/g, '').toUpperCase(); if (cleaned.length === 6) { return `${cleaned.slice(0, 3)} ${cleaned.slice(3)}`; } return cleaned; }, // Sanitize text input sanitizeText(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }, // Debounce function for input handling debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, // Check if we're on mobile isMobile() { return window.innerWidth <= 768; }, // Format date for display formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('en-CA', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } }; // Make utils globally available window.Utils = Utils; // Initialize app when DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.mainApp = new MainApp(); // Initialize campaigns grid if (typeof CampaignsGrid !== 'undefined') { window.campaignsGrid = new CampaignsGrid(); window.campaignsGrid.init(); } // Add some basic accessibility improvements document.addEventListener('keydown', (e) => { // Allow Escape to close modals (handled in individual modules) // Add tab navigation improvements if needed }); // Add responsive behavior window.addEventListener('resize', Utils.debounce(() => { // Handle responsive layout changes if needed const isMobile = Utils.isMobile(); document.body.classList.toggle('mobile', isMobile); }, 250)); // Initial mobile class document.body.classList.toggle('mobile', Utils.isMobile()); });