Files
stalendeuren/lib/store.ts
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

229 lines
6.0 KiB
TypeScript

import { create } from 'zustand';
import {
calculateHoleWidth,
calculateHoleMinWidth,
calculateHoleMaxWidth,
calculateSidePanelWidth,
calculateDoorLeafWidth,
type DoorConfig,
type SidePanel,
} from './calculations';
import type { GlassPattern } from './glass-patterns';
import { calculatePrice, type PriceBreakdown } from './pricing';
export type DoorType = 'taats' | 'scharnier' | 'paneel';
export type GridType =
| 'geen'
| '2-vlak'
| '3-vlak'
| '4-vlak'
| '6-vlak'
| '8-vlak'
| 'kruis'
| 'ongelijk-3'
| 'boerderij'
| 'herenhuis';
export type Finish = 'zwart' | 'brons' | 'grijs' | 'goud' | 'beige' | 'ral';
export type GlassColor = 'helder' | 'grijs' | 'brons' | 'mat-blank' | 'mat-brons' | 'mat-zwart';
export type FrameSize = 20 | 30 | 40;
export type Handle = 'beugelgreep' | 'hoekgreep' | 'maangreep' | 'ovaalgreep' | 'klink' | 'u-greep' | 'geen';
interface ConfiguratorState {
// Door configuration
doorType: DoorType;
doorConfig: DoorConfig;
sidePanel: SidePanel;
// Styling
gridType: GridType;
finish: Finish;
glassColor: GlassColor;
handle: Handle;
glassPattern: GlassPattern;
frameSize: FrameSize;
// Dimensions (in mm)
width: number;
height: number;
// Calculated values
holeWidth: number;
doorLeafWidth: number;
sidePanelWidth: number;
minWidth: number;
maxWidth: number;
// Pricing
priceBreakdown: PriceBreakdown;
// Contact info
name: string;
email: string;
phone: string;
note: string;
// Extra options
extraOptions: string[];
// Screenshot
screenshotDataUrl: string | null;
// Actions
setDoorType: (type: DoorType) => void;
setDoorConfig: (config: DoorConfig) => void;
setSidePanel: (panel: SidePanel) => void;
setGridType: (type: GridType) => void;
setFinish: (finish: Finish) => void;
setGlassColor: (color: GlassColor) => void;
setHandle: (handle: Handle) => void;
setGlassPattern: (pattern: GlassPattern) => void;
setFrameSize: (size: FrameSize) => void;
setWidth: (width: number) => void;
setHeight: (height: number) => void;
setDimensions: (width: number, height: number) => void;
setName: (name: string) => void;
setEmail: (email: string) => void;
setPhone: (phone: string) => void;
setNote: (note: string) => void;
toggleExtraOption: (option: string) => void;
setScreenshotDataUrl: (url: string | null) => void;
}
// Helper function for recalculation
const recalculate = (get: () => ConfiguratorState, set: (state: Partial<ConfiguratorState>) => void) => {
const { width, doorConfig, sidePanel } = get();
set({
holeWidth: calculateHoleWidth(width, doorConfig, sidePanel),
doorLeafWidth: calculateDoorLeafWidth(width, doorConfig, sidePanel),
sidePanelWidth: calculateSidePanelWidth(width, doorConfig, sidePanel),
minWidth: calculateHoleMinWidth(doorConfig, sidePanel),
maxWidth: calculateHoleMaxWidth(doorConfig, sidePanel),
});
};
// Helper function for price recalculation
const recalculatePrice = (get: () => ConfiguratorState, set: (state: Partial<ConfiguratorState>) => void) => {
const { doorLeafWidth, height, doorType, gridType, doorConfig, sidePanel, handle, finish, frameSize } = get();
const priceBreakdown = calculatePrice(doorLeafWidth, height, doorType, gridType, doorConfig, sidePanel, handle, finish, frameSize);
set({ priceBreakdown });
};
export const useConfiguratorStore = create<ConfiguratorState>((set, get) => ({
// Initial state
doorType: 'taats',
doorConfig: 'enkele',
sidePanel: 'geen',
gridType: '3-vlak',
finish: 'zwart',
glassColor: 'helder',
handle: 'beugelgreep',
glassPattern: 'standard',
frameSize: 40,
width: 1000,
height: 2400,
// Initial calculated values
holeWidth: 1160,
doorLeafWidth: 1000,
sidePanelWidth: 0,
minWidth: 860,
maxWidth: 1360,
// Initial price
priceBreakdown: calculatePrice(1000, 2400, 'taats', '3-vlak', 'enkele', 'geen', 'beugelgreep', 'zwart', 40),
// Contact info
name: '',
email: '',
phone: '',
note: '',
// Extra options
extraOptions: [],
// Screenshot
screenshotDataUrl: null,
// Actions with automatic recalculation
setDoorType: (doorType) => {
set({ doorType });
recalculate(get, set);
recalculatePrice(get, set);
},
setDoorConfig: (doorConfig) => {
set({ doorConfig });
recalculate(get, set);
recalculatePrice(get, set);
},
setSidePanel: (sidePanel) => {
set({ sidePanel });
recalculate(get, set);
recalculatePrice(get, set);
},
setGridType: (gridType) => {
set({ gridType });
recalculatePrice(get, set);
},
setFinish: (finish) => {
set({ finish });
recalculatePrice(get, set);
},
setGlassColor: (glassColor) => set({ glassColor }),
setHandle: (handle) => {
set({ handle });
recalculatePrice(get, set);
},
setGlassPattern: (glassPattern) => set({ glassPattern }),
setFrameSize: (frameSize) => {
set({ frameSize });
recalculatePrice(get, set);
},
setWidth: (width) => {
const { doorConfig, sidePanel } = get();
const minWidth = calculateHoleMinWidth(doorConfig, sidePanel);
const maxWidth = calculateHoleMaxWidth(doorConfig, sidePanel);
const clampedWidth = Math.max(minWidth, Math.min(maxWidth, width));
set({ width: clampedWidth });
recalculate(get, set);
recalculatePrice(get, set);
},
setHeight: (height) => {
const clampedHeight = Math.max(1800, Math.min(3000, height));
set({ height: clampedHeight });
recalculatePrice(get, set);
},
setDimensions: (width, height) => {
get().setWidth(width);
get().setHeight(height);
},
setName: (name) => set({ name }),
setEmail: (email) => set({ email }),
setPhone: (phone) => set({ phone }),
setNote: (note) => set({ note }),
toggleExtraOption: (option) => {
const { extraOptions } = get();
if (extraOptions.includes(option)) {
set({ extraOptions: extraOptions.filter((o) => o !== option) });
} else {
set({ extraOptions: [...extraOptions, option] });
}
},
setScreenshotDataUrl: (screenshotDataUrl) => set({ screenshotDataUrl }),
}));