Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a8f5465f9 | ||
|
|
d8ac001926 | ||
|
|
e5d3f90d45 | ||
|
|
673c896e2f | ||
|
|
deea63fa5e | ||
|
|
d31333538b | ||
|
|
4af9979a21 |
@@ -82,7 +82,7 @@ describe('Txs infinite list item', () => {
|
||||
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders data correctly', () => {
|
||||
it('renders data even with missing time', () => {
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
@@ -105,5 +105,33 @@ describe('Txs infinite list item', () => {
|
||||
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
|
||||
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
|
||||
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
|
||||
expect(screen.getByTestId('tx-time')).toHaveTextContent('-');
|
||||
});
|
||||
|
||||
it('renders data correctly', () => {
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxsInfiniteListItem
|
||||
type="testType"
|
||||
submitter="testPubKey"
|
||||
hash="testTxHash"
|
||||
block="1"
|
||||
code={0}
|
||||
command={{}}
|
||||
createdAt="1970-11-01T18:07:15Z"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
|
||||
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
|
||||
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
|
||||
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
|
||||
expect(screen.getByTestId('tx-time').textContent).toMatch(/years ago/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { PartyLink } from '../links';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import type { Screen } from '@vegaprotocol/react-helpers';
|
||||
import { useMemo } from 'react';
|
||||
import { TimeAgo } from '../time-ago';
|
||||
|
||||
const DEFAULT_TRUNCATE_LENGTH = 7;
|
||||
|
||||
@@ -32,6 +33,7 @@ export const TxsInfiniteListItem = ({
|
||||
type,
|
||||
block,
|
||||
command,
|
||||
createdAt,
|
||||
}: Partial<BlockExplorerTransactionResult>) => {
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const idTruncateLength = useMemo(
|
||||
@@ -85,6 +87,11 @@ export const TxsInfiniteListItem = ({
|
||||
endChars={5}
|
||||
/>
|
||||
</td>
|
||||
{['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize) && (
|
||||
<td className="text-sm items-center font-mono" data-testid="tx-time">
|
||||
{createdAt ? <TimeAgo date={createdAt} /> : '-'}
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||
import EmptyList from '../empty-list/empty-list';
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface TxsInfiniteListProps {
|
||||
hasMoreTxs: boolean;
|
||||
@@ -19,7 +20,16 @@ interface ItemProps {
|
||||
}
|
||||
|
||||
const Item = ({ tx }: ItemProps) => {
|
||||
const { hash, submitter, type, command, block, code, index: blockIndex } = tx;
|
||||
const {
|
||||
hash,
|
||||
submitter,
|
||||
type,
|
||||
command,
|
||||
block,
|
||||
code,
|
||||
createdAt,
|
||||
index: blockIndex,
|
||||
} = tx;
|
||||
return (
|
||||
<TxsInfiniteListItem
|
||||
type={type}
|
||||
@@ -29,6 +39,7 @@ const Item = ({ tx }: ItemProps) => {
|
||||
hash={hash}
|
||||
block={block}
|
||||
index={blockIndex}
|
||||
createdAt={createdAt}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -39,6 +50,7 @@ export const TxsInfiniteList = ({
|
||||
className,
|
||||
hasFilters = false,
|
||||
}: TxsInfiniteListProps) => {
|
||||
const { screenSize } = useScreenDimensions();
|
||||
if (!txs || txs.length === 0) {
|
||||
if (!areTxsLoading) {
|
||||
return (
|
||||
@@ -66,6 +78,9 @@ export const TxsInfiniteList = ({
|
||||
<th>{t('Type')}</th>
|
||||
<th className="text-left">{t('From')}</th>
|
||||
<th>{t('Block')}</th>
|
||||
{['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize) && (
|
||||
<th>{t('Time')}</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -14,6 +14,10 @@ export interface BlockExplorerTransactionResult {
|
||||
value: string;
|
||||
};
|
||||
error?: string;
|
||||
// These aren't strictly optional but are new in 0.73.0 so we need to make them optional
|
||||
createdAt?: string;
|
||||
version?: string;
|
||||
pow?: string;
|
||||
}
|
||||
|
||||
export interface BlockExplorerTransactions {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"next",
|
||||
"next/core-web-vitals"
|
||||
],
|
||||
"ignorePatterns": ["!**/*", "__generated__"],
|
||||
"ignorePatterns": ["!**/*", "__generated__", ".next"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
|
||||
@@ -71,7 +71,7 @@ export const useReferral = (args: UseReferralArgs) => {
|
||||
variables: {
|
||||
code: referralSet?.id as string,
|
||||
aggregationEpochs:
|
||||
args.aggregationEpochs != null
|
||||
args.aggregationEpochs !== null
|
||||
? args.aggregationEpochs
|
||||
: DEFAULT_AGGREGATION_DAYS,
|
||||
},
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
truncateMiddle,
|
||||
ExternalLink,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
@@ -28,6 +30,7 @@ import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import maxBy from 'lodash/maxBy';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
|
||||
export const ReferralStatistics = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
@@ -201,7 +204,7 @@ export const Statistics = ({
|
||||
'Total commission (last %s epochs)',
|
||||
(details?.windowLength || DEFAULT_AGGREGATION_DAYS).toString()
|
||||
)}
|
||||
description={t('(qUSD)')}
|
||||
description={<QUSDTooltip />}
|
||||
>
|
||||
{getNumberFormat(0).format(Number(totalCommissionValue))}
|
||||
</StatTile>
|
||||
@@ -233,7 +236,12 @@ export const Statistics = ({
|
||||
<StatTile title={t('Discount')}>{discountFactorValue * 100}%</StatTile>
|
||||
);
|
||||
const runningVolumeTile = (
|
||||
<StatTile title={t('Combined volume')}>
|
||||
<StatTile
|
||||
title={t(
|
||||
'Combined volume (last %s epochs)',
|
||||
details?.windowLength.toString()
|
||||
)}
|
||||
>
|
||||
{compactNumFormat.format(runningVolumeValue)}
|
||||
</StatTile>
|
||||
);
|
||||
@@ -241,24 +249,14 @@ export const Statistics = ({
|
||||
<StatTile title={t('Epochs in set')}>{epochsValue}</StatTile>
|
||||
);
|
||||
const nextTierVolumeTile = (
|
||||
<StatTile
|
||||
title={t(
|
||||
'Volume to next tier %s',
|
||||
nextBenefitTierValue?.tier ? `(${nextBenefitTierValue.tier})` : ''
|
||||
)}
|
||||
>
|
||||
<StatTile title={t('Volume to next tier')}>
|
||||
{nextBenefitTierVolumeValue <= 0
|
||||
? '0'
|
||||
: compactNumFormat.format(nextBenefitTierVolumeValue)}
|
||||
</StatTile>
|
||||
);
|
||||
const nextTierEpochsTile = (
|
||||
<StatTile
|
||||
title={t(
|
||||
'Epochs to next tier %s',
|
||||
nextBenefitTierValue?.tier ? `(${nextBenefitTierValue.tier})` : ''
|
||||
)}
|
||||
>
|
||||
<StatTile title={t('Epochs to next tier')}>
|
||||
{nextBenefitTierEpochsValue <= 0 ? '0' : nextBenefitTierEpochsValue}
|
||||
</StatTile>
|
||||
);
|
||||
@@ -339,11 +337,16 @@ export const Statistics = ({
|
||||
},
|
||||
{
|
||||
name: 'commission',
|
||||
displayName: t(
|
||||
'Commission earned (last %s epochs)',
|
||||
(
|
||||
details?.windowLength || DEFAULT_AGGREGATION_DAYS
|
||||
).toString()
|
||||
displayName: (
|
||||
<>
|
||||
{t('Commission earned in')} <QUSDTooltip />{' '}
|
||||
{t(
|
||||
'(last %s epochs)',
|
||||
(
|
||||
details?.windowLength || DEFAULT_AGGREGATION_DAYS
|
||||
).toString()
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
@@ -373,3 +376,25 @@ export const Statistics = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const QUSDTooltip = () => (
|
||||
<Tooltip
|
||||
description={
|
||||
<>
|
||||
<p className="mb-1">
|
||||
{t(
|
||||
'qUSD provides a rough USD equivalent of balances across all assets using the value of "Quantum" for that asset'
|
||||
)}
|
||||
</p>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.QUANTUM}>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
underline={true}
|
||||
>
|
||||
<span>{t('qUSD')}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import { forwardRef, type HTMLAttributes } from 'react';
|
||||
import { forwardRef, type ReactNode, type HTMLAttributes } from 'react';
|
||||
import { BORDER_COLOR, GRADIENT } from './constants';
|
||||
|
||||
type TableColumnDefinition = {
|
||||
displayName?: string;
|
||||
displayName?: ReactNode;
|
||||
name: string;
|
||||
tooltip?: string;
|
||||
className?: string;
|
||||
@@ -46,7 +46,7 @@ export const Table = forwardRef<
|
||||
INNER_BORDER_STYLE
|
||||
)}
|
||||
>
|
||||
<span className="flex flex-row gap-2 items-center">
|
||||
<span className="flex flex-row items-center gap-2">
|
||||
<span>{displayName}</span>
|
||||
{tooltip ? (
|
||||
<Tooltip description={tooltip}>
|
||||
@@ -102,17 +102,14 @@ export const Table = forwardRef<
|
||||
key={`${i}-${name}`}
|
||||
>
|
||||
{/** display column name in mobile view */}
|
||||
{!noCollapse &&
|
||||
!noHeader &&
|
||||
displayName &&
|
||||
displayName.length > 0 && (
|
||||
<span
|
||||
aria-hidden
|
||||
className="md:hidden font-mono text-xs px-0 text-vega-clight-100 dark:text-vega-cdark-100"
|
||||
>
|
||||
{displayName}
|
||||
</span>
|
||||
)}
|
||||
{!noCollapse && !noHeader && displayName && (
|
||||
<span
|
||||
aria-hidden
|
||||
className="px-0 font-mono text-xs md:hidden text-vega-clight-100 dark:text-vega-cdark-100"
|
||||
>
|
||||
{displayName}
|
||||
</span>
|
||||
)}
|
||||
<span>{d[name]}</span>
|
||||
</td>
|
||||
))}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const Tile = ({
|
||||
|
||||
type StatTileProps = {
|
||||
title: string;
|
||||
description?: string;
|
||||
description?: ReactNode;
|
||||
children?: ReactNode;
|
||||
};
|
||||
export const StatTile = ({ title, description, children }: StatTileProps) => {
|
||||
@@ -67,11 +67,11 @@ export const CodeTile = ({
|
||||
title={t('Your referral code')}
|
||||
description={createdAt ? t('(Created at: %s)', createdAt) : undefined}
|
||||
>
|
||||
<div className="flex gap-2 items-center justify-between">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Tooltip
|
||||
description={
|
||||
<div className="break-all">
|
||||
<span className="text-xl bg-rainbow bg-clip-text text-transparent">
|
||||
<span className="text-xl text-transparent bg-rainbow bg-clip-text">
|
||||
{code}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -9,12 +9,17 @@ import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { Transfer } from '@vegaprotocol/wallet';
|
||||
import { useVegaTransactionStore } from '@vegaprotocol/web3';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { accountsDataProvider } from './accounts-data-provider';
|
||||
import { TransferForm } from './transfer-form';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import { Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const ALLOWED_ACCOUNTS = [
|
||||
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
Schema.AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
];
|
||||
|
||||
export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
const { pubKey, pubKeys } = useVegaWallet();
|
||||
const { param } = useNetworkParam(NetworkParams.transfer_fee_factor);
|
||||
@@ -33,20 +38,19 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
[create]
|
||||
);
|
||||
|
||||
const assets = useMemo(() => {
|
||||
if (!data) return [];
|
||||
return data
|
||||
.filter(
|
||||
(account) => account.type === Schema.AccountType.ACCOUNT_TYPE_GENERAL
|
||||
)
|
||||
.map((account) => ({
|
||||
id: account.asset.id,
|
||||
symbol: account.asset.symbol,
|
||||
name: account.asset.name,
|
||||
decimals: account.asset.decimals,
|
||||
balance: addDecimal(account.balance, account.asset.decimals),
|
||||
}));
|
||||
}, [data]);
|
||||
const accounts = data
|
||||
? data.filter((account) => ALLOWED_ACCOUNTS.includes(account.type))
|
||||
: [];
|
||||
|
||||
const assets = accounts.map((account) => ({
|
||||
id: account.asset.id,
|
||||
symbol: account.asset.symbol,
|
||||
name: account.asset.name,
|
||||
decimals: account.asset.decimals,
|
||||
balance: addDecimal(account.balance, account.asset.decimals),
|
||||
}));
|
||||
|
||||
if (data === null) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -69,6 +73,7 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
assetId={assetId}
|
||||
feeFactor={param}
|
||||
submitTransfer={transfer}
|
||||
accounts={accounts}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,38 @@
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { AddressField, TransferFee, TransferForm } from './transfer-form';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { addDecimal, formatNumber, removeDecimal } from '@vegaprotocol/utils';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('TransferForm', () => {
|
||||
const submit = () => fireEvent.submit(screen.getByTestId('transfer-form'));
|
||||
const submit = async () => {
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', { name: 'Confirm transfer' })
|
||||
);
|
||||
};
|
||||
|
||||
const selectAsset = async (asset: {
|
||||
id: string;
|
||||
balance: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
}) => {
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
fireEvent.change(document.querySelector('select[name="asset"]')!, {
|
||||
target: { value: asset.id },
|
||||
});
|
||||
|
||||
// assert rich select as updated
|
||||
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
||||
asset.name
|
||||
);
|
||||
expect(await screen.findByTestId('asset-balance')).toHaveTextContent(
|
||||
formatNumber(asset.balance, asset.decimals)
|
||||
);
|
||||
};
|
||||
|
||||
const amount = '100';
|
||||
const pubKey =
|
||||
'70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680';
|
||||
@@ -32,6 +52,18 @@ describe('TransferForm', () => {
|
||||
assets: [asset],
|
||||
feeFactor: '0.001',
|
||||
submitTransfer: jest.fn(),
|
||||
accounts: [
|
||||
{
|
||||
type: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
asset,
|
||||
balance: '100',
|
||||
},
|
||||
{
|
||||
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
asset,
|
||||
balance: '100',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
it('form tooltips correctly displayed', async () => {
|
||||
@@ -42,49 +74,45 @@ describe('TransferForm', () => {
|
||||
// 1003-TRAN-019
|
||||
render(<TransferForm {...props} />);
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
await selectAsset(asset);
|
||||
|
||||
// set valid amount
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: amount },
|
||||
});
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
await userEvent.type(amountInput, amount);
|
||||
expect(amountInput).toHaveValue(amount);
|
||||
|
||||
userEvent.hover(screen.getByText('Include transfer fee'));
|
||||
const includeTransferLabel = screen.getByText('Include transfer fee');
|
||||
await userEvent.hover(includeTransferLabel);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
'The fee will be taken from the amount you are transferring.'
|
||||
);
|
||||
await userEvent.unhover(screen.getByText('Include transfer fee'));
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
const transferFee = screen.getByText('Transfer fee');
|
||||
await userEvent.hover(transferFee);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
/transfer.fee.factor/
|
||||
);
|
||||
await userEvent.unhover(transferFee);
|
||||
|
||||
userEvent.hover(screen.getByText('Transfer fee'));
|
||||
const amountToBeTransferred = screen.getByText('Amount to be transferred');
|
||||
await userEvent.hover(amountToBeTransferred);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
/without the fee/
|
||||
);
|
||||
await userEvent.unhover(amountToBeTransferred);
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.hover(screen.getByText('Amount to be transferred'));
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.hover(screen.getByText('Total amount (with fee)'));
|
||||
|
||||
await waitFor(() => {
|
||||
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||
expect(tooltips[0]).toBeVisible();
|
||||
});
|
||||
const totalAmountWithFee = screen.getByText('Total amount (with fee)');
|
||||
await userEvent.hover(totalAmountWithFee);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
/total amount taken from your account/
|
||||
);
|
||||
});
|
||||
|
||||
it('validates a manually entered address', async () => {
|
||||
@@ -92,30 +120,17 @@ describe('TransferForm', () => {
|
||||
// 1003-TRAN-013
|
||||
// 1003-TRAN-004
|
||||
render(<TransferForm {...props} />);
|
||||
submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(4);
|
||||
const toggle = screen.getByText('Enter manually');
|
||||
fireEvent.click(toggle);
|
||||
await userEvent.click(toggle);
|
||||
// has switched to input
|
||||
expect(toggle).toHaveTextContent('Select from wallet');
|
||||
expect(screen.getByLabelText('Vega key')).toHaveAttribute('type', 'text');
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: 'invalid-address' },
|
||||
});
|
||||
await waitFor(() => {
|
||||
const errors = screen.getAllByTestId('input-error-text');
|
||||
expect(errors[0]).toHaveTextContent('Invalid Vega key');
|
||||
});
|
||||
|
||||
// same pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: pubKey },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const errors = screen.getAllByTestId('input-error-text');
|
||||
expect(errors[0]).toHaveTextContent('Vega key is the same');
|
||||
});
|
||||
await userEvent.type(screen.getByLabelText('Vega key'), 'invalid-address');
|
||||
expect(screen.getAllByTestId('input-error-text')[0]).toHaveTextContent(
|
||||
'Invalid Vega key'
|
||||
);
|
||||
});
|
||||
|
||||
it('validates fields and submits', async () => {
|
||||
@@ -127,28 +142,25 @@ describe('TransferForm', () => {
|
||||
render(<TransferForm {...props} />);
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
|
||||
expect(keySelect.children).toHaveLength(2);
|
||||
const keySelect = screen.getByLabelText<HTMLSelectElement>('Vega key');
|
||||
expect(keySelect.children).toHaveLength(3);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
|
||||
'',
|
||||
pubKey,
|
||||
props.pubKeys[1],
|
||||
]);
|
||||
|
||||
submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(4);
|
||||
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
await selectAsset(asset);
|
||||
|
||||
// assert rich select as updated
|
||||
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
||||
@@ -158,37 +170,38 @@ describe('TransferForm', () => {
|
||||
formatNumber(asset.balance, asset.decimals)
|
||||
);
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
|
||||
// Test amount validation
|
||||
fireEvent.change(amountInput, {
|
||||
target: { value: '0.00000001' },
|
||||
});
|
||||
await userEvent.type(amountInput, '0.00000001');
|
||||
expect(
|
||||
await screen.findByText('Value is below minimum')
|
||||
).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(amountInput, {
|
||||
target: { value: '9999999' },
|
||||
});
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, '9999999');
|
||||
expect(
|
||||
await screen.findByText(/cannot transfer more/i)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// set valid amount
|
||||
fireEvent.change(amountInput, {
|
||||
target: { value: amount },
|
||||
});
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, amount);
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(
|
||||
new BigNumber(props.feeFactor).times(amount).toFixed()
|
||||
);
|
||||
|
||||
submit();
|
||||
await submit();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
@@ -200,59 +213,50 @@ describe('TransferForm', () => {
|
||||
|
||||
describe('IncludeFeesCheckbox', () => {
|
||||
it('validates fields and submits when checkbox is checked', async () => {
|
||||
render(<TransferForm {...props} />);
|
||||
const mockSubmit = jest.fn();
|
||||
render(<TransferForm {...props} submitTransfer={mockSubmit} />);
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
|
||||
expect(keySelect.children).toHaveLength(2);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
|
||||
'',
|
||||
props.pubKeys[1],
|
||||
]);
|
||||
const keySelect = screen.getByLabelText<HTMLSelectElement>('Vega key');
|
||||
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
||||
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
||||
pubKeyOptions
|
||||
);
|
||||
|
||||
submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(4);
|
||||
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
await selectAsset(asset);
|
||||
|
||||
// assert rich select as updated
|
||||
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
||||
asset.name
|
||||
);
|
||||
expect(await screen.findByTestId('asset-balance')).toHaveTextContent(
|
||||
formatNumber(asset.balance, asset.decimals)
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
|
||||
// 1003-TRAN-022
|
||||
expect(checkbox).not.toBeChecked();
|
||||
act(() => {
|
||||
/* fire events that update state */
|
||||
// set valid amount
|
||||
fireEvent.change(amountInput, {
|
||||
target: { value: amount },
|
||||
});
|
||||
// check include fees checkbox
|
||||
fireEvent.click(checkbox);
|
||||
});
|
||||
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, amount);
|
||||
await userEvent.click(checkbox);
|
||||
|
||||
expect(checkbox).toBeChecked();
|
||||
const expectedFee = new BigNumber(amount)
|
||||
.times(props.feeFactor)
|
||||
.toFixed();
|
||||
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
|
||||
|
||||
// 1003-TRAN-020
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
|
||||
@@ -262,18 +266,17 @@ describe('TransferForm', () => {
|
||||
amount
|
||||
);
|
||||
|
||||
submit();
|
||||
await submit();
|
||||
|
||||
await waitFor(() => {
|
||||
// 1003-TRAN-023
|
||||
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(mockSubmit).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(amount, asset.decimals),
|
||||
amount: removeDecimal(expectedAmount, asset.decimals),
|
||||
oneOff: {},
|
||||
});
|
||||
});
|
||||
@@ -284,46 +287,29 @@ describe('TransferForm', () => {
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
|
||||
expect(keySelect.children).toHaveLength(2);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
|
||||
'',
|
||||
props.pubKeys[1],
|
||||
]);
|
||||
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
||||
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
||||
pubKeyOptions
|
||||
);
|
||||
|
||||
submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(4);
|
||||
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
|
||||
// assert rich select as updated
|
||||
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
||||
asset.name
|
||||
);
|
||||
expect(await screen.findByTestId('asset-balance')).toHaveTextContent(
|
||||
formatNumber(asset.balance, asset.decimals)
|
||||
);
|
||||
await selectAsset(asset);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
act(() => {
|
||||
/* fire events that update state */
|
||||
// set valid amount
|
||||
fireEvent.change(amountInput, {
|
||||
target: { value: amount },
|
||||
});
|
||||
});
|
||||
|
||||
await userEvent.type(amountInput, amount);
|
||||
expect(checkbox).not.toBeChecked();
|
||||
const expectedFee = new BigNumber(amount)
|
||||
.times(props.feeFactor)
|
||||
@@ -351,11 +337,11 @@ describe('TransferForm', () => {
|
||||
// select should be shown as multiple pubkeys provided
|
||||
expect(screen.getByText('select')).toBeInTheDocument();
|
||||
expect(screen.queryByText('input')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByText('Enter manually'));
|
||||
await userEvent.click(screen.getByText('Enter manually'));
|
||||
expect(screen.queryByText('select')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('input')).toBeInTheDocument();
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(screen.getByText('Select from wallet'));
|
||||
await userEvent.click(screen.getByText('Select from wallet'));
|
||||
expect(screen.getByText('select')).toBeInTheDocument();
|
||||
expect(screen.queryByText('input')).not.toBeInTheDocument();
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(2);
|
||||
|
||||
@@ -24,22 +24,30 @@ import type { ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { AssetOption, Balance } from '@vegaprotocol/assets';
|
||||
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
||||
|
||||
interface FormFields {
|
||||
toAddress: string;
|
||||
asset: string;
|
||||
amount: string;
|
||||
fromAccount: AccountType;
|
||||
}
|
||||
|
||||
interface Asset {
|
||||
id: string;
|
||||
symbol: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
balance: string;
|
||||
}
|
||||
|
||||
interface TransferFormProps {
|
||||
pubKey: string | null;
|
||||
pubKeys: string[] | null;
|
||||
assets: Array<{
|
||||
id: string;
|
||||
symbol: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
balance: string;
|
||||
assets: Array<Asset>;
|
||||
accounts: Array<{
|
||||
type: AccountType;
|
||||
asset: { id: string; symbol: string };
|
||||
}>;
|
||||
assetId?: string;
|
||||
feeFactor: string | null;
|
||||
@@ -53,6 +61,7 @@ export const TransferForm = ({
|
||||
assetId: initialAssetId,
|
||||
feeFactor,
|
||||
submitTransfer,
|
||||
accounts,
|
||||
}: TransferFormProps) => {
|
||||
const {
|
||||
control,
|
||||
@@ -67,6 +76,7 @@ export const TransferForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const selectedPubKey = watch('toAddress');
|
||||
const amount = watch('amount');
|
||||
const assetId = watch('asset');
|
||||
|
||||
@@ -102,10 +112,16 @@ export const TransferForm = ({
|
||||
if (!transferAmount) {
|
||||
throw new Error('Submitted transfer with no amount selected');
|
||||
}
|
||||
const transfer = normalizeTransfer(fields.toAddress, transferAmount, {
|
||||
id: asset.id,
|
||||
decimals: asset.decimals,
|
||||
});
|
||||
const transfer = normalizeTransfer(
|
||||
fields.toAddress,
|
||||
transferAmount,
|
||||
fields.fromAccount,
|
||||
AccountType.ACCOUNT_TYPE_GENERAL, // field is readonly in the form
|
||||
{
|
||||
id: asset.id,
|
||||
decimals: asset.decimals,
|
||||
}
|
||||
);
|
||||
submitTransfer(transfer);
|
||||
},
|
||||
[asset, submitTransfer, transferAmount]
|
||||
@@ -137,57 +153,53 @@ export const TransferForm = ({
|
||||
className="text-sm"
|
||||
data-testid="transfer-form"
|
||||
>
|
||||
<TradingFormGroup label="Vega key" labelFor="to-address">
|
||||
<TradingFormGroup label="Vega key" labelFor="toAddress">
|
||||
<AddressField
|
||||
pubKeys={pubKeys}
|
||||
onChange={() => setValue('toAddress', '')}
|
||||
select={
|
||||
<TradingSelect
|
||||
{...register('toAddress')}
|
||||
id="to-address"
|
||||
id="toAddress"
|
||||
defaultValue=""
|
||||
>
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{pubKeys?.length &&
|
||||
pubKeys
|
||||
.filter((pk) => pk !== pubKey) // remove currently selected pubkey
|
||||
.map((pk) => (
|
||||
pubKeys.map((pk) => {
|
||||
const text = pk === pubKey ? t('Current key: ') + pk : pk;
|
||||
|
||||
return (
|
||||
<option key={pk} value={pk}>
|
||||
{pk}
|
||||
{text}
|
||||
</option>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
}
|
||||
input={
|
||||
<TradingInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={true} // focus input immediately after is shown
|
||||
id="to-address"
|
||||
id="toAddress"
|
||||
type="text"
|
||||
{...register('toAddress', {
|
||||
validate: {
|
||||
required,
|
||||
vegaPublicKey,
|
||||
sameKey: (value) => {
|
||||
if (value === pubKey) {
|
||||
return t('Vega key is the same as current key');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
})}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{errors.toAddress?.message && (
|
||||
<TradingInputError forInput="to-address">
|
||||
<TradingInputError forInput="toAddress">
|
||||
{errors.toAddress.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label="Asset" labelFor="asset">
|
||||
<TradingFormGroup label={t('Asset')} labelFor="asset">
|
||||
<Controller
|
||||
control={control}
|
||||
name="asset"
|
||||
@@ -228,6 +240,63 @@ export const TransferForm = ({
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('From account')} labelFor="fromAccount">
|
||||
<TradingSelect
|
||||
id="fromAccount"
|
||||
defaultValue=""
|
||||
{...register('fromAccount', {
|
||||
validate: {
|
||||
required,
|
||||
sameAccount: (value) => {
|
||||
if (
|
||||
pubKey === selectedPubKey &&
|
||||
value === AccountType.ACCOUNT_TYPE_GENERAL
|
||||
) {
|
||||
return t(
|
||||
'Cannot transfer to the same account type for the connected key'
|
||||
);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
})}
|
||||
>
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{accounts
|
||||
.filter((a) => {
|
||||
if (!assetId) return true;
|
||||
return assetId === a.asset.id;
|
||||
})
|
||||
.map((a) => {
|
||||
return (
|
||||
<option value={a.type} key={`${a.type}-${a.asset.id}`}>
|
||||
{AccountTypeMapping[a.type]} ({a.asset.symbol})
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
{errors.fromAccount?.message && (
|
||||
<TradingInputError forInput="fromAccount">
|
||||
{errors.fromAccount.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('To account')} labelFor="toAccount">
|
||||
<TradingSelect
|
||||
id="toAccount"
|
||||
defaultValue={AccountType.ACCOUNT_TYPE_GENERAL}
|
||||
>
|
||||
<option value={AccountType.ACCOUNT_TYPE_GENERAL}>
|
||||
{asset
|
||||
? `${AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]} (${
|
||||
asset.symbol
|
||||
})`
|
||||
: AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]}
|
||||
</option>
|
||||
</TradingSelect>
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label="Amount" labelFor="amount">
|
||||
<TradingInput
|
||||
id="amount"
|
||||
@@ -391,7 +460,7 @@ export const AddressField = ({
|
||||
setIsInput((curr) => !curr);
|
||||
onChange();
|
||||
}}
|
||||
className="absolute top-0 right-0 ml-auto text-sm underline"
|
||||
className="absolute top-0 right-0 ml-auto text-xs underline"
|
||||
>
|
||||
{isInput ? t('Select from wallet') : t('Enter manually')}
|
||||
</button>
|
||||
|
||||
@@ -23,3 +23,4 @@ export * from './lib/type-helpers';
|
||||
export * from './lib/cells/grid-progress-bar';
|
||||
|
||||
export * from './lib/use-datagrid-events';
|
||||
export * from './lib/pagination';
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Pagination } from './pagination';
|
||||
|
||||
describe('Pagination', () => {
|
||||
const props = {
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
},
|
||||
count: 0,
|
||||
onLoad: () => undefined,
|
||||
showRetentionMessage: false,
|
||||
hasDisplayedRows: false,
|
||||
};
|
||||
|
||||
it('renders message for 0 rows', async () => {
|
||||
const mockOnLoad = jest.fn();
|
||||
render(<Pagination {...props} onLoad={mockOnLoad} />);
|
||||
expect(screen.getByText('0 rows loaded')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Load more' }));
|
||||
expect(mockOnLoad).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders mesasge for multiple rows', async () => {
|
||||
const mockOnLoad = jest.fn();
|
||||
const count = 10;
|
||||
render(<Pagination {...props} count={count} onLoad={mockOnLoad} />);
|
||||
expect(screen.getByText(`${count} rows loaded`)).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Load more' }));
|
||||
expect(mockOnLoad).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders message for a single row', async () => {
|
||||
const mockOnLoad = jest.fn();
|
||||
const count = 1;
|
||||
render(<Pagination {...props} count={count} onLoad={mockOnLoad} />);
|
||||
expect(screen.getByText(`${count} row loaded`)).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Load more' }));
|
||||
expect(mockOnLoad).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders the data rentention message', () => {
|
||||
render(<Pagination {...props} showRetentionMessage={true} />);
|
||||
expect(screen.getByText(/data node retention/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the row filter message', () => {
|
||||
render(<Pagination {...props} count={1} hasDisplayedRows={false} />);
|
||||
expect(screen.getByText(/No rows matching/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { TradingButton as Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const Pagination = ({
|
||||
count,
|
||||
pageInfo,
|
||||
onLoad,
|
||||
hasDisplayedRows,
|
||||
showRetentionMessage,
|
||||
}: {
|
||||
count: number;
|
||||
pageInfo: { hasNextPage?: boolean } | null;
|
||||
onLoad: () => void;
|
||||
hasDisplayedRows: boolean;
|
||||
showRetentionMessage: boolean;
|
||||
}) => {
|
||||
let rowMessage = '';
|
||||
|
||||
if (count && !pageInfo?.hasNextPage) {
|
||||
rowMessage = t('all %s rows loaded', count.toString());
|
||||
} else {
|
||||
if (count === 1) {
|
||||
rowMessage = t('%s row loaded', count.toString());
|
||||
} else {
|
||||
rowMessage = t('%s rows loaded', count.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between p-1 border-t border-default">
|
||||
<div className="text-xs">
|
||||
{false}
|
||||
{showRetentionMessage &&
|
||||
t(
|
||||
'Depending on data node retention you may not be able see the "full" history'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center text-xs">
|
||||
<span>{rowMessage}</span>
|
||||
{pageInfo?.hasNextPage ? (
|
||||
<Button size="extra-small" className="ml-1" onClick={onLoad}>
|
||||
{t('Load more')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
{count && hasDisplayedRows === false ? (
|
||||
<div className="absolute text-xs top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
||||
{t('No rows matching selected filters')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
|
||||
|
||||
const data: EstimateFeesQuery = {
|
||||
epoch: {
|
||||
id: '2',
|
||||
id: '3',
|
||||
},
|
||||
volumeDiscountStats: {
|
||||
edges: [
|
||||
@@ -117,7 +117,7 @@ describe('useEstimateFees', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 0 discounts if discount stats are not at the current epoch', () => {
|
||||
it('returns 0 discounts if discount stats are not at the current epoch -1', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useEstimateFees(
|
||||
{
|
||||
@@ -136,7 +136,7 @@ describe('useEstimateFees', () => {
|
||||
});
|
||||
|
||||
it('returns discounts', () => {
|
||||
data.epoch.id = '1';
|
||||
data.epoch.id = '2';
|
||||
const { result } = renderHook(() =>
|
||||
useEstimateFees(
|
||||
{
|
||||
|
||||
@@ -34,14 +34,13 @@ export const useEstimateFees = (
|
||||
skip: !pubKey || !order?.size || !order?.price || order.postOnly,
|
||||
});
|
||||
const data = loading ? currentData || previousData : currentData;
|
||||
const atEpoch = (Number(data?.epoch.id) || 0) - 1;
|
||||
const volumeDiscountFactor =
|
||||
(data?.volumeDiscountStats.edges[0]?.node.atEpoch.toString() ===
|
||||
data?.epoch.id &&
|
||||
(data?.volumeDiscountStats.edges[0]?.node.atEpoch === atEpoch &&
|
||||
data?.volumeDiscountStats.edges[0]?.node.discountFactor) ||
|
||||
'0';
|
||||
const referralDiscountFactor =
|
||||
(data?.referralSetStats.edges[0]?.node.atEpoch.toString() ===
|
||||
data?.epoch.id &&
|
||||
(data?.referralSetStats.edges[0]?.node.atEpoch === atEpoch &&
|
||||
data?.referralSetStats.edges[0]?.node.discountFactor) ||
|
||||
'0';
|
||||
if (order?.postOnly) {
|
||||
|
||||
@@ -84,6 +84,7 @@ export const DocsLinks = VEGA_DOCS_URL
|
||||
ETH_DATA_SOURCES: `${VEGA_DOCS_URL}/concepts/trading-on-vega/data-sources#ethereum-data-sources`,
|
||||
ICEBERG_ORDERS: `${VEGA_DOCS_URL}/concepts/trading-on-vega/orders#iceberg-order`,
|
||||
POST_REDUCE_ONLY: `${VEGA_DOCS_URL}/concepts/trading-on-vega/orders#conditional-order-parameters`,
|
||||
QUANTUM: `${VEGA_DOCS_URL}/concepts/assets/asset-framework#quantum`,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ import { useCallback, useRef, useState } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FillsTable } from './fills-table';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { Pagination } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import { fillsWithMarketProvider } from './fills-data-provider';
|
||||
import { TradingButton as Button } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface FillsManagerProps {
|
||||
partyId: string;
|
||||
@@ -52,31 +52,13 @@ export const FillsManager = ({
|
||||
overlayNoRowsTemplate={error ? error.message : t('No fills')}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
<div className="flex justify-between border-t border-default p-1 items-center">
|
||||
<div className="text-xs">
|
||||
{t(
|
||||
'Depending on data node retention you may not be able see the "full" history'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex text-xs items-center">
|
||||
{data?.length && !pageInfo?.hasNextPage
|
||||
? t('all %s items loaded', [data.length.toString()])
|
||||
: t('%s items loaded', [
|
||||
data?.length ? data.length.toString() : ' ',
|
||||
])}
|
||||
{pageInfo?.hasNextPage ? (
|
||||
<Button size="extra-small" className="ml-1" onClick={() => load()}>
|
||||
{t('Load more')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
{data?.length && hasDisplayedRow === false ? (
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs">
|
||||
{t('No fills matching selected filters')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Pagination
|
||||
count={data?.length || 0}
|
||||
pageInfo={pageInfo}
|
||||
showRetentionMessage={true}
|
||||
onLoad={load}
|
||||
hasDisplayedRows={hasDisplayedRow || false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,10 +2,10 @@ import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FundingPaymentsTable } from './funding-payments-table';
|
||||
import { Pagination } from '@vegaprotocol/datagrid';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { fundingPaymentsWithMarketProvider } from './funding-payments-data-provider';
|
||||
import { TradingButton as Button } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface FundingPaymentsManagerProps {
|
||||
partyId: string;
|
||||
@@ -57,30 +57,13 @@ export const FundingPaymentsManager = ({
|
||||
overlayNoRowsTemplate={error ? error.message : t('No funding payments')}
|
||||
{...props}
|
||||
/>
|
||||
<div className="flex justify-between border-t border-default p-1 items-center">
|
||||
<div className="text-xs">
|
||||
{t(
|
||||
'Depending on data node retention you may not be able see the "full" history'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex text-xs items-center">
|
||||
{data?.length && !pageInfo?.hasNextPage
|
||||
? t('all %s items loaded', [data.length.toString()])
|
||||
: t('%s items loaded', [
|
||||
data?.length ? data.length.toString() : ' ',
|
||||
])}
|
||||
{pageInfo?.hasNextPage ? (
|
||||
<Button size="extra-small" className="ml-1" onClick={() => load()}>
|
||||
{t('Load more')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
{data?.length && hasDisplayedRow === false ? (
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs">
|
||||
{t('No funding payments matching selected filters')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Pagination
|
||||
count={data?.length || 0}
|
||||
pageInfo={pageInfo}
|
||||
onLoad={load}
|
||||
hasDisplayedRows={hasDisplayedRow || false}
|
||||
showRetentionMessage={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useCallback, useRef, useState, useEffect } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { OrderListTable } from '../order-list';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { Pagination } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
|
||||
import { normalizeOrderAmendment } from '@vegaprotocol/wallet';
|
||||
@@ -11,7 +12,7 @@ import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/web3';
|
||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import { OrderViewDialog } from '../order-list/order-view-dialog';
|
||||
import { Splash, TradingButton as Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export enum Filter {
|
||||
'Open' = 'Open',
|
||||
@@ -90,61 +91,34 @@ export const OrderListManager = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full relative">
|
||||
<div className="flex flex-col h-full">
|
||||
<OrderListTable
|
||||
rowData={data}
|
||||
ref={gridRef}
|
||||
filter={filter}
|
||||
onCancel={cancel}
|
||||
onEdit={setEditOrder}
|
||||
onView={setViewOrder}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
onFilterChanged={(event) => {
|
||||
onRowDataUpdated(event);
|
||||
if (onFilterChanged) {
|
||||
onFilterChanged(event);
|
||||
}
|
||||
}}
|
||||
onRowDataUpdated={onRowDataUpdated}
|
||||
isReadOnly={isReadOnly}
|
||||
overlayNoRowsTemplate={noRowsMessage || t('No orders')}
|
||||
{...props}
|
||||
/>
|
||||
<div className="flex justify-between border-t border-default p-1 items-center">
|
||||
<div className="text-xs">
|
||||
{variables.filter?.liveOnly
|
||||
? null
|
||||
: t(
|
||||
'Depending on data node retention you may not be able see the "full" history'
|
||||
)}
|
||||
</div>
|
||||
{data ? (
|
||||
<div className="flex text-xs items-center">
|
||||
{data?.length && !pageInfo?.hasNextPage
|
||||
? t('all %s items loaded', [data.length.toString()])
|
||||
: t('%s items loaded', [
|
||||
data?.length ? data.length.toString() : ' ',
|
||||
])}
|
||||
{pageInfo?.hasNextPage ? (
|
||||
<Button
|
||||
size="extra-small"
|
||||
className="ml-1"
|
||||
onClick={() => load()}
|
||||
>
|
||||
{t('Load more')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{data?.length && hasDisplayedRow === false ? (
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs">
|
||||
{t('No orders matching selected filters')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex flex-col h-full">
|
||||
<OrderListTable
|
||||
rowData={data}
|
||||
ref={gridRef}
|
||||
filter={filter}
|
||||
onCancel={cancel}
|
||||
onEdit={setEditOrder}
|
||||
onView={setViewOrder}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
onFilterChanged={(event) => {
|
||||
onRowDataUpdated(event);
|
||||
if (onFilterChanged) {
|
||||
onFilterChanged(event);
|
||||
}
|
||||
}}
|
||||
onRowDataUpdated={onRowDataUpdated}
|
||||
isReadOnly={isReadOnly}
|
||||
overlayNoRowsTemplate={noRowsMessage || t('No orders')}
|
||||
{...props}
|
||||
/>
|
||||
<Pagination
|
||||
count={data?.length || 0}
|
||||
pageInfo={pageInfo}
|
||||
onLoad={load}
|
||||
hasDisplayedRows={hasDisplayedRow || false}
|
||||
showRetentionMessage={variables.filter?.liveOnly || true}
|
||||
/>
|
||||
</div>
|
||||
{editOrder && (
|
||||
<OrderEditDialog
|
||||
|
||||
@@ -3,10 +3,10 @@ import { tradesWithMarketProvider } from './trades-data-provider';
|
||||
import { TradesTable } from './trades-table';
|
||||
import { useDealTicketFormValues } from '@vegaprotocol/deal-ticket';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Pagination } from '@vegaprotocol/datagrid';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { TradingButton as Button } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface TradesContainerProps {
|
||||
marketId: string;
|
||||
@@ -49,30 +49,13 @@ export const TradesManager = ({
|
||||
overlayNoRowsTemplate={error ? error.message : t('No trades')}
|
||||
{...props}
|
||||
/>
|
||||
<div className="flex justify-between border-t border-default p-1 items-center">
|
||||
<div className="text-xs">
|
||||
{t(
|
||||
'Depending on data node retention you may not be able see the "full" history'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex text-xs items-center">
|
||||
{data?.length && !pageInfo?.hasNextPage
|
||||
? t('all %s items loaded', [data.length.toString()])
|
||||
: t('%s items loaded', [
|
||||
data?.length ? data.length.toString() : ' ',
|
||||
])}
|
||||
{pageInfo?.hasNextPage ? (
|
||||
<Button size="extra-small" className="ml-1" onClick={() => load()}>
|
||||
{t('Load more')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
{data?.length && hasDisplayedRow === false ? (
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xs">
|
||||
{t('No trades matching selected filters')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Pagination
|
||||
count={data?.length || 0}
|
||||
pageInfo={pageInfo}
|
||||
onLoad={load}
|
||||
hasDisplayedRows={hasDisplayedRow || false}
|
||||
showRetentionMessage={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
|
||||
import type { Market, Order } from '@vegaprotocol/types';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import type { AccountType } from '@vegaprotocol/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { sha3_256 } from 'js-sha3';
|
||||
@@ -47,15 +47,17 @@ export const normalizeOrderAmendment = <T extends Exact<OrderAmendment, T>>(
|
||||
export const normalizeTransfer = <T extends Exact<Transfer, T>>(
|
||||
address: string,
|
||||
amount: string,
|
||||
fromAccountType: AccountType,
|
||||
toAccountType: AccountType,
|
||||
asset: {
|
||||
id: string;
|
||||
decimals: number;
|
||||
}
|
||||
): Transfer => {
|
||||
return {
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: address,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountType,
|
||||
toAccountType,
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(amount, asset.decimals),
|
||||
// oneOff or recurring required otherwise wallet will error
|
||||
|
||||
Reference in New Issue
Block a user