Root causes fixed:
1. API method: search_destination_api() not search_destination()
2. Field filter: removed country check that failed before user selection
3. Field now always added if store sells to Indonesia
The woocommerce_checkout_fields filter now:
- Checks if Rajaongkir is active
- Checks if Indonesia is in allowed countries
- Always adds field (frontend will show/hide based on country)
Based on deep analysis of Rajaongkir plugin:
- Destinations searched via RajaOngkir API (no local DB table)
- Uses Cekongkir_API::search_destination() for search
- Session 'selected_destination_id' required for rates
- Added SPA-aware checkout field injection
Code snippet includes:
1. REST endpoint /woonoow/v1/rajaongkir/destinations
2. Checkout field filter with REST context detection
3. Session bridge via woonoow/shipping/before_calculate
Issues fixed:
1. Country field was disabled when API failed (length 0)
- Changed: disabled={countries.length <= 1} → disabled={countries.length === 1}
- Only disables in single-country mode now
2. State field was disabled when no preloaded states
- Changed: Falls back to text input instead of disabled SearchableSelect
- Allows manual state entry for countries without state list
3. /countries API required admin permission
- Added public /countries endpoint to CheckoutController
- Uses permission_callback __return_true for customer checkout access
- Returns countries, states, and default_country
Backend (CheckoutController):
- Enhanced get_fields() API with custom_attributes, search_endpoint,
search_param, min_chars, input_class, default
- Supports new 'searchable_select' field type for API-backed search
Customer SPA:
- Created DynamicCheckoutField component for all field types
- Checkout fetches fields from /checkout/fields API
- Renders custom fields from PHP filters (billing + shipping)
- searchable_select type with live API search
- Custom field data included in checkout submission
This enables:
- Checkout Field Editor Pro compatibility
- Rajaongkir destination_id via simple code snippet
- Any plugin using woocommerce_checkout_fields filter
Updated RAJAONGKIR_INTEGRATION.md with code snippet approach.
Admin SPA:
- Changed billing/shipping state from Select to SearchableSelect
Customer SPA:
- Added cmdk package for command palette
- Created popover, command, and searchable-select UI components
- Added searchable country and state fields to checkout
- Fetches countries/states from /countries API
- Auto-clears state when country changes
Backend:
- Added generic woonoow/shipping/before_calculate hook
- Removed hardcoded Rajaongkir session handling
Documentation:
- Updated RAJAONGKIR_INTEGRATION.md with:
- Complete searchable destination selector plugin code
- JavaScript implementation
- React component version
- REST API endpoint for destination search
The wpAdminUrl config already includes admin.php?page=woonoow,
so constructing /admin.php?page=wc-settings on top of it was wrong.
Now uses siteUrl + /wp-admin for external WC links.
When WC packages array is empty, manually calculate rates by:
1. Get zone matching the shipping address
2. Call get_rates_for_package() on each method
3. Or fallback to method title/cost for simple methods like Free Shipping
Also added debug info to response to help diagnose issues.
Removed static method-level fallback. Shipping method selector now:
1. Shows 'Enter shipping address to see available rates' when address incomplete
2. Calls calculate_shipping endpoint to get actual WC_Shipping_Rate objects
3. Displays rate-level options (e.g., JNE REG, JNE YES) not method-level
This ensures third-party shipping plugins like Rajaongkir, UPS, FedEx
display their courier rates correctly.
1. Shipping method selector now shows static shippings list when
address is not complete, instead of 'No shipping methods available'.
Only shows the empty message when address IS complete but no methods
matched.
2. Variation selector in Dialog and Drawer now displays attribute names
(Size, Dispenser) in semibold and values (30ml, pump) in normal
weight for better visual hierarchy.
WooCommerce stores variation custom attribute meta keys in lowercase
(e.g., attribute_size), but we were using the original case from
parent attributes (e.g., attribute_Size). This caused empty attribute
values in the admin Order Form variation selector.
Fix: Use sanitize_title() to normalize the attribute name.
Instead of mounting to body (which breaks scoped styles), we now
mount the popover portal to #woonoow-admin-app. This ensures
dropdowns inherit the correct CSS variables and styling.
- Added global styles for [cmdk-root], [cmdk-list], [cmdk-item]
- Forced white background and border for [data-radix-popper-content-wrapper]
- Fixed missing styles that caused transparent/transparent dropdowns
- Guest users see wishlist icon in header (uses /wishlist page)
- Logged-in users don't see it (they use /my-account/wishlist instead)
- Applied to all 3 layout styles: Classic, Modern, Boutique
Root cause: LicensingModule::init() was called from within
plugins_loaded but then tried to add ANOTHER plugins_loaded action
for LicenseManager::init(). Since plugins_loaded already fired,
LicenseManager::init() never ran and WooCommerce order hooks
were never registered.
Fix: Call self::maybe_init_manager() directly instead of
scheduling via add_action.
- Added woocommerce_payment_complete hook
- Added woocommerce_thankyou hook for COD/virtual orders
- Added is_virtual_order helper to detect virtual-only orders
- generate_licenses_for_order now called from multiple hooks
(safe due to license_exists_for_order_item check)
1. Category Selection Bug:
- Added 'id' alias to category/tag API responses
- Frontend uses cat.id which was undefined (API returned term_id)
2. Checkout Redirect:
- Changed from window.location.href + reload to navigate()
- Added !isProcessing check to empty cart condition
3. Sidebar Shipping for Virtual-Only:
- Hide Shipping Method section when isVirtualOnly
- Hide Shipping row in totals when isVirtualOnly
4. License Table:
- Table creation runs via ensure_tables() on plugins_loaded
Added specific selectors:
- #woonoow-admin-app header
- #woonoow-admin-app nav
- #woonoow-admin-app [data-submenubar]
- #woonoow-admin-app [data-bottomnav]
These target the exact WooNooW app elements that need hiding.
Invoice Page (/orders/:id/invoice):
- A4-ready layout (210mm x 297mm)
- Store header, invoice number, QR code
- Billing/shipping address sections
- Styled items table with alternating rows
- Totals summary with conditional display
- Thank you footer
- Back to Order and Print buttons
Label Page (/orders/:id/label):
- 4x6 inch thermal label layout
- Ship To address with phone
- Items list (physical products only)
- Shipping method
- QR code for scanning
- Back to Order and Print buttons
Order Detail:
- Removed print-mode logic
- Removed print-only layouts
- Invoice/Label buttons now link to dedicated pages
- Label button still hidden for virtual-only orders
- Added license_duration_days field to ProductVariant type
- Added License Duration input to each variation card
- Backend: ProductsController saves/loads variation-level _license_duration_days meta
- Allows different license periods per variation (e.g., 1-year, 2-year, lifetime)
Copy Cart/Checkout Links:
- Added to GeneralTab for simple products (same pattern as variations)
- Link generation with add-to-cart and redirect params
Licensing Settings:
- 'Enable licensing for this product' checkbox in Additional Options
- License settings panel: activation limit, duration (days)
- State management in ProductFormTabbed
- Backend: ProductsController saves/loads licensing meta fields
Backend:
- _licensing_enabled, _license_activation_limit, _license_duration_days post meta
- LicensingSettings.php with key format, activation limits, expiry settings
- LicenseManager.php with key generation, activation/deactivation, validation
- LicensingModule.php with WooCommerce product meta integration
- LicensesController.php with admin, customer, and public API endpoints
- Database tables: woonoow_licenses, woonoow_license_activations
- has_settings enabled in ModuleRegistry
- Fetch avatar settings in AccountLayout on mount
- Display custom avatar or gravatar in sidebar
- Dispatch woonoow:avatar-updated event on upload/remove
- Listen for event in AccountLayout for real-time sync
1. Admin Store Link - Add to WP admin bar (Menu.php) with proper option check
2. Activity Log - Fix Loading text to show correct state after data loads
3. Avatar Upload - Use correct option key woonoow_allow_custom_avatar
4. Downloadable Files - Connect to WooCommerce native:
- Add downloads array to format_product_full
- Add downloads/download_limit/download_expiry handling in update_product
- Add downloads handling in create_product
Customer Avatar Upload:
- Add /account/avatar endpoint for upload/delete
- Add /account/avatar-settings endpoint for settings
- Update AccountDetails.tsx with avatar upload UI
- Support base64 image upload with validation
Product Downloadable Files:
- Create DownloadsTab component for file management
- Add downloads state to ProductFormTabbed
- Show Downloads tab when 'downloadable' is checked
- Support file name, URL, download limit, and expiry
- Add Store link to admin header (visible when customer SPA is enabled)
- Add storeUrl and customerSpaEnabled to WNW_CONFIG in Assets.php and StandaloneAdmin.php
- Update window.d.ts with new WNW_CONFIG properties
- Create ActivityLog.tsx component with search, filters, and pagination
- Add /notifications/logs API endpoint to NotificationsController
- Update Notifications.tsx to link to activity log page
- Add ActivityLog route to App.tsx
1. Add allow_custom_avatar toggle to Customer Settings
2. Implement coupon apply/remove in Cart and Checkout pages
3. Update Cart interface with coupons array and discount_total
4. Implement Downloads page to fetch from /account/downloads API
- Created Newsletter/index.tsx as tabs container
- Extracted Newsletter/Subscribers.tsx (from old Newsletter.tsx)
- Moved Campaigns to Newsletter/Campaigns.tsx
- Updated App.tsx routes (campaigns now under newsletter)
- Removed separate Campaigns card from Marketing index
- Follows Customer Notifications tab pattern for consistency
- Sticky not possible when page is inside overflow-auto container
- Using standard flexbox layout where sidebar and content scroll together
- Separate mobile (fixed overlay) and desktop (inline) sidebars
- Clean, simple layout matching typical documentation patterns
- Fixed sidebar to not use inset-y-0 (was overriding top offset)
- Mobile: fixed positioning with sidebarTopClass
- Desktop: lg:sticky for proper sticky behavior
- Added Help item to NavigationRegistry::get_base_tree
- Empty children array means no submenu bar displayed
- Incremented NAV_VERSION to 1.0.9 to trigger cache rebuild
- Help icon: help-circle
- Added X-WP-Nonce header to docs API fetch calls in Help page
- Fixed build-production.sh to include docs/ folder (changed --exclude='*.md' to --exclude='/*.md')
- This allows root-level docs like README.md to be excluded while keeping docs/ folder
Phase 1: Core Documentation
- Created docs/ folder with 8 markdown documentation files
- Getting Started, Installation, Troubleshooting, FAQ
- Configuration docs (Appearance, SPA Mode)
- Feature docs (Shop, Checkout)
- PHP registry with filter hook for addon extensibility
Phase 2: Documentation Viewer
- DocsController.php with REST API endpoints
- GET /woonoow/v1/docs - List all docs (with addon hook)
- GET /woonoow/v1/docs/{slug} - Get document content
- Admin SPA /help route with sidebar navigation
- Markdown rendering with react-markdown
- Added Help & Docs to More page for mobile access
Filter Hook: woonoow_docs_registry
Addons can register their own documentation sections.
- Removed shortcode replacement for Cart, Checkout, My Account pages
- WooCommerce pages now keep their original [woocommerce_*] shortcodes
- Plugin only creates dedicated SPA page (/store) with [woonoow_spa]
- Auto-sets spa_page in appearance settings
This aligns with template override approach - WC pages render normally
when SPA is disabled, and redirect to SPA when mode is 'full'.