fix(checkout): fix disabled country/state and add public countries API
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
This commit is contained in:
@@ -628,18 +628,27 @@ export default function Checkout() {
|
||||
value={billingData.country}
|
||||
onChange={(v) => setBillingData({ ...billingData, country: v })}
|
||||
placeholder="Select country"
|
||||
disabled={countries.length <= 1}
|
||||
disabled={countries.length === 1}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">State / Province *</label>
|
||||
<SearchableSelect
|
||||
options={billingStateOptions}
|
||||
value={billingData.state}
|
||||
onChange={(v) => setBillingData({ ...billingData, state: v })}
|
||||
placeholder={billingStateOptions.length ? "Select state" : "N/A"}
|
||||
disabled={!billingStateOptions.length}
|
||||
/>
|
||||
{billingStateOptions.length > 0 ? (
|
||||
<SearchableSelect
|
||||
options={billingStateOptions}
|
||||
value={billingData.state}
|
||||
onChange={(v) => setBillingData({ ...billingData, state: v })}
|
||||
placeholder="Select state"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
value={billingData.state}
|
||||
onChange={(e) => setBillingData({ ...billingData, state: e.target.value })}
|
||||
placeholder="Enter state/province"
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Postcode / ZIP *</label>
|
||||
@@ -801,18 +810,27 @@ export default function Checkout() {
|
||||
value={shippingData.country}
|
||||
onChange={(v) => setShippingData({ ...shippingData, country: v })}
|
||||
placeholder="Select country"
|
||||
disabled={countries.length <= 1}
|
||||
disabled={countries.length === 1}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">State / Province *</label>
|
||||
<SearchableSelect
|
||||
options={shippingStateOptions}
|
||||
value={shippingData.state}
|
||||
onChange={(v) => setShippingData({ ...shippingData, state: v })}
|
||||
placeholder={shippingStateOptions.length ? "Select state" : "N/A"}
|
||||
disabled={!shippingStateOptions.length}
|
||||
/>
|
||||
{shippingStateOptions.length > 0 ? (
|
||||
<SearchableSelect
|
||||
options={shippingStateOptions}
|
||||
value={shippingData.state}
|
||||
onChange={(v) => setShippingData({ ...shippingData, state: v })}
|
||||
placeholder="Select state"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
value={shippingData.state}
|
||||
onChange={(e) => setShippingData({ ...shippingData, state: e.target.value })}
|
||||
placeholder="Enter state/province"
|
||||
className="w-full border rounded-lg px-4 py-2"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Postcode / ZIP *</label>
|
||||
|
||||
@@ -32,6 +32,12 @@ class CheckoutController {
|
||||
'callback' => [ new self(), 'get_fields' ],
|
||||
'permission_callback' => [ \WooNooW\Api\Permissions::class, 'anon_or_wp_nonce' ],
|
||||
]);
|
||||
// Public countries endpoint for customer checkout form
|
||||
register_rest_route($namespace, '/countries', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [ new self(), 'get_countries' ],
|
||||
'permission_callback' => '__return_true', // Public - needed for checkout
|
||||
]);
|
||||
// Public order view endpoint for thank you page
|
||||
register_rest_route($namespace, '/checkout/order/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
@@ -729,4 +735,42 @@ class CheckoutController {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get countries and states for checkout form
|
||||
* Public endpoint - no authentication required
|
||||
*/
|
||||
public function get_countries(): array {
|
||||
$wc_countries = WC()->countries;
|
||||
|
||||
// Get allowed selling countries
|
||||
$allowed = $wc_countries->get_allowed_countries();
|
||||
|
||||
// Format for frontend
|
||||
$countries = [];
|
||||
foreach ($allowed as $code => $name) {
|
||||
$countries[] = [
|
||||
'code' => $code,
|
||||
'name' => $name,
|
||||
];
|
||||
}
|
||||
|
||||
// Get states for all allowed countries
|
||||
$states = [];
|
||||
foreach (array_keys($allowed) as $country_code) {
|
||||
$country_states = $wc_countries->get_states($country_code);
|
||||
if (!empty($country_states) && is_array($country_states)) {
|
||||
$states[$country_code] = $country_states;
|
||||
}
|
||||
}
|
||||
|
||||
// Get default country
|
||||
$default_country = $wc_countries->get_base_country();
|
||||
|
||||
return [
|
||||
'countries' => $countries,
|
||||
'states' => $states,
|
||||
'default_country' => $default_country,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user