diff --git a/assets/src/js/react/frontend/listing-owner-dashboard/app.tsx b/assets/src/js/react/frontend/listing-owner-dashboard/app.tsx index 5a50742601..c5b95e5c95 100644 --- a/assets/src/js/react/frontend/listing-owner-dashboard/app.tsx +++ b/assets/src/js/react/frontend/listing-owner-dashboard/app.tsx @@ -1,14 +1,17 @@ /** * WordPress dependencies */ +import apiFetch from '@wordpress/api-fetch'; +import { Button, DropdownMenu, Modal } from '@wordpress/components'; +import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { moreVertical } from '@wordpress/icons'; -import { DropdownMenu } from '@wordpress/components'; /** * External dependencies */ import { Column } from '@shamim-ahmed/components/build-types/gutenberg/table/types'; +import { registerCrudStore, useCrudStore } from '@shamim-ahmed/data'; import { Table } from '@shamim-ahmed/dashboard'; import moment from 'moment'; @@ -23,7 +26,10 @@ import { OrderTableContainer } from './style'; import { ActionsDropdownWrapper } from './style'; const checkoutPageUrl = directorist_admin_order.checkout_page_url; -const columns: Column[] = [ +const orderStoreName = 'directorist/orders'; +const orderStorePath = '/directorist/v1/orders'; + +const baseColumns: Column[] = [ { id: 'id', label: __('Order Id', 'directorist'), @@ -98,50 +104,131 @@ const columns: Column[] = [ ); }, }, - { - id: 'actions', - label: __('Actions', 'directorist'), - render: ({ item }) => { - const controls: any[] = []; +]; - if (item?.status === 'pending') { - controls.push({ - title: __('Pay', 'directorist'), - onClick: () => { - window.location.href = `${checkoutPageUrl}?checkout_type=payment&order_id=${item?.id}`; - }, - }); - } +export default function App() { + registerCrudStore({ name: orderStoreName, path: orderStorePath }); + const { updateItem } = useCrudStore({ name: orderStoreName }); + const [cancelItem, setCancelItem] = useState(null); + const [isCancelling, setIsCancelling] = useState(false); + const [cancelError, setCancelError] = useState(''); - return ( - - - + const handleCancelOrder = async () => { + if (!cancelItem?.id) return; + + setIsCancelling(true); + setCancelError(''); + + try { + await apiFetch({ + path: `/directorist/v1/orders/${cancelItem.id}/cancel`, + method: 'POST', + }); + updateItem(cancelItem.id, { status: 'cancelled' }); + setCancelItem(null); + } catch (error) { + setCancelError( + error?.message || + __('Failed to cancel order. Please try again.', 'directorist') ); + } finally { + setIsCancelling(false); + } + }; + + const columns: Column[] = [ + ...baseColumns, + { + id: 'actions', + label: __('Actions', 'directorist'), + render: ({ item }) => { + if (item?.status !== 'pending') { + return ; + } + + const controls: any[] = [ + { + title: __('Pay', 'directorist'), + onClick: () => { + window.location.href = `${checkoutPageUrl}?checkout_type=payment&order_id=${item?.id}`; + }, + }, + { + title: __('Cancel', 'directorist'), + onClick: () => { + setCancelError(''); + setCancelItem(item); + }, + }, + ]; + + return ( + + + + ); + }, }, - }, -]; + ]; -export default function App() { return ( + + {cancelItem && ( + { + if (!isCancelling) setCancelItem(null); + }} + > +

+ {__( + 'Are you sure you want to cancel this pending order?', + 'directorist' + )} +

+ {cancelError && ( +

{cancelError}

+ )} +
+ + +
+
+ )} ); } diff --git a/includes/rest-api/Version1/class-order-controller.php b/includes/rest-api/Version1/class-order-controller.php index d6d76ef0fd..1ae691157c 100644 --- a/includes/rest-api/Version1/class-order-controller.php +++ b/includes/rest-api/Version1/class-order-controller.php @@ -120,6 +120,26 @@ public function register_routes() { ], ] ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)/cancel', + [ + [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'cancel' ], + 'permission_callback' => [ $this, 'auth_permissions_check' ], + 'args' => [ + 'id' => [ + 'description' => __( 'The order ID.' ), + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'required' => true, + ], + ], + ], + ] + ); } public function admin_index( WP_REST_Request $request ) { @@ -223,6 +243,34 @@ public function update_status( WP_REST_Request $request ) { ] ); } + public function cancel( WP_REST_Request $request ) { + $repository = directorist_order_repository(); + $order = $repository->get_by_id( $request->get_param( 'id' ) ); + + if ( ! $order ) { + return new WP_Error( 'rest_not_found', __( 'The order was not found' ), [ 'status' => 404 ] ); + } + + if ( (int) $order->user_id !== get_current_user_id() ) { + return new WP_Error( 'rest_forbidden', __( 'You are not authorized to cancel this order.' ), [ 'status' => 403 ] ); + } + + if ( OrderStatus::PENDING !== $order->status ) { + return new WP_Error( 'rest_invalid_status', __( 'Only pending orders can be cancelled.' ), [ 'status' => 400 ] ); + } + + $dto = $repository->to_dto( $order ); + $dto->set_status( OrderStatus::CANCELLED ); + + $repository->update( $dto ); + + return rest_ensure_response( + [ + 'message' => esc_html__( 'Order was cancelled successfully' ), + ] + ); + } + protected function store_args(): array { return [ 'user_id' => [ @@ -268,4 +316,4 @@ protected function index_args(): array { ], ]; } -} \ No newline at end of file +} diff --git a/templates/checkout/checkout.php b/templates/checkout/checkout.php index 2f83288e5d..3a423713c8 100644 --- a/templates/checkout/checkout.php +++ b/templates/checkout/checkout.php @@ -65,13 +65,14 @@

- +