## 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
11 KiB
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:
- They don't have access to our build pipeline
- They only get the compiled
app.js - 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
Level 2: Exposed React Runtime (Recommended)
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
Recommended Approach: Level 2 (Exposed React)
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:
- ✅ Expose React on
window.WooNooW.React - ✅ Expose common components on
window.WooNooW.components - ✅ Addons can use vanilla JS (no build) or React (with build)
- ✅ Addons don't bundle React (use ours)
- ✅ Proper documentation for developers
Result:
- Simple addons: Vanilla JS, no build
- Advanced addons: React with build, external React
- Best of both worlds!