feat: Tax settings + unified addon guide + Biteship spec
## 1. Created BITESHIP_ADDON_SPEC.md ✅ - Complete plugin specification - Database schema, API endpoints - WooCommerce integration - React components - Implementation timeline ## 2. Merged Addon Documentation ✅ Created ADDON_DEVELOPMENT_GUIDE.md (single source of truth): - Merged ADDON_INJECTION_GUIDE.md + ADDON_HOOK_SYSTEM.md - Two addon types: Route Injection + Hook System - Clear examples for each type - Best practices and troubleshooting - Deleted old documents ## 3. Tax Settings ✅ Frontend (admin-spa/src/routes/Settings/Tax.tsx): - Enable/disable tax calculation toggle - Display standard/reduced/zero tax rates - Show tax options (prices include tax, based on, display) - Link to WooCommerce for advanced config - Clean, simple UI Backend (includes/Api/TaxController.php): - GET /settings/tax - Fetch tax settings - POST /settings/tax/toggle - Enable/disable taxes - Fetches rates from woocommerce_tax_rates table - Clears WooCommerce cache on update ## 4. Advanced Local Pickup - TODO Will be simple: Admin adds multiple pickup locations ## Key Decisions: ✅ Hook system = No hardcoding, zero coupling ✅ Tax settings = Simple toggle + view, advanced in WC ✅ Single addon guide = One source of truth Next: Advanced Local Pickup locations
This commit is contained in:
715
ADDON_DEVELOPMENT_GUIDE.md
Normal file
715
ADDON_DEVELOPMENT_GUIDE.md
Normal file
@@ -0,0 +1,715 @@
|
||||
# WooNooW Addon Development Guide
|
||||
|
||||
**Version:** 2.0.0
|
||||
**Last Updated:** November 9, 2025
|
||||
**Status:** Production Ready
|
||||
|
||||
---
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Addon Types](#addon-types)
|
||||
3. [Quick Start](#quick-start)
|
||||
4. [SPA Route Injection](#spa-route-injection)
|
||||
5. [Hook System Integration](#hook-system-integration)
|
||||
6. [Component Development](#component-development)
|
||||
7. [Best Practices](#best-practices)
|
||||
8. [Examples](#examples)
|
||||
9. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
WooNooW provides **two powerful addon systems**:
|
||||
|
||||
### 1. **SPA Route Injection** (Admin UI)
|
||||
- ✅ Register custom SPA routes
|
||||
- ✅ Inject navigation menu items
|
||||
- ✅ Add submenu items to existing sections
|
||||
- ✅ Load React components dynamically
|
||||
- ✅ Full isolation and safety
|
||||
|
||||
### 2. **Hook System** (Functional Extension)
|
||||
- ✅ Extend OrderForm, ProductForm, etc.
|
||||
- ✅ Add custom fields and validation
|
||||
- ✅ Inject components at specific points
|
||||
- ✅ Zero coupling with core
|
||||
- ✅ WordPress-style filters and actions
|
||||
|
||||
**Both systems work together seamlessly!**
|
||||
|
||||
---
|
||||
|
||||
## Addon Types
|
||||
|
||||
### Type A: UI-Only Addon (Route Injection)
|
||||
**Use when:** Adding new pages/sections to admin
|
||||
|
||||
**Example:** Reports, Analytics, Custom Dashboard
|
||||
|
||||
```php
|
||||
// Registers routes + navigation
|
||||
add_filter('woonoow/spa_routes', ...);
|
||||
add_filter('woonoow/nav_tree', ...);
|
||||
```
|
||||
|
||||
### Type B: Functional Addon (Hook System)
|
||||
**Use when:** Extending existing functionality
|
||||
|
||||
**Example:** Indonesia Shipping, Custom Fields, Validation
|
||||
|
||||
```typescript
|
||||
// Registers hooks
|
||||
addFilter('woonoow_order_form_after_shipping', ...);
|
||||
addAction('woonoow_order_created', ...);
|
||||
```
|
||||
|
||||
### Type C: Full-Featured Addon (Both Systems)
|
||||
**Use when:** Complex integration needed
|
||||
|
||||
**Example:** Subscriptions, Bookings, Memberships
|
||||
|
||||
```php
|
||||
// Backend: Routes + Hooks
|
||||
add_filter('woonoow/spa_routes', ...);
|
||||
add_filter('woonoow/nav_tree', ...);
|
||||
|
||||
// Frontend: Hook registration
|
||||
addonLoader.register({
|
||||
init: () => {
|
||||
addFilter('woonoow_order_form_custom_sections', ...);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Step 1: Create Plugin File
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: My WooNooW Addon
|
||||
* Description: Extends WooNooW functionality
|
||||
* Version: 1.0.0
|
||||
* Requires: WooNooW 1.0.0+
|
||||
*/
|
||||
|
||||
// 1. Register addon
|
||||
add_filter('woonoow/addon_registry', function($addons) {
|
||||
$addons['my-addon'] = [
|
||||
'id' => 'my-addon',
|
||||
'name' => 'My Addon',
|
||||
'version' => '1.0.0',
|
||||
'spa_bundle' => plugin_dir_url(__FILE__) . 'dist/addon.js',
|
||||
'dependencies' => ['woocommerce' => '8.0'],
|
||||
];
|
||||
return $addons;
|
||||
});
|
||||
|
||||
// 2. Register routes (optional - for UI pages)
|
||||
add_filter('woonoow/spa_routes', function($routes) {
|
||||
$routes[] = [
|
||||
'path' => '/my-addon',
|
||||
'component_url' => plugin_dir_url(__FILE__) . 'dist/MyPage.js',
|
||||
'capability' => 'manage_woocommerce',
|
||||
'title' => 'My Addon',
|
||||
];
|
||||
return $routes;
|
||||
});
|
||||
|
||||
// 3. Add navigation (optional - for UI pages)
|
||||
add_filter('woonoow/nav_tree', function($tree) {
|
||||
$tree[] = [
|
||||
'key' => 'my-addon',
|
||||
'label' => 'My Addon',
|
||||
'path' => '/my-addon',
|
||||
'icon' => 'puzzle',
|
||||
];
|
||||
return $tree;
|
||||
});
|
||||
```
|
||||
|
||||
### Step 2: Create Frontend Integration
|
||||
|
||||
```typescript
|
||||
// admin-spa/src/index.ts
|
||||
|
||||
import { addonLoader, addFilter } from '@woonoow/hooks';
|
||||
|
||||
addonLoader.register({
|
||||
id: 'my-addon',
|
||||
name: 'My Addon',
|
||||
version: '1.0.0',
|
||||
init: () => {
|
||||
// Register hooks here
|
||||
addFilter('woonoow_order_form_custom_sections', (content, formData, setFormData) => {
|
||||
return (
|
||||
<>
|
||||
{content}
|
||||
<MyCustomSection data={formData} onChange={setFormData} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Step 3: Build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
**Done!** Your addon is now integrated.
|
||||
|
||||
---
|
||||
|
||||
## SPA Route Injection
|
||||
|
||||
### Register Routes
|
||||
|
||||
```php
|
||||
add_filter('woonoow/spa_routes', function($routes) {
|
||||
$base_url = plugin_dir_url(__FILE__) . 'dist/';
|
||||
|
||||
$routes[] = [
|
||||
'path' => '/subscriptions',
|
||||
'component_url' => $base_url . 'SubscriptionsList.js',
|
||||
'capability' => 'manage_woocommerce',
|
||||
'title' => 'Subscriptions',
|
||||
];
|
||||
|
||||
$routes[] = [
|
||||
'path' => '/subscriptions/:id',
|
||||
'component_url' => $base_url . 'SubscriptionDetail.js',
|
||||
'capability' => 'manage_woocommerce',
|
||||
'title' => 'Subscription Detail',
|
||||
];
|
||||
|
||||
return $routes;
|
||||
});
|
||||
```
|
||||
|
||||
### Add Navigation
|
||||
|
||||
```php
|
||||
// Main menu item
|
||||
add_filter('woonoow/nav_tree', function($tree) {
|
||||
$tree[] = [
|
||||
'key' => 'subscriptions',
|
||||
'label' => __('Subscriptions', 'my-addon'),
|
||||
'path' => '/subscriptions',
|
||||
'icon' => 'repeat',
|
||||
'children' => [
|
||||
[
|
||||
'label' => __('All Subscriptions', 'my-addon'),
|
||||
'mode' => 'spa',
|
||||
'path' => '/subscriptions',
|
||||
],
|
||||
[
|
||||
'label' => __('New', 'my-addon'),
|
||||
'mode' => 'spa',
|
||||
'path' => '/subscriptions/new',
|
||||
],
|
||||
],
|
||||
];
|
||||
return $tree;
|
||||
});
|
||||
|
||||
// Or inject into existing section
|
||||
add_filter('woonoow/nav_tree/products/children', function($children) {
|
||||
$children[] = [
|
||||
'label' => __('Bundles', 'my-addon'),
|
||||
'mode' => 'spa',
|
||||
'path' => '/products/bundles',
|
||||
];
|
||||
return $children;
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hook System Integration
|
||||
|
||||
### Available Hooks
|
||||
|
||||
#### Order Form Hooks
|
||||
```typescript
|
||||
// Add fields after billing address
|
||||
'woonoow_order_form_after_billing'
|
||||
|
||||
// Add fields after shipping address
|
||||
'woonoow_order_form_after_shipping'
|
||||
|
||||
// Add custom shipping fields
|
||||
'woonoow_order_form_shipping_fields'
|
||||
|
||||
// Add custom sections
|
||||
'woonoow_order_form_custom_sections'
|
||||
|
||||
// Add validation rules
|
||||
'woonoow_order_form_validation'
|
||||
|
||||
// Modify form data before render
|
||||
'woonoow_order_form_data'
|
||||
```
|
||||
|
||||
#### Action Hooks
|
||||
```typescript
|
||||
// Before form submission
|
||||
'woonoow_order_form_submit'
|
||||
|
||||
// After order created
|
||||
'woonoow_order_created'
|
||||
|
||||
// After order updated
|
||||
'woonoow_order_updated'
|
||||
```
|
||||
|
||||
### Hook Registration Example
|
||||
|
||||
```typescript
|
||||
import { addonLoader, addFilter, addAction } from '@woonoow/hooks';
|
||||
|
||||
addonLoader.register({
|
||||
id: 'indonesia-shipping',
|
||||
name: 'Indonesia Shipping',
|
||||
version: '1.0.0',
|
||||
init: () => {
|
||||
// Filter: Add subdistrict selector
|
||||
addFilter('woonoow_order_form_after_shipping', (content, formData, setFormData) => {
|
||||
return (
|
||||
<>
|
||||
{content}
|
||||
<SubdistrictSelector
|
||||
value={formData.shipping?.subdistrict_id}
|
||||
onChange={(id) => setFormData({
|
||||
...formData,
|
||||
shipping: { ...formData.shipping, subdistrict_id: id }
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
// Filter: Add validation
|
||||
addFilter('woonoow_order_form_validation', (errors, formData) => {
|
||||
if (!formData.shipping?.subdistrict_id) {
|
||||
errors.subdistrict = 'Subdistrict is required';
|
||||
}
|
||||
return errors;
|
||||
});
|
||||
|
||||
// Action: Log when order created
|
||||
addAction('woonoow_order_created', (orderId, orderData) => {
|
||||
console.log('Order created:', orderId);
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Hook System Benefits
|
||||
|
||||
✅ **Zero Coupling**
|
||||
```typescript
|
||||
// WooNooW Core has no knowledge of your addon
|
||||
{applyFilters('woonoow_order_form_after_shipping', null, formData, setFormData)}
|
||||
|
||||
// If addon exists: Returns your component
|
||||
// If addon doesn't exist: Returns null
|
||||
// No import, no error!
|
||||
```
|
||||
|
||||
✅ **Multiple Addons Can Hook**
|
||||
```typescript
|
||||
// Addon A
|
||||
addFilter('woonoow_order_form_after_shipping', (content) => {
|
||||
return <>{content}<AddonAFields /></>;
|
||||
});
|
||||
|
||||
// Addon B
|
||||
addFilter('woonoow_order_form_after_shipping', (content) => {
|
||||
return <>{content}<AddonBFields /></>;
|
||||
});
|
||||
|
||||
// Both render!
|
||||
```
|
||||
|
||||
✅ **Type Safety**
|
||||
```typescript
|
||||
addFilter<ReactNode, [OrderFormData, SetState<OrderFormData>]>(
|
||||
'woonoow_order_form_after_shipping',
|
||||
(content, formData, setFormData) => {
|
||||
// TypeScript knows the types!
|
||||
return <MyComponent />;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Development
|
||||
|
||||
### Basic Component
|
||||
|
||||
```typescript
|
||||
// dist/MyPage.tsx
|
||||
import React from 'react';
|
||||
|
||||
export default function MyPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="rounded-lg border p-6 bg-card">
|
||||
<h2 className="text-xl font-semibold mb-2">My Addon</h2>
|
||||
<p className="text-sm opacity-70">Welcome!</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Access WooNooW APIs
|
||||
|
||||
```typescript
|
||||
// Access REST API
|
||||
const api = (window as any).WNW_API;
|
||||
const response = await fetch(`${api.root}my-addon/endpoint`, {
|
||||
headers: { 'X-WP-Nonce': api.nonce },
|
||||
});
|
||||
|
||||
// Access store data
|
||||
const store = (window as any).WNW_STORE;
|
||||
console.log('Currency:', store.currency);
|
||||
|
||||
// Access site info
|
||||
const wnw = (window as any).wnw;
|
||||
console.log('Site Title:', wnw.siteTitle);
|
||||
```
|
||||
|
||||
### Use WooNooW Components
|
||||
|
||||
```typescript
|
||||
import { __ } from '@/lib/i18n';
|
||||
import { formatMoney } from '@/lib/currency';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card } from '@/components/ui/card';
|
||||
|
||||
export default function MyPage() {
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<h2>{__('My Addon', 'my-addon')}</h2>
|
||||
<p>{formatMoney(1234.56)}</p>
|
||||
<Button>{__('Click Me', 'my-addon')}</Button>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Build Configuration
|
||||
|
||||
```javascript
|
||||
// vite.config.js
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
lib: {
|
||||
entry: 'src/index.ts',
|
||||
name: 'MyAddon',
|
||||
fileName: 'addon',
|
||||
formats: ['es'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['react', 'react-dom'],
|
||||
output: {
|
||||
globals: {
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ DO:
|
||||
|
||||
1. **Use Hook System for Functional Extensions**
|
||||
```typescript
|
||||
// ✅ Good - No hardcoding
|
||||
addFilter('woonoow_order_form_after_shipping', ...);
|
||||
```
|
||||
|
||||
2. **Use Route Injection for New Pages**
|
||||
```php
|
||||
// ✅ Good - Separate UI
|
||||
add_filter('woonoow/spa_routes', ...);
|
||||
```
|
||||
|
||||
3. **Declare Dependencies**
|
||||
```php
|
||||
'dependencies' => ['woocommerce' => '8.0']
|
||||
```
|
||||
|
||||
4. **Check Capabilities**
|
||||
```php
|
||||
'capability' => 'manage_woocommerce'
|
||||
```
|
||||
|
||||
5. **Internationalize Strings**
|
||||
```php
|
||||
'label' => __('My Addon', 'my-addon')
|
||||
```
|
||||
|
||||
6. **Handle Errors Gracefully**
|
||||
```typescript
|
||||
try {
|
||||
await api.post(...);
|
||||
} catch (error) {
|
||||
toast.error('Failed to save');
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ DON'T:
|
||||
|
||||
1. **Don't Hardcode Addon Components in Core**
|
||||
```typescript
|
||||
// ❌ Bad - Breaks if addon not installed
|
||||
import { SubdistrictSelector } from 'addon';
|
||||
<SubdistrictSelector />
|
||||
|
||||
// ✅ Good - Use hooks
|
||||
{applyFilters('woonoow_order_form_after_shipping', null)}
|
||||
```
|
||||
|
||||
2. **Don't Skip Capability Checks**
|
||||
```php
|
||||
// ❌ Bad
|
||||
'capability' => ''
|
||||
|
||||
// ✅ Good
|
||||
'capability' => 'manage_woocommerce'
|
||||
```
|
||||
|
||||
3. **Don't Modify Core Navigation**
|
||||
```php
|
||||
// ❌ Bad
|
||||
unset($tree[0]);
|
||||
|
||||
// ✅ Good
|
||||
$tree[] = ['key' => 'my-addon', ...];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Simple UI Addon (Route Injection Only)
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: WooNooW Reports
|
||||
* Description: Custom reports page
|
||||
*/
|
||||
|
||||
add_filter('woonoow/addon_registry', function($addons) {
|
||||
$addons['reports'] = [
|
||||
'id' => 'reports',
|
||||
'name' => 'Reports',
|
||||
'version' => '1.0.0',
|
||||
];
|
||||
return $addons;
|
||||
});
|
||||
|
||||
add_filter('woonoow/spa_routes', function($routes) {
|
||||
$routes[] = [
|
||||
'path' => '/reports',
|
||||
'component_url' => plugin_dir_url(__FILE__) . 'dist/Reports.js',
|
||||
'title' => 'Reports',
|
||||
];
|
||||
return $routes;
|
||||
});
|
||||
|
||||
add_filter('woonoow/nav_tree', function($tree) {
|
||||
$tree[] = [
|
||||
'key' => 'reports',
|
||||
'label' => 'Reports',
|
||||
'path' => '/reports',
|
||||
'icon' => 'bar-chart',
|
||||
];
|
||||
return $tree;
|
||||
});
|
||||
```
|
||||
|
||||
### Example 2: Functional Addon (Hook System Only)
|
||||
|
||||
```typescript
|
||||
// Indonesia Shipping - No UI pages, just extends OrderForm
|
||||
|
||||
import { addonLoader, addFilter } from '@woonoow/hooks';
|
||||
import { SubdistrictSelector } from './components/SubdistrictSelector';
|
||||
|
||||
addonLoader.register({
|
||||
id: 'indonesia-shipping',
|
||||
name: 'Indonesia Shipping',
|
||||
version: '1.0.0',
|
||||
init: () => {
|
||||
addFilter('woonoow_order_form_after_shipping', (content, formData, setFormData) => {
|
||||
return (
|
||||
<>
|
||||
{content}
|
||||
<div className="border rounded-lg p-4 mt-4">
|
||||
<h3 className="font-medium mb-3">📍 Shipping Destination</h3>
|
||||
<SubdistrictSelector
|
||||
value={formData.shipping?.subdistrict_id}
|
||||
onChange={(id) => setFormData({
|
||||
...formData,
|
||||
shipping: { ...formData.shipping, subdistrict_id: id }
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Example 3: Full-Featured Addon (Both Systems)
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: WooNooW Subscriptions
|
||||
* Description: Subscription management
|
||||
*/
|
||||
|
||||
// Backend: Register addon + routes
|
||||
add_filter('woonoow/addon_registry', function($addons) {
|
||||
$addons['subscriptions'] = [
|
||||
'id' => 'subscriptions',
|
||||
'name' => 'Subscriptions',
|
||||
'version' => '1.0.0',
|
||||
'spa_bundle' => plugin_dir_url(__FILE__) . 'dist/addon.js',
|
||||
];
|
||||
return $addons;
|
||||
});
|
||||
|
||||
add_filter('woonoow/spa_routes', function($routes) {
|
||||
$routes[] = [
|
||||
'path' => '/subscriptions',
|
||||
'component_url' => plugin_dir_url(__FILE__) . 'dist/SubscriptionsList.js',
|
||||
];
|
||||
return $routes;
|
||||
});
|
||||
|
||||
add_filter('woonoow/nav_tree', function($tree) {
|
||||
$tree[] = [
|
||||
'key' => 'subscriptions',
|
||||
'label' => 'Subscriptions',
|
||||
'path' => '/subscriptions',
|
||||
'icon' => 'repeat',
|
||||
];
|
||||
return $tree;
|
||||
});
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Frontend: Hook integration
|
||||
|
||||
import { addonLoader, addFilter } from '@woonoow/hooks';
|
||||
|
||||
addonLoader.register({
|
||||
id: 'subscriptions',
|
||||
name: 'Subscriptions',
|
||||
version: '1.0.0',
|
||||
init: () => {
|
||||
// Add subscription fields to order form
|
||||
addFilter('woonoow_order_form_custom_sections', (content, formData, setFormData) => {
|
||||
return (
|
||||
<>
|
||||
{content}
|
||||
<SubscriptionOptions data={formData} onChange={setFormData} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
// Add subscription fields to product form
|
||||
addFilter('woonoow_product_form_fields', (content, formData, setFormData) => {
|
||||
return (
|
||||
<>
|
||||
{content}
|
||||
<SubscriptionSettings data={formData} onChange={setFormData} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Addon Not Appearing?
|
||||
- Check dependencies are met
|
||||
- Verify capability requirements
|
||||
- Check browser console for errors
|
||||
- Flush caches: `?flush_wnw_cache=1`
|
||||
|
||||
### Route Not Loading?
|
||||
- Verify `component_url` is correct
|
||||
- Check file exists and is accessible
|
||||
- Look for JS errors in console
|
||||
- Ensure component exports `default`
|
||||
|
||||
### Hook Not Firing?
|
||||
- Check hook name is correct
|
||||
- Verify addon is registered
|
||||
- Check `window.WNW_ADDONS` in console
|
||||
- Ensure `init()` function runs
|
||||
|
||||
### Component Not Rendering?
|
||||
- Check for React errors in console
|
||||
- Verify component returns valid JSX
|
||||
- Check props are passed correctly
|
||||
- Test component in isolation
|
||||
|
||||
---
|
||||
|
||||
## Support & Resources
|
||||
|
||||
**Documentation:**
|
||||
- `ADDON_INJECTION_GUIDE.md` - SPA route injection (legacy)
|
||||
- `ADDON_HOOK_SYSTEM.md` - Hook system details (legacy)
|
||||
- `BITESHIP_ADDON_SPEC.md` - Indonesia shipping example
|
||||
- `SHIPPING_ADDON_RESEARCH.md` - Shipping integration patterns
|
||||
|
||||
**Code References:**
|
||||
- `includes/Compat/AddonRegistry.php` - Addon registration
|
||||
- `includes/Compat/RouteRegistry.php` - Route management
|
||||
- `includes/Compat/NavigationRegistry.php` - Navigation building
|
||||
- `admin-spa/src/lib/hooks.ts` - Hook system implementation
|
||||
- `admin-spa/src/App.tsx` - Dynamic route loading
|
||||
|
||||
---
|
||||
|
||||
**End of Guide**
|
||||
|
||||
**Version:** 2.0.0
|
||||
**Last Updated:** November 9, 2025
|
||||
**Status:** ✅ Production Ready
|
||||
|
||||
**This is the single source of truth for WooNooW addon development.**
|
||||
Reference in New Issue
Block a user