Files
WooNooW/ADDON_REACT_INTEGRATION.md
dwindown 66a194155c feat: Enhance Store Details with branding features
## 1. Architecture Decisions 

Created two comprehensive documents:

### A. ARCHITECTURE_DECISION_CUSTOMER_SPA.md
**Decision: Hybrid Approach (Option C)**

**WooNooW Plugin ($149/year):**
- Admin-SPA (full featured) 
- Customer-SPA (basic cart/checkout/account) 
- Shortcode mode (works with any theme) 
- Full SPA mode (optional) 

**Premium Themes ($79/year each):**
- Enhanced customer-spa components
- Industry-specific designs
- Optional upsell

**Revenue Analysis:**
- Option A (Core): $149K/year
- Option B (Separate): $137K/year
- **Option C (Hybrid): $164K/year**  Winner!

**Benefits:**
- 60% users get complete solution
- 30% agencies can customize
- 10% enterprise have flexibility
- Higher revenue potential
- Better market positioning

### B. ADDON_REACT_INTEGRATION.md
**Clarified addon development approach**

**Level 1: Vanilla JS** (No build)
- Simple addons use window.WooNooW API
- No build process needed
- Easy for PHP developers

**Level 2: Exposed React** (Recommended)
- WooNooW exposes React on window
- Addons can use React without bundling it
- Build with external React
- Best of both worlds

**Level 3: Slot-Based** (Advanced)
- Full React component integration
- Type safety
- Modern DX

**Implementation:**
```typescript
window.WooNooW = {
  React: React,
  ReactDOM: ReactDOM,
  hooks: { addFilter, addAction },
  components: { Button, Input, Select },
  utils: { api, toast },
};
```

---

## 2. Enhanced Store Details Page 

### New Components Created:

**A. ImageUpload Component**
- Drag & drop support
- WordPress media library integration
- File validation (type, size)
- Preview with remove button
- Loading states

**B. ColorPicker Component**
- Native color picker
- Hex input with validation
- Preset colors
- Live preview
- Popover UI

### Store Details Enhancements:

**Added to Store Identity Card:**
-  Store tagline input
-  Store logo upload (2MB max)
-  Store icon upload (1MB max)

**New Brand Colors Card:**
-  Primary color picker
-  Accent color picker
-  Error color picker
-  Reset to default button
-  Live preview

**Features:**
- All branding in one place
- No separate Brand & Appearance tab needed
- Clean, professional UI
- Easy to use
- Industry standard

---

## Summary

**Architecture:**
-  Customer-SPA in core (hybrid approach)
-  Addon React integration clarified
-  Revenue model optimized

**Implementation:**
-  ImageUpload component
-  ColorPicker component
-  Enhanced Store Details page
-  Branding features integrated

**Result:**
- Clean, focused settings
- Professional branding tools
- Better revenue potential
- Clear development path
2025-11-10 22:12:10 +07:00

11 KiB

Addon React Integration - How It Works

The Question

"How can addon developers use React if we only ship built app.js?"

You're absolutely right to question this! Let me clarify the architecture.


Current Misunderstanding

What I showed in examples:

// This WON'T work for external addons!
import { addonLoader, addFilter } from '@woonoow/hooks';
import { DestinationSearch } from './components/DestinationSearch';

addonLoader.register({
  id: 'rajaongkir-bridge',
  init: () => {
    addFilter('woonoow_order_form_after_shipping', (content) => {
      return <DestinationSearch />; // ❌ Can't do this!
    });
  }
});

Problem: External addons can't import React components because:

  1. They don't have access to our build pipeline
  2. They only get the compiled app.js
  3. React is bundled, not exposed

Solution: Three Integration Levels

Level 1: Vanilla JS/jQuery (Basic)

For simple addons that just need to inject HTML/JS

// addon-bridge.js (vanilla JS, no build needed)
(function() {
  // Wait for WooNooW to load
  window.addEventListener('woonoow:loaded', function() {
    // Access WooNooW hooks
    window.WooNooW.addFilter('woonoow_order_form_after_shipping', function(container, formData) {
      // Inject HTML
      const div = document.createElement('div');
      div.innerHTML = `
        <div class="rajaongkir-destination">
          <label>Shipping Destination</label>
          <select id="rajaongkir-dest">
            <option>Select destination...</option>
          </select>
        </div>
      `;
      container.appendChild(div);
      
      // Add event listeners
      document.getElementById('rajaongkir-dest').addEventListener('change', function(e) {
        // Update WooNooW state
        window.WooNooW.updateFormData({
          shipping: {
            ...formData.shipping,
            destination_id: e.target.value
          }
        });
      });
      
      return container;
    });
  });
})();

