1274 lines
54 KiB
JavaScript
1274 lines
54 KiB
JavaScript
// 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();
|
|
}
|
|
|
|
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 = '<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) {
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
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 = `
|
|
<div class="empty-state" style="grid-column: 1 / -1;">
|
|
<h3>No campaigns yet</h3>
|
|
<p>You haven't created any campaigns yet.</p>
|
|
<p><button class="btn btn-primary" data-action="go-to-create">Create Your First Campaign</button></p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
listDiv.innerHTML = this.campaigns.map(campaign => `
|
|
<div class="campaign-card" data-campaign-id="${campaign.id}">
|
|
${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>Emails Sent:</strong> ${campaign.emailCount || 0}</p>
|
|
<p><strong>Created:</strong> ${this.formatDate(campaign.created_at)}</p>
|
|
${campaign.description ? `<p><strong>Description:</strong> ${this.escapeHtml(campaign.description)}</p>` : ''}
|
|
</div>
|
|
|
|
<div class="campaign-actions">
|
|
<button class="btn btn-primary" data-action="view-campaign" data-campaign-slug="${campaign.slug}">
|
|
View Public Page
|
|
</button>
|
|
${this.canEditCampaign(campaign) ? `
|
|
<button class="btn btn-secondary" data-action="edit-campaign" data-campaign-id="${campaign.id}">
|
|
Edit
|
|
</button>
|
|
` : ''}
|
|
<button class="btn btn-success" data-action="view-analytics" data-campaign-id="${campaign.id}">
|
|
Analytics
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).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 = `
|
|
<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="stats-grid">
|
|
<div class="stat-card">
|
|
<h3>${analytics.totalEmails || 0}</h3>
|
|
<p>Total Emails</p>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>${analytics.smtpEmails || 0}</h3>
|
|
<p>SMTP Emails</p>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>${analytics.mailtoClicks || 0}</h3>
|
|
<p>Mailto Clicks</p>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>${analytics.successfulEmails || 0}</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);
|
|
}
|
|
});
|
|
}
|
|
|
|
openEditModal(campaign) {
|
|
const modal = document.createElement('div');
|
|
modal.className = 'modal-overlay';
|
|
const isAdmin = !!this.user?.isAdmin;
|
|
|
|
modal.innerHTML = `
|
|
<div class="modal-content" style="max-width: 720px;">
|
|
<div class="modal-header">
|
|
<h2>Edit Campaign</h2>
|
|
<button class="modal-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="edit-campaign-form" class="campaign-form">
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label for="edit-title">Campaign Title *</label>
|
|
<input type="text" id="edit-title" name="title" required>
|
|
</div>
|
|
|
|
<div class="form-group status-select">
|
|
<label for="edit-status">Campaign Status</label>
|
|
<select id="edit-status" name="status" ${isAdmin ? '' : 'disabled'}>
|
|
<option value="draft">📝 Draft</option>
|
|
<option value="active">🚀 Active</option>
|
|
<option value="paused">⏸️ Paused</option>
|
|
<option value="archived">📦 Archived</option>
|
|
</select>
|
|
${isAdmin ? '' : '<small style="display:block;margin-top:0.5rem;color:#666;">Only administrators can change campaign status.</small>'}
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="edit-description">Description</label>
|
|
<textarea id="edit-description" name="description" rows="3"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="edit-email-subject">Email Subject *</label>
|
|
<input type="text" id="edit-email-subject" name="email_subject" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="edit-email-body">Email Body *</label>
|
|
<textarea id="edit-email-body" name="email_body" rows="8" required></textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="edit-call-to-action">Call to Action</label>
|
|
<textarea id="edit-call-to-action" name="call_to_action" rows="3"></textarea>
|
|
</div>
|
|
|
|
<div class="section-header">⚙️ Campaign Settings</div>
|
|
<div class="form-group">
|
|
<div class="checkbox-group">
|
|
<div class="checkbox-item">
|
|
<input type="checkbox" id="edit-allow-smtp" name="allow_smtp_email">
|
|
<label for="edit-allow-smtp">📧 Allow SMTP Email Sending</label>
|
|
</div>
|
|
<div class="checkbox-item">
|
|
<input type="checkbox" id="edit-allow-mailto" name="allow_mailto_link">
|
|
<label for="edit-allow-mailto">🔗 Allow Mailto Links</label>
|
|
</div>
|
|
<div class="checkbox-item">
|
|
<input type="checkbox" id="edit-collect-info" name="collect_user_info">
|
|
<label for="edit-collect-info">👤 Collect User Information</label>
|
|
</div>
|
|
<div class="checkbox-item">
|
|
<input type="checkbox" id="edit-show-count" name="show_email_count">
|
|
<label for="edit-show-count">📊 Show Email Count</label>
|
|
</div>
|
|
<div class="checkbox-item">
|
|
<input type="checkbox" id="edit-allow-editing" name="allow_email_editing">
|
|
<label for="edit-allow-editing">✏️ Allow Email Editing</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-header">🏛️ Target Government Levels</div>
|
|
<div class="form-group">
|
|
<div class="checkbox-group">
|
|
<div class="checkbox-item">
|
|
<input type="checkbox" id="edit-federal" name="target_government_levels" value="Federal">
|
|
<label for="edit-federal">🍁 Federal (MPs)</label>
|
|
</div>
|
|
<div class="checkbox-item">
|
|
<input type="checkbox" id="edit-provincial" name="target_government_levels" value="Provincial">
|
|
<label for="edit-provincial">🏛️ Provincial (MLAs)</label>
|
|
</div>
|
|
<div class="checkbox-item">
|
|
<input type="checkbox" id="edit-municipal" name="target_government_levels" value="Municipal">
|
|
<label for="edit-municipal">🏙️ Municipal (Mayors, Councillors)</label>
|
|
</div>
|
|
<div class="checkbox-item">
|
|
<input type="checkbox" id="edit-school" name="target_government_levels" value="School Board">
|
|
<label for="edit-school">🎓 School Board</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<button type="submit" class="btn btn-primary">Update Campaign</button>
|
|
<button type="button" class="btn btn-secondary" data-action="cancel-modal">Cancel</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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 = `
|
|
<div class="empty-state">
|
|
<h3>Error loading responses</h3>
|
|
<p>${this.escapeHtml(error.message)}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
} 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 = '<option value="">All My 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';
|
|
}
|
|
|
|
renderResponsesList(responses) {
|
|
const listDiv = document.getElementById('responses-list');
|
|
if (!listDiv) return;
|
|
|
|
if (responses.length === 0) {
|
|
listDiv.innerHTML = `
|
|
<div class="empty-state">
|
|
<h3>No responses found</h3>
|
|
<p>There are no responses matching your filters.</p>
|
|
</div>
|
|
`;
|
|
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 = `
|
|
<button class="btn btn-success" data-action="approve-response" data-response-id="${response.id}">✅ Approve</button>
|
|
<button class="btn btn-danger" data-action="reject-response" data-response-id="${response.id}">❌ Reject</button>
|
|
`;
|
|
} else if (response.status === 'approved') {
|
|
actionButtons = `
|
|
<button class="btn btn-secondary" data-action="pending-response" data-response-id="${response.id}">⏳ Move to Pending</button>
|
|
<button class="btn btn-danger" data-action="reject-response" data-response-id="${response.id}">❌ Reject</button>
|
|
`;
|
|
} else if (response.status === 'rejected') {
|
|
actionButtons = `
|
|
<button class="btn btn-success" data-action="approve-response" data-response-id="${response.id}">✅ Approve</button>
|
|
<button class="btn btn-secondary" data-action="pending-response" data-response-id="${response.id}">⏳ Move to Pending</button>
|
|
`;
|
|
}
|
|
|
|
return `
|
|
<div class="response-card ${response.status}" data-response-id="${response.id}">
|
|
<div class="response-header">
|
|
<div>
|
|
<span class="status-badge status-${response.status}">${statusLabel}</span>
|
|
</div>
|
|
<small style="color: #666;">Submitted ${this.formatDate(response.created_at)}</small>
|
|
</div>
|
|
|
|
<div class="response-meta">
|
|
<p><strong>Campaign:</strong> ${this.escapeHtml(response.campaign_slug)}</p>
|
|
<p><strong>${levelEmoji} Representative:</strong> ${this.escapeHtml(response.representative_name)}</p>
|
|
${response.representative_title ? `<p><strong>Title:</strong> ${this.escapeHtml(response.representative_title)}</p>` : ''}
|
|
<p><strong>Response Type:</strong> ${this.escapeHtml(response.response_type)}</p>
|
|
${response.submitted_by_name ? `<p><strong>Submitted By:</strong> ${this.escapeHtml(response.submitted_by_name)}</p>` : ''}
|
|
${response.submitted_by_email ? `<p><strong>Email:</strong> ${this.escapeHtml(response.submitted_by_email)}</p>` : ''}
|
|
</div>
|
|
|
|
<div class="response-content">
|
|
<h4>Response:</h4>
|
|
<p>${this.escapeHtml(response.response_text)}</p>
|
|
${response.user_comment ? `
|
|
<h4 style="margin-top: 1rem;">Submitter Comment:</h4>
|
|
<p>${this.escapeHtml(response.user_comment)}</p>
|
|
` : ''}
|
|
${response.screenshot_url ? `
|
|
<img src="${response.screenshot_url}" alt="Response screenshot" class="response-screenshot">
|
|
` : ''}
|
|
</div>
|
|
|
|
<div class="response-actions">
|
|
${actionButtons}
|
|
<span style="margin-left: auto; color: #666;">👍 ${response.upvote_count || 0} upvotes</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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();
|
|
}); |