A tonne of updates; site info, documentation, campaign phone numbers, soccial share buttons, and several other things

This commit is contained in:
2025-10-04 12:25:25 -06:00
parent e1daa57e79
commit ea9c38d860
77 changed files with 6383 additions and 2425 deletions

View File

@@ -90,6 +90,82 @@ async function cacheRepresentatives(postalCode, representatives, representData)
}
class CampaignsController {
// Get public campaigns (no authentication required)
async getPublicCampaigns(req, res, next) {
try {
const campaigns = await nocoDB.getAllCampaigns();
// Filter to only active campaigns and normalize data structure
const activeCampaigns = await Promise.all(
campaigns
.filter(campaign => {
const status = normalizeStatus(campaign['Status'] || campaign.status);
return status === 'active';
})
.map(async (campaign) => {
const id = campaign.ID || campaign.Id || campaign.id;
// Debug: Log specific fields we're looking for
console.log(`Campaign ${id}:`, {
'Show Call Count': campaign['Show Call Count'],
'show_call_count': campaign.show_call_count,
'Show Email Count': campaign['Show Email Count'],
'show_email_count': campaign.show_email_count
});
// Get email count if show_email_count is enabled
let emailCount = null;
const showEmailCount = campaign['Show Email Count'] || campaign.show_email_count;
console.log(`Getting email count for campaign ID: ${id}, showEmailCount: ${showEmailCount}`);
if (showEmailCount && id != null) {
emailCount = await nocoDB.getCampaignEmailCount(id);
console.log(`Email count result: ${emailCount}`);
}
// Get call count if show_call_count is enabled
let callCount = null;
const showCallCount = campaign['Show Call Count'] || campaign.show_call_count;
console.log(`Getting call count for campaign ID: ${id}, showCallCount: ${showCallCount}`);
if (showCallCount && id != null) {
callCount = await nocoDB.getCampaignCallCount(id);
console.log(`Call count result: ${callCount}`);
}
const rawTargetLevels = campaign['Target Government Levels'] || campaign.target_government_levels;
const normalizedTargetLevels = normalizeTargetLevels(rawTargetLevels);
// Return only public-facing information
return {
id,
slug: campaign['Campaign Slug'] || campaign.slug,
title: campaign['Campaign Title'] || campaign.title,
description: campaign['Description'] || campaign.description,
call_to_action: campaign['Call to Action'] || campaign.call_to_action,
cover_photo: campaign['Cover Photo'] || campaign.cover_photo,
show_email_count: showEmailCount,
show_call_count: showCallCount,
target_government_levels: normalizedTargetLevels,
created_at: campaign.CreatedAt || campaign.created_at,
emailCount,
callCount
};
})
);
res.json({
success: true,
campaigns: activeCampaigns
});
} catch (error) {
console.error('Get public campaigns error:', error);
res.status(500).json({
success: false,
error: 'Failed to retrieve campaigns',
message: error.message
});
}
}
// Get all campaigns (for admin panel)
async getAllCampaigns(req, res, next) {
try {
@@ -249,9 +325,23 @@ class CampaignsController {
let emailCount = null;
const showEmailCount = campaign['Show Email Count'] || campaign.show_email_count;
if (showEmailCount) {
const id = campaign.Id ?? campaign.id;
const id = campaign.ID || campaign.Id || campaign.id;
console.log('Getting email count for campaign ID:', id);
if (id != null) {
emailCount = await nocoDB.getCampaignEmailCount(id);
console.log('Email count result:', emailCount);
}
}
// Get call count if enabled
let callCount = null;
const showCallCount = campaign['Show Call Count'] || campaign.show_call_count;
if (showCallCount) {
const id = campaign.ID || campaign.Id || campaign.id;
console.log('Getting call count for campaign ID:', id);
if (id != null) {
callCount = await nocoDB.getCampaignCallCount(id);
console.log('Call count result:', callCount);
}
}
@@ -274,9 +364,11 @@ class CampaignsController {
allow_mailto_link: campaign['Allow Mailto Link'] || campaign.allow_mailto_link,
collect_user_info: campaign['Collect User Info'] || campaign.collect_user_info,
show_email_count: campaign['Show Email Count'] || campaign.show_email_count,
show_call_count: campaign['Show Call Count'] || campaign.show_call_count,
allow_email_editing: campaign['Allow Email Editing'] || campaign.allow_email_editing,
target_government_levels: normalizeTargetLevels(campaign['Target Government Levels'] || campaign.target_government_levels),
emailCount
emailCount,
callCount
}
});
} catch (error) {
@@ -419,6 +511,10 @@ class CampaignsController {
}
}
// Track old slug for cascade updates
const oldSlug = existingCampaign['Campaign Slug'] || existingCampaign.slug;
let newSlug = oldSlug;
if (updates.title) {
let slug = generateSlug(updates.title);
@@ -433,6 +529,7 @@ class CampaignsController {
}
}
updates.slug = slug;
newSlug = slug;
}
if (updates.target_government_levels !== undefined) {
@@ -472,6 +569,19 @@ class CampaignsController {
const campaign = await nocoDB.updateCampaign(id, updates);
// If slug changed, update references in related tables
if (oldSlug && newSlug && oldSlug !== newSlug) {
console.log(`Campaign slug changed from '${oldSlug}' to '${newSlug}', updating references...`);
const cascadeResult = await nocoDB.updateCampaignSlugReferences(id, oldSlug, newSlug);
if (cascadeResult.success) {
console.log(`Successfully updated slug references: ${cascadeResult.updatedCampaignEmails} campaign emails, ${cascadeResult.updatedCallLogs} call logs`);
} else {
console.warn(`Failed to update some slug references:`, cascadeResult.error);
// Don't fail the main update - cascade is a best-effort operation
}
}
res.json({
success: true,
campaign: {
@@ -892,6 +1002,74 @@ class CampaignsController {
});
}
}
// Track campaign phone call
async trackCampaignCall(req, res, next) {
try {
const { slug } = req.params;
const {
representativeName,
representativeTitle,
phoneNumber,
officeType,
userEmail,
userName,
postalCode
} = req.body;
// Validate required fields
if (!representativeName || !phoneNumber) {
return res.status(400).json({
success: false,
error: 'Representative name and phone number are required'
});
}
// Get campaign
const campaign = await nocoDB.getCampaignBySlug(slug);
if (!campaign) {
return res.status(404).json({
success: false,
error: 'Campaign not found'
});
}
const campaignStatus = normalizeStatus(campaign['Status'] || campaign.status);
if (campaignStatus !== 'active') {
return res.status(403).json({
success: false,
error: 'Campaign is not currently active'
});
}
// Log the call
await nocoDB.logCall({
representativeName,
representativeTitle: representativeTitle || null,
phoneNumber,
officeType: officeType || null,
callerName: userName || null,
callerEmail: userEmail || null,
postalCode: postalCode || null,
campaignId: campaign.ID || campaign.Id || campaign.id,
campaignSlug: slug,
callerIP: req.ip || req.connection?.remoteAddress || null,
timestamp: new Date().toISOString()
});
res.json({
success: true,
message: 'Call tracked successfully'
});
} catch (error) {
console.error('Track campaign call error:', error);
res.status(500).json({
success: false,
error: 'Failed to track call',
message: error.message
});
}
}
}
// Export controller instance and upload middleware

View File

@@ -180,6 +180,55 @@ class RepresentativesController {
});
}
}
async trackCall(req, res, next) {
try {
const {
representativeName,
representativeTitle,
phoneNumber,
officeType,
userEmail,
userName,
postalCode
} = req.body;
// Validate required fields
if (!representativeName || !phoneNumber) {
return res.status(400).json({
success: false,
error: 'Representative name and phone number are required'
});
}
// Log the call
await nocoDB.logCall({
representativeName,
representativeTitle: representativeTitle || null,
phoneNumber,
officeType: officeType || null,
callerName: userName || null,
callerEmail: userEmail || null,
postalCode: postalCode || null,
campaignId: null,
campaignSlug: null,
callerIP: req.ip || req.connection?.remoteAddress || null,
timestamp: new Date().toISOString()
});
res.json({
success: true,
message: 'Call tracked successfully'
});
} catch (error) {
console.error('Track call error:', error);
res.status(500).json({
success: false,
error: 'Failed to track call',
message: error.message
});
}
}
}
module.exports = new RepresentativesController();