Pros:

  • No build process needed
  • Works immediately
  • Easy for PHP developers
  • No dependencies

Cons:

  • No React benefits
  • Manual DOM manipulation
  • No type safety

WooNooW exposes React on window for addons to use

WooNooW Core Setup:

// admin-spa/src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';

// Expose React for addons
window.WooNooW = {
  React: React,
  ReactDOM: ReactDOM,
  hooks: {
    addFilter: addFilter,
    addAction: addAction,
    // ... other hooks
  },
  components: {
    // Expose common components
    Button: Button,
    Input: Input,
    Select: Select,
    // ... other UI components
  }
};

Addon Development (with build):

// addon-bridge.js (built with Vite/Webpack)
const { React, hooks, components } = window.WooNooW;
const { addFilter } = hooks;
const { Button, Select } = components;

// Addon can now use React!
function DestinationSearch({ value, onChange }) {
  const [destinations, setDestinations] = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  
  React.useEffect(() => {
    // Fetch destinations
    fetch('/wp-json/rajaongkir/v1/destinations')
      .then(res => res.json())
      .then(data => setDestinations(data));
  }, []);
  
  return React.createElement('div', { className: 'rajaongkir-search' },
    React.createElement('label', null, 'Shipping Destination'),
    React.createElement(Select, {
      value: value,
      onChange: onChange,
      options: destinations,
      loading: loading
    })
  );
}

// Register with WooNooW
addFilter('woonoow_order_form_after_shipping', function(container, formData, setFormData) {
  const root = ReactDOM.createRoot(container);
  root.render(
    React.createElement(DestinationSearch, {
      value: formData.shipping?.destination_id,
      onChange: (value) => setFormData({
        ...formData,
        shipping: { ...formData.shipping, destination_id: value }
      })
    })
  );
  return container;
});

Addon Build Setup:

// vite.config.js
export default {
  build: {
    lib: {
      entry: 'src/addon.js',
      name: 'RajaongkirBridge',
      fileName: 'addon'
    },
    rollupOptions: {
      external: ['react', 'react-dom'], // Don't bundle React
      output: {
        globals: {
          react: 'window.WooNooW.React',
          'react-dom': 'window.WooNooW.ReactDOM'
        }
      }
    }
  }
};

Pros:

  • Can use React
  • Access to WooNooW components
  • Better DX
  • Type safety (with TypeScript)

Cons:

  • Requires build process
  • More complex setup

Level 3: Slot-Based Rendering (Advanced)

WooNooW renders addon components via slots

WooNooW Core:

// OrderForm.tsx
function OrderForm() {
  // ... form logic
  
  return (
    <div>
      {/* ... shipping fields ... */}
      
      {/* Slot for addons to inject */}
      <AddonSlot 
        name="order_form_after_shipping"
        props={{ formData, setFormData }}
      />
    </div>
  );
}

// AddonSlot.tsx
function AddonSlot({ name, props }) {
  const slots = useAddonSlots(name);
  
  return (
    <>
      {slots.map((slot, index) => (
        <div key={index} data-addon-slot={slot.id}>
          {slot.component(props)}
        </div>
      ))}
    </>
  );
}

Addon Registration (PHP):

// rajaongkir-bridge.php
add_filter('woonoow/addon_slots', function($slots) {
    $slots['order_form_after_shipping'][] = [
        'id' => 'rajaongkir-destination',
        'component' => 'RajaongkirDestination', // Component name
        'script' => plugin_dir_url(__FILE__) . 'dist/addon.js',
        'priority' => 10,
    ];
    return $slots;
});

Addon Component (React with build):

// addon/src/DestinationSearch.tsx
import React, { useState, useEffect } from 'react';

