Updated campagin cover photos
This commit is contained in:
@@ -84,13 +84,55 @@
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.campaign-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.campaign-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0;
|
||||
background: white;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.campaign-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.campaign-card-cover {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
min-height: 180px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.campaign-card-cover-overlay {
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 60%, transparent 100%);
|
||||
width: 100%;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.campaign-card-cover-overlay h3 {
|
||||
margin: 0;
|
||||
color: white;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.campaign-card > .campaign-meta,
|
||||
.campaign-card > .campaign-actions {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.campaign-header {
|
||||
@@ -98,6 +140,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.campaign-header h3 {
|
||||
@@ -150,7 +193,7 @@
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
@@ -700,7 +743,7 @@
|
||||
<p>Loading campaigns...</p>
|
||||
</div>
|
||||
|
||||
<div id="campaigns-list" class="campaign-list">
|
||||
<div id="campaigns-list" class="campaign-grid">
|
||||
<!-- Campaigns will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -770,6 +813,12 @@ Sincerely,
|
||||
placeholder="Join thousands of Albertans in protecting our provincial parks. Send an email to your representatives today!"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="create-cover-photo">🖼️ Cover Photo</label>
|
||||
<input type="file" id="create-cover-photo" name="cover_photo" accept="image/jpeg,image/jpg,image/png,image/gif,image/webp">
|
||||
<small style="color: #666; font-size: 0.9em;">Upload a cover image for your campaign (max 5MB, jpeg/jpg/png/gif/webp)</small>
|
||||
</div>
|
||||
|
||||
<div class="section-header">⚙️ Campaign Settings</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox-group">
|
||||
@@ -880,6 +929,13 @@ Sincerely,
|
||||
<textarea id="edit-call-to-action" name="call_to_action" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-cover-photo">🖼️ Cover Photo</label>
|
||||
<div id="current-cover-photo" style="margin-bottom: 0.5rem;"></div>
|
||||
<input type="file" id="edit-cover-photo" name="cover_photo" accept="image/jpeg,image/jpg,image/png,image/gif,image/webp">
|
||||
<small style="color: #666; font-size: 0.9em;">Upload a new cover image (max 5MB, jpeg/jpg/png/gif/webp) or leave empty to keep current</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Campaign Settings</label>
|
||||
<div class="checkbox-group">
|
||||
|
||||
@@ -12,11 +12,40 @@
|
||||
padding: 3rem 0;
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.campaign-header.has-cover {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
min-height: 350px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.campaign-header.has-cover::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.campaign-header > * {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.campaign-header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.campaign-stats {
|
||||
|
||||
@@ -85,10 +85,11 @@
|
||||
.campaign-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
padding: 0;
|
||||
background: white;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.campaign-card:hover {
|
||||
@@ -96,11 +97,42 @@
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.campaign-card-cover {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
min-height: 180px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.campaign-card-cover-overlay {
|
||||
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 60%, transparent 100%);
|
||||
width: 100%;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.campaign-card-cover-overlay h3 {
|
||||
margin: 0;
|
||||
color: white;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.campaign-card > .campaign-meta,
|
||||
.campaign-card > .campaign-actions {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.campaign-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.campaign-header h3 {
|
||||
|
||||
@@ -467,10 +467,19 @@ class AdminPanel {
|
||||
|
||||
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>
|
||||
${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>
|
||||
@@ -531,22 +540,34 @@ class AdminPanel {
|
||||
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',
|
||||
target_government_levels: Array.from(formData.getAll('target_government_levels'))
|
||||
};
|
||||
|
||||
// Convert checkboxes to boolean values
|
||||
const campaignFormData = new FormData();
|
||||
campaignFormData.append('title', formData.get('title'));
|
||||
campaignFormData.append('description', formData.get('description') || '');
|
||||
campaignFormData.append('email_subject', formData.get('email_subject'));
|
||||
campaignFormData.append('email_body', formData.get('email_body'));
|
||||
campaignFormData.append('call_to_action', formData.get('call_to_action') || '');
|
||||
campaignFormData.append('status', formData.get('status'));
|
||||
campaignFormData.append('allow_smtp_email', formData.get('allow_smtp_email') === 'on');
|
||||
campaignFormData.append('allow_mailto_link', formData.get('allow_mailto_link') === 'on');
|
||||
campaignFormData.append('collect_user_info', formData.get('collect_user_info') === 'on');
|
||||
campaignFormData.append('show_email_count', formData.get('show_email_count') === 'on');
|
||||
|
||||
// Handle target_government_levels array
|
||||
const targetLevels = Array.from(formData.getAll('target_government_levels'));
|
||||
targetLevels.forEach(level => {
|
||||
campaignFormData.append('target_government_levels', level);
|
||||
});
|
||||
|
||||
// Handle cover photo file upload
|
||||
const coverPhotoFile = formData.get('cover_photo');
|
||||
if (coverPhotoFile && coverPhotoFile.size > 0) {
|
||||
campaignFormData.append('cover_photo', coverPhotoFile);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await window.apiClient.post('/admin/campaigns', campaignData);
|
||||
const response = await window.apiClient.postFormData('/admin/campaigns', campaignFormData);
|
||||
|
||||
if (response.success) {
|
||||
this.showMessage('Campaign created successfully!', 'success');
|
||||
@@ -584,6 +605,19 @@ class AdminPanel {
|
||||
// Status select
|
||||
form.querySelector('[name="status"]').value = campaign.status || 'draft';
|
||||
|
||||
// Show current cover photo if exists
|
||||
const currentCoverDiv = document.getElementById('current-cover-photo');
|
||||
if (campaign.cover_photo) {
|
||||
currentCoverDiv.innerHTML = `
|
||||
<div style="margin-bottom: 0.5rem;">
|
||||
<small style="color: #666;">Current cover photo:</small><br>
|
||||
<img src="/uploads/${campaign.cover_photo}" alt="Current cover" style="max-width: 200px; max-height: 150px; border-radius: 4px; margin-top: 0.25rem;">
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
currentCoverDiv.innerHTML = '<small style="color: #999;">No cover photo uploaded</small>';
|
||||
}
|
||||
|
||||
// Checkboxes
|
||||
form.querySelector('[name="allow_smtp_email"]').checked = campaign.allow_smtp_email;
|
||||
form.querySelector('[name="allow_mailto_link"]').checked = campaign.allow_mailto_link;
|
||||
@@ -612,25 +646,34 @@ class AdminPanel {
|
||||
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'))
|
||||
};
|
||||
|
||||
// Convert checkboxes to boolean values and build FormData for upload
|
||||
const updateFormData = new FormData();
|
||||
updateFormData.append('title', formData.get('title'));
|
||||
updateFormData.append('description', formData.get('description') || '');
|
||||
updateFormData.append('email_subject', formData.get('email_subject'));
|
||||
updateFormData.append('email_body', formData.get('email_body'));
|
||||
updateFormData.append('call_to_action', formData.get('call_to_action') || '');
|
||||
updateFormData.append('status', formData.get('status'));
|
||||
updateFormData.append('allow_smtp_email', formData.get('allow_smtp_email') === 'on');
|
||||
updateFormData.append('allow_mailto_link', formData.get('allow_mailto_link') === 'on');
|
||||
updateFormData.append('collect_user_info', formData.get('collect_user_info') === 'on');
|
||||
updateFormData.append('show_email_count', formData.get('show_email_count') === 'on');
|
||||
|
||||
// Handle target_government_levels array
|
||||
const targetLevels = Array.from(formData.getAll('target_government_levels'));
|
||||
targetLevels.forEach(level => {
|
||||
updateFormData.append('target_government_levels', level);
|
||||
});
|
||||
|
||||
// Handle cover photo file upload if a new one is selected
|
||||
const coverPhotoFile = formData.get('cover_photo');
|
||||
if (coverPhotoFile && coverPhotoFile.size > 0) {
|
||||
updateFormData.append('cover_photo', coverPhotoFile);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await window.apiClient.makeRequest(`/admin/campaigns/${this.currentCampaign.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(updates)
|
||||
});
|
||||
const response = await window.apiClient.putFormData(`/admin/campaigns/${this.currentCampaign.id}`, updateFormData);
|
||||
|
||||
if (response.success) {
|
||||
this.showMessage('Campaign updated successfully!', 'success');
|
||||
|
||||
@@ -45,6 +45,56 @@ class APIClient {
|
||||
});
|
||||
}
|
||||
|
||||
async postFormData(endpoint, formData) {
|
||||
// Don't set Content-Type header - browser will set it with boundary for multipart/form-data
|
||||
const config = {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, config);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
const error = new Error(data.error || data.message || `HTTP ${response.status}`);
|
||||
error.status = response.status;
|
||||
error.data = data;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('API request failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async putFormData(endpoint, formData) {
|
||||
// Don't set Content-Type header - browser will set it with boundary for multipart/form-data
|
||||
const config = {
|
||||
method: 'PUT',
|
||||
body: formData
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseURL}${endpoint}`, config);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
const error = new Error(data.error || data.message || `HTTP ${response.status}`);
|
||||
error.status = response.status;
|
||||
error.data = data;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('API request failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Health check
|
||||
async checkHealth() {
|
||||
return this.get('/health');
|
||||
|
||||
@@ -39,6 +39,8 @@ class CampaignPage {
|
||||
}
|
||||
|
||||
this.campaign = data.campaign;
|
||||
console.log('Campaign data loaded:', this.campaign);
|
||||
console.log('Cover photo value:', this.campaign.cover_photo);
|
||||
this.renderCampaign();
|
||||
} catch (error) {
|
||||
this.showError('Failed to load campaign: ' + error.message);
|
||||
@@ -54,6 +56,25 @@ class CampaignPage {
|
||||
document.getElementById('campaign-title').textContent = this.campaign.title;
|
||||
document.getElementById('campaign-description').textContent = this.campaign.description;
|
||||
|
||||
// Add cover photo if available
|
||||
const headerElement = document.querySelector('.campaign-header');
|
||||
if (this.campaign.cover_photo) {
|
||||
// Clean the cover_photo value - it should just be a filename
|
||||
const coverPhotoFilename = String(this.campaign.cover_photo).trim();
|
||||
console.log('Cover photo filename:', coverPhotoFilename);
|
||||
|
||||
if (coverPhotoFilename && coverPhotoFilename !== 'null' && coverPhotoFilename !== 'undefined') {
|
||||
headerElement.classList.add('has-cover');
|
||||
headerElement.style.backgroundImage = `url('/uploads/${coverPhotoFilename}')`;
|
||||
} else {
|
||||
headerElement.classList.remove('has-cover');
|
||||
headerElement.style.backgroundImage = '';
|
||||
}
|
||||
} else {
|
||||
headerElement.classList.remove('has-cover');
|
||||
headerElement.style.backgroundImage = '';
|
||||
}
|
||||
|
||||
// Show email count if enabled
|
||||
if (this.campaign.show_email_count && this.campaign.emailCount !== null) {
|
||||
document.getElementById('email-count').textContent = this.campaign.emailCount;
|
||||
|
||||
@@ -482,10 +482,19 @@ class UserDashboard {
|
||||
|
||||
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>
|
||||
${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>
|
||||
|
||||
Reference in New Issue
Block a user