feat: updated withdrawal tab (849) (#1579)
* feat: updated withdrawal tab (849) * added pending table to the token app, added pending completed filtering to the data provider * amended and added unit tests * amended time formats * added units * fixes errors * addressed comments
This commit is contained in:
parent
4ca22c4e98
commit
48ce7978ee
@ -5,6 +5,7 @@ import { Heading } from '../../components/heading';
|
||||
import { SplashLoader } from '../../components/splash-loader';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import {
|
||||
PendingWithdrawalsTable,
|
||||
useWithdrawals,
|
||||
WithdrawalDialogs,
|
||||
WithdrawalsTable,
|
||||
@ -30,7 +31,7 @@ const Withdrawals = ({ name }: RouteChildProps) => {
|
||||
const WithdrawPendingContainer = () => {
|
||||
const [withdrawDialog, setWithdrawDialog] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const { withdrawals, loading, error } = useWithdrawals();
|
||||
const { pending, completed, loading, error } = useWithdrawals();
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
@ -58,7 +59,11 @@ const WithdrawPendingContainer = () => {
|
||||
<p>{t('withdrawalsText')}</p>
|
||||
<p className="mb-8">{t('withdrawalsPreparedWarningText')}</p>
|
||||
<div className="w-full h-[500px]">
|
||||
<WithdrawalsTable withdrawals={withdrawals} />
|
||||
{pending && pending.length > 0 && (
|
||||
<PendingWithdrawalsTable rowData={pending} />
|
||||
)}
|
||||
<h4 className="pt-3 pb-1">{t('Withdrawal history')}</h4>
|
||||
<WithdrawalsTable rowData={completed} />
|
||||
</div>
|
||||
<WithdrawalDialogs
|
||||
withdrawDialog={withdrawDialog}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
PendingWithdrawalsTable,
|
||||
useWithdrawals,
|
||||
WithdrawalDialogs,
|
||||
WithdrawalsTable,
|
||||
@ -10,7 +11,7 @@ import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { Web3Container } from '@vegaprotocol/web3';
|
||||
|
||||
export const WithdrawalsContainer = () => {
|
||||
const { withdrawals, loading, error } = useWithdrawals();
|
||||
const { pending, completed, loading, error } = useWithdrawals();
|
||||
const [withdrawDialog, setWithdrawDialog] = useState(false);
|
||||
|
||||
return (
|
||||
@ -25,17 +26,27 @@ export const WithdrawalsContainer = () => {
|
||||
onClick={() => setWithdrawDialog(true)}
|
||||
data-testid="withdraw-dialog-button"
|
||||
>
|
||||
{t('Withdraw')}
|
||||
{t('Make withdrawal')}
|
||||
</Button>
|
||||
</header>
|
||||
<div>
|
||||
<div className="h-full px-4">
|
||||
<AsyncRenderer
|
||||
data={withdrawals}
|
||||
data={{ pending, completed }}
|
||||
loading={loading}
|
||||
error={error}
|
||||
render={(data) => {
|
||||
return <WithdrawalsTable withdrawals={data} />;
|
||||
}}
|
||||
render={({ pending, completed }) => (
|
||||
<>
|
||||
{pending && pending.length > 0 && (
|
||||
<>
|
||||
<h4 className="pt-3 pb-1">{t('Pending withdrawals')}</h4>
|
||||
<PendingWithdrawalsTable rowData={pending} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<h4 className="pt-3 pb-1">{t('Withdrawal history')}</h4>
|
||||
<WithdrawalsTable rowData={completed} />
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,6 +4,9 @@ import type {
|
||||
ValueFormatterParams,
|
||||
} from 'ag-grid-community';
|
||||
|
||||
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
|
||||
import type { AgGridReactProps } from 'ag-grid-react';
|
||||
|
||||
export * from './ag-grid-lazy';
|
||||
export * from './ag-grid-dynamic';
|
||||
|
||||
@ -27,3 +30,16 @@ export type VegaICellRendererParams<
|
||||
TRow,
|
||||
TField extends Field = string
|
||||
> = RowHelper<ICellRendererParams, TRow, TField>;
|
||||
|
||||
export interface GetRowsParams<T> extends IGetRowsParams {
|
||||
successCallback(rowsThisBlock: T[], lastRow?: number): void;
|
||||
}
|
||||
|
||||
export interface Datasource<T> extends IDatasource {
|
||||
getRows(params: GetRowsParams<T>): void;
|
||||
}
|
||||
|
||||
export interface TypedDataAgGrid<T> extends AgGridReactProps {
|
||||
rowData?: T[] | null;
|
||||
datasource?: Datasource<T>;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ export * from './lib/withdraw-form';
|
||||
export * from './lib/withdraw-form-container';
|
||||
export * from './lib/withdraw-manager';
|
||||
export * from './lib/withdrawals-table';
|
||||
export * from './lib/pending-withdrawals-table';
|
||||
export * from './lib/withdrawal-feedback';
|
||||
export * from './lib/use-complete-withdraw';
|
||||
export * from './lib/use-create-withdraw';
|
||||
|
65
libs/withdraws/src/lib/pending-withdrawals-table.spec.tsx
Normal file
65
libs/withdraws/src/lib/pending-withdrawals-table.spec.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { generateWithdrawal } from './test-helpers';
|
||||
import { CompleteCell } from './pending-withdrawals-table';
|
||||
import { PendingWithdrawalsTable } from './pending-withdrawals-table';
|
||||
import { getTimeFormat } from '@vegaprotocol/react-helpers';
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import type { WithdrawalFields } from './__generated__/WithdrawalFields';
|
||||
|
||||
jest.mock('@web3-react/core', () => ({
|
||||
useWeb3React: () => ({ provider: undefined }),
|
||||
}));
|
||||
|
||||
const generateTable = (props: TypedDataAgGrid<WithdrawalFields>) => (
|
||||
<MockedProvider>
|
||||
<PendingWithdrawalsTable {...props} />
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
describe('PendingWithdrawalsTable', () => {
|
||||
it('displays correct columns', async () => {
|
||||
const withdrawal = generateWithdrawal();
|
||||
await act(async () => {
|
||||
render(generateTable({ rowData: [withdrawal] }));
|
||||
});
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(5);
|
||||
expect(headers.map((h) => h.textContent?.trim())).toEqual([
|
||||
'Asset',
|
||||
'Amount',
|
||||
'Recipient',
|
||||
'Created',
|
||||
'',
|
||||
]);
|
||||
});
|
||||
it('displays given withdrawals', async () => {
|
||||
const withdrawal = generateWithdrawal();
|
||||
await act(async () => {
|
||||
render(generateTable({ rowData: [withdrawal] }));
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
'asset-symbol',
|
||||
'1.00',
|
||||
'123456…123456',
|
||||
getTimeFormat().format(new Date(withdrawal.createdTimestamp)),
|
||||
'Complete withdrawal',
|
||||
];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('CompleteCell', () => {
|
||||
const mockComplete = jest.fn();
|
||||
const data = generateWithdrawal();
|
||||
it('opens the dialog', () => {
|
||||
render(<CompleteCell complete={mockComplete} data={data} />);
|
||||
fireEvent.click(
|
||||
screen.getByText('Complete withdrawal', { selector: 'button' })
|
||||
);
|
||||
expect(mockComplete).toBeCalled();
|
||||
});
|
||||
});
|
201
libs/withdraws/src/lib/pending-withdrawals-table.tsx
Normal file
201
libs/withdraws/src/lib/pending-withdrawals-table.tsx
Normal file
@ -0,0 +1,201 @@
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import {
|
||||
getDateTimeFormat,
|
||||
t,
|
||||
truncateByChars,
|
||||
addDecimalsFormatNumber,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
TypedDataAgGrid,
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Dialog,
|
||||
Link,
|
||||
AgGridDynamic as AgGrid,
|
||||
Intent,
|
||||
Loader,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useCompleteWithdraw } from './use-complete-withdraw';
|
||||
import type { WithdrawalFields } from './__generated__/WithdrawalFields';
|
||||
import type { VerifyState } from './use-verify-withdrawal';
|
||||
import { ApprovalStatus, useVerifyWithdrawal } from './use-verify-withdrawal';
|
||||
|
||||
export const PendingWithdrawalsTable = (
|
||||
props: TypedDataAgGrid<WithdrawalFields>
|
||||
) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const {
|
||||
submit,
|
||||
reset: resetTx,
|
||||
Dialog: EthereumTransactionDialog,
|
||||
} = useCompleteWithdraw();
|
||||
const {
|
||||
verify,
|
||||
state: verifyState,
|
||||
reset: resetVerification,
|
||||
} = useVerifyWithdrawal();
|
||||
|
||||
return (
|
||||
<>
|
||||
<AgGrid
|
||||
overlayNoRowsTemplate={t('No withdrawals')}
|
||||
defaultColDef={{ flex: 1, resizable: true }}
|
||||
style={{ width: '100%' }}
|
||||
components={{ CompleteCell }}
|
||||
suppressCellFocus={true}
|
||||
domLayout="autoHeight"
|
||||
rowHeight={30}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn headerName="Asset" field="asset.symbol" />
|
||||
<AgGridColumn
|
||||
headerName={t('Amount')}
|
||||
field="amount"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<WithdrawalFields, 'amount'>) => {
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Recipient')}
|
||||
field="details.receiverAddress"
|
||||
cellRenderer={({
|
||||
ethUrl,
|
||||
value,
|
||||
valueFormatted,
|
||||
}: VegaICellRendererParams<
|
||||
WithdrawalFields,
|
||||
'details.receiverAddress'
|
||||
> & {
|
||||
ethUrl: string;
|
||||
}) => (
|
||||
<Link
|
||||
title={t('View on Etherscan (opens in a new tab)')}
|
||||
href={`${ethUrl}/address/${value}`}
|
||||
data-testid="etherscan-link"
|
||||
target="_blank"
|
||||
>
|
||||
{valueFormatted}
|
||||
</Link>
|
||||
)}
|
||||
cellRendererParams={{ ethUrl: ETHERSCAN_URL }}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
WithdrawalFields,
|
||||
'details.receiverAddress'
|
||||
>) => {
|
||||
if (!value) return '-';
|
||||
return truncateByChars(value);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Created')}
|
||||
field="createdTimestamp"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
WithdrawalFields,
|
||||
'createdTimestamp'
|
||||
>) => {
|
||||
return getDateTimeFormat().format(new Date(value));
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName=""
|
||||
field="status"
|
||||
flex={2}
|
||||
cellRendererParams={{
|
||||
complete: async (withdrawal: WithdrawalFields) => {
|
||||
const verified = await verify(withdrawal);
|
||||
|
||||
if (!verified) {
|
||||
return;
|
||||
}
|
||||
|
||||
submit(withdrawal.id);
|
||||
},
|
||||
}}
|
||||
cellRenderer="CompleteCell"
|
||||
/>
|
||||
</AgGrid>
|
||||
<Dialog
|
||||
title={t('Withdrawal verification')}
|
||||
onChange={(isOpen) => {
|
||||
if (!isOpen) {
|
||||
resetTx();
|
||||
resetVerification();
|
||||
}
|
||||
}}
|
||||
open={verifyState.dialogOpen}
|
||||
size="small"
|
||||
{...getVerifyDialogProps(verifyState.status)}
|
||||
>
|
||||
<VerificationStatus state={verifyState} />
|
||||
</Dialog>
|
||||
<EthereumTransactionDialog />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export type CompleteCellProps = {
|
||||
data: WithdrawalFields;
|
||||
complete: (withdrawal: WithdrawalFields) => void;
|
||||
};
|
||||
export const CompleteCell = ({ data, complete }: CompleteCellProps) => (
|
||||
<Button size="xs" onClick={() => complete(data)}>
|
||||
{t('Complete withdrawal')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
const getVerifyDialogProps = (status: ApprovalStatus) => {
|
||||
if (status === ApprovalStatus.Error) {
|
||||
return {
|
||||
intent: Intent.Danger,
|
||||
icon: <Icon name="warning-sign" />,
|
||||
};
|
||||
}
|
||||
|
||||
if (status === ApprovalStatus.Pending) {
|
||||
return { intent: Intent.None, icon: <Loader size="small" /> };
|
||||
}
|
||||
|
||||
if (status === ApprovalStatus.Delayed) {
|
||||
return { intent: Intent.Warning, icon: <Icon name="time" /> };
|
||||
}
|
||||
|
||||
return { intent: Intent.None };
|
||||
};
|
||||
|
||||
const VerificationStatus = ({ state }: { state: VerifyState }) => {
|
||||
if (state.status === ApprovalStatus.Error) {
|
||||
return <p>{t('Something went wrong')}</p>;
|
||||
}
|
||||
|
||||
if (state.status === ApprovalStatus.Pending) {
|
||||
return <p>{t('Verifying...')}</p>;
|
||||
}
|
||||
|
||||
if (state.status === ApprovalStatus.Delayed && state.completeTimestamp) {
|
||||
const formattedTime = getDateTimeFormat().format(
|
||||
new Date(state.completeTimestamp)
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<p className="mb-2">
|
||||
{t("The amount you're withdrawing has triggered a time delay")}
|
||||
</p>
|
||||
<p>{t(`Cannot be completed until ${formattedTime}`)}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
@ -111,11 +111,33 @@ export const useWithdrawals = () => {
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
/**
|
||||
* withdrawals that have to be completed by a user.
|
||||
*/
|
||||
const pending = useMemo(() => {
|
||||
return withdrawals.filter((w) => !w.txHash);
|
||||
}, [withdrawals]);
|
||||
|
||||
/**
|
||||
* withdrawals that are completed or being completed
|
||||
*/
|
||||
const completed = useMemo(() => {
|
||||
return withdrawals
|
||||
.filter((w) => w.txHash)
|
||||
.sort((a, b) =>
|
||||
(b.withdrawnTimestamp || b.createdTimestamp).localeCompare(
|
||||
a.withdrawnTimestamp || a.createdTimestamp
|
||||
)
|
||||
);
|
||||
}, [withdrawals]);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
withdrawals,
|
||||
pending,
|
||||
completed,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,24 +1,18 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { WithdrawalStatus, WithdrawalStatusMapping } from '@vegaprotocol/types';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { getTimeFormat } from '@vegaprotocol/react-helpers';
|
||||
import { WithdrawalStatus } from '@vegaprotocol/types';
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { generateWithdrawal } from './test-helpers';
|
||||
import type {
|
||||
StatusCellProps,
|
||||
WithdrawalsTableProps,
|
||||
} from './withdrawals-table';
|
||||
import { StatusCell } from './withdrawals-table';
|
||||
import { WithdrawalsTable } from './withdrawals-table';
|
||||
import type { Withdrawals_party_withdrawalsConnection_edges_node } from './__generated__/Withdrawals';
|
||||
import type { WithdrawalFields } from './__generated__/WithdrawalFields';
|
||||
|
||||
jest.mock('@web3-react/core', () => ({
|
||||
useWeb3React: () => ({ provider: undefined }),
|
||||
}));
|
||||
|
||||
const generateJsx = (props: WithdrawalsTableProps) => (
|
||||
const generateJsx = (props: TypedDataAgGrid<WithdrawalFields>) => (
|
||||
<MockedProvider>
|
||||
<WithdrawalsTable {...props} />
|
||||
</MockedProvider>
|
||||
@ -28,7 +22,7 @@ describe('renders the correct columns', () => {
|
||||
it('incomplete withdrawal', async () => {
|
||||
const withdrawal = generateWithdrawal();
|
||||
await act(async () => {
|
||||
render(generateJsx({ withdrawals: [withdrawal] }));
|
||||
render(generateJsx({ rowData: [withdrawal] }));
|
||||
});
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
@ -37,19 +31,19 @@ describe('renders the correct columns', () => {
|
||||
'Asset',
|
||||
'Amount',
|
||||
'Recipient',
|
||||
'Created at',
|
||||
'TX hash',
|
||||
'Completed',
|
||||
'Status',
|
||||
'Transaction',
|
||||
]);
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
'asset-symbol',
|
||||
addDecimalsFormatNumber(withdrawal.amount, withdrawal.asset.decimals),
|
||||
'123456\u2026123456',
|
||||
getDateTimeFormat().format(new Date(withdrawal.createdTimestamp)),
|
||||
'1.00',
|
||||
'123456…123456',
|
||||
'-',
|
||||
'Pending',
|
||||
'-',
|
||||
WithdrawalStatusMapping[withdrawal.status],
|
||||
];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
@ -59,21 +53,22 @@ describe('renders the correct columns', () => {
|
||||
it('completed withdrawal', async () => {
|
||||
const withdrawal = generateWithdrawal({
|
||||
txHash: '0x1234567891011121314',
|
||||
withdrawnTimestamp: '2022-04-21T00:00:00',
|
||||
status: WithdrawalStatus.STATUS_FINALIZED,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(generateJsx({ withdrawals: [withdrawal] }));
|
||||
render(generateJsx({ rowData: [withdrawal] }));
|
||||
});
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
'asset-symbol',
|
||||
addDecimalsFormatNumber(withdrawal.amount, withdrawal.asset.decimals),
|
||||
'1.00',
|
||||
'123456…123456',
|
||||
getDateTimeFormat().format(new Date(withdrawal.createdTimestamp)),
|
||||
getTimeFormat().format(new Date(withdrawal.withdrawnTimestamp as string)),
|
||||
'Completed',
|
||||
'0x1234…121314',
|
||||
WithdrawalStatusMapping[withdrawal.status],
|
||||
];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
@ -82,51 +77,47 @@ describe('renders the correct columns', () => {
|
||||
});
|
||||
|
||||
describe('StatusCell', () => {
|
||||
let props: StatusCellProps;
|
||||
let withdrawal: Withdrawals_party_withdrawalsConnection_edges_node;
|
||||
let mockComplete: jest.Mock;
|
||||
let props: { data: WithdrawalFields };
|
||||
let withdrawal: WithdrawalFields;
|
||||
|
||||
beforeEach(() => {
|
||||
withdrawal = generateWithdrawal();
|
||||
mockComplete = jest.fn();
|
||||
// @ts-ignore dont need full ICellRendererParams
|
||||
props = {
|
||||
value: withdrawal.status,
|
||||
data: withdrawal,
|
||||
complete: mockComplete,
|
||||
};
|
||||
});
|
||||
|
||||
it('Open', () => {
|
||||
props.value = WithdrawalStatus.STATUS_FINALIZED;
|
||||
props.data.pendingOnForeignChain = false;
|
||||
props.data.txHash = null;
|
||||
render(<StatusCell {...props} />);
|
||||
|
||||
expect(screen.getByText('Open')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByText('Complete', { selector: 'button' }));
|
||||
expect(mockComplete).toHaveBeenCalled();
|
||||
expect(screen.getByText('Pending')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Pending', () => {
|
||||
props.value = WithdrawalStatus.STATUS_FINALIZED;
|
||||
props.data.pendingOnForeignChain = true;
|
||||
props.data.txHash = '0x123';
|
||||
render(<StatusCell {...props} />);
|
||||
|
||||
expect(screen.getByText('Pending')).toBeInTheDocument();
|
||||
expect(screen.getByText('View on Etherscan')).toHaveAttribute(
|
||||
'href',
|
||||
expect.stringContaining(props.data.txHash)
|
||||
);
|
||||
});
|
||||
|
||||
it('Finalized', () => {
|
||||
props.value = WithdrawalStatus.STATUS_FINALIZED;
|
||||
it('Completed', () => {
|
||||
props.data.pendingOnForeignChain = false;
|
||||
props.data.txHash = '0x123';
|
||||
props.data.status = WithdrawalStatus.STATUS_FINALIZED;
|
||||
render(<StatusCell {...props} />);
|
||||
|
||||
expect(screen.getByText('Finalized')).toBeInTheDocument();
|
||||
expect(screen.getByText('Completed')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Rejected', () => {
|
||||
props.data.pendingOnForeignChain = false;
|
||||
props.data.txHash = '0x123';
|
||||
props.data.status = WithdrawalStatus.STATUS_REJECTED;
|
||||
render(<StatusCell {...props} />);
|
||||
|
||||
expect(screen.getByText('Rejected')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -6,49 +6,28 @@ import {
|
||||
addDecimalsFormatNumber,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
TypedDataAgGrid,
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Dialog,
|
||||
Link,
|
||||
AgGridDynamic as AgGrid,
|
||||
Intent,
|
||||
Loader,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Link, AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useCompleteWithdraw } from './use-complete-withdraw';
|
||||
import type { WithdrawalFields } from './__generated__/WithdrawalFields';
|
||||
import type { VerifyState } from './use-verify-withdrawal';
|
||||
import { ApprovalStatus, useVerifyWithdrawal } from './use-verify-withdrawal';
|
||||
import { WithdrawalStatus } from '@vegaprotocol/types';
|
||||
|
||||
export interface WithdrawalsTableProps {
|
||||
withdrawals: WithdrawalFields[];
|
||||
}
|
||||
|
||||
export const WithdrawalsTable = ({ withdrawals }: WithdrawalsTableProps) => {
|
||||
export const WithdrawalsTable = (props: TypedDataAgGrid<WithdrawalFields>) => {
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const {
|
||||
submit,
|
||||
reset: resetTx,
|
||||
Dialog: EthereumTransactionDialog,
|
||||
} = useCompleteWithdraw();
|
||||
const {
|
||||
verify,
|
||||
state: verifyState,
|
||||
reset: resetVerification,
|
||||
} = useVerifyWithdrawal();
|
||||
|
||||
return (
|
||||
<>
|
||||
<AgGrid
|
||||
rowData={withdrawals}
|
||||
overlayNoRowsTemplate={t('No withdrawals')}
|
||||
defaultColDef={{ flex: 1, resizable: true }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
components={{ StatusCell, RecipientCell }}
|
||||
style={{ width: '100%' }}
|
||||
components={{ RecipientCell, StatusCell }}
|
||||
suppressCellFocus={true}
|
||||
domLayout="autoHeight"
|
||||
rowHeight={30}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn headerName="Asset" field="asset.symbol" />
|
||||
<AgGridColumn
|
||||
@ -77,19 +56,26 @@ export const WithdrawalsTable = ({ withdrawals }: WithdrawalsTableProps) => {
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Created at')}
|
||||
field="createdTimestamp"
|
||||
headerName={t('Completed')}
|
||||
field="withdrawnTimestamp"
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
WithdrawalFields,
|
||||
'createdTimestamp'
|
||||
'withdrawnTimestamp'
|
||||
>) => {
|
||||
return getDateTimeFormat().format(new Date(value));
|
||||
const ts = data.withdrawnTimestamp;
|
||||
if (!ts) return '-';
|
||||
return getDateTimeFormat().format(new Date(ts));
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('TX hash')}
|
||||
headerName={t('Status')}
|
||||
field="status"
|
||||
cellRenderer="StatusCell"
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Transaction')}
|
||||
field="txHash"
|
||||
cellRenderer={({
|
||||
value,
|
||||
@ -107,80 +93,21 @@ export const WithdrawalsTable = ({ withdrawals }: WithdrawalsTableProps) => {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
field="status"
|
||||
cellRenderer="StatusCell"
|
||||
cellRendererParams={{
|
||||
complete: async (withdrawal: WithdrawalFields) => {
|
||||
const verified = await verify(withdrawal);
|
||||
|
||||
if (!verified) {
|
||||
return;
|
||||
}
|
||||
|
||||
submit(withdrawal.id);
|
||||
},
|
||||
ethUrl: ETHERSCAN_URL,
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
<Dialog
|
||||
title={t('Withdrawal verification')}
|
||||
onChange={(isOpen) => {
|
||||
if (!isOpen) {
|
||||
resetTx();
|
||||
resetVerification();
|
||||
}
|
||||
}}
|
||||
open={verifyState.dialogOpen}
|
||||
size="small"
|
||||
{...getVerifyDialogProps(verifyState.status)}
|
||||
>
|
||||
<VerificationStatus state={verifyState} />
|
||||
</Dialog>
|
||||
<EthereumTransactionDialog />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export interface StatusCellProps
|
||||
extends VegaICellRendererParams<WithdrawalFields, 'status'> {
|
||||
ethUrl: string;
|
||||
complete: (withdrawal: WithdrawalFields) => void;
|
||||
export const StatusCell = ({ data }: { data: WithdrawalFields }) => {
|
||||
if (data.pendingOnForeignChain || !data.txHash) {
|
||||
return <span>{t('Pending')}</span>;
|
||||
}
|
||||
|
||||
export const StatusCell = ({ ethUrl, data, complete }: StatusCellProps) => {
|
||||
if (data.pendingOnForeignChain) {
|
||||
return (
|
||||
<div className="flex justify-between gap-8">
|
||||
{t('Pending')}
|
||||
{data.txHash && (
|
||||
<Link
|
||||
title={t('View transaction on Etherscan')}
|
||||
href={`${ethUrl}/tx/${data.txHash}`}
|
||||
data-testid="etherscan-link"
|
||||
target="_blank"
|
||||
>
|
||||
{t('View on Etherscan')}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
if (data.status === WithdrawalStatus.STATUS_FINALIZED) {
|
||||
return <span>{t('Completed')}</span>;
|
||||
}
|
||||
|
||||
if (!data.txHash) {
|
||||
return (
|
||||
<div className="flex justify-between gap-8">
|
||||
{t('Open')}
|
||||
<button className="underline" onClick={() => complete(data)}>
|
||||
{t('Complete')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
if (data.status === WithdrawalStatus.STATUS_REJECTED) {
|
||||
return <span>{t('Rejected')}</span>;
|
||||
}
|
||||
|
||||
return <span>{t('Finalized')}</span>;
|
||||
return <span>{t('Failed')}</span>;
|
||||
};
|
||||
|
||||
export interface RecipientCellProps
|
||||
@ -204,48 +131,3 @@ const RecipientCell = ({
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const getVerifyDialogProps = (status: ApprovalStatus) => {
|
||||
if (status === ApprovalStatus.Error) {
|
||||
return {
|
||||
intent: Intent.Danger,
|
||||
icon: <Icon name="warning-sign" />,
|
||||
};
|
||||
}
|
||||
|
||||
if (status === ApprovalStatus.Pending) {
|
||||
return { intent: Intent.None, icon: <Loader size="small" /> };
|
||||
}
|
||||
|
||||
if (status === ApprovalStatus.Delayed) {
|
||||
return { intent: Intent.Warning, icon: <Icon name="time" /> };
|
||||
}
|
||||
|
||||
return { intent: Intent.None };
|
||||
};
|
||||
|
||||
const VerificationStatus = ({ state }: { state: VerifyState }) => {
|
||||
if (state.status === ApprovalStatus.Error) {
|
||||
return <p>{t('Something went wrong')}</p>;
|
||||
}
|
||||
|
||||
if (state.status === ApprovalStatus.Pending) {
|
||||
return <p>{t('Verifying...')}</p>;
|
||||
}
|
||||
|
||||
if (state.status === ApprovalStatus.Delayed && state.completeTimestamp) {
|
||||
const formattedTime = getDateTimeFormat().format(
|
||||
new Date(state.completeTimestamp)
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<p className="mb-2">
|
||||
{t("The amount you're withdrawing has triggered a time delay")}
|
||||
</p>
|
||||
<p>{t(`Cannot be completed until ${formattedTime}`)}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user