366 lines
11 KiB
JavaScript
366 lines
11 KiB
JavaScript
// Response Wall JavaScript
|
|
|
|
let currentCampaignSlug = null;
|
|
let currentOffset = 0;
|
|
let currentSort = 'recent';
|
|
let currentLevel = '';
|
|
const LIMIT = 20;
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('Response Wall: Initializing...');
|
|
|
|
// Get campaign slug from URL if present
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
currentCampaignSlug = urlParams.get('campaign');
|
|
|
|
console.log('Campaign slug:', currentCampaignSlug);
|
|
|
|
if (!currentCampaignSlug) {
|
|
showError('No campaign specified');
|
|
return;
|
|
}
|
|
|
|
// Load initial data
|
|
loadResponseStats();
|
|
loadResponses(true);
|
|
|
|
// Set up event listeners
|
|
document.getElementById('sort-select').addEventListener('change', (e) => {
|
|
currentSort = e.target.value;
|
|
loadResponses(true);
|
|
});
|
|
|
|
document.getElementById('level-filter').addEventListener('change', (e) => {
|
|
currentLevel = e.target.value;
|
|
loadResponses(true);
|
|
});
|
|
|
|
const submitBtn = document.getElementById('submit-response-btn');
|
|
console.log('Submit button found:', !!submitBtn);
|
|
if (submitBtn) {
|
|
submitBtn.addEventListener('click', () => {
|
|
console.log('Submit button clicked');
|
|
openSubmitModal();
|
|
});
|
|
}
|
|
|
|
// Use event delegation for empty state button since it's dynamically shown
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.id === 'empty-state-submit-btn') {
|
|
console.log('Empty state button clicked');
|
|
openSubmitModal();
|
|
}
|
|
});
|
|
|
|
const modalCloseBtn = document.getElementById('modal-close-btn');
|
|
if (modalCloseBtn) {
|
|
modalCloseBtn.addEventListener('click', closeSubmitModal);
|
|
}
|
|
|
|
const cancelBtn = document.getElementById('cancel-submit-btn');
|
|
if (cancelBtn) {
|
|
cancelBtn.addEventListener('click', closeSubmitModal);
|
|
}
|
|
|
|
const loadMoreBtn = document.getElementById('load-more-btn');
|
|
if (loadMoreBtn) {
|
|
loadMoreBtn.addEventListener('click', loadMoreResponses);
|
|
}
|
|
|
|
const form = document.getElementById('submit-response-form');
|
|
if (form) {
|
|
form.addEventListener('submit', handleSubmitResponse);
|
|
}
|
|
|
|
console.log('Response Wall: Initialization complete');
|
|
});
|
|
|
|
// Load response statistics
|
|
async function loadResponseStats() {
|
|
try {
|
|
const response = await fetch(`/api/campaigns/${currentCampaignSlug}/response-stats`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
document.getElementById('stat-total-responses').textContent = data.stats.totalResponses;
|
|
document.getElementById('stat-verified').textContent = data.stats.verifiedResponses;
|
|
document.getElementById('stat-upvotes').textContent = data.stats.totalUpvotes;
|
|
document.getElementById('stats-banner').style.display = 'flex';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading stats:', error);
|
|
}
|
|
}
|
|
|
|
// Load responses
|
|
async function loadResponses(reset = false) {
|
|
if (reset) {
|
|
currentOffset = 0;
|
|
document.getElementById('responses-container').innerHTML = '';
|
|
}
|
|
|
|
showLoading(true);
|
|
|
|
try {
|
|
const params = new URLSearchParams({
|
|
sort: currentSort,
|
|
level: currentLevel,
|
|
offset: currentOffset,
|
|
limit: LIMIT
|
|
});
|
|
|
|
const response = await fetch(`/api/campaigns/${currentCampaignSlug}/responses?${params}`);
|
|
const data = await response.json();
|
|
|
|
showLoading(false);
|
|
|
|
if (data.success && data.responses.length > 0) {
|
|
renderResponses(data.responses);
|
|
|
|
// Show/hide load more button
|
|
if (data.pagination.hasMore) {
|
|
document.getElementById('load-more-container').style.display = 'block';
|
|
} else {
|
|
document.getElementById('load-more-container').style.display = 'none';
|
|
}
|
|
} else if (reset) {
|
|
showEmptyState();
|
|
}
|
|
} catch (error) {
|
|
showLoading(false);
|
|
showError('Failed to load responses');
|
|
console.error('Error loading responses:', error);
|
|
}
|
|
}
|
|
|
|
// Render responses
|
|
function renderResponses(responses) {
|
|
const container = document.getElementById('responses-container');
|
|
|
|
responses.forEach(response => {
|
|
const card = createResponseCard(response);
|
|
container.appendChild(card);
|
|
});
|
|
}
|
|
|
|
// Create response card element
|
|
function createResponseCard(response) {
|
|
const card = document.createElement('div');
|
|
card.className = 'response-card';
|
|
card.dataset.responseId = response.id;
|
|
|
|
const createdDate = new Date(response.created_at).toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
});
|
|
|
|
let badges = `<span class="badge badge-level">${escapeHtml(response.representative_level)}</span>`;
|
|
badges += `<span class="badge badge-type">${escapeHtml(response.response_type)}</span>`;
|
|
if (response.is_verified) {
|
|
badges = `<span class="badge badge-verified">✓ Verified</span>` + badges;
|
|
}
|
|
|
|
let submittedBy = 'Anonymous';
|
|
if (!response.is_anonymous && response.submitted_by_name) {
|
|
submittedBy = escapeHtml(response.submitted_by_name);
|
|
}
|
|
|
|
let userCommentHtml = '';
|
|
if (response.user_comment) {
|
|
userCommentHtml = `
|
|
<div class="user-comment">
|
|
<span class="user-comment-label">Constituent's Comment:</span>
|
|
${escapeHtml(response.user_comment)}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
let screenshotHtml = '';
|
|
if (response.screenshot_url) {
|
|
screenshotHtml = `
|
|
<div class="response-screenshot">
|
|
<img src="${escapeHtml(response.screenshot_url)}"
|
|
alt="Response screenshot"
|
|
data-image-url="${escapeHtml(response.screenshot_url)}"
|
|
class="screenshot-image">
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
const upvoteClass = response.hasUpvoted ? 'upvoted' : '';
|
|
|
|
card.innerHTML = `
|
|
<div class="response-header">
|
|
<div class="response-rep-info">
|
|
<h3>${escapeHtml(response.representative_name)}</h3>
|
|
<div class="rep-meta">
|
|
${response.representative_title ? `<span>${escapeHtml(response.representative_title)}</span>` : ''}
|
|
<span>${createdDate}</span>
|
|
</div>
|
|
</div>
|
|
<div class="response-badges">
|
|
${badges}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="response-content">
|
|
<div class="response-text">${escapeHtml(response.response_text)}</div>
|
|
${userCommentHtml}
|
|
${screenshotHtml}
|
|
</div>
|
|
|
|
<div class="response-footer">
|
|
<div class="response-meta">
|
|
Submitted by ${submittedBy}
|
|
</div>
|
|
<div class="response-actions">
|
|
<button class="upvote-btn ${upvoteClass}" data-response-id="${response.id}">
|
|
<span class="upvote-icon">👍</span>
|
|
<span class="upvote-count">${response.upvote_count || 0}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add event listener for upvote button
|
|
const upvoteBtn = card.querySelector('.upvote-btn');
|
|
upvoteBtn.addEventListener('click', function() {
|
|
toggleUpvote(response.id, this);
|
|
});
|
|
|
|
// Add event listener for screenshot image if present
|
|
const screenshotImg = card.querySelector('.screenshot-image');
|
|
if (screenshotImg) {
|
|
screenshotImg.addEventListener('click', function() {
|
|
viewImage(this.dataset.imageUrl);
|
|
});
|
|
}
|
|
|
|
return card;
|
|
}
|
|
|
|
// Toggle upvote
|
|
async function toggleUpvote(responseId, button) {
|
|
const isUpvoted = button.classList.contains('upvoted');
|
|
const url = `/api/responses/${responseId}/upvote`;
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
method: isUpvoted ? 'DELETE' : 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
// Update button state
|
|
button.classList.toggle('upvoted');
|
|
button.querySelector('.upvote-count').textContent = data.upvoteCount;
|
|
|
|
// Reload stats
|
|
loadResponseStats();
|
|
} else {
|
|
showError(data.error || 'Failed to update upvote');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error toggling upvote:', error);
|
|
showError('Failed to update upvote');
|
|
}
|
|
}
|
|
|
|
// Load more responses
|
|
function loadMoreResponses() {
|
|
currentOffset += LIMIT;
|
|
loadResponses(false);
|
|
}
|
|
|
|
// Open submit modal
|
|
function openSubmitModal() {
|
|
console.log('openSubmitModal called');
|
|
const modal = document.getElementById('submit-modal');
|
|
if (modal) {
|
|
modal.style.display = 'block';
|
|
console.log('Modal displayed');
|
|
} else {
|
|
console.error('Modal element not found');
|
|
}
|
|
}
|
|
|
|
// Close submit modal
|
|
function closeSubmitModal() {
|
|
document.getElementById('submit-modal').style.display = 'none';
|
|
document.getElementById('submit-response-form').reset();
|
|
}
|
|
|
|
// Handle response submission
|
|
async function handleSubmitResponse(e) {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(e.target);
|
|
|
|
try {
|
|
const response = await fetch(`/api/campaigns/${currentCampaignSlug}/responses`, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showSuccess(data.message || 'Response submitted successfully! It will appear after moderation.');
|
|
closeSubmitModal();
|
|
// Don't reload responses since submission is pending approval
|
|
} else {
|
|
showError(data.error || 'Failed to submit response');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error submitting response:', error);
|
|
showError('Failed to submit response');
|
|
}
|
|
}
|
|
|
|
// View image in modal/new tab
|
|
function viewImage(url) {
|
|
window.open(url, '_blank');
|
|
}
|
|
|
|
// Utility functions
|
|
function showLoading(show) {
|
|
document.getElementById('loading').style.display = show ? 'block' : 'none';
|
|
}
|
|
|
|
function showEmptyState() {
|
|
document.getElementById('empty-state').style.display = 'block';
|
|
document.getElementById('responses-container').innerHTML = '';
|
|
document.getElementById('load-more-container').style.display = 'none';
|
|
}
|
|
|
|
function showError(message) {
|
|
// Simple alert for now - could integrate with existing error display system
|
|
alert('Error: ' + message);
|
|
}
|
|
|
|
function showSuccess(message) {
|
|
// Simple alert for now - could integrate with existing success display system
|
|
alert(message);
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
if (!text) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// Close modal when clicking outside
|
|
window.onclick = function(event) {
|
|
const modal = document.getElementById('submit-modal');
|
|
if (event.target === modal) {
|
|
closeSubmitModal();
|
|
}
|
|
};
|