// User Dashboard JavaScript class UserDashboard { constructor() { this.user = null; this.campaigns = []; this.analytics = {}; this.authManager = null; this.currentCampaign = null; // Track campaign being edited } async init() { // Check authentication first if (typeof authManager !== 'undefined') { this.authManager = authManager; const isAuth = await this.authManager.checkSession(); if (!isAuth) { window.location.href = '/login.html'; return; } this.user = this.authManager.user; this.setupUserInterface(); } else { // Fallback if authManager not loaded window.location.href = '/login.html'; return; } this.setupEventListeners(); this.loadUserCampaigns(); this.loadAnalytics(); // Check for pending campaign from email verification this.checkPendingCampaign(); } setupUserInterface() { if (!this.user) return; // Update user info display const userNameEl = document.getElementById('user-name'); const userEmailEl = document.getElementById('user-email'); const userRoleBadge = document.getElementById('user-role-badge'); if (userNameEl) userNameEl.textContent = this.user.name || this.user.email; if (userEmailEl) userEmailEl.textContent = this.user.email; if (userRoleBadge) { const userType = this.user.userType || 'user'; userRoleBadge.className = `user-badge ${userType}`; userRoleBadge.textContent = userType === 'admin' ? 'Administrator' : userType === 'temp' ? 'Temporary User' : 'User'; } // Update account form this.populateAccountForm(); // Show admin link if user is admin const createBtn = document.getElementById('create-campaign-btn'); if (createBtn && this.user.isAdmin) { createBtn.textContent = 'Admin Panel'; } else if (createBtn) { createBtn.style.display = 'none'; } } populateAccountForm() { if (!this.user) return; const nameInput = document.getElementById('account-name'); const emailInput = document.getElementById('account-email'); const phoneInput = document.getElementById('account-phone'); const roleInput = document.getElementById('account-role'); const expiresInput = document.getElementById('account-expires'); const expirationGroup = document.getElementById('account-expiration'); if (nameInput) nameInput.value = this.user.name || ''; if (emailInput) emailInput.value = this.user.email || ''; if (phoneInput) phoneInput.value = this.user.phone || ''; if (roleInput) { const roleText = this.user.isAdmin ? 'Administrator' : this.user.userType === 'temp' ? 'Temporary User' : 'Standard User'; roleInput.value = roleText; } // Show expiration info for temp users if (this.user.userType === 'temp' && expiresInput && expirationGroup) { if (this.user.expiresAt) { expiresInput.value = new Date(this.user.expiresAt).toLocaleDateString(); expirationGroup.style.display = 'block'; } } } setupEventListeners() { // Tab navigation document.querySelectorAll('.nav-btn').forEach(btn => { btn.addEventListener('click', (e) => { const tab = e.target.dataset.tab; this.switchTab(tab); }); }); // Logout button const logoutBtn = document.getElementById('logout-btn'); if (logoutBtn) { logoutBtn.addEventListener('click', () => { this.authManager.logout(); }); } // Form submissions const createForm = document.getElementById('create-campaign-form'); if (createForm) { createForm.addEventListener('submit', (e) => { this.handleCreateCampaign(e); }); } const editForm = document.getElementById('edit-campaign-form'); if (editForm) { editForm.addEventListener('submit', (e) => { this.handleUpdateCampaign(e); }); } // Response filter changes const responseCampaignFilter = document.getElementById('responses-campaign-filter'); const responseStatusFilter = document.getElementById('responses-status-filter'); if (responseCampaignFilter) { responseCampaignFilter.addEventListener('change', () => { this.loadResponses(); }); } if (responseStatusFilter) { responseStatusFilter.addEventListener('change', () => { this.loadResponses(); }); } // Campaign actions using event delegation document.addEventListener('click', (e) => { if (e.target.matches('[data-action="view-campaign"]')) { this.viewCampaign(e.target.dataset.campaignSlug); } if (e.target.matches('[data-action="edit-campaign"]')) { this.editCampaign(e.target.dataset.campaignId); } if (e.target.matches('[data-action="view-analytics"]')) { this.viewCampaignAnalytics(e.target.dataset.campaignId); } if (e.target.matches('[data-action="cancel-create"]')) { this.switchTab('campaigns'); } if (e.target.matches('[data-action="cancel-edit"]')) { this.switchTab('campaigns'); } if (e.target.matches('[data-action="go-to-create"]')) { this.switchTab('create'); } // Response moderation actions if (e.target.matches('[data-action="approve-response"]')) { this.moderateResponse(e.target.dataset.responseId, 'approved'); } if (e.target.matches('[data-action="reject-response"]')) { this.moderateResponse(e.target.dataset.responseId, 'rejected'); } if (e.target.matches('[data-action="pending-response"]')) { this.moderateResponse(e.target.dataset.responseId, 'pending'); } }); // Setup campaign selector dropdowns this.setupCampaignSelectors(); } setupCampaignSelectors() { // Setup Create Campaign Selector const createSelector = document.getElementById('create-campaign-selector'); const createDropdown = document.getElementById('create-dropdown-menu'); if (createSelector && createDropdown) { this.setupDropdown(createSelector, createDropdown, 'create'); } // Setup Edit Campaign Selector const editSelector = document.getElementById('edit-campaign-selector'); const editDropdown = document.getElementById('edit-dropdown-menu'); if (editSelector && editDropdown) { this.setupDropdown(editSelector, editDropdown, 'edit'); } } setupDropdown(input, dropdown, type) { // Show dropdown on focus input.addEventListener('focus', () => { this.populateDropdown(dropdown, type); dropdown.classList.add('show'); }); // Hide dropdown when clicking outside document.addEventListener('click', (e) => { if (!input.contains(e.target) && !dropdown.contains(e.target)) { dropdown.classList.remove('show'); } }); // Filter campaigns on input input.addEventListener('input', () => { this.filterDropdown(input, dropdown, type); }); // Handle dropdown item selection dropdown.addEventListener('click', (e) => { if (e.target.classList.contains('dropdown-item')) { const campaignId = e.target.dataset.campaignId; const campaignTitle = e.target.textContent; input.value = campaignTitle; dropdown.classList.remove('show'); if (type === 'create' && campaignId !== 'new') { this.populateCreateFormFromCampaign(campaignId); } else if (type === 'edit' && campaignId) { this.loadCampaignForEdit(campaignId); } else if (type === 'create' && campaignId === 'new') { this.clearCreateForm(); } } }); } populateDropdown(dropdown, type) { dropdown.innerHTML = ''; if (type === 'create') { dropdown.innerHTML = ''; } else { dropdown.innerHTML = ''; } if (this.campaigns && this.campaigns.length > 0) { // Filter campaigns based on what user can do const availableCampaigns = type === 'edit' ? this.campaigns.filter(campaign => this.canEditCampaign(campaign)) : this.campaigns; availableCampaigns.forEach(campaign => { const item = document.createElement('div'); item.className = 'dropdown-item'; item.dataset.campaignId = campaign.id; item.textContent = `${campaign.title} (${campaign.status})`; dropdown.appendChild(item); }); if (type === 'edit' && availableCampaigns.length === 0) { const noResults = document.createElement('div'); noResults.className = 'dropdown-item no-results'; noResults.textContent = 'No editable campaigns found'; dropdown.appendChild(noResults); } } else { const noResults = document.createElement('div'); noResults.className = 'dropdown-item no-results'; noResults.textContent = 'No campaigns found'; dropdown.appendChild(noResults); } } filterDropdown(input, dropdown, type) { const searchTerm = input.value.toLowerCase(); // Re-populate the dropdown to ensure we have the right campaigns this.populateDropdown(dropdown, type); const items = dropdown.querySelectorAll('.dropdown-item:not(.no-results)'); let hasVisibleItems = false; items.forEach(item => { if (item.dataset.campaignId === 'new' || item.dataset.campaignId === '') { // Always show default items item.style.display = 'block'; hasVisibleItems = true; } else { const text = item.textContent.toLowerCase(); if (text.includes(searchTerm)) { item.style.display = 'block'; hasVisibleItems = true; } else { item.style.display = 'none'; } } }); // Show/hide no results message let noResultsItem = dropdown.querySelector('.no-results'); if (!hasVisibleItems && searchTerm) { if (!noResultsItem) { noResultsItem = document.createElement('div'); noResultsItem.className = 'dropdown-item no-results'; dropdown.appendChild(noResultsItem); } noResultsItem.textContent = 'No campaigns found'; noResultsItem.style.display = 'block'; } else if (noResultsItem && searchTerm) { noResultsItem.style.display = 'none'; } dropdown.classList.add('show'); } populateCreateFormFromCampaign(campaignId) { const campaign = this.campaigns.find(c => c.id === campaignId); if (!campaign) return; // Populate form fields with campaign data as template document.getElementById('create-title').value = `Copy of ${campaign.title}`; document.getElementById('create-description').value = campaign.description || ''; document.getElementById('create-email-subject').value = campaign.email_subject || ''; document.getElementById('create-email-body').value = campaign.email_body || ''; document.getElementById('create-call-to-action').value = campaign.call_to_action || ''; // Set checkboxes document.getElementById('create-allow-smtp').checked = campaign.allow_smtp_email !== false; document.getElementById('create-allow-mailto').checked = campaign.allow_mailto_link !== false; document.getElementById('create-collect-info').checked = campaign.collect_user_info !== false; document.getElementById('create-show-count').checked = campaign.show_email_count !== false; document.getElementById('create-allow-editing').checked = campaign.allow_email_editing === true; // Set government levels const targetLevels = campaign.target_government_levels || []; document.querySelectorAll('input[name="target_government_levels"]').forEach(checkbox => { checkbox.checked = targetLevels.includes(checkbox.value); }); } clearCreateForm() { // Clear all form fields document.getElementById('create-title').value = ''; document.getElementById('create-description').value = ''; document.getElementById('create-email-subject').value = ''; document.getElementById('create-email-body').value = ''; document.getElementById('create-call-to-action').value = ''; // Reset checkboxes to defaults document.getElementById('create-allow-smtp').checked = true; document.getElementById('create-allow-mailto').checked = true; document.getElementById('create-collect-info').checked = true; document.getElementById('create-show-count').checked = true; document.getElementById('create-allow-editing').checked = false; // Reset government levels to defaults document.querySelectorAll('input[name="target_government_levels"]').forEach(checkbox => { checkbox.checked = ['Federal', 'Provincial', 'Municipal'].includes(checkbox.value); }); } async loadCampaignForEdit(campaignId) { // Find campaign in already loaded campaigns array const campaign = this.campaigns.find(c => String(c.id) === String(campaignId)); if (!campaign) { this.showMessage('Campaign not found', 'error'); return; } if (!this.canEditCampaign(campaign)) { this.showMessage('You do not have permission to edit this campaign', 'error'); return; } this.currentCampaign = campaign; this.populateEditForm(); this.switchTab('edit'); } switchTab(tabName) { // Hide all tabs document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.remove('active'); }); // Remove active class from nav buttons document.querySelectorAll('.nav-btn').forEach(btn => { btn.classList.remove('active'); }); // Show selected tab const targetTab = document.getElementById(`${tabName}-tab`); if (targetTab) { targetTab.classList.add('active'); } // Update nav button const targetNavBtn = document.querySelector(`[data-tab="${tabName}"]`); if (targetNavBtn) { targetNavBtn.classList.add('active'); } // Load data for specific tabs if (tabName === 'campaigns') { this.loadUserCampaigns(); } else if (tabName === 'analytics') { this.loadAnalytics(); } else if (tabName === 'responses') { this.loadResponses(); } } checkPendingCampaign() { // Check for pending campaign data from email verification const pendingCampaignData = sessionStorage.getItem('pendingCampaign'); if (pendingCampaignData) { try { const campaignData = JSON.parse(pendingCampaignData); // Clear the session storage sessionStorage.removeItem('pendingCampaign'); // Pre-populate the create campaign form this.populateCampaignFormFromEmail(campaignData); // Switch to create tab this.switchTab('create'); // Show helpful message this.showMessage( 'πŸŽ‰ Your email content has been loaded! Please add a campaign title and description, then publish your campaign.', 'success', 10000 ); // Show additional info for new users if (this.user && !this.user.lastLogin) { setTimeout(() => { this.showMessage( 'Tip: You can update your password anytime in the Account Settings tab.', 'info', 8000 ); }, 2000); } } catch (error) { console.error('Error loading pending campaign:', error); } } } populateCampaignFormFromEmail(campaignData) { // Pre-fill form fields with email data const titleField = document.getElementById('create-title'); const descriptionField = document.getElementById('create-description'); const subjectField = document.getElementById('create-email-subject'); const bodyField = document.getElementById('create-email-body'); // Auto-generate title from subject if (titleField && campaignData.subject) { const suggestedTitle = campaignData.subject.length > 50 ? campaignData.subject.substring(0, 47) + '...' : campaignData.subject; titleField.value = suggestedTitle; } // Add helpful description template if (descriptionField && campaignData.postalCode) { descriptionField.value = `Join others from ${campaignData.postalCode} and across the region in sending this important message to our representatives.`; } // Fill in email content if (subjectField && campaignData.subject) { subjectField.value = campaignData.subject; } if (bodyField && campaignData.message) { bodyField.value = campaignData.message; } // Add a subtle highlight to show pre-filled fields [titleField, descriptionField, subjectField, bodyField].forEach(field => { if (field && field.value) { field.style.backgroundColor = '#fffacd'; // Light yellow // Remove highlight on focus field.addEventListener('focus', function() { this.style.backgroundColor = ''; }, { once: true }); } }); // Scroll to top of form const createTab = document.getElementById('create-tab'); if (createTab) { createTab.scrollTop = 0; } } async loadUserCampaigns() { const loadingDiv = document.getElementById('campaigns-loading'); const listDiv = document.getElementById('campaigns-list'); if (loadingDiv) loadingDiv.classList.remove('hidden'); if (listDiv) listDiv.innerHTML = ''; try { const endpoint = this.user.isAdmin ? '/admin/campaigns' : '/campaigns'; const response = await window.apiClient.get(endpoint); if (response.success) { const campaigns = Array.isArray(response.campaigns) ? response.campaigns : []; this.campaigns = campaigns .map(campaign => this.normalizeCampaignFromApi(campaign)) .filter(Boolean); this.renderCampaignList(); } else { throw new Error(response.error || 'Failed to load campaigns'); } } catch (error) { console.error('Load campaigns error:', error); this.showMessage('Failed to load campaigns: ' + error.message, 'error'); } finally { if (loadingDiv) loadingDiv.classList.add('hidden'); } } normalizeCampaignFromApi(campaign) { if (!campaign) return null; const normalized = { ...campaign, id: campaign.id ?? campaign.ID ?? campaign.Id ?? null, created_by_user_id: campaign.created_by_user_id ?? campaign['Created By User ID'] ?? null, created_by_user_email: campaign.created_by_user_email ?? campaign['Created By User Email'] ?? null, created_by_user_name: campaign.created_by_user_name ?? campaign['Created By User Name'] ?? null }; // Ensure IDs are comparable as strings if (normalized.created_by_user_id !== undefined && normalized.created_by_user_id !== null) { normalized.created_by_user_id = String(normalized.created_by_user_id); } if (typeof normalized.status === 'string') { normalized.status = normalized.status.toLowerCase(); } ['allow_smtp_email', 'allow_mailto_link', 'collect_user_info', 'show_email_count', 'allow_email_editing'].forEach(key => { if (normalized[key] !== undefined) { normalized[key] = normalized[key] === true || normalized[key] === 'true' || normalized[key] === 1 || normalized[key] === '1'; } }); // Normalize target government levels to an array if (Array.isArray(normalized.target_government_levels)) { normalized.target_government_levels = normalized.target_government_levels; } else if (typeof normalized.target_government_levels === 'string' && normalized.target_government_levels.length > 0) { normalized.target_government_levels = normalized.target_government_levels.split(',').map(level => level.trim()).filter(Boolean); } else { normalized.target_government_levels = []; } return normalized; } isCampaignOwner(campaign) { if (!campaign || !this.user) return false; const userId = this.user.id != null ? String(this.user.id) : (this.user.userId != null ? String(this.user.userId) : null); const userEmail = this.user.email ? String(this.user.email).toLowerCase() : null; const campaignOwnerId = campaign.created_by_user_id != null ? String(campaign.created_by_user_id) : null; const campaignOwnerEmail = campaign.created_by_user_email ? String(campaign.created_by_user_email).toLowerCase() : null; return ( (userId && campaignOwnerId && userId === campaignOwnerId) || (userEmail && campaignOwnerEmail && userEmail === campaignOwnerEmail) ); } canEditCampaign(campaign) { if (this.user?.isAdmin) { return true; } return this.isCampaignOwner(campaign); } renderCampaignList() { const listDiv = document.getElementById('campaigns-list'); if (!listDiv) return; if (this.campaigns.length === 0) { listDiv.innerHTML = `

