// Response Wall JavaScript let currentCampaignSlug = null; let currentOffset = 0; let currentSort = 'recent'; let currentLevel = ''; const LIMIT = 20; let loadedRepresentatives = []; // Initialize document.addEventListener('DOMContentLoaded', () => { console.log('Response Wall: Initializing...'); // Get campaign slug from URL if present const urlParams = new URLSearchParams(window.location.search); currentCampaignSlug = urlParams.get('campaign'); console.log('Campaign slug:', currentCampaignSlug); if (!currentCampaignSlug) { showError('No campaign specified'); return; } // Load initial data loadResponseStats(); loadResponses(true); // Set up event listeners document.getElementById('sort-select').addEventListener('change', (e) => { currentSort = e.target.value; loadResponses(true); }); document.getElementById('level-filter').addEventListener('change', (e) => { currentLevel = e.target.value; loadResponses(true); }); const submitBtn = document.getElementById('submit-response-btn'); console.log('Submit button found:', !!submitBtn); if (submitBtn) { submitBtn.addEventListener('click', () => { console.log('Submit button clicked'); openSubmitModal(); }); } // Use event delegation for empty state button since it's dynamically shown document.addEventListener('click', (e) => { if (e.target.id === 'empty-state-submit-btn') { console.log('Empty state button clicked'); openSubmitModal(); } }); const modalCloseBtn = document.getElementById('modal-close-btn'); if (modalCloseBtn) { modalCloseBtn.addEventListener('click', closeSubmitModal); } const cancelBtn = document.getElementById('cancel-submit-btn'); if (cancelBtn) { cancelBtn.addEventListener('click', closeSubmitModal); } const loadMoreBtn = document.getElementById('load-more-btn'); if (loadMoreBtn) { loadMoreBtn.addEventListener('click', loadMoreResponses); } const form = document.getElementById('submit-response-form'); if (form) { form.addEventListener('submit', handleSubmitResponse); } // Postal code lookup button const lookupBtn = document.getElementById('lookup-rep-btn'); if (lookupBtn) { lookupBtn.addEventListener('click', handlePostalLookup); } // Postal code input formatting const postalInput = document.getElementById('modal-postal-code'); if (postalInput) { postalInput.addEventListener('input', formatPostalCodeInput); postalInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); handlePostalLookup(); } }); } // Representative selection const repSelect = document.getElementById('rep-select'); if (repSelect) { repSelect.addEventListener('change', handleRepresentativeSelect); } console.log('Response Wall: Initialization complete'); }); // Postal Code Lookup Functions function formatPostalCodeInput(e) { let value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, ''); // Format as A1A 1A1 if (value.length > 3) { value = value.slice(0, 3) + ' ' + value.slice(3, 6); } e.target.value = value; } function validatePostalCode(postalCode) { const cleaned = postalCode.replace(/\s/g, ''); // Check format: Letter-Number-Letter Number-Letter-Number const regex = /^[A-Z]\d[A-Z]\d[A-Z]\d$/; if (!regex.test(cleaned)) { return { valid: false, message: 'Please enter a valid postal code format (A1A 1A1)' }; } // Check if it's an Alberta postal code (starts with T) if (!cleaned.startsWith('T')) { return { valid: false, message: 'This tool is designed for Alberta postal codes only (starting with T)' }; } return { valid: true }; } async function handlePostalLookup() { const postalInput = document.getElementById('modal-postal-code'); const postalCode = postalInput.value.trim(); if (!postalCode) { showError('Please enter a postal code'); return; } const validation = validatePostalCode(postalCode); if (!validation.valid) { showError(validation.message); return; } const lookupBtn = document.getElementById('lookup-rep-btn'); lookupBtn.disabled = true; lookupBtn.textContent = '🔄 Searching...'; try { const response = await window.apiClient.getRepresentativesByPostalCode(postalCode); const data = response.data || response; loadedRepresentatives = data.representatives || []; if (loadedRepresentatives.length === 0) { showError('No representatives found for this postal code'); document.getElementById('rep-select-group').style.display = 'none'; } else { displayRepresentativeOptions(loadedRepresentatives); showSuccess(`Found ${loadedRepresentatives.length} representatives`); } } catch (error) { console.error('Postal lookup failed:', error); showError('Failed to lookup representatives: ' + error.message); } finally { lookupBtn.disabled = false; lookupBtn.textContent = '🔍 Search'; } } function displayRepresentativeOptions(representatives) { const repSelect = document.getElementById('rep-select'); const repSelectGroup = document.getElementById('rep-select-group'); // Clear existing options repSelect.innerHTML = ''; // Add representatives as options representatives.forEach((rep, index) => { const option = document.createElement('option'); option.value = index; // Format display text let displayText = rep.name; if (rep.district_name) { displayText += ` - ${rep.district_name}`; } if (rep.party_name) { displayText += ` (${rep.party_name})`; } displayText += ` [${rep.elected_office || 'Representative'}]`; option.textContent = displayText; repSelect.appendChild(option); }); // Show the select group repSelectGroup.style.display = 'block'; } function handleRepresentativeSelect(e) { const selectedIndex = e.target.value; if (selectedIndex === '') return; const rep = loadedRepresentatives[selectedIndex]; if (!rep) return; // Auto-fill form fields document.getElementById('representative-name').value = rep.name || ''; document.getElementById('representative-title').value = rep.elected_office || ''; // Set government level based on elected office const level = determineGovernmentLevel(rep.elected_office); document.getElementById('representative-level').value = level; // Store email for verification option if (rep.email) { // Handle email being either string or array const emailValue = Array.isArray(rep.email) ? rep.email[0] : rep.email; document.getElementById('representative-email').value = emailValue; // Enable verification checkbox if we have an email const verificationCheckbox = document.getElementById('send-verification'); verificationCheckbox.disabled = false; } else { document.getElementById('representative-email').value = ''; // Disable verification checkbox if no email const verificationCheckbox = document.getElementById('send-verification'); verificationCheckbox.disabled = true; verificationCheckbox.checked = false; } showSuccess('Representative details filled. Please complete the rest of the form.'); } function determineGovernmentLevel(electedOffice) { if (!electedOffice) return ''; const office = electedOffice.toLowerCase(); if (office.includes('mp') || office.includes('member of parliament')) { return 'Federal'; } else if (office.includes('mla') || office.includes('member of the legislative assembly')) { return 'Provincial'; } else if (office.includes('councillor') || office.includes('councilor') || office.includes('mayor')) { return 'Municipal'; } else if (office.includes('trustee') || office.includes('school board')) { return 'School Board'; } return ''; } // Load response statistics async function loadResponseStats() { try { const response = await fetch(`/api/campaigns/${currentCampaignSlug}/response-stats`); const data = await response.json(); if (data.success) { document.getElementById('stat-total-responses').textContent = data.stats.totalResponses; document.getElementById('stat-verified').textContent = data.stats.verifiedResponses; document.getElementById('stat-upvotes').textContent = data.stats.totalUpvotes; document.getElementById('stats-banner').style.display = 'flex'; } } catch (error) { console.error('Error loading stats:', error); } } // Load responses async function loadResponses(reset = false) { if (reset) { currentOffset = 0; document.getElementById('responses-container').innerHTML = ''; } showLoading(true); try { const params = new URLSearchParams({ sort: currentSort, level: currentLevel, offset: currentOffset, limit: LIMIT }); const response = await fetch(`/api/campaigns/${currentCampaignSlug}/responses?${params}`); const data = await response.json(); showLoading(false); if (data.success && data.responses.length > 0) { renderResponses(data.responses); // Show/hide load more button if (data.pagination.hasMore) { document.getElementById('load-more-container').style.display = 'block'; } else { document.getElementById('load-more-container').style.display = 'none'; } } else if (reset) { showEmptyState(); } } catch (error) { showLoading(false); showError('Failed to load responses'); console.error('Error loading responses:', error); } } // Render responses function renderResponses(responses) { const container = document.getElementById('responses-container'); responses.forEach(response => { const card = createResponseCard(response); container.appendChild(card); }); } // Create response card element function createResponseCard(response) { const card = document.createElement('div'); card.className = 'response-card'; card.dataset.responseId = response.id; const createdDate = new Date(response.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); let badges = `${escapeHtml(response.representative_level)}`; badges += `${escapeHtml(response.response_type)}`; if (response.is_verified) { badges = `✓ Verified` + badges; } let submittedBy = 'Anonymous'; if (!response.is_anonymous && response.submitted_by_name) { submittedBy = escapeHtml(response.submitted_by_name); } let userCommentHtml = ''; if (response.user_comment) { userCommentHtml = `