Files
stalendeuren/components/offerte/step-options.tsx
Ubuntu 3d788740cb feat: Latest production version with interior scene and glass
Includes room interior with floor, walls, glass you can see through,
and all uncommitted production changes that were running live.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 14:50:31 +00:00

288 lines
11 KiB
TypeScript

"use client";
import { useConfiguratorStore, type Finish, type GlassColor, type Handle, type FrameSize } from "@/lib/store";
import { glassPatternOptions, type GlassPattern } from "@/lib/glass-patterns";
import { Check } from "lucide-react";
// ============================================
// OPTIONS DATA
// ============================================
const finishOptions: Array<{
value: Finish;
label: string;
description: string;
swatch: string;
}> = [
{ value: "zwart", label: "Mat Zwart", description: "Klassiek en tijdloos", swatch: "#1A1A1A" },
{ value: "brons", label: "Brons", description: "Warm en industrieel", swatch: "#8B6F47" },
{ value: "grijs", label: "Antraciet", description: "Modern en neutraal", swatch: "#525252" },
{ value: "goud", label: "Goud", description: "Luxe en opvallend", swatch: "#B8860B" },
{ value: "beige", label: "Beige", description: "Zacht en natuurlijk", swatch: "#C8B88A" },
{ value: "ral", label: "RAL Kleur", description: "Op maat, +EUR 200", swatch: "#4A6741" },
];
const glassColorOptions: Array<{
value: GlassColor;
label: string;
description: string;
swatch: string;
}> = [
{ value: "helder", label: "Helder", description: "Maximale transparantie", swatch: "#dbeafe" },
{ value: "grijs", label: "Rookglas", description: "Getint grijs glas", swatch: "#4B5563" },
{ value: "brons", label: "Bronsglas", description: "Warm getint glas", swatch: "#92764A" },
{ value: "mat-blank", label: "Mat Blank", description: "Zacht diffuus licht", swatch: "#e2e2e2" },
{ value: "mat-brons", label: "Mat Brons", description: "Warm en gedempd", swatch: "#A0845C" },
{ value: "mat-zwart", label: "Mat Zwart", description: "Privacy glas", swatch: "#2D2D2D" },
];
const handleOptions: Array<{
value: Handle;
label: string;
description: string;
}> = [
{ value: "beugelgreep", label: "Beugelgreep", description: "Verticale staaf met montageblokken" },
{ value: "hoekgreep", label: "Hoekgreep", description: "L-vormige minimalistisch design" },
{ value: "maangreep", label: "Maangreep", description: "Gebogen half-maanvormige greep" },
{ value: "ovaalgreep", label: "Ovaalgreep", description: "Moderne ovale trekgreep" },
{ value: "klink", label: "Deurklink", description: "Klassieke deurklink met hendel" },
{ value: "u-greep", label: "U-Greep", description: "Eenvoudige rechte staaf" },
{ value: "geen", label: "Geen greep", description: "Voor vaste panelen" },
];
const frameSizeOptions: Array<{
value: FrameSize;
label: string;
description: string;
}> = [
{ value: 20, label: "Smal (20mm)", description: "Minimalistisch profiel" },
{ value: 30, label: "Standaard (30mm)", description: "Populairste keuze" },
{ value: 40, label: "Robuust (40mm)", description: "Industrieel karakter" },
];
// ============================================
// SHARED SELECTION COMPONENTS
// ============================================
function SelectionButton({
selected,
onClick,
children,
}: {
selected: boolean;
onClick: () => void;
children: React.ReactNode;
}) {
return (
<button
type="button"
onClick={onClick}
className={`group relative rounded-xl border-2 p-4 text-left transition-all ${
selected
? "border-[#C4D668] bg-[#1A2E2E] text-white"
: "border-gray-200 bg-white text-gray-900 hover:border-[#1A2E2E]/30"
}`}
>
<div className="flex items-start justify-between">
{children}
{selected && (
<div className="ml-2 flex size-6 shrink-0 items-center justify-center rounded-full bg-[#C4D668]">
<Check className="size-4 text-[#1A2E2E]" />
</div>
)}
</div>
</button>
);
}
// ============================================
// MAIN COMPONENT
// ============================================
export function StepOptions() {
const {
finish, handle, glassPattern, glassColor, frameSize,
setFinish, setHandle, setGlassPattern, setGlassColor, setFrameSize,
} = useConfiguratorStore();
return (
<div className="space-y-8">
{/* Finish Selection */}
<div>
<h2 className="mb-2 text-lg font-bold text-[#1A2E2E]">Afwerking</h2>
<p className="mb-4 text-sm text-gray-600">
Kies de kleur en afwerking van het staal.
</p>
<div className="grid grid-cols-2 gap-3">
{finishOptions.map((option) => {
const selected = finish === option.value;
return (
<button
key={option.value}
type="button"
onClick={() => setFinish(option.value)}
className={`group relative flex flex-col items-center rounded-xl border-2 p-3 transition-all ${
selected
? "border-[#C4D668] bg-[#1A2E2E] text-white ring-2 ring-[#C4D668]"
: "border-gray-200 bg-white text-gray-900 hover:border-[#1A2E2E]/30"
}`}
>
<div
className="mb-2 size-10 rounded-lg border-2 border-white shadow-md"
style={{ backgroundColor: option.swatch }}
/>
<h3 className="text-sm font-bold">{option.label}</h3>
<p className={`mt-0.5 text-xs ${selected ? "text-white/70" : "text-gray-500"}`}>
{option.description}
</p>
{selected && (
<div className="absolute right-2 top-2 flex size-5 items-center justify-center rounded-full bg-[#C4D668]">
<Check className="size-3 text-[#1A2E2E]" />
</div>
)}
</button>
);
})}
</div>
</div>
{/* Glass Color Selection */}
<div>
<h2 className="mb-2 text-lg font-bold text-[#1A2E2E]">Glaskleur</h2>
<p className="mb-4 text-sm text-gray-600">
Kies het type en de kleur van het glas.
</p>
<div className="grid grid-cols-3 gap-3">
{glassColorOptions.map((option) => {
const selected = glassColor === option.value;
return (
<button
key={option.value}
type="button"
onClick={() => setGlassColor(option.value)}
className={`group relative flex flex-col items-center rounded-xl border-2 p-3 transition-all ${
selected
? "border-[#C4D668] bg-[#1A2E2E] text-white ring-2 ring-[#C4D668]"
: "border-gray-200 bg-white text-gray-900 hover:border-[#1A2E2E]/30"
}`}
>
<div
className="mb-2 size-8 rounded-full border-2 border-white shadow-md"
style={{ backgroundColor: option.swatch }}
/>
<h3 className="text-xs font-bold">{option.label}</h3>
{selected && (
<div className="absolute right-1.5 top-1.5 flex size-4 items-center justify-center rounded-full bg-[#C4D668]">
<Check className="size-2.5 text-[#1A2E2E]" />
</div>
)}
</button>
);
})}
</div>
</div>
{/* Frame Size Selection */}
<div>
<h2 className="mb-2 text-lg font-bold text-[#1A2E2E]">Profielbreedte</h2>
<p className="mb-4 text-sm text-gray-600">
Kies de breedte van het stalen profiel.
</p>
<div className="grid grid-cols-3 gap-3">
{frameSizeOptions.map((option) => {
const selected = frameSize === option.value;
return (
<button
key={option.value}
type="button"
onClick={() => setFrameSize(option.value)}
className={`group relative flex flex-col items-center rounded-xl border-2 px-2 py-3 transition-all ${
selected
? "border-[#C4D668] bg-[#1A2E2E] text-white ring-2 ring-[#C4D668]"
: "border-gray-200 bg-white text-gray-900 hover:border-[#1A2E2E]/30"
}`}
>
{/* Visual profile width indicator */}
<div className="mb-2 flex h-12 items-center justify-center">
<div
className={`rounded-sm ${selected ? "bg-[#C4D668]" : "bg-[#1A2E2E]"}`}
style={{ width: `${option.value * 0.4}px`, height: "40px" }}
/>
</div>
<h3 className="text-xs font-bold">{option.label}</h3>
<p className={`mt-0.5 text-[10px] ${selected ? "text-white/70" : "text-gray-500"}`}>
{option.description}
</p>
{selected && (
<div className="absolute right-1.5 top-1.5 flex size-4 items-center justify-center rounded-full bg-[#C4D668]">
<Check className="size-2.5 text-[#1A2E2E]" />
</div>
)}
</button>
);
})}
</div>
</div>
{/* Glass Pattern Selection */}
<div>
<h2 className="mb-2 text-lg font-bold text-[#1A2E2E]">Glaspatroon</h2>
<p className="mb-4 text-sm text-gray-600">
Kies het decoratieve patroon voor de glaspanelen.
</p>
<div className="grid gap-3">
{glassPatternOptions.map((option) => {
const selected = glassPattern === option.value;
return (
<SelectionButton
key={option.value}
selected={selected}
onClick={() => setGlassPattern(option.value)}
>
<div className="flex-1">
<h3 className="font-bold">{option.label}</h3>
<p className={`mt-1 text-sm ${selected ? "text-white/80" : "text-gray-500"}`}>
{option.description}
</p>
</div>
</SelectionButton>
);
})}
</div>
</div>
{/* Handle Selection */}
<div>
<h2 className="mb-2 text-lg font-bold text-[#1A2E2E]">Greep</h2>
<p className="mb-4 text-sm text-gray-600">
Selecteer het type handgreep.
</p>
<div className="grid gap-3">
{handleOptions.map((option) => {
const selected = handle === option.value;
return (
<SelectionButton
key={option.value}
selected={selected}
onClick={() => setHandle(option.value)}
>
<div className="flex-1">
<h3 className="font-bold">{option.label}</h3>
<p className={`mt-1 text-sm ${selected ? "text-white/80" : "text-gray-500"}`}>
{option.description}
</p>
</div>
</SelectionButton>
);
})}
</div>
</div>
</div>
);
}