export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Route: Handle Form Submission (POST)
if (request.method === "POST" && url.pathname === "/register") {
try {
const formData = await request.formData();
const data = Object.fromEntries(formData.entries());
if (!data.firstName || !data.lastName || !data.email || !data.paymentAgreement || !data.adSpaceSelectionToggle) {
return new Response(JSON.stringify({ success: false, error: "Missing required fields." }), {
status: 400,
headers: { "Content-Type": "application/json" }
});
}
// Check if user explicitly wants to buy ad space
const wantsAdSpace = data.adSpaceSelectionToggle === "yes";
// If ad space is selected, verify an option was chosen
if (wantsAdSpace && !data.adSizeSelection) {
return new Response(JSON.stringify({ success: false, error: "Please select an advertising size option." }), {
status: 400,
headers: { "Content-Type": "application/json" }
});
}
const adultCount = parseInt(data.adults) || 0;
const youthCount = parseInt(data.youth) || 0;
const childCount = parseInt(data.children) || 0;
const totalTickets = adultCount + youthCount + childCount;
const eligibleMealsMax = adultCount + youthCount;
// Server-side Meal Selection check
const totalMealsSelected = (parseInt(data.filetMignon) || 0) + (parseInt(data.salmon) || 0) + (parseInt(data.vegetarian) || 0);
if (totalMealsSelected > eligibleMealsMax) {
return new Response(JSON.stringify({ success: false, error: `You selected ${totalMealsSelected} meals, but only purchased ${eligibleMealsMax} total General and Youth ticket(s).` }), {
status: 400,
headers: { "Content-Type": "application/json" }
});
}
// Server-side Ticket vs T-Shirt check
const totalShirts = (
(parseInt(data.tshirt_ys) || 0) + (parseInt(data.tshirt_ym) || 0) +
(parseInt(data.tshirt_yl) || 0) + (parseInt(data.tshirt_yxl) || 0) +
(parseInt(data.tshirt_ms) || 0) + (parseInt(data.tshirt_mm) || 0) +
(parseInt(data.tshirt_ml) || 0) + (parseInt(data.tshirt_mxl) || 0) + (parseInt(data.tshirt_mxxl) || 0) +
(parseInt(data.tshirt_ws) || 0) + (parseInt(data.tshirt_wm) || 0) +
(parseInt(data.tshirt_wl) || 0) + (parseInt(data.tshirt_wxl) || 0)
);
if (totalShirts > totalTickets) {
return new Response(JSON.stringify({ success: false, error: `You selected ${totalShirts} shirts, but only purchased ${totalTickets} ticket(s). Please reduce your shirt counts.` }), {
status: 400,
headers: { "Content-Type": "application/json" }
});
}
let adCost = 0;
if (wantsAdSpace) {
const pricingMap = {
'color-back': 1000,
'color-inside-back': 500,
'color-inside-front': 500,
'color-full': 300,
'color-half': 150,
'bw-full': 150,
'bw-half': 75,
'bw-quarter': 50
};
adCost = pricingMap[data.adSizeSelection] || 0;
}
const computedTotal = (adultCount * 135) + (youthCount * 75) + (childCount * 40) + adCost;
// Structured payload mapping fields directly to columns on your spreadsheet
const sheetPayload = {
timestamp: new Date().toLocaleString("en-US", { timeZone: "America/Los_Angeles" }),
firstName: data.firstName,
lastName: data.lastName,
email: data.email,
phone: data.phone || '',
address: data.address,
adultTickets: adultCount,
youthTickets: youthCount,
childTickets: childCount,
partyNames: data.partyNames || '',
seatingRequests: data.seatingRequestNames || '',
filetMignon: parseInt(data.filetMignon) || 0,
salmon: parseInt(data.salmon) || 0,
vegetarian: parseInt(data.vegetarian) || 0,
tshirt_ys: parseInt(data.tshirt_ys) || 0,
tshirt_ym: parseInt(data.tshirt_ym) || 0,
tshirt_yl: parseInt(data.tshirt_yl) || 0,
tshirt_yxl: parseInt(data.tshirt_yxl) || 0,
tshirt_ms: parseInt(data.tshirt_ms) || 0,
tshirt_mm: parseInt(data.tshirt_mm) || 0,
tshirt_ml: parseInt(data.tshirt_ml) || 0,
tshirt_mxl: parseInt(data.tshirt_mxl) || 0,
tshirt_mxxl: parseInt(data.tshirt_mxxl) || 0, // Confirmed matching fixed layout configuration label
tshirt_ws: parseInt(data.tshirt_ws) || 0,
tshirt_wm: parseInt(data.tshirt_wm) || 0,
tshirt_wl: parseInt(data.tshirt_wl) || 0,
tshirt_wxl: parseInt(data.tshirt_wxl) || 0,
orderedAdSpace: wantsAdSpace ? 'Yes' : 'No',
adSize: wantsAdSpace ? data.adSizeSelection : 'None',
totalAmount: `$${computedTotal.toFixed(2)}`
};
const GOOGLE_SHEETS_WEBAPP_URL = "https://script.google.com/macros/s/AKfycbxceCyb9AhQlnrylXsNUEkdyMiamxnucBhU0zfdFrPgW63wECfW2H-zAwuUxqVpmJGScw/exec";
ctx.waitUntil(
fetch(GOOGLE_SHEETS_WEBAPP_URL, {
method: "POST",
mode: "no-cors",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(sheetPayload)
})
);
return new Response(JSON.stringify({ success: true, message: "Registration captured successfully!" }), {
status: 200,
headers: { "Content-Type": "application/json" }
});
} catch (err) {
return new Response(JSON.stringify({ success: false, error: err.message }), {
status: 500,
headers: { "Content-Type": "application/json" }
});
}
}
// Route: Serve UI Webpage (GET)
return new Response(htmlTemplate, {
headers: { "Content-Type": "text/html; charset=utf-8" },
});
},
};
// Complete Self-Contained Frontend Template
const htmlTemplate = `
Minoans 40th Anniversary Gala Registration
1986 – 2026
MINOAN DANCERS
40th Anniversary Gala
Join us as we celebrate 40 years of the Minoan Dancers—honoring our faith, our lifelong friendships, and our enduring dedication to Greek dance and cultural preservation.
Event Details
Date & Time
Saturday, October 24, 2026
5:00 PM Cocktail Reception | 6:00 PM Dinner
Venue
Nativity of Christ Greek Church
Novato, California
Ticket Pricing
General Admission (18+): $135
Youth Tickets (Ages 11-17): $75
Children (10 & Under): $40
Evening Highlights
Commemorating 40 years of memories of the Minoan Dancers with Live Musicians, a live DJ, an exclusive Silent Auction, and traditional dancing through the decades.
© 2026 Minoan Dancers. All Rights Reserved.
`;