auto-claude: subtask-2-2 - Create API route for contact form submission

This commit is contained in:
2026-01-25 11:54:00 +01:00
parent e9082be2d9
commit e39bb8951d
+127
View File
@@ -0,0 +1,127 @@
import { NextRequest, NextResponse } from 'next/server';
import { supabaseRateLimiter } from '@/utils/rateLimitSupabase';
interface ContactFormData {
name: string;
email: string;
message: string;
}
/**
* Extract client IP address from Next.js request headers
* Handles Vercel's forwarding headers and fallback scenarios
*/
function getClientIp(request: NextRequest): string {
// Check Vercel's forwarded IP header first
const forwardedFor = request.headers.get('x-forwarded-for');
if (forwardedFor) {
// x-forwarded-for may contain multiple IPs, take the first one
return forwardedFor.split(',')[0].trim();
}
// Check for real IP header
const realIp = request.headers.get('x-real-ip');
if (realIp) {
return realIp;
}
// Fallback to a default identifier for development
return 'unknown-ip';
}
/**
* Validate contact form data
*/
function validateFormData(data: unknown): data is ContactFormData {
if (!data || typeof data !== 'object') {
return false;
}
const formData = data as Record<string, unknown>;
return (
typeof formData.name === 'string' &&
formData.name.trim().length > 0 &&
typeof formData.email === 'string' &&
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email) &&
typeof formData.message === 'string' &&
formData.message.trim().length > 0
);
}
/**
* POST /api/contact
* Handle contact form submissions with rate limiting
*/
export async function POST(request: NextRequest) {
try {
// Extract client IP for rate limiting
const clientIp = getClientIp(request);
// Check rate limit
const isRateLimited = await supabaseRateLimiter.isRateLimited(clientIp);
if (isRateLimited) {
const timeToReset = await supabaseRateLimiter.getTimeToReset(clientIp);
const retryAfterSeconds = Math.ceil(timeToReset / 1000);
return NextResponse.json(
{
error: 'Too many requests. Please try again later.',
retryAfter: retryAfterSeconds,
},
{
status: 429,
headers: {
'X-RateLimit-Remaining': '0',
'Retry-After': retryAfterSeconds.toString(),
},
}
);
}
// Parse and validate request body
const body = await request.json();
if (!validateFormData(body)) {
return NextResponse.json(
{ error: 'Invalid form data. Please check all fields.' },
{ status: 400 }
);
}
const formData = body as ContactFormData;
// Get remaining attempts for response headers
const remainingAttempts = await supabaseRateLimiter.getRemainingAttempts(clientIp);
// TODO: In a real implementation, you would:
// 1. Send email via a service like SendGrid, Resend, or AWS SES
// 2. Store the message in a database
// 3. Send a confirmation email to the user
// For now, we'll just simulate success
// Simulate processing delay
await new Promise(resolve => setTimeout(resolve, 500));
return NextResponse.json(
{
success: true,
message: 'Your message has been received. We will get back to you soon!',
},
{
status: 200,
headers: {
'X-RateLimit-Remaining': remainingAttempts.toString(),
},
}
);
} catch (error) {
console.error('Error processing contact form:', error);
return NextResponse.json(
{ error: 'An unexpected error occurred. Please try again later.' },
{ status: 500 }
);
}
}