135 lines
3.7 KiB
TypeScript
135 lines
3.7 KiB
TypeScript
import { Router } from 'express';
|
|
import { query } from '../db';
|
|
import { z } from 'zod';
|
|
|
|
const router = Router();
|
|
|
|
// Validation schema
|
|
const waitlistSchema = z.object({
|
|
email: z.string().email('Invalid email address'),
|
|
source: z.string().optional().default('landing_page'),
|
|
referrer: z.string().optional(),
|
|
});
|
|
|
|
// POST /api/waitlist - Add email to waitlist
|
|
router.post('/', async (req, res) => {
|
|
try {
|
|
const data = waitlistSchema.parse(req.body);
|
|
|
|
// Check if email already exists
|
|
const existing = await query(
|
|
'SELECT id FROM waitlist_leads WHERE email = $1',
|
|
[data.email.toLowerCase()]
|
|
);
|
|
|
|
if (existing.rows.length > 0) {
|
|
// Already on waitlist - return success anyway (don't reveal they're already signed up)
|
|
const countResult = await query('SELECT COUNT(*) FROM waitlist_leads');
|
|
const position = parseInt(countResult.rows[0].count, 10);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'You\'re on the list!',
|
|
position,
|
|
alreadySignedUp: true,
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Insert new lead
|
|
await query(
|
|
'INSERT INTO waitlist_leads (email, source, referrer) VALUES ($1, $2, $3)',
|
|
[data.email.toLowerCase(), data.source, data.referrer || null]
|
|
);
|
|
|
|
// Get current position (total count)
|
|
const countResult = await query('SELECT COUNT(*) FROM waitlist_leads');
|
|
const position = parseInt(countResult.rows[0].count, 10);
|
|
|
|
console.log(`✅ Waitlist signup: ${data.email} (Position #${position})`);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'You\'re on the list!',
|
|
position,
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'validation_error',
|
|
message: error.errors[0].message,
|
|
});
|
|
return;
|
|
}
|
|
|
|
console.error('Waitlist signup error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'server_error',
|
|
message: 'Failed to join waitlist. Please try again.',
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/waitlist/count - Get current waitlist count (public)
|
|
router.get('/count', async (_req, res) => {
|
|
try {
|
|
const result = await query('SELECT COUNT(*) FROM waitlist_leads');
|
|
const count = parseInt(result.rows[0].count, 10);
|
|
|
|
// Add a base number to make it look more impressive at launch
|
|
const displayCount = count + 430; // Starting with "430+ waiting"
|
|
|
|
res.json({
|
|
success: true,
|
|
count: displayCount,
|
|
});
|
|
} catch (error) {
|
|
console.error('Waitlist count error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
count: 430, // Fallback to base number
|
|
});
|
|
}
|
|
});
|
|
|
|
// GET /api/waitlist/admin - Get waitlist leads (Admin only)
|
|
router.get('/admin', async (req, res) => {
|
|
try {
|
|
const adminPassword = process.env.ADMIN_PASSWORD;
|
|
const providedPassword = req.headers['x-admin-password'];
|
|
|
|
if (!adminPassword || providedPassword !== adminPassword) {
|
|
res.status(401).json({
|
|
success: false,
|
|
message: 'Unauthorized',
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Get stats
|
|
const countResult = await query('SELECT COUNT(*) FROM waitlist_leads');
|
|
const total = parseInt(countResult.rows[0].count, 10);
|
|
|
|
// Get leads
|
|
const leadsResult = await query(
|
|
'SELECT * FROM waitlist_leads ORDER BY created_at DESC LIMIT 100'
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
total,
|
|
leads: leadsResult.rows,
|
|
});
|
|
} catch (error) {
|
|
console.error('Waitlist admin error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Server error',
|
|
});
|
|
}
|
|
});
|
|
|
|
export default router;
|