No campaigns yet

You haven't created any campaigns yet.

`; return; } listDiv.innerHTML = this.campaigns.map(campaign => `
${campaign.cover_photo ? `

${this.escapeHtml(campaign.title)}

${campaign.status}
` : `

${this.escapeHtml(campaign.title)}

${campaign.status}
`}

Slug: /campaign/${campaign.slug}

Emails Sent: ${campaign.emailCount || 0}

Created: ${this.formatDate(campaign.created_at)}

${campaign.description ? `

Description: ${this.escapeHtml(campaign.description)}

` : ''}
${this.canEditCampaign(campaign) ? ` ` : ''}
`).join(''); } async loadAnalytics() { const loadingDiv = document.getElementById('analytics-loading'); if (loadingDiv) loadingDiv.classList.remove('hidden'); try { // Calculate analytics from campaigns data const totalCampaigns = this.campaigns.length; const activeCampaigns = this.campaigns.filter(c => c.status === 'active').length; const totalEmails = this.campaigns.reduce((sum, c) => sum + (c.emailCount || 0), 0); // Update analytics display const totalCampaignsEl = document.getElementById('total-campaigns'); const activeCampaignsEl = document.getElementById('active-campaigns'); const totalEmailsEl = document.getElementById('total-emails'); const totalUsersReachedEl = document.getElementById('total-users-reached'); if (totalCampaignsEl) totalCampaignsEl.textContent = totalCampaigns; if (activeCampaignsEl) activeCampaignsEl.textContent = activeCampaigns; if (totalEmailsEl) totalEmailsEl.textContent = totalEmails; if (totalUsersReachedEl) totalUsersReachedEl.textContent = Math.floor(totalEmails * 0.8); // Estimate } catch (error) { console.error('Analytics error:', error); this.showMessage('Failed to load analytics: ' + error.message, 'error'); } finally { if (loadingDiv) loadingDiv.classList.add('hidden'); } } viewCampaign(slug) { window.open(`/campaign/${slug}`, '_blank'); } editCampaign(campaignId) { const campaign = this.campaigns.find(c => String(c.id) === String(campaignId)); if (!campaign) { this.showMessage('Campaign not found', 'error'); return; } if (!this.canEditCampaign(campaign)) { this.showMessage('You do not have permission to edit this campaign', 'error'); return; } this.currentCampaign = campaign; this.switchTab('edit'); this.populateEditForm(); } async viewCampaignAnalytics(campaignId) { try { const endpoint = this.user.isAdmin ? `/admin/campaigns/${campaignId}/analytics` : `/campaigns/${campaignId}/analytics`; const response = await window.apiClient.get(endpoint); if (response.success) { this.showAnalyticsModal(response.analytics); } else { throw new Error(response.error || 'Failed to load campaign analytics'); } } catch (error) { console.error('Campaign analytics error:', error); this.showMessage('Failed to load campaign analytics: ' + error.message, 'error'); } } showAnalyticsModal(analytics) { // Create a simple analytics modal const modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.innerHTML = ` `; document.body.appendChild(modal); // Close modal handlers modal.querySelector('.modal-close').addEventListener('click', () => { document.body.removeChild(modal); }); modal.addEventListener('click', (e) => { if (e.target === modal) { document.body.removeChild(modal); } }); } openEditModal(campaign) { const modal = document.createElement('div'); modal.className = 'modal-overlay'; const isAdmin = !!this.user?.isAdmin; modal.innerHTML = ` `; document.body.appendChild(modal); const form = modal.querySelector('#edit-campaign-form'); this.populateEditForm(form, campaign); form.addEventListener('submit', (event) => this.handleEditCampaignSubmit(event, campaign.id, modal)); const closeModal = () => this.closeModal(modal); modal.querySelector('.modal-close').addEventListener('click', closeModal); modal.addEventListener('click', (event) => { if (event.target === modal) { closeModal(); } }); const cancelBtn = modal.querySelector('[data-action="cancel-modal"]'); if (cancelBtn) { cancelBtn.addEventListener('click', closeModal); } } populateEditForm(form, campaign) { if (!form || !campaign) return; // Populate basic fields form.querySelector('[name="title"]').value = campaign.title || ''; form.querySelector('[name="description"]').value = campaign.description || ''; form.querySelector('[name="email_subject"]').value = campaign.email_subject || ''; form.querySelector('[name="email_body"]').value = campaign.email_body || ''; form.querySelector('[name="call_to_action"]').value = campaign.call_to_action || ''; // Status select const statusSelect = form.querySelector('[name="status"]'); if (statusSelect) { statusSelect.value = campaign.status || 'draft'; } // Campaign settings checkboxes form.querySelector('[name="allow_smtp_email"]').checked = !!campaign.allow_smtp_email; form.querySelector('[name="allow_mailto_link"]').checked = !!campaign.allow_mailto_link; form.querySelector('[name="collect_user_info"]').checked = !!campaign.collect_user_info; form.querySelector('[name="show_email_count"]').checked = !!campaign.show_email_count; form.querySelector('[name="allow_email_editing"]').checked = !!campaign.allow_email_editing; // Target government levels const targetLevels = Array.isArray(campaign.target_government_levels) ? campaign.target_government_levels : (campaign.target_government_levels ? campaign.target_government_levels.split(',').map(l => l.trim()) : []); form.querySelectorAll('input[name="target_government_levels"]').forEach(checkbox => { checkbox.checked = targetLevels.includes(checkbox.value); }); } // New method for edit tab form population populateEditForm() { if (!this.currentCampaign) return; const form = document.getElementById('edit-campaign-form'); const campaign = this.currentCampaign; // Populate form fields form.querySelector('[name="title"]').value = campaign.title || ''; form.querySelector('[name="description"]').value = campaign.description || ''; form.querySelector('[name="email_subject"]').value = campaign.email_subject || ''; form.querySelector('[name="email_body"]').value = campaign.email_body || ''; form.querySelector('[name="call_to_action"]').value = campaign.call_to_action || ''; // Status select form.querySelector('[name="status"]').value = campaign.status || 'draft'; // Checkboxes form.querySelector('[name="allow_smtp_email"]').checked = !!campaign.allow_smtp_email; form.querySelector('[name="allow_mailto_link"]').checked = !!campaign.allow_mailto_link; form.querySelector('[name="collect_user_info"]').checked = !!campaign.collect_user_info; form.querySelector('[name="show_email_count"]').checked = !!campaign.show_email_count; form.querySelector('[name="allow_email_editing"]').checked = !!campaign.allow_email_editing; form.querySelector('[name="show_response_wall"]').checked = !!campaign.show_response_wall; // Government levels const targetLevels = Array.isArray(campaign.target_government_levels) ? campaign.target_government_levels : (campaign.target_government_levels ? campaign.target_government_levels.split(',').map(l => l.trim()) : []); form.querySelectorAll('[name="target_government_levels"]').forEach(checkbox => { checkbox.checked = targetLevels.includes(checkbox.value); }); } async handleUpdateCampaign(e) { e.preventDefault(); if (!this.currentCampaign) return; const formData = new FormData(e.target); const updates = { title: formData.get('title'), description: formData.get('description'), email_subject: formData.get('email_subject'), email_body: formData.get('email_body'), call_to_action: formData.get('call_to_action'), status: formData.get('status'), // Allow all users to change status for campaigns they own allow_smtp_email: formData.get('allow_smtp_email') === 'on', allow_mailto_link: formData.get('allow_mailto_link') === 'on', collect_user_info: formData.get('collect_user_info') === 'on', show_email_count: formData.get('show_email_count') === 'on', allow_email_editing: formData.get('allow_email_editing') === 'on', show_response_wall: formData.get('show_response_wall') === 'on', target_government_levels: Array.from(formData.getAll('target_government_levels')) }; console.log('Updating campaign with data:', updates); console.log('Current campaign:', this.currentCampaign); console.log('User info:', this.user); try { const endpoint = this.user?.isAdmin ? `/admin/campaigns/${this.currentCampaign.id}` : `/campaigns/${this.currentCampaign.id}`; console.log('Using endpoint:', endpoint); const response = await window.apiClient.makeRequest(endpoint, { method: 'PUT', body: JSON.stringify(updates) }); console.log('Update response:', response); if (response.success) { this.showMessage('Campaign updated successfully!', 'success'); const updatedCampaign = this.normalizeCampaignFromApi(response.campaign); this.updateCampaignInState(updatedCampaign); this.renderCampaignList(); this.loadAnalytics(); this.switchTab('campaigns'); } else { throw new Error(response.error || 'Failed to update campaign'); } } catch (error) { console.error('Update campaign error:', error); const errorMsg = error?.data?.error || error.message || 'Failed to update campaign'; this.showMessage('Failed to update campaign: ' + errorMsg, 'error'); } } async handleEditCampaignSubmit(event, campaignId, modal) { event.preventDefault(); const form = event.target; const formData = new FormData(form); const updates = { title: formData.get('title'), description: formData.get('description'), email_subject: formData.get('email_subject'), email_body: formData.get('email_body'), call_to_action: formData.get('call_to_action'), status: this.user?.isAdmin ? (formData.get('status') || 'draft') : 'draft', allow_smtp_email: formData.get('allow_smtp_email') === 'on', allow_mailto_link: formData.get('allow_mailto_link') === 'on', collect_user_info: formData.get('collect_user_info') === 'on', show_email_count: formData.get('show_email_count') === 'on', allow_email_editing: formData.get('allow_email_editing') === 'on', target_government_levels: Array.from(formData.getAll('target_government_levels')) }; try { const endpoint = this.user?.isAdmin ? `/admin/campaigns/${campaignId}` : `/campaigns/${campaignId}`; const response = await window.apiClient.makeRequest(endpoint, { method: 'PUT', body: JSON.stringify(updates) }); if (response.success) { this.closeModal(modal); const updatedCampaign = this.normalizeCampaignFromApi(response.campaign); this.updateCampaignInState(updatedCampaign); this.renderCampaignList(); this.loadAnalytics(); this.showMessage('Campaign updated successfully!', 'success'); } else { throw new Error(response.error || 'Failed to update campaign'); } } catch (error) { console.error('Update campaign error:', error); const errorMsg = error?.data?.error || error.message || 'Failed to update campaign'; this.showMessage('Failed to update campaign: ' + errorMsg, 'error'); } } updateCampaignInState(updatedCampaign) { if (!updatedCampaign) return; const updatedId = updatedCampaign.id != null ? String(updatedCampaign.id) : null; const index = this.campaigns.findIndex(c => String(c.id) === updatedId); if (index >= 0) { this.campaigns[index] = this.normalizeCampaignFromApi({ ...this.campaigns[index], ...updatedCampaign }); } else { this.campaigns.unshift(this.normalizeCampaignFromApi(updatedCampaign)); } } closeModal(modal) { if (modal && modal.parentNode) { modal.parentNode.removeChild(modal); } } showMessage(message, type = 'info') { const container = document.getElementById('message-container'); if (container) { container.className = `message-${type}`; container.textContent = message; container.classList.remove('hidden'); setTimeout(() => { container.classList.add('hidden'); }, 5000); } } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } formatDate(dateString) { if (!dateString) return 'N/A'; try { return new Date(dateString).toLocaleDateString('en-CA', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch (error) { return dateString; } } // Response Moderation Methods async loadResponses() { const loadingDiv = document.getElementById('responses-loading'); const listDiv = document.getElementById('responses-list'); if (loadingDiv) loadingDiv.classList.remove('hidden'); try { // Populate campaign filter dropdown if not already done await this.populateResponseCampaignFilter(); // Get filter values const campaignSlug = document.getElementById('responses-campaign-filter')?.value || ''; const status = document.getElementById('responses-status-filter')?.value || 'all'; // Build query params const params = new URLSearchParams({ status: status, limit: 100 }); if (campaignSlug) { params.append('campaign_slug', campaignSlug); } const response = await window.apiClient.get(`/admin/responses?${params.toString()}`); if (response.success) { this.renderResponsesList(response.responses || []); } else { throw new Error(response.error || 'Failed to load responses'); } } catch (error) { console.error('Load responses error:', error); if (listDiv) { listDiv.innerHTML = `

