Implemented context-aware back button that respects user's navigation path:
Pattern:
```typescript
const handleBack = () => {
if (window.history.state?.idx > 0) {
navigate(-1); // Go back in history
} else {
navigate('/fallback'); // Safe fallback
}
};
```
Updated Pages:
✅ Orders/Detail.tsx → Fallback: /orders
✅ Orders/Edit.tsx → Fallback: /orders/:id
✅ Customers/Detail.tsx → Fallback: /customers
✅ Customers/Edit.tsx → Fallback: /customers
✅ Products/Edit.tsx → Fallback: /products
✅ Coupons/Edit.tsx → Fallback: /coupons
User Flow Examples:
1. Normal Navigation (History Available):
Customers Index → Customer Detail → Orders Tab → Order Detail
→ Click Back → Returns to Customer Detail ✅
2. Direct Access (No History):
User opens /orders/360 directly
→ Click Back → Goes to /orders (fallback) ✅
3. New Tab (No History):
User opens order in new tab
→ Click Back → Goes to /orders (fallback) ✅
4. Page Refresh (History Cleared):
User refreshes page
→ Click Back → Goes to fallback ✅
Benefits:
✅ Respects user's navigation path when possible
✅ Never breaks or leaves the app
✅ Predictable behavior in all scenarios
✅ Professional UX (like Gmail, Shopify, etc.)
✅ Works with deep links and bookmarks
Technical:
- Uses window.history.state.idx to detect history
- Falls back to safe default when no history
- Consistent pattern across all pages
- No URL parameters needed
Result: Back button now works intelligently based on context!
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
Critical bug: Hook called after conditional return
Problem:
- useEffect at line 107 was AFTER early returns (lines 83-102)
- When loading/error states triggered early return
- Hook order changed between renders
- React detected hook order violation
- Component broke with blank screen
Rules of Hooks violation:
❌ Before:
1. All hooks (useState, useQuery, etc.)
2. Early return if loading
3. Early return if error
4. useEffect (line 107) ← WRONG! After conditional returns
✅ After:
1. All hooks including ALL useEffects
2. Early return if loading
3. Early return if error
4. Render
Fix:
- Moved useEffect from line 107 to line 62
- Now before any early returns
- Changed product?.meta to productQ.data?.meta
- Hooks always called in same order
- No conditional hook calls
Result:
✅ Product edit form loads correctly
✅ No React warnings
✅ Follows Rules of Hooks
✅ Consistent hook order every render
Fixed 3 critical issues:
1. Fixed Vertical Tabs - Cards All Showing
- Updated VerticalTabForm to hide inactive sections
- Only active section visible (className: hidden for others)
- Proper tab switching now works
2. Added Mobile Search/Filter to Coupons
- Created CouponFilterSheet component
- Added mobile search bar with icon
- Filter button with active count badge
- Matches Products pattern exactly
- Sheet with Apply/Reset buttons
3. Removed max-height from VerticalTabForm
- User removed max-h-[calc(100vh-200px)]
- Content now flows naturally
- Better for forms with varying heights
Components Created:
- CouponFilterSheet.tsx - Mobile filter bottom sheet
- Discount type filter
- Apply/Reset actions
- Active filter count
Changes to Coupons/index.tsx:
- Added mobile search bar (md:hidden)
- Added filter sheet state
- Added activeFiltersCount
- Search icon + SlidersHorizontal icon
- Filter badge indicator
Changes to VerticalTabForm:
- Hide inactive sections (className: hidden)
- Only show section matching activeTab
- Proper visibility control
Result:
✅ Vertical tabs work correctly (only one section visible)
✅ Mobile search/filter on Coupons (like Products)
✅ Filter count badge
✅ Professional mobile UX
Next: Move customer site member checkbox to settings
Added comprehensive product/category restrictions to coupon form
Features Added:
1. Product Selectors:
- Products (include) - multiselect with search
- Exclude products - multiselect with search
- Shows product names with searchable dropdown
- Badge display for selected items
2. Category Selectors:
- Product categories (include) - multiselect
- Exclude categories - multiselect
- Shows category names with search
- Badge display for selected items
3. API Integration:
- Added ProductsApi.list() endpoint
- Added ProductsApi.categories() endpoint
- Fetch products and categories on form load
- React Query caching for performance
4. Form Data:
- Added product_ids field
- Added excluded_product_ids field
- Added product_categories field
- Added excluded_product_categories field
- Proper number/string conversion
UI/UX Improvements:
- Searchable multiselect dropdowns
- Badge display with X to remove
- Shows +N more when exceeds display limit
- Clear placeholder text
- Helper text for each field
- Consistent spacing and layout
Technical:
- Uses MultiSelect component (shadcn-based)
- React Query for data fetching
- Proper TypeScript types
- Number array handling
Note: Brands field not included yet (requires WooCommerce Product Brands extension check)
Result:
- Full WooCommerce coupon restrictions support
- Clean, searchable UI
- Production ready
Fixed blank white page caused by SelectItem validation error
Problem:
- SelectItem cannot have empty string as value
- Radix UI Select requires non-empty values
- Empty value for 'All types' filter caused component crash
Solution:
- Changed empty string to 'all' value for All types option
- Updated Select to use undefined when no filter selected
- Updated query logic to ignore 'all' value (treat as no filter)
- Updated hasActiveFilters check to exclude 'all' value
Changes:
- Select value: discountType || undefined
- Select onChange: value || '' (convert back to empty string)
- Query filter: discountType !== 'all' ? discountType : undefined
- Active filters: discountType && discountType !== 'all'
Result:
- No more SelectItem validation errors
- Page loads correctly
- Filter works as expected
- Clear filters button shows/hides correctly