From 87be70e78b02a5032da87ed3d7ed9eb56f3d6ae5 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sat, 14 Feb 2026 01:11:55 +0000 Subject: [PATCH] feat: Production-ready configurator with Dutch standards, pricing & visual UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update door-models.ts: 7mm VSG 33.1 safety glass, 15mm offset, Taats pivot 60mm - Add pricing engine (lib/pricing.ts): steel €45/m + glass €140/m² + €650 base - Wire reactive pricing into Zustand store on every config change - Fix 3D materials: glass thickness 0.007m, corrected roughness/metalness - Upgrade scene: apartment environment, wider contact shadows - Add Dutch height presets: Renovatie 2015mm, Nieuwbouw 2315mm, Plafondhoog 2500mm - Replace text buttons with visual SVG tiles for door type & grid selection Co-Authored-By: Claude Opus 4.6 --- components/configurator/door-3d-enhanced.tsx | 14 +- components/configurator/scene.tsx | 10 +- components/offerte/step-dimensions.tsx | 38 ++++ components/offerte/step-product.tsx | 196 +++++++++++++------ lib/door-models.ts | 17 +- lib/pricing.ts | 126 ++++++++++++ lib/store.ts | 31 ++- 7 files changed, 349 insertions(+), 83 deletions(-) create mode 100644 lib/pricing.ts diff --git a/components/configurator/door-3d-enhanced.tsx b/components/configurator/door-3d-enhanced.tsx index 4e09273..fad1eea 100644 --- a/components/configurator/door-3d-enhanced.tsx +++ b/components/configurator/door-3d-enhanced.tsx @@ -54,27 +54,25 @@ function SteelMaterialTextured({ color, finish }: { color: string; finish: strin ); } catch (error) { - // Fallback to solid color if texture fails return ; } } /** * Fallback Steel Material (Solid Color) - * Used when textures fail to load or as initial state */ function SteelMaterialFallback({ color }: { color: string }) { return ( ); @@ -88,12 +86,12 @@ const GlassMaterial = () => ( ); diff --git a/components/configurator/scene.tsx b/components/configurator/scene.tsx index 78255ba..add7578 100644 --- a/components/configurator/scene.tsx +++ b/components/configurator/scene.tsx @@ -230,16 +230,16 @@ export function Scene3D() { {/* Premium Studio Lighting */} - {/* Studio Environment for photorealistic steel reflections */} - + {/* Apartment Environment for warm, realistic steel reflections */} + {/* High-Resolution Contact Shadows for grounding */} diff --git a/components/offerte/step-dimensions.tsx b/components/offerte/step-dimensions.tsx index 0d3b92f..08e3711 100644 --- a/components/offerte/step-dimensions.tsx +++ b/components/offerte/step-dimensions.tsx @@ -150,6 +150,44 @@ export function StepDimensions() { /> + {/* Height Presets - Dutch Market Standards */} +
+ +

+ Kies een standaard hoogte of stel handmatig in. +

+
+ {[ + { label: 'Renovatie', value: 2015, desc: '201.5 cm' }, + { label: 'Nieuwbouw', value: 2315, desc: '231.5 cm' }, + { label: 'Plafondhoog', value: 2500, desc: '250+ cm' }, + ].map((preset) => { + const isActive = height === preset.value; + return ( + + ); + })} +
+
+ {/* Height Control */}
diff --git a/components/offerte/step-product.tsx b/components/offerte/step-product.tsx index 72929df..68e044f 100644 --- a/components/offerte/step-product.tsx +++ b/components/offerte/step-product.tsx @@ -4,6 +4,88 @@ import { useConfiguratorStore, type DoorType } from "@/lib/store"; import { useFormContext } from "@/components/offerte/form-context"; import { Check } from "lucide-react"; +// Door type visual icons (inline SVGs) +function TaatsIcon({ selected }: { selected: boolean }) { + const stroke = selected ? "#C4D668" : "#1A2E2E"; + const fill = selected ? "#C4D668" : "none"; + return ( + + {/* Door frame */} + + {/* Glass */} + + {/* Pivot point (center) */} + + {/* Rotation arrow */} + + + + ); +} + +function ScharnierIcon({ selected }: { selected: boolean }) { + const stroke = selected ? "#C4D668" : "#1A2E2E"; + const fill = selected ? "#C4D668" : "none"; + return ( + + {/* Door frame */} + + {/* Glass */} + + {/* Hinge dots on left side */} + + + {/* Rotation arrow from hinge side */} + + + + ); +} + +function PaneelIcon({ selected }: { selected: boolean }) { + const stroke = selected ? "#C4D668" : "#1A2E2E"; + return ( + + {/* Door frame */} + + {/* Glass */} + + {/* Fixed indicator - lock symbol */} + + + + ); +} + +const doorTypeIcons: Record React.ReactElement> = { + taats: TaatsIcon, + scharnier: ScharnierIcon, + paneel: PaneelIcon, +}; + +// Grid type visual illustrations (CSS-based rectangles with dividers) +function GridIllustration({ dividers, selected }: { dividers: number; selected: boolean }) { + const borderColor = selected ? "border-[#C4D668]" : "border-[#1A2E2E]/40"; + const dividerBg = selected ? "bg-[#C4D668]" : "bg-[#1A2E2E]/30"; + const glassBg = selected ? "bg-[#C4D668]/10" : "bg-gray-100"; + + return ( +
+ {dividers === 0 && ( +
+ )} + {dividers > 0 && + Array.from({ length: dividers + 1 }).map((_, i) => ( +
+ {i > 0 &&
} +
+
+ )) + } +
+ ); +} + const doorTypes: Array<{ value: DoorType; label: string; @@ -12,17 +94,17 @@ const doorTypes: Array<{ { value: "taats", label: "Taatsdeur", - description: "Pivoterende deur met verticaal draaimechanisme", + description: "Pivoterende deur", }, { value: "scharnier", label: "Scharnierdeur", - description: "Klassieke deur met zijscharnieren", + description: "Zijscharnieren", }, { value: "paneel", label: "Vast Paneel", - description: "Vast glaspaneel zonder bewegend mechanisme", + description: "Geen beweging", }, ]; @@ -30,10 +112,11 @@ const gridTypes: Array<{ value: "3-vlak" | "4-vlak" | "geen"; label: string; description: string; + dividers: number; }> = [ - { value: "geen", label: "Geen verdeling", description: "Volledig vlak" }, - { value: "3-vlak", label: "3-vlaks", description: "2 horizontale balken" }, - { value: "4-vlak", label: "4-vlaks", description: "3 horizontale balken" }, + { value: "geen", label: "Geen", description: "Volledig vlak", dividers: 0 }, + { value: "3-vlak", label: "3-vlaks", description: "2 balken", dividers: 2 }, + { value: "4-vlak", label: "4-vlaks", description: "3 balken", dividers: 3 }, ]; export function StepProduct() { @@ -41,73 +124,61 @@ export function StepProduct() { const { doorType, gridType, setDoorType, setGridType } = useConfiguratorStore(); - function handleDoorTypeSelect(type: DoorType) { - setDoorType(type); - } - - function handleGridTypeSelect(type: "3-vlak" | "4-vlak" | "geen") { - setGridType(type); - } - - function handleContinue() { - nextStep(); - } - return (
- {/* Door Type Selection */} + {/* Door Type Selection - Visual Tiles */}

Kies uw deurtype

Selecteer het type stalen deur dat u wilt configureren.

-
+
{doorTypes.map((type) => { const selected = doorType === type.value; + const IconComponent = doorTypeIcons[type.value]; return ( ); })}
- {/* Grid Type Selection */} + {/* Grid Type Selection - Visual Tiles */}

Verdeling

Kies het aantal horizontale vlakken.

-
+
{gridTypes.map((type) => { const selected = gridType === type.value; @@ -115,30 +186,29 @@ export function StepProduct() { ); })} @@ -147,7 +217,7 @@ export function StepProduct() { {/* Continue Button */}