docs: Clean up obsolete docs and create Customer SPA Master Plan
Documentation Cleanup: ✅ Archived 6 obsolete/completed docs to archive/: - CUSTOMER_DATA_FLOW_ANALYSIS.md - CALCULATION_EFFICIENCY_AUDIT.md - PHASE_COMPLETE.md - PRODUCT_FORM_UX_IMPROVEMENTS.md - PROGRESS_NOTE.md - TASKS_SUMMARY.md New Documentation: ✅ CUSTOMER_SPA_MASTER_PLAN.md - Comprehensive strategy Includes: 1. Architecture Overview - Hybrid plugin architecture - customer-spa folder structure - Frontend/Backend separation 2. Deployment Modes - Shortcode Mode (default, works with any theme) - Full SPA Mode (maximum performance) - Hybrid Mode (best of both worlds) 3. Feature Scope - Phase 1: Core Commerce (MVP) - Phase 2: Enhanced Features - Phase 3: Advanced Features 4. UX Best Practices - Research-backed patterns (Baymard Institute) - Cart UX (drawer, mini cart, shipping threshold) - Checkout UX (progress, guest, autocomplete) - Product Page UX (images, CTA, social proof) 5. Technical Stack - React 18 + Vite - Zustand + React Query - TailwindCSS + shadcn/ui - PWA with Workbox 6. Implementation Roadmap - 10 sprints (20 weeks) - Foundation → Catalog → Cart → Account → Polish 7. API Requirements - 15+ new endpoints needed - Shop, Cart, Checkout, Account APIs 8. Performance Targets - Core Web Vitals - Bundle sizes - Load times 9. Settings & Configuration - Frontend mode selection - Feature toggles - Customization options 10. Migration Strategy - From WooCommerce default - Rollback plan - Success metrics Result: Clear, actionable plan for Customer SPA development!
This commit is contained in:
368
archive/CALCULATION_EFFICIENCY_AUDIT.md
Normal file
368
archive/CALCULATION_EFFICIENCY_AUDIT.md
Normal file
@@ -0,0 +1,368 @@
|
||||
# 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
|
||||
345
archive/CUSTOMER_DATA_FLOW_ANALYSIS.md
Normal file
345
archive/CUSTOMER_DATA_FLOW_ANALYSIS.md
Normal file
@@ -0,0 +1,345 @@
|
||||
# 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
|
||||
26
archive/PHASE_COMPLETE.md
Normal file
26
archive/PHASE_COMPLETE.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 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! 🚀
|
||||
373
archive/PRODUCT_FORM_UX_IMPROVEMENTS.md
Normal file
373
archive/PRODUCT_FORM_UX_IMPROVEMENTS.md
Normal file
@@ -0,0 +1,373 @@
|
||||
# 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
|
||||
3147
archive/PROGRESS_NOTE.md
Normal file
3147
archive/PROGRESS_NOTE.md
Normal file
File diff suppressed because it is too large
Load Diff
130
archive/TASKS_SUMMARY.md
Normal file
130
archive/TASKS_SUMMARY.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user