# WHP Site Builder v2 (Craft.js) - Project Documentation ## Overview A visual drag-and-drop website builder rebuilt from the ground up using Craft.js, React 18, and TypeScript. Replaces the legacy GrapesJS-based editor (`/workspace/site-builder/`). Users create multi-page websites without writing code, with server-side storage through WHP's PHP API layer. **Stack:** Vite 6 + React 18 + TypeScript 5 + @craftjs/core 0.2.x **Bundle:** ~460KB JS + ~15KB CSS **Version:** 2.0.0 ## File Structure ``` craft/ ├── index.html # HTML shell (loads fonts, FA icons, mounts React) ├── package.json # Dependencies and scripts (v2.0.0) ├── tsconfig.json # TypeScript config (ES2020, strict, path aliases) ├── vite.config.ts # Vite config (builds to dist/js/editor.js + dist/css/editor.css) ├── CLAUDE.md # This file ├── README.md # Brief project readme ├── FEATURES.md # User-facing features list ├── dist/ # Build output (not committed) │ ├── index.html │ ├── js/editor.js │ ├── css/editor.css │ └── assets/ ├── src/ │ ├── main.tsx # Entry point: reads WHP_CONFIG, mounts │ ├── App.tsx # Wraps with providers, passes resolver │ │ │ ├── types/ │ │ └── index.ts # WhpConfig, PageData, AssetData, StyleProps, DeviceMode │ │ │ ├── state/ │ │ ├── EditorConfigContext.tsx # React context for WHP_CONFIG (useEditorConfig hook) │ │ ├── PageContext.tsx # Multi-page state (pages, header, footer, CRUD, switching) │ │ └── SiteDesignContext.tsx # Site-wide design tokens (17 properties, Basic/Advanced) │ │ │ ├── editor/ │ │ ├── EditorShell.tsx # 3-panel layout: TopBar + LeftPanel + Canvas + RightPanel + ContextMenu │ │ └── Canvas.tsx # Craft.js with device-width switching │ │ │ ├── components/ │ │ ├── resolver.ts # Component map for Craft.js serialization (20 components) │ │ ├── layout/ │ │ │ ├── Container.tsx # Generic container (div/section/article/header/footer/main) │ │ │ ├── Section.tsx # Full-width section with centered inner container │ │ │ ├── ColumnLayout.tsx # Flex columns (1-6, with split ratios) │ │ │ ├── BackgroundSection.tsx # Section with background image/gradient overlay │ │ │ ├── HeaderZone.tsx # Page-level header zone wrapper │ │ │ └── FooterZone.tsx # Page-level footer zone wrapper │ │ ├── basic/ │ │ │ ├── Heading.tsx # Inline-editable heading (h1-h6) │ │ │ ├── TextBlock.tsx # Inline-editable paragraph │ │ │ ├── ButtonLink.tsx # Styled with color presets │ │ │ ├── Navbar.tsx # Navigation bar (text/image logo, page links, external links, CTA) │ │ │ ├── Footer.tsx # Footer component (links, copyright, social) │ │ │ ├── Divider.tsx # Horizontal rule (color, thickness) │ │ │ └── Spacer.tsx # Vertical spacing element │ │ ├── media/ │ │ │ ├── ImageBlock.tsx # Image with placeholder, upload, browse, sizing │ │ │ └── VideoBlock.tsx # Video embed (YouTube, Vimeo, direct files, background mode) │ │ ├── sections/ │ │ │ ├── HeroSimple.tsx # Pre-built hero section with heading, subtext, CTA │ │ │ ├── FeaturesGrid.tsx # 3-column feature cards grid │ │ │ └── CTASection.tsx # Call-to-action banner section │ │ └── forms/ │ │ ├── FormContainer.tsx # Form wrapper with action/method │ │ ├── InputField.tsx # Input field with label and placeholder │ │ ├── TextareaField.tsx # Textarea field with label │ │ └── FormButton.tsx # Submit button with styling │ │ │ ├── panels/ │ │ ├── topbar/ │ │ │ ├── TopBar.tsx # Back button, domain badge, device switcher, undo/redo, save, templates │ │ │ └── TemplateModal.tsx # Template browser with categories and one-click loading │ │ ├── left/ │ │ │ ├── LeftPanel.tsx # Tabs: Blocks | Pages | Layers | Assets │ │ │ ├── BlocksPanel.tsx # Draggable block toolbox with categories │ │ │ ├── PagesPanel.tsx # Multi-page CRUD, header/footer editing │ │ │ ├── LayersPanel.tsx # Component hierarchy tree view │ │ │ └── AssetsPanel.tsx # Asset browser with upload, drag-drop, thumbnails │ │ ├── right/ │ │ │ ├── RightPanel.tsx # Tabs: Styles | Settings | Head │ │ │ ├── GuidedStyles.tsx # Context-aware style panel (shows selected type) │ │ │ └── SiteDesignPanel.tsx # Site-wide design tokens editor (Basic/Advanced tabs) │ │ └── context-menu/ │ │ └── ContextMenu.tsx # Right-click context menu (duplicate, copy, paste, delete, etc.) │ │ │ ├── hooks/ │ │ ├── useWhpApi.ts # Save/load/deploy via WHP API (with auto-save) │ │ ├── useAssets.ts # Asset upload, browse, delete via WHP API │ │ ├── useContextMenu.ts # Right-click menu state management │ │ └── useKeyboardShortcuts.ts # Keyboard shortcut handler (undo, redo, delete, etc.) │ │ │ ├── templates/ │ │ ├── index.ts # Template exports │ │ └── definitions.ts # 16 template definitions across 4 categories │ │ │ ├── ui/ │ │ └── SettingsTabs.tsx # Reusable General/Style/Advanced tabs for component settings │ │ │ ├── constants/ │ │ └── presets.ts # Color, font, spacing, radius, gradient, device width presets │ │ │ ├── utils/ │ │ ├── style-helpers.ts # cssPropsToString(), mergeStyles() │ │ └── html-export.ts # Recursive node-to-HTML renderer, full page export │ │ │ └── styles/ │ └── editor.css # Dark theme, CSS variables ``` ## Running Locally ```bash cd /workspace/site-builder/craft npm install npm run dev ``` Opens at `http://localhost:5173`. The Vite dev server proxies `/api` requests to `http://192.168.1.105:8080` (the WHP staging server) for save/load during development. In standalone mode (no WHP_CONFIG on `window`), the editor runs fully client-side without save/load functionality. ## Building ```bash npm run build ``` Runs `tsc && vite build`. Output goes to `dist/`: - `dist/index.html` - HTML shell - `dist/js/editor.js` - Single JS bundle - `dist/css/editor.css` - All styles - `dist/assets/` - Static assets (if any) ## Deploying to WHP Copy the built `dist/` contents into the WHP site-builder web directory: ```bash # Build cd /workspace/site-builder/craft && npm run build # Deploy to WHP Docker container cp dist/index.html /docker/whp/web/site-builder/editor.html cp -r dist/js/ /docker/whp/web/site-builder/js/ cp -r dist/css/ /docker/whp/web/site-builder/css/ ``` The PHP wrapper (`/docker/whp/web/site-builder/index.php`) injects `WHP_CONFIG` into the HTML before serving it, so the editor gets the current user's session, CSRF token, site ID, etc. ## Architecture ### Key Decisions 1. **No iframe** - The canvas renders directly in the DOM (unlike GrapesJS which uses an iframe). This simplifies drag-and-drop and avoids cross-origin issues but means editor CSS must not leak into user content. 2. **Inline styles** - All component styling uses React `CSSProperties` (inline styles). No class-based CSS for user content. This makes HTML export trivial and avoids stylesheet management. 3. **Single Frame, multi-page** - Craft.js `` holds one page at a time. Page switching serializes the current state, stores it, and deserializes the new page's state. 4. **Header/Footer as separate pages** - Header and Footer are stored as independent Craft.js states (like pages) that render above and below every page. Editing them uses the same canvas but with a distinct editing mode. This provides site-wide shared navigation and footer. 5. **API compatibility** - The save endpoint sends data in the same format as the GrapesJS version (`{ site_id, name, html, css, grapesjs: serializedJson }`), so the PHP backend doesn't need changes. 6. **Component-based architecture** - Each visual element is a React component that doubles as a Craft.js `UserComponent`. All rendering, settings UI, and HTML export are co-located in one file. 7. **Site Design Tokens** - A `SiteDesignContext` provides 17 design properties (colors, fonts, radii, nav style) that components can reference. Templates import their own design tokens when loaded. ### Component Architecture Every component in `src/components/` follows this pattern: ```typescript import { UserComponent, useNode } from '@craftjs/core'; // 1. Props interface interface MyComponentProps { text?: string; style?: CSSProperties; } // 2. The component itself (renders in editor canvas) export const MyComponent: UserComponent = ({ text, style }) => { const { connectors: { connect, drag } } = useNode(); return
{ if (r) connect(drag(r)); }} style={style}>{text}
; }; // 3. Settings panel (rendered in right panel when selected) const MyComponentSettings: React.FC = () => { const { actions: { setProp }, props } = useNode((node) => ({ props: node.data.props as MyComponentProps, })); return
/* preset buttons, inputs, etc. */
; }; // 4. Craft config (displayName, default props, rules, related settings) MyComponent.craft = { displayName: 'My Component', props: { text: 'Default text', style: {} }, rules: { canDrag: () => true, canMoveIn: () => false, canMoveOut: () => true }, related: { settings: MyComponentSettings }, }; // 5. HTML export (static method for serializing to HTML string) (MyComponent as any).toHtml = (props: MyComponentProps, childrenHtml: string) => { return { html: `
${childrenHtml}
` }; }; ``` ### Component Resolver All components must be registered in `src/components/resolver.ts`. This map is passed to `` so Craft.js can serialize/deserialize the node tree. ```typescript export const componentResolver = { Container, Section, ColumnLayout, BackgroundSection, Heading, TextBlock, ButtonLink, Navbar, Footer, Divider, Spacer, ImageBlock, VideoBlock, HeroSimple, FeaturesGrid, CTASection, FormContainer, InputField, TextareaField, FormButton, }; ``` ## WHP Integration ### PHP Wrapper The WHP control panel serves the editor through `index.php`, which: 1. Verifies user authentication 2. Validates the `site_id` parameter 3. Generates a CSRF token 4. Injects a `WHP_CONFIG` object into the HTML as a `