tonne of imporvements, debugs, new ui
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Panel - Alberta Influence Campaign Tool</title>
|
||||
<title>Admin Panel - BNKops Influence Campaign Tool</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
<style>
|
||||
@@ -286,6 +286,187 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Enhanced Form Styling */
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 2px solid #e0e6ed;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
transition: border-color 0.3s, box-shadow 0.3s;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Enhanced Status Select */
|
||||
.status-select {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.status-select select {
|
||||
appearance: none;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
padding: 1rem 3rem 1rem 1rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.status-select select option {
|
||||
background: white;
|
||||
color: #2c3e50;
|
||||
font-weight: 500;
|
||||
padding: 0.75rem;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.status-select select option:hover,
|
||||
.status-select select option:checked {
|
||||
background: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-select::after {
|
||||
content: '▼';
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: white;
|
||||
pointer-events: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.status-select select:focus {
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
/* Enhanced Checkbox Styling */
|
||||
.checkbox-group {
|
||||
background: #f8f9fa;
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
border: 2px solid #e9ecef;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #e9ecef;
|
||||
margin-bottom: 0.75rem;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-item:hover {
|
||||
border-color: #3498db;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(52, 152, 219, 0.15);
|
||||
}
|
||||
|
||||
.checkbox-item input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin-right: 0.75rem;
|
||||
transform: scale(1.2);
|
||||
accent-color: #3498db;
|
||||
}
|
||||
|
||||
.checkbox-item label {
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.checkbox-item:has(input:checked) {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.checkbox-item:has(input:checked) label {
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Enhanced Form Grid */
|
||||
.form-grid {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* Section Headers */
|
||||
.section-header {
|
||||
color: #2c3e50;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin: 2rem 0 1rem 0;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
}
|
||||
|
||||
/* Enhanced Buttons */
|
||||
.btn {
|
||||
padding: 1rem 2rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.admin-nav {
|
||||
flex-direction: column;
|
||||
@@ -324,7 +505,6 @@
|
||||
<div id="campaigns-tab" class="tab-content active">
|
||||
<div class="form-row" style="align-items: center; margin-bottom: 2rem;">
|
||||
<h2 style="margin: 0;">Active Campaigns</h2>
|
||||
<button class="btn btn-primary" data-action="create-campaign">Create New Campaign</button>
|
||||
</div>
|
||||
|
||||
<div id="campaigns-loading" class="loading hidden">
|
||||
@@ -349,6 +529,16 @@
|
||||
placeholder="Save Alberta Parks">
|
||||
</div>
|
||||
|
||||
<div class="form-group status-select">
|
||||
<label for="create-status">Campaign Status</label>
|
||||
<select id="create-status" name="status">
|
||||
<option value="draft">📝 Draft</option>
|
||||
<option value="active">🚀 Active</option>
|
||||
<option value="paused">⏸️ Paused</option>
|
||||
<option value="archived">📦 Archived</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="create-description">Description</label>
|
||||
<textarea id="create-description" name="description" rows="3"
|
||||
@@ -379,46 +569,46 @@ Sincerely,
|
||||
placeholder="Join thousands of Albertans in protecting our provincial parks. Send an email to your representatives today!"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="section-header">⚙️ Campaign Settings</div>
|
||||
<div class="form-group">
|
||||
<label>Campaign Settings</label>
|
||||
<div class="checkbox-group">
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="create-allow-smtp" name="allow_smtp_email" checked>
|
||||
<label for="create-allow-smtp">Allow SMTP Email Sending</label>
|
||||
<label for="create-allow-smtp">📧 Allow SMTP Email Sending</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="create-allow-mailto" name="allow_mailto_link" checked>
|
||||
<label for="create-allow-mailto">Allow Mailto Links</label>
|
||||
<label for="create-allow-mailto">🔗 Allow Mailto Links</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="create-collect-info" name="collect_user_info" checked>
|
||||
<label for="create-collect-info">Collect User Information</label>
|
||||
<label for="create-collect-info">👤 Collect User Information</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="create-show-count" name="show_email_count" checked>
|
||||
<label for="create-show-count">Show Email Count</label>
|
||||
<label for="create-show-count">📊 Show Email Count</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-header">🏛️ Target Government Levels</div>
|
||||
<div class="form-group">
|
||||
<label>Target Government Levels</label>
|
||||
<div class="checkbox-group">
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="create-federal" name="target_government_levels" value="Federal" checked>
|
||||
<label for="create-federal">Federal (MPs)</label>
|
||||
<label for="create-federal">🍁 Federal (MPs)</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="create-provincial" name="target_government_levels" value="Provincial" checked>
|
||||
<label for="create-provincial">Provincial (MLAs)</label>
|
||||
<label for="create-provincial">🏛️ Provincial (MLAs)</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="create-municipal" name="target_government_levels" value="Municipal" checked>
|
||||
<label for="create-municipal">Municipal (Mayors, Councillors)</label>
|
||||
<label for="create-municipal">🏙️ Municipal (Mayors, Councillors)</label>
|
||||
</div>
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="create-school" name="target_government_levels" value="School Board">
|
||||
<label for="create-school">School Board</label>
|
||||
<label for="create-school">🎓 School Board</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title id="page-title">Campaign - Alberta Influence Tool</title>
|
||||
<title id="page-title">Campaign - BNKops Influence Tool</title>
|
||||
<link rel="stylesheet" href="/css/styles.css">
|
||||
<style>
|
||||
.campaign-header {
|
||||
@@ -266,12 +266,12 @@
|
||||
|
||||
<div id="optional-fields" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label for="user-name">Your Name (Optional)</label>
|
||||
<label for="user-name">Your Name</label>
|
||||
<input type="text" id="user-name" name="userName" placeholder="Your full name">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="user-email">Your Email (Optional)</label>
|
||||
<label for="user-email">Your Email (Optional - If you would like a reply)</label>
|
||||
<input type="email" id="user-email" name="userEmail" placeholder="your@email.com">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,75 @@ header p {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 16px;
|
||||
margin: 2px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: white;
|
||||
background-color: #005a9c;
|
||||
border-color: #005a9c;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #004a7c;
|
||||
border-color: #004a7c;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
color: #005a9c;
|
||||
background-color: white;
|
||||
border-color: #005a9c;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
color: white;
|
||||
background-color: #005a9c;
|
||||
border-color: #005a9c;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
color: white;
|
||||
background-color: #28a745;
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #218838;
|
||||
border-color: #1e7e34;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
@@ -278,6 +347,17 @@ header p {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.call-representative {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.call-representative:hover {
|
||||
background-color: #218838;
|
||||
border-color: #1e7e34;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal {
|
||||
position: fixed;
|
||||
|
||||
5
influence/app/public/favicon.ico
Normal file
5
influence/app/public/favicon.ico
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Minimal favicon to prevent 404 errors -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<circle cx="16" cy="16" r="14" fill="#007bff"/>
|
||||
<text x="16" y="20" text-anchor="middle" fill="white" font-family="Arial" font-size="16" font-weight="bold">I</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 283 B |
@@ -3,14 +3,14 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Alberta Influence Campaign Tool</title>
|
||||
<title>BNKops Influence Campaign Tool</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Alberta Influence Campaign Tool</h1>
|
||||
<h1>BNKops Influence Tool</h1>
|
||||
<p>Connect with your elected representatives across all levels of government</p>
|
||||
</header>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<h2>Find Your Representatives</h2>
|
||||
<form id="postal-form">
|
||||
<div class="form-group">
|
||||
<label for="postal-code">Enter your Alberta postal code:</label>
|
||||
<label for="postal-code">Enter your postal code:</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -49,8 +49,8 @@ class CampaignPage {
|
||||
|
||||
renderCampaign() {
|
||||
// Update page title and header
|
||||
document.title = `${this.campaign.title} - Alberta Influence Tool`;
|
||||
document.getElementById('page-title').textContent = `${this.campaign.title} - Alberta Influence Tool`;
|
||||
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;
|
||||
|
||||
@@ -74,6 +74,15 @@ class CampaignPage {
|
||||
// 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);
|
||||
}
|
||||
@@ -131,9 +140,38 @@ class CampaignPage {
|
||||
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...');
|
||||
|
||||
@@ -180,10 +218,11 @@ class CampaignPage {
|
||||
<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>
|
||||
${rep.email ? `
|
||||
<div class="rep-actions">
|
||||
<div class="rep-actions">
|
||||
${rep.email ? `
|
||||
<button class="btn btn-primary" data-action="send-email"
|
||||
data-email="${rep.email}"
|
||||
data-name="${rep.name}"
|
||||
@@ -191,8 +230,18 @@ class CampaignPage {
|
||||
data-level="${this.getGovernmentLevel(rep)}">
|
||||
Send Email
|
||||
</button>
|
||||
</div>
|
||||
` : '<p style="text-align: center; color: #6c757d;">No email available</p>'}
|
||||
` : ''}
|
||||
${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('');
|
||||
|
||||
@@ -214,6 +263,17 @@ class CampaignPage {
|
||||
});
|
||||
});
|
||||
|
||||
// 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) {
|
||||
@@ -232,6 +292,67 @@ class CampaignPage {
|
||||
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;
|
||||
|
||||
|
||||
@@ -107,6 +107,10 @@ class RepresentativesDisplay {
|
||||
const party = rep.party_name || 'Party not specified';
|
||||
const photoUrl = rep.photo_url || null;
|
||||
|
||||
// Extract phone numbers from offices array
|
||||
const phoneNumbers = this.extractPhoneNumbers(rep.offices || []);
|
||||
const primaryPhone = phoneNumbers.length > 0 ? phoneNumbers[0] : null;
|
||||
|
||||
const emailButton = email ?
|
||||
`<button class="btn btn-primary compose-email"
|
||||
data-email="${email}"
|
||||
@@ -117,6 +121,16 @@ class RepresentativesDisplay {
|
||||
</button>` :
|
||||
'<span class="text-muted">No email available</span>';
|
||||
|
||||
// Add call button if phone number is available
|
||||
const callButton = primaryPhone ?
|
||||
`<button class="btn btn-success call-representative"
|
||||
data-phone="${primaryPhone.number}"
|
||||
data-office-type="${primaryPhone.type}"
|
||||
data-name="${name}"
|
||||
data-office="${office}">
|
||||
📞 Call
|
||||
</button>` : '';
|
||||
|
||||
const profileUrl = rep.url ?
|
||||
`<a href="${rep.url}" target="_blank" class="btn btn-secondary">View Profile</a>` : '';
|
||||
|
||||
@@ -155,9 +169,11 @@ class RepresentativesDisplay {
|
||||
<p><strong>District:</strong> ${district}</p>
|
||||
${party !== 'Party not specified' ? `<p><strong>Party:</strong> ${party}</p>` : ''}
|
||||
${email ? `<p><strong>Email:</strong> ${email}</p>` : ''}
|
||||
${primaryPhone ? `<p><strong>Phone:</strong> ${primaryPhone.number} ${primaryPhone.type ? `(${primaryPhone.type})` : ''}</p>` : ''}
|
||||
</div>
|
||||
<div class="rep-actions">
|
||||
${emailButton}
|
||||
${callButton}
|
||||
${profileUrl}
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,6 +181,23 @@ class RepresentativesDisplay {
|
||||
`;
|
||||
}
|
||||
|
||||
extractPhoneNumbers(offices) {
|
||||
const phoneNumbers = [];
|
||||
|
||||
if (Array.isArray(offices)) {
|
||||
offices.forEach(office => {
|
||||
if (office.tel) {
|
||||
phoneNumbers.push({
|
||||
number: office.tel,
|
||||
type: office.type || 'office'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return phoneNumbers;
|
||||
}
|
||||
|
||||
attachEventListeners() {
|
||||
// Add event listeners for compose email buttons
|
||||
const composeButtons = this.container.querySelectorAll('.compose-email');
|
||||
@@ -183,6 +216,36 @@ class RepresentativesDisplay {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Add event listeners for call buttons
|
||||
const callButtons = this.container.querySelectorAll('.call-representative');
|
||||
callButtons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
const phone = e.target.dataset.phone;
|
||||
const name = e.target.dataset.name;
|
||||
const office = e.target.dataset.office;
|
||||
const officeType = e.target.dataset.officeType;
|
||||
|
||||
this.handleCallClick(phone, name, office, officeType);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleCallClick(phone, name, office, 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}${officeInfo}?\n\nPhone: ${phone}`;
|
||||
|
||||
if (confirm(message)) {
|
||||
// Attempt to initiate the call
|
||||
window.location.href = telLink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Login - Alberta Influence Campaign Tool</title>
|
||||
<title>Admin Login - BNKops Influence Campaign Tool</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user