# Builder V2 - Creating New Components Guide

## Overview
This guide covers the complete process of creating new components for the React/Craft.js Page Builder V2, including common issues and their solutions.

## Table of Contents
1. [Component Creation Checklist](#component-creation-checklist)
2. [Step-by-Step Process](#step-by-step-process)
3. [Component Structure](#component-structure)
4. [Common Issues & Solutions](#common-issues--solutions)
5. [Example: Blog Posts Component](#example-blog-posts-component)

---

## Component Creation Checklist

When creating a new component, you MUST complete ALL these steps:

- [ ] **Step 1:** Create component file in `resources/js/builder-v2/components/user/YourComponent.jsx`
- [ ] **Step 2:** Define the component with proper props and `useNode` hook
- [ ] **Step 3:** Create settings panel component
- [ ] **Step 4:** Add `.craft` configuration object
- [ ] **Step 5:** Import component in `resources/js/builder-v2/main.jsx`
- [ ] **Step 6:** ⚠️ **CRITICAL:** Add component to resolver in `main.jsx`
- [ ] **Step 7:** Import component in `resources/js/builder-v2/components/Sidebar.jsx`
- [ ] **Step 8:** Add component to appropriate section in Sidebar
- [ ] **Step 9:** Run `npm run build`
- [ ] **Step 10:** Test drag-and-drop functionality
- [ ] **Step 11:** Test settings panel
- [ ] **Step 12:** ⚠️ **CRITICAL FOR FRONTEND:** Add component to BOTH render-v2.blade.php files:
  - `resources/views/pages/render-v2.blade.php`
  - `resources/views/global-sections/render-v2.blade.php`
- [ ] **Step 13:** Test frontend rendering on public website

---

## Step-by-Step Process

### Step 1: Create Component File

Create a new file in `resources/js/builder-v2/components/user/YourComponent.jsx`:

```jsx
import React from 'react';
import { useNode } from '@craftjs/core';

/**
 * Your Component
 * Description of what this component does
 */
export const YourComponent = ({
    prop1 = 'default',
    prop2 = 'value',
    padding = '20px',
    margin = '0px 0px 20px 0px'
}) => {
    const { connectors: { connect, drag } } = useNode();

    return (
        <div
            ref={(ref) => connect(drag(ref))}
            style={{
                padding: padding,
                margin: margin
            }}
        >
            {/* Your component JSX here */}
        </div>
    );
};
```

### Step 2: Create Settings Panel

Add the settings component in the same file:

```jsx
const YourComponentSettings = () => {
    const { actions: { setProp }, props } = useNode((node) => ({
        props: node.data.props
    }));

    return (
        <div className="settings-panel">
            <div className="mb-3">
                <label className="form-label">Prop 1</label>
                <input
                    type="text"
                    className="form-control"
                    value={props.prop1}
                    onChange={(e) => setProp((props) => props.prop1 = e.target.value)}
                />
            </div>

            {/* Add more settings fields */}
        </div>
    );
};
```

### Step 3: Add Craft Configuration

Add the `.craft` object at the end of the file:

```jsx
YourComponent.craft = {
    displayName: 'Your Component',
    props: {
        prop1: 'default',
        prop2: 'value',
        padding: '20px',
        margin: '0px 0px 20px 0px'
    },
    related: {
        settings: YourComponentSettings
    }
};
```

### Step 4: Import in main.jsx

**File:** `resources/js/builder-v2/main.jsx`

Add import at the top:
```jsx
import { YourComponent } from './components/user/YourComponent';
```

### Step 5: ⚠️ CRITICAL - Add to Resolver

**File:** `resources/js/builder-v2/main.jsx`

Add component to the resolver object (around line 72):

```jsx
<Editor
    resolver={{
        Container,
        Heading,
        // ... other components
        PaymentForm,
        YourComponent  // ⚠️ MUST BE HERE!
    }}
    enabled={true}
    onRender={RenderNode}
>
```

**WHY THIS IS CRITICAL:**
- If the component is NOT in the resolver, you'll get `Uncaught Error: Invariant failed` when dragging
- Craft.js needs to know about ALL components that can be serialized/deserialized
- This is the #1 cause of drag-and-drop failures

### Step 6: Add to Sidebar

**File:** `resources/js/builder-v2/components/Sidebar.jsx`

1. Import the component:
```jsx
import { YourComponent } from './user/YourComponent';
```

2. Add to appropriate component group:
```jsx
const componentGroups = [
    // ... existing groups
    {
        name: 'Your Section',
        components: [
            {
                name: 'Your Component',
                icon: 'bi-icon-name',
                component: YourComponent,
                defaultProps: {}  // Or { websiteId } if needed
            }
        ]
    }
];
```

### Step 7: Build and Test Builder

```bash
# Build the React app
npm run build

# Test in browser:
# 1. Hard refresh (Cmd+Shift+R or Ctrl+Shift+F5)
# 2. Check component appears in sidebar
# 3. Drag to canvas
# 4. Click to edit settings
# 5. Save page
```

### Step 8: ⚠️ CRITICAL - Add Frontend Renderer to BOTH Files

To make your component render on the public website, you MUST add rendering code to **TWO files**.

**Why?** The system loads `global-sections/render-v2.blade.php` first (for headers/footers). Its `renderNode()` function takes precedence. If your component is only in `pages/render-v2.blade.php`, it will never execute.

**File 1:** `resources/views/pages/render-v2.blade.php`

1. Add case to switch statement (around line 134):
```php
switch ($type) {
    // ... existing cases

    case 'YourComponent':
        return renderYourComponent($props, $websiteId);

    default:
        return '<div></div>';
}
```

2. Add render function (after other render functions, before closing PHP tag):
```php
/**
 * Render YourComponent
 */
if (!function_exists('renderYourComponent')) {
function renderYourComponent($props, $websiteId = null) {
    // Extract props with defaults
    $prop1 = $props['prop1'] ?? 'default';
    $prop2 = $props['prop2'] ?? 'value';

    // Get spacing styles
    $spacingStyles = getSpacingStyles($props);

    // Build HTML
    $html = '<div class="your-component" style="' . implode('; ', $spacingStyles) . '">';
    $html .= '<h2>' . htmlspecialchars($prop1) . '</h2>';
    $html .= '<p>' . htmlspecialchars($prop2) . '</p>';
    $html .= '</div>';

    return $html;
}
}
```

**File 2:** `resources/views/global-sections/render-v2.blade.php`

⚠️ **Copy EXACT SAME CODE** from File 1:

1. Add case to switch statement (around line 131):
```php
case 'YourComponent':
    return renderYourComponent($props, $websiteId);
```

2. Add render function (same location as File 1, after renderPaymentForm):
```php
// ⚠️ EXACT SAME FUNCTION as pages/render-v2.blade.php
/**
 * Render YourComponent
 */
if (!function_exists('renderYourComponent')) {
function renderYourComponent($props, $websiteId = null) {
    // ... IDENTICAL CODE ...
}
}
```

**File Locations:**
```
resources/views/
├── pages/
│   └── render-v2.blade.php           ⚠️ File 1 - Add component here
└── global-sections/
    └── render-v2.blade.php           ⚠️ File 2 - Add component here (CRITICAL!)
```

**Clear Caches:**
```bash
php artisan view:clear
php artisan cache:clear
php artisan config:clear
```

### Step 9: Test Frontend Rendering

1. Visit the public website URL (not the builder)
2. Verify component renders correctly
3. Check all props are displayed
4. Verify responsive behavior
5. Check browser console for errors

---

## Component Structure

### Required Elements

1. **Export statement:**
   ```jsx
   export const YourComponent = ({ ...props }) => {
   ```

2. **useNode hook:**
   ```jsx
   const { connectors: { connect, drag } } = useNode();
   ```

3. **Ref connection:**
   ```jsx
   ref={(ref) => connect(drag(ref))}
   ```

4. **Craft configuration:**
   ```jsx
   YourComponent.craft = {
       displayName: 'Component Name',
       props: { /* defaults */ },
       related: { settings: YourComponentSettings }
   };
   ```

### Optional Elements

1. **State management:**
   ```jsx
   const [state, setState] = useState(defaultValue);
   ```

2. **Effects:**
   ```jsx
   useEffect(() => {
       // Side effects
   }, [dependencies]);
   ```

3. **Getting websiteId from DOM:**
   ```jsx
   useEffect(() => {
       if (!websiteId) {
           const builderElement = document.getElementById('page-builder-v2-root');
           if (builderElement) {
               const id = builderElement.getAttribute('data-website-id');
               setActualWebsiteId(id);
           }
       }
   }, [websiteId]);
   ```

---

## Common Issues & Solutions

### Issue 1: "Uncaught Error: Invariant failed" when dragging

**Symptoms:**
- Component appears in sidebar
- Error occurs when trying to drag to canvas
- Error: `Invariant failed at Ee` in console

**Solution:**
The component is NOT registered in the resolver in `main.jsx`.

**Fix:**
```jsx
// resources/js/builder-v2/main.jsx

// 1. Import the component
import { YourComponent } from './components/user/YourComponent';

// 2. Add to resolver
<Editor
    resolver={{
        // ... other components
        YourComponent  // ⚠️ ADD HERE
    }}
>
```

**Why this happens:**
Craft.js needs to serialize components to JSON when saving. If a component isn't in the resolver, Craft.js doesn't know how to handle it and throws an invariant error.

### Issue 2: Component not visible in sidebar

**Symptoms:**
- Component doesn't appear in the sidebar at all

**Solutions:**

1. **Check import in Sidebar.jsx:**
   ```jsx
   import { YourComponent } from './user/YourComponent';
   ```

2. **Check component is added to componentGroups:**
   ```jsx
   {
       name: 'Section Name',
       components: [
           {
               name: 'Your Component',
               icon: 'bi-icon',
               component: YourComponent,
               defaultProps: {}
           }
       ]
   }
   ```

3. **Check if component is in conditional section:**
   If component is inside `if (websiteModules.ecommerce)`, it won't show unless e-commerce is enabled.

4. **Clear cache and rebuild:**
   ```bash
   npm run build
   # Then hard refresh browser
   ```

### Issue 3: Component drags but doesn't render

**Symptoms:**
- Component drags to canvas successfully
- Canvas shows blank or error

**Solutions:**

1. **Check ref connection:**
   ```jsx
   <div ref={(ref) => connect(drag(ref))}>
   ```

2. **Check for JavaScript errors in component:**
   - Open browser console
   - Look for runtime errors
   - Fix any undefined variables or props

3. **Verify props have defaults:**
   ```jsx
   export const YourComponent = ({
       prop1 = 'default',  // ✅ Always provide defaults
       prop2 = 'value'
   }) => {
   ```

### Issue 4: Settings panel not working

**Symptoms:**
- Can't edit component settings
- Settings panel empty or shows "No settings available"

**Solutions:**

1. **Check settings component is defined:**
   ```jsx
   const YourComponentSettings = () => {
       // Settings JSX
   };
   ```

2. **Check craft.related.settings:**
   ```jsx
   YourComponent.craft = {
       related: {
           settings: YourComponentSettings  // ⚠️ Must match function name
       }
   };
   ```

3. **Check useNode hook in settings:**
   ```jsx
   const { actions: { setProp }, props } = useNode((node) => ({
       props: node.data.props
   }));
   ```

### Issue 5: Props not updating

**Symptoms:**
- Settings panel changes don't reflect in component

**Solution:**
Ensure setProp is called correctly:

```jsx
// ✅ Correct
onChange={(e) => setProp((props) => props.propName = e.target.value)}

// ❌ Wrong
onChange={(e) => setProp({ propName: e.target.value })}
```

### Issue 6: ⚠️ CRITICAL - Component works in builder but NOT on public website

**Symptoms:**
- Component drags and drops perfectly in the builder
- Settings panel works correctly
- Component saves successfully
- **BUT** component appears empty or doesn't render on the public website
- No errors in the console

**Root Cause:**
There are **TWO separate render-v2.blade.php files** in the system:

1. **`resources/views/pages/render-v2.blade.php`** - Used for rendering page content
2. **`resources/views/global-sections/render-v2.blade.php`** - Used for rendering headers/footers (global sections)

Both files define a `renderNode()` function using `if (!function_exists('renderNode'))`. The global-sections file loads FIRST (for headers/footers), so its `renderNode()` function takes precedence. If your component's case statement is only in the pages file, it will NEVER execute!

**Solution:**
You MUST add your component's rendering code to **BOTH files**.

**Step-by-Step Fix:**

1. **Add case statement to BOTH files:**

**File 1:** `resources/views/pages/render-v2.blade.php` (around line 134)
```php
switch ($type) {
    // ... other cases

    case 'YourComponent':
        return renderYourComponent($props, $websiteId);

    // ... more cases
}
```

**File 2:** `resources/views/global-sections/render-v2.blade.php` (around line 131)
```php
switch ($type) {
    // ... other cases

    case 'YourComponent':
        return renderYourComponent($props, $websiteId);

    // ... more cases
}
```

2. **Add render function to BOTH files:**

**File 1:** `resources/views/pages/render-v2.blade.php` (after other render functions)
```php
/**
 * Render YourComponent
 */
if (!function_exists('renderYourComponent')) {
function renderYourComponent($props, $websiteId = null) {
    // Get props
    $prop1 = $props['prop1'] ?? 'default';
    $prop2 = $props['prop2'] ?? 'value';

    // Get spacing
    $spacingStyles = getSpacingStyles($props);

    // Build HTML
    $html = '<div class="your-component" style="' . implode('; ', $spacingStyles) . '">';
    $html .= '<!-- Your component HTML here -->';
    $html .= '</div>';

    return $html;
}
}
```

**File 2:** `resources/views/global-sections/render-v2.blade.php` (same location)
```php
// ⚠️ COPY THE EXACT SAME FUNCTION from pages/render-v2.blade.php
/**
 * Render YourComponent
 */
if (!function_exists('renderYourComponent')) {
function renderYourComponent($props, $websiteId = null) {
    // ... EXACT SAME CODE ...
}
}
```

3. **Clear all caches:**
```bash
php artisan view:clear
php artisan cache:clear
php artisan config:clear
rm -f storage/framework/views/*.php
```

4. **Test on public website**

**Why This Happens:**

```
Page Load Flow:
┌─────────────────────────────────────────┐
│ 1. Laravel loads global-sections first  │
│    (for header/footer rendering)         │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│ 2. global-sections/render-v2.blade.php  │
│    defines renderNode() function        │
│    - Has: Hero, Slider, Menu, etc.      │
│    - Missing: YourComponent              │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│ 3. Laravel loads page content           │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│ 4. pages/render-v2.blade.php loads      │
│    but renderNode() already defined!    │
│    if (!function_exists('renderNode'))  │
│    returns TRUE, so this version        │
│    is SKIPPED entirely                  │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│ 5. Your component falls through to      │
│    default case = empty <div></div>     │
└─────────────────────────────────────────┘
```

**Important Notes:**

- Both files must have IDENTICAL render functions for each component
- If you update one file, update BOTH files
- The `if (!function_exists())` wrapper prevents duplicate function definitions, but means only the FIRST file's version is used
- This is BY DESIGN for the multi-tenancy global sections system
- Always test on the PUBLIC website, not just the builder

**Example - Posts Component:**

The Posts component was affected by this exact issue. The solution was:

1. Added `case 'Posts':` to line 131 of global-sections/render-v2.blade.php
2. Copied entire `renderPosts()` function (lines 2056-2247) from pages/render-v2.blade.php to global-sections/render-v2.blade.php at line 2041
3. Cleared all caches
4. Posts component now renders perfectly on public website

---

## Example: Blog Posts Component

### Complete Implementation

**File:** `resources/js/builder-v2/components/user/BlogPosts.jsx`

```jsx
import React from 'react';
import { useNode } from '@craftjs/core';

/**
 * Blog Posts Component
 * Displays recent blog posts with filtering options
 */
export const BlogPosts = ({
    padding = '40px 0px',
    margin = '0px 0px 20px 0px'
}) => {
    const { connectors: { connect, drag } } = useNode();

    return (
        <div
            ref={(ref) => connect(drag(ref))}
            style={{
                padding: padding,
                margin: margin,
                minHeight: '200px',
                border: '2px dashed #667eea',
                borderRadius: '8px',
                background: '#f9f9f9',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                textAlign: 'center'
            }}
        >
            <div>
                <i className="bi bi-file-earmark-text" style={{ fontSize: '3rem', color: '#667eea' }}></i>
                <h4 className="mt-3">Blog Posts</h4>
                <p className="text-muted">Drag me to the canvas!</p>
            </div>
        </div>
    );
};

const BlogPostsSettings = () => {
    const { actions: { setProp }, props } = useNode((node) => ({
        props: node.data.props
    }));

    return (
        <div className="settings-panel">
            <div className="mb-3">
                <label className="form-label">Padding</label>
                <input
                    type="text"
                    className="form-control"
                    value={props.padding}
                    onChange={(e) => setProp((props) => props.padding = e.target.value)}
                />
            </div>

            <div className="mb-3">
                <label className="form-label">Margin</label>
                <input
                    type="text"
                    className="form-control"
                    value={props.margin}
                    onChange={(e) => setProp((props) => props.margin = e.target.value)}
                />
            </div>
        </div>
    );
};

BlogPosts.craft = {
    displayName: 'Blog Posts',
    props: {
        padding: '40px 0px',
        margin: '0px 0px 20px 0px'
    },
    related: {
        settings: BlogPostsSettings
    }
};
```

### Registration in main.jsx

```jsx
// Import
import { BlogPosts } from './components/user/BlogPosts';

// Resolver (CRITICAL!)
<Editor
    resolver={{
        Container,
        Heading,
        // ... other components
        BlogPosts  // ⚠️ MUST BE HERE
    }}
>
```

### Registration in Sidebar.jsx

```jsx
// Import
import { BlogPosts } from './user/BlogPosts';

// Component groups
{
    name: 'Content',
    components: [
        {
            name: 'Blog Posts',
            icon: 'bi-file-earmark-text',
            component: BlogPosts,
            defaultProps: {}
        }
    ]
}
```

---

## Testing Checklist

After creating a component, verify:

- [ ] Component appears in sidebar
- [ ] Component can be dragged to canvas
- [ ] Component renders correctly on canvas
- [ ] Settings panel opens when clicking component
- [ ] Settings changes update the component
- [ ] Component can be saved
- [ ] Component loads correctly after page reload
- [ ] Component renders on frontend (if applicable)
- [ ] No console errors
- [ ] Component works in both new pages and existing pages

---

## File Locations Reference

### React Builder Files (resources/js/builder-v2/)

```
resources/js/builder-v2/
├── main.jsx                          # ⚠️ Import & Resolver registration
├── builder-v2.css                    # Global styles
├── components/
│   ├── PageBuilder.jsx               # Main builder component
│   ├── Sidebar.jsx                   # ⚠️ Import & Add to sidebar
│   ├── Viewport.jsx                  # Canvas area
│   ├── SettingsPanel.jsx            # Right panel
│   ├── Toolbar.jsx                   # Top toolbar
│   ├── RenderNode.jsx               # Node renderer
│   └── user/
│       ├── YourComponent.jsx        # ⚠️ Create component here
│       ├── Container.jsx
│       ├── Heading.jsx
│       └── ... (other components)
```

### Frontend Renderer Files (resources/views/)

⚠️ **CRITICAL:** You MUST add components to BOTH render-v2.blade.php files!

```
resources/views/
├── pages/
│   ├── builder-v2.blade.php          # Builder UI
│   └── render-v2.blade.php           # ⚠️ ADD COMPONENTS HERE (File 1)
│
└── global-sections/
    └── render-v2.blade.php           # ⚠️ AND HERE! (File 2 - CRITICAL!)
```

**Why Two Files?**
- `global-sections/render-v2.blade.php` - Loads FIRST for headers/footers
- `pages/render-v2.blade.php` - Loads SECOND for page content
- Only the FIRST file's `renderNode()` function executes
- If your component is only in File 1, it will be SKIPPED
- **Solution:** Add components to BOTH files with IDENTICAL code

---

## Best Practices

1. **Always provide default props**
   ```jsx
   export const Component = ({ prop = 'default' }) => {
   ```

2. **Use semantic HTML**
   ```jsx
   <article>, <section>, <header>, etc.
   ```

3. **Make components responsive**
   ```jsx
   className="col-lg-4 col-md-6 col-sm-12"
   ```

4. **Add proper accessibility**
   ```jsx
   alt="...", aria-label="...", role="..."
   ```

5. **Handle loading states**
   ```jsx
   {loading ? <Spinner /> : <Content />}
   ```

6. **Provide meaningful placeholders**
   - Show preview data in builder
   - Show empty states when no data

7. **Use Bootstrap icons**
   ```jsx
   <i className="bi bi-icon-name"></i>
   ```

8. **Keep settings simple**
   - Only expose settings users actually need
   - Group related settings together
   - Use appropriate input types

---

## Debugging Tips

### Check if component is registered:

Open browser console and run:
```javascript
console.log(window.Craft)
```

Look for your component name in the resolver.

### Check component props:

In settings panel:
```jsx
console.log('Current props:', props);
```

### Check if component is being created:

In component:
```jsx
console.log('Component rendering:', { prop1, prop2 });
```

### Verify build:

```bash
# Check if new build was created
ls -la public/build/assets/main-*.js

# Check file timestamp matches your build time
```

---

## Summary

**The FIVE CRITICAL Steps:**

1. ✅ Create component in `components/user/YourComponent.jsx`
2. ✅ Import in `main.jsx`
3. ⚠️ **ADD TO RESOLVER in `main.jsx`** (Most commonly forgotten!)
4. ⚠️ **ADD TO pages/render-v2.blade.php** (For frontend rendering)
5. ⚠️ **ADD TO global-sections/render-v2.blade.php** (CRITICAL - Often missed!)

**Remember:**
- If you can see the component but can't drag it → You forgot Step 3 (resolver)
- If component works in builder but NOT on public website → You forgot Step 5 (global-sections file)

---

## Need Help?

Common commands:
```bash
# Build
npm run build

# Clear caches
php artisan view:clear
php artisan cache:clear
php artisan config:clear
php artisan route:clear

# Check for errors
npm run build 2>&1 | grep -i error
```

---

## Changelog

### Version 1.1 (2025-10-27)
- ⚠️ **CRITICAL:** Added documentation for dual render-v2.blade.php files
- Added Issue 6: Component works in builder but not on public website
- Added Step 8: Frontend renderer to BOTH files requirement
- Updated File Locations Reference with both render files
- Updated Summary with 5 critical steps
- Added detailed explanation of why two files exist and load order

### Version 1.0 (2025-10-27)
- Initial guide creation

---

Last Updated: 2025-10-27
Version: 1.1
