feat(withdraws): improve ux surrounding withdraw balances thresholds and delays (#3402)
Co-authored-by: m.ray <16125548+MadalinaRaicu@users.noreply.github.com>
This commit is contained in:
parent
45f2e926c3
commit
bbfda65bcc
@ -11,6 +11,7 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { Asset } from './asset-data-provider';
|
||||
import { WITHDRAW_THRESHOLD_TOOLTIP_TEXT } from './constants';
|
||||
|
||||
type Rows = {
|
||||
key: AssetDetail;
|
||||
@ -121,9 +122,7 @@ export const rows: Rows = [
|
||||
{
|
||||
key: AssetDetail.WITHDRAWAL_THRESHOLD,
|
||||
label: t('Withdrawal threshold'),
|
||||
tooltip: t(
|
||||
'The maximum you can withdraw instantly. There’s no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them'
|
||||
),
|
||||
tooltip: WITHDRAW_THRESHOLD_TOOLTIP_TEXT,
|
||||
value: (asset) =>
|
||||
num(asset, (asset.source as Schema.ERC20).withdrawThreshold),
|
||||
},
|
||||
|
4
libs/assets/src/lib/constants.ts
Normal file
4
libs/assets/src/lib/constants.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
export const WITHDRAW_THRESHOLD_TOOLTIP_TEXT = t(
|
||||
"The maximum you can withdraw instantly. There's no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them"
|
||||
);
|
@ -5,3 +5,4 @@ export * from './assets-data-provider';
|
||||
export * from './asset-details-dialog';
|
||||
export * from './asset-details-table';
|
||||
export * from './asset-option';
|
||||
export * from './constants';
|
||||
|
@ -70,6 +70,8 @@ export const Notification = ({
|
||||
'text-vega-green dark:text-vega-green': intent === Intent.Success,
|
||||
'text-yellow-600 dark:text-yellow': intent === Intent.Warning,
|
||||
'text-vega-pink': intent === Intent.Danger,
|
||||
'mt-1': !!title,
|
||||
'mt-[0.125rem]': !title,
|
||||
},
|
||||
'flex items-start mt-1'
|
||||
)}
|
||||
@ -78,11 +80,16 @@ export const Notification = ({
|
||||
</div>
|
||||
<div className="flex flex-col flex-grow items-start gap-1.5">
|
||||
{title && (
|
||||
<div className="whitespace-nowrap overflow-hidden text-ellipsis uppercase leading-6">
|
||||
<div
|
||||
key="title"
|
||||
className="whitespace-nowrap overflow-hidden text-ellipsis uppercase leading-6"
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-sm [word-break:break-word]">{message}</div>
|
||||
<div key="message" className="text-sm [word-break:break-word]">
|
||||
{message}
|
||||
</div>
|
||||
{buttonProps && (
|
||||
<Button
|
||||
size={buttonProps.size || 'sm'}
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
removeDecimal,
|
||||
required,
|
||||
isAssetTypeERC20,
|
||||
formatNumber,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
@ -14,12 +15,16 @@ import {
|
||||
FormGroup,
|
||||
Input,
|
||||
InputError,
|
||||
Notification,
|
||||
RichSelect,
|
||||
ExternalLink,
|
||||
Intent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ButtonHTMLAttributes } from 'react';
|
||||
import type { ControllerRenderProps } from 'react-hook-form';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { useForm, Controller, useWatch } from 'react-hook-form';
|
||||
import type { WithdrawalArgs } from './use-create-withdraw';
|
||||
import { WithdrawLimits } from './withdraw-limits';
|
||||
@ -45,6 +50,47 @@ export interface WithdrawFormProps {
|
||||
submitWithdraw: (withdrawal: WithdrawalArgs) => void;
|
||||
}
|
||||
|
||||
const WithdrawDelayNotification = ({
|
||||
threshold,
|
||||
delay,
|
||||
symbol,
|
||||
decimals,
|
||||
}: {
|
||||
threshold: BigNumber;
|
||||
delay: number | undefined;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
}) => {
|
||||
const replacements = [
|
||||
symbol,
|
||||
delay ? formatDistanceToNow(Date.now() + delay * 1000) : ' ',
|
||||
];
|
||||
return (
|
||||
<Notification
|
||||
intent={Intent.Warning}
|
||||
testId={
|
||||
threshold.isFinite()
|
||||
? 'amount-withdrawal-delay-notification'
|
||||
: 'withdrawals-delay-notification'
|
||||
}
|
||||
message={[
|
||||
!threshold.isFinite()
|
||||
? t('All %s withdrawals are subject to a %s delay.', replacements)
|
||||
: t('Withdrawals of %s %s or more will be delayed for %s.', [
|
||||
formatNumber(threshold, decimals),
|
||||
...replacements,
|
||||
]),
|
||||
<ExternalLink
|
||||
className="ml-1"
|
||||
href="https://docs.vega.xyz/testnet/concepts/deposits-withdrawals#withdrawal-limits"
|
||||
>
|
||||
{t('Read more')}
|
||||
</ExternalLink>,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithdrawForm = ({
|
||||
assets,
|
||||
balance,
|
||||
@ -113,6 +159,12 @@ export const WithdrawForm = ({
|
||||
);
|
||||
};
|
||||
|
||||
const showWithdrawDelayNotification =
|
||||
delay &&
|
||||
selectedAsset &&
|
||||
(!threshold.isFinite() ||
|
||||
new BigNumber(amount).isGreaterThanOrEqualTo(threshold));
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 text-sm">
|
||||
@ -206,6 +258,16 @@ export const WithdrawForm = ({
|
||||
{t('Use maximum')}
|
||||
</UseButton>
|
||||
)}
|
||||
{showWithdrawDelayNotification && (
|
||||
<div className="mt-2">
|
||||
<WithdrawDelayNotification
|
||||
threshold={threshold}
|
||||
symbol={selectedAsset.symbol}
|
||||
decimals={selectedAsset.decimals}
|
||||
delay={delay}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</FormGroup>
|
||||
<Button
|
||||
data-testid="submit-withdrawal"
|
||||
|
@ -1,7 +1,12 @@
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { CompactNumber } from '@vegaprotocol/react-helpers';
|
||||
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
|
||||
import { WITHDRAW_THRESHOLD_TOOLTIP_TEXT } from '@vegaprotocol/assets';
|
||||
import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
|
||||
@ -25,7 +30,13 @@ export const WithdrawLimits = ({
|
||||
? formatDistanceToNow(Date.now() + delay * 1000)
|
||||
: t('None');
|
||||
|
||||
const limits = [
|
||||
const limits: {
|
||||
key: string;
|
||||
label: string;
|
||||
value: string | JSX.Element;
|
||||
rawValue?: BigNumber;
|
||||
tooltip?: string;
|
||||
}[] = [
|
||||
{
|
||||
key: 'BALANCE_AVAILABLE',
|
||||
label: t('Balance available'),
|
||||
@ -36,24 +47,35 @@ export const WithdrawLimits = ({
|
||||
'-'
|
||||
),
|
||||
},
|
||||
{
|
||||
];
|
||||
if (threshold.isFinite()) {
|
||||
limits.push({
|
||||
key: 'WITHDRAWAL_THRESHOLD',
|
||||
label: t('Delayed withdrawal threshold'),
|
||||
tooltip: WITHDRAW_THRESHOLD_TOOLTIP_TEXT,
|
||||
rawValue: threshold,
|
||||
value: <CompactNumber number={threshold} decimals={asset.decimals} />,
|
||||
},
|
||||
{
|
||||
key: 'DELAY_TIME',
|
||||
label: t('Delay time'),
|
||||
value: delayTime,
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
limits.push({
|
||||
key: 'DELAY_TIME',
|
||||
label: t('Delay time'),
|
||||
value: delayTime,
|
||||
});
|
||||
|
||||
return (
|
||||
<KeyValueTable>
|
||||
{limits.map(({ key, label, rawValue, value }) => (
|
||||
{limits.map(({ key, label, rawValue, value, tooltip }) => (
|
||||
<KeyValueTableRow key={key}>
|
||||
<div data-testid={`${key}_label`}>{label}</div>
|
||||
<div data-testid={`${key}_label`}>
|
||||
{tooltip ? (
|
||||
<Tooltip description={tooltip}>
|
||||
<span>{label}</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
label
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
data-testid={`${key}_value`}
|
||||
className="truncate"
|
||||
|
@ -12,15 +12,17 @@ jest.mock('@web3-react/core', () => ({
|
||||
useWeb3React: () => ({ account: ethereumAddress }),
|
||||
}));
|
||||
|
||||
const withdrawAsset = {
|
||||
asset,
|
||||
balance: new BigNumber(1),
|
||||
min: new BigNumber(0.0000001),
|
||||
threshold: new BigNumber(1000),
|
||||
delay: 10,
|
||||
handleSelectAsset: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('./use-withdraw-asset', () => ({
|
||||
useWithdrawAsset: () => ({
|
||||
asset,
|
||||
balance: new BigNumber(1),
|
||||
min: new BigNumber(0.0000001),
|
||||
threshold: new BigNumber(1000),
|
||||
delay: 10,
|
||||
handleSelectAsset: jest.fn(),
|
||||
}),
|
||||
useWithdrawAsset: () => withdrawAsset,
|
||||
}));
|
||||
|
||||
jest.mock('@vegaprotocol/web3', () => ({
|
||||
@ -109,4 +111,25 @@ describe('WithdrawManager', () => {
|
||||
});
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
};
|
||||
|
||||
it('shows withdraw delay notification if amount greater than threshold', async () => {
|
||||
render(generateJsx(props));
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '1000' },
|
||||
});
|
||||
expect(
|
||||
await screen.findByTestId('amount-withdrawal-delay-notification')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows withdraw delay notification if threshold is 0', async () => {
|
||||
withdrawAsset.threshold = new BigNumber(Infinity);
|
||||
render(generateJsx(props));
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '0.01' },
|
||||
});
|
||||
expect(
|
||||
await screen.findByTestId('withdrawals-delay-notification')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -70,7 +70,7 @@
|
||||
"react-hook-form": "^7.27.0",
|
||||
"react-i18next": "^11.11.4",
|
||||
"react-intersection-observer": "^9.2.2",
|
||||
"react-markdown": "^8.0.5",
|
||||
"react-markdown": "^8.0.6",
|
||||
"react-router-dom": "^6.9.0",
|
||||
"react-syntax-highlighter": "^15.4.5",
|
||||
"react-use-websocket": "^3.0.0",
|
||||
|
@ -19541,10 +19541,10 @@ react-lifecycles-compat@^3.0.4:
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||
|
||||
react-markdown@^8.0.5:
|
||||
version "8.0.5"
|
||||
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.5.tgz#c9a70a33ca9aeeafb769c6582e7e38843b9d70ad"
|
||||
integrity sha512-jGJolWWmOWAvzf+xMdB9zwStViODyyFQhNB/bwCerbBKmrTmgmA599CGiOlP58OId1IMoIRsA8UdI1Lod4zb5A==
|
||||
react-markdown@^8.0.6:
|
||||
version "8.0.6"
|
||||
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.6.tgz#3e939018f8bfce800ffdf22cf50aba3cdded7ad1"
|
||||
integrity sha512-KgPWsYgHuftdx510wwIzpwf+5js/iHqBR+fzxefv8Khk3mFbnioF1bmL2idHN3ler0LMQmICKeDrWnZrX9mtbQ==
|
||||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
"@types/prop-types" "^15.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user