"use client" import { useState } from "react" import { useForm, Controller } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { ArrowRight, ArrowLeft, MapPin, Ruler, Settings, User, Check, Loader2, CheckCircle2, } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Textarea } from "@/components/ui/textarea" import { Switch } from "@/components/ui/switch" import { Slider } from "@/components/ui/slider" import { CONSTRAINTS, FLOORING_TYPES, type FlooringType } from "@/lib/constants" import { validateDanishPostalCode, getDistance } from "@/lib/distance" import { calculatePrice, formatPrice, formatEstimate, type CalculationDetails, } from "@/lib/calculations" const formSchema = z.object({ postalCode: z .string() .length(4, "Postnummer skal være 4 cifre") .refine(validateDanishPostalCode, "Vi dækker desværre ikke dette område"), address: z.string().optional(), area: z.coerce .number() .min(CONSTRAINTS.MIN_AREA, `Minimum ${CONSTRAINTS.MIN_AREA} m²`) .max(CONSTRAINTS.MAX_AREA, `Maximum ${CONSTRAINTS.MAX_AREA} m²`), height: z.coerce .number() .min(CONSTRAINTS.MIN_HEIGHT, `Minimum ${CONSTRAINTS.MIN_HEIGHT} cm`) .max(CONSTRAINTS.MAX_HEIGHT, `Maximum ${CONSTRAINTS.MAX_HEIGHT} cm`), includeInsulation: z.boolean(), includeFloorHeating: z.boolean(), includeCompound: z.boolean(), flooringType: z.string(), name: z.string().refine((val) => { const parts = val.trim().split(/\s+/) return parts.length >= 2 && parts[0].length >= 3 && parts[1].length >= 3 }, "Indtast fornavn og efternavn (mindst 3 tegn hver)"), email: z.string().email("Ugyldig email"), phone: z.string().regex(/^\d{8}$/, "Telefonnummer skal være 8 cifre"), remarks: z.string().optional(), }) type FormData = z.infer interface StepWizardProps { onComplete: (result: CalculationDetails, formData: FormData) => void } const steps = [ { id: 1, name: "Placering", icon: MapPin }, { id: 2, name: "Gulvmål", icon: Ruler }, { id: 3, name: "Løsning", icon: Settings }, { id: 4, name: "Kontakt", icon: User }, ] export function StepWizard({ onComplete }: StepWizardProps) { const [currentStep, setCurrentStep] = useState(1) const [isCalculating, setIsCalculating] = useState(false) const [showHeightTip, setShowHeightTip] = useState(false) const { register, handleSubmit, formState: { errors }, watch, control, trigger, getValues, } = useForm({ resolver: zodResolver(formSchema), defaultValues: { postalCode: "", address: "", area: 75, height: 15, includeInsulation: true, includeFloorHeating: true, includeCompound: true, flooringType: "STANDARD", name: "", email: "", phone: "", remarks: "", }, mode: "onChange", }) const watchedValues = watch() const validateStep = async (step: number): Promise => { let fieldsToValidate: (keyof FormData)[] = [] switch (step) { case 1: fieldsToValidate = ["postalCode"] break case 2: fieldsToValidate = ["area", "height"] break case 3: fieldsToValidate = [] break case 4: fieldsToValidate = ["name", "email", "phone"] break } const result = await trigger(fieldsToValidate) return result } const nextStep = async () => { const isValid = await validateStep(currentStep) if (isValid && currentStep < 4) { setCurrentStep(currentStep + 1) } } const prevStep = () => { if (currentStep > 1) { setCurrentStep(currentStep - 1) } } const onSubmit = async (data: FormData) => { setIsCalculating(true) try { let distance: number try { const params = new URLSearchParams({ postalCode: data.postalCode, ...(data.address && { address: data.address }), }) const response = await fetch(`/api/distance?${params}`) const distanceData = await response.json() distance = distanceData.distance } catch { distance = getDistance(data.postalCode) } const result = calculatePrice({ area: data.area, height: data.height, postalCode: data.postalCode, distance, includeInsulation: data.includeInsulation, includeFloorHeating: data.includeFloorHeating, includeCompound: data.includeCompound, flooringType: data.flooringType as FlooringType, }) onComplete(result, data) } finally { setIsCalculating(false) } } return (
{/* Progress Steps */}
{steps.map((step, index) => { const Icon = step.icon const isActive = currentStep === step.id const isCompleted = currentStep > step.id return (
{index > 0 && (
)} {index < steps.length - 1 && (
)}
{isCompleted ? : }
{step.name}
) })}
{/* Form Card */}
{/* Step 1: Location */} {currentStep === 1 && (

Hvor skal gulvet lægges?

Vi dækker Sjælland, Lolland-Falster og Fyn

{errors.postalCode && (

{errors.postalCode.message}

)}
)} {/* Step 2: Floor Dimensions */} {currentStep === 2 && (

Hvor stort er gulvet?

Angiv areal og ønsket gulvhøjde

{/* Area Slider */}
{watchedValues.area}
( field.onChange(value)} className="py-4" /> )} />
{CONSTRAINTS.MIN_AREA} m² {CONSTRAINTS.MAX_AREA} m²
{/* Height Slider */}
{watchedValues.height} cm
( field.onChange(value)} className="py-4" /> )} />
{CONSTRAINTS.MIN_HEIGHT} cm {CONSTRAINTS.MAX_HEIGHT} cm
)} {/* Step 3: Components */} {currentStep === 3 && (

Hvad skal inkluderes?

Vælg de komponenter du ønsker

{/* Always included */}

Altid inkluderet:

Isolering PUR skumisolering
Gulvstøbning Flydespartel

Tilvalg:

( )} />
{/* Flooring Type */} {true && (
(
{Object.entries(FLOORING_TYPES).map(([key, type]) => (
)}
)} {/* Step 4: Contact */} {currentStep === 4 && (

Dine kontaktoplysninger

Så vi kan sende dit prisoverslag

{errors.name &&

{errors.name.message}

}
{errors.email && (

{errors.email.message}

)}
{errors.phone && (

{errors.phone.message}

)}