1066 lines
45 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);
});
}
// 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');
}
});
// 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();
}
}
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">&times;</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">&times;</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;
// 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',
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;
}
}
// 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',
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();
});