fix: PHP Fatal Error and attribute input UX

Critical Fixes:

1.  PHP Fatal Error - FIXED
   Problem: call_user_func() error - Permissions::check_admin does not exist
   Cause: Method name mismatch in ProductsController.php
   Solution: Changed all 8 occurrences from:
     'permission_callback' => [Permissions::class, 'check_admin']
   To:
     'permission_callback' => [Permissions::class, 'check_admin_permission']

   Affected routes:
   - GET /products
   - GET /products/:id
   - POST /products
   - PUT /products/:id
   - DELETE /products/:id
   - GET /products/categories
   - GET /products/tags
   - GET /products/attributes

2.  Attribute Options Input - FIXED
   Problem: Cannot type anything after first word (cursor jumps)
   Cause: Controlled input with immediate state update on onChange
   Solution: Changed to uncontrolled input with onBlur

   Changes:
   - value → defaultValue (uncontrolled)
   - onChange → onBlur (update on blur)
   - Added key prop for proper re-rendering
   - Added onKeyDown for Enter key support
   - Updated help text: "press Enter or click away"

   Now you can:
    Type: Red, Blue, Green (naturally!)
    Type: Red | Blue | Green (pipe works too!)
    Press Enter to save
    Click away to save
    No cursor jumping!

Result:
- Products index page loads without PHP error
- Attribute options input works naturally
- Both comma and pipe separators supported
This commit is contained in:
dwindown
2025-11-19 23:04:58 +07:00
parent d13a356331
commit 7bab3d809d
2 changed files with 18 additions and 11 deletions

View File

@@ -136,8 +136,9 @@ export function VariationsTab({
<div> <div>
<Label>{__('Options')}</Label> <Label>{__('Options')}</Label>
<Input <Input
value={attr.options.join(', ')} key={`options-${index}-${attr.options.length}`}
onChange={(e) => { defaultValue={attr.options.join(', ')}
onBlur={(e) => {
const input = e.target.value; const input = e.target.value;
// Split by comma or pipe, trim whitespace // Split by comma or pipe, trim whitespace
const options = input const options = input
@@ -146,11 +147,17 @@ export function VariationsTab({
.filter(Boolean); .filter(Boolean);
updateAttribute(index, 'options', options); updateAttribute(index, 'options', options);
}} }}
onKeyDown={(e) => {
// Update on Enter key
if (e.key === 'Enter') {
e.currentTarget.blur();
}
}}
placeholder={__('Red, Blue, Green (comma or pipe separated)')} placeholder={__('Red, Blue, Green (comma or pipe separated)')}
className="mt-1.5" className="mt-1.5"
/> />
<p className="text-xs text-muted-foreground mt-1"> <p className="text-xs text-muted-foreground mt-1">
{__('Type naturally: Red, Blue, Green (comma or | works)')} {__('Type naturally: Red, Blue, Green (press Enter or click away)')}
</p> </p>
</div> </div>
</div> </div>

View File

@@ -28,56 +28,56 @@ class ProductsController {
register_rest_route('woonoow/v1', '/products', [ register_rest_route('woonoow/v1', '/products', [
'methods' => 'GET', 'methods' => 'GET',
'callback' => [__CLASS__, 'get_products'], 'callback' => [__CLASS__, 'get_products'],
'permission_callback' => [Permissions::class, 'check_admin'], 'permission_callback' => [Permissions::class, 'check_admin_permission'],
]); ]);
// Get single product // Get single product
register_rest_route('woonoow/v1', '/products/(?P<id>\d+)', [ register_rest_route('woonoow/v1', '/products/(?P<id>\d+)', [
'methods' => 'GET', 'methods' => 'GET',
'callback' => [__CLASS__, 'get_product'], 'callback' => [__CLASS__, 'get_product'],
'permission_callback' => [Permissions::class, 'check_admin'], 'permission_callback' => [Permissions::class, 'check_admin_permission'],
]); ]);
// Create product // Create product
register_rest_route('woonoow/v1', '/products', [ register_rest_route('woonoow/v1', '/products', [
'methods' => 'POST', 'methods' => 'POST',
'callback' => [__CLASS__, 'create_product'], 'callback' => [__CLASS__, 'create_product'],
'permission_callback' => [Permissions::class, 'check_admin'], 'permission_callback' => [Permissions::class, 'check_admin_permission'],
]); ]);
// Update product // Update product
register_rest_route('woonoow/v1', '/products/(?P<id>\d+)', [ register_rest_route('woonoow/v1', '/products/(?P<id>\d+)', [
'methods' => 'PUT', 'methods' => 'PUT',
'callback' => [__CLASS__, 'update_product'], 'callback' => [__CLASS__, 'update_product'],
'permission_callback' => [Permissions::class, 'check_admin'], 'permission_callback' => [Permissions::class, 'check_admin_permission'],
]); ]);
// Delete product // Delete product
register_rest_route('woonoow/v1', '/products/(?P<id>\d+)', [ register_rest_route('woonoow/v1', '/products/(?P<id>\d+)', [
'methods' => 'DELETE', 'methods' => 'DELETE',
'callback' => [__CLASS__, 'delete_product'], 'callback' => [__CLASS__, 'delete_product'],
'permission_callback' => [Permissions::class, 'check_admin'], 'permission_callback' => [Permissions::class, 'check_admin_permission'],
]); ]);
// Get product categories // Get product categories
register_rest_route('woonoow/v1', '/products/categories', [ register_rest_route('woonoow/v1', '/products/categories', [
'methods' => 'GET', 'methods' => 'GET',
'callback' => [__CLASS__, 'get_categories'], 'callback' => [__CLASS__, 'get_categories'],
'permission_callback' => [Permissions::class, 'check_admin'], 'permission_callback' => [Permissions::class, 'check_admin_permission'],
]); ]);
// Get product tags // Get product tags
register_rest_route('woonoow/v1', '/products/tags', [ register_rest_route('woonoow/v1', '/products/tags', [
'methods' => 'GET', 'methods' => 'GET',
'callback' => [__CLASS__, 'get_tags'], 'callback' => [__CLASS__, 'get_tags'],
'permission_callback' => [Permissions::class, 'check_admin'], 'permission_callback' => [Permissions::class, 'check_admin_permission'],
]); ]);
// Get product attributes // Get product attributes
register_rest_route('woonoow/v1', '/products/attributes', [ register_rest_route('woonoow/v1', '/products/attributes', [
'methods' => 'GET', 'methods' => 'GET',
'callback' => [__CLASS__, 'get_attributes'], 'callback' => [__CLASS__, 'get_attributes'],
'permission_callback' => [Permissions::class, 'check_admin'], 'permission_callback' => [Permissions::class, 'check_admin_permission'],
]); ]);
} }