foamking/app/api/quote-request/route.ts
mikl0s 4889ead690 chore: strip debug console.log/error statements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 21:54:39 +00:00

302 lines
12 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server"
import nodemailer from "nodemailer"
import { formatPrice, type CalculationDetails } from "@/lib/calculations"
import { FLOORING_TYPES } from "@/lib/constants"
import { saveQuote } from "@/lib/db"
interface QuoteRequestBody {
customerInfo: {
name: string
email: string
phone: string
postalCode: string
address?: string
remarks?: string
}
calculationDetails: CalculationDetails
}
function createTransporter() {
return nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || "587"),
secure: process.env.SMTP_PORT === "465",
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
})
}
function getFlooringTypeName(type: string): string {
return FLOORING_TYPES[type as keyof typeof FLOORING_TYPES]?.name || type
}
function formatCustomerEmail(
customer: QuoteRequestBody["customerInfo"],
details: CalculationDetails,
trackingUrl: string
): string {
const components = []
if (details.includeInsulation) components.push(`Isolering (${details.insulationThickness} cm)`)
if (details.includeFloorHeating) components.push("Gulvvarme syntetisk net + Ø16 PEX (excl. tilslutning)")
if (details.includeCompound)
components.push(`Flydespartel (${getFlooringTypeName(details.flooringType)})`)
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; }
.header { background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%); color: white; padding: 30px; text-align: center; }
.header h1 { margin: 0; font-size: 24px; }
.content { padding: 30px; background: #f9f9f9; }
.price-box { background: white; border-radius: 12px; padding: 24px; text-align: center; margin: 20px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.price { font-size: 36px; font-weight: bold; color: #1e3a5f; }
.details { background: white; border-radius: 8px; padding: 20px; margin: 20px 0; }
.details h3 { margin-top: 0; color: #1e3a5f; }
.details ul { margin: 0; padding-left: 20px; }
.details li { margin: 8px 0; }
.footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
.note { background: #fff3cd; border-left: 4px solid #ffc107; padding: 12px; margin: 20px 0; font-size: 14px; }
</style>
</head>
<body>
<div class="header">
<h1>Foam King Gulve</h1>
<p>Dit prisoverslag</p>
</div>
<div class="content">
<p>Kære ${customer.name},</p>
<p>Tak for din interesse i Foam King Gulve. Her er dit prisoverslag baseret på de oplysninger du har indtastet:</p>
<div class="price-box">
<div class="price">${formatPrice(Math.round(details.totalInclVat))}</div>
<div style="color: #666;">inkl. moms</div>
</div>
<div class="details">
<h3>Projektdetaljer</h3>
<ul>
<li><strong>Areal:</strong> ${details.area} m²</li>
<li><strong>Gulvhøjde:</strong> ${details.height} cm</li>
<li><strong>Placering:</strong> ${customer.postalCode}${customer.address ? `, ${customer.address}` : ""}</li>
</ul>
</div>
<div class="details">
<h3>Inkluderet i prisen</h3>
<ul>
${components.map((c) => `<li>${c}</li>`).join("")}
<li>Transport</li>
<li>Startgebyr (udstyr og sikkerhed)</li>
</ul>
</div>
<div class="note">
<strong>Bemærk:</strong> Dette er et vejledende prisoverslag og kan variere med ±10.000 kr afhængigt af konkrete forhold på stedet.
</div>
<p>Vi kontakter dig snarest for at aftale et uforpligtende besøg, hvor vi kan give dig et præcist og bindende tilbud.</p>
<p>Har du spørgsmål i mellemtiden, er du velkommen til at kontakte os.</p>
<p>Med venlig hilsen,<br>
<strong>Foam King ApS</strong><br>
Tlf: 35 90 10 66<br>
Email: info@foamking.dk</p>
</div>
<div class="footer">
<p>Foam King ApS · Søgårdsvej 7, 4550 Asnæs · CVR: 44 48 54 51</p>
<p style="margin-top: 15px;">
<img src="${trackingUrl}" alt="Byg Garanti" width="120" height="auto" style="display: inline-block;" />
</p>
</div>
</body>
</html>
`
}
function formatFoamKingEmail(
customer: QuoteRequestBody["customerInfo"],
details: CalculationDetails,
quoteLink: string,
quoteId: number
): string {
const components = []
if (details.includeInsulation)
components.push(
`Isolering: ${details.insulationThickness} cm (${formatPrice(Math.round(details.insulation))})`
)
if (details.includeFloorHeating) {
components.push(`Gulvvarme: ${formatPrice(Math.round(details.floorHeating))}`)
components.push(`Syntetisk net: ${formatPrice(Math.round(details.syntheticNet))}`)
}
if (details.includeCompound) {
components.push(
`Flydespartel (${getFlooringTypeName(details.flooringType)}): ${formatPrice(Math.round(details.selfLevelingCompound))}`
)
components.push(
`Pumpebil (${details.compoundWeight} kg): ${formatPrice(Math.round(details.pumpTruckFee))}`
)
}
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 700px; margin: 0 auto; }
.header { background: #e67e22; color: white; padding: 20px; }
.header h1 { margin: 0; font-size: 20px; }
.section { padding: 20px; border-bottom: 1px solid #eee; }
.section h2 { color: #1e3a5f; font-size: 16px; margin-top: 0; text-transform: uppercase; }
table { width: 100%; border-collapse: collapse; }
td { padding: 8px 0; }
td:first-child { color: #666; width: 50%; }
td:last-child { text-align: right; }
.price-row { background: #f5f5f5; font-weight: bold; }
.price-row td { padding: 12px 8px; }
.total { font-size: 18px; color: #1e3a5f; }
.remarks { background: #fffbeb; padding: 15px; border-left: 4px solid #f59e0b; margin-top: 10px; }
</style>
</head>
<body>
<div class="header">
<h1>Ny tilbudsanmodning #${quoteId}</h1>
</div>
<div style="background: #3b82f6; padding: 15px 20px; text-align: center;">
<a href="${quoteLink}" style="color: white; text-decoration: none; font-weight: bold; font-size: 16px;">
Se detaljeret tilbud online &rarr;
</a>
</div>
<div class="section">
<h2>Kundeoplysninger</h2>
<table>
<tr><td>Navn:</td><td><strong>${customer.name}</strong></td></tr>
<tr><td>Email:</td><td><a href="mailto:${customer.email}">${customer.email}</a></td></tr>
<tr><td>Telefon:</td><td><a href="tel:${customer.phone}">${customer.phone}</a></td></tr>
<tr><td>Postnummer:</td><td>${customer.postalCode}</td></tr>
${customer.address ? `<tr><td>Adresse:</td><td>${customer.address}</td></tr>` : ""}
</table>
${customer.remarks ? `<div class="remarks"><strong>Bemærkninger fra kunden:</strong><br>${customer.remarks}</div>` : ""}
</div>
<div class="section">
<h2>Projektspecifikationer</h2>
<table>
<tr><td>Gulvareal:</td><td><strong>${details.area} m²</strong></td></tr>
<tr><td>Gulvhøjde:</td><td>${details.height} cm</td></tr>
<tr><td>Isoleringstykkelse:</td><td>${details.insulationThickness} cm</td></tr>
<tr><td>Spartelvægt:</td><td>${details.compoundWeight} kg</td></tr>
<tr><td>Afstand (tur/retur):</td><td>${details.distance} km</td></tr>
</table>
</div>
<div class="section">
<h2>Prisberegning</h2>
<table>
${components
.map((c) => {
const [label, price] = c.split(": ")
return `<tr><td style="color: #666;">${label}:</td><td style="text-align: right;">${price}</td></tr>`
})
.join("")}
<tr><td style="color: #666;">Startgebyr:</td><td style="text-align: right;">${formatPrice(Math.round(details.startFee))}</td></tr>
<tr><td colspan="2" style="border-top: 1px solid #ddd; padding-top: 12px;"></td></tr>
<tr><td style="color: #666;">Subtotal:</td><td style="text-align: right;">${formatPrice(Math.round(details.subtotal))}</td></tr>
<tr><td style="color: #666;">Afdækning (0.7%):</td><td style="text-align: right;">${formatPrice(Math.round(details.coveringFee))}</td></tr>
<tr><td style="color: #666;">Affald (0.25%):</td><td style="text-align: right;">${formatPrice(Math.round(details.wasteFee))}</td></tr>
<tr><td style="color: #666;">Transport:</td><td style="text-align: right;">${formatPrice(Math.round(details.transport))}</td></tr>
${details.bridgeFee > 0 ? `<tr><td style="color: #666;">Storebælt:</td><td style="text-align: right;">${formatPrice(details.bridgeFee)}</td></tr>` : ""}
<tr style="background: #f5f5f5; font-weight: bold;"><td style="padding: 12px 8px;">Total ekskl. moms:</td><td style="text-align: right; padding: 12px 8px;">${formatPrice(Math.round(details.totalExclVat))}</td></tr>
<tr><td style="color: #666;">Moms (25%):</td><td style="text-align: right;">${formatPrice(Math.round(details.vat))}</td></tr>
<tr style="background: #f5f5f5; font-weight: bold;"><td style="padding: 12px 8px; font-size: 18px; color: #1e3a5f;">Total inkl. moms:</td><td style="text-align: right; padding: 12px 8px; font-size: 18px; color: #1e3a5f;">${formatPrice(Math.round(details.totalInclVat))}</td></tr>
</table>
</div>
<div class="section" style="border: none;">
<p style="color: #666; font-size: 12px;">
Denne anmodning er genereret automatisk fra prisberegneren på beregner.foamking.dk<br>
Tidspunkt: ${new Date().toLocaleString("da-DK", { timeZone: "Europe/Copenhagen" })}
</p>
</div>
</body>
</html>
`
}
export async function POST(request: NextRequest) {
try {
const body: QuoteRequestBody = await request.json()
const { customerInfo, calculationDetails } = body
if (!customerInfo || !calculationDetails) {
return NextResponse.json({ error: "Manglende data" }, { status: 400 })
}
// Save quote to database
const { id: quoteId, slug } = saveQuote({
postalCode: customerInfo.postalCode,
address: customerInfo.address,
area: calculationDetails.area,
height: calculationDetails.height,
includeFloorHeating: calculationDetails.includeFloorHeating,
flooringType: calculationDetails.flooringType,
customerName: customerInfo.name,
customerEmail: customerInfo.email,
customerPhone: customerInfo.phone,
remarks: customerInfo.remarks,
totalExclVat: calculationDetails.totalExclVat,
totalInclVat: calculationDetails.totalInclVat,
})
// Generate the quote link
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://beregner.foamking.dk"
const quoteLink = `${baseUrl}/tilbud/${slug}`
const transporter = createTransporter()
// Get Foam King recipients (supports comma-separated emails)
const foamKingEmails = (process.env.EMAIL_TO || "info@foamking.dk")
.split(",")
.map((email) => email.trim())
.filter((email) => email.length > 0)
const fromName = process.env.EMAIL_FROM_NAME || "Foam King Prisberegner"
// Generate tracking URL for email open tracking
const trackingUrl = `${baseUrl}/api/track/${quoteId}`
// Send email to customer
await transporter.sendMail({
from: `"${fromName}" <${process.env.SMTP_USER}>`,
to: customerInfo.email,
subject: "Dit prisoverslag fra Foam King Gulve",
html: formatCustomerEmail(customerInfo, calculationDetails, trackingUrl),
})
// Send email to Foam King
await transporter.sendMail({
from: `"${fromName}" <${process.env.SMTP_USER}>`,
to: foamKingEmails,
replyTo: customerInfo.email,
subject: `Tilbud #${quoteId}: ${customerInfo.name} - ${customerInfo.postalCode} - ${calculationDetails.area}`,
html: formatFoamKingEmail(customerInfo, calculationDetails, quoteLink, quoteId),
})
return NextResponse.json({
success: true,
message: "Tak! Vi har modtaget din anmodning og sendt en bekræftelse til din email.",
})
} catch {
return NextResponse.json({ error: "Der opstod en fejl. Prøv igen senere." }, { status: 500 })
}
}