// Admin Panel JavaScript class AdminPanel { constructor() { this.currentCampaign = null; this.campaigns = []; this.users = []; this.authManager = null; } async init() { console.log('AdminPanel init started'); // Check authentication first if (typeof authManager !== 'undefined') { this.authManager = authManager; const isAuth = await this.authManager.checkSession(); if (!isAuth || !this.authManager.user?.isAdmin) { window.location.href = '/login.html'; return; } this.setupUserInterface(); } else { // Fallback if authManager not loaded window.location.href = '/login.html'; return; } console.log('Setting up event listeners and form interactions'); this.setupEventListeners(); this.setupFormInteractions(); this.setupCustomRecipientsHandlers(); this.loadCampaigns(); console.log('AdminPanel init completed'); } setupUserInterface() { // Add user info to header const adminHeader = document.querySelector('.admin-header .admin-container'); if (adminHeader && this.authManager.user) { const userInfo = document.createElement('div'); userInfo.style.cssText = 'position: absolute; top: 1rem; right: 2rem; color: white; font-size: 0.9rem;'; userInfo.innerHTML = ` Welcome, ${this.authManager.user.name || this.authManager.user.email} `; adminHeader.style.position = 'relative'; adminHeader.appendChild(userInfo); // Add logout event listener document.getElementById('logout-btn').addEventListener('click', () => { this.authManager.logout(); }); } } setupEventListeners() { // Tab navigation document.querySelectorAll('.nav-btn').forEach(btn => { btn.addEventListener('click', (e) => { const tab = e.target.dataset.tab; this.switchTab(tab); }); }); // Form submissions document.getElementById('create-campaign-form').addEventListener('submit', (e) => { this.handleCreateCampaign(e); }); document.getElementById('edit-campaign-form').addEventListener('submit', (e) => { this.handleUpdateCampaign(e); }); document.getElementById('user-form').addEventListener('submit', (e) => { this.handleCreateUser(e); }); document.getElementById('email-form').addEventListener('submit', (e) => { this.handleEmailAllUsers(e); }); // Cancel buttons - using event delegation for proper handling document.addEventListener('click', (e) => { 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="create-user"]')) { this.showUserModal(); } if (e.target.matches('[data-action="close-user-modal"]')) { this.hideUserModal(); } if (e.target.matches('[data-action="close-email-modal"]')) { this.hideEmailModal(); } if (e.target.matches('[data-action="delete-user"]')) { this.deleteUser(e.target.dataset.userId); } if (e.target.matches('[data-action="send-login-details"]')) { this.sendLoginDetails(e.target.dataset.userId); } if (e.target.matches('[data-action="email-all-users"]')) { this.showEmailModal(); } }); // User type change handler const userTypeSelect = document.getElementById('user-type'); if (userTypeSelect) { userTypeSelect.addEventListener('change', (e) => { this.handleUserTypeChange(e.target.value); }); } // Response status filter const responseStatusSelect = document.getElementById('admin-response-status'); if (responseStatusSelect) { responseStatusSelect.addEventListener('change', () => { this.loadAdminResponses(); }); } // Response campaign filter const responseCampaignFilter = document.getElementById('admin-campaign-filter'); if (responseCampaignFilter) { responseCampaignFilter.addEventListener('change', () => { this.loadAdminResponses(); }); } } setupFormInteractions() { // Create campaign button const createBtn = document.querySelector('[data-action="create-campaign"]'); if (createBtn) { createBtn.addEventListener('click', () => this.switchTab('create')); } // Cancel buttons const cancelCreateBtn = document.querySelector('[data-action="cancel-create"]'); if (cancelCreateBtn) { cancelCreateBtn.addEventListener('click', () => this.switchTab('campaigns')); } const cancelEditBtn = document.querySelector('[data-action="cancel-edit"]'); if (cancelEditBtn) { cancelEditBtn.addEventListener('click', () => this.switchTab('campaigns')); } // Handle checkbox changes for government levels document.querySelectorAll('input[name="target_government_levels"]').forEach(checkbox => { checkbox.addEventListener('change', () => { this.updateGovernmentLevelsPreview(); }); }); // Handle settings toggles document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { checkbox.addEventListener('change', () => { this.handleSettingsChange(checkbox); }); }); // 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; console.log('Dropdown item selected:', { campaignId, campaignTitle, type }); input.value = campaignTitle; dropdown.classList.remove('show'); if (type === 'create' && campaignId !== 'new') { console.log('Calling populateCreateFormFromCampaign with ID:', campaignId); this.populateCreateFormFromCampaign(campaignId); } else if (type === 'edit' && campaignId) { this.loadCampaignForEdit(campaignId); } else if (type === 'create' && campaignId === 'new') { this.clearCreateForm(); } } }); } populateDropdown(dropdown, type) { console.log('populateDropdown called:', { type, campaignsCount: this.campaigns?.length }); dropdown.innerHTML = ''; if (type === 'create') { dropdown.innerHTML = ''; } else { dropdown.innerHTML = ''; } if (this.campaigns && this.campaigns.length > 0) { console.log('Adding campaigns to dropdown:', this.campaigns.map(c => ({ id: c.id, title: c.title }))); // Admin can edit all campaigns this.campaigns.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); }); } else { console.log('No campaigns available for dropdown'); 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'); } refreshDropdowns() { // Refresh create dropdown if it exists const createDropdown = document.getElementById('create-dropdown-menu'); if (createDropdown) { this.populateDropdown(createDropdown, 'create'); } // Refresh edit dropdown if it exists const editDropdown = document.getElementById('edit-dropdown-menu'); if (editDropdown) { this.populateDropdown(editDropdown, 'edit'); } } populateCreateFormFromCampaign(campaignId) { console.log('populateCreateFormFromCampaign called with ID:', campaignId); console.log('Available campaigns:', this.campaigns); const campaign = this.campaigns.find(c => String(c.id) === String(campaignId)); console.log('Found campaign:', campaign); if (!campaign) { console.error('Campaign not found for ID:', campaignId); console.error('Available campaign IDs:', this.campaigns?.map(c => c.id)); 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 || ''; document.getElementById('create-status').value = 'draft'; // Always set to draft for new campaigns // 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); }); console.log('Form populated successfully with campaign:', campaign.title); } 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 = ''; document.getElementById('create-status').value = 'draft'; // 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) { try { const response = await window.apiClient.get(`/admin/campaigns/${campaignId}`); if (response.success) { this.currentCampaign = response.campaign; this.populateEditForm(); this.switchTab('edit'); } else { throw new Error(response.error || 'Failed to load campaign'); } } catch (error) { console.error('Load campaign error:', error); this.showMessage('Failed to load campaign: ' + error.message, 'error'); } } 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'); } // Special handling for different tabs if (tabName === 'campaigns') { this.loadCampaigns(); } else if (tabName === 'create') { // Ensure campaigns are loaded for template selection if (!this.campaigns || this.campaigns.length === 0) { this.loadCampaigns(); } } else if (tabName === 'edit') { // Ensure campaigns are loaded for editing if (!this.campaigns || this.campaigns.length === 0) { this.loadCampaigns(); } if (this.currentCampaign) { this.populateEditForm(); } } else if (tabName === 'responses') { this.loadAdminResponses(); } else if (tabName === 'users') { this.loadUsers(); } // Refresh dropdowns when switching to create or edit tabs if (tabName === 'create' || tabName === 'edit') { setTimeout(() => { const createDropdown = document.getElementById('create-dropdown-menu'); const editDropdown = document.getElementById('edit-dropdown-menu'); if (tabName === 'create' && createDropdown) { this.populateDropdown(createDropdown, 'create'); } if (tabName === 'edit' && editDropdown) { this.populateDropdown(editDropdown, 'edit'); } }, 100); } } async loadCampaigns() { const loadingDiv = document.getElementById('campaigns-loading'); const listDiv = document.getElementById('campaigns-list'); loadingDiv.classList.remove('hidden'); listDiv.innerHTML = ''; try { const response = await window.apiClient.get('/admin/campaigns'); if (response.success) { this.campaigns = response.campaigns; this.renderCampaignList(); this.refreshDropdowns(); // Refresh dropdowns when campaigns are loaded } 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 { loadingDiv.classList.add('hidden'); } } renderCampaignList() { const listDiv = document.getElementById('campaigns-list'); if (this.campaigns.length === 0) { listDiv.innerHTML = `

No campaigns yet

Create your first campaign to get started.

`; 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}

Email Count: ${campaign.emailCount || 0}

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

${campaign.created_by_user_name || campaign.created_by_user_email ? `

Created By: ${this.escapeHtml(campaign.created_by_user_name || campaign.created_by_user_email)}

` : ''}
View Public Page
`).join(''); // Attach event listeners to campaign actions this.attachCampaignActionListeners(); } attachCampaignActionListeners() { // Edit campaign buttons document.querySelectorAll('[data-action="edit-campaign"]').forEach(btn => { btn.addEventListener('click', (e) => { const campaignId = parseInt(e.target.dataset.campaignId); this.editCampaign(campaignId); }); }); // Delete campaign buttons document.querySelectorAll('[data-action="delete-campaign"]').forEach(btn => { btn.addEventListener('click', (e) => { const campaignId = parseInt(e.target.dataset.campaignId); this.deleteCampaign(campaignId); }); }); // Analytics buttons document.querySelectorAll('[data-action="view-analytics"]').forEach(btn => { btn.addEventListener('click', (e) => { const campaignId = parseInt(e.target.dataset.campaignId); this.viewAnalytics(campaignId); }); }); } async handleCreateCampaign(e) { e.preventDefault(); const formData = new FormData(e.target); // Convert checkboxes to boolean values const campaignFormData = new FormData(); campaignFormData.append('title', formData.get('title')); campaignFormData.append('description', formData.get('description') || ''); campaignFormData.append('email_subject', formData.get('email_subject')); campaignFormData.append('email_body', formData.get('email_body')); campaignFormData.append('call_to_action', formData.get('call_to_action') || ''); campaignFormData.append('status', formData.get('status')); campaignFormData.append('allow_smtp_email', formData.get('allow_smtp_email') === 'on'); campaignFormData.append('allow_mailto_link', formData.get('allow_mailto_link') === 'on'); campaignFormData.append('collect_user_info', formData.get('collect_user_info') === 'on'); campaignFormData.append('show_email_count', formData.get('show_email_count') === 'on'); campaignFormData.append('allow_email_editing', formData.get('allow_email_editing') === 'on'); campaignFormData.append('show_response_wall', formData.get('show_response_wall') === 'on'); campaignFormData.append('allow_custom_recipients', formData.get('allow_custom_recipients') === 'on'); // Handle target_government_levels array const targetLevels = Array.from(formData.getAll('target_government_levels')); targetLevels.forEach(level => { campaignFormData.append('target_government_levels', level); }); // Handle cover photo file upload const coverPhotoFile = formData.get('cover_photo'); if (coverPhotoFile && coverPhotoFile.size > 0) { campaignFormData.append('cover_photo', coverPhotoFile); } try { const response = await window.apiClient.postFormData('/admin/campaigns', campaignFormData); if (response.success) { this.showMessage('Campaign created successfully!', 'success'); e.target.reset(); this.switchTab('campaigns'); } 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'); } } editCampaign(campaignId) { this.currentCampaign = this.campaigns.find(c => c.id === campaignId); if (this.currentCampaign) { this.switchTab('edit'); } } 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'; // Show current cover photo if exists const currentCoverDiv = document.getElementById('current-cover-photo'); if (campaign.cover_photo) { currentCoverDiv.innerHTML = `
Current cover photo:
Current cover
`; } else { currentCoverDiv.innerHTML = 'No cover photo uploaded'; } // 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; form.querySelector('[name="allow_custom_recipients"]').checked = campaign.allow_custom_recipients || false; // Show/hide custom recipients section based on checkbox this.toggleCustomRecipientsSection(campaign.allow_custom_recipients); // Load custom recipients if enabled if (campaign.allow_custom_recipients && window.CustomRecipients) { console.log('Campaign has custom recipients enabled, initializing module...'); console.log('Campaign slug:', campaign.slug); // Use setTimeout to ensure the section is visible before loading setTimeout(() => { console.log('Calling CustomRecipients.init() and loadRecipients()'); window.CustomRecipients.init(campaign.slug); window.CustomRecipients.loadRecipients(campaign.slug); }, 200); } else { console.log('Custom recipients not enabled or module not loaded:', { allow_custom_recipients: campaign.allow_custom_recipients, moduleLoaded: !!window.CustomRecipients }); } // Government levels let targetLevels = []; if (campaign.target_government_levels) { if (Array.isArray(campaign.target_government_levels)) { targetLevels = campaign.target_government_levels; } else if (typeof campaign.target_government_levels === 'string') { targetLevels = 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); // Convert checkboxes to boolean values and build FormData for upload const updateFormData = new FormData(); updateFormData.append('title', formData.get('title')); updateFormData.append('description', formData.get('description') || ''); updateFormData.append('email_subject', formData.get('email_subject')); updateFormData.append('email_body', formData.get('email_body')); updateFormData.append('call_to_action', formData.get('call_to_action') || ''); updateFormData.append('status', formData.get('status')); updateFormData.append('allow_smtp_email', formData.get('allow_smtp_email') === 'on'); updateFormData.append('allow_mailto_link', formData.get('allow_mailto_link') === 'on'); updateFormData.append('collect_user_info', formData.get('collect_user_info') === 'on'); updateFormData.append('show_email_count', formData.get('show_email_count') === 'on'); updateFormData.append('allow_email_editing', formData.get('allow_email_editing') === 'on'); updateFormData.append('show_response_wall', formData.get('show_response_wall') === 'on'); updateFormData.append('allow_custom_recipients', formData.get('allow_custom_recipients') === 'on'); // Handle target_government_levels array const targetLevels = Array.from(formData.getAll('target_government_levels')); targetLevels.forEach(level => { updateFormData.append('target_government_levels', level); }); // Handle cover photo file upload if a new one is selected const coverPhotoFile = formData.get('cover_photo'); if (coverPhotoFile && coverPhotoFile.size > 0) { updateFormData.append('cover_photo', coverPhotoFile); } try { const response = await window.apiClient.putFormData(`/admin/campaigns/${this.currentCampaign.id}`, updateFormData); if (response.success) { this.showMessage('Campaign updated successfully!', 'success'); this.switchTab('campaigns'); } else { throw new Error(response.error || 'Failed to update campaign'); } } catch (error) { console.error('Update campaign error:', error); this.showMessage('Failed to update campaign: ' + error.message, 'error'); } } async deleteCampaign(campaignId) { const campaign = this.campaigns.find(c => c.id === campaignId); if (!campaign) return; if (!confirm(`Are you sure you want to delete the campaign "${campaign.title}"? This action cannot be undone.`)) { return; } try { const response = await window.apiClient.makeRequest(`/admin/campaigns/${campaignId}`, { method: 'DELETE' }); if (response.success) { this.showMessage('Campaign deleted successfully!', 'success'); this.loadCampaigns(); } else { throw new Error(response.error || 'Failed to delete campaign'); } } catch (error) { console.error('Delete campaign error:', error); this.showMessage('Failed to delete campaign: ' + error.message, 'error'); } } async viewAnalytics(campaignId) { try { const response = await window.apiClient.get(`/admin/campaigns/${campaignId}/analytics`); if (response.success) { this.showAnalyticsModal(response.analytics); } else { throw new Error(response.error || 'Failed to load analytics'); } } catch (error) { console.error('Analytics error:', error); this.showMessage('Failed to load 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); } }); } updateGovernmentLevelsPreview() { const checkboxes = document.querySelectorAll('input[name="target_government_levels"]:checked'); const levels = Array.from(checkboxes).map(cb => cb.value); // Could update a preview somewhere if needed console.log('Selected government levels:', levels); } handleSettingsChange(checkbox) { // Handle real-time settings changes if needed console.log(`Setting ${checkbox.name} changed to:`, checkbox.checked); } showMessage(message, type = 'info') { const container = document.getElementById('message-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; } } // User Management Methods async loadUsers() { const loadingDiv = document.getElementById('users-loading'); const listDiv = document.getElementById('users-list'); loadingDiv.classList.remove('hidden'); listDiv.innerHTML = ''; try { const response = await window.apiClient.get('/admin/users'); if (response.success) { this.users = response.users; this.renderUserList(); } else { throw new Error(response.error || 'Failed to load users'); } } catch (error) { console.error('Load users error:', error); this.showMessage('Failed to load users: ' + error.message, 'error'); } finally { loadingDiv.classList.add('hidden'); } } renderUserList() { const listDiv = document.getElementById('users-list'); if (this.users.length === 0) { listDiv.innerHTML = `

No users yet

Create your first user to get started.

`; return; } // Add email all users button at the top listDiv.innerHTML = `
`; const userCards = this.users.map(user => { const isExpired = user.userType === 'temp' && user.ExpiresAt && new Date(user.ExpiresAt) < new Date(); const userTypeClass = isExpired ? 'expired' : (user.userType || 'user'); return `
${isExpired ? 'EXPIRED' : (user.Admin || user.admin ? 'ADMIN' : userTypeClass.toUpperCase())}
${user.Id !== this.authManager?.user?.id ? ` ` : 'Current User'}
`; }).join(''); listDiv.innerHTML += userCards; } showUserModal() { const modal = document.getElementById('user-modal'); const form = document.getElementById('user-form'); form.reset(); document.getElementById('user-modal-title').textContent = 'Add New User'; modal.classList.remove('hidden'); } hideUserModal() { const modal = document.getElementById('user-modal'); modal.classList.add('hidden'); } showEmailModal() { const modal = document.getElementById('email-modal'); const form = document.getElementById('email-form'); form.reset(); modal.classList.remove('hidden'); } hideEmailModal() { const modal = document.getElementById('email-modal'); modal.classList.add('hidden'); } handleUserTypeChange(userType) { const tempOptions = document.getElementById('temp-user-options'); if (tempOptions) { tempOptions.style.display = userType === 'temp' ? 'block' : 'none'; } } async handleCreateUser(e) { e.preventDefault(); const formData = new FormData(e.target); const userData = { email: formData.get('email'), password: formData.get('password'), name: formData.get('name'), phone: formData.get('phone'), userType: formData.get('userType'), isAdmin: formData.get('isAdmin') === 'on' || formData.get('userType') === 'admin', expireDays: formData.get('userType') === 'temp' ? parseInt(formData.get('expireDays')) : undefined }; try { const response = await window.apiClient.post('/admin/users', userData); if (response.success) { this.showMessage('User created successfully!', 'success'); this.hideUserModal(); this.loadUsers(); } else { throw new Error(response.error || 'Failed to create user'); } } catch (error) { console.error('Create user error:', error); this.showMessage('Failed to create user: ' + error.message, 'error'); } } async deleteUser(userId) { const user = this.users.find(u => (u.Id || u.id) == userId); if (!user) return; if (!confirm(`Are you sure you want to delete the user "${user.Email || user.email}"? This action cannot be undone.`)) { return; } try { const response = await window.apiClient.makeRequest(`/admin/users/${userId}`, { method: 'DELETE' }); if (response.success) { this.showMessage('User deleted successfully!', 'success'); this.loadUsers(); } else { throw new Error(response.error || 'Failed to delete user'); } } catch (error) { console.error('Delete user error:', error); this.showMessage('Failed to delete user: ' + error.message, 'error'); } } async sendLoginDetails(userId) { try { const response = await window.apiClient.post(`/admin/users/${userId}/send-login-details`); if (response.success) { this.showMessage('Login details sent successfully!', 'success'); } else { throw new Error(response.error || 'Failed to send login details'); } } catch (error) { console.error('Send login details error:', error); this.showMessage('Failed to send login details: ' + error.message, 'error'); } } async handleEmailAllUsers(e) { e.preventDefault(); const formData = new FormData(e.target); const emailData = { subject: formData.get('subject'), content: formData.get('content') }; try { const response = await window.apiClient.post('/admin/users/email-all', emailData); if (response.success) { this.showMessage(`Email sent successfully! ${response.results.successful.length} sent, ${response.results.failed.length} failed.`, 'success'); this.hideEmailModal(); } else { throw new Error(response.error || 'Failed to send emails'); } } catch (error) { console.error('Email all users error:', error); this.showMessage('Failed to send emails: ' + error.message, 'error'); } } // Response Moderation Functions async loadAdminResponses() { const status = document.getElementById('admin-response-status').value; const campaignSlug = document.getElementById('admin-campaign-filter')?.value || ''; const container = document.getElementById('admin-responses-container'); const loading = document.getElementById('responses-loading'); // Populate campaign filter if not already done await this.populateAdminCampaignFilter(); loading.classList.remove('hidden'); container.innerHTML = ''; try { const params = new URLSearchParams({ status, limit: 100 }); if (campaignSlug) { params.append('campaign_slug', campaignSlug); } const response = await window.apiClient.get(`/admin/responses?${params}`); loading.classList.add('hidden'); if (response.success && response.responses.length > 0) { this.renderAdminResponses(response.responses); } else { container.innerHTML = '

No responses found.

'; } } catch (error) { loading.classList.add('hidden'); console.error('Error loading responses:', error); this.showMessage('Failed to load responses', 'error'); } } async populateAdminCampaignFilter() { const filterSelect = document.getElementById('admin-campaign-filter'); if (!filterSelect || filterSelect.dataset.populated === 'true') return; // Use already loaded campaigns or fetch them if (this.campaigns.length === 0) { await this.loadCampaigns(); } // 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'; } renderAdminResponses(responses) { const container = document.getElementById('admin-responses-container'); console.log('Rendering admin responses:', responses.length, 'responses'); if (responses.length > 0) { console.log('First response sample:', responses[0]); } container.innerHTML = responses.map(response => { const createdDate = new Date(response.created_at).toLocaleString(); const statusClass = { 'pending': 'warning', 'approved': 'success', 'rejected': 'danger' }[response.status] || 'secondary'; return `

${this.escapeHtml(response.representative_name)}

${this.escapeHtml(response.representative_level)}${this.escapeHtml(response.response_type)}${createdDate}
${response.status.toUpperCase()} ${response.is_verified ? '✓ VERIFIED' : ''}
Response:

${this.escapeHtml(response.response_text)}

${response.user_comment ? `
User Comment:

${this.escapeHtml(response.user_comment)}

` : ''} ${response.screenshot_url ? `
Screenshot
` : ''}
Submitted by: ${response.is_anonymous ? 'Anonymous' : (this.escapeHtml(response.submitted_by_name) || this.escapeHtml(response.submitted_by_email) || 'Unknown')} • Campaign: ${this.escapeHtml(response.campaign_slug)} • Upvotes: ${response.upvote_count || 0}
${response.status === 'pending' ? ` ` : ''} ${response.status === 'approved' && !response.is_verified ? ` ` : ''} ${response.status === 'approved' && response.is_verified ? ` ` : ''} ${response.status === 'rejected' ? ` ` : ''} ${response.status === 'approved' ? ` ` : ''}
`; }).join(''); // Add event delegation for response actions this.setupResponseActionListeners(); } async approveResponse(id) { await this.updateResponseStatus(id, 'approved'); } async rejectResponse(id) { await this.updateResponseStatus(id, 'rejected'); } async updateResponseStatus(id, status) { try { const response = await window.apiClient.patch(`/admin/responses/${id}/status`, { status }); if (response.success) { this.showMessage(`Response ${status} successfully!`, 'success'); this.loadAdminResponses(); } else { throw new Error(response.error || 'Failed to update response status'); } } catch (error) { console.error('Error updating response status:', error); this.showMessage('Failed to update response status: ' + error.message, 'error'); } } async toggleVerified(id, isVerified) { try { const response = await window.apiClient.patch(`/admin/responses/${id}`, { is_verified: isVerified }); if (response.success) { this.showMessage(isVerified ? 'Response marked as verified!' : 'Verification removed!', 'success'); this.loadAdminResponses(); } else { throw new Error(response.error || 'Failed to update verification status'); } } catch (error) { console.error('Error updating verification:', error); this.showMessage('Failed to update verification: ' + error.message, 'error'); } } async deleteResponse(id) { if (!confirm('Are you sure you want to delete this response? This action cannot be undone.')) return; try { const response = await window.apiClient.delete(`/admin/responses/${id}`); if (response.success) { this.showMessage('Response deleted successfully!', 'success'); this.loadAdminResponses(); } else { throw new Error(response.error || 'Failed to delete response'); } } catch (error) { console.error('Error deleting response:', error); this.showMessage('Failed to delete response: ' + error.message, 'error'); } } setupResponseActionListeners() { const container = document.getElementById('admin-responses-container'); if (!container) return; // Remove old listener if exists to avoid duplicates const oldListener = container._responseActionListener; if (oldListener) { container.removeEventListener('click', oldListener); } // Create new listener const listener = (e) => { const target = e.target; const action = target.dataset.action; const responseId = target.dataset.responseId; console.log('Response action clicked:', { action, responseId, target }); if (!action || !responseId) { console.log('Missing action or responseId, ignoring click'); return; } switch (action) { case 'approve-response': this.approveResponse(parseInt(responseId)); break; case 'reject-response': this.rejectResponse(parseInt(responseId)); break; case 'verify-response': const isVerified = target.dataset.verified === 'true'; this.toggleVerified(parseInt(responseId), isVerified); break; case 'delete-response': this.deleteResponse(parseInt(responseId)); break; } }; // Store listener reference and add it container._responseActionListener = listener; container.addEventListener('click', listener); } escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } toggleCustomRecipientsSection(show) { const section = document.getElementById('edit-custom-recipients-section'); console.log('Toggling custom recipients section:', show, 'section found:', section); if (section) { section.style.display = show ? 'block' : 'none'; console.log('Section display set to:', section.style.display); } } setupCustomRecipientsHandlers() { console.log('Setting up custom recipients handlers'); // Use event delegation on the document for the checkbox // This way it will work even if the checkbox is added dynamically document.addEventListener('change', (e) => { // Handle edit form checkbox if (e.target.id === 'edit-allow-custom-recipients') { console.log('Custom recipients checkbox changed:', e.target.checked); this.toggleCustomRecipientsSection(e.target.checked); // Initialize custom recipients module if enabled if (e.target.checked && this.currentCampaign && window.CustomRecipients) { console.log('Initializing CustomRecipients module for campaign:', this.currentCampaign.slug); window.CustomRecipients.init(this.currentCampaign.slug); window.CustomRecipients.loadRecipients(this.currentCampaign.slug); } } // Handle create form checkbox if (e.target.id === 'create-allow-custom-recipients') { console.log('Create form: Custom recipients checkbox changed:', e.target.checked); this.toggleCreateCustomRecipientsInfo(e.target.checked); } }); console.log('Custom recipients handlers set up with event delegation'); } toggleCreateCustomRecipientsInfo(show) { const section = document.getElementById('create-custom-recipients-section'); console.log('Toggling create custom recipients info:', show, 'section found:', section); if (section) { section.style.display = show ? 'block' : 'none'; console.log('Create section display set to:', section.style.display); } } } // Initialize admin panel when DOM is loaded document.addEventListener('DOMContentLoaded', async () => { window.adminPanel = new AdminPanel(); await window.adminPanel.init(); });