chore: format limits (547) (#1742)

This commit is contained in:
Art 2022-10-19 16:23:18 +02:00 committed by GitHub
parent 5d04efe52d
commit 50611a4ba6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 275 additions and 102 deletions

View File

@ -77,18 +77,24 @@ describe('withdraw', { tags: '@smoke' }, () => {
},
});
selectAsset(asset1Name);
cy.getByTestId('balance-available')
.should('contain.text', 'Balance available')
.find('td')
.should('have.text', '1,000.00000');
cy.getByTestId('withdrawal-threshold')
.should('contain.text', 'Delayed withdrawal threshold')
.find('td')
.should('contain.text', '1m+');
cy.getByTestId('delay-time')
.should('contain.text', 'Delay time')
.find('td')
.should('have.text', 'None');
cy.getByTestId('BALANCE_AVAILABLE_label').should(
'contain.text',
'Balance available'
);
cy.getByTestId('BALANCE_AVAILABLE_value').should(
'have.text',
'1,000.00000'
);
cy.getByTestId('WITHDRAWAL_THRESHOLD_label').should(
'contain.text',
'Delayed withdrawal threshold'
);
cy.getByTestId('WITHDRAWAL_THRESHOLD_value').should(
'contain.text',
'100.00000'
);
cy.getByTestId('DELAY_TIME_label').should('contain.text', 'Delay time');
cy.getByTestId('DELAY_TIME_value').should('have.text', 'None');
cy.get(amountField).clear().type('10');
cy.getByTestId(submitWithdrawBtn).click();
cy.getByTestId('dialog-title').should(

View File

@ -227,20 +227,12 @@ describe('Deposit form', () => {
);
// Check deposit limit is displayed
expect(
screen.getByText('Balance available', { selector: 'th' })
.nextElementSibling
).toHaveTextContent(balance.toString());
expect(
screen.getByText('Maximum total deposit amount', { selector: 'th' })
.nextElementSibling
).toHaveTextContent(max.toString());
expect(
screen.getByText('Deposited', { selector: 'th' }).nextElementSibling
).toHaveTextContent(deposited.toString());
expect(
screen.getByText('Remaining', { selector: 'th' }).nextElementSibling
).toHaveTextContent(max.minus(deposited).toString());
expect(screen.getByTestId('BALANCE_AVAILABLE_value')).toHaveTextContent(
'50'
);
expect(screen.getByTestId('MAX_LIMIT_value')).toHaveTextContent('20');
expect(screen.getByTestId('DEPOSITED_value')).toHaveTextContent('10');
expect(screen.getByTestId('REMAINING_value')).toHaveTextContent('10');
fireEvent.change(screen.getByLabelText('Amount'), {
target: { value: '8' },

View File

@ -220,7 +220,12 @@ export const DepositForm = ({
</FormGroup>
{selectedAsset && max && deposited && (
<div className="mb-6">
<DepositLimits max={max} deposited={deposited} balance={balance} />
<DepositLimits
max={max}
deposited={deposited}
balance={balance}
asset={selectedAsset}
/>
</div>
)}
<FormGroup label={t('Amount')} labelFor="amount">

View File

@ -1,60 +1,65 @@
import { formatNumber, t } from '@vegaprotocol/react-helpers';
import type { Asset } from '@vegaprotocol/assets';
import { compactNumber, t } from '@vegaprotocol/react-helpers';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import type BigNumber from 'bignumber.js';
// Note: all of the values here are with correct asset's decimals
// See `libs/deposits/src/lib/use-deposit-balances.ts`
interface DepositLimitsProps {
max: BigNumber;
deposited: BigNumber;
asset: Asset;
balance?: BigNumber;
}
export const DepositLimits = ({
max,
deposited,
asset,
balance,
}: DepositLimitsProps) => {
let maxLimit = '';
if (max.isEqualTo(Infinity)) {
maxLimit = t('No limit');
} else if (max.isGreaterThan(1_000_000)) {
maxLimit = t('1m+');
} else {
maxLimit = max.toString();
}
let remaining = '';
if (deposited.isEqualTo(0)) {
remaining = maxLimit;
} else {
const amountRemaining = max.minus(deposited);
remaining = amountRemaining.isGreaterThan(1_000_000)
? t('1m+')
: amountRemaining.toString();
}
const limits = [
{
key: 'BALANCE_AVAILABLE',
label: t('Balance available'),
rawValue: balance,
value: balance ? compactNumber(balance, asset.decimals) : '-',
},
{
key: 'MAX_LIMIT',
label: t('Maximum total deposit amount'),
rawValue: max,
value: compactNumber(max, asset.decimals),
},
{
key: 'DEPOSITED',
label: t('Deposited'),
rawValue: deposited,
value: compactNumber(deposited, asset.decimals),
},
{
key: 'REMAINING',
label: t('Remaining'),
rawValue: max.minus(deposited),
value: compactNumber(max.minus(deposited), asset.decimals),
},
];
return (
<table className="w-full text-sm">
<tbody>
<tr>
<th className="text-left font-normal">{t('Balance available')}</th>
<td className="text-right">
{balance ? formatNumber(balance) : '-'}
</td>
</tr>
<tr>
<th className="text-left font-normal">
{t('Maximum total deposit amount')}
</th>
<td className="text-right">{maxLimit}</td>
</tr>
<tr>
<th className="text-left font-normal">{t('Deposited')}</th>
<td className="text-right">{formatNumber(deposited)}</td>
</tr>
<tr>
<th className="text-left font-normal">{t('Remaining')}</th>
<td className="text-right">{remaining}</td>
</tr>
</tbody>
</table>
<KeyValueTable>
{limits.map(({ key, label, rawValue, value }) => (
<KeyValueTableRow>
<div data-testid={`${key}_label`}>{label}</div>
<div
data-testid={`${key}_value`}
className="truncate"
title={rawValue?.toString()}
>
{value}
</div>
</KeyValueTableRow>
))}
</KeyValueTable>
);
};

View File

@ -4,6 +4,7 @@ import {
formatNumberPercentage,
toNumberParts,
isNumeric,
compactNumber,
} from './number';
describe('formatNumber and formatNumberPercentage', () => {
@ -89,3 +90,93 @@ describe('isNumeric', () => {
}
);
});
describe('compactNumber', () => {
const short: [BigNumber, string | JSX.Element, number | 'infer'][] = [
[new BigNumber(Infinity), '∞', 'infer'],
[new BigNumber(-Infinity), '-∞', 'infer'],
[new BigNumber(0), '0', 'infer'],
[new BigNumber(1), '1', 'infer'],
[new BigNumber(100), '100', 'infer'],
[new BigNumber(100.456601), '100.456601', 'infer'],
[new BigNumber(1_000), '1,000', 'infer'],
[new BigNumber(999_999), '999,999', 'infer'],
[new BigNumber(1_000_000), '1M', 'infer'],
[new BigNumber(100_000_000), '100M', 'infer'],
[new BigNumber(1_000_000_000), '1B', 'infer'],
[new BigNumber(1_000_000_000_000), '1T', 'infer'],
[new BigNumber(3.23e12), '3.23T', 2],
[new BigNumber(3.23e12), '3.23000T', 5],
[
new BigNumber(3.23e24),
<span>
3.23000{' '}
<span>
&times; 10<sup>24</sup>
</span>
</span>,
5,
],
[
new BigNumber(1.579208923731619e59),
<span>
1.57921{' '}
<span>
&times; 10
<sup>59</sup>
</span>
</span>,
5,
],
];
it.each(short)(
'compacts %d to %p (decimal places: %p)',
(input, output, decimals) => {
expect(compactNumber(input, decimals)).toEqual(output);
}
);
const long: [BigNumber, string | JSX.Element, number | 'infer'][] = [
[new BigNumber(Infinity), '∞', 'infer'],
[new BigNumber(-Infinity), '-∞', 'infer'],
[new BigNumber(0), '0', 'infer'],
[new BigNumber(1), '1', 'infer'],
[new BigNumber(100), '100', 'infer'],
[new BigNumber(100.456601), '100.456601', 'infer'],
[new BigNumber(1_000), '1,000', 'infer'],
[new BigNumber(999_999), '999,999', 'infer'],
[new BigNumber(1_000_000), '1 million', 'infer'],
[new BigNumber(100_000_000), '100 million', 'infer'],
[new BigNumber(1_000_000_000), '1 billion', 'infer'],
[new BigNumber(1_000_000_000_000), '1 trillion', 'infer'],
[new BigNumber(3.23e12), '3.23 trillion', 2],
[new BigNumber(3.23e12), '3.23000 trillion', 5],
[
new BigNumber(3.23e24),
<span>
3.23000{' '}
<span>
&times; 10<sup>24</sup>
</span>
</span>,
5,
],
[
new BigNumber(1.579208923731619e59),
<span>
1.57921{' '}
<span>
&times; 10
<sup>59</sup>
</span>
</span>,
5,
],
];
it.each(long)(
'compacts %d to %p (decimal places: %p)',
(input, output, decimals) => {
expect(compactNumber(input, decimals, 'long')).toEqual(output);
}
);
});

View File

@ -100,3 +100,67 @@ export const useNumberParts = (
export const isNumeric = (
value?: string | number | BigNumber | null
): value is NonNullable<number | string> => /^-?\d*\.?\d+$/.test(String(value));
const INFINITY = '∞';
const DEFAULT_COMPACT_ABOVE = 1_000_000;
const DEFAULT_COMPACT_CAP = new BigNumber(1e24);
/**
* Compacts given number to human readable format.
* @param number
* @param decimals Number of decimal places
* @param compactDisplay Display mode; short -> 1e6 == 1M; ling -> 1e6 1 million
* @param compactAbove Compact number above threshold
* @param cap Use scientific notation above threshold
*/
export const compactNumber = (
number: BigNumber,
decimals: number | 'infer' = 'infer',
compactDisplay: 'short' | 'long' = 'short',
compactAbove = DEFAULT_COMPACT_ABOVE,
cap = DEFAULT_COMPACT_CAP
) => {
if (!number.isFinite()) return `${number.isNegative() ? '-' : ''}${INFINITY}`;
const decimalPlaces =
(decimals === 'infer' ? number.decimalPlaces() : decimals) || 0;
if (number.isLessThan(DEFAULT_COMPACT_ABOVE)) {
return formatNumber(number, decimalPlaces);
}
/**
* Note: it compacts number up to 1_000_000_000_000 (1e12) -> 1T, all above is formatted as iteration of T.
* Example: 1.579208923731619e59 -> 157,920,892,373,161,900,000,000,000,000,000,000,000,000,000,000T
*/
const compactNumFormat = new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: Math.max(0, decimalPlaces),
maximumFractionDigits: Math.max(0, decimalPlaces),
notation: 'compact',
compactDisplay,
});
const scientificNumFormat = new Intl.NumberFormat(getUserLocale(), {
minimumFractionDigits: Math.max(0, decimalPlaces),
maximumFractionDigits: Math.max(0, decimalPlaces),
notation: 'scientific',
});
if (number.isGreaterThan(DEFAULT_COMPACT_CAP)) {
const r = /E(\d+)$/i;
const formatted = scientificNumFormat.format(Number(number));
const eNotation = formatted.match(r);
if (eNotation && eNotation.length > 1) {
const power = eNotation[1];
return (
<span>
{formatted.replace(r, '')}{' '}
<span>
&times; 10
<sup>{power}</sup>
</span>
</span>
);
}
}
return compactNumFormat.format(Number(number));
};

View File

@ -1,5 +1,4 @@
import type { Asset } from '@vegaprotocol/assets';
import { formatNumber } from '@vegaprotocol/react-helpers';
import {
ethereumAddress,
minSafe,
@ -147,7 +146,8 @@ export const WithdrawForm = ({
amount={amount}
threshold={threshold}
delay={delay}
balance={formatNumber(balance, selectedAsset.decimals)}
balance={balance}
asset={selectedAsset}
/>
</div>
)}

View File

@ -1,12 +1,15 @@
import { t } from '@vegaprotocol/react-helpers';
import type { Asset } from '@vegaprotocol/assets';
import { compactNumber, t } from '@vegaprotocol/react-helpers';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import BigNumber from 'bignumber.js';
import { formatDistanceToNow } from 'date-fns';
interface WithdrawLimitsProps {
amount: string;
threshold: BigNumber;
balance: string;
balance: BigNumber;
delay: number | undefined;
asset: Asset;
}
export const WithdrawLimits = ({
@ -14,40 +17,47 @@ export const WithdrawLimits = ({
threshold,
balance,
delay,
asset,
}: WithdrawLimitsProps) => {
let text = '';
if (threshold.isEqualTo(Infinity)) {
text = t('No limit');
} else if (threshold.isGreaterThan(1_000_000)) {
text = t('1m+');
} else {
text = threshold.toString();
}
const delayTime =
new BigNumber(amount).isGreaterThan(threshold) && delay
? formatDistanceToNow(Date.now() + delay * 1000)
: t('None');
const limits = [
{
key: 'BALANCE_AVAILABLE',
label: t('Balance available'),
rawValue: balance,
value: balance ? compactNumber(balance, asset.decimals) : '-',
},
{
key: 'WITHDRAWAL_THRESHOLD',
label: t('Delayed withdrawal threshold'),
rawValue: threshold,
value: compactNumber(threshold, asset.decimals),
},
{
key: 'DELAY_TIME',
label: t('Delay time'),
value: delayTime,
},
];
return (
<table className="w-full text-sm">
<tbody>
<tr data-testid="balance-available">
<th className="text-left font-normal">{t('Balance available')}</th>
<td className="text-right">{balance}</td>
</tr>
<tr data-testid="withdrawal-threshold">
<th className="text-left font-normal">
{t('Delayed withdrawal threshold')}
</th>
<td className="text-right">{text}</td>
</tr>
<tr data-testid="delay-time">
<th className="text-left font-normal">{t('Delay time')}</th>
<td className="text-right">{delayTime}</td>
</tr>
</tbody>
</table>
<KeyValueTable>
{limits.map(({ key, label, rawValue, value }) => (
<KeyValueTableRow>
<div data-testid={`${key}_label`}>{label}</div>
<div
data-testid={`${key}_value`}
className="truncate"
title={rawValue?.toString()}
>
{value}
</div>
</KeyValueTableRow>
))}
</KeyValueTable>
);
};