diff --git a/packages/ui-components/src/__tests__/OrderDetail.test.ts b/packages/ui-components/src/__tests__/OrderDetail.test.ts
index ff7a1b45f9..d67ab98296 100644
--- a/packages/ui-components/src/__tests__/OrderDetail.test.ts
+++ b/packages/ui-components/src/__tests__/OrderDetail.test.ts
@@ -248,6 +248,94 @@ describe("OrderDetail", () => {
});
});
+ it("renders a remove dropdown (not a bare remove button) when onRemoveAndWithdrawAll is provided", async () => {
+ mockMatchesAccount.mockReturnValue(true);
+ render(OrderDetail, {
+ props: { ...defaultProps, onRemoveAndWithdrawAll: vi.fn() },
+ context: new Map([["$$_queryClient", queryClient]]),
+ });
+
+ await waitFor(() => {
+ // When onRemoveAndWithdrawAll is provided, the dropdown trigger renders.
+ expect(screen.getByTestId("remove-order-menu")).toBeInTheDocument();
+ });
+
+ // Dropdown items are hidden until the trigger is opened.
+ expect(
+ screen.queryByTestId("remove-and-withdraw-all-button"),
+ ).not.toBeInTheDocument();
+
+ await userEvent.click(screen.getByTestId("remove-order-menu"));
+
+ await waitFor(() => {
+ expect(screen.getByTestId("remove-button")).toBeInTheDocument();
+ expect(
+ screen.getByTestId("remove-and-withdraw-all-button"),
+ ).toBeInTheDocument();
+ });
+ });
+
+ it("calls onRemove (not onRemoveAndWithdrawAll) when the Remove dropdown item is clicked", async () => {
+ mockMatchesAccount.mockReturnValue(true);
+ const onRemove = vi.fn();
+ const onRemoveAndWithdrawAll = vi.fn();
+ render(OrderDetail, {
+ props: { ...defaultProps, onRemove, onRemoveAndWithdrawAll },
+ context: new Map([["$$_queryClient", queryClient]]),
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId("remove-order-menu")).toBeInTheDocument();
+ });
+ await userEvent.click(screen.getByTestId("remove-order-menu"));
+ await userEvent.click(await screen.findByTestId("remove-button"));
+
+ expect(onRemove).toHaveBeenCalledWith(mockRaindexClient, mockOrder);
+ expect(onRemoveAndWithdrawAll).not.toHaveBeenCalled();
+ });
+
+ it("calls onRemoveAndWithdrawAll with the order's vaultsList when that dropdown item is clicked", async () => {
+ mockMatchesAccount.mockReturnValue(true);
+ const onRemove = vi.fn();
+ const onRemoveAndWithdrawAll = vi.fn();
+ render(OrderDetail, {
+ props: { ...defaultProps, onRemove, onRemoveAndWithdrawAll },
+ context: new Map([["$$_queryClient", queryClient]]),
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId("remove-order-menu")).toBeInTheDocument();
+ });
+ await userEvent.click(screen.getByTestId("remove-order-menu"));
+ await userEvent.click(
+ await screen.findByTestId("remove-and-withdraw-all-button"),
+ );
+
+ expect(onRemoveAndWithdrawAll).toHaveBeenCalledWith(
+ mockRaindexClient,
+ mockOrder,
+ mockOrder.vaultsList,
+ );
+ expect(onRemove).not.toHaveBeenCalled();
+ });
+
+ it("does not show the remove dropdown for inactive orders even with onRemoveAndWithdrawAll", async () => {
+ mockMatchesAccount.mockReturnValue(true);
+ (mockRaindexClient.getOrderByHash as Mock).mockResolvedValue({
+ value: { ...mockOrder, active: false },
+ });
+ render(OrderDetail, {
+ props: { ...defaultProps, onRemoveAndWithdrawAll: vi.fn() },
+ context: new Map([["$$_queryClient", queryClient]]),
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText("Order")).toBeInTheDocument();
+ });
+ expect(screen.queryByTestId("remove-order-menu")).not.toBeInTheDocument();
+ expect(screen.queryByTestId("remove-button")).not.toBeInTheDocument();
+ });
+
it("does not show remove button if account does not match owner", async () => {
mockMatchesAccount.mockReturnValue(false);
diff --git a/packages/ui-components/src/lib/components/detail/OrderDetail.svelte b/packages/ui-components/src/lib/components/detail/OrderDetail.svelte
index 5e9d5ef451..d680a4af68 100644
--- a/packages/ui-components/src/lib/components/detail/OrderDetail.svelte
+++ b/packages/ui-components/src/lib/components/detail/OrderDetail.svelte
@@ -12,7 +12,7 @@
import { QKEY_ORDER } from '../../queries/keys';
import CodeMirrorRainlang from '../CodeMirrorRainlang.svelte';
import { createQuery, useQueryClient } from '@tanstack/svelte-query';
- import { Button, TabItem, Tabs, Tooltip } from 'flowbite-svelte';
+ import { Button, Dropdown, DropdownItem, TabItem, Tabs, Tooltip } from 'flowbite-svelte';
import { onDestroy } from 'svelte';
// import OrderApy from '../tables/OrderAPY.svelte';
import type { DebugTradeModalHandler, QuoteDebugModalHandler } from '../../types/modal';
@@ -21,6 +21,7 @@
import {
ArrowDownToBracketOutline,
ArrowUpFromBracketOutline,
+ ChevronDownOutline,
InfoCircleOutline,
WalletOutline
} from 'flowbite-svelte-icons';
@@ -51,6 +52,19 @@
*/
export let onRemove: (raindexClient: RaindexClient, order: RaindexOrder) => void;
+ /** Callback function when remove-and-withdraw-all action is triggered for an order.
+ * Removes the order and withdraws all of its vault balances in a single transaction.
+ * @param order The order to remove
+ * @param vaultsList The VaultsList struct containing the order's vaults to withdraw from
+ */
+ export let onRemoveAndWithdrawAll:
+ | ((
+ raindexClient: RaindexClient,
+ order: RaindexOrder,
+ vaultsList: RaindexVaultsList
+ ) => void)
+ | undefined = undefined;
+
/** Callback function when deposit action is triggered for a vault
* @param vault The vault to deposit into
*/
@@ -142,11 +156,32 @@
{#if matchesAccount(data.owner) && data.active}
-
+ {#if onRemoveAndWithdrawAll}
+
+
+ onRemove(raindexClient, data)}
+ data-testid="remove-button">Remove
+ onRemoveAndWithdrawAll(raindexClient, data, data.vaultsList)}
+ data-testid="remove-and-withdraw-all-button"
+ >Remove and withdraw all vaults
+
+ {:else}
+
+ {/if}
{/if}
{#if data.active && onTakeOrder}