feat(landing): Add kontakt, datenschutz pages, integrate brevo newsletter and contact, add error-handler
This commit is contained in:
135
landing/server/api/contact/send.post.ts
Normal file
135
landing/server/api/contact/send.post.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const contactSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
email: z.string().email(),
|
||||
message: z.string().min(10).max(5000)
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
// Validate request body
|
||||
const body = await readBody(event)
|
||||
const result = contactSchema.safeParse(body)
|
||||
|
||||
if (!result.success) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Invalid form data',
|
||||
data: result.error.flatten()
|
||||
})
|
||||
}
|
||||
|
||||
const { name, email, message } = result.data
|
||||
|
||||
// Check if API key is configured
|
||||
if (!config.brevoApiKey || config.brevoApiKey === 'NOT_SET') {
|
||||
console.error('BREVO_API_KEY is not configured')
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Email service is not configured'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// Build HTML email content
|
||||
const htmlContent = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
|
||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
||||
.header { background: linear-gradient(135deg, #0ea5e9, #06b6d4); color: white; padding: 24px; border-radius: 12px 12px 0 0; }
|
||||
.content { background: #f8fafc; padding: 24px; border: 1px solid #e2e8f0; border-top: none; border-radius: 0 0 12px 12px; }
|
||||
.field { margin-bottom: 16px; }
|
||||
.label { font-weight: 600; color: #64748b; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.value { margin-top: 4px; padding: 12px; background: white; border-radius: 8px; border: 1px solid #e2e8f0; }
|
||||
.message-value { white-space: pre-wrap; }
|
||||
.footer { margin-top: 24px; padding-top: 16px; border-top: 1px solid #e2e8f0; font-size: 12px; color: #94a3b8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 style="margin: 0; font-size: 24px;">Neue Kontaktanfrage</h1>
|
||||
<p style="margin: 8px 0 0; opacity: 0.9;">über GremiumHub Website</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="field">
|
||||
<div class="label">Name</div>
|
||||
<div class="value">${escapeHtml(name)}</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">E-Mail</div>
|
||||
<div class="value"><a href="mailto:${escapeHtml(email)}">${escapeHtml(email)}</a></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">Nachricht</div>
|
||||
<div class="value message-value">${escapeHtml(message)}</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
Diese Nachricht wurde über das Kontaktformular auf gremiumhub.de gesendet.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`.trim()
|
||||
|
||||
// Send email via Brevo SMTP API
|
||||
const response = await $fetch<{ messageId?: string }>('https://api.brevo.com/v3/smtp/email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'api-key': config.brevoApiKey,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: {
|
||||
sender: {
|
||||
name: config.brevoSenderName,
|
||||
email: config.brevoSenderEmail
|
||||
},
|
||||
to: [
|
||||
{
|
||||
email: config.brevoContactEmail,
|
||||
name: 'GremiumHub Team'
|
||||
}
|
||||
],
|
||||
replyTo: {
|
||||
email,
|
||||
name
|
||||
},
|
||||
subject: `Kontaktanfrage von ${name}`,
|
||||
htmlContent
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
messageId: response.messageId
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const fetchError = error as { statusCode?: number; data?: { code?: string; message?: string } }
|
||||
|
||||
console.error('Brevo SMTP API error:', fetchError.data || error)
|
||||
|
||||
throw createError({
|
||||
statusCode: fetchError.statusCode || 500,
|
||||
statusMessage: fetchError.data?.message || 'Failed to send message'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Helper function to escape HTML special characters
|
||||
function escapeHtml(text: string): string {
|
||||
const htmlEntities: Record<string, string> = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}
|
||||
return text.replace(/[&<>"']/g, (char) => htmlEntities[char] ?? char)
|
||||
}
|
||||
84
landing/server/api/newsletter/subscribe.post.ts
Normal file
84
landing/server/api/newsletter/subscribe.post.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const subscribeSchema = z.object({
|
||||
email: z.string().email()
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
// Validate request body
|
||||
const body = await readBody(event)
|
||||
const result = subscribeSchema.safeParse(body)
|
||||
|
||||
if (!result.success) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Invalid email address'
|
||||
})
|
||||
}
|
||||
|
||||
const { email } = result.data
|
||||
|
||||
// Check if API key is configured
|
||||
if (!config.brevoApiKey) {
|
||||
console.error('BREVO_API_KEY is not configured')
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Newsletter service is not configured'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// Build request body for Brevo Contacts API
|
||||
const brevoBody: {
|
||||
email: string
|
||||
updateEnabled: boolean
|
||||
listIds?: number[]
|
||||
} = {
|
||||
email,
|
||||
updateEnabled: true
|
||||
}
|
||||
|
||||
// Add list ID if configured
|
||||
if (config.brevoNewsletterListId) {
|
||||
const listId = parseInt(config.brevoNewsletterListId, 10)
|
||||
if (!isNaN(listId)) {
|
||||
brevoBody.listIds = [listId]
|
||||
}
|
||||
}
|
||||
|
||||
const response = await $fetch('https://api.brevo.com/v3/contacts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'api-key': config.brevoApiKey,
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: brevoBody
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
id: (response as { id?: number })?.id
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
// Handle Brevo API errors
|
||||
const fetchError = error as { statusCode?: number; data?: { code?: string; message?: string } }
|
||||
|
||||
// Contact already exists (duplicate_parameter error) - treat as success
|
||||
if (fetchError.data?.code === 'duplicate_parameter') {
|
||||
return {
|
||||
success: true,
|
||||
message: 'Already subscribed'
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Brevo API error:', fetchError.data || error)
|
||||
|
||||
throw createError({
|
||||
statusCode: fetchError.statusCode || 500,
|
||||
statusMessage: fetchError.data?.message || 'Failed to subscribe to newsletter'
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user