Fix/Order dialog state (#850)
* feat: remove dialog state handling from dialog and split out edit dialog * feat: add complete state to use-vega-transaction, fix cancel dialog * feat: add custom dialog content for order submission * feat: handle custom title and custom intent * feat: separate components, make order dialog wrapper more generic * feat: remove dialog wrapper and add icon to dialog * chore: remove other dialog wrappers and use icon and title props on main dialog * chore: adjust default color of dialog text * fix: tests for tx dialog and vega tx hook * fix: order edit and cancel hook tests * chore: add edit dialog to stories * fix: e2e test for deal ticket * feat: return dialog from hook * refactor: add use-order-event hook to dedupe bus event logic * refactor: add custom title and intent to order submit dialog * chore: remove console logs * fix: type error due to component being named idalog * chore: add helper function for converting nanoseconds * chore: remove capitalization text transform to dialog titles * chore: remove unused import * feat: handle titles and intents for cancel and edit * chore: remove unused var
This commit is contained in:
parent
19a79afcc9
commit
a5f9ed90e8
@ -28,13 +28,6 @@ const mockTx = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('deal ticket orders', () => {
|
describe('deal ticket orders', () => {
|
||||||
const orderSizeField = 'order-size';
|
|
||||||
const orderPriceField = 'order-price';
|
|
||||||
const orderTIFDropDown = 'order-tif';
|
|
||||||
const placeOrderBtn = 'place-order';
|
|
||||||
const orderStatusHeader = 'order-status-header';
|
|
||||||
const orderTransactionHash = 'tx-block-explorer';
|
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.mockGQL((req) => {
|
cy.mockGQL((req) => {
|
||||||
mockTradingPage(req, MarketState.Active);
|
mockTradingPage(req, MarketState.Active);
|
||||||
@ -107,7 +100,15 @@ describe('deal ticket orders', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const testOrder = (order: Order, expected?: Partial<Order>) => {
|
const testOrder = (order: Order, expected?: Partial<Order>) => {
|
||||||
|
const orderSizeField = 'order-size';
|
||||||
|
const orderPriceField = 'order-price';
|
||||||
|
const orderTIFDropDown = 'order-tif';
|
||||||
|
const placeOrderBtn = 'place-order';
|
||||||
|
const dialogTitle = 'dialog-title';
|
||||||
|
const orderTransactionHash = 'tx-block-explorer';
|
||||||
|
|
||||||
const { type, side, size, price, timeInForce, expiresAt } = order;
|
const { type, side, size, price, timeInForce, expiresAt } = order;
|
||||||
|
|
||||||
cy.get(`[name="order-type"][value="${type}"`).click({ force: true }); // force as input is hidden and displayed as a button
|
cy.get(`[name="order-type"][value="${type}"`).click({ force: true }); // force as input is hidden and displayed as a button
|
||||||
cy.get(`[name="order-side"][value="${side}"`).click({ force: true });
|
cy.get(`[name="order-side"][value="${side}"`).click({ force: true });
|
||||||
cy.getByTestId(orderSizeField).clear().type(size);
|
cy.getByTestId(orderSizeField).clear().type(size);
|
||||||
@ -139,7 +140,7 @@ describe('deal ticket orders', () => {
|
|||||||
...expectedOrder,
|
...expectedOrder,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
cy.getByTestId(orderStatusHeader).should(
|
cy.getByTestId(dialogTitle).should(
|
||||||
'have.text',
|
'have.text',
|
||||||
'Awaiting network confirmation'
|
'Awaiting network confirmation'
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useState } from 'react';
|
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||||
import { VegaTransactionDialog, VegaTxStatus } from '@vegaprotocol/wallet';
|
|
||||||
import { DealTicket } from './deal-ticket';
|
import { DealTicket } from './deal-ticket';
|
||||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
||||||
import { useOrderSubmit } from '@vegaprotocol/orders';
|
import { useOrderSubmit, OrderFeedback } from '@vegaprotocol/orders';
|
||||||
import { OrderStatus } from '@vegaprotocol/types';
|
import { OrderStatus } from '@vegaprotocol/types';
|
||||||
|
import { Icon, Intent } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
export interface DealTicketManagerProps {
|
export interface DealTicketManagerProps {
|
||||||
market: DealTicketQuery_market;
|
market: DealTicketQuery_market;
|
||||||
@ -15,28 +16,15 @@ export const DealTicketManager = ({
|
|||||||
market,
|
market,
|
||||||
children,
|
children,
|
||||||
}: DealTicketManagerProps) => {
|
}: DealTicketManagerProps) => {
|
||||||
const [orderDialogOpen, setOrderDialogOpen] = useState(false);
|
const { submit, transaction, finalizedOrder, TransactionDialog } =
|
||||||
const { submit, transaction, finalizedOrder, reset } = useOrderSubmit(market);
|
useOrderSubmit(market);
|
||||||
const getDialogTitle = (status?: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case OrderStatus.Active:
|
|
||||||
return 'Order submitted';
|
|
||||||
case OrderStatus.Filled:
|
|
||||||
return 'Order filled';
|
|
||||||
case OrderStatus.PartiallyFilled:
|
|
||||||
return 'Order partially filled';
|
|
||||||
case OrderStatus.Parked:
|
|
||||||
return 'Order parked';
|
|
||||||
default:
|
|
||||||
return 'Submission failed';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{children || (
|
{children || (
|
||||||
<DealTicket
|
<DealTicket
|
||||||
market={market}
|
market={market}
|
||||||
submit={submit}
|
submit={(order) => submit(order)}
|
||||||
transactionStatus={
|
transactionStatus={
|
||||||
transaction.status === VegaTxStatus.Requested ||
|
transaction.status === VegaTxStatus.Requested ||
|
||||||
transaction.status === VegaTxStatus.Pending
|
transaction.status === VegaTxStatus.Pending
|
||||||
@ -45,15 +33,68 @@ export const DealTicketManager = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<VegaTransactionDialog
|
<TransactionDialog
|
||||||
key={`submit-order-dialog-${transaction.txHash}`}
|
|
||||||
orderDialogOpen={orderDialogOpen}
|
|
||||||
setOrderDialogOpen={setOrderDialogOpen}
|
|
||||||
finalizedOrder={finalizedOrder}
|
|
||||||
transaction={transaction}
|
|
||||||
reset={reset}
|
|
||||||
title={getDialogTitle(finalizedOrder?.status)}
|
title={getDialogTitle(finalizedOrder?.status)}
|
||||||
/>
|
intent={getDialogIntent(finalizedOrder?.status)}
|
||||||
|
icon={getDialogIcon(finalizedOrder?.status)}
|
||||||
|
>
|
||||||
|
<OrderFeedback transaction={transaction} order={finalizedOrder} />
|
||||||
|
</TransactionDialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDialogTitle = (status?: OrderStatus): string | undefined => {
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case OrderStatus.Active:
|
||||||
|
return t('Order submitted');
|
||||||
|
case OrderStatus.Filled:
|
||||||
|
return t('Order filled');
|
||||||
|
case OrderStatus.PartiallyFilled:
|
||||||
|
return t('Order partially filled');
|
||||||
|
case OrderStatus.Parked:
|
||||||
|
return t('Order parked');
|
||||||
|
default:
|
||||||
|
return t('Submission failed');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDialogIntent = (status?: OrderStatus): Intent | undefined => {
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case OrderStatus.Parked:
|
||||||
|
case OrderStatus.Expired:
|
||||||
|
return Intent.Warning;
|
||||||
|
case OrderStatus.Rejected:
|
||||||
|
case OrderStatus.Stopped:
|
||||||
|
case OrderStatus.Cancelled:
|
||||||
|
return Intent.Danger;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDialogIcon = (status?: OrderStatus): ReactNode | undefined => {
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case OrderStatus.Parked:
|
||||||
|
case OrderStatus.Expired:
|
||||||
|
return <Icon name="warning-sign" size={20} />;
|
||||||
|
case OrderStatus.Rejected:
|
||||||
|
case OrderStatus.Stopped:
|
||||||
|
case OrderStatus.Cancelled:
|
||||||
|
return <Icon name="error" size={20} />;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
export * from './order-data-provider';
|
export * from './order-data-provider';
|
||||||
|
export * from './order-feedback';
|
||||||
export * from './order-list';
|
export * from './order-list';
|
||||||
export * from './order-list-manager';
|
export * from './order-list-manager';
|
||||||
export * from './order-list-container';
|
export * from './order-list-container';
|
||||||
|
export * from './mocks/generate-orders';
|
||||||
|
@ -6,42 +6,42 @@ import {
|
|||||||
Side,
|
Side,
|
||||||
} from '@vegaprotocol/types';
|
} from '@vegaprotocol/types';
|
||||||
import type { Orders_party_ordersConnection_edges_node } from '../';
|
import type { Orders_party_ordersConnection_edges_node } from '../';
|
||||||
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
|
||||||
export const generateOrder = (
|
export const generateOrder = (
|
||||||
partialOrder: Partial<Orders_party_ordersConnection_edges_node>
|
partialOrder?: PartialDeep<Orders_party_ordersConnection_edges_node>
|
||||||
) =>
|
) => {
|
||||||
merge(
|
const order: Orders_party_ordersConnection_edges_node = {
|
||||||
{
|
__typename: 'Order',
|
||||||
__typename: 'Order',
|
id: 'order-id2',
|
||||||
id: 'order-id2',
|
market: {
|
||||||
market: {
|
__typename: 'Market',
|
||||||
__typename: 'Market',
|
id: 'market-id',
|
||||||
id: 'market-id',
|
name: 'market-name',
|
||||||
name: 'market-name',
|
decimalPlaces: 2,
|
||||||
decimalPlaces: 2,
|
positionDecimalPlaces: 2,
|
||||||
positionDecimalPlaces: 2,
|
tradableInstrument: {
|
||||||
tradableInstrument: {
|
__typename: 'TradableInstrument',
|
||||||
__typename: 'TradableInstrument',
|
instrument: {
|
||||||
instrument: {
|
__typename: 'Instrument',
|
||||||
__typename: 'Instrument',
|
code: 'instrument-code',
|
||||||
code: 'instrument-code',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
size: '10',
|
},
|
||||||
type: OrderType.Market,
|
size: '10',
|
||||||
status: OrderStatus.Active,
|
type: OrderType.Market,
|
||||||
side: Side.Buy,
|
status: OrderStatus.Active,
|
||||||
remaining: '5',
|
side: Side.Buy,
|
||||||
price: '',
|
remaining: '5',
|
||||||
timeInForce: OrderTimeInForce.IOC,
|
price: '',
|
||||||
createdAt: new Date().toISOString(),
|
timeInForce: OrderTimeInForce.IOC,
|
||||||
updatedAt: null,
|
createdAt: new Date().toISOString(),
|
||||||
expiresAt: null,
|
updatedAt: null,
|
||||||
rejectionReason: null,
|
expiresAt: null,
|
||||||
} as Orders_party_ordersConnection_edges_node,
|
rejectionReason: null,
|
||||||
partialOrder
|
};
|
||||||
);
|
return merge(order, partialOrder);
|
||||||
|
};
|
||||||
|
|
||||||
export const limitOrder = generateOrder({
|
export const limitOrder = generateOrder({
|
||||||
id: 'limit-order',
|
id: 'limit-order',
|
||||||
|
1
libs/orders/src/lib/components/order-feedback/index.ts
Normal file
1
libs/orders/src/lib/components/order-feedback/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './order-feedback';
|
@ -0,0 +1,87 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { formatLabel } from '@vegaprotocol/react-helpers';
|
||||||
|
import {
|
||||||
|
OrderRejectionReason,
|
||||||
|
OrderStatus,
|
||||||
|
OrderType,
|
||||||
|
Side,
|
||||||
|
} from '@vegaprotocol/types';
|
||||||
|
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||||
|
import { generateOrder } from '../mocks/generate-orders';
|
||||||
|
import type { OrderFeedbackProps } from './order-feedback';
|
||||||
|
import { OrderFeedback } from './order-feedback';
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/environment', () => ({
|
||||||
|
useEnvironment: () => ({
|
||||||
|
VEGA_EXPLORER_URL: 'https://test.explorer.vega.network',
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('OrderFeedback', () => {
|
||||||
|
let props: OrderFeedbackProps;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
props = {
|
||||||
|
transaction: {
|
||||||
|
status: VegaTxStatus.Complete,
|
||||||
|
error: null,
|
||||||
|
txHash: 'tx-hash',
|
||||||
|
signature: null,
|
||||||
|
},
|
||||||
|
order: null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders null if no order provided', () => {
|
||||||
|
const { container } = render(<OrderFeedback {...props} />);
|
||||||
|
expect(container).toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders error reason', () => {
|
||||||
|
const orderFields = {
|
||||||
|
status: OrderStatus.Rejected,
|
||||||
|
rejectionReason: OrderRejectionReason.OrderAmendFailure,
|
||||||
|
};
|
||||||
|
const order = generateOrder(orderFields);
|
||||||
|
render(<OrderFeedback {...props} order={order} />);
|
||||||
|
expect(screen.getByTestId('error-reason')).toHaveTextContent(
|
||||||
|
`Reason: ${formatLabel(orderFields.rejectionReason)}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render order details when order is placed successfully', () => {
|
||||||
|
const order = generateOrder({
|
||||||
|
type: OrderType.Limit,
|
||||||
|
price: '100',
|
||||||
|
size: '200',
|
||||||
|
side: Side.Buy,
|
||||||
|
market: {
|
||||||
|
decimalPlaces: 2,
|
||||||
|
positionDecimalPlaces: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
render(<OrderFeedback {...props} order={order} />);
|
||||||
|
expect(screen.getByTestId('order-confirmed')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('tx-block-explorer')).toHaveTextContent(
|
||||||
|
// eslint-disable-next-line
|
||||||
|
props.transaction.txHash!
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('tx-block-explorer')).toHaveTextContent(
|
||||||
|
// eslint-disable-next-line
|
||||||
|
props.transaction.txHash!
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Market').nextElementSibling).toHaveTextContent(
|
||||||
|
// eslint-disable-next-line
|
||||||
|
order.market!.name
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Status').nextElementSibling).toHaveTextContent(
|
||||||
|
order.status
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Price').nextElementSibling).toHaveTextContent(
|
||||||
|
'1.00'
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Amount').nextElementSibling).toHaveTextContent(
|
||||||
|
`+ 200`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
116
libs/orders/src/lib/components/order-feedback/order-feedback.tsx
Normal file
116
libs/orders/src/lib/components/order-feedback/order-feedback.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
|
import type { OrderEvent_busEvents_event_Order } from '../../order-hooks/__generated__';
|
||||||
|
import {
|
||||||
|
addDecimalsFormatNumber,
|
||||||
|
formatLabel,
|
||||||
|
t,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
|
import { OrderStatus, OrderType, Side } from '@vegaprotocol/types';
|
||||||
|
import type { VegaTxState } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
|
export interface OrderFeedbackProps {
|
||||||
|
transaction: VegaTxState;
|
||||||
|
order: OrderEvent_busEvents_event_Order | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrderFeedback = ({ transaction, order }: OrderFeedbackProps) => {
|
||||||
|
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||||
|
const labelClass = 'font-bold';
|
||||||
|
if (!order) return null;
|
||||||
|
|
||||||
|
// Order on network but was rejected
|
||||||
|
if (order.status === OrderStatus.Rejected) {
|
||||||
|
return (
|
||||||
|
<p data-testid="error-reason">
|
||||||
|
{order.rejectionReason &&
|
||||||
|
t(`Reason: ${formatLabel(order.rejectionReason)}`)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.status === OrderStatus.Cancelled) {
|
||||||
|
return (
|
||||||
|
<div data-testid="order-confirmed">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
|
||||||
|
{order.market && (
|
||||||
|
<div>
|
||||||
|
<p className={labelClass}>{t(`Market`)}</p>
|
||||||
|
<p>{t(`${order.market.name}`)}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{transaction.txHash && (
|
||||||
|
<div>
|
||||||
|
<p className={labelClass}>{t('Transaction')}</p>
|
||||||
|
<a
|
||||||
|
className="underline break-words"
|
||||||
|
data-testid="tx-block-explorer"
|
||||||
|
href={`${VEGA_EXPLORER_URL}/txs/0x${transaction.txHash}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{transaction.txHash}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid="order-confirmed">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
|
||||||
|
{order.market && (
|
||||||
|
<div>
|
||||||
|
<p className={labelClass}>{t(`Market`)}</p>
|
||||||
|
<p>{t(`${order.market.name}`)}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<p className={labelClass}>{t(`Status`)}</p>
|
||||||
|
<p>{t(`${order.status}`)}</p>
|
||||||
|
</div>
|
||||||
|
{order.type === OrderType.Limit && order.market && (
|
||||||
|
<div>
|
||||||
|
<p className={labelClass}>{t(`Price`)}</p>
|
||||||
|
<p>
|
||||||
|
{addDecimalsFormatNumber(order.price, order.market.decimalPlaces)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<p className={labelClass}>{t(`Amount`)}</p>
|
||||||
|
<p
|
||||||
|
className={
|
||||||
|
order.side === Side.Buy ? 'text-vega-green' : 'text-vega-red'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{`${order.side === Side.Buy ? '+' : '-'} ${addDecimalsFormatNumber(
|
||||||
|
order.size,
|
||||||
|
order.market?.positionDecimalPlaces ?? 0
|
||||||
|
)}
|
||||||
|
`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{transaction.txHash && (
|
||||||
|
<div>
|
||||||
|
<p className={labelClass}>{t('Transaction')}</p>
|
||||||
|
<a
|
||||||
|
className="underline break-words"
|
||||||
|
data-testid="tx-block-explorer"
|
||||||
|
href={`${VEGA_EXPLORER_URL}/txs/0x${transaction.txHash}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{transaction.txHash}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -4,16 +4,22 @@ import {
|
|||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { OrderType } from '@vegaprotocol/types';
|
import { OrderType } from '@vegaprotocol/types';
|
||||||
import { FormGroup, Input, InputError, Button } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
FormGroup,
|
||||||
|
Input,
|
||||||
|
InputError,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
Icon,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import Icon from 'react-syntax-highlighter';
|
import type { OrderFields } from '../order-data-provider';
|
||||||
import { OrderDialogWrapper } from '@vegaprotocol/wallet';
|
|
||||||
import type { Order } from '@vegaprotocol/wallet';
|
|
||||||
|
|
||||||
interface OrderEditDialogProps {
|
interface OrderEditDialogProps {
|
||||||
title: string;
|
isOpen: boolean;
|
||||||
order: Order | null;
|
onChange: (isOpen: boolean) => void;
|
||||||
edit: (body: Order) => Promise<unknown>;
|
order: OrderFields | null;
|
||||||
|
onSubmit: (fields: FormFields) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormFields {
|
interface FormFields {
|
||||||
@ -21,9 +27,10 @@ interface FormFields {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const OrderEditDialog = ({
|
export const OrderEditDialog = ({
|
||||||
|
isOpen,
|
||||||
|
onChange,
|
||||||
order,
|
order,
|
||||||
title,
|
onSubmit,
|
||||||
edit,
|
|
||||||
}: OrderEditDialogProps) => {
|
}: OrderEditDialogProps) => {
|
||||||
const headerClassName = 'text-h5 font-bold text-black dark:text-white';
|
const headerClassName = 'text-h5 font-bold text-black dark:text-white';
|
||||||
const {
|
const {
|
||||||
@ -37,9 +44,16 @@ export const OrderEditDialog = ({
|
|||||||
: '',
|
: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!order) return null;
|
if (!order) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrderDialogWrapper title={title} icon={<Icon name="hand-up" size={20} />}>
|
<Dialog
|
||||||
|
open={isOpen}
|
||||||
|
onChange={onChange}
|
||||||
|
title={t('Edit order')}
|
||||||
|
icon={<Icon name="edit" />}
|
||||||
|
>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
{order.market && (
|
{order.market && (
|
||||||
<div>
|
<div>
|
||||||
@ -49,7 +63,7 @@ export const OrderEditDialog = ({
|
|||||||
)}
|
)}
|
||||||
{order.type === OrderType.Limit && order.market && (
|
{order.type === OrderType.Limit && order.market && (
|
||||||
<div>
|
<div>
|
||||||
<p className={headerClassName}>{t(`Last price`)}</p>
|
<p className={headerClassName}>{t(`Current price`)}</p>
|
||||||
<p>
|
<p>
|
||||||
{addDecimalsFormatNumber(order.price, order.market.decimalPlaces)}
|
{addDecimalsFormatNumber(order.price, order.market.decimalPlaces)}
|
||||||
</p>
|
</p>
|
||||||
@ -71,15 +85,7 @@ export const OrderEditDialog = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 py-12">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 py-12">
|
||||||
<form
|
<form onSubmit={handleSubmit(onSubmit)} data-testid="edit-order">
|
||||||
onSubmit={handleSubmit(async (data) => {
|
|
||||||
await edit({
|
|
||||||
...order,
|
|
||||||
price: data.entryPrice,
|
|
||||||
});
|
|
||||||
})}
|
|
||||||
data-testid="edit-order"
|
|
||||||
>
|
|
||||||
<FormGroup label={t('Entry price')} labelFor="entryPrice">
|
<FormGroup label={t('Entry price')} labelFor="entryPrice">
|
||||||
<Input
|
<Input
|
||||||
{...register('entryPrice', { required: t('Required') })}
|
{...register('entryPrice', { required: t('Required') })}
|
||||||
@ -97,6 +103,6 @@ export const OrderEditDialog = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</OrderDialogWrapper>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import type { Story, Meta } from '@storybook/react';
|
import type { Story, Meta } from '@storybook/react';
|
||||||
import { OrderType, OrderStatus, OrderTimeInForce } from '@vegaprotocol/types';
|
|
||||||
import { OrderList, OrderListTable } from './order-list';
|
import { OrderList, OrderListTable } from './order-list';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { Order, VegaTxState } from '@vegaprotocol/wallet';
|
import type { VegaTxState } from '@vegaprotocol/wallet';
|
||||||
import { VegaTransactionDialog, VegaTxStatus } from '@vegaprotocol/wallet';
|
import { VegaTransactionDialog, VegaTxStatus } from '@vegaprotocol/wallet';
|
||||||
import { generateOrdersArray } from '../mocks';
|
import { generateOrdersArray } from '../mocks';
|
||||||
|
import { OrderEditDialog } from './order-edit-dialog';
|
||||||
|
import type { OrderFields } from '../order-data-provider';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
component: OrderList,
|
component: OrderList,
|
||||||
@ -18,9 +19,6 @@ const Template: Story = (args) => {
|
|||||||
<OrderListTable
|
<OrderListTable
|
||||||
rowData={args.data}
|
rowData={args.data}
|
||||||
cancel={cancel}
|
cancel={cancel}
|
||||||
setEditOrderDialogOpen={() => {
|
|
||||||
return;
|
|
||||||
}}
|
|
||||||
setEditOrder={() => {
|
setEditOrder={() => {
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
@ -31,47 +29,43 @@ const Template: Story = (args) => {
|
|||||||
|
|
||||||
const Template2: Story = (args) => {
|
const Template2: Story = (args) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [editOrder, setEditOrder] = useState<OrderFields | null>(null);
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
setOpen(!open);
|
setOpen(!open);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
const transaction: VegaTxState = {
|
const transaction: VegaTxState = {
|
||||||
status: VegaTxStatus.Default,
|
status: VegaTxStatus.Requested,
|
||||||
error: null,
|
error: null,
|
||||||
txHash: null,
|
txHash: null,
|
||||||
signature: null,
|
signature: null,
|
||||||
|
dialogOpen: false,
|
||||||
};
|
};
|
||||||
const finalizedOrder: Order = {
|
|
||||||
status: OrderStatus.Cancelled,
|
|
||||||
rejectionReason: null,
|
|
||||||
size: '10',
|
|
||||||
price: '1000',
|
|
||||||
market: { name: 'ETH/DAI (30 Jun 2022)', decimalPlaces: 5 },
|
|
||||||
type: OrderType.Limit,
|
|
||||||
timeInForce: OrderTimeInForce.GTC,
|
|
||||||
};
|
|
||||||
const reset = () => null;
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ height: 1000 }}>
|
<div style={{ height: 1000 }}>
|
||||||
<OrderListTable
|
<OrderListTable
|
||||||
rowData={args.data}
|
rowData={args.data}
|
||||||
cancel={cancel}
|
cancel={cancel}
|
||||||
setEditOrderDialogOpen={() => {
|
setEditOrder={(order) => {
|
||||||
return;
|
setEditOrder(order);
|
||||||
}}
|
|
||||||
setEditOrder={() => {
|
|
||||||
return;
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<VegaTransactionDialog
|
<VegaTransactionDialog
|
||||||
orderDialogOpen={open}
|
isOpen={open}
|
||||||
setOrderDialogOpen={setOpen}
|
onChange={setOpen}
|
||||||
finalizedOrder={finalizedOrder}
|
|
||||||
transaction={transaction}
|
transaction={transaction}
|
||||||
reset={reset}
|
/>
|
||||||
title={'Order cancelled'}
|
<OrderEditDialog
|
||||||
|
isOpen={Boolean(editOrder)}
|
||||||
|
onChange={(isOpen) => {
|
||||||
|
if (!isOpen) setEditOrder(null);
|
||||||
|
}}
|
||||||
|
order={editOrder}
|
||||||
|
onSubmit={(fields) => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,11 @@ import {
|
|||||||
getDateTimeFormat,
|
getDateTimeFormat,
|
||||||
t,
|
t,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { AgGridDynamic as AgGrid, Button } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
AgGridDynamic as AgGrid,
|
||||||
|
Button,
|
||||||
|
Intent,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import type {
|
import type {
|
||||||
ICellRendererParams,
|
ICellRendererParams,
|
||||||
ValueFormatterParams,
|
ValueFormatterParams,
|
||||||
@ -21,83 +25,61 @@ import type { Orders_party_ordersConnection_edges_node } from '../';
|
|||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
import { useOrderCancel } from '../../order-hooks/use-order-cancel';
|
import { useOrderCancel } from '../../order-hooks/use-order-cancel';
|
||||||
import { VegaTransactionDialog } from '@vegaprotocol/wallet';
|
|
||||||
import { useOrderEdit } from '../../order-hooks/use-order-edit';
|
import { useOrderEdit } from '../../order-hooks/use-order-edit';
|
||||||
import { OrderEditDialog } from './order-edit-dialog';
|
import { OrderEditDialog } from './order-edit-dialog';
|
||||||
|
import type { OrderFields } from '../order-data-provider/__generated__';
|
||||||
|
import { OrderFeedback } from '../order-feedback';
|
||||||
|
|
||||||
type OrderListProps = AgGridReactProps | AgReactUiProps;
|
type OrderListProps = AgGridReactProps | AgReactUiProps;
|
||||||
|
|
||||||
export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const [cancelOrderDialogOpen, setCancelOrderDialogOpen] = useState(false);
|
const [editOrder, setEditOrder] = useState<OrderFields | null>(null);
|
||||||
const [editOrderDialogOpen, setEditOrderDialogOpen] = useState(false);
|
const orderCancel = useOrderCancel();
|
||||||
const [editOrder, setEditOrder] =
|
const orderEdit = useOrderEdit(editOrder);
|
||||||
useState<Orders_party_ordersConnection_edges_node | null>(null);
|
|
||||||
|
|
||||||
const { transaction, updatedOrder, reset, cancel } = useOrderCancel();
|
|
||||||
const {
|
|
||||||
transaction: editTransaction,
|
|
||||||
updatedOrder: editedOrder,
|
|
||||||
reset: resetEdit,
|
|
||||||
edit,
|
|
||||||
} = useOrderEdit();
|
|
||||||
const getCancelDialogTitle = (status?: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case OrderStatus.Cancelled:
|
|
||||||
return 'Order cancelled';
|
|
||||||
case OrderStatus.Rejected:
|
|
||||||
return 'Order rejected';
|
|
||||||
case OrderStatus.Expired:
|
|
||||||
return 'Order expired';
|
|
||||||
default:
|
|
||||||
return 'Cancellation failed';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const getEditDialogTitle = () =>
|
|
||||||
editedOrder
|
|
||||||
? t(
|
|
||||||
`Order ${
|
|
||||||
editOrder?.market?.tradableInstrument.instrument.code ?? ''
|
|
||||||
} updated`
|
|
||||||
)
|
|
||||||
: t(
|
|
||||||
`Edit ${
|
|
||||||
editOrder?.market?.tradableInstrument.instrument.code ?? ''
|
|
||||||
} order`
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<OrderListTable
|
<OrderListTable
|
||||||
{...props}
|
{...props}
|
||||||
cancel={cancel}
|
cancel={(order) => {
|
||||||
|
if (!order.market) return;
|
||||||
|
orderCancel.cancel({
|
||||||
|
orderId: order.id,
|
||||||
|
marketId: order.market.id,
|
||||||
|
});
|
||||||
|
}}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
setEditOrderDialogOpen={setEditOrderDialogOpen}
|
|
||||||
setEditOrder={setEditOrder}
|
setEditOrder={setEditOrder}
|
||||||
/>
|
/>
|
||||||
<VegaTransactionDialog
|
<orderCancel.TransactionDialog
|
||||||
key={`cancel-order-dialog-${transaction.txHash}`}
|
title={getCancelDialogTitle(orderCancel.cancelledOrder?.status)}
|
||||||
orderDialogOpen={cancelOrderDialogOpen}
|
intent={getCancelDialogIntent(orderCancel.cancelledOrder?.status)}
|
||||||
setOrderDialogOpen={setCancelOrderDialogOpen}
|
|
||||||
transaction={transaction}
|
|
||||||
reset={reset}
|
|
||||||
title={getCancelDialogTitle(updatedOrder?.status)}
|
|
||||||
finalizedOrder={updatedOrder}
|
|
||||||
/>
|
|
||||||
<VegaTransactionDialog
|
|
||||||
key={`edit-order-dialog-${transaction.txHash}`}
|
|
||||||
orderDialogOpen={editOrderDialogOpen}
|
|
||||||
setOrderDialogOpen={setEditOrderDialogOpen}
|
|
||||||
transaction={editTransaction}
|
|
||||||
reset={resetEdit}
|
|
||||||
title={getEditDialogTitle()}
|
|
||||||
finalizedOrder={editedOrder}
|
|
||||||
>
|
>
|
||||||
<OrderEditDialog
|
<OrderFeedback
|
||||||
title={getEditDialogTitle()}
|
transaction={orderCancel.transaction}
|
||||||
order={editOrder}
|
order={orderCancel.cancelledOrder}
|
||||||
edit={edit}
|
|
||||||
/>
|
/>
|
||||||
</VegaTransactionDialog>
|
</orderCancel.TransactionDialog>
|
||||||
|
<orderEdit.TransactionDialog
|
||||||
|
title={getEditDialogTitle(orderEdit.updatedOrder?.status)}
|
||||||
|
>
|
||||||
|
<OrderFeedback
|
||||||
|
transaction={orderEdit.transaction}
|
||||||
|
order={orderEdit.updatedOrder}
|
||||||
|
/>
|
||||||
|
</orderEdit.TransactionDialog>
|
||||||
|
<OrderEditDialog
|
||||||
|
isOpen={Boolean(editOrder)}
|
||||||
|
onChange={(isOpen) => {
|
||||||
|
if (!isOpen) setEditOrder(null);
|
||||||
|
}}
|
||||||
|
order={editOrder}
|
||||||
|
onSubmit={(fields) => {
|
||||||
|
setEditOrder(null);
|
||||||
|
orderEdit.edit({ price: fields.entryPrice });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -111,15 +93,12 @@ type OrderListTableValueFormatterParams = Omit<
|
|||||||
};
|
};
|
||||||
|
|
||||||
type OrderListTableProps = (AgGridReactProps | AgReactUiProps) & {
|
type OrderListTableProps = (AgGridReactProps | AgReactUiProps) & {
|
||||||
cancel: (body?: unknown) => Promise<unknown>;
|
cancel: (order: OrderFields) => void;
|
||||||
setEditOrderDialogOpen: (value: boolean) => void;
|
setEditOrder: (order: OrderFields) => void;
|
||||||
setEditOrder: (
|
|
||||||
order: Orders_party_ordersConnection_edges_node | null
|
|
||||||
) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||||
({ cancel, setEditOrderDialogOpen, setEditOrder, ...props }, ref) => {
|
({ cancel, setEditOrder, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -260,54 +239,36 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
|||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
field="edit"
|
field="edit"
|
||||||
cellRenderer={({ data }: ICellRendererParams) => {
|
cellRenderer={({ data }: ICellRendererParams) => {
|
||||||
if (
|
if (!data) return null;
|
||||||
![
|
if (isOrderActive(data.status)) {
|
||||||
OrderStatus.Cancelled,
|
|
||||||
OrderStatus.Rejected,
|
|
||||||
OrderStatus.Expired,
|
|
||||||
OrderStatus.Filled,
|
|
||||||
OrderStatus.Stopped,
|
|
||||||
].includes(data.status)
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
data-testid="edit"
|
data-testid="edit"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditOrderDialogOpen(true);
|
|
||||||
setEditOrder(data);
|
setEditOrder(data);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit
|
{t('Edit')}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
field="cancel"
|
field="cancel"
|
||||||
cellRenderer={({ data }: ICellRendererParams) => {
|
cellRenderer={({ data }: ICellRendererParams) => {
|
||||||
if (
|
if (!data) return null;
|
||||||
![
|
if (isOrderActive(data.status)) {
|
||||||
OrderStatus.Cancelled,
|
|
||||||
OrderStatus.Rejected,
|
|
||||||
OrderStatus.Expired,
|
|
||||||
OrderStatus.Filled,
|
|
||||||
OrderStatus.Stopped,
|
|
||||||
].includes(data.status)
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button data-testid="cancel" onClick={() => cancel(data)}>
|
||||||
data-testid="cancel"
|
|
||||||
onClick={async () => {
|
|
||||||
await cancel(data);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -315,3 +276,61 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an order is active to determine if it can be edited or cancelled
|
||||||
|
*/
|
||||||
|
const isOrderActive = (status: OrderStatus) => {
|
||||||
|
return ![
|
||||||
|
OrderStatus.Cancelled,
|
||||||
|
OrderStatus.Rejected,
|
||||||
|
OrderStatus.Expired,
|
||||||
|
OrderStatus.Filled,
|
||||||
|
OrderStatus.Stopped,
|
||||||
|
].includes(status);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEditDialogTitle = (status?: OrderStatus): string | undefined => {
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case OrderStatus.Active:
|
||||||
|
return t('Order updated');
|
||||||
|
case OrderStatus.Filled:
|
||||||
|
return t('Order filled');
|
||||||
|
case OrderStatus.PartiallyFilled:
|
||||||
|
return t('Order partially filled');
|
||||||
|
case OrderStatus.Parked:
|
||||||
|
return t('Order parked');
|
||||||
|
default:
|
||||||
|
return t('Submission failed');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCancelDialogIntent = (status?: OrderStatus): Intent | undefined => {
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case OrderStatus.Cancelled:
|
||||||
|
return Intent.Success;
|
||||||
|
default:
|
||||||
|
return Intent.Danger;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCancelDialogTitle = (status?: OrderStatus): string | undefined => {
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case OrderStatus.Cancelled:
|
||||||
|
return t('Order cancelled');
|
||||||
|
default:
|
||||||
|
return t('Order cancellation failed');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export * from './components';
|
export * from './components';
|
||||||
export * from './order-hooks';
|
export * from './order-hooks';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
export * from './market';
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import type { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
export interface Market {
|
|
||||||
__typename?: string;
|
|
||||||
id: string;
|
|
||||||
positionDecimalPlaces: number;
|
|
||||||
state: MarketState;
|
|
||||||
decimalPlaces: number;
|
|
||||||
tradingMode: MarketTradingMode;
|
|
||||||
tradableInstrument?: any;
|
|
||||||
depth?: any;
|
|
||||||
}
|
|
@ -40,6 +40,12 @@ export interface OrderEvent_busEvents_event_Order_market {
|
|||||||
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
|
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
|
||||||
*/
|
*/
|
||||||
decimalPlaces: number;
|
decimalPlaces: number;
|
||||||
|
/**
|
||||||
|
* positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
|
||||||
|
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
|
||||||
|
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
|
||||||
|
*/
|
||||||
|
positionDecimalPlaces: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderEvent_busEvents_event_Order {
|
export interface OrderEvent_busEvents_event_Order {
|
||||||
@ -76,6 +82,10 @@ export interface OrderEvent_busEvents_event_Order {
|
|||||||
* The timeInForce of order (determines how and if it executes, and whether it persists on the book)
|
* The timeInForce of order (determines how and if it executes, and whether it persists on the book)
|
||||||
*/
|
*/
|
||||||
timeInForce: OrderTimeInForce;
|
timeInForce: OrderTimeInForce;
|
||||||
|
/**
|
||||||
|
* Expiration time of this order (ISO-8601 RFC3339+Nano formatted date)
|
||||||
|
*/
|
||||||
|
expiresAt: string | null;
|
||||||
/**
|
/**
|
||||||
* Whether the order is to buy or sell
|
* Whether the order is to buy or sell
|
||||||
*/
|
*/
|
||||||
|
@ -20,6 +20,7 @@ export const ORDER_EVENT_SUB = gql`
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
decimalPlaces
|
decimalPlaces
|
||||||
|
positionDecimalPlaces
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import type { MockedResponse } from '@apollo/client/testing';
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import { act, renderHook } from '@testing-library/react-hooks';
|
import { act, renderHook } from '@testing-library/react-hooks';
|
||||||
import { MarketState, MarketTradingMode, OrderType } from '@vegaprotocol/types';
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
|
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
import type {
|
import type {
|
||||||
@ -15,33 +14,6 @@ import type {
|
|||||||
} from './__generated__/OrderEvent';
|
} from './__generated__/OrderEvent';
|
||||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
import { ORDER_EVENT_SUB } from './order-event-query';
|
||||||
|
|
||||||
const defaultMarket = {
|
|
||||||
__typename: 'Market',
|
|
||||||
id: 'market-id',
|
|
||||||
decimalPlaces: 2,
|
|
||||||
positionDecimalPlaces: 1,
|
|
||||||
tradingMode: MarketTradingMode.Continuous,
|
|
||||||
state: MarketState.Active,
|
|
||||||
name: 'market-name',
|
|
||||||
tradableInstrument: {
|
|
||||||
__typename: 'TradableInstrument',
|
|
||||||
instrument: {
|
|
||||||
__typename: 'Instrument',
|
|
||||||
product: {
|
|
||||||
__typename: 'Future',
|
|
||||||
quoteName: 'quote-name',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
depth: {
|
|
||||||
__typename: 'MarketDepth',
|
|
||||||
lastTrade: {
|
|
||||||
__typename: 'Trade',
|
|
||||||
price: '100',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultWalletContext = {
|
const defaultWalletContext = {
|
||||||
keypair: null,
|
keypair: null,
|
||||||
keypairs: [],
|
keypairs: [],
|
||||||
@ -147,23 +119,15 @@ describe('useOrderCancel', () => {
|
|||||||
expect(result.current.transaction.error).toEqual(null);
|
expect(result.current.transaction.error).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not sendTx if no keypair', async () => {
|
it('should not sendTx if no keypair', () => {
|
||||||
const mockSendTx = jest.fn();
|
const mockSendTx = jest.fn();
|
||||||
const order = {
|
|
||||||
type: OrderType.Market,
|
|
||||||
size: '10',
|
|
||||||
price: '1234567.89',
|
|
||||||
status: '',
|
|
||||||
rejectionReason: null,
|
|
||||||
market: defaultMarket,
|
|
||||||
};
|
|
||||||
const { result } = setup({
|
const { result } = setup({
|
||||||
sendTx: mockSendTx,
|
sendTx: mockSendTx,
|
||||||
keypairs: [],
|
keypairs: [],
|
||||||
keypair: null,
|
keypair: null,
|
||||||
});
|
});
|
||||||
await act(async () => {
|
act(() => {
|
||||||
result.current.cancel(order);
|
result.current.cancel({ orderId: 'order-id', marketId: 'market-id' });
|
||||||
});
|
});
|
||||||
expect(mockSendTx).not.toHaveBeenCalled();
|
expect(mockSendTx).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -173,30 +137,24 @@ describe('useOrderCancel', () => {
|
|||||||
const keypair = {
|
const keypair = {
|
||||||
pub: '0x123',
|
pub: '0x123',
|
||||||
} as VegaKeyExtended;
|
} as VegaKeyExtended;
|
||||||
const order = {
|
|
||||||
type: OrderType.Limit,
|
|
||||||
size: '10',
|
|
||||||
price: '1234567.89',
|
|
||||||
status: '',
|
|
||||||
rejectionReason: null,
|
|
||||||
market: defaultMarket,
|
|
||||||
};
|
|
||||||
const { result } = setup({
|
const { result } = setup({
|
||||||
sendTx: mockSendTx,
|
sendTx: mockSendTx,
|
||||||
keypairs: [keypair],
|
keypairs: [keypair],
|
||||||
keypair,
|
keypair,
|
||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
const args = {
|
||||||
result.current.cancel(order);
|
orderId: 'order-id',
|
||||||
|
marketId: 'market-id',
|
||||||
|
};
|
||||||
|
act(() => {
|
||||||
|
result.current.cancel(args);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockSendTx).toHaveBeenCalledWith({
|
expect(mockSendTx).toHaveBeenCalledWith({
|
||||||
pubKey: keypair.pub,
|
pubKey: keypair.pub,
|
||||||
propagate: true,
|
propagate: true,
|
||||||
orderCancellation: {
|
orderCancellation: args,
|
||||||
marketId: 'market-id',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,116 +1,68 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useVegaWallet, useVegaTransaction } from '@vegaprotocol/wallet';
|
import { useVegaWallet, useVegaTransaction } from '@vegaprotocol/wallet';
|
||||||
import { useApolloClient } from '@apollo/client';
|
import type { OrderEvent_busEvents_event_Order } from './__generated__/OrderEvent';
|
||||||
import type {
|
|
||||||
OrderEvent,
|
|
||||||
OrderEventVariables,
|
|
||||||
OrderEvent_busEvents_event_Order,
|
|
||||||
} from './__generated__/OrderEvent';
|
|
||||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { OrderStatus } from '@vegaprotocol/types';
|
import { useOrderEvent } from './use-order-event';
|
||||||
import { determineId } from '@vegaprotocol/react-helpers';
|
|
||||||
import type { Subscription } from 'zen-observable-ts';
|
interface CancelOrderArgs {
|
||||||
|
orderId: string;
|
||||||
|
marketId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const useOrderCancel = () => {
|
export const useOrderCancel = () => {
|
||||||
const { keypair } = useVegaWallet();
|
const { keypair } = useVegaWallet();
|
||||||
const { send, transaction, reset: resetTransaction } = useVegaTransaction();
|
const waitForOrderEvent = useOrderEvent();
|
||||||
const [updatedOrder, setUpdatedOrder] =
|
|
||||||
useState<OrderEvent_busEvents_event_Order | null>(null);
|
|
||||||
const client = useApolloClient();
|
|
||||||
const subRef = useRef<Subscription | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const [cancelledOrder, setCancelledOrder] =
|
||||||
return () => {
|
useState<OrderEvent_busEvents_event_Order | null>(null);
|
||||||
subRef.current?.unsubscribe();
|
|
||||||
setUpdatedOrder(null);
|
const {
|
||||||
resetTransaction();
|
send,
|
||||||
};
|
transaction,
|
||||||
}, [resetTransaction]);
|
reset: resetTransaction,
|
||||||
|
setComplete,
|
||||||
|
TransactionDialog,
|
||||||
|
} = useVegaTransaction();
|
||||||
|
|
||||||
const reset = useCallback(() => {
|
const reset = useCallback(() => {
|
||||||
resetTransaction();
|
resetTransaction();
|
||||||
setUpdatedOrder(null);
|
setCancelledOrder(null);
|
||||||
subRef.current?.unsubscribe();
|
|
||||||
}, [resetTransaction]);
|
}, [resetTransaction]);
|
||||||
|
|
||||||
const cancel = useCallback(
|
const cancel = useCallback(
|
||||||
async (order) => {
|
async (args: CancelOrderArgs) => {
|
||||||
if (!keypair) {
|
if (!keypair) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
setCancelledOrder(null);
|
||||||
[
|
|
||||||
OrderStatus.Cancelled,
|
|
||||||
OrderStatus.Rejected,
|
|
||||||
OrderStatus.Expired,
|
|
||||||
OrderStatus.Filled,
|
|
||||||
OrderStatus.Stopped,
|
|
||||||
].includes(order.status)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setUpdatedOrder(null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await send({
|
await send({
|
||||||
pubKey: keypair.pub,
|
pubKey: keypair.pub,
|
||||||
propagate: true,
|
propagate: true,
|
||||||
orderCancellation: {
|
orderCancellation: {
|
||||||
orderId: order.id,
|
orderId: args.orderId,
|
||||||
marketId: order.market.id,
|
marketId: args.marketId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res?.signature) {
|
waitForOrderEvent(args.orderId, keypair.pub, (cancelledOrder) => {
|
||||||
const resId = order.id ?? determineId(res.signature);
|
setCancelledOrder(cancelledOrder);
|
||||||
setUpdatedOrder(null);
|
setComplete();
|
||||||
|
});
|
||||||
if (resId) {
|
|
||||||
// Start a subscription looking for the newly created order
|
|
||||||
subRef.current = client
|
|
||||||
.subscribe<OrderEvent, OrderEventVariables>({
|
|
||||||
query: ORDER_EVENT_SUB,
|
|
||||||
variables: { partyId: keypair?.pub || '' },
|
|
||||||
})
|
|
||||||
.subscribe(({ data }) => {
|
|
||||||
if (!data?.busEvents?.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No types available for the subscription result
|
|
||||||
const matchingOrderEvent = data.busEvents.find((e) => {
|
|
||||||
if (e.event.__typename !== 'Order') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.event.id === resId;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
matchingOrderEvent &&
|
|
||||||
matchingOrderEvent.event.__typename === 'Order'
|
|
||||||
) {
|
|
||||||
setUpdatedOrder(matchingOrderEvent.event);
|
|
||||||
subRef.current?.unsubscribe();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Sentry.captureException(e);
|
Sentry.captureException(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[client, keypair, send]
|
[keypair, send, setComplete, waitForOrderEvent]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transaction,
|
transaction,
|
||||||
updatedOrder,
|
cancelledOrder,
|
||||||
|
TransactionDialog,
|
||||||
cancel,
|
cancel,
|
||||||
reset,
|
reset,
|
||||||
};
|
};
|
||||||
|
@ -3,11 +3,7 @@ import type {
|
|||||||
VegaKeyExtended,
|
VegaKeyExtended,
|
||||||
VegaWalletContextShape,
|
VegaWalletContextShape,
|
||||||
} from '@vegaprotocol/wallet';
|
} from '@vegaprotocol/wallet';
|
||||||
import {
|
import { VegaWalletOrderTimeInForce } from '@vegaprotocol/wallet';
|
||||||
VegaWalletOrderSide,
|
|
||||||
VegaWalletOrderTimeInForce,
|
|
||||||
VegaWalletOrderType,
|
|
||||||
} from '@vegaprotocol/wallet';
|
|
||||||
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
|
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useOrderEdit } from './use-order-edit';
|
import { useOrderEdit } from './use-order-edit';
|
||||||
@ -18,15 +14,8 @@ import type {
|
|||||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
import { ORDER_EVENT_SUB } from './order-event-query';
|
||||||
import type { MockedResponse } from '@apollo/client/testing';
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import {
|
import type { OrderFields } from '../components';
|
||||||
MarketTradingMode,
|
import { generateOrder } from '../components';
|
||||||
MarketState,
|
|
||||||
OrderTimeInForce,
|
|
||||||
} from '@vegaprotocol/types';
|
|
||||||
import type {
|
|
||||||
OrderAmendmentBodyOrderAmendment,
|
|
||||||
OrderAmendmentBody,
|
|
||||||
} from '@vegaprotocol/vegawallet-service-api-client';
|
|
||||||
|
|
||||||
const defaultWalletContext = {
|
const defaultWalletContext = {
|
||||||
keypair: null,
|
keypair: null,
|
||||||
@ -38,7 +27,7 @@ const defaultWalletContext = {
|
|||||||
connector: null,
|
connector: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
function setup(context?: Partial<VegaWalletContextShape>) {
|
function setup(order: OrderFields, context?: Partial<VegaWalletContextShape>) {
|
||||||
const mocks: MockedResponse<OrderEvent> = {
|
const mocks: MockedResponse<OrderEvent> = {
|
||||||
request: {
|
request: {
|
||||||
query: ORDER_EVENT_SUB,
|
query: ORDER_EVENT_SUB,
|
||||||
@ -119,86 +108,47 @@ function setup(context?: Partial<VegaWalletContextShape>) {
|
|||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
return renderHook(() => useOrderEdit(), { wrapper });
|
return renderHook(() => useOrderEdit(order), { wrapper });
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultMarket = {
|
|
||||||
__typename: 'Market',
|
|
||||||
id: 'market-id',
|
|
||||||
decimalPlaces: 2,
|
|
||||||
positionDecimalPlaces: 1,
|
|
||||||
tradingMode: MarketTradingMode.Continuous,
|
|
||||||
state: MarketState.Active,
|
|
||||||
tradableInstrument: {
|
|
||||||
__typename: 'TradableInstrument',
|
|
||||||
instrument: {
|
|
||||||
__typename: 'Instrument',
|
|
||||||
product: {
|
|
||||||
__typename: 'Future',
|
|
||||||
quoteName: 'quote-name',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
depth: {
|
|
||||||
__typename: 'MarketDepth',
|
|
||||||
lastTrade: {
|
|
||||||
__typename: 'Trade',
|
|
||||||
price: '100',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const order = {
|
|
||||||
id: 'order-id',
|
|
||||||
type: VegaWalletOrderType.Limit,
|
|
||||||
size: '10',
|
|
||||||
timeInForce: OrderTimeInForce.GTT, // order timeInForce is transformed to wallet timeInForce
|
|
||||||
side: VegaWalletOrderSide.Buy,
|
|
||||||
price: '1234567.89',
|
|
||||||
expiration: new Date('2022-01-01'),
|
|
||||||
expiresAt: new Date('2022-01-01'),
|
|
||||||
status: VegaTxStatus.Pending,
|
|
||||||
rejectionReason: null,
|
|
||||||
market: {
|
|
||||||
id: 'market-id',
|
|
||||||
decimalPlaces: 2,
|
|
||||||
name: 'ETHDAI',
|
|
||||||
positionDecimalPlaces: 2,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('useOrderEdit', () => {
|
describe('useOrderEdit', () => {
|
||||||
it('should edit a correctly formatted order', async () => {
|
it('should edit a correctly formatted order', async () => {
|
||||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve({}));
|
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve({}));
|
||||||
const keypair = {
|
const keypair = {
|
||||||
pub: '0x123',
|
pub: '0x123',
|
||||||
} as VegaKeyExtended;
|
} as VegaKeyExtended;
|
||||||
const { result } = setup({
|
const order = generateOrder({
|
||||||
|
price: '123456789',
|
||||||
|
market: { decimalPlaces: 2 },
|
||||||
|
});
|
||||||
|
const { result } = setup(order, {
|
||||||
sendTx: mockSendTx,
|
sendTx: mockSendTx,
|
||||||
keypairs: [keypair],
|
keypairs: [keypair],
|
||||||
keypair,
|
keypair,
|
||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
act(() => {
|
||||||
result.current.edit(order);
|
result.current.edit({ price: '1234567.89' });
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockSendTx).toHaveBeenCalledWith({
|
expect(mockSendTx).toHaveBeenCalledWith({
|
||||||
pubKey: keypair.pub,
|
pubKey: keypair.pub,
|
||||||
propagate: true,
|
propagate: true,
|
||||||
orderAmendment: {
|
orderAmendment: {
|
||||||
orderId: 'order-id',
|
orderId: order.id,
|
||||||
marketId: defaultMarket.id, // Market provided from hook argument
|
// eslint-disable-next-line
|
||||||
timeInForce: VegaWalletOrderTimeInForce.GTT,
|
marketId: order.market!.id,
|
||||||
|
timeInForce: VegaWalletOrderTimeInForce[order.timeInForce],
|
||||||
price: { value: '123456789' }, // Decimal removed
|
price: { value: '123456789' }, // Decimal removed
|
||||||
sizeDelta: 0,
|
sizeDelta: 0,
|
||||||
expiresAt: { value: order.expiration?.getTime() + '000000' }, // Nanoseconds append
|
expiresAt: undefined,
|
||||||
} as unknown as OrderAmendmentBodyOrderAmendment,
|
},
|
||||||
} as OrderAmendmentBody);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has the correct default state', () => {
|
it('has the correct default state', () => {
|
||||||
const { result } = setup();
|
const order = generateOrder();
|
||||||
|
const { result } = setup(order);
|
||||||
expect(typeof result.current.edit).toEqual('function');
|
expect(typeof result.current.edit).toEqual('function');
|
||||||
expect(typeof result.current.reset).toEqual('function');
|
expect(typeof result.current.reset).toEqual('function');
|
||||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
|
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
|
||||||
@ -207,8 +157,9 @@ describe('useOrderEdit', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not sendTx if no keypair', async () => {
|
it('should not sendTx if no keypair', async () => {
|
||||||
|
const order = generateOrder();
|
||||||
const mockSendTx = jest.fn();
|
const mockSendTx = jest.fn();
|
||||||
const { result } = setup({
|
const { result } = setup(order, {
|
||||||
sendTx: mockSendTx,
|
sendTx: mockSendTx,
|
||||||
keypairs: [],
|
keypairs: [],
|
||||||
keypair: null,
|
keypair: null,
|
||||||
|
@ -1,50 +1,51 @@
|
|||||||
import { useApolloClient } from '@apollo/client';
|
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/react-helpers';
|
||||||
import { determineId, removeDecimal } from '@vegaprotocol/react-helpers';
|
import { useState, useCallback } from 'react';
|
||||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
import {
|
||||||
import type { Order } from '@vegaprotocol/wallet';
|
useVegaTransaction,
|
||||||
import { VegaWalletOrderTimeInForce } from '@vegaprotocol/wallet';
|
useVegaWallet,
|
||||||
import { useVegaTransaction, useVegaWallet } from '@vegaprotocol/wallet';
|
VegaWalletOrderTimeInForce,
|
||||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
} from '@vegaprotocol/wallet';
|
||||||
import type { Subscription } from 'zen-observable-ts';
|
import type { OrderEvent_busEvents_event_Order } from './__generated__';
|
||||||
import type {
|
|
||||||
OrderEvent_busEvents_event_Order,
|
|
||||||
OrderEvent,
|
|
||||||
OrderEventVariables,
|
|
||||||
} from './__generated__';
|
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
|
import type { OrderFields } from '../components';
|
||||||
|
import { useOrderEvent } from './use-order-event';
|
||||||
|
|
||||||
export const useOrderEdit = () => {
|
// Can only edit price for now
|
||||||
|
export interface EditOrderArgs {
|
||||||
|
price: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useOrderEdit = (order: OrderFields | null) => {
|
||||||
const { keypair } = useVegaWallet();
|
const { keypair } = useVegaWallet();
|
||||||
const { send, transaction, reset: resetTransaction } = useVegaTransaction();
|
|
||||||
const [updatedOrder, setUpdatedOrder] =
|
const [updatedOrder, setUpdatedOrder] =
|
||||||
useState<OrderEvent_busEvents_event_Order | null>(null);
|
useState<OrderEvent_busEvents_event_Order | null>(null);
|
||||||
const client = useApolloClient();
|
|
||||||
const subRef = useRef<Subscription | null>(null);
|
const {
|
||||||
|
send,
|
||||||
|
transaction,
|
||||||
|
reset: resetTransaction,
|
||||||
|
setComplete,
|
||||||
|
TransactionDialog,
|
||||||
|
} = useVegaTransaction();
|
||||||
|
|
||||||
|
const waitForOrderEvent = useOrderEvent();
|
||||||
|
|
||||||
const reset = useCallback(() => {
|
const reset = useCallback(() => {
|
||||||
resetTransaction();
|
resetTransaction();
|
||||||
setUpdatedOrder(null);
|
setUpdatedOrder(null);
|
||||||
subRef.current?.unsubscribe();
|
|
||||||
}, [resetTransaction]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
resetTransaction();
|
|
||||||
setUpdatedOrder(null);
|
|
||||||
subRef.current?.unsubscribe();
|
|
||||||
};
|
|
||||||
}, [resetTransaction]);
|
}, [resetTransaction]);
|
||||||
|
|
||||||
const edit = useCallback(
|
const edit = useCallback(
|
||||||
async (order: Order) => {
|
async (args: EditOrderArgs) => {
|
||||||
if (!keypair || !order.market || !order.market.id) {
|
if (!keypair || !order || !order.market) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUpdatedOrder(null);
|
setUpdatedOrder(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await send({
|
await send({
|
||||||
pubKey: keypair.pub,
|
pubKey: keypair.pub,
|
||||||
propagate: true,
|
propagate: true,
|
||||||
orderAmendment: {
|
orderAmendment: {
|
||||||
@ -52,70 +53,35 @@ export const useOrderEdit = () => {
|
|||||||
marketId: order.market.id,
|
marketId: order.market.id,
|
||||||
// @ts-ignore fix me please!
|
// @ts-ignore fix me please!
|
||||||
price: {
|
price: {
|
||||||
value: removeDecimal(order.price, order.market?.decimalPlaces),
|
value: removeDecimal(args.price, order.market.decimalPlaces),
|
||||||
},
|
},
|
||||||
timeInForce: VegaWalletOrderTimeInForce[order.timeInForce],
|
timeInForce: VegaWalletOrderTimeInForce[order.timeInForce],
|
||||||
// @ts-ignore fix me please!
|
// @ts-ignore fix me please!
|
||||||
sizeDelta: 0,
|
sizeDelta: 0,
|
||||||
// @ts-ignore fix me please!
|
|
||||||
expiresAt: order.expiresAt
|
expiresAt: order.expiresAt
|
||||||
? {
|
? {
|
||||||
value:
|
value: toNanoSeconds(new Date(order.expiresAt)), // Wallet expects timestamp in nanoseconds
|
||||||
// Wallet expects timestamp in nanoseconds,
|
|
||||||
// we don't have that level of accuracy so just append 6 zeroes
|
|
||||||
new Date(order.expiresAt).getTime().toString() + '000000',
|
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res?.signature) {
|
waitForOrderEvent(order.id, keypair.pub, (updatedOrder) => {
|
||||||
const resId = order.id ?? determineId(res.signature);
|
setUpdatedOrder(updatedOrder);
|
||||||
setUpdatedOrder(null);
|
setComplete();
|
||||||
|
});
|
||||||
if (resId) {
|
|
||||||
// Start a subscription looking for the newly created order
|
|
||||||
subRef.current = client
|
|
||||||
.subscribe<OrderEvent, OrderEventVariables>({
|
|
||||||
query: ORDER_EVENT_SUB,
|
|
||||||
variables: { partyId: keypair?.pub || '' },
|
|
||||||
})
|
|
||||||
.subscribe(({ data }) => {
|
|
||||||
if (!data?.busEvents?.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No types available for the subscription result
|
|
||||||
const matchingOrderEvent = data.busEvents.find((e) => {
|
|
||||||
if (e.event.__typename !== 'Order') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.event.id === resId;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
matchingOrderEvent &&
|
|
||||||
matchingOrderEvent.event.__typename === 'Order'
|
|
||||||
) {
|
|
||||||
setUpdatedOrder(matchingOrderEvent.event);
|
|
||||||
subRef.current?.unsubscribe();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Sentry.captureException(e);
|
Sentry.captureException(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[client, keypair, send]
|
[keypair, send, order, setComplete, waitForOrderEvent]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transaction,
|
transaction,
|
||||||
updatedOrder,
|
updatedOrder,
|
||||||
|
TransactionDialog,
|
||||||
edit,
|
edit,
|
||||||
reset,
|
reset,
|
||||||
};
|
};
|
||||||
|
59
libs/orders/src/lib/order-hooks/use-order-event.ts
Normal file
59
libs/orders/src/lib/order-hooks/use-order-event.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { useApolloClient } from '@apollo/client';
|
||||||
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
import { ORDER_EVENT_SUB } from './order-event-query';
|
||||||
|
import type {
|
||||||
|
OrderEvent,
|
||||||
|
OrderEventVariables,
|
||||||
|
OrderEvent_busEvents_event_Order,
|
||||||
|
} from './__generated__';
|
||||||
|
import type { Subscription } from 'zen-observable-ts';
|
||||||
|
|
||||||
|
export const useOrderEvent = () => {
|
||||||
|
const client = useApolloClient();
|
||||||
|
const subRef = useRef<Subscription | null>(null);
|
||||||
|
|
||||||
|
const waitForOrderEvent = useCallback(
|
||||||
|
(
|
||||||
|
id: string,
|
||||||
|
partyId: string,
|
||||||
|
callback: (order: OrderEvent_busEvents_event_Order) => void
|
||||||
|
) => {
|
||||||
|
subRef.current = client
|
||||||
|
.subscribe<OrderEvent, OrderEventVariables>({
|
||||||
|
query: ORDER_EVENT_SUB,
|
||||||
|
variables: { partyId },
|
||||||
|
})
|
||||||
|
.subscribe(({ data }) => {
|
||||||
|
if (!data?.busEvents?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No types available for the subscription result
|
||||||
|
const matchingOrderEvent = data.busEvents.find((e) => {
|
||||||
|
if (e.event.__typename !== 'Order') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.event.id === id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
matchingOrderEvent &&
|
||||||
|
matchingOrderEvent.event.__typename === 'Order'
|
||||||
|
) {
|
||||||
|
callback(matchingOrderEvent.event);
|
||||||
|
subRef.current?.unsubscribe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[client]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
subRef.current?.unsubscribe();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return waitForOrderEvent;
|
||||||
|
};
|
@ -21,6 +21,7 @@ import { ORDER_EVENT_SUB } from './order-event-query';
|
|||||||
import type { MockedResponse } from '@apollo/client/testing';
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import type { Market } from '../market';
|
import type { Market } from '../market';
|
||||||
|
import { toNanoSeconds } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
const defaultMarket = {
|
const defaultMarket = {
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
@ -179,7 +180,9 @@ describe('useOrderSubmit', () => {
|
|||||||
side: VegaWalletOrderSide.Buy,
|
side: VegaWalletOrderSide.Buy,
|
||||||
timeInForce: VegaWalletOrderTimeInForce.GTT,
|
timeInForce: VegaWalletOrderTimeInForce.GTT,
|
||||||
price: '123456789', // Decimal removed
|
price: '123456789', // Decimal removed
|
||||||
expiresAt: order.expiration?.getTime() + '000000', // Nanoseconds append
|
expiresAt: order.expiration
|
||||||
|
? toNanoSeconds(order.expiration)
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,39 +1,52 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useApolloClient } from '@apollo/client';
|
import type { OrderEvent_busEvents_event_Order } from './__generated__';
|
||||||
import type { Order } from '../utils/get-default-order';
|
|
||||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
|
||||||
import type {
|
import type {
|
||||||
OrderEvent,
|
VegaWalletOrderTimeInForce,
|
||||||
OrderEventVariables,
|
VegaWalletOrderSide,
|
||||||
OrderEvent_busEvents_event_Order,
|
} from '@vegaprotocol/wallet';
|
||||||
} from './__generated__';
|
|
||||||
import { VegaWalletOrderType, useVegaWallet } from '@vegaprotocol/wallet';
|
import { VegaWalletOrderType, useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { determineId, removeDecimal } from '@vegaprotocol/react-helpers';
|
import {
|
||||||
|
determineId,
|
||||||
|
removeDecimal,
|
||||||
|
toNanoSeconds,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { useVegaTransaction } from '@vegaprotocol/wallet';
|
import { useVegaTransaction } from '@vegaprotocol/wallet';
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import type { Market } from '../market';
|
import { useOrderEvent } from './use-order-event';
|
||||||
import type { Subscription } from 'zen-observable-ts';
|
|
||||||
|
export interface Order {
|
||||||
|
type: VegaWalletOrderType;
|
||||||
|
size: string;
|
||||||
|
side: VegaWalletOrderSide;
|
||||||
|
timeInForce: VegaWalletOrderTimeInForce;
|
||||||
|
price?: string;
|
||||||
|
expiration?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Market {
|
||||||
|
id: string;
|
||||||
|
decimalPlaces: number;
|
||||||
|
positionDecimalPlaces: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const useOrderSubmit = (market: Market) => {
|
export const useOrderSubmit = (market: Market) => {
|
||||||
const { keypair } = useVegaWallet();
|
const { keypair } = useVegaWallet();
|
||||||
const { send, transaction, reset: resetTransaction } = useVegaTransaction();
|
const waitForOrderEvent = useOrderEvent();
|
||||||
|
|
||||||
|
const {
|
||||||
|
send,
|
||||||
|
transaction,
|
||||||
|
reset: resetTransaction,
|
||||||
|
setComplete,
|
||||||
|
TransactionDialog,
|
||||||
|
} = useVegaTransaction();
|
||||||
|
|
||||||
const [finalizedOrder, setFinalizedOrder] =
|
const [finalizedOrder, setFinalizedOrder] =
|
||||||
useState<OrderEvent_busEvents_event_Order | null>(null);
|
useState<OrderEvent_busEvents_event_Order | null>(null);
|
||||||
const client = useApolloClient();
|
|
||||||
const subRef = useRef<Subscription | null>(null);
|
|
||||||
|
|
||||||
const reset = useCallback(() => {
|
const reset = useCallback(() => {
|
||||||
resetTransaction();
|
resetTransaction();
|
||||||
setFinalizedOrder(null);
|
setFinalizedOrder(null);
|
||||||
subRef.current?.unsubscribe();
|
|
||||||
}, [resetTransaction]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
resetTransaction();
|
|
||||||
setFinalizedOrder(null);
|
|
||||||
subRef.current?.unsubscribe();
|
|
||||||
};
|
|
||||||
}, [resetTransaction]);
|
}, [resetTransaction]);
|
||||||
|
|
||||||
const submit = useCallback(
|
const submit = useCallback(
|
||||||
@ -43,6 +56,7 @@ export const useOrderSubmit = (market: Market) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFinalizedOrder(null);
|
setFinalizedOrder(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await send({
|
const res = await send({
|
||||||
pubKey: keypair.pub,
|
pubKey: keypair.pub,
|
||||||
@ -58,9 +72,7 @@ export const useOrderSubmit = (market: Market) => {
|
|||||||
side: order.side,
|
side: order.side,
|
||||||
timeInForce: order.timeInForce,
|
timeInForce: order.timeInForce,
|
||||||
expiresAt: order.expiration
|
expiresAt: order.expiration
|
||||||
? // Wallet expects timestamp in nanoseconds, we don't have that level of accuracy so
|
? toNanoSeconds(order.expiration) // Wallet expects timestampe in nanoseconds
|
||||||
// just append 6 zeroes
|
|
||||||
order.expiration.getTime().toString() + '000000'
|
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -68,34 +80,10 @@ export const useOrderSubmit = (market: Market) => {
|
|||||||
if (res?.signature) {
|
if (res?.signature) {
|
||||||
const resId = determineId(res.signature);
|
const resId = determineId(res.signature);
|
||||||
if (resId) {
|
if (resId) {
|
||||||
// Start a subscription looking for the newly created order
|
waitForOrderEvent(resId, keypair.pub, (order) => {
|
||||||
subRef.current = client
|
setFinalizedOrder(order);
|
||||||
.subscribe<OrderEvent, OrderEventVariables>({
|
setComplete();
|
||||||
query: ORDER_EVENT_SUB,
|
});
|
||||||
variables: { partyId: keypair?.pub || '' },
|
|
||||||
})
|
|
||||||
.subscribe(({ data }) => {
|
|
||||||
if (!data?.busEvents?.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No types available for the subscription result
|
|
||||||
const matchingOrderEvent = data.busEvents.find((e) => {
|
|
||||||
if (e.event.__typename !== 'Order') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.event.id === resId;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
matchingOrderEvent &&
|
|
||||||
matchingOrderEvent.event.__typename === 'Order'
|
|
||||||
) {
|
|
||||||
setFinalizedOrder(matchingOrderEvent.event);
|
|
||||||
subRef.current?.unsubscribe();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
@ -104,19 +92,13 @@ export const useOrderSubmit = (market: Market) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[keypair, send, market, setComplete, waitForOrderEvent]
|
||||||
client,
|
|
||||||
keypair,
|
|
||||||
send,
|
|
||||||
market.id,
|
|
||||||
market.decimalPlaces,
|
|
||||||
market.positionDecimalPlaces,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transaction,
|
transaction,
|
||||||
finalizedOrder,
|
finalizedOrder,
|
||||||
|
TransactionDialog,
|
||||||
submit,
|
submit,
|
||||||
reset,
|
reset,
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
import type { FieldErrors } from 'react-hook-form';
|
import type { FieldErrors } from 'react-hook-form';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import type { Order } from '@vegaprotocol/wallet';
|
|
||||||
import {
|
import {
|
||||||
useVegaWallet,
|
useVegaWallet,
|
||||||
VegaWalletOrderTimeInForce as OrderTimeInForce,
|
VegaWalletOrderTimeInForce as OrderTimeInForce,
|
||||||
VegaWalletOrderType as OrderType,
|
VegaWalletOrderType as OrderType,
|
||||||
} from '@vegaprotocol/wallet';
|
} from '@vegaprotocol/wallet';
|
||||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||||
import type { Market } from '../market';
|
|
||||||
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
|
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
|
||||||
|
import type { Order } from './use-order-submit';
|
||||||
|
|
||||||
export type ValidationProps = {
|
export type ValidationArgs = {
|
||||||
step: number;
|
step: number;
|
||||||
market: Market;
|
market: {
|
||||||
|
state: MarketState;
|
||||||
|
tradingMode: MarketTradingMode;
|
||||||
|
positionDecimalPlaces: number;
|
||||||
|
};
|
||||||
orderType: OrderType;
|
orderType: OrderType;
|
||||||
orderTimeInForce: OrderTimeInForce;
|
orderTimeInForce: OrderTimeInForce;
|
||||||
fieldErrors?: FieldErrors<Order>;
|
fieldErrors?: FieldErrors<Order>;
|
||||||
@ -34,7 +37,7 @@ export const useOrderValidation = ({
|
|||||||
fieldErrors = {},
|
fieldErrors = {},
|
||||||
orderType,
|
orderType,
|
||||||
orderTimeInForce,
|
orderTimeInForce,
|
||||||
}: ValidationProps) => {
|
}: ValidationArgs) => {
|
||||||
const { keypair } = useVegaWallet();
|
const { keypair } = useVegaWallet();
|
||||||
|
|
||||||
const { message, isDisabled } = useMemo(() => {
|
const { message, isDisabled } = useMemo(() => {
|
||||||
|
@ -4,38 +4,11 @@ import {
|
|||||||
VegaWalletOrderSide,
|
VegaWalletOrderSide,
|
||||||
} from '@vegaprotocol/wallet';
|
} from '@vegaprotocol/wallet';
|
||||||
import { toDecimal } from '@vegaprotocol/react-helpers';
|
import { toDecimal } from '@vegaprotocol/react-helpers';
|
||||||
import type { Market } from '../market';
|
import type { Order, Market } from '../order-hooks';
|
||||||
import type { OrderStatus } from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
export type Order =
|
|
||||||
| {
|
|
||||||
size: string;
|
|
||||||
type: VegaWalletOrderType.Market;
|
|
||||||
timeInForce: VegaWalletOrderTimeInForce;
|
|
||||||
side: VegaWalletOrderSide;
|
|
||||||
price?: never;
|
|
||||||
expiration?: never;
|
|
||||||
rejectionReason: string | null;
|
|
||||||
status?: OrderStatus;
|
|
||||||
market?: Market | null;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
size: string;
|
|
||||||
type: VegaWalletOrderType.Limit;
|
|
||||||
timeInForce: VegaWalletOrderTimeInForce;
|
|
||||||
side: VegaWalletOrderSide;
|
|
||||||
price?: string;
|
|
||||||
expiration?: Date;
|
|
||||||
rejectionReason: string | null;
|
|
||||||
status?: OrderStatus;
|
|
||||||
market?: Market | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDefaultOrder = (market: Market): Order => ({
|
export const getDefaultOrder = (market: Market): Order => ({
|
||||||
type: VegaWalletOrderType.Market,
|
type: VegaWalletOrderType.Market,
|
||||||
side: VegaWalletOrderSide.Buy,
|
side: VegaWalletOrderSide.Buy,
|
||||||
timeInForce: VegaWalletOrderTimeInForce.IOC,
|
timeInForce: VegaWalletOrderTimeInForce.IOC,
|
||||||
size: String(toDecimal(market.positionDecimalPlaces)),
|
size: String(toDecimal(market.positionDecimalPlaces)),
|
||||||
rejectionReason: null,
|
|
||||||
market: null,
|
|
||||||
});
|
});
|
||||||
|
@ -8,4 +8,5 @@ export * from './lib/i18n';
|
|||||||
export * from './lib/pagination';
|
export * from './lib/pagination';
|
||||||
export * from './lib/remove-0x';
|
export * from './lib/remove-0x';
|
||||||
export * from './lib/storage';
|
export * from './lib/storage';
|
||||||
|
export * from './lib/time';
|
||||||
export * from './lib/validate';
|
export * from './lib/validate';
|
||||||
|
3
libs/react-helpers/src/lib/time.ts
Normal file
3
libs/react-helpers/src/lib/time.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const toNanoSeconds = (date: Date) => {
|
||||||
|
return date.getTime().toString() + '000000';
|
||||||
|
};
|
10
libs/types/src/__generated__/globalTypes.ts
generated
10
libs/types/src/__generated__/globalTypes.ts
generated
@ -293,6 +293,16 @@ export enum WithdrawalStatus {
|
|||||||
Rejected = "Rejected",
|
Rejected = "Rejected",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination constructs to support cursor based pagination in the API
|
||||||
|
*/
|
||||||
|
export interface Pagination {
|
||||||
|
first?: number | null;
|
||||||
|
after?: string | null;
|
||||||
|
last?: number | null;
|
||||||
|
before?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
//==============================================================
|
//==============================================================
|
||||||
// END Enums and Input Objects
|
// END Enums and Input Objects
|
||||||
//==============================================================
|
//==============================================================
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
export * from './__generated__';
|
export * from './__generated__';
|
||||||
export * from './candle';
|
export * from './candle';
|
||||||
export * from './pagination';
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
export interface Pagination {
|
|
||||||
first?: number;
|
|
||||||
after?: string;
|
|
||||||
last?: number;
|
|
||||||
before?: string;
|
|
||||||
}
|
|
@ -10,6 +10,7 @@ interface DialogProps {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
onChange?: (isOpen: boolean) => void;
|
onChange?: (isOpen: boolean) => void;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
icon?: ReactNode;
|
||||||
intent?: Intent;
|
intent?: Intent;
|
||||||
titleClassNames?: string;
|
titleClassNames?: string;
|
||||||
contentClassNames?: string;
|
contentClassNames?: string;
|
||||||
@ -20,6 +21,7 @@ export function Dialog({
|
|||||||
open,
|
open,
|
||||||
onChange,
|
onChange,
|
||||||
title,
|
title,
|
||||||
|
icon,
|
||||||
intent,
|
intent,
|
||||||
titleClassNames,
|
titleClassNames,
|
||||||
contentClassNames,
|
contentClassNames,
|
||||||
@ -50,15 +52,20 @@ export function Dialog({
|
|||||||
className="focus:outline-none focus-visible:outline-none"
|
className="focus:outline-none focus-visible:outline-none"
|
||||||
/>
|
/>
|
||||||
</DialogPrimitives.Close>
|
</DialogPrimitives.Close>
|
||||||
{title && (
|
<div className="flex gap-12 max-w-full">
|
||||||
<h1
|
{icon && <div className="pt-8 fill-current">{icon}</div>}
|
||||||
className={`text-h5 text-black-95 dark:text-white-95 mt-0 mb-20 ${titleClassNames}`}
|
<div data-testid="dialog-content" className="flex-1">
|
||||||
data-testid="dialog-title"
|
{title && (
|
||||||
>
|
<h1
|
||||||
{title}
|
className={`text-h4 font-bold text-black-95 dark:text-white-95 mt-0 mb-6 ${titleClassNames}`}
|
||||||
</h1>
|
data-testid="dialog-title"
|
||||||
)}
|
>
|
||||||
{children}
|
{title}
|
||||||
|
</h1>
|
||||||
|
)}
|
||||||
|
<div className="text-black-60 dark:text-white-60">{children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</DialogPrimitives.Content>
|
</DialogPrimitives.Content>
|
||||||
</DialogPrimitives.Portal>
|
</DialogPrimitives.Portal>
|
||||||
</DialogPrimitives.Root>
|
</DialogPrimitives.Root>
|
||||||
|
@ -2,12 +2,8 @@ import { act, renderHook } from '@testing-library/react-hooks';
|
|||||||
import type { VegaWalletContextShape } from './context';
|
import type { VegaWalletContextShape } from './context';
|
||||||
import { VegaWalletContext } from './context';
|
import { VegaWalletContext } from './context';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import {
|
import { useVegaTransaction, VegaTxStatus } from './use-vega-transaction';
|
||||||
initialState,
|
import type { OrderSubmissionBody } from '@vegaprotocol/vegawallet-service-api-client';
|
||||||
useVegaTransaction,
|
|
||||||
VegaTxStatus,
|
|
||||||
} from './use-vega-transaction';
|
|
||||||
import type { OrderSubmission } from './types';
|
|
||||||
|
|
||||||
const defaultWalletContext = {
|
const defaultWalletContext = {
|
||||||
keypair: null,
|
keypair: null,
|
||||||
@ -42,7 +38,7 @@ it('If provider returns null status should be default', async () => {
|
|||||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(null));
|
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(null));
|
||||||
const { result } = setup({ sendTx: mockSendTx });
|
const { result } = setup({ sendTx: mockSendTx });
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.send({} as OrderSubmission);
|
result.current.send({} as OrderSubmissionBody);
|
||||||
});
|
});
|
||||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
|
expect(result.current.transaction.status).toEqual(VegaTxStatus.Default);
|
||||||
});
|
});
|
||||||
@ -54,7 +50,7 @@ it('Handles a single error', async () => {
|
|||||||
.mockReturnValue(Promise.resolve({ error: errorMessage }));
|
.mockReturnValue(Promise.resolve({ error: errorMessage }));
|
||||||
const { result } = setup({ sendTx: mockSendTx });
|
const { result } = setup({ sendTx: mockSendTx });
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.send({} as OrderSubmission);
|
result.current.send({} as OrderSubmissionBody);
|
||||||
});
|
});
|
||||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Error);
|
expect(result.current.transaction.status).toEqual(VegaTxStatus.Error);
|
||||||
expect(result.current.transaction.error).toEqual({ error: errorMessage });
|
expect(result.current.transaction.error).toEqual({ error: errorMessage });
|
||||||
@ -69,7 +65,7 @@ it('Handles multiple errors', async () => {
|
|||||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(errorObj));
|
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(errorObj));
|
||||||
const { result } = setup({ sendTx: mockSendTx });
|
const { result } = setup({ sendTx: mockSendTx });
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.send({} as OrderSubmission);
|
result.current.send({} as OrderSubmissionBody);
|
||||||
});
|
});
|
||||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Error);
|
expect(result.current.transaction.status).toEqual(VegaTxStatus.Error);
|
||||||
expect(result.current.transaction.error).toEqual(errorObj);
|
expect(result.current.transaction.error).toEqual(errorObj);
|
||||||
@ -90,7 +86,7 @@ it('Returns the signature if successful', async () => {
|
|||||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(successObj));
|
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(successObj));
|
||||||
const { result } = setup({ sendTx: mockSendTx });
|
const { result } = setup({ sendTx: mockSendTx });
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current.send({} as OrderSubmission);
|
result.current.send({} as OrderSubmissionBody);
|
||||||
});
|
});
|
||||||
expect(result.current.transaction.status).toEqual(VegaTxStatus.Pending);
|
expect(result.current.transaction.status).toEqual(VegaTxStatus.Pending);
|
||||||
expect(result.current.transaction.txHash).toEqual(successObj.txHash);
|
expect(result.current.transaction.txHash).toEqual(successObj.txHash);
|
||||||
@ -98,14 +94,3 @@ it('Returns the signature if successful', async () => {
|
|||||||
successObj.tx.signature.value
|
successObj.tx.signature.value
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Resets transaction state if user rejects', async () => {
|
|
||||||
const mockSendTx = jest
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue(Promise.resolve({ error: 'User rejected' }));
|
|
||||||
const { result } = setup({ sendTx: mockSendTx });
|
|
||||||
await act(async () => {
|
|
||||||
result.current.send({} as OrderSubmission);
|
|
||||||
});
|
|
||||||
expect(result.current.transaction).toEqual(initialState);
|
|
||||||
});
|
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import type { TransactionSubmission } from './wallet-types';
|
import type { TransactionSubmission } from './wallet-types';
|
||||||
import { useVegaWallet } from './use-vega-wallet';
|
import { useVegaWallet } from './use-vega-wallet';
|
||||||
import type { SendTxError } from './context';
|
import type { SendTxError } from './context';
|
||||||
|
import { VegaTransactionDialog } from './vega-transaction-dialog';
|
||||||
|
import type { Intent } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
|
export interface DialogProps {
|
||||||
|
children?: JSX.Element;
|
||||||
|
intent?: Intent;
|
||||||
|
title?: string;
|
||||||
|
icon?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
export enum VegaTxStatus {
|
export enum VegaTxStatus {
|
||||||
Default = 'Default',
|
Default = 'Default',
|
||||||
Requested = 'Requested',
|
Requested = 'Requested',
|
||||||
Pending = 'Pending',
|
Pending = 'Pending',
|
||||||
Error = 'Error',
|
Error = 'Error',
|
||||||
// Note no complete state as we have to use api calls/subs to check if
|
Complete = 'Complete',
|
||||||
// our transaction was completed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VegaTxState {
|
export interface VegaTxState {
|
||||||
@ -17,6 +26,7 @@ export interface VegaTxState {
|
|||||||
error: object | null;
|
error: object | null;
|
||||||
txHash: string | null;
|
txHash: string | null;
|
||||||
signature: string | null;
|
signature: string | null;
|
||||||
|
dialogOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialState = {
|
export const initialState = {
|
||||||
@ -24,6 +34,7 @@ export const initialState = {
|
|||||||
error: null,
|
error: null,
|
||||||
txHash: null,
|
txHash: null,
|
||||||
signature: null,
|
signature: null,
|
||||||
|
dialogOpen: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useVegaTransaction = () => {
|
export const useVegaTransaction = () => {
|
||||||
@ -48,6 +59,10 @@ export const useVegaTransaction = () => {
|
|||||||
setTransaction(initialState);
|
setTransaction(initialState);
|
||||||
}, [setTransaction]);
|
}, [setTransaction]);
|
||||||
|
|
||||||
|
const setComplete = useCallback(() => {
|
||||||
|
setTransaction({ status: VegaTxStatus.Complete });
|
||||||
|
}, [setTransaction]);
|
||||||
|
|
||||||
const send = useCallback(
|
const send = useCallback(
|
||||||
async (tx: TransactionSubmission) => {
|
async (tx: TransactionSubmission) => {
|
||||||
setTransaction({
|
setTransaction({
|
||||||
@ -55,6 +70,7 @@ export const useVegaTransaction = () => {
|
|||||||
txHash: null,
|
txHash: null,
|
||||||
signature: null,
|
signature: null,
|
||||||
status: VegaTxStatus.Requested,
|
status: VegaTxStatus.Requested,
|
||||||
|
dialogOpen: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await sendTx(tx);
|
const res = await sendTx(tx);
|
||||||
@ -63,18 +79,14 @@ export const useVegaTransaction = () => {
|
|||||||
setTransaction({ status: VegaTxStatus.Default });
|
setTransaction({ status: VegaTxStatus.Default });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if ('errors' in res) {
|
||||||
if ('error' in res) {
|
handleError(res);
|
||||||
// Close dialog if user rejects the transaction
|
} else if ('error' in res) {
|
||||||
if (res.error === 'User rejected') {
|
if (res.error === 'User rejected') {
|
||||||
reset();
|
reset();
|
||||||
} else {
|
} else {
|
||||||
handleError(res);
|
handleError(res);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
} else if ('errors' in res) {
|
|
||||||
handleError(res);
|
|
||||||
return null;
|
|
||||||
} else if (res.tx?.signature?.value && res.txHash) {
|
} else if (res.tx?.signature?.value && res.txHash) {
|
||||||
setTransaction({
|
setTransaction({
|
||||||
status: VegaTxStatus.Pending,
|
status: VegaTxStatus.Pending,
|
||||||
@ -91,5 +103,25 @@ export const useVegaTransaction = () => {
|
|||||||
[sendTx, handleError, setTransaction, reset]
|
[sendTx, handleError, setTransaction, reset]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { send, transaction, reset };
|
const TransactionDialog = useMemo(() => {
|
||||||
|
return (props: DialogProps) => (
|
||||||
|
<VegaTransactionDialog
|
||||||
|
{...props}
|
||||||
|
isOpen={transaction.dialogOpen}
|
||||||
|
onChange={(isOpen) => {
|
||||||
|
if (!isOpen) reset();
|
||||||
|
setTransaction({ dialogOpen: isOpen });
|
||||||
|
}}
|
||||||
|
transaction={transaction}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [transaction, setTransaction, reset]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
send,
|
||||||
|
transaction,
|
||||||
|
reset,
|
||||||
|
setComplete,
|
||||||
|
TransactionDialog,
|
||||||
|
};
|
||||||
};
|
};
|
@ -1,10 +1,7 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { OrderStatus, OrderType } from '@vegaprotocol/types';
|
|
||||||
import type { VegaTxState } from '../use-vega-transaction';
|
|
||||||
import { VegaTxStatus } from '../use-vega-transaction';
|
import { VegaTxStatus } from '../use-vega-transaction';
|
||||||
import type { Order } from '../wallet-types';
|
|
||||||
import type { VegaTransactionDialogProps } from './vega-transaction-dialog';
|
import type { VegaTransactionDialogProps } from './vega-transaction-dialog';
|
||||||
import { VegaDialog, VegaTransactionDialog } from './vega-transaction-dialog';
|
import { VegaTransactionDialog } from './vega-transaction-dialog';
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/environment', () => ({
|
jest.mock('@vegaprotocol/environment', () => ({
|
||||||
useEnvironment: () => ({
|
useEnvironment: () => ({
|
||||||
@ -13,222 +10,109 @@ jest.mock('@vegaprotocol/environment', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe('VegaTransactionDialog', () => {
|
describe('VegaTransactionDialog', () => {
|
||||||
let defaultProps: VegaTransactionDialogProps;
|
let props: VegaTransactionDialogProps;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
defaultProps = {
|
props = {
|
||||||
orderDialogOpen: true,
|
isOpen: true,
|
||||||
setOrderDialogOpen: () => false,
|
onChange: () => false,
|
||||||
transaction: {
|
transaction: {
|
||||||
status: VegaTxStatus.Default,
|
|
||||||
error: null,
|
|
||||||
txHash: null,
|
|
||||||
signature: null,
|
|
||||||
},
|
|
||||||
finalizedOrder: {
|
|
||||||
status: OrderStatus.Cancelled,
|
|
||||||
rejectionReason: null,
|
|
||||||
size: '10',
|
|
||||||
price: '1000',
|
|
||||||
market: null,
|
|
||||||
type: OrderType.Limit,
|
|
||||||
},
|
|
||||||
reset: jest.fn(),
|
|
||||||
title: 'Order cancelled',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render when an order is successfully cancelled', () => {
|
|
||||||
render(<VegaTransactionDialog {...defaultProps} />);
|
|
||||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
|
||||||
'Order cancelled'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render when an order is not successfully cancelled', () => {
|
|
||||||
const transaction: VegaTxState = {
|
|
||||||
status: VegaTxStatus.Default,
|
|
||||||
error: null,
|
|
||||||
txHash: null,
|
|
||||||
signature: null,
|
|
||||||
};
|
|
||||||
const finalizedOrder: Order = {
|
|
||||||
status: OrderStatus.Active,
|
|
||||||
rejectionReason: null,
|
|
||||||
size: '10',
|
|
||||||
price: '1000',
|
|
||||||
market: null,
|
|
||||||
type: OrderType.Limit,
|
|
||||||
};
|
|
||||||
const propsForTest = {
|
|
||||||
transaction,
|
|
||||||
finalizedOrder,
|
|
||||||
};
|
|
||||||
|
|
||||||
render(
|
|
||||||
<VegaTransactionDialog
|
|
||||||
{...defaultProps}
|
|
||||||
{...propsForTest}
|
|
||||||
title={'Cancellation failed'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
|
||||||
'Cancellation failed'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('TransactionDialog', () => {
|
|
||||||
it('should render when an order is successful', () => {
|
|
||||||
const transaction: VegaTxState = {
|
|
||||||
status: VegaTxStatus.Default,
|
|
||||||
error: null,
|
|
||||||
txHash: null,
|
|
||||||
signature: null,
|
|
||||||
};
|
|
||||||
const finalizedOrder: Order = {
|
|
||||||
status: OrderStatus.Active,
|
|
||||||
rejectionReason: null,
|
|
||||||
size: '10',
|
|
||||||
price: '1000',
|
|
||||||
market: null,
|
|
||||||
type: OrderType.Limit,
|
|
||||||
};
|
|
||||||
render(
|
|
||||||
<VegaDialog
|
|
||||||
finalizedOrder={finalizedOrder}
|
|
||||||
transaction={transaction}
|
|
||||||
title={'Order placed'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
|
||||||
'Order placed'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render when transaction is requested', () => {
|
|
||||||
const transaction: VegaTxState = {
|
|
||||||
status: VegaTxStatus.Requested,
|
status: VegaTxStatus.Requested,
|
||||||
error: null,
|
error: null,
|
||||||
txHash: null,
|
txHash: null,
|
||||||
signature: null,
|
signature: null,
|
||||||
};
|
},
|
||||||
const finalizedOrder: Order = {
|
};
|
||||||
status: OrderStatus.Active,
|
|
||||||
rejectionReason: null,
|
|
||||||
size: '10',
|
|
||||||
price: '1000',
|
|
||||||
market: null,
|
|
||||||
type: OrderType.Limit,
|
|
||||||
};
|
|
||||||
render(
|
|
||||||
<VegaDialog
|
|
||||||
finalizedOrder={finalizedOrder}
|
|
||||||
transaction={transaction}
|
|
||||||
title={'Order tx'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
|
||||||
'Confirm transaction in wallet'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render when transaction has error', () => {
|
|
||||||
const transaction: VegaTxState = {
|
|
||||||
status: VegaTxStatus.Error,
|
|
||||||
error: null,
|
|
||||||
txHash: null,
|
|
||||||
signature: null,
|
|
||||||
};
|
|
||||||
const finalizedOrder: Order = {
|
|
||||||
status: OrderStatus.Active,
|
|
||||||
rejectionReason: null,
|
|
||||||
size: '10',
|
|
||||||
price: '1000',
|
|
||||||
market: null,
|
|
||||||
type: OrderType.Limit,
|
|
||||||
};
|
|
||||||
render(
|
|
||||||
<VegaDialog
|
|
||||||
finalizedOrder={finalizedOrder}
|
|
||||||
transaction={transaction}
|
|
||||||
title={'Order tx'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
|
||||||
'Order rejected by wallet'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render when an order is rejected', () => {
|
|
||||||
const transaction: VegaTxState = {
|
|
||||||
status: VegaTxStatus.Default,
|
|
||||||
error: null,
|
|
||||||
txHash: null,
|
|
||||||
signature: null,
|
|
||||||
};
|
|
||||||
const finalizedOrder: Order = {
|
|
||||||
status: OrderStatus.Rejected,
|
|
||||||
rejectionReason: null,
|
|
||||||
size: '10',
|
|
||||||
price: '1000',
|
|
||||||
market: null,
|
|
||||||
type: OrderType.Limit,
|
|
||||||
};
|
|
||||||
render(
|
|
||||||
<VegaDialog
|
|
||||||
finalizedOrder={finalizedOrder}
|
|
||||||
transaction={transaction}
|
|
||||||
title={'Order title'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
|
||||||
'Order failed'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render when pending consensus', () => {
|
|
||||||
const transaction: VegaTxState = {
|
|
||||||
status: VegaTxStatus.Error,
|
|
||||||
error: null,
|
|
||||||
txHash: null,
|
|
||||||
signature: null,
|
|
||||||
};
|
|
||||||
render(
|
|
||||||
<VegaDialog
|
|
||||||
finalizedOrder={null}
|
|
||||||
transaction={transaction}
|
|
||||||
title={'Order title'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
|
||||||
'Order rejected by wallet'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render awaiting network confirmation and add link to tx in block explorer', () => {
|
|
||||||
const transaction: VegaTxState = {
|
|
||||||
status: VegaTxStatus.Default,
|
|
||||||
error: null,
|
|
||||||
txHash: 'TxHash',
|
|
||||||
signature: null,
|
|
||||||
};
|
|
||||||
render(
|
|
||||||
<VegaDialog
|
|
||||||
finalizedOrder={null}
|
|
||||||
transaction={transaction}
|
|
||||||
title={'Order Tx'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
|
||||||
'Awaiting network confirmation'
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('tx-block-explorer')).toHaveTextContent(
|
|
||||||
'View in block explorer'
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('tx-block-explorer')).toHaveAttribute(
|
|
||||||
'href',
|
|
||||||
'https://test.explorer.vega.network/txs/0xTxHash'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('requested', () => {
|
||||||
|
render(<VegaTransactionDialog {...props} />);
|
||||||
|
expect(screen.getByTestId('dialog-title')).toHaveTextContent(/confirm/i);
|
||||||
|
expect(screen.getByTestId(VegaTxStatus.Requested)).toHaveTextContent(
|
||||||
|
/please open your wallet/i
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pending', () => {
|
||||||
|
render(
|
||||||
|
<VegaTransactionDialog
|
||||||
|
{...props}
|
||||||
|
transaction={{
|
||||||
|
...props.transaction,
|
||||||
|
txHash: 'tx-hash',
|
||||||
|
status: VegaTxStatus.Pending,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('dialog-title')).toHaveTextContent(/awaiting/i);
|
||||||
|
expect(screen.getByTestId(VegaTxStatus.Pending)).toHaveTextContent(
|
||||||
|
/please wait/i
|
||||||
|
);
|
||||||
|
testBlockExplorerLink('tx-hash');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('error', () => {
|
||||||
|
render(
|
||||||
|
<VegaTransactionDialog
|
||||||
|
{...props}
|
||||||
|
transaction={{
|
||||||
|
...props.transaction,
|
||||||
|
error: { message: 'rejected' },
|
||||||
|
status: VegaTxStatus.Error,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('dialog-title')).toHaveTextContent(/failed/i);
|
||||||
|
expect(screen.getByTestId(VegaTxStatus.Error)).toHaveTextContent(
|
||||||
|
/rejected/i
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('default complete', () => {
|
||||||
|
render(
|
||||||
|
<VegaTransactionDialog
|
||||||
|
{...props}
|
||||||
|
transaction={{
|
||||||
|
...props.transaction,
|
||||||
|
txHash: 'tx-hash',
|
||||||
|
status: VegaTxStatus.Complete,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('dialog-title')).toHaveTextContent(/complete/i);
|
||||||
|
expect(screen.getByTestId(VegaTxStatus.Complete)).toHaveTextContent(
|
||||||
|
/confirmed/i
|
||||||
|
);
|
||||||
|
testBlockExplorerLink('tx-hash');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('custom complete', () => {
|
||||||
|
render(
|
||||||
|
<VegaTransactionDialog
|
||||||
|
{...props}
|
||||||
|
transaction={{
|
||||||
|
...props.transaction,
|
||||||
|
txHash: 'tx-hash',
|
||||||
|
status: VegaTxStatus.Complete,
|
||||||
|
}}
|
||||||
|
title="Custom title"
|
||||||
|
>
|
||||||
|
<div>Custom content</div>
|
||||||
|
</VegaTransactionDialog>
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('dialog-title')).toHaveTextContent(
|
||||||
|
'Custom title'
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Custom content')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
function testBlockExplorerLink(txHash: string) {
|
||||||
|
expect(screen.getByTestId('tx-block-explorer')).toHaveTextContent(
|
||||||
|
'View in block explorer'
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('tx-block-explorer')).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
`https://test.explorer.vega.network/txs/0x${txHash}`
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,155 +1,94 @@
|
|||||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
import { useEffect } from 'react';
|
import get from 'lodash/get';
|
||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import { Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
import type { VegaTxState } from '../use-vega-transaction';
|
import type { VegaTxState } from '../use-vega-transaction';
|
||||||
import { VegaTxStatus } from '../use-vega-transaction';
|
import { VegaTxStatus } from '../use-vega-transaction';
|
||||||
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import type { ReactNode } from 'react';
|
|
||||||
import {
|
|
||||||
addDecimalsFormatNumber,
|
|
||||||
formatLabel,
|
|
||||||
t,
|
|
||||||
} from '@vegaprotocol/react-helpers';
|
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
|
||||||
import { OrderType } from '@vegaprotocol/types';
|
|
||||||
import type { Order } from '../wallet-types';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
|
|
||||||
export interface VegaTransactionDialogProps {
|
export interface VegaTransactionDialogProps {
|
||||||
orderDialogOpen: boolean;
|
isOpen: boolean;
|
||||||
setOrderDialogOpen: (isOpen: boolean) => void;
|
onChange: (isOpen: boolean) => void;
|
||||||
finalizedOrder: Order | null;
|
|
||||||
transaction: VegaTxState;
|
transaction: VegaTxState;
|
||||||
reset: () => void;
|
|
||||||
title?: string;
|
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
intent?: Intent;
|
||||||
|
title?: string;
|
||||||
|
icon?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDialogIntent = (
|
|
||||||
finalizedOrder: Order | null,
|
|
||||||
transaction: VegaTxState
|
|
||||||
) => {
|
|
||||||
if (finalizedOrder) {
|
|
||||||
return !finalizedOrder.rejectionReason ? Intent.Success : Intent.Danger;
|
|
||||||
}
|
|
||||||
switch (transaction.status) {
|
|
||||||
case VegaTxStatus.Requested:
|
|
||||||
return Intent.Warning;
|
|
||||||
case VegaTxStatus.Pending:
|
|
||||||
return Intent.Warning;
|
|
||||||
case VegaTxStatus.Error:
|
|
||||||
return Intent.Danger;
|
|
||||||
default:
|
|
||||||
return Intent.None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const VegaTransactionDialog = ({
|
export const VegaTransactionDialog = ({
|
||||||
orderDialogOpen,
|
isOpen,
|
||||||
setOrderDialogOpen,
|
onChange,
|
||||||
finalizedOrder,
|
|
||||||
transaction,
|
transaction,
|
||||||
reset,
|
|
||||||
title = '',
|
|
||||||
children,
|
children,
|
||||||
|
intent,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
}: VegaTransactionDialogProps) => {
|
}: VegaTransactionDialogProps) => {
|
||||||
// open / close dialog
|
const computedIntent = intent ? intent : getIntent(transaction);
|
||||||
useEffect(() => {
|
const computedTitle = title ? title : getTitle(transaction);
|
||||||
if (transaction.status !== VegaTxStatus.Default || finalizedOrder) {
|
const computedIcon = icon ? icon : getIcon(transaction);
|
||||||
setOrderDialogOpen(true);
|
// Each dialog can specify custom dialog content using data returned via
|
||||||
} else {
|
// the subscription that confirms the transaction. So if we get a success state
|
||||||
setOrderDialogOpen(false);
|
// and this custom content is provided, render it
|
||||||
}
|
const content =
|
||||||
}, [finalizedOrder, setOrderDialogOpen, transaction.status]);
|
transaction.status === VegaTxStatus.Complete && children ? (
|
||||||
|
children
|
||||||
|
) : (
|
||||||
|
<VegaDialog transaction={transaction} />
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={orderDialogOpen}
|
open={isOpen}
|
||||||
onChange={(isOpen) => {
|
onChange={onChange}
|
||||||
setOrderDialogOpen(isOpen);
|
intent={computedIntent}
|
||||||
|
title={computedTitle}
|
||||||
// If closing reset
|
icon={computedIcon}
|
||||||
if (!isOpen) {
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
intent={getDialogIntent(finalizedOrder, transaction)}
|
|
||||||
>
|
>
|
||||||
<VegaDialog
|
{content}
|
||||||
key={`${title.toLowerCase().split(' ').join('-')}-tx-${
|
|
||||||
transaction.txHash
|
|
||||||
}`}
|
|
||||||
transaction={transaction}
|
|
||||||
finalizedOrder={finalizedOrder}
|
|
||||||
title={title}
|
|
||||||
children={children}
|
|
||||||
/>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface VegaDialogProps {
|
interface VegaDialogProps {
|
||||||
transaction: VegaTxState;
|
transaction: VegaTxState;
|
||||||
finalizedOrder: Order | null;
|
|
||||||
title: string;
|
|
||||||
children?: ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VegaDialog = ({
|
/**
|
||||||
transaction,
|
* Default dialog content
|
||||||
finalizedOrder,
|
*/
|
||||||
title,
|
export const VegaDialog = ({ transaction }: VegaDialogProps) => {
|
||||||
children,
|
|
||||||
}: VegaDialogProps) => {
|
|
||||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||||
const headerClassName = 'text-h5 font-bold text-black dark:text-white';
|
|
||||||
|
|
||||||
if (children && transaction.status === VegaTxStatus.Default) {
|
|
||||||
return <div>{children}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rejected by wallet
|
|
||||||
if (transaction.status === VegaTxStatus.Requested) {
|
if (transaction.status === VegaTxStatus.Requested) {
|
||||||
return (
|
return (
|
||||||
<OrderDialogWrapper
|
<p data-testid={transaction.status}>
|
||||||
title="Confirm transaction in wallet"
|
{t(
|
||||||
icon={<Icon name="hand-up" size={20} />}
|
'Please open your wallet application and confirm or reject the transaction'
|
||||||
>
|
)}
|
||||||
<p>
|
</p>
|
||||||
{t(
|
|
||||||
'Please open your wallet application and confirm or reject the transaction'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</OrderDialogWrapper>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transaction error
|
|
||||||
if (transaction.status === VegaTxStatus.Error) {
|
if (transaction.status === VegaTxStatus.Error) {
|
||||||
return (
|
return (
|
||||||
<OrderDialogWrapper
|
<div data-testid={transaction.status}>
|
||||||
title="Order rejected by wallet"
|
|
||||||
icon={<Icon name="warning-sign" size={20} />}
|
|
||||||
>
|
|
||||||
{transaction.error && (
|
{transaction.error && (
|
||||||
<pre className="text-ui break-all whitespace-pre-wrap">
|
<pre className="text-ui break-all whitespace-pre-wrap">
|
||||||
{get(transaction.error, 'error') ??
|
{get(transaction.error, 'error') ??
|
||||||
JSON.stringify(transaction.error, null, 2)}
|
JSON.stringify(transaction.error, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
</OrderDialogWrapper>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pending consensus
|
if (transaction.status === VegaTxStatus.Pending) {
|
||||||
if (!finalizedOrder) {
|
|
||||||
return (
|
return (
|
||||||
<OrderDialogWrapper
|
<div data-testid={transaction.status}>
|
||||||
title="Awaiting network confirmation"
|
<p className="break-all">
|
||||||
icon={<Loader size="small" />}
|
{t('Please wait for your transaction to be confirmed')} -
|
||||||
>
|
{transaction.txHash && (
|
||||||
{transaction.txHash && (
|
|
||||||
<p className="break-all">
|
|
||||||
{t('Waiting for few more blocks')} -
|
|
||||||
<a
|
<a
|
||||||
className="underline"
|
className="underline"
|
||||||
data-testid="tx-block-explorer"
|
data-testid="tx-block-explorer"
|
||||||
@ -159,108 +98,77 @@ export const VegaDialog = ({
|
|||||||
>
|
>
|
||||||
{t('View in block explorer')}
|
{t('View in block explorer')}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
)}
|
||||||
)}
|
|
||||||
</OrderDialogWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order on network but was rejected
|
|
||||||
if (finalizedOrder.status === 'Rejected') {
|
|
||||||
return (
|
|
||||||
<OrderDialogWrapper
|
|
||||||
title="Order failed"
|
|
||||||
icon={<Icon name="warning-sign" size={20} />}
|
|
||||||
>
|
|
||||||
<p data-testid="error-reason">
|
|
||||||
{finalizedOrder.rejectionReason &&
|
|
||||||
t(`Reason: ${formatLabel(finalizedOrder.rejectionReason)}`)}
|
|
||||||
</p>
|
</p>
|
||||||
</OrderDialogWrapper>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (transaction.status === VegaTxStatus.Complete) {
|
||||||
<OrderDialogWrapper title={title} icon={<Icon name="tick" size={20} />}>
|
return (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
<div data-testid={transaction.status}>
|
||||||
{finalizedOrder.market && (
|
<p className="break-all">
|
||||||
<div>
|
{t('Your transaction has been confirmed')} -
|
||||||
<p className={headerClassName}>{t(`Market`)}</p>
|
{transaction.txHash && (
|
||||||
<p>{t(`${finalizedOrder.market.name}`)}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<p className={headerClassName}>{t(`Status`)}</p>
|
|
||||||
<p>{t(`${finalizedOrder.status}`)}</p>
|
|
||||||
</div>
|
|
||||||
{finalizedOrder.type === OrderType.Limit && finalizedOrder.market && (
|
|
||||||
<div>
|
|
||||||
<p className={headerClassName}>{t(`Price`)}</p>
|
|
||||||
<p>
|
|
||||||
{addDecimalsFormatNumber(
|
|
||||||
finalizedOrder.price,
|
|
||||||
finalizedOrder.market.decimalPlaces
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<p className={headerClassName}>{t(`Amount`)}</p>
|
|
||||||
<p
|
|
||||||
className={
|
|
||||||
finalizedOrder.side === 'Buy'
|
|
||||||
? 'text-vega-green'
|
|
||||||
: 'text-vega-red'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{`${finalizedOrder.side === 'Buy' ? '+' : '-'} ${
|
|
||||||
finalizedOrder.size
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 gap-8">
|
|
||||||
{transaction.txHash && (
|
|
||||||
<div>
|
|
||||||
<p className={headerClassName}>{t(`Transaction`)}</p>
|
|
||||||
<a
|
<a
|
||||||
className="underline break-words"
|
className="underline"
|
||||||
data-testid="tx-block-explorer"
|
data-testid="tx-block-explorer"
|
||||||
href={`${VEGA_EXPLORER_URL}/txs/0x${transaction.txHash}`}
|
href={`${VEGA_EXPLORER_URL}/txs/0x${transaction.txHash}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
{transaction.txHash}
|
{t('View in block explorer')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
)}
|
||||||
)}
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</OrderDialogWrapper>
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface OrderDialogWrapperProps {
|
const getIntent = (transaction: VegaTxState) => {
|
||||||
children: ReactNode;
|
switch (transaction.status) {
|
||||||
icon: ReactNode;
|
case VegaTxStatus.Requested:
|
||||||
title: string;
|
return Intent.Warning;
|
||||||
}
|
case VegaTxStatus.Pending:
|
||||||
|
return Intent.Warning;
|
||||||
export const OrderDialogWrapper = ({
|
case VegaTxStatus.Error:
|
||||||
children,
|
return Intent.Danger;
|
||||||
icon,
|
case VegaTxStatus.Complete:
|
||||||
title,
|
return Intent.Success;
|
||||||
}: OrderDialogWrapperProps) => {
|
default:
|
||||||
const headerClassName = 'text-h4 font-bold text-black dark:text-white';
|
return Intent.None;
|
||||||
return (
|
}
|
||||||
<div className="flex gap-12 max-w-full">
|
};
|
||||||
<div className="pt-8 fill-current">{icon}</div>
|
|
||||||
<div data-testid="order-wrapper" className="flex-1">
|
const getTitle = (transaction: VegaTxState) => {
|
||||||
<h1 data-testid="order-status-header" className={headerClassName}>
|
switch (transaction.status) {
|
||||||
{title}
|
case VegaTxStatus.Requested:
|
||||||
</h1>
|
return t('Confirm transaction in wallet');
|
||||||
{children}
|
case VegaTxStatus.Pending:
|
||||||
</div>
|
return t('Awaiting network confirmation');
|
||||||
</div>
|
case VegaTxStatus.Error:
|
||||||
);
|
return t('Transaction failed');
|
||||||
|
case VegaTxStatus.Complete:
|
||||||
|
return t('Transaction complete');
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIcon = (transaction: VegaTxState) => {
|
||||||
|
switch (transaction.status) {
|
||||||
|
case VegaTxStatus.Requested:
|
||||||
|
return <Icon name="hand-up" size={20} />;
|
||||||
|
case VegaTxStatus.Pending:
|
||||||
|
return <Loader size="small" />;
|
||||||
|
case VegaTxStatus.Error:
|
||||||
|
return <Icon name="warning-sign" size={20} />;
|
||||||
|
case VegaTxStatus.Complete:
|
||||||
|
return <Icon name="tick" size={20} />;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import type { OrderTimeInForce } from '@vegaprotocol/types';
|
|
||||||
import type {
|
import type {
|
||||||
DelegateSubmissionBody,
|
DelegateSubmissionBody,
|
||||||
OrderCancellationBody,
|
OrderCancellationBody,
|
||||||
@ -37,23 +36,3 @@ export type TransactionSubmission =
|
|||||||
| DelegateSubmissionBody
|
| DelegateSubmissionBody
|
||||||
| UndelegateSubmissionBody
|
| UndelegateSubmissionBody
|
||||||
| OrderAmendmentBody;
|
| OrderAmendmentBody;
|
||||||
|
|
||||||
export interface Market {
|
|
||||||
name: string;
|
|
||||||
positionDecimalPlaces?: number;
|
|
||||||
decimalPlaces: number;
|
|
||||||
id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Order {
|
|
||||||
id?: string;
|
|
||||||
status?: string;
|
|
||||||
rejectionReason?: string | null;
|
|
||||||
size: string;
|
|
||||||
price: string;
|
|
||||||
market: Market | null;
|
|
||||||
type: string | null;
|
|
||||||
side?: string;
|
|
||||||
timeInForce: OrderTimeInForce;
|
|
||||||
expiresAt?: Date | string | null;
|
|
||||||
}
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
import type { ReactNode } from 'react';
|
|
||||||
|
|
||||||
interface DialogWrapperProps {
|
|
||||||
children: ReactNode;
|
|
||||||
icon: ReactNode;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DialogWrapper = ({
|
|
||||||
children,
|
|
||||||
icon,
|
|
||||||
title,
|
|
||||||
}: DialogWrapperProps) => {
|
|
||||||
return (
|
|
||||||
<div className="flex gap-12 max-w-full text-ui">
|
|
||||||
<div className="pt-8 fill-current">{icon}</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<h1 className="text-h4 text-black dark:text-white mb-12">{title}</h1>
|
|
||||||
<div className="text-black-40 dark:text-white-40">{children}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -4,7 +4,6 @@ import { isEthereumError } from '../ethereum-error';
|
|||||||
import type { EthTxState } from '../use-ethereum-transaction';
|
import type { EthTxState } from '../use-ethereum-transaction';
|
||||||
import { EthTxStatus } from '../use-ethereum-transaction';
|
import { EthTxStatus } from '../use-ethereum-transaction';
|
||||||
import { ConfirmRow, TxRow, ConfirmationEventRow } from './dialog-rows';
|
import { ConfirmRow, TxRow, ConfirmationEventRow } from './dialog-rows';
|
||||||
import { DialogWrapper } from './dialog-wrapper';
|
|
||||||
|
|
||||||
export interface TransactionDialogProps {
|
export interface TransactionDialogProps {
|
||||||
name: string;
|
name: string;
|
||||||
@ -101,11 +100,16 @@ export const TransactionDialog = ({
|
|||||||
return propsMap[status];
|
return propsMap[status];
|
||||||
};
|
};
|
||||||
|
|
||||||
const { intent, ...wrapperProps } = getWrapperProps();
|
const { intent, title, icon } = getWrapperProps();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={transaction.dialogOpen} onChange={onChange} intent={intent}>
|
<Dialog
|
||||||
<DialogWrapper {...wrapperProps}>{renderContent()}</DialogWrapper>
|
open={transaction.dialogOpen}
|
||||||
|
onChange={onChange}
|
||||||
|
intent={intent}
|
||||||
|
title={title}
|
||||||
|
icon={icon}
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -25,38 +25,22 @@ export const WithdrawDialog = ({
|
|||||||
onDialogChange,
|
onDialogChange,
|
||||||
}: WithdrawDialogProps) => {
|
}: WithdrawDialogProps) => {
|
||||||
const { ETHERSCAN_URL } = useEnvironment();
|
const { ETHERSCAN_URL } = useEnvironment();
|
||||||
const { intent, ...props } = getProps(approval, vegaTx, ethTx, ETHERSCAN_URL);
|
const { intent, title, icon, children } = getProps(
|
||||||
return (
|
approval,
|
||||||
<Dialog open={dialogOpen} intent={intent} onChange={onDialogChange}>
|
vegaTx,
|
||||||
<DialogWrapper {...props} />
|
ethTx,
|
||||||
</Dialog>
|
ETHERSCAN_URL
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
interface DialogWrapperProps {
|
|
||||||
children: ReactNode;
|
|
||||||
icon: ReactNode;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DialogWrapper = ({
|
|
||||||
children,
|
|
||||||
icon,
|
|
||||||
title,
|
|
||||||
}: DialogWrapperProps) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-12 max-w-full text-ui">
|
<Dialog
|
||||||
<div className="pt-8 fill-current">{icon}</div>
|
open={dialogOpen}
|
||||||
<div className="flex-1">
|
onChange={onDialogChange}
|
||||||
<h1
|
intent={intent}
|
||||||
data-testid="dialog-title"
|
title={title}
|
||||||
className="text-h4 text-black dark:text-white capitalize mb-12"
|
icon={icon}
|
||||||
>
|
>
|
||||||
{title}
|
{children}
|
||||||
</h1>
|
</Dialog>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -118,6 +102,12 @@ const getProps = (
|
|||||||
intent: Intent.None,
|
intent: Intent.None,
|
||||||
children: <Step>Awaiting transaction</Step>,
|
children: <Step>Awaiting transaction</Step>,
|
||||||
},
|
},
|
||||||
|
[VegaTxStatus.Complete]: {
|
||||||
|
title: t('Withdrawal transaction complete'),
|
||||||
|
icon: <Icon name="tick" />,
|
||||||
|
intent: Intent.Success,
|
||||||
|
children: <Step>Withdrawal created</Step>,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const completeProps = {
|
const completeProps = {
|
||||||
|
Loading…
Reference in New Issue
Block a user