feat(2408): trading data grid snags (#2513)
* feat: trading data grid snags * feat: fix e2e tests, fix use order list data avoid rerender condition
This commit is contained in:
parent
1f96ccea68
commit
4608683bde
@ -66,7 +66,7 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
|
|||||||
|
|
||||||
it('data should be properly rendered', () => {
|
it('data should be properly rendered', () => {
|
||||||
cy.get('.ag-center-cols-container .ag-row').should('have.length', 5);
|
cy.get('.ag-center-cols-container .ag-row').should('have.length', 5);
|
||||||
cy.get('[title="tEURO"] button').click();
|
cy.contains('.ag-center-cols-container button', 'tEURO').click();
|
||||||
cy.getByTestId('dialog-title').should(
|
cy.getByTestId('dialog-title').should(
|
||||||
'have.text',
|
'have.text',
|
||||||
'Asset details - tEURO'
|
'Asset details - tEURO'
|
||||||
|
@ -41,7 +41,7 @@ const AccountsManager = () => {
|
|||||||
const rowsThisBlock = dataRef.current
|
const rowsThisBlock = dataRef.current
|
||||||
? dataRef.current.slice(startRow, endRow)
|
? dataRef.current.slice(startRow, endRow)
|
||||||
: [];
|
: [];
|
||||||
const lastRow = dataRef.current?.length ?? -1;
|
const lastRow = dataRef.current ? dataRef.current.length : 0;
|
||||||
successCallback(rowsThisBlock, lastRow);
|
successCallback(rowsThisBlock, lastRow);
|
||||||
};
|
};
|
||||||
const { columnDefs, defaultColDef } = useAccountColumnDefinitions();
|
const { columnDefs, defaultColDef } = useAccountColumnDefinitions();
|
||||||
|
@ -5,7 +5,6 @@ import { Heading } from '../../components/heading';
|
|||||||
import { SplashLoader } from '../../components/splash-loader';
|
import { SplashLoader } from '../../components/splash-loader';
|
||||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||||
import {
|
import {
|
||||||
PendingWithdrawalsTable,
|
|
||||||
useWithdrawals,
|
useWithdrawals,
|
||||||
useWithdrawalDialog,
|
useWithdrawalDialog,
|
||||||
WithdrawalsTable,
|
WithdrawalsTable,
|
||||||
@ -30,7 +29,7 @@ const Withdrawals = ({ name }: RouteChildProps) => {
|
|||||||
const WithdrawPendingContainer = () => {
|
const WithdrawPendingContainer = () => {
|
||||||
const openWithdrawalDialog = useWithdrawalDialog((state) => state.open);
|
const openWithdrawalDialog = useWithdrawalDialog((state) => state.open);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { pending, completed, loading, error } = useWithdrawals();
|
const { data, loading, error } = useWithdrawals();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
@ -60,11 +59,7 @@ const WithdrawPendingContainer = () => {
|
|||||||
<p>{t('withdrawalsText')}</p>
|
<p>{t('withdrawalsText')}</p>
|
||||||
<p className="mb-8">{t('withdrawalsPreparedWarningText')}</p>
|
<p className="mb-8">{t('withdrawalsPreparedWarningText')}</p>
|
||||||
<div className="w-full h-[500px]">
|
<div className="w-full h-[500px]">
|
||||||
{pending && pending.length > 0 && (
|
<WithdrawalsTable rowData={data} />
|
||||||
<PendingWithdrawalsTable rowData={pending} />
|
|
||||||
)}
|
|
||||||
<h4 className="pt-3 pb-1">{t('Withdrawal history')}</h4>
|
|
||||||
<WithdrawalsTable rowData={completed} />
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -20,9 +20,19 @@ describe('accounts', { tags: '@smoke' }, () => {
|
|||||||
|
|
||||||
cy.getByTestId('tab-accounts')
|
cy.getByTestId('tab-accounts')
|
||||||
.get(tradingAccountRowId)
|
.get(tradingAccountRowId)
|
||||||
.find('[col-id="breakdown"]')
|
.find('[col-id="breakdown"] [data-testid="breakdown"]')
|
||||||
.should('have.text', 'Breakdown');
|
.should('have.text', 'Breakdown');
|
||||||
|
|
||||||
|
cy.getByTestId('tab-accounts')
|
||||||
|
.get(tradingAccountRowId)
|
||||||
|
.find('[col-id="breakdown"] [data-testid="deposit"]')
|
||||||
|
.should('have.text', 'Deposit');
|
||||||
|
|
||||||
|
cy.getByTestId('tab-accounts')
|
||||||
|
.get(tradingAccountRowId)
|
||||||
|
.find('[col-id="breakdown"] [data-testid="withdraw"]')
|
||||||
|
.should('have.text', 'Withdraw');
|
||||||
|
|
||||||
cy.getByTestId('tab-accounts')
|
cy.getByTestId('tab-accounts')
|
||||||
.get(tradingAccountRowId)
|
.get(tradingAccountRowId)
|
||||||
.find('[col-id="deposited"]')
|
.find('[col-id="deposited"]')
|
||||||
|
@ -16,7 +16,11 @@ describe('Portfolio page', { tags: '@smoke' }, () => {
|
|||||||
cy.getByTestId('"Ledger entries"').click();
|
cy.getByTestId('"Ledger entries"').click();
|
||||||
const headers = [
|
const headers = [
|
||||||
'Sender',
|
'Sender',
|
||||||
|
'Account type',
|
||||||
|
'Market',
|
||||||
'Receiver',
|
'Receiver',
|
||||||
|
'Account type',
|
||||||
|
'Market',
|
||||||
'Transfer Type',
|
'Transfer Type',
|
||||||
'Quantity',
|
'Quantity',
|
||||||
'Asset',
|
'Asset',
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
||||||
import {
|
import {
|
||||||
PendingWithdrawalsTable,
|
|
||||||
useWithdrawals,
|
useWithdrawals,
|
||||||
useWithdrawalDialog,
|
useWithdrawalDialog,
|
||||||
WithdrawalsTable,
|
WithdrawalsTable,
|
||||||
@ -9,35 +8,27 @@ import { t } from '@vegaprotocol/react-helpers';
|
|||||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||||
|
|
||||||
export const WithdrawalsContainer = () => {
|
export const WithdrawalsContainer = () => {
|
||||||
const { pending, completed, loading, error } = useWithdrawals();
|
const { data, loading, error } = useWithdrawals();
|
||||||
const openWithdrawDialog = useWithdrawalDialog((state) => state.open);
|
const openWithdrawDialog = useWithdrawalDialog((state) => state.open);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VegaWalletContainer>
|
<VegaWalletContainer>
|
||||||
<div className="h-full relative grid grid-rows-[1fr,min-content]">
|
<div className="h-full relative grid grid-rows-[1fr,min-content]">
|
||||||
<div className="h-full">
|
<div className="h-full relative">
|
||||||
<AsyncRenderer
|
<WithdrawalsTable
|
||||||
data={{ pending, completed }}
|
data-testid="withdrawals-history"
|
||||||
loading={loading}
|
rowData={data}
|
||||||
error={error}
|
noRowsOverlayComponent={() => null}
|
||||||
render={({ pending, completed }) => (
|
|
||||||
<>
|
|
||||||
{pending && pending.length > 0 && (
|
|
||||||
<>
|
|
||||||
<h4 className="pt-3 pb-1">{t('Pending withdrawals')}</h4>
|
|
||||||
<PendingWithdrawalsTable rowData={pending} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{completed && completed.length > 0 && (
|
|
||||||
<h4 className="pt-3 pb-1">{t('Withdrawal history')}</h4>
|
|
||||||
)}
|
|
||||||
<WithdrawalsTable
|
|
||||||
data-testid="withdrawals-history"
|
|
||||||
rowData={completed}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
<div className="pointer-events-none absolute inset-0">
|
||||||
|
<AsyncRenderer
|
||||||
|
data={data}
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
noDataCondition={(data) => !(data && data.length)}
|
||||||
|
noDataMessage={t('No withdrawals')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
||||||
<Button
|
<Button
|
||||||
|
@ -43,7 +43,7 @@ export const AccountManager = ({
|
|||||||
const rowsThisBlock = dataRef.current
|
const rowsThisBlock = dataRef.current
|
||||||
? dataRef.current.slice(startRow, endRow)
|
? dataRef.current.slice(startRow, endRow)
|
||||||
: [];
|
: [];
|
||||||
const lastRow = dataRef.current?.length ?? undefined;
|
const lastRow = dataRef.current ? dataRef.current.length : 0;
|
||||||
successCallback(rowsThisBlock, lastRow);
|
successCallback(rowsThisBlock, lastRow);
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
@ -58,9 +58,10 @@ export const AccountManager = ({
|
|||||||
onClickDeposit={onClickDeposit}
|
onClickDeposit={onClickDeposit}
|
||||||
onClickWithdraw={onClickWithdraw}
|
onClickWithdraw={onClickWithdraw}
|
||||||
/>
|
/>
|
||||||
<div className="pointer-events-none absolute inset-0 top-5">
|
<div className="pointer-events-none absolute inset-0">
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
data={data?.length ? data : null}
|
data={data}
|
||||||
|
noDataCondition={(data) => !(data && data.length)}
|
||||||
error={error}
|
error={error}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
noDataMessage={t('No accounts')}
|
noDataMessage={t('No accounts')}
|
||||||
|
@ -38,7 +38,7 @@ describe('AccountsTable', () => {
|
|||||||
<AccountTable rowData={singleRowData} onClickAsset={() => null} />
|
<AccountTable rowData={singleRowData} onClickAsset={() => null} />
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const expectedHeaders = ['Asset', 'Total', 'Used', 'Available', '', ''];
|
const expectedHeaders = ['Asset', 'Total', 'Used', 'Available', ''];
|
||||||
const headers = await screen.findAllByRole('columnheader');
|
const headers = await screen.findAllByRole('columnheader');
|
||||||
expect(headers).toHaveLength(expectedHeaders.length);
|
expect(headers).toHaveLength(expectedHeaders.length);
|
||||||
expect(
|
expect(
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
t,
|
t,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import type { VegaICellRendererParams } from '@vegaprotocol/ui-toolkit';
|
import type { VegaICellRendererParams } from '@vegaprotocol/ui-toolkit';
|
||||||
import { Button, ButtonLink, Dialog } from '@vegaprotocol/ui-toolkit';
|
import { ButtonLink, Dialog } from '@vegaprotocol/ui-toolkit';
|
||||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||||
import { AgGridColumn } from 'ag-grid-react';
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
@ -42,7 +42,6 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
|||||||
overlayNoRowsTemplate={t('No accounts')}
|
overlayNoRowsTemplate={t('No accounts')}
|
||||||
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
rowHeight={34}
|
|
||||||
tooltipShowDelay={500}
|
tooltipShowDelay={500}
|
||||||
defaultColDef={{
|
defaultColDef={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@ -64,7 +63,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
|||||||
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
|
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
|
||||||
return value ? (
|
return value ? (
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
data-testid="deposit"
|
data-testid="asset"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
onClickAsset(data.asset.id);
|
onClickAsset(data.asset.id);
|
||||||
@ -129,55 +128,44 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
|||||||
maxWidth={300}
|
maxWidth={300}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName=""
|
colId="breakdown"
|
||||||
field="breakdown"
|
|
||||||
minWidth={150}
|
|
||||||
cellRenderer={({
|
|
||||||
value,
|
|
||||||
}: VegaICellRendererParams<AccountFields, 'breakdown'>) => {
|
|
||||||
return (
|
|
||||||
<ButtonLink
|
|
||||||
data-testid="breakdown"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenBreakdown(!openBreakdown);
|
|
||||||
setBreakdown(value || null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Breakdown')}
|
|
||||||
</ButtonLink>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
colId="transact"
|
|
||||||
headerName=""
|
headerName=""
|
||||||
sortable={false}
|
sortable={false}
|
||||||
minWidth={250}
|
minWidth={200}
|
||||||
|
type="rightAligned"
|
||||||
cellRenderer={({
|
cellRenderer={({
|
||||||
data,
|
data,
|
||||||
}: VegaICellRendererParams<AccountFields>) => {
|
}: VegaICellRendererParams<AccountFields>) => {
|
||||||
return data ? (
|
return data ? (
|
||||||
<div className="flex gap-2 justify-end">
|
<>
|
||||||
<Button
|
<ButtonLink
|
||||||
size="xs"
|
data-testid="breakdown"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenBreakdown(!openBreakdown);
|
||||||
|
setBreakdown(data.breakdown || null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Breakdown')}
|
||||||
|
</ButtonLink>
|
||||||
|
<span className="mx-1" />
|
||||||
|
<ButtonLink
|
||||||
data-testid="deposit"
|
data-testid="deposit"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClickDeposit && onClickDeposit(data.asset.id);
|
onClickDeposit && onClickDeposit(data.asset.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('Deposit')}
|
{t('Deposit')}
|
||||||
</Button>
|
</ButtonLink>
|
||||||
|
<span className="mx-1" />
|
||||||
<Button
|
<ButtonLink
|
||||||
size="xs"
|
|
||||||
data-testid="withdraw"
|
data-testid="withdraw"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
onClickWithdraw && onClickWithdraw(data.asset.id)
|
onClickWithdraw && onClickWithdraw(data.asset.id)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t('Withdraw')}
|
{t('Withdraw')}
|
||||||
</Button>
|
</ButtonLink>
|
||||||
</div>
|
</>
|
||||||
) : null;
|
) : null;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -28,7 +28,7 @@ export const accountsQuery = (
|
|||||||
return merge(defaultAccounts, override);
|
return merge(defaultAccounts, override);
|
||||||
};
|
};
|
||||||
|
|
||||||
const accountFields: AccountFieldsFragment[] = [
|
export const accountFields: AccountFieldsFragment[] = [
|
||||||
{
|
{
|
||||||
__typename: 'AccountBalance',
|
__typename: 'AccountBalance',
|
||||||
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { FillsTable } from './fills-table';
|
import { FillsTable } from './fills-table';
|
||||||
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
|
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
|
||||||
import { useFillsList } from './use-fills-list';
|
import { useFillsList } from './use-fills-list';
|
||||||
@ -31,21 +32,25 @@ export const FillsManager = ({ partyId, marketId }: FillsManagerProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<div className="h-full relative">
|
||||||
loading={loading}
|
|
||||||
error={error}
|
|
||||||
data={data}
|
|
||||||
noDataCondition={() => false}
|
|
||||||
>
|
|
||||||
<FillsTable
|
<FillsTable
|
||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
partyId={partyId}
|
partyId={partyId}
|
||||||
rowModelType={data?.length ? 'infinite' : 'clientSide'}
|
rowModelType="infinite"
|
||||||
rowData={data?.length ? undefined : []}
|
|
||||||
datasource={{ getRows }}
|
datasource={{ getRows }}
|
||||||
onBodyScrollEnd={onBodyScrollEnd}
|
onBodyScrollEnd={onBodyScrollEnd}
|
||||||
onBodyScroll={onBodyScroll}
|
onBodyScroll={onBodyScroll}
|
||||||
|
noRowsOverlayComponent={() => null}
|
||||||
/>
|
/>
|
||||||
</AsyncRenderer>
|
<div className="pointer-events-none absolute inset-0">
|
||||||
|
<AsyncRenderer
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
data={data}
|
||||||
|
noDataMessage={t('No fills')}
|
||||||
|
noDataCondition={(data) => !(data && data.length)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -54,8 +54,12 @@ export const useFillsList = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataRef.current = data;
|
dataRef.current = data;
|
||||||
|
const avoidRerender = !!(
|
||||||
|
(dataRef.current?.length && data?.length) ||
|
||||||
|
(!dataRef.current?.length && !data?.length)
|
||||||
|
);
|
||||||
gridRef.current?.api?.refreshInfiniteCache();
|
gridRef.current?.api?.refreshInfiniteCache();
|
||||||
return true;
|
return avoidRerender;
|
||||||
}
|
}
|
||||||
dataRef.current = data;
|
dataRef.current = data;
|
||||||
return false;
|
return false;
|
||||||
|
@ -34,14 +34,14 @@ export const LedgerManager = ({ partyId }: LedgerManagerProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="h-full relative">
|
||||||
<LedgerTable
|
<LedgerTable
|
||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
rowModelType="infinite"
|
rowModelType="infinite"
|
||||||
datasource={{ getRows }}
|
datasource={{ getRows }}
|
||||||
onFilterChanged={onFilterChanged}
|
onFilterChanged={onFilterChanged}
|
||||||
/>
|
/>
|
||||||
<div className="pointer-events-none absolute inset-0 top-5">
|
<div className="pointer-events-none absolute inset-0">
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
@ -50,6 +50,6 @@ export const LedgerManager = ({ partyId }: LedgerManagerProps) => {
|
|||||||
noDataCondition={(data) => !(data && data.length)}
|
noDataCondition={(data) => !(data && data.length)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import type {
|
import type {
|
||||||
VegaValueFormatterParams,
|
VegaValueFormatterParams,
|
||||||
VegaICellRendererParams,
|
|
||||||
TypedDataAgGrid,
|
TypedDataAgGrid,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||||
@ -35,58 +34,6 @@ export const TransferTooltipCellComponent = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type LedgerCellRendererProps = {
|
|
||||||
accountType?: Types.AccountType | null;
|
|
||||||
partyId?: string | null;
|
|
||||||
marketName?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const LedgerCellRenderer = ({
|
|
||||||
accountType,
|
|
||||||
partyId,
|
|
||||||
marketName,
|
|
||||||
}: LedgerCellRendererProps) => {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col justify-around leading-5 h-full">
|
|
||||||
<div
|
|
||||||
className="flex"
|
|
||||||
title={`${t('ID')}: ${truncateByChars(partyId || '-')}`}
|
|
||||||
>
|
|
||||||
{truncateByChars(partyId || '')}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="flex"
|
|
||||||
title={`${t('Account type')}: ${
|
|
||||||
accountType ? AccountTypeMapping[accountType] : '-'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{accountType && AccountTypeMapping[accountType]}
|
|
||||||
</div>
|
|
||||||
<div className="flex" title={`${t('Market')}: ${marketName || '-'}`}>
|
|
||||||
{marketName}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const SenderCellRenderer = ({ data }: VegaICellRendererParams<LedgerEntry>) => {
|
|
||||||
const props = {
|
|
||||||
accountType: data?.senderAccountType,
|
|
||||||
partyId: data?.senderPartyId,
|
|
||||||
marketName: data?.marketSender?.tradableInstrument?.instrument?.code,
|
|
||||||
};
|
|
||||||
return <LedgerCellRenderer {...props} />;
|
|
||||||
};
|
|
||||||
const ReceiverCellRenderer = ({
|
|
||||||
data,
|
|
||||||
}: VegaICellRendererParams<LedgerEntry>) => {
|
|
||||||
const props = {
|
|
||||||
accountType: data?.receiverAccountType,
|
|
||||||
partyId: data?.receiverPartyId,
|
|
||||||
marketName: data?.marketReceiver?.tradableInstrument?.instrument?.code,
|
|
||||||
};
|
|
||||||
return <LedgerCellRenderer {...props} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
|
type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
|
||||||
|
|
||||||
export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
||||||
@ -95,7 +42,6 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
|||||||
<AgGrid
|
<AgGrid
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
overlayNoRowsTemplate={t('No entries')}
|
overlayNoRowsTemplate={t('No entries')}
|
||||||
rowHeight={70}
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
getRowId={({ data }) => data.id}
|
getRowId={({ data }) => data.id}
|
||||||
tooltipShowDelay={500}
|
tooltipShowDelay={500}
|
||||||
@ -109,13 +55,59 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
|||||||
>
|
>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Sender')}
|
headerName={t('Sender')}
|
||||||
|
field="senderPartyId"
|
||||||
|
cellRenderer={({
|
||||||
|
value,
|
||||||
|
}: VegaValueFormatterParams<LedgerEntry, 'senderPartyId'>) =>
|
||||||
|
truncateByChars(value || '')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Account type')}
|
||||||
field="senderAccountType"
|
field="senderAccountType"
|
||||||
cellRenderer={SenderCellRenderer}
|
cellRenderer={({
|
||||||
|
value,
|
||||||
|
}: VegaValueFormatterParams<LedgerEntry, 'senderAccountType'>) =>
|
||||||
|
value ? AccountTypeMapping[value] : '-'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Market')}
|
||||||
|
field="marketSender.tradableInstrument.instrument.code"
|
||||||
|
cellRenderer={({
|
||||||
|
value,
|
||||||
|
}: VegaValueFormatterParams<
|
||||||
|
LedgerEntry,
|
||||||
|
'marketSender.tradableInstrument.instrument.code'
|
||||||
|
>) => value || '-'}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Receiver')}
|
headerName={t('Receiver')}
|
||||||
|
field="receiverPartyId"
|
||||||
|
cellRenderer={({
|
||||||
|
value,
|
||||||
|
}: VegaValueFormatterParams<LedgerEntry, 'receiverPartyId'>) =>
|
||||||
|
truncateByChars(value || '')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Account type')}
|
||||||
field="receiverAccountType"
|
field="receiverAccountType"
|
||||||
cellRenderer={ReceiverCellRenderer}
|
cellRenderer={({
|
||||||
|
value,
|
||||||
|
}: VegaValueFormatterParams<LedgerEntry, 'receiverAccountType'>) =>
|
||||||
|
value ? AccountTypeMapping[value] : '-'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Market')}
|
||||||
|
field="marketReceiver.tradableInstrument.instrument.code"
|
||||||
|
cellRenderer={({
|
||||||
|
value,
|
||||||
|
}: VegaValueFormatterParams<
|
||||||
|
LedgerEntry,
|
||||||
|
'marketReceiver.tradableInstrument.instrument.code'
|
||||||
|
>) => value || '-'}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Transfer Type')}
|
headerName={t('Transfer Type')}
|
||||||
|
@ -57,7 +57,6 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
|||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
overlayNoRowsTemplate={t('No liquidity provisions')}
|
overlayNoRowsTemplate={t('No liquidity provisions')}
|
||||||
getRowId={({ data }) => getId(data)}
|
getRowId={({ data }) => getId(data)}
|
||||||
rowHeight={34}
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
tooltipShowDelay={500}
|
tooltipShowDelay={500}
|
||||||
defaultColDef={{
|
defaultColDef={{
|
||||||
|
@ -11,7 +11,7 @@ import type {
|
|||||||
VegaICellRendererParams,
|
VegaICellRendererParams,
|
||||||
TypedDataAgGrid,
|
TypedDataAgGrid,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
import { AgGridDynamic as AgGrid, ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||||
import { AgGridColumn } from 'ag-grid-react';
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
@ -58,14 +58,13 @@ export const MarketListTable = forwardRef<
|
|||||||
'tradableInstrument.instrument.product.settlementAsset'
|
'tradableInstrument.instrument.product.settlementAsset'
|
||||||
>) =>
|
>) =>
|
||||||
value ? (
|
value ? (
|
||||||
<button
|
<ButtonLink
|
||||||
className="hover:underline"
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
openAssetDetailsDialog(value.id, e.target as HTMLElement);
|
openAssetDetailsDialog(value.id, e.target as HTMLElement);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{value.symbol}
|
{value.symbol}
|
||||||
</button>
|
</ButtonLink>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
|
@ -53,7 +53,7 @@ it('Renders an error state', () => {
|
|||||||
it('Renders the order list if orders provided', async () => {
|
it('Renders the order list if orders provided', async () => {
|
||||||
// @ts-ignore Orderlist is read only but we need to override with the forwardref to
|
// @ts-ignore Orderlist is read only but we need to override with the forwardref to
|
||||||
// avoid warnings about padding refs
|
// avoid warnings about padding refs
|
||||||
orderListMock.OrderList = forwardRef(() => <div>OrderList</div>);
|
orderListMock.OrderListTable = forwardRef(() => <div>OrderList</div>);
|
||||||
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
|
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
|
||||||
data: [{ id: '1' } as OrderFieldsFragment],
|
data: [{ id: '1' } as OrderFieldsFragment],
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t, truncateByChars } from '@vegaprotocol/react-helpers';
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import type {
|
import type {
|
||||||
BodyScrollEvent,
|
BodyScrollEvent,
|
||||||
@ -7,17 +7,66 @@ import type {
|
|||||||
FilterChangedEvent,
|
FilterChangedEvent,
|
||||||
SortChangedEvent,
|
SortChangedEvent,
|
||||||
} from 'ag-grid-community';
|
} from 'ag-grid-community';
|
||||||
|
import { Button, Intent } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
|
||||||
import { OrderList } from '../order-list/order-list';
|
import { OrderListTable } from '../order-list/order-list';
|
||||||
import { useOrderListData } from './use-order-list-data';
|
import { useOrderListData } from './use-order-list-data';
|
||||||
|
import { useHasActiveOrder } from '../../order-hooks/use-has-active-order';
|
||||||
import type { Filter, Sort } from './use-order-list-data';
|
import type { Filter, Sort } from './use-order-list-data';
|
||||||
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
|
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { TransactionResult } from '@vegaprotocol/wallet';
|
||||||
|
import type { VegaTxState } from '@vegaprotocol/wallet';
|
||||||
|
import { useOrderCancel } from '../../order-hooks/use-order-cancel';
|
||||||
|
import { useOrderEdit } from '../../order-hooks/use-order-edit';
|
||||||
|
import { OrderFeedback } from '../order-feedback';
|
||||||
|
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||||
|
import type { OrderEventFieldsFragment } from '../../order-hooks';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
import type { Order } from '../order-data-provider';
|
||||||
|
|
||||||
export interface OrderListManagerProps {
|
export interface OrderListManagerProps {
|
||||||
partyId: string;
|
partyId: string;
|
||||||
marketId?: string;
|
marketId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TransactionComplete = ({
|
||||||
|
transaction,
|
||||||
|
transactionResult,
|
||||||
|
}: {
|
||||||
|
transaction: VegaTxState;
|
||||||
|
transactionResult?: TransactionResult;
|
||||||
|
}) => {
|
||||||
|
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||||
|
if (!transactionResult) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{transactionResult.status ? (
|
||||||
|
<p>{t('Transaction successful')}</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-vega-pink">
|
||||||
|
{t('Transaction failed')}: {transactionResult.error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{transaction.txHash && (
|
||||||
|
<>
|
||||||
|
<p className="font-semibold mt-4">{t('Transaction')}</p>
|
||||||
|
<p>
|
||||||
|
<Link
|
||||||
|
href={`${VEGA_EXPLORER_URL}/txs/${transaction.txHash}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{truncateByChars(transaction.txHash)}
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const OrderListManager = ({
|
export const OrderListManager = ({
|
||||||
partyId,
|
partyId,
|
||||||
marketId,
|
marketId,
|
||||||
@ -26,6 +75,10 @@ export const OrderListManager = ({
|
|||||||
const scrolledToTop = useRef(true);
|
const scrolledToTop = useRef(true);
|
||||||
const [sort, setSort] = useState<Sort[] | undefined>();
|
const [sort, setSort] = useState<Sort[] | undefined>();
|
||||||
const [filter, setFilter] = useState<Filter | undefined>();
|
const [filter, setFilter] = useState<Filter | undefined>();
|
||||||
|
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||||
|
const orderCancel = useOrderCancel();
|
||||||
|
const orderEdit = useOrderEdit(editOrder);
|
||||||
|
const hasActiveOrder = useHasActiveOrder(marketId);
|
||||||
|
|
||||||
const { data, error, loading, addNewRows, getRows } = useOrderListData({
|
const { data, error, loading, addNewRows, getRows } = useOrderListData({
|
||||||
partyId,
|
partyId,
|
||||||
@ -71,25 +124,158 @@ export const OrderListManager = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<OrderList
|
<div className="h-full relative grid grid-rows-[1fr,min-content]">
|
||||||
ref={gridRef}
|
<div className="h-full relative">
|
||||||
rowModelType="infinite"
|
<OrderListTable
|
||||||
datasource={{ getRows }}
|
ref={gridRef}
|
||||||
onBodyScrollEnd={onBodyScrollEnd}
|
rowModelType="infinite"
|
||||||
onBodyScroll={onBodyScroll}
|
datasource={{ getRows }}
|
||||||
onFilterChanged={onFilterChanged}
|
onBodyScrollEnd={onBodyScrollEnd}
|
||||||
onSortChanged={onSortChange}
|
onBodyScroll={onBodyScroll}
|
||||||
marketId={marketId}
|
onFilterChanged={onFilterChanged}
|
||||||
/>
|
onSortChanged={onSortChange}
|
||||||
<div className="pointer-events-none absolute inset-0 top-5">
|
cancel={(order: Order) => {
|
||||||
<AsyncRenderer
|
if (!order.market) return;
|
||||||
loading={loading}
|
orderCancel.cancel({
|
||||||
error={error}
|
orderId: order.id,
|
||||||
data={data}
|
marketId: order.market.id,
|
||||||
noDataMessage={t('No orders')}
|
});
|
||||||
noDataCondition={(data) => !(data && data.length)}
|
}}
|
||||||
/>
|
setEditOrder={setEditOrder}
|
||||||
|
/>
|
||||||
|
<div className="pointer-events-none absolute inset-0">
|
||||||
|
<AsyncRenderer
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
data={data}
|
||||||
|
noDataMessage={t('No orders')}
|
||||||
|
noDataCondition={(data) => !(data && data.length)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{hasActiveOrder && (
|
||||||
|
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => orderCancel.cancel({ marketId })}
|
||||||
|
data-testid="cancelAll"
|
||||||
|
>
|
||||||
|
{t('Cancel all')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<orderCancel.Dialog
|
||||||
|
title={getCancelDialogTitle(orderCancel)}
|
||||||
|
intent={getCancelDialogIntent(orderCancel)}
|
||||||
|
content={{
|
||||||
|
Complete: orderCancel.cancelledOrder ? (
|
||||||
|
<OrderFeedback
|
||||||
|
transaction={orderCancel.transaction}
|
||||||
|
order={orderCancel.cancelledOrder}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TransactionComplete {...orderCancel} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<orderEdit.Dialog
|
||||||
|
title={getEditDialogTitle(orderEdit.updatedOrder?.status)}
|
||||||
|
content={{
|
||||||
|
Complete: (
|
||||||
|
<OrderFeedback
|
||||||
|
transaction={orderEdit.transaction}
|
||||||
|
order={orderEdit.updatedOrder}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{editOrder && (
|
||||||
|
<OrderEditDialog
|
||||||
|
isOpen={Boolean(editOrder)}
|
||||||
|
onChange={(isOpen) => {
|
||||||
|
if (!isOpen) setEditOrder(null);
|
||||||
|
}}
|
||||||
|
order={editOrder}
|
||||||
|
onSubmit={(fields) => {
|
||||||
|
setEditOrder(null);
|
||||||
|
orderEdit.edit({ price: fields.limitPrice, size: fields.size });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getEditDialogTitle = (
|
||||||
|
status?: Schema.OrderStatus
|
||||||
|
): string | undefined => {
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case Schema.OrderStatus.STATUS_ACTIVE:
|
||||||
|
return t('Order updated');
|
||||||
|
case Schema.OrderStatus.STATUS_FILLED:
|
||||||
|
return t('Order filled');
|
||||||
|
case Schema.OrderStatus.STATUS_PARTIALLY_FILLED:
|
||||||
|
return t('Order partially filled');
|
||||||
|
case Schema.OrderStatus.STATUS_PARKED:
|
||||||
|
return t('Order parked');
|
||||||
|
case Schema.OrderStatus.STATUS_STOPPED:
|
||||||
|
return t('Order stopped');
|
||||||
|
case Schema.OrderStatus.STATUS_EXPIRED:
|
||||||
|
return t('Order expired');
|
||||||
|
case Schema.OrderStatus.STATUS_CANCELLED:
|
||||||
|
return t('Order cancelled');
|
||||||
|
case Schema.OrderStatus.STATUS_REJECTED:
|
||||||
|
return t('Order rejected');
|
||||||
|
default:
|
||||||
|
return t('Order amendment failed');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCancelDialogIntent = ({
|
||||||
|
cancelledOrder,
|
||||||
|
transactionResult,
|
||||||
|
}: {
|
||||||
|
cancelledOrder: OrderEventFieldsFragment | null;
|
||||||
|
transactionResult?: TransactionResult;
|
||||||
|
}): Intent | undefined => {
|
||||||
|
if (cancelledOrder) {
|
||||||
|
if (cancelledOrder.status === Schema.OrderStatus.STATUS_CANCELLED) {
|
||||||
|
return Intent.Success;
|
||||||
|
}
|
||||||
|
return Intent.Danger;
|
||||||
|
}
|
||||||
|
if (transactionResult) {
|
||||||
|
if ('error' in transactionResult && transactionResult.error) {
|
||||||
|
return Intent.Danger;
|
||||||
|
}
|
||||||
|
return Intent.Success;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCancelDialogTitle = ({
|
||||||
|
cancelledOrder,
|
||||||
|
transactionResult,
|
||||||
|
}: {
|
||||||
|
cancelledOrder: OrderEventFieldsFragment | null;
|
||||||
|
transactionResult?: TransactionResult;
|
||||||
|
}): string | undefined => {
|
||||||
|
if (cancelledOrder) {
|
||||||
|
if (cancelledOrder.status === Schema.OrderStatus.STATUS_CANCELLED) {
|
||||||
|
return t('Order cancelled');
|
||||||
|
}
|
||||||
|
return t('Order cancellation failed');
|
||||||
|
}
|
||||||
|
if (transactionResult) {
|
||||||
|
if (transactionResult.status) {
|
||||||
|
return t('Orders cancelled');
|
||||||
|
}
|
||||||
|
return t('Orders not cancelled');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
@ -155,10 +155,11 @@ export const useOrderListData = ({
|
|||||||
).length;
|
).length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataRef.current = filterOrders(data, variables);
|
const filteredData = filterOrders(data, variables);
|
||||||
|
dataRef.current = filteredData;
|
||||||
const avoidRerender = !!(
|
const avoidRerender = !!(
|
||||||
(dataRef.current?.length && data?.length) ||
|
(dataRef.current?.length && filteredData?.length) ||
|
||||||
(!dataRef.current?.length && !data?.length)
|
(!dataRef.current?.length && !filteredData?.length)
|
||||||
);
|
);
|
||||||
gridRef.current?.api?.refreshInfiniteCache();
|
gridRef.current?.api?.refreshInfiniteCache();
|
||||||
return avoidRerender;
|
return avoidRerender;
|
||||||
|
@ -16,11 +16,9 @@ import {
|
|||||||
} from '../mocks/generate-orders';
|
} from '../mocks/generate-orders';
|
||||||
|
|
||||||
const defaultProps: OrderListTableProps = {
|
const defaultProps: OrderListTableProps = {
|
||||||
hasActiveOrder: true,
|
|
||||||
rowData: [],
|
rowData: [],
|
||||||
setEditOrder: jest.fn(),
|
setEditOrder: jest.fn(),
|
||||||
cancel: jest.fn(),
|
cancel: jest.fn(),
|
||||||
cancelAll: jest.fn(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateJsx = (
|
const generateJsx = (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { Story, Meta } from '@storybook/react';
|
import type { Story, Meta } from '@storybook/react';
|
||||||
import { OrderList, OrderListTable } from './order-list';
|
import { OrderListTable } from './order-list';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { VegaTxState } from '@vegaprotocol/wallet';
|
import type { VegaTxState } from '@vegaprotocol/wallet';
|
||||||
import { VegaTransactionDialog, VegaTxStatus } from '@vegaprotocol/wallet';
|
import { VegaTransactionDialog, VegaTxStatus } from '@vegaprotocol/wallet';
|
||||||
@ -8,8 +8,8 @@ import { OrderEditDialog } from './order-edit-dialog';
|
|||||||
import type { Order } from '../order-data-provider';
|
import type { Order } from '../order-data-provider';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
component: OrderList,
|
component: OrderListTable,
|
||||||
title: 'OrderList',
|
title: 'OrderListTable',
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
const Template: Story = (args) => {
|
const Template: Story = (args) => {
|
||||||
@ -17,10 +17,8 @@ const Template: Story = (args) => {
|
|||||||
return (
|
return (
|
||||||
<div style={{ height: 1000 }}>
|
<div style={{ height: 1000 }}>
|
||||||
<OrderListTable
|
<OrderListTable
|
||||||
hasActiveOrder
|
|
||||||
rowData={args.data}
|
rowData={args.data}
|
||||||
cancel={cancel}
|
cancel={cancel}
|
||||||
cancelAll={cancel}
|
|
||||||
setEditOrder={() => {
|
setEditOrder={() => {
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
@ -47,10 +45,8 @@ const Template2: Story = (args) => {
|
|||||||
<>
|
<>
|
||||||
<div style={{ height: 1000 }}>
|
<div style={{ height: 1000 }}>
|
||||||
<OrderListTable
|
<OrderListTable
|
||||||
hasActiveOrder
|
|
||||||
rowData={args.data}
|
rowData={args.data}
|
||||||
cancel={cancel}
|
cancel={cancel}
|
||||||
cancelAll={cancel}
|
|
||||||
setEditOrder={setEditOrder}
|
setEditOrder={setEditOrder}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
|
||||||
import {
|
import {
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
getDateTimeFormat,
|
getDateTimeFormat,
|
||||||
@ -6,28 +5,19 @@ import {
|
|||||||
negativeClassNames,
|
negativeClassNames,
|
||||||
positiveClassNames,
|
positiveClassNames,
|
||||||
t,
|
t,
|
||||||
truncateByChars,
|
|
||||||
SetFilter,
|
SetFilter,
|
||||||
DateRangeFilter,
|
DateRangeFilter,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import {
|
import {
|
||||||
AgGridDynamic as AgGrid,
|
AgGridDynamic as AgGrid,
|
||||||
Button,
|
ButtonLink,
|
||||||
Intent,
|
|
||||||
Link,
|
Link,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import type { TransactionResult } from '@vegaprotocol/wallet';
|
|
||||||
import type { VegaTxState } from '@vegaprotocol/wallet';
|
|
||||||
import { AgGridColumn } from 'ag-grid-react';
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { forwardRef, useState } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import type { TypedDataAgGrid } from '@vegaprotocol/ui-toolkit';
|
import type { TypedDataAgGrid } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useOrderCancel } from '../../order-hooks/use-order-cancel';
|
|
||||||
import { useHasActiveOrder } from '../../order-hooks/use-has-active-order';
|
|
||||||
import { useOrderEdit } from '../../order-hooks/use-order-edit';
|
|
||||||
import { OrderFeedback } from '../order-feedback';
|
|
||||||
import { OrderEditDialog } from './order-edit-dialog';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
VegaICellRendererParams,
|
VegaICellRendererParams,
|
||||||
@ -35,124 +25,16 @@ import type {
|
|||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import type { Order } from '../order-data-provider';
|
import type { Order } from '../order-data-provider';
|
||||||
import type { OrderEventFieldsFragment } from '../../order-hooks';
|
|
||||||
|
|
||||||
type OrderListProps = TypedDataAgGrid<Order> & { marketId?: string };
|
type OrderListProps = TypedDataAgGrid<Order> & { marketId?: string };
|
||||||
|
|
||||||
export const TransactionComplete = ({
|
|
||||||
transaction,
|
|
||||||
transactionResult,
|
|
||||||
}: {
|
|
||||||
transaction: VegaTxState;
|
|
||||||
transactionResult?: TransactionResult;
|
|
||||||
}) => {
|
|
||||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
|
||||||
if (!transactionResult) return null;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{transactionResult.status ? (
|
|
||||||
<p>{t('Transaction successful')}</p>
|
|
||||||
) : (
|
|
||||||
<p className="text-vega-pink">
|
|
||||||
{t('Transaction failed')}: {transactionResult.error}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{transaction.txHash && (
|
|
||||||
<>
|
|
||||||
<p className="font-semibold mt-4">{t('Transaction')}</p>
|
|
||||||
<p>
|
|
||||||
<Link
|
|
||||||
href={`${VEGA_EXPLORER_URL}/txs/${transaction.txHash}`}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{truncateByChars(transaction.txHash)}
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
|
||||||
(props, ref) => {
|
|
||||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
|
||||||
const orderCancel = useOrderCancel();
|
|
||||||
const orderEdit = useOrderEdit(editOrder);
|
|
||||||
const hasActiveOrder = useHasActiveOrder(props.marketId);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<OrderListTable
|
|
||||||
{...props}
|
|
||||||
hasActiveOrder={hasActiveOrder}
|
|
||||||
cancelAll={() => {
|
|
||||||
orderCancel.cancel({
|
|
||||||
marketId: props.marketId,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
cancel={(order: Order) => {
|
|
||||||
if (!order.market) return;
|
|
||||||
orderCancel.cancel({
|
|
||||||
orderId: order.id,
|
|
||||||
marketId: order.market.id,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
ref={ref}
|
|
||||||
setEditOrder={setEditOrder}
|
|
||||||
/>
|
|
||||||
<orderCancel.Dialog
|
|
||||||
title={getCancelDialogTitle(orderCancel)}
|
|
||||||
intent={getCancelDialogIntent(orderCancel)}
|
|
||||||
content={{
|
|
||||||
Complete: orderCancel.cancelledOrder ? (
|
|
||||||
<OrderFeedback
|
|
||||||
transaction={orderCancel.transaction}
|
|
||||||
order={orderCancel.cancelledOrder}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<TransactionComplete {...orderCancel} />
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<orderEdit.Dialog
|
|
||||||
title={getEditDialogTitle(orderEdit.updatedOrder?.status)}
|
|
||||||
content={{
|
|
||||||
Complete: (
|
|
||||||
<OrderFeedback
|
|
||||||
transaction={orderEdit.transaction}
|
|
||||||
order={orderEdit.updatedOrder}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{editOrder && (
|
|
||||||
<OrderEditDialog
|
|
||||||
isOpen={Boolean(editOrder)}
|
|
||||||
onChange={(isOpen) => {
|
|
||||||
if (!isOpen) setEditOrder(null);
|
|
||||||
}}
|
|
||||||
order={editOrder}
|
|
||||||
onSubmit={(fields) => {
|
|
||||||
setEditOrder(null);
|
|
||||||
orderEdit.edit({ price: fields.limitPrice, size: fields.size });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export type OrderListTableProps = OrderListProps & {
|
export type OrderListTableProps = OrderListProps & {
|
||||||
cancel: (order: Order) => void;
|
cancel: (order: Order) => void;
|
||||||
cancelAll: () => void;
|
|
||||||
hasActiveOrder: boolean;
|
|
||||||
setEditOrder: (order: Order) => void;
|
setEditOrder: (order: Order) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||||
({ cancel, cancelAll, setEditOrder, hasActiveOrder, ...props }, ref) => {
|
({ cancel, setEditOrder, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -164,8 +46,6 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
|||||||
}}
|
}}
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
getRowId={({ data }) => data.id}
|
getRowId={({ data }) => data.id}
|
||||||
rowHeight={34}
|
|
||||||
pinnedBottomRowData={[{}]}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
@ -202,9 +82,6 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
|||||||
data,
|
data,
|
||||||
node,
|
node,
|
||||||
}: VegaValueFormatterParams<Order, 'size'>) => {
|
}: VegaValueFormatterParams<Order, 'size'>) => {
|
||||||
if (node?.rowPinned) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (!data?.market || !isNumeric(value)) {
|
if (!data?.market || !isNumeric(value)) {
|
||||||
return '-';
|
return '-';
|
||||||
}
|
}
|
||||||
@ -230,9 +107,6 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
|||||||
value,
|
value,
|
||||||
node,
|
node,
|
||||||
}: VegaValueFormatterParams<Order, 'type'>) => {
|
}: VegaValueFormatterParams<Order, 'type'>) => {
|
||||||
if (node?.rowPinned) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
if (order?.peggedOrder) return t('Pegged');
|
if (order?.peggedOrder) return t('Pegged');
|
||||||
if (order?.liquidityProvision) return t('Liquidity provision');
|
if (order?.liquidityProvision) return t('Liquidity provision');
|
||||||
@ -279,9 +153,6 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
|||||||
value,
|
value,
|
||||||
node,
|
node,
|
||||||
}: VegaValueFormatterParams<Order, 'remaining'>) => {
|
}: VegaValueFormatterParams<Order, 'remaining'>) => {
|
||||||
if (node?.rowPinned) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (!data?.market || !isNumeric(value) || !isNumeric(data.size)) {
|
if (!data?.market || !isNumeric(value) || !isNumeric(data.size)) {
|
||||||
return '-';
|
return '-';
|
||||||
}
|
}
|
||||||
@ -304,9 +175,6 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
|||||||
data,
|
data,
|
||||||
node,
|
node,
|
||||||
}: VegaValueFormatterParams<Order, 'price'>) => {
|
}: VegaValueFormatterParams<Order, 'price'>) => {
|
||||||
if (node?.rowPinned) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
!data?.market ||
|
!data?.market ||
|
||||||
data.type === Schema.OrderType.TYPE_MARKET ||
|
data.type === Schema.OrderType.TYPE_MARKET ||
|
||||||
@ -355,9 +223,6 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
|||||||
value,
|
value,
|
||||||
node,
|
node,
|
||||||
}: VegaValueFormatterParams<Order, 'updatedAt'>) => {
|
}: VegaValueFormatterParams<Order, 'updatedAt'>) => {
|
||||||
if (node?.rowPinned) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -366,44 +231,22 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
|||||||
headerName=""
|
headerName=""
|
||||||
field="status"
|
field="status"
|
||||||
minWidth={150}
|
minWidth={150}
|
||||||
|
type="rightAligned"
|
||||||
cellRenderer={({ data, node }: VegaICellRendererParams<Order>) => {
|
cellRenderer={({ data, node }: VegaICellRendererParams<Order>) => {
|
||||||
if (node?.rowPinned) {
|
return data && isOrderAmendable(data) ? (
|
||||||
return (
|
<>
|
||||||
hasActiveOrder && (
|
<ButtonLink
|
||||||
<div className="flex gap-2 items-center h-full justify-end">
|
data-testid="edit"
|
||||||
<Button
|
onClick={() => setEditOrder(data)}
|
||||||
size="xs"
|
>
|
||||||
data-testid="cancelAll"
|
{t('Edit')}
|
||||||
onClick={() => cancelAll()}
|
</ButtonLink>
|
||||||
>
|
<span className="mx-1" />
|
||||||
{t('Cancel all')}
|
<ButtonLink data-testid="cancel" onClick={() => cancel(data)}>
|
||||||
</Button>
|
{t('Cancel')}
|
||||||
</div>
|
</ButtonLink>
|
||||||
)
|
</>
|
||||||
);
|
) : null;
|
||||||
}
|
|
||||||
if (isOrderAmendable(data)) {
|
|
||||||
return data ? (
|
|
||||||
<div className="flex gap-2 items-center h-full justify-end">
|
|
||||||
<Button
|
|
||||||
data-testid="edit"
|
|
||||||
onClick={() => setEditOrder(data)}
|
|
||||||
size="xs"
|
|
||||||
>
|
|
||||||
{t('Edit')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
data-testid="cancel"
|
|
||||||
onClick={() => cancel(data)}
|
|
||||||
>
|
|
||||||
{t('Cancel')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</AgGrid>
|
</AgGrid>
|
||||||
@ -436,76 +279,3 @@ export const isOrderAmendable = (order: Order | undefined) => {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEditDialogTitle = (
|
|
||||||
status?: Schema.OrderStatus
|
|
||||||
): string | undefined => {
|
|
||||||
if (!status) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case Schema.OrderStatus.STATUS_ACTIVE:
|
|
||||||
return t('Order updated');
|
|
||||||
case Schema.OrderStatus.STATUS_FILLED:
|
|
||||||
return t('Order filled');
|
|
||||||
case Schema.OrderStatus.STATUS_PARTIALLY_FILLED:
|
|
||||||
return t('Order partially filled');
|
|
||||||
case Schema.OrderStatus.STATUS_PARKED:
|
|
||||||
return t('Order parked');
|
|
||||||
case Schema.OrderStatus.STATUS_STOPPED:
|
|
||||||
return t('Order stopped');
|
|
||||||
case Schema.OrderStatus.STATUS_EXPIRED:
|
|
||||||
return t('Order expired');
|
|
||||||
case Schema.OrderStatus.STATUS_CANCELLED:
|
|
||||||
return t('Order cancelled');
|
|
||||||
case Schema.OrderStatus.STATUS_REJECTED:
|
|
||||||
return t('Order rejected');
|
|
||||||
default:
|
|
||||||
return t('Order amendment failed');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCancelDialogIntent = ({
|
|
||||||
cancelledOrder,
|
|
||||||
transactionResult,
|
|
||||||
}: {
|
|
||||||
cancelledOrder: OrderEventFieldsFragment | null;
|
|
||||||
transactionResult?: TransactionResult;
|
|
||||||
}): Intent | undefined => {
|
|
||||||
if (cancelledOrder) {
|
|
||||||
if (cancelledOrder.status === Schema.OrderStatus.STATUS_CANCELLED) {
|
|
||||||
return Intent.Success;
|
|
||||||
}
|
|
||||||
return Intent.Danger;
|
|
||||||
}
|
|
||||||
if (transactionResult) {
|
|
||||||
if ('error' in transactionResult && transactionResult.error) {
|
|
||||||
return Intent.Danger;
|
|
||||||
}
|
|
||||||
return Intent.Success;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCancelDialogTitle = ({
|
|
||||||
cancelledOrder,
|
|
||||||
transactionResult,
|
|
||||||
}: {
|
|
||||||
cancelledOrder: OrderEventFieldsFragment | null;
|
|
||||||
transactionResult?: TransactionResult;
|
|
||||||
}): string | undefined => {
|
|
||||||
if (cancelledOrder) {
|
|
||||||
if (cancelledOrder.status === Schema.OrderStatus.STATUS_CANCELLED) {
|
|
||||||
return t('Order cancelled');
|
|
||||||
}
|
|
||||||
return t('Order cancellation failed');
|
|
||||||
}
|
|
||||||
if (transactionResult) {
|
|
||||||
if (transactionResult.status) {
|
|
||||||
return t('Orders cancelled');
|
|
||||||
}
|
|
||||||
return t('Orders not cancelled');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
@ -24,20 +24,22 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
|
|||||||
} = useClosePosition();
|
} = useClosePosition();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="h-full relative">
|
||||||
<AsyncRenderer
|
<PositionsTable
|
||||||
loading={loading}
|
ref={gridRef}
|
||||||
error={error}
|
rowData={data}
|
||||||
data={data}
|
onClose={(position) => submit(position)}
|
||||||
noDataMessage={t('No positions')}
|
noRowsOverlayComponent={() => null}
|
||||||
noDataCondition={(data) => !(data && data.length)}
|
/>
|
||||||
>
|
<div className="pointer-events-none absolute inset-0">
|
||||||
<PositionsTable
|
<AsyncRenderer
|
||||||
ref={gridRef}
|
loading={loading}
|
||||||
rowData={data}
|
error={error}
|
||||||
onClose={(position) => submit(position)}
|
data={data}
|
||||||
|
noDataMessage={t('No positions')}
|
||||||
|
noDataCondition={(data) => !(data && data.length)}
|
||||||
/>
|
/>
|
||||||
</AsyncRenderer>
|
</div>
|
||||||
<Dialog
|
<Dialog
|
||||||
intent={getDialogIntent(transactionResult)}
|
intent={getDialogIntent(transactionResult)}
|
||||||
icon={getDialogIcon(transactionResult)}
|
icon={getDialogIcon(transactionResult)}
|
||||||
@ -55,7 +57,7 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
|
|||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,12 +62,11 @@ it('Render correct columns', async () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Splits market name', async () => {
|
it('renders market name', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(<PositionsTable rowData={singleRowData} />);
|
render(<PositionsTable rowData={singleRowData} />);
|
||||||
});
|
});
|
||||||
expect(screen.getByText('ETH/BTC')).toBeTruthy();
|
expect(screen.getByText('ETH/BTC (31 july 2022)')).toBeTruthy();
|
||||||
expect(screen.getByText('31 july 2022')).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Does not fail if the market name does not match the split pattern', async () => {
|
it('Does not fail if the market name does not match the split pattern', async () => {
|
||||||
|
@ -25,30 +25,15 @@ import { AgGridColumn } from 'ag-grid-react';
|
|||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import type { Position } from './positions-data-providers';
|
import type { Position } from './positions-data-providers';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { Button, TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
import { ButtonLink, TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||||
import { getRowId } from './use-positions-data';
|
import { getRowId } from './use-positions-data';
|
||||||
|
import type { VegaICellRendererParams } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
interface Props extends TypedDataAgGrid<Position> {
|
interface Props extends TypedDataAgGrid<Position> {
|
||||||
onClose?: (data: Position) => void;
|
onClose?: (data: Position) => void;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarketNameCellProps {
|
|
||||||
valueFormatted?: [string, string];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MarketNameCell = ({ valueFormatted }: MarketNameCellProps) => {
|
|
||||||
if (valueFormatted && valueFormatted[1]) {
|
|
||||||
return (
|
|
||||||
<div className="leading-tight">
|
|
||||||
<div>{valueFormatted[0]}</div>
|
|
||||||
<div>{valueFormatted[1]}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (valueFormatted && valueFormatted[0]) || undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface AmountCellProps {
|
export interface AmountCellProps {
|
||||||
valueFormatted?: Pick<
|
valueFormatted?: Pick<
|
||||||
Position,
|
Position,
|
||||||
@ -80,24 +65,6 @@ export const AmountCell = ({ valueFormatted }: AmountCellProps) => {
|
|||||||
|
|
||||||
AmountCell.displayName = 'AmountCell';
|
AmountCell.displayName = 'AmountCell';
|
||||||
|
|
||||||
const ButtonCell = ({
|
|
||||||
onClick,
|
|
||||||
data,
|
|
||||||
}: {
|
|
||||||
onClick: (position: Position) => void;
|
|
||||||
data: Position;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
data-testid="close-position"
|
|
||||||
onClick={() => onClick(data)}
|
|
||||||
size="xs"
|
|
||||||
>
|
|
||||||
{t('Close')}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PositionsTable = forwardRef<AgGridReact, Props>(
|
export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||||
({ onClose, ...props }, ref) => {
|
({ onClose, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
@ -105,7 +72,6 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
overlayNoRowsTemplate={t('No positions')}
|
overlayNoRowsTemplate={t('No positions')}
|
||||||
getRowId={getRowId}
|
getRowId={getRowId}
|
||||||
rowHeight={34}
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
tooltipShowDelay={500}
|
tooltipShowDelay={500}
|
||||||
defaultColDef={{
|
defaultColDef={{
|
||||||
@ -119,24 +85,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
components={{ AmountCell, PriceFlashCell, ProgressBarCell }}
|
components={{ AmountCell, PriceFlashCell, ProgressBarCell }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<AgGridColumn
|
<AgGridColumn headerName={t('Market')} field="marketName" />
|
||||||
headerName={t('Market')}
|
|
||||||
field="marketName"
|
|
||||||
cellRenderer={MarketNameCell}
|
|
||||||
valueFormatter={({
|
|
||||||
value,
|
|
||||||
}: VegaValueFormatterParams<Position, 'marketName'>) => {
|
|
||||||
if (!value) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
// split market name into two parts, 'Part1 (Part2)' or 'Part1 - Part2'
|
|
||||||
const matches = value.match(/^(.*)(\((.*)\)| - (.*))\s*$/);
|
|
||||||
if (matches && matches[1] && matches[3]) {
|
|
||||||
return [matches[1].trim(), matches[3].trim()];
|
|
||||||
}
|
|
||||||
return [value];
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Notional')}
|
headerName={t('Notional')}
|
||||||
headerTooltip={t('Mark price x open volume.')}
|
headerTooltip={t('Mark price x open volume.')}
|
||||||
@ -413,12 +362,15 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
|||||||
/>
|
/>
|
||||||
{onClose ? (
|
{onClose ? (
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
cellRendererSelector={(): CellRendererSelectorResult => {
|
type="rightAligned"
|
||||||
return {
|
cellRenderer={({ data }: VegaICellRendererParams<Position>) => (
|
||||||
component: ButtonCell,
|
<ButtonLink
|
||||||
};
|
data-testid="close-position"
|
||||||
}}
|
onClick={() => data && onClose(data)}
|
||||||
cellRendererParams={{ onClick: onClose }}
|
>
|
||||||
|
{t('Close')}
|
||||||
|
</ButtonLink>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</AgGrid>
|
</AgGrid>
|
||||||
|
@ -20,13 +20,17 @@ const agGridDarkVariables = `
|
|||||||
|
|
||||||
.ag-theme-balham-dark .ag-row {
|
.ag-theme-balham-dark .ag-row {
|
||||||
border-width: 1px 0;
|
border-width: 1px 0;
|
||||||
border-bottom: solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-theme-balham-dark .ag-react-container {
|
.ag-theme-balham-dark .ag-react-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-theme-balham-dark .ag-cell, .ag-theme-balham-dark .ag-full-width-row .ag-cell-wrapper.ag-row-group {
|
||||||
|
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AgGrid = ({
|
export const AgGrid = ({
|
||||||
|
@ -20,13 +20,17 @@ const agGridLightVariables = `
|
|||||||
|
|
||||||
.ag-theme-balham .ag-row {
|
.ag-theme-balham .ag-row {
|
||||||
border-width: 1px 0;
|
border-width: 1px 0;
|
||||||
border-bottom: solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ag-theme-balham .ag-react-container {
|
.ag-theme-balham .ag-react-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-theme-balham .ag-cell, .ag-theme-balham .ag-full-width-row .ag-cell-wrapper.ag-row-group {
|
||||||
|
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AgGrid = ({
|
export const AgGrid = ({
|
||||||
|
@ -4,7 +4,6 @@ export * from './lib/withdraw-form';
|
|||||||
export * from './lib/withdraw-form-container';
|
export * from './lib/withdraw-form-container';
|
||||||
export * from './lib/withdraw-manager';
|
export * from './lib/withdraw-manager';
|
||||||
export * from './lib/withdrawals-table';
|
export * from './lib/withdrawals-table';
|
||||||
export * from './lib/pending-withdrawals-table';
|
|
||||||
export * from './lib/withdrawal-feedback';
|
export * from './lib/withdrawal-feedback';
|
||||||
export * from './lib/use-complete-withdraw';
|
export * from './lib/use-complete-withdraw';
|
||||||
export * from './lib/use-create-withdraw';
|
export * from './lib/use-create-withdraw';
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
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 { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
|
|
||||||
|
|
||||||
jest.mock('@web3-react/core', () => ({
|
|
||||||
useWeb3React: () => ({ provider: undefined }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const generateTable = (props: TypedDataAgGrid<WithdrawalFieldsFragment>) => (
|
|
||||||
<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();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,177 +0,0 @@
|
|||||||
import { AgGridColumn } from 'ag-grid-react';
|
|
||||||
import {
|
|
||||||
getDateTimeFormat,
|
|
||||||
t,
|
|
||||||
truncateByChars,
|
|
||||||
addDecimalsFormatNumber,
|
|
||||||
isNumeric,
|
|
||||||
} from '@vegaprotocol/react-helpers';
|
|
||||||
import type {
|
|
||||||
TypedDataAgGrid,
|
|
||||||
VegaICellRendererParams,
|
|
||||||
VegaValueFormatterParams,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import {
|
|
||||||
Link,
|
|
||||||
AgGridDynamic as AgGrid,
|
|
||||||
Intent,
|
|
||||||
Loader,
|
|
||||||
Icon,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
|
||||||
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
|
|
||||||
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
|
|
||||||
import type { VerifyState } from './use-verify-withdrawal';
|
|
||||||
import { ApprovalStatus } from './use-verify-withdrawal';
|
|
||||||
|
|
||||||
export const PendingWithdrawalsTable = (
|
|
||||||
props: TypedDataAgGrid<WithdrawalFieldsFragment>
|
|
||||||
) => {
|
|
||||||
const { ETHERSCAN_URL } = useEnvironment();
|
|
||||||
const createWithdrawApproval = useEthWithdrawApprovalsStore(
|
|
||||||
(store) => store.create
|
|
||||||
);
|
|
||||||
|
|
||||||
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<WithdrawalFieldsFragment, 'amount'>) => {
|
|
||||||
return isNumeric(value) && data?.asset
|
|
||||||
? addDecimalsFormatNumber(value, data.asset.decimals)
|
|
||||||
: null;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Recipient')}
|
|
||||||
field="details.receiverAddress"
|
|
||||||
cellRenderer={({
|
|
||||||
ethUrl,
|
|
||||||
value,
|
|
||||||
valueFormatted,
|
|
||||||
}: VegaICellRendererParams<
|
|
||||||
WithdrawalFieldsFragment,
|
|
||||||
'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<
|
|
||||||
WithdrawalFieldsFragment,
|
|
||||||
'details.receiverAddress'
|
|
||||||
>) => {
|
|
||||||
if (!value) return '-';
|
|
||||||
return truncateByChars(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Created')}
|
|
||||||
field="createdTimestamp"
|
|
||||||
valueFormatter={({
|
|
||||||
value,
|
|
||||||
}: VegaValueFormatterParams<
|
|
||||||
WithdrawalFieldsFragment,
|
|
||||||
'createdTimestamp'
|
|
||||||
>) => {
|
|
||||||
return value ? getDateTimeFormat().format(new Date(value)) : '';
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AgGridColumn
|
|
||||||
headerName=""
|
|
||||||
field="status"
|
|
||||||
flex={2}
|
|
||||||
cellRendererParams={{
|
|
||||||
complete: (withdrawal: WithdrawalFieldsFragment) => {
|
|
||||||
createWithdrawApproval(withdrawal);
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
cellRenderer="CompleteCell"
|
|
||||||
/>
|
|
||||||
</AgGrid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CompleteCellProps = {
|
|
||||||
data: WithdrawalFieldsFragment;
|
|
||||||
complete: (withdrawal: WithdrawalFieldsFragment) => void;
|
|
||||||
};
|
|
||||||
export const CompleteCell = ({ data, complete }: CompleteCellProps) => (
|
|
||||||
<Button
|
|
||||||
data-testid="complete-withdrawal"
|
|
||||||
size="xs"
|
|
||||||
onClick={() => complete(data)}
|
|
||||||
>
|
|
||||||
{t('Complete withdrawal')}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
|
|
||||||
export 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 };
|
|
||||||
};
|
|
||||||
|
|
||||||
export 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;
|
|
||||||
};
|
|
@ -1,8 +1,7 @@
|
|||||||
import orderBy from 'lodash/orderBy';
|
|
||||||
import type { UpdateQueryFn } from '@apollo/client/core/watchQueryOptions';
|
import type { UpdateQueryFn } from '@apollo/client/core/watchQueryOptions';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import uniqBy from 'lodash/uniqBy';
|
import uniqBy from 'lodash/uniqBy';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
useWithdrawalsQuery,
|
useWithdrawalsQuery,
|
||||||
WithdrawalEventDocument,
|
WithdrawalEventDocument,
|
||||||
@ -27,7 +26,7 @@ export const useWithdrawals = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pubKey) return;
|
if (!pubKey) return;
|
||||||
|
|
||||||
const unsub = subscribeToMore<
|
const unsubscribe = subscribeToMore<
|
||||||
WithdrawalEventSubscription,
|
WithdrawalEventSubscription,
|
||||||
WithdrawalEventSubscriptionVariables
|
WithdrawalEventSubscriptionVariables
|
||||||
>({
|
>({
|
||||||
@ -37,49 +36,23 @@ export const useWithdrawals = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsub();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, [pubKey, subscribeToMore]);
|
}, [pubKey, subscribeToMore]);
|
||||||
|
|
||||||
const withdrawals = useMemo(() => {
|
|
||||||
if (!data?.party?.withdrawalsConnection?.edges) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return orderBy(
|
|
||||||
removePaginationWrapper(data.party.withdrawalsConnection.edges),
|
|
||||||
'createdTimestamp',
|
|
||||||
'desc'
|
|
||||||
);
|
|
||||||
}, [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 {
|
return {
|
||||||
data,
|
data: removePaginationWrapper(
|
||||||
|
data?.party?.withdrawalsConnection?.edges ?? []
|
||||||
|
).sort((a, b) => {
|
||||||
|
if (!b.txHash !== !a.txHash) {
|
||||||
|
return b.txHash ? -1 : 1;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
b.txHash ? b.withdrawnTimestamp : b.createdTimestamp
|
||||||
|
).localeCompare(a.txHash ? a.withdrawnTimestamp : a.createdTimestamp);
|
||||||
|
}),
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
withdrawals,
|
|
||||||
pending,
|
|
||||||
completed,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,11 +26,12 @@ describe('renders the correct columns', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const headers = screen.getAllByRole('columnheader');
|
const headers = screen.getAllByRole('columnheader');
|
||||||
expect(headers).toHaveLength(6);
|
expect(headers).toHaveLength(7);
|
||||||
expect(headers.map((h) => h.textContent?.trim())).toEqual([
|
expect(headers.map((h) => h.textContent?.trim())).toEqual([
|
||||||
'Asset',
|
'Asset',
|
||||||
'Amount',
|
'Amount',
|
||||||
'Recipient',
|
'Recipient',
|
||||||
|
'Created',
|
||||||
'Completed',
|
'Completed',
|
||||||
'Status',
|
'Status',
|
||||||
'Transaction',
|
'Transaction',
|
||||||
@ -41,9 +42,10 @@ describe('renders the correct columns', () => {
|
|||||||
'asset-symbol',
|
'asset-symbol',
|
||||||
'1.00',
|
'1.00',
|
||||||
'123456…123456',
|
'123456…123456',
|
||||||
|
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)),
|
||||||
'-',
|
'-',
|
||||||
'Pending',
|
'Pending',
|
||||||
'-',
|
'Complete withdrawal',
|
||||||
];
|
];
|
||||||
cells.forEach((cell, i) => {
|
cells.forEach((cell, i) => {
|
||||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||||
@ -66,6 +68,7 @@ describe('renders the correct columns', () => {
|
|||||||
'asset-symbol',
|
'asset-symbol',
|
||||||
'1.00',
|
'1.00',
|
||||||
'123456…123456',
|
'123456…123456',
|
||||||
|
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)),
|
||||||
getTimeFormat().format(new Date(withdrawal.withdrawnTimestamp as string)),
|
getTimeFormat().format(new Date(withdrawal.withdrawnTimestamp as string)),
|
||||||
'Completed',
|
'Completed',
|
||||||
'0x1234…121314',
|
'0x1234…121314',
|
||||||
|
@ -11,25 +11,42 @@ import type {
|
|||||||
VegaICellRendererParams,
|
VegaICellRendererParams,
|
||||||
VegaValueFormatterParams,
|
VegaValueFormatterParams,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { Link, AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
Link,
|
||||||
|
ButtonLink,
|
||||||
|
AgGridDynamic as AgGrid,
|
||||||
|
Intent,
|
||||||
|
Icon,
|
||||||
|
Loader,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
|
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
|
||||||
|
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
import type { VerifyState } from './use-verify-withdrawal';
|
||||||
|
import { ApprovalStatus } from './use-verify-withdrawal';
|
||||||
|
|
||||||
export const WithdrawalsTable = (
|
export const WithdrawalsTable = (
|
||||||
props: TypedDataAgGrid<WithdrawalFieldsFragment>
|
props: TypedDataAgGrid<WithdrawalFieldsFragment>
|
||||||
) => {
|
) => {
|
||||||
const { ETHERSCAN_URL } = useEnvironment();
|
const { ETHERSCAN_URL } = useEnvironment();
|
||||||
|
const createWithdrawApproval = useEthWithdrawApprovalsStore(
|
||||||
|
(store) => store.create
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
overlayNoRowsTemplate={t('No withdrawals')}
|
overlayNoRowsTemplate={t('No withdrawals')}
|
||||||
defaultColDef={{ flex: 1, resizable: true }}
|
defaultColDef={{ flex: 1, resizable: true }}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
components={{ RecipientCell, StatusCell }}
|
components={{
|
||||||
|
RecipientCell,
|
||||||
|
StatusCell,
|
||||||
|
EtherscanLinkCell,
|
||||||
|
CompleteCell,
|
||||||
|
}}
|
||||||
suppressCellFocus={true}
|
suppressCellFocus={true}
|
||||||
domLayout="autoHeight"
|
domLayout="autoHeight"
|
||||||
rowHeight={30}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<AgGridColumn headerName="Asset" field="asset.symbol" />
|
<AgGridColumn headerName="Asset" field="asset.symbol" />
|
||||||
@ -60,19 +77,25 @@ export const WithdrawalsTable = (
|
|||||||
return truncateByChars(value);
|
return truncateByChars(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Created')}
|
||||||
|
field="createdTimestamp"
|
||||||
|
valueFormatter={({
|
||||||
|
value,
|
||||||
|
}: VegaValueFormatterParams<
|
||||||
|
WithdrawalFieldsFragment,
|
||||||
|
'createdTimestamp'
|
||||||
|
>) => (value ? getDateTimeFormat().format(new Date(value)) : '-')}
|
||||||
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Completed')}
|
headerName={t('Completed')}
|
||||||
field="withdrawnTimestamp"
|
field="withdrawnTimestamp"
|
||||||
valueFormatter={({
|
valueFormatter={({
|
||||||
data,
|
value,
|
||||||
}: VegaValueFormatterParams<
|
}: VegaValueFormatterParams<
|
||||||
WithdrawalFieldsFragment,
|
WithdrawalFieldsFragment,
|
||||||
'withdrawnTimestamp'
|
'withdrawnTimestamp'
|
||||||
>) => {
|
>) => (value ? getDateTimeFormat().format(new Date(value)) : '-')}
|
||||||
const ts = data?.withdrawnTimestamp;
|
|
||||||
if (!ts) return '-';
|
|
||||||
return getDateTimeFormat().format(new Date(ts));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Status')}
|
headerName={t('Status')}
|
||||||
@ -82,26 +105,57 @@ export const WithdrawalsTable = (
|
|||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
headerName={t('Transaction')}
|
headerName={t('Transaction')}
|
||||||
field="txHash"
|
field="txHash"
|
||||||
cellRenderer={({
|
flex={2}
|
||||||
value,
|
cellRendererParams={{
|
||||||
}: VegaValueFormatterParams<WithdrawalFieldsFragment, 'txHash'>) => {
|
complete: (withdrawal: WithdrawalFieldsFragment) => {
|
||||||
if (!value) return '-';
|
createWithdrawApproval(withdrawal);
|
||||||
return (
|
},
|
||||||
<Link
|
|
||||||
title={t('View transaction on Etherscan')}
|
|
||||||
href={`${ETHERSCAN_URL}/tx/${value}`}
|
|
||||||
data-testid="etherscan-link"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{truncateByChars(value)}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
|
cellRendererSelector={({
|
||||||
|
data,
|
||||||
|
}: VegaICellRendererParams<WithdrawalFieldsFragment>) => ({
|
||||||
|
component: data?.txHash ? 'EtherscanLinkCell' : 'CompleteCell',
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</AgGrid>
|
</AgGrid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CompleteCellProps = {
|
||||||
|
data: WithdrawalFieldsFragment;
|
||||||
|
complete: (withdrawal: WithdrawalFieldsFragment) => void;
|
||||||
|
};
|
||||||
|
export const CompleteCell = ({ data, complete }: CompleteCellProps) =>
|
||||||
|
data.pendingOnForeignChain ? (
|
||||||
|
'-'
|
||||||
|
) : (
|
||||||
|
<ButtonLink
|
||||||
|
data-testid="complete-withdrawal"
|
||||||
|
onClick={() => complete(data)}
|
||||||
|
>
|
||||||
|
{t('Complete withdrawal')}
|
||||||
|
</ButtonLink>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const EtherscanLinkCell = ({
|
||||||
|
value,
|
||||||
|
ethUrl,
|
||||||
|
}: VegaValueFormatterParams<WithdrawalFieldsFragment, 'txHash'> & {
|
||||||
|
ethUrl: string;
|
||||||
|
}) => {
|
||||||
|
if (!value) return '-';
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
title={t('View transaction on Etherscan')}
|
||||||
|
href={`${ethUrl}/tx/${value}`}
|
||||||
|
data-testid="etherscan-link"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{truncateByChars(value)}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const StatusCell = ({ data }: { data: WithdrawalFieldsFragment }) => {
|
export const StatusCell = ({ data }: { data: WithdrawalFieldsFragment }) => {
|
||||||
if (data.pendingOnForeignChain || !data.txHash) {
|
if (data.pendingOnForeignChain || !data.txHash) {
|
||||||
return <span>{t('Pending')}</span>;
|
return <span>{t('Pending')}</span>;
|
||||||
@ -139,3 +193,48 @@ const RecipientCell = ({
|
|||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export 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 };
|
||||||
|
};
|
||||||
|
|
||||||
|
export 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