foamking/app/api/quote-request/route.ts
mikl0s 7d2bbae1c6 Initial implementation of Foam King Gulve price calculator
Features:
- Complete Next.js 16 app with TypeScript and Tailwind CSS
- Customer-facing price calculator form with validation
- Admin mode showing detailed price breakdowns
- Accurate price calculations matching business requirements
- Responsive design with custom shadcn/ui theme
- API endpoint for quote requests
- Danish postal code distance calculations
- Complete test coverage against documentation examples

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-10 14:27:28 +00:00

116 lines
No EOL
3.4 KiB
TypeScript

import { NextResponse } from "next/server"
import { z } from "zod"
import { formatPrice } from "@/lib/calculations"
import type { CalculationDetails } from "@/lib/calculations"
const quoteRequestSchema = z.object({
customerInfo: z.object({
name: z.string(),
email: z.string().email(),
phone: z.string(),
postalCode: z.string(),
address: z.string().optional(),
remarks: z.string().optional(),
}),
calculationDetails: z.object({
area: z.number(),
height: z.number(),
postalCode: z.string(),
distance: z.number(),
totalInclVat: z.number(),
// We'll validate other fields exist but not their exact shape
}) as z.ZodType<CalculationDetails>,
})
export async function POST(request: Request) {
try {
const body = await request.json()
const { customerInfo, calculationDetails } = quoteRequestSchema.parse(body)
// Format email content
const emailContent = formatEmailContent(customerInfo, calculationDetails)
// In production, you would send this via an email service
// For now, we'll just log it and return success
console.log("Quote request email:", emailContent)
// TODO: Implement actual email sending using a service like:
// - SendGrid
// - AWS SES
// - Resend
// - Nodemailer with SMTP
return NextResponse.json({
success: true,
message: "Tilbudsanmodning modtaget. Vi kontakter dig snarest muligt.",
})
} catch (error) {
console.error("Quote request error:", error)
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Ugyldige data", details: error.errors },
{ status: 400 }
)
}
return NextResponse.json(
{ error: "Der opstod en fejl. Prøv igen senere." },
{ status: 500 }
)
}
}
function formatEmailContent(
customerInfo: z.infer<typeof quoteRequestSchema>["customerInfo"],
details: CalculationDetails
): string {
return `
Ny tilbudsanmodning fra Foam King Gulve Prisberegner
KUNDEOPLYSNINGER:
-----------------
Navn: ${customerInfo.name}
Email: ${customerInfo.email}
Telefon: ${customerInfo.phone}
Postnummer: ${customerInfo.postalCode}
Adresse: ${customerInfo.address || "Ikke angivet"}
PROJEKTDETALJER:
----------------
Gulvareal: ${details.area}
Gulvhøjde: ${details.height} cm
Isoleringstykkelse: ${details.insulationThickness} cm
Isoleringsvolumen: ${details.insulationVolume.toFixed(2)}
Spartelvægt: ${details.compoundWeight.toLocaleString("da-DK")} kg
PRISBEREGNING:
--------------
Isolering: ${formatPrice(details.insulation)}
Gulvvarme: ${formatPrice(details.floorHeating)}
Syntetisk net: ${formatPrice(details.syntheticNet)}
Flydespartel: ${formatPrice(details.selfLevelingCompound)}
Pumpebil-tillæg: ${formatPrice(details.pumpTruckFee)}
Startgebyr: ${formatPrice(details.startFee)}
Subtotal: ${formatPrice(details.subtotal)}
Tillæg (afdækning + affald): ${formatPrice(details.totalFees)}
Transport: ${formatPrice(details.transport)}
${details.bridgeFee > 0 ? `Storebælt-tillæg: ${formatPrice(details.bridgeFee)}` : ""}
Total ekskl. moms: ${formatPrice(details.totalExclVat)}
Moms (25%): ${formatPrice(details.vat)}
TOTAL INKL. MOMS: ${formatPrice(details.totalInclVat)}
BEMÆRKNINGER:
-------------
${customerInfo.remarks || "Ingen bemærkninger"}
AFSTAND:
--------
Kørselsafstand (tur-retur): ${details.distance} km
---
Sendt fra beregner.foamking.dk
`.trim()
}