feat: Add toggle functionality to Shipping methods

Implemented inline enable/disable for shipping methods.

Frontend Changes:
 Allow HTML in shipping method names and prices
 Add toggle switches to each shipping method
 Loading state while toggling
 Toast notifications for success/error
 Optimistic UI updates via React Query

Backend Changes:
 POST /settings/shipping/zones/{zone_id}/methods/{instance_id}/toggle
 Enable/disable shipping methods
 Clear WooCommerce shipping cache
 Proper error handling

User Experience:
- Quick enable/disable without leaving page
- Similar to Payments page pattern
- Complex configuration still in WooCommerce
- Edit zone button for detailed settings
- Add zone button for new zones

Result:
 Functional shipping management
 No need to redirect for simple toggles
 Maintains WooCommerce compatibility
 Clean, intuitive interface
This commit is contained in:
dwindown
2025-11-08 21:44:19 +07:00
parent 3b0bc43194
commit a8a4b1deee
2 changed files with 140 additions and 4 deletions

View File

@@ -27,6 +27,7 @@ interface ShippingZone {
export default function ShippingPage() { export default function ShippingPage() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin'; const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin';
const [togglingMethod, setTogglingMethod] = useState<string | null>(null);
// Fetch shipping zones from WooCommerce // Fetch shipping zones from WooCommerce
const { data: zones = [], isLoading, refetch } = useQuery({ const { data: zones = [], isLoading, refetch } = useQuery({
@@ -35,6 +36,28 @@ export default function ShippingPage() {
staleTime: 5 * 60 * 1000, // 5 minutes staleTime: 5 * 60 * 1000, // 5 minutes
}); });
// Toggle shipping method mutation
const toggleMutation = useMutation({
mutationFn: async ({ zoneId, instanceId, enabled }: { zoneId: number; instanceId: number; enabled: boolean }) => {
return api.post(`/settings/shipping/zones/${zoneId}/methods/${instanceId}/toggle`, { enabled });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['shipping-zones'] });
toast.success(__('Shipping method updated successfully'));
},
onError: (error: any) => {
toast.error(error?.message || __('Failed to update shipping method'));
},
onSettled: () => {
setTogglingMethod(null);
},
});
const handleToggle = (zoneId: number, instanceId: number, enabled: boolean) => {
setTogglingMethod(`${zoneId}-${instanceId}`);
toggleMutation.mutate({ zoneId, instanceId, enabled });
};
if (isLoading) { if (isLoading) {
return ( return (
<SettingsLayout <SettingsLayout
@@ -138,10 +161,13 @@ export default function ShippingPage() {
key={rate.id} key={rate.id}
className="flex items-center justify-between py-2 px-3 bg-muted/50 rounded-md" className="flex items-center justify-between py-2 px-3 bg-muted/50 rounded-md"
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 flex-1">
<Truck className="h-4 w-4 text-muted-foreground" /> <Truck className="h-4 w-4 text-muted-foreground" />
<div> <div className="flex-1">
<span className="text-sm font-medium">{rate.name}</span> <span
className="text-sm font-medium"
dangerouslySetInnerHTML={{ __html: rate.name }}
/>
{rate.transitTime && ( {rate.transitTime && (
<span className="text-xs text-muted-foreground ml-2"> <span className="text-xs text-muted-foreground ml-2">
{rate.transitTime} {rate.transitTime}
@@ -154,7 +180,19 @@ export default function ShippingPage() {
)} )}
</div> </div>
</div> </div>
<span className="text-sm font-semibold">{rate.price}</span> <div className="flex items-center gap-3">
<span
className="text-sm font-semibold"
dangerouslySetInnerHTML={{ __html: rate.price }}
/>
<ToggleField
id={`${zone.id}-${rate.instance_id}`}
label=""
checked={rate.enabled}
onCheckedChange={(checked) => handleToggle(zone.id, rate.instance_id, checked)}
disabled={togglingMethod === `${zone.id}-${rate.instance_id}`}
/>
</div>
</div> </div>
))} ))}
</div> </div>

View File

@@ -32,6 +32,33 @@ class ShippingController extends WP_REST_Controller {
), ),
) )
); );
// Toggle shipping method
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/zones/(?P<zone_id>\d+)/methods/(?P<instance_id>\d+)/toggle',
array(
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'toggle_method' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'zone_id' => array(
'required' => true,
'type' => 'integer',
),
'instance_id' => array(
'required' => true,
'type' => 'integer',
),
'enabled' => array(
'required' => true,
'type' => 'boolean',
),
),
),
)
);
} }
/** /**
@@ -153,6 +180,77 @@ class ShippingController extends WP_REST_Controller {
} }
} }
/**
* Toggle shipping method enabled/disabled
*/
public function toggle_method( WP_REST_Request $request ) {
try {
$zone_id = $request->get_param( 'zone_id' );
$instance_id = $request->get_param( 'instance_id' );
$enabled = $request->get_param( 'enabled' );
// Get the zone
$zone = \WC_Shipping_Zones::get_zone( $zone_id );
if ( ! $zone ) {
return new WP_REST_Response(
array(
'error' => 'zone_not_found',
'message' => __( 'Shipping zone not found', 'woonoow' ),
),
404
);
}
// Get all shipping methods for this zone
$shipping_methods = $zone->get_shipping_methods();
$method_found = false;
foreach ( $shipping_methods as $method ) {
if ( $method->instance_id == $instance_id ) {
$method_found = true;
// Update the enabled status
$method->enabled = $enabled ? 'yes' : 'no';
update_option( $method->get_instance_option_key(), $method->instance_settings );
break;
}
}
if ( ! $method_found ) {
return new WP_REST_Response(
array(
'error' => 'method_not_found',
'message' => __( 'Shipping method not found', 'woonoow' ),
),
404
);
}
// Clear shipping cache
\WC_Cache_Helper::get_transient_version( 'shipping', true );
return new WP_REST_Response(
array(
'success' => true,
'message' => $enabled
? __( 'Shipping method enabled', 'woonoow' )
: __( 'Shipping method disabled', 'woonoow' ),
),
200
);
} catch ( \Exception $e ) {
return new WP_REST_Response(
array(
'error' => 'toggle_failed',
'message' => $e->getMessage(),
),
500
);
}
}
/** /**
* Check if user has permission to manage shipping * Check if user has permission to manage shipping
*/ */