Files
stalendeuren/components/configurator/door-visualizer.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

122 lines
4.2 KiB
TypeScript

"use client";
import { Suspense, useCallback } from "react";
import { useConfiguratorStore } from "@/lib/store";
import { Scene3D } from "./scene";
import { Camera } from "lucide-react";
function LoadingFallback() {
return (
<div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-slate-100 to-slate-200">
<div className="text-center">
<div className="mb-4 size-12 animate-spin rounded-full border-4 border-[#1A2E2E] border-t-[#C4D668]" />
<p className="text-sm font-medium text-[#1A2E2E]">3D Scene laden...</p>
</div>
</div>
);
}
function formatPrice(amount: number): string {
return new Intl.NumberFormat("nl-NL", {
style: "currency",
currency: "EUR",
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(amount);
}
export function DoorVisualizer() {
const { doorType, gridType, finish, handle, priceBreakdown, setScreenshotDataUrl } =
useConfiguratorStore();
const handleScreenshot = useCallback(() => {
const canvas = document.querySelector("canvas");
if (!canvas) return;
const dataUrl = canvas.toDataURL("image/png");
setScreenshotDataUrl(dataUrl);
// Also trigger download
const link = document.createElement("a");
link.download = "proinn-deur-configuratie.png";
link.href = dataUrl;
link.click();
}, [setScreenshotDataUrl]);
return (
<div className="relative h-full w-full overflow-hidden rounded-[2.5rem]">
{/* Live Preview Badge */}
<div className="absolute left-8 top-8 z-10">
<div className="flex items-center gap-2 rounded-full bg-[#1A2E2E] px-4 py-2 text-sm font-semibold text-white shadow-lg">
<div className="size-2 animate-pulse rounded-full bg-[#C4D668]" />
3D Voorbeeld
</div>
</div>
{/* Screenshot Button */}
<div className="absolute right-8 top-8 z-10">
<button
onClick={handleScreenshot}
className="flex items-center gap-2 rounded-full bg-[#1A2E2E]/80 px-3 py-2 text-xs font-medium text-white shadow-lg backdrop-blur-sm transition-all hover:bg-[#1A2E2E]"
>
<Camera className="size-3.5" />
Screenshot
</button>
</div>
{/* 3D Scene */}
<Suspense fallback={<LoadingFallback />}>
<Scene3D />
</Suspense>
{/* Live Price Badge */}
<div className="absolute right-8 bottom-24 z-10 lg:bottom-8">
<div className="rounded-2xl bg-[#1A2E2E] px-5 py-3 text-right shadow-lg">
<div className="text-[10px] font-medium uppercase tracking-wider text-gray-400">
Indicatieprijs
</div>
<div className="text-xl font-bold text-[#C4D668]">
{formatPrice(priceBreakdown.totalPrice)}
</div>
</div>
</div>
{/* Configuration Info Card */}
<div className="absolute bottom-8 left-8 z-10">
<div className="rounded-2xl bg-white/90 p-4 shadow-lg backdrop-blur-sm">
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<div className="text-xs font-medium text-gray-500">Type</div>
<div className="font-semibold capitalize text-[#1A2E2E]">
{doorType}
</div>
</div>
<div>
<div className="text-xs font-medium text-gray-500">Verdeling</div>
<div className="font-semibold text-[#1A2E2E]">{gridType}</div>
</div>
<div>
<div className="text-xs font-medium text-gray-500">Afwerking</div>
<div className="font-semibold capitalize text-[#1A2E2E]">
{finish}
</div>
</div>
<div>
<div className="text-xs font-medium text-gray-500">Greep</div>
<div className="font-semibold capitalize text-[#1A2E2E]">
{handle}
</div>
</div>
</div>
</div>
</div>
{/* Controls Hint */}
<div className="absolute bottom-8 right-8 z-10 hidden lg:hidden">
<div className="rounded-xl bg-[#1A2E2E]/80 px-3 py-2 text-xs text-white backdrop-blur-sm">
<p className="font-medium">Drag to rotate - Scroll to zoom</p>
</div>
</div>
</div>
);
}