chore: format limits (547) (#1742)
This commit is contained in:
parent
5d04efe52d
commit
50611a4ba6
@ -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(
|
||||
|
@ -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' },
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
× 10<sup>24</sup>
|
||||
</span>
|
||||
</span>,
|
||||
5,
|
||||
],
|
||||
[
|
||||
new BigNumber(1.579208923731619e59),
|
||||
<span>
|
||||
1.57921{' '}
|
||||
<span>
|
||||
× 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>
|
||||
× 10<sup>24</sup>
|
||||
</span>
|
||||
</span>,
|
||||
5,
|
||||
],
|
||||
[
|
||||
new BigNumber(1.579208923731619e59),
|
||||
<span>
|
||||
1.57921{' '}
|
||||
<span>
|
||||
× 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);
|
||||
}
|
||||
);
|
||||
});
|
@ -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>
|
||||
× 10
|
||||
<sup>{power}</sup>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return compactNumFormat.format(Number(number));
|
||||
};
|
@ -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>
|
||||
)}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user