export function RajaongkirDestination({ formData, setFormData }) {
  const [destinations, setDestinations] = useState([]);
  
  useEffect(() => {
    fetch('/wp-json/rajaongkir/v1/destinations')
      .then(res => res.json())
      .then(setDestinations);
  }, []);
  
  return (
    <div className="rajaongkir-destination">
      <label>Shipping Destination</label>
      <select
        value={formData.shipping?.destination_id || ''}
        onChange={(e) => setFormData({
          ...formData,
          shipping: {
            ...formData.shipping,
            destination_id: e.target.value
          }
        })}
      >
        <option value="">Select destination...</option>
        {destinations.map(dest => (
          <option key={dest.id} value={dest.id}>
            {dest.label}
          </option>
        ))}
      </select>
    </div>
  );
}

// Export for WooNooW to load
window.WooNooWAddons = window.WooNooWAddons || {};
window.WooNooWAddons.RajaongkirDestination = RajaongkirDestination;

Pros:

  • Full React support
  • Type safety
  • Modern DX
  • Proper component lifecycle

Cons:

  • Most complex
  • Requires build process
  • More WooNooW core complexity

Implementation in WooNooW Core:

// admin-spa/src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient } from '@tanstack/react-query';

// UI Components
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select } from '@/components/ui/select';
import { Label } from '@/components/ui/label';
// ... other components

// Hooks
import { addFilter, addAction, applyFilters, doAction } from '@/lib/hooks';

// Expose WooNooW API
window.WooNooW = {
  // React runtime
  React: React,
  ReactDOM: ReactDOM,
  
  // Hooks system
  hooks: {
    addFilter,
    addAction,
    applyFilters,
    doAction,
  },
  
  // UI Components (shadcn/ui)
  components: {
    Button,
    Input,
    Select,
    Label,
    // ... expose commonly used components
  },
  
  // Utilities
  utils: {
    api: api, // API client
    toast: toast, // Toast notifications
  },
  
  // Version
  version: '1.0.0',
};

// Emit loaded event
window.dispatchEvent(new CustomEvent('woonoow:loaded'));

Addon Developer Experience:

Option 1: Vanilla JS (No Build)

// addon.js
(function() {
  const { React, hooks, components } = window.WooNooW;
  const { addFilter } = hooks;
  const { Select } = components;
  
  addFilter('woonoow_order_form_after_shipping', function(container, props) {
    // Use React.createElement (no JSX)
    const element = React.createElement(Select, {
      label: 'Destination',
      options: [...],
      value: props.formData.shipping?.destination_id,
      onChange: (value) => props.setFormData({...})
    });
    
    const root = ReactDOM.createRoot(container);
    root.render(element);
    
    return container;
  });
})();

Option 2: With Build (JSX Support)

// addon/src/index.tsx
const { React, hooks, components } = window.WooNooW;
const { addFilter } = hooks;
const { Select } = components;

function DestinationSearch({ formData, setFormData }) {
  return (
    <Select
      label="Destination"
      options={[...]}
      value={formData.shipping?.destination_id}
      onChange={(value) => setFormData({
        ...formData,
        shipping: { ...formData.shipping, destination_id: value }
      })}
    />
  );
}

addFilter('woonoow_order_form_after_shipping', (container, props) => {
  const root = ReactDOM.createRoot(container);
  root.render(<DestinationSearch {...props} />);
  return container;
});
// vite.config.js
export default {
  build: {
    lib: {
      entry: 'src/index.tsx',
      formats: ['iife'],
      name: 'RajaongkirAddon'
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'window.WooNooW.React',
          'react-dom': 'window.WooNooW.ReactDOM'
        }
      }
    }
  }
};

Documentation for Addon Developers

Quick Start Guide:

# WooNooW Addon Development

## Level 1: Vanilla JS (Easiest)

No build process needed. Just use `window.WooNooW` API.

## Level 2: React with Build (Recommended)

1. Setup project:
   npm init
   npm install --save-dev vite @types/react

2. Configure vite.config.js (see example above)

3. Use WooNooW's React:
   const { React } = window.WooNooW;

4. Build:
   npm run build

5. Enqueue in WordPress:
   wp_enqueue_script('my-addon', plugin_dir_url(__FILE__) . 'dist/addon.js', ['woonoow-admin'], '1.0.0', true);

Summary

Your concern was valid!

Solution:

  1. Expose React on window.WooNooW.React
  2. Expose common components on window.WooNooW.components
  3. Addons can use vanilla JS (no build) or React (with build)
  4. Addons don't bundle React (use ours)
  5. Proper documentation for developers

Result:

  • Simple addons: Vanilla JS, no build
  • Advanced addons: React with build, external React
  • Best of both worlds!