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>
288 lines
11 KiB
TypeScript
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>
|
|
);
|
|
}
|