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:
@@ -27,6 +27,7 @@ interface ShippingZone {
|
||||
export default function ShippingPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin';
|
||||
const [togglingMethod, setTogglingMethod] = useState<string | null>(null);
|
||||
|
||||
// Fetch shipping zones from WooCommerce
|
||||
const { data: zones = [], isLoading, refetch } = useQuery({
|
||||
@@ -35,6 +36,28 @@ export default function ShippingPage() {
|
||||
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) {
|
||||
return (
|
||||
<SettingsLayout
|
||||
@@ -138,10 +161,13 @@ export default function ShippingPage() {
|
||||
key={rate.id}
|
||||
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" />
|
||||
<div>
|
||||
<span className="text-sm font-medium">{rate.name}</span>
|
||||
<div className="flex-1">
|
||||
<span
|
||||
className="text-sm font-medium"
|
||||
dangerouslySetInnerHTML={{ __html: rate.name }}
|
||||
/>
|
||||
{rate.transitTime && (
|
||||
<span className="text-xs text-muted-foreground ml-2">
|
||||
• {rate.transitTime}
|
||||
@@ -154,7 +180,19 @@ export default function ShippingPage() {
|
||||
)}
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user