docs: consolidate markdown documentation into master guides and remove obsolete files
This commit is contained in:
@@ -1,325 +0,0 @@
|
|||||||
# Addon Bridge Pattern - Rajaongkir Example
|
|
||||||
|
|
||||||
## Philosophy
|
|
||||||
|
|
||||||
**WooNooW Core = Zero Addon Dependencies**
|
|
||||||
|
|
||||||
We don't integrate specific addons into WooNooW core. Instead, we provide:
|
|
||||||
1. **Hook system** for addons to extend functionality
|
|
||||||
2. **Bridge snippets** for compatibility with existing plugins
|
|
||||||
3. **Addon development guide** for building proper WooNooW addons
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Problem: Rajaongkir Plugin
|
|
||||||
|
|
||||||
Rajaongkir is a WooCommerce plugin that:
|
|
||||||
- Removes standard address fields (city, state)
|
|
||||||
- Adds custom destination dropdown
|
|
||||||
- Stores data in WooCommerce session
|
|
||||||
- Works on WooCommerce checkout page
|
|
||||||
|
|
||||||
**It doesn't work with WooNooW OrderForm because:**
|
|
||||||
- OrderForm uses standard WooCommerce fields
|
|
||||||
- Rajaongkir expects session-based destination
|
|
||||||
- No destination = No shipping calculation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Solution: Bridge Snippet (Not Core Integration!)
|
|
||||||
|
|
||||||
### Option A: Standalone Bridge Plugin
|
|
||||||
|
|
||||||
Create a tiny bridge plugin that makes Rajaongkir work with WooNooW:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Plugin Name: WooNooW Rajaongkir Bridge
|
|
||||||
* Description: Makes Rajaongkir plugin work with WooNooW OrderForm
|
|
||||||
* Version: 1.0.0
|
|
||||||
* Requires: WooNooW, Rajaongkir Official
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Hook into WooNooW's shipping calculation
|
|
||||||
add_filter('woonoow_before_shipping_calculate', function($shipping_data) {
|
|
||||||
// If Indonesia and has city, convert to Rajaongkir destination
|
|
||||||
if ($shipping_data['country'] === 'ID' && !empty($shipping_data['city'])) {
|
|
||||||
// Search Rajaongkir API for destination
|
|
||||||
$api = Cekongkir_API::get_instance();
|
|
||||||
$results = $api->search_destination_api($shipping_data['city']);
|
|
||||||
|
|
||||||
if (!empty($results[0])) {
|
|
||||||
// Set Rajaongkir session data
|
|
||||||
WC()->session->set('selected_destination_id', $results[0]['id']);
|
|
||||||
WC()->session->set('selected_destination_label', $results[0]['text']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $shipping_data;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add Rajaongkir destination field to OrderForm via hook system
|
|
||||||
add_action('wp_enqueue_scripts', function() {
|
|
||||||
if (!is_admin()) return;
|
|
||||||
|
|
||||||
wp_enqueue_script(
|
|
||||||
'woonoow-rajaongkir-bridge',
|
|
||||||
plugin_dir_url(__FILE__) . 'dist/bridge.js',
|
|
||||||
['woonoow-admin'],
|
|
||||||
'1.0.0',
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Frontend (bridge.js):**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { addonLoader, addFilter } from '@woonoow/hooks';
|
|
||||||
|
|
||||||
addonLoader.register({
|
|
||||||
id: 'rajaongkir-bridge',
|
|
||||||
name: 'Rajaongkir Bridge',
|
|
||||||
version: '1.0.0',
|
|
||||||
init: () => {
|
|
||||||
// Add destination search field after shipping address
|
|
||||||
addFilter('woonoow_order_form_after_shipping', (content, formData, setFormData) => {
|
|
||||||
// Only for Indonesia
|
|
||||||
if (formData.shipping?.country !== 'ID') return content;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{content}
|
|
||||||
<div className="border rounded-lg p-4 mt-4">
|
|
||||||
<h3 className="font-medium mb-3">📍 Shipping Destination</h3>
|
|
||||||
<RajaongkirDestinationSearch
|
|
||||||
value={formData.shipping?.destination_id}
|
|
||||||
onChange={(id, label) => {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
shipping: {
|
|
||||||
...formData.shipping,
|
|
||||||
destination_id: id,
|
|
||||||
destination_label: label,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option B: Code Snippet (No Plugin)
|
|
||||||
|
|
||||||
For users who don't want a separate plugin, provide a code snippet:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Add to theme's functions.php or custom plugin
|
|
||||||
|
|
||||||
// Bridge Rajaongkir with WooNooW
|
|
||||||
add_filter('woonoow_shipping_data', function($data) {
|
|
||||||
if ($data['country'] === 'ID' && !empty($data['city'])) {
|
|
||||||
// Auto-search and set destination
|
|
||||||
$api = Cekongkir_API::get_instance();
|
|
||||||
$results = $api->search_destination_api($data['city']);
|
|
||||||
if (!empty($results[0])) {
|
|
||||||
WC()->session->set('selected_destination_id', $results[0]['id']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $data;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Proper Solution: Build WooNooW Addon
|
|
||||||
|
|
||||||
Instead of bridging Rajaongkir, build a proper WooNooW addon:
|
|
||||||
|
|
||||||
**WooNooW Indonesia Shipping Addon**
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Plugin Name: WooNooW Indonesia Shipping
|
|
||||||
* Description: Indonesia shipping with Rajaongkir API
|
|
||||||
* Version: 1.0.0
|
|
||||||
* Requires: WooNooW 1.0.0+
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Register addon
|
|
||||||
add_filter('woonoow/addon_registry', function($addons) {
|
|
||||||
$addons['indonesia-shipping'] = [
|
|
||||||
'id' => 'indonesia-shipping',
|
|
||||||
'name' => 'Indonesia Shipping',
|
|
||||||
'version' => '1.0.0',
|
|
||||||
'spa_bundle' => plugin_dir_url(__FILE__) . 'dist/addon.js',
|
|
||||||
'dependencies' => ['woocommerce' => '8.0'],
|
|
||||||
];
|
|
||||||
return $addons;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add API endpoints
|
|
||||||
add_action('rest_api_init', function() {
|
|
||||||
register_rest_route('woonoow/v1', '/indonesia/search-destination', [
|
|
||||||
'methods' => 'GET',
|
|
||||||
'callback' => function($req) {
|
|
||||||
$query = $req->get_param('query');
|
|
||||||
$api = new RajaongkirAPI(get_option('rajaongkir_api_key'));
|
|
||||||
return $api->searchDestination($query);
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
register_rest_route('woonoow/v1', '/indonesia/calculate-shipping', [
|
|
||||||
'methods' => 'POST',
|
|
||||||
'callback' => function($req) {
|
|
||||||
$origin = $req->get_param('origin');
|
|
||||||
$destination = $req->get_param('destination');
|
|
||||||
$weight = $req->get_param('weight');
|
|
||||||
|
|
||||||
$api = new RajaongkirAPI(get_option('rajaongkir_api_key'));
|
|
||||||
return $api->calculateShipping($origin, $destination, $weight);
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Frontend:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// dist/addon.ts
|
|
||||||
|
|
||||||
import { addonLoader, addFilter } from '@woonoow/hooks';
|
|
||||||
import { DestinationSearch } from './components/DestinationSearch';
|
|
||||||
|
|
||||||
addonLoader.register({
|
|
||||||
id: 'indonesia-shipping',
|
|
||||||
name: 'Indonesia Shipping',
|
|
||||||
version: '1.0.0',
|
|
||||||
init: () => {
|
|
||||||
// Add destination field
|
|
||||||
addFilter('woonoow_order_form_after_shipping', (content, formData, setFormData) => {
|
|
||||||
if (formData.shipping?.country !== 'ID') return content;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{content}
|
|
||||||
<DestinationSearch
|
|
||||||
value={formData.shipping?.destination_id}
|
|
||||||
onChange={(id, label) => {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
shipping: { ...formData.shipping, destination_id: id, destination_label: label }
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add validation
|
|
||||||
addFilter('woonoow_order_form_validation', (errors, formData) => {
|
|
||||||
if (formData.shipping?.country === 'ID' && !formData.shipping?.destination_id) {
|
|
||||||
errors.destination = 'Please select shipping destination';
|
|
||||||
}
|
|
||||||
return errors;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Comparison
|
|
||||||
|
|
||||||
### Bridge Snippet (Quick Fix)
|
|
||||||
✅ Works immediately
|
|
||||||
✅ No new plugin needed
|
|
||||||
✅ Minimal code
|
|
||||||
❌ Depends on Rajaongkir plugin
|
|
||||||
❌ Limited features
|
|
||||||
❌ Not ideal UX
|
|
||||||
|
|
||||||
### Proper WooNooW Addon (Best Practice)
|
|
||||||
✅ Native WooNooW integration
|
|
||||||
✅ Better UX
|
|
||||||
✅ More features
|
|
||||||
✅ Independent of Rajaongkir plugin
|
|
||||||
✅ Can use any shipping API
|
|
||||||
❌ More development effort
|
|
||||||
❌ Separate plugin to maintain
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recommendation
|
|
||||||
|
|
||||||
**For WooNooW Core:**
|
|
||||||
- ❌ Don't integrate Rajaongkir
|
|
||||||
- ✅ Provide hook system
|
|
||||||
- ✅ Document bridge pattern
|
|
||||||
- ✅ Provide code snippets
|
|
||||||
|
|
||||||
**For Users:**
|
|
||||||
- **Quick fix:** Use bridge snippet
|
|
||||||
- **Best practice:** Build proper addon or use community addon
|
|
||||||
|
|
||||||
**For Community:**
|
|
||||||
- Build "WooNooW Indonesia Shipping" addon
|
|
||||||
- Publish on WordPress.org
|
|
||||||
- Support Rajaongkir, Biteship, and other Indonesian shipping APIs
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Hook Points Needed in WooNooW Core
|
|
||||||
|
|
||||||
To support addons like this, WooNooW core should provide:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Before shipping calculation
|
|
||||||
apply_filters('woonoow_before_shipping_calculate', $shipping_data);
|
|
||||||
|
|
||||||
// After shipping calculation
|
|
||||||
apply_filters('woonoow_after_shipping_calculate', $rates, $shipping_data);
|
|
||||||
|
|
||||||
// Modify shipping data
|
|
||||||
apply_filters('woonoow_shipping_data', $data);
|
|
||||||
```
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Frontend hooks
|
|
||||||
'woonoow_order_form_after_shipping'
|
|
||||||
'woonoow_order_form_shipping_fields'
|
|
||||||
'woonoow_order_form_validation'
|
|
||||||
'woonoow_order_form_submit'
|
|
||||||
```
|
|
||||||
|
|
||||||
**These hooks already exist in our addon system!**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
**WooNooW Core = Zero addon dependencies**
|
|
||||||
|
|
||||||
Instead of integrating Rajaongkir into core:
|
|
||||||
1. Provide hook system ✅ (Already done)
|
|
||||||
2. Document bridge pattern ✅ (This document)
|
|
||||||
3. Encourage community addons ✅
|
|
||||||
|
|
||||||
This keeps WooNooW core:
|
|
||||||
- Clean
|
|
||||||
- Maintainable
|
|
||||||
- Flexible
|
|
||||||
- Extensible
|
|
||||||
|
|
||||||
Users can choose:
|
|
||||||
- Bridge snippet (quick fix)
|
|
||||||
- Proper addon (best practice)
|
|
||||||
- Build their own
|
|
||||||
|
|
||||||
**No bloat in core!**
|
|
||||||
@@ -1,715 +0,0 @@
|
|||||||
# 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.**
|
|
||||||
@@ -1,616 +0,0 @@
|
|||||||
# Addon-Module Integration: Design Decisions
|
|
||||||
|
|
||||||
**Date**: December 26, 2025
|
|
||||||
**Status**: 🎯 Decision Document
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Dynamic Categories (RECOMMENDED)
|
|
||||||
|
|
||||||
### ❌ Problem with Static Categories
|
|
||||||
```php
|
|
||||||
// BAD: Empty categories if no modules use them
|
|
||||||
public static function get_categories() {
|
|
||||||
return [
|
|
||||||
'shipping' => 'Shipping & Fulfillment', // Empty if no shipping modules!
|
|
||||||
'payments' => 'Payments & Checkout', // Empty if no payment modules!
|
|
||||||
];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Solution: Dynamic Category Generation
|
|
||||||
|
|
||||||
```php
|
|
||||||
class ModuleRegistry {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get categories dynamically from registered modules
|
|
||||||
*/
|
|
||||||
public static function get_categories() {
|
|
||||||
$all_modules = self::get_all_modules();
|
|
||||||
$categories = [];
|
|
||||||
|
|
||||||
// Extract unique categories from modules
|
|
||||||
foreach ($all_modules as $module) {
|
|
||||||
$cat = $module['category'] ?? 'other';
|
|
||||||
if (!isset($categories[$cat])) {
|
|
||||||
$categories[$cat] = self::get_category_label($cat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by predefined order (if exists), then alphabetically
|
|
||||||
$order = ['marketing', 'customers', 'products', 'shipping', 'payments', 'analytics', 'other'];
|
|
||||||
uksort($categories, function($a, $b) use ($order) {
|
|
||||||
$pos_a = array_search($a, $order);
|
|
||||||
$pos_b = array_search($b, $order);
|
|
||||||
if ($pos_a === false) $pos_a = 999;
|
|
||||||
if ($pos_b === false) $pos_b = 999;
|
|
||||||
return $pos_a - $pos_b;
|
|
||||||
});
|
|
||||||
|
|
||||||
return $categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get human-readable label for category
|
|
||||||
*/
|
|
||||||
private static function get_category_label($category) {
|
|
||||||
$labels = [
|
|
||||||
'marketing' => __('Marketing & Sales', 'woonoow'),
|
|
||||||
'customers' => __('Customer Experience', 'woonoow'),
|
|
||||||
'products' => __('Products & Inventory', 'woonoow'),
|
|
||||||
'shipping' => __('Shipping & Fulfillment', 'woonoow'),
|
|
||||||
'payments' => __('Payments & Checkout', 'woonoow'),
|
|
||||||
'analytics' => __('Analytics & Reports', 'woonoow'),
|
|
||||||
'other' => __('Other Extensions', 'woonoow'),
|
|
||||||
];
|
|
||||||
|
|
||||||
return $labels[$category] ?? ucfirst($category);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Group modules by category
|
|
||||||
*/
|
|
||||||
public static function get_grouped_modules() {
|
|
||||||
$all_modules = self::get_all_modules();
|
|
||||||
$grouped = [];
|
|
||||||
|
|
||||||
foreach ($all_modules as $module) {
|
|
||||||
$cat = $module['category'] ?? 'other';
|
|
||||||
if (!isset($grouped[$cat])) {
|
|
||||||
$grouped[$cat] = [];
|
|
||||||
}
|
|
||||||
$grouped[$cat][] = $module;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $grouped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Benefits
|
|
||||||
- ✅ No empty categories
|
|
||||||
- ✅ Addons can define custom categories
|
|
||||||
- ✅ Single registration point (module only)
|
|
||||||
- ✅ Auto-sorted by predefined order
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Module Settings URL Pattern (RECOMMENDED)
|
|
||||||
|
|
||||||
### ❌ Problem with Custom URLs
|
|
||||||
```php
|
|
||||||
'settings_url' => '/settings/shipping/biteship', // Conflict risk!
|
|
||||||
'settings_url' => '/marketing/newsletter', // Inconsistent!
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Solution: Convention-Based Pattern
|
|
||||||
|
|
||||||
#### Option A: Standardized Pattern (RECOMMENDED)
|
|
||||||
```php
|
|
||||||
// Module registration - NO settings_url needed!
|
|
||||||
$addons['biteship-shipping'] = [
|
|
||||||
'id' => 'biteship-shipping',
|
|
||||||
'name' => 'Biteship Shipping',
|
|
||||||
'has_settings' => true, // Just a flag!
|
|
||||||
];
|
|
||||||
|
|
||||||
// Auto-generated URL pattern:
|
|
||||||
// /settings/modules/{module_id}
|
|
||||||
// Example: /settings/modules/biteship-shipping
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Backend: Auto Route Registration
|
|
||||||
```php
|
|
||||||
class ModuleRegistry {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register module settings routes automatically
|
|
||||||
*/
|
|
||||||
public static function register_settings_routes() {
|
|
||||||
$modules = self::get_all_modules();
|
|
||||||
|
|
||||||
foreach ($modules as $module) {
|
|
||||||
if (empty($module['has_settings'])) continue;
|
|
||||||
|
|
||||||
// Auto-register route: /settings/modules/{module_id}
|
|
||||||
add_filter('woonoow/spa_routes', function($routes) use ($module) {
|
|
||||||
$routes[] = [
|
|
||||||
'path' => "/settings/modules/{$module['id']}",
|
|
||||||
'component_url' => $module['settings_component'] ?? null,
|
|
||||||
'title' => sprintf(__('%s Settings', 'woonoow'), $module['label']),
|
|
||||||
];
|
|
||||||
return $routes;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Frontend: Automatic Navigation
|
|
||||||
```tsx
|
|
||||||
// Modules.tsx - Gear icon auto-links
|
|
||||||
{module.has_settings && module.enabled && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => navigate(`/settings/modules/${module.id}`)}
|
|
||||||
>
|
|
||||||
<Settings className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Benefits
|
|
||||||
- ✅ No URL conflicts (enforced pattern)
|
|
||||||
- ✅ Consistent navigation
|
|
||||||
- ✅ Simpler addon registration
|
|
||||||
- ✅ Auto-generated breadcrumbs
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Form Builder vs Custom HTML (HYBRID APPROACH)
|
|
||||||
|
|
||||||
### ✅ Recommended: Provide Both Options
|
|
||||||
|
|
||||||
#### Option A: Schema-Based Form Builder (For Simple Settings)
|
|
||||||
```php
|
|
||||||
// Addon defines settings schema
|
|
||||||
add_filter('woonoow/module_settings_schema', function($schemas) {
|
|
||||||
$schemas['biteship-shipping'] = [
|
|
||||||
'api_key' => [
|
|
||||||
'type' => 'text',
|
|
||||||
'label' => 'API Key',
|
|
||||||
'description' => 'Your Biteship API key',
|
|
||||||
'required' => true,
|
|
||||||
],
|
|
||||||
'enable_tracking' => [
|
|
||||||
'type' => 'toggle',
|
|
||||||
'label' => 'Enable Tracking',
|
|
||||||
'default' => true,
|
|
||||||
],
|
|
||||||
'default_courier' => [
|
|
||||||
'type' => 'select',
|
|
||||||
'label' => 'Default Courier',
|
|
||||||
'options' => [
|
|
||||||
'jne' => 'JNE',
|
|
||||||
'jnt' => 'J&T Express',
|
|
||||||
'sicepat' => 'SiCepat',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
return $schemas;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Auto-rendered form** - No React needed!
|
|
||||||
|
|
||||||
#### Option B: Custom React Component (For Complex Settings)
|
|
||||||
```php
|
|
||||||
// Addon provides custom React component
|
|
||||||
add_filter('woonoow/addon_registry', function($addons) {
|
|
||||||
$addons['biteship-shipping'] = [
|
|
||||||
'id' => 'biteship-shipping',
|
|
||||||
'has_settings' => true,
|
|
||||||
'settings_component' => plugin_dir_url(__FILE__) . 'dist/Settings.js',
|
|
||||||
];
|
|
||||||
return $addons;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Full control** - Custom React UI
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
|
|
||||||
```php
|
|
||||||
class ModuleSettingsRenderer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render settings page
|
|
||||||
*/
|
|
||||||
public static function render($module_id) {
|
|
||||||
$module = ModuleRegistry::get_module($module_id);
|
|
||||||
|
|
||||||
// Option 1: Has custom component
|
|
||||||
if (!empty($module['settings_component'])) {
|
|
||||||
return self::render_custom_component($module);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option 2: Has schema - auto-generate form
|
|
||||||
$schema = apply_filters('woonoow/module_settings_schema', []);
|
|
||||||
if (isset($schema[$module_id])) {
|
|
||||||
return self::render_schema_form($module_id, $schema[$module_id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option 3: No settings
|
|
||||||
return ['error' => 'No settings available'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Benefits
|
|
||||||
- ✅ Simple addons use schema (no React needed)
|
|
||||||
- ✅ Complex addons use custom components
|
|
||||||
- ✅ Consistent data persistence for both
|
|
||||||
- ✅ Gradual complexity curve
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Settings Data Persistence (STANDARDIZED)
|
|
||||||
|
|
||||||
### ✅ Recommended: Unified Settings API
|
|
||||||
|
|
||||||
#### Backend: Automatic Persistence
|
|
||||||
```php
|
|
||||||
class ModuleSettingsController extends WP_REST_Controller {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /woonoow/v1/modules/{module_id}/settings
|
|
||||||
*/
|
|
||||||
public function get_settings($request) {
|
|
||||||
$module_id = $request['module_id'];
|
|
||||||
$settings = get_option("woonoow_module_{$module_id}_settings", []);
|
|
||||||
|
|
||||||
// Apply defaults from schema
|
|
||||||
$schema = apply_filters('woonoow/module_settings_schema', []);
|
|
||||||
if (isset($schema[$module_id])) {
|
|
||||||
$settings = wp_parse_args($settings, self::get_defaults($schema[$module_id]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return rest_ensure_response($settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /woonoow/v1/modules/{module_id}/settings
|
|
||||||
*/
|
|
||||||
public function update_settings($request) {
|
|
||||||
$module_id = $request['module_id'];
|
|
||||||
$new_settings = $request->get_json_params();
|
|
||||||
|
|
||||||
// Validate against schema
|
|
||||||
$schema = apply_filters('woonoow/module_settings_schema', []);
|
|
||||||
if (isset($schema[$module_id])) {
|
|
||||||
$validated = self::validate_settings($new_settings, $schema[$module_id]);
|
|
||||||
if (is_wp_error($validated)) {
|
|
||||||
return $validated;
|
|
||||||
}
|
|
||||||
$new_settings = $validated;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save
|
|
||||||
update_option("woonoow_module_{$module_id}_settings", $new_settings);
|
|
||||||
|
|
||||||
// Allow addons to react
|
|
||||||
do_action("woonoow/module_settings_updated/{$module_id}", $new_settings);
|
|
||||||
|
|
||||||
return rest_ensure_response(['success' => true]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Frontend: Unified Hook
|
|
||||||
```tsx
|
|
||||||
// useModuleSettings.ts
|
|
||||||
export function useModuleSettings(moduleId: string) {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const { data: settings, isLoading } = useQuery({
|
|
||||||
queryKey: ['module-settings', moduleId],
|
|
||||||
queryFn: async () => {
|
|
||||||
const response = await api.get(`/modules/${moduleId}/settings`);
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateSettings = useMutation({
|
|
||||||
mutationFn: async (newSettings: any) => {
|
|
||||||
return api.post(`/modules/${moduleId}/settings`, newSettings);
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['module-settings', moduleId] });
|
|
||||||
toast.success('Settings saved');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { settings, isLoading, updateSettings };
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Addon Usage
|
|
||||||
```tsx
|
|
||||||
// Custom settings component
|
|
||||||
export default function BiteshipSettings() {
|
|
||||||
const { settings, updateSettings } = useModuleSettings('biteship-shipping');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsLayout title="Biteship Settings">
|
|
||||||
<SettingsCard>
|
|
||||||
<Input
|
|
||||||
label="API Key"
|
|
||||||
value={settings?.api_key || ''}
|
|
||||||
onChange={(e) => updateSettings.mutate({ api_key: e.target.value })}
|
|
||||||
/>
|
|
||||||
</SettingsCard>
|
|
||||||
</SettingsLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Benefits
|
|
||||||
- ✅ Consistent storage pattern: `woonoow_module_{id}_settings`
|
|
||||||
- ✅ Automatic validation (if schema provided)
|
|
||||||
- ✅ React hook for easy access
|
|
||||||
- ✅ Action hooks for addon logic
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. React Extension Pattern (DOCUMENTED)
|
|
||||||
|
|
||||||
### ✅ Solution: Window API + Build Externals
|
|
||||||
|
|
||||||
#### WooNooW Core Exposes React
|
|
||||||
```typescript
|
|
||||||
// admin-spa/src/main.tsx
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
// Expose for addons
|
|
||||||
window.WooNooW = {
|
|
||||||
React,
|
|
||||||
ReactDOM,
|
|
||||||
hooks: {
|
|
||||||
useQuery,
|
|
||||||
useMutation,
|
|
||||||
useModuleSettings, // Our custom hook!
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
SettingsLayout,
|
|
||||||
SettingsCard,
|
|
||||||
Button,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Switch,
|
|
||||||
// ... all shadcn components
|
|
||||||
},
|
|
||||||
utils: {
|
|
||||||
api,
|
|
||||||
toast,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Addon Development
|
|
||||||
```typescript
|
|
||||||
// addon/src/Settings.tsx
|
|
||||||
const { React, hooks, components, utils } = window.WooNooW;
|
|
||||||
const { useModuleSettings } = hooks;
|
|
||||||
const { SettingsLayout, SettingsCard, Input, Button } = components;
|
|
||||||
const { toast } = utils;
|
|
||||||
|
|
||||||
export default function BiteshipSettings() {
|
|
||||||
const { settings, updateSettings } = useModuleSettings('biteship-shipping');
|
|
||||||
const [apiKey, setApiKey] = React.useState(settings?.api_key || '');
|
|
||||||
|
|
||||||
const handleSave = () => {
|
|
||||||
updateSettings.mutate({ api_key: apiKey });
|
|
||||||
};
|
|
||||||
|
|
||||||
return React.createElement(SettingsLayout, { title: 'Biteship Settings' },
|
|
||||||
React.createElement(SettingsCard, null,
|
|
||||||
React.createElement(Input, {
|
|
||||||
label: 'API Key',
|
|
||||||
value: apiKey,
|
|
||||||
onChange: (e) => setApiKey(e.target.value),
|
|
||||||
}),
|
|
||||||
React.createElement(Button, { onClick: handleSave }, 'Save')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### With JSX (Build Required)
|
|
||||||
```tsx
|
|
||||||
// addon/src/Settings.tsx
|
|
||||||
const { React, hooks, components } = window.WooNooW;
|
|
||||||
const { useModuleSettings } = hooks;
|
|
||||||
const { SettingsLayout, SettingsCard, Input, Button } = components;
|
|
||||||
|
|
||||||
export default function BiteshipSettings() {
|
|
||||||
const { settings, updateSettings } = useModuleSettings('biteship-shipping');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsLayout title="Biteship Settings">
|
|
||||||
<SettingsCard>
|
|
||||||
<Input
|
|
||||||
label="API Key"
|
|
||||||
value={settings?.api_key || ''}
|
|
||||||
onChange={(e) => updateSettings.mutate({ api_key: e.target.value })}
|
|
||||||
/>
|
|
||||||
</SettingsCard>
|
|
||||||
</SettingsLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// vite.config.js
|
|
||||||
export default {
|
|
||||||
build: {
|
|
||||||
lib: {
|
|
||||||
entry: 'src/Settings.tsx',
|
|
||||||
formats: ['es'],
|
|
||||||
},
|
|
||||||
rollupOptions: {
|
|
||||||
external: ['react', 'react-dom'],
|
|
||||||
output: {
|
|
||||||
globals: {
|
|
||||||
react: 'window.WooNooW.React',
|
|
||||||
'react-dom': 'window.WooNooW.ReactDOM',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Benefits
|
|
||||||
- ✅ Addons don't bundle React (use ours)
|
|
||||||
- ✅ Access to all WooNooW components
|
|
||||||
- ✅ Consistent UI automatically
|
|
||||||
- ✅ Type safety with TypeScript
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Newsletter as Addon Example (RECOMMENDED)
|
|
||||||
|
|
||||||
### ✅ Yes, Refactor Newsletter as Built-in Addon
|
|
||||||
|
|
||||||
#### Why This is Valuable
|
|
||||||
|
|
||||||
1. **Dogfooding** - We use our own addon system
|
|
||||||
2. **Example** - Best reference for addon developers
|
|
||||||
3. **Consistency** - Newsletter follows same pattern as external addons
|
|
||||||
4. **Testing** - Proves the system works
|
|
||||||
|
|
||||||
#### Proposed Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
includes/
|
|
||||||
Modules/
|
|
||||||
Newsletter/
|
|
||||||
NewsletterModule.php # Module registration
|
|
||||||
NewsletterController.php # API endpoints (moved from Api/)
|
|
||||||
NewsletterSettings.php # Settings schema
|
|
||||||
|
|
||||||
admin-spa/src/modules/
|
|
||||||
Newsletter/
|
|
||||||
Settings.tsx # Settings page
|
|
||||||
Subscribers.tsx # Subscribers page
|
|
||||||
index.ts # Module exports
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Registration Pattern
|
|
||||||
```php
|
|
||||||
// includes/Modules/Newsletter/NewsletterModule.php
|
|
||||||
class NewsletterModule {
|
|
||||||
|
|
||||||
public static function register() {
|
|
||||||
// Register as module
|
|
||||||
add_filter('woonoow/builtin_modules', function($modules) {
|
|
||||||
$modules['newsletter'] = [
|
|
||||||
'id' => 'newsletter',
|
|
||||||
'label' => __('Newsletter', 'woonoow'),
|
|
||||||
'description' => __('Email newsletter subscriptions', 'woonoow'),
|
|
||||||
'category' => 'marketing',
|
|
||||||
'icon' => 'mail',
|
|
||||||
'default_enabled' => true,
|
|
||||||
'has_settings' => true,
|
|
||||||
'settings_component' => self::get_settings_url(),
|
|
||||||
];
|
|
||||||
return $modules;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register routes (only if enabled)
|
|
||||||
if (ModuleRegistry::is_enabled('newsletter')) {
|
|
||||||
self::register_routes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function register_routes() {
|
|
||||||
// Settings route
|
|
||||||
add_filter('woonoow/spa_routes', function($routes) {
|
|
||||||
$routes[] = [
|
|
||||||
'path' => '/settings/modules/newsletter',
|
|
||||||
'component_url' => plugins_url('admin-spa/dist/modules/Newsletter/Settings.js', WOONOOW_FILE),
|
|
||||||
];
|
|
||||||
return $routes;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subscribers route
|
|
||||||
add_filter('woonoow/spa_routes', function($routes) {
|
|
||||||
$routes[] = [
|
|
||||||
'path' => '/marketing/newsletter',
|
|
||||||
'component_url' => plugins_url('admin-spa/dist/modules/Newsletter/Subscribers.js', WOONOOW_FILE),
|
|
||||||
];
|
|
||||||
return $routes;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Benefits
|
|
||||||
- ✅ Newsletter becomes reference implementation
|
|
||||||
- ✅ Proves addon system works for complex modules
|
|
||||||
- ✅ Shows best practices
|
|
||||||
- ✅ Easier to maintain (follows pattern)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary of Decisions
|
|
||||||
|
|
||||||
| # | Question | Decision | Rationale |
|
|
||||||
|---|----------|----------|-----------|
|
|
||||||
| 1 | Categories | **Dynamic from modules** | No empty categories, single registration |
|
|
||||||
| 2 | Settings URL | **Pattern: `/settings/modules/{id}`** | No conflicts, consistent, auto-generated |
|
|
||||||
| 3 | Form Builder | **Hybrid: Schema + Custom** | Simple for basic, flexible for complex |
|
|
||||||
| 4 | Data Persistence | **Unified API + Hook** | Consistent storage, easy access |
|
|
||||||
| 5 | React Extension | **Window API + Externals** | No bundling, access to components |
|
|
||||||
| 6 | Newsletter Refactor | **Yes, as example** | Dogfooding, reference implementation |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Order
|
|
||||||
|
|
||||||
### Phase 1: Foundation
|
|
||||||
1. ✅ Dynamic category generation
|
|
||||||
2. ✅ Standardized settings URL pattern
|
|
||||||
3. ✅ Module settings API endpoints
|
|
||||||
4. ✅ `useModuleSettings` hook
|
|
||||||
|
|
||||||
### Phase 2: Form System
|
|
||||||
1. ✅ Schema-based form renderer
|
|
||||||
2. ✅ Custom component loader
|
|
||||||
3. ✅ Settings validation
|
|
||||||
|
|
||||||
### Phase 3: UI Enhancement
|
|
||||||
1. ✅ Search input on Modules page
|
|
||||||
2. ✅ Category filter pills
|
|
||||||
3. ✅ Gear icon with auto-routing
|
|
||||||
|
|
||||||
### Phase 4: Example
|
|
||||||
1. ✅ Refactor Newsletter as built-in addon
|
|
||||||
2. ✅ Document pattern
|
|
||||||
3. ✅ Create external addon example (Biteship)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
**Ready to implement?** We have clear decisions on all 6 questions. Should we:
|
|
||||||
|
|
||||||
1. Start with Phase 1 (Foundation)?
|
|
||||||
2. Create the schema-based form system first?
|
|
||||||
3. Refactor Newsletter as proof-of-concept?
|
|
||||||
|
|
||||||
**Your call!** All design decisions are documented and justified.
|
|
||||||
@@ -1,476 +0,0 @@
|
|||||||
# Addon-Module Integration Strategy
|
|
||||||
|
|
||||||
**Date**: December 26, 2025
|
|
||||||
**Status**: 🎯 Proposal
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Vision
|
|
||||||
|
|
||||||
**Module Registry as the Single Source of Truth for all extensions** - both built-in modules and external addons.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Current State Analysis
|
|
||||||
|
|
||||||
### What We Have
|
|
||||||
|
|
||||||
#### 1. **Module System** (Just Built)
|
|
||||||
- `ModuleRegistry.php` - Manages built-in modules
|
|
||||||
- Enable/disable functionality
|
|
||||||
- Module metadata (label, description, features, icon)
|
|
||||||
- Categories (Marketing, Customers, Products)
|
|
||||||
- Settings page UI with toggles
|
|
||||||
|
|
||||||
#### 2. **Addon System** (Existing)
|
|
||||||
- `AddonRegistry.php` - Manages external addons
|
|
||||||
- SPA route injection
|
|
||||||
- Hook system integration
|
|
||||||
- Navigation tree injection
|
|
||||||
- React component loading
|
|
||||||
|
|
||||||
### The Opportunity
|
|
||||||
|
|
||||||
**These two systems should be unified!** An addon is just an external module.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Proposed Integration
|
|
||||||
|
|
||||||
### Concept: Unified Extension Registry
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────┐
|
|
||||||
│ Module Registry (Single Source) │
|
|
||||||
├─────────────────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ Built-in Modules External Addons │
|
|
||||||
│ ├─ Newsletter ├─ Biteship Shipping │
|
|
||||||
│ ├─ Wishlist ├─ Subscriptions │
|
|
||||||
│ ├─ Affiliate ├─ Bookings │
|
|
||||||
│ ├─ Subscription └─ Custom Reports │
|
|
||||||
│ └─ Licensing │
|
|
||||||
│ │
|
|
||||||
│ All share same interface: │
|
|
||||||
│ • Enable/disable toggle │
|
|
||||||
│ • Settings page (optional) │
|
|
||||||
│ • Icon & metadata │
|
|
||||||
│ • Feature list │
|
|
||||||
│ │
|
|
||||||
└─────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Plan
|
|
||||||
|
|
||||||
### Phase 1: Extend Module Registry for Addons
|
|
||||||
|
|
||||||
#### Backend: ModuleRegistry.php Enhancement
|
|
||||||
|
|
||||||
```php
|
|
||||||
class ModuleRegistry {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all modules (built-in + addons)
|
|
||||||
*/
|
|
||||||
public static function get_all_modules() {
|
|
||||||
$builtin = self::get_builtin_modules();
|
|
||||||
$addons = self::get_addon_modules();
|
|
||||||
|
|
||||||
return array_merge($builtin, $addons);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get addon modules from AddonRegistry
|
|
||||||
*/
|
|
||||||
private static function get_addon_modules() {
|
|
||||||
$addons = apply_filters('woonoow/addon_registry', []);
|
|
||||||
$modules = [];
|
|
||||||
|
|
||||||
foreach ($addons as $addon_id => $addon) {
|
|
||||||
$modules[$addon_id] = [
|
|
||||||
'id' => $addon_id,
|
|
||||||
'label' => $addon['name'],
|
|
||||||
'description' => $addon['description'] ?? '',
|
|
||||||
'category' => $addon['category'] ?? 'addons',
|
|
||||||
'icon' => $addon['icon'] ?? 'puzzle',
|
|
||||||
'default_enabled' => false,
|
|
||||||
'features' => $addon['features'] ?? [],
|
|
||||||
'is_addon' => true,
|
|
||||||
'version' => $addon['version'] ?? '1.0.0',
|
|
||||||
'author' => $addon['author'] ?? '',
|
|
||||||
'settings_url' => $addon['settings_url'] ?? '', // NEW!
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $modules;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Addon Registration Enhancement
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Addon developers register with enhanced metadata
|
|
||||||
add_filter('woonoow/addon_registry', function($addons) {
|
|
||||||
$addons['biteship-shipping'] = [
|
|
||||||
'id' => 'biteship-shipping',
|
|
||||||
'name' => 'Biteship Shipping',
|
|
||||||
'description' => 'Indonesia shipping with Biteship API',
|
|
||||||
'version' => '1.0.0',
|
|
||||||
'author' => 'WooNooW Team',
|
|
||||||
'category' => 'shipping', // NEW!
|
|
||||||
'icon' => 'truck', // NEW!
|
|
||||||
'features' => [ // NEW!
|
|
||||||
'Real-time shipping rates',
|
|
||||||
'Multiple couriers',
|
|
||||||
'Tracking integration',
|
|
||||||
],
|
|
||||||
'settings_url' => '/settings/shipping/biteship', // NEW!
|
|
||||||
'spa_bundle' => plugin_dir_url(__FILE__) . 'dist/addon.js',
|
|
||||||
];
|
|
||||||
return $addons;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 2: Module Settings Page with Gear Icon
|
|
||||||
|
|
||||||
#### UI Enhancement: Modules.tsx
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
{modules.map((module) => (
|
|
||||||
<div className="flex items-start gap-4 p-4 border rounded-lg">
|
|
||||||
{/* Icon */}
|
|
||||||
<div className={`p-3 rounded-lg ${module.enabled ? 'bg-primary/10' : 'bg-muted'}`}>
|
|
||||||
{getIcon(module.icon)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center gap-2 mb-1">
|
|
||||||
<h3 className="font-medium">{module.label}</h3>
|
|
||||||
{module.enabled && <Badge>Active</Badge>}
|
|
||||||
{module.is_addon && <Badge variant="outline">Addon</Badge>}
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground mb-2">{module.description}</p>
|
|
||||||
|
|
||||||
{/* Features */}
|
|
||||||
<ul className="space-y-1">
|
|
||||||
{module.features.map((feature, i) => (
|
|
||||||
<li key={i} className="text-xs text-muted-foreground">
|
|
||||||
• {feature}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Actions */}
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{/* Settings Gear Icon - Only if module has settings */}
|
|
||||||
{module.settings_url && module.enabled && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => navigate(module.settings_url)}
|
|
||||||
title="Module Settings"
|
|
||||||
>
|
|
||||||
<Settings className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Enable/Disable Toggle */}
|
|
||||||
<Switch
|
|
||||||
checked={module.enabled}
|
|
||||||
onCheckedChange={(enabled) => toggleModule.mutate({ moduleId: module.id, enabled })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 3: Dynamic Categories
|
|
||||||
|
|
||||||
#### Support for Addon Categories
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ModuleRegistry.php
|
|
||||||
public static function get_categories() {
|
|
||||||
return [
|
|
||||||
'marketing' => __('Marketing & Sales', 'woonoow'),
|
|
||||||
'customers' => __('Customer Experience', 'woonoow'),
|
|
||||||
'products' => __('Products & Inventory', 'woonoow'),
|
|
||||||
'shipping' => __('Shipping & Fulfillment', 'woonoow'), // NEW!
|
|
||||||
'payments' => __('Payments & Checkout', 'woonoow'), // NEW!
|
|
||||||
'analytics' => __('Analytics & Reports', 'woonoow'), // NEW!
|
|
||||||
'addons' => __('Other Extensions', 'woonoow'), // Fallback
|
|
||||||
];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Frontend: Dynamic Category Rendering
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Modules.tsx
|
|
||||||
const { data: modulesData } = useQuery({
|
|
||||||
queryKey: ['modules'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const response = await api.get('/modules');
|
|
||||||
return response as ModulesData;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get unique categories from modules
|
|
||||||
const categories = Object.keys(modulesData?.grouped || {});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsLayout title="Module Management">
|
|
||||||
{categories.map((category) => {
|
|
||||||
const modules = modulesData.grouped[category] || [];
|
|
||||||
if (modules.length === 0) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsCard
|
|
||||||
key={category}
|
|
||||||
title={getCategoryLabel(category)}
|
|
||||||
description={`Manage ${category} modules`}
|
|
||||||
>
|
|
||||||
{/* Module cards */}
|
|
||||||
</SettingsCard>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</SettingsLayout>
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
|
|
||||||
### 1. **Unified Management**
|
|
||||||
- ✅ One place to see all extensions (built-in + addons)
|
|
||||||
- ✅ Consistent enable/disable interface
|
|
||||||
- ✅ Unified metadata (icon, description, features)
|
|
||||||
|
|
||||||
### 2. **Better UX**
|
|
||||||
- ✅ Users don't need to distinguish between "modules" and "addons"
|
|
||||||
- ✅ Settings gear icon for quick access to module configuration
|
|
||||||
- ✅ Clear visual indication of what's enabled
|
|
||||||
|
|
||||||
### 3. **Developer Experience**
|
|
||||||
- ✅ Addon developers use familiar pattern
|
|
||||||
- ✅ Automatic integration with module system
|
|
||||||
- ✅ No extra work to appear in Modules page
|
|
||||||
|
|
||||||
### 4. **Extensibility**
|
|
||||||
- ✅ Dynamic categories support any addon type
|
|
||||||
- ✅ Settings URL allows deep linking to config
|
|
||||||
- ✅ Version and author info for better management
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Example: Biteship Addon Integration
|
|
||||||
|
|
||||||
### Addon Registration (PHP)
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Plugin Name: WooNooW Biteship Shipping
|
|
||||||
* Description: Indonesia shipping with Biteship API
|
|
||||||
* Version: 1.0.0
|
|
||||||
* Author: WooNooW Team
|
|
||||||
*/
|
|
||||||
|
|
||||||
add_filter('woonoow/addon_registry', function($addons) {
|
|
||||||
$addons['biteship-shipping'] = [
|
|
||||||
'id' => 'biteship-shipping',
|
|
||||||
'name' => 'Biteship Shipping',
|
|
||||||
'description' => 'Real-time shipping rates from Indonesian couriers',
|
|
||||||
'version' => '1.0.0',
|
|
||||||
'author' => 'WooNooW Team',
|
|
||||||
'category' => 'shipping',
|
|
||||||
'icon' => 'truck',
|
|
||||||
'features' => [
|
|
||||||
'JNE, J&T, SiCepat, and more',
|
|
||||||
'Real-time rate calculation',
|
|
||||||
'Shipment tracking',
|
|
||||||
'Automatic label printing',
|
|
||||||
],
|
|
||||||
'settings_url' => '/settings/shipping/biteship',
|
|
||||||
'spa_bundle' => plugin_dir_url(__FILE__) . 'dist/addon.js',
|
|
||||||
];
|
|
||||||
return $addons;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register settings route
|
|
||||||
add_filter('woonoow/spa_routes', function($routes) {
|
|
||||||
$routes[] = [
|
|
||||||
'path' => '/settings/shipping/biteship',
|
|
||||||
'component_url' => plugin_dir_url(__FILE__) . 'dist/Settings.js',
|
|
||||||
'title' => 'Biteship Settings',
|
|
||||||
];
|
|
||||||
return $routes;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result in Modules Page
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────┐
|
|
||||||
│ Shipping & Fulfillment │
|
|
||||||
├─────────────────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ 🚚 Biteship Shipping [⚙️] [Toggle] │
|
|
||||||
│ Real-time shipping rates from Indonesian... │
|
|
||||||
│ • JNE, J&T, SiCepat, and more │
|
|
||||||
│ • Real-time rate calculation │
|
|
||||||
│ • Shipment tracking │
|
|
||||||
│ • Automatic label printing │
|
|
||||||
│ │
|
|
||||||
│ Version: 1.0.0 | By: WooNooW Team | [Addon] │
|
|
||||||
│ │
|
|
||||||
└─────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
Clicking ⚙️ navigates to `/settings/shipping/biteship`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migration Path
|
|
||||||
|
|
||||||
### Step 1: Enhance ModuleRegistry (Backward Compatible)
|
|
||||||
- Add `get_addon_modules()` method
|
|
||||||
- Merge built-in + addon modules
|
|
||||||
- No breaking changes
|
|
||||||
|
|
||||||
### Step 2: Update Modules UI
|
|
||||||
- Add gear icon for settings
|
|
||||||
- Add "Addon" badge
|
|
||||||
- Support dynamic categories
|
|
||||||
|
|
||||||
### Step 3: Document for Addon Developers
|
|
||||||
- Update ADDON_DEVELOPMENT_GUIDE.md
|
|
||||||
- Add examples with new metadata
|
|
||||||
- Show settings page pattern
|
|
||||||
|
|
||||||
### Step 4: Update Existing Addons (Optional)
|
|
||||||
- Addons work without changes
|
|
||||||
- Enhanced metadata is optional
|
|
||||||
- Settings URL is optional
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Changes
|
|
||||||
|
|
||||||
### New Module Properties
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface Module {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
description: string;
|
|
||||||
category: string;
|
|
||||||
icon: string;
|
|
||||||
default_enabled: boolean;
|
|
||||||
features: string[];
|
|
||||||
enabled: boolean;
|
|
||||||
|
|
||||||
// NEW for addons
|
|
||||||
is_addon?: boolean;
|
|
||||||
version?: string;
|
|
||||||
author?: string;
|
|
||||||
settings_url?: string; // Route to settings page
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### New API Endpoint (Optional)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// GET /woonoow/v1/modules/:module_id/settings
|
|
||||||
// Returns module-specific settings schema
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Settings Page Pattern
|
|
||||||
|
|
||||||
### Option 1: Dedicated Route (Recommended)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Addon registers its own settings route
|
|
||||||
add_filter('woonoow/spa_routes', function($routes) {
|
|
||||||
$routes[] = [
|
|
||||||
'path' => '/settings/my-addon',
|
|
||||||
'component_url' => plugin_dir_url(__FILE__) . 'dist/Settings.js',
|
|
||||||
];
|
|
||||||
return $routes;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Modal/Drawer (Alternative)
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Modules page opens modal with addon settings
|
|
||||||
<Dialog open={settingsOpen} onOpenChange={setSettingsOpen}>
|
|
||||||
<DialogContent>
|
|
||||||
<AddonSettings moduleId={selectedModule} />
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Backward Compatibility
|
|
||||||
|
|
||||||
### Existing Addons Continue to Work
|
|
||||||
- ✅ No breaking changes
|
|
||||||
- ✅ Enhanced metadata is optional
|
|
||||||
- ✅ Addons without metadata still function
|
|
||||||
- ✅ Gradual migration path
|
|
||||||
|
|
||||||
### Existing Modules Unaffected
|
|
||||||
- ✅ Built-in modules work as before
|
|
||||||
- ✅ No changes to existing module logic
|
|
||||||
- ✅ Only UI enhancement
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
### What This Achieves
|
|
||||||
|
|
||||||
1. **Newsletter Footer Integration** ✅
|
|
||||||
- Newsletter form respects module status
|
|
||||||
- Hidden from footer builder when disabled
|
|
||||||
|
|
||||||
2. **Addon-Module Unification** 🎯
|
|
||||||
- Addons appear in Module Registry
|
|
||||||
- Same enable/disable interface
|
|
||||||
- Settings gear icon for configuration
|
|
||||||
|
|
||||||
3. **Better Developer Experience** 🎯
|
|
||||||
- Consistent registration pattern
|
|
||||||
- Automatic UI integration
|
|
||||||
- Optional settings page routing
|
|
||||||
|
|
||||||
4. **Better User Experience** 🎯
|
|
||||||
- One place to manage all extensions
|
|
||||||
- Clear visual hierarchy
|
|
||||||
- Quick access to settings
|
|
||||||
|
|
||||||
### Next Steps
|
|
||||||
|
|
||||||
1. ✅ Newsletter footer integration (DONE)
|
|
||||||
2. 🎯 Enhance ModuleRegistry for addon support
|
|
||||||
3. 🎯 Add settings URL support to Modules UI
|
|
||||||
4. 🎯 Update documentation
|
|
||||||
5. 🎯 Create example addon with settings
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**This creates a truly unified extension system where built-in modules and external addons are first-class citizens with the same management interface.**
|
|
||||||
@@ -1,499 +0,0 @@
|
|||||||
# 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:**
|
|
||||||
```tsx
|
|
||||||
// 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**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 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:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 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):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 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:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 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:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 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):
|
|
||||||
|
|
||||||
```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):
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 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:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 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;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 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:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# 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!
|
|
||||||
@@ -1,500 +0,0 @@
|
|||||||
# Architecture Decision: Customer-SPA Placement
|
|
||||||
|
|
||||||
## The Question
|
|
||||||
|
|
||||||
Should `customer-spa` be:
|
|
||||||
- **Option A:** Built into WooNooW core plugin (alongside `admin-spa`)
|
|
||||||
- **Option B:** Separate WooNooW theme (standalone product)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Option A: Customer-SPA in Core Plugin
|
|
||||||
|
|
||||||
### Structure:
|
|
||||||
```
|
|
||||||
woonoow/
|
|
||||||
├── admin-spa/ (Admin interface)
|
|
||||||
├── customer-spa/ (Customer-facing: Cart, Checkout, My Account)
|
|
||||||
├── includes/
|
|
||||||
│ ├── Frontend/ (Customer frontend logic)
|
|
||||||
│ └── Admin/ (Admin backend logic)
|
|
||||||
└── woonoow.php
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pros ✅
|
|
||||||
|
|
||||||
#### 1. **Unified Product**
|
|
||||||
- Single installation
|
|
||||||
- Single license
|
|
||||||
- Single update process
|
|
||||||
- Easier for customers to understand
|
|
||||||
|
|
||||||
#### 2. **Technical Cohesion**
|
|
||||||
- Shared API endpoints
|
|
||||||
- Shared authentication
|
|
||||||
- Shared state management
|
|
||||||
- Shared utilities and helpers
|
|
||||||
|
|
||||||
#### 3. **Development Efficiency**
|
|
||||||
- Shared components library
|
|
||||||
- Shared TypeScript types
|
|
||||||
- Shared build pipeline
|
|
||||||
- Single codebase to maintain
|
|
||||||
|
|
||||||
#### 4. **Market Positioning**
|
|
||||||
- "Complete WooCommerce modernization"
|
|
||||||
- Easier to sell as single product
|
|
||||||
- Higher perceived value
|
|
||||||
- Simpler pricing model
|
|
||||||
|
|
||||||
#### 5. **User Experience**
|
|
||||||
- Consistent design language
|
|
||||||
- Seamless admin-to-frontend flow
|
|
||||||
- Single settings interface
|
|
||||||
- Unified branding
|
|
||||||
|
|
||||||
### Cons ❌
|
|
||||||
|
|
||||||
#### 1. **Plugin Size**
|
|
||||||
- Larger download (~5-10MB)
|
|
||||||
- More files to load
|
|
||||||
- Potential performance concern
|
|
||||||
|
|
||||||
#### 2. **Flexibility**
|
|
||||||
- Users must use our frontend
|
|
||||||
- Can't use with other themes easily
|
|
||||||
- Less customization freedom
|
|
||||||
|
|
||||||
#### 3. **Theme Compatibility**
|
|
||||||
- May conflict with theme styles
|
|
||||||
- Requires CSS isolation
|
|
||||||
- More testing needed
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Option B: Customer-SPA as Theme
|
|
||||||
|
|
||||||
### Structure:
|
|
||||||
```
|
|
||||||
woonoow/ (Plugin)
|
|
||||||
├── admin-spa/ (Admin interface only)
|
|
||||||
└── includes/
|
|
||||||
└── Admin/
|
|
||||||
|
|
||||||
woonoow-theme/ (Theme)
|
|
||||||
├── customer-spa/ (Customer-facing)
|
|
||||||
├── templates/
|
|
||||||
└── style.css
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pros ✅
|
|
||||||
|
|
||||||
#### 1. **WordPress Best Practices**
|
|
||||||
- Themes handle frontend
|
|
||||||
- Plugins handle functionality
|
|
||||||
- Clear separation of concerns
|
|
||||||
- Follows WP conventions
|
|
||||||
|
|
||||||
#### 2. **Flexibility**
|
|
||||||
- Users can choose theme
|
|
||||||
- Can create child themes
|
|
||||||
- Easier customization
|
|
||||||
- Better for agencies
|
|
||||||
|
|
||||||
#### 3. **Market Segmentation**
|
|
||||||
- Sell plugin separately (~$99)
|
|
||||||
- Sell theme separately (~$79)
|
|
||||||
- Bundle discount (~$149)
|
|
||||||
- More revenue potential
|
|
||||||
|
|
||||||
#### 4. **Lighter Plugin**
|
|
||||||
- Smaller plugin size
|
|
||||||
- Faster admin load
|
|
||||||
- Only admin functionality
|
|
||||||
- Better performance
|
|
||||||
|
|
||||||
#### 5. **Theme Ecosystem**
|
|
||||||
- Can create multiple themes
|
|
||||||
- Different industries (fashion, electronics, etc.)
|
|
||||||
- Premium theme marketplace
|
|
||||||
- More business opportunities
|
|
||||||
|
|
||||||
### Cons ❌
|
|
||||||
|
|
||||||
#### 1. **Complexity for Users**
|
|
||||||
- Two products to install
|
|
||||||
- Two licenses to manage
|
|
||||||
- Two update processes
|
|
||||||
- More confusing
|
|
||||||
|
|
||||||
#### 2. **Technical Challenges**
|
|
||||||
- API communication between plugin/theme
|
|
||||||
- Version compatibility issues
|
|
||||||
- More testing required
|
|
||||||
- Harder to maintain
|
|
||||||
|
|
||||||
#### 3. **Market Confusion**
|
|
||||||
- "Do I need both?"
|
|
||||||
- "Why separate products?"
|
|
||||||
- Higher barrier to entry
|
|
||||||
- More support questions
|
|
||||||
|
|
||||||
#### 4. **Development Overhead**
|
|
||||||
- Two repositories
|
|
||||||
- Two build processes
|
|
||||||
- Two release cycles
|
|
||||||
- More maintenance
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Market Analysis
|
|
||||||
|
|
||||||
### Target Market Segments:
|
|
||||||
|
|
||||||
#### Segment 1: Small Business Owners (60%)
|
|
||||||
**Needs:**
|
|
||||||
- Simple, all-in-one solution
|
|
||||||
- Easy to install and use
|
|
||||||
- Don't care about technical details
|
|
||||||
- Want "it just works"
|
|
||||||
|
|
||||||
**Preference:** ✅ **Option A** (Core Plugin)
|
|
||||||
- Single product easier to understand
|
|
||||||
- Less technical knowledge required
|
|
||||||
- Lower barrier to entry
|
|
||||||
|
|
||||||
#### Segment 2: Agencies & Developers (30%)
|
|
||||||
**Needs:**
|
|
||||||
- Flexibility and customization
|
|
||||||
- Can build custom themes
|
|
||||||
- Want control over frontend
|
|
||||||
- Multiple client sites
|
|
||||||
|
|
||||||
**Preference:** ✅ **Option B** (Theme)
|
|
||||||
- More flexibility
|
|
||||||
- Can create custom themes
|
|
||||||
- Better for white-label
|
|
||||||
- Professional workflow
|
|
||||||
|
|
||||||
#### Segment 3: Enterprise (10%)
|
|
||||||
**Needs:**
|
|
||||||
- Full control
|
|
||||||
- Custom development
|
|
||||||
- Scalability
|
|
||||||
- Support
|
|
||||||
|
|
||||||
**Preference:** 🤷 **Either works**
|
|
||||||
- Will customize anyway
|
|
||||||
- Have development team
|
|
||||||
- Budget not a concern
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Competitor Analysis
|
|
||||||
|
|
||||||
### Shopify
|
|
||||||
- **All-in-one platform**
|
|
||||||
- Admin + Frontend unified
|
|
||||||
- Themes available but optional
|
|
||||||
- Core experience complete
|
|
||||||
|
|
||||||
**Lesson:** Users expect complete solution
|
|
||||||
|
|
||||||
### WooCommerce
|
|
||||||
- **Plugin + Theme separation**
|
|
||||||
- Plugin = functionality
|
|
||||||
- Theme = design
|
|
||||||
- Standard WordPress approach
|
|
||||||
|
|
||||||
**Lesson:** Separation is familiar to WP users
|
|
||||||
|
|
||||||
### SureCart
|
|
||||||
- **All-in-one plugin**
|
|
||||||
- Handles admin + checkout
|
|
||||||
- Works with any theme
|
|
||||||
- Shortcode-based frontend
|
|
||||||
|
|
||||||
**Lesson:** Plugin can handle both
|
|
||||||
|
|
||||||
### NorthCommerce
|
|
||||||
- **All-in-one plugin**
|
|
||||||
- Complete replacement
|
|
||||||
- Own frontend + admin
|
|
||||||
- Theme-agnostic
|
|
||||||
|
|
||||||
**Lesson:** Modern solutions are unified
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Technical Considerations
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
**Option A (Core Plugin):**
|
|
||||||
```
|
|
||||||
Admin page load: 200KB (admin-spa)
|
|
||||||
Customer page load: 300KB (customer-spa)
|
|
||||||
Total plugin size: 8MB
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option B (Theme):**
|
|
||||||
```
|
|
||||||
Admin page load: 200KB (admin-spa)
|
|
||||||
Customer page load: 300KB (customer-spa from theme)
|
|
||||||
Plugin size: 4MB
|
|
||||||
Theme size: 4MB
|
|
||||||
```
|
|
||||||
|
|
||||||
**Winner:** Tie (same total load)
|
|
||||||
|
|
||||||
### Maintenance
|
|
||||||
|
|
||||||
**Option A:**
|
|
||||||
- Single codebase
|
|
||||||
- Single release
|
|
||||||
- Easier version control
|
|
||||||
- Less coordination
|
|
||||||
|
|
||||||
**Option B:**
|
|
||||||
- Two codebases
|
|
||||||
- Coordinated releases
|
|
||||||
- Version compatibility matrix
|
|
||||||
- More complexity
|
|
||||||
|
|
||||||
**Winner:** ✅ **Option A**
|
|
||||||
|
|
||||||
### Flexibility
|
|
||||||
|
|
||||||
**Option A:**
|
|
||||||
- Users can disable customer-spa via settings
|
|
||||||
- Can use with any theme (shortcodes)
|
|
||||||
- Hybrid approach possible
|
|
||||||
|
|
||||||
**Option B:**
|
|
||||||
- Full theme control
|
|
||||||
- Can create variations
|
|
||||||
- Better for customization
|
|
||||||
|
|
||||||
**Winner:** ✅ **Option B**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Hybrid Approach (Recommended)
|
|
||||||
|
|
||||||
### Best of Both Worlds:
|
|
||||||
|
|
||||||
**WooNooW Plugin (Core):**
|
|
||||||
```
|
|
||||||
woonoow/
|
|
||||||
├── admin-spa/ (Always active)
|
|
||||||
├── customer-spa/ (Optional, can be disabled)
|
|
||||||
├── includes/
|
|
||||||
│ ├── Admin/
|
|
||||||
│ └── Frontend/
|
|
||||||
│ ├── Shortcodes/ (For any theme)
|
|
||||||
│ └── SPA/ (Full SPA mode)
|
|
||||||
└── woonoow.php
|
|
||||||
```
|
|
||||||
|
|
||||||
**Settings:**
|
|
||||||
```php
|
|
||||||
// WooNooW > Settings > Developer
|
|
||||||
Frontend Mode:
|
|
||||||
○ Disabled (use theme)
|
|
||||||
● Shortcodes (hybrid - works with any theme)
|
|
||||||
○ Full SPA (replace theme frontend)
|
|
||||||
```
|
|
||||||
|
|
||||||
**WooNooW Themes (Optional):**
|
|
||||||
```
|
|
||||||
woonoow-theme-storefront/ (Free, basic)
|
|
||||||
woonoow-theme-fashion/ (Premium, $79)
|
|
||||||
woonoow-theme-electronics/ (Premium, $79)
|
|
||||||
```
|
|
||||||
|
|
||||||
### How It Works:
|
|
||||||
|
|
||||||
#### Mode 1: Disabled
|
|
||||||
- Plugin only provides admin-spa
|
|
||||||
- Theme handles all frontend
|
|
||||||
- For users who want full theme control
|
|
||||||
|
|
||||||
#### Mode 2: Shortcodes (Default)
|
|
||||||
- Plugin provides cart/checkout/account components
|
|
||||||
- Works with ANY theme
|
|
||||||
- Hybrid approach (SSR + SPA islands)
|
|
||||||
- Best compatibility
|
|
||||||
|
|
||||||
#### Mode 3: Full SPA
|
|
||||||
- Plugin takes over entire frontend
|
|
||||||
- Theme only provides header/footer
|
|
||||||
- Maximum performance
|
|
||||||
- For performance-critical sites
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Revenue Model Comparison
|
|
||||||
|
|
||||||
### Option A: Unified Plugin
|
|
||||||
|
|
||||||
**Pricing:**
|
|
||||||
- WooNooW Plugin: $149/year
|
|
||||||
- Includes admin + customer SPA
|
|
||||||
- All features
|
|
||||||
|
|
||||||
**Projected Revenue (1000 customers):**
|
|
||||||
- $149,000/year
|
|
||||||
|
|
||||||
### Option B: Separate Products
|
|
||||||
|
|
||||||
**Pricing:**
|
|
||||||
- WooNooW Plugin (admin only): $99/year
|
|
||||||
- WooNooW Theme: $79/year
|
|
||||||
- Bundle: $149/year (save $29)
|
|
||||||
|
|
||||||
**Projected Revenue (1000 customers):**
|
|
||||||
- 60% buy bundle: $89,400
|
|
||||||
- 30% buy plugin only: $29,700
|
|
||||||
- 10% buy both separately: $17,800
|
|
||||||
- **Total: $136,900/year**
|
|
||||||
|
|
||||||
**Winner:** ✅ **Option A** ($12,100 more revenue)
|
|
||||||
|
|
||||||
### Option C: Hybrid Approach
|
|
||||||
|
|
||||||
**Pricing:**
|
|
||||||
- WooNooW Plugin (includes basic customer-spa): $149/year
|
|
||||||
- Premium Themes: $79/year each
|
|
||||||
- Bundle (plugin + premium theme): $199/year
|
|
||||||
|
|
||||||
**Projected Revenue (1000 customers):**
|
|
||||||
- 70% plugin only: $104,300
|
|
||||||
- 20% plugin + theme bundle: $39,800
|
|
||||||
- 10% plugin + multiple themes: $20,000
|
|
||||||
- **Total: $164,100/year**
|
|
||||||
|
|
||||||
**Winner:** ✅ **Option C** ($27,200 more revenue!)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recommendation: Hybrid Approach (Option C)
|
|
||||||
|
|
||||||
### Implementation:
|
|
||||||
|
|
||||||
**Phase 1: Core Plugin with Customer-SPA**
|
|
||||||
```
|
|
||||||
woonoow/
|
|
||||||
├── admin-spa/ ✅ Full admin interface
|
|
||||||
├── customer-spa/ ✅ Basic cart/checkout/account
|
|
||||||
│ ├── Cart.tsx
|
|
||||||
│ ├── Checkout.tsx
|
|
||||||
│ └── MyAccount.tsx
|
|
||||||
└── includes/
|
|
||||||
├── Admin/
|
|
||||||
└── Frontend/
|
|
||||||
├── Shortcodes/ ✅ [woonoow_cart], [woonoow_checkout]
|
|
||||||
└── SPA/ ✅ Full SPA mode (optional)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Phase 2: Premium Themes (Optional)**
|
|
||||||
```
|
|
||||||
woonoow-theme-fashion/
|
|
||||||
├── customer-spa/ ✅ Enhanced components
|
|
||||||
│ ├── ProductCard.tsx
|
|
||||||
│ ├── CategoryGrid.tsx
|
|
||||||
│ └── SearchBar.tsx
|
|
||||||
└── templates/
|
|
||||||
├── header.php
|
|
||||||
└── footer.php
|
|
||||||
```
|
|
||||||
|
|
||||||
### Benefits:
|
|
||||||
|
|
||||||
✅ **For Users:**
|
|
||||||
- Single product to start ($149)
|
|
||||||
- Works with any theme (shortcodes)
|
|
||||||
- Optional premium themes for better design
|
|
||||||
- Flexible deployment
|
|
||||||
|
|
||||||
✅ **For Us:**
|
|
||||||
- Higher base revenue
|
|
||||||
- Additional theme revenue
|
|
||||||
- Easier to sell
|
|
||||||
- Less support complexity
|
|
||||||
|
|
||||||
✅ **For Developers:**
|
|
||||||
- Can use basic customer-spa
|
|
||||||
- Can build custom themes
|
|
||||||
- Can extend with hooks
|
|
||||||
- Maximum flexibility
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Decision Matrix
|
|
||||||
|
|
||||||
| Criteria | Option A (Core) | Option B (Theme) | Option C (Hybrid) |
|
|
||||||
|----------|----------------|------------------|-------------------|
|
|
||||||
| **User Experience** | ⭐⭐⭐⭐⭐ Simple | ⭐⭐⭐ Complex | ⭐⭐⭐⭐ Flexible |
|
|
||||||
| **Revenue Potential** | ⭐⭐⭐⭐ $149K | ⭐⭐⭐ $137K | ⭐⭐⭐⭐⭐ $164K |
|
|
||||||
| **Development Effort** | ⭐⭐⭐⭐ Medium | ⭐⭐ High | ⭐⭐⭐ Medium-High |
|
|
||||||
| **Maintenance** | ⭐⭐⭐⭐⭐ Easy | ⭐⭐ Hard | ⭐⭐⭐⭐ Moderate |
|
|
||||||
| **Flexibility** | ⭐⭐⭐ Limited | ⭐⭐⭐⭐⭐ Maximum | ⭐⭐⭐⭐ High |
|
|
||||||
| **Market Fit** | ⭐⭐⭐⭐ Good | ⭐⭐⭐ Okay | ⭐⭐⭐⭐⭐ Excellent |
|
|
||||||
| **WP Best Practices** | ⭐⭐⭐ Okay | ⭐⭐⭐⭐⭐ Perfect | ⭐⭐⭐⭐ Good |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Final Recommendation
|
|
||||||
|
|
||||||
### ✅ **Option C: Hybrid Approach**
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
|
|
||||||
1. **WooNooW Plugin ($149/year):**
|
|
||||||
- Admin-SPA (full featured)
|
|
||||||
- Customer-SPA (basic cart/checkout/account)
|
|
||||||
- Shortcode mode (works with any theme)
|
|
||||||
- Full SPA mode (optional)
|
|
||||||
|
|
||||||
2. **Premium Themes ($79/year each):**
|
|
||||||
- Enhanced customer-spa components
|
|
||||||
- Industry-specific designs
|
|
||||||
- Advanced features
|
|
||||||
- Professional layouts
|
|
||||||
|
|
||||||
3. **Bundles:**
|
|
||||||
- Plugin + Theme: $199/year (save $29)
|
|
||||||
- Plugin + 3 Themes: $299/year (save $87)
|
|
||||||
|
|
||||||
### Why This Works:
|
|
||||||
|
|
||||||
✅ **60% of users** (small businesses) get complete solution in one plugin
|
|
||||||
✅ **30% of users** (agencies) can build custom themes or buy premium
|
|
||||||
✅ **10% of users** (enterprise) have maximum flexibility
|
|
||||||
✅ **Higher revenue** potential with theme marketplace
|
|
||||||
✅ **Easier to maintain** than fully separate products
|
|
||||||
✅ **Better market positioning** than competitors
|
|
||||||
|
|
||||||
### Next Steps:
|
|
||||||
|
|
||||||
**Phase 1 (Current):** Build admin-spa ✅
|
|
||||||
**Phase 2 (Next):** Build basic customer-spa in core plugin
|
|
||||||
**Phase 3 (Future):** Launch premium theme marketplace
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
**Build customer-spa into WooNooW core plugin with:**
|
|
||||||
- Shortcode mode (default, works with any theme)
|
|
||||||
- Full SPA mode (optional, for performance)
|
|
||||||
- Premium themes as separate products (optional)
|
|
||||||
|
|
||||||
**This gives us:**
|
|
||||||
- Best user experience
|
|
||||||
- Highest revenue potential
|
|
||||||
- Maximum flexibility
|
|
||||||
- Sustainable business model
|
|
||||||
- Competitive advantage
|
|
||||||
|
|
||||||
**Decision: Option C (Hybrid Approach)** ✅
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
# Documentation Cleanup Summary - December 26, 2025
|
|
||||||
|
|
||||||
## ✅ Cleanup Results
|
|
||||||
|
|
||||||
### Before
|
|
||||||
- **Total Files**: 74 markdown files
|
|
||||||
- **Status**: Cluttered with obsolete fixes, completed features, and duplicate docs
|
|
||||||
|
|
||||||
### After
|
|
||||||
- **Total Files**: 43 markdown files (42% reduction)
|
|
||||||
- **Status**: Clean, organized, only relevant documentation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🗑️ Deleted Files (32 total)
|
|
||||||
|
|
||||||
### Completed Fixes (10 files)
|
|
||||||
- FIXES_APPLIED.md
|
|
||||||
- REAL_FIX.md
|
|
||||||
- CANONICAL_REDIRECT_FIX.md
|
|
||||||
- HEADER_FIXES_APPLIED.md
|
|
||||||
- FINAL_FIXES.md
|
|
||||||
- FINAL_FIXES_APPLIED.md
|
|
||||||
- FIX_500_ERROR.md
|
|
||||||
- HASHROUTER_FIXES.md
|
|
||||||
- INLINE_SPACING_FIX.md
|
|
||||||
- DIRECT_ACCESS_FIX.md
|
|
||||||
|
|
||||||
### Completed Features (8 files)
|
|
||||||
- APPEARANCE_MENU_RESTRUCTURE.md
|
|
||||||
- SETTINGS-RESTRUCTURE.md
|
|
||||||
- HEADER_FOOTER_REDESIGN.md
|
|
||||||
- TYPOGRAPHY-PLAN.md
|
|
||||||
- CUSTOMER_SPA_SETTINGS.md
|
|
||||||
- CUSTOMER_SPA_STATUS.md
|
|
||||||
- CUSTOMER_SPA_THEME_SYSTEM.md
|
|
||||||
- CUSTOMER_SPA_ARCHITECTURE.md
|
|
||||||
|
|
||||||
### Product Page (5 files)
|
|
||||||
- PRODUCT_PAGE_VISUAL_OVERHAUL.md
|
|
||||||
- PRODUCT_PAGE_FINAL_STATUS.md
|
|
||||||
- PRODUCT_PAGE_REVIEW_REPORT.md
|
|
||||||
- PRODUCT_PAGE_ANALYSIS_REPORT.md
|
|
||||||
- PRODUCT_CART_COMPLETE.md
|
|
||||||
|
|
||||||
### Meta/Compat (2 files)
|
|
||||||
- IMPLEMENTATION_PLAN_META_COMPAT.md
|
|
||||||
- METABOX_COMPAT.md
|
|
||||||
|
|
||||||
### Old Audits (1 file)
|
|
||||||
- DOCS_AUDIT_REPORT.md
|
|
||||||
|
|
||||||
### Shipping Research (2 files)
|
|
||||||
- SHIPPING_ADDON_RESEARCH.md
|
|
||||||
- SHIPPING_FIELD_HOOKS.md
|
|
||||||
|
|
||||||
### Process Docs (3 files)
|
|
||||||
- DEPLOYMENT_GUIDE.md
|
|
||||||
- TESTING_CHECKLIST.md
|
|
||||||
- TROUBLESHOOTING.md
|
|
||||||
|
|
||||||
### Other (1 file)
|
|
||||||
- PLUGIN_ZIP_GUIDE.md
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Merged Files (2 → 1)
|
|
||||||
|
|
||||||
### Shipping Documentation
|
|
||||||
**Merged into**: `SHIPPING_INTEGRATION.md`
|
|
||||||
- RAJAONGKIR_INTEGRATION.md
|
|
||||||
- BITESHIP_ADDON_SPEC.md
|
|
||||||
|
|
||||||
**Result**: Single comprehensive shipping integration guide
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 New Documentation Created (3 files)
|
|
||||||
|
|
||||||
1. **DOCS_CLEANUP_AUDIT.md** - This cleanup audit report
|
|
||||||
2. **SHIPPING_INTEGRATION.md** - Consolidated shipping guide
|
|
||||||
3. **FEATURE_ROADMAP.md** - Comprehensive feature roadmap
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Essential Documentation Kept (20 files)
|
|
||||||
|
|
||||||
### Core Documentation (4)
|
|
||||||
- README.md
|
|
||||||
- API_ROUTES.md
|
|
||||||
- HOOKS_REGISTRY.md
|
|
||||||
- VALIDATION_HOOKS.md
|
|
||||||
|
|
||||||
### Architecture & Patterns (5)
|
|
||||||
- ADDON_BRIDGE_PATTERN.md
|
|
||||||
- ADDON_DEVELOPMENT_GUIDE.md
|
|
||||||
- ADDON_REACT_INTEGRATION.md
|
|
||||||
- PAYMENT_GATEWAY_PATTERNS.md
|
|
||||||
- ARCHITECTURE_DECISION_CUSTOMER_SPA.md
|
|
||||||
|
|
||||||
### System Guides (5)
|
|
||||||
- NOTIFICATION_SYSTEM.md
|
|
||||||
- I18N_IMPLEMENTATION_GUIDE.md
|
|
||||||
- EMAIL_DEBUGGING_GUIDE.md
|
|
||||||
- FILTER_HOOKS_GUIDE.md
|
|
||||||
- MARKDOWN_SYNTAX_AND_VARIABLES.md
|
|
||||||
|
|
||||||
### Active Plans (4)
|
|
||||||
- NEWSLETTER_CAMPAIGN_PLAN.md
|
|
||||||
- SETUP_WIZARD_DESIGN.md
|
|
||||||
- TAX_SETTINGS_DESIGN.md
|
|
||||||
- CUSTOMER_SPA_MASTER_PLAN.md
|
|
||||||
|
|
||||||
### Integration Guides (2)
|
|
||||||
- SHIPPING_INTEGRATION.md (merged)
|
|
||||||
- PAYMENT_GATEWAY_FAQ.md
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Benefits Achieved
|
|
||||||
|
|
||||||
1. **Clarity** ✅
|
|
||||||
- Only relevant, up-to-date documentation
|
|
||||||
- No confusion about what's current vs historical
|
|
||||||
|
|
||||||
2. **Maintainability** ✅
|
|
||||||
- Fewer docs to keep in sync
|
|
||||||
- Easier to update
|
|
||||||
|
|
||||||
3. **Onboarding** ✅
|
|
||||||
- New developers can find what they need
|
|
||||||
- Clear structure and organization
|
|
||||||
|
|
||||||
4. **Focus** ✅
|
|
||||||
- Clear what's active vs completed
|
|
||||||
- Roadmap for future features
|
|
||||||
|
|
||||||
5. **Size** ✅
|
|
||||||
- Smaller plugin zip (no obsolete docs)
|
|
||||||
- Faster repository operations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Feature Roadmap Created
|
|
||||||
|
|
||||||
Comprehensive plan for 6 major modules:
|
|
||||||
|
|
||||||
### 1. Module Management System 🔴 High Priority
|
|
||||||
- Centralized enable/disable control
|
|
||||||
- Settings UI with categories
|
|
||||||
- Navigation integration
|
|
||||||
- **Effort**: 1 week
|
|
||||||
|
|
||||||
### 2. Newsletter Campaigns 🔴 High Priority
|
|
||||||
- Campaign management (CRUD)
|
|
||||||
- Batch email sending
|
|
||||||
- Template system (reuse notification templates)
|
|
||||||
- Stats and reporting
|
|
||||||
- **Effort**: 2-3 weeks
|
|
||||||
|
|
||||||
### 3. Wishlist Notifications 🟡 Medium Priority
|
|
||||||
- Price drop alerts
|
|
||||||
- Back in stock notifications
|
|
||||||
- Low stock alerts
|
|
||||||
- Wishlist reminders
|
|
||||||
- **Effort**: 1-2 weeks
|
|
||||||
|
|
||||||
### 4. Affiliate Program 🟡 Medium Priority
|
|
||||||
- Referral tracking
|
|
||||||
- Commission management
|
|
||||||
- Affiliate dashboard
|
|
||||||
- Payout system
|
|
||||||
- **Effort**: 3-4 weeks
|
|
||||||
|
|
||||||
### 5. Product Subscriptions 🟢 Low Priority
|
|
||||||
- Recurring billing
|
|
||||||
- Subscription management
|
|
||||||
- Renewal automation
|
|
||||||
- Customer dashboard
|
|
||||||
- **Effort**: 4-5 weeks
|
|
||||||
|
|
||||||
### 6. Software Licensing 🟢 Low Priority
|
|
||||||
- License key generation
|
|
||||||
- Activation management
|
|
||||||
- Validation API
|
|
||||||
- Customer dashboard
|
|
||||||
- **Effort**: 3-4 weeks
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Next Steps
|
|
||||||
|
|
||||||
1. ✅ Documentation cleanup complete
|
|
||||||
2. ✅ Feature roadmap created
|
|
||||||
3. ⏭️ Review and approve roadmap
|
|
||||||
4. ⏭️ Prioritize modules based on business needs
|
|
||||||
5. ⏭️ Start implementation with Module 1 (Module Management)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Impact Summary
|
|
||||||
|
|
||||||
| Metric | Before | After | Change |
|
|
||||||
|--------|--------|-------|--------|
|
|
||||||
| Total Docs | 74 | 43 | -42% |
|
|
||||||
| Obsolete Docs | 32 | 0 | -100% |
|
|
||||||
| Duplicate Docs | 6 | 1 | -83% |
|
|
||||||
| Active Plans | 4 | 4 | - |
|
|
||||||
| New Roadmaps | 0 | 1 | +1 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ Key Achievements
|
|
||||||
|
|
||||||
1. **Removed 32 obsolete files** - No more confusion about completed work
|
|
||||||
2. **Merged 2 shipping docs** - Single source of truth for shipping integration
|
|
||||||
3. **Created comprehensive roadmap** - Clear vision for next 6 modules
|
|
||||||
4. **Organized remaining docs** - Easy to find what you need
|
|
||||||
5. **Reduced clutter by 42%** - Cleaner repository and faster operations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📖 Documentation Structure (Final)
|
|
||||||
|
|
||||||
```
|
|
||||||
Root Documentation (43 files)
|
|
||||||
├── Core (4)
|
|
||||||
│ ├── README.md
|
|
||||||
│ ├── API_ROUTES.md
|
|
||||||
│ ├── HOOKS_REGISTRY.md
|
|
||||||
│ └── VALIDATION_HOOKS.md
|
|
||||||
├── Architecture (5)
|
|
||||||
│ ├── ADDON_BRIDGE_PATTERN.md
|
|
||||||
│ ├── ADDON_DEVELOPMENT_GUIDE.md
|
|
||||||
│ ├── ADDON_REACT_INTEGRATION.md
|
|
||||||
│ ├── PAYMENT_GATEWAY_PATTERNS.md
|
|
||||||
│ └── ARCHITECTURE_DECISION_CUSTOMER_SPA.md
|
|
||||||
├── System Guides (5)
|
|
||||||
│ ├── NOTIFICATION_SYSTEM.md
|
|
||||||
│ ├── I18N_IMPLEMENTATION_GUIDE.md
|
|
||||||
│ ├── EMAIL_DEBUGGING_GUIDE.md
|
|
||||||
│ ├── FILTER_HOOKS_GUIDE.md
|
|
||||||
│ └── MARKDOWN_SYNTAX_AND_VARIABLES.md
|
|
||||||
├── Active Plans (4)
|
|
||||||
│ ├── NEWSLETTER_CAMPAIGN_PLAN.md
|
|
||||||
│ ├── SETUP_WIZARD_DESIGN.md
|
|
||||||
│ ├── TAX_SETTINGS_DESIGN.md
|
|
||||||
│ └── CUSTOMER_SPA_MASTER_PLAN.md
|
|
||||||
├── Integration Guides (2)
|
|
||||||
│ ├── SHIPPING_INTEGRATION.md
|
|
||||||
│ └── PAYMENT_GATEWAY_FAQ.md
|
|
||||||
└── Roadmaps (3)
|
|
||||||
├── FEATURE_ROADMAP.md (NEW)
|
|
||||||
├── DOCS_CLEANUP_AUDIT.md (NEW)
|
|
||||||
└── CLEANUP_SUMMARY.md (NEW)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Cleanup Status**: ✅ Complete
|
|
||||||
**Roadmap Status**: ✅ Complete
|
|
||||||
**Ready for**: Implementation Phase
|
|
||||||
@@ -1,749 +0,0 @@
|
|||||||
# Customer SPA Master Plan
|
|
||||||
## WooNooW Frontend Architecture & Implementation Strategy
|
|
||||||
|
|
||||||
**Version:** 1.0
|
|
||||||
**Date:** November 21, 2025
|
|
||||||
**Status:** Planning Phase
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
This document outlines the comprehensive strategy for building WooNooW's customer-facing SPA, including architecture decisions, deployment modes, UX best practices, and implementation roadmap.
|
|
||||||
|
|
||||||
### Key Decisions
|
|
||||||
|
|
||||||
✅ **Hybrid Architecture** - Plugin includes customer-spa with flexible deployment modes
|
|
||||||
✅ **Progressive Enhancement** - Works with any theme, optional full SPA mode
|
|
||||||
✅ **Mobile-First PWA** - Fast, app-like experience on all devices
|
|
||||||
✅ **SEO-Friendly** - Server-side rendering for product pages, SPA for interactions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
1. [Architecture Overview](#architecture-overview)
|
|
||||||
2. [Deployment Modes](#deployment-modes)
|
|
||||||
3. [SEO Strategy](#seo-strategy)
|
|
||||||
4. [Tracking & Analytics](#tracking--analytics)
|
|
||||||
5. [Feature Scope](#feature-scope)
|
|
||||||
6. [UX Best Practices](#ux-best-practices)
|
|
||||||
7. [Technical Stack](#technical-stack)
|
|
||||||
8. [Implementation Roadmap](#implementation-roadmap)
|
|
||||||
9. [API Requirements](#api-requirements)
|
|
||||||
10. [Performance Targets](#performance-targets)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
### Hybrid Plugin Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
woonoow/
|
|
||||||
├── admin-spa/ # Admin interface ONLY
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── routes/ # Admin pages (Dashboard, Products, Orders)
|
|
||||||
│ │ └── components/ # Admin components
|
|
||||||
│ └── public/
|
|
||||||
│
|
|
||||||
├── customer-spa/ # Customer frontend ONLY (Storefront + My Account)
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── pages/ # Customer pages
|
|
||||||
│ │ │ ├── Shop/ # Product listing
|
|
||||||
│ │ │ ├── Product/ # Product detail
|
|
||||||
│ │ │ ├── Cart/ # Shopping cart
|
|
||||||
│ │ │ ├── Checkout/ # Checkout process
|
|
||||||
│ │ │ └── Account/ # My Account (orders, profile, addresses)
|
|
||||||
│ │ ├── components/
|
|
||||||
│ │ │ ├── ProductCard/
|
|
||||||
│ │ │ ├── CartDrawer/
|
|
||||||
│ │ │ ├── CheckoutForm/
|
|
||||||
│ │ │ └── AddressForm/
|
|
||||||
│ │ └── lib/
|
|
||||||
│ │ ├── api/ # API client
|
|
||||||
│ │ ├── cart/ # Cart state management
|
|
||||||
│ │ ├── checkout/ # Checkout logic
|
|
||||||
│ │ └── tracking/ # Analytics & pixel tracking
|
|
||||||
│ └── public/
|
|
||||||
│
|
|
||||||
└── includes/
|
|
||||||
├── Admin/ # Admin backend (serves admin-spa)
|
|
||||||
│ ├── AdminController.php
|
|
||||||
│ └── MenuManager.php
|
|
||||||
│
|
|
||||||
└── Frontend/ # Customer backend (serves customer-spa)
|
|
||||||
├── ShortcodeManager.php # [woonoow_cart], [woonoow_checkout]
|
|
||||||
├── SpaManager.php # Full SPA mode handler
|
|
||||||
└── Api/ # Customer API endpoints
|
|
||||||
├── ShopController.php
|
|
||||||
├── CartController.php
|
|
||||||
└── CheckoutController.php
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key Points:**
|
|
||||||
- ✅ **admin-spa/** - Admin interface only
|
|
||||||
- ✅ **customer-spa/** - Storefront + My Account in one app
|
|
||||||
- ✅ **includes/Admin/** - Admin backend logic
|
|
||||||
- ✅ **includes/Frontend/** - Customer backend logic
|
|
||||||
- ✅ Clear separation of concerns
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Deployment Modes
|
|
||||||
|
|
||||||
### Mode 1: Shortcode Mode (Default) ⭐ RECOMMENDED
|
|
||||||
|
|
||||||
**Use Case:** Works with ANY WordPress theme
|
|
||||||
|
|
||||||
**How it works:**
|
|
||||||
```php
|
|
||||||
// In theme template or page builder
|
|
||||||
[woonoow_shop]
|
|
||||||
[woonoow_cart]
|
|
||||||
[woonoow_checkout]
|
|
||||||
[woonoow_account]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ Compatible with all themes
|
|
||||||
- ✅ Works with page builders (Elementor, Divi, etc.)
|
|
||||||
- ✅ Progressive enhancement
|
|
||||||
- ✅ SEO-friendly (SSR for products)
|
|
||||||
- ✅ Easy migration from WooCommerce
|
|
||||||
|
|
||||||
**Architecture:**
|
|
||||||
- Theme provides layout/header/footer
|
|
||||||
- WooNooW provides interactive components
|
|
||||||
- Hybrid SSR + SPA islands pattern
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Mode 2: Full SPA Mode
|
|
||||||
|
|
||||||
**Use Case:** Maximum performance, app-like experience
|
|
||||||
|
|
||||||
**How it works:**
|
|
||||||
```php
|
|
||||||
// Settings > Frontend > Mode: Full SPA
|
|
||||||
// WooNooW takes over entire frontend
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ Fastest performance
|
|
||||||
- ✅ Smooth page transitions
|
|
||||||
- ✅ Offline support (PWA)
|
|
||||||
- ✅ App-like experience
|
|
||||||
- ✅ Optimized for mobile
|
|
||||||
|
|
||||||
**Architecture:**
|
|
||||||
- Single-page application
|
|
||||||
- Client-side routing
|
|
||||||
- Theme provides minimal wrapper
|
|
||||||
- API-driven data fetching
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Mode 3: Hybrid Mode
|
|
||||||
|
|
||||||
**Use Case:** Best of both worlds
|
|
||||||
|
|
||||||
**How it works:**
|
|
||||||
- Product pages: SSR (SEO)
|
|
||||||
- Cart/Checkout: SPA (UX)
|
|
||||||
- My Account: SPA (performance)
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ SEO for product pages
|
|
||||||
- ✅ Fast interactions for cart/checkout
|
|
||||||
- ✅ Balanced approach
|
|
||||||
- ✅ Flexible deployment
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## SEO Strategy
|
|
||||||
|
|
||||||
### Hybrid Rendering for SEO Compatibility
|
|
||||||
|
|
||||||
**Problem:** Full SPA can hurt SEO because search engines see empty HTML.
|
|
||||||
|
|
||||||
**Solution:** Hybrid rendering - SSR for SEO-critical pages, CSR for interactive pages.
|
|
||||||
|
|
||||||
### Rendering Strategy
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────┬──────────────┬─────────────────┐
|
|
||||||
│ Page Type │ Rendering │ SEO Needed? │
|
|
||||||
├─────────────────────┼──────────────┼─────────────────┤
|
|
||||||
│ Product Listing │ SSR │ ✅ Yes │
|
|
||||||
│ Product Detail │ SSR │ ✅ Yes │
|
|
||||||
│ Category Pages │ SSR │ ✅ Yes │
|
|
||||||
│ Search Results │ SSR │ ✅ Yes │
|
|
||||||
│ Cart │ CSR (SPA) │ ❌ No │
|
|
||||||
│ Checkout │ CSR (SPA) │ ❌ No │
|
|
||||||
│ My Account │ CSR (SPA) │ ❌ No │
|
|
||||||
│ Order Confirmation │ CSR (SPA) │ ❌ No │
|
|
||||||
└─────────────────────┴──────────────┴─────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### How SSR Works
|
|
||||||
|
|
||||||
**Product Page Example:**
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
// WordPress renders full HTML (SEO-friendly)
|
|
||||||
get_header();
|
|
||||||
|
|
||||||
$product = wc_get_product( get_the_ID() );
|
|
||||||
?>
|
|
||||||
|
|
||||||
<!-- Server-rendered HTML for SEO -->
|
|
||||||
<div id="woonoow-product" data-product-id="<?php echo $product->get_id(); ?>">
|
|
||||||
<h1><?php echo $product->get_name(); ?></h1>
|
|
||||||
<div class="price"><?php echo $product->get_price_html(); ?></div>
|
|
||||||
<div class="description"><?php echo $product->get_description(); ?></div>
|
|
||||||
|
|
||||||
<!-- SEO plugins inject meta tags here -->
|
|
||||||
<?php do_action('woocommerce_after_single_product'); ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php
|
|
||||||
get_footer();
|
|
||||||
// React hydrates this div for interactivity (add to cart, variations, etc.)
|
|
||||||
?>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ **Yoast SEO** works - sees full HTML
|
|
||||||
- ✅ **RankMath** works - sees full HTML
|
|
||||||
- ✅ **Google** crawls full content
|
|
||||||
- ✅ **Social sharing** shows correct meta tags
|
|
||||||
- ✅ **React adds interactivity** after page load
|
|
||||||
|
|
||||||
### SEO Plugin Compatibility
|
|
||||||
|
|
||||||
**Supported SEO Plugins:**
|
|
||||||
- ✅ Yoast SEO
|
|
||||||
- ✅ RankMath
|
|
||||||
- ✅ All in One SEO
|
|
||||||
- ✅ SEOPress
|
|
||||||
- ✅ The SEO Framework
|
|
||||||
|
|
||||||
**How it works:**
|
|
||||||
1. WordPress renders product page with full HTML
|
|
||||||
2. SEO plugin injects meta tags, schema markup
|
|
||||||
3. React hydrates for interactivity
|
|
||||||
4. Search engines see complete, SEO-optimized HTML
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tracking & Analytics
|
|
||||||
|
|
||||||
### Full Compatibility with Tracking Plugins
|
|
||||||
|
|
||||||
**Goal:** Ensure all tracking plugins work seamlessly with customer-spa.
|
|
||||||
|
|
||||||
### Strategy: Trigger WooCommerce Events
|
|
||||||
|
|
||||||
**Key Insight:** Keep WooCommerce classes and trigger WooCommerce events so tracking plugins can listen.
|
|
||||||
|
|
||||||
### Supported Tracking Plugins
|
|
||||||
|
|
||||||
✅ **PixelMySite** - Facebook, TikTok, Pinterest pixels
|
|
||||||
✅ **Google Analytics** - GA4, Universal Analytics
|
|
||||||
✅ **Google Tag Manager** - Full dataLayer support
|
|
||||||
✅ **Facebook Pixel** - Standard events
|
|
||||||
✅ **TikTok Pixel** - E-commerce events
|
|
||||||
✅ **Pinterest Tag** - Conversion tracking
|
|
||||||
✅ **Snapchat Pixel** - E-commerce events
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
|
|
||||||
**1. Keep WooCommerce Classes:**
|
|
||||||
```jsx
|
|
||||||
// customer-spa components use WooCommerce classes
|
|
||||||
<button
|
|
||||||
className="single_add_to_cart_button" // WooCommerce class
|
|
||||||
data-product_id="123" // WooCommerce data attr
|
|
||||||
onClick={handleAddToCart}
|
|
||||||
>
|
|
||||||
Add to Cart
|
|
||||||
</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
**2. Trigger WooCommerce Events:**
|
|
||||||
```typescript
|
|
||||||
// customer-spa/src/lib/tracking.ts
|
|
||||||
|
|
||||||
export const trackAddToCart = (product: Product, quantity: number) => {
|
|
||||||
// 1. WooCommerce event (for PixelMySite and other plugins)
|
|
||||||
jQuery(document.body).trigger('added_to_cart', [
|
|
||||||
product.id,
|
|
||||||
quantity,
|
|
||||||
product.price
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 2. Google Analytics / GTM
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
window.dataLayer.push({
|
|
||||||
event: 'add_to_cart',
|
|
||||||
ecommerce: {
|
|
||||||
items: [{
|
|
||||||
item_id: product.id,
|
|
||||||
item_name: product.name,
|
|
||||||
price: product.price,
|
|
||||||
quantity: quantity
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. Facebook Pixel (if loaded by plugin)
|
|
||||||
if (typeof fbq !== 'undefined') {
|
|
||||||
fbq('track', 'AddToCart', {
|
|
||||||
content_ids: [product.id],
|
|
||||||
content_name: product.name,
|
|
||||||
value: product.price * quantity,
|
|
||||||
currency: 'USD'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const trackBeginCheckout = (cart: Cart) => {
|
|
||||||
// WooCommerce event
|
|
||||||
jQuery(document.body).trigger('wc_checkout_loaded');
|
|
||||||
|
|
||||||
// Google Analytics
|
|
||||||
window.dataLayer?.push({
|
|
||||||
event: 'begin_checkout',
|
|
||||||
ecommerce: {
|
|
||||||
items: cart.items.map(item => ({
|
|
||||||
item_id: item.product_id,
|
|
||||||
item_name: item.name,
|
|
||||||
price: item.price,
|
|
||||||
quantity: item.quantity
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const trackPurchase = (order: Order) => {
|
|
||||||
// WooCommerce event
|
|
||||||
jQuery(document.body).trigger('wc_order_completed', [
|
|
||||||
order.id,
|
|
||||||
order.total
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Google Analytics
|
|
||||||
window.dataLayer?.push({
|
|
||||||
event: 'purchase',
|
|
||||||
ecommerce: {
|
|
||||||
transaction_id: order.id,
|
|
||||||
value: order.total,
|
|
||||||
currency: order.currency,
|
|
||||||
items: order.items.map(item => ({
|
|
||||||
item_id: item.product_id,
|
|
||||||
item_name: item.name,
|
|
||||||
price: item.price,
|
|
||||||
quantity: item.quantity
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**3. Usage in Components:**
|
|
||||||
```tsx
|
|
||||||
// customer-spa/src/pages/Product/AddToCartButton.tsx
|
|
||||||
|
|
||||||
import { trackAddToCart } from '@/lib/tracking';
|
|
||||||
|
|
||||||
function AddToCartButton({ product }: Props) {
|
|
||||||
const handleClick = async () => {
|
|
||||||
// Add to cart via API
|
|
||||||
await cartApi.add(product.id, quantity);
|
|
||||||
|
|
||||||
// Track event (triggers all pixels)
|
|
||||||
trackAddToCart(product, quantity);
|
|
||||||
|
|
||||||
// Show success message
|
|
||||||
toast.success('Added to cart!');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className="single_add_to_cart_button"
|
|
||||||
onClick={handleClick}
|
|
||||||
>
|
|
||||||
Add to Cart
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### E-commerce Events Tracked
|
|
||||||
|
|
||||||
```
|
|
||||||
✅ View Product
|
|
||||||
✅ Add to Cart
|
|
||||||
✅ Remove from Cart
|
|
||||||
✅ View Cart
|
|
||||||
✅ Begin Checkout
|
|
||||||
✅ Add Shipping Info
|
|
||||||
✅ Add Payment Info
|
|
||||||
✅ Purchase
|
|
||||||
✅ Refund
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result
|
|
||||||
|
|
||||||
**All tracking plugins work out of the box!**
|
|
||||||
- PixelMySite listens to WooCommerce events ✅
|
|
||||||
- Google Analytics receives dataLayer events ✅
|
|
||||||
- Facebook/TikTok pixels fire correctly ✅
|
|
||||||
- Store owner doesn't need to change anything ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Feature Scope
|
|
||||||
|
|
||||||
### Phase 1: Core Commerce (MVP)
|
|
||||||
|
|
||||||
#### 1. Product Catalog
|
|
||||||
- Product listing with filters
|
|
||||||
- Product detail page
|
|
||||||
- Product search
|
|
||||||
- Category navigation
|
|
||||||
- Product variations
|
|
||||||
- Image gallery with zoom
|
|
||||||
- Related products
|
|
||||||
|
|
||||||
#### 2. Shopping Cart
|
|
||||||
- Add to cart (AJAX)
|
|
||||||
- Cart drawer/sidebar
|
|
||||||
- Update quantities
|
|
||||||
- Remove items
|
|
||||||
- Apply coupons
|
|
||||||
- Shipping calculator
|
|
||||||
- Cart persistence (localStorage)
|
|
||||||
|
|
||||||
#### 3. Checkout
|
|
||||||
- Single-page checkout
|
|
||||||
- Guest checkout
|
|
||||||
- Address autocomplete
|
|
||||||
- Shipping method selection
|
|
||||||
- Payment method selection
|
|
||||||
- Order review
|
|
||||||
- Order confirmation
|
|
||||||
|
|
||||||
#### 4. My Account
|
|
||||||
- Dashboard overview
|
|
||||||
- Order history
|
|
||||||
- Order details
|
|
||||||
- Download invoices
|
|
||||||
- Track shipments
|
|
||||||
- Edit profile
|
|
||||||
- Change password
|
|
||||||
- Manage addresses
|
|
||||||
- Payment methods
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 2: Enhanced Features
|
|
||||||
|
|
||||||
#### 5. Wishlist
|
|
||||||
- Add to wishlist
|
|
||||||
- Wishlist page
|
|
||||||
- Share wishlist
|
|
||||||
- Move to cart
|
|
||||||
|
|
||||||
#### 6. Product Reviews
|
|
||||||
- Write review
|
|
||||||
- Upload photos
|
|
||||||
- Rating system
|
|
||||||
- Review moderation
|
|
||||||
- Helpful votes
|
|
||||||
|
|
||||||
#### 7. Quick View
|
|
||||||
- Product quick view modal
|
|
||||||
- Add to cart from quick view
|
|
||||||
- Variation selection
|
|
||||||
|
|
||||||
#### 8. Product Compare
|
|
||||||
- Add to compare
|
|
||||||
- Compare table
|
|
||||||
- Side-by-side comparison
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 3: Advanced Features
|
|
||||||
|
|
||||||
#### 9. Subscriptions
|
|
||||||
- Subscription products
|
|
||||||
- Manage subscriptions
|
|
||||||
- Pause/resume
|
|
||||||
- Change frequency
|
|
||||||
- Update payment method
|
|
||||||
|
|
||||||
#### 10. Memberships
|
|
||||||
- Member-only products
|
|
||||||
- Member pricing
|
|
||||||
- Membership dashboard
|
|
||||||
- Access control
|
|
||||||
|
|
||||||
#### 11. Digital Downloads
|
|
||||||
- Download manager
|
|
||||||
- License keys
|
|
||||||
- Version updates
|
|
||||||
- Download limits
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UX Best Practices
|
|
||||||
|
|
||||||
### Research-Backed Patterns
|
|
||||||
|
|
||||||
Based on Baymard Institute research and industry leaders:
|
|
||||||
|
|
||||||
#### Cart UX
|
|
||||||
✅ **Persistent cart drawer** - Always accessible, slides from right
|
|
||||||
✅ **Mini cart preview** - Show items without leaving page
|
|
||||||
✅ **Free shipping threshold** - "Add $X more for free shipping"
|
|
||||||
✅ **Save for later** - Move items to wishlist
|
|
||||||
✅ **Stock indicators** - "Only 3 left in stock"
|
|
||||||
✅ **Estimated delivery** - Show delivery date
|
|
||||||
|
|
||||||
#### Checkout UX
|
|
||||||
✅ **Progress indicator** - Show steps (Shipping → Payment → Review)
|
|
||||||
✅ **Guest checkout** - Don't force account creation
|
|
||||||
✅ **Address autocomplete** - Google Places API
|
|
||||||
✅ **Inline validation** - Real-time error messages
|
|
||||||
✅ **Trust signals** - Security badges, SSL indicators
|
|
||||||
✅ **Mobile-optimized** - Large touch targets, numeric keyboards
|
|
||||||
✅ **One-page checkout** - Minimize steps
|
|
||||||
✅ **Save payment methods** - For returning customers
|
|
||||||
|
|
||||||
#### Product Page UX
|
|
||||||
✅ **High-quality images** - Multiple angles, zoom
|
|
||||||
✅ **Clear CTA** - Prominent "Add to Cart" button
|
|
||||||
✅ **Stock status** - In stock / Out of stock / Pre-order
|
|
||||||
✅ **Shipping info** - Delivery estimate
|
|
||||||
✅ **Size guide** - For apparel
|
|
||||||
✅ **Social proof** - Reviews, ratings
|
|
||||||
✅ **Related products** - Cross-sell
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Technical Stack
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- **Framework:** React 18 (with Suspense, Transitions)
|
|
||||||
- **Routing:** React Router v6
|
|
||||||
- **State:** Zustand (cart, checkout state)
|
|
||||||
- **Data Fetching:** TanStack Query (React Query)
|
|
||||||
- **Forms:** React Hook Form + Zod validation
|
|
||||||
- **Styling:** TailwindCSS + shadcn/ui
|
|
||||||
- **Build:** Vite
|
|
||||||
- **PWA:** Workbox (service worker)
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- **API:** WordPress REST API (custom endpoints)
|
|
||||||
- **Authentication:** WordPress nonces + JWT (optional)
|
|
||||||
- **Session:** WooCommerce session handler
|
|
||||||
- **Cache:** Transients API + Object cache
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
- **Code Splitting:** Route-based lazy loading
|
|
||||||
- **Image Optimization:** WebP, lazy loading, blur placeholders
|
|
||||||
- **Caching:** Service worker, API response cache
|
|
||||||
- **CDN:** Static assets on CDN
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Roadmap
|
|
||||||
|
|
||||||
### Sprint 1-2: Foundation (2 weeks)
|
|
||||||
- [ ] Setup customer-spa build system
|
|
||||||
- [ ] Create base layout components
|
|
||||||
- [ ] Implement routing
|
|
||||||
- [ ] Setup API client
|
|
||||||
- [ ] Cart state management
|
|
||||||
- [ ] Authentication flow
|
|
||||||
|
|
||||||
### Sprint 3-4: Product Catalog (2 weeks)
|
|
||||||
- [ ] Product listing page
|
|
||||||
- [ ] Product filters
|
|
||||||
- [ ] Product search
|
|
||||||
- [ ] Product detail page
|
|
||||||
- [ ] Product variations
|
|
||||||
- [ ] Image gallery
|
|
||||||
|
|
||||||
### Sprint 5-6: Cart & Checkout (2 weeks)
|
|
||||||
- [ ] Cart drawer component
|
|
||||||
- [ ] Cart page
|
|
||||||
- [ ] Checkout form
|
|
||||||
- [ ] Address autocomplete
|
|
||||||
- [ ] Shipping calculator
|
|
||||||
- [ ] Payment integration
|
|
||||||
|
|
||||||
### Sprint 7-8: My Account (2 weeks)
|
|
||||||
- [ ] Account dashboard
|
|
||||||
- [ ] Order history
|
|
||||||
- [ ] Order details
|
|
||||||
- [ ] Profile management
|
|
||||||
- [ ] Address book
|
|
||||||
- [ ] Download manager
|
|
||||||
|
|
||||||
### Sprint 9-10: Polish & Testing (2 weeks)
|
|
||||||
- [ ] Mobile optimization
|
|
||||||
- [ ] Performance tuning
|
|
||||||
- [ ] Accessibility audit
|
|
||||||
- [ ] Browser testing
|
|
||||||
- [ ] User testing
|
|
||||||
- [ ] Bug fixes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Requirements
|
|
||||||
|
|
||||||
### New Endpoints Needed
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /woonoow/v1/shop/products
|
|
||||||
GET /woonoow/v1/shop/products/:id
|
|
||||||
GET /woonoow/v1/shop/categories
|
|
||||||
GET /woonoow/v1/shop/search
|
|
||||||
|
|
||||||
POST /woonoow/v1/cart/add
|
|
||||||
POST /woonoow/v1/cart/update
|
|
||||||
POST /woonoow/v1/cart/remove
|
|
||||||
GET /woonoow/v1/cart
|
|
||||||
POST /woonoow/v1/cart/apply-coupon
|
|
||||||
|
|
||||||
POST /woonoow/v1/checkout/calculate
|
|
||||||
POST /woonoow/v1/checkout/create-order
|
|
||||||
GET /woonoow/v1/checkout/payment-methods
|
|
||||||
GET /woonoow/v1/checkout/shipping-methods
|
|
||||||
|
|
||||||
GET /woonoow/v1/account/orders
|
|
||||||
GET /woonoow/v1/account/orders/:id
|
|
||||||
GET /woonoow/v1/account/downloads
|
|
||||||
POST /woonoow/v1/account/profile
|
|
||||||
POST /woonoow/v1/account/password
|
|
||||||
POST /woonoow/v1/account/addresses
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Targets
|
|
||||||
|
|
||||||
### Core Web Vitals
|
|
||||||
- **LCP (Largest Contentful Paint):** < 2.5s
|
|
||||||
- **FID (First Input Delay):** < 100ms
|
|
||||||
- **CLS (Cumulative Layout Shift):** < 0.1
|
|
||||||
|
|
||||||
### Bundle Sizes
|
|
||||||
- **Initial JS:** < 150KB (gzipped)
|
|
||||||
- **Initial CSS:** < 50KB (gzipped)
|
|
||||||
- **Route chunks:** < 50KB each (gzipped)
|
|
||||||
|
|
||||||
### Page Load Times
|
|
||||||
- **Product page:** < 1.5s (3G)
|
|
||||||
- **Cart page:** < 1s
|
|
||||||
- **Checkout page:** < 1.5s
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Settings & Configuration
|
|
||||||
|
|
||||||
### Frontend Settings Panel
|
|
||||||
|
|
||||||
```
|
|
||||||
WooNooW > Settings > Frontend
|
|
||||||
├── Mode
|
|
||||||
│ ○ Disabled (use theme)
|
|
||||||
│ ● Shortcodes (default)
|
|
||||||
│ ○ Full SPA
|
|
||||||
├── Features
|
|
||||||
│ ☑ Product catalog
|
|
||||||
│ ☑ Shopping cart
|
|
||||||
│ ☑ Checkout
|
|
||||||
│ ☑ My Account
|
|
||||||
│ ☐ Wishlist (Phase 2)
|
|
||||||
│ ☐ Product reviews (Phase 2)
|
|
||||||
├── Performance
|
|
||||||
│ ☑ Enable PWA
|
|
||||||
│ ☑ Offline mode
|
|
||||||
│ ☑ Image lazy loading
|
|
||||||
│ Cache duration: 1 hour
|
|
||||||
└── Customization
|
|
||||||
Primary color: #000000
|
|
||||||
Font family: System
|
|
||||||
Border radius: 8px
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migration Strategy
|
|
||||||
|
|
||||||
### From WooCommerce Default
|
|
||||||
|
|
||||||
1. **Install WooNooW** - Keep WooCommerce active
|
|
||||||
2. **Enable Shortcode Mode** - Test on staging
|
|
||||||
3. **Replace pages** - Cart, Checkout, My Account
|
|
||||||
4. **Test thoroughly** - All user flows
|
|
||||||
5. **Go live** - Switch DNS
|
|
||||||
6. **Monitor** - Analytics, errors
|
|
||||||
|
|
||||||
### Rollback Plan
|
|
||||||
|
|
||||||
- Keep WooCommerce pages as backup
|
|
||||||
- Settings toggle to disable customer-spa
|
|
||||||
- Fallback to WooCommerce templates
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
### Business Metrics
|
|
||||||
- Cart abandonment rate: < 60% (industry avg: 70%)
|
|
||||||
- Checkout completion rate: > 40%
|
|
||||||
- Mobile conversion rate: > 2%
|
|
||||||
- Page load time: < 2s
|
|
||||||
|
|
||||||
### Technical Metrics
|
|
||||||
- Lighthouse score: > 90
|
|
||||||
- Core Web Vitals: All green
|
|
||||||
- Error rate: < 0.1%
|
|
||||||
- API response time: < 200ms
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Competitive Analysis
|
|
||||||
|
|
||||||
### Shopify Hydrogen
|
|
||||||
- **Pros:** Fast, modern, React-based
|
|
||||||
- **Cons:** Shopify-only, complex setup
|
|
||||||
- **Lesson:** Simplify developer experience
|
|
||||||
|
|
||||||
### WooCommerce Blocks
|
|
||||||
- **Pros:** Native WooCommerce integration
|
|
||||||
- **Cons:** Limited customization, slow
|
|
||||||
- **Lesson:** Provide flexibility
|
|
||||||
|
|
||||||
### SureCart
|
|
||||||
- **Pros:** Simple, fast checkout
|
|
||||||
- **Cons:** Limited features
|
|
||||||
- **Lesson:** Focus on core experience first
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. ✅ Review and approve this plan
|
|
||||||
2. ⏳ Create detailed technical specs
|
|
||||||
3. ⏳ Setup customer-spa project structure
|
|
||||||
4. ⏳ Begin Sprint 1 (Foundation)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Decision Required:** Approve this plan to proceed with implementation.
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
# Documentation Cleanup Audit - December 2025
|
|
||||||
|
|
||||||
**Total Files Found**: 74 markdown files
|
|
||||||
**Audit Date**: December 26, 2025
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Audit Categories
|
|
||||||
|
|
||||||
### ✅ KEEP - Essential & Active (18 files)
|
|
||||||
|
|
||||||
#### Core Documentation
|
|
||||||
1. **README.md** - Main plugin documentation
|
|
||||||
2. **API_ROUTES.md** - API endpoint reference
|
|
||||||
3. **HOOKS_REGISTRY.md** - Filter/action hooks registry
|
|
||||||
4. **VALIDATION_HOOKS.md** - Email/phone validation hooks (NEW)
|
|
||||||
|
|
||||||
#### Architecture & Patterns
|
|
||||||
5. **ADDON_BRIDGE_PATTERN.md** - Addon architecture
|
|
||||||
6. **ADDON_DEVELOPMENT_GUIDE.md** - Addon development guide
|
|
||||||
7. **ADDON_REACT_INTEGRATION.md** - React addon integration
|
|
||||||
8. **PAYMENT_GATEWAY_PATTERNS.md** - Payment gateway patterns
|
|
||||||
9. **ARCHITECTURE_DECISION_CUSTOMER_SPA.md** - Customer SPA architecture
|
|
||||||
|
|
||||||
#### System Guides
|
|
||||||
10. **NOTIFICATION_SYSTEM.md** - Notification system documentation
|
|
||||||
11. **I18N_IMPLEMENTATION_GUIDE.md** - Translation system
|
|
||||||
12. **EMAIL_DEBUGGING_GUIDE.md** - Email troubleshooting
|
|
||||||
13. **FILTER_HOOKS_GUIDE.md** - Filter hooks guide
|
|
||||||
14. **MARKDOWN_SYNTAX_AND_VARIABLES.md** - Email template syntax
|
|
||||||
|
|
||||||
#### Active Plans
|
|
||||||
15. **NEWSLETTER_CAMPAIGN_PLAN.md** - Newsletter campaign architecture (NEW)
|
|
||||||
16. **SETUP_WIZARD_DESIGN.md** - Setup wizard design
|
|
||||||
17. **TAX_SETTINGS_DESIGN.md** - Tax settings UI/UX
|
|
||||||
18. **CUSTOMER_SPA_MASTER_PLAN.md** - Customer SPA roadmap
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🗑️ DELETE - Obsolete/Completed (32 files)
|
|
||||||
|
|
||||||
#### Completed Fixes (Delete - Issues Resolved)
|
|
||||||
1. **FIXES_APPLIED.md** - Old fixes log
|
|
||||||
2. **REAL_FIX.md** - Temporary fix doc
|
|
||||||
3. **CANONICAL_REDIRECT_FIX.md** - Fix completed
|
|
||||||
4. **HEADER_FIXES_APPLIED.md** - Fix completed
|
|
||||||
5. **FINAL_FIXES.md** - Fix completed
|
|
||||||
6. **FINAL_FIXES_APPLIED.md** - Fix completed
|
|
||||||
7. **FIX_500_ERROR.md** - Fix completed
|
|
||||||
8. **HASHROUTER_FIXES.md** - Fix completed
|
|
||||||
9. **INLINE_SPACING_FIX.md** - Fix completed
|
|
||||||
10. **DIRECT_ACCESS_FIX.md** - Fix completed
|
|
||||||
|
|
||||||
#### Completed Features (Delete - Implemented)
|
|
||||||
11. **APPEARANCE_MENU_RESTRUCTURE.md** - Menu restructured
|
|
||||||
12. **SETTINGS-RESTRUCTURE.md** - Settings restructured
|
|
||||||
13. **HEADER_FOOTER_REDESIGN.md** - Redesign completed
|
|
||||||
14. **TYPOGRAPHY-PLAN.md** - Typography implemented
|
|
||||||
15. **CUSTOMER_SPA_SETTINGS.md** - Settings implemented
|
|
||||||
16. **CUSTOMER_SPA_STATUS.md** - Status outdated
|
|
||||||
17. **CUSTOMER_SPA_THEME_SYSTEM.md** - Theme system built
|
|
||||||
|
|
||||||
#### Product Page (Delete - Completed)
|
|
||||||
18. **PRODUCT_PAGE_VISUAL_OVERHAUL.md** - Overhaul completed
|
|
||||||
19. **PRODUCT_PAGE_FINAL_STATUS.md** - Status outdated
|
|
||||||
20. **PRODUCT_PAGE_REVIEW_REPORT.md** - Review completed
|
|
||||||
21. **PRODUCT_PAGE_ANALYSIS_REPORT.md** - Analysis completed
|
|
||||||
22. **PRODUCT_CART_COMPLETE.md** - Feature completed
|
|
||||||
|
|
||||||
#### Meta/Compat (Delete - Implemented)
|
|
||||||
23. **IMPLEMENTATION_PLAN_META_COMPAT.md** - Implemented
|
|
||||||
24. **METABOX_COMPAT.md** - Implemented
|
|
||||||
|
|
||||||
#### Old Audit Reports (Delete - Superseded)
|
|
||||||
25. **DOCS_AUDIT_REPORT.md** - Old audit (Nov 2025)
|
|
||||||
|
|
||||||
#### Shipping Research (Delete - Superseded by Integration)
|
|
||||||
26. **SHIPPING_ADDON_RESEARCH.md** - Research phase done
|
|
||||||
27. **SHIPPING_FIELD_HOOKS.md** - Hooks documented in HOOKS_REGISTRY
|
|
||||||
|
|
||||||
#### Deployment/Testing (Delete - Process Docs)
|
|
||||||
28. **DEPLOYMENT_GUIDE.md** - Deployment is automated
|
|
||||||
29. **TESTING_CHECKLIST.md** - Testing is ongoing
|
|
||||||
30. **TROUBLESHOOTING.md** - Issues resolved
|
|
||||||
|
|
||||||
#### Customer SPA (Delete - Superseded)
|
|
||||||
31. **CUSTOMER_SPA_ARCHITECTURE.md** - Superseded by MASTER_PLAN
|
|
||||||
|
|
||||||
#### Other
|
|
||||||
32. **PLUGIN_ZIP_GUIDE.md** - Just created, can be deleted (packaging automated)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 📦 MERGE - Consolidate Related (6 files)
|
|
||||||
|
|
||||||
#### Shipping Documentation → Create `SHIPPING_INTEGRATION.md`
|
|
||||||
1. **RAJAONGKIR_INTEGRATION.md** - RajaOngkir integration
|
|
||||||
2. **BITESHIP_ADDON_SPEC.md** - Biteship addon spec
|
|
||||||
→ **Merge into**: `SHIPPING_INTEGRATION.md` (shipping addons guide)
|
|
||||||
|
|
||||||
#### Customer SPA → Keep only `CUSTOMER_SPA_MASTER_PLAN.md`
|
|
||||||
3. **CUSTOMER_SPA_ARCHITECTURE.md** - Architecture details
|
|
||||||
4. **CUSTOMER_SPA_SETTINGS.md** - Settings details
|
|
||||||
5. **CUSTOMER_SPA_STATUS.md** - Status updates
|
|
||||||
6. **CUSTOMER_SPA_THEME_SYSTEM.md** - Theme system
|
|
||||||
→ **Action**: Delete 3-6, keep only MASTER_PLAN
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 📝 UPDATE - Needs Refresh (18 files remaining)
|
|
||||||
|
|
||||||
Files to keep but may need updates as features evolve.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Cleanup Actions
|
|
||||||
|
|
||||||
### Phase 1: Delete Obsolete (32 files)
|
|
||||||
```bash
|
|
||||||
# Completed fixes
|
|
||||||
rm FIXES_APPLIED.md REAL_FIX.md CANONICAL_REDIRECT_FIX.md
|
|
||||||
rm HEADER_FIXES_APPLIED.md FINAL_FIXES.md FINAL_FIXES_APPLIED.md
|
|
||||||
rm FIX_500_ERROR.md HASHROUTER_FIXES.md INLINE_SPACING_FIX.md
|
|
||||||
rm DIRECT_ACCESS_FIX.md
|
|
||||||
|
|
||||||
# Completed features
|
|
||||||
rm APPEARANCE_MENU_RESTRUCTURE.md SETTINGS-RESTRUCTURE.md
|
|
||||||
rm HEADER_FOOTER_REDESIGN.md TYPOGRAPHY-PLAN.md
|
|
||||||
rm CUSTOMER_SPA_SETTINGS.md CUSTOMER_SPA_STATUS.md
|
|
||||||
rm CUSTOMER_SPA_THEME_SYSTEM.md CUSTOMER_SPA_ARCHITECTURE.md
|
|
||||||
|
|
||||||
# Product page
|
|
||||||
rm PRODUCT_PAGE_VISUAL_OVERHAUL.md PRODUCT_PAGE_FINAL_STATUS.md
|
|
||||||
rm PRODUCT_PAGE_REVIEW_REPORT.md PRODUCT_PAGE_ANALYSIS_REPORT.md
|
|
||||||
rm PRODUCT_CART_COMPLETE.md
|
|
||||||
|
|
||||||
# Meta/compat
|
|
||||||
rm IMPLEMENTATION_PLAN_META_COMPAT.md METABOX_COMPAT.md
|
|
||||||
|
|
||||||
# Old audits
|
|
||||||
rm DOCS_AUDIT_REPORT.md
|
|
||||||
|
|
||||||
# Shipping research
|
|
||||||
rm SHIPPING_ADDON_RESEARCH.md SHIPPING_FIELD_HOOKS.md
|
|
||||||
|
|
||||||
# Process docs
|
|
||||||
rm DEPLOYMENT_GUIDE.md TESTING_CHECKLIST.md TROUBLESHOOTING.md
|
|
||||||
|
|
||||||
# Other
|
|
||||||
rm PLUGIN_ZIP_GUIDE.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Merge Shipping Docs
|
|
||||||
```bash
|
|
||||||
# Create consolidated shipping guide
|
|
||||||
cat RAJAONGKIR_INTEGRATION.md BITESHIP_ADDON_SPEC.md > SHIPPING_INTEGRATION.md
|
|
||||||
# Edit and clean up SHIPPING_INTEGRATION.md
|
|
||||||
rm RAJAONGKIR_INTEGRATION.md BITESHIP_ADDON_SPEC.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: Update Package Script
|
|
||||||
Update `scripts/package-zip.mjs` to exclude `*.md` files from production zip.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Results
|
|
||||||
|
|
||||||
| Category | Before | After | Reduction |
|
|
||||||
|----------|--------|-------|-----------|
|
|
||||||
| Total Files | 74 | 20 | 73% |
|
|
||||||
| Essential Docs | 18 | 18 | - |
|
|
||||||
| Obsolete | 32 | 0 | 100% |
|
|
||||||
| Merged | 6 | 1 | 83% |
|
|
||||||
|
|
||||||
**Final Documentation Set**: 20 essential files
|
|
||||||
- Core: 4 files
|
|
||||||
- Architecture: 5 files
|
|
||||||
- System Guides: 5 files
|
|
||||||
- Active Plans: 4 files
|
|
||||||
- Shipping: 1 file (merged)
|
|
||||||
- Addon Development: 1 file (merged)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Benefits
|
|
||||||
|
|
||||||
1. **Clarity** - Only relevant, up-to-date documentation
|
|
||||||
2. **Maintainability** - Less docs to keep in sync
|
|
||||||
3. **Onboarding** - Easier for new developers
|
|
||||||
4. **Focus** - Clear what's active vs historical
|
|
||||||
5. **Size** - Smaller plugin zip (no obsolete docs)
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
# All Issues Fixed - Ready for Testing
|
|
||||||
|
|
||||||
## ✅ Issue 1: Image Not Covering Container - FIXED
|
|
||||||
|
|
||||||
**Problem:** Images weren't filling their aspect-ratio containers properly.
|
|
||||||
|
|
||||||
**Root Cause:** The `aspect-square` div creates a container with padding-bottom, but child elements need `absolute` positioning to fill it.
|
|
||||||
|
|
||||||
**Solution:** Added `absolute inset-0` to all images:
|
|
||||||
```tsx
|
|
||||||
// Before
|
|
||||||
<img className="w-full h-full object-cover" />
|
|
||||||
|
|
||||||
// After
|
|
||||||
<img className="absolute inset-0 w-full h-full object-cover object-center" />
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `customer-spa/src/components/ProductCard.tsx` (all 4 layouts)
|
|
||||||
|
|
||||||
**Result:** Images now properly fill their containers without gaps.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Issue 2: TypeScript Lint Errors - FIXED
|
|
||||||
|
|
||||||
**Problem:** Multiple TypeScript errors causing fragile code that's easy to corrupt.
|
|
||||||
|
|
||||||
**Solution:** Created proper type definitions:
|
|
||||||
|
|
||||||
**New File:** `customer-spa/src/types/product.ts`
|
|
||||||
```typescript
|
|
||||||
export interface Product {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
price: string;
|
|
||||||
regular_price?: string;
|
|
||||||
sale_price?: string;
|
|
||||||
on_sale: boolean;
|
|
||||||
stock_status: 'instock' | 'outofstock' | 'onbackorder';
|
|
||||||
image?: string;
|
|
||||||
// ... more fields
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProductsResponse {
|
|
||||||
products: Product[];
|
|
||||||
total: number;
|
|
||||||
total_pages: number;
|
|
||||||
current_page: number;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `customer-spa/src/types/product.ts` (created)
|
|
||||||
- `customer-spa/src/pages/Shop/index.tsx` (added types)
|
|
||||||
- `customer-spa/src/pages/Product/index.tsx` (added types)
|
|
||||||
|
|
||||||
**Result:** Zero TypeScript errors, code is now stable and safe to modify.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Issue 3: Direct URL Access - FIXED
|
|
||||||
|
|
||||||
**Problem:** Accessing `/product/edukasi-anak` directly redirected to `/shop`.
|
|
||||||
|
|
||||||
**Root Cause:** PHP template override wasn't checking for `is_product()`.
|
|
||||||
|
|
||||||
**Solution:** Added `is_product()` check in full SPA mode:
|
|
||||||
```php
|
|
||||||
// Before
|
|
||||||
if (is_woocommerce() || is_cart() || is_checkout() || is_account_page())
|
|
||||||
|
|
||||||
// After
|
|
||||||
if (is_woocommerce() || is_product() || is_cart() || is_checkout() || is_account_page())
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `includes/Frontend/TemplateOverride.php` (line 83)
|
|
||||||
|
|
||||||
**Result:** Direct product URLs now work correctly, no redirect.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Issue 4: Add to Cart API - COMPLETE
|
|
||||||
|
|
||||||
**Problem:** Add to cart failed because REST API endpoint didn't exist.
|
|
||||||
|
|
||||||
**Solution:** Created complete Cart API Controller with all endpoints:
|
|
||||||
|
|
||||||
**New File:** `includes/Api/Controllers/CartController.php`
|
|
||||||
|
|
||||||
**Endpoints Created:**
|
|
||||||
- `GET /cart` - Get cart contents
|
|
||||||
- `POST /cart/add` - Add product to cart
|
|
||||||
- `POST /cart/update` - Update item quantity
|
|
||||||
- `POST /cart/remove` - Remove item from cart
|
|
||||||
- `POST /cart/clear` - Clear entire cart
|
|
||||||
- `POST /cart/apply-coupon` - Apply coupon code
|
|
||||||
- `POST /cart/remove-coupon` - Remove coupon
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Proper WooCommerce cart integration
|
|
||||||
- Stock validation
|
|
||||||
- Error handling
|
|
||||||
- Formatted responses with totals
|
|
||||||
- Coupon support
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `includes/Api/Controllers/CartController.php` (created)
|
|
||||||
- `includes/Api/Routes.php` (registered controller)
|
|
||||||
|
|
||||||
**Result:** Add to cart now works! Full cart functionality available.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Testing Checklist
|
|
||||||
|
|
||||||
### 1. Test TypeScript (No Errors)
|
|
||||||
```bash
|
|
||||||
cd customer-spa
|
|
||||||
npm run build
|
|
||||||
# Should complete without errors
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Test Images
|
|
||||||
1. Go to `/shop`
|
|
||||||
2. Check all product images
|
|
||||||
3. Should fill containers completely
|
|
||||||
4. No gaps or distortion
|
|
||||||
|
|
||||||
### 3. Test Direct URLs
|
|
||||||
1. Copy product URL: `https://woonoow.local/product/edukasi-anak`
|
|
||||||
2. Open in new tab
|
|
||||||
3. Should load product page directly
|
|
||||||
4. No redirect to `/shop`
|
|
||||||
|
|
||||||
### 4. Test Add to Cart
|
|
||||||
1. Go to shop page
|
|
||||||
2. Click "Add to Cart" on any product
|
|
||||||
3. Should show success toast
|
|
||||||
4. Check browser console - no errors
|
|
||||||
5. Cart count should update
|
|
||||||
|
|
||||||
### 5. Test Product Page
|
|
||||||
1. Click any product
|
|
||||||
2. Should navigate to `/product/slug-name`
|
|
||||||
3. See full product details
|
|
||||||
4. Change quantity
|
|
||||||
5. Click "Add to Cart"
|
|
||||||
6. Should work and show success
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 What's Working Now
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- ✅ Shop page with products
|
|
||||||
- ✅ Product detail page
|
|
||||||
- ✅ Search and filters
|
|
||||||
- ✅ Pagination
|
|
||||||
- ✅ Add to cart functionality
|
|
||||||
- ✅ 4 layout variants (Classic, Modern, Boutique, Launch)
|
|
||||||
- ✅ Currency formatting
|
|
||||||
- ✅ Direct URL access
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- ✅ Settings API
|
|
||||||
- ✅ Cart API (complete)
|
|
||||||
- ✅ Template override system
|
|
||||||
- ✅ Mode detection (disabled/full/checkout-only)
|
|
||||||
|
|
||||||
### Code Quality
|
|
||||||
- ✅ Zero TypeScript errors
|
|
||||||
- ✅ Proper type definitions
|
|
||||||
- ✅ Stable, maintainable code
|
|
||||||
- ✅ No fragile patterns
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📁 Files Changed Summary
|
|
||||||
|
|
||||||
```
|
|
||||||
customer-spa/src/
|
|
||||||
├── types/
|
|
||||||
│ └── product.ts # NEW - Type definitions
|
|
||||||
├── components/
|
|
||||||
│ └── ProductCard.tsx # FIXED - Image positioning
|
|
||||||
├── pages/
|
|
||||||
│ ├── Shop/index.tsx # FIXED - Added types
|
|
||||||
│ └── Product/index.tsx # FIXED - Added types
|
|
||||||
|
|
||||||
includes/
|
|
||||||
├── Frontend/
|
|
||||||
│ └── TemplateOverride.php # FIXED - Added is_product()
|
|
||||||
└── Api/
|
|
||||||
├── Controllers/
|
|
||||||
│ └── CartController.php # NEW - Complete cart API
|
|
||||||
└── Routes.php # MODIFIED - Registered cart controller
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Next Steps
|
|
||||||
|
|
||||||
### Immediate Testing
|
|
||||||
1. Clear browser cache
|
|
||||||
2. Test all 4 issues above
|
|
||||||
3. Verify no console errors
|
|
||||||
|
|
||||||
### Future Development
|
|
||||||
1. Cart page UI
|
|
||||||
2. Checkout page
|
|
||||||
3. Thank you page
|
|
||||||
4. My Account pages
|
|
||||||
5. Homepage builder
|
|
||||||
6. Navigation integration
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🐛 Known Issues (None!)
|
|
||||||
|
|
||||||
All major issues are now fixed. The codebase is:
|
|
||||||
- ✅ Type-safe
|
|
||||||
- ✅ Stable
|
|
||||||
- ✅ Maintainable
|
|
||||||
- ✅ Fully functional
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** ALL 4 ISSUES FIXED ✅
|
|
||||||
**Ready for:** Full testing and continued development
|
|
||||||
**Code Quality:** Excellent - No TypeScript errors, proper types, clean code
|
|
||||||
@@ -1,434 +0,0 @@
|
|||||||
# HashRouter Solution - The Right Approach
|
|
||||||
|
|
||||||
## Problem
|
|
||||||
Direct product URLs like `https://woonoow.local/product/edukasi-anak` don't work because WordPress owns the `/product/` route.
|
|
||||||
|
|
||||||
## Why Admin SPA Works
|
|
||||||
|
|
||||||
Admin SPA uses HashRouter:
|
|
||||||
```
|
|
||||||
https://woonoow.local/wp-admin/admin.php?page=woonoow#/dashboard
|
|
||||||
↑
|
|
||||||
Hash routing
|
|
||||||
```
|
|
||||||
|
|
||||||
**How it works:**
|
|
||||||
1. WordPress loads: `/wp-admin/admin.php?page=woonoow`
|
|
||||||
2. React takes over: `#/dashboard`
|
|
||||||
3. Everything after `#` is client-side only
|
|
||||||
4. WordPress never sees or processes it
|
|
||||||
5. Works perfectly ✅
|
|
||||||
|
|
||||||
## Why Customer SPA Should Use HashRouter Too
|
|
||||||
|
|
||||||
### The Conflict
|
|
||||||
|
|
||||||
**WordPress owns these routes:**
|
|
||||||
- `/product/` - WooCommerce product pages
|
|
||||||
- `/cart/` - WooCommerce cart
|
|
||||||
- `/checkout/` - WooCommerce checkout
|
|
||||||
- `/my-account/` - WooCommerce account
|
|
||||||
|
|
||||||
**We can't override them reliably** because:
|
|
||||||
- WordPress processes the URL first
|
|
||||||
- Theme templates load before our SPA
|
|
||||||
- Canonical redirects interfere
|
|
||||||
- SEO and caching issues
|
|
||||||
|
|
||||||
### The Solution: HashRouter
|
|
||||||
|
|
||||||
Use hash-based routing like Admin SPA:
|
|
||||||
|
|
||||||
```
|
|
||||||
https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
↑
|
|
||||||
Hash routing
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ WordPress loads `/shop` (valid page)
|
|
||||||
- ✅ React handles `#/product/edukasi-anak`
|
|
||||||
- ✅ No WordPress conflicts
|
|
||||||
- ✅ Works for direct access
|
|
||||||
- ✅ Works for sharing links
|
|
||||||
- ✅ Works for email campaigns
|
|
||||||
- ✅ Reliable and predictable
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
|
|
||||||
### Changed File: App.tsx
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Before
|
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
<BrowserRouter>
|
|
||||||
<Routes>
|
|
||||||
<Route path="/product/:slug" element={<Product />} />
|
|
||||||
</Routes>
|
|
||||||
</BrowserRouter>
|
|
||||||
|
|
||||||
// After
|
|
||||||
import { HashRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
<HashRouter>
|
|
||||||
<Routes>
|
|
||||||
<Route path="/product/:slug" element={<Product />} />
|
|
||||||
</Routes>
|
|
||||||
</HashRouter>
|
|
||||||
```
|
|
||||||
|
|
||||||
**That's it!** React Router's `Link` components automatically use hash URLs.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## URL Format
|
|
||||||
|
|
||||||
### Shop Page
|
|
||||||
```
|
|
||||||
https://woonoow.local/shop
|
|
||||||
https://woonoow.local/shop#/
|
|
||||||
https://woonoow.local/shop#/shop
|
|
||||||
```
|
|
||||||
|
|
||||||
All work! The SPA loads on `/shop` page.
|
|
||||||
|
|
||||||
### Product Pages
|
|
||||||
```
|
|
||||||
https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
https://woonoow.local/shop#/product/test-variable
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cart
|
|
||||||
```
|
|
||||||
https://woonoow.local/shop#/cart
|
|
||||||
```
|
|
||||||
|
|
||||||
### Checkout
|
|
||||||
```
|
|
||||||
https://woonoow.local/shop#/checkout
|
|
||||||
```
|
|
||||||
|
|
||||||
### My Account
|
|
||||||
```
|
|
||||||
https://woonoow.local/shop#/my-account
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### URL Structure
|
|
||||||
```
|
|
||||||
https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
↑ ↑
|
|
||||||
| └─ Client-side route (React Router)
|
|
||||||
└────── Server-side route (WordPress)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Request Flow
|
|
||||||
|
|
||||||
1. **Browser requests:** `https://woonoow.local/shop#/product/edukasi-anak`
|
|
||||||
2. **WordPress receives:** `https://woonoow.local/shop`
|
|
||||||
- The `#/product/edukasi-anak` part is NOT sent to server
|
|
||||||
3. **WordPress loads:** Shop page template with SPA
|
|
||||||
4. **React Router sees:** `#/product/edukasi-anak`
|
|
||||||
5. **React Router shows:** Product component
|
|
||||||
6. **Result:** Product page displays ✅
|
|
||||||
|
|
||||||
### Why This Works
|
|
||||||
|
|
||||||
**Hash fragments are client-side only:**
|
|
||||||
- Browsers don't send hash to server
|
|
||||||
- WordPress never sees `#/product/edukasi-anak`
|
|
||||||
- No conflicts with WordPress routes
|
|
||||||
- React Router handles everything after `#`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
|
|
||||||
### 1. Direct Access ✅
|
|
||||||
User types URL in browser:
|
|
||||||
```
|
|
||||||
https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
```
|
|
||||||
**Result:** Product page loads directly
|
|
||||||
|
|
||||||
### 2. Sharing Links ✅
|
|
||||||
User shares product link:
|
|
||||||
```
|
|
||||||
Copy: https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
Paste in chat/email
|
|
||||||
Click link
|
|
||||||
```
|
|
||||||
**Result:** Product page loads for recipient
|
|
||||||
|
|
||||||
### 3. Email Campaigns ✅
|
|
||||||
Admin sends promotional email:
|
|
||||||
```html
|
|
||||||
<a href="https://woonoow.local/shop#/product/special-offer">
|
|
||||||
Check out our special offer!
|
|
||||||
</a>
|
|
||||||
```
|
|
||||||
**Result:** Product page loads when clicked
|
|
||||||
|
|
||||||
### 4. Social Media ✅
|
|
||||||
Share on Facebook, Twitter, etc:
|
|
||||||
```
|
|
||||||
https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
```
|
|
||||||
**Result:** Product page loads when clicked
|
|
||||||
|
|
||||||
### 5. Bookmarks ✅
|
|
||||||
User bookmarks product page:
|
|
||||||
```
|
|
||||||
Bookmark: https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
```
|
|
||||||
**Result:** Product page loads when bookmark opened
|
|
||||||
|
|
||||||
### 6. QR Codes ✅
|
|
||||||
Generate QR code for product:
|
|
||||||
```
|
|
||||||
QR → https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
```
|
|
||||||
**Result:** Product page loads when scanned
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Comparison: BrowserRouter vs HashRouter
|
|
||||||
|
|
||||||
| Feature | BrowserRouter | HashRouter |
|
|
||||||
|---------|---------------|------------|
|
|
||||||
| **URL Format** | `/product/slug` | `#/product/slug` |
|
|
||||||
| **Clean URLs** | ✅ Yes | ❌ Has `#` |
|
|
||||||
| **SEO** | ✅ Better | ⚠️ Acceptable |
|
|
||||||
| **Direct Access** | ❌ Conflicts | ✅ Works |
|
|
||||||
| **WordPress Conflicts** | ❌ Many | ✅ None |
|
|
||||||
| **Sharing** | ❌ Unreliable | ✅ Reliable |
|
|
||||||
| **Email Links** | ❌ Breaks | ✅ Works |
|
|
||||||
| **Setup Complexity** | ❌ Complex | ✅ Simple |
|
|
||||||
| **Reliability** | ❌ Fragile | ✅ Solid |
|
|
||||||
|
|
||||||
**Winner:** HashRouter for Customer SPA ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## SEO Considerations
|
|
||||||
|
|
||||||
### Hash URLs and SEO
|
|
||||||
|
|
||||||
**Modern search engines handle hash URLs:**
|
|
||||||
- Google can crawl hash URLs
|
|
||||||
- Bing supports hash routing
|
|
||||||
- Social media platforms parse them
|
|
||||||
|
|
||||||
**Best practices:**
|
|
||||||
1. Use server-side rendering for SEO-critical pages
|
|
||||||
2. Add proper meta tags
|
|
||||||
3. Use canonical URLs
|
|
||||||
4. Submit sitemap with actual product URLs
|
|
||||||
|
|
||||||
### Our Approach
|
|
||||||
|
|
||||||
**For SEO:**
|
|
||||||
- WooCommerce product pages still exist
|
|
||||||
- Search engines index actual product URLs
|
|
||||||
- Canonical tags point to real products
|
|
||||||
|
|
||||||
**For Users:**
|
|
||||||
- SPA provides better UX
|
|
||||||
- Hash URLs work reliably
|
|
||||||
- No broken links
|
|
||||||
|
|
||||||
**Best of both worlds!** ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migration Notes
|
|
||||||
|
|
||||||
### Existing Links
|
|
||||||
|
|
||||||
If you already shared links with BrowserRouter format:
|
|
||||||
|
|
||||||
**Old format:**
|
|
||||||
```
|
|
||||||
https://woonoow.local/product/edukasi-anak
|
|
||||||
```
|
|
||||||
|
|
||||||
**New format:**
|
|
||||||
```
|
|
||||||
https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution:** Add redirect or keep both working:
|
|
||||||
```php
|
|
||||||
// In TemplateOverride.php
|
|
||||||
if (is_product()) {
|
|
||||||
// Redirect to hash URL
|
|
||||||
$product_slug = get_post_field('post_name', get_the_ID());
|
|
||||||
wp_redirect(home_url("/shop#/product/$product_slug"));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
### Test 1: Direct Access
|
|
||||||
1. Open new browser tab
|
|
||||||
2. Type: `https://woonoow.local/shop#/product/edukasi-anak`
|
|
||||||
3. Press Enter
|
|
||||||
4. **Expected:** Product page loads ✅
|
|
||||||
|
|
||||||
### Test 2: Navigation
|
|
||||||
1. Go to shop page
|
|
||||||
2. Click product
|
|
||||||
3. **Expected:** URL changes to `#/product/slug` ✅
|
|
||||||
4. **Expected:** Product page shows ✅
|
|
||||||
|
|
||||||
### Test 3: Refresh
|
|
||||||
1. On product page
|
|
||||||
2. Press F5
|
|
||||||
3. **Expected:** Page reloads, product still shows ✅
|
|
||||||
|
|
||||||
### Test 4: Bookmark
|
|
||||||
1. Bookmark product page
|
|
||||||
2. Close browser
|
|
||||||
3. Open bookmark
|
|
||||||
4. **Expected:** Product page loads ✅
|
|
||||||
|
|
||||||
### Test 5: Share Link
|
|
||||||
1. Copy product URL
|
|
||||||
2. Open in incognito window
|
|
||||||
3. **Expected:** Product page loads ✅
|
|
||||||
|
|
||||||
### Test 6: Back Button
|
|
||||||
1. Navigate: Shop → Product → Cart
|
|
||||||
2. Press back button
|
|
||||||
3. **Expected:** Goes back to product ✅
|
|
||||||
4. Press back again
|
|
||||||
5. **Expected:** Goes back to shop ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Advantages Over BrowserRouter
|
|
||||||
|
|
||||||
### 1. Zero WordPress Conflicts
|
|
||||||
- No canonical redirect issues
|
|
||||||
- No 404 problems
|
|
||||||
- No template override complexity
|
|
||||||
- No rewrite rule conflicts
|
|
||||||
|
|
||||||
### 2. Reliable Direct Access
|
|
||||||
- Always works
|
|
||||||
- No server configuration needed
|
|
||||||
- No .htaccess rules
|
|
||||||
- No WordPress query manipulation
|
|
||||||
|
|
||||||
### 3. Perfect for Sharing
|
|
||||||
- Links work everywhere
|
|
||||||
- Email campaigns reliable
|
|
||||||
- Social media compatible
|
|
||||||
- QR codes work
|
|
||||||
|
|
||||||
### 4. Simple Implementation
|
|
||||||
- One line change (BrowserRouter → HashRouter)
|
|
||||||
- No PHP changes needed
|
|
||||||
- No server configuration
|
|
||||||
- No complex debugging
|
|
||||||
|
|
||||||
### 5. Consistent with Admin SPA
|
|
||||||
- Same routing approach
|
|
||||||
- Proven to work
|
|
||||||
- Easy to understand
|
|
||||||
- Maintainable
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Real-World Examples
|
|
||||||
|
|
||||||
### Example 1: Product Promotion
|
|
||||||
```
|
|
||||||
Email subject: Special Offer on Edukasi Anak!
|
|
||||||
Email body: Click here to view:
|
|
||||||
https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
```
|
|
||||||
✅ Works perfectly
|
|
||||||
|
|
||||||
### Example 2: Social Media Post
|
|
||||||
```
|
|
||||||
Facebook post:
|
|
||||||
"Check out our new product! 🎉
|
|
||||||
https://woonoow.local/shop#/product/edukasi-anak"
|
|
||||||
```
|
|
||||||
✅ Link works for all followers
|
|
||||||
|
|
||||||
### Example 3: Customer Support
|
|
||||||
```
|
|
||||||
Support: "Please check this product page:"
|
|
||||||
https://woonoow.local/shop#/product/edukasi-anak
|
|
||||||
|
|
||||||
Customer: *clicks link*
|
|
||||||
```
|
|
||||||
✅ Page loads immediately
|
|
||||||
|
|
||||||
### Example 4: Affiliate Marketing
|
|
||||||
```
|
|
||||||
Affiliate link:
|
|
||||||
https://woonoow.local/shop#/product/edukasi-anak?ref=affiliate123
|
|
||||||
```
|
|
||||||
✅ Works with query parameters
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
**Problem:** BrowserRouter conflicts with WordPress routes
|
|
||||||
|
|
||||||
**Solution:** Use HashRouter like Admin SPA
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ Direct access works
|
|
||||||
- ✅ Sharing works
|
|
||||||
- ✅ Email campaigns work
|
|
||||||
- ✅ No WordPress conflicts
|
|
||||||
- ✅ Simple and reliable
|
|
||||||
|
|
||||||
**Trade-off:**
|
|
||||||
- URLs have `#` in them
|
|
||||||
- Acceptable for SPA use case
|
|
||||||
|
|
||||||
**Result:** Reliable, shareable product links! 🎉
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
1. **customer-spa/src/App.tsx**
|
|
||||||
- Changed: `BrowserRouter` → `HashRouter`
|
|
||||||
- That's it!
|
|
||||||
|
|
||||||
## URL Examples
|
|
||||||
|
|
||||||
**Shop:**
|
|
||||||
- `https://woonoow.local/shop`
|
|
||||||
- `https://woonoow.local/shop#/`
|
|
||||||
|
|
||||||
**Products:**
|
|
||||||
- `https://woonoow.local/shop#/product/edukasi-anak`
|
|
||||||
- `https://woonoow.local/shop#/product/test-variable`
|
|
||||||
|
|
||||||
**Cart:**
|
|
||||||
- `https://woonoow.local/shop#/cart`
|
|
||||||
|
|
||||||
**Checkout:**
|
|
||||||
- `https://woonoow.local/shop#/checkout`
|
|
||||||
|
|
||||||
**Account:**
|
|
||||||
- `https://woonoow.local/shop#/my-account`
|
|
||||||
|
|
||||||
All work perfectly! ✅
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
# WooNooW Customer SPA - Implementation Status
|
|
||||||
|
|
||||||
## ✅ Phase 1-3: COMPLETE
|
|
||||||
|
|
||||||
### 1. Core Infrastructure
|
|
||||||
- ✅ Template override system
|
|
||||||
- ✅ SPA mount points
|
|
||||||
- ✅ React Router setup
|
|
||||||
- ✅ TanStack Query integration
|
|
||||||
|
|
||||||
### 2. Settings System
|
|
||||||
- ✅ REST API endpoints (`/wp-json/woonoow/v1/settings/customer-spa`)
|
|
||||||
- ✅ Settings Controller with validation
|
|
||||||
- ✅ Admin SPA Settings UI (`Settings > Customer SPA`)
|
|
||||||
- ✅ Three modes: Disabled, Full SPA, Checkout-Only
|
|
||||||
- ✅ Four layouts: Classic, Modern, Boutique, Launch
|
|
||||||
- ✅ Color customization (primary, secondary, accent)
|
|
||||||
- ✅ Typography presets (4 options)
|
|
||||||
- ✅ Checkout pages configuration
|
|
||||||
|
|
||||||
### 3. Theme System
|
|
||||||
- ✅ ThemeProvider context
|
|
||||||
- ✅ Design token system (CSS variables)
|
|
||||||
- ✅ Google Fonts loading
|
|
||||||
- ✅ Layout detection hooks
|
|
||||||
- ✅ Mode detection hooks
|
|
||||||
- ✅ Dark mode support
|
|
||||||
|
|
||||||
### 4. Layout Components
|
|
||||||
- ✅ **Classic Layout** - Traditional with sidebar, 4-column footer
|
|
||||||
- ✅ **Modern Layout** - Centered logo, minimalist
|
|
||||||
- ✅ **Boutique Layout** - Luxury serif fonts, elegant
|
|
||||||
- ✅ **Launch Layout** - Minimal checkout flow
|
|
||||||
|
|
||||||
### 5. Currency System
|
|
||||||
- ✅ WooCommerce currency integration
|
|
||||||
- ✅ Respects decimal places
|
|
||||||
- ✅ Thousand/decimal separators
|
|
||||||
- ✅ Symbol positioning
|
|
||||||
- ✅ Helper functions (`formatPrice`, `formatDiscount`, etc.)
|
|
||||||
|
|
||||||
### 6. Product Components
|
|
||||||
- ✅ **ProductCard** with 4 layout variants
|
|
||||||
- ✅ Sale badges with discount percentage
|
|
||||||
- ✅ Stock status handling
|
|
||||||
- ✅ Add to cart functionality
|
|
||||||
- ✅ Responsive images with hover effects
|
|
||||||
|
|
||||||
### 7. Shop Page
|
|
||||||
- ✅ Product grid with ProductCard
|
|
||||||
- ✅ Search functionality
|
|
||||||
- ✅ Category filtering
|
|
||||||
- ✅ Pagination
|
|
||||||
- ✅ Loading states
|
|
||||||
- ✅ Empty states
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 What's Working Now
|
|
||||||
|
|
||||||
### Admin Side:
|
|
||||||
1. Navigate to **WooNooW > Settings > Customer SPA**
|
|
||||||
2. Configure:
|
|
||||||
- Mode (Disabled/Full/Checkout-Only)
|
|
||||||
- Layout (Classic/Modern/Boutique/Launch)
|
|
||||||
- Colors (Primary, Secondary, Accent)
|
|
||||||
- Typography (4 presets)
|
|
||||||
- Checkout pages (for Checkout-Only mode)
|
|
||||||
3. Settings save via REST API
|
|
||||||
4. Settings load on page refresh
|
|
||||||
|
|
||||||
### Frontend Side:
|
|
||||||
1. Visit WooCommerce shop page
|
|
||||||
2. See:
|
|
||||||
- Selected layout (header + footer)
|
|
||||||
- Custom brand colors applied
|
|
||||||
- Products with layout-specific cards
|
|
||||||
- Proper currency formatting
|
|
||||||
- Sale badges and discounts
|
|
||||||
- Search and filters
|
|
||||||
- Pagination
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Layout Showcase
|
|
||||||
|
|
||||||
### Classic Layout
|
|
||||||
- Traditional ecommerce design
|
|
||||||
- Sidebar navigation
|
|
||||||
- Border cards with shadow on hover
|
|
||||||
- 4-column footer
|
|
||||||
- **Best for:** B2B, traditional retail
|
|
||||||
|
|
||||||
### Modern Layout
|
|
||||||
- Minimalist, clean design
|
|
||||||
- Centered logo and navigation
|
|
||||||
- Hover overlay with CTA
|
|
||||||
- Simple centered footer
|
|
||||||
- **Best for:** Fashion, lifestyle brands
|
|
||||||
|
|
||||||
### Boutique Layout
|
|
||||||
- Luxury, elegant design
|
|
||||||
- Serif fonts throughout
|
|
||||||
- 3:4 aspect ratio images
|
|
||||||
- Uppercase tracking
|
|
||||||
- **Best for:** High-end fashion, luxury goods
|
|
||||||
|
|
||||||
### Launch Layout
|
|
||||||
- Single product funnel
|
|
||||||
- Minimal header (logo only)
|
|
||||||
- No footer distractions
|
|
||||||
- Prominent "Buy Now" buttons
|
|
||||||
- **Best for:** Digital products, courses, launches
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Testing Guide
|
|
||||||
|
|
||||||
### 1. Enable Customer SPA
|
|
||||||
```
|
|
||||||
Admin > WooNooW > Settings > Customer SPA
|
|
||||||
- Select "Full SPA" mode
|
|
||||||
- Choose a layout
|
|
||||||
- Pick colors
|
|
||||||
- Save
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Test Shop Page
|
|
||||||
```
|
|
||||||
Visit: /shop or your WooCommerce shop page
|
|
||||||
Expected:
|
|
||||||
- Layout header/footer
|
|
||||||
- Product grid with selected layout style
|
|
||||||
- Currency formatted correctly
|
|
||||||
- Search works
|
|
||||||
- Category filter works
|
|
||||||
- Pagination works
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Test Different Layouts
|
|
||||||
```
|
|
||||||
Switch between layouts in settings
|
|
||||||
Refresh shop page
|
|
||||||
See different card styles and layouts
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Test Checkout-Only Mode
|
|
||||||
```
|
|
||||||
- Select "Checkout Only" mode
|
|
||||||
- Check which pages to override
|
|
||||||
- Visit shop page (should use theme)
|
|
||||||
- Visit checkout page (should use SPA)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Next Steps
|
|
||||||
|
|
||||||
### Phase 4: Homepage Builder (Pending)
|
|
||||||
- Hero section component
|
|
||||||
- Featured products section
|
|
||||||
- Categories section
|
|
||||||
- Testimonials section
|
|
||||||
- Drag-and-drop ordering
|
|
||||||
- Section configuration
|
|
||||||
|
|
||||||
### Phase 5: Navigation Integration (Pending)
|
|
||||||
- Fetch WordPress menus via API
|
|
||||||
- Render in SPA layouts
|
|
||||||
- Mobile menu
|
|
||||||
- Cart icon with count
|
|
||||||
- User account dropdown
|
|
||||||
|
|
||||||
### Phase 6: Complete Pages (In Progress)
|
|
||||||
- ✅ Shop page
|
|
||||||
- ⏳ Product detail page
|
|
||||||
- ⏳ Cart page
|
|
||||||
- ⏳ Checkout page
|
|
||||||
- ⏳ Thank you page
|
|
||||||
- ⏳ My Account pages
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🐛 Known Issues
|
|
||||||
|
|
||||||
### TypeScript Warnings
|
|
||||||
- API response types not fully defined
|
|
||||||
- Won't prevent app from running
|
|
||||||
- Can be fixed with proper type definitions
|
|
||||||
|
|
||||||
### To Fix Later:
|
|
||||||
- Add proper TypeScript interfaces for API responses
|
|
||||||
- Add loading states for all components
|
|
||||||
- Add error boundaries
|
|
||||||
- Add analytics tracking
|
|
||||||
- Add SEO meta tags
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📁 File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
customer-spa/
|
|
||||||
├── src/
|
|
||||||
│ ├── App.tsx # Main app with ThemeProvider
|
|
||||||
│ ├── main.tsx # Entry point
|
|
||||||
│ ├── contexts/
|
|
||||||
│ │ └── ThemeContext.tsx # Theme configuration & hooks
|
|
||||||
│ ├── layouts/
|
|
||||||
│ │ └── BaseLayout.tsx # 4 layout components
|
|
||||||
│ ├── components/
|
|
||||||
│ │ └── ProductCard.tsx # Layout-aware product card
|
|
||||||
│ ├── lib/
|
|
||||||
│ │ └── currency.ts # WooCommerce currency utilities
|
|
||||||
│ ├── pages/
|
|
||||||
│ │ └── Shop/
|
|
||||||
│ │ └── index.tsx # Shop page with ProductCard
|
|
||||||
│ └── styles/
|
|
||||||
│ └── theme.css # Design tokens
|
|
||||||
|
|
||||||
includes/
|
|
||||||
├── Api/Controllers/
|
|
||||||
│ └── SettingsController.php # Settings REST API
|
|
||||||
├── Frontend/
|
|
||||||
│ ├── Assets.php # Pass settings to frontend
|
|
||||||
│ └── TemplateOverride.php # SPA template override
|
|
||||||
└── Compat/
|
|
||||||
└── NavigationRegistry.php # Admin menu structure
|
|
||||||
|
|
||||||
admin-spa/
|
|
||||||
└── src/routes/Settings/
|
|
||||||
└── CustomerSPA.tsx # Settings UI
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Ready for Production?
|
|
||||||
|
|
||||||
### ✅ Ready:
|
|
||||||
- Settings system
|
|
||||||
- Theme system
|
|
||||||
- Layout system
|
|
||||||
- Currency formatting
|
|
||||||
- Shop page
|
|
||||||
- Product cards
|
|
||||||
|
|
||||||
### ⏳ Needs Work:
|
|
||||||
- Complete all pages
|
|
||||||
- Add navigation
|
|
||||||
- Add homepage builder
|
|
||||||
- Add proper error handling
|
|
||||||
- Add loading states
|
|
||||||
- Add analytics
|
|
||||||
- Add SEO
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 Support
|
|
||||||
|
|
||||||
For issues or questions:
|
|
||||||
1. Check this document
|
|
||||||
2. Check `CUSTOMER_SPA_ARCHITECTURE.md`
|
|
||||||
3. Check `CUSTOMER_SPA_SETTINGS.md`
|
|
||||||
4. Check `CUSTOMER_SPA_THEME_SYSTEM.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Last Updated:** Phase 3 Complete
|
|
||||||
**Status:** Shop page functional, ready for testing
|
|
||||||
**Next:** Complete remaining pages (Product, Cart, Checkout, Account)
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
# Module System Integration Summary
|
|
||||||
|
|
||||||
**Date**: December 26, 2025
|
|
||||||
**Status**: ✅ Complete
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
All module-related features have been wired to check module status before displaying. When a module is disabled, its features are completely hidden from both admin and customer interfaces.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Integrated Features
|
|
||||||
|
|
||||||
### 1. Newsletter Module (`newsletter`)
|
|
||||||
|
|
||||||
#### Admin SPA
|
|
||||||
**File**: `admin-spa/src/routes/Marketing/Newsletter.tsx`
|
|
||||||
- ✅ Added `useModules()` hook
|
|
||||||
- ✅ Shows disabled state UI when module is off
|
|
||||||
- ✅ Provides link to Module Settings
|
|
||||||
- ✅ Blocks access to newsletter subscribers page
|
|
||||||
|
|
||||||
**Navigation**:
|
|
||||||
- ✅ Newsletter menu item hidden when module disabled (NavigationRegistry.php)
|
|
||||||
|
|
||||||
**Result**: When newsletter module is OFF:
|
|
||||||
- ❌ No "Newsletter" menu item in Marketing
|
|
||||||
- ❌ Newsletter page shows disabled message
|
|
||||||
- ✅ User redirected to enable module in settings
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Wishlist Module (`wishlist`)
|
|
||||||
|
|
||||||
#### Customer SPA
|
|
||||||
|
|
||||||
**File**: `customer-spa/src/pages/Account/Wishlist.tsx`
|
|
||||||
- ✅ Added `useModules()` hook
|
|
||||||
- ✅ Shows disabled state UI when module is off
|
|
||||||
- ✅ Provides "Continue Shopping" button
|
|
||||||
- ✅ Blocks access to wishlist page
|
|
||||||
|
|
||||||
**File**: `customer-spa/src/pages/Product/index.tsx`
|
|
||||||
- ✅ Added `useModules()` hook
|
|
||||||
- ✅ Wishlist button hidden when module disabled
|
|
||||||
- ✅ Combined with settings check (`wishlistEnabled`)
|
|
||||||
|
|
||||||
**File**: `customer-spa/src/components/ProductCard.tsx`
|
|
||||||
- ✅ Added `useModules()` hook
|
|
||||||
- ✅ Created `showWishlist` variable combining module + settings
|
|
||||||
- ✅ All 4 layout variants updated (Classic, Modern, Boutique, Launch)
|
|
||||||
- ✅ Heart icon hidden when module disabled
|
|
||||||
|
|
||||||
**File**: `customer-spa/src/pages/Account/components/AccountLayout.tsx`
|
|
||||||
- ✅ Added `useModules()` hook
|
|
||||||
- ✅ Wishlist menu item filtered out when module disabled
|
|
||||||
- ✅ Combined with settings check
|
|
||||||
|
|
||||||
#### Backend API
|
|
||||||
**File**: `includes/Frontend/WishlistController.php`
|
|
||||||
- ✅ All endpoints check module status
|
|
||||||
- ✅ Returns 403 error when module disabled
|
|
||||||
- ✅ Endpoints: get, add, remove, clear
|
|
||||||
|
|
||||||
**Result**: When wishlist module is OFF:
|
|
||||||
- ❌ No heart icon on product cards (all layouts)
|
|
||||||
- ❌ No wishlist button on product pages
|
|
||||||
- ❌ No "Wishlist" menu item in My Account
|
|
||||||
- ❌ Wishlist page shows disabled message
|
|
||||||
- ❌ All wishlist API endpoints return 403
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Affiliate Module (`affiliate`)
|
|
||||||
|
|
||||||
**Status**: Not yet implemented (module registered, no features built)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Subscription Module (`subscription`)
|
|
||||||
|
|
||||||
**Status**: Not yet implemented (module registered, no features built)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. Licensing Module (`licensing`)
|
|
||||||
|
|
||||||
**Status**: Not yet implemented (module registered, no features built)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Pattern
|
|
||||||
|
|
||||||
### Frontend Check (React)
|
|
||||||
```tsx
|
|
||||||
import { useModules } from '@/hooks/useModules';
|
|
||||||
|
|
||||||
export default function MyComponent() {
|
|
||||||
const { isEnabled } = useModules();
|
|
||||||
|
|
||||||
if (!isEnabled('my_module')) {
|
|
||||||
return <DisabledStateUI />;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal component render
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backend Check (PHP)
|
|
||||||
```php
|
|
||||||
use WooNooW\Core\ModuleRegistry;
|
|
||||||
|
|
||||||
public function my_endpoint($request) {
|
|
||||||
if (!ModuleRegistry::is_enabled('my_module')) {
|
|
||||||
return new WP_Error('module_disabled', 'Module is disabled', ['status' => 403]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process request
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Navigation Check (PHP)
|
|
||||||
```php
|
|
||||||
// In NavigationRegistry.php
|
|
||||||
if (ModuleRegistry::is_enabled('my_module')) {
|
|
||||||
$children[] = ['label' => 'My Feature', 'path' => '/my-feature'];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
### Admin SPA (1 file)
|
|
||||||
1. `admin-spa/src/routes/Marketing/Newsletter.tsx` - Newsletter page module check
|
|
||||||
|
|
||||||
### Customer SPA (4 files)
|
|
||||||
1. `customer-spa/src/pages/Account/Wishlist.tsx` - Wishlist page module check
|
|
||||||
2. `customer-spa/src/pages/Product/index.tsx` - Product page wishlist button
|
|
||||||
3. `customer-spa/src/components/ProductCard.tsx` - Product card wishlist hearts
|
|
||||||
4. `customer-spa/src/pages/Account/components/AccountLayout.tsx` - Account menu filtering
|
|
||||||
|
|
||||||
### Backend (2 files)
|
|
||||||
1. `includes/Frontend/WishlistController.php` - API endpoint protection
|
|
||||||
2. `includes/Compat/NavigationRegistry.php` - Navigation filtering
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
### Newsletter Module
|
|
||||||
- [ ] Toggle newsletter OFF in Settings > Modules
|
|
||||||
- [ ] Verify "Newsletter" menu item disappears from Marketing
|
|
||||||
- [ ] Try accessing `/marketing/newsletter` directly
|
|
||||||
- [ ] Expected: Shows disabled message with link to settings
|
|
||||||
- [ ] Toggle newsletter ON
|
|
||||||
- [ ] Verify menu item reappears
|
|
||||||
|
|
||||||
### Wishlist Module
|
|
||||||
- [ ] Toggle wishlist OFF in Settings > Modules
|
|
||||||
- [ ] Visit shop page
|
|
||||||
- [ ] Expected: No heart icons on product cards
|
|
||||||
- [ ] Visit product page
|
|
||||||
- [ ] Expected: No wishlist button
|
|
||||||
- [ ] Visit My Account
|
|
||||||
- [ ] Expected: No "Wishlist" menu item
|
|
||||||
- [ ] Try accessing `/my-account/wishlist` directly
|
|
||||||
- [ ] Expected: Shows disabled message
|
|
||||||
- [ ] Try API call: `GET /woonoow/v1/account/wishlist`
|
|
||||||
- [ ] Expected: 403 error "Wishlist module is disabled"
|
|
||||||
- [ ] Toggle wishlist ON
|
|
||||||
- [ ] Verify all features reappear
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Impact
|
|
||||||
|
|
||||||
### Caching
|
|
||||||
- Module status cached for 5 minutes via React Query
|
|
||||||
- Navigation tree rebuilt automatically when modules toggled
|
|
||||||
- Minimal overhead (~1 DB query per page load)
|
|
||||||
|
|
||||||
### Bundle Size
|
|
||||||
- No impact - features still in bundle, just conditionally rendered
|
|
||||||
- Future: Could implement code splitting for disabled modules
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
### Phase 2
|
|
||||||
1. **Code Splitting**: Lazy load module components when enabled
|
|
||||||
2. **Module Dependencies**: Prevent disabling if other modules depend on it
|
|
||||||
3. **Bulk Operations**: Enable/disable multiple modules at once
|
|
||||||
4. **Module Analytics**: Track which modules are most used
|
|
||||||
|
|
||||||
### Phase 3
|
|
||||||
1. **Third-party Modules**: Allow installing external modules
|
|
||||||
2. **Module Marketplace**: Browse and install community modules
|
|
||||||
3. **Module Updates**: Version management for modules
|
|
||||||
4. **Module Settings**: Per-module configuration pages
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Developer Notes
|
|
||||||
|
|
||||||
### Adding Module Checks to New Features
|
|
||||||
|
|
||||||
1. **Import the hook**:
|
|
||||||
```tsx
|
|
||||||
import { useModules } from '@/hooks/useModules';
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Check module status**:
|
|
||||||
```tsx
|
|
||||||
const { isEnabled } = useModules();
|
|
||||||
if (!isEnabled('module_id')) return null;
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Backend protection**:
|
|
||||||
```php
|
|
||||||
if (!ModuleRegistry::is_enabled('module_id')) {
|
|
||||||
return new WP_Error('module_disabled', 'Module disabled', ['status' => 403]);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Navigation filtering**:
|
|
||||||
```php
|
|
||||||
if (ModuleRegistry::is_enabled('module_id')) {
|
|
||||||
$children[] = ['label' => 'Feature', 'path' => '/feature'];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Pitfalls
|
|
||||||
|
|
||||||
1. **Don't forget backend checks** - Frontend checks can be bypassed
|
|
||||||
2. **Check both module + settings** - Some features have dual toggles
|
|
||||||
3. **Update navigation version** - Increment when adding/removing menu items
|
|
||||||
4. **Clear cache on toggle** - ModuleRegistry auto-clears navigation cache
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
✅ **Newsletter Module**: Fully integrated (admin page + navigation)
|
|
||||||
✅ **Wishlist Module**: Fully integrated (frontend UI + backend API + navigation)
|
|
||||||
⏳ **Affiliate Module**: Registered, awaiting implementation
|
|
||||||
⏳ **Subscription Module**: Registered, awaiting implementation
|
|
||||||
⏳ **Licensing Module**: Registered, awaiting implementation
|
|
||||||
|
|
||||||
**Total Integration Points**: 7 files modified, 11 integration points added
|
|
||||||
|
|
||||||
**Next Steps**: Implement Newsletter Campaigns feature (as per FEATURE_ROADMAP.md)
|
|
||||||
@@ -1,379 +0,0 @@
|
|||||||
# Phase 2, 3, 4 Implementation Summary
|
|
||||||
|
|
||||||
**Date**: December 26, 2025
|
|
||||||
**Status**: ✅ Complete
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Successfully implemented the complete addon-module integration system with schema-based forms, custom React components, and a working example addon.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 2: Schema-Based Form System ✅
|
|
||||||
|
|
||||||
### Backend Components
|
|
||||||
|
|
||||||
#### 1. **ModuleSettingsController.php** (NEW)
|
|
||||||
- `GET /modules/{id}/settings` - Fetch module settings
|
|
||||||
- `POST /modules/{id}/settings` - Save module settings
|
|
||||||
- `GET /modules/{id}/schema` - Fetch settings schema
|
|
||||||
- Automatic validation against schema
|
|
||||||
- Action hooks: `woonoow/module_settings_updated/{module_id}`
|
|
||||||
- Storage pattern: `woonoow_module_{module_id}_settings`
|
|
||||||
|
|
||||||
#### 2. **NewsletterSettings.php** (NEW)
|
|
||||||
- Example implementation with 8 fields
|
|
||||||
- Demonstrates all field types
|
|
||||||
- Shows dynamic options (WordPress pages)
|
|
||||||
- Registers schema via `woonoow/module_settings_schema` filter
|
|
||||||
|
|
||||||
### Frontend Components
|
|
||||||
|
|
||||||
#### 1. **SchemaField.tsx** (NEW)
|
|
||||||
- Supports 8 field types: text, textarea, email, url, number, toggle, checkbox, select
|
|
||||||
- Automatic validation (required, min/max)
|
|
||||||
- Error display per field
|
|
||||||
- Description and placeholder support
|
|
||||||
|
|
||||||
#### 2. **SchemaForm.tsx** (NEW)
|
|
||||||
- Renders complete form from schema object
|
|
||||||
- Manages form state
|
|
||||||
- Submit handling with loading state
|
|
||||||
- Error display integration
|
|
||||||
|
|
||||||
#### 3. **ModuleSettings.tsx** (NEW)
|
|
||||||
- Generic settings page at `/settings/modules/:moduleId`
|
|
||||||
- Auto-detects schema vs custom component
|
|
||||||
- Fetches schema from API
|
|
||||||
- Uses `useModuleSettings` hook
|
|
||||||
- "Back to Modules" navigation
|
|
||||||
|
|
||||||
#### 4. **useModuleSettings.ts** (NEW)
|
|
||||||
- React hook for settings management
|
|
||||||
- Auto-invalidates queries on save
|
|
||||||
- Toast notifications
|
|
||||||
- `saveSetting(key, value)` helper
|
|
||||||
|
|
||||||
### Features Delivered
|
|
||||||
|
|
||||||
✅ No-code settings forms via schema
|
|
||||||
✅ Automatic validation
|
|
||||||
✅ Persistent storage
|
|
||||||
✅ Newsletter example with 8 fields
|
|
||||||
✅ Gear icon shows on modules with settings
|
|
||||||
✅ Settings page auto-routes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 3: Advanced Features ✅
|
|
||||||
|
|
||||||
### Window API Exposure
|
|
||||||
|
|
||||||
#### **windowAPI.ts** (NEW)
|
|
||||||
Exposes comprehensive API to addon developers via `window.WooNooW`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
window.WooNooW = {
|
|
||||||
React,
|
|
||||||
ReactDOM,
|
|
||||||
hooks: {
|
|
||||||
useQuery, useMutation, useQueryClient,
|
|
||||||
useModules, useModuleSettings
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
Button, Input, Label, Textarea, Switch, Select,
|
|
||||||
Checkbox, Badge, Card, SettingsLayout, SettingsCard,
|
|
||||||
SchemaForm, SchemaField
|
|
||||||
},
|
|
||||||
icons: {
|
|
||||||
Settings, Save, Trash2, Edit, Plus, X, Check,
|
|
||||||
AlertCircle, Info, Loader2, Chevrons...
|
|
||||||
},
|
|
||||||
utils: {
|
|
||||||
api, toast, __
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits**:
|
|
||||||
- Addons don't bundle React (use ours)
|
|
||||||
- Access to all UI components
|
|
||||||
- Consistent styling automatically
|
|
||||||
- Type-safe with TypeScript definitions
|
|
||||||
|
|
||||||
### Dynamic Component Loader
|
|
||||||
|
|
||||||
#### **DynamicComponentLoader.tsx** (NEW)
|
|
||||||
- Loads external React components from addon URLs
|
|
||||||
- Script injection with error handling
|
|
||||||
- Loading and error states
|
|
||||||
- Global namespace management per module
|
|
||||||
|
|
||||||
**Usage**:
|
|
||||||
```tsx
|
|
||||||
<DynamicComponentLoader
|
|
||||||
componentUrl="https://example.com/addon.js"
|
|
||||||
moduleId="my-addon"
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
### TypeScript Definitions
|
|
||||||
|
|
||||||
#### **types/woonoow-addon.d.ts** (NEW)
|
|
||||||
- Complete type definitions for `window.WooNooW`
|
|
||||||
- Field schema types
|
|
||||||
- Module registration types
|
|
||||||
- Settings schema types
|
|
||||||
- Enables IntelliSense for addon developers
|
|
||||||
|
|
||||||
### Integration
|
|
||||||
|
|
||||||
- Window API initialized in `App.tsx` on mount
|
|
||||||
- `ModuleSettings.tsx` uses `DynamicComponentLoader` for custom components
|
|
||||||
- Seamless fallback to schema-based forms
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 4: Production Polish ✅
|
|
||||||
|
|
||||||
### Biteship Example Addon
|
|
||||||
|
|
||||||
Complete working example demonstrating both approaches:
|
|
||||||
|
|
||||||
#### **examples/biteship-addon/** (NEW)
|
|
||||||
|
|
||||||
**Files**:
|
|
||||||
- `biteship-addon.php` - Main plugin file
|
|
||||||
- `src/Settings.jsx` - Custom React component
|
|
||||||
- `package.json` - Build configuration
|
|
||||||
- `README.md` - Complete documentation
|
|
||||||
|
|
||||||
**Features Demonstrated**:
|
|
||||||
1. Module registration with metadata
|
|
||||||
2. Schema-based settings (Option A)
|
|
||||||
3. Custom React component (Option B)
|
|
||||||
4. Settings persistence
|
|
||||||
5. Module enable/disable integration
|
|
||||||
6. Shipping rate calculation hook
|
|
||||||
7. Settings change reactions
|
|
||||||
8. Test connection button
|
|
||||||
9. Real-world UI patterns
|
|
||||||
|
|
||||||
**Both Approaches Shown**:
|
|
||||||
- **Schema**: 8 fields, no React needed, auto-generated form
|
|
||||||
- **Custom**: Full React component using `window.WooNooW` API
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
Comprehensive README includes:
|
|
||||||
- Installation instructions
|
|
||||||
- File structure
|
|
||||||
- API usage examples
|
|
||||||
- Build configuration
|
|
||||||
- Settings schema reference
|
|
||||||
- Module registration reference
|
|
||||||
- Testing guide
|
|
||||||
- Next steps for real implementation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Bug Fixes
|
|
||||||
|
|
||||||
### Footer Newsletter Form
|
|
||||||
**Problem**: Form not showing despite module enabled
|
|
||||||
**Cause**: Redundant module checks (component + layout)
|
|
||||||
**Solution**: Removed check from `NewsletterForm.tsx`, kept layout-level filtering
|
|
||||||
|
|
||||||
**Files Modified**:
|
|
||||||
- `customer-spa/src/layouts/BaseLayout.tsx` - Added section filtering
|
|
||||||
- `customer-spa/src/components/NewsletterForm.tsx` - Removed redundant check
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Created/Modified
|
|
||||||
|
|
||||||
### New Files (15)
|
|
||||||
|
|
||||||
**Backend**:
|
|
||||||
1. `includes/Api/ModuleSettingsController.php` - Settings API
|
|
||||||
2. `includes/Modules/NewsletterSettings.php` - Example schema
|
|
||||||
|
|
||||||
**Frontend**:
|
|
||||||
3. `admin-spa/src/components/forms/SchemaField.tsx` - Field renderer
|
|
||||||
4. `admin-spa/src/components/forms/SchemaForm.tsx` - Form renderer
|
|
||||||
5. `admin-spa/src/routes/Settings/ModuleSettings.tsx` - Settings page
|
|
||||||
6. `admin-spa/src/hooks/useModuleSettings.ts` - Settings hook
|
|
||||||
7. `admin-spa/src/lib/windowAPI.ts` - Window API exposure
|
|
||||||
8. `admin-spa/src/components/DynamicComponentLoader.tsx` - Component loader
|
|
||||||
|
|
||||||
**Types**:
|
|
||||||
9. `types/woonoow-addon.d.ts` - TypeScript definitions
|
|
||||||
|
|
||||||
**Example Addon**:
|
|
||||||
10. `examples/biteship-addon/biteship-addon.php` - Main file
|
|
||||||
11. `examples/biteship-addon/src/Settings.jsx` - React component
|
|
||||||
12. `examples/biteship-addon/package.json` - Build config
|
|
||||||
13. `examples/biteship-addon/README.md` - Documentation
|
|
||||||
|
|
||||||
**Documentation**:
|
|
||||||
14. `PHASE_2_3_4_SUMMARY.md` - This file
|
|
||||||
|
|
||||||
### Modified Files (6)
|
|
||||||
|
|
||||||
1. `admin-spa/src/App.tsx` - Added Window API initialization, ModuleSettings route
|
|
||||||
2. `includes/Api/Routes.php` - Registered ModuleSettingsController
|
|
||||||
3. `includes/Core/ModuleRegistry.php` - Added `has_settings: true` to newsletter
|
|
||||||
4. `woonoow.php` - Initialize NewsletterSettings
|
|
||||||
5. `customer-spa/src/layouts/BaseLayout.tsx` - Newsletter section filtering
|
|
||||||
6. `customer-spa/src/components/NewsletterForm.tsx` - Removed redundant check
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Endpoints Added
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /woonoow/v1/modules/{module_id}/settings
|
|
||||||
POST /woonoow/v1/modules/{module_id}/settings
|
|
||||||
GET /woonoow/v1/modules/{module_id}/schema
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## For Addon Developers
|
|
||||||
|
|
||||||
### Quick Start (Schema-Based)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// 1. Register addon
|
|
||||||
add_filter('woonoow/addon_registry', function($addons) {
|
|
||||||
$addons['my-addon'] = [
|
|
||||||
'name' => 'My Addon',
|
|
||||||
'category' => 'shipping',
|
|
||||||
'has_settings' => true,
|
|
||||||
];
|
|
||||||
return $addons;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Register schema
|
|
||||||
add_filter('woonoow/module_settings_schema', function($schemas) {
|
|
||||||
$schemas['my-addon'] = [
|
|
||||||
'api_key' => [
|
|
||||||
'type' => 'text',
|
|
||||||
'label' => 'API Key',
|
|
||||||
'required' => true,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
return $schemas;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. Use settings
|
|
||||||
$settings = get_option('woonoow_module_my-addon_settings');
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result**: Automatic settings page with form, validation, and persistence!
|
|
||||||
|
|
||||||
### Quick Start (Custom React)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Use window.WooNooW API
|
|
||||||
const { React, hooks, components } = window.WooNooW;
|
|
||||||
const { useModuleSettings } = hooks;
|
|
||||||
const { SettingsLayout, Button, Input } = components;
|
|
||||||
|
|
||||||
function MySettings() {
|
|
||||||
const { settings, updateSettings } = useModuleSettings('my-addon');
|
|
||||||
|
|
||||||
return React.createElement(SettingsLayout, { title: 'My Settings' },
|
|
||||||
React.createElement(Input, {
|
|
||||||
value: settings?.api_key || '',
|
|
||||||
onChange: (e) => updateSettings.mutate({ api_key: e.target.value })
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export to global
|
|
||||||
window.WooNooWAddon_my_addon = MySettings;
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
### Phase 2 ✅
|
|
||||||
- [x] Newsletter module shows gear icon
|
|
||||||
- [x] Settings page loads at `/settings/modules/newsletter`
|
|
||||||
- [x] Form renders with 8 fields
|
|
||||||
- [x] Settings save correctly
|
|
||||||
- [x] Settings persist on refresh
|
|
||||||
- [x] Validation works (required fields)
|
|
||||||
- [x] Select dropdown shows WordPress pages
|
|
||||||
|
|
||||||
### Phase 3 ✅
|
|
||||||
- [x] `window.WooNooW` API available in console
|
|
||||||
- [x] All components accessible
|
|
||||||
- [x] All hooks accessible
|
|
||||||
- [x] Dynamic component loader works
|
|
||||||
|
|
||||||
### Phase 4 ✅
|
|
||||||
- [x] Biteship addon structure complete
|
|
||||||
- [x] Both schema and custom approaches documented
|
|
||||||
- [x] Example component uses Window API
|
|
||||||
- [x] Build configuration provided
|
|
||||||
|
|
||||||
### Bug Fixes ✅
|
|
||||||
- [x] Footer newsletter form shows when module enabled
|
|
||||||
- [x] Footer newsletter section hides when module disabled
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Impact
|
|
||||||
|
|
||||||
- **Window API**: Initialized once on app mount (~5ms)
|
|
||||||
- **Dynamic Loader**: Lazy loads components only when needed
|
|
||||||
- **Schema Forms**: No runtime overhead, pure React
|
|
||||||
- **Settings API**: Cached by React Query
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Backward Compatibility
|
|
||||||
|
|
||||||
✅ **100% Backward Compatible**
|
|
||||||
- Existing modules work without changes
|
|
||||||
- Schema registration is optional
|
|
||||||
- Custom components are optional
|
|
||||||
- Addons without settings still function
|
|
||||||
- No breaking changes to existing APIs
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps (Optional)
|
|
||||||
|
|
||||||
### For Core
|
|
||||||
- [ ] Add conditional field visibility to schema
|
|
||||||
- [ ] Add field dependencies (show field B if field A is true)
|
|
||||||
- [ ] Add file upload field type
|
|
||||||
- [ ] Add color picker field type
|
|
||||||
- [ ] Add repeater field type
|
|
||||||
|
|
||||||
### For Addons
|
|
||||||
- [ ] Create more example addons
|
|
||||||
- [ ] Create addon starter template repository
|
|
||||||
- [ ] Create video tutorials
|
|
||||||
- [ ] Create addon marketplace
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
**Phase 2, 3, and 4 are complete!** The system now provides:
|
|
||||||
|
|
||||||
1. **Schema-based forms** - No-code settings for simple addons
|
|
||||||
2. **Custom React components** - Full control for complex addons
|
|
||||||
3. **Window API** - Complete toolkit for addon developers
|
|
||||||
4. **Working example** - Biteship addon demonstrates everything
|
|
||||||
5. **TypeScript support** - Type-safe development
|
|
||||||
6. **Documentation** - Comprehensive guides and examples
|
|
||||||
|
|
||||||
**The module system is now production-ready for both built-in modules and external addons!**
|
|
||||||
@@ -1,400 +0,0 @@
|
|||||||
# ✅ Product Page Implementation - COMPLETE
|
|
||||||
|
|
||||||
## 📊 Summary
|
|
||||||
|
|
||||||
Successfully implemented a complete, industry-standard product page for Customer SPA based on extensive research from Baymard Institute and e-commerce best practices.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 What We Implemented
|
|
||||||
|
|
||||||
### **Phase 1: Core Features** ✅ COMPLETE
|
|
||||||
|
|
||||||
#### 1. Image Gallery with Thumbnail Slider
|
|
||||||
- ✅ Large main image display (aspect-square)
|
|
||||||
- ✅ Horizontal scrollable thumbnail slider
|
|
||||||
- ✅ Arrow navigation (left/right) for >4 images
|
|
||||||
- ✅ Active thumbnail highlighted with ring border
|
|
||||||
- ✅ Click thumbnail to change main image
|
|
||||||
- ✅ Smooth scroll animation
|
|
||||||
- ✅ Hidden scrollbar for clean UI
|
|
||||||
- ✅ Responsive (swipeable on mobile)
|
|
||||||
|
|
||||||
#### 2. Variation Selector
|
|
||||||
- ✅ Dropdown for each variation attribute
|
|
||||||
- ✅ "Choose an option" placeholder
|
|
||||||
- ✅ Auto-switch main image when variation selected
|
|
||||||
- ✅ Auto-update price based on variation
|
|
||||||
- ✅ Auto-update stock status
|
|
||||||
- ✅ Validation: Disable Add to Cart until all options selected
|
|
||||||
- ✅ Error toast if incomplete selection
|
|
||||||
|
|
||||||
#### 3. Enhanced Buy Section
|
|
||||||
- ✅ Product title (H1)
|
|
||||||
- ✅ Price display:
|
|
||||||
- Regular price (strikethrough if on sale)
|
|
||||||
- Sale price (red, highlighted)
|
|
||||||
- "SALE" badge
|
|
||||||
- ✅ Stock status:
|
|
||||||
- Green dot + "In Stock"
|
|
||||||
- Red dot + "Out of Stock"
|
|
||||||
- ✅ Short description
|
|
||||||
- ✅ Quantity selector (plus/minus buttons)
|
|
||||||
- ✅ Add to Cart button (large, prominent)
|
|
||||||
- ✅ Wishlist/Save button (heart icon)
|
|
||||||
- ✅ Product meta (SKU, categories)
|
|
||||||
|
|
||||||
#### 4. Product Information Sections
|
|
||||||
- ✅ Vertical tab layout (NOT horizontal - per best practices)
|
|
||||||
- ✅ Three tabs:
|
|
||||||
- Description (full HTML content)
|
|
||||||
- Additional Information (specs table)
|
|
||||||
- Reviews (placeholder)
|
|
||||||
- ✅ Active tab highlighted
|
|
||||||
- ✅ Smooth transitions
|
|
||||||
- ✅ Scannable specifications table
|
|
||||||
|
|
||||||
#### 5. Navigation & UX
|
|
||||||
- ✅ Breadcrumb navigation
|
|
||||||
- ✅ Back to shop button (error state)
|
|
||||||
- ✅ Loading skeleton
|
|
||||||
- ✅ Error handling
|
|
||||||
- ✅ Toast notifications
|
|
||||||
- ✅ Responsive grid layout
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📐 Layout Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ Breadcrumb: Shop > Product Name │
|
|
||||||
├──────────────────────┬──────────────────────────────────┤
|
|
||||||
│ │ Product Name (H1) │
|
|
||||||
│ Main Image │ $99.00 $79.00 SALE │
|
|
||||||
│ (Large, Square) │ ● In Stock │
|
|
||||||
│ │ │
|
|
||||||
│ │ Short description... │
|
|
||||||
│ [Thumbnail Slider] │ │
|
|
||||||
│ ◀ [img][img][img] ▶│ Color: [Dropdown ▼] │
|
|
||||||
│ │ Size: [Dropdown ▼] │
|
|
||||||
│ │ │
|
|
||||||
│ │ Quantity: [-] 1 [+] │
|
|
||||||
│ │ │
|
|
||||||
│ │ [🛒 Add to Cart] [♡] │
|
|
||||||
│ │ │
|
|
||||||
│ │ SKU: ABC123 │
|
|
||||||
│ │ Categories: Category Name │
|
|
||||||
├──────────────────────┴──────────────────────────────────┤
|
|
||||||
│ [Description] [Additional Info] [Reviews] │
|
|
||||||
│ ───────────── │
|
|
||||||
│ Full product description... │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Visual Design
|
|
||||||
|
|
||||||
### Colors:
|
|
||||||
- **Sale Price:** `text-red-600` (#DC2626)
|
|
||||||
- **Stock In:** `text-green-600` (#10B981)
|
|
||||||
- **Stock Out:** `text-red-600` (#EF4444)
|
|
||||||
- **Active Thumbnail:** `border-primary` + `ring-2 ring-primary`
|
|
||||||
- **Active Tab:** `border-primary text-primary`
|
|
||||||
|
|
||||||
### Spacing:
|
|
||||||
- Section gap: `gap-8 lg:gap-12`
|
|
||||||
- Thumbnail size: `w-20 h-20`
|
|
||||||
- Thumbnail gap: `gap-2`
|
|
||||||
- Button height: `h-12`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 User Interactions
|
|
||||||
|
|
||||||
### Image Gallery:
|
|
||||||
1. **Click Thumbnail** → Main image changes
|
|
||||||
2. **Click Arrow** → Thumbnails scroll horizontally
|
|
||||||
3. **Swipe (mobile)** → Scroll thumbnails
|
|
||||||
|
|
||||||
### Variation Selection:
|
|
||||||
1. **Select Color** → Dropdown changes
|
|
||||||
2. **Select Size** → Dropdown changes
|
|
||||||
3. **Both Selected** →
|
|
||||||
- Price updates
|
|
||||||
- Stock status updates
|
|
||||||
- Main image switches to variation image
|
|
||||||
- Add to Cart enabled
|
|
||||||
|
|
||||||
### Add to Cart:
|
|
||||||
1. **Click Button** →
|
|
||||||
2. **Validation** (if variable product)
|
|
||||||
3. **API Call** (add to cart)
|
|
||||||
4. **Success Toast** (with "View Cart" action)
|
|
||||||
5. **Cart Count Updates** (in header)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ Technical Implementation
|
|
||||||
|
|
||||||
### State Management:
|
|
||||||
```typescript
|
|
||||||
const [selectedImage, setSelectedImage] = useState<string>();
|
|
||||||
const [selectedVariation, setSelectedVariation] = useState<any>(null);
|
|
||||||
const [selectedAttributes, setSelectedAttributes] = useState<Record<string, string>>({});
|
|
||||||
const [quantity, setQuantity] = useState(1);
|
|
||||||
const [activeTab, setActiveTab] = useState('description');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Features:
|
|
||||||
|
|
||||||
#### Auto-Switch Variation Image:
|
|
||||||
```typescript
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedVariation && selectedVariation.image) {
|
|
||||||
setSelectedImage(selectedVariation.image);
|
|
||||||
}
|
|
||||||
}, [selectedVariation]);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Find Matching Variation:
|
|
||||||
```typescript
|
|
||||||
useEffect(() => {
|
|
||||||
if (product?.type === 'variable' && Object.keys(selectedAttributes).length > 0) {
|
|
||||||
const variation = product.variations.find(v => {
|
|
||||||
return Object.entries(selectedAttributes).every(([key, value]) => {
|
|
||||||
const attrKey = `attribute_${key.toLowerCase()}`;
|
|
||||||
return v.attributes[attrKey] === value.toLowerCase();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
setSelectedVariation(variation || null);
|
|
||||||
}
|
|
||||||
}, [selectedAttributes, product]);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Thumbnail Scroll:
|
|
||||||
```typescript
|
|
||||||
const scrollThumbnails = (direction: 'left' | 'right') => {
|
|
||||||
if (thumbnailsRef.current) {
|
|
||||||
const scrollAmount = 200;
|
|
||||||
thumbnailsRef.current.scrollBy({
|
|
||||||
left: direction === 'left' ? -scrollAmount : scrollAmount,
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Documentation Created
|
|
||||||
|
|
||||||
### 1. PRODUCT_PAGE_SOP.md
|
|
||||||
**Purpose:** Industry best practices guide
|
|
||||||
**Content:**
|
|
||||||
- Research-backed UX guidelines
|
|
||||||
- Layout recommendations
|
|
||||||
- Image gallery requirements
|
|
||||||
- Buy section elements
|
|
||||||
- Trust & social proof
|
|
||||||
- Mobile optimization
|
|
||||||
- What to avoid
|
|
||||||
|
|
||||||
### 2. PRODUCT_PAGE_IMPLEMENTATION.md
|
|
||||||
**Purpose:** Implementation roadmap
|
|
||||||
**Content:**
|
|
||||||
- Current state analysis
|
|
||||||
- Phase 1, 2, 3 priorities
|
|
||||||
- Component structure
|
|
||||||
- Acceptance criteria
|
|
||||||
- Estimated timeline
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Acceptance Criteria - ALL MET
|
|
||||||
|
|
||||||
### Image Gallery:
|
|
||||||
- [x] Thumbnails scroll horizontally
|
|
||||||
- [x] Show 4 thumbnails at a time on desktop
|
|
||||||
- [x] Arrow buttons appear when >4 images
|
|
||||||
- [x] Active thumbnail has colored border + ring
|
|
||||||
- [x] Click thumbnail changes main image
|
|
||||||
- [x] Swipeable on mobile (native scroll)
|
|
||||||
- [x] Smooth scroll animation
|
|
||||||
|
|
||||||
### Variation Selector:
|
|
||||||
- [x] Dropdown for each attribute
|
|
||||||
- [x] "Choose an option" placeholder
|
|
||||||
- [x] When variation selected, image auto-switches
|
|
||||||
- [x] Price updates based on variation
|
|
||||||
- [x] Stock status updates
|
|
||||||
- [x] Add to Cart disabled until all attributes selected
|
|
||||||
- [x] Clear error message if incomplete
|
|
||||||
|
|
||||||
### Buy Section:
|
|
||||||
- [x] Sale price shown in red
|
|
||||||
- [x] Regular price strikethrough
|
|
||||||
- [x] Savings badge ("SALE")
|
|
||||||
- [x] Stock status color-coded
|
|
||||||
- [x] Quantity buttons work correctly
|
|
||||||
- [x] Add to Cart shows loading state (via toast)
|
|
||||||
- [x] Success toast with cart preview action
|
|
||||||
- [x] Cart count updates in header
|
|
||||||
|
|
||||||
### Product Info:
|
|
||||||
- [x] Tabs work correctly
|
|
||||||
- [x] Description renders HTML
|
|
||||||
- [x] Specifications show as table
|
|
||||||
- [x] Mobile: sections accessible
|
|
||||||
- [x] Active tab highlighted
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Admin SPA Enhancements
|
|
||||||
|
|
||||||
### Sortable Images with Visual Dropzone:
|
|
||||||
- ✅ Dashed border (shows sortable)
|
|
||||||
- ✅ Ring highlight on drag-over (shows drop target)
|
|
||||||
- ✅ Opacity change when dragging (shows what's moving)
|
|
||||||
- ✅ Smooth transitions
|
|
||||||
- ✅ First image = Featured (auto-labeled)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 Mobile Optimization
|
|
||||||
|
|
||||||
- ✅ Responsive grid (1 col mobile, 2 cols desktop)
|
|
||||||
- ✅ Touch-friendly controls (44x44px minimum)
|
|
||||||
- ✅ Swipeable thumbnail slider
|
|
||||||
- ✅ Adequate spacing between elements
|
|
||||||
- ✅ Readable text sizes
|
|
||||||
- ✅ Accessible form controls
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Performance
|
|
||||||
|
|
||||||
- ✅ Lazy loading (React Query)
|
|
||||||
- ✅ Skeleton loading state
|
|
||||||
- ✅ Optimized images (from WP Media Library)
|
|
||||||
- ✅ Smooth animations (CSS transitions)
|
|
||||||
- ✅ No layout shift
|
|
||||||
- ✅ Fast interaction response
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 What's Next (Phase 2)
|
|
||||||
|
|
||||||
### Planned for Next Sprint:
|
|
||||||
1. **Reviews Section**
|
|
||||||
- Display WooCommerce reviews
|
|
||||||
- Star rating
|
|
||||||
- Review count
|
|
||||||
- Filter/sort options
|
|
||||||
|
|
||||||
2. **Trust Elements**
|
|
||||||
- Payment method icons
|
|
||||||
- Secure checkout badge
|
|
||||||
- Free shipping threshold
|
|
||||||
- Return policy link
|
|
||||||
|
|
||||||
3. **Related Products**
|
|
||||||
- Horizontal carousel
|
|
||||||
- Product cards
|
|
||||||
- "You may also like"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 Success Metrics
|
|
||||||
|
|
||||||
### User Experience:
|
|
||||||
- ✅ Clear product information hierarchy
|
|
||||||
- ✅ Intuitive variation selection
|
|
||||||
- ✅ Visual feedback on all interactions
|
|
||||||
- ✅ No horizontal tabs (27% overlook rate avoided)
|
|
||||||
- ✅ Vertical layout (only 8% overlook rate)
|
|
||||||
|
|
||||||
### Conversion Optimization:
|
|
||||||
- ✅ Large, prominent Add to Cart button
|
|
||||||
- ✅ Clear pricing with sale indicators
|
|
||||||
- ✅ Stock status visibility
|
|
||||||
- ✅ Easy quantity adjustment
|
|
||||||
- ✅ Variation validation prevents errors
|
|
||||||
|
|
||||||
### Industry Standards:
|
|
||||||
- ✅ Follows Baymard Institute guidelines
|
|
||||||
- ✅ Implements best practices from research
|
|
||||||
- ✅ Mobile-first approach
|
|
||||||
- ✅ Accessibility considerations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 Related Commits
|
|
||||||
|
|
||||||
1. **f397ef8** - Product images with WP Media Library integration
|
|
||||||
2. **c37ecb8** - Complete product page implementation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Files Changed
|
|
||||||
|
|
||||||
### Customer SPA:
|
|
||||||
- `customer-spa/src/pages/Product/index.tsx` - Complete rebuild (476 lines)
|
|
||||||
- `customer-spa/src/index.css` - Added scrollbar-hide utility
|
|
||||||
|
|
||||||
### Admin SPA:
|
|
||||||
- `admin-spa/src/routes/Products/partials/tabs/GeneralTab.tsx` - Enhanced dropzone
|
|
||||||
|
|
||||||
### Documentation:
|
|
||||||
- `PRODUCT_PAGE_SOP.md` - Industry best practices (400+ lines)
|
|
||||||
- `PRODUCT_PAGE_IMPLEMENTATION.md` - Implementation plan (300+ lines)
|
|
||||||
- `PRODUCT_PAGE_COMPLETE.md` - This summary
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Testing Checklist
|
|
||||||
|
|
||||||
### Manual Testing:
|
|
||||||
- [ ] Test simple product (no variations)
|
|
||||||
- [ ] Test variable product (with variations)
|
|
||||||
- [ ] Test product with 1 image
|
|
||||||
- [ ] Test product with 5+ images
|
|
||||||
- [ ] Test variation image switching
|
|
||||||
- [ ] Test add to cart (simple)
|
|
||||||
- [ ] Test add to cart (variable, incomplete)
|
|
||||||
- [ ] Test add to cart (variable, complete)
|
|
||||||
- [ ] Test quantity selector
|
|
||||||
- [ ] Test thumbnail slider arrows
|
|
||||||
- [ ] Test tab switching
|
|
||||||
- [ ] Test breadcrumb navigation
|
|
||||||
- [ ] Test mobile responsiveness
|
|
||||||
- [ ] Test loading states
|
|
||||||
- [ ] Test error states
|
|
||||||
|
|
||||||
### Browser Testing:
|
|
||||||
- [ ] Chrome
|
|
||||||
- [ ] Firefox
|
|
||||||
- [ ] Safari
|
|
||||||
- [ ] Edge
|
|
||||||
- [ ] Mobile Safari
|
|
||||||
- [ ] Mobile Chrome
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏆 Achievements
|
|
||||||
|
|
||||||
✅ **Research-Driven Design** - Based on Baymard Institute 2025 UX research
|
|
||||||
✅ **Industry Standards** - Follows e-commerce best practices
|
|
||||||
✅ **Complete Implementation** - All Phase 1 features delivered
|
|
||||||
✅ **Comprehensive Documentation** - SOP + Implementation guide
|
|
||||||
✅ **Mobile-Optimized** - Responsive and touch-friendly
|
|
||||||
✅ **Performance-Focused** - Fast loading and smooth interactions
|
|
||||||
✅ **User-Centric** - Clear hierarchy and intuitive controls
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** ✅ COMPLETE
|
|
||||||
**Quality:** ⭐⭐⭐⭐⭐
|
|
||||||
**Ready for:** Production Testing
|
|
||||||
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
# Product Page Critical Fixes - Complete ✅
|
|
||||||
|
|
||||||
**Date:** November 26, 2025
|
|
||||||
**Status:** All Critical Issues Resolved
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Issues Fixed
|
|
||||||
|
|
||||||
### Issue #1: Variation Price Not Updating ✅
|
|
||||||
|
|
||||||
**Problem:**
|
|
||||||
```tsx
|
|
||||||
// WRONG - Using sale_price check
|
|
||||||
const isOnSale = selectedVariation
|
|
||||||
? parseFloat(selectedVariation.sale_price || '0') > 0
|
|
||||||
: product.on_sale;
|
|
||||||
```
|
|
||||||
|
|
||||||
**Root Cause:**
|
|
||||||
- Logic was checking if `sale_price` exists, not comparing prices
|
|
||||||
- Didn't account for variations where `regular_price > price` but no explicit `sale_price` field
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```tsx
|
|
||||||
// CORRECT - Compare regular_price vs price
|
|
||||||
const currentPrice = selectedVariation?.price || product.price;
|
|
||||||
const regularPrice = selectedVariation?.regular_price || product.regular_price;
|
|
||||||
const isOnSale = regularPrice && currentPrice && parseFloat(currentPrice) < parseFloat(regularPrice);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:**
|
|
||||||
- ✅ Price updates correctly when variation selected
|
|
||||||
- ✅ Sale badge shows when variation price < regular price
|
|
||||||
- ✅ Discount percentage calculates accurately
|
|
||||||
- ✅ Works for both simple and variable products
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Issue #2: Variation Images Not in Gallery ✅
|
|
||||||
|
|
||||||
**Problem:**
|
|
||||||
```tsx
|
|
||||||
// WRONG - Only showing product.images
|
|
||||||
{product.images && product.images.length > 1 && (
|
|
||||||
<div>
|
|
||||||
{product.images.map((img, index) => (
|
|
||||||
<img src={img} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Root Cause:**
|
|
||||||
- Gallery only included `product.images` array
|
|
||||||
- Variation images exist in `product.variations[].image`
|
|
||||||
- When user selected variation, image would switch but wasn't clickable in gallery
|
|
||||||
- Thumbnails didn't show variation images
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```tsx
|
|
||||||
// Build complete image gallery including variation images
|
|
||||||
const allImages = React.useMemo(() => {
|
|
||||||
const images = [...(product.images || [])];
|
|
||||||
|
|
||||||
// Add variation images if they don't exist in main gallery
|
|
||||||
if (product.type === 'variable' && product.variations) {
|
|
||||||
(product.variations as any[]).forEach(variation => {
|
|
||||||
if (variation.image && !images.includes(variation.image)) {
|
|
||||||
images.push(variation.image);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return images;
|
|
||||||
}, [product]);
|
|
||||||
|
|
||||||
// Use allImages everywhere
|
|
||||||
{allImages && allImages.length > 1 && (
|
|
||||||
<div>
|
|
||||||
{allImages.map((img, index) => (
|
|
||||||
<img src={img} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:**
|
|
||||||
- ✅ All variation images appear in gallery
|
|
||||||
- ✅ Users can click thumbnails to see variation images
|
|
||||||
- ✅ Dots navigation shows all images (mobile)
|
|
||||||
- ✅ Thumbnail slider shows all images (desktop)
|
|
||||||
- ✅ No duplicate images (checked with `!images.includes()`)
|
|
||||||
- ✅ Performance optimized with `useMemo`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Complete Fix Summary
|
|
||||||
|
|
||||||
### What Was Fixed:
|
|
||||||
|
|
||||||
1. **Price Calculation Logic**
|
|
||||||
- Changed from `sale_price` check to price comparison
|
|
||||||
- Now correctly identifies sale state
|
|
||||||
- Works for all product types
|
|
||||||
|
|
||||||
2. **Image Gallery Construction**
|
|
||||||
- Added `allImages` computed array
|
|
||||||
- Merges `product.images` + `variation.images`
|
|
||||||
- Removes duplicates
|
|
||||||
- Used in all gallery components:
|
|
||||||
- Main image display
|
|
||||||
- Dots navigation (mobile)
|
|
||||||
- Thumbnail slider (desktop)
|
|
||||||
|
|
||||||
3. **Auto-Select First Variation** (from previous fix)
|
|
||||||
- Auto-selects first option on load
|
|
||||||
- Triggers price and image updates
|
|
||||||
|
|
||||||
4. **Variation Matching** (from previous fix)
|
|
||||||
- Robust attribute matching
|
|
||||||
- Handles multiple WooCommerce formats
|
|
||||||
- Case-insensitive comparison
|
|
||||||
|
|
||||||
5. **Above-the-Fold Optimization** (from previous fix)
|
|
||||||
- Compressed spacing
|
|
||||||
- Responsive sizing
|
|
||||||
- Collapsible description
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Testing Checklist
|
|
||||||
|
|
||||||
### Variable Product Testing:
|
|
||||||
- ✅ First variation auto-selected on load
|
|
||||||
- ✅ Price shows variation price immediately
|
|
||||||
- ✅ Image shows variation image immediately
|
|
||||||
- ✅ Variation images appear in gallery
|
|
||||||
- ✅ Clicking variation updates price
|
|
||||||
- ✅ Clicking variation updates image
|
|
||||||
- ✅ Sale badge shows correctly
|
|
||||||
- ✅ Discount percentage accurate
|
|
||||||
- ✅ Stock status updates per variation
|
|
||||||
|
|
||||||
### Image Gallery Testing:
|
|
||||||
- ✅ All product images visible
|
|
||||||
- ✅ All variation images visible
|
|
||||||
- ✅ No duplicate images
|
|
||||||
- ✅ Dots navigation works (mobile)
|
|
||||||
- ✅ Thumbnail slider works (desktop)
|
|
||||||
- ✅ Clicking thumbnail changes main image
|
|
||||||
- ✅ Selected thumbnail highlighted
|
|
||||||
- ✅ Arrow buttons work (if >4 images)
|
|
||||||
|
|
||||||
### Simple Product Testing:
|
|
||||||
- ✅ Price displays correctly
|
|
||||||
- ✅ Sale badge shows if on sale
|
|
||||||
- ✅ Images display in gallery
|
|
||||||
- ✅ No errors in console
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📈 Impact
|
|
||||||
|
|
||||||
### User Experience:
|
|
||||||
- ✅ Complete product state on load (no blank price/image)
|
|
||||||
- ✅ Accurate pricing at all times
|
|
||||||
- ✅ All product images accessible
|
|
||||||
- ✅ Smooth variation switching
|
|
||||||
- ✅ Clear visual feedback
|
|
||||||
|
|
||||||
### Conversion Rate:
|
|
||||||
- **Before:** Users confused by missing prices/images
|
|
||||||
- **After:** Professional, complete product presentation
|
|
||||||
- **Expected Impact:** +10-15% conversion improvement
|
|
||||||
|
|
||||||
### Code Quality:
|
|
||||||
- ✅ Performance optimized (`useMemo`)
|
|
||||||
- ✅ No duplicate logic
|
|
||||||
- ✅ Clean, maintainable code
|
|
||||||
- ✅ Proper React patterns
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Remaining Tasks
|
|
||||||
|
|
||||||
### High Priority:
|
|
||||||
1. ⏳ Reviews hierarchy (show before description)
|
|
||||||
2. ⏳ Admin Appearance menu
|
|
||||||
3. ⏳ Trust badges repeater
|
|
||||||
|
|
||||||
### Medium Priority:
|
|
||||||
4. ⏳ Full-width layout option
|
|
||||||
5. ⏳ Fullscreen image lightbox
|
|
||||||
6. ⏳ Sticky bottom bar (mobile)
|
|
||||||
|
|
||||||
### Low Priority:
|
|
||||||
7. ⏳ Related products section
|
|
||||||
8. ⏳ Customer photo gallery
|
|
||||||
9. ⏳ Size guide modal
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 Key Learnings
|
|
||||||
|
|
||||||
### Price Calculation:
|
|
||||||
- Always compare `regular_price` vs `price`, not check for `sale_price` field
|
|
||||||
- WooCommerce may not set `sale_price` explicitly
|
|
||||||
- Variation prices override product prices
|
|
||||||
|
|
||||||
### Image Gallery:
|
|
||||||
- Variation images are separate from product images
|
|
||||||
- Must merge arrays to show complete gallery
|
|
||||||
- Use `useMemo` to avoid recalculation on every render
|
|
||||||
- Check for duplicates when merging
|
|
||||||
|
|
||||||
### Variation Handling:
|
|
||||||
- Auto-select improves UX significantly
|
|
||||||
- Attribute matching needs to be flexible (multiple formats)
|
|
||||||
- Always update price AND image when variation changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** ✅ All Critical Issues Resolved
|
|
||||||
**Quality:** ⭐⭐⭐⭐⭐
|
|
||||||
**Ready for:** Production Testing
|
|
||||||
**Confidence:** HIGH
|
|
||||||
@@ -1,517 +0,0 @@
|
|||||||
# Product Page Design Decision Framework
|
|
||||||
## Research vs. Convention vs. Context
|
|
||||||
|
|
||||||
**Date:** November 26, 2025
|
|
||||||
**Question:** Should we follow research or follow what big players do?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🤔 The Dilemma
|
|
||||||
|
|
||||||
### The Argument FOR Following Big Players:
|
|
||||||
|
|
||||||
**You're absolutely right:**
|
|
||||||
|
|
||||||
1. **Cognitive Load is Real**
|
|
||||||
- Users have learned Tokopedia/Shopify patterns
|
|
||||||
- "Don't make me think" - users expect familiar patterns
|
|
||||||
- Breaking convention = friction = lost sales
|
|
||||||
|
|
||||||
2. **They Have Data We Don't**
|
|
||||||
- Tokopedia: Millions of transactions
|
|
||||||
- Shopify: Thousands of stores tested
|
|
||||||
- A/B tested to death
|
|
||||||
- Real money on the line
|
|
||||||
|
|
||||||
3. **Convention > Research Sometimes**
|
|
||||||
- Research is general, their data is specific
|
|
||||||
- Research is lab, their data is real-world
|
|
||||||
- Research is Western, their data is local (Indonesia for Tokopedia)
|
|
||||||
|
|
||||||
4. **Mobile Thumbnails Example:**
|
|
||||||
- If 76% of sites don't use thumbnails...
|
|
||||||
- ...then 76% of users are trained to use dots
|
|
||||||
- Breaking this = re-training users
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔬 The Argument FOR Following Research:
|
|
||||||
|
|
||||||
### But Research Has Valid Points:
|
|
||||||
|
|
||||||
1. **Big Players Optimize for THEIR Context**
|
|
||||||
- Tokopedia: Marketplace with millions of products (need speed)
|
|
||||||
- Shopify: Multi-tenant platform (one-size-fits-all)
|
|
||||||
- WooNooW: Custom plugin (we can do better)
|
|
||||||
|
|
||||||
2. **They Optimize for Different Metrics**
|
|
||||||
- Tokopedia: Transaction volume (speed > perfection)
|
|
||||||
- Shopify: Platform adoption (simple > optimal)
|
|
||||||
- WooNooW: Conversion rate (quality > speed)
|
|
||||||
|
|
||||||
3. **Research Finds Universal Truths**
|
|
||||||
- Hit area issues are physics, not preference
|
|
||||||
- Information scent is cognitive science
|
|
||||||
- Accidental taps are measurable errors
|
|
||||||
|
|
||||||
4. **Convention Can Be Wrong**
|
|
||||||
- Just because everyone does it doesn't make it right
|
|
||||||
- "Best practices" evolve
|
|
||||||
- Someone has to lead the change
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 The REAL Answer: Context-Driven Decision Making
|
|
||||||
|
|
||||||
### Framework for Each Pattern:
|
|
||||||
|
|
||||||
```
|
|
||||||
FOR EACH DESIGN PATTERN:
|
|
||||||
├─ Is it LEARNED BEHAVIOR? (convention)
|
|
||||||
│ ├─ YES → Follow convention (low friction)
|
|
||||||
│ └─ NO → Follow research (optimize)
|
|
||||||
│
|
|
||||||
├─ Is it CONTEXT-SPECIFIC?
|
|
||||||
│ ├─ Marketplace → Follow Tokopedia
|
|
||||||
│ ├─ Brand Store → Follow Shopify
|
|
||||||
│ └─ Custom Plugin → Follow Research
|
|
||||||
│
|
|
||||||
├─ What's the COST OF FRICTION?
|
|
||||||
│ ├─ HIGH → Follow convention
|
|
||||||
│ └─ LOW → Follow research
|
|
||||||
│
|
|
||||||
└─ Can we GET THE BEST OF BOTH?
|
|
||||||
└─ Hybrid approach
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Pattern-by-Pattern Analysis
|
|
||||||
|
|
||||||
### 1. IMAGE GALLERY THUMBNAILS
|
|
||||||
|
|
||||||
#### Convention (Tokopedia/Shopify):
|
|
||||||
- Mobile: Dots only
|
|
||||||
- Desktop: Thumbnails
|
|
||||||
|
|
||||||
#### Research (Baymard):
|
|
||||||
- Mobile: Thumbnails better
|
|
||||||
- Desktop: Thumbnails essential
|
|
||||||
|
|
||||||
#### Analysis:
|
|
||||||
|
|
||||||
**Is it learned behavior?**
|
|
||||||
- ✅ YES - Users know how to swipe
|
|
||||||
- ✅ YES - Users know dots mean "more images"
|
|
||||||
- ⚠️ BUT - Users also know thumbnails (from desktop)
|
|
||||||
|
|
||||||
**Cost of friction?**
|
|
||||||
- 🟡 MEDIUM - Users can adapt
|
|
||||||
- Research shows errors, but users still complete tasks
|
|
||||||
|
|
||||||
**Context:**
|
|
||||||
- Tokopedia: Millions of products, need speed (dots save space)
|
|
||||||
- WooNooW: Fewer products, need quality (thumbnails show detail)
|
|
||||||
|
|
||||||
#### 🎯 DECISION: **HYBRID APPROACH**
|
|
||||||
|
|
||||||
```
|
|
||||||
Mobile:
|
|
||||||
├─ Show 3-4 SMALL thumbnails (not full width)
|
|
||||||
├─ Scrollable horizontally
|
|
||||||
├─ Add dots as SECONDARY indicator
|
|
||||||
└─ Best of both worlds
|
|
||||||
|
|
||||||
Why:
|
|
||||||
├─ Thumbnails: Information scent (research)
|
|
||||||
├─ Small size: Doesn't dominate screen (convention)
|
|
||||||
├─ Dots: Familiar pattern (convention)
|
|
||||||
└─ Users get preview + familiar UI
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rationale:**
|
|
||||||
- Not breaking convention (dots still there)
|
|
||||||
- Adding value (thumbnails for preview)
|
|
||||||
- Low friction (users understand both)
|
|
||||||
- Better UX (research-backed)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. VARIATION SELECTORS
|
|
||||||
|
|
||||||
#### Convention (Tokopedia/Shopify):
|
|
||||||
- Pills/Buttons for all variations
|
|
||||||
- All visible at once
|
|
||||||
|
|
||||||
#### Our Current:
|
|
||||||
- Dropdowns
|
|
||||||
|
|
||||||
#### Research (Nielsen Norman):
|
|
||||||
- Pills > Dropdowns
|
|
||||||
|
|
||||||
#### Analysis:
|
|
||||||
|
|
||||||
**Is it learned behavior?**
|
|
||||||
- ✅ YES - Pills are now standard
|
|
||||||
- ✅ YES - E-commerce trained users on this
|
|
||||||
- ❌ NO - Dropdowns are NOT e-commerce convention
|
|
||||||
|
|
||||||
**Cost of friction?**
|
|
||||||
- 🔴 HIGH - Dropdowns are unexpected in e-commerce
|
|
||||||
- Users expect to see all options
|
|
||||||
|
|
||||||
**Context:**
|
|
||||||
- This is universal across all e-commerce
|
|
||||||
- Not context-specific
|
|
||||||
|
|
||||||
#### 🎯 DECISION: **FOLLOW CONVENTION (Pills)**
|
|
||||||
|
|
||||||
```
|
|
||||||
Replace dropdowns with pills/buttons
|
|
||||||
|
|
||||||
Why:
|
|
||||||
├─ Convention is clear (everyone uses pills)
|
|
||||||
├─ Research agrees (pills are better)
|
|
||||||
├─ No downside (pills are superior)
|
|
||||||
└─ Users expect this pattern
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rationale:**
|
|
||||||
- Convention + Research align
|
|
||||||
- No reason to use dropdowns
|
|
||||||
- Clear winner
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. TYPOGRAPHY HIERARCHY
|
|
||||||
|
|
||||||
#### Convention (Varies):
|
|
||||||
- Tokopedia: Price > Title (marketplace)
|
|
||||||
- Shopify: Title > Price (brand store)
|
|
||||||
|
|
||||||
#### Our Current:
|
|
||||||
- Price: 48-60px (HUGE)
|
|
||||||
- Title: 24-32px
|
|
||||||
|
|
||||||
#### Research:
|
|
||||||
- Title should be primary
|
|
||||||
|
|
||||||
#### Analysis:
|
|
||||||
|
|
||||||
**Is it learned behavior?**
|
|
||||||
- ⚠️ CONTEXT-DEPENDENT
|
|
||||||
- Marketplace: Price-focused (comparison)
|
|
||||||
- Brand Store: Product-focused (storytelling)
|
|
||||||
|
|
||||||
**Cost of friction?**
|
|
||||||
- 🟢 LOW - Users adapt to hierarchy quickly
|
|
||||||
- Not a learned interaction, just visual weight
|
|
||||||
|
|
||||||
**Context:**
|
|
||||||
- WooNooW: Custom plugin for brand stores
|
|
||||||
- Not a marketplace
|
|
||||||
- More like Shopify than Tokopedia
|
|
||||||
|
|
||||||
#### 🎯 DECISION: **FOLLOW SHOPIFY (Title Primary)**
|
|
||||||
|
|
||||||
```
|
|
||||||
Title: 28-32px (primary)
|
|
||||||
Price: 24-28px (secondary, but prominent)
|
|
||||||
|
|
||||||
Why:
|
|
||||||
├─ We're not a marketplace (no price comparison)
|
|
||||||
├─ Brand stores need product focus
|
|
||||||
├─ Research supports this
|
|
||||||
└─ Shopify (our closer analog) does this
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rationale:**
|
|
||||||
- Context matters (we're not Tokopedia)
|
|
||||||
- Shopify is better analog
|
|
||||||
- Research agrees
|
|
||||||
- Low friction to change
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. DESCRIPTION PATTERN
|
|
||||||
|
|
||||||
#### Convention (Varies):
|
|
||||||
- Tokopedia: "Show More" (folded)
|
|
||||||
- Shopify: Auto-expanded accordion
|
|
||||||
|
|
||||||
#### Our Current:
|
|
||||||
- Collapsed accordion
|
|
||||||
|
|
||||||
#### Research:
|
|
||||||
- Don't hide primary content
|
|
||||||
|
|
||||||
#### Analysis:
|
|
||||||
|
|
||||||
**Is it learned behavior?**
|
|
||||||
- ⚠️ BOTH patterns are common
|
|
||||||
- Users understand both
|
|
||||||
- No strong convention
|
|
||||||
|
|
||||||
**Cost of friction?**
|
|
||||||
- 🟢 LOW - Users know how to expand
|
|
||||||
- But research shows some users miss collapsed content
|
|
||||||
|
|
||||||
**Context:**
|
|
||||||
- Primary content should be visible
|
|
||||||
- Secondary content can be collapsed
|
|
||||||
|
|
||||||
#### 🎯 DECISION: **FOLLOW SHOPIFY (Auto-Expand Description)**
|
|
||||||
|
|
||||||
```
|
|
||||||
Description: Auto-expanded on load
|
|
||||||
Other sections: Collapsed (Specs, Shipping, Reviews)
|
|
||||||
|
|
||||||
Why:
|
|
||||||
├─ Description is primary content
|
|
||||||
├─ Research says don't hide it
|
|
||||||
├─ Shopify does this (our analog)
|
|
||||||
└─ Low friction (users can collapse if needed)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rationale:**
|
|
||||||
- Best of both worlds
|
|
||||||
- Primary visible, secondary hidden
|
|
||||||
- Research-backed
|
|
||||||
- Convention-friendly
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎓 The Meta-Lesson
|
|
||||||
|
|
||||||
### When to Follow Convention:
|
|
||||||
|
|
||||||
1. **Strong learned behavior** (e.g., hamburger menu, swipe gestures)
|
|
||||||
2. **High cost of friction** (e.g., checkout flow, payment)
|
|
||||||
3. **Universal pattern** (e.g., search icon, cart icon)
|
|
||||||
4. **No clear winner** (e.g., both patterns work equally well)
|
|
||||||
|
|
||||||
### When to Follow Research:
|
|
||||||
|
|
||||||
1. **Convention is weak** (e.g., new patterns, no standard)
|
|
||||||
2. **Low cost of friction** (e.g., visual hierarchy, spacing)
|
|
||||||
3. **Research shows clear winner** (e.g., thumbnails vs dots)
|
|
||||||
4. **We can improve on convention** (e.g., hybrid approaches)
|
|
||||||
|
|
||||||
### When to Follow Context:
|
|
||||||
|
|
||||||
1. **Marketplace vs Brand Store** (different goals)
|
|
||||||
2. **Local vs Global** (cultural differences)
|
|
||||||
3. **Mobile vs Desktop** (different constraints)
|
|
||||||
4. **Our specific users** (if we have data)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Final Decision Framework
|
|
||||||
|
|
||||||
### For WooNooW Product Page:
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ DECISION MATRIX │
|
|
||||||
├─────────────────────────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ Pattern Convention Research Decision │
|
|
||||||
│ ─────────────────────────────────────────────────────── │
|
|
||||||
│ Image Thumbnails Dots Thumbs HYBRID ⭐ │
|
|
||||||
│ Variation Selector Pills Pills PILLS ✅ │
|
|
||||||
│ Typography Varies Title>$ TITLE>$ ✅ │
|
|
||||||
│ Description Varies Visible VISIBLE ✅ │
|
|
||||||
│ Sticky Bottom Bar Common N/A YES ✅ │
|
|
||||||
│ Fullscreen Lightbox Common Good YES ✅ │
|
|
||||||
│ Social Proof Top Common Good YES ✅ │
|
|
||||||
│ │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 The Hybrid Approach (Best of Both Worlds)
|
|
||||||
|
|
||||||
### Image Gallery - Our Solution:
|
|
||||||
|
|
||||||
```
|
|
||||||
Mobile:
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ │
|
|
||||||
│ [Main Image] │
|
|
||||||
│ │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ [▭] [▭] [▭] [▭] ← Small thumbnails │
|
|
||||||
│ ● ○ ○ ○ ← Dots below │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
|
|
||||||
Benefits:
|
|
||||||
├─ Thumbnails: Information scent ✅
|
|
||||||
├─ Small size: Doesn't dominate ✅
|
|
||||||
├─ Dots: Familiar indicator ✅
|
|
||||||
├─ Swipe: Still works ✅
|
|
||||||
└─ Best of all worlds ⭐
|
|
||||||
```
|
|
||||||
|
|
||||||
### Why This Works:
|
|
||||||
|
|
||||||
1. **Convention Respected:**
|
|
||||||
- Dots are still there (familiar)
|
|
||||||
- Swipe still works (learned behavior)
|
|
||||||
- Doesn't look "weird"
|
|
||||||
|
|
||||||
2. **Research Applied:**
|
|
||||||
- Thumbnails provide preview (information scent)
|
|
||||||
- Larger hit areas (fewer errors)
|
|
||||||
- Users can jump to specific image
|
|
||||||
|
|
||||||
3. **Context Optimized:**
|
|
||||||
- Small thumbnails (mobile-friendly)
|
|
||||||
- Not as prominent as desktop (saves space)
|
|
||||||
- Progressive enhancement
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Real-World Examples of Hybrid Success
|
|
||||||
|
|
||||||
### Amazon (The Master of Hybrid):
|
|
||||||
|
|
||||||
**Mobile Image Gallery:**
|
|
||||||
- ✅ Small thumbnails (4-5 visible)
|
|
||||||
- ✅ Dots below thumbnails
|
|
||||||
- ✅ Swipe gesture works
|
|
||||||
- ✅ Tap thumbnail to jump
|
|
||||||
|
|
||||||
**Why Amazon does this:**
|
|
||||||
- They have MORE data than anyone
|
|
||||||
- They A/B test EVERYTHING
|
|
||||||
- This is their optimized solution
|
|
||||||
- Hybrid > Pure convention or pure research
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Our Final Recommendations
|
|
||||||
|
|
||||||
### HIGH PRIORITY (Implement Now):
|
|
||||||
|
|
||||||
1. **Variation Pills** ✅
|
|
||||||
- Convention + Research align
|
|
||||||
- Clear winner
|
|
||||||
- No downside
|
|
||||||
|
|
||||||
2. **Auto-Expand Description** ✅
|
|
||||||
- Research-backed
|
|
||||||
- Low friction
|
|
||||||
- Shopify does this
|
|
||||||
|
|
||||||
3. **Title > Price Hierarchy** ✅
|
|
||||||
- Context-appropriate
|
|
||||||
- Research-backed
|
|
||||||
- Shopify analog
|
|
||||||
|
|
||||||
4. **Hybrid Thumbnail Gallery** ⭐
|
|
||||||
- Best of both worlds
|
|
||||||
- Small thumbnails + dots
|
|
||||||
- Amazon does this
|
|
||||||
|
|
||||||
### MEDIUM PRIORITY (Consider):
|
|
||||||
|
|
||||||
5. **Sticky Bottom Bar (Mobile)** 🤔
|
|
||||||
- Convention (Tokopedia does this)
|
|
||||||
- Good for mobile UX
|
|
||||||
- Test with users
|
|
||||||
|
|
||||||
6. **Fullscreen Lightbox** ✅
|
|
||||||
- Convention (Shopify does this)
|
|
||||||
- Research supports
|
|
||||||
- Clear value
|
|
||||||
|
|
||||||
### LOW PRIORITY (Later):
|
|
||||||
|
|
||||||
7. **Social Proof at Top** ✅
|
|
||||||
- Convention + Research align
|
|
||||||
- When we have reviews
|
|
||||||
|
|
||||||
8. **Estimated Delivery** ✅
|
|
||||||
- Convention (Tokopedia does this)
|
|
||||||
- High value
|
|
||||||
- When we have shipping data
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎓 Key Takeaways
|
|
||||||
|
|
||||||
### 1. **Convention is Not Always Right**
|
|
||||||
- But it's not always wrong either
|
|
||||||
- Respect learned behavior
|
|
||||||
- Break convention carefully
|
|
||||||
|
|
||||||
### 2. **Research is Not Always Applicable**
|
|
||||||
- Context matters
|
|
||||||
- Local vs global
|
|
||||||
- Marketplace vs brand store
|
|
||||||
|
|
||||||
### 3. **Hybrid Approaches Win**
|
|
||||||
- Don't choose sides
|
|
||||||
- Get best of both worlds
|
|
||||||
- Amazon proves this works
|
|
||||||
|
|
||||||
### 4. **Test, Don't Guess**
|
|
||||||
- Convention + Research = hypothesis
|
|
||||||
- Real users = truth
|
|
||||||
- Be ready to pivot
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 The Answer to Your Question
|
|
||||||
|
|
||||||
> "So what is our best decision to refer?"
|
|
||||||
|
|
||||||
**Answer: NEITHER exclusively. Use a DECISION FRAMEWORK.**
|
|
||||||
|
|
||||||
```
|
|
||||||
FOR EACH PATTERN:
|
|
||||||
1. Identify the convention (what big players do)
|
|
||||||
2. Identify the research (what studies say)
|
|
||||||
3. Identify the context (what we need)
|
|
||||||
4. Identify the friction (cost of change)
|
|
||||||
5. Choose the best fit (or hybrid)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Specific to thumbnails:**
|
|
||||||
|
|
||||||
❌ **Don't blindly follow research** (full thumbnails might be too much)
|
|
||||||
❌ **Don't blindly follow convention** (dots have real problems)
|
|
||||||
✅ **Use hybrid approach** (small thumbnails + dots)
|
|
||||||
|
|
||||||
**Why:**
|
|
||||||
- Respects convention (dots still there)
|
|
||||||
- Applies research (thumbnails for preview)
|
|
||||||
- Optimizes for context (mobile-friendly size)
|
|
||||||
- Minimizes friction (users understand both)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Implementation Strategy
|
|
||||||
|
|
||||||
### Phase 1: Low-Friction Changes
|
|
||||||
1. Variation pills (convention + research align)
|
|
||||||
2. Auto-expand description (low friction)
|
|
||||||
3. Typography adjustment (low friction)
|
|
||||||
|
|
||||||
### Phase 2: Hybrid Approaches
|
|
||||||
4. Small thumbnails + dots (test with users)
|
|
||||||
5. Sticky bottom bar (test with users)
|
|
||||||
6. Fullscreen lightbox (convention + research)
|
|
||||||
|
|
||||||
### Phase 3: Data-Driven Optimization
|
|
||||||
7. A/B test hybrid vs pure convention
|
|
||||||
8. Measure: bounce rate, time on page, conversion
|
|
||||||
9. Iterate based on real data
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** ✅ Framework Complete
|
|
||||||
**Philosophy:** Pragmatic, not dogmatic
|
|
||||||
**Goal:** Best UX for OUR users, not theoretical perfection
|
|
||||||
@@ -1,543 +0,0 @@
|
|||||||
# Product Page Fixes - IMPLEMENTED ✅
|
|
||||||
|
|
||||||
**Date:** November 26, 2025
|
|
||||||
**Reference:** PRODUCT_PAGE_REVIEW_REPORT.md
|
|
||||||
**Status:** Critical Fixes Complete
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ CRITICAL FIXES IMPLEMENTED
|
|
||||||
|
|
||||||
### Fix #1: Above-the-Fold Optimization ✅
|
|
||||||
|
|
||||||
**Problem:** CTA below fold on common laptop resolutions (1366x768, 1440x900)
|
|
||||||
|
|
||||||
**Solution Implemented:**
|
|
||||||
```tsx
|
|
||||||
// Compressed spacing throughout
|
|
||||||
<div className="grid md:grid-cols-2 gap-6 lg:gap-8"> // was gap-8 lg:gap-12
|
|
||||||
|
|
||||||
// Responsive title sizing
|
|
||||||
<h1 className="text-xl md:text-2xl lg:text-3xl"> // was text-2xl md:text-3xl
|
|
||||||
|
|
||||||
// Reduced margins
|
|
||||||
mb-3 // was mb-4 or mb-6
|
|
||||||
|
|
||||||
// Collapsible short description on mobile
|
|
||||||
<details className="mb-3 md:mb-4">
|
|
||||||
<summary className="md:hidden">Product Details</summary>
|
|
||||||
<div className="md:block">{shortDescription}</div>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
// Compact trust badges
|
|
||||||
<div className="grid grid-cols-3 gap-2 text-xs lg:text-sm">
|
|
||||||
<div className="flex flex-col items-center">
|
|
||||||
<svg className="w-5 h-5 lg:w-6 lg:h-6" />
|
|
||||||
<p>Free Ship</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
// Compact CTA
|
|
||||||
<button className="h-12 lg:h-14"> // was h-14
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:**
|
|
||||||
- ✅ All critical elements fit above fold on 1366x768
|
|
||||||
- ✅ No scroll required to see Add to Cart
|
|
||||||
- ✅ Trust badges visible
|
|
||||||
- ✅ Responsive scaling for larger screens
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Fix #2: Auto-Select First Variation ✅
|
|
||||||
|
|
||||||
**Problem:** Variable products load without any variation selected
|
|
||||||
|
|
||||||
**Solution Implemented:**
|
|
||||||
```tsx
|
|
||||||
// AUTO-SELECT FIRST VARIATION (Issue #2 from report)
|
|
||||||
useEffect(() => {
|
|
||||||
if (product?.type === 'variable' && product.attributes && Object.keys(selectedAttributes).length === 0) {
|
|
||||||
const initialAttributes: Record<string, string> = {};
|
|
||||||
|
|
||||||
product.attributes.forEach((attr: any) => {
|
|
||||||
if (attr.variation && attr.options && attr.options.length > 0) {
|
|
||||||
initialAttributes[attr.name] = attr.options[0];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Object.keys(initialAttributes).length > 0) {
|
|
||||||
setSelectedAttributes(initialAttributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [product]);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:**
|
|
||||||
- ✅ First variation auto-selected on page load
|
|
||||||
- ✅ Price shows variation price immediately
|
|
||||||
- ✅ Image shows variation image immediately
|
|
||||||
- ✅ User sees complete product state
|
|
||||||
- ✅ Matches Amazon, Tokopedia, Shopify behavior
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Fix #3: Variation Image Switching ✅
|
|
||||||
|
|
||||||
**Problem:** Variation images not showing when attributes selected
|
|
||||||
|
|
||||||
**Solution Implemented:**
|
|
||||||
```tsx
|
|
||||||
// Find matching variation when attributes change (FIXED - Issue #3, #4)
|
|
||||||
useEffect(() => {
|
|
||||||
if (product?.type === 'variable' && product.variations && Object.keys(selectedAttributes).length > 0) {
|
|
||||||
const variation = (product.variations as any[]).find(v => {
|
|
||||||
if (!v.attributes) return false;
|
|
||||||
|
|
||||||
return Object.entries(selectedAttributes).every(([attrName, attrValue]) => {
|
|
||||||
// Try multiple attribute key formats
|
|
||||||
const normalizedName = attrName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
||||||
const possibleKeys = [
|
|
||||||
`attribute_pa_${normalizedName}`,
|
|
||||||
`attribute_${normalizedName}`,
|
|
||||||
`attribute_${attrName.toLowerCase()}`,
|
|
||||||
attrName,
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const key of possibleKeys) {
|
|
||||||
if (v.attributes[key]) {
|
|
||||||
const varValue = v.attributes[key].toLowerCase();
|
|
||||||
const selValue = attrValue.toLowerCase();
|
|
||||||
if (varValue === selValue) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
setSelectedVariation(variation || null);
|
|
||||||
} else if (product?.type !== 'variable') {
|
|
||||||
setSelectedVariation(null);
|
|
||||||
}
|
|
||||||
}, [selectedAttributes, product]);
|
|
||||||
|
|
||||||
// Auto-switch image when variation selected
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedVariation && selectedVariation.image) {
|
|
||||||
setSelectedImage(selectedVariation.image);
|
|
||||||
}
|
|
||||||
}, [selectedVariation]);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:**
|
|
||||||
- ✅ Variation matching works with multiple attribute key formats
|
|
||||||
- ✅ Handles WooCommerce attribute naming conventions
|
|
||||||
- ✅ Image switches immediately when variation selected
|
|
||||||
- ✅ Robust error handling
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Fix #4: Variation Price Updating ✅
|
|
||||||
|
|
||||||
**Problem:** Price not updating when variation selected
|
|
||||||
|
|
||||||
**Solution Implemented:**
|
|
||||||
```tsx
|
|
||||||
// Price calculation uses selectedVariation
|
|
||||||
const currentPrice = selectedVariation?.price || product.price;
|
|
||||||
const regularPrice = selectedVariation?.regular_price || product.regular_price;
|
|
||||||
const isOnSale = regularPrice && currentPrice && parseFloat(currentPrice) < parseFloat(regularPrice);
|
|
||||||
|
|
||||||
// Display
|
|
||||||
{isOnSale && regularPrice ? (
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="text-2xl font-bold text-red-600">
|
|
||||||
{formatPrice(currentPrice)}
|
|
||||||
</span>
|
|
||||||
<span className="text-lg text-gray-400 line-through">
|
|
||||||
{formatPrice(regularPrice)}
|
|
||||||
</span>
|
|
||||||
<span className="bg-red-600 text-white px-3 py-1.5 rounded-md text-sm font-bold">
|
|
||||||
SAVE {Math.round((1 - parseFloat(currentPrice) / parseFloat(regularPrice)) * 100)}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="text-2xl font-bold">{formatPrice(currentPrice)}</span>
|
|
||||||
)}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:**
|
|
||||||
- ✅ Price updates immediately when variation selected
|
|
||||||
- ✅ Sale price calculation works correctly
|
|
||||||
- ✅ Discount percentage shows accurately
|
|
||||||
- ✅ Fallback to base product price if no variation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Fix #5: Quantity Box Spacing ✅
|
|
||||||
|
|
||||||
**Problem:** Large empty space in quantity section looked unfinished
|
|
||||||
|
|
||||||
**Solution Implemented:**
|
|
||||||
```tsx
|
|
||||||
// BEFORE:
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center gap-4 border-2 p-3 w-fit">
|
|
||||||
<button>-</button>
|
|
||||||
<input />
|
|
||||||
<button>+</button>
|
|
||||||
</div>
|
|
||||||
{/* Large gap here */}
|
|
||||||
<button>Add to Cart</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
// AFTER:
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<span className="text-sm font-semibold">Quantity:</span>
|
|
||||||
<div className="flex items-center border-2 rounded-lg">
|
|
||||||
<button className="p-2.5">-</button>
|
|
||||||
<input className="w-14" />
|
|
||||||
<button className="p-2.5">+</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button>Add to Cart</button>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:**
|
|
||||||
- ✅ Tighter spacing (space-y-3 instead of space-y-4)
|
|
||||||
- ✅ Label added for clarity
|
|
||||||
- ✅ Smaller padding (p-2.5 instead of p-3)
|
|
||||||
- ✅ Narrower input (w-14 instead of w-16)
|
|
||||||
- ✅ Visual grouping improved
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 PENDING FIXES (Next Phase)
|
|
||||||
|
|
||||||
### Fix #6: Reviews Hierarchy (HIGH PRIORITY)
|
|
||||||
|
|
||||||
**Current:** Reviews collapsed in accordion at bottom
|
|
||||||
**Required:** Reviews prominent, auto-expanded, BEFORE description
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
```tsx
|
|
||||||
// Reorder sections
|
|
||||||
<div className="space-y-8">
|
|
||||||
{/* 1. Product Info (above fold) */}
|
|
||||||
<ProductInfo />
|
|
||||||
|
|
||||||
{/* 2. Reviews FIRST (auto-expanded) - Issue #6 */}
|
|
||||||
<div className="border-t-2 pt-8">
|
|
||||||
<div className="flex items-center justify-between mb-6">
|
|
||||||
<h2 className="text-2xl font-bold">Customer Reviews</h2>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Stars rating={4.8} />
|
|
||||||
<span className="font-bold">4.8</span>
|
|
||||||
<span className="text-gray-600">(127 reviews)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Show 3-5 recent reviews */}
|
|
||||||
<ReviewsList limit={5} />
|
|
||||||
<button>See all reviews →</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 3. Description (auto-expanded) */}
|
|
||||||
<div className="border-t-2 pt-8">
|
|
||||||
<h2 className="text-2xl font-bold mb-4">Product Description</h2>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: description }} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 4. Specifications (collapsed) */}
|
|
||||||
<Accordion title="Specifications">
|
|
||||||
<SpecTable />
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Research Support:**
|
|
||||||
- Spiegel Research: 270% conversion boost
|
|
||||||
- Reviews are #1 factor in purchase decisions
|
|
||||||
- Tokopedia shows reviews BEFORE description
|
|
||||||
- Shopify shows reviews auto-expanded
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Fix #7: Admin Appearance Menu (MEDIUM PRIORITY)
|
|
||||||
|
|
||||||
**Current:** No appearance settings
|
|
||||||
**Required:** Admin menu for store customization
|
|
||||||
|
|
||||||
**Implementation Plan:**
|
|
||||||
|
|
||||||
#### 1. Add to NavigationRegistry.php:
|
|
||||||
```php
|
|
||||||
private static function get_base_tree(): array {
|
|
||||||
return [
|
|
||||||
// ... existing sections ...
|
|
||||||
|
|
||||||
[
|
|
||||||
'key' => 'appearance',
|
|
||||||
'label' => __('Appearance', 'woonoow'),
|
|
||||||
'path' => '/appearance',
|
|
||||||
'icon' => 'palette',
|
|
||||||
'children' => [
|
|
||||||
['label' => __('Store Style', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/store-style'],
|
|
||||||
['label' => __('Trust Badges', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/trust-badges'],
|
|
||||||
['label' => __('Product Alerts', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/product-alerts'],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
// Settings comes after Appearance
|
|
||||||
[
|
|
||||||
'key' => 'settings',
|
|
||||||
// ...
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Create REST API Endpoints:
|
|
||||||
```php
|
|
||||||
// includes/Admin/Rest/AppearanceController.php
|
|
||||||
class AppearanceController {
|
|
||||||
public static function register() {
|
|
||||||
register_rest_route('wnw/v1', '/appearance/settings', [
|
|
||||||
'methods' => 'GET',
|
|
||||||
'callback' => [__CLASS__, 'get_settings'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
register_rest_route('wnw/v1', '/appearance/settings', [
|
|
||||||
'methods' => 'POST',
|
|
||||||
'callback' => [__CLASS__, 'update_settings'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function get_settings() {
|
|
||||||
return [
|
|
||||||
'layout_style' => get_option('wnw_layout_style', 'boxed'),
|
|
||||||
'container_width' => get_option('wnw_container_width', '1200'),
|
|
||||||
'trust_badges' => get_option('wnw_trust_badges', self::get_default_badges()),
|
|
||||||
'show_coupon_alert' => get_option('wnw_show_coupon_alert', true),
|
|
||||||
'show_stock_alert' => get_option('wnw_show_stock_alert', true),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function get_default_badges() {
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
'icon' => 'truck',
|
|
||||||
'icon_color' => '#10B981',
|
|
||||||
'title' => 'Free Shipping',
|
|
||||||
'description' => 'On orders over $50',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'icon' => 'rotate-ccw',
|
|
||||||
'icon_color' => '#3B82F6',
|
|
||||||
'title' => '30-Day Returns',
|
|
||||||
'description' => 'Money-back guarantee',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'icon' => 'shield-check',
|
|
||||||
'icon_color' => '#374151',
|
|
||||||
'title' => 'Secure Checkout',
|
|
||||||
'description' => 'SSL encrypted payment',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Create Admin SPA Pages:
|
|
||||||
```tsx
|
|
||||||
// admin-spa/src/pages/Appearance/StoreStyle.tsx
|
|
||||||
export default function StoreStyle() {
|
|
||||||
const [settings, setSettings] = useState({
|
|
||||||
layout_style: 'boxed',
|
|
||||||
container_width: '1200',
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Store Style</h1>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<label>Layout Style</label>
|
|
||||||
<select value={settings.layout_style}>
|
|
||||||
<option value="boxed">Boxed</option>
|
|
||||||
<option value="fullwidth">Full Width</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label>Container Width</label>
|
|
||||||
<select value={settings.container_width}>
|
|
||||||
<option value="1200">1200px (Standard)</option>
|
|
||||||
<option value="1400">1400px (Wide)</option>
|
|
||||||
<option value="custom">Custom</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// admin-spa/src/pages/Appearance/TrustBadges.tsx
|
|
||||||
export default function TrustBadges() {
|
|
||||||
const [badges, setBadges] = useState([]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Trust Badges</h1>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{badges.map((badge, index) => (
|
|
||||||
<div key={index} className="border p-4 rounded-lg">
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<label>Icon</label>
|
|
||||||
<IconPicker value={badge.icon} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Icon Color</label>
|
|
||||||
<ColorPicker value={badge.icon_color} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Title</label>
|
|
||||||
<input value={badge.title} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Description</label>
|
|
||||||
<input value={badge.description} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button onClick={() => removeBadge(index)}>Remove</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<button onClick={addBadge}>Add Badge</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. Update Customer SPA:
|
|
||||||
```tsx
|
|
||||||
// customer-spa/src/pages/Product/index.tsx
|
|
||||||
const { data: appearanceSettings } = useQuery({
|
|
||||||
queryKey: ['appearance-settings'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const response = await fetch('/wp-json/wnw/v1/appearance/settings');
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use settings
|
|
||||||
<Container className={appearanceSettings?.layout_style === 'fullwidth' ? 'max-w-full' : 'max-w-7xl'}>
|
|
||||||
{/* Trust Badges from settings */}
|
|
||||||
<div className="grid grid-cols-3 gap-2">
|
|
||||||
{appearanceSettings?.trust_badges?.map(badge => (
|
|
||||||
<div key={badge.title}>
|
|
||||||
<Icon name={badge.icon} color={badge.icon_color} />
|
|
||||||
<p>{badge.title}</p>
|
|
||||||
<p className="text-xs">{badge.description}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Implementation Status
|
|
||||||
|
|
||||||
### ✅ COMPLETED (Phase 1):
|
|
||||||
1. ✅ Above-the-fold optimization
|
|
||||||
2. ✅ Auto-select first variation
|
|
||||||
3. ✅ Variation image switching
|
|
||||||
4. ✅ Variation price updating
|
|
||||||
5. ✅ Quantity box spacing
|
|
||||||
|
|
||||||
### 🔄 IN PROGRESS (Phase 2):
|
|
||||||
6. ⏳ Reviews hierarchy reorder
|
|
||||||
7. ⏳ Admin Appearance menu
|
|
||||||
8. ⏳ Trust badges repeater
|
|
||||||
9. ⏳ Product alerts system
|
|
||||||
|
|
||||||
### 📋 PLANNED (Phase 3):
|
|
||||||
10. ⏳ Full-width layout option
|
|
||||||
11. ⏳ Fullscreen image lightbox
|
|
||||||
12. ⏳ Sticky bottom bar (mobile)
|
|
||||||
13. ⏳ Social proof enhancements
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Testing Results
|
|
||||||
|
|
||||||
### Manual Testing:
|
|
||||||
- ✅ Variable product loads with first variation selected
|
|
||||||
- ✅ Price updates when variation changed
|
|
||||||
- ✅ Image switches when variation changed
|
|
||||||
- ✅ All elements fit above fold on 1366x768
|
|
||||||
- ✅ Quantity selector has proper spacing
|
|
||||||
- ✅ Trust badges are compact and visible
|
|
||||||
- ✅ Responsive behavior works correctly
|
|
||||||
|
|
||||||
### Browser Testing:
|
|
||||||
- ✅ Chrome (desktop) - Working
|
|
||||||
- ✅ Firefox (desktop) - Working
|
|
||||||
- ✅ Safari (desktop) - Working
|
|
||||||
- ⏳ Mobile Safari (iOS) - Pending
|
|
||||||
- ⏳ Mobile Chrome (Android) - Pending
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📈 Expected Impact
|
|
||||||
|
|
||||||
### User Experience:
|
|
||||||
- ✅ No scroll required for CTA (1366x768)
|
|
||||||
- ✅ Immediate product state (auto-select)
|
|
||||||
- ✅ Accurate price/image (variation sync)
|
|
||||||
- ✅ Cleaner UI (spacing fixes)
|
|
||||||
- ⏳ Prominent social proof (reviews - pending)
|
|
||||||
|
|
||||||
### Conversion Rate:
|
|
||||||
- Current: Baseline
|
|
||||||
- Expected after Phase 1: +5-10%
|
|
||||||
- Expected after Phase 2 (reviews): +15-30%
|
|
||||||
- Expected after Phase 3 (full implementation): +20-35%
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Next Steps
|
|
||||||
|
|
||||||
### Immediate (This Session):
|
|
||||||
1. ✅ Implement critical product page fixes
|
|
||||||
2. ⏳ Create Appearance navigation section
|
|
||||||
3. ⏳ Create REST API endpoints
|
|
||||||
4. ⏳ Create Admin SPA pages
|
|
||||||
5. ⏳ Update Customer SPA to read settings
|
|
||||||
|
|
||||||
### Short Term (Next Session):
|
|
||||||
6. Reorder reviews hierarchy
|
|
||||||
7. Test on real devices
|
|
||||||
8. Performance optimization
|
|
||||||
9. Accessibility audit
|
|
||||||
|
|
||||||
### Medium Term (Future):
|
|
||||||
10. Fullscreen lightbox
|
|
||||||
11. Sticky bottom bar
|
|
||||||
12. Related products
|
|
||||||
13. Customer photo gallery
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** ✅ Phase 1 Complete (5/5 critical fixes)
|
|
||||||
**Quality:** ⭐⭐⭐⭐⭐
|
|
||||||
**Ready for:** Phase 2 Implementation
|
|
||||||
**Confidence:** HIGH (Research-backed + Tested)
|
|
||||||
@@ -1,331 +0,0 @@
|
|||||||
# Product Page Implementation Plan
|
|
||||||
|
|
||||||
## 🎯 What We Have (Current State)
|
|
||||||
|
|
||||||
### Backend (API):
|
|
||||||
✅ Product data with variations
|
|
||||||
✅ Product attributes
|
|
||||||
✅ Images array (featured + gallery)
|
|
||||||
✅ Variation images
|
|
||||||
✅ Price, stock status, SKU
|
|
||||||
✅ Description, short description
|
|
||||||
✅ Categories, tags
|
|
||||||
✅ Related products
|
|
||||||
|
|
||||||
### Frontend (Existing):
|
|
||||||
✅ Basic product page structure
|
|
||||||
✅ Image gallery with thumbnails (implemented but needs enhancement)
|
|
||||||
✅ Add to cart functionality
|
|
||||||
✅ Cart store (Zustand)
|
|
||||||
✅ Toast notifications
|
|
||||||
✅ Responsive layout
|
|
||||||
|
|
||||||
### Missing:
|
|
||||||
❌ Horizontal scrollable thumbnail slider
|
|
||||||
❌ Variation selector dropdowns
|
|
||||||
❌ Variation image auto-switching
|
|
||||||
❌ Reviews section
|
|
||||||
❌ Specifications table
|
|
||||||
❌ Shipping/Returns info
|
|
||||||
❌ Wishlist/Save feature
|
|
||||||
❌ Related products display
|
|
||||||
❌ Social proof elements
|
|
||||||
❌ Trust badges
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Implementation Priority (What Makes Sense Now)
|
|
||||||
|
|
||||||
### **Phase 1: Core Product Page (Implement Now)** ⭐
|
|
||||||
|
|
||||||
#### 1.1 Image Gallery Enhancement
|
|
||||||
- ✅ Horizontal scrollable thumbnail slider
|
|
||||||
- ✅ Arrow navigation for >4 images
|
|
||||||
- ✅ Active thumbnail highlight
|
|
||||||
- ✅ Click thumbnail to change main image
|
|
||||||
- ✅ Responsive (swipeable on mobile)
|
|
||||||
|
|
||||||
**Why:** Critical for user experience, especially for products with multiple images
|
|
||||||
|
|
||||||
#### 1.2 Variation Selector
|
|
||||||
- ✅ Dropdown for each attribute
|
|
||||||
- ✅ Auto-switch image when variation selected
|
|
||||||
- ✅ Update price based on variation
|
|
||||||
- ✅ Update stock status
|
|
||||||
- ✅ Disable Add to Cart if no variation selected
|
|
||||||
|
|
||||||
**Why:** Essential for variable products, directly impacts conversion
|
|
||||||
|
|
||||||
#### 1.3 Enhanced Buy Section
|
|
||||||
- ✅ Price display (regular + sale)
|
|
||||||
- ✅ Stock status with color coding
|
|
||||||
- ✅ Quantity selector (plus/minus buttons)
|
|
||||||
- ✅ Add to Cart button (with loading state)
|
|
||||||
- ✅ Product meta (SKU, categories)
|
|
||||||
|
|
||||||
**Why:** Core e-commerce functionality
|
|
||||||
|
|
||||||
#### 1.4 Product Information Sections
|
|
||||||
- ✅ Tabs for Description, Additional Info, Reviews
|
|
||||||
- ✅ Vertical layout (avoid horizontal tabs)
|
|
||||||
- ✅ Specifications table (from attributes)
|
|
||||||
- ✅ Expandable sections on mobile
|
|
||||||
|
|
||||||
**Why:** Users need detailed product info, research shows vertical > horizontal
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **Phase 2: Trust & Conversion (Next Sprint)** 🎯
|
|
||||||
|
|
||||||
#### 2.1 Reviews Section
|
|
||||||
- ⏳ Display existing WooCommerce reviews
|
|
||||||
- ⏳ Star rating display
|
|
||||||
- ⏳ Review count
|
|
||||||
- ⏳ Link to write review (WooCommerce native)
|
|
||||||
|
|
||||||
**Why:** Reviews are #2 most important content after images
|
|
||||||
|
|
||||||
#### 2.2 Trust Elements
|
|
||||||
- ⏳ Payment method icons
|
|
||||||
- ⏳ Secure checkout badge
|
|
||||||
- ⏳ Free shipping threshold
|
|
||||||
- ⏳ Return policy link
|
|
||||||
|
|
||||||
**Why:** Builds trust, reduces cart abandonment
|
|
||||||
|
|
||||||
#### 2.3 Related Products
|
|
||||||
- ⏳ Display related products (from API)
|
|
||||||
- ⏳ Horizontal carousel
|
|
||||||
- ⏳ Product cards
|
|
||||||
|
|
||||||
**Why:** Increases average order value
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **Phase 3: Advanced Features (Future)** 🚀
|
|
||||||
|
|
||||||
#### 3.1 Wishlist/Save for Later
|
|
||||||
- 📅 Add to wishlist button
|
|
||||||
- 📅 Wishlist page
|
|
||||||
- 📅 Persist across sessions
|
|
||||||
|
|
||||||
#### 3.2 Social Proof
|
|
||||||
- 📅 "X people viewing"
|
|
||||||
- 📅 "X sold today"
|
|
||||||
- 📅 Customer photos
|
|
||||||
|
|
||||||
#### 3.3 Enhanced Media
|
|
||||||
- 📅 Image zoom/lightbox
|
|
||||||
- 📅 Video support
|
|
||||||
- 📅 360° view
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ Phase 1 Implementation Details
|
|
||||||
|
|
||||||
### Component Structure:
|
|
||||||
```
|
|
||||||
Product/
|
|
||||||
├── index.tsx (main component)
|
|
||||||
├── components/
|
|
||||||
│ ├── ImageGallery.tsx
|
|
||||||
│ ├── ThumbnailSlider.tsx
|
|
||||||
│ ├── VariationSelector.tsx
|
|
||||||
│ ├── BuySection.tsx
|
|
||||||
│ ├── ProductTabs.tsx
|
|
||||||
│ ├── SpecificationTable.tsx
|
|
||||||
│ └── ProductMeta.tsx
|
|
||||||
```
|
|
||||||
|
|
||||||
### State Management:
|
|
||||||
```typescript
|
|
||||||
// Product page state
|
|
||||||
const [product, setProduct] = useState<Product | null>(null);
|
|
||||||
const [selectedImage, setSelectedImage] = useState<string>('');
|
|
||||||
const [selectedVariation, setSelectedVariation] = useState<any>(null);
|
|
||||||
const [selectedAttributes, setSelectedAttributes] = useState<Record<string, string>>({});
|
|
||||||
const [quantity, setQuantity] = useState(1);
|
|
||||||
const [activeTab, setActiveTab] = useState('description');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Features:
|
|
||||||
|
|
||||||
#### 1. Thumbnail Slider
|
|
||||||
```tsx
|
|
||||||
<div className="relative">
|
|
||||||
{/* Prev Arrow */}
|
|
||||||
<button onClick={scrollPrev} className="absolute left-0">
|
|
||||||
<ChevronLeft />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Scrollable Container */}
|
|
||||||
<div ref={sliderRef} className="flex overflow-x-auto scroll-smooth gap-2">
|
|
||||||
{images.map((img, i) => (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
onClick={() => setSelectedImage(img)}
|
|
||||||
className={selectedImage === img ? 'ring-2 ring-primary' : ''}
|
|
||||||
>
|
|
||||||
<img src={img} />
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Next Arrow */}
|
|
||||||
<button onClick={scrollNext} className="absolute right-0">
|
|
||||||
<ChevronRight />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Variation Selector
|
|
||||||
```tsx
|
|
||||||
{product.attributes?.map(attr => (
|
|
||||||
<div key={attr.name}>
|
|
||||||
<label>{attr.name}</label>
|
|
||||||
<select
|
|
||||||
value={selectedAttributes[attr.name] || ''}
|
|
||||||
onChange={(e) => handleAttributeChange(attr.name, e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="">Choose {attr.name}</option>
|
|
||||||
{attr.options.map(option => (
|
|
||||||
<option key={option} value={option}>{option}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Auto-Switch Variation Image
|
|
||||||
```typescript
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedVariation && selectedVariation.image) {
|
|
||||||
setSelectedImage(selectedVariation.image);
|
|
||||||
}
|
|
||||||
}, [selectedVariation]);
|
|
||||||
|
|
||||||
// Find matching variation
|
|
||||||
useEffect(() => {
|
|
||||||
if (product?.variations && Object.keys(selectedAttributes).length > 0) {
|
|
||||||
const variation = product.variations.find(v => {
|
|
||||||
return Object.entries(selectedAttributes).every(([key, value]) => {
|
|
||||||
return v.attributes[key] === value;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
setSelectedVariation(variation || null);
|
|
||||||
}
|
|
||||||
}, [selectedAttributes, product]);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📐 Layout Design
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ Breadcrumb: Home > Shop > Category > Product Name │
|
|
||||||
├──────────────────────┬──────────────────────────────────┤
|
|
||||||
│ │ Product Name (H1) │
|
|
||||||
│ Main Image │ ⭐⭐⭐⭐⭐ (24 reviews) │
|
|
||||||
│ (Large) │ │
|
|
||||||
│ │ $99.00 $79.00 (Save 20%) │
|
|
||||||
│ │ ✅ In Stock │
|
|
||||||
│ │ │
|
|
||||||
│ [Thumbnail Slider] │ Short description text... │
|
|
||||||
│ ◀ [img][img][img] ▶│ │
|
|
||||||
│ │ Color: [Dropdown ▼] │
|
|
||||||
│ │ Size: [Dropdown ▼] │
|
|
||||||
│ │ │
|
|
||||||
│ │ Quantity: [-] 1 [+] │
|
|
||||||
│ │ │
|
|
||||||
│ │ [🛒 Add to Cart] │
|
|
||||||
│ │ [♡ Add to Wishlist] │
|
|
||||||
│ │ │
|
|
||||||
│ │ 🔒 Secure Checkout │
|
|
||||||
│ │ 🚚 Free Shipping over $50 │
|
|
||||||
│ │ ↩️ 30-Day Returns │
|
|
||||||
├──────────────────────┴──────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ [Description] [Additional Info] [Reviews (24)] │
|
|
||||||
│ ───────────── │
|
|
||||||
│ │
|
|
||||||
│ Full product description here... │
|
|
||||||
│ • Feature 1 │
|
|
||||||
│ • Feature 2 │
|
|
||||||
│ │
|
|
||||||
├─────────────────────────────────────────────────────────┤
|
|
||||||
│ Related Products │
|
|
||||||
│ [Product] [Product] [Product] [Product] │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Styling Guidelines
|
|
||||||
|
|
||||||
### Colors:
|
|
||||||
```css
|
|
||||||
--price-sale: #DC2626 (red)
|
|
||||||
--stock-in: #10B981 (green)
|
|
||||||
--stock-low: #F59E0B (orange)
|
|
||||||
--stock-out: #EF4444 (red)
|
|
||||||
--primary-cta: var(--primary)
|
|
||||||
--border-active: var(--primary)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Spacing:
|
|
||||||
```css
|
|
||||||
--section-gap: 2rem
|
|
||||||
--element-gap: 1rem
|
|
||||||
--thumbnail-size: 80px
|
|
||||||
--thumbnail-gap: 0.5rem
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Acceptance Criteria
|
|
||||||
|
|
||||||
### Image Gallery:
|
|
||||||
- [ ] Thumbnails scroll horizontally
|
|
||||||
- [ ] Show 4 thumbnails at a time on desktop
|
|
||||||
- [ ] Arrow buttons appear when >4 images
|
|
||||||
- [ ] Active thumbnail has colored border
|
|
||||||
- [ ] Click thumbnail changes main image
|
|
||||||
- [ ] Swipeable on mobile
|
|
||||||
- [ ] Smooth scroll animation
|
|
||||||
|
|
||||||
### Variation Selector:
|
|
||||||
- [ ] Dropdown for each attribute
|
|
||||||
- [ ] "Choose an option" placeholder
|
|
||||||
- [ ] When variation selected, image auto-switches
|
|
||||||
- [ ] Price updates based on variation
|
|
||||||
- [ ] Stock status updates
|
|
||||||
- [ ] Add to Cart disabled until all attributes selected
|
|
||||||
- [ ] Clear error message if incomplete
|
|
||||||
|
|
||||||
### Buy Section:
|
|
||||||
- [ ] Sale price shown in red
|
|
||||||
- [ ] Regular price strikethrough
|
|
||||||
- [ ] Savings percentage/amount shown
|
|
||||||
- [ ] Stock status color-coded
|
|
||||||
- [ ] Quantity buttons work correctly
|
|
||||||
- [ ] Add to Cart shows loading state
|
|
||||||
- [ ] Success toast with cart preview
|
|
||||||
- [ ] Cart count updates in header
|
|
||||||
|
|
||||||
### Product Info:
|
|
||||||
- [ ] Tabs work correctly
|
|
||||||
- [ ] Description renders HTML
|
|
||||||
- [ ] Specifications show as table
|
|
||||||
- [ ] Mobile: sections collapsible
|
|
||||||
- [ ] Smooth scroll to reviews
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Ready to Implement
|
|
||||||
|
|
||||||
**Estimated Time:** 4-6 hours
|
|
||||||
**Priority:** HIGH
|
|
||||||
**Dependencies:** None (all APIs ready)
|
|
||||||
|
|
||||||
Let's build Phase 1 now! 🎯
|
|
||||||
@@ -1,545 +0,0 @@
|
|||||||
# Product Page Implementation - COMPLETE ✅
|
|
||||||
|
|
||||||
**Date:** November 26, 2025
|
|
||||||
**Reference:** STORE_UI_UX_GUIDE.md
|
|
||||||
**Status:** Implemented & Ready for Testing
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Implementation Summary
|
|
||||||
|
|
||||||
Successfully rebuilt the product page following the **STORE_UI_UX_GUIDE.md** standards, incorporating lessons from Tokopedia, Shopify, Amazon, and UX research.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ What Was Implemented
|
|
||||||
|
|
||||||
### 1. Typography Hierarchy (FIXED)
|
|
||||||
|
|
||||||
**Before:**
|
|
||||||
```
|
|
||||||
Price: 48-60px (TOO BIG)
|
|
||||||
Title: 24-32px
|
|
||||||
```
|
|
||||||
|
|
||||||
**After (per UI/UX Guide):**
|
|
||||||
```
|
|
||||||
Title: 28-32px (PRIMARY)
|
|
||||||
Price: 24px (SECONDARY)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rationale:** We're not a marketplace (like Tokopedia). Title should be primary hierarchy.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Image Gallery
|
|
||||||
|
|
||||||
#### Desktop:
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ [Main Image] │
|
|
||||||
│ (object-contain, padding) │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
[▭] [▭] [▭] [▭] [▭] ← Thumbnails (96-112px)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ Thumbnails: 96-112px (w-24 md:w-28)
|
|
||||||
- ✅ Horizontal scrollable
|
|
||||||
- ✅ Arrow navigation if >4 images
|
|
||||||
- ✅ Active thumbnail: Primary border + ring-4
|
|
||||||
- ✅ Click thumbnail → change main image
|
|
||||||
|
|
||||||
#### Mobile:
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ [Main Image] │
|
|
||||||
│ ● ○ ○ ○ ○ │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ Dots only (NO thumbnails)
|
|
||||||
- ✅ Active dot: Primary color, elongated (w-6)
|
|
||||||
- ✅ Inactive dots: Gray (w-2)
|
|
||||||
- ✅ Click dot → change image
|
|
||||||
- ✅ Swipe gesture supported (native)
|
|
||||||
|
|
||||||
**Rationale:** Convention (Amazon, Tokopedia, Shopify all use dots only on mobile)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Variation Selectors (PILLS)
|
|
||||||
|
|
||||||
**Before:**
|
|
||||||
```html
|
|
||||||
<select>
|
|
||||||
<option>Choose Color</option>
|
|
||||||
<option>Black</option>
|
|
||||||
<option>White</option>
|
|
||||||
</select>
|
|
||||||
```
|
|
||||||
|
|
||||||
**After:**
|
|
||||||
```html
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<button class="min-w-[44px] min-h-[44px] px-4 py-2 rounded-lg border-2">
|
|
||||||
Black
|
|
||||||
</button>
|
|
||||||
<button class="min-w-[44px] min-h-[44px] px-4 py-2 rounded-lg border-2">
|
|
||||||
White
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ All options visible at once
|
|
||||||
- ✅ Pills: min 44x44px (touch target)
|
|
||||||
- ✅ Active state: Primary background + white text
|
|
||||||
- ✅ Hover state: Border color change
|
|
||||||
- ✅ No dropdowns (better UX)
|
|
||||||
|
|
||||||
**Rationale:** Convention + Research align (Nielsen Norman Group)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Product Information Sections
|
|
||||||
|
|
||||||
**Pattern:** Vertical Accordions (NOT Horizontal Tabs)
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ▼ Product Description │ ← Auto-expanded
|
|
||||||
│ Full description text... │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ▶ Specifications │ ← Collapsed
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ▶ Customer Reviews │ ← Collapsed
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ Description: Auto-expanded on load
|
|
||||||
- ✅ Other sections: Collapsed by default
|
|
||||||
- ✅ Arrow icon: Rotates on expand/collapse
|
|
||||||
- ✅ Smooth animation
|
|
||||||
- ✅ Full-width clickable header
|
|
||||||
|
|
||||||
**Rationale:** Research (Baymard: 27% overlook horizontal tabs, only 8% overlook vertical)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. Specifications Table
|
|
||||||
|
|
||||||
**Pattern:** Scannable Two-Column Table
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ Material │ 100% Cotton │
|
|
||||||
│ Weight │ 250g │
|
|
||||||
│ Color │ Black, White, Gray │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ Label column: Bold, gray background
|
|
||||||
- ✅ Value column: Regular weight
|
|
||||||
- ✅ Padding: py-4 px-6
|
|
||||||
- ✅ Border: Bottom border on each row
|
|
||||||
|
|
||||||
**Rationale:** Research (scannable > plain table)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. Buy Section
|
|
||||||
|
|
||||||
**Structure:**
|
|
||||||
1. Product Title (H1) - PRIMARY
|
|
||||||
2. Price - SECONDARY (not overwhelming)
|
|
||||||
3. Stock Status (badge with icon)
|
|
||||||
4. Short Description
|
|
||||||
5. Variation Selectors (pills)
|
|
||||||
6. Quantity Selector
|
|
||||||
7. Add to Cart (prominent CTA)
|
|
||||||
8. Wishlist Button
|
|
||||||
9. Trust Badges
|
|
||||||
10. Product Meta
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ Title: text-2xl md:text-3xl
|
|
||||||
- ✅ Price: text-2xl (balanced)
|
|
||||||
- ✅ Stock badge: Inline-flex with icon
|
|
||||||
- ✅ Pills: 44x44px minimum
|
|
||||||
- ✅ Add to Cart: h-14, full width
|
|
||||||
- ✅ Trust badges: 3 items (shipping, returns, secure)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 Responsive Behavior
|
|
||||||
|
|
||||||
### Breakpoints:
|
|
||||||
```css
|
|
||||||
Mobile: < 768px
|
|
||||||
Desktop: >= 768px
|
|
||||||
```
|
|
||||||
|
|
||||||
### Image Gallery:
|
|
||||||
- **Mobile:** Dots only, swipe gesture
|
|
||||||
- **Desktop:** Thumbnails + arrows
|
|
||||||
|
|
||||||
### Layout:
|
|
||||||
- **Mobile:** Single column (grid-cols-1)
|
|
||||||
- **Desktop:** Two columns (grid-cols-2)
|
|
||||||
|
|
||||||
### Typography:
|
|
||||||
- **Title:** text-2xl md:text-3xl
|
|
||||||
- **Price:** text-2xl (same on both)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Design Tokens Used
|
|
||||||
|
|
||||||
### Colors:
|
|
||||||
```css
|
|
||||||
Primary: #222222
|
|
||||||
Sale Price: #DC2626 (red-600)
|
|
||||||
Success: #10B981 (green-600)
|
|
||||||
Error: #EF4444 (red-500)
|
|
||||||
Gray Scale: 50-900
|
|
||||||
```
|
|
||||||
|
|
||||||
### Spacing:
|
|
||||||
```css
|
|
||||||
Gap: gap-8 lg:gap-12
|
|
||||||
Padding: p-4, px-6, py-4
|
|
||||||
Margin: mb-4, mb-6
|
|
||||||
```
|
|
||||||
|
|
||||||
### Typography:
|
|
||||||
```css
|
|
||||||
Title: text-2xl md:text-3xl font-bold
|
|
||||||
Price: text-2xl font-bold
|
|
||||||
Body: text-base
|
|
||||||
Small: text-sm
|
|
||||||
```
|
|
||||||
|
|
||||||
### Touch Targets:
|
|
||||||
```css
|
|
||||||
Minimum: 44x44px (min-w-[44px] min-h-[44px])
|
|
||||||
Buttons: h-14 (Add to Cart)
|
|
||||||
Pills: 44x44px minimum
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Checklist (Per UI/UX Guide)
|
|
||||||
|
|
||||||
### Above the Fold:
|
|
||||||
- [x] Breadcrumb navigation
|
|
||||||
- [x] Product title (H1)
|
|
||||||
- [x] Price display (with sale if applicable)
|
|
||||||
- [x] Stock status badge
|
|
||||||
- [x] Main product image
|
|
||||||
- [x] Image navigation (thumbnails/dots)
|
|
||||||
- [x] Variation selectors (pills)
|
|
||||||
- [x] Quantity selector
|
|
||||||
- [x] Add to Cart button
|
|
||||||
- [x] Trust badges
|
|
||||||
|
|
||||||
### Below the Fold:
|
|
||||||
- [x] Product description (auto-expanded)
|
|
||||||
- [x] Specifications table (collapsed)
|
|
||||||
- [x] Reviews section (collapsed)
|
|
||||||
- [x] Product meta (SKU, categories)
|
|
||||||
- [ ] Related products (future)
|
|
||||||
|
|
||||||
### Mobile Specific:
|
|
||||||
- [x] Dots for image navigation
|
|
||||||
- [x] Large touch targets (44x44px)
|
|
||||||
- [x] Responsive text sizes
|
|
||||||
- [x] Collapsible sections
|
|
||||||
- [ ] Sticky bottom bar (future)
|
|
||||||
|
|
||||||
### Desktop Specific:
|
|
||||||
- [x] Thumbnails for image navigation
|
|
||||||
- [x] Hover states
|
|
||||||
- [x] Larger layout (2-column grid)
|
|
||||||
- [x] Arrow navigation for thumbnails
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Technical Implementation
|
|
||||||
|
|
||||||
### Key Components:
|
|
||||||
```tsx
|
|
||||||
// State management
|
|
||||||
const [selectedImage, setSelectedImage] = useState<string>();
|
|
||||||
const [selectedVariation, setSelectedVariation] = useState<any>(null);
|
|
||||||
const [selectedAttributes, setSelectedAttributes] = useState<Record<string, string>>({});
|
|
||||||
const [quantity, setQuantity] = useState(1);
|
|
||||||
const [activeTab, setActiveTab] = useState<'description' | 'additional' | 'reviews' | ''>('description');
|
|
||||||
|
|
||||||
// Image navigation
|
|
||||||
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
|
||||||
const scrollThumbnails = (direction: 'left' | 'right') => { ... };
|
|
||||||
|
|
||||||
// Variation handling
|
|
||||||
const handleAttributeChange = (attributeName: string, value: string) => { ... };
|
|
||||||
|
|
||||||
// Auto-switch variation image
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedVariation && selectedVariation.image) {
|
|
||||||
setSelectedImage(selectedVariation.image);
|
|
||||||
}
|
|
||||||
}, [selectedVariation]);
|
|
||||||
```
|
|
||||||
|
|
||||||
### CSS Utilities:
|
|
||||||
```css
|
|
||||||
/* Hide scrollbar */
|
|
||||||
.scrollbar-hide::-webkit-scrollbar { display: none; }
|
|
||||||
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
|
|
||||||
|
|
||||||
/* Responsive visibility */
|
|
||||||
.hidden.md\\:block { display: none; }
|
|
||||||
@media (min-width: 768px) { .hidden.md\\:block { display: block; } }
|
|
||||||
|
|
||||||
/* Image override */
|
|
||||||
.\\!h-full { height: 100% !important; }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Key Decisions Made
|
|
||||||
|
|
||||||
### 1. Dots vs Thumbnails on Mobile
|
|
||||||
- **Decision:** Dots only (no thumbnails)
|
|
||||||
- **Rationale:** Convention (Amazon, Tokopedia, Shopify)
|
|
||||||
- **Evidence:** User screenshot of Amazon confirmed this
|
|
||||||
|
|
||||||
### 2. Pills vs Dropdowns
|
|
||||||
- **Decision:** Pills/buttons
|
|
||||||
- **Rationale:** Convention + Research align
|
|
||||||
- **Evidence:** Nielsen Norman Group guidelines
|
|
||||||
|
|
||||||
### 3. Title vs Price Hierarchy
|
|
||||||
- **Decision:** Title > Price
|
|
||||||
- **Rationale:** Context (we're not a marketplace)
|
|
||||||
- **Evidence:** Shopify (our closer analog) does this
|
|
||||||
|
|
||||||
### 4. Tabs vs Accordions
|
|
||||||
- **Decision:** Vertical accordions
|
|
||||||
- **Rationale:** Research (27% overlook tabs)
|
|
||||||
- **Evidence:** Baymard Institute study
|
|
||||||
|
|
||||||
### 5. Description Auto-Expand
|
|
||||||
- **Decision:** Auto-expanded on load
|
|
||||||
- **Rationale:** Don't hide primary content
|
|
||||||
- **Evidence:** Shopify does this
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Before vs After
|
|
||||||
|
|
||||||
### Typography:
|
|
||||||
```
|
|
||||||
BEFORE:
|
|
||||||
Title: 24-32px
|
|
||||||
Price: 48-60px (TOO BIG)
|
|
||||||
|
|
||||||
AFTER:
|
|
||||||
Title: 28-32px (PRIMARY)
|
|
||||||
Price: 24px (SECONDARY)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Variations:
|
|
||||||
```
|
|
||||||
BEFORE:
|
|
||||||
<select> dropdown (hides options)
|
|
||||||
|
|
||||||
AFTER:
|
|
||||||
Pills/buttons (all visible)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Image Gallery:
|
|
||||||
```
|
|
||||||
BEFORE:
|
|
||||||
Mobile: Thumbnails (redundant with dots)
|
|
||||||
Desktop: Thumbnails
|
|
||||||
|
|
||||||
AFTER:
|
|
||||||
Mobile: Dots only (convention)
|
|
||||||
Desktop: Thumbnails (standard)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Information Sections:
|
|
||||||
```
|
|
||||||
BEFORE:
|
|
||||||
Horizontal tabs (27% overlook)
|
|
||||||
|
|
||||||
AFTER:
|
|
||||||
Vertical accordions (8% overlook)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Performance Optimizations
|
|
||||||
|
|
||||||
### Images:
|
|
||||||
- ✅ Lazy loading (React Query)
|
|
||||||
- ✅ object-contain (shows full product)
|
|
||||||
- ✅ !h-full (overrides WooCommerce)
|
|
||||||
- ✅ Alt text for accessibility
|
|
||||||
|
|
||||||
### Loading States:
|
|
||||||
- ✅ Skeleton loading
|
|
||||||
- ✅ Smooth transitions
|
|
||||||
- ✅ No layout shift
|
|
||||||
|
|
||||||
### Code Splitting:
|
|
||||||
- ✅ Route-based splitting
|
|
||||||
- ✅ Component lazy loading
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ♿ Accessibility
|
|
||||||
|
|
||||||
### WCAG 2.1 AA Compliance:
|
|
||||||
- ✅ Semantic HTML (h1, nav, main)
|
|
||||||
- ✅ Alt text for images
|
|
||||||
- ✅ ARIA labels for icons
|
|
||||||
- ✅ Keyboard navigation
|
|
||||||
- ✅ Focus indicators
|
|
||||||
- ✅ Color contrast (4.5:1 minimum)
|
|
||||||
- ✅ Touch targets (44x44px)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 References
|
|
||||||
|
|
||||||
### Research Sources:
|
|
||||||
- Baymard Institute - Product Page UX
|
|
||||||
- Nielsen Norman Group - Variation Guidelines
|
|
||||||
- WCAG 2.1 - Accessibility Standards
|
|
||||||
|
|
||||||
### Convention Sources:
|
|
||||||
- Amazon - Image gallery patterns
|
|
||||||
- Tokopedia - Mobile UX patterns
|
|
||||||
- Shopify - E-commerce patterns
|
|
||||||
|
|
||||||
### Internal Documents:
|
|
||||||
- STORE_UI_UX_GUIDE.md (living document)
|
|
||||||
- PRODUCT_PAGE_ANALYSIS_REPORT.md (research)
|
|
||||||
- PRODUCT_PAGE_DECISION_FRAMEWORK.md (philosophy)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Testing Checklist
|
|
||||||
|
|
||||||
### Manual Testing:
|
|
||||||
- [ ] Test simple product (no variations)
|
|
||||||
- [ ] Test variable product (with variations)
|
|
||||||
- [ ] Test product with 1 image
|
|
||||||
- [ ] Test product with 5+ images
|
|
||||||
- [ ] Test variation image switching
|
|
||||||
- [ ] Test add to cart (simple)
|
|
||||||
- [ ] Test add to cart (variable)
|
|
||||||
- [ ] Test quantity selector
|
|
||||||
- [ ] Test thumbnail slider (desktop)
|
|
||||||
- [ ] Test dots navigation (mobile)
|
|
||||||
- [ ] Test accordion expand/collapse
|
|
||||||
- [ ] Test breadcrumb navigation
|
|
||||||
- [ ] Test mobile responsiveness
|
|
||||||
- [ ] Test loading states
|
|
||||||
- [ ] Test error states
|
|
||||||
|
|
||||||
### Browser Testing:
|
|
||||||
- [ ] Chrome (desktop)
|
|
||||||
- [ ] Firefox (desktop)
|
|
||||||
- [ ] Safari (desktop)
|
|
||||||
- [ ] Edge (desktop)
|
|
||||||
- [ ] Mobile Safari (iOS)
|
|
||||||
- [ ] Mobile Chrome (Android)
|
|
||||||
|
|
||||||
### Accessibility Testing:
|
|
||||||
- [ ] Keyboard navigation
|
|
||||||
- [ ] Screen reader (NVDA/JAWS)
|
|
||||||
- [ ] Color contrast
|
|
||||||
- [ ] Touch target sizes
|
|
||||||
- [ ] Focus indicators
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 Success Metrics
|
|
||||||
|
|
||||||
### User Experience:
|
|
||||||
- ✅ Clear visual hierarchy (Title > Price)
|
|
||||||
- ✅ Familiar patterns (dots, pills, accordions)
|
|
||||||
- ✅ No cognitive overload
|
|
||||||
- ✅ Fast interaction (no dropdowns)
|
|
||||||
- ✅ Mobile-optimized (dots, large targets)
|
|
||||||
|
|
||||||
### Technical:
|
|
||||||
- ✅ Follows UI/UX Guide
|
|
||||||
- ✅ Research-backed decisions
|
|
||||||
- ✅ Convention-compliant
|
|
||||||
- ✅ Accessible (WCAG 2.1 AA)
|
|
||||||
- ✅ Performant (lazy loading)
|
|
||||||
|
|
||||||
### Business:
|
|
||||||
- ✅ Conversion-optimized layout
|
|
||||||
- ✅ Trust badges prominent
|
|
||||||
- ✅ Clear CTAs
|
|
||||||
- ✅ Reduced friction (pills > dropdowns)
|
|
||||||
- ✅ Better mobile UX
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 Next Steps
|
|
||||||
|
|
||||||
### HIGH PRIORITY:
|
|
||||||
1. Test on real devices (mobile + desktop)
|
|
||||||
2. Verify variation image switching
|
|
||||||
3. Test with real product data
|
|
||||||
4. Verify add to cart flow
|
|
||||||
5. Check responsive breakpoints
|
|
||||||
|
|
||||||
### MEDIUM PRIORITY:
|
|
||||||
6. Add fullscreen lightbox for images
|
|
||||||
7. Implement sticky bottom bar (mobile)
|
|
||||||
8. Add social proof (reviews count)
|
|
||||||
9. Add estimated delivery info
|
|
||||||
10. Optimize images (WebP)
|
|
||||||
|
|
||||||
### LOW PRIORITY:
|
|
||||||
11. Add related products section
|
|
||||||
12. Add customer photo gallery
|
|
||||||
13. Add size guide (if applicable)
|
|
||||||
14. Add wishlist functionality
|
|
||||||
15. Add product comparison
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Files Changed
|
|
||||||
|
|
||||||
### Modified:
|
|
||||||
- `customer-spa/src/pages/Product/index.tsx` (complete rebuild)
|
|
||||||
|
|
||||||
### Created:
|
|
||||||
- `STORE_UI_UX_GUIDE.md` (living document)
|
|
||||||
- `PRODUCT_PAGE_ANALYSIS_REPORT.md` (research)
|
|
||||||
- `PRODUCT_PAGE_DECISION_FRAMEWORK.md` (philosophy)
|
|
||||||
- `PRODUCT_PAGE_IMPLEMENTATION_COMPLETE.md` (this file)
|
|
||||||
|
|
||||||
### No Changes Needed:
|
|
||||||
- `customer-spa/src/index.css` (scrollbar-hide already exists)
|
|
||||||
- Backend APIs (already provide correct data)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** ✅ COMPLETE
|
|
||||||
**Quality:** ⭐⭐⭐⭐⭐
|
|
||||||
**Ready for:** Testing & Review
|
|
||||||
**Follows:** STORE_UI_UX_GUIDE.md v1.0
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
# Product Page - Research-Backed Fixes Applied
|
|
||||||
|
|
||||||
## 🎯 Issues Fixed
|
|
||||||
|
|
||||||
### 1. ❌ Horizontal Tabs → ✅ Vertical Collapsible Sections
|
|
||||||
|
|
||||||
**Research Finding (PRODUCT_PAGE_SOP.md):**
|
|
||||||
> "Avoid Horizontal Tabs - 27% of users overlook horizontal tabs entirely"
|
|
||||||
> "Vertical Collapsed Sections - Only 8% overlook content (vs 27% for tabs)"
|
|
||||||
|
|
||||||
**What Was Wrong:**
|
|
||||||
- Used WooCommerce-style horizontal tabs (Description | Additional Info | Reviews)
|
|
||||||
- 27% of users would miss this content
|
|
||||||
|
|
||||||
**What Was Fixed:**
|
|
||||||
```tsx
|
|
||||||
// BEFORE: Horizontal Tabs
|
|
||||||
<div className="flex gap-8">
|
|
||||||
<button>Description</button>
|
|
||||||
<button>Additional Information</button>
|
|
||||||
<button>Reviews</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
// AFTER: Vertical Collapsible Sections
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="border rounded-lg">
|
|
||||||
<button className="w-full flex justify-between p-5 bg-gray-50">
|
|
||||||
<h2>Product Description</h2>
|
|
||||||
<svg>↓</svg>
|
|
||||||
</button>
|
|
||||||
{expanded && <div className="p-6">Content</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ Only 8% overlook rate (vs 27%)
|
|
||||||
- ✅ Better mobile UX
|
|
||||||
- ✅ Scannable layout
|
|
||||||
- ✅ Clear visual hierarchy
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. ❌ Plain Table → ✅ Scannable Specifications Table
|
|
||||||
|
|
||||||
**Research Finding (PRODUCT_PAGE_SOP.md):**
|
|
||||||
> "Format: Scannable table"
|
|
||||||
> "Two-column layout (Label | Value)"
|
|
||||||
> "Grouped by category"
|
|
||||||
|
|
||||||
**What Was Wrong:**
|
|
||||||
- Plain table with minimal styling
|
|
||||||
- Hard to scan quickly
|
|
||||||
|
|
||||||
**What Was Fixed:**
|
|
||||||
```tsx
|
|
||||||
// BEFORE: Plain table
|
|
||||||
<table className="w-full">
|
|
||||||
<tbody>
|
|
||||||
<tr className="border-b">
|
|
||||||
<td className="py-3">{attr.name}</td>
|
|
||||||
<td className="py-3">{attr.options}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
// AFTER: Scannable table with visual hierarchy
|
|
||||||
<table className="w-full">
|
|
||||||
<tbody>
|
|
||||||
<tr className="border-b last:border-0">
|
|
||||||
<td className="py-4 px-6 font-semibold text-gray-900 bg-gray-50 w-1/3">
|
|
||||||
{attr.name}
|
|
||||||
</td>
|
|
||||||
<td className="py-4 px-6 text-gray-700">
|
|
||||||
{attr.options}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ Gray background on labels for contrast
|
|
||||||
- ✅ Bold labels for scannability
|
|
||||||
- ✅ More padding for readability
|
|
||||||
- ✅ Clear visual separation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. ❌ Mobile Width Overflow → ✅ Responsive Layout
|
|
||||||
|
|
||||||
**What Was Wrong:**
|
|
||||||
- Thumbnail slider caused horizontal scroll on mobile
|
|
||||||
- Trust badges text overflowed
|
|
||||||
- No width constraints
|
|
||||||
|
|
||||||
**What Was Fixed:**
|
|
||||||
|
|
||||||
#### Thumbnail Slider:
|
|
||||||
```tsx
|
|
||||||
// BEFORE:
|
|
||||||
<div className="relative">
|
|
||||||
<div className="flex gap-3 overflow-x-auto px-10">
|
|
||||||
|
|
||||||
// AFTER:
|
|
||||||
<div className="relative w-full overflow-hidden">
|
|
||||||
<div className="flex gap-3 overflow-x-auto px-10">
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Trust Badges:
|
|
||||||
```tsx
|
|
||||||
// BEFORE:
|
|
||||||
<div>
|
|
||||||
<p className="font-semibold">Free Shipping</p>
|
|
||||||
<p className="text-gray-600">On orders over $50</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
// AFTER:
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<p className="font-semibold truncate">Free Shipping</p>
|
|
||||||
<p className="text-gray-600 text-xs truncate">On orders over $50</p>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ No horizontal scroll on mobile
|
|
||||||
- ✅ Text truncates gracefully
|
|
||||||
- ✅ Proper flex layout
|
|
||||||
- ✅ Smaller text on mobile (text-xs)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. ✅ Image Height Override (!h-full)
|
|
||||||
|
|
||||||
**What Was Required:**
|
|
||||||
- Override WooCommerce default image styles
|
|
||||||
- Ensure consistent image heights
|
|
||||||
|
|
||||||
**What Was Fixed:**
|
|
||||||
```tsx
|
|
||||||
// Applied to ALL images:
|
|
||||||
className="w-full !h-full object-cover"
|
|
||||||
|
|
||||||
// Locations:
|
|
||||||
1. Main product image
|
|
||||||
2. Thumbnail images
|
|
||||||
3. Empty state placeholder
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ Overrides WooCommerce CSS
|
|
||||||
- ✅ Consistent aspect ratios
|
|
||||||
- ✅ No layout shift
|
|
||||||
- ✅ Proper image display
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Before vs After Comparison
|
|
||||||
|
|
||||||
### Layout Structure:
|
|
||||||
|
|
||||||
**BEFORE (WooCommerce Clone):**
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ Image Gallery │
|
|
||||||
│ Product Info │
|
|
||||||
│ │
|
|
||||||
│ [Description] [Additional] [Reviews]│ ← Horizontal Tabs (27% overlook)
|
|
||||||
│ ───────────── │
|
|
||||||
│ Content here... │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**AFTER (Research-Backed):**
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ Image Gallery (larger thumbnails) │
|
|
||||||
│ Product Info (prominent price) │
|
|
||||||
│ Trust Badges (shipping, returns) │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────────────────────┐ │
|
|
||||||
│ │ ▼ Product Description │ │ ← Vertical Sections (8% overlook)
|
|
||||||
│ └─────────────────────────────────┘ │
|
|
||||||
│ ┌─────────────────────────────────┐ │
|
|
||||||
│ │ ▼ Specifications (scannable) │ │
|
|
||||||
│ └─────────────────────────────────┘ │
|
|
||||||
│ ┌─────────────────────────────────┐ │
|
|
||||||
│ │ ▼ Customer Reviews │ │
|
|
||||||
│ └─────────────────────────────────┘ │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Research Compliance Checklist
|
|
||||||
|
|
||||||
### From PRODUCT_PAGE_SOP.md:
|
|
||||||
|
|
||||||
- [x] **Avoid Horizontal Tabs** - Now using vertical sections
|
|
||||||
- [x] **Scannable Table** - Specifications have clear visual hierarchy
|
|
||||||
- [x] **Mobile-First** - Fixed width overflow issues
|
|
||||||
- [x] **Prominent Price** - 4xl-5xl font size in highlighted box
|
|
||||||
- [x] **Trust Badges** - Free shipping, returns, secure checkout
|
|
||||||
- [x] **Stock Status** - Large badge with icon
|
|
||||||
- [x] **Larger Thumbnails** - 96-112px (was 80px)
|
|
||||||
- [x] **Sale Badge** - Floating on image
|
|
||||||
- [x] **Image Override** - !h-full on all images
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 Mobile Optimizations Applied
|
|
||||||
|
|
||||||
1. **Responsive Text:**
|
|
||||||
- Trust badges: `text-xs` on mobile
|
|
||||||
- Price: `text-4xl md:text-5xl`
|
|
||||||
- Title: `text-2xl md:text-3xl`
|
|
||||||
|
|
||||||
2. **Overflow Prevention:**
|
|
||||||
- Thumbnail slider: `w-full overflow-hidden`
|
|
||||||
- Trust badges: `min-w-0 flex-1 truncate`
|
|
||||||
- Tables: Proper padding and spacing
|
|
||||||
|
|
||||||
3. **Touch Targets:**
|
|
||||||
- Quantity buttons: `p-3` (larger)
|
|
||||||
- Collapsible sections: `p-5` (full width)
|
|
||||||
- Add to Cart: `h-14` (prominent)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Performance Impact
|
|
||||||
|
|
||||||
### User Experience:
|
|
||||||
- **27% → 8%** content overlook rate (tabs → vertical)
|
|
||||||
- **Faster scanning** with visual hierarchy
|
|
||||||
- **Better mobile UX** with no overflow
|
|
||||||
- **Higher conversion** with prominent CTAs
|
|
||||||
|
|
||||||
### Technical:
|
|
||||||
- ✅ No layout shift
|
|
||||||
- ✅ Smooth animations
|
|
||||||
- ✅ Proper responsive breakpoints
|
|
||||||
- ✅ Accessible collapsible sections
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Key Takeaways
|
|
||||||
|
|
||||||
### What We Learned:
|
|
||||||
1. **Research > Assumptions** - Following Baymard Institute data beats copying WooCommerce
|
|
||||||
2. **Vertical > Horizontal** - 3x better visibility for vertical sections
|
|
||||||
3. **Mobile Constraints** - Always test for overflow on small screens
|
|
||||||
4. **Visual Hierarchy** - Scannable tables beat plain tables
|
|
||||||
|
|
||||||
### What Makes This Different:
|
|
||||||
- ❌ Not a WooCommerce clone
|
|
||||||
- ✅ Research-backed design decisions
|
|
||||||
- ✅ Industry best practices
|
|
||||||
- ✅ Conversion-optimized layout
|
|
||||||
- ✅ Mobile-first approach
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 Result
|
|
||||||
|
|
||||||
A product page that:
|
|
||||||
- Follows Baymard Institute 2025 UX research
|
|
||||||
- Reduces content overlook from 27% to 8%
|
|
||||||
- Works perfectly on mobile (no overflow)
|
|
||||||
- Has clear visual hierarchy
|
|
||||||
- Prioritizes conversion elements
|
|
||||||
- Overrides WooCommerce styles properly
|
|
||||||
|
|
||||||
**Status:** ✅ Research-Compliant | ✅ Mobile-Optimized | ✅ Conversion-Focused
|
|
||||||
@@ -1,436 +0,0 @@
|
|||||||
# Product Page Design SOP - Industry Best Practices
|
|
||||||
|
|
||||||
**Document Version:** 1.0
|
|
||||||
**Last Updated:** November 26, 2025
|
|
||||||
**Purpose:** Guide for building industry-standard product pages in Customer SPA
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Executive Summary
|
|
||||||
|
|
||||||
This SOP consolidates research-backed best practices for e-commerce product pages based on Baymard Institute's 2025 UX research and industry standards. Since Customer SPA is not fully customizable by end-users, we must implement the best practices as defaults.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Core Principles
|
|
||||||
|
|
||||||
1. **Avoid Horizontal Tabs** - 27% of users overlook horizontal tabs entirely
|
|
||||||
2. **Vertical Collapsed Sections** - Only 8% overlook content (vs 27% for tabs)
|
|
||||||
3. **Images Are Critical** - After images, reviews are the most important content
|
|
||||||
4. **Trust & Social Proof** - Essential for conversion
|
|
||||||
5. **Mobile-First** - But optimize desktop experience separately
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📐 Layout Structure (Priority Order)
|
|
||||||
|
|
||||||
### 1. **Hero Section** (Above the Fold)
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────┐
|
|
||||||
│ Breadcrumb │
|
|
||||||
├──────────────┬──────────────────────────┤
|
|
||||||
│ │ Product Title │
|
|
||||||
│ Product │ Price (with sale) │
|
|
||||||
│ Images │ Rating & Reviews Count │
|
|
||||||
│ Gallery │ Stock Status │
|
|
||||||
│ │ Short Description │
|
|
||||||
│ │ Variations Selector │
|
|
||||||
│ │ Quantity │
|
|
||||||
│ │ Add to Cart Button │
|
|
||||||
│ │ Wishlist/Save │
|
|
||||||
│ │ Trust Badges │
|
|
||||||
└──────────────┴──────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. **Product Information** (Below the Fold - Vertical Sections)
|
|
||||||
- ✅ Full Description (expandable)
|
|
||||||
- ✅ Specifications/Attributes (scannable table)
|
|
||||||
- ✅ Shipping & Returns Info
|
|
||||||
- ✅ Size Guide (if applicable)
|
|
||||||
- ✅ Reviews Section
|
|
||||||
- ✅ Related Products
|
|
||||||
- ✅ Recently Viewed
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🖼️ Image Gallery Requirements
|
|
||||||
|
|
||||||
### Must-Have Features:
|
|
||||||
1. **Main Image Display**
|
|
||||||
- Large, zoomable image
|
|
||||||
- High resolution (min 1200px width)
|
|
||||||
- Aspect ratio: 1:1 or 4:3
|
|
||||||
|
|
||||||
2. **Thumbnail Slider**
|
|
||||||
- Horizontal scrollable
|
|
||||||
- 4-6 visible thumbnails
|
|
||||||
- Active thumbnail highlighted
|
|
||||||
- Arrow navigation for >4 images
|
|
||||||
- Touch/swipe enabled on mobile
|
|
||||||
|
|
||||||
3. **Image Types Required:**
|
|
||||||
- ✅ Product on white background (default)
|
|
||||||
- ✅ "In Scale" images (with reference object/person)
|
|
||||||
- ✅ "Human Model" images (for wearables)
|
|
||||||
- ✅ Lifestyle/context images
|
|
||||||
- ✅ Detail shots (close-ups)
|
|
||||||
- ✅ 360° view (optional but recommended)
|
|
||||||
|
|
||||||
4. **Variation Images:**
|
|
||||||
- Each variation should have its own image
|
|
||||||
- Auto-switch main image when variation selected
|
|
||||||
- Variation image highlighted in thumbnail slider
|
|
||||||
|
|
||||||
### Image Gallery Interaction:
|
|
||||||
```javascript
|
|
||||||
// User Flow:
|
|
||||||
1. Click thumbnail → Change main image
|
|
||||||
2. Select variation → Auto-switch to variation image
|
|
||||||
3. Click main image → Open lightbox/zoom
|
|
||||||
4. Swipe thumbnails → Scroll horizontally
|
|
||||||
5. Hover thumbnail → Preview in main (desktop)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛒 Buy Section Elements
|
|
||||||
|
|
||||||
### Required Elements (in order):
|
|
||||||
1. **Product Title** - H1, clear, descriptive
|
|
||||||
2. **Price Display:**
|
|
||||||
- Regular price (strikethrough if on sale)
|
|
||||||
- Sale price (highlighted in red/primary)
|
|
||||||
- Savings amount/percentage
|
|
||||||
- Unit price (for bulk items)
|
|
||||||
|
|
||||||
3. **Rating & Reviews:**
|
|
||||||
- Star rating (visual)
|
|
||||||
- Number of reviews (clickable → scroll to reviews)
|
|
||||||
- "Write a Review" link
|
|
||||||
|
|
||||||
4. **Stock Status:**
|
|
||||||
- ✅ In Stock (green)
|
|
||||||
- ⚠️ Low Stock (orange, show quantity)
|
|
||||||
- ❌ Out of Stock (red, "Notify Me" option)
|
|
||||||
|
|
||||||
5. **Variation Selector:**
|
|
||||||
- Dropdown for each attribute
|
|
||||||
- Visual swatches for colors
|
|
||||||
- Size chart link (for apparel)
|
|
||||||
- Clear labels
|
|
||||||
- Disabled options grayed out
|
|
||||||
|
|
||||||
6. **Quantity Selector:**
|
|
||||||
- Plus/minus buttons
|
|
||||||
- Number input
|
|
||||||
- Min/max validation
|
|
||||||
- Bulk pricing info (if applicable)
|
|
||||||
|
|
||||||
7. **Action Buttons:**
|
|
||||||
- **Primary:** Add to Cart (large, prominent)
|
|
||||||
- **Secondary:** Buy Now (optional)
|
|
||||||
- **Tertiary:** Add to Wishlist/Save for Later
|
|
||||||
|
|
||||||
8. **Trust Elements:**
|
|
||||||
- Security badges (SSL, payment methods)
|
|
||||||
- Free shipping threshold
|
|
||||||
- Return policy summary
|
|
||||||
- Warranty info
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Product Information Sections
|
|
||||||
|
|
||||||
### 1. Description Section
|
|
||||||
```
|
|
||||||
Format: Vertical collapsed/expandable
|
|
||||||
- Short description (2-3 sentences) always visible
|
|
||||||
- Full description expandable
|
|
||||||
- Rich text formatting
|
|
||||||
- Bullet points for features
|
|
||||||
- Video embed support
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Specifications/Attributes
|
|
||||||
```
|
|
||||||
Format: Scannable table
|
|
||||||
- Two-column layout (Label | Value)
|
|
||||||
- Grouped by category
|
|
||||||
- Tooltips for technical terms
|
|
||||||
- Expandable for long lists
|
|
||||||
- Copy-to-clipboard for specs
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Shipping & Returns
|
|
||||||
```
|
|
||||||
Always visible near buy section:
|
|
||||||
- Estimated delivery date
|
|
||||||
- Shipping cost calculator
|
|
||||||
- Return policy link
|
|
||||||
- Free shipping threshold
|
|
||||||
- International shipping info
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Size Guide (Apparel/Footwear)
|
|
||||||
```
|
|
||||||
- Modal/drawer popup
|
|
||||||
- Size chart table
|
|
||||||
- Measurement instructions
|
|
||||||
- Fit guide (slim, regular, loose)
|
|
||||||
- Model measurements
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⭐ Reviews Section
|
|
||||||
|
|
||||||
### Must-Have Features:
|
|
||||||
1. **Review Summary:**
|
|
||||||
- Overall rating (large)
|
|
||||||
- Rating distribution (5-star breakdown)
|
|
||||||
- Total review count
|
|
||||||
- Verified purchase badge
|
|
||||||
|
|
||||||
2. **Review Filters:**
|
|
||||||
- Sort by: Most Recent, Highest Rating, Lowest Rating, Most Helpful
|
|
||||||
- Filter by: Rating (1-5 stars), Verified Purchase, With Photos
|
|
||||||
|
|
||||||
3. **Individual Review Display:**
|
|
||||||
- Reviewer name (or anonymous)
|
|
||||||
- Rating (stars)
|
|
||||||
- Date
|
|
||||||
- Verified purchase badge
|
|
||||||
- Review text
|
|
||||||
- Helpful votes (thumbs up/down)
|
|
||||||
- Seller response (if any)
|
|
||||||
- Review images (clickable gallery)
|
|
||||||
|
|
||||||
4. **Review Submission:**
|
|
||||||
- Star rating (required)
|
|
||||||
- Title (optional)
|
|
||||||
- Review text (required, min 50 chars)
|
|
||||||
- Photo upload (optional)
|
|
||||||
- Recommend product (yes/no)
|
|
||||||
- Fit guide (for apparel)
|
|
||||||
|
|
||||||
5. **Review Images Gallery:**
|
|
||||||
- Navigate all customer photos
|
|
||||||
- Filter reviews by "with photos"
|
|
||||||
- Lightbox view
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎁 Promotions & Offers
|
|
||||||
|
|
||||||
### Display Locations:
|
|
||||||
1. **Product Badge** (on image)
|
|
||||||
- "Sale" / "New" / "Limited"
|
|
||||||
- Percentage off
|
|
||||||
- Free shipping
|
|
||||||
|
|
||||||
2. **Price Section:**
|
|
||||||
- Coupon code field
|
|
||||||
- Auto-apply available coupons
|
|
||||||
- Bulk discount tiers
|
|
||||||
- Member pricing
|
|
||||||
|
|
||||||
3. **Sticky Banner** (optional):
|
|
||||||
- Site-wide promotions
|
|
||||||
- Flash sales countdown
|
|
||||||
- Free shipping threshold
|
|
||||||
|
|
||||||
### Coupon Integration:
|
|
||||||
```
|
|
||||||
- Auto-detect applicable coupons
|
|
||||||
- One-click apply
|
|
||||||
- Show savings in cart preview
|
|
||||||
- Stackable coupons indicator
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 Trust & Social Proof Elements
|
|
||||||
|
|
||||||
### 1. Trust Badges (Near Add to Cart):
|
|
||||||
- Payment security (SSL, PCI)
|
|
||||||
- Payment methods accepted
|
|
||||||
- Money-back guarantee
|
|
||||||
- Secure checkout badge
|
|
||||||
|
|
||||||
### 2. Social Proof:
|
|
||||||
- "X people viewing this now"
|
|
||||||
- "X sold in last 24 hours"
|
|
||||||
- "X people added to cart today"
|
|
||||||
- Customer photos/UGC
|
|
||||||
- Influencer endorsements
|
|
||||||
|
|
||||||
### 3. Credibility Indicators:
|
|
||||||
- Brand certifications
|
|
||||||
- Awards & recognition
|
|
||||||
- Press mentions
|
|
||||||
- Expert reviews
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 Mobile Optimization
|
|
||||||
|
|
||||||
### Mobile-Specific Considerations:
|
|
||||||
1. **Image Gallery:**
|
|
||||||
- Swipeable main image
|
|
||||||
- Thumbnail strip below (horizontal scroll)
|
|
||||||
- Pinch to zoom
|
|
||||||
|
|
||||||
2. **Sticky Add to Cart:**
|
|
||||||
- Fixed bottom bar
|
|
||||||
- Price + Add to Cart always visible
|
|
||||||
- Collapse on scroll down, expand on scroll up
|
|
||||||
|
|
||||||
3. **Collapsed Sections:**
|
|
||||||
- All info sections collapsed by default
|
|
||||||
- Tap to expand
|
|
||||||
- Smooth animations
|
|
||||||
|
|
||||||
4. **Touch Targets:**
|
|
||||||
- Min 44x44px for buttons
|
|
||||||
- Adequate spacing between elements
|
|
||||||
- Large, thumb-friendly controls
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Visual Design Guidelines
|
|
||||||
|
|
||||||
### Typography:
|
|
||||||
- **Product Title:** 28-32px, bold
|
|
||||||
- **Price:** 24-28px, bold
|
|
||||||
- **Body Text:** 14-16px
|
|
||||||
- **Labels:** 12-14px, medium weight
|
|
||||||
|
|
||||||
### Colors:
|
|
||||||
- **Primary CTA:** High contrast, brand color
|
|
||||||
- **Sale Price:** Red (#DC2626) or brand accent
|
|
||||||
- **Success:** Green (#10B981)
|
|
||||||
- **Warning:** Orange (#F59E0B)
|
|
||||||
- **Error:** Red (#EF4444)
|
|
||||||
|
|
||||||
### Spacing:
|
|
||||||
- Section padding: 24-32px
|
|
||||||
- Element spacing: 12-16px
|
|
||||||
- Button padding: 12px 24px
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 Interaction Patterns
|
|
||||||
|
|
||||||
### 1. Variation Selection:
|
|
||||||
```javascript
|
|
||||||
// When user selects variation:
|
|
||||||
1. Update price
|
|
||||||
2. Update stock status
|
|
||||||
3. Switch main image
|
|
||||||
4. Update SKU
|
|
||||||
5. Highlight variation image in gallery
|
|
||||||
6. Enable/disable Add to Cart
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Add to Cart:
|
|
||||||
```javascript
|
|
||||||
// On Add to Cart click:
|
|
||||||
1. Validate selection (all variations selected)
|
|
||||||
2. Show loading state
|
|
||||||
3. Add to cart (API call)
|
|
||||||
4. Show success toast with cart preview
|
|
||||||
5. Update cart count in header
|
|
||||||
6. Offer "View Cart" or "Continue Shopping"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Image Gallery:
|
|
||||||
```javascript
|
|
||||||
// Image interactions:
|
|
||||||
1. Click thumbnail → Change main image
|
|
||||||
2. Click main image → Open lightbox
|
|
||||||
3. Swipe main image → Next/prev image
|
|
||||||
4. Hover thumbnail → Preview (desktop)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Performance Metrics
|
|
||||||
|
|
||||||
### Key Metrics to Track:
|
|
||||||
- Time to First Contentful Paint (< 1.5s)
|
|
||||||
- Largest Contentful Paint (< 2.5s)
|
|
||||||
- Image load time (< 1s)
|
|
||||||
- Add to Cart conversion rate
|
|
||||||
- Bounce rate
|
|
||||||
- Time on page
|
|
||||||
- Scroll depth
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Implementation Checklist
|
|
||||||
|
|
||||||
### Phase 1: Core Features (MVP)
|
|
||||||
- [ ] Responsive image gallery with thumbnails
|
|
||||||
- [ ] Horizontal scrollable thumbnail slider
|
|
||||||
- [ ] Variation selector with image switching
|
|
||||||
- [ ] Price display with sale pricing
|
|
||||||
- [ ] Stock status indicator
|
|
||||||
- [ ] Quantity selector
|
|
||||||
- [ ] Add to Cart button
|
|
||||||
- [ ] Product description (expandable)
|
|
||||||
- [ ] Specifications table
|
|
||||||
- [ ] Breadcrumb navigation
|
|
||||||
|
|
||||||
### Phase 2: Enhanced Features
|
|
||||||
- [ ] Reviews section with filtering
|
|
||||||
- [ ] Review submission form
|
|
||||||
- [ ] Related products carousel
|
|
||||||
- [ ] Wishlist/Save for later
|
|
||||||
- [ ] Share buttons
|
|
||||||
- [ ] Shipping calculator
|
|
||||||
- [ ] Size guide modal
|
|
||||||
- [ ] Image zoom/lightbox
|
|
||||||
|
|
||||||
### Phase 3: Advanced Features
|
|
||||||
- [ ] 360° product view
|
|
||||||
- [ ] Video integration
|
|
||||||
- [ ] Live chat integration
|
|
||||||
- [ ] Recently viewed products
|
|
||||||
- [ ] Personalized recommendations
|
|
||||||
- [ ] Social proof notifications
|
|
||||||
- [ ] Coupon auto-apply
|
|
||||||
- [ ] Bulk pricing display
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚫 What to Avoid
|
|
||||||
|
|
||||||
1. ❌ Horizontal tabs for content
|
|
||||||
2. ❌ Hiding critical info below the fold
|
|
||||||
3. ❌ Auto-playing videos
|
|
||||||
4. ❌ Intrusive popups
|
|
||||||
5. ❌ Tiny product images
|
|
||||||
6. ❌ Unclear variation selectors
|
|
||||||
7. ❌ Hidden shipping costs
|
|
||||||
8. ❌ Complicated checkout process
|
|
||||||
9. ❌ Fake urgency/scarcity
|
|
||||||
10. ❌ Too many CTAs (decision paralysis)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 References
|
|
||||||
|
|
||||||
- Baymard Institute - Product Page UX 2025
|
|
||||||
- Nielsen Norman Group - E-commerce UX
|
|
||||||
- Shopify - Product Page Best Practices
|
|
||||||
- ConvertCart - Social Proof Guidelines
|
|
||||||
- Google - Mobile Page Speed Guidelines
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 Version History
|
|
||||||
|
|
||||||
| Version | Date | Changes |
|
|
||||||
|---------|------|---------|
|
|
||||||
| 1.0 | 2025-11-26 | Initial SOP creation based on industry research |
|
|
||||||
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
# Rajaongkir Integration with WooNooW SPA
|
|
||||||
|
|
||||||
This guide explains how to integrate Rajaongkir's destination selector with WooNooW's customer checkout SPA.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Before using this integration:
|
|
||||||
|
|
||||||
1. **Rajaongkir Plugin Installed & Active**
|
|
||||||
2. **WooCommerce Shipping Zone Configured**
|
|
||||||
- Go to: WC → Settings → Shipping → Zones
|
|
||||||
- Add Rajaongkir method to your Indonesia zone
|
|
||||||
3. **Valid API Key** (Check in Rajaongkir settings)
|
|
||||||
4. **Couriers Selected** (In Rajaongkir settings)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Code Snippet
|
|
||||||
|
|
||||||
Add this to **Code Snippets** or **WPCodebox**:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Rajaongkir Bridge for WooNooW SPA Checkout
|
|
||||||
*
|
|
||||||
* Enables searchable destination field in WooNooW checkout
|
|
||||||
* and bridges data to Rajaongkir plugin.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 1. REST API Endpoint: Search destinations via Rajaongkir API
|
|
||||||
// ============================================================
|
|
||||||
add_action('rest_api_init', function() {
|
|
||||||
register_rest_route('woonoow/v1', '/rajaongkir/destinations', [
|
|
||||||
'methods' => 'GET',
|
|
||||||
'callback' => 'woonoow_rajaongkir_search_destinations',
|
|
||||||
'permission_callback' => '__return_true',
|
|
||||||
'args' => [
|
|
||||||
'search' => [
|
|
||||||
'required' => false,
|
|
||||||
'type' => 'string',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
function woonoow_rajaongkir_search_destinations($request) {
|
|
||||||
$search = sanitize_text_field($request->get_param('search') ?? '');
|
|
||||||
|
|
||||||
if (strlen($search) < 3) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Rajaongkir plugin is active
|
|
||||||
if (!class_exists('Cekongkir_API')) {
|
|
||||||
return new WP_Error('rajaongkir_missing', 'Rajaongkir plugin not active', ['status' => 400]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use Rajaongkir's API class for the search
|
|
||||||
// NOTE: Method is search_destination_api() not search_destination()
|
|
||||||
$api = Cekongkir_API::get_instance();
|
|
||||||
$results = $api->search_destination_api($search);
|
|
||||||
|
|
||||||
if (is_wp_error($results)) {
|
|
||||||
error_log('Rajaongkir search error: ' . $results->get_error_message());
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_array($results)) {
|
|
||||||
error_log('Rajaongkir search returned non-array: ' . print_r($results, true));
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format for WooNooW's SearchableSelect component
|
|
||||||
$formatted = [];
|
|
||||||
foreach ($results as $r) {
|
|
||||||
$formatted[] = [
|
|
||||||
'value' => (string) ($r['id'] ?? ''),
|
|
||||||
'label' => $r['label'] ?? $r['text'] ?? '',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit results
|
|
||||||
return array_slice($formatted, 0, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 2. Add destination field and hide redundant fields for Indonesia
|
|
||||||
// The destination_id from Rajaongkir contains province/city/subdistrict
|
|
||||||
// ============================================================
|
|
||||||
add_filter('woocommerce_checkout_fields', function($fields) {
|
|
||||||
// Check if Rajaongkir is active
|
|
||||||
if (!class_exists('Cekongkir_API')) {
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if store sells to Indonesia (check allowed countries)
|
|
||||||
$allowed = WC()->countries->get_allowed_countries();
|
|
||||||
if (!isset($allowed['ID'])) {
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Indonesia is the ONLY allowed country
|
|
||||||
$indonesia_only = count($allowed) === 1 && isset($allowed['ID']);
|
|
||||||
|
|
||||||
// If Indonesia only, hide country/state/city fields (Rajaongkir destination has all this)
|
|
||||||
if ($indonesia_only) {
|
|
||||||
// Hide billing fields
|
|
||||||
if (isset($fields['billing']['billing_country'])) {
|
|
||||||
$fields['billing']['billing_country']['type'] = 'hidden';
|
|
||||||
$fields['billing']['billing_country']['default'] = 'ID';
|
|
||||||
$fields['billing']['billing_country']['required'] = false;
|
|
||||||
}
|
|
||||||
if (isset($fields['billing']['billing_state'])) {
|
|
||||||
$fields['billing']['billing_state']['type'] = 'hidden';
|
|
||||||
$fields['billing']['billing_state']['required'] = false;
|
|
||||||
}
|
|
||||||
if (isset($fields['billing']['billing_city'])) {
|
|
||||||
$fields['billing']['billing_city']['type'] = 'hidden';
|
|
||||||
$fields['billing']['billing_city']['required'] = false;
|
|
||||||
}
|
|
||||||
if (isset($fields['billing']['billing_postcode'])) {
|
|
||||||
$fields['billing']['billing_postcode']['type'] = 'hidden';
|
|
||||||
$fields['billing']['billing_postcode']['required'] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide shipping fields
|
|
||||||
if (isset($fields['shipping']['shipping_country'])) {
|
|
||||||
$fields['shipping']['shipping_country']['type'] = 'hidden';
|
|
||||||
$fields['shipping']['shipping_country']['default'] = 'ID';
|
|
||||||
$fields['shipping']['shipping_country']['required'] = false;
|
|
||||||
}
|
|
||||||
if (isset($fields['shipping']['shipping_state'])) {
|
|
||||||
$fields['shipping']['shipping_state']['type'] = 'hidden';
|
|
||||||
$fields['shipping']['shipping_state']['required'] = false;
|
|
||||||
}
|
|
||||||
if (isset($fields['shipping']['shipping_city'])) {
|
|
||||||
$fields['shipping']['shipping_city']['type'] = 'hidden';
|
|
||||||
$fields['shipping']['shipping_city']['required'] = false;
|
|
||||||
}
|
|
||||||
if (isset($fields['shipping']['shipping_postcode'])) {
|
|
||||||
$fields['shipping']['shipping_postcode']['type'] = 'hidden';
|
|
||||||
$fields['shipping']['shipping_postcode']['required'] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destination field definition (reused for billing and shipping)
|
|
||||||
$destination_field = [
|
|
||||||
'type' => 'searchable_select',
|
|
||||||
'label' => __('Destination (Province, City, Subdistrict)', 'woonoow'),
|
|
||||||
'required' => $indonesia_only, // Required if Indonesia only
|
|
||||||
'priority' => 85,
|
|
||||||
'class' => ['form-row-wide'],
|
|
||||||
'placeholder' => __('Search destination...', 'woonoow'),
|
|
||||||
// WooNooW-specific: API endpoint configuration
|
|
||||||
// NOTE: Path is relative to /wp-json/woonoow/v1
|
|
||||||
'search_endpoint' => '/rajaongkir/destinations',
|
|
||||||
'search_param' => 'search',
|
|
||||||
'min_chars' => 3,
|
|
||||||
// Custom attribute to indicate this is for Indonesia only
|
|
||||||
'custom_attributes' => [
|
|
||||||
'data-show-for-country' => 'ID',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add to billing (used when "Ship to different address" is NOT checked)
|
|
||||||
$fields['billing']['billing_destination_id'] = $destination_field;
|
|
||||||
|
|
||||||
// Add to shipping (used when "Ship to different address" IS checked)
|
|
||||||
$fields['shipping']['shipping_destination_id'] = $destination_field;
|
|
||||||
|
|
||||||
return $fields;
|
|
||||||
}, 20); // Priority 20 to run after Rajaongkir's own filter
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 3. Bridge WooNooW shipping data to Rajaongkir session
|
|
||||||
// Sets destination_id in WC session for Rajaongkir to use
|
|
||||||
// ============================================================
|
|
||||||
add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
|
|
||||||
// Check if Rajaongkir is active
|
|
||||||
if (!class_exists('Cekongkir_API')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For Indonesia-only stores, always set country to ID
|
|
||||||
$allowed = WC()->countries->get_allowed_countries();
|
|
||||||
$indonesia_only = count($allowed) === 1 && isset($allowed['ID']);
|
|
||||||
|
|
||||||
if ($indonesia_only) {
|
|
||||||
WC()->customer->set_shipping_country('ID');
|
|
||||||
WC()->customer->set_billing_country('ID');
|
|
||||||
} elseif (!empty($shipping['country'])) {
|
|
||||||
WC()->customer->set_shipping_country($shipping['country']);
|
|
||||||
WC()->customer->set_billing_country($shipping['country']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only process Rajaongkir for Indonesia
|
|
||||||
$country = $shipping['country'] ?? WC()->customer->get_shipping_country();
|
|
||||||
if ($country !== 'ID') {
|
|
||||||
// Clear destination for non-Indonesia
|
|
||||||
WC()->session->__unset('selected_destination_id');
|
|
||||||
WC()->session->__unset('selected_destination_label');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get destination_id from shipping data (various possible keys)
|
|
||||||
$destination_id = $shipping['destination_id']
|
|
||||||
?? $shipping['shipping_destination_id']
|
|
||||||
?? $shipping['billing_destination_id']
|
|
||||||
?? null;
|
|
||||||
|
|
||||||
if (empty($destination_id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set session for Rajaongkir
|
|
||||||
WC()->session->set('selected_destination_id', intval($destination_id));
|
|
||||||
|
|
||||||
// Also set label if provided
|
|
||||||
$label = $shipping['destination_label']
|
|
||||||
?? $shipping['shipping_destination_id_label']
|
|
||||||
?? $shipping['billing_destination_id_label']
|
|
||||||
?? '';
|
|
||||||
if ($label) {
|
|
||||||
WC()->session->set('selected_destination_label', sanitize_text_field($label));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear shipping cache to force recalculation
|
|
||||||
WC()->session->set('shipping_for_package_0', false);
|
|
||||||
|
|
||||||
}, 10, 2);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
### 1. Test the API Endpoint
|
|
||||||
|
|
||||||
After adding the snippet:
|
|
||||||
```
|
|
||||||
GET /wp-json/woonoow/v1/rajaongkir/destinations?search=bandung
|
|
||||||
```
|
|
||||||
|
|
||||||
Should return:
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{"value": "1234", "label": "Jawa Barat, Bandung, Kota"},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Test Checkout Flow
|
|
||||||
|
|
||||||
1. Add product to cart
|
|
||||||
2. Go to SPA checkout: `/store/checkout`
|
|
||||||
3. Set country to Indonesia
|
|
||||||
4. "Destination" field should appear
|
|
||||||
5. Type 2+ characters to search
|
|
||||||
6. Select a destination
|
|
||||||
7. Rajaongkir rates should appear
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### API returns empty?
|
|
||||||
|
|
||||||
Check `debug.log` for errors:
|
|
||||||
```php
|
|
||||||
// Added logging in the search function
|
|
||||||
error_log('Rajaongkir search error: ...');
|
|
||||||
```
|
|
||||||
|
|
||||||
Common issues:
|
|
||||||
- Invalid Rajaongkir API key
|
|
||||||
- Rajaongkir plugin not active
|
|
||||||
- API quota exceeded
|
|
||||||
|
|
||||||
### Field not appearing?
|
|
||||||
|
|
||||||
1. Ensure snippet is active
|
|
||||||
2. Check if store sells to Indonesia
|
|
||||||
3. Check browser console for JS errors
|
|
||||||
|
|
||||||
### Rajaongkir rates not showing?
|
|
||||||
|
|
||||||
1. Check session is set:
|
|
||||||
```php
|
|
||||||
add_action('woonoow/shipping/before_calculate', function($shipping) {
|
|
||||||
error_log('Shipping data: ' . print_r($shipping, true));
|
|
||||||
}, 5);
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Check Rajaongkir is enabled in shipping zone
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Known Limitations
|
|
||||||
|
|
||||||
1. **Field visibility**: Currently field always shows for checkout. Future improvement: hide in React when country ≠ ID.
|
|
||||||
|
|
||||||
2. **Session timing**: Must select destination before calculating shipping.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Documentation
|
|
||||||
|
|
||||||
- [SHIPPING_INTEGRATION.md](SHIPPING_INTEGRATION.md) - General shipping patterns
|
|
||||||
- [HOOKS_REGISTRY.md](HOOKS_REGISTRY.md) - WooNooW hooks reference
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
# Product Page Redirect Debugging
|
|
||||||
|
|
||||||
## Issue
|
|
||||||
Direct access to product URLs like `/product/edukasi-anak` redirects to `/shop`.
|
|
||||||
|
|
||||||
## Debugging Steps
|
|
||||||
|
|
||||||
### 1. Check Console Logs
|
|
||||||
Open browser console and navigate to: `https://woonoow.local/product/edukasi-anak`
|
|
||||||
|
|
||||||
Look for these logs:
|
|
||||||
```
|
|
||||||
Product Component - Slug: edukasi-anak
|
|
||||||
Product Component - Current URL: https://woonoow.local/product/edukasi-anak
|
|
||||||
Product Query - Starting fetch for slug: edukasi-anak
|
|
||||||
Product API Response: {...}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Possible Causes
|
|
||||||
|
|
||||||
#### A. WordPress Canonical Redirect
|
|
||||||
WordPress might be redirecting the URL because it doesn't recognize `/product/` as a valid route.
|
|
||||||
|
|
||||||
**Solution:** Disable canonical redirects for SPA pages.
|
|
||||||
|
|
||||||
#### B. React Router Not Matching
|
|
||||||
The route might not be matching correctly.
|
|
||||||
|
|
||||||
**Check:** Does the slug parameter get extracted?
|
|
||||||
|
|
||||||
#### C. WooCommerce Redirect
|
|
||||||
WooCommerce might be redirecting to shop page.
|
|
||||||
|
|
||||||
**Check:** Is `is_product()` returning true?
|
|
||||||
|
|
||||||
#### D. 404 Handling
|
|
||||||
WordPress might be treating it as 404 and redirecting.
|
|
||||||
|
|
||||||
**Check:** Is the page returning 404 status?
|
|
||||||
|
|
||||||
### 3. Quick Tests
|
|
||||||
|
|
||||||
#### Test 1: Check if Template Loads
|
|
||||||
Add this to `spa-full-page.php` at the top:
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
error_log('SPA Template Loaded - is_product: ' . (is_product() ? 'yes' : 'no'));
|
|
||||||
error_log('Current URL: ' . $_SERVER['REQUEST_URI']);
|
|
||||||
?>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Test 2: Check React Router
|
|
||||||
Add this to `App.tsx`:
|
|
||||||
```tsx
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('Current Path:', window.location.pathname);
|
|
||||||
console.log('Is Product Route:', window.location.pathname.includes('/product/'));
|
|
||||||
}, []);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Test 3: Check if Assets Load
|
|
||||||
Open Network tab and check if `customer-spa.js` loads on product page.
|
|
||||||
|
|
||||||
### 4. Likely Solution
|
|
||||||
|
|
||||||
The issue is probably WordPress canonical redirect. Add this to `TemplateOverride.php`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
public static function init() {
|
|
||||||
// ... existing code ...
|
|
||||||
|
|
||||||
// Disable canonical redirects for SPA pages
|
|
||||||
add_filter('redirect_canonical', [__CLASS__, 'disable_canonical_redirect'], 10, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function disable_canonical_redirect($redirect_url, $requested_url) {
|
|
||||||
$settings = get_option('woonoow_customer_spa_settings', []);
|
|
||||||
$mode = isset($settings['mode']) ? $settings['mode'] : 'disabled';
|
|
||||||
|
|
||||||
if ($mode === 'full') {
|
|
||||||
// Check if this is a SPA route
|
|
||||||
$spa_routes = ['/product/', '/cart', '/checkout', '/my-account'];
|
|
||||||
|
|
||||||
foreach ($spa_routes as $route) {
|
|
||||||
if (strpos($requested_url, $route) !== false) {
|
|
||||||
return false; // Disable redirect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $redirect_url;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Alternative: Use Hash Router
|
|
||||||
|
|
||||||
If canonical redirects can't be disabled, use HashRouter instead:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// In App.tsx
|
|
||||||
import { HashRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
// Change BrowserRouter to HashRouter
|
|
||||||
<HashRouter>
|
|
||||||
{/* routes */}
|
|
||||||
</HashRouter>
|
|
||||||
```
|
|
||||||
|
|
||||||
URLs will be: `https://woonoow.local/#/product/edukasi-anak`
|
|
||||||
|
|
||||||
This works because everything after `#` is client-side only.
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Add console logs (already done)
|
|
||||||
2. Test and check console
|
|
||||||
3. If slug is undefined → React Router issue
|
|
||||||
4. If slug is defined but redirects → WordPress redirect issue
|
|
||||||
5. Apply appropriate fix
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
# WooNooW Shipping Bridge Pattern
|
|
||||||
|
|
||||||
This document describes a generic pattern for integrating any external shipping API with WooNooW's SPA checkout.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
WooNooW provides hooks and endpoints that allow any shipping plugin to:
|
|
||||||
1. **Register custom checkout fields** (searchable selects, dropdowns, etc.)
|
|
||||||
2. **Bridge data to the plugin's session/API** before shipping calculation
|
|
||||||
3. **Display live rates** from external APIs
|
|
||||||
|
|
||||||
This pattern is NOT specific to Rajaongkir - it can be used for any shipping provider.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ WooNooW Customer SPA │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
│ 1. Checkout loads → calls /checkout/fields │
|
|
||||||
│ 2. Renders custom fields (e.g., searchable destination) │
|
|
||||||
│ 3. User fills form → calls /checkout/shipping-rates │
|
|
||||||
│ 4. Hook triggers → shipping plugin calculates rates │
|
|
||||||
│ 5. Rates displayed → user selects → order submitted │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Your Bridge Snippet │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
│ • woocommerce_checkout_fields → Add custom fields │
|
|
||||||
│ • register_rest_route → API endpoint for field data │
|
|
||||||
│ • woonoow/shipping/before_calculate → Set session/data │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Shipping Plugin (Any) │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
│ • Reads from WC session or customer data │
|
|
||||||
│ • Calls external API (Rajaongkir, Sicepat, JNE, etc.) │
|
|
||||||
│ • Returns rates via get_rates_for_package() │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Generic Bridge Template
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* [PROVIDER_NAME] Bridge for WooNooW SPA Checkout
|
|
||||||
*
|
|
||||||
* Replace [PROVIDER_NAME], [PROVIDER_CLASS], and [PROVIDER_SESSION_KEY]
|
|
||||||
* with your shipping plugin's specifics.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 1. REST API Endpoint: Search/fetch data from provider
|
|
||||||
// ============================================================
|
|
||||||
add_action('rest_api_init', function() {
|
|
||||||
register_rest_route('woonoow/v1', '/[provider]/search', [
|
|
||||||
'methods' => 'GET',
|
|
||||||
'callback' => 'woonoow_[provider]_search',
|
|
||||||
'permission_callback' => '__return_true', // Public for customer use
|
|
||||||
'args' => [
|
|
||||||
'search' => ['type' => 'string', 'required' => false],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
function woonoow_[provider]_search($request) {
|
|
||||||
$search = sanitize_text_field($request->get_param('search') ?? '');
|
|
||||||
|
|
||||||
if (strlen($search) < 3) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if plugin is active
|
|
||||||
if (!class_exists('[PROVIDER_CLASS]')) {
|
|
||||||
return new WP_Error('[provider]_missing', 'Plugin not active', ['status' => 400]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call provider's API
|
|
||||||
// $results = [PROVIDER_CLASS]::search($search);
|
|
||||||
|
|
||||||
// Format for WooNooW's SearchableSelect
|
|
||||||
$formatted = [];
|
|
||||||
foreach ($results as $r) {
|
|
||||||
$formatted[] = [
|
|
||||||
'value' => (string) $r['id'],
|
|
||||||
'label' => $r['name'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_slice($formatted, 0, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 2. Add custom field(s) to checkout
|
|
||||||
// ============================================================
|
|
||||||
add_filter('woocommerce_checkout_fields', function($fields) {
|
|
||||||
if (!class_exists('[PROVIDER_CLASS]')) {
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
$custom_field = [
|
|
||||||
'type' => 'searchable_select', // or 'select', 'text'
|
|
||||||
'label' => __('[Field Label]', 'woonoow'),
|
|
||||||
'required' => true,
|
|
||||||
'priority' => 85,
|
|
||||||
'class' => ['form-row-wide'],
|
|
||||||
'placeholder' => __('Search...', 'woonoow'),
|
|
||||||
'search_endpoint' => '/[provider]/search', // Relative to /wp-json/woonoow/v1
|
|
||||||
'search_param' => 'search',
|
|
||||||
'min_chars' => 3,
|
|
||||||
];
|
|
||||||
|
|
||||||
$fields['billing']['billing_[provider]_field'] = $custom_field;
|
|
||||||
$fields['shipping']['shipping_[provider]_field'] = $custom_field;
|
|
||||||
|
|
||||||
return $fields;
|
|
||||||
}, 20);
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 3. Bridge data to provider before shipping calculation
|
|
||||||
// ============================================================
|
|
||||||
add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
|
|
||||||
if (!class_exists('[PROVIDER_CLASS]')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get custom field value from shipping data
|
|
||||||
$field_value = $shipping['[provider]_field']
|
|
||||||
?? $shipping['shipping_[provider]_field']
|
|
||||||
?? $shipping['billing_[provider]_field']
|
|
||||||
?? null;
|
|
||||||
|
|
||||||
if (empty($field_value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set in WC session for the shipping plugin to use
|
|
||||||
WC()->session->set('[PROVIDER_SESSION_KEY]', $field_value);
|
|
||||||
|
|
||||||
// Clear shipping cache to force recalculation
|
|
||||||
WC()->session->set('shipping_for_package_0', false);
|
|
||||||
|
|
||||||
}, 10, 2);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Supported Field Types
|
|
||||||
|
|
||||||
| Type | Description | Use Case |
|
|
||||||
|------|-------------|----------|
|
|
||||||
| `searchable_select` | Dropdown with API search | Destinations, locations, service points |
|
|
||||||
| `select` | Static dropdown | Service types, delivery options |
|
|
||||||
| `text` | Free text input | Reference numbers, notes |
|
|
||||||
| `hidden` | Hidden field | Default values, auto-set data |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## WooNooW Hooks Reference
|
|
||||||
|
|
||||||
| Hook | Type | Description |
|
|
||||||
|------|------|-------------|
|
|
||||||
| `woonoow/shipping/before_calculate` | Action | Called before shipping rates are calculated |
|
|
||||||
| `woocommerce_checkout_fields` | Filter | Standard WC filter for checkout fields |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Example 1: Sicepat Integration
|
|
||||||
```php
|
|
||||||
// Endpoint
|
|
||||||
register_rest_route('woonoow/v1', '/sicepat/destinations', ...);
|
|
||||||
|
|
||||||
// Session key
|
|
||||||
WC()->session->set('sicepat_destination_code', $code);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 2: JNE Direct API
|
|
||||||
```php
|
|
||||||
// Endpoint for origin selection
|
|
||||||
register_rest_route('woonoow/v1', '/jne/origins', ...);
|
|
||||||
register_rest_route('woonoow/v1', '/jne/destinations', ...);
|
|
||||||
|
|
||||||
// Multiple session keys
|
|
||||||
WC()->session->set('jne_origin', $origin);
|
|
||||||
WC()->session->set('jne_destination', $destination);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Checklist for New Integration
|
|
||||||
|
|
||||||
- [ ] Identify the shipping plugin's class name
|
|
||||||
- [ ] Find what session/data it reads for API calls
|
|
||||||
- [ ] Create REST endpoint for searchable data
|
|
||||||
- [ ] Add checkout field(s) via filter
|
|
||||||
- [ ] Bridge data via `woonoow/shipping/before_calculate`
|
|
||||||
- [ ] Test shipping rate calculation
|
|
||||||
- [ ] Document in dedicated `[PROVIDER]_INTEGRATION.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Documentation
|
|
||||||
|
|
||||||
- [RAJAONGKIR_INTEGRATION.md](RAJAONGKIR_INTEGRATION.md) - Rajaongkir-specific implementation
|
|
||||||
- [SHIPPING_INTEGRATION.md](SHIPPING_INTEGRATION.md) - General shipping patterns
|
|
||||||
- [HOOKS_REGISTRY.md](HOOKS_REGISTRY.md) - All WooNooW hooks
|
|
||||||
@@ -1,322 +0,0 @@
|
|||||||
# Shipping Integration Guide
|
|
||||||
|
|
||||||
This document consolidates shipping integration patterns and addon specifications for WooNooW.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
WooNooW supports flexible shipping integration through:
|
|
||||||
1. **Standard WooCommerce Shipping Methods** - Works with any WC shipping plugin
|
|
||||||
2. **Custom Shipping Addons** - Build shipping addons using WooNooW addon bridge
|
|
||||||
3. **Indonesian Shipping** - Special handling for Indonesian address systems
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Indonesian Shipping Challenges
|
|
||||||
|
|
||||||
### RajaOngkir Integration Issue
|
|
||||||
|
|
||||||
**Problem**: RajaOngkir plugin doesn't use standard WooCommerce address fields.
|
|
||||||
|
|
||||||
#### How RajaOngkir Works:
|
|
||||||
|
|
||||||
1. **Removes Standard Fields:**
|
|
||||||
```php
|
|
||||||
// class-cekongkir.php
|
|
||||||
public function customize_checkout_fields($fields) {
|
|
||||||
unset($fields['billing']['billing_state']);
|
|
||||||
unset($fields['billing']['billing_city']);
|
|
||||||
unset($fields['shipping']['shipping_state']);
|
|
||||||
unset($fields['shipping']['shipping_city']);
|
|
||||||
return $fields;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Adds Custom Destination Dropdown:**
|
|
||||||
```php
|
|
||||||
<select id="cart-destination" name="cart_destination">
|
|
||||||
<option>Search and select location...</option>
|
|
||||||
</select>
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Stores in Session:**
|
|
||||||
```php
|
|
||||||
WC()->session->set('selected_destination_id', $destination_id);
|
|
||||||
WC()->session->set('selected_destination_label', $destination_label);
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Triggers Shipping Calculation:**
|
|
||||||
```php
|
|
||||||
WC()->cart->calculate_shipping();
|
|
||||||
WC()->cart->calculate_totals();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Why Standard Implementation Fails:
|
|
||||||
|
|
||||||
- WooNooW OrderForm uses standard fields: `city`, `state`, `postcode`
|
|
||||||
- RajaOngkir ignores these fields
|
|
||||||
- RajaOngkir only reads from session: `selected_destination_id`
|
|
||||||
|
|
||||||
#### Solution:
|
|
||||||
|
|
||||||
Use **Biteship** instead (see below) or create custom RajaOngkir addon that:
|
|
||||||
- Hooks into WooNooW OrderForm
|
|
||||||
- Adds Indonesian address selector
|
|
||||||
- Syncs with RajaOngkir session
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Biteship Integration Addon
|
|
||||||
|
|
||||||
### Plugin Specification
|
|
||||||
|
|
||||||
**Plugin Name:** WooNooW Indonesia Shipping
|
|
||||||
**Description:** Indonesian shipping integration using Biteship Rate API
|
|
||||||
**Version:** 1.0.0
|
|
||||||
**Requires:** WooNooW 1.0.0+, WooCommerce 8.0+
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- ✅ Indonesian address fields (Province, City, District, Subdistrict)
|
|
||||||
- ✅ Real-time shipping rate calculation
|
|
||||||
- ✅ Multiple courier support (JNE, SiCepat, J&T, AnterAja, etc.)
|
|
||||||
- ✅ Works in frontend checkout AND admin order form
|
|
||||||
- ✅ No subscription required (uses free Biteship Rate API)
|
|
||||||
|
|
||||||
### Implementation Phases
|
|
||||||
|
|
||||||
#### Phase 1: Core Functionality
|
|
||||||
- WooCommerce Shipping Method integration
|
|
||||||
- Biteship Rate API integration
|
|
||||||
- Indonesian address database (Province → Subdistrict)
|
|
||||||
- Frontend checkout integration
|
|
||||||
- Admin settings page
|
|
||||||
|
|
||||||
#### Phase 2: SPA Integration
|
|
||||||
- REST API endpoints for address data
|
|
||||||
- REST API for rate calculation
|
|
||||||
- React components (SubdistrictSelector, CourierSelector)
|
|
||||||
- Hook integration with WooNooW OrderForm
|
|
||||||
- Admin order form support
|
|
||||||
|
|
||||||
#### Phase 3: Advanced Features
|
|
||||||
- Rate caching (reduce API calls)
|
|
||||||
- Custom rate markup
|
|
||||||
- Free shipping threshold
|
|
||||||
- Multi-origin support
|
|
||||||
- Shipping label generation (optional, requires paid Biteship plan)
|
|
||||||
|
|
||||||
### Plugin Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
woonoow-indonesia-shipping/
|
|
||||||
├── woonoow-indonesia-shipping.php
|
|
||||||
├── includes/
|
|
||||||
│ ├── class-shipping-method.php
|
|
||||||
│ ├── class-biteship-api.php
|
|
||||||
│ ├── class-address-database.php
|
|
||||||
│ └── class-rest-controller.php
|
|
||||||
├── admin/
|
|
||||||
│ ├── class-settings.php
|
|
||||||
│ └── views/
|
|
||||||
├── assets/
|
|
||||||
│ ├── js/
|
|
||||||
│ │ ├── checkout.js
|
|
||||||
│ │ └── admin-order.js
|
|
||||||
│ └── css/
|
|
||||||
└── data/
|
|
||||||
└── indonesia-addresses.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### API Integration
|
|
||||||
|
|
||||||
#### Biteship Rate API Endpoint
|
|
||||||
```
|
|
||||||
POST https://api.biteship.com/v1/rates/couriers
|
|
||||||
```
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"origin_area_id": "IDNP6IDNC148IDND1820IDZ16094",
|
|
||||||
"destination_area_id": "IDNP9IDNC235IDND3256IDZ41551",
|
|
||||||
"couriers": "jne,sicepat,jnt",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"name": "Product Name",
|
|
||||||
"value": 100000,
|
|
||||||
"weight": 1000,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"object": "courier_pricing",
|
|
||||||
"pricing": [
|
|
||||||
{
|
|
||||||
"courier_name": "JNE",
|
|
||||||
"courier_service_name": "REG",
|
|
||||||
"price": 15000,
|
|
||||||
"duration": "2-3 days"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### React Components
|
|
||||||
|
|
||||||
#### SubdistrictSelector Component
|
|
||||||
```tsx
|
|
||||||
interface SubdistrictSelectorProps {
|
|
||||||
value: {
|
|
||||||
province_id: string;
|
|
||||||
city_id: string;
|
|
||||||
district_id: string;
|
|
||||||
subdistrict_id: string;
|
|
||||||
};
|
|
||||||
onChange: (value: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SubdistrictSelector({ value, onChange }: SubdistrictSelectorProps) {
|
|
||||||
// Cascading dropdowns: Province → City → District → Subdistrict
|
|
||||||
// Uses WooNooW API: /woonoow/v1/shipping/indonesia/provinces
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### CourierSelector Component
|
|
||||||
```tsx
|
|
||||||
interface CourierSelectorProps {
|
|
||||||
origin: string;
|
|
||||||
destination: string;
|
|
||||||
items: CartItem[];
|
|
||||||
onSelect: (courier: ShippingRate) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CourierSelector({ origin, destination, items, onSelect }: CourierSelectorProps) {
|
|
||||||
// Fetches rates from Biteship
|
|
||||||
// Displays courier options with prices
|
|
||||||
// Uses WooNooW API: /woonoow/v1/shipping/indonesia/rates
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Hook Integration
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Register shipping addon
|
|
||||||
add_filter('woonoow/shipping/address_fields', function($fields) {
|
|
||||||
if (get_option('woonoow_indonesia_shipping_enabled')) {
|
|
||||||
return [
|
|
||||||
'province' => [
|
|
||||||
'type' => 'select',
|
|
||||||
'label' => 'Province',
|
|
||||||
'required' => true,
|
|
||||||
],
|
|
||||||
'city' => [
|
|
||||||
'type' => 'select',
|
|
||||||
'label' => 'City',
|
|
||||||
'required' => true,
|
|
||||||
],
|
|
||||||
'district' => [
|
|
||||||
'type' => 'select',
|
|
||||||
'label' => 'District',
|
|
||||||
'required' => true,
|
|
||||||
],
|
|
||||||
'subdistrict' => [
|
|
||||||
'type' => 'select',
|
|
||||||
'label' => 'Subdistrict',
|
|
||||||
'required' => true,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return $fields;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register React component
|
|
||||||
add_filter('woonoow/checkout/shipping_selector', function($component) {
|
|
||||||
if (get_option('woonoow_indonesia_shipping_enabled')) {
|
|
||||||
return 'IndonesiaShippingSelector';
|
|
||||||
}
|
|
||||||
return $component;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## General Shipping Integration
|
|
||||||
|
|
||||||
### Standard WooCommerce Shipping
|
|
||||||
|
|
||||||
WooNooW automatically supports any WooCommerce shipping plugin that uses standard shipping methods:
|
|
||||||
|
|
||||||
- WooCommerce Flat Rate
|
|
||||||
- WooCommerce Free Shipping
|
|
||||||
- WooCommerce Local Pickup
|
|
||||||
- Table Rate Shipping
|
|
||||||
- Distance Rate Shipping
|
|
||||||
- Any third-party shipping plugin
|
|
||||||
|
|
||||||
### Custom Shipping Addons
|
|
||||||
|
|
||||||
To create a custom shipping addon:
|
|
||||||
|
|
||||||
1. **Create WooCommerce Shipping Method**
|
|
||||||
```php
|
|
||||||
class Custom_Shipping_Method extends WC_Shipping_Method {
|
|
||||||
public function calculate_shipping($package = []) {
|
|
||||||
// Your shipping calculation logic
|
|
||||||
$this->add_rate([
|
|
||||||
'id' => $this->id,
|
|
||||||
'label' => $this->title,
|
|
||||||
'cost' => $cost,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Register with WooCommerce**
|
|
||||||
```php
|
|
||||||
add_filter('woocommerce_shipping_methods', function($methods) {
|
|
||||||
$methods['custom_shipping'] = 'Custom_Shipping_Method';
|
|
||||||
return $methods;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Add SPA Integration (Optional)**
|
|
||||||
```php
|
|
||||||
// REST API for frontend
|
|
||||||
register_rest_route('woonoow/v1', '/shipping/custom/rates', [
|
|
||||||
'methods' => 'POST',
|
|
||||||
'callback' => 'get_custom_shipping_rates',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// React component hook
|
|
||||||
add_filter('woonoow/checkout/shipping_fields', function($fields) {
|
|
||||||
// Add custom fields if needed
|
|
||||||
return $fields;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Use Standard WC Fields** - Whenever possible, use standard WooCommerce address fields
|
|
||||||
2. **Cache Rates** - Cache shipping rates to reduce API calls
|
|
||||||
3. **Error Handling** - Always provide fallback rates if API fails
|
|
||||||
4. **Mobile Friendly** - Ensure shipping selectors work well on mobile
|
|
||||||
5. **Admin Support** - Make sure shipping works in admin order form too
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [WooCommerce Shipping Method Tutorial](https://woocommerce.com/document/shipping-method-api/)
|
|
||||||
- [Biteship API Documentation](https://biteship.com/docs)
|
|
||||||
- [WooNooW Addon Development Guide](ADDON_DEVELOPMENT_GUIDE.md)
|
|
||||||
- [WooNooW Hooks Registry](HOOKS_REGISTRY.md)
|
|
||||||
@@ -1,327 +0,0 @@
|
|||||||
# Shipping Method Types - WooCommerce Core Structure
|
|
||||||
|
|
||||||
## The Two Types of Shipping Methods
|
|
||||||
|
|
||||||
WooCommerce has TWO fundamentally different shipping method types:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Type 1: Static Methods (WooCommerce Core)
|
|
||||||
|
|
||||||
**Characteristics:**
|
|
||||||
- No API calls
|
|
||||||
- Fixed rates or free
|
|
||||||
- Configured once in settings
|
|
||||||
- Available immediately
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
- Free Shipping
|
|
||||||
- Flat Rate
|
|
||||||
- Local Pickup
|
|
||||||
|
|
||||||
**Structure:**
|
|
||||||
```
|
|
||||||
Method: Free Shipping
|
|
||||||
├── Conditions: Order total > $50
|
|
||||||
└── Cost: $0
|
|
||||||
```
|
|
||||||
|
|
||||||
**In Create Order:**
|
|
||||||
```
|
|
||||||
User fills address
|
|
||||||
→ Static methods appear immediately
|
|
||||||
→ User selects one
|
|
||||||
→ Done!
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Type 2: Live Rate Methods (API-based)
|
|
||||||
|
|
||||||
**Characteristics:**
|
|
||||||
- Requires API call
|
|
||||||
- Dynamic rates based on address + weight
|
|
||||||
- Returns multiple service options
|
|
||||||
- Needs "Calculate" button
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
- UPS (International)
|
|
||||||
- FedEx, DHL
|
|
||||||
- Indonesian Shipping Addons (J&T, JNE, SiCepat)
|
|
||||||
|
|
||||||
**Structure:**
|
|
||||||
```
|
|
||||||
Method: UPS Live Rates
|
|
||||||
├── API Credentials configured
|
|
||||||
└── On calculate:
|
|
||||||
├── Service: UPS Ground - $15.00
|
|
||||||
├── Service: UPS 2nd Day - $25.00
|
|
||||||
└── Service: UPS Next Day - $45.00
|
|
||||||
```
|
|
||||||
|
|
||||||
**In Create Order:**
|
|
||||||
```
|
|
||||||
User fills address
|
|
||||||
→ Click "Calculate Shipping"
|
|
||||||
→ API returns service options
|
|
||||||
→ User selects one
|
|
||||||
→ Done!
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The Real Hierarchy
|
|
||||||
|
|
||||||
### Static Method (Simple):
|
|
||||||
```
|
|
||||||
Method
|
|
||||||
└── (No sub-levels)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Live Rate Method (Complex):
|
|
||||||
```
|
|
||||||
Method
|
|
||||||
├── Courier (if applicable)
|
|
||||||
│ ├── Service Option 1
|
|
||||||
│ ├── Service Option 2
|
|
||||||
│ └── Service Option 3
|
|
||||||
└── Or directly:
|
|
||||||
├── Service Option 1
|
|
||||||
└── Service Option 2
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Indonesian Shipping Example
|
|
||||||
|
|
||||||
**Method:** Indonesian Shipping (API-based)
|
|
||||||
**API:** Biteship / RajaOngkir / Custom
|
|
||||||
|
|
||||||
**After Calculate:**
|
|
||||||
```
|
|
||||||
J&T Express
|
|
||||||
├── Regular Service: Rp15,000 (2-3 days)
|
|
||||||
└── Express Service: Rp25,000 (1 day)
|
|
||||||
|
|
||||||
JNE
|
|
||||||
├── REG: Rp18,000 (2-4 days)
|
|
||||||
├── YES: Rp28,000 (1-2 days)
|
|
||||||
└── OKE: Rp12,000 (3-5 days)
|
|
||||||
|
|
||||||
SiCepat
|
|
||||||
├── Regular: Rp16,000 (2-3 days)
|
|
||||||
└── BEST: Rp20,000 (1-2 days)
|
|
||||||
```
|
|
||||||
|
|
||||||
**User sees:** Courier name + Service name + Price + Estimate
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UPS Example (International)
|
|
||||||
|
|
||||||
**Method:** UPS Live Rates
|
|
||||||
**API:** UPS API
|
|
||||||
|
|
||||||
**After Calculate:**
|
|
||||||
```
|
|
||||||
UPS Ground: $15.00 (5-7 business days)
|
|
||||||
UPS 2nd Day Air: $25.00 (2 business days)
|
|
||||||
UPS Next Day Air: $45.00 (1 business day)
|
|
||||||
```
|
|
||||||
|
|
||||||
**User sees:** Service name + Price + Estimate
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Address Field Requirements
|
|
||||||
|
|
||||||
### Static Methods:
|
|
||||||
- Country
|
|
||||||
- State/Province
|
|
||||||
- City
|
|
||||||
- Postal Code
|
|
||||||
- Address Line 1
|
|
||||||
- Address Line 2 (optional)
|
|
||||||
|
|
||||||
### Live Rate Methods:
|
|
||||||
|
|
||||||
**International (UPS, FedEx, DHL):**
|
|
||||||
- Country
|
|
||||||
- State/Province
|
|
||||||
- City
|
|
||||||
- **Postal Code** (REQUIRED - used for rate calculation)
|
|
||||||
- Address Line 1
|
|
||||||
- Address Line 2 (optional)
|
|
||||||
|
|
||||||
**Indonesian (J&T, JNE, SiCepat):**
|
|
||||||
- Country: Indonesia
|
|
||||||
- Province
|
|
||||||
- City/Regency
|
|
||||||
- **Subdistrict** (REQUIRED - used for rate calculation)
|
|
||||||
- Postal Code
|
|
||||||
- Address Line 1
|
|
||||||
- Address Line 2 (optional)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The Pattern
|
|
||||||
|
|
||||||
| Method Type | Address Requirement | Rate Calculation |
|
|
||||||
|-------------|---------------------|------------------|
|
|
||||||
| Static | Basic address | Fixed/Free |
|
|
||||||
| Live Rate (International) | **Postal Code** required | API call with postal code |
|
|
||||||
| Live Rate (Indonesian) | **Subdistrict** required | API call with subdistrict ID |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation in Create Order
|
|
||||||
|
|
||||||
### Current Problem:
|
|
||||||
- We probably require subdistrict for ALL methods
|
|
||||||
- This breaks international live rate methods
|
|
||||||
|
|
||||||
### Solution:
|
|
||||||
|
|
||||||
**Step 1: Detect Available Methods**
|
|
||||||
```javascript
|
|
||||||
const availableMethods = getShippingMethods(address.country);
|
|
||||||
|
|
||||||
const needsSubdistrict = availableMethods.some(method =>
|
|
||||||
method.type === 'live_rate' && method.country === 'ID'
|
|
||||||
);
|
|
||||||
|
|
||||||
const needsPostalCode = availableMethods.some(method =>
|
|
||||||
method.type === 'live_rate' && method.country !== 'ID'
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: Show Conditional Fields**
|
|
||||||
```javascript
|
|
||||||
// Always show
|
|
||||||
- Country
|
|
||||||
- State/Province
|
|
||||||
- City
|
|
||||||
- Address Line 1
|
|
||||||
|
|
||||||
// Conditional
|
|
||||||
if (needsSubdistrict) {
|
|
||||||
- Subdistrict (required)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsPostalCode || needsSubdistrict) {
|
|
||||||
- Postal Code (required)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always optional
|
|
||||||
- Address Line 2
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: Calculate Shipping**
|
|
||||||
```javascript
|
|
||||||
// Static methods
|
|
||||||
if (method.type === 'static') {
|
|
||||||
return method.cost; // Immediate
|
|
||||||
}
|
|
||||||
|
|
||||||
// Live rate methods
|
|
||||||
if (method.type === 'live_rate') {
|
|
||||||
const rates = await fetchLiveRates({
|
|
||||||
method: method.id,
|
|
||||||
address: {
|
|
||||||
country: address.country,
|
|
||||||
state: address.state,
|
|
||||||
city: address.city,
|
|
||||||
subdistrict: address.subdistrict, // If Indonesian
|
|
||||||
postal_code: address.postal_code, // If international
|
|
||||||
// ... other fields
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return rates; // Array of service options
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UI Flow
|
|
||||||
|
|
||||||
### Scenario 1: Static Method Only
|
|
||||||
```
|
|
||||||
1. User fills basic address
|
|
||||||
2. Shipping options appear immediately:
|
|
||||||
- Free Shipping: $0
|
|
||||||
- Flat Rate: $10
|
|
||||||
3. User selects one
|
|
||||||
4. Done!
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scenario 2: Indonesian Live Rate
|
|
||||||
```
|
|
||||||
1. User fills address including subdistrict
|
|
||||||
2. Click "Calculate Shipping"
|
|
||||||
3. API returns:
|
|
||||||
- J&T Regular: Rp15,000
|
|
||||||
- JNE REG: Rp18,000
|
|
||||||
- SiCepat BEST: Rp20,000
|
|
||||||
4. User selects one
|
|
||||||
5. Done!
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scenario 3: International Live Rate
|
|
||||||
```
|
|
||||||
1. User fills address including postal code
|
|
||||||
2. Click "Calculate Shipping"
|
|
||||||
3. API returns:
|
|
||||||
- UPS Ground: $15.00
|
|
||||||
- UPS 2nd Day: $25.00
|
|
||||||
4. User selects one
|
|
||||||
5. Done!
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scenario 4: Mixed (Static + Live Rate)
|
|
||||||
```
|
|
||||||
1. User fills address
|
|
||||||
2. Static methods appear immediately:
|
|
||||||
- Free Shipping: $0
|
|
||||||
3. Click "Calculate Shipping" for live rates
|
|
||||||
4. Live rates appear:
|
|
||||||
- UPS Ground: $15.00
|
|
||||||
5. User selects from all options
|
|
||||||
6. Done!
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## WooCommerce Core Behavior
|
|
||||||
|
|
||||||
**Yes, this is all in WooCommerce core!**
|
|
||||||
|
|
||||||
- Static methods: `WC_Shipping_Method` class
|
|
||||||
- Live rate methods: Extend `WC_Shipping_Method` with API logic
|
|
||||||
- Service options: Stored as shipping rates with method_id + instance_id
|
|
||||||
|
|
||||||
**WooCommerce handles:**
|
|
||||||
- Method registration
|
|
||||||
- Rate calculation
|
|
||||||
- Service option display
|
|
||||||
- Selection and storage
|
|
||||||
|
|
||||||
**We need to handle:**
|
|
||||||
- Conditional address fields
|
|
||||||
- "Calculate" button for live rates
|
|
||||||
- Service option display in Create Order
|
|
||||||
- Proper address validation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Investigate Create Order address field logic
|
|
||||||
2. Add conditional field display based on available methods
|
|
||||||
3. Add "Calculate Shipping" button for live rates
|
|
||||||
4. Display service options properly
|
|
||||||
5. Test with:
|
|
||||||
- Static methods only
|
|
||||||
- Indonesian live rates
|
|
||||||
- International live rates
|
|
||||||
- Mixed scenarios
|
|
||||||
@@ -1,415 +0,0 @@
|
|||||||
# Sprint 1-2 Completion Report ✅ COMPLETE
|
|
||||||
|
|
||||||
**Status:** ✅ All objectives achieved and tested
|
|
||||||
**Date Completed:** November 22, 2025
|
|
||||||
## Customer SPA Foundation
|
|
||||||
|
|
||||||
**Date:** November 22, 2025
|
|
||||||
**Status:** ✅ Foundation Complete - Ready for Build & Testing
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
Sprint 1-2 objectives have been **successfully completed**. The customer-spa foundation is now in place with:
|
|
||||||
- ✅ Backend API controllers (Shop, Cart, Account)
|
|
||||||
- ✅ Frontend base layout components (Header, Footer, Container)
|
|
||||||
- ✅ WordPress integration (Shortcodes, Asset loading)
|
|
||||||
- ✅ Authentication flow (using WordPress user session)
|
|
||||||
- ✅ Routing structure
|
|
||||||
- ✅ State management (Zustand for cart)
|
|
||||||
- ✅ API client with endpoints
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What Was Built
|
|
||||||
|
|
||||||
### 1. Backend API Controllers ✅
|
|
||||||
|
|
||||||
Created three new customer-facing API controllers in `includes/Frontend/`:
|
|
||||||
|
|
||||||
#### **ShopController.php**
|
|
||||||
```
|
|
||||||
GET /woonoow/v1/shop/products # List products with filters
|
|
||||||
GET /woonoow/v1/shop/products/{id} # Get single product (with variations)
|
|
||||||
GET /woonoow/v1/shop/categories # List categories
|
|
||||||
GET /woonoow/v1/shop/search # Search products
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Product listing with pagination, category filter, search
|
|
||||||
- Single product with detailed info (variations, gallery, related products)
|
|
||||||
- Category listing with images
|
|
||||||
- Product search
|
|
||||||
|
|
||||||
#### **CartController.php**
|
|
||||||
```
|
|
||||||
GET /woonoow/v1/cart # Get cart contents
|
|
||||||
POST /woonoow/v1/cart/add # Add item to cart
|
|
||||||
POST /woonoow/v1/cart/update # Update cart item quantity
|
|
||||||
POST /woonoow/v1/cart/remove # Remove item from cart
|
|
||||||
POST /woonoow/v1/cart/apply-coupon # Apply coupon
|
|
||||||
POST /woonoow/v1/cart/remove-coupon # Remove coupon
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Full cart CRUD operations
|
|
||||||
- Coupon management
|
|
||||||
- Cart totals calculation (subtotal, tax, shipping, discount)
|
|
||||||
- WooCommerce session integration
|
|
||||||
|
|
||||||
#### **AccountController.php**
|
|
||||||
```
|
|
||||||
GET /woonoow/v1/account/orders # Get customer orders
|
|
||||||
GET /woonoow/v1/account/orders/{id} # Get single order
|
|
||||||
GET /woonoow/v1/account/profile # Get customer profile
|
|
||||||
POST /woonoow/v1/account/profile # Update profile
|
|
||||||
POST /woonoow/v1/account/password # Update password
|
|
||||||
GET /woonoow/v1/account/addresses # Get addresses
|
|
||||||
POST /woonoow/v1/account/addresses # Update addresses
|
|
||||||
GET /woonoow/v1/account/downloads # Get digital downloads
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Order history with pagination
|
|
||||||
- Order details with items, addresses, totals
|
|
||||||
- Profile management
|
|
||||||
- Password update
|
|
||||||
- Billing/shipping address management
|
|
||||||
- Digital downloads support
|
|
||||||
- Permission checks (logged-in users only)
|
|
||||||
|
|
||||||
**Files Created:**
|
|
||||||
- `includes/Frontend/ShopController.php`
|
|
||||||
- `includes/Frontend/CartController.php`
|
|
||||||
- `includes/Frontend/AccountController.php`
|
|
||||||
|
|
||||||
**Integration:**
|
|
||||||
- Updated `includes/Api/Routes.php` to register frontend controllers
|
|
||||||
- All routes registered under `woonoow/v1` namespace
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. WordPress Integration ✅
|
|
||||||
|
|
||||||
#### **Assets Manager** (`includes/Frontend/Assets.php`)
|
|
||||||
- Enqueues customer-spa JS/CSS on pages with shortcodes
|
|
||||||
- Adds inline config with API URL, nonce, user info
|
|
||||||
- Supports both production build and dev mode
|
|
||||||
- Smart loading (only loads when needed)
|
|
||||||
|
|
||||||
#### **Shortcodes Manager** (`includes/Frontend/Shortcodes.php`)
|
|
||||||
Created four shortcodes:
|
|
||||||
- `[woonoow_shop]` - Product listing page
|
|
||||||
- `[woonoow_cart]` - Shopping cart page
|
|
||||||
- `[woonoow_checkout]` - Checkout page (requires login)
|
|
||||||
- `[woonoow_account]` - My account page (requires login)
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Renders mount point for React app
|
|
||||||
- Passes data attributes for page-specific config
|
|
||||||
- Login requirement for protected pages
|
|
||||||
- Loading state placeholder
|
|
||||||
|
|
||||||
**Integration:**
|
|
||||||
- Updated `includes/Core/Bootstrap.php` to initialize frontend classes
|
|
||||||
- Assets and shortcodes auto-load on `plugins_loaded` hook
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Frontend Components ✅
|
|
||||||
|
|
||||||
#### **Base Layout Components**
|
|
||||||
Created in `customer-spa/src/components/Layout/`:
|
|
||||||
|
|
||||||
**Header.tsx**
|
|
||||||
- Logo and navigation
|
|
||||||
- Cart icon with item count badge
|
|
||||||
- User account link (if logged in)
|
|
||||||
- Search button
|
|
||||||
- Mobile menu button
|
|
||||||
- Sticky header with backdrop blur
|
|
||||||
|
|
||||||
**Footer.tsx**
|
|
||||||
- Multi-column footer (About, Shop, Account, Support)
|
|
||||||
- Links to main pages
|
|
||||||
- Copyright notice
|
|
||||||
- Responsive grid layout
|
|
||||||
|
|
||||||
**Container.tsx**
|
|
||||||
- Responsive container wrapper
|
|
||||||
- Uses `container-safe` utility class
|
|
||||||
- Consistent padding and max-width
|
|
||||||
|
|
||||||
**Layout.tsx**
|
|
||||||
- Main layout wrapper
|
|
||||||
- Header + Content + Footer structure
|
|
||||||
- Flex layout with sticky footer
|
|
||||||
|
|
||||||
#### **UI Components**
|
|
||||||
- `components/ui/button.tsx` - Button component with variants (shadcn/ui pattern)
|
|
||||||
|
|
||||||
#### **Utilities**
|
|
||||||
- `lib/utils.ts` - Helper functions:
|
|
||||||
- `cn()` - Tailwind class merging
|
|
||||||
- `formatPrice()` - Currency formatting
|
|
||||||
- `formatDate()` - Date formatting
|
|
||||||
- `debounce()` - Debounce function
|
|
||||||
|
|
||||||
**Integration:**
|
|
||||||
- Updated `App.tsx` to use Layout wrapper
|
|
||||||
- All pages now render inside consistent layout
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Authentication Flow ✅
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- Uses WordPress session (no separate auth needed)
|
|
||||||
- User info passed via `window.woonoowCustomer.user`
|
|
||||||
- Nonce-based API authentication
|
|
||||||
- Login requirement enforced at shortcode level
|
|
||||||
|
|
||||||
**User Data Available:**
|
|
||||||
```typescript
|
|
||||||
window.woonoowCustomer = {
|
|
||||||
apiUrl: '/wp-json/woonoow/v1',
|
|
||||||
nonce: 'wp_rest_nonce',
|
|
||||||
siteUrl: 'https://site.local',
|
|
||||||
user: {
|
|
||||||
isLoggedIn: true,
|
|
||||||
id: 123
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Protected Routes:**
|
|
||||||
- Checkout page requires login
|
|
||||||
- Account pages require login
|
|
||||||
- API endpoints check `is_user_logged_in()`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
woonoow/
|
|
||||||
├── includes/
|
|
||||||
│ ├── Frontend/ # NEW - Customer-facing backend
|
|
||||||
│ │ ├── ShopController.php # Product catalog API
|
|
||||||
│ │ ├── CartController.php # Cart operations API
|
|
||||||
│ │ ├── AccountController.php # Customer account API
|
|
||||||
│ │ ├── Assets.php # Asset loading
|
|
||||||
│ │ └── Shortcodes.php # Shortcode handlers
|
|
||||||
│ ├── Api/
|
|
||||||
│ │ └── Routes.php # UPDATED - Register frontend routes
|
|
||||||
│ └── Core/
|
|
||||||
│ └── Bootstrap.php # UPDATED - Initialize frontend
|
|
||||||
│
|
|
||||||
└── customer-spa/
|
|
||||||
├── src/
|
|
||||||
│ ├── components/
|
|
||||||
│ │ ├── Layout/ # NEW - Layout components
|
|
||||||
│ │ │ ├── Header.tsx
|
|
||||||
│ │ │ ├── Footer.tsx
|
|
||||||
│ │ │ ├── Container.tsx
|
|
||||||
│ │ │ └── Layout.tsx
|
|
||||||
│ │ └── ui/ # NEW - UI components
|
|
||||||
│ │ └── button.tsx
|
|
||||||
│ ├── lib/
|
|
||||||
│ │ ├── api/
|
|
||||||
│ │ │ └── client.ts # EXISTING - API client
|
|
||||||
│ │ ├── cart/
|
|
||||||
│ │ │ └── store.ts # EXISTING - Cart state
|
|
||||||
│ │ └── utils.ts # NEW - Utility functions
|
|
||||||
│ ├── pages/ # EXISTING - Page placeholders
|
|
||||||
│ ├── App.tsx # UPDATED - Add Layout wrapper
|
|
||||||
│ └── index.css # EXISTING - Global styles
|
|
||||||
└── package.json # EXISTING - Dependencies
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sprint 1-2 Checklist
|
|
||||||
|
|
||||||
According to `CUSTOMER_SPA_MASTER_PLAN.md`, Sprint 1-2 tasks:
|
|
||||||
|
|
||||||
- [x] **Setup customer-spa build system** - ✅ Vite + React + TypeScript configured
|
|
||||||
- [x] **Create base layout components** - ✅ Header, Footer, Container, Layout
|
|
||||||
- [x] **Implement routing** - ✅ React Router with routes for all pages
|
|
||||||
- [x] **Setup API client** - ✅ Client exists with all endpoints defined
|
|
||||||
- [x] **Cart state management** - ✅ Zustand store with persistence
|
|
||||||
- [x] **Authentication flow** - ✅ WordPress session integration
|
|
||||||
|
|
||||||
**All Sprint 1-2 objectives completed!** ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps (Sprint 3-4)
|
|
||||||
|
|
||||||
### Immediate: Build & Test
|
|
||||||
1. **Build customer-spa:**
|
|
||||||
```bash
|
|
||||||
cd customer-spa
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Create test pages in WordPress:**
|
|
||||||
- Create page "Shop" with `[woonoow_shop]`
|
|
||||||
- Create page "Cart" with `[woonoow_cart]`
|
|
||||||
- Create page "Checkout" with `[woonoow_checkout]`
|
|
||||||
- Create page "My Account" with `[woonoow_account]`
|
|
||||||
|
|
||||||
3. **Test API endpoints:**
|
|
||||||
```bash
|
|
||||||
# Test shop API
|
|
||||||
curl "https://woonoow.local/wp-json/woonoow/v1/shop/products"
|
|
||||||
|
|
||||||
# Test cart API
|
|
||||||
curl "https://woonoow.local/wp-json/woonoow/v1/cart"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sprint 3-4: Product Catalog
|
|
||||||
According to the master plan:
|
|
||||||
- [ ] Product listing page (with real data)
|
|
||||||
- [ ] Product filters (category, price, search)
|
|
||||||
- [ ] Product search functionality
|
|
||||||
- [ ] Product detail page (with variations)
|
|
||||||
- [ ] Product variations selector
|
|
||||||
- [ ] Image gallery with zoom
|
|
||||||
- [ ] Related products section
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Technical Notes
|
|
||||||
|
|
||||||
### API Design
|
|
||||||
- All customer-facing routes use `/woonoow/v1` namespace
|
|
||||||
- Public routes (shop) use `'permission_callback' => '__return_true'`
|
|
||||||
- Protected routes (account) check `is_user_logged_in()`
|
|
||||||
- Consistent response format with proper HTTP status codes
|
|
||||||
|
|
||||||
### Frontend Architecture
|
|
||||||
- **Hybrid approach:** Works with any theme via shortcodes
|
|
||||||
- **Progressive enhancement:** Theme provides layout, WooNooW provides interactivity
|
|
||||||
- **Mobile-first:** Responsive design with Tailwind utilities
|
|
||||||
- **Performance:** Code splitting, lazy loading, optimized builds
|
|
||||||
|
|
||||||
### WordPress Integration
|
|
||||||
- **Safe activation:** No database changes, reversible
|
|
||||||
- **Theme compatibility:** Works with any theme
|
|
||||||
- **SEO-friendly:** Server-rendered product pages (future)
|
|
||||||
- **Tracking-ready:** WooCommerce event triggers for pixels (future)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Known Limitations
|
|
||||||
|
|
||||||
### Current Sprint (1-2)
|
|
||||||
1. **Pages are placeholders** - Need real implementations in Sprint 3-4
|
|
||||||
2. **No product data rendering** - API works, but UI needs to consume it
|
|
||||||
3. **No checkout flow** - CheckoutController not created yet (Sprint 5-6)
|
|
||||||
4. **No cart drawer** - Cart page exists, but no slide-out drawer yet
|
|
||||||
|
|
||||||
### Future Sprints
|
|
||||||
- Sprint 3-4: Product catalog implementation
|
|
||||||
- Sprint 5-6: Cart drawer + Checkout flow
|
|
||||||
- Sprint 7-8: My Account pages implementation
|
|
||||||
- Sprint 9-10: Polish, testing, performance optimization
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
### Backend API Testing
|
|
||||||
- [ ] Test `/shop/products` - Returns product list
|
|
||||||
- [ ] Test `/shop/products/{id}` - Returns single product
|
|
||||||
- [ ] Test `/shop/categories` - Returns categories
|
|
||||||
- [ ] Test `/cart` - Returns empty cart
|
|
||||||
- [ ] Test `/cart/add` - Adds product to cart
|
|
||||||
- [ ] Test `/account/orders` - Requires login, returns orders
|
|
||||||
|
|
||||||
### Frontend Testing
|
|
||||||
- [ ] Build customer-spa successfully
|
|
||||||
- [ ] Create test pages with shortcodes
|
|
||||||
- [ ] Verify assets load on shortcode pages
|
|
||||||
- [ ] Check `window.woonoowCustomer` config exists
|
|
||||||
- [ ] Verify Header renders with cart count
|
|
||||||
- [ ] Verify Footer renders with links
|
|
||||||
- [ ] Test navigation between pages
|
|
||||||
|
|
||||||
### Integration Testing
|
|
||||||
- [ ] Shortcodes render mount point
|
|
||||||
- [ ] React app mounts on shortcode pages
|
|
||||||
- [ ] API calls work from frontend
|
|
||||||
- [ ] Cart state persists in localStorage
|
|
||||||
- [ ] User login state detected correctly
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
|
|
||||||
✅ **Sprint 1-2 is complete when:**
|
|
||||||
- [x] Backend API controllers created and registered
|
|
||||||
- [x] Frontend layout components created
|
|
||||||
- [x] WordPress integration (shortcodes, assets) working
|
|
||||||
- [x] Authentication flow implemented
|
|
||||||
- [x] Build system configured
|
|
||||||
- [ ] **Build succeeds** (pending: run `npm run build`)
|
|
||||||
- [ ] **Test pages work** (pending: create WordPress pages)
|
|
||||||
|
|
||||||
**Status:** 5/7 complete - Ready for build & testing phase
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Commands Reference
|
|
||||||
|
|
||||||
### Build Customer SPA
|
|
||||||
```bash
|
|
||||||
cd /Users/dwindown/Local\ Sites/woonoow/app/public/wp-content/plugins/woonoow/customer-spa
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dev Mode (Hot Reload)
|
|
||||||
```bash
|
|
||||||
cd customer-spa
|
|
||||||
npm run dev
|
|
||||||
# Runs at https://woonoow.local:5174
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test API Endpoints
|
|
||||||
```bash
|
|
||||||
# Shop API
|
|
||||||
curl "https://woonoow.local/wp-json/woonoow/v1/shop/products"
|
|
||||||
|
|
||||||
# Cart API
|
|
||||||
curl "https://woonoow.local/wp-json/woonoow/v1/cart" \
|
|
||||||
-H "X-WP-Nonce: YOUR_NONCE"
|
|
||||||
|
|
||||||
# Account API (requires auth)
|
|
||||||
curl "https://woonoow.local/wp-json/woonoow/v1/account/orders" \
|
|
||||||
-H "X-WP-Nonce: YOUR_NONCE" \
|
|
||||||
-H "Cookie: wordpress_logged_in_..."
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
**Sprint 1-2 foundation is complete!** 🎉
|
|
||||||
|
|
||||||
The customer-spa now has:
|
|
||||||
- ✅ Solid backend API foundation
|
|
||||||
- ✅ Clean frontend architecture
|
|
||||||
- ✅ WordPress integration layer
|
|
||||||
- ✅ Authentication flow
|
|
||||||
- ✅ Base layout components
|
|
||||||
|
|
||||||
**Ready for:**
|
|
||||||
- Building the customer-spa
|
|
||||||
- Creating test pages
|
|
||||||
- Moving to Sprint 3-4 (Product Catalog implementation)
|
|
||||||
|
|
||||||
**Next session:** Build, test, and start implementing real product listing page.
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
# Sprint 3-4: Product Catalog & Cart
|
|
||||||
|
|
||||||
**Duration:** Sprint 3-4 (2 weeks)
|
|
||||||
**Status:** 🚀 Ready to Start
|
|
||||||
**Prerequisites:** ✅ Sprint 1-2 Complete
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Objectives
|
|
||||||
|
|
||||||
Build out the complete product catalog experience and shopping cart functionality.
|
|
||||||
|
|
||||||
### Sprint 3: Product Catalog Enhancement
|
|
||||||
1. **Product Detail Page** - Full product view with variations
|
|
||||||
2. **Product Filters** - Category, price, attributes
|
|
||||||
3. **Product Search** - Real-time search with debouncing
|
|
||||||
4. **Product Sorting** - Price, popularity, rating, date
|
|
||||||
|
|
||||||
### Sprint 4: Shopping Cart
|
|
||||||
1. **Cart Page** - View and manage cart items
|
|
||||||
2. **Cart Sidebar** - Quick cart preview
|
|
||||||
3. **Cart API Integration** - Sync with WooCommerce cart
|
|
||||||
4. **Coupon Application** - Apply and remove coupons
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sprint 3: Product Catalog Enhancement
|
|
||||||
|
|
||||||
### 1. Product Detail Page (`/product/:id`)
|
|
||||||
|
|
||||||
**File:** `customer-spa/src/pages/Product/index.tsx`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Product images gallery with zoom
|
|
||||||
- Product title, price, description
|
|
||||||
- Variation selector (size, color, etc.)
|
|
||||||
- Quantity selector
|
|
||||||
- Add to cart button
|
|
||||||
- Related products
|
|
||||||
- Product reviews (if enabled)
|
|
||||||
|
|
||||||
**API Endpoints:**
|
|
||||||
- `GET /shop/products/:id` - Get product details
|
|
||||||
- `GET /shop/products/:id/related` - Get related products (optional)
|
|
||||||
|
|
||||||
**Components to Create:**
|
|
||||||
- `ProductGallery.tsx` - Image gallery with thumbnails
|
|
||||||
- `VariationSelector.tsx` - Select product variations
|
|
||||||
- `QuantityInput.tsx` - Quantity selector
|
|
||||||
- `ProductMeta.tsx` - SKU, categories, tags
|
|
||||||
- `RelatedProducts.tsx` - Related products carousel
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Product Filters
|
|
||||||
|
|
||||||
**File:** `customer-spa/src/components/Shop/Filters.tsx`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Category filter (tree structure)
|
|
||||||
- Price range slider
|
|
||||||
- Attribute filters (color, size, brand, etc.)
|
|
||||||
- Stock status filter
|
|
||||||
- On sale filter
|
|
||||||
- Clear all filters button
|
|
||||||
|
|
||||||
**State Management:**
|
|
||||||
- Use URL query parameters for filters
|
|
||||||
- Persist filters in URL for sharing
|
|
||||||
|
|
||||||
**Components:**
|
|
||||||
- `CategoryFilter.tsx` - Hierarchical category tree
|
|
||||||
- `PriceRangeFilter.tsx` - Price slider
|
|
||||||
- `AttributeFilter.tsx` - Checkbox list for attributes
|
|
||||||
- `ActiveFilters.tsx` - Show active filters with remove buttons
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Product Search Enhancement
|
|
||||||
|
|
||||||
**Current:** Basic search input
|
|
||||||
**Enhancement:** Real-time search with suggestions
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Search as you type
|
|
||||||
- Search suggestions dropdown
|
|
||||||
- Recent searches
|
|
||||||
- Popular searches
|
|
||||||
- Product thumbnails in results
|
|
||||||
- Keyboard navigation (arrow keys, enter, escape)
|
|
||||||
|
|
||||||
**File:** `customer-spa/src/components/Shop/SearchBar.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Product Sorting
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Sort by: Default, Popularity, Rating, Price (low to high), Price (high to low), Latest
|
|
||||||
- Dropdown selector
|
|
||||||
- Persist in URL
|
|
||||||
|
|
||||||
**File:** `customer-spa/src/components/Shop/SortDropdown.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sprint 4: Shopping Cart
|
|
||||||
|
|
||||||
### 1. Cart Page (`/cart`)
|
|
||||||
|
|
||||||
**File:** `customer-spa/src/pages/Cart/index.tsx`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Cart items list with thumbnails
|
|
||||||
- Quantity adjustment (+ / -)
|
|
||||||
- Remove item button
|
|
||||||
- Update cart button
|
|
||||||
- Cart totals (subtotal, tax, shipping, total)
|
|
||||||
- Coupon code input
|
|
||||||
- Proceed to checkout button
|
|
||||||
- Continue shopping link
|
|
||||||
- Empty cart state
|
|
||||||
|
|
||||||
**Components:**
|
|
||||||
- `CartItem.tsx` - Single cart item row
|
|
||||||
- `CartTotals.tsx` - Cart totals summary
|
|
||||||
- `CouponForm.tsx` - Apply coupon code
|
|
||||||
- `EmptyCart.tsx` - Empty cart message
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Cart Sidebar/Drawer
|
|
||||||
|
|
||||||
**File:** `customer-spa/src/components/Cart/CartDrawer.tsx`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Slide-in from right
|
|
||||||
- Mini cart items (max 5, then scroll)
|
|
||||||
- Cart totals
|
|
||||||
- View cart button
|
|
||||||
- Checkout button
|
|
||||||
- Close button
|
|
||||||
- Backdrop overlay
|
|
||||||
|
|
||||||
**Trigger:**
|
|
||||||
- Click cart icon in header
|
|
||||||
- Auto-open when item added (optional)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Cart API Integration
|
|
||||||
|
|
||||||
**Endpoints:**
|
|
||||||
- `GET /cart` - Get current cart
|
|
||||||
- `POST /cart/add` - Add item to cart
|
|
||||||
- `PUT /cart/update` - Update item quantity
|
|
||||||
- `DELETE /cart/remove` - Remove item
|
|
||||||
- `POST /cart/apply-coupon` - Apply coupon
|
|
||||||
- `DELETE /cart/remove-coupon` - Remove coupon
|
|
||||||
|
|
||||||
**State Management:**
|
|
||||||
- Zustand store already created (`customer-spa/src/lib/cart/store.ts`)
|
|
||||||
- Sync with WooCommerce session
|
|
||||||
- Persist cart in localStorage
|
|
||||||
- Handle cart conflicts (server vs local)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Coupon System
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Apply coupon code
|
|
||||||
- Show discount amount
|
|
||||||
- Show coupon description
|
|
||||||
- Remove coupon button
|
|
||||||
- Error handling (invalid, expired, usage limit)
|
|
||||||
|
|
||||||
**Backend:**
|
|
||||||
- Already implemented in `CartController.php`
|
|
||||||
- `POST /cart/apply-coupon`
|
|
||||||
- `DELETE /cart/remove-coupon`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Technical Considerations
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
- Lazy load product images
|
|
||||||
- Implement infinite scroll for product grid (optional)
|
|
||||||
- Cache product data with TanStack Query
|
|
||||||
- Debounce search and filter inputs
|
|
||||||
|
|
||||||
### UX Enhancements
|
|
||||||
- Loading skeletons for all states
|
|
||||||
- Optimistic updates for cart actions
|
|
||||||
- Toast notifications for user feedback
|
|
||||||
- Smooth transitions and animations
|
|
||||||
- Mobile-first responsive design
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
- Network errors
|
|
||||||
- Out of stock products
|
|
||||||
- Invalid variations
|
|
||||||
- Cart conflicts
|
|
||||||
- API timeouts
|
|
||||||
|
|
||||||
### Accessibility
|
|
||||||
- Keyboard navigation
|
|
||||||
- Screen reader support
|
|
||||||
- Focus management
|
|
||||||
- ARIA labels
|
|
||||||
- Color contrast
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Order
|
|
||||||
|
|
||||||
### Week 1 (Sprint 3)
|
|
||||||
1. **Day 1-2:** Product Detail Page
|
|
||||||
- Basic layout and product info
|
|
||||||
- Image gallery
|
|
||||||
- Add to cart functionality
|
|
||||||
|
|
||||||
2. **Day 3:** Variation Selector
|
|
||||||
- Handle simple and variable products
|
|
||||||
- Update price based on variation
|
|
||||||
- Validation
|
|
||||||
|
|
||||||
3. **Day 4-5:** Filters & Search
|
|
||||||
- Category filter
|
|
||||||
- Price range filter
|
|
||||||
- Search enhancement
|
|
||||||
- Sort dropdown
|
|
||||||
|
|
||||||
### Week 2 (Sprint 4)
|
|
||||||
1. **Day 1-2:** Cart Page
|
|
||||||
- Cart items list
|
|
||||||
- Quantity adjustment
|
|
||||||
- Cart totals
|
|
||||||
- Coupon application
|
|
||||||
|
|
||||||
2. **Day 3:** Cart Drawer
|
|
||||||
- Slide-in sidebar
|
|
||||||
- Mini cart items
|
|
||||||
- Quick actions
|
|
||||||
|
|
||||||
3. **Day 4:** Cart API Integration
|
|
||||||
- Sync with backend
|
|
||||||
- Handle conflicts
|
|
||||||
- Error handling
|
|
||||||
|
|
||||||
4. **Day 5:** Polish & Testing
|
|
||||||
- Responsive design
|
|
||||||
- Loading states
|
|
||||||
- Error states
|
|
||||||
- Cross-browser testing
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
|
|
||||||
### Sprint 3
|
|
||||||
- ✅ Product detail page displays all product info
|
|
||||||
- ✅ Variations can be selected and price updates
|
|
||||||
- ✅ Filters work and update product list
|
|
||||||
- ✅ Search returns relevant results
|
|
||||||
- ✅ Sorting works correctly
|
|
||||||
|
|
||||||
### Sprint 4
|
|
||||||
- ✅ Cart page displays all cart items
|
|
||||||
- ✅ Quantity can be adjusted
|
|
||||||
- ✅ Items can be removed
|
|
||||||
- ✅ Coupons can be applied and removed
|
|
||||||
- ✅ Cart drawer opens and closes smoothly
|
|
||||||
- ✅ Cart syncs with WooCommerce backend
|
|
||||||
- ✅ Cart persists across page reloads
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Review this plan
|
|
||||||
2. Confirm priorities
|
|
||||||
3. Start with Product Detail Page
|
|
||||||
4. Implement features incrementally
|
|
||||||
5. Test each feature before moving to next
|
|
||||||
|
|
||||||
**Ready to start Sprint 3?** 🚀
|
|
||||||
@@ -1,634 +0,0 @@
|
|||||||
# WooNooW Store UI/UX Guide
|
|
||||||
## Official Design System & Standards
|
|
||||||
|
|
||||||
**Version:** 1.0
|
|
||||||
**Last Updated:** November 26, 2025
|
|
||||||
**Status:** Living Document (Updated by conversation)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Purpose
|
|
||||||
|
|
||||||
This document serves as the single source of truth for all UI/UX decisions in WooNooW Customer SPA. All design and implementation decisions should reference this guide.
|
|
||||||
|
|
||||||
**Philosophy:** Pragmatic, not dogmatic. Follow convention when strong, follow research when clear, use hybrid when beneficial.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Core Principles
|
|
||||||
|
|
||||||
1. **Convention Over Innovation** - Users expect familiar patterns
|
|
||||||
2. **Research-Backed Decisions** - When convention is weak or wrong
|
|
||||||
3. **Mobile-First Approach** - Design for mobile, enhance for desktop
|
|
||||||
4. **Performance Matters** - Fast > Feature-rich
|
|
||||||
5. **Accessibility Always** - WCAG 2.1 AA minimum
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📐 Layout Standards
|
|
||||||
|
|
||||||
### Container Widths
|
|
||||||
|
|
||||||
```css
|
|
||||||
Mobile: 100% (with padding)
|
|
||||||
Tablet: 768px max-width
|
|
||||||
Desktop: 1200px max-width
|
|
||||||
Wide: 1400px max-width
|
|
||||||
```
|
|
||||||
|
|
||||||
### Spacing Scale
|
|
||||||
|
|
||||||
```css
|
|
||||||
xs: 0.25rem (4px)
|
|
||||||
sm: 0.5rem (8px)
|
|
||||||
md: 1rem (16px)
|
|
||||||
lg: 1.5rem (24px)
|
|
||||||
xl: 2rem (32px)
|
|
||||||
2xl: 3rem (48px)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Breakpoints
|
|
||||||
|
|
||||||
```css
|
|
||||||
sm: 640px
|
|
||||||
md: 768px
|
|
||||||
lg: 1024px
|
|
||||||
xl: 1280px
|
|
||||||
2xl: 1536px
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Typography
|
|
||||||
|
|
||||||
### Hierarchy
|
|
||||||
|
|
||||||
```
|
|
||||||
H1 (Product Title): 28-32px, bold
|
|
||||||
H2 (Section Title): 24-28px, bold
|
|
||||||
H3 (Subsection): 20-24px, semibold
|
|
||||||
Price (Primary): 24-28px, bold
|
|
||||||
Price (Sale): 24-28px, bold, red
|
|
||||||
Price (Regular): 18-20px, line-through, gray
|
|
||||||
Body: 16px, regular
|
|
||||||
Small: 14px, regular
|
|
||||||
Tiny: 12px, regular
|
|
||||||
```
|
|
||||||
|
|
||||||
### Font Stack
|
|
||||||
|
|
||||||
```css
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
|
||||||
Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rules
|
|
||||||
|
|
||||||
- ✅ Title > Price in hierarchy (we're not a marketplace)
|
|
||||||
- ✅ Use weight and color for emphasis, not just size
|
|
||||||
- ✅ Line height: 1.5 for body, 1.2 for headings
|
|
||||||
- ❌ Don't use more than 3 font sizes per section
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🖼️ Product Page Standards
|
|
||||||
|
|
||||||
### Image Gallery
|
|
||||||
|
|
||||||
#### Desktop:
|
|
||||||
```
|
|
||||||
Layout:
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ [Main Image] │
|
|
||||||
│ (Large, square) │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
[▭] [▭] [▭] [▭] [▭] ← Thumbnails (96-112px)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- ✅ Thumbnails: 96-112px (24-28 in Tailwind)
|
|
||||||
- ✅ Horizontal scrollable if >4 images
|
|
||||||
- ✅ Active thumbnail: Primary border + ring
|
|
||||||
- ✅ Main image: object-contain with padding
|
|
||||||
- ✅ Click thumbnail → change main image
|
|
||||||
- ✅ Click main image → fullscreen lightbox
|
|
||||||
|
|
||||||
#### Mobile:
|
|
||||||
```
|
|
||||||
Layout:
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ [Main Image] │
|
|
||||||
│ (Full width, square) │
|
|
||||||
│ ● ○ ○ ○ ○ │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- ✅ Dots only (NO thumbnails)
|
|
||||||
- ✅ Swipe gesture for navigation
|
|
||||||
- ✅ Dots: 8-10px, centered below image
|
|
||||||
- ✅ Active dot: Primary color, larger
|
|
||||||
- ✅ Image counter optional (e.g., "1/5")
|
|
||||||
- ❌ NO thumbnails (redundant with dots)
|
|
||||||
|
|
||||||
**Rationale:** Convention (Amazon, Tokopedia, Shopify all use dots only on mobile)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Variation Selectors
|
|
||||||
|
|
||||||
#### Pattern: Pills/Buttons (NOT Dropdowns)
|
|
||||||
|
|
||||||
**Color Variations:**
|
|
||||||
```html
|
|
||||||
[⬜ White] [⬛ Black] [🔴 Red] [🔵 Blue]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Size/Text Variations:**
|
|
||||||
```html
|
|
||||||
[36] [37] [38] [39] [40] [41]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- ✅ All options visible at once
|
|
||||||
- ✅ Pills: min 44x44px (touch target)
|
|
||||||
- ✅ Active state: Primary background + white text
|
|
||||||
- ✅ Hover state: Border color change
|
|
||||||
- ✅ Disabled state: Gray + opacity 50%
|
|
||||||
- ❌ NO dropdowns (hides options, poor UX)
|
|
||||||
|
|
||||||
**Rationale:** Convention + Research align (Nielsen Norman Group)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Product Information Sections
|
|
||||||
|
|
||||||
#### Pattern: Vertical Accordions
|
|
||||||
|
|
||||||
**Desktop & Mobile:**
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ▼ Product Description │ ← Auto-expanded
|
|
||||||
│ Full description text... │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ▶ Specifications │ ← Collapsed
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ▶ Customer Reviews │ ← Collapsed
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- ✅ Description: Auto-expanded on load
|
|
||||||
- ✅ Other sections: Collapsed by default
|
|
||||||
- ✅ Arrow icon: Rotates on expand/collapse
|
|
||||||
- ✅ Smooth animation: 200-300ms
|
|
||||||
- ✅ Full-width clickable header
|
|
||||||
- ❌ NO horizontal tabs (27% overlook rate)
|
|
||||||
|
|
||||||
**Rationale:** Research (Baymard: vertical > horizontal)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Specifications Table
|
|
||||||
|
|
||||||
**Pattern: Scannable Two-Column Table**
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ Material │ 100% Cotton │
|
|
||||||
│ Weight │ 250g │
|
|
||||||
│ Color │ Black, White, Gray │
|
|
||||||
│ Size │ S, M, L, XL │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- ✅ Label column: 33% width, bold, gray background
|
|
||||||
- ✅ Value column: 67% width, regular weight
|
|
||||||
- ✅ Padding: py-4 px-6
|
|
||||||
- ✅ Border: Bottom border on each row
|
|
||||||
- ✅ Last row: No border
|
|
||||||
- ❌ NO plain table (hard to scan)
|
|
||||||
|
|
||||||
**Rationale:** Research (scannable > plain)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Buy Section
|
|
||||||
|
|
||||||
#### Desktop & Mobile:
|
|
||||||
|
|
||||||
**Structure:**
|
|
||||||
```
|
|
||||||
1. Product Title (H1)
|
|
||||||
2. Price (prominent, but not overwhelming)
|
|
||||||
3. Stock Status (badge with icon)
|
|
||||||
4. Short Description (if exists)
|
|
||||||
5. Variation Selectors (pills)
|
|
||||||
6. Quantity Selector (large buttons)
|
|
||||||
7. Add to Cart (prominent CTA)
|
|
||||||
8. Wishlist Button (secondary)
|
|
||||||
9. Trust Badges (shipping, returns, secure)
|
|
||||||
10. Product Meta (SKU, categories)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Price Display:**
|
|
||||||
```html
|
|
||||||
<!-- On Sale -->
|
|
||||||
<div>
|
|
||||||
<span class="text-2xl font-bold text-red-600">$79.00</span>
|
|
||||||
<span class="text-lg text-gray-400 line-through">$99.00</span>
|
|
||||||
<span class="bg-red-600 text-white px-3 py-1 rounded">SAVE 20%</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Regular -->
|
|
||||||
<span class="text-2xl font-bold">$99.00</span>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Stock Status:**
|
|
||||||
```html
|
|
||||||
<!-- In Stock -->
|
|
||||||
<div class="bg-green-50 text-green-700 px-4 py-2.5 rounded-lg border border-green-200">
|
|
||||||
<svg>✓</svg>
|
|
||||||
<span>In Stock - Ships Today</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Out of Stock -->
|
|
||||||
<div class="bg-red-50 text-red-700 px-4 py-2.5 rounded-lg border border-red-200">
|
|
||||||
<svg>✗</svg>
|
|
||||||
<span>Out of Stock</span>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Add to Cart Button:**
|
|
||||||
```html
|
|
||||||
<!-- Desktop & Mobile -->
|
|
||||||
<button class="w-full h-14 text-lg font-bold bg-primary text-white rounded-lg shadow-lg hover:shadow-xl">
|
|
||||||
<ShoppingCart /> Add to Cart
|
|
||||||
</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Trust Badges:**
|
|
||||||
```html
|
|
||||||
<div class="space-y-3 border-t-2 pt-4">
|
|
||||||
<!-- Free Shipping -->
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<svg class="w-6 h-6 text-green-600">🚚</svg>
|
|
||||||
<div>
|
|
||||||
<p class="font-semibold">Free Shipping</p>
|
|
||||||
<p class="text-xs text-gray-600">On orders over $50</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Returns -->
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<svg class="w-6 h-6 text-blue-600">↩</svg>
|
|
||||||
<div>
|
|
||||||
<p class="font-semibold">30-Day Returns</p>
|
|
||||||
<p class="text-xs text-gray-600">Money-back guarantee</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Secure -->
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<svg class="w-6 h-6 text-gray-700">🔒</svg>
|
|
||||||
<div>
|
|
||||||
<p class="font-semibold">Secure Checkout</p>
|
|
||||||
<p class="text-xs text-gray-600">SSL encrypted payment</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Mobile-Specific Patterns
|
|
||||||
|
|
||||||
#### Sticky Bottom Bar (Optional - Future Enhancement)
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ $79.00 [Add to Cart] │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- ✅ Fixed at bottom on scroll
|
|
||||||
- ✅ Shows price + CTA
|
|
||||||
- ✅ Appears after scrolling past buy section
|
|
||||||
- ✅ z-index: 50 (above content)
|
|
||||||
- ✅ Shadow for depth
|
|
||||||
|
|
||||||
**Rationale:** Convention (Tokopedia does this)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Color System
|
|
||||||
|
|
||||||
### Primary Colors
|
|
||||||
|
|
||||||
```css
|
|
||||||
Primary: #222222 (dark gray/black)
|
|
||||||
Primary Hover: #000000
|
|
||||||
Primary Light: #F5F5F5
|
|
||||||
```
|
|
||||||
|
|
||||||
### Semantic Colors
|
|
||||||
|
|
||||||
```css
|
|
||||||
Success: #10B981 (green)
|
|
||||||
Error: #EF4444 (red)
|
|
||||||
Warning: #F59E0B (orange)
|
|
||||||
Info: #3B82F6 (blue)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sale/Discount
|
|
||||||
|
|
||||||
```css
|
|
||||||
Sale Price: #DC2626 (red-600)
|
|
||||||
Sale Badge: #DC2626 bg, white text
|
|
||||||
Savings: #DC2626 text
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stock Status
|
|
||||||
|
|
||||||
```css
|
|
||||||
In Stock: #10B981 (green-600)
|
|
||||||
Low Stock: #F59E0B (orange-500)
|
|
||||||
Out of Stock: #EF4444 (red-500)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Neutral Scale
|
|
||||||
|
|
||||||
```css
|
|
||||||
Gray 50: #F9FAFB
|
|
||||||
Gray 100: #F3F4F6
|
|
||||||
Gray 200: #E5E7EB
|
|
||||||
Gray 300: #D1D5DB
|
|
||||||
Gray 400: #9CA3AF
|
|
||||||
Gray 500: #6B7280
|
|
||||||
Gray 600: #4B5563
|
|
||||||
Gray 700: #374151
|
|
||||||
Gray 800: #1F2937
|
|
||||||
Gray 900: #111827
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔘 Interactive Elements
|
|
||||||
|
|
||||||
### Buttons
|
|
||||||
|
|
||||||
**Primary CTA:**
|
|
||||||
```css
|
|
||||||
Height: h-14 (56px)
|
|
||||||
Padding: px-6
|
|
||||||
Font: text-lg font-bold
|
|
||||||
Border Radius: rounded-lg
|
|
||||||
Shadow: shadow-lg hover:shadow-xl
|
|
||||||
```
|
|
||||||
|
|
||||||
**Secondary:**
|
|
||||||
```css
|
|
||||||
Height: h-12 (48px)
|
|
||||||
Padding: px-4
|
|
||||||
Font: text-base font-semibold
|
|
||||||
Border: border-2
|
|
||||||
```
|
|
||||||
|
|
||||||
**Quantity Buttons:**
|
|
||||||
```css
|
|
||||||
Size: 44x44px minimum (touch target)
|
|
||||||
Border: border-2
|
|
||||||
Icon: Plus/Minus (20px)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Touch Targets
|
|
||||||
|
|
||||||
**Minimum Sizes:**
|
|
||||||
```css
|
|
||||||
Mobile: 44x44px (WCAG AAA)
|
|
||||||
Desktop: 40x40px (acceptable)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- ✅ All interactive elements: min 44x44px on mobile
|
|
||||||
- ✅ Adequate spacing between targets (8px min)
|
|
||||||
- ✅ Visual feedback on tap/click
|
|
||||||
- ✅ Disabled state clearly indicated
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🖼️ Images
|
|
||||||
|
|
||||||
### Product Images
|
|
||||||
|
|
||||||
**Main Image:**
|
|
||||||
```css
|
|
||||||
Aspect Ratio: 1:1 (square)
|
|
||||||
Object Fit: object-contain (shows full product)
|
|
||||||
Padding: p-4 (breathing room)
|
|
||||||
Background: white or light gray
|
|
||||||
Border: border-2 border-gray-200
|
|
||||||
Shadow: shadow-lg
|
|
||||||
```
|
|
||||||
|
|
||||||
**Thumbnails:**
|
|
||||||
```css
|
|
||||||
Desktop: 96-112px (w-24 md:w-28)
|
|
||||||
Mobile: N/A (use dots)
|
|
||||||
Aspect Ratio: 1:1
|
|
||||||
Object Fit: object-cover
|
|
||||||
Border: border-2
|
|
||||||
Active: border-primary ring-4 ring-primary
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- ✅ Always use `!h-full` to override WooCommerce styles
|
|
||||||
- ✅ Lazy loading for performance
|
|
||||||
- ✅ Alt text for accessibility
|
|
||||||
- ✅ WebP format when possible
|
|
||||||
- ❌ Never use object-cover for main image (crops product)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 Responsive Behavior
|
|
||||||
|
|
||||||
### Grid Layout
|
|
||||||
|
|
||||||
**Product Page:**
|
|
||||||
```css
|
|
||||||
Mobile: grid-cols-1 (single column)
|
|
||||||
Desktop: grid-cols-2 (image | info)
|
|
||||||
Gap: gap-8 lg:gap-12
|
|
||||||
```
|
|
||||||
|
|
||||||
### Image Gallery
|
|
||||||
|
|
||||||
**Desktop:**
|
|
||||||
- Thumbnails: Horizontal scroll if >4 images
|
|
||||||
- Arrows: Show when >4 images
|
|
||||||
- Layout: Main image + thumbnail strip below
|
|
||||||
|
|
||||||
**Mobile:**
|
|
||||||
- Dots: Always visible
|
|
||||||
- Swipe: Primary interaction
|
|
||||||
- Counter: Optional (e.g., "1/5")
|
|
||||||
|
|
||||||
### Typography
|
|
||||||
|
|
||||||
**Responsive Sizes:**
|
|
||||||
```css
|
|
||||||
Title: text-2xl md:text-3xl
|
|
||||||
Price: text-2xl md:text-2xl (same)
|
|
||||||
Body: text-base (16px, no change)
|
|
||||||
Small: text-sm md:text-sm (same)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ♿ Accessibility
|
|
||||||
|
|
||||||
### WCAG 2.1 AA Requirements
|
|
||||||
|
|
||||||
**Color Contrast:**
|
|
||||||
- Text: 4.5:1 minimum
|
|
||||||
- Large text (18px+): 3:1 minimum
|
|
||||||
- Interactive elements: 3:1 minimum
|
|
||||||
|
|
||||||
**Keyboard Navigation:**
|
|
||||||
- ✅ All interactive elements focusable
|
|
||||||
- ✅ Visible focus indicators
|
|
||||||
- ✅ Logical tab order
|
|
||||||
- ✅ Skip links for main content
|
|
||||||
|
|
||||||
**Screen Readers:**
|
|
||||||
- ✅ Semantic HTML (h1, h2, nav, main, etc.)
|
|
||||||
- ✅ Alt text for images
|
|
||||||
- ✅ ARIA labels for icons
|
|
||||||
- ✅ Live regions for dynamic content
|
|
||||||
|
|
||||||
**Touch Targets:**
|
|
||||||
- ✅ Minimum 44x44px on mobile
|
|
||||||
- ✅ Adequate spacing (8px min)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Performance
|
|
||||||
|
|
||||||
### Loading Strategy
|
|
||||||
|
|
||||||
**Critical:**
|
|
||||||
- Hero image (main product image)
|
|
||||||
- Product title, price, CTA
|
|
||||||
- Variation selectors
|
|
||||||
|
|
||||||
**Deferred:**
|
|
||||||
- Thumbnails (lazy load)
|
|
||||||
- Description content
|
|
||||||
- Reviews section
|
|
||||||
- Related products
|
|
||||||
|
|
||||||
**Rules:**
|
|
||||||
- ✅ Lazy load images below fold
|
|
||||||
- ✅ Skeleton loading states
|
|
||||||
- ✅ Optimize images (WebP, compression)
|
|
||||||
- ✅ Code splitting for routes
|
|
||||||
- ❌ No layout shift (reserve space)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Component Checklist
|
|
||||||
|
|
||||||
### Product Page Must-Haves
|
|
||||||
|
|
||||||
**Above the Fold:**
|
|
||||||
- [ ] Breadcrumb navigation
|
|
||||||
- [ ] Product title (H1)
|
|
||||||
- [ ] Price display (with sale if applicable)
|
|
||||||
- [ ] Stock status badge
|
|
||||||
- [ ] Main product image
|
|
||||||
- [ ] Image navigation (thumbnails/dots)
|
|
||||||
- [ ] Variation selectors (pills)
|
|
||||||
- [ ] Quantity selector
|
|
||||||
- [ ] Add to Cart button
|
|
||||||
- [ ] Trust badges
|
|
||||||
|
|
||||||
**Below the Fold:**
|
|
||||||
- [ ] Product description (auto-expanded)
|
|
||||||
- [ ] Specifications table (collapsed)
|
|
||||||
- [ ] Reviews section (collapsed)
|
|
||||||
- [ ] Product meta (SKU, categories)
|
|
||||||
- [ ] Related products (future)
|
|
||||||
|
|
||||||
**Mobile Specific:**
|
|
||||||
- [ ] Dots for image navigation
|
|
||||||
- [ ] Large touch targets (44x44px)
|
|
||||||
- [ ] Responsive text sizes
|
|
||||||
- [ ] Collapsible sections
|
|
||||||
- [ ] Optional: Sticky bottom bar
|
|
||||||
|
|
||||||
**Desktop Specific:**
|
|
||||||
- [ ] Thumbnails for image navigation
|
|
||||||
- [ ] Hover states
|
|
||||||
- [ ] Larger layout (2-column grid)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Decision Log
|
|
||||||
|
|
||||||
### Image Gallery
|
|
||||||
- **Decision:** Dots only on mobile, thumbnails on desktop
|
|
||||||
- **Rationale:** Convention (Amazon, Tokopedia, Shopify)
|
|
||||||
- **Date:** Nov 26, 2025
|
|
||||||
|
|
||||||
### Variation Selectors
|
|
||||||
- **Decision:** Pills/buttons, not dropdowns
|
|
||||||
- **Rationale:** Convention + Research align (NN/g)
|
|
||||||
- **Date:** Nov 26, 2025
|
|
||||||
|
|
||||||
### Typography Hierarchy
|
|
||||||
- **Decision:** Title > Price (28-32px > 24-28px)
|
|
||||||
- **Rationale:** Context (we're not a marketplace)
|
|
||||||
- **Date:** Nov 26, 2025
|
|
||||||
|
|
||||||
### Description Pattern
|
|
||||||
- **Decision:** Auto-expanded accordion
|
|
||||||
- **Rationale:** Research (don't hide primary content)
|
|
||||||
- **Date:** Nov 26, 2025
|
|
||||||
|
|
||||||
### Tabs vs Accordions
|
|
||||||
- **Decision:** Vertical accordions, not horizontal tabs
|
|
||||||
- **Rationale:** Research (27% overlook tabs)
|
|
||||||
- **Date:** Nov 26, 2025
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 References
|
|
||||||
|
|
||||||
### Research Sources
|
|
||||||
- Baymard Institute UX Research
|
|
||||||
- Nielsen Norman Group Guidelines
|
|
||||||
- WCAG 2.1 Accessibility Standards
|
|
||||||
|
|
||||||
### Convention Sources
|
|
||||||
- Amazon (marketplace reference)
|
|
||||||
- Tokopedia (marketplace reference)
|
|
||||||
- Shopify (e-commerce reference)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 Version History
|
|
||||||
|
|
||||||
**v1.0 - Nov 26, 2025**
|
|
||||||
- Initial guide created
|
|
||||||
- Product page standards defined
|
|
||||||
- Decision framework established
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** ✅ Active
|
|
||||||
**Maintenance:** Updated by conversation
|
|
||||||
**Owner:** WooNooW Development Team
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
# Bug Fixes & User Feedback Resolution
|
|
||||||
|
|
||||||
## All 7 Issues Resolved ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 1. WordPress Media Library Not Loading
|
|
||||||
|
|
||||||
**Issue:**
|
|
||||||
- Error: "WordPress media library is not loaded. Please refresh the page."
|
|
||||||
- Blocking users from inserting images
|
|
||||||
|
|
||||||
**Root Cause:**
|
|
||||||
- WordPress Media API (`window.wp.media`) not available in some contexts
|
|
||||||
- No fallback mechanism
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```typescript
|
|
||||||
// Added fallback to URL prompt
|
|
||||||
if (typeof window.wp === 'undefined' || typeof window.wp.media === 'undefined') {
|
|
||||||
const url = window.prompt('WordPress Media library is not loaded. Please enter image URL:');
|
|
||||||
if (url) {
|
|
||||||
onSelect({ url, id: 0, title: 'External Image', filename: url.split('/').pop() || 'image' });
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Result:**
|
|
||||||
- Users can still insert images via URL if WP Media fails
|
|
||||||
- Better error handling
|
|
||||||
- No blocking errors
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Button Variables - Too Many Options
|
|
||||||
|
|
||||||
**Issue:**
|
|
||||||
- All variables shown in button link field
|
|
||||||
- Confusing for users (why show customer_name for a link?)
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```typescript
|
|
||||||
// Filter to only show URL variables
|
|
||||||
{variables.filter(v => v.includes('_url')).map((variable) => (
|
|
||||||
<code onClick={() => setButtonHref(buttonHref + `{${variable}}`)}>{`{${variable}}`}</code>
|
|
||||||
))}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Before:**
|
|
||||||
```
|
|
||||||
{order_number} {order_total} {customer_name} {customer_email} ...
|
|
||||||
```
|
|
||||||
|
|
||||||
**After:**
|
|
||||||
```
|
|
||||||
{order_url} {store_url}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `components/ui/rich-text-editor.tsx`
|
|
||||||
- `components/EmailBuilder/EmailBuilder.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Color Customization - Future Feature
|
|
||||||
|
|
||||||
**Issue:**
|
|
||||||
- Colors are hardcoded:
|
|
||||||
- Hero card gradient: `#667eea` to `#764ba2`
|
|
||||||
- Button primary: `#7f54b3`
|
|
||||||
- Button secondary border: `#7f54b3`
|
|
||||||
|
|
||||||
**Plan:**
|
|
||||||
- Will be added to email customization form
|
|
||||||
- Allow users to set brand colors
|
|
||||||
- Apply to all email templates
|
|
||||||
- Store in settings
|
|
||||||
|
|
||||||
**Note:**
|
|
||||||
Confirmed for future implementation. Not blocking current release.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4 & 5. Headings Not Visible in Editor & Builder
|
|
||||||
|
|
||||||
**Issue:**
|
|
||||||
- Headings (H1-H4) looked like paragraphs
|
|
||||||
- No visual distinction
|
|
||||||
- Confusing for users
|
|
||||||
|
|
||||||
**Root Cause:**
|
|
||||||
- No CSS styles applied to heading elements
|
|
||||||
- Default browser styles insufficient
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
Added Tailwind utility classes for heading styles:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// RichTextEditor
|
|
||||||
className="prose prose-sm max-w-none [&_h1]:text-3xl [&_h1]:font-bold [&_h1]:mt-4 [&_h1]:mb-2 [&_h2]:text-2xl [&_h2]:font-bold [&_h2]:mt-3 [&_h2]:mb-2 [&_h3]:text-xl [&_h3]:font-bold [&_h3]:mt-2 [&_h3]:mb-1 [&_h4]:text-lg [&_h4]:font-bold [&_h4]:mt-2 [&_h4]:mb-1"
|
|
||||||
|
|
||||||
// BlockRenderer (builder preview)
|
|
||||||
className="prose prose-sm max-w-none [&_h1]:text-3xl [&_h1]:font-bold [&_h1]:mt-0 [&_h1]:mb-4 [&_h2]:text-2xl [&_h2]:font-bold [&_h2]:mt-0 [&_h2]:mb-3 [&_h3]:text-xl [&_h3]:font-bold [&_h3]:mt-0 [&_h3]:mb-2 [&_h4]:text-lg [&_h4]:font-bold [&_h4]:mt-0 [&_h4]:mb-2"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Heading Sizes:**
|
|
||||||
- **H1**: 3xl (1.875rem / 30px), bold
|
|
||||||
- **H2**: 2xl (1.5rem / 24px), bold
|
|
||||||
- **H3**: xl (1.25rem / 20px), bold
|
|
||||||
- **H4**: lg (1.125rem / 18px), bold
|
|
||||||
|
|
||||||
**Result:**
|
|
||||||
- Headings now visually distinct
|
|
||||||
- Clear hierarchy
|
|
||||||
- Matches email preview
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `components/ui/rich-text-editor.tsx`
|
|
||||||
- `components/EmailBuilder/BlockRenderer.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. Missing Order Items Variable
|
|
||||||
|
|
||||||
**Issue:**
|
|
||||||
- No variable for product list/table
|
|
||||||
- Users can't show ordered products in emails
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
Added `order_items` variable to order variables:
|
|
||||||
|
|
||||||
```php
|
|
||||||
'order_items' => __('Order Items (formatted table)', 'woonoow'),
|
|
||||||
```
|
|
||||||
|
|
||||||
**Usage:**
|
|
||||||
```html
|
|
||||||
[card]
|
|
||||||
<h2>Order Summary</h2>
|
|
||||||
{order_items}
|
|
||||||
[/card]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Will Render:**
|
|
||||||
```html
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Product Name</td>
|
|
||||||
<td>Quantity</td>
|
|
||||||
<td>Price</td>
|
|
||||||
</tr>
|
|
||||||
<!-- ... product rows ... -->
|
|
||||||
</table>
|
|
||||||
```
|
|
||||||
|
|
||||||
**File Modified:**
|
|
||||||
- `includes/Core/Notifications/TemplateProvider.php`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. Edit Icon on Spacer & Divider
|
|
||||||
|
|
||||||
**Issue:**
|
|
||||||
- Edit button (✎) shown for spacer and divider
|
|
||||||
- No options to edit (they have no configurable properties)
|
|
||||||
- Clicking does nothing
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
Conditional rendering of edit button:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
{/* Only show edit button for card and button blocks */}
|
|
||||||
{(block.type === 'card' || block.type === 'button') && (
|
|
||||||
<button onClick={onEdit} title={__('Edit')}>✎</button>
|
|
||||||
)}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Controls Now:**
|
|
||||||
- **Card**: ↑ ↓ ✎ × (all controls)
|
|
||||||
- **Button**: ↑ ↓ ✎ × (all controls)
|
|
||||||
- **Spacer**: ↑ ↓ × (no edit)
|
|
||||||
- **Divider**: ↑ ↓ × (no edit)
|
|
||||||
|
|
||||||
**File Modified:**
|
|
||||||
- `components/EmailBuilder/BlockRenderer.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
### Issue 1: WP Media Fallback
|
|
||||||
- [ ] Try inserting image when WP Media is loaded
|
|
||||||
- [ ] Try inserting image when WP Media is not loaded
|
|
||||||
- [ ] Verify fallback prompt appears
|
|
||||||
- [ ] Verify image inserts correctly
|
|
||||||
|
|
||||||
### Issue 2: Button Variables
|
|
||||||
- [ ] Open button dialog in RichTextEditor
|
|
||||||
- [ ] Verify only URL variables shown
|
|
||||||
- [ ] Open button dialog in EmailBuilder
|
|
||||||
- [ ] Verify only URL variables shown
|
|
||||||
|
|
||||||
### Issue 3: Color Customization
|
|
||||||
- [ ] Note documented for future implementation
|
|
||||||
- [ ] Colors currently hardcoded (expected)
|
|
||||||
|
|
||||||
### Issue 4 & 5: Heading Display
|
|
||||||
- [ ] Create card with H1 heading
|
|
||||||
- [ ] Verify H1 is large and bold in editor
|
|
||||||
- [ ] Verify H1 is large and bold in builder
|
|
||||||
- [ ] Test H2, H3, H4 similarly
|
|
||||||
- [ ] Verify preview matches
|
|
||||||
|
|
||||||
### Issue 6: Order Items Variable
|
|
||||||
- [ ] Check variable list includes `order_items`
|
|
||||||
- [ ] Insert `{order_items}` in template
|
|
||||||
- [ ] Verify description shows "formatted table"
|
|
||||||
|
|
||||||
### Issue 7: Edit Icon Removal
|
|
||||||
- [ ] Hover over spacer block
|
|
||||||
- [ ] Verify no edit button (only ↑ ↓ ×)
|
|
||||||
- [ ] Hover over divider block
|
|
||||||
- [ ] Verify no edit button (only ↑ ↓ ×)
|
|
||||||
- [ ] Hover over card block
|
|
||||||
- [ ] Verify edit button present (↑ ↓ ✎ ×)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
All 7 user-reported issues have been resolved:
|
|
||||||
|
|
||||||
1. ✅ **WP Media Fallback** - No more blocking errors
|
|
||||||
2. ✅ **Button Variables Filtered** - Only relevant variables shown
|
|
||||||
3. ✅ **Color Customization Noted** - Future feature documented
|
|
||||||
4. ✅ **Headings Visible in Editor** - Proper styling applied
|
|
||||||
5. ✅ **Headings Visible in Builder** - Consistent with editor
|
|
||||||
6. ✅ **Order Items Variable** - Product list support added
|
|
||||||
7. ✅ **Edit Icon Removed** - Only on editable blocks
|
|
||||||
|
|
||||||
**Status: Ready for Testing** 🚀
|
|
||||||
@@ -1,329 +0,0 @@
|
|||||||
# Email Template & Builder System - Complete ✅
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
The WooNooW email template and builder system is now production-ready with improved templates, enhanced markdown support, and a fully functional visual builder.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 What's Complete
|
|
||||||
|
|
||||||
### 1. **Default Email Templates** ✅
|
|
||||||
**File:** `includes/Email/DefaultTemplates.php`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ 16 production-ready email templates (9 customer + 7 staff)
|
|
||||||
- ✅ Modern, clean markdown format (easy to read and edit)
|
|
||||||
- ✅ Professional, friendly tone
|
|
||||||
- ✅ Complete variable support
|
|
||||||
- ✅ Ready to use without any customization
|
|
||||||
|
|
||||||
**Templates Included:**
|
|
||||||
|
|
||||||
**Customer Templates:**
|
|
||||||
1. Order Placed - Initial order confirmation
|
|
||||||
2. Order Confirmed - Payment confirmed, ready to ship
|
|
||||||
3. Order Shipped - Tracking information
|
|
||||||
4. Order Completed - Delivery confirmation with review request
|
|
||||||
5. Order Cancelled - Cancellation notice with refund info
|
|
||||||
6. Payment Received - Payment confirmation
|
|
||||||
7. Payment Failed - Payment issue with resolution steps
|
|
||||||
8. Customer Registered - Welcome email with account benefits
|
|
||||||
9. Customer VIP Upgraded - VIP status announcement
|
|
||||||
|
|
||||||
**Staff Templates:**
|
|
||||||
1. Order Placed - New order notification
|
|
||||||
2. Order Confirmed - Order ready to process
|
|
||||||
3. Order Shipped - Shipment confirmation
|
|
||||||
4. Order Completed - Order lifecycle complete
|
|
||||||
5. Order Cancelled - Cancellation with action items
|
|
||||||
6. Payment Received - Payment notification
|
|
||||||
7. Payment Failed - Payment failure alert
|
|
||||||
|
|
||||||
**Template Syntax:**
|
|
||||||
```
|
|
||||||
[card type="hero"]
|
|
||||||
Welcome message here
|
|
||||||
[/card]
|
|
||||||
|
|
||||||
[card]
|
|
||||||
**Order Number:** #{order_number}
|
|
||||||
**Order Total:** {order_total}
|
|
||||||
[/card]
|
|
||||||
|
|
||||||
[button url="{order_url}"]View Order Details[/button]
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
© {current_year} {site_name}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. **Enhanced Markdown Parser** ✅
|
|
||||||
**File:** `admin-spa/src/lib/markdown-parser.ts`
|
|
||||||
|
|
||||||
**New Features:**
|
|
||||||
- ✅ Button shortcode: `[button url="..."]Text[/button]`
|
|
||||||
- ✅ Horizontal rules: `---`
|
|
||||||
- ✅ Checkmarks and bullet points: `✓` `•` `-` `*`
|
|
||||||
- ✅ Card blocks with types: `[card type="success"]...[/card]`
|
|
||||||
- ✅ Bold, italic, headings, lists, links
|
|
||||||
- ✅ Variable support: `{variable_name}`
|
|
||||||
|
|
||||||
**Supported Markdown:**
|
|
||||||
```markdown
|
|
||||||
# Heading 1
|
|
||||||
## Heading 2
|
|
||||||
### Heading 3
|
|
||||||
|
|
||||||
**Bold text**
|
|
||||||
*Italic text*
|
|
||||||
|
|
||||||
- List item
|
|
||||||
• Bullet point
|
|
||||||
✓ Checkmark item
|
|
||||||
|
|
||||||
[Link text](url)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[card type="hero"]
|
|
||||||
Card content
|
|
||||||
[/card]
|
|
||||||
|
|
||||||
[button url="#"]Button Text[/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. **Visual Email Builder** ✅
|
|
||||||
**File:** `admin-spa/src/components/EmailBuilder/EmailBuilder.tsx`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ Drag-and-drop block editor
|
|
||||||
- ✅ Card blocks (default, success, info, warning, hero)
|
|
||||||
- ✅ Button blocks (solid/outline, width/alignment controls)
|
|
||||||
- ✅ Image blocks with WordPress media library integration
|
|
||||||
- ✅ Divider and spacer blocks
|
|
||||||
- ✅ Rich text editor with variable insertion
|
|
||||||
- ✅ Mobile fallback UI (desktop-only message)
|
|
||||||
- ✅ WordPress media modal integration (z-index and pointer-events fixed)
|
|
||||||
- ✅ Dialog outside-click prevention with WP media exception
|
|
||||||
|
|
||||||
**Block Types:**
|
|
||||||
1. **Card** - Content container with type variants
|
|
||||||
2. **Button** - CTA button with style and layout options
|
|
||||||
3. **Image** - Image with alignment and width controls
|
|
||||||
4. **Divider** - Horizontal line separator
|
|
||||||
5. **Spacer** - Vertical spacing control
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. **Preview System** ✅
|
|
||||||
**File:** `admin-spa/src/routes/Settings/Notifications/EditTemplate.tsx`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ Live preview with actual branding colors
|
|
||||||
- ✅ Sample data for all variables
|
|
||||||
- ✅ Mobile-responsive preview (reduced padding on small screens)
|
|
||||||
- ✅ Button shortcode parsing
|
|
||||||
- ✅ Card parsing with type support
|
|
||||||
- ✅ Variable replacement with sample data
|
|
||||||
|
|
||||||
**Mobile Responsive:**
|
|
||||||
```css
|
|
||||||
@media only screen and (max-width: 600px) {
|
|
||||||
body { padding: 8px; }
|
|
||||||
.card-gutter { padding: 0 8px; }
|
|
||||||
.card { padding: 20px 16px; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. **Variable System** ✅
|
|
||||||
|
|
||||||
**Complete Variable Support:**
|
|
||||||
|
|
||||||
**Order Variables:**
|
|
||||||
- `{order_number}` - Order number/ID
|
|
||||||
- `{order_date}` - Order creation date
|
|
||||||
- `{order_total}` - Total order amount
|
|
||||||
- `{order_url}` - Link to view order
|
|
||||||
- `{order_item_table}` - Formatted order items table
|
|
||||||
- `{completion_date}` - Order completion date
|
|
||||||
|
|
||||||
**Customer Variables:**
|
|
||||||
- `{customer_name}` - Customer's full name
|
|
||||||
- `{customer_email}` - Customer's email
|
|
||||||
- `{customer_phone}` - Customer's phone
|
|
||||||
|
|
||||||
**Payment Variables:**
|
|
||||||
- `{payment_method}` - Payment method used
|
|
||||||
- `{payment_status}` - Payment status
|
|
||||||
- `{payment_date}` - Payment date
|
|
||||||
- `{transaction_id}` - Transaction ID
|
|
||||||
- `{payment_retry_url}` - URL to retry payment
|
|
||||||
|
|
||||||
**Shipping Variables:**
|
|
||||||
- `{tracking_number}` - Tracking number
|
|
||||||
- `{tracking_url}` - Tracking URL
|
|
||||||
- `{shipping_carrier}` - Carrier name
|
|
||||||
- `{shipping_address}` - Full shipping address
|
|
||||||
- `{billing_address}` - Full billing address
|
|
||||||
|
|
||||||
**URL Variables:**
|
|
||||||
- `{order_url}` - Order details page
|
|
||||||
- `{review_url}` - Leave review page
|
|
||||||
- `{shop_url}` - Shop homepage
|
|
||||||
- `{my_account_url}` - Customer account page
|
|
||||||
- `{vip_dashboard_url}` - VIP dashboard
|
|
||||||
|
|
||||||
**Store Variables:**
|
|
||||||
- `{site_name}` - Store name
|
|
||||||
- `{store_url}` - Store URL
|
|
||||||
- `{support_email}` - Support email
|
|
||||||
- `{current_year}` - Current year
|
|
||||||
|
|
||||||
**VIP Variables:**
|
|
||||||
- `{vip_free_shipping_threshold}` - Free shipping threshold
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. **Bug Fixes** ✅
|
|
||||||
|
|
||||||
**WordPress Media Modal Integration:**
|
|
||||||
- ✅ Fixed z-index conflict (WP media now appears above Radix components)
|
|
||||||
- ✅ Fixed pointer-events blocking (WP media is now fully clickable)
|
|
||||||
- ✅ Fixed dialog closing when selecting image (dialog stays open)
|
|
||||||
- ✅ Added exception for WP media in outside-click prevention
|
|
||||||
|
|
||||||
**CSS Fixes:**
|
|
||||||
```css
|
|
||||||
/* WordPress Media Modal z-index fix */
|
|
||||||
.media-modal {
|
|
||||||
z-index: 999999 !important;
|
|
||||||
pointer-events: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-modal-content {
|
|
||||||
z-index: 1000000 !important;
|
|
||||||
pointer-events: auto !important;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Dialog Fix:**
|
|
||||||
```typescript
|
|
||||||
onInteractOutside={(e) => {
|
|
||||||
const wpMediaOpen = document.querySelector('.media-modal');
|
|
||||||
if (wpMediaOpen) {
|
|
||||||
e.preventDefault(); // Keep dialog open when WP media is active
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.preventDefault(); // Prevent closing for other outside clicks
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 Mobile Strategy
|
|
||||||
|
|
||||||
**Current Implementation (Optimal):**
|
|
||||||
- ✅ **Preview Tab** - Works on mobile (read-only viewing)
|
|
||||||
- ✅ **Code Tab** - Works on mobile (advanced users can edit)
|
|
||||||
- ❌ **Builder Tab** - Desktop-only with clear message
|
|
||||||
|
|
||||||
**Why This Works:**
|
|
||||||
- Users can view email previews on any device
|
|
||||||
- Power users can make quick code edits on mobile
|
|
||||||
- Visual builder requires desktop for optimal UX
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 Email Customization Features
|
|
||||||
|
|
||||||
**Available in Settings:**
|
|
||||||
1. **Brand Colors**
|
|
||||||
- Primary color
|
|
||||||
- Secondary color
|
|
||||||
- Hero gradient (start/end)
|
|
||||||
- Hero text color
|
|
||||||
- Button text color
|
|
||||||
|
|
||||||
2. **Layout**
|
|
||||||
- Body background color
|
|
||||||
- Logo upload
|
|
||||||
- Header text
|
|
||||||
- Footer text
|
|
||||||
|
|
||||||
3. **Social Links**
|
|
||||||
- Facebook, Twitter, Instagram, LinkedIn, YouTube, Website
|
|
||||||
- Custom icon color (white/color)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Ready for Production
|
|
||||||
|
|
||||||
**What Store Owners Get:**
|
|
||||||
1. ✅ Professional email templates out-of-the-box
|
|
||||||
2. ✅ Easy customization with visual builder
|
|
||||||
3. ✅ Code mode for advanced users
|
|
||||||
4. ✅ Live preview with branding
|
|
||||||
5. ✅ Mobile-friendly emails
|
|
||||||
6. ✅ Complete variable system
|
|
||||||
7. ✅ WordPress media library integration
|
|
||||||
|
|
||||||
**No Setup Required:**
|
|
||||||
- Templates are ready to use immediately
|
|
||||||
- Store owners can start selling without editing emails
|
|
||||||
- Customization is optional but easy
|
|
||||||
- However, backend integration is still required for full functionality
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps (REQUIRED)
|
|
||||||
|
|
||||||
**IMPORTANT: Backend Integration Still Needed**
|
|
||||||
|
|
||||||
The new `DefaultTemplates.php` is ready but NOT YET WIRED to the backend!
|
|
||||||
|
|
||||||
**Current State:**
|
|
||||||
- New templates created: `includes/Email/DefaultTemplates.php`
|
|
||||||
- Backend still using old: `includes/Core/Notifications/DefaultEmailTemplates.php`
|
|
||||||
|
|
||||||
**To Complete Integration:**
|
|
||||||
1. Update `includes/Core/Notifications/DefaultEmailTemplates.php` to use new `DefaultTemplates` class
|
|
||||||
2. Or replace old class entirely with new one
|
|
||||||
3. Update API controller to return correct event counts per recipient
|
|
||||||
4. Wire up to database on plugin activation
|
|
||||||
5. Hook into WooCommerce order status changes
|
|
||||||
6. Test email sending
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```php
|
|
||||||
use WooNooW\Email\DefaultTemplates;
|
|
||||||
|
|
||||||
// On plugin activation
|
|
||||||
$templates = DefaultTemplates::get_all_templates();
|
|
||||||
foreach ($templates['customer'] as $event => $body) {
|
|
||||||
$subject = DefaultTemplates::get_default_subject('customer', $event);
|
|
||||||
// Save to database
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Phase Complete
|
|
||||||
|
|
||||||
The email template and builder system is now **production-ready** and can be shipped to users!
|
|
||||||
|
|
||||||
**Key Achievements:**
|
|
||||||
- ✅ 16 professional email templates
|
|
||||||
- ✅ Visual builder with drag-and-drop
|
|
||||||
- ✅ WordPress media library integration
|
|
||||||
- ✅ Mobile-responsive preview
|
|
||||||
- ✅ Complete variable system
|
|
||||||
- ✅ All bugs fixed
|
|
||||||
- ✅ Ready for general store owners
|
|
||||||
|
|
||||||
**Time to move on to the next phase!** 🎉
|
|
||||||
@@ -1,388 +0,0 @@
|
|||||||
# Email Builder - All Improvements Complete! 🎉
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
All 5 user-requested improvements have been successfully implemented, creating a professional, user-friendly email template builder that respects WordPress conventions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 1. Heading Selector in RichTextEditor
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
Users couldn't control heading levels without typing HTML manually.
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Added a dropdown selector in the RichTextEditor toolbar.
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Dropdown with options: Paragraph, H1, H2, H3, H4
|
|
||||||
- Visual feedback (shows active heading level)
|
|
||||||
- One-click heading changes
|
|
||||||
- User controls document structure
|
|
||||||
|
|
||||||
**UI Location:**
|
|
||||||
```
|
|
||||||
[Paragraph ▼] [B] [I] [List] [Link] ...
|
|
||||||
↑
|
|
||||||
First item in toolbar
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `components/ui/rich-text-editor.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 2. Styled Buttons in Cards
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- Buttons in TipTap cards looked raw (unstyled)
|
|
||||||
- Different appearance from standalone buttons
|
|
||||||
- Not editable (couldn't change text/URL by clicking)
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Created a custom TipTap extension for buttons with proper styling.
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Same inline styles as standalone buttons
|
|
||||||
- Solid & Outline styles available
|
|
||||||
- Fully editable via dialog
|
|
||||||
- Non-editable in editor (atomic node)
|
|
||||||
- Click button icon → dialog opens
|
|
||||||
|
|
||||||
**Button Styles:**
|
|
||||||
```css
|
|
||||||
Solid (Primary):
|
|
||||||
background: #7f54b3
|
|
||||||
color: white
|
|
||||||
padding: 14px 28px
|
|
||||||
|
|
||||||
Outline (Secondary):
|
|
||||||
background: transparent
|
|
||||||
color: #7f54b3
|
|
||||||
border: 2px solid #7f54b3
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files Created:**
|
|
||||||
- `components/ui/tiptap-button-extension.ts`
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `components/ui/rich-text-editor.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 3. Variable Pills for Button Links
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- Users had to type `{variable_name}` manually
|
|
||||||
- Easy to make typos
|
|
||||||
- No suggestions or discovery
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Added clickable variable pills under Button Link inputs.
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Visual display of available variables
|
|
||||||
- One-click insertion
|
|
||||||
- No typing errors
|
|
||||||
- Works in both:
|
|
||||||
- RichTextEditor button dialog
|
|
||||||
- EmailBuilder button dialog
|
|
||||||
|
|
||||||
**UI:**
|
|
||||||
```
|
|
||||||
Button Link
|
|
||||||
┌─────────────────────────┐
|
|
||||||
│ {order_url} │
|
|
||||||
└─────────────────────────┘
|
|
||||||
|
|
||||||
{order_number} {order_total} {customer_name} ...
|
|
||||||
↑ Click any pill to insert
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `components/ui/rich-text-editor.tsx`
|
|
||||||
- `components/EmailBuilder/EmailBuilder.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 4. WordPress Media Modal for TipTap Images
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- Prompt dialog for image URL
|
|
||||||
- Manual URL entry required
|
|
||||||
- No access to media library
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Integrated WordPress native Media Modal for image selection.
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Native WordPress Media Modal
|
|
||||||
- Browse existing uploads
|
|
||||||
- Upload new images
|
|
||||||
- Full media library features
|
|
||||||
- Auto-sets: src, alt, title
|
|
||||||
|
|
||||||
**User Flow:**
|
|
||||||
1. Click image icon in RichTextEditor toolbar
|
|
||||||
2. WordPress Media Modal opens
|
|
||||||
3. Select from library OR upload new
|
|
||||||
4. Image inserted with proper attributes
|
|
||||||
|
|
||||||
**Files Created:**
|
|
||||||
- `lib/wp-media.ts` (WordPress Media helper)
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `components/ui/rich-text-editor.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 5. WordPress Media Modal for Store Logos/Favicon
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- Only drag-and-drop or file picker available
|
|
||||||
- No access to existing media library
|
|
||||||
- Couldn't reuse uploaded assets
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Added "Choose from Media Library" button to ImageUpload component.
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- WordPress Media Modal integration
|
|
||||||
- Filtered by media type:
|
|
||||||
- **Logo**: PNG, JPEG, SVG, WebP
|
|
||||||
- **Favicon**: PNG, ICO
|
|
||||||
- Browse and reuse existing assets
|
|
||||||
- Drag-and-drop still works
|
|
||||||
|
|
||||||
**UI:**
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────┐
|
|
||||||
│ [Upload Icon] │
|
|
||||||
│ │
|
|
||||||
│ Drop image here or click │
|
|
||||||
│ Max size: 2MB │
|
|
||||||
│ │
|
|
||||||
│ [Choose from Media Library] │
|
|
||||||
└─────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files Modified:**
|
|
||||||
- `components/ui/image-upload.tsx`
|
|
||||||
- `routes/Settings/Store.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 New Files Created
|
|
||||||
|
|
||||||
### 1. `lib/wp-media.ts`
|
|
||||||
WordPress Media Modal integration helper.
|
|
||||||
|
|
||||||
**Functions:**
|
|
||||||
- `openWPMedia()` - Core function with options
|
|
||||||
- `openWPMediaImage()` - For general images
|
|
||||||
- `openWPMediaLogo()` - For logos (filtered)
|
|
||||||
- `openWPMediaFavicon()` - For favicons (filtered)
|
|
||||||
|
|
||||||
**Interface:**
|
|
||||||
```typescript
|
|
||||||
interface WPMediaFile {
|
|
||||||
url: string;
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
filename: string;
|
|
||||||
alt?: string;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. `components/ui/tiptap-button-extension.ts`
|
|
||||||
Custom TipTap node for styled buttons.
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Renders with inline styles
|
|
||||||
- Atomic node (non-editable)
|
|
||||||
- Data attributes for editing
|
|
||||||
- Matches email rendering exactly
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 User Experience Improvements
|
|
||||||
|
|
||||||
### For Non-Technical Users
|
|
||||||
- **Heading Control**: No HTML knowledge needed
|
|
||||||
- **Visual Buttons**: Professional styling automatically
|
|
||||||
- **Variable Discovery**: See all available variables
|
|
||||||
- **Media Library**: Familiar WordPress interface
|
|
||||||
|
|
||||||
### For Tech-Savvy Users
|
|
||||||
- **Code Mode**: Still available with CodeMirror
|
|
||||||
- **Full Control**: Can edit raw HTML
|
|
||||||
- **Professional Tools**: Syntax highlighting, auto-completion
|
|
||||||
|
|
||||||
### For Everyone
|
|
||||||
- **Consistent UX**: Matches WordPress conventions
|
|
||||||
- **No Learning Curve**: Familiar interfaces
|
|
||||||
- **Professional Results**: Beautiful emails every time
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🙏 Respecting WordPress
|
|
||||||
|
|
||||||
### Why This Matters
|
|
||||||
|
|
||||||
**1. Familiar Interface**
|
|
||||||
Users already know WordPress Media Modal from Posts/Pages.
|
|
||||||
|
|
||||||
**2. Existing Assets**
|
|
||||||
Access to all uploaded media, no re-uploading.
|
|
||||||
|
|
||||||
**3. Better UX**
|
|
||||||
No manual URL entry, visual selection.
|
|
||||||
|
|
||||||
**4. Professional**
|
|
||||||
Native WordPress integration, not a custom solution.
|
|
||||||
|
|
||||||
**5. Consistent**
|
|
||||||
Same experience across WordPress admin.
|
|
||||||
|
|
||||||
### WordPress Integration Details
|
|
||||||
|
|
||||||
**Uses:**
|
|
||||||
- `window.wp.media` API
|
|
||||||
- WordPress REST API for uploads
|
|
||||||
- Proper nonce handling
|
|
||||||
- User permissions respected
|
|
||||||
|
|
||||||
**Compatible with:**
|
|
||||||
- WordPress Media Library
|
|
||||||
- Custom upload handlers
|
|
||||||
- Media organization plugins
|
|
||||||
- CDN integrations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Complete Feature List
|
|
||||||
|
|
||||||
### Email Builder Features
|
|
||||||
✅ Visual block-based editor
|
|
||||||
✅ Drag-and-drop reordering
|
|
||||||
✅ Card blocks with rich content
|
|
||||||
✅ Standalone buttons (outside cards)
|
|
||||||
✅ Dividers and spacers
|
|
||||||
✅ Code mode with CodeMirror
|
|
||||||
✅ Variable insertion
|
|
||||||
✅ Preview mode
|
|
||||||
✅ Responsive design
|
|
||||||
|
|
||||||
### RichTextEditor Features
|
|
||||||
✅ Heading selector (H1-H4, Paragraph)
|
|
||||||
✅ Bold, Italic formatting
|
|
||||||
✅ Bullet and numbered lists
|
|
||||||
✅ Links
|
|
||||||
✅ Text alignment (left, center, right)
|
|
||||||
✅ Image insertion (WordPress Media)
|
|
||||||
✅ Button insertion (styled)
|
|
||||||
✅ Variable insertion (pills)
|
|
||||||
✅ Undo/Redo
|
|
||||||
|
|
||||||
### Store Settings Features
|
|
||||||
✅ Logo upload (light mode)
|
|
||||||
✅ Logo upload (dark mode)
|
|
||||||
✅ Favicon upload
|
|
||||||
✅ WordPress Media Modal integration
|
|
||||||
✅ Drag-and-drop upload
|
|
||||||
✅ File type filtering
|
|
||||||
✅ Preview display
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Installation & Testing
|
|
||||||
|
|
||||||
### Install Dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd admin-spa
|
|
||||||
|
|
||||||
# TipTap Extensions
|
|
||||||
npm install @tiptap/extension-text-align @tiptap/extension-image
|
|
||||||
|
|
||||||
# CodeMirror
|
|
||||||
npm install codemirror @codemirror/lang-html @codemirror/theme-one-dark
|
|
||||||
|
|
||||||
# Radix UI
|
|
||||||
npm install @radix-ui/react-radio-group
|
|
||||||
```
|
|
||||||
|
|
||||||
### Or Install All at Once
|
|
||||||
```bash
|
|
||||||
npm install @tiptap/extension-text-align @tiptap/extension-image codemirror @codemirror/lang-html @codemirror/theme-one-dark @radix-ui/react-radio-group
|
|
||||||
```
|
|
||||||
|
|
||||||
### Start Development Server
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Checklist
|
|
||||||
|
|
||||||
**Email Builder:**
|
|
||||||
- [ ] Add card with rich content
|
|
||||||
- [ ] Use heading selector (H1-H4)
|
|
||||||
- [ ] Insert styled button in card
|
|
||||||
- [ ] Add standalone button
|
|
||||||
- [ ] Click variable pills to insert
|
|
||||||
- [ ] Insert image via WordPress Media
|
|
||||||
- [ ] Test text alignment
|
|
||||||
- [ ] Preview email
|
|
||||||
- [ ] Switch to code mode
|
|
||||||
- [ ] Save template
|
|
||||||
|
|
||||||
**Store Settings:**
|
|
||||||
- [ ] Upload logo (light) via drag-and-drop
|
|
||||||
- [ ] Upload logo (dark) via Media Library
|
|
||||||
- [ ] Upload favicon via Media Library
|
|
||||||
- [ ] Remove and re-upload
|
|
||||||
- [ ] Verify preview display
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Summary
|
|
||||||
|
|
||||||
### What We Built
|
|
||||||
|
|
||||||
A **professional, user-friendly email template builder** that:
|
|
||||||
- Respects WordPress conventions
|
|
||||||
- Provides visual editing for beginners
|
|
||||||
- Offers code mode for experts
|
|
||||||
- Integrates seamlessly with WordPress Media
|
|
||||||
- Produces beautiful, responsive emails
|
|
||||||
|
|
||||||
### Key Achievements
|
|
||||||
|
|
||||||
1. **No HTML Knowledge Required** - Visual builder handles everything
|
|
||||||
2. **Professional Styling** - Buttons and content look great
|
|
||||||
3. **WordPress Integration** - Native Media Modal support
|
|
||||||
4. **Variable System** - Easy dynamic content insertion
|
|
||||||
5. **Flexible** - Visual builder OR code mode
|
|
||||||
|
|
||||||
### Production Ready
|
|
||||||
|
|
||||||
All features tested and working:
|
|
||||||
- ✅ Block structure optimized
|
|
||||||
- ✅ Rich content editing
|
|
||||||
- ✅ WordPress Media integration
|
|
||||||
- ✅ Variable insertion
|
|
||||||
- ✅ Professional styling
|
|
||||||
- ✅ Code mode available
|
|
||||||
- ✅ Responsive design
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 Result
|
|
||||||
|
|
||||||
**The PERFECT email template builder for WooCommerce!**
|
|
||||||
|
|
||||||
Combines the simplicity of a visual builder with the power of code editing, all while respecting WordPress conventions and providing a familiar user experience.
|
|
||||||
|
|
||||||
**Best of all worlds!** 🚀
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
# Email Customization - Complete Implementation! 🎉
|
|
||||||
|
|
||||||
## ✅ All 5 Tasks Completed
|
|
||||||
|
|
||||||
### 1. Logo URL with WP Media Library
|
|
||||||
**Status:** ✅ Complete
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- "Select" button opens WordPress Media Library
|
|
||||||
- Logo preview below input field
|
|
||||||
- Can paste URL or select from media
|
|
||||||
- Proper image sizing (200x60px recommended)
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
- Uses `openWPMediaLogo()` from wp-media.ts
|
|
||||||
- Preview shows selected logo
|
|
||||||
- Applied to email header in EmailRenderer
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Footer Text with {current_year} Variable
|
|
||||||
**Status:** ✅ Complete
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Placeholder shows `© {current_year} Your Store`
|
|
||||||
- Help text explains dynamic year variable
|
|
||||||
- Backend replaces {current_year} with actual year
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
```php
|
|
||||||
$footer_text = str_replace('{current_year}', date('Y'), $footer_text);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```
|
|
||||||
Input: © {current_year} My Store. All rights reserved.
|
|
||||||
Output: © 2024 My Store. All rights reserved.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Social Links in Footer
|
|
||||||
**Status:** ✅ Complete
|
|
||||||
|
|
||||||
**Supported Platforms:**
|
|
||||||
- Facebook
|
|
||||||
- Twitter
|
|
||||||
- Instagram
|
|
||||||
- LinkedIn
|
|
||||||
- YouTube
|
|
||||||
- Website
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Add/remove social links
|
|
||||||
- Platform dropdown with icons
|
|
||||||
- URL input for each
|
|
||||||
- Rendered as icons in email footer
|
|
||||||
- Centered alignment
|
|
||||||
|
|
||||||
**UI:**
|
|
||||||
```
|
|
||||||
[Facebook ▼] [https://facebook.com/yourpage] [🗑️]
|
|
||||||
[Twitter ▼] [https://twitter.com/yourhandle] [🗑️]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Email Output:**
|
|
||||||
```html
|
|
||||||
<div class="social-icons" style="margin-top: 16px; text-align: center;">
|
|
||||||
<a href="https://facebook.com/..."><img src="..." /></a>
|
|
||||||
<a href="https://twitter.com/..."><img src="..." /></a>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Backend API & Integration
|
|
||||||
**Status:** ✅ Complete
|
|
||||||
|
|
||||||
**API Endpoints:**
|
|
||||||
```
|
|
||||||
GET /woonoow/v1/notifications/email-settings
|
|
||||||
POST /woonoow/v1/notifications/email-settings
|
|
||||||
DELETE /woonoow/v1/notifications/email-settings
|
|
||||||
```
|
|
||||||
|
|
||||||
**Database:**
|
|
||||||
- Stored in wp_options as `woonoow_email_settings`
|
|
||||||
- JSON structure with all settings
|
|
||||||
- Defaults provided if not set
|
|
||||||
|
|
||||||
**Security:**
|
|
||||||
- Permission checks (manage_woocommerce)
|
|
||||||
- Input sanitization (sanitize_hex_color, esc_url_raw)
|
|
||||||
- Platform whitelist for social links
|
|
||||||
- URL validation
|
|
||||||
|
|
||||||
**Email Rendering:**
|
|
||||||
- EmailRenderer.php applies settings
|
|
||||||
- Logo/header text
|
|
||||||
- Footer with {current_year}
|
|
||||||
- Social icons
|
|
||||||
- Hero card colors
|
|
||||||
- Button colors (ready)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. Hero Card Text Color
|
|
||||||
**Status:** ✅ Complete
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Separate color picker for hero text
|
|
||||||
- Applied to headings and paragraphs
|
|
||||||
- Live preview in settings
|
|
||||||
- Usually white for dark gradients
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
```php
|
|
||||||
if ($type === 'hero' || $type === 'success') {
|
|
||||||
$style .= sprintf(
|
|
||||||
' background: linear-gradient(135deg, %s 0%%, %s 100%%);',
|
|
||||||
$hero_gradient_start,
|
|
||||||
$hero_gradient_end
|
|
||||||
);
|
|
||||||
$content_style .= sprintf(' color: %s;', $hero_text_color);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Preview:**
|
|
||||||
```
|
|
||||||
[#667eea] → [#764ba2] [#ffffff]
|
|
||||||
Gradient Start End Text Color
|
|
||||||
|
|
||||||
Preview:
|
|
||||||
┌─────────────────────────────┐
|
|
||||||
│ Preview (white text) │
|
|
||||||
│ This is how your hero │
|
|
||||||
│ cards will look │
|
|
||||||
└─────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Complete Settings Structure
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface EmailSettings {
|
|
||||||
// Brand Colors
|
|
||||||
primary_color: string; // #7f54b3
|
|
||||||
secondary_color: string; // #7f54b3
|
|
||||||
|
|
||||||
// Hero Card
|
|
||||||
hero_gradient_start: string; // #667eea
|
|
||||||
hero_gradient_end: string; // #764ba2
|
|
||||||
hero_text_color: string; // #ffffff
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
button_text_color: string; // #ffffff
|
|
||||||
|
|
||||||
// Branding
|
|
||||||
logo_url: string; // https://...
|
|
||||||
header_text: string; // Store Name
|
|
||||||
footer_text: string; // © {current_year} ...
|
|
||||||
|
|
||||||
// Social
|
|
||||||
social_links: SocialLink[]; // [{platform, url}]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### Frontend → Backend
|
|
||||||
1. User customizes settings in UI
|
|
||||||
2. Clicks "Save Settings"
|
|
||||||
3. POST to `/notifications/email-settings`
|
|
||||||
4. Backend sanitizes and stores in wp_options
|
|
||||||
|
|
||||||
### Backend → Email
|
|
||||||
1. Email triggered (order placed, etc.)
|
|
||||||
2. EmailRenderer loads settings
|
|
||||||
3. Applies colors, logo, footer
|
|
||||||
4. Renders with custom branding
|
|
||||||
5. Sends to customer
|
|
||||||
|
|
||||||
### Preview
|
|
||||||
1. EditTemplate loads settings
|
|
||||||
2. Applies to preview iframe
|
|
||||||
3. User sees real-time preview
|
|
||||||
4. Colors, logo, footer all visible
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- `routes/Settings/Notifications.tsx` - Added card
|
|
||||||
- `routes/Settings/Notifications/EmailCustomization.tsx` - NEW
|
|
||||||
- `App.tsx` - Added route
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- `includes/Api/NotificationsController.php` - API endpoints
|
|
||||||
- `includes/Core/Notifications/EmailRenderer.php` - Apply settings
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
### Settings Page
|
|
||||||
- [ ] Navigate to Settings → Notifications → Email Customization
|
|
||||||
- [ ] Change primary color → See button preview update
|
|
||||||
- [ ] Change hero gradient → See preview update
|
|
||||||
- [ ] Change hero text color → See preview text color change
|
|
||||||
- [ ] Click "Select" for logo → Media library opens
|
|
||||||
- [ ] Select logo → Preview shows below
|
|
||||||
- [ ] Add footer text with {current_year}
|
|
||||||
- [ ] Add social links (Facebook, Twitter, etc.)
|
|
||||||
- [ ] Click "Save Settings" → Success message
|
|
||||||
- [ ] Refresh page → Settings persist
|
|
||||||
- [ ] Click "Reset to Defaults" → Confirm → Settings reset
|
|
||||||
|
|
||||||
### Email Rendering
|
|
||||||
- [ ] Trigger test email (place order)
|
|
||||||
- [ ] Check email has custom logo (if set)
|
|
||||||
- [ ] Check email has custom header text (if set)
|
|
||||||
- [ ] Check hero cards have custom gradient
|
|
||||||
- [ ] Check hero cards have custom text color
|
|
||||||
- [ ] Check footer has {current_year} replaced with actual year
|
|
||||||
- [ ] Check footer has social icons
|
|
||||||
- [ ] Click social icons → Go to correct URLs
|
|
||||||
|
|
||||||
### Preview
|
|
||||||
- [ ] Edit email template
|
|
||||||
- [ ] Switch to Preview tab
|
|
||||||
- [ ] See custom colors applied
|
|
||||||
- [ ] See logo/header
|
|
||||||
- [ ] See footer with social icons
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps (Optional Enhancements)
|
|
||||||
|
|
||||||
### Button Color Application
|
|
||||||
Currently ready but needs template update:
|
|
||||||
```php
|
|
||||||
$primary_color = $email_settings['primary_color'] ?? '#7f54b3';
|
|
||||||
$button_text_color = $email_settings['button_text_color'] ?? '#ffffff';
|
|
||||||
|
|
||||||
// Apply to .button class in template
|
|
||||||
```
|
|
||||||
|
|
||||||
### Social Icon Assets
|
|
||||||
Need to create/host social icon images:
|
|
||||||
- facebook.png
|
|
||||||
- twitter.png
|
|
||||||
- instagram.png
|
|
||||||
- linkedin.png
|
|
||||||
- youtube.png
|
|
||||||
- website.png
|
|
||||||
|
|
||||||
Or use Font Awesome / inline SVG.
|
|
||||||
|
|
||||||
### Preview Integration
|
|
||||||
Update EditTemplate preview to fetch and apply email settings:
|
|
||||||
```typescript
|
|
||||||
const { data: emailSettings } = useQuery({
|
|
||||||
queryKey: ['email-settings'],
|
|
||||||
queryFn: () => api.get('/notifications/email-settings'),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply to preview styles
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
✅ **User Experience:**
|
|
||||||
- Easy logo selection (WP Media Library)
|
|
||||||
- Visual color pickers
|
|
||||||
- Live previews
|
|
||||||
- One-click save
|
|
||||||
- One-click reset
|
|
||||||
|
|
||||||
✅ **Functionality:**
|
|
||||||
- All settings saved to database
|
|
||||||
- All settings applied to emails
|
|
||||||
- Dynamic {current_year} variable
|
|
||||||
- Social links rendered
|
|
||||||
- Colors applied to cards
|
|
||||||
|
|
||||||
✅ **Code Quality:**
|
|
||||||
- Proper sanitization
|
|
||||||
- Security checks
|
|
||||||
- Type safety (TypeScript)
|
|
||||||
- Validation (platform whitelist)
|
|
||||||
- Fallback defaults
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 Complete!
|
|
||||||
|
|
||||||
All 5 tasks implemented and tested:
|
|
||||||
1. ✅ Logo with WP Media Library
|
|
||||||
2. ✅ Footer {current_year} variable
|
|
||||||
3. ✅ Social links
|
|
||||||
4. ✅ Backend API & email rendering
|
|
||||||
5. ✅ Hero text color
|
|
||||||
|
|
||||||
**Ready for production!** 🚀
|
|
||||||
@@ -1,532 +0,0 @@
|
|||||||
# Email Builder UX Refinements - Complete! 🎉
|
|
||||||
|
|
||||||
**Date:** November 13, 2025
|
|
||||||
**Status:** ✅ ALL TASKS COMPLETE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Successfully implemented all 7 major refinements to the email builder UX, including expanded social media integration, color customization, and comprehensive default email templates for all notification events.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Task 1: Expanded Social Media Platforms
|
|
||||||
|
|
||||||
### Platforms Added
|
|
||||||
- **Original:** Facebook, Twitter, Instagram, LinkedIn, YouTube, Website
|
|
||||||
- **New Additions:**
|
|
||||||
- X (Twitter rebrand)
|
|
||||||
- Discord
|
|
||||||
- Spotify
|
|
||||||
- Telegram
|
|
||||||
- WhatsApp
|
|
||||||
- Threads
|
|
||||||
- Website (Earth icon)
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
- **Frontend:** `EmailCustomization.tsx`
|
|
||||||
- Updated `getSocialIcon()` with all Lucide icons
|
|
||||||
- Expanded select dropdown with all platforms
|
|
||||||
- Each platform has appropriate icon and label
|
|
||||||
|
|
||||||
- **Backend:** `NotificationsController.php`
|
|
||||||
- Updated `allowed_platforms` array
|
|
||||||
- Validation for all new platforms
|
|
||||||
- Sanitization maintained
|
|
||||||
|
|
||||||
### Files Modified
|
|
||||||
- `admin-spa/src/routes/Settings/Notifications/EmailCustomization.tsx`
|
|
||||||
- `includes/Api/NotificationsController.php`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Task 2: PNG Icons Instead of Emoji
|
|
||||||
|
|
||||||
### Icon Assets
|
|
||||||
- **Location:** `/assets/icons/`
|
|
||||||
- **Format:** `mage--{platform}-{color}.png`
|
|
||||||
- **Platforms:** All 11 social platforms
|
|
||||||
- **Colors:** Black and White variants (22 total files)
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
- **Email Rendering:** `EmailRenderer.php`
|
|
||||||
- Updated `get_social_icon_url()` to return PNG URLs
|
|
||||||
- Uses plugin URL + assets path
|
|
||||||
- Dynamic color selection
|
|
||||||
|
|
||||||
- **Preview:** `EditTemplate.tsx`
|
|
||||||
- PNG icons in preview HTML
|
|
||||||
- Uses `pluginUrl` from window object
|
|
||||||
- Matches actual email rendering
|
|
||||||
|
|
||||||
### Benefits
|
|
||||||
- More accurate than emoji
|
|
||||||
- Consistent across email clients
|
|
||||||
- Professional appearance
|
|
||||||
- Better control over styling
|
|
||||||
|
|
||||||
### Files Modified
|
|
||||||
- `includes/Core/Notifications/EmailRenderer.php`
|
|
||||||
- `admin-spa/src/routes/Settings/Notifications/EditTemplate.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Task 3: Icon Color Selection (Black/White)
|
|
||||||
|
|
||||||
### New Setting
|
|
||||||
- **Field:** `social_icon_color`
|
|
||||||
- **Type:** Select dropdown
|
|
||||||
- **Options:**
|
|
||||||
- White Icons (for dark backgrounds)
|
|
||||||
- Black Icons (for light backgrounds)
|
|
||||||
- **Default:** White
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
- **Frontend:** `EmailCustomization.tsx`
|
|
||||||
- Select component with two options
|
|
||||||
- Clear labeling and description
|
|
||||||
- Saved with other settings
|
|
||||||
|
|
||||||
- **Backend:**
|
|
||||||
- `NotificationsController.php`: Validation (white/black only)
|
|
||||||
- `EmailRenderer.php`: Applied to icon URLs
|
|
||||||
- Default value in settings
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
```php
|
|
||||||
// Backend
|
|
||||||
$icon_color = $email_settings['social_icon_color'] ?? 'white';
|
|
||||||
$icon_url = $this->get_social_icon_url($platform, $icon_color);
|
|
||||||
|
|
||||||
// Frontend
|
|
||||||
const socialIconColor = settings.social_icon_color || 'white';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Files Modified
|
|
||||||
- `admin-spa/src/routes/Settings/Notifications/EmailCustomization.tsx`
|
|
||||||
- `includes/Api/NotificationsController.php`
|
|
||||||
- `includes/Core/Notifications/EmailRenderer.php`
|
|
||||||
- `admin-spa/src/routes/Settings/Notifications/EditTemplate.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Task 4: Body Background Color Setting
|
|
||||||
|
|
||||||
### New Setting
|
|
||||||
- **Field:** `body_bg_color`
|
|
||||||
- **Type:** Color picker + hex input
|
|
||||||
- **Default:** `#f8f8f8` (light gray)
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
- **UI Component:**
|
|
||||||
- Color picker for visual selection
|
|
||||||
- Text input for hex code entry
|
|
||||||
- Live preview in customization form
|
|
||||||
- Descriptive help text
|
|
||||||
|
|
||||||
- **Application:**
|
|
||||||
- Email body background in actual emails
|
|
||||||
- Preview iframe background
|
|
||||||
- Consistent across all email templates
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
```typescript
|
|
||||||
// Frontend
|
|
||||||
const bodyBgColor = settings.body_bg_color || '#f8f8f8';
|
|
||||||
|
|
||||||
// Applied to preview
|
|
||||||
body { background: ${bodyBgColor}; }
|
|
||||||
```
|
|
||||||
|
|
||||||
### Files Modified
|
|
||||||
- `admin-spa/src/routes/Settings/Notifications/EmailCustomization.tsx`
|
|
||||||
- `includes/Api/NotificationsController.php`
|
|
||||||
- `admin-spa/src/routes/Settings/Notifications/EditTemplate.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Task 5: Editor Mode Preview Styling
|
|
||||||
|
|
||||||
### Current Behavior
|
|
||||||
- **Editor Mode:** Shows content structure (blocks, HTML)
|
|
||||||
- **Preview Mode:** Shows final styled result with all customizations
|
|
||||||
|
|
||||||
### Design Decision
|
|
||||||
This is **intentional and follows standard email builder UX patterns**:
|
|
||||||
- Editor mode = content editing focus
|
|
||||||
- Preview mode = visual result preview
|
|
||||||
- Separation of concerns improves usability
|
|
||||||
|
|
||||||
### Rationale
|
|
||||||
- Users edit content in editor mode without distraction
|
|
||||||
- Preview mode shows exact final appearance
|
|
||||||
- Standard pattern in tools like Mailchimp, SendGrid, etc.
|
|
||||||
- Prevents confusion between editing and viewing
|
|
||||||
|
|
||||||
### Status
|
|
||||||
✅ **Working as designed** - No changes needed
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Task 6: Hero Preview Text Color Fix
|
|
||||||
|
|
||||||
### Issue
|
|
||||||
Hero card preview in customization form wasn't using selected `hero_text_color`.
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Applied color directly to child elements instead of parent:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Before (color inheritance not working)
|
|
||||||
<div style={{ background: gradient, color: heroTextColor }}>
|
|
||||||
<h3>Preview</h3>
|
|
||||||
<p>Text</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
// After (explicit color on each element)
|
|
||||||
<div style={{ background: gradient }}>
|
|
||||||
<h3 style={{ color: heroTextColor }}>Preview</h3>
|
|
||||||
<p style={{ color: heroTextColor }}>Text</p>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result
|
|
||||||
- Hero preview now correctly shows selected text color
|
|
||||||
- Live updates as user changes color
|
|
||||||
- Matches actual email rendering
|
|
||||||
|
|
||||||
### Files Modified
|
|
||||||
- `admin-spa/src/routes/Settings/Notifications/EmailCustomization.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Task 7: Complete Default Email Content
|
|
||||||
|
|
||||||
### New File Created
|
|
||||||
**`includes/Core/Notifications/DefaultEmailTemplates.php`**
|
|
||||||
|
|
||||||
Comprehensive default templates for all notification events with professional, card-based HTML.
|
|
||||||
|
|
||||||
### Templates Included
|
|
||||||
|
|
||||||
#### Order Events
|
|
||||||
|
|
||||||
**1. Order Placed (Staff)**
|
|
||||||
```
|
|
||||||
[card type="hero"]
|
|
||||||
New Order Received!
|
|
||||||
Order from {customer_name}
|
|
||||||
[/card]
|
|
||||||
[card] Order Details [/card]
|
|
||||||
[card] Customer Details [/card]
|
|
||||||
[card] Order Items [/card]
|
|
||||||
[button] View Order Details [/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
**2. Order Processing (Customer)**
|
|
||||||
```
|
|
||||||
[card type="success"]
|
|
||||||
Order Confirmed!
|
|
||||||
Thank you message
|
|
||||||
[/card]
|
|
||||||
[card] Order Summary [/card]
|
|
||||||
[card] What's Next [/card]
|
|
||||||
[card] Order Items [/card]
|
|
||||||
[button] Track Your Order [/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
**3. Order Completed (Customer)**
|
|
||||||
```
|
|
||||||
[card type="success"]
|
|
||||||
Order Completed!
|
|
||||||
Enjoy your purchase
|
|
||||||
[/card]
|
|
||||||
[card] Order Details [/card]
|
|
||||||
[card] Thank You Message [/card]
|
|
||||||
[button] View Order [/button]
|
|
||||||
[button outline] Continue Shopping [/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
**4. Order Cancelled (Staff)**
|
|
||||||
```
|
|
||||||
[card type="warning"]
|
|
||||||
Order Cancelled
|
|
||||||
[/card]
|
|
||||||
[card] Order Details [/card]
|
|
||||||
[button] View Order Details [/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
**5. Order Refunded (Customer)**
|
|
||||||
```
|
|
||||||
[card type="info"]
|
|
||||||
Refund Processed
|
|
||||||
[/card]
|
|
||||||
[card] Refund Details [/card]
|
|
||||||
[card] What Happens Next [/card]
|
|
||||||
[button] View Order [/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Product Events
|
|
||||||
|
|
||||||
**6. Low Stock Alert (Staff)**
|
|
||||||
```
|
|
||||||
[card type="warning"]
|
|
||||||
Low Stock Alert
|
|
||||||
[/card]
|
|
||||||
[card] Product Details [/card]
|
|
||||||
[card] Action Required [/card]
|
|
||||||
[button] View Product [/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
**7. Out of Stock Alert (Staff)**
|
|
||||||
```
|
|
||||||
[card type="warning"]
|
|
||||||
Out of Stock Alert
|
|
||||||
[/card]
|
|
||||||
[card] Product Details [/card]
|
|
||||||
[card] Immediate Action Required [/card]
|
|
||||||
[button] Manage Product [/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Customer Events
|
|
||||||
|
|
||||||
**8. New Customer (Customer)**
|
|
||||||
```
|
|
||||||
[card type="hero"]
|
|
||||||
Welcome!
|
|
||||||
Thank you for creating an account
|
|
||||||
[/card]
|
|
||||||
[card] Your Account Details [/card]
|
|
||||||
[card] Get Started (feature list) [/card]
|
|
||||||
[button] Go to My Account [/button]
|
|
||||||
[button outline] Start Shopping [/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
**9. Customer Note (Customer)**
|
|
||||||
```
|
|
||||||
[card type="info"]
|
|
||||||
Order Note Added
|
|
||||||
[/card]
|
|
||||||
[card] Order Details [/card]
|
|
||||||
[card] Note from Store [/card]
|
|
||||||
[button] View Order [/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Integration
|
|
||||||
|
|
||||||
**Updated `TemplateProvider.php`:**
|
|
||||||
```php
|
|
||||||
public static function get_default_templates() {
|
|
||||||
// Generate email templates from DefaultEmailTemplates
|
|
||||||
foreach ($events as $event_id => $recipient_type) {
|
|
||||||
$default = DefaultEmailTemplates::get_template($event_id, $recipient_type);
|
|
||||||
$templates["{$event_id}_email"] = [
|
|
||||||
'event_id' => $event_id,
|
|
||||||
'channel_id' => 'email',
|
|
||||||
'subject' => $default['subject'],
|
|
||||||
'body' => $default['body'],
|
|
||||||
'variables' => self::get_variables_for_event($event_id),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
// ... push templates
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- ✅ All 9 events covered
|
|
||||||
- ✅ Separate staff/customer templates
|
|
||||||
- ✅ Professional copy and structure
|
|
||||||
- ✅ Card-based modern design
|
|
||||||
- ✅ Multiple card types (hero, success, warning, info)
|
|
||||||
- ✅ Multiple buttons with styles
|
|
||||||
- ✅ Proper variable placeholders
|
|
||||||
- ✅ Consistent branding
|
|
||||||
- ✅ Push notification templates included
|
|
||||||
|
|
||||||
### Files Created/Modified
|
|
||||||
- `includes/Core/Notifications/DefaultEmailTemplates.php` (NEW)
|
|
||||||
- `includes/Core/Notifications/TemplateProvider.php` (UPDATED)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Technical Summary
|
|
||||||
|
|
||||||
### Settings Schema
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface EmailSettings {
|
|
||||||
// Colors
|
|
||||||
primary_color: string; // #7f54b3
|
|
||||||
secondary_color: string; // #7f54b3
|
|
||||||
hero_gradient_start: string; // #667eea
|
|
||||||
hero_gradient_end: string; // #764ba2
|
|
||||||
hero_text_color: string; // #ffffff
|
|
||||||
button_text_color: string; // #ffffff
|
|
||||||
body_bg_color: string; // #f8f8f8 (NEW)
|
|
||||||
social_icon_color: 'white' | 'black'; // (NEW)
|
|
||||||
|
|
||||||
// Branding
|
|
||||||
logo_url: string;
|
|
||||||
header_text: string;
|
|
||||||
footer_text: string;
|
|
||||||
|
|
||||||
// Social Links
|
|
||||||
social_links: Array<{
|
|
||||||
platform: string; // 11 platforms supported
|
|
||||||
url: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### API Endpoints
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /woonoow/v1/notifications/email-settings
|
|
||||||
POST /woonoow/v1/notifications/email-settings
|
|
||||||
DELETE /woonoow/v1/notifications/email-settings
|
|
||||||
```
|
|
||||||
|
|
||||||
### Storage
|
|
||||||
- **Option Key:** `woonoow_email_settings`
|
|
||||||
- **Sanitization:** All inputs sanitized
|
|
||||||
- **Validation:** Colors, URLs, platforms validated
|
|
||||||
- **Defaults:** Comprehensive defaults provided
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
### Social Media Integration
|
|
||||||
- [x] All 11 platforms appear in dropdown
|
|
||||||
- [x] Icons display correctly in customization UI
|
|
||||||
- [x] PNG icons render in email preview
|
|
||||||
- [x] PNG icons render in actual emails
|
|
||||||
- [x] Black/white icon selection works
|
|
||||||
- [x] Social links save and load correctly
|
|
||||||
|
|
||||||
### Color Settings
|
|
||||||
- [x] Body background color picker works
|
|
||||||
- [x] Body background applies to preview
|
|
||||||
- [x] Body background applies to emails
|
|
||||||
- [x] Icon color selection works
|
|
||||||
- [x] Hero text color preview fixed
|
|
||||||
- [x] All colors save and persist
|
|
||||||
|
|
||||||
### Default Templates
|
|
||||||
- [x] All 9 email events have templates
|
|
||||||
- [x] Staff templates appropriate for admins
|
|
||||||
- [x] Customer templates appropriate for customers
|
|
||||||
- [x] Card syntax correct
|
|
||||||
- [x] Variables properly placed
|
|
||||||
- [x] Buttons included where needed
|
|
||||||
- [x] Push templates complete
|
|
||||||
|
|
||||||
### Integration
|
|
||||||
- [x] Settings API working
|
|
||||||
- [x] Frontend loads settings
|
|
||||||
- [x] Preview reflects settings
|
|
||||||
- [x] Emails use settings
|
|
||||||
- [x] Reset functionality works
|
|
||||||
- [x] Save functionality works
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Changed
|
|
||||||
|
|
||||||
### Frontend (React/TypeScript)
|
|
||||||
```
|
|
||||||
admin-spa/src/routes/Settings/Notifications/
|
|
||||||
├── EmailCustomization.tsx (Updated - UI for all settings)
|
|
||||||
└── EditTemplate.tsx (Updated - Preview with PNG icons)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backend (PHP)
|
|
||||||
```
|
|
||||||
includes/
|
|
||||||
├── Api/
|
|
||||||
│ └── NotificationsController.php (Updated - API endpoints)
|
|
||||||
└── Core/Notifications/
|
|
||||||
├── EmailRenderer.php (Updated - PNG icons, colors)
|
|
||||||
├── TemplateProvider.php (Updated - Integration)
|
|
||||||
└── DefaultEmailTemplates.php (NEW - All default content)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Assets
|
|
||||||
```
|
|
||||||
assets/icons/
|
|
||||||
├── mage--discord-black.png
|
|
||||||
├── mage--discord-white.png
|
|
||||||
├── mage--earth-black.png
|
|
||||||
├── mage--earth-white.png
|
|
||||||
├── mage--facebook-black.png
|
|
||||||
├── mage--facebook-white.png
|
|
||||||
├── mage--instagram-black.png
|
|
||||||
├── mage--instagram-white.png
|
|
||||||
├── mage--linkedin-black.png
|
|
||||||
├── mage--linkedin-white.png
|
|
||||||
├── mage--spotify-black.png
|
|
||||||
├── mage--spotify-white.png
|
|
||||||
├── mage--telegram-black.png
|
|
||||||
├── mage--telegram-white.png
|
|
||||||
├── mage--threads-black.png
|
|
||||||
├── mage--threads-white.png
|
|
||||||
├── mage--whatsapp-black.png
|
|
||||||
├── mage--whatsapp-white.png
|
|
||||||
├── mage--x-black.png
|
|
||||||
├── mage--x-white.png
|
|
||||||
├── mage--youtube-black.png
|
|
||||||
└── mage--youtube-white.png
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps (Optional Enhancements)
|
|
||||||
|
|
||||||
### Future Improvements
|
|
||||||
1. **Email Template Variables**
|
|
||||||
- Add more dynamic variables
|
|
||||||
- Variable preview in editor
|
|
||||||
- Variable documentation
|
|
||||||
|
|
||||||
2. **Template Library**
|
|
||||||
- Pre-built template variations
|
|
||||||
- Industry-specific templates
|
|
||||||
- Seasonal templates
|
|
||||||
|
|
||||||
3. **A/B Testing**
|
|
||||||
- Test different subject lines
|
|
||||||
- Test different layouts
|
|
||||||
- Analytics integration
|
|
||||||
|
|
||||||
4. **Advanced Customization**
|
|
||||||
- Font family selection
|
|
||||||
- Font size controls
|
|
||||||
- Spacing/padding controls
|
|
||||||
- Border radius controls
|
|
||||||
|
|
||||||
5. **Conditional Content**
|
|
||||||
- Show/hide based on order value
|
|
||||||
- Show/hide based on customer type
|
|
||||||
- Dynamic product recommendations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
All 7 tasks successfully completed! The email builder now has:
|
|
||||||
- ✅ Expanded social media platform support (11 platforms)
|
|
||||||
- ✅ Professional PNG icons with color selection
|
|
||||||
- ✅ Body background color customization
|
|
||||||
- ✅ Fixed hero preview text color
|
|
||||||
- ✅ Complete default templates for all events
|
|
||||||
- ✅ Comprehensive documentation
|
|
||||||
|
|
||||||
The email system is now production-ready with professional defaults and extensive customization options.
|
|
||||||
|
|
||||||
**Total Commits:** 2
|
|
||||||
**Total Files Modified:** 6
|
|
||||||
**Total Files Created:** 23 (22 icons + 1 template class)
|
|
||||||
**Lines of Code:** ~1,500+
|
|
||||||
|
|
||||||
🎉 **Project Status: COMPLETE**
|
|
||||||
@@ -1,414 +0,0 @@
|
|||||||
# Final UX Improvements - Session Complete! 🎉
|
|
||||||
|
|
||||||
## All 6 Improvements Implemented
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. ✅ Dialog Scrollable Body with Fixed Header/Footer
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
Long content made header (with close button) and footer (with action buttons) disappear. Users couldn't close dialog or take action.
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
- Changed dialog to flexbox layout (`flex flex-col`)
|
|
||||||
- Added `DialogBody` component with `overflow-y-auto`
|
|
||||||
- Header and footer fixed with borders
|
|
||||||
- Max height `90vh` for viewport fit
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
```tsx
|
|
||||||
<DialogContent> (flex flex-col max-h-[90vh])
|
|
||||||
<DialogHeader> (px-6 pt-6 pb-4 border-b) - FIXED
|
|
||||||
<DialogBody> (flex-1 overflow-y-auto px-6 py-4) - SCROLLABLE
|
|
||||||
<DialogFooter> (px-6 py-4 border-t mt-auto) - FIXED
|
|
||||||
</DialogContent>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Files
|
|
||||||
- `components/ui/dialog.tsx`
|
|
||||||
- `components/ui/rich-text-editor.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. ✅ Dialog Close-Proof (No Outside Click)
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
Accidental outside clicks closed dialog, losing user input and causing confusion.
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
```tsx
|
|
||||||
<DialogPrimitive.Content
|
|
||||||
onPointerDownOutside={(e) => e.preventDefault()}
|
|
||||||
onInteractOutside={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result
|
|
||||||
- Must click X button or Cancel to close
|
|
||||||
- No accidental dismissal
|
|
||||||
- No lost UI control
|
|
||||||
- Better user confidence
|
|
||||||
|
|
||||||
### Files
|
|
||||||
- `components/ui/dialog.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. ✅ Code Mode Button Moved to Left
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
Inconsistent layout - Code Mode button was grouped with Editor/Preview tabs on the right.
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Moved Code Mode button next to "Message Body" label on the left.
|
|
||||||
|
|
||||||
### Before
|
|
||||||
```
|
|
||||||
Message Body [Editor|Preview] [Code Mode]
|
|
||||||
```
|
|
||||||
|
|
||||||
### After
|
|
||||||
```
|
|
||||||
Message Body [Code Mode] [Editor|Preview]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result
|
|
||||||
- Logical grouping
|
|
||||||
- Editor/Preview tabs stay together on right
|
|
||||||
- Code Mode is a mode toggle, not a tab
|
|
||||||
- Consistent, professional layout
|
|
||||||
|
|
||||||
### Files
|
|
||||||
- `routes/Settings/Notifications/EditTemplate.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. ✅ Markdown Support in Code Mode! 🎉
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
HTML is verbose and not user-friendly for tech-savvy users who prefer Markdown.
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Full Markdown support with custom syntax for email-specific features.
|
|
||||||
|
|
||||||
### Markdown Syntax
|
|
||||||
|
|
||||||
**Standard Markdown:**
|
|
||||||
```markdown
|
|
||||||
# Heading 1
|
|
||||||
## Heading 2
|
|
||||||
### Heading 3
|
|
||||||
|
|
||||||
**Bold text**
|
|
||||||
*Italic text*
|
|
||||||
|
|
||||||
- List item 1
|
|
||||||
- List item 2
|
|
||||||
|
|
||||||
[Link text](https://example.com)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Card Blocks:**
|
|
||||||
```markdown
|
|
||||||
:::card
|
|
||||||
# Your heading
|
|
||||||
Your content here
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::card[success]
|
|
||||||
✅ Success message
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::card[warning]
|
|
||||||
⚠️ Warning message
|
|
||||||
:::
|
|
||||||
```
|
|
||||||
|
|
||||||
**Button Blocks:**
|
|
||||||
```markdown
|
|
||||||
[button](https://example.com){Click Here}
|
|
||||||
|
|
||||||
[button style="outline"](https://example.com){Secondary Button}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Variables:**
|
|
||||||
```markdown
|
|
||||||
Hi {customer_name},
|
|
||||||
|
|
||||||
Your order #{order_number} totaling {order_total} is ready!
|
|
||||||
```
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- Bidirectional conversion (HTML ↔ Markdown)
|
|
||||||
- Toggle button: "📝 Switch to Markdown" / "🔧 Switch to HTML"
|
|
||||||
- Syntax highlighting for both modes
|
|
||||||
- Preserves all email features
|
|
||||||
- Easier for non-HTML users
|
|
||||||
|
|
||||||
### Files
|
|
||||||
- `lib/markdown-parser.ts` - Parser implementation
|
|
||||||
- `components/ui/code-editor.tsx` - Mode toggle
|
|
||||||
- `routes/Settings/Notifications/EditTemplate.tsx` - Enable support
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
```bash
|
|
||||||
npm install @codemirror/lang-markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. ✅ Realistic Variable Simulations in Preview
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
Variables showed as raw text like `{order_items_list}` in preview, making it hard to judge layout.
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Added realistic HTML simulations for better preview experience.
|
|
||||||
|
|
||||||
### order_items_list Simulation
|
|
||||||
```html
|
|
||||||
<ul style="list-style: none; padding: 0; margin: 16px 0;">
|
|
||||||
<li style="padding: 12px; background: #f9f9f9; border-radius: 6px; margin-bottom: 8px;">
|
|
||||||
<strong>Premium T-Shirt</strong> × 2<br>
|
|
||||||
<span style="color: #666;">Size: L, Color: Blue</span><br>
|
|
||||||
<span style="font-weight: 600;">$49.98</span>
|
|
||||||
</li>
|
|
||||||
<li style="padding: 12px; background: #f9f9f9; border-radius: 6px; margin-bottom: 8px;">
|
|
||||||
<strong>Classic Jeans</strong> × 1<br>
|
|
||||||
<span style="color: #666;">Size: 32, Color: Dark Blue</span><br>
|
|
||||||
<span style="font-weight: 600;">$79.99</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
```
|
|
||||||
|
|
||||||
### order_items_table Simulation
|
|
||||||
```html
|
|
||||||
<table style="width: 100%; border-collapse: collapse; margin: 16px 0;">
|
|
||||||
<thead>
|
|
||||||
<tr style="background: #f5f5f5;">
|
|
||||||
<th style="padding: 12px; text-align: left;">Product</th>
|
|
||||||
<th style="padding: 12px; text-align: center;">Qty</th>
|
|
||||||
<th style="padding: 12px; text-align: right;">Price</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td style="padding: 12px;">
|
|
||||||
<strong>Premium T-Shirt</strong><br>
|
|
||||||
<span style="color: #666; font-size: 13px;">Size: L, Color: Blue</span>
|
|
||||||
</td>
|
|
||||||
<td style="padding: 12px; text-align: center;">2</td>
|
|
||||||
<td style="padding: 12px; text-align: right;">$49.98</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result
|
|
||||||
- Users see realistic email preview
|
|
||||||
- Can judge layout and design accurately
|
|
||||||
- No guessing what variables will look like
|
|
||||||
- Professional presentation
|
|
||||||
- Better design decisions
|
|
||||||
|
|
||||||
### Files
|
|
||||||
- `routes/Settings/Notifications/EditTemplate.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. ✅ Smart Back Navigation to Accordion
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- Back button used `navigate(-1)`
|
|
||||||
- Returned to parent page but wrong tab
|
|
||||||
- Required 2-3 clicks to get back to Email accordion
|
|
||||||
- Lost context, poor UX
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Navigate with query params to preserve context.
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
|
|
||||||
**EditTemplate.tsx:**
|
|
||||||
```tsx
|
|
||||||
<Button onClick={() => navigate(`/settings/notifications?tab=${channelId}&event=${eventId}`)}>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Templates.tsx:**
|
|
||||||
```tsx
|
|
||||||
const [openAccordion, setOpenAccordion] = useState<string | undefined>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const eventParam = searchParams.get('event');
|
|
||||||
if (eventParam) {
|
|
||||||
setOpenAccordion(eventParam);
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
|
||||||
|
|
||||||
<Accordion value={openAccordion} onValueChange={setOpenAccordion}>
|
|
||||||
{/* ... */}
|
|
||||||
</Accordion>
|
|
||||||
```
|
|
||||||
|
|
||||||
### User Flow
|
|
||||||
1. User in Email accordion, editing "Order Placed" template
|
|
||||||
2. Clicks Back button
|
|
||||||
3. Returns to Notifications page with `?tab=email&event=order_placed`
|
|
||||||
4. Email accordion auto-opens
|
|
||||||
5. "Order Placed" template visible
|
|
||||||
6. Perfect context preservation!
|
|
||||||
|
|
||||||
### Result
|
|
||||||
- One-click return to context
|
|
||||||
- No confusion
|
|
||||||
- No extra clicks
|
|
||||||
- Professional navigation
|
|
||||||
- Context always preserved
|
|
||||||
|
|
||||||
### Files
|
|
||||||
- `routes/Settings/Notifications/EditTemplate.tsx`
|
|
||||||
- `routes/Settings/Notifications/Templates.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
### What We Built
|
|
||||||
Six critical UX improvements that transform the email builder from good to **perfect**.
|
|
||||||
|
|
||||||
### Key Achievements
|
|
||||||
|
|
||||||
1. **Healthy Dialogs** - Scrollable body, fixed header/footer, no accidental closing
|
|
||||||
2. **Logical Layout** - Code Mode button in correct position
|
|
||||||
3. **Markdown Support** - Easier editing for tech-savvy users
|
|
||||||
4. **Realistic Previews** - See exactly what emails will look like
|
|
||||||
5. **Smart Navigation** - Context-aware back button
|
|
||||||
|
|
||||||
### Impact
|
|
||||||
|
|
||||||
**For Users:**
|
|
||||||
- No frustration
|
|
||||||
- Faster workflow
|
|
||||||
- Better previews
|
|
||||||
- Professional tools
|
|
||||||
- Intuitive navigation
|
|
||||||
|
|
||||||
**For Business:**
|
|
||||||
- Happy users
|
|
||||||
- Fewer support tickets
|
|
||||||
- Better email designs
|
|
||||||
- Professional product
|
|
||||||
- Competitive advantage
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
### 1. Dialog Improvements
|
|
||||||
- [ ] Paste long content in dialog
|
|
||||||
- [ ] Verify header stays visible
|
|
||||||
- [ ] Verify footer stays visible
|
|
||||||
- [ ] Body scrolls independently
|
|
||||||
- [ ] Click outside dialog - should NOT close
|
|
||||||
- [ ] Click X button - closes
|
|
||||||
- [ ] Click Cancel - closes
|
|
||||||
|
|
||||||
### 2. Code Mode Button
|
|
||||||
- [ ] Verify button is left of label
|
|
||||||
- [ ] Verify Editor/Preview tabs on right
|
|
||||||
- [ ] Toggle Code Mode
|
|
||||||
- [ ] Layout looks professional
|
|
||||||
|
|
||||||
### 3. Markdown Support
|
|
||||||
- [ ] Toggle to Markdown mode
|
|
||||||
- [ ] Write Markdown syntax
|
|
||||||
- [ ] Use :::card blocks
|
|
||||||
- [ ] Use [button] syntax
|
|
||||||
- [ ] Toggle back to HTML
|
|
||||||
- [ ] Verify conversion works both ways
|
|
||||||
|
|
||||||
### 4. Variable Simulations
|
|
||||||
- [ ] Use {order_items_list} in template
|
|
||||||
- [ ] Preview shows realistic list
|
|
||||||
- [ ] Use {order_items_table} in template
|
|
||||||
- [ ] Preview shows realistic table
|
|
||||||
- [ ] Verify styling looks good
|
|
||||||
|
|
||||||
### 5. Back Navigation
|
|
||||||
- [ ] Open Email accordion
|
|
||||||
- [ ] Edit a template
|
|
||||||
- [ ] Click Back
|
|
||||||
- [ ] Verify returns to Email accordion
|
|
||||||
- [ ] Verify accordion is open
|
|
||||||
- [ ] Verify correct template visible
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
### New Package Required
|
|
||||||
```bash
|
|
||||||
npm install @codemirror/lang-markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
### Complete Install Command
|
|
||||||
```bash
|
|
||||||
cd admin-spa
|
|
||||||
npm install @tiptap/extension-text-align @tiptap/extension-image codemirror @codemirror/lang-html @codemirror/lang-markdown @codemirror/theme-one-dark @radix-ui/react-radio-group
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
### Components
|
|
||||||
1. `components/ui/dialog.tsx` - Scrollable body, close-proof
|
|
||||||
2. `components/ui/code-editor.tsx` - Markdown support
|
|
||||||
3. `components/ui/rich-text-editor.tsx` - Use DialogBody
|
|
||||||
|
|
||||||
### Routes
|
|
||||||
4. `routes/Settings/Notifications/EditTemplate.tsx` - Layout, simulations, navigation
|
|
||||||
5. `routes/Settings/Notifications/Templates.tsx` - Accordion state management
|
|
||||||
|
|
||||||
### Libraries
|
|
||||||
6. `lib/markdown-parser.ts` - NEW - Markdown ↔ HTML conversion
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
7. `DEPENDENCIES.md` - Updated with markdown package
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 Result
|
|
||||||
|
|
||||||
**The PERFECT email builder experience!**
|
|
||||||
|
|
||||||
All user feedback addressed:
|
|
||||||
- ✅ Healthy dialogs
|
|
||||||
- ✅ Logical layout
|
|
||||||
- ✅ Markdown support
|
|
||||||
- ✅ Realistic previews
|
|
||||||
- ✅ Smart navigation
|
|
||||||
- ✅ Professional UX
|
|
||||||
|
|
||||||
**Ready for production!** 🚀
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
### Lint Warnings
|
|
||||||
The following lint warnings are expected and can be ignored:
|
|
||||||
- `mso-table-lspace` and `mso-table-rspace` in `templates/emails/modern.html` - These are Microsoft Outlook-specific CSS properties
|
|
||||||
|
|
||||||
### Future Enhancements
|
|
||||||
- Variable categorization (order vs account vs product)
|
|
||||||
- Color customization UI
|
|
||||||
- More default templates
|
|
||||||
- Template preview mode
|
|
||||||
- A/B testing support
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Session Complete! All 6 improvements implemented successfully!** ✨
|
|
||||||
@@ -1,424 +0,0 @@
|
|||||||
# UX Improvements - Perfect Builder Experience! 🎯
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Six major UX improvements implemented to create the perfect email builder experience. These changes address real user pain points and make the builder intuitive and professional.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Prevent Link/Button Navigation in Builder ✅
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- Clicking links or buttons in the builder redirected users
|
|
||||||
- Users couldn't edit button text (clicking opened the link)
|
|
||||||
- Frustrating experience, broke editing workflow
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
**BlockRenderer (Email Builder):**
|
|
||||||
```typescript
|
|
||||||
const handleClick = (e: React.MouseEvent) => {
|
|
||||||
const target = e.target as HTMLElement;
|
|
||||||
if (target.tagName === 'A' || target.tagName === 'BUTTON' ||
|
|
||||||
target.closest('a') || target.closest('button')) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="group relative" onClick={handleClick}>
|
|
||||||
{/* Block content */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**RichTextEditor (TipTap):**
|
|
||||||
```typescript
|
|
||||||
editorProps: {
|
|
||||||
handleClick: (view, pos, event) => {
|
|
||||||
const target = event.target as HTMLElement;
|
|
||||||
if (target.tagName === 'A' || target.closest('a')) {
|
|
||||||
event.preventDefault();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result
|
|
||||||
- Links and buttons are now **editable only**
|
|
||||||
- No accidental navigation
|
|
||||||
- Click to edit, not to follow
|
|
||||||
- Perfect editing experience
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Default Templates Use Raw Buttons ✅
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- Default templates had buttons wrapped in cards:
|
|
||||||
```html
|
|
||||||
[card]
|
|
||||||
<p style="text-align: center;">
|
|
||||||
<a href="{order_url}" class="button">View Order</a>
|
|
||||||
</p>
|
|
||||||
[/card]
|
|
||||||
```
|
|
||||||
- Didn't match current block structure
|
|
||||||
- Confusing for users
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Changed to raw button blocks:
|
|
||||||
```html
|
|
||||||
[button link="{order_url}" style="solid"]View Order Details[/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Before & After
|
|
||||||
|
|
||||||
**Before:**
|
|
||||||
```
|
|
||||||
[card]
|
|
||||||
<p><a class="button">Track Order</a></p>
|
|
||||||
<p>Questions? Contact us.</p>
|
|
||||||
[/card]
|
|
||||||
```
|
|
||||||
|
|
||||||
**After:**
|
|
||||||
```
|
|
||||||
[button link="{order_url}" style="solid"]Track Your Order[/button]
|
|
||||||
|
|
||||||
[card]
|
|
||||||
<p>Questions? Contact us.</p>
|
|
||||||
[/card]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result
|
|
||||||
- Matches block structure
|
|
||||||
- Buttons are standalone blocks
|
|
||||||
- Easier to edit and rearrange
|
|
||||||
- Consistent with builder UI
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Split Order Items: List & Table ✅
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- Only one `{order_items}` variable
|
|
||||||
- No control over presentation format
|
|
||||||
- Users want different styles for different emails
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Split into two variables:
|
|
||||||
|
|
||||||
**`{order_items_list}`** - Formatted List
|
|
||||||
```html
|
|
||||||
<ul>
|
|
||||||
<li>Product Name × 2 - $50.00</li>
|
|
||||||
<li>Another Product × 1 - $25.00</li>
|
|
||||||
</ul>
|
|
||||||
```
|
|
||||||
|
|
||||||
**`{order_items_table}`** - Formatted Table
|
|
||||||
```html
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Product</th>
|
|
||||||
<th>Qty</th>
|
|
||||||
<th>Price</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Product Name</td>
|
|
||||||
<td>2</td>
|
|
||||||
<td>$50.00</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use Cases
|
|
||||||
- **List format**: Simple, compact, mobile-friendly
|
|
||||||
- **Table format**: Detailed, professional, desktop-optimized
|
|
||||||
|
|
||||||
### Result
|
|
||||||
- Better control over presentation
|
|
||||||
- Choose format based on email type
|
|
||||||
- Professional-looking order summaries
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Payment URL Variable Added ✅
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- No way to link to payment page
|
|
||||||
- Users couldn't send payment reminders
|
|
||||||
- Missing critical functionality
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
Added `{payment_url}` variable with smart strategy:
|
|
||||||
|
|
||||||
**Strategy:**
|
|
||||||
```php
|
|
||||||
if (manual_payment) {
|
|
||||||
// Use order details URL or thank you page
|
|
||||||
// Contains payment instructions
|
|
||||||
$payment_url = get_order_url();
|
|
||||||
} else if (api_payment) {
|
|
||||||
// Use payment gateway URL
|
|
||||||
// From order payment_meta
|
|
||||||
$payment_url = get_payment_gateway_url();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation:**
|
|
||||||
```php
|
|
||||||
'payment_url' => __('Payment URL (for pending payments)', 'woonoow'),
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use Cases
|
|
||||||
- **Pending payment emails**: "Complete your payment"
|
|
||||||
- **Failed payment emails**: "Retry payment"
|
|
||||||
- **Payment reminder emails**: "Your payment is waiting"
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```html
|
|
||||||
[card type="warning"]
|
|
||||||
<h2>⏳ Payment Pending</h2>
|
|
||||||
<p>Your order is waiting for payment.</p>
|
|
||||||
[/card]
|
|
||||||
|
|
||||||
[button link="{payment_url}" style="solid"]Complete Payment[/button]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result
|
|
||||||
- Complete payment workflow
|
|
||||||
- Better conversion rates
|
|
||||||
- Professional payment reminders
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Variable Categorization Strategy 📝
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- All variables shown for all events
|
|
||||||
- Confusing (why show `order_items` for account emails?)
|
|
||||||
- Poor UX
|
|
||||||
|
|
||||||
### Strategy (For Future Implementation)
|
|
||||||
|
|
||||||
**Order-Related Events:**
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
order_number, order_total, order_status,
|
|
||||||
order_items_list, order_items_table,
|
|
||||||
payment_url, tracking_number,
|
|
||||||
customer_name, customer_email,
|
|
||||||
shipping_address, billing_address
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Account-Related Events:**
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
customer_name, customer_email,
|
|
||||||
login_url, account_url,
|
|
||||||
reset_password_url
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Product-Related Events:**
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
product_name, product_url,
|
|
||||||
product_price, product_image,
|
|
||||||
stock_quantity
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Implementation Plan
|
|
||||||
1. Add event categories to event definitions
|
|
||||||
2. Filter variables by event category
|
|
||||||
3. Show only relevant variables in UI
|
|
||||||
4. Better UX, less confusion
|
|
||||||
|
|
||||||
### Result (When Implemented)
|
|
||||||
- Contextual variables only
|
|
||||||
- Cleaner UI
|
|
||||||
- Faster template creation
|
|
||||||
- Less user confusion
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. WordPress Media Library Fixed ✅
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
- WordPress Media library not loaded
|
|
||||||
- Error: "WordPress media library is not loaded"
|
|
||||||
- Browser prompt fallback (poor UX)
|
|
||||||
- Store logos/favicon upload broken
|
|
||||||
|
|
||||||
### Root Cause
|
|
||||||
```php
|
|
||||||
// Missing in Assets.php
|
|
||||||
wp_enqueue_media(); // ← Not called!
|
|
||||||
```
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
|
|
||||||
**Assets.php:**
|
|
||||||
```php
|
|
||||||
public static function enqueue($hook) {
|
|
||||||
if ($hook !== 'toplevel_page_woonoow') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enqueue WordPress Media library for image uploads
|
|
||||||
wp_enqueue_media(); // ← Added!
|
|
||||||
|
|
||||||
// ... rest of code
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**wp-media.ts (Better Error Handling):**
|
|
||||||
```typescript
|
|
||||||
if (typeof window.wp === 'undefined' || typeof window.wp.media === 'undefined') {
|
|
||||||
console.error('WordPress media library is not available');
|
|
||||||
console.error('window.wp:', typeof window.wp);
|
|
||||||
console.error('window.wp.media:', typeof (window as any).wp?.media);
|
|
||||||
|
|
||||||
alert('WordPress Media library is not loaded.\n\n' +
|
|
||||||
'Please ensure you are in WordPress admin and the page has fully loaded.\n\n' +
|
|
||||||
'If the problem persists, try refreshing the page.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Result
|
|
||||||
- WordPress Media Modal loads properly
|
|
||||||
- No more errors
|
|
||||||
- Professional image selection
|
|
||||||
- Store logos/favicon upload works
|
|
||||||
- Better error messages with debugging info
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
### 1. Link/Button Navigation
|
|
||||||
- [ ] Click link in card content → no navigation
|
|
||||||
- [ ] Click button in builder → no navigation
|
|
||||||
- [ ] Click button in RichTextEditor → no navigation
|
|
||||||
- [ ] Edit button text by clicking → works
|
|
||||||
- [ ] Links/buttons work in email preview
|
|
||||||
|
|
||||||
### 2. Default Templates
|
|
||||||
- [ ] Create new template from default
|
|
||||||
- [ ] Verify buttons are standalone blocks
|
|
||||||
- [ ] Verify buttons not wrapped in cards
|
|
||||||
- [ ] Edit button easily
|
|
||||||
- [ ] Rearrange blocks easily
|
|
||||||
|
|
||||||
### 3. Order Items Variables
|
|
||||||
- [ ] Insert `{order_items_list}` → shows list format
|
|
||||||
- [ ] Insert `{order_items_table}` → shows table format
|
|
||||||
- [ ] Preview both formats
|
|
||||||
- [ ] Verify formatting in email
|
|
||||||
|
|
||||||
### 4. Payment URL
|
|
||||||
- [ ] Insert `{payment_url}` in button
|
|
||||||
- [ ] Verify variable appears in list
|
|
||||||
- [ ] Test with pending payment order
|
|
||||||
- [ ] Test with manual payment
|
|
||||||
- [ ] Test with API payment gateway
|
|
||||||
|
|
||||||
### 5. WordPress Media
|
|
||||||
- [ ] Click image icon in RichTextEditor
|
|
||||||
- [ ] Verify WP Media Modal opens
|
|
||||||
- [ ] Select image from library
|
|
||||||
- [ ] Upload new image
|
|
||||||
- [ ] Click "Choose from Media Library" in Store settings
|
|
||||||
- [ ] Upload logo (light mode)
|
|
||||||
- [ ] Upload logo (dark mode)
|
|
||||||
- [ ] Upload favicon
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
### What We Built
|
|
||||||
A **perfect email builder experience** with:
|
|
||||||
- No accidental navigation
|
|
||||||
- Intuitive block structure
|
|
||||||
- Flexible content formatting
|
|
||||||
- Complete payment workflow
|
|
||||||
- Professional image management
|
|
||||||
|
|
||||||
### Key Achievements
|
|
||||||
|
|
||||||
1. **✅ No Navigation in Builder** - Links/buttons editable only
|
|
||||||
2. **✅ Raw Button Blocks** - Matches current structure
|
|
||||||
3. **✅ List & Table Formats** - Better control
|
|
||||||
4. **✅ Payment URL** - Complete workflow
|
|
||||||
5. **📝 Variable Strategy** - Future improvement
|
|
||||||
6. **✅ WP Media Fixed** - Professional uploads
|
|
||||||
|
|
||||||
### Impact
|
|
||||||
|
|
||||||
**For Users:**
|
|
||||||
- Faster template creation
|
|
||||||
- No frustration
|
|
||||||
- Professional results
|
|
||||||
- Intuitive workflow
|
|
||||||
|
|
||||||
**For Business:**
|
|
||||||
- Better conversion (payment URLs)
|
|
||||||
- Professional emails
|
|
||||||
- Happy users
|
|
||||||
- Fewer support tickets
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
### Frontend (TypeScript/React)
|
|
||||||
1. `components/EmailBuilder/BlockRenderer.tsx` - Prevent navigation
|
|
||||||
2. `components/ui/rich-text-editor.tsx` - Prevent navigation
|
|
||||||
3. `lib/wp-media.ts` - Better error handling
|
|
||||||
|
|
||||||
### Backend (PHP)
|
|
||||||
4. `includes/Admin/Assets.php` - Enqueue WP Media
|
|
||||||
5. `includes/Core/Notifications/TemplateProvider.php` - Variables & defaults
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
### Immediate
|
|
||||||
1. Test all features
|
|
||||||
2. Verify WP Media loads
|
|
||||||
3. Test payment URL generation
|
|
||||||
4. Verify order items formatting
|
|
||||||
|
|
||||||
### Future
|
|
||||||
1. Implement variable categorization
|
|
||||||
2. Add color customization UI
|
|
||||||
3. Create more default templates
|
|
||||||
4. Add template preview mode
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 Result
|
|
||||||
|
|
||||||
**The PERFECT email builder experience!**
|
|
||||||
|
|
||||||
All pain points addressed:
|
|
||||||
- ✅ No accidental navigation
|
|
||||||
- ✅ Intuitive editing
|
|
||||||
- ✅ Professional features
|
|
||||||
- ✅ WordPress integration
|
|
||||||
- ✅ Complete workflow
|
|
||||||
|
|
||||||
**Ready for production!** 🚀
|
|
||||||
@@ -1,368 +0,0 @@
|
|||||||
# Calculation Efficiency Audit
|
|
||||||
|
|
||||||
## 🚨 CRITICAL ISSUE FOUND
|
|
||||||
|
|
||||||
### Current Implementation (BLOATED):
|
|
||||||
|
|
||||||
**Frontend makes 2 separate API calls:**
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Call 1: Get shipping rates
|
|
||||||
const shippingRates = useQuery({
|
|
||||||
queryFn: () => api.post('/shipping/calculate', { items, shipping })
|
|
||||||
});
|
|
||||||
|
|
||||||
// Call 2: Get order preview with taxes
|
|
||||||
const orderPreview = useQuery({
|
|
||||||
queryFn: () => api.post('/orders/preview', { items, billing, shipping, shipping_method, coupons })
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Backend processes cart TWICE:**
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Endpoint 1: /shipping/calculate
|
|
||||||
WC()->cart->empty_cart();
|
|
||||||
WC()->cart->add_to_cart(...); // Add items
|
|
||||||
WC()->cart->calculate_shipping(); // Calculate
|
|
||||||
WC()->cart->calculate_totals(); // Calculate
|
|
||||||
WC()->cart->empty_cart(); // Clean up
|
|
||||||
|
|
||||||
// Endpoint 2: /orders/preview (AGAIN!)
|
|
||||||
WC()->cart->empty_cart();
|
|
||||||
WC()->cart->add_to_cart(...); // Add items AGAIN
|
|
||||||
WC()->cart->calculate_shipping(); // Calculate AGAIN
|
|
||||||
WC()->cart->calculate_totals(); // Calculate AGAIN
|
|
||||||
WC()->cart->empty_cart(); // Clean up AGAIN
|
|
||||||
```
|
|
||||||
|
|
||||||
### Problems:
|
|
||||||
|
|
||||||
❌ **2 HTTP requests** instead of 1
|
|
||||||
❌ **Cart initialized twice** (expensive)
|
|
||||||
❌ **Items added twice** (database queries)
|
|
||||||
❌ **Shipping calculated twice** (API calls to UPS, Rajaongkir, etc.)
|
|
||||||
❌ **Taxes calculated twice** (database queries)
|
|
||||||
❌ **Network latency doubled**
|
|
||||||
❌ **Server load doubled**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ SOLUTION: Single Unified Endpoint
|
|
||||||
|
|
||||||
### New Endpoint: `/woonoow/v1/orders/calculate`
|
|
||||||
|
|
||||||
**Single request with all data:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Frontend: ONE API call
|
|
||||||
const calculation = useQuery({
|
|
||||||
queryFn: () => api.post('/orders/calculate', {
|
|
||||||
items: [{ product_id: 1, qty: 2 }],
|
|
||||||
billing: { country: 'ID', state: 'JB', city: 'Bandung' },
|
|
||||||
shipping: { country: 'ID', state: 'JB', city: 'Bandung' },
|
|
||||||
coupons: ['SAVE10'],
|
|
||||||
// Optional: If user already selected shipping method
|
|
||||||
shipping_method: 'flat_rate:1',
|
|
||||||
})
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Single response with everything:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"subtotal": 100000,
|
|
||||||
"shipping": {
|
|
||||||
"methods": [
|
|
||||||
{
|
|
||||||
"id": "cekongkir:jne:reg",
|
|
||||||
"label": "JNE REG",
|
|
||||||
"cost": 31000,
|
|
||||||
"selected": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cekongkir:jne:yes",
|
|
||||||
"label": "JNE YES",
|
|
||||||
"cost": 42000,
|
|
||||||
"selected": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"selected_method": null,
|
|
||||||
"selected_cost": 0
|
|
||||||
},
|
|
||||||
"coupons": [
|
|
||||||
{
|
|
||||||
"code": "SAVE10",
|
|
||||||
"discount": 10000,
|
|
||||||
"valid": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"taxes": [
|
|
||||||
{
|
|
||||||
"label": "PPN 11%",
|
|
||||||
"amount": 13310
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total_tax": 13310,
|
|
||||||
"total": 134310,
|
|
||||||
"breakdown": {
|
|
||||||
"subtotal": 100000,
|
|
||||||
"shipping": 31000,
|
|
||||||
"discount": -10000,
|
|
||||||
"tax": 13310,
|
|
||||||
"total": 134310
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backend: ONE cart initialization
|
|
||||||
|
|
||||||
```php
|
|
||||||
public static function calculate_order( WP_REST_Request $req ) {
|
|
||||||
$items = $req->get_param('items');
|
|
||||||
$billing = $req->get_param('billing');
|
|
||||||
$shipping = $req->get_param('shipping');
|
|
||||||
$coupons = $req->get_param('coupons') ?? [];
|
|
||||||
$selected_method = $req->get_param('shipping_method');
|
|
||||||
|
|
||||||
// Initialize cart ONCE
|
|
||||||
WC()->cart->empty_cart();
|
|
||||||
WC()->session->init();
|
|
||||||
|
|
||||||
// Add items ONCE
|
|
||||||
foreach ($items as $item) {
|
|
||||||
WC()->cart->add_to_cart($item['product_id'], $item['qty']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set addresses ONCE
|
|
||||||
WC()->customer->set_billing_country($billing['country']);
|
|
||||||
WC()->customer->set_shipping_country($shipping['country']);
|
|
||||||
// ... set other fields
|
|
||||||
|
|
||||||
// Apply coupons ONCE
|
|
||||||
foreach ($coupons as $code) {
|
|
||||||
WC()->cart->apply_coupon($code);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate shipping ONCE
|
|
||||||
WC()->cart->calculate_shipping();
|
|
||||||
|
|
||||||
// Get all available shipping methods
|
|
||||||
$packages = WC()->shipping()->get_packages();
|
|
||||||
$shipping_methods = [];
|
|
||||||
foreach ($packages[0]['rates'] as $rate) {
|
|
||||||
$shipping_methods[] = [
|
|
||||||
'id' => $rate->get_id(),
|
|
||||||
'label' => $rate->get_label(),
|
|
||||||
'cost' => $rate->get_cost(),
|
|
||||||
'selected' => $rate->get_id() === $selected_method,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user selected a method, set it
|
|
||||||
if ($selected_method) {
|
|
||||||
WC()->session->set('chosen_shipping_methods', [$selected_method]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate totals ONCE (includes tax)
|
|
||||||
WC()->cart->calculate_totals();
|
|
||||||
|
|
||||||
// Build response
|
|
||||||
return new WP_REST_Response([
|
|
||||||
'subtotal' => WC()->cart->get_subtotal(),
|
|
||||||
'shipping' => [
|
|
||||||
'methods' => $shipping_methods,
|
|
||||||
'selected_method' => $selected_method,
|
|
||||||
'selected_cost' => WC()->cart->get_shipping_total(),
|
|
||||||
],
|
|
||||||
'coupons' => WC()->cart->get_applied_coupons(),
|
|
||||||
'taxes' => WC()->cart->get_tax_totals(),
|
|
||||||
'total_tax' => WC()->cart->get_total_tax(),
|
|
||||||
'total' => WC()->cart->get_total('edit'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Comparison
|
|
||||||
|
|
||||||
### Before (Current - BLOATED):
|
|
||||||
|
|
||||||
```
|
|
||||||
User fills address
|
|
||||||
↓
|
|
||||||
Frontend: POST /shipping/calculate (500ms)
|
|
||||||
↓ Backend: Init cart, add items, calculate shipping
|
|
||||||
↓ Response: { methods: [...] }
|
|
||||||
↓
|
|
||||||
User sees shipping options
|
|
||||||
↓
|
|
||||||
User selects shipping method
|
|
||||||
↓
|
|
||||||
Frontend: POST /orders/preview (500ms)
|
|
||||||
↓ Backend: Init cart AGAIN, add items AGAIN, calculate AGAIN
|
|
||||||
↓ Response: { total, tax, ... }
|
|
||||||
↓
|
|
||||||
User sees total
|
|
||||||
|
|
||||||
TOTAL TIME: ~1000ms
|
|
||||||
TOTAL REQUESTS: 2
|
|
||||||
CART INITIALIZED: 2 times
|
|
||||||
SHIPPING CALCULATED: 2 times
|
|
||||||
```
|
|
||||||
|
|
||||||
### After (Optimized - LIGHTNING):
|
|
||||||
|
|
||||||
```
|
|
||||||
User fills address
|
|
||||||
↓
|
|
||||||
Frontend: POST /orders/calculate (300ms)
|
|
||||||
↓ Backend: Init cart ONCE, add items ONCE, calculate ONCE
|
|
||||||
↓ Response: { shipping: { methods: [...] }, total, tax, ... }
|
|
||||||
↓
|
|
||||||
User sees shipping options AND total
|
|
||||||
|
|
||||||
TOTAL TIME: ~300ms (70% faster!)
|
|
||||||
TOTAL REQUESTS: 1 (50% reduction)
|
|
||||||
CART INITIALIZED: 1 time (50% reduction)
|
|
||||||
SHIPPING CALCULATED: 1 time (50% reduction)
|
|
||||||
```
|
|
||||||
|
|
||||||
### When User Changes Shipping Method:
|
|
||||||
|
|
||||||
**Before:**
|
|
||||||
```
|
|
||||||
User selects different shipping
|
|
||||||
↓
|
|
||||||
Frontend: POST /orders/preview (500ms)
|
|
||||||
↓ Backend: Init cart, add items, calculate
|
|
||||||
↓ Response: { total, tax }
|
|
||||||
```
|
|
||||||
|
|
||||||
**After:**
|
|
||||||
```
|
|
||||||
User selects different shipping
|
|
||||||
↓
|
|
||||||
Frontend: POST /orders/calculate with shipping_method (300ms)
|
|
||||||
↓ Backend: Init cart ONCE, calculate with selected method
|
|
||||||
↓ Response: { shipping: { selected_cost }, total, tax }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Plan
|
|
||||||
|
|
||||||
### Step 1: Create Unified Endpoint
|
|
||||||
|
|
||||||
```php
|
|
||||||
// includes/Api/OrdersController.php
|
|
||||||
|
|
||||||
public function register() {
|
|
||||||
register_rest_route( self::NS, '/orders/calculate', [
|
|
||||||
'methods' => 'POST',
|
|
||||||
'callback' => [ __CLASS__, 'calculate_order' ],
|
|
||||||
'permission_callback' => [ __CLASS__, 'check_permission' ],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Update Frontend
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// OrderForm.tsx
|
|
||||||
|
|
||||||
// REMOVE these two separate queries:
|
|
||||||
// const shippingRates = useQuery(...);
|
|
||||||
// const orderPreview = useQuery(...);
|
|
||||||
|
|
||||||
// REPLACE with single unified query:
|
|
||||||
const { data: calculation, isLoading } = useQuery({
|
|
||||||
queryKey: [
|
|
||||||
'order-calculation',
|
|
||||||
items,
|
|
||||||
bCountry, bState, bCity, bPost,
|
|
||||||
effectiveShippingAddress,
|
|
||||||
shippingMethod,
|
|
||||||
validatedCoupons
|
|
||||||
],
|
|
||||||
queryFn: async () => {
|
|
||||||
return api.post('/orders/calculate', {
|
|
||||||
items: items.map(i => ({ product_id: i.product_id, qty: i.qty })),
|
|
||||||
billing: { country: bCountry, state: bState, city: bCity, postcode: bPost },
|
|
||||||
shipping: effectiveShippingAddress,
|
|
||||||
shipping_method: shippingMethod,
|
|
||||||
coupons: validatedCoupons.map(c => c.code),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
enabled: items.length > 0 && isShippingAddressComplete,
|
|
||||||
staleTime: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use the data:
|
|
||||||
const shippingMethods = calculation?.shipping?.methods || [];
|
|
||||||
const orderTotal = calculation?.total || 0;
|
|
||||||
const orderTax = calculation?.total_tax || 0;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Deprecate Old Endpoints
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Mark as deprecated, remove in next major version
|
|
||||||
// /shipping/calculate - DEPRECATED
|
|
||||||
// /orders/preview - DEPRECATED
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
|
|
||||||
✅ **50% fewer HTTP requests**
|
|
||||||
✅ **70% faster response time**
|
|
||||||
✅ **50% less server load**
|
|
||||||
✅ **50% less database queries**
|
|
||||||
✅ **50% fewer external API calls** (UPS, Rajaongkir)
|
|
||||||
✅ **Better user experience** (instant feedback)
|
|
||||||
✅ **Lower hosting costs**
|
|
||||||
✅ **More scalable**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migration Path
|
|
||||||
|
|
||||||
### Phase 1: Add New Endpoint (Non-breaking)
|
|
||||||
- Add `/orders/calculate` endpoint
|
|
||||||
- Keep old endpoints working
|
|
||||||
- Update frontend to use new endpoint
|
|
||||||
|
|
||||||
### Phase 2: Deprecation Notice
|
|
||||||
- Add deprecation warnings to old endpoints
|
|
||||||
- Update documentation
|
|
||||||
|
|
||||||
### Phase 3: Remove Old Endpoints (Next major version)
|
|
||||||
- Remove `/shipping/calculate`
|
|
||||||
- Remove `/orders/preview`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
**Current implementation is bloated like WooCommerce.**
|
|
||||||
|
|
||||||
We're making the same mistake WooCommerce makes - separate requests for shipping and totals, causing:
|
|
||||||
- Double cart initialization
|
|
||||||
- Double calculation
|
|
||||||
- Double API calls
|
|
||||||
- Slow performance
|
|
||||||
|
|
||||||
**Solution: Single unified `/orders/calculate` endpoint that returns everything in one request.**
|
|
||||||
|
|
||||||
This is what we discussed at the beginning - **efficient, lightning-fast, no bloat**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** ❌ NOT IMPLEMENTED YET
|
|
||||||
**Priority:** 🚨 CRITICAL
|
|
||||||
**Impact:** 🔥 HIGH - Performance bottleneck
|
|
||||||
**Effort:** ⚡ MEDIUM - ~2 hours to implement
|
|
||||||
@@ -1,345 +0,0 @@
|
|||||||
# Customer Data Flow Analysis
|
|
||||||
|
|
||||||
## Issue Report
|
|
||||||
**Problem:** Customer `billing_phone` shows "Indonesia" instead of phone number
|
|
||||||
**Source:** Customer created via Order module
|
|
||||||
**Impact:** Incorrect customer data stored in database
|
|
||||||
|
|
||||||
## Data Flow Investigation
|
|
||||||
|
|
||||||
### A. Customer as Guest (Not Site Member)
|
|
||||||
|
|
||||||
#### 1. Order Creation Flow
|
|
||||||
**File:** `OrdersController.php` → `create_order()` method
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
1. Order form submits billing data including `phone`
|
|
||||||
2. Data flows through `$billing['phone']` parameter
|
|
||||||
3. Order billing is set via `$order->set_billing_phone($billing['phone'])`
|
|
||||||
4. **No WC_Customer object created** - guest orders only store data in order meta
|
|
||||||
|
|
||||||
**Code Location:** Lines 780-1120
|
|
||||||
|
|
||||||
```php
|
|
||||||
// Guest customer - data only in order
|
|
||||||
$order->set_billing_first_name($billing['first_name'] ?? '');
|
|
||||||
$order->set_billing_last_name($billing['last_name'] ?? '');
|
|
||||||
$order->set_billing_email($billing['email'] ?? '');
|
|
||||||
$order->set_billing_phone($billing['phone'] ?? ''); // ← Data here
|
|
||||||
// ... more fields
|
|
||||||
```
|
|
||||||
|
|
||||||
**Issue:** Guest customers don't have WC_Customer records, so viewing them in Customer module will fail or show incorrect data.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### B. Customer as Site Member
|
|
||||||
|
|
||||||
#### 1. Existing Member - Order Creation
|
|
||||||
**File:** `OrdersController.php` → Lines 1020-1064
|
|
||||||
|
|
||||||
**Flow:**
|
|
||||||
1. User exists, found by email
|
|
||||||
2. **Upgrade subscriber to customer role** (if needed)
|
|
||||||
3. Create `WC_Customer` object
|
|
||||||
4. **Update customer data** from order billing/shipping
|
|
||||||
5. Save customer
|
|
||||||
6. Link order to customer
|
|
||||||
|
|
||||||
**Code:**
|
|
||||||
```php
|
|
||||||
if ($user) {
|
|
||||||
// Upgrade role if needed
|
|
||||||
if (in_array('subscriber', (array) $user->roles, true)) {
|
|
||||||
$user->set_role('customer');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update customer billing & shipping data
|
|
||||||
$customer = new \WC_Customer($user->ID);
|
|
||||||
|
|
||||||
// Update billing address
|
|
||||||
if (! empty($billing['first_name'])) $customer->set_billing_first_name($billing['first_name']);
|
|
||||||
if (! empty($billing['last_name'])) $customer->set_billing_last_name($billing['last_name']);
|
|
||||||
if (! empty($billing['email'])) $customer->set_billing_email($billing['email']);
|
|
||||||
if (! empty($billing['phone'])) $customer->set_billing_phone($billing['phone']); // ← HERE
|
|
||||||
// ... more fields
|
|
||||||
|
|
||||||
$customer->save();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation:** Uses `! empty()` check
|
|
||||||
**Problem:** If `$billing['phone']` contains "Indonesia" (non-empty string), it will be saved!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 2. New Member - Auto-Registration
|
|
||||||
**File:** `OrdersController.php` → Lines 1065-1118
|
|
||||||
|
|
||||||
**Flow:**
|
|
||||||
1. User doesn't exist
|
|
||||||
2. Auto-register setting is ON
|
|
||||||
3. Create WordPress user
|
|
||||||
4. Create `WC_Customer` object
|
|
||||||
5. Set billing/shipping from order data
|
|
||||||
6. Save customer
|
|
||||||
7. Send welcome email
|
|
||||||
|
|
||||||
**Code:**
|
|
||||||
```php
|
|
||||||
elseif ($register_member) {
|
|
||||||
// Create user
|
|
||||||
$user_id = wp_insert_user($userdata);
|
|
||||||
|
|
||||||
if (!is_wp_error($user_id)) {
|
|
||||||
$customer = new \WC_Customer($user_id);
|
|
||||||
|
|
||||||
// Billing address
|
|
||||||
if (! empty($billing['first_name'])) $customer->set_billing_first_name($billing['first_name']);
|
|
||||||
// ...
|
|
||||||
if (! empty($billing['phone'])) $customer->set_billing_phone($billing['phone']); // ← HERE
|
|
||||||
// ...
|
|
||||||
|
|
||||||
$customer->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Same Issue:** `! empty()` check allows "Indonesia" to be saved.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### C. Customer Module Direct Edit
|
|
||||||
|
|
||||||
**File:** `CustomersController.php` → `update_customer()` method (Lines 232-310)
|
|
||||||
|
|
||||||
**Flow:**
|
|
||||||
1. Receive customer data via PUT request
|
|
||||||
2. Update WordPress user meta
|
|
||||||
3. Update `WC_Customer` billing/shipping
|
|
||||||
4. Save customer
|
|
||||||
|
|
||||||
**Code:**
|
|
||||||
```php
|
|
||||||
$customer = new WC_Customer($id);
|
|
||||||
|
|
||||||
// Billing address
|
|
||||||
if (!empty($data['billing'])) {
|
|
||||||
$billing = $data['billing'];
|
|
||||||
if (isset($billing['first_name'])) $customer->set_billing_first_name(...);
|
|
||||||
// ...
|
|
||||||
if (isset($billing['phone'])) $customer->set_billing_phone(sanitize_text_field($billing['phone']));
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
$customer->save();
|
|
||||||
```
|
|
||||||
|
|
||||||
**Validation:** Uses `isset()` check (better than `! empty()`)
|
|
||||||
**Sanitization:** Uses `sanitize_text_field()` ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Root Cause Analysis
|
|
||||||
|
|
||||||
### Possible Sources of "Indonesia" Value
|
|
||||||
|
|
||||||
1. **Frontend Default Value**
|
|
||||||
- Check `OrderForm.tsx` for default country/phone values
|
|
||||||
- Check if "Indonesia" is being set as placeholder or default
|
|
||||||
|
|
||||||
2. **Backend Fallback**
|
|
||||||
- Check if WooCommerce has default country settings
|
|
||||||
- Check if there's a fallback to country name instead of phone
|
|
||||||
|
|
||||||
3. **Data Validation Issue**
|
|
||||||
- `! empty()` check allows ANY non-empty string
|
|
||||||
- No validation that phone is actually a phone number
|
|
||||||
- No sanitization before saving
|
|
||||||
|
|
||||||
4. **Virtual Products Case**
|
|
||||||
- When cart has only virtual products, address fields are hidden
|
|
||||||
- Phone field might still be submitted with wrong value
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Issues Found
|
|
||||||
|
|
||||||
### 1. ❌ Weak Validation in OrdersController
|
|
||||||
|
|
||||||
**Problem:**
|
|
||||||
```php
|
|
||||||
if (! empty($billing['phone'])) $customer->set_billing_phone($billing['phone']);
|
|
||||||
```
|
|
||||||
|
|
||||||
- `! empty()` allows "Indonesia", "test", "abc", etc.
|
|
||||||
- No phone number format validation
|
|
||||||
- No sanitization
|
|
||||||
|
|
||||||
**Should Be:**
|
|
||||||
```php
|
|
||||||
if (isset($billing['phone']) && $billing['phone'] !== '') {
|
|
||||||
$customer->set_billing_phone(sanitize_text_field($billing['phone']));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. ❌ No Data Sanitization in Order Creation
|
|
||||||
|
|
||||||
**Problem:**
|
|
||||||
- Direct assignment without sanitization
|
|
||||||
- Allows any string value
|
|
||||||
- No format validation
|
|
||||||
|
|
||||||
**Should Add:**
|
|
||||||
- `sanitize_text_field()` for all text fields
|
|
||||||
- Phone number format validation
|
|
||||||
- Empty string handling
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. ❌ Inconsistent Validation Between Controllers
|
|
||||||
|
|
||||||
**OrdersController:**
|
|
||||||
- Uses `! empty()` check
|
|
||||||
- No sanitization
|
|
||||||
|
|
||||||
**CustomersController:**
|
|
||||||
- Uses `isset()` check ✅
|
|
||||||
- Has `sanitize_text_field()` ✅
|
|
||||||
|
|
||||||
**Should:** Use same validation pattern everywhere
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. ❌ Virtual Products Address Handling
|
|
||||||
|
|
||||||
**Current Behavior:**
|
|
||||||
- Frontend hides address fields for virtual products
|
|
||||||
- But phone field is ALWAYS shown
|
|
||||||
- Backend might receive wrong data
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- OrderForm.tsx line 1023: `setBPhone(data.billing.phone || '');`
|
|
||||||
- Does this fallback to something else?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Action Items
|
|
||||||
|
|
||||||
### 1. Fix OrdersController Validation
|
|
||||||
|
|
||||||
**File:** `OrdersController.php`
|
|
||||||
**Lines:** 1039-1047, 1088-1096
|
|
||||||
|
|
||||||
**Change:**
|
|
||||||
```php
|
|
||||||
// OLD (Lines 1042)
|
|
||||||
if (! empty($billing['phone'])) $customer->set_billing_phone($billing['phone']);
|
|
||||||
|
|
||||||
// NEW
|
|
||||||
if (isset($billing['phone']) && trim($billing['phone']) !== '') {
|
|
||||||
$customer->set_billing_phone(sanitize_text_field($billing['phone']));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Apply to:
|
|
||||||
- Existing member update (lines 1039-1047)
|
|
||||||
- New member creation (lines 1088-1096)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Add Phone Number Validation
|
|
||||||
|
|
||||||
**Create Helper Function:**
|
|
||||||
```php
|
|
||||||
private static function sanitize_phone($phone) {
|
|
||||||
if (empty($phone)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove non-numeric characters except + and spaces
|
|
||||||
$phone = preg_replace('/[^0-9+\s-]/', '', $phone);
|
|
||||||
|
|
||||||
// Trim whitespace
|
|
||||||
$phone = trim($phone);
|
|
||||||
|
|
||||||
// If result is empty or just symbols, return empty
|
|
||||||
if (empty($phone) || preg_match('/^[+\s-]+$/', $phone)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $phone;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Check Frontend Data Source
|
|
||||||
|
|
||||||
**File:** `OrderForm.tsx`
|
|
||||||
**Line:** ~1023
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
- Where does `data.billing.phone` come from?
|
|
||||||
- Is there a default value being set?
|
|
||||||
- Is "Indonesia" coming from country field?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Test Cases
|
|
||||||
|
|
||||||
**A. Guest Customer:**
|
|
||||||
1. Create order with phone = "08123456789"
|
|
||||||
2. Check order billing_phone
|
|
||||||
3. Verify no WC_Customer created
|
|
||||||
|
|
||||||
**B. Existing Member:**
|
|
||||||
1. Create order with existing customer
|
|
||||||
2. Phone = "08123456789"
|
|
||||||
3. Check WC_Customer billing_phone updated
|
|
||||||
4. Create another order with same customer
|
|
||||||
5. Phone = "08198765432"
|
|
||||||
6. Verify WC_Customer phone updated to new value
|
|
||||||
|
|
||||||
**C. New Member (Auto-register):**
|
|
||||||
1. Create order with new email
|
|
||||||
2. Auto-register ON
|
|
||||||
3. Phone = "08123456789"
|
|
||||||
4. Verify WC_Customer created with correct phone
|
|
||||||
|
|
||||||
**D. Virtual Products:**
|
|
||||||
1. Create order with only virtual products
|
|
||||||
2. Verify phone field behavior
|
|
||||||
3. Check what value is submitted
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Expected Behavior
|
|
||||||
|
|
||||||
### Order Creation with Existing Member
|
|
||||||
1. Order billing data should update WC_Customer data
|
|
||||||
2. Phone should be validated and sanitized
|
|
||||||
3. Empty phone should clear WC_Customer phone (not set to country name)
|
|
||||||
|
|
||||||
### Order Creation with New Member
|
|
||||||
1. WC_Customer should be created with correct data
|
|
||||||
2. Phone should be validated and sanitized
|
|
||||||
3. No fallback to country name
|
|
||||||
|
|
||||||
### Virtual Products
|
|
||||||
1. Phone field should still work correctly
|
|
||||||
2. No address fields needed
|
|
||||||
3. Phone should not default to country name
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. ✅ Update PROJECT_SOP.md with mobile UX patterns
|
|
||||||
2. 🔄 Find source of "Indonesia" value
|
|
||||||
3. ⏳ Fix validation in OrdersController
|
|
||||||
4. ⏳ Add phone sanitization helper
|
|
||||||
5. ⏳ Test all scenarios
|
|
||||||
6. ⏳ Document fix in commit message
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Phase Complete ✅
|
|
||||||
|
|
||||||
**Date:** November 15, 2025
|
|
||||||
|
|
||||||
## Completed
|
|
||||||
|
|
||||||
### 1. Email Queue ✅
|
|
||||||
- Already implemented via MailQueue + WooEmailOverride
|
|
||||||
- Prevents 30s timeout
|
|
||||||
|
|
||||||
### 2. Documentation ✅
|
|
||||||
- Reduced 56 → 27 files (52% reduction)
|
|
||||||
- Created NOTIFICATION_SYSTEM.md (consolidated)
|
|
||||||
- Deleted 30 obsolete docs
|
|
||||||
|
|
||||||
### 3. Git Push ✅
|
|
||||||
- 3 commits pushed to main
|
|
||||||
- Remote: git.backoffice.biz.id
|
|
||||||
|
|
||||||
### 4. Plugin Zip ✅
|
|
||||||
- File: woonoow.zip
|
|
||||||
- Size: 1.4MB
|
|
||||||
- Location: /wp-content/plugins/woonoow.zip
|
|
||||||
- Guide: PLUGIN_ZIP_GUIDE.md
|
|
||||||
|
|
||||||
## Ready for Distribution! 🚀
|
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
# Product Form UX Improvements
|
|
||||||
|
|
||||||
## Problem Statement
|
|
||||||
|
|
||||||
The original product form (`ProductForm.tsx`) had **600+ lines in a single file** with all fields visible at once, creating an overwhelming and exhausting experience for users adding/editing products.
|
|
||||||
|
|
||||||
### Issues with Old Form:
|
|
||||||
❌ **Cognitive Overload** - Too many fields visible simultaneously
|
|
||||||
❌ **Poor Mobile UX** - Long scrolling, hard to navigate
|
|
||||||
❌ **Difficult Maintenance** - Single 600-line file
|
|
||||||
❌ **Confusing Variations** - Comma-separated input (requires shift key)
|
|
||||||
❌ **No Visual Hierarchy** - Everything at same level
|
|
||||||
❌ **No Contextual Help** - Users unsure what fields mean
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Solution: Modern Tabbed Interface
|
|
||||||
|
|
||||||
Redesigned with **5 modular tabs** inspired by industry leaders (Shopify, Shopee, Wix, Magento).
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
ProductFormTabbed.tsx (250 lines)
|
|
||||||
├── GeneralTab.tsx (180 lines)
|
|
||||||
├── PricingTab.tsx (100 lines)
|
|
||||||
├── InventoryTab.tsx (90 lines)
|
|
||||||
├── VariationsTab.tsx (200 lines)
|
|
||||||
└── OrganizationTab.tsx (120 lines)
|
|
||||||
|
|
||||||
Total: ~950 lines across 6 files (vs 600 lines in 1 file)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tab Breakdown
|
|
||||||
|
|
||||||
### 1️⃣ General Tab
|
|
||||||
**Focus:** Basic product information
|
|
||||||
|
|
||||||
**Fields:**
|
|
||||||
- Product name *
|
|
||||||
- Product type (simple/variable/grouped/external)
|
|
||||||
- Status (publish/draft/pending/private)
|
|
||||||
- Long description
|
|
||||||
- Short description
|
|
||||||
- Virtual product checkbox
|
|
||||||
- Downloadable product checkbox
|
|
||||||
- Featured product checkbox
|
|
||||||
|
|
||||||
**UX Features:**
|
|
||||||
- Clear labels with asterisks for required fields
|
|
||||||
- Inline help text below each field
|
|
||||||
- Type-specific descriptions (e.g., "A standalone product" for simple)
|
|
||||||
- Logical grouping with separators
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2️⃣ Pricing Tab
|
|
||||||
**Focus:** Product pricing
|
|
||||||
|
|
||||||
**Fields:**
|
|
||||||
- SKU (optional)
|
|
||||||
- Regular price * (for simple products)
|
|
||||||
- Sale price (optional)
|
|
||||||
|
|
||||||
**UX Features:**
|
|
||||||
- Dollar sign icons for price inputs
|
|
||||||
- Savings calculator (shows "Customers save X%")
|
|
||||||
- Green success banner when sale price is set
|
|
||||||
- Contextual help for each field
|
|
||||||
- Pre-filled for variations (base price)
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```
|
|
||||||
Regular Price: $100
|
|
||||||
Sale Price: $80
|
|
||||||
→ Shows: "💰 Customers save 20%"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3️⃣ Inventory Tab
|
|
||||||
**Focus:** Stock management
|
|
||||||
|
|
||||||
**Fields:**
|
|
||||||
- Manage stock toggle
|
|
||||||
- Stock quantity (when enabled)
|
|
||||||
- Stock status (in stock/out of stock/on backorder)
|
|
||||||
|
|
||||||
**UX Features:**
|
|
||||||
- Progressive disclosure (quantity only shown when enabled)
|
|
||||||
- Color-coded status badges:
|
|
||||||
- 🟢 In Stock (green)
|
|
||||||
- 🔴 Out of Stock (red)
|
|
||||||
- 🟡 On Backorder (amber)
|
|
||||||
- Visual border for nested fields
|
|
||||||
- Clear explanations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4️⃣ Variations Tab
|
|
||||||
**Focus:** Product variations (variable products only)
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- **Add Attribute** button
|
|
||||||
- Attribute cards with:
|
|
||||||
- Attribute name (e.g., Color, Size)
|
|
||||||
- Options input with **pipe separator** (`|`)
|
|
||||||
- "Use for variations" checkbox
|
|
||||||
- **Generate Variations** button
|
|
||||||
- Variation list with badges
|
|
||||||
- Per-variation inputs (SKU, price, sale, stock)
|
|
||||||
|
|
||||||
**Key Improvement: Pipe Separator**
|
|
||||||
```
|
|
||||||
❌ Old: Red, Blue, Green (comma = shift key)
|
|
||||||
✅ New: Red | Blue | Green (pipe = no shift!)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Variation Generation:**
|
|
||||||
```
|
|
||||||
Attributes:
|
|
||||||
- Color: Red | Blue
|
|
||||||
- Size: S | M | L
|
|
||||||
|
|
||||||
Generated Variations (6):
|
|
||||||
1. Color: Red, Size: S
|
|
||||||
2. Color: Red, Size: M
|
|
||||||
3. Color: Red, Size: L
|
|
||||||
4. Color: Blue, Size: S
|
|
||||||
5. Color: Blue, Size: M
|
|
||||||
6. Color: Blue, Size: L
|
|
||||||
```
|
|
||||||
|
|
||||||
**UX Features:**
|
|
||||||
- Empty state with icon and message
|
|
||||||
- Numbered attribute badges
|
|
||||||
- Visual attribute cards
|
|
||||||
- Pre-filled prices (inherit base price)
|
|
||||||
- Compact variation display
|
|
||||||
- Success toast with count
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5️⃣ Organization Tab
|
|
||||||
**Focus:** Categories and tags
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Categories (checkboxes)
|
|
||||||
- Tags (pill buttons)
|
|
||||||
|
|
||||||
**UX Features:**
|
|
||||||
- Clear visual separation
|
|
||||||
- Interactive pill buttons for tags (toggle on/off)
|
|
||||||
- Active state styling
|
|
||||||
- Empty states
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UX Principles Applied
|
|
||||||
|
|
||||||
### ✅ Progressive Disclosure
|
|
||||||
Only show relevant fields:
|
|
||||||
- Variations tab **disabled** for non-variable products
|
|
||||||
- Stock quantity **hidden** unless "Manage stock" enabled
|
|
||||||
- Variation-specific pricing only for variable products
|
|
||||||
|
|
||||||
### ✅ Visual Hierarchy
|
|
||||||
- Card-based layout
|
|
||||||
- Clear section titles and descriptions
|
|
||||||
- Separators between logical groups
|
|
||||||
- Badges for status and counts
|
|
||||||
|
|
||||||
### ✅ Inline Help
|
|
||||||
Every field has contextual help text:
|
|
||||||
```
|
|
||||||
SKU
|
|
||||||
[Input field]
|
|
||||||
"Stock Keeping Unit (optional)"
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Smart Defaults
|
|
||||||
- Product type: Simple
|
|
||||||
- Status: Published
|
|
||||||
- Stock status: In Stock
|
|
||||||
- Variation prices: Pre-filled with base price
|
|
||||||
|
|
||||||
### ✅ Visual Feedback
|
|
||||||
- Savings percentage calculator
|
|
||||||
- Color-coded badges
|
|
||||||
- Success/error toasts
|
|
||||||
- Loading states
|
|
||||||
- Disabled states
|
|
||||||
|
|
||||||
### ✅ Validation Routing
|
|
||||||
Form automatically switches to tab with errors:
|
|
||||||
```typescript
|
|
||||||
if (!name.trim()) {
|
|
||||||
toast.error('Product name is required');
|
|
||||||
setActiveTab('general'); // Auto-switch!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ Mobile Optimized
|
|
||||||
- Responsive tab layout (icons only on mobile)
|
|
||||||
- Touch-friendly buttons
|
|
||||||
- Stacked inputs on small screens
|
|
||||||
- Pull-to-refresh support
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Comparison: Old vs New
|
|
||||||
|
|
||||||
| Aspect | Old Form | New Tabbed Form |
|
|
||||||
|--------|----------|-----------------|
|
|
||||||
| **Lines of Code** | 600 in 1 file | ~950 in 6 files |
|
|
||||||
| **Maintainability** | ❌ Hard | ✅ Easy (modular) |
|
|
||||||
| **Cognitive Load** | ❌ High | ✅ Low (progressive) |
|
|
||||||
| **Mobile UX** | ❌ Poor | ✅ Excellent |
|
|
||||||
| **Visual Hierarchy** | ❌ Flat | ✅ Clear |
|
|
||||||
| **Contextual Help** | ❌ None | ✅ Everywhere |
|
|
||||||
| **Variation Input** | ❌ Comma (shift) | ✅ Pipe (no shift) |
|
|
||||||
| **Validation** | ❌ Generic | ✅ Tab-specific |
|
|
||||||
| **Extensibility** | ❌ Hard | ✅ Easy (add tabs) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Industry Benchmarking
|
|
||||||
|
|
||||||
### Shopify
|
|
||||||
- ✅ Tabbed interface
|
|
||||||
- ✅ Progressive disclosure
|
|
||||||
- ✅ Inline help text
|
|
||||||
- ✅ Visual status badges
|
|
||||||
|
|
||||||
### Shopee (Seller Center)
|
|
||||||
- ✅ Step-by-step wizard
|
|
||||||
- ✅ Smart defaults
|
|
||||||
- ✅ Visual feedback
|
|
||||||
- ✅ Mobile-first design
|
|
||||||
|
|
||||||
### WooCommerce (Default)
|
|
||||||
- ❌ Single long form (like our old one)
|
|
||||||
- ❌ Overwhelming for new users
|
|
||||||
- ❌ Poor mobile experience
|
|
||||||
|
|
||||||
### Magento
|
|
||||||
- ✅ Accordion sections
|
|
||||||
- ✅ Advanced/basic toggle
|
|
||||||
- ✅ Contextual help
|
|
||||||
|
|
||||||
**Our Approach:** Best of Shopify + Shopee with WooCommerce compatibility.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## User Flow Comparison
|
|
||||||
|
|
||||||
### Old Flow (Single Form)
|
|
||||||
```
|
|
||||||
1. Open form
|
|
||||||
2. See 50+ fields at once 😰
|
|
||||||
3. Scroll... scroll... scroll...
|
|
||||||
4. Forget what you filled
|
|
||||||
5. Submit (maybe)
|
|
||||||
```
|
|
||||||
|
|
||||||
### New Flow (Tabbed)
|
|
||||||
```
|
|
||||||
1. Open form
|
|
||||||
2. Start with General (5-8 fields) ✅
|
|
||||||
3. Move to Pricing (3 fields) ✅
|
|
||||||
4. Configure Inventory (2-3 fields) ✅
|
|
||||||
5. Add Variations if needed (focused) ✅
|
|
||||||
6. Set Categories/Tags ✅
|
|
||||||
7. Submit with confidence! 🎉
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Technical Benefits
|
|
||||||
|
|
||||||
### Modular Architecture
|
|
||||||
Each tab is self-contained:
|
|
||||||
- Easy to test
|
|
||||||
- Easy to modify
|
|
||||||
- Easy to extend
|
|
||||||
- Clear responsibilities
|
|
||||||
|
|
||||||
### Type Safety
|
|
||||||
Full TypeScript support:
|
|
||||||
```typescript
|
|
||||||
type GeneralTabProps = {
|
|
||||||
name: string;
|
|
||||||
setName: (value: string) => void;
|
|
||||||
type: 'simple' | 'variable' | 'grouped' | 'external';
|
|
||||||
// ... etc
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Reusability
|
|
||||||
Same form for create and edit:
|
|
||||||
```tsx
|
|
||||||
<ProductFormTabbed
|
|
||||||
mode="create"
|
|
||||||
onSubmit={handleCreate}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ProductFormTabbed
|
|
||||||
mode="edit"
|
|
||||||
initial={productData}
|
|
||||||
onSubmit={handleUpdate}
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
### Phase 2
|
|
||||||
- [ ] Image upload with drag-and-drop
|
|
||||||
- [ ] Rich text editor for descriptions
|
|
||||||
- [ ] Bulk variation editing
|
|
||||||
- [ ] Variation templates
|
|
||||||
|
|
||||||
### Phase 3
|
|
||||||
- [ ] SEO tab (meta title, description, keywords)
|
|
||||||
- [ ] Shipping tab (weight, dimensions)
|
|
||||||
- [ ] Advanced tab (custom fields)
|
|
||||||
- [ ] Related products selector
|
|
||||||
|
|
||||||
### Phase 4
|
|
||||||
- [ ] AI-powered descriptions
|
|
||||||
- [ ] Smart pricing suggestions
|
|
||||||
- [ ] Inventory forecasting
|
|
||||||
- [ ] Multi-language support
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Metrics to Track
|
|
||||||
|
|
||||||
### User Experience
|
|
||||||
- ⏱️ Time to create product (expect 30% reduction)
|
|
||||||
- 📊 Form completion rate (expect increase)
|
|
||||||
- 🔄 Form abandonment rate (expect decrease)
|
|
||||||
- 😊 User satisfaction score
|
|
||||||
|
|
||||||
### Technical
|
|
||||||
- 🐛 Bug reports (expect decrease)
|
|
||||||
- 🔧 Maintenance time (expect decrease)
|
|
||||||
- 📈 Code coverage (easier to test)
|
|
||||||
- 🚀 Performance (no impact, same bundle size)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
The new tabbed product form provides a **significantly better user experience** while maintaining **technical excellence**. By following industry best practices and focusing on progressive disclosure, we've created a form that is:
|
|
||||||
|
|
||||||
✅ **Less Overwhelming** - Focused, step-by-step approach
|
|
||||||
✅ **More Intuitive** - Clear labels, inline help, visual feedback
|
|
||||||
✅ **Better Organized** - Logical grouping, modular architecture
|
|
||||||
✅ **Mobile-Friendly** - Responsive, touch-optimized
|
|
||||||
✅ **Easier to Maintain** - Modular, type-safe, well-documented
|
|
||||||
|
|
||||||
**Result:** Admins can add/edit products faster and with more confidence! 🎉
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Implemented:** November 19, 2025
|
|
||||||
**Team:** WooNooW Development
|
|
||||||
**Status:** ✅ Production Ready
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,130 +0,0 @@
|
|||||||
# Tasks Summary - November 11, 2025
|
|
||||||
|
|
||||||
## ✅ Task 1: Translation Support Audit
|
|
||||||
|
|
||||||
### Status: COMPLETED ✓
|
|
||||||
|
|
||||||
**Findings:**
|
|
||||||
- Most settings pages already have `__` translation function imported
|
|
||||||
- **Missing translation support:**
|
|
||||||
- `Store.tsx` - Needs `__` import and string wrapping
|
|
||||||
- `Payments.tsx` - Needs `__` import and string wrapping
|
|
||||||
- `Developer.tsx` - Needs `__` import and string wrapping
|
|
||||||
|
|
||||||
**Action Required:**
|
|
||||||
Add translation support to these 3 files (can be done during next iteration)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Task 2: Documentation Audit
|
|
||||||
|
|
||||||
### Status: COMPLETED ✓
|
|
||||||
|
|
||||||
**Actions Taken:**
|
|
||||||
1. ✅ Created `DOCS_AUDIT_REPORT.md` - Comprehensive audit of all 36 MD files
|
|
||||||
2. ✅ Deleted 12 obsolete documents:
|
|
||||||
- CUSTOMER_SETTINGS_404_FIX.md
|
|
||||||
- MENU_FIX_SUMMARY.md
|
|
||||||
- DASHBOARD_TWEAKS_TODO.md
|
|
||||||
- DASHBOARD_PLAN.md
|
|
||||||
- SPA_ADMIN_MENU_PLAN.md
|
|
||||||
- STANDALONE_ADMIN_SETUP.md
|
|
||||||
- STANDALONE_MODE_SUMMARY.md
|
|
||||||
- SETTINGS_PAGES_PLAN.md
|
|
||||||
- SETTINGS_PAGES_PLAN_V2.md
|
|
||||||
- SETTINGS_TREE_PLAN.md
|
|
||||||
- SETTINGS_PLACEMENT_STRATEGY.md
|
|
||||||
- TAX_NOTIFICATIONS_PLAN.md
|
|
||||||
|
|
||||||
**Result:**
|
|
||||||
- Reduced from 36 to 24 documents (33% reduction)
|
|
||||||
- Clearer focus on active development
|
|
||||||
- Easier navigation for developers
|
|
||||||
|
|
||||||
**Remaining Documents:**
|
|
||||||
- 15 essential docs (keep as-is)
|
|
||||||
- 9 docs to consolidate later (low priority)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚧 Task 3: Notification Settings Implementation
|
|
||||||
|
|
||||||
### Status: IN PROGRESS
|
|
||||||
|
|
||||||
**Plan:** Follow NOTIFICATION_STRATEGY.md
|
|
||||||
|
|
||||||
### Phase 1: Core Framework (Current)
|
|
||||||
1. **Backend (PHP)**
|
|
||||||
- [ ] Create `NotificationManager` class
|
|
||||||
- [ ] Create `EmailChannel` class (built-in)
|
|
||||||
- [ ] Create notification events registry
|
|
||||||
- [ ] Create REST API endpoints
|
|
||||||
- [ ] Add hooks for addon integration
|
|
||||||
|
|
||||||
2. **Frontend (React)**
|
|
||||||
- [ ] Update `Notifications.tsx` settings page
|
|
||||||
- [ ] Create channel cards UI
|
|
||||||
- [ ] Create event configuration UI
|
|
||||||
- [ ] Add channel toggle/enable functionality
|
|
||||||
- [ ] Add template editor (email)
|
|
||||||
|
|
||||||
3. **Database**
|
|
||||||
- [ ] Notification events table (optional)
|
|
||||||
- [ ] Use wp_options for settings
|
|
||||||
- [ ] Channel configurations
|
|
||||||
|
|
||||||
### Implementation Steps
|
|
||||||
|
|
||||||
#### Step 1: Backend Core
|
|
||||||
```
|
|
||||||
includes/Core/Notifications/
|
|
||||||
├── NotificationManager.php # Main manager
|
|
||||||
├── NotificationEvent.php # Event class
|
|
||||||
├── Channels/
|
|
||||||
│ └── EmailChannel.php # Built-in email
|
|
||||||
└── NotificationSettingsProvider.php # Settings CRUD
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 2: REST API
|
|
||||||
```
|
|
||||||
includes/Api/NotificationsController.php
|
|
||||||
- GET /notifications/channels # List available channels
|
|
||||||
- GET /notifications/events # List notification events
|
|
||||||
- GET /notifications/settings # Get all settings
|
|
||||||
- POST /notifications/settings # Save settings
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 3: Frontend UI
|
|
||||||
```
|
|
||||||
admin-spa/src/routes/Settings/Notifications.tsx
|
|
||||||
- Channel cards (email + addon channels)
|
|
||||||
- Event configuration per category
|
|
||||||
- Toggle channels per event
|
|
||||||
- Recipient selection (admin/customer/both)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Features
|
|
||||||
- ✅ Email channel built-in
|
|
||||||
- ✅ Addon integration via hooks
|
|
||||||
- ✅ Per-event channel selection
|
|
||||||
- ✅ Recipient targeting
|
|
||||||
- ✅ Template system ready
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Next Actions
|
|
||||||
|
|
||||||
### Immediate
|
|
||||||
1. ✅ Commit documentation cleanup
|
|
||||||
2. 🚧 Start notification system implementation
|
|
||||||
3. ⏳ Add translation to Store/Payments/Developer pages
|
|
||||||
|
|
||||||
### This Session
|
|
||||||
- Implement notification core framework
|
|
||||||
- Create REST API endpoints
|
|
||||||
- Build basic UI for notification settings
|
|
||||||
|
|
||||||
### Future
|
|
||||||
- Build Telegram addon as proof of concept
|
|
||||||
- Create addon development template
|
|
||||||
- Document notification addon API
|
|
||||||
185
docs/ADDONS_GUIDE.md
Normal file
185
docs/ADDONS_GUIDE.md
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
# WooNooW Addon & Module Development Guide
|
||||||
|
|
||||||
|
**Version:** 2.0.0
|
||||||
|
**Status:** Production Ready
|
||||||
|
|
||||||
|
This document is the single source of truth for extending WooNooW. It covers everything from basic code snippets (Bridge Patterns) to full-featured React SPAs (Addon Modules).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
1. [Philosophy & Architecture](#philosophy--architecture)
|
||||||
|
2. [Addon Types & Integration Levels](#addon-types--integration-levels)
|
||||||
|
- Level 1: Vanilla JS / Bridge Patterns
|
||||||
|
- Level 2: React Runtime Expansion (Recommended)
|
||||||
|
- Level 3: Slot-Based Components
|
||||||
|
3. [Developing a Full Addon](#developing-a-full-addon)
|
||||||
|
- Registration
|
||||||
|
- Settings & Options
|
||||||
|
- The Hook System
|
||||||
|
4. [Addon & Built-in Module Unification](#addon--built-in-module-unification)
|
||||||
|
5. [Examples](#examples)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Philosophy & Architecture
|
||||||
|
|
||||||
|
**WooNooW Core = Zero Addon Dependencies**
|
||||||
|
|
||||||
|
We don't integrate specific addons (like shipping or payment plugins) directly into WooNooW core. Instead, we provide:
|
||||||
|
1. **Hook system** for external addons to extend functionality.
|
||||||
|
2. **Exposed React Runtime** so addons don't have to bundle heavy libraries.
|
||||||
|
3. **Unified Module Registry** where built-in features (like Affiliates, Newsletters) and external addons share the same UI and enablement toggles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Addon Types & Integration Levels
|
||||||
|
|
||||||
|
### Level 1: Vanilla JS / Bridge Patterns (Basic)
|
||||||
|
Use this for simple snippets to make existing WooCommerce plugins (e.g. Rajaongkir) work with WooNooW.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Wait for WooNooW to load
|
||||||
|
window.addEventListener('woonoow:loaded', function() {
|
||||||
|
window.WooNooW.hooks.addFilter('woonoow_order_form_after_shipping', function(container, formData) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.innerHTML = `<select id="custom-dest"><option>Select...</option></select>`;
|
||||||
|
container.appendChild(div);
|
||||||
|
return container;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Level 2: Exposed React Runtime (Recommended)
|
||||||
|
WooNooW exposes `React`, `ReactDOM`, its `hooks`, and its `components` on `window.WooNooW`. This allows addons to build rich UIs without bundling React.
|
||||||
|
|
||||||
|
**Webpack/Vite Setup (Externals):**
|
||||||
|
```javascript
|
||||||
|
// vite.config.js
|
||||||
|
export default {
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['react', 'react-dom'],
|
||||||
|
output: {
|
||||||
|
globals: {
|
||||||
|
react: 'window.WooNooW.React',
|
||||||
|
'react-dom': 'window.WooNooW.ReactDOM'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```typescript
|
||||||
|
const { React, hooks, components } = window.WooNooW;
|
||||||
|
const { addFilter } = hooks;
|
||||||
|
const { Select } = components;
|
||||||
|
|
||||||
|
function CustomField({ formData, setFormData }) {
|
||||||
|
return <Select label="Custom" onChange={(val) => setFormData({...})} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
addFilter('woonoow_order_form_after_shipping', (container, props) => {
|
||||||
|
const root = window.WooNooW.ReactDOM.createRoot(container);
|
||||||
|
root.render(<CustomField {...props} />);
|
||||||
|
return container;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Level 3: SPA Route Injection (Admin Pages)
|
||||||
|
You can inject entire new pages into the WooNooW admin SPA.
|
||||||
|
```php
|
||||||
|
add_filter('woonoow/spa_routes', function($routes) {
|
||||||
|
$routes[] = [
|
||||||
|
'path' => '/my-addon',
|
||||||
|
'component_url' => plugin_dir_url(__FILE__) . 'dist/MyPage.js',
|
||||||
|
'title' => 'My Addon',
|
||||||
|
];
|
||||||
|
return $routes;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Developing a Full Addon
|
||||||
|
|
||||||
|
### Step 1: Registration
|
||||||
|
Register the addon via PHP filters so WooNooW knows it exists.
|
||||||
|
|
||||||
|
```php
|
||||||
|
add_filter('woonoow/addon_registry', function($addons) {
|
||||||
|
$addons['my-addon'] = [
|
||||||
|
'id' => 'my-addon',
|
||||||
|
'name' => 'My Custom Addon',
|
||||||
|
'description' => 'Does something awesome.',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
'category' => 'shipping', // Auto-groups in the UI
|
||||||
|
'icon' => 'truck',
|
||||||
|
'spa_bundle' => plugin_dir_url(__FILE__) . 'dist/addon.js',
|
||||||
|
'has_settings' => true, // Enables the ⚙️ icon
|
||||||
|
];
|
||||||
|
return $addons;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Settings & Options
|
||||||
|
By declaring `'has_settings' => true`, WooNooW expects your settings to live at `/settings/modules/{addon_id}`.
|
||||||
|
|
||||||
|
**Option A: Schema-based (No React needed)**
|
||||||
|
```php
|
||||||
|
add_filter('woonoow/module_settings_schema', function($schemas) {
|
||||||
|
$schemas['my-addon'] = [
|
||||||
|
'api_key' => ['type' => 'text', 'label' => 'API Key'],
|
||||||
|
];
|
||||||
|
return $schemas;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Custom React Settings Component**
|
||||||
|
```php
|
||||||
|
// In addon_registry registration array:
|
||||||
|
'settings_component' => plugin_dir_url(__FILE__) . 'dist/Settings.js',
|
||||||
|
```
|
||||||
|
Access data in React:
|
||||||
|
```typescript
|
||||||
|
const { useModuleSettings } = window.WooNooW.hooks;
|
||||||
|
const { settings, updateSettings } = useModuleSettings('my-addon');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Addon & Built-in Module Unification
|
||||||
|
|
||||||
|
**Concept:** External addons and built-in features (e.g. Affiliates, Newsletters) look and act exactly the same in the Admin UI.
|
||||||
|
- Both use `ModuleRegistry`.
|
||||||
|
- Both generate dynamic categories (e.g. "Marketing", "Shipping").
|
||||||
|
- Both persist settings to `woonoow_module_{id}_settings`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Hook System Reference
|
||||||
|
|
||||||
|
### Frontend Extension Hooks
|
||||||
|
```typescript
|
||||||
|
// Add fields
|
||||||
|
'woonoow_order_form_after_billing'
|
||||||
|
'woonoow_order_form_after_shipping'
|
||||||
|
'woonoow_order_form_custom_sections'
|
||||||
|
|
||||||
|
// Validation & Data Modification
|
||||||
|
'woonoow_order_form_validation'
|
||||||
|
'woonoow_order_form_data'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend Action Hooks
|
||||||
|
```php
|
||||||
|
apply_filters('woonoow_before_shipping_calculate', $shipping_data);
|
||||||
|
apply_filters('woonoow_after_shipping_calculate', $rates, $shipping_data);
|
||||||
|
apply_filters('woonoow_shipping_data', $data);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*For specific implementation examples, consult the `examples/` directory in the repository.*
|
||||||
141
docs/CUSTOMER_SPA_ARCHITECTURE.md
Normal file
141
docs/CUSTOMER_SPA_ARCHITECTURE.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# WooNooW Customer SPA Architecture & Master Plan
|
||||||
|
|
||||||
|
**Version:** 2.0.0
|
||||||
|
**Status:** Production Ready
|
||||||
|
|
||||||
|
This document consolidates the architectural decisions, deployment modes, UI/UX standards, and routing strategies for the WooNooW Customer SPA. It is the single source of truth for frontend development on the customer-facing side.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
1. [Architecture Decision: Hybrid Approach](#architecture-decision-hybrid-approach)
|
||||||
|
2. [Deployment Modes](#deployment-modes)
|
||||||
|
3. [Routing Strategy: HashRouter](#routing-strategy-hashrouter)
|
||||||
|
4. [SEO & Tracking Strategy](#seo--tracking-strategy)
|
||||||
|
5. [UI/UX Design Standards](#uiux-design-standards)
|
||||||
|
6. [Implementation Roadmap](#implementation-roadmap)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Architecture Decision: Hybrid Approach
|
||||||
|
|
||||||
|
We previously debated whether the `customer-spa` should be built into the core plugin or sold as a separate standalone theme.
|
||||||
|
|
||||||
|
**The Decision: Option C (Hybrid Approach)**
|
||||||
|
|
||||||
|
We build the Customer SPA into the WooNooW core plugin alongside the Admin SPA.
|
||||||
|
```text
|
||||||
|
woonoow/
|
||||||
|
├── admin-spa/ (Admin interface ONLY)
|
||||||
|
├── customer-spa/ (Customer Storefront + My Account)
|
||||||
|
└── includes/
|
||||||
|
├── Admin/ (Backend logic for admin-spa)
|
||||||
|
└── Frontend/ (Backend logic for customer-spa)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Better user experience (one product to install).
|
||||||
|
- Highest revenue potential (core plugin covers 60% of users, premium themes can be sold separately for agencies).
|
||||||
|
- Maximum flexibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Deployment Modes
|
||||||
|
|
||||||
|
To accommodate all store owners (from small businesses using traditional themes to enterprises wanting App-like experiences), the Customer SPA supports flexible deployment:
|
||||||
|
|
||||||
|
### Mode 1: Shortcode Mode (Default / Recommended)
|
||||||
|
**Use Case:** Works with ANY WordPress theme (Divi, Elementor, Flatsome).
|
||||||
|
**How it works:** Basic components are injected via shortcodes (`[woonoow_cart]`, `[woonoow_checkout]`).
|
||||||
|
**Benefit:** Zero theme conflicts, progressive enhancement.
|
||||||
|
|
||||||
|
### Mode 2: Full SPA Mode
|
||||||
|
**Use Case:** Maximum performance, standalone app-like experience.
|
||||||
|
**How it works:** WooNooW takes over the entire frontend routing via a dedicated settings toggle.
|
||||||
|
**Benefit:** Offline PWA support, fastest page transitions.
|
||||||
|
|
||||||
|
### Mode 3: Hybrid Rendering
|
||||||
|
**Use Case:** Best of both worlds for SEO and speed.
|
||||||
|
**How it works:** Product/Category pages use traditional WordPress PHP templates (SSR) for robust SEO, while Cart/Checkout/MyAccount use full SPA rendering for interactivity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Routing Strategy: HashRouter
|
||||||
|
|
||||||
|
Direct SPA URLs (like `https://woonoow.local/product/foo`) conflict heavily with WordPress's native rewrite rules. We cannot reliably override WordPress routing without breaking standard themes or SEO canonicals.
|
||||||
|
|
||||||
|
**The Solution: HashRouter for SPA interactions**
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://woonoow.local/shop#/product/edukasi-anak
|
||||||
|
↑
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why HashRouter?
|
||||||
|
- **Zero WordPress Conflicts:** WordPress loads the base `/shop` page template. React Router takes over everything after the `#`.
|
||||||
|
- **Direct Access Works:** Users can bookmark or share the URL, and it will load flawlessly.
|
||||||
|
- **Consistent with Admin SPA:** Both Admin and Customer SPAs utilize memory/hash routing to prevent 404s and permalink crashes.
|
||||||
|
|
||||||
|
*(Note: For strict SEO product pages in Hybrid mode, the actual WooCommerce Product URL is indexed, while internal SPA browsing uses the HashRouter).*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. SEO & Tracking Strategy
|
||||||
|
|
||||||
|
### Hybrid SEO Compatibility
|
||||||
|
Because pure SPAs hurt SEO (empty HTML sent to crawler), we utilize Hybrid Rendering:
|
||||||
|
1. **Product Pages = SSR:** WordPress outputs full HTML with schema markup using standard hooks so Yoast / RankMath work perfectly.
|
||||||
|
2. **Checkout/Cart = CSR:** These pages don't need SEO indexing, so they are pure React.
|
||||||
|
|
||||||
|
### Analytics Tracking
|
||||||
|
To ensure compatibility with Google Analytics, PixelMySite, Facebook Pixel, etc., the React components **trigger standard WooCommerce jQuery events**.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Example: Triggering a native woo event inside React
|
||||||
|
jQuery(document.body).trigger('added_to_cart', [product.id, quantity, product.price]);
|
||||||
|
```
|
||||||
|
This guarantees that 3rd-party tracking plugins installed on the site continue to function out-of-the-box without requiring custom API webhooks for the storefront.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. UI/UX Design Standards
|
||||||
|
|
||||||
|
Our frontend philosophy: **Pragmatic, not dogmatic.** Follow e-commerce conventions for learned behaviors (e.g. swiping), rely on UX research when conventions are broken, and prioritize mobile-first performance.
|
||||||
|
|
||||||
|
### Core Layout Rules
|
||||||
|
- **Typography:** Hierarchy is `Title > Price`. We optimize for Brand Stores, not chaotic Marketplaces.
|
||||||
|
- **Variation Selectors:** Use Pills/Buttons. **Never** use Dropdowns for variation selection (high friction).
|
||||||
|
- **Product Information:** Use Vertical Accordions instead of Horizontal Tabs (27% of users overlook horizontal tabs).
|
||||||
|
- **Images:**
|
||||||
|
- Mobile: Use dots (learned behavior from Amazon/Tokopedia).
|
||||||
|
- Desktop: Use small horizontal thumbnails below the main image.
|
||||||
|
|
||||||
|
### Buy Section Hierarchy
|
||||||
|
1. Product Title (H1)
|
||||||
|
2. Price & Sale formatting
|
||||||
|
3. Stock Status Badge
|
||||||
|
4. Variation Selectors (Pills)
|
||||||
|
5. Quantity Spinner
|
||||||
|
6. **Add to Cart (Primary CTA)**
|
||||||
|
7. Trust Badges (Shipping, Returns)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Implementation Roadmap
|
||||||
|
|
||||||
|
### Phase 1: Core Commerce (MVP)
|
||||||
|
- Basic Product listing / catalog with filters.
|
||||||
|
- Shopping Cart sidebar/drawer (state managed by Zustand).
|
||||||
|
- Single-page checkout (HashRouter).
|
||||||
|
- My Account dashboard (React Router).
|
||||||
|
|
||||||
|
### Phase 2: Trust & Conversion
|
||||||
|
- Wishlist / Save for later functionality.
|
||||||
|
- Product reviews integration with WooCommerce comments.
|
||||||
|
- Related Products and Cross-Sells carousels.
|
||||||
|
|
||||||
|
### Phase 3: Advanced
|
||||||
|
- Subscriptions & Memberships UI.
|
||||||
|
- Digital Downloads manager in My Account.
|
||||||
|
- Progressive Web App (PWA) manifest generation and offline Service Worker.
|
||||||
91
docs/PRODUCT_PAGE_ARCHITECTURE.md
Normal file
91
docs/PRODUCT_PAGE_ARCHITECTURE.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# WooNooW Product Page Architecture & UX Guide
|
||||||
|
|
||||||
|
**Version:** 2.0.0
|
||||||
|
**Status:** Production Ready
|
||||||
|
|
||||||
|
This document serves as the master guide for the WooNooW Customer SPA Product Page. It consolidates our design decision framework, standard operating procedures (SOP), and implementation phases.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
1. [Design Decision Framework](#design-decision-framework)
|
||||||
|
2. [Layout & Structure SOP](#layout--structure-sop)
|
||||||
|
3. [Implementation Phases](#implementation-phases)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Design Decision Framework
|
||||||
|
|
||||||
|
When building the WooNooW SPA, we constantly face the dilemma: **Should we follow E-commerce Convention (e.g. Tokopedia, Shopify) or UX Research (e.g. Baymard Institute)?**
|
||||||
|
|
||||||
|
**Our Approach: Context-Driven Hybrid Decisions**
|
||||||
|
|
||||||
|
| Pattern | Convention | Research | Our Decision | Rationale |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **Image Thumbnails** | Dots (Mobile) | Visible Thumbs | **Hybrid** | Small thumbnails + dots underneath. Retains swipeability but adds information scent. |
|
||||||
|
| **Variation Selector** | Pills | Pills | **Pills** | Clear winner. Dropdowns add unnecessary friction. |
|
||||||
|
| **Typography** | Varies | Title > Price | **Title > Price** | We optimize for Brand Stores (product focus) rather than Marketplaces (price focus). |
|
||||||
|
| **Description** | Varies | Visible | **Auto-Expand** | Primary content must be visible. Secondary tabs (Reviews/Specs) can be categorized. |
|
||||||
|
| **Sticky Bottom Bar** | Common | Good | **Yes** | Essential for long-scrolling mobile experiences. |
|
||||||
|
|
||||||
|
**The Meta-Lesson:**
|
||||||
|
- Follow Convention for strongly learned behaviors (e.g., swiping).
|
||||||
|
- Follow Research when convention is weak or when research points to a clear, frictionless improvement.
|
||||||
|
- Always optimize for OUR context: We are building a high-conversion, brand-focused store SPA, not a massive chaotic marketplace.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Layout & Structure SOP
|
||||||
|
|
||||||
|
### 2.1 Hero Section (Above the Fold)
|
||||||
|
The visual hierarchy must prioritize the core purchasing loop:
|
||||||
|
1. **Product Images** (Left on Desktop, Top on Mobile)
|
||||||
|
2. **Title & Price** (Right on Desktop, Below Image on Mobile)
|
||||||
|
3. **Rating & Review Count**
|
||||||
|
4. **Variation Selectors** (Pills)
|
||||||
|
5. **Quantity & Add to Cart CTA**
|
||||||
|
6. **Trust Badges**
|
||||||
|
|
||||||
|
### 2.2 Image Gallery Requirements
|
||||||
|
- **Main Image:** High-res, zoomable/lightbox enabled.
|
||||||
|
- **Thumbnail Slider:** Horizontal scrolling, 4-6 visible thumbnails. Active thumbnail highlighted.
|
||||||
|
- **Variation Auto-Switch:** Selecting a color pill MUST instantly switch the main gallery image to the corresponding photo.
|
||||||
|
|
||||||
|
### 2.3 Product Information (Below the Fold)
|
||||||
|
**AVOID HORIZONTAL TABS.** Research indicates 27% of users overlook them.
|
||||||
|
Instead, use **Vertical Collapsed Sections (Accordions)**:
|
||||||
|
- Description (Auto-expanded)
|
||||||
|
- Specifications Table
|
||||||
|
- Shipping / Returns Policy
|
||||||
|
- Reviews
|
||||||
|
- Related Products
|
||||||
|
|
||||||
|
### 2.4 Mobile Optimization
|
||||||
|
- Image gallery must be native-feel swipeable.
|
||||||
|
- Sticky Add-to-Cart bottom bar on scroll.
|
||||||
|
- Touch targets minimum 44x44px.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Implementation Phases
|
||||||
|
|
||||||
|
If extending or modifying the product page, follow this feature prioritization:
|
||||||
|
|
||||||
|
### Phase 1: Core E-Commerce Loop (MVP)
|
||||||
|
- Horizontal thumbnail slider with arrow navigation on desktop.
|
||||||
|
- Variation selector with automatic image switching and price updating.
|
||||||
|
- Add to Cart functionality with stock validation (disable button if no variation chosen).
|
||||||
|
- Vertical accordion layout for Description and Info.
|
||||||
|
|
||||||
|
### Phase 2: Trust & Conversion Optimization
|
||||||
|
- Inject WooCommerce native Reviews.
|
||||||
|
- Inject Trust elements (Secure checkout, Payment methods).
|
||||||
|
- Auto-calculate and display Free Shipping thresholds.
|
||||||
|
- Related products carousel.
|
||||||
|
|
||||||
|
### Phase 3: Advanced Media & Personalization (Future)
|
||||||
|
- Wishlist / Save for later.
|
||||||
|
- Social Proof badges ("20 people viewing this").
|
||||||
|
- 360° product views and Video embeds.
|
||||||
|
- Auto-apply coupons and bulk tier discounts.
|
||||||
186
docs/SHIPPING_GUIDE.md
Normal file
186
docs/SHIPPING_GUIDE.md
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
# WooNooW Shipping & Integrations Guide
|
||||||
|
|
||||||
|
**Version:** 2.0.0
|
||||||
|
**Status:** Production Ready
|
||||||
|
|
||||||
|
This document outlines how shipping methods work within WooNooW, and how to bridge complex external/API shipping plugins (such as RajaOngkir or Sicepat) fully into the Customer SPA Order Form.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
1. [How WooNooW Handles Shipping](#how-woonoow-handles-shipping)
|
||||||
|
2. [The Shipping Bridge Pattern](#the-shipping-bridge-pattern)
|
||||||
|
3. [RajaOngkir Integration Example](#rajaongkir-integration-example)
|
||||||
|
4. [Custom Shipping Addons](#custom-shipping-addons)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. How WooNooW Handles Shipping
|
||||||
|
|
||||||
|
### Two Types of Shipping Methods
|
||||||
|
|
||||||
|
WooCommerce Core inherently provides two types of shipping:
|
||||||
|
|
||||||
|
**1. Static Methods (e.g. Free Shipping, Flat Rate)**
|
||||||
|
- Immediate calculation (no API).
|
||||||
|
- Configured via basic Address fields (Country, State, City, ZIP).
|
||||||
|
|
||||||
|
**2. Live Rate Methods (API Based)**
|
||||||
|
- Need an API Call (e.g., UPS, FedEx, or local Indonesian couriers like JNE/J&T).
|
||||||
|
- May require extremely specific address fragments to calculate rates accurately.
|
||||||
|
- *International APIs (UPS/FedEx):* Usually rely on **Postal Code**.
|
||||||
|
- *Indonesian APIs (RajaOngkir/Biteship):* Usually rely on **Subdistrict IDs**.
|
||||||
|
|
||||||
|
### The Problem
|
||||||
|
|
||||||
|
If a customer uses an Indonesian shipping plugin, the plugin may remove default WooCommerce fields (like `city`/`state`) and inject a custom dropdown that searches an API for a destination. The plugin then writes that Destination ID to the WooCommerce Session and forces shipping recalculation.
|
||||||
|
|
||||||
|
Because WooNooW's SPA bypasses normal PHP frontend rendering, native injected dropdowns from external plugins will not appear automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The Shipping Bridge Pattern
|
||||||
|
|
||||||
|
To make external shipping plugins work natively within WooNooW's SPA checkout, use our Bridge Hook System.
|
||||||
|
|
||||||
|
You need 3 components:
|
||||||
|
1. **REST API Endpoint:** To let the SPA search the provider's valid locations.
|
||||||
|
2. **Checkout Field Injection:** To inject a `searchable_select` field into the SPA order form natively.
|
||||||
|
3. **Bridge Action Hook:** To take the selected value from the order form and write it to the WooCommerce Session before shipping calculates.
|
||||||
|
|
||||||
|
### Generic Bridge Skeleton
|
||||||
|
|
||||||
|
```php
|
||||||
|
// 1. Endpoint for SPA to search locations
|
||||||
|
add_action('rest_api_init', function() {
|
||||||
|
register_rest_route('woonoow/v1', '/provider/search', [
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => 'my_provider_search_func',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Inject field into WooNooW SPA (native JSON structure, not HTML)
|
||||||
|
add_filter('woocommerce_checkout_fields', function($fields) {
|
||||||
|
if (!class_exists('My_Shipping_Class')) return $fields;
|
||||||
|
|
||||||
|
$fields['shipping']['shipping_provider_field'] = [
|
||||||
|
'type' => 'searchable_select',
|
||||||
|
'label' => 'Select Location',
|
||||||
|
'required' => true,
|
||||||
|
'priority' => 85,
|
||||||
|
'search_endpoint' => '/provider/search', // Relative to wp-json/woonoow/v1
|
||||||
|
'search_param' => 'search',
|
||||||
|
'min_chars' => 3,
|
||||||
|
];
|
||||||
|
return $fields;
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
// 3. Bridge Data to Provider Session
|
||||||
|
add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
|
||||||
|
if (!class_exists('My_Shipping_Class')) return;
|
||||||
|
|
||||||
|
$val = $shipping['shipping_provider_field'] ?? null;
|
||||||
|
if ($val) {
|
||||||
|
WC()->session->set('my_provider_session_key', $val);
|
||||||
|
// Force recalc
|
||||||
|
WC()->session->set('shipping_for_package_0', false);
|
||||||
|
}
|
||||||
|
}, 10, 2);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. RajaOngkir Integration Example
|
||||||
|
|
||||||
|
If your user wants RajaOngkir running natively inside the WooNooW SPA checkout, this snippet represents the "Best Practice Bridge".
|
||||||
|
|
||||||
|
*Note: Drop this into a Code Snippets plugin or functions.php*
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// 1. Search Endpoint
|
||||||
|
add_action('rest_api_init', function() {
|
||||||
|
register_rest_route('woonoow/v1', '/rajaongkir/destinations', [
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => function($req) {
|
||||||
|
$search = sanitize_text_field($req->get_param('search') ?? '');
|
||||||
|
if (strlen($search) < 3 || !class_exists('Cekongkir_API')) return [];
|
||||||
|
|
||||||
|
$api = Cekongkir_API::get_instance();
|
||||||
|
$results = $api->search_destination_api($search);
|
||||||
|
|
||||||
|
$formatted = [];
|
||||||
|
if (is_array($results)) {
|
||||||
|
foreach ($results as $r) {
|
||||||
|
$formatted[] = [
|
||||||
|
'value' => (string) ($r['id'] ?? ''),
|
||||||
|
'label' => $r['label'] ?? $r['text'] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_slice($formatted, 0, 50);
|
||||||
|
},
|
||||||
|
'permission_callback' => '__return_true'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Register Native SPA Field & cleanup unnecessary native fields
|
||||||
|
add_filter('woocommerce_checkout_fields', function($fields) {
|
||||||
|
if (!class_exists('Cekongkir_API')) return $fields;
|
||||||
|
|
||||||
|
// Check if store only ships to Indonesia to safely hide fallback fields
|
||||||
|
$allowed = WC()->countries->get_allowed_countries();
|
||||||
|
$indonesia_only = (count($allowed) === 1 && isset($allowed['ID']));
|
||||||
|
|
||||||
|
if ($indonesia_only) {
|
||||||
|
$fields['shipping']['shipping_country']['type'] = 'hidden';
|
||||||
|
$fields['shipping']['shipping_state']['type'] = 'hidden';
|
||||||
|
$fields['shipping']['shipping_city']['type'] = 'hidden';
|
||||||
|
$fields['shipping']['shipping_postcode']['type'] = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
$dest_field = [
|
||||||
|
'type' => 'searchable_select',
|
||||||
|
'label' => __('Destination (Province, City, Subdistrict)', 'woonoow'),
|
||||||
|
'required' => $indonesia_only,
|
||||||
|
'priority' => 85,
|
||||||
|
'search_endpoint' => '/rajaongkir/destinations',
|
||||||
|
'search_param' => 'search',
|
||||||
|
'min_chars' => 3,
|
||||||
|
'custom_attributes' => ['data-show-for-country' => 'ID'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$fields['billing']['billing_destination_id'] = $dest_field;
|
||||||
|
$fields['shipping']['shipping_destination_id'] = $dest_field;
|
||||||
|
return $fields;
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
// 3. Bridge Selection back into WooCommerce specific session key
|
||||||
|
add_action('woonoow/shipping/before_calculate', function($shipping, $items) {
|
||||||
|
if (!class_exists('Cekongkir_API')) return;
|
||||||
|
|
||||||
|
$country = $shipping['country'] ?? WC()->customer->get_shipping_country();
|
||||||
|
if ($country !== 'ID') return;
|
||||||
|
|
||||||
|
$dest_id = $shipping['destination_id']
|
||||||
|
?? $shipping['shipping_destination_id']
|
||||||
|
?? $shipping['billing_destination_id'] ?? null;
|
||||||
|
|
||||||
|
if ($dest_id) {
|
||||||
|
WC()->session->set('selected_destination_id', intval($dest_id));
|
||||||
|
WC()->session->set('shipping_for_package_0', false);
|
||||||
|
}
|
||||||
|
}, 10, 2);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Custom Shipping Addons
|
||||||
|
|
||||||
|
If you do not want to use an external plugin and instead wish to build an official WooNooW extension (like `WooNooW Indonesia Shipping` with Biteship capabilities), treat it as a standard WooNooW Addon Module.
|
||||||
|
|
||||||
|
1. Develop a native React UI component using `ADDONS_GUIDE.md`.
|
||||||
|
2. Intercept `woonoow_order_form_after_shipping` via Hook.
|
||||||
|
3. Validate and handle the custom data payload via the standard WooCommerce REST endpoints.
|
||||||
Reference in New Issue
Block a user