511 lines
20 KiB
JavaScript

// Admin Panel JavaScript
class AdminPanel {
constructor() {
this.currentCampaign = null;
this.campaigns = [];
this.authManager = null;
}
async init() {
// 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;
}
this.setupEventListeners();
this.setupFormInteractions();
this.loadCampaigns();
}
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() {
}
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);
});
// 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');
}
});
this.loadCampaigns();
}
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);
});
});
}
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 === 'edit' && this.currentCampaign) {
this.populateEditForm();
}
}
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();
} 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" data-campaign-id="${campaign.id}">
<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>
</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);
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'),
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',
target_government_levels: Array.from(formData.getAll('target_government_levels'))
};
try {
const response = await window.apiClient.post('/admin/campaigns', campaignData);
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';
// 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;
// Government levels
const targetLevels = 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_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',
target_government_levels: Array.from(formData.getAll('target_government_levels'))
};
try {
const response = await window.apiClient.makeRequest(`/admin/campaigns/${this.currentCampaign.id}`, {
method: 'PUT',
body: JSON.stringify(updates)
});
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">&times;</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;
}
}
}
// Initialize admin panel when DOM is loaded
document.addEventListener('DOMContentLoaded', async () => {
window.adminPanel = new AdminPanel();
await window.adminPanel.init();
});