valley-commons/api/waitlist.js

130 lines
4.1 KiB
JavaScript

// Vercel serverless function to handle waitlist submissions
// This keeps Google Sheets credentials private (server-side only)
module.exports = async function handler(req, res) {
// Only allow POST requests
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
// CORS headers for frontend access
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
// Handle preflight requests
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
try {
const { email, name, involvement } = req.body;
// Validate email
if (!email || !email.includes('@')) {
return res.status(400).json({ error: 'Valid email is required' });
}
// Validate name
if (!name || name.trim() === '') {
return res.status(400).json({ error: 'Name is required' });
}
// Validate involvement
if (!involvement || involvement.trim() === '') {
return res.status(400).json({ error: 'Please describe your desired involvement' });
}
// Get credentials from environment variables
const credentials = process.env.GOOGLE_SERVICE_ACCOUNT;
const spreadsheetId = process.env.GOOGLE_SHEET_ID;
const sheetName = process.env.GOOGLE_SHEET_NAME || 'Waitlist';
if (!credentials || !spreadsheetId) {
console.error('Missing Google Sheets configuration');
return res.status(500).json({ error: 'Server configuration error' });
}
// Parse service account credentials
// Trim whitespace and handle multi-line JSON from environment variable
const credentialsTrimmed = credentials.trim();
let serviceAccount;
try {
serviceAccount = JSON.parse(credentialsTrimmed);
} catch (parseError) {
console.error('Failed to parse service account credentials:', parseError.message);
return res.status(500).json({ error: 'Server configuration error' });
}
// Import googleapis (will be installed as dependency)
const { google } = require('googleapis');
// Authenticate with Google Sheets API
const auth = new google.auth.GoogleAuth({
credentials: serviceAccount,
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});
const sheets = google.sheets({ version: 'v4', auth });
// Prepare the data to append
const timestamp = new Date().toISOString();
const values = [[timestamp, email, name || '', involvement || '']];
// Append to the sheet
await sheets.spreadsheets.values.append({
spreadsheetId,
range: `${sheetName}!A:D`,
valueInputOption: 'USER_ENTERED',
insertDataOption: 'INSERT_ROWS',
resource: {
values,
},
});
// Success response
return res.status(200).json({
success: true,
message: 'Successfully joined the waitlist!'
});
} catch (error) {
// Log detailed error for debugging (visible in Vercel logs)
console.error('Error adding to waitlist:', {
message: error.message,
code: error.code,
status: error.response?.status,
statusText: error.response?.statusText,
details: error.response?.data,
stack: error.stack
});
// Provide more specific error messages for common issues
if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
return res.status(500).json({
error: 'Network error connecting to Google Sheets. Please try again later.'
});
}
if (error.response?.status === 403) {
console.error('Permission denied - check service account has access to sheet');
return res.status(500).json({
error: 'Permission error. Please contact support.'
});
}
if (error.response?.status === 404) {
console.error('Sheet not found - check spreadsheet ID and sheet name');
return res.status(500).json({
error: 'Sheet configuration error. Please contact support.'
});
}
// Don't expose internal errors to client
return res.status(500).json({
error: 'Failed to join waitlist. Please try again later.'
});
}
}