Error loading responses

${this.escapeHtml(error.message)}

`; } } finally { if (loadingDiv) loadingDiv.classList.add('hidden'); } } async populateResponseCampaignFilter() { const filterSelect = document.getElementById('responses-campaign-filter'); if (!filterSelect || filterSelect.dataset.populated === 'true') return; // Use already loaded campaigns if (this.campaigns.length === 0) { await this.loadUserCampaigns(); } // Clear existing options except the first one filterSelect.innerHTML = ''; // Add campaign options this.campaigns.forEach(campaign => { const option = document.createElement('option'); option.value = campaign.slug; option.textContent = campaign.title; filterSelect.appendChild(option); }); filterSelect.dataset.populated = 'true'; } renderResponsesList(responses) { const listDiv = document.getElementById('responses-list'); if (!listDiv) return; if (responses.length === 0) { listDiv.innerHTML = `

No responses found

There are no responses matching your filters.

`; return; } listDiv.innerHTML = responses.map(response => this.renderResponseCard(response)).join(''); } renderResponseCard(response) { const statusLabel = { pending: '⏳ Pending', approved: 'βœ… Approved', rejected: '❌ Rejected' }[response.status] || response.status; const levelEmoji = { 'Federal': '🍁', 'Provincial': 'πŸ›οΈ', 'Municipal': 'πŸ™οΈ', 'School Board': 'πŸŽ“' }[response.representative_level] || 'πŸ“'; // Show appropriate action buttons based on status let actionButtons = ''; if (response.status === 'pending') { actionButtons = ` `; } else if (response.status === 'approved') { actionButtons = ` `; } else if (response.status === 'rejected') { actionButtons = ` `; } return `
${statusLabel}
Submitted ${this.formatDate(response.created_at)}

