Switch to static output and add Azure SWA deployment
- Set output: 'static' in astro.config.mjs; remove Vercel/Netlify adapters - Remove API routes (contact, newsletter) incompatible with static mode - Add Azure SWA deploy workflow using @azure/static-web-apps-cli via npx - Add public/staticwebapp.config.json for SWA routing and 404 fallback Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,108 +0,0 @@
|
||||
export const prerender = false;
|
||||
|
||||
import type { APIRoute } from 'astro';
|
||||
import { z } from 'astro/zod';
|
||||
import { Resend } from 'resend';
|
||||
import siteConfig from '@/config/site.config';
|
||||
|
||||
const contactSchema = z.object({
|
||||
name: z.string().min(2, 'Name must be at least 2 characters').max(100),
|
||||
email: z.email('Please enter a valid email address'),
|
||||
subject: z.string().max(200).optional(),
|
||||
message: z.string().min(10, 'Message must be at least 10 characters').max(5000),
|
||||
honeypot: z.string().max(0), // Anti-spam: must be empty
|
||||
});
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
|
||||
const data = {
|
||||
name: formData.get('name')?.toString() || '',
|
||||
email: formData.get('email')?.toString() || '',
|
||||
subject: formData.get('subject')?.toString() || '',
|
||||
message: formData.get('message')?.toString() || '',
|
||||
honeypot: formData.get('honeypot')?.toString() || '',
|
||||
};
|
||||
|
||||
// Validate
|
||||
const result = contactSchema.safeParse(data);
|
||||
|
||||
if (!result.success) {
|
||||
const fieldErrors: Record<string, string[]> = {};
|
||||
for (const error of result.error.issues) {
|
||||
const field = error.path[0] as string;
|
||||
if (!fieldErrors[field]) {
|
||||
fieldErrors[field] = [];
|
||||
}
|
||||
fieldErrors[field].push(error.message);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, errors: fieldErrors }),
|
||||
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
// Honeypot check (bot detection)
|
||||
if (result.data.honeypot) {
|
||||
return new Response(JSON.stringify({ success: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
// Send email via Resend
|
||||
const apiKey = import.meta.env.RESEND_API_KEY;
|
||||
if (!apiKey) {
|
||||
console.error('RESEND_API_KEY is not set');
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, errors: { form: ['Email service is not configured'] } }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
const resend = new Resend(apiKey);
|
||||
|
||||
const toEmail = siteConfig.email;
|
||||
const fromEmail = import.meta.env.RESEND_FROM_EMAIL || toEmail;
|
||||
const siteLabel = siteConfig.name;
|
||||
|
||||
const subject = result.data.subject
|
||||
? `[${siteLabel}] ${result.data.subject}`
|
||||
: `[${siteLabel}] New contact from ${result.data.name}`;
|
||||
|
||||
const { error } = await resend.emails.send({
|
||||
from: `Contact Form <${fromEmail}>`,
|
||||
to: toEmail,
|
||||
replyTo: result.data.email,
|
||||
subject,
|
||||
html: `
|
||||
<p><strong>Name:</strong> ${result.data.name}</p>
|
||||
<p><strong>Email:</strong> ${result.data.email}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${result.data.message.replace(/\n/g, '<br>')}</p>
|
||||
`,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Resend error:', error);
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, errors: { form: [error.message || 'Failed to send email'] } }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ success: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact form error:', error);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, errors: { form: ['An unexpected error occurred'] } }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { z } from 'astro/zod';
|
||||
import { Resend } from 'resend';
|
||||
|
||||
const newsletterSchema = z.object({
|
||||
email: z.email('Please enter a valid email address'),
|
||||
honeypot: z.string().max(0).optional(),
|
||||
});
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const email = formData.get('email')?.toString() || '';
|
||||
const honeypot = formData.get('website')?.toString() || '';
|
||||
|
||||
// Check honeypot - if filled, it's likely a bot
|
||||
if (honeypot) {
|
||||
return new Response(JSON.stringify({ success: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
const result = newsletterSchema.safeParse({ email, honeypot });
|
||||
|
||||
if (!result.success) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: result.error.issues[0]?.message || 'Please enter a valid email address',
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const apiKey = import.meta.env.RESEND_API_KEY;
|
||||
const audienceId = import.meta.env.RESEND_AUDIENCE_ID;
|
||||
|
||||
if (!apiKey || !audienceId) {
|
||||
console.error('Newsletter: RESEND_API_KEY or RESEND_AUDIENCE_ID is not configured');
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Newsletter service is not configured.',
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const resend = new Resend(apiKey);
|
||||
const { error } = await resend.contacts.create({
|
||||
audienceId,
|
||||
email: result.data.email,
|
||||
unsubscribed: false,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Resend newsletter error:', error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Subscription failed. Please try again.',
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ success: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Newsletter error:', error);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: 'Subscription failed. Please try again.',
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user