1. Added Emoji Flags to Country/Region Select ✅
Before: Indonesia
After: 🇮🇩 Indonesia
Implementation:
- Uses same countryCodeToEmoji() helper
- Flags for all countries in dropdown
- Better visual identification
2. Implemented Bank Account Repeater Field ✅
New field type: 'account'
- Add/remove multiple bank accounts
- Each account has 6 fields:
* Account Name (required)
* Account Number (required)
* Bank Name (required)
* Sort Code / Branch Code (optional)
* IBAN (optional)
* BIC / SWIFT (optional)
UI Features:
✅ Compact card layout with muted background
✅ 2-column grid on desktop, 1-column on mobile
✅ Delete button per account (trash icon)
✅ Add button at bottom with plus icon
✅ Account numbering (Account 1, Account 2, etc.)
✅ Smaller inputs (h-9) for compact layout
✅ Clear labels with required indicators
Perfect for:
- Direct Bank Transfer (BACS)
- Manual payment methods
- Multiple bank account management
3. Updated GenericGatewayForm ✅
Added support:
- New 'account' field type
- BankAccount interface
- Repeater logic (add/remove/update)
- Plus and Trash2 icons from lucide-react
Data structure:
interface BankAccount {
account_name: string;
account_number: string;
bank_name: string;
sort_code?: string;
iban?: string;
bic?: string;
}
Benefits:
✅ Country select now has visual flags
✅ Bank accounts are easy to manage
✅ Compact, responsive UI
✅ Clear visual hierarchy
✅ Supports international formats (IBAN, BIC, Sort Code)
Files Modified:
- Store.tsx: Added flags to country select
- GenericGatewayForm.tsx: Bank account repeater
- SubmenuBar.tsx: Fullscreen prop (user change)
1. Made Settings Submenu Sticky ✅
Problem: Settings submenu wasn't sticky like Dashboard
Solution: Added sticky positioning to SubmenuBar
Added classes:
- sticky top-0 z-20
- bg-background/95 backdrop-blur
- supports-[backdrop-filter]:bg-background/60
Result: ✅ Settings submenu now stays at top when scrolling
2. Switched to Emoji Flags ✅
Problem: Base64 images not showing in select options
Better Solution: Use native emoji flags
Benefits:
- ✅ No image loading required
- ✅ Native OS rendering
- ✅ Smaller bundle size
- ✅ Better performance
- ✅ Always works (no broken images)
Implementation:
function countryCodeToEmoji(countryCode: string): string {
const codePoints = countryCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}
// AE → 🇦🇪
// US → 🇺🇸
// ID → 🇮🇩
3. Updated Currency Select ✅
Before: [Image] United Arab Emirates dirham (AED)
After: 🇦🇪 United Arab Emirates dirham (AED)
- Emoji flag in label
- No separate icon prop needed
- Works immediately
4. Updated Store Summary ✅
Before: [Image] Your store is located in Indonesia
After: 🇮🇩 Your store is located in Indonesia
- Dynamic emoji flag based on currency
- Cleaner implementation
- No image loading
5. Simplified SearchableSelect ✅
- Removed icon prop (not needed with emoji)
- Removed image rendering code
- Simpler component API
Files Modified:
- SubmenuBar.tsx: Added sticky positioning
- Store.tsx: Emoji flags + helper function
- searchable-select.tsx: Removed icon support
Why Emoji > Images:
✅ Universal support (all modern browsers/OS)
✅ No loading time
✅ No broken images
✅ Smaller code
✅ Native rendering
✅ Accessibility friendly
1. Fixed Submenu Active State ✅
Problem: First submenu always active due to pathname.startsWith()
- /dashboard matches /dashboard/analytics
- Both items show as active
Solution: Use exact match instead
- const isActive = pathname === it.path
- Only clicked item shows as active
Files: DashboardSubmenuBar.tsx, SubmenuBar.tsx
2. Fixed Currency Symbol Display ✅
Problem: HTML entities showing (ءإ)
Solution: Use currency code when symbol has HTML entities
Before: United Arab Emirates dirham (ءإ)
After: United Arab Emirates dirham (AED)
Logic:
const displaySymbol = (!currency.symbol || currency.symbol.includes('&#'))
? currency.code
: currency.symbol;
3. Integrated Flags.json ✅
A. Moved flags.json to admin-spa/src/data/
B. Added flag support to SearchableSelect component
- New icon prop in Option interface
- Displays flag before label in trigger
- Displays flag before label in dropdown
C. Currency select now shows flags
- Flag icon next to each currency
- Visual country identification
- Better UX for currency selection
D. Dynamic store summary with flag
Before: 🇮🇩 Your store is located in Indonesia
After: [FLAG] Your store is located in Indonesia
- Flag based on selected currency
- Country name from flags.json
- Currency name (not just code)
- Dynamic updates when currency changes
Benefits:
✅ Clear submenu navigation
✅ Readable currency symbols
✅ Visual country flags
✅ Better currency selection UX
✅ Dynamic store location display
Files Modified:
- DashboardSubmenuBar.tsx: Exact match for active state
- SubmenuBar.tsx: Exact match for active state
- Store.tsx: Currency symbol fix + flags integration
- searchable-select.tsx: Icon support
- flags.json: Moved to admin-spa/src/data/
Problem: Payment gateway settings modal was using Dialog on all screen sizes
Solution: Split into responsive Dialog (desktop) and Drawer (mobile)
Changes:
1. Added Drawer and useMediaQuery imports
2. Added isDesktop hook: useMediaQuery("(min-width: 768px)")
3. Split modal into two conditional renders:
- Desktop (≥768px): Dialog with horizontal footer layout
- Mobile (<768px): Drawer with vertical footer layout
Desktop Layout (Dialog):
- Center modal overlay
- Horizontal footer: Cancel | View in WC | Save
- max-h-[80vh] for scrolling
Mobile Layout (Drawer):
- Bottom sheet (slides up from bottom)
- Vertical footer (full width buttons):
1. Save Settings (primary)
2. View in WooCommerce (ghost)
3. Cancel (outline)
- max-h-[90vh] for more screen space
- Swipe down to dismiss
Benefits:
✅ Native mobile experience with bottom sheet
✅ Easier to reach buttons on mobile (bottom of screen)
✅ Better one-handed use
✅ Swipe gesture to dismiss
✅ Desktop keeps familiar modal experience
User Changes Applied:
- AlertDialog z-index: z-50 → z-[999] (higher than other modals)
- Dialog max-height: max-h-[100vh] → max-h-[80vh] (better desktop UX)
Files Modified:
- Payments.tsx: Responsive Dialog/Drawer implementation
- alert-dialog.tsx: Increased z-index for proper layering
1. Reverted Accordion Grouping ✅
Problem: Payment titles are editable by users
- User renames "BNI Virtual Account" to "BNI VA 2"
- Grouping breaks - gateway moves to new accordion
- Confusing UX when titles change
Solution: Back to flat list
- All payment methods in one list
- Titles can be edited without breaking layout
- Simpler, more predictable behavior
2. Added AlertDialog Component ✅
Installed: @radix-ui/react-alert-dialog
Created: alert-dialog.tsx (shadcn pattern)
Use for confirmations:
- "Are you sure you want to delete?"
- "Discard unsaved changes?"
- "Disable payment method?"
Example:
<AlertDialog>
<AlertDialogTrigger>Delete</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
Shadcn Dialog Components:
✅ Dialog - Forms, settings (@radix-ui/react-dialog)
✅ Drawer - Mobile bottom sheet (vaul)
✅ AlertDialog - Confirmations (@radix-ui/react-alert-dialog)
All three are official shadcn components!
Created responsive dialog pattern for better mobile UX:
Components Added:
1. drawer.tsx - Vaul-based drawer component (bottom sheet)
2. responsive-dialog.tsx - Smart wrapper that switches based on screen size
3. use-media-query.ts - Hook to detect screen size
Pattern:
- Desktop (≥768px): Use Dialog (modal overlay)
- Mobile (<768px): Use Drawer (bottom sheet)
- Provides consistent API for both
Usage Example:
<ResponsiveDialog
open={isOpen}
onOpenChange={setIsOpen}
title="Settings"
description="Configure your options"
footer={<Button>Save</Button>}
>
<FormContent />
</ResponsiveDialog>
Benefits:
- Better mobile UX with native-feeling bottom sheet
- Easier to reach buttons on mobile
- Consistent desktop experience
- Single component API
Dependencies:
- npm install vaul (drawer library)
- @radix-ui/react-dialog (already installed)
Next Steps:
- Convert payment gateway modal to use ResponsiveDialog
- Use AlertDialog for confirmations
- Apply pattern to other modals in project
Note: Payment gateway modal needs custom implementation
due to complex layout (scrollable body + sticky footer)
1. Remove Enable/Disable Checkbox ✅
- Already controlled by toggle in main UI
- Skip rendering 'enabled' field in GenericGatewayForm
- Cleaner form, less redundancy
2. Use Field Default as Default Value ✅
- Already working: field.value ?? field.default
- Backend sends current value, falls back to default
- No changes needed
3. Group Online Payments by Provider ✅
- Installed @radix-ui/react-accordion
- Created accordion.tsx component
- Group by gateway.title (provider name)
- Show provider with method count
- Expand to see individual methods
Structure:
TriPay (3 payment methods)
├─ BNI Virtual Account
├─ Mandiri Virtual Account
└─ BCA Virtual Account
PayPal (1 payment method)
└─ PayPal
Benefits:
- Cleaner UI with less clutter
- Easy to find specific provider
- Shows method count at a glance
- Multiple providers can be expanded
- Better organization for many gateways
Files Modified:
- GenericGatewayForm.tsx: Skip enabled field
- Payments.tsx: Accordion grouping by provider
- accordion.tsx: New component (shadcn pattern)
Next: Dialog/Drawer responsive pattern
Mobile Improvements:
1. Modal footer buttons now stack vertically on mobile
- Order: Save Settings (primary) -> View in WooCommerce -> Cancel
- Full width buttons on mobile for easier tapping
- Responsive padding: px-4 on mobile, px-6 on desktop
2. Refresh button moved inline with title
- Added action prop to SettingsLayout
- Refresh button now appears next to Payments title
- Cleaner, more compact layout
Payment Categories Simplified:
3. Removed Payment Providers section
- PayPal, Stripe are also 3rd party, not different
- Confusing to separate providers from other gateways
- All non-manual gateways now in single category
4. Renamed to Online Payment Methods
- Was: Manual + Payment Providers + 3rd Party
- Now: Manual + Online Payment Methods
- Clearer distinction: offline vs online payments
5. Unified styling for all online gateways
- Same card style as manual methods
- Status badges (Enabled/Disabled)
- Requirements alerts
- Manage button always visible
Mobile UX:
- Footer buttons: flex-col on mobile, flex-row on desktop
- Proper button ordering with CSS order utilities
- Responsive spacing and padding
- Touch-friendly button sizes
Files Modified:
- Payments.tsx: Mobile footer + simplified categories
- SettingsLayout.tsx: Added action prop for header actions
Result:
✅ Better mobile experience
✅ Clearer payment method organization
✅ Consistent styling across all gateways
✅ Issue 1: Modal Not Showing Current Values (FIXED!)
Problem: Opening modal showed defaults, not current saved values
Root Cause: Backend only sent field.default, not current value
Solution:
- Backend: Added field.value with current saved value
- normalize_field() now includes: value: $current_settings[$key]
- Frontend: Use field.value ?? field.default for initial data
- GenericGatewayForm initializes with current values
Result: ✅ Modal now shows "BNI Virtual Account 2" not "BNI Virtual Account"
✅ Issue 2: Sticky Modal Footer (FIXED!)
Problem: Footer scrolls away with long forms
Solution:
- Restructured modal: header + scrollable body + sticky footer
- DialogContent: flex flex-col with overflow on body only
- Footer: sticky bottom-0 with border-t
- Save button triggers form.requestSubmit()
Result: ✅ Cancel, View in WooCommerce, Save always visible
✅ Issue 3: HTML in Descriptions (FIXED!)
Problem: TriPay icon shows as raw HTML string
Solution:
- Changed: {field.description}
- To: dangerouslySetInnerHTML={{ __html: field.description }}
- Respects vendor creativity (images, formatting, links)
Result: ✅ TriPay icon image renders properly
📋 Technical Details:
Backend Changes (PaymentGatewaysProvider.php):
- get_gateway_settings() passes $current_settings to extractors
- normalize_field() adds 'value' => $current_settings[$key]
- All fields now have both default and current value
Frontend Changes:
- GatewayField interface: Added value?: string | boolean
- GenericGatewayForm: Initialize with field.value
- Modal structure: Header + Body (scroll) + Footer (sticky)
- Descriptions: Render as HTML with dangerouslySetInnerHTML
Files Modified:
- PaymentGatewaysProvider.php: Add current values to fields
- Payments.tsx: Restructure modal layout + add value to interface
- GenericGatewayForm.tsx: Use field.value + sticky footer + HTML descriptions
🎯 Result:
✅ Modal shows current saved values
✅ Footer always visible (no scrolling)
✅ Vendor HTML/images render properly
🔴 Issue 1: Toggle Loading State (CRITICAL FIX)
Problem: Optimistic update lies - toggle appears to work but fails
Solution:
- Removed ALL optimistic updates
- Added loading state tracking (togglingGateway)
- Disabled toggle during mutation
- Show real server state only
- User sees loading, not lies
Result: ✅ Honest UI - shows loading, then real state
🔴 Issue 2: 30s Save Time (CRITICAL FIX)
Problem: Saving gateway settings takes 30 seconds
Root Cause: WooCommerce analytics/tracking HTTP requests
Solution:
- Block HTTP during save: add_filter('pre_http_request', '__return_true', 999)
- Save settings (fast)
- Re-enable HTTP: remove_filter()
- Same fix as orders module
Result: ✅ Save now takes 1-2 seconds instead of 30s
🟡 Issue 3: Inconsistent Input Styling (FIXED)
Problem: email/tel inputs look different (browser defaults)
Solution:
- Added appearance-none to Input component
- Override -webkit-appearance
- Override -moz-appearance (for number inputs)
- Consistent styling for ALL input types
Result: ✅ All inputs look identical regardless of type
📋 Technical Details:
Toggle Flow (No More Lies):
User clicks → Disable toggle → Show loading → API call → Success → Refetch → Enable toggle
Save Flow (Fast):
Block HTTP → Save to DB → Unblock HTTP → Return (1-2s)
Input Styling:
text, email, tel, number, url, password → All identical appearance
Files Modified:
- Payments.tsx: Removed optimistic, added loading state
- PaymentGatewaysProvider.php: Block HTTP during save
- input.tsx: Override browser default styles
🎯 Result:
✅ No more lying optimistic updates
✅ 30s → 1-2s save time
✅ Consistent input styling
✅ Issue 1: Toggle Not Saving (CRITICAL FIX)
Problem: Toggle appeared to work but didn't persist
Root Cause: Missing query invalidation after toggle
Solution:
- Added queryClient.invalidateQueries after successful toggle
- Now fetches real server state after optimistic update
- Ensures SPA and WooCommerce stay in sync
✅ Issue 2: SearchableSelect Default Value
Problem: Showing 'Select country...' when Indonesia selected
Root Cause: WooCommerce stores country as 'ID:DKI_JAKARTA'
Solution:
- Split country:state format in backend
- Extract country code only for select
- Added timezone fallback to 'UTC' if empty
✅ Issue 3: 3rd Party Gateway Settings
Problem: TriPay showing 'Configure in WooCommerce' link
Solution:
- Replaced external link with Settings button
- Now opens GenericGatewayForm modal
- All WC form_fields render automatically
- TriPay fields (enable_icon, expired, checkout_method) work!
📋 Files Modified:
- Payments.tsx: Added invalidation + settings button
- StoreSettingsProvider.php: Split country format
- All 3rd party gateways now configurable in SPA
🎯 Result:
✅ Toggle saves correctly to WooCommerce
✅ Country/timezone show selected values
✅ All gateways with form_fields are editable
✅ No more 'Configure in WooCommerce' for compliant gateways
✅ Issue 1: Modal Z-Index Fixed
- Increased dialog z-index: z-[9999] → z-[99999]
- Now properly appears above fullscreen mode (z-50)
✅ Issue 2: Searchable Select for Large Lists
- Replaced Select with SearchableSelect for:
- Countries (200+ options)
- Currencies (100+ options)
- Timezones (400+ options)
- Users can now type to search instead of scrolling
- Better UX for large datasets
✅ Issue 3: Input Type Support
- Input component already supports type attribute
- No changes needed (already working)
✅ Issue 4: Timezone Options Fixed
- Replaced optgroup (not supported) with flat list
- SearchableSelect handles filtering by continent name
- Shows: 'Asia/Jakarta (UTC+7:00)'
- Search includes continent, city, and offset
📊 Result:
- ✅ Modal always on top
- ✅ Easy search for countries/currencies/timezones
- ✅ No more scrolling through hundreds of options
- ✅ Better accessibility
Addresses user feedback issues 1-4
✅ Store.tsx - Complete API Integration:
- Replaced mock data with real API calls
- useQuery for fetching settings, countries, timezones, currencies
- useMutation for saving settings
- Optimistic updates and error handling
✅ Real Data Sources:
- Countries: 200+ countries from WooCommerce (WC_Countries)
- Timezones: 400+ timezones from PHP with UTC offsets
- Currencies: 100+ currencies with symbols
- Settings: All WooCommerce store options
✅ UI Improvements:
- Country select: Full list instead of 5 hardcoded
- Timezone select: Grouped by continent with UTC offsets
- Currency select: Full list with symbols
- Already using shadcn components (Input, Select)
✅ Performance:
- 1 hour cache for static data (countries, timezones, currencies)
- 1 minute cache for settings
- Proper loading states
📋 Addresses user feedback:
- ✅ Wire real options for country and timezone
- ✅ Contact fields already use shadcn components
Next: Create custom BACS form with bank account repeater
✅ Generic form builder for payment gateways:
Features:
- Supports 8 field types: text, password, checkbox, select, textarea, number, email, url
- Auto-categorizes fields: Basic, API, Advanced
- Multi-page tabs for 20+ fields
- Single page for < 20 fields
- Unsupported field warning with link to WC settings
- Field validation (required, placeholder, etc.)
- Loading/saving states
- Dirty state detection
- Link to WC settings for complex cases
Code Quality:
- TypeScript strict mode
- ESLint clean (0 errors, 0 warnings in new file)
- Proper type safety
- Performance optimized (SUPPORTED_FIELD_TYPES outside component)
Next: Update Payments.tsx to use real API
- Installed @radix-ui/react-switch
- Created switch.tsx following existing UI component patterns
- Fixes import error in ToggleField component
- Dev server now running successfully