Campaign: ${this.escapeHtml(response.campaign_slug)}

${levelEmoji} Representative: ${this.escapeHtml(response.representative_name)}

${response.representative_title ? `

Title: ${this.escapeHtml(response.representative_title)}

` : ''}

Response Type: ${this.escapeHtml(response.response_type)}

${response.submitted_by_name ? `

Submitted By: ${this.escapeHtml(response.submitted_by_name)}

` : ''} ${response.submitted_by_email ? `

Email: ${this.escapeHtml(response.submitted_by_email)}

` : ''}

Response:

${this.escapeHtml(response.response_text)}

${response.user_comment ? `

Submitter Comment:

${this.escapeHtml(response.user_comment)}

` : ''} ${response.screenshot_url ? ` Response screenshot ` : ''}
${actionButtons} πŸ‘ ${response.upvote_count || 0} upvotes
`; } async moderateResponse(responseId, newStatus) { try { const response = await window.apiClient.patch(`/admin/responses/${responseId}/status`, { status: newStatus }); if (response.success) { this.showMessage(`Response ${newStatus} successfully!`, 'success'); // Reload responses to update the display await this.loadResponses(); } else { throw new Error(response.error || 'Failed to update response status'); } } catch (error) { console.error('Moderate response error:', error); this.showMessage('Failed to moderate response: ' + error.message, 'error'); } } // Campaign Creation Methods async handleCreateCampaign(e) { e.preventDefault(); const formData = new FormData(e.target); const campaignData = { title: formData.get('title'), description: formData.get('description'), email_subject: formData.get('email_subject'), email_body: formData.get('email_body'), call_to_action: formData.get('call_to_action'), status: formData.get('status'), allow_smtp_email: formData.get('allow_smtp_email') === 'on', allow_mailto_link: formData.get('allow_mailto_link') === 'on', collect_user_info: formData.get('collect_user_info') === 'on', show_email_count: formData.get('show_email_count') === 'on', allow_email_editing: formData.get('allow_email_editing') === 'on', show_response_wall: formData.get('show_response_wall') === 'on', target_government_levels: Array.from(formData.getAll('target_government_levels')) }; try { if (!this.user.isAdmin) { campaignData.status = 'draft'; } const endpoint = this.user.isAdmin ? '/admin/campaigns' : '/campaigns'; const response = await window.apiClient.post(endpoint, campaignData); if (response.success) { this.showMessage('Campaign created successfully!', 'success'); e.target.reset(); this.switchTab('campaigns'); // Reload campaigns to show the new one this.loadUserCampaigns(); } else { throw new Error(response.error || 'Failed to create campaign'); } } catch (error) { console.error('Create campaign error:', error); this.showMessage('Failed to create campaign: ' + error.message, 'error'); } } } // Initialize dashboard when DOM is loaded document.addEventListener('DOMContentLoaded', async () => { window.userDashboard = new UserDashboard(); await window.userDashboard.init(); });