From 275b045b5f6a08445670b3ed31b7001f185e63a2 Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 20 Nov 2025 23:52:23 +0700 Subject: [PATCH] docs: Update PROJECT_SOP and add customer data flow analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Updated PROJECT_SOP.md: ✅ Added mobile card linkable pattern with full example ✅ Added submenu mobile hiding rules and behavior matrix ✅ Documented stopPropagation pattern for checkboxes ✅ Added ChevronRight icon requirement ✅ Documented active:scale animation for tap feedback ✅ Added spacing rules (space-y-3 for cards) 2. Created CUSTOMER_DATA_FLOW_ANALYSIS.md: ✅ Comprehensive analysis of customer data flow ✅ Documented 2 customer types: Guest vs Site Member ✅ Identified validation issues in OrdersController ✅ Found weak ! empty() checks allowing bad data ✅ Documented inconsistent validation between controllers ✅ Created action items for fixes ✅ Added test cases for all scenarios Key Findings: ❌ OrdersController uses ! empty() - allows 'Indonesia' string ❌ No phone number sanitization in order creation ❌ No validation that phone is actually a phone number ✅ CustomersController has better validation (isset + sanitize) Next: Investigate source of 'Indonesia' value and implement fixes --- CUSTOMER_DATA_FLOW_ANALYSIS.md | 345 ++++++++++++++++++++ PROJECT_SOP.md | 83 ++++- admin-spa/src/routes/Coupons/CouponForm.tsx | 4 +- 3 files changed, 424 insertions(+), 8 deletions(-) create mode 100644 CUSTOMER_DATA_FLOW_ANALYSIS.md diff --git a/CUSTOMER_DATA_FLOW_ANALYSIS.md b/CUSTOMER_DATA_FLOW_ANALYSIS.md new file mode 100644 index 0000000..d038e53 --- /dev/null +++ b/CUSTOMER_DATA_FLOW_ANALYSIS.md @@ -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 diff --git a/PROJECT_SOP.md b/PROJECT_SOP.md index a3b0a21..ef30b64 100644 --- a/PROJECT_SOP.md +++ b/PROJECT_SOP.md @@ -263,6 +263,39 @@ When updating an existing module to follow this pattern: ``` +**Submenu Mobile Behavior:** + +To reduce clutter on mobile detail/new/edit pages, submenu MUST be hidden on mobile for these pages: + +```typescript +// In SubmenuBar.tsx +const isDetailPage = /\/(orders|products|coupons|customers)\/(?:new|\d+(?:\/edit)?)$/.test(pathname); +const hiddenOnMobile = isDetailPage ? 'hidden md:block' : ''; + +return ( +
+ {/* Submenu items */} +
+); +``` + +**Rules:** +1. ✅ **Hide on mobile** for detail/new/edit pages (has own tabs + back button) +2. ✅ **Show on desktop** for all pages (useful for quick navigation) +3. ✅ **Show on mobile** for index pages only (list views) +4. ✅ **Use regex pattern** to detect detail/new/edit pages +5. ❌ **Never hide on desktop** - always useful for navigation +6. ❌ **Never show on mobile detail pages** - causes clutter + +**Behavior Matrix:** + +| Page Type | Mobile | Desktop | Reason | +|-----------|--------|---------|--------| +| Index (`/orders`) | ✅ Show | ✅ Show | Main navigation | +| New (`/orders/new`) | ❌ Hide | ✅ Show | Has form tabs + back button | +| Edit (`/orders/123/edit`) | ❌ Hide | ✅ Show | Has form tabs + back button | +| Detail (`/orders/123`) | ❌ Hide | ✅ Show | Has detail tabs + back button | + **Toolbar (React):** ```typescript @@ -394,18 +427,55 @@ All CRUD list pages MUST follow these consistent UI patterns: ``` -**Mobile Card Pattern:** +**Mobile Card Pattern (Linkable):** + +Mobile cards MUST be fully tappable (whole card is a link) for better UX: + ```tsx -
+
{items.map(item => ( - - {/* Card content */} - + +
+ {/* Checkbox with stopPropagation */} +
{ e.preventDefault(); e.stopPropagation(); onSelect(item.id); }}> + +
+ + {/* Content */} +
+

{item.name}

+
{item.description}
+
+ {item.stats} +
+
{item.amount}
+
+ + {/* Chevron */} + +
+ ))}
``` -**Rules:** +**Card Rules:** +1. ✅ **Whole card is Link** - Better mobile UX (single tap to view/edit) +2. ✅ **Use `space-y-3`** - Consistent spacing between cards +3. ✅ **Checkbox stopPropagation** - Prevent navigation when selecting +4. ✅ **ChevronRight icon** - Visual indicator card is tappable +5. ✅ **Active scale animation** - `active:scale-[0.98]` for tap feedback +6. ✅ **Hover effect** - `hover:bg-accent/50` for desktop hover +7. ✅ **Shadow** - `shadow-sm` for depth +8. ✅ **Rounded corners** - `rounded-xl` for modern look +9. ❌ **Never use separate edit button** - Whole card should be tappable +10. ❌ **Never use `space-y-2`** - Use `space-y-3` for consistency + +**Table Rules:** 1. ✅ **Always use `p-3`** for table cells (NOT `px-3 py-2`) 2. ✅ **Always add `hover:bg-muted/30`** to body rows 3. ✅ **Always use `bg-muted/50`** for table headers @@ -419,6 +489,7 @@ All CRUD list pages MUST follow these consistent UI patterns: - Desktop: Show table with `hidden md:block` - Mobile: Show cards with `md:hidden` - Both views must support same actions (select, edit, delete) +- Cards must be linkable (whole card tappable) #### Variable Product Handling in Order Forms diff --git a/admin-spa/src/routes/Coupons/CouponForm.tsx b/admin-spa/src/routes/Coupons/CouponForm.tsx index f0b5821..2219bcf 100644 --- a/admin-spa/src/routes/Coupons/CouponForm.tsx +++ b/admin-spa/src/routes/Coupons/CouponForm.tsx @@ -80,8 +80,8 @@ export default function CouponForm({ const tabs = [ { id: 'general', label: __('General'), icon: }, - { id: 'restrictions', label: __('Usage restrictions'), icon: }, - { id: 'limits', label: __('Usage limits'), icon: }, + { id: 'restrictions', label: __('Restrictions'), icon: }, + { id: 'limits', label: __('Limits'), icon: }, ]; return (