diff --git a/src/components/Balance.jsx b/src/components/Balance.jsx index efdb8388..4e0c0d9c 100644 --- a/src/components/Balance.jsx +++ b/src/components/Balance.jsx @@ -1,74 +1,115 @@ import React, { useState, useEffect } from 'react' -import Sprite from './Sprite' import { BTC, SATS, btcToSats, satsToBtc } from '../utils' +import Sprite from './Sprite' -const UNIT_MODE_BTC = 0 -const UNIT_MODE_SATS = 1 -const UNIT_MODE_HIDDEN = 2 +const DISPLAY_MODE_BTC = 0 +const DISPLAY_MODE_SATS = 1 +const DISPLAY_MODE_HIDDEN = 2 -const getUnitMode = (unit, showBalance) => { - if (showBalance && unit === SATS) return UNIT_MODE_SATS - if (showBalance && unit === BTC) return UNIT_MODE_BTC +const decimalPoint = '\u002E' +const nbHalfSpace = '\u202F' - return UNIT_MODE_HIDDEN +const getDisplayMode = (unit, showBalance) => { + if (showBalance && unit === SATS) return DISPLAY_MODE_SATS + if (showBalance && unit === BTC) return DISPLAY_MODE_BTC + + return DISPLAY_MODE_HIDDEN } -export default function Balance({ value, unit, showBalance = false }) { - const [unitMode, setUnitMode] = useState(getUnitMode(unit, showBalance)) - - useEffect(() => { - setUnitMode(getUnitMode(unit, showBalance)) - }, [unit, showBalance]) - - if (unitMode === UNIT_MODE_HIDDEN) { - return ( - - - ***** - - - - - - ) - } - - const btcFormatter = new Intl.NumberFormat('en-US', { +const formatBtc = (value) => { + const formatter = new Intl.NumberFormat('en-US', { minimumIntegerDigits: 1, minimumFractionDigits: 8, }) - const satFormatter = new Intl.NumberFormat('en-US', { + const numberString = formatter.format(value) + + const [integerPart, fractionalPart] = numberString.split(decimalPoint) + + const formattedFractionalPart = fractionalPart + .split('') + .map((char, idx) => (idx === 2 || idx === 5 ? `${nbHalfSpace}${char}` : char)) + .join('') + + return integerPart + decimalPoint + formattedFractionalPart +} + +const formatSats = (value) => { + const formatter = new Intl.NumberFormat('en-US', { minimumIntegerDigits: 1, minimumFractionDigits: 0, }) - const isSats = value === parseInt(value) - const isBTC = !isSats && typeof value === 'string' && value.indexOf('.') > -1 + return formatter.format(value) +} - const btcSymbol = {'\u20BF'} - const satSymbol = - - const balanceJSX = (symbolJSX, formattedValue, isPrefix = true) => { - return ( - - {isPrefix ? symbolJSX : ''} - {formattedValue} - {!isPrefix ? symbolJSX : ''} - - ) - } - - if (isBTC && unitMode === UNIT_MODE_BTC) return balanceJSX(btcSymbol, btcFormatter.format(value)) - if (isSats && unitMode === UNIT_MODE_SATS) return balanceJSX(satSymbol, satFormatter.format(value), false) - - if (isBTC && unitMode === UNIT_MODE_SATS) return balanceJSX(satSymbol, satFormatter.format(btcToSats(value)), false) - if (isSats && unitMode === UNIT_MODE_BTC) return balanceJSX(btcSymbol, btcFormatter.format(satsToBtc(value))) - - // Something unexpected happened. Simply render what was passed in the props. +const BalanceComponent = ({ symbol, value, symbolIsPrefix }) => { return ( - - {value} {unit} + + {symbolIsPrefix && symbol} + {value} + {!symbolIsPrefix && symbol} ) } + +/** + * Render balances nicely formatted. + * + * @param {valueString}: The balance value to render. + * Integer values are treated as SATS while decimal numbers with a decimal point (.) are treated as BTC. + * For example: + * - 0, 10, 2100000000000000 are treated as a value in SATS; while + * - 0.00000000, 150.00000001, 21000000.00000000 are treated as a value in BTC. + * @param {convertToUnit}: The unit to convert the `valueString` to. + * Possible options are `BTC` and `SATS` from `src/utils.js` + * @param {showBalance}: A flag indicating whether to render or hide the balance. + * Hidden balances are masked with `*****`. + */ +export default function Balance({ valueString, convertToUnit, showBalance = false }) { + const [displayMode, setDisplayMode] = useState(DISPLAY_MODE_HIDDEN) + + useEffect(() => { + setDisplayMode(getDisplayMode(convertToUnit, showBalance)) + }, [convertToUnit, showBalance]) + + if (displayMode === DISPLAY_MODE_HIDDEN) { + return ( + + + + } + value={'*****'} + symbolIsPrefix={false} + /> + ) + } + + if (typeof valueString !== 'string') { + console.warn(' component expects string input') + return + } + + // Treat integers as sats. + const valueIsSats = valueString === Number.parseInt(valueString).toString() + // Treat decimal numbers as btc. + const valueIsBtc = !valueIsSats && !Number.isNaN(Number.parseFloat(valueString)) && valueString.indexOf('.') > -1 + + const btcSymbol = {'\u20BF'} + const satSymbol = + + if (valueIsBtc && displayMode === DISPLAY_MODE_BTC) + return + if (valueIsSats && displayMode === DISPLAY_MODE_SATS) + return + + if (valueIsBtc && displayMode === DISPLAY_MODE_SATS) + return + if (valueIsSats && displayMode === DISPLAY_MODE_BTC) + return + + console.warn(' component cannot determine balance format') + return +} diff --git a/src/components/Balance.test.jsx b/src/components/Balance.test.jsx new file mode 100644 index 00000000..287ede3c --- /dev/null +++ b/src/components/Balance.test.jsx @@ -0,0 +1,114 @@ +import React from 'react' +import { render, screen } from '../testUtils' +import { BTC, SATS } from '../utils' + +import Balance from './Balance' + +describe('', () => { + it('should render BTC using satscomma formatting', () => { + render() + expect(screen.getByText(`123.45 600 000`)).toBeInTheDocument() + }) + + it('should hide balance for BTC by default', () => { + render() + expect(screen.getByText(`*****`)).toBeInTheDocument() + expect(screen.queryByText(`123.45 600 000`)).not.toBeInTheDocument() + }) + + it('should hide balance for SATS by default', () => { + render() + expect(screen.getByText(`*****`)).toBeInTheDocument() + expect(screen.queryByText(`123`)).not.toBeInTheDocument() + }) + + it('should render a string BTC value correctly as BTC', () => { + render() + expect(screen.getByText(`123.03 224 961`)).toBeInTheDocument() + }) + + it('should render a string BTC value correctly as SATS', () => { + render() + expect(screen.getByText(`12,303,224,961`)).toBeInTheDocument() + }) + + it('should render a zero string BTC value correctly as BTC', () => { + render() + expect(screen.getByText(`0.00 000 000`)).toBeInTheDocument() + }) + + it('should render a zero string BTC value correctly as SATS', () => { + render() + expect(screen.getByText(`0`)).toBeInTheDocument() + }) + + it('should render a large string BTC value correctly as BTC', () => { + render() + expect(screen.getByText(`20,999,999.97 690 000`)).toBeInTheDocument() + }) + + it('should render a large string BTC value correctly as SATS', () => { + render() + expect(screen.getByText(`2,099,999,997,690,000`)).toBeInTheDocument() + }) + + it('should render a max string BTC value correctly as BTC', () => { + render() + expect(screen.getByText(`21,000,000.00 000 000`)).toBeInTheDocument() + }) + + it('should render a max string BTC value correctly as SATS', () => { + render() + expect(screen.getByText(`2,100,000,000,000,000`)).toBeInTheDocument() + }) + + it('should render a number BTC value as fallback', () => { + render() + expect(screen.getByText(`123.456`)).toBeInTheDocument() + }) + + it('should render a string SATS value correctly as SATS', () => { + render() + expect(screen.getByText(`43,000`)).toBeInTheDocument() + }) + + it('should render a string SATS value correctly as BTC', () => { + render() + expect(screen.getByText(`0.00 043 000`)).toBeInTheDocument() + }) + + it('should render a zero string SATS value correctly as BTC', () => { + render() + expect(screen.getByText(`0.00 000 000`)).toBeInTheDocument() + }) + + it('should render a zero string SATS value correctly as SATS', () => { + render() + expect(screen.getByText(`0`)).toBeInTheDocument() + }) + + it('should render a large string SATS value correctly as BTC', () => { + render() + expect(screen.getByText(`20,999,999.97 690 000`)).toBeInTheDocument() + }) + + it('should render a large string SATS value correctly as SATS', () => { + render() + expect(screen.getByText(`2,099,999,997,690,000`)).toBeInTheDocument() + }) + + it('should render a max string SATS value correctly as BTC', () => { + render() + expect(screen.getByText(`21,000,000.00 000 000`)).toBeInTheDocument() + }) + + it('should render a max string SATS value correctly as SATS', () => { + render() + expect(screen.getByText(`2,100,000,000,000,000`)).toBeInTheDocument() + }) + + it('should render a number SATS value as fallback', () => { + render() + expect(screen.getByText(`43000`)).toBeInTheDocument() + }) +}) diff --git a/src/components/CurrentWalletMagic.jsx b/src/components/CurrentWalletMagic.jsx index c6915363..c79f775b 100644 --- a/src/components/CurrentWalletMagic.jsx +++ b/src/components/CurrentWalletMagic.jsx @@ -13,7 +13,7 @@ const WalletHeader = ({ name, balance, unit, showBalance }) => {
{walletDisplayName(name)}

- +

) @@ -54,7 +54,7 @@ const PrivacyLevel = ({ numAccounts, level, balance }) => { {outlinedShields}
- +
) diff --git a/src/components/DisplayAccounts.jsx b/src/components/DisplayAccounts.jsx index db96ae0e..5154e6c4 100644 --- a/src/components/DisplayAccounts.jsx +++ b/src/components/DisplayAccounts.jsx @@ -18,7 +18,7 @@ const BranchEntry = ({ entry, ...props }) => { {hdPath} - + {address} {labels && {labels}} @@ -45,7 +45,7 @@ export default function DisplayAccounts({ accounts, ...props }) { - + @@ -65,7 +65,7 @@ export default function DisplayAccounts({ accounts, ...props }) {
{titleize(type)}
- + diff --git a/src/components/DisplayUTXOs.jsx b/src/components/DisplayUTXOs.jsx index 52fa47f4..9ec6bd75 100644 --- a/src/components/DisplayUTXOs.jsx +++ b/src/components/DisplayUTXOs.jsx @@ -61,7 +61,7 @@ const Utxo = ({ utxo, ...props }) => {
- +
{utxo.confirmations} Confirmations diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 4d8219ce..57c3ef52 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -15,7 +15,7 @@ const WalletPreview = ({ wallet, walletInfo, unit, showBalance }) => { {wallet &&
{walletDisplayName(wallet.name)}
} {walletInfo && walletInfo?.total_balance && unit ? (
- +
) : (
diff --git a/src/index.css b/src/index.css index 6745b76e..8bdaf537 100644 --- a/src/index.css +++ b/src/index.css @@ -578,22 +578,6 @@ h2 { } } -/* Balance Styles */ - -.balance-wrapper { - display: inline-flex; - align-items: center; -} - -.balance { - display: flex; - align-items: center; -} - -.bitcoin-symbol { - padding-right: 0.1em; -} - /* Onboarding */ .onboarding button {