492 lines
18 KiB
JavaScript
492 lines
18 KiB
JavaScript
// Campaign Page Management Module
|
|
class CampaignPage {
|
|
constructor() {
|
|
this.campaign = null;
|
|
this.representatives = [];
|
|
this.userInfo = {};
|
|
this.currentStep = 1;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
// Get campaign slug from URL
|
|
const pathParts = window.location.pathname.split('/');
|
|
this.campaignSlug = pathParts[pathParts.length - 1];
|
|
|
|
// Set up form handlers
|
|
document.getElementById('user-info-form').addEventListener('submit', (e) => {
|
|
this.handleUserInfoSubmit(e);
|
|
});
|
|
|
|
// Postal code formatting
|
|
document.getElementById('user-postal-code').addEventListener('input', (e) => {
|
|
this.formatPostalCode(e);
|
|
});
|
|
|
|
// Load campaign data
|
|
this.loadCampaign();
|
|
}
|
|
|
|
async loadCampaign() {
|
|
this.showLoading('Loading campaign...');
|
|
|
|
try {
|
|
const response = await fetch(`/api/campaigns/${this.campaignSlug}`);
|
|
const data = await response.json();
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.error || 'Failed to load campaign');
|
|
}
|
|
|
|
this.campaign = data.campaign;
|
|
this.renderCampaign();
|
|
} catch (error) {
|
|
this.showError('Failed to load campaign: ' + error.message);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
renderCampaign() {
|
|
// Update page title and header
|
|
document.title = `${this.campaign.title} - BNKops Influence Tool`;
|
|
document.getElementById('page-title').textContent = `${this.campaign.title} - BNKops Influence Tool`;
|
|
document.getElementById('campaign-title').textContent = this.campaign.title;
|
|
document.getElementById('campaign-description').textContent = this.campaign.description;
|
|
|
|
// Show email count if enabled
|
|
if (this.campaign.show_email_count && this.campaign.emailCount !== null) {
|
|
document.getElementById('email-count').textContent = this.campaign.emailCount;
|
|
document.getElementById('campaign-stats').style.display = 'block';
|
|
}
|
|
|
|
// Show call to action
|
|
if (this.campaign.call_to_action) {
|
|
document.getElementById('call-to-action').innerHTML = `<p><strong>${this.campaign.call_to_action}</strong></p>`;
|
|
document.getElementById('call-to-action').style.display = 'block';
|
|
}
|
|
|
|
// Show email preview
|
|
document.getElementById('preview-subject').textContent = this.campaign.email_subject;
|
|
document.getElementById('preview-body').textContent = this.campaign.email_body;
|
|
document.getElementById('email-preview').style.display = 'block';
|
|
|
|
// Set up email method options
|
|
this.setupEmailMethodOptions();
|
|
|
|
// Show optional fields if user info collection is enabled
|
|
if (this.campaign.collect_user_info) {
|
|
const optionalFields = document.getElementById('optional-fields');
|
|
if (optionalFields) {
|
|
optionalFields.style.display = 'block';
|
|
console.log('Showing optional user info fields');
|
|
}
|
|
}
|
|
|
|
// Set initial step
|
|
this.setStep(1);
|
|
}
|
|
|
|
setupEmailMethodOptions() {
|
|
const emailMethodSection = document.getElementById('email-method-selection');
|
|
const allowSMTP = this.campaign.allow_smtp_email;
|
|
const allowMailto = this.campaign.allow_mailto_link;
|
|
|
|
if (!emailMethodSection) {
|
|
console.warn('Email method selection element not found');
|
|
return;
|
|
}
|
|
|
|
// Configure existing radio buttons instead of replacing HTML
|
|
const smtpRadio = document.getElementById('method-smtp');
|
|
const mailtoRadio = document.getElementById('method-mailto');
|
|
|
|
if (allowSMTP && allowMailto) {
|
|
// Both methods allowed - keep default setup
|
|
smtpRadio.disabled = false;
|
|
mailtoRadio.disabled = false;
|
|
smtpRadio.checked = true;
|
|
} else if (allowSMTP && !allowMailto) {
|
|
// Only SMTP allowed
|
|
smtpRadio.disabled = false;
|
|
mailtoRadio.disabled = true;
|
|
smtpRadio.checked = true;
|
|
} else if (!allowSMTP && allowMailto) {
|
|
// Only mailto allowed
|
|
smtpRadio.disabled = true;
|
|
mailtoRadio.disabled = false;
|
|
mailtoRadio.checked = true;
|
|
} else {
|
|
// Neither allowed - hide the section
|
|
emailMethodSection.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
formatPostalCode(e) {
|
|
let value = e.target.value.replace(/\s/g, '').toUpperCase();
|
|
if (value.length > 3) {
|
|
value = value.substring(0, 3) + ' ' + value.substring(3, 6);
|
|
}
|
|
e.target.value = value;
|
|
}
|
|
|
|
async handleUserInfoSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(e.target);
|
|
this.userInfo = {
|
|
postalCode: formData.get('postalCode').replace(/\s/g, '').toUpperCase(),
|
|
userName: formData.get('userName') || '',
|
|
userEmail: formData.get('userEmail') || ''
|
|
};
|
|
|
|
// Track user info when they click "Find My Representatives"
|
|
await this.trackUserInfo();
|
|
|
|
await this.loadRepresentatives();
|
|
}
|
|
|
|
async trackUserInfo() {
|
|
try {
|
|
const response = await fetch(`/api/campaigns/${this.campaignSlug}/track-user`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
userEmail: this.userInfo.userEmail,
|
|
userName: this.userInfo.userName,
|
|
postalCode: this.userInfo.postalCode
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (!data.success) {
|
|
console.warn('Failed to track user info:', data.error);
|
|
// Don't throw error - this is just tracking, shouldn't block the user
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to track user info:', error.message);
|
|
// Don't throw error - this is just tracking, shouldn't block the user
|
|
}
|
|
}
|
|
|
|
async loadRepresentatives() {
|
|
this.showLoading('Finding your representatives...');
|
|
|
|
try {
|
|
const response = await fetch(`/api/campaigns/${this.campaignSlug}/representatives/${this.userInfo.postalCode}`);
|
|
const data = await response.json();
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.error || 'Failed to load representatives');
|
|
}
|
|
|
|
this.representatives = data.representatives;
|
|
this.renderRepresentatives();
|
|
this.setStep(2);
|
|
|
|
// Scroll to representatives section
|
|
document.getElementById('representatives-section').scrollIntoView({
|
|
behavior: 'smooth'
|
|
});
|
|
} catch (error) {
|
|
this.showError('Failed to load representatives: ' + error.message);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
renderRepresentatives() {
|
|
const list = document.getElementById('representatives-list');
|
|
|
|
if (this.representatives.length === 0) {
|
|
list.innerHTML = '<p>No representatives found for your area. Please check your postal code.</p>';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = this.representatives.map(rep => `
|
|
<div class="rep-card">
|
|
<div class="rep-info">
|
|
${rep.photo_url ?
|
|
`<img src="${rep.photo_url}" alt="${rep.name}" class="rep-photo">` :
|
|
`<div class="rep-photo"></div>`
|
|
}
|
|
<div class="rep-details">
|
|
<h4>${rep.name}</h4>
|
|
<p>${rep.elected_office || 'Representative'}</p>
|
|
<p>${rep.party_name || ''}</p>
|
|
${rep.email ? `<p>📧 ${rep.email}</p>` : ''}
|
|
${this.getPhoneNumber(rep) ? `<p>📞 ${this.getPhoneNumber(rep)}</p>` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="rep-actions">
|
|
${rep.email ? `
|
|
<button class="btn btn-primary" data-action="send-email"
|
|
data-email="${rep.email}"
|
|
data-name="${rep.name}"
|
|
data-title="${rep.elected_office || ''}"
|
|
data-level="${this.getGovernmentLevel(rep)}">
|
|
Send Email
|
|
</button>
|
|
` : ''}
|
|
${this.getPhoneNumber(rep) ? `
|
|
<button class="btn btn-success" data-action="call-representative"
|
|
data-phone="${this.getPhoneNumber(rep)}"
|
|
data-name="${rep.name}"
|
|
data-title="${rep.elected_office || ''}"
|
|
data-office-type="${this.getPhoneOfficeType(rep)}">
|
|
📞 Call
|
|
</button>
|
|
` : ''}
|
|
${!rep.email && !this.getPhoneNumber(rep) ? '<p style="text-align: center; color: #6c757d;">No contact information available</p>' : ''}
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
// Attach event listeners to send email buttons
|
|
this.attachEmailButtonListeners();
|
|
|
|
document.getElementById('representatives-section').style.display = 'block';
|
|
}
|
|
|
|
attachEmailButtonListeners() {
|
|
// Send email buttons
|
|
document.querySelectorAll('[data-action="send-email"]').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
const email = e.target.dataset.email;
|
|
const name = e.target.dataset.name;
|
|
const title = e.target.dataset.title;
|
|
const level = e.target.dataset.level;
|
|
this.sendEmail(email, name, title, level);
|
|
});
|
|
});
|
|
|
|
// Call buttons
|
|
document.querySelectorAll('[data-action="call-representative"]').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
const phone = e.target.dataset.phone;
|
|
const name = e.target.dataset.name;
|
|
const title = e.target.dataset.title;
|
|
const officeType = e.target.dataset.officeType;
|
|
this.callRepresentative(phone, name, title, officeType);
|
|
});
|
|
});
|
|
|
|
// Reload page button
|
|
const reloadBtn = document.querySelector('[data-action="reload-page"]');
|
|
if (reloadBtn) {
|
|
reloadBtn.addEventListener('click', () => {
|
|
location.reload();
|
|
});
|
|
}
|
|
}
|
|
|
|
getGovernmentLevel(rep) {
|
|
const office = (rep.elected_office || '').toLowerCase();
|
|
if (office.includes('mp') || office.includes('member of parliament')) return 'Federal';
|
|
if (office.includes('mla') || office.includes('legislative assembly')) return 'Provincial';
|
|
if (office.includes('mayor') || office.includes('councillor')) return 'Municipal';
|
|
if (office.includes('school')) return 'School Board';
|
|
return 'Other';
|
|
}
|
|
|
|
getPhoneNumber(rep) {
|
|
if (!rep.offices || !Array.isArray(rep.offices)) {
|
|
return null;
|
|
}
|
|
|
|
// Find the first office with a phone number
|
|
const officeWithPhone = rep.offices.find(office => office.tel);
|
|
return officeWithPhone ? officeWithPhone.tel : null;
|
|
}
|
|
|
|
getPhoneOfficeType(rep) {
|
|
if (!rep.offices || !Array.isArray(rep.offices)) {
|
|
return 'office';
|
|
}
|
|
|
|
const officeWithPhone = rep.offices.find(office => office.tel);
|
|
return officeWithPhone ? (officeWithPhone.type || 'office') : 'office';
|
|
}
|
|
|
|
callRepresentative(phone, name, title, officeType) {
|
|
// Clean the phone number for tel: link (remove spaces, dashes, parentheses)
|
|
const cleanPhone = phone.replace(/[\s\-\(\)]/g, '');
|
|
|
|
// Create tel: link
|
|
const telLink = `tel:${cleanPhone}`;
|
|
|
|
// Show confirmation dialog with formatted information
|
|
const officeInfo = officeType ? ` (${officeType} office)` : '';
|
|
const message = `Call ${name}${title ? ` - ${title}` : ''}${officeInfo}?\n\nPhone: ${phone}`;
|
|
|
|
if (confirm(message)) {
|
|
// Attempt to initiate the call
|
|
window.location.href = telLink;
|
|
|
|
// Track the call attempt
|
|
this.trackCall(phone, name, title, officeType);
|
|
}
|
|
}
|
|
|
|
async trackCall(phone, name, title, officeType) {
|
|
try {
|
|
await fetch(`/api/campaigns/${this.campaignSlug}/track-call`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
userEmail: this.userInfo.userEmail,
|
|
userName: this.userInfo.userName,
|
|
postalCode: this.userInfo.postalCode,
|
|
recipientPhone: phone,
|
|
recipientName: name,
|
|
recipientTitle: title,
|
|
officeType: officeType
|
|
})
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to track call:', error);
|
|
}
|
|
}
|
|
|
|
async sendEmail(recipientEmail, recipientName, recipientTitle, recipientLevel) {
|
|
const emailMethod = document.querySelector('input[name="emailMethod"]:checked').value;
|
|
|
|
if (emailMethod === 'mailto') {
|
|
this.openMailtoLink(recipientEmail);
|
|
} else {
|
|
await this.sendSMTPEmail(recipientEmail, recipientName, recipientTitle, recipientLevel);
|
|
}
|
|
}
|
|
|
|
openMailtoLink(recipientEmail) {
|
|
const subject = encodeURIComponent(this.campaign.email_subject);
|
|
const body = encodeURIComponent(this.campaign.email_body);
|
|
const mailtoUrl = `mailto:${recipientEmail}?subject=${subject}&body=${body}`;
|
|
|
|
// Track the mailto click
|
|
this.trackEmail(recipientEmail, '', '', '', 'mailto');
|
|
|
|
window.open(mailtoUrl);
|
|
}
|
|
|
|
async sendSMTPEmail(recipientEmail, recipientName, recipientTitle, recipientLevel) {
|
|
this.showLoading('Sending email...');
|
|
|
|
try {
|
|
const response = await fetch(`/api/campaigns/${this.campaignSlug}/send-email`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
userEmail: this.userInfo.userEmail,
|
|
userName: this.userInfo.userName,
|
|
postalCode: this.userInfo.postalCode,
|
|
recipientEmail,
|
|
recipientName,
|
|
recipientTitle,
|
|
recipientLevel,
|
|
emailMethod: 'smtp'
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
this.showSuccess('Email sent successfully!');
|
|
} else {
|
|
throw new Error(data.error || 'Failed to send email');
|
|
}
|
|
} catch (error) {
|
|
this.showError('Failed to send email: ' + error.message);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
async trackEmail(recipientEmail, recipientName, recipientTitle, recipientLevel, emailMethod) {
|
|
try {
|
|
await fetch(`/api/campaigns/${this.campaignSlug}/send-email`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
userEmail: this.userInfo.userEmail,
|
|
userName: this.userInfo.userName,
|
|
postalCode: this.userInfo.postalCode,
|
|
recipientEmail,
|
|
recipientName,
|
|
recipientTitle,
|
|
recipientLevel,
|
|
emailMethod
|
|
})
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to track email:', error);
|
|
}
|
|
}
|
|
|
|
setStep(step) {
|
|
// Reset all steps
|
|
document.querySelectorAll('.step').forEach(s => {
|
|
s.classList.remove('active', 'completed');
|
|
});
|
|
|
|
// Mark completed steps
|
|
for (let i = 1; i < step; i++) {
|
|
document.getElementById(`step-${this.getStepName(i)}`).classList.add('completed');
|
|
}
|
|
|
|
// Mark current step
|
|
document.getElementById(`step-${this.getStepName(step)}`).classList.add('active');
|
|
|
|
this.currentStep = step;
|
|
}
|
|
|
|
getStepName(step) {
|
|
const steps = ['', 'info', 'postal', 'send'];
|
|
return steps[step] || 'info';
|
|
}
|
|
|
|
showLoading(message) {
|
|
document.getElementById('loading-message').textContent = message;
|
|
document.getElementById('loading-overlay').style.display = 'flex';
|
|
}
|
|
|
|
hideLoading() {
|
|
document.getElementById('loading-overlay').style.display = 'none';
|
|
}
|
|
|
|
showError(message) {
|
|
const errorDiv = document.getElementById('error-message');
|
|
errorDiv.textContent = message;
|
|
errorDiv.style.display = 'block';
|
|
|
|
setTimeout(() => {
|
|
errorDiv.style.display = 'none';
|
|
}, 5000);
|
|
}
|
|
|
|
showSuccess(message) {
|
|
// Update email count if enabled
|
|
if (this.campaign.show_email_count) {
|
|
const countElement = document.getElementById('email-count');
|
|
const currentCount = parseInt(countElement.textContent) || 0;
|
|
countElement.textContent = currentCount + 1;
|
|
}
|
|
|
|
// You could show a toast or update UI to indicate success
|
|
alert(message); // Simple for now, could be improved with better UI
|
|
}
|
|
}
|
|
|
|
// Initialize the campaign page when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.campaignPage = new CampaignPage();
|
|
}); |