diff --git a/admin-spa/src/routes/Settings/Payments.tsx b/admin-spa/src/routes/Settings/Payments.tsx index 796a977..a393f60 100644 --- a/admin-spa/src/routes/Settings/Payments.tsx +++ b/admin-spa/src/routes/Settings/Payments.tsx @@ -74,10 +74,11 @@ function SortableGatewayItem({ gateway, children }: { gateway: PaymentGateway; c return (
-
+ {/* Drag handle - hidden on mobile for better UX */} +
-
+
{children}
@@ -102,22 +103,36 @@ export default function PaymentsPage() { ); // Fetch all payment gateways - const { data: gateways = [], isLoading, refetch } = useQuery({ + const { data: gatewayData, isLoading, refetch } = useQuery({ queryKey: ['payment-gateways'], queryFn: () => api.get('/payments/gateways'), refetchOnWindowFocus: true, staleTime: 5 * 60 * 1000, // 5 minutes }); - // Initialize order from gateways + // Extract gateways and order from response + const gateways = gatewayData?.gateways || []; + const savedOrder = gatewayData?.order || { manual: [], online: [] }; + + // Initialize order from saved order or gateways React.useEffect(() => { if (gateways.length > 0 && manualOrder.length === 0 && onlineOrder.length === 0) { - const manual = gateways.filter((g: PaymentGateway) => g.type === 'manual').map((g: PaymentGateway) => g.id); - const online = gateways.filter((g: PaymentGateway) => g.type === 'provider' || g.type === 'other').map((g: PaymentGateway) => g.id); - setManualOrder(manual); - setOnlineOrder(online); + // Use saved order if available, otherwise use gateway order + if (savedOrder.manual.length > 0) { + setManualOrder(savedOrder.manual); + } else { + const manual = gateways.filter((g: PaymentGateway) => g.type === 'manual').map((g: PaymentGateway) => g.id); + setManualOrder(manual); + } + + if (savedOrder.online.length > 0) { + setOnlineOrder(savedOrder.online); + } else { + const online = gateways.filter((g: PaymentGateway) => g.type === 'provider' || g.type === 'other').map((g: PaymentGateway) => g.id); + setOnlineOrder(online); + } } - }, [gateways, manualOrder.length, onlineOrder.length]); + }, [gateways, savedOrder, manualOrder.length, onlineOrder.length]); // Toggle gateway mutation const toggleMutation = useMutation({ @@ -162,7 +177,7 @@ export default function PaymentsPage() { }; // Handle drag end for manual gateways - const handleManualDragEnd = (event: DragEndEvent) => { + const handleManualDragEnd = async (event: DragEndEvent) => { const { active, over } = event; if (!over || active.id === over.id) return; @@ -172,12 +187,23 @@ export default function PaymentsPage() { const newOrder = arrayMove(manualOrder, oldIndex, newIndex); setManualOrder(newOrder); - // TODO: Save order to backend - toast.success('Payment methods reordered'); + // Save order to backend + try { + await api.post('/payments/gateways/order', { + category: 'manual', + order: newOrder, + }); + toast.success('Payment methods reordered'); + } catch (error) { + console.error('Failed to save order:', error); + toast.error('Failed to save order'); + // Revert order on error + setManualOrder(manualOrder); + } }; // Handle drag end for online gateways - const handleOnlineDragEnd = (event: DragEndEvent) => { + const handleOnlineDragEnd = async (event: DragEndEvent) => { const { active, over } = event; if (!over || active.id === over.id) return; @@ -187,8 +213,19 @@ export default function PaymentsPage() { const newOrder = arrayMove(onlineOrder, oldIndex, newIndex); setOnlineOrder(newOrder); - // TODO: Save order to backend - toast.success('Payment methods reordered'); + // Save order to backend + try { + await api.post('/payments/gateways/order', { + category: 'online', + order: newOrder, + }); + toast.success('Payment methods reordered'); + } catch (error) { + console.error('Failed to save order:', error); + toast.error('Failed to save order'); + // Revert order on error + setOnlineOrder(onlineOrder); + } }; const handleSaveGateway = async (settings: Record) => { diff --git a/includes/Api/PaymentsController.php b/includes/Api/PaymentsController.php index 823ea93..a4a507c 100644 --- a/includes/Api/PaymentsController.php +++ b/includes/Api/PaymentsController.php @@ -94,6 +94,31 @@ class PaymentsController extends WP_REST_Controller { ], ], ]); + + // POST /woonoow/v1/payments/gateways/order + register_rest_route($this->namespace, '/' . $this->rest_base . '/gateways/order', [ + [ + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => [$this, 'save_gateway_order'], + 'permission_callback' => [$this, 'check_permission'], + 'args' => [ + 'category' => [ + 'description' => 'Gateway category (manual or online)', + 'type' => 'string', + 'required' => true, + 'enum' => ['manual', 'online'], + ], + 'order' => [ + 'description' => 'Array of gateway IDs in desired order', + 'type' => 'array', + 'required' => true, + 'items' => [ + 'type' => 'string', + ], + ], + ], + ], + ]); } /** @@ -106,7 +131,17 @@ class PaymentsController extends WP_REST_Controller { try { $gateways = PaymentGatewaysProvider::get_gateways(); - $response = rest_ensure_response($gateways); + // Get saved order + $manual_order = get_option('woonoow_payment_gateway_order_manual', []); + $online_order = get_option('woonoow_payment_gateway_order_online', []); + + $response = rest_ensure_response([ + 'gateways' => $gateways, + 'order' => [ + 'manual' => $manual_order, + 'online' => $online_order, + ], + ]); // Cache for 5 minutes $response->header('Cache-Control', 'max-age=300'); @@ -265,6 +300,48 @@ class PaymentsController extends WP_REST_Controller { } } + /** + * Save gateway order + * + * @param WP_REST_Request $request Request object + * @return WP_REST_Response|WP_Error Response object or error + */ + public function save_gateway_order(WP_REST_Request $request) { + $category = $request->get_param('category'); + $order = $request->get_param('order'); + + // Validate category + if (!in_array($category, ['manual', 'online'], true)) { + return new WP_Error( + 'invalid_category', + 'Category must be either "manual" or "online"', + ['status' => 400] + ); + } + + // Validate order is array + if (!is_array($order)) { + return new WP_Error( + 'invalid_order', + 'Order must be an array of gateway IDs', + ['status' => 400] + ); + } + + // Save to WordPress options + $option_key = 'woonoow_payment_gateway_order_' . $category; + update_option($option_key, $order, false); + + error_log(sprintf('[WooNooW] Saved %s gateway order: %s', $category, implode(', ', $order))); + + return rest_ensure_response([ + 'success' => true, + 'message' => 'Gateway order saved successfully', + 'category' => $category, + 'order' => $order, + ]); + } + /** * Check permission *