1417 lines
60 KiB
JavaScript
1417 lines
60 KiB
JavaScript
// 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}
|
|
<button id="logout-btn" style="margin-left: 1rem; padding: 0.5rem 1rem; background: rgba(255,255,255,0.2); border: 1px solid rgba(255,255,255,0.3); color: white; border-radius: 4px; cursor: pointer;">Logout</button>
|
|
`;
|
|
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 = '<div class="dropdown-item" data-campaign-id="new">Create New Campaign</div>';
|
|
} else {
|
|
dropdown.innerHTML = '<div class="dropdown-item" data-campaign-id="">Select a campaign to edit...</div>';
|
|
}
|
|
|
|
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 = `
|
|
<div class="empty-state">
|
|
<h3>No campaigns yet</h3>
|
|
<p>Create your first campaign to get started.</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
listDiv.innerHTML = this.campaigns.map(campaign => `
|
|
<div class="campaign-card ${campaign.highlight_campaign ? 'highlighted' : ''}" data-campaign-id="${campaign.id}">
|
|
${campaign.highlight_campaign ? '<div class="campaign-highlight-badge">⭐ Highlighted</div>' : ''}
|
|
${campaign.cover_photo ? `
|
|
<div class="campaign-card-cover" style="background-image: url('/uploads/${campaign.cover_photo}');">
|
|
<div class="campaign-card-cover-overlay">
|
|
<h3>${this.escapeHtml(campaign.title)}</h3>
|
|
<span class="status-badge status-${campaign.status}">${campaign.status}</span>
|
|
</div>
|
|
</div>
|
|
` : `
|
|
<div class="campaign-header">
|
|
<h3>${this.escapeHtml(campaign.title)}</h3>
|
|
<span class="status-badge status-${campaign.status}">${campaign.status}</span>
|
|
</div>
|
|
`}
|
|
|
|
<div class="campaign-meta">
|
|
<p><strong>Slug:</strong> <code>/campaign/${campaign.slug}</code></p>
|
|
<p><strong>Email Count:</strong> ${campaign.emailCount || 0}</p>
|
|
<p><strong>Created:</strong> ${this.formatDate(campaign.created_at)}</p>
|
|
${campaign.created_by_user_name || campaign.created_by_user_email ?
|
|
`<p><strong>Created By:</strong> ${this.escapeHtml(campaign.created_by_user_name || campaign.created_by_user_email)}</p>` : ''}
|
|
</div>
|
|
|
|
<div class="campaign-actions">
|
|
<button class="btn btn-secondary" data-action="edit-campaign" data-campaign-id="${campaign.id}">
|
|
Edit
|
|
</button>
|
|
<button class="btn btn-secondary" data-action="view-analytics" data-campaign-id="${campaign.id}">
|
|
Analytics
|
|
</button>
|
|
<a href="/campaign/${campaign.slug}" target="_blank" class="btn btn-secondary">
|
|
View Public Page
|
|
</a>
|
|
<button class="btn btn-danger" data-action="delete-campaign" data-campaign-id="${campaign.id}">
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).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');
|
|
campaignFormData.append('highlight_campaign', formData.get('highlight_campaign') === '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 = `
|
|
<div style="margin-bottom: 0.5rem;">
|
|
<small style="color: #666;">Current cover photo:</small><br>
|
|
<img src="/uploads/${campaign.cover_photo}" alt="Current cover" style="max-width: 200px; max-height: 150px; border-radius: 4px; margin-top: 0.25rem;">
|
|
</div>
|
|
`;
|
|
} else {
|
|
currentCoverDiv.innerHTML = '<small style="color: #999;">No cover photo uploaded</small>';
|
|
}
|
|
|
|
// 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;
|
|
form.querySelector('[name="highlight_campaign"]').checked = campaign.highlight_campaign || 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');
|
|
updateFormData.append('highlight_campaign', formData.get('highlight_campaign') === '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 = `
|
|
<div class="modal-content" style="max-width: 800px;">
|
|
<div class="modal-header">
|
|
<h2>Campaign Analytics</h2>
|
|
<button class="modal-close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="analytics-grid">
|
|
<div class="analytics-stat">
|
|
<h3>${analytics.totalEmails}</h3>
|
|
<p>Total Emails</p>
|
|
</div>
|
|
<div class="analytics-stat">
|
|
<h3>${analytics.smtpEmails}</h3>
|
|
<p>SMTP Emails</p>
|
|
</div>
|
|
<div class="analytics-stat">
|
|
<h3>${analytics.mailtoClicks}</h3>
|
|
<p>Mailto Clicks</p>
|
|
</div>
|
|
<div class="analytics-stat">
|
|
<h3>${analytics.successfulEmails}</h3>
|
|
<p>Successful</p>
|
|
</div>
|
|
</div>
|
|
${Object.keys(analytics.byLevel).length > 0 ? `
|
|
<h3>By Government Level</h3>
|
|
<div class="level-stats">
|
|
${Object.entries(analytics.byLevel).map(([level, count]) =>
|
|
`<div class="level-stat"><strong>${level}:</strong> ${count}</div>`
|
|
).join('')}
|
|
</div>
|
|
` : ''}
|
|
${analytics.recentEmails.length > 0 ? `
|
|
<h3>Recent Activity</h3>
|
|
<div class="recent-activity">
|
|
${analytics.recentEmails.slice(0, 5).map(email => `
|
|
<div class="activity-item">
|
|
<strong>${email.user_name || 'Anonymous'}</strong>
|
|
→ ${email.recipient_name} (${email.recipient_level})
|
|
<span class="timestamp">${this.formatDate(email.timestamp)}</span>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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 = `
|
|
<div class="empty-state">
|
|
<h3>No users yet</h3>
|
|
<p>Create your first user to get started.</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
// Add email all users button at the top
|
|
listDiv.innerHTML = `
|
|
<div style="margin-bottom: 2rem; text-align: center;">
|
|
<button class="btn btn-secondary" data-action="email-all-users">📧 Email All Users</button>
|
|
</div>
|
|
`;
|
|
|
|
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 `
|
|
<div class="user-card" data-user-id="${user.Id || user.id}">
|
|
<div class="user-header">
|
|
<div class="user-info">
|
|
<h4>${this.escapeHtml(user.Name || user.name || 'No Name')}</h4>
|
|
<p>${this.escapeHtml(user.Email || user.email)}</p>
|
|
${user.Phone || user.phone ? `<p>📞 ${this.escapeHtml(user.Phone || user.phone)}</p>` : ''}
|
|
${user.ExpiresAt ? `<p>⏰ Expires: ${this.formatDate(user.ExpiresAt)}</p>` : ''}
|
|
${user['Last Login'] ? `<p>🕒 Last Login: ${this.formatDate(user['Last Login'])}</p>` : ''}
|
|
</div>
|
|
<div class="user-badges">
|
|
<span class="user-badge ${userTypeClass}">
|
|
${isExpired ? 'EXPIRED' : (user.Admin || user.admin ? 'ADMIN' : userTypeClass.toUpperCase())}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="user-actions">
|
|
<button class="btn btn-secondary btn-small" data-action="send-login-details" data-user-id="${user.Id || user.id}">
|
|
📧 Send Login Details
|
|
</button>
|
|
${user.Id !== this.authManager?.user?.id ? `
|
|
<button class="btn btn-danger btn-small" data-action="delete-user" data-user-id="${user.Id || user.id}">
|
|
🗑️ Delete
|
|
</button>
|
|
` : '<span class="btn btn-secondary btn-small" style="opacity: 0.5;">Current User</span>'}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).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 = '<p style="text-align: center; color: #7f8c8d; padding: 2rem;">No responses found.</p>';
|
|
}
|
|
} 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 = '<option value="">All Campaigns</option>';
|
|
|
|
// 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 `
|
|
<div class="response-admin-card" style="background: white; border: 1px solid #ddd; border-radius: 8px; padding: 1.5rem; margin-bottom: 1.5rem;">
|
|
<div style="display: flex; justify-content: space-between; margin-bottom: 1rem;">
|
|
<div>
|
|
<h4 style="margin: 0 0 0.5rem 0;">${this.escapeHtml(response.representative_name)}</h4>
|
|
<div style="color: #7f8c8d; font-size: 0.9rem;">
|
|
<span>${this.escapeHtml(response.representative_level)}</span> •
|
|
<span>${this.escapeHtml(response.response_type)}</span> •
|
|
<span>${createdDate}</span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<span class="badge badge-${statusClass}" style="padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.85rem;">
|
|
${response.status.toUpperCase()}
|
|
</span>
|
|
${response.is_verified ? '<span class="badge badge-success" style="padding: 0.25rem 0.75rem; border-radius: 12px; font-size: 0.85rem; margin-left: 0.5rem;">✓ VERIFIED</span>' : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div style="background: #f8f9fa; padding: 1rem; border-left: 4px solid #3498db; border-radius: 4px; margin-bottom: 1rem;">
|
|
<strong>Response:</strong>
|
|
<p style="margin: 0.5rem 0 0 0; white-space: pre-wrap;">${this.escapeHtml(response.response_text)}</p>
|
|
</div>
|
|
|
|
${response.user_comment ? `
|
|
<div style="background: #fff3cd; padding: 0.75rem; border-left: 4px solid #ffc107; border-radius: 4px; margin-bottom: 1rem;">
|
|
<strong>User Comment:</strong>
|
|
<p style="margin: 0.5rem 0 0 0;">${this.escapeHtml(response.user_comment)}</p>
|
|
</div>
|
|
` : ''}
|
|
|
|
${response.screenshot_url ? `
|
|
<div style="margin-bottom: 1rem;">
|
|
<img src="${this.escapeHtml(response.screenshot_url)}" style="max-width: 300px; border-radius: 4px; border: 1px solid #ddd;" alt="Screenshot">
|
|
</div>
|
|
` : ''}
|
|
|
|
<div style="color: #7f8c8d; font-size: 0.9rem; margin-bottom: 1rem;">
|
|
<strong>Submitted by:</strong> ${response.is_anonymous ? 'Anonymous' : (this.escapeHtml(response.submitted_by_name) || this.escapeHtml(response.submitted_by_email) || 'Unknown')} •
|
|
<strong>Campaign:</strong> ${this.escapeHtml(response.campaign_slug)} •
|
|
<strong>Upvotes:</strong> ${response.upvote_count || 0}
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
|
${response.status === 'pending' ? `
|
|
<button class="btn btn-success btn-sm" data-action="approve-response" data-response-id="${response.id}">✓ Approve</button>
|
|
<button class="btn btn-danger btn-sm" data-action="reject-response" data-response-id="${response.id}">✗ Reject</button>
|
|
` : ''}
|
|
${response.status === 'approved' && !response.is_verified ? `
|
|
<button class="btn btn-primary btn-sm" data-action="verify-response" data-response-id="${response.id}" data-verified="true">Mark as Verified</button>
|
|
` : ''}
|
|
${response.status === 'approved' && response.is_verified ? `
|
|
<button class="btn btn-secondary btn-sm" data-action="verify-response" data-response-id="${response.id}" data-verified="false">Remove Verification</button>
|
|
` : ''}
|
|
${response.status === 'rejected' ? `
|
|
<button class="btn btn-success btn-sm" data-action="approve-response" data-response-id="${response.id}">✓ Approve</button>
|
|
` : ''}
|
|
${response.status === 'approved' ? `
|
|
<button class="btn btn-warning btn-sm" data-action="reject-response" data-response-id="${response.id}">Unpublish</button>
|
|
` : ''}
|
|
<button class="btn btn-danger btn-sm" data-action="delete-response" data-response-id="${response.id}">🗑 Delete</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).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();
|
|
}); |