Refactored Community Events page to be dynamic and API-driven. Implemented a vertical carousel with category filtering and color-coded event cards. Added new data pipeline to fetch event details from Zoho Calendar API at build time. Fixed infinite rebuild loop in Eleventy by configuring the file watcher to ignore the intermediary data file.
173 lines
6.9 KiB
JavaScript
173 lines
6.9 KiB
JavaScript
require('dotenv').config();
|
||
const fetch = require('node-fetch');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const calendarUid = process.env.ZOHO_CALENDAR_UID;
|
||
let accessToken = process.env.ZOHO_ACCESS_TOKEN;
|
||
const refreshToken = process.env.ZOHO_REFRESH_TOKEN;
|
||
const clientId = process.env.ZOHO_CLIENT_ID;
|
||
const clientSecret = process.env.ZOHO_CLIENT_SECRET;
|
||
|
||
// Dates for current month
|
||
const currentDate = new Date();
|
||
const currentYear = currentDate.getFullYear();
|
||
const currentMonth = (currentDate.getMonth() + 1).toString().padStart(2, '0');
|
||
const lastDay = new Date(currentYear, currentDate.getMonth() + 1, 0).getDate();
|
||
const startDate = `${currentYear}${currentMonth}01`;
|
||
const endDate = `${currentYear}${currentMonth}${lastDay}`;
|
||
|
||
const zohoApiUrl = `https://calendar.zoho.com/api/v1/calendars/${calendarUid}`;
|
||
|
||
async function refreshAccessToken() {
|
||
const params = new URLSearchParams();
|
||
params.append('grant_type', 'refresh_token');
|
||
params.append('client_id', clientId);
|
||
params.append('client_secret', clientSecret);
|
||
params.append('refresh_token', refreshToken);
|
||
|
||
const response = await fetch('https://accounts.zoho.com/oauth/v2/token', {
|
||
method: 'POST',
|
||
body: params
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (!data.access_token) {
|
||
throw new Error('Failed to refresh access token: ' + JSON.stringify(data));
|
||
}
|
||
|
||
const envPath = path.resolve(process.cwd(), '.env');
|
||
const envContents = fs.readFileSync(envPath, 'utf-8')
|
||
.split('\n')
|
||
.map(line => line.startsWith('ZOHO_ACCESS_TOKEN=') ? `ZOHO_ACCESS_TOKEN=${data.access_token}` : line)
|
||
.join('\n');
|
||
fs.writeFileSync(envPath, envContents, 'utf-8');
|
||
|
||
accessToken = data.access_token;
|
||
}
|
||
|
||
function parseZohoTimestamp(start, isAllDay) {
|
||
if (isAllDay) {
|
||
const year = parseInt(start.slice(0, 4));
|
||
const month = parseInt(start.slice(4, 6)) - 1;
|
||
const day = parseInt(start.slice(6, 8));
|
||
return new Date(year, month, day);
|
||
} else {
|
||
const year = parseInt(start.slice(0, 4));
|
||
const month = parseInt(start.slice(4, 6)) - 1;
|
||
const day = parseInt(start.slice(6, 8));
|
||
const hour = parseInt(start.slice(9, 11));
|
||
const minute = parseInt(start.slice(11, 13));
|
||
const second = parseInt(start.slice(13, 15));
|
||
return new Date(year, month, day, hour, minute, second);
|
||
}
|
||
}
|
||
|
||
function formatEventDate(startDate, endDate, isAllDay) {
|
||
const start = parseZohoTimestamp(startDate, isAllDay);
|
||
if (isAllDay) {
|
||
return start.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
|
||
} else {
|
||
const end = parseZohoTimestamp(endDate, isAllDay);
|
||
const optionsDate = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
|
||
const optionsTime = { hour: 'numeric', minute: '2-digit', hour12: true };
|
||
const dateStr = start.toLocaleDateString('en-US', optionsDate);
|
||
const startTimeStr = start.toLocaleTimeString('en-US', optionsTime);
|
||
const endTimeStr = end.toLocaleTimeString('en-US', optionsTime);
|
||
return `${dateStr} • ${startTimeStr} – ${endTimeStr}`;
|
||
}
|
||
}
|
||
|
||
module.exports = async function() {
|
||
try {
|
||
const eventsListUrl = `${zohoApiUrl}/events?range={"start":"${startDate}","end":"${endDate}"}`;
|
||
console.log(`Making initial API call to: ${eventsListUrl}`);
|
||
console.log(`Using access token`);
|
||
|
||
let response = await fetch(eventsListUrl, {
|
||
headers: { Authorization: `Zoho-oauthtoken ${accessToken}` }
|
||
});
|
||
|
||
if (response.status === 401) {
|
||
console.log("Access token is expired, refreshing...");
|
||
await refreshAccessToken();
|
||
response = await fetch(eventsListUrl, {
|
||
headers: { Authorization: `Zoho-oauthtoken ${accessToken}` }
|
||
});
|
||
}
|
||
|
||
if (!response.ok) {
|
||
const text = await response.text();
|
||
throw new Error(`API call failed: ${response.status} ${response.statusText} - ${text}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
console.log("Initial API call successful. Received data:");
|
||
console.dir(data, { depth: null });
|
||
|
||
const normalizedEventsPromises = data.events.map(async (event) => {
|
||
const basicEvent = {
|
||
calid: event.calid,
|
||
title: event.title,
|
||
uid: event.uid,
|
||
start: event.dateandtime.start,
|
||
end: event.dateandtime.end,
|
||
isallday: event.isallday,
|
||
location: event.location,
|
||
displayDate: formatEventDate(event.dateandtime.start, event.dateandtime.end, event.isallday),
|
||
color: event.color,
|
||
};
|
||
|
||
// This logic is necessary to get the description from a second API call
|
||
if (basicEvent.uid) {
|
||
const eventDetailsUrl = `${zohoApiUrl}/events/${basicEvent.uid}`;
|
||
console.log(`Making details API call to: ${eventDetailsUrl}`);
|
||
|
||
const detailsResponse = await fetch(eventDetailsUrl, {
|
||
headers: { Authorization: `Zoho-oauthtoken ${accessToken}` }
|
||
});
|
||
|
||
if (detailsResponse.ok) {
|
||
const detailsData = await detailsResponse.json();
|
||
if (detailsData.events && detailsData.events.length > 0) {
|
||
basicEvent.description = detailsData.events[0].description;
|
||
console.log(`Description added for event UID: ${basicEvent.uid}`);
|
||
}
|
||
} else {
|
||
console.warn(`Failed to fetch details for event UID: ${basicEvent.uid}`);
|
||
}
|
||
}
|
||
|
||
// Assign category based on color
|
||
switch (basicEvent.color) {
|
||
case '#7CAA56':
|
||
basicEvent.category = 'sales';
|
||
break;
|
||
case '#FC6060':
|
||
basicEvent.category = 'workshop';
|
||
break;
|
||
case '#FFC464':
|
||
basicEvent.category = 'community';
|
||
break;
|
||
default:
|
||
basicEvent.category = 'uncategorized';
|
||
}
|
||
|
||
return basicEvent;
|
||
});
|
||
|
||
const normalizedEvents = await Promise.all(normalizedEventsPromises);
|
||
console.log("All events processed. Final normalized events array:", normalizedEvents);
|
||
|
||
const filePath = path.resolve(__dirname, '..', '_data', 'eventsJson.js');
|
||
const fileContents = `module.exports = { normalizedEvents: ${JSON.stringify(normalizedEvents, null, 2)} };`;
|
||
fs.writeFileSync(filePath, fileContents, 'utf-8');
|
||
console.log(`Data successfully written to ${filePath}`);
|
||
|
||
return normalizedEvents;
|
||
|
||
} catch (error) {
|
||
console.error("Error fetching Zoho events:", error);
|
||
return [];
|
||
}
|
||
}; |