diff --git a/web/cashtab/src/components/Common/BalanceHeaderToken.js b/web/cashtab/src/components/Common/BalanceHeaderToken.js new file mode 100644 --- /dev/null +++ b/web/cashtab/src/components/Common/BalanceHeaderToken.js @@ -0,0 +1,21 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { formatTokenBalance } from 'utils/formatting'; +import { BalanceHeaderWrap } from 'components/Common/Atoms'; + +const BalanceHeaderToken = ({ balance, ticker, tokenDecimals }) => { + return ( + + {formatTokenBalance(balance, tokenDecimals)} {ticker} + + ); +}; + +// balance may be a string (XEC balance) or a BigNumber object (token balance) +BalanceHeaderToken.propTypes = { + balance: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + ticker: PropTypes.string, + tokenDecimals: PropTypes.number, +}; + +export default BalanceHeaderToken; diff --git a/web/cashtab/src/components/Home/TokenList.js b/web/cashtab/src/components/Home/TokenList.js --- a/web/cashtab/src/components/Home/TokenList.js +++ b/web/cashtab/src/components/Home/TokenList.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import TokenListItem from './TokenListItem'; import { Link } from 'react-router-dom'; -import { formatBalance } from 'utils/formatting'; +import { formatTokenBalance } from 'utils/formatting'; const TokenList = ({ tokens }) => { return ( @@ -12,7 +12,10 @@ ))} diff --git a/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap b/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap --- a/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap +++ b/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap @@ -398,9 +398,7 @@ TBS -

- 6.001 -

+

diff --git a/web/cashtab/src/components/Send/SendToken.js b/web/cashtab/src/components/Send/SendToken.js --- a/web/cashtab/src/components/Send/SendToken.js +++ b/web/cashtab/src/components/Send/SendToken.js @@ -24,7 +24,7 @@ } from 'components/Common/EnhancedInputs'; import useBCH from 'hooks/useBCH'; import { SidePaddingCtn } from 'components/Common/Atoms'; -import BalanceHeader from 'components/Common/BalanceHeader'; +import BalanceHeaderToken from 'components/Common/BalanceHeaderToken'; import { Redirect } from 'react-router-dom'; import useWindowDimensions from 'hooks/useWindowDimensions'; import usePrevious from 'hooks/usePrevious'; @@ -494,10 +494,11 @@ - diff --git a/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap b/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap --- a/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap +++ b/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap @@ -9,15 +9,8 @@
- - - 6.001 - - TBS - - + + TBS
{ @@ -137,4 +139,23 @@ it(`test formatFiatBalance with undefined input`, () => { expect(formatFiatBalance(undefined, 'en-US')).toBe(undefined); }); + it(`returns undefined formatTokenBalance with undefined inputs`, () => { + expect(formatTokenBalance(undefined, undefined)).toBe(undefined); + }); + it(`test formatTokenBalance with valid balance & decimal inputs`, () => { + const testBalance = new BigNumber(100.00000001); + expect(formatTokenBalance(testBalance, 8)).toBe('100.00000001'); + }); + it(`returns undefined when passed invalid decimals parameter`, () => { + const testBalance = new BigNumber(100.00000001); + expect(formatTokenBalance(testBalance, 'cheese')).toBe(undefined); + }); + it(`returns undefined when passed invalid balance parameter`, () => { + const testBalance = '100.000010122'; + expect(formatTokenBalance(testBalance, 9)).toBe(undefined); + }); + it(`maintains trailing zeros in balance per tokenDecimal parameter`, () => { + const testBalance = new BigNumber(10000); + expect(formatTokenBalance(testBalance, 8)).toBe('10,000.00000000'); + }); }); diff --git a/web/cashtab/src/utils/formatting.js b/web/cashtab/src/utils/formatting.js --- a/web/cashtab/src/utils/formatting.js +++ b/web/cashtab/src/utils/formatting.js @@ -1,5 +1,5 @@ import { currency } from 'components/Common/Ticker.js'; - +import BigNumber from 'bignumber.js'; export const formatDate = (dateString, userLocale = 'en') => { const options = { month: 'short', day: 'numeric', year: 'numeric' }; const dateFormattingError = 'Unable to format date.'; @@ -70,3 +70,44 @@ return unformattedBalance; } }; + +// unformattedBalance will always be a BigNumber, tokenDecimal will always be a number +export const formatTokenBalance = ( + unformattedBalance, + tokenDecimal, + defaultLocale = 'en', +) => { + let formattedTokenBalance; + let convertedTokenBalance; + let locale = defaultLocale; + try { + if ( + tokenDecimal === undefined || + unformattedBalance === undefined || + typeof tokenDecimal !== 'number' || + !BigNumber.isBigNumber(unformattedBalance) + ) { + return undefined; + } + if (navigator && navigator.language) { + locale = navigator.language; + } + + // Use toFixed to get a string with the correct decimal places + formattedTokenBalance = new BigNumber(unformattedBalance).toFixed( + tokenDecimal, + ); + // formattedTokenBalance is converted into a number as toLocaleString does not work with a string + convertedTokenBalance = parseFloat( + formattedTokenBalance, + ).toLocaleString(locale, { + minimumFractionDigits: tokenDecimal, + }); + + return convertedTokenBalance; + } catch (err) { + console.log(`Error in formatTokenBalance for ${unformattedBalance}`); + console.log(err); + return unformattedBalance; + } +};