diff --git a/sample.env b/sample.env index 1c672d2d702..fde95503572 100644 --- a/sample.env +++ b/sample.env @@ -31,6 +31,7 @@ REACT_APP_MIDGARD_URL=https://midgard.thorchain.info/v2 REACT_APP_FRIENDLY_CAPTCHA_SITE_KEY=FCMM7AFC0S6A8NUK +REACT_APP_FEATURE_MULTI_CURRENCY=false REACT_APP_FEATURE_OSMOSIS=false REACT_APP_FEATURE_WALLETCONNECT_WALLET=false REACT_APP_FEATURE_AVALANCHE=false diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json index d00fe2a0df5..5dbc2632445 100644 --- a/src/assets/translations/en/main.json +++ b/src/assets/translations/en/main.json @@ -695,6 +695,7 @@ "settings": { "settings": "Settings", "currency": "Currency", + "currencyFormat": "Currency Format", "language": "Language", "balanceThreshold": "Balance Threshold", "balanceThresholdTooltip": "Hide balances below this value", diff --git a/src/components/Amount/Amount.tsx b/src/components/Amount/Amount.tsx index 615585c83ce..7e93cfab47a 100644 --- a/src/components/Amount/Amount.tsx +++ b/src/components/Amount/Amount.tsx @@ -10,6 +10,9 @@ type AmountProps = { value: number | string prefix?: string suffix?: string + omitDecimalTrailingZeros?: boolean + abbreviated?: boolean + maximumFractionDigits?: number } & TextProps export function Amount({ @@ -17,16 +20,18 @@ export function Amount({ prefix = '', suffix = '', maximumFractionDigits, + omitDecimalTrailingZeros = false, + abbreviated = false, ...props }: any): React.ReactElement { const { number: { toString }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() return ( {prefix} - {toString(value, { maximumFractionDigits })} + {toString(value, { maximumFractionDigits, omitDecimalTrailingZeros, abbreviated })} {suffix} ) @@ -56,13 +61,14 @@ const Crypto = ({ maximumFractionDigits = 8, prefix, suffix, + omitDecimalTrailingZeros = false, ...props }: CryptoAmountProps) => { const { number: { toCrypto, toParts }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() - const crypto = toCrypto(value, symbol, { maximumFractionDigits }) + const crypto = toCrypto(value, symbol, { maximumFractionDigits, omitDecimalTrailingZeros }) if (!cryptoSymbolStyle) { return ( @@ -93,12 +99,27 @@ const Crypto = ({ ) } -const Fiat = ({ value, fiatSymbolStyle, fiatType, prefix, suffix, ...props }: FiatAmountProps) => { +const Fiat = ({ + value, + fiatSymbolStyle, + fiatType, + prefix, + suffix, + maximumFractionDigits, + omitDecimalTrailingZeros = false, + abbreviated = false, + ...props +}: FiatAmountProps) => { const { number: { toFiat, toParts }, - } = useLocaleFormatter({ fiatType: fiatType || 'USD' }) + } = useLocaleFormatter({ fiatType }) - const fiat = toFiat(value, { fiatType }) + const fiat = toFiat(value, { + fiatType, + omitDecimalTrailingZeros, + abbreviated, + maximumFractionDigits, + }) if (!fiatSymbolStyle) { return ( @@ -132,7 +153,7 @@ const Fiat = ({ value, fiatSymbolStyle, fiatType, prefix, suffix, ...props }: Fi const Percent = ({ value, autoColor, options, prefix, suffix, ...props }: PercentAmountProps) => { const { number: { toPercent }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() const formattedNumber = toPercent(value, options) const color = useMemo(() => { @@ -155,17 +176,6 @@ const Percent = ({ value, autoColor, options, prefix, suffix, ...props }: Percen ) } -const Supply = ({ value, ...props }: AmountProps) => { - const { - number: { toSupply }, - } = useLocaleFormatter({ fiatType: 'USD' }) - - const volume = toSupply(value) - - return {volume} -} - Amount.Crypto = Crypto Amount.Fiat = Fiat Amount.Percent = Percent -Amount.Supply = Supply diff --git a/src/components/Approval/Approval.tsx b/src/components/Approval/Approval.tsx index 7bfd02e1291..d68e9478afb 100644 --- a/src/components/Approval/Approval.tsx +++ b/src/components/Approval/Approval.tsx @@ -42,7 +42,7 @@ export const Approval = () => { const { approveInfinite, checkApprovalNeeded, updateTrade } = useSwapper() const { number: { toCrypto, toFiat }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() const { state: { isConnected }, dispatch, diff --git a/src/components/AssetHeader/AssetChart.tsx b/src/components/AssetHeader/AssetChart.tsx index a4c230592d2..cf61754490f 100644 --- a/src/components/AssetHeader/AssetChart.tsx +++ b/src/components/AssetHeader/AssetChart.tsx @@ -56,7 +56,7 @@ type AssetChartProps = { export const AssetChart = ({ accountId, assetId, isLoaded }: AssetChartProps) => { const { number: { toFiat }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() const [percentChange, setPercentChange] = useState(0) const alertIconColor = useColorModeValue('blue.500', 'blue.200') const [timeframe, setTimeframe] = useState(DEFAULT_HISTORY_TIMEFRAME) diff --git a/src/components/AssetHeader/AssetMarketData.tsx b/src/components/AssetHeader/AssetMarketData.tsx index 04d8af06279..88bcb7d147c 100644 --- a/src/components/AssetHeader/AssetMarketData.tsx +++ b/src/components/AssetHeader/AssetMarketData.tsx @@ -104,7 +104,7 @@ export const AssetMarketData: React.FC = ({ assetId }) => - + )} @@ -114,7 +114,11 @@ export const AssetMarketData: React.FC = ({ assetId }) => - + )} diff --git a/src/components/DeFi/components/AssetInput.tsx b/src/components/DeFi/components/AssetInput.tsx index b8eb7f3c76a..b7e50c22793 100644 --- a/src/components/DeFi/components/AssetInput.tsx +++ b/src/components/DeFi/components/AssetInput.tsx @@ -71,7 +71,7 @@ export const AssetInput: React.FC = ({ }) => { const { number: { localeParts }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() const translate = useTranslate() const amountRef = useRef(null) const [isFiat, setIsFiat] = useState(false) diff --git a/src/components/Graph/PrimaryChart/PrimaryChart.tsx b/src/components/Graph/PrimaryChart/PrimaryChart.tsx index 3f8d39a0ea4..8c1142222b6 100644 --- a/src/components/Graph/PrimaryChart/PrimaryChart.tsx +++ b/src/components/Graph/PrimaryChart/PrimaryChart.tsx @@ -53,7 +53,7 @@ export const PrimaryChart = ({ const { number: { toFiat }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() const [chartColor] = useToken('colors', [color]) const tooltipBg = useColorModeValue('white', colors.gray[800]) diff --git a/src/components/Modals/Send/Form.tsx b/src/components/Modals/Send/Form.tsx index 5773561febb..ecb46e6e003 100644 --- a/src/components/Modals/Send/Form.tsx +++ b/src/components/Modals/Send/Form.tsx @@ -15,7 +15,7 @@ import { import { SelectAssetRoutes } from 'components/SelectAssets/SelectAssetCommon' import { SelectAssetRouter } from 'components/SelectAssets/SelectAssetRouter' import { AccountSpecifier } from 'state/slices/accountSpecifiersSlice/accountSpecifiersSlice' -import { selectMarketDataById } from 'state/slices/selectors' +import { selectMarketDataById, selectSelectedCurrency } from 'state/slices/selectors' import { useAppSelector } from 'state/store' import { useFormSend } from './hooks/useFormSend/useFormSend' @@ -50,6 +50,7 @@ export const Form = ({ asset: initialAsset, accountId }: SendFormProps) => { const location = useLocation() const history = useHistory() const { handleSend } = useFormSend() + const selectedCurrency = useAppSelector(selectSelectedCurrency) const marketData = useAppSelector(state => selectMarketDataById(state, initialAsset.assetId)) const methods = useForm({ @@ -63,7 +64,7 @@ export const Form = ({ asset: initialAsset, accountId }: SendFormProps) => { cryptoAmount: '', cryptoSymbol: initialAsset?.symbol, fiatAmount: '', - fiatSymbol: 'USD', // TODO: use user preferences to get default fiat currency + fiatSymbol: selectedCurrency, }, }) @@ -72,7 +73,7 @@ export const Form = ({ asset: initialAsset, accountId }: SendFormProps) => { methods.setValue(SendFormFields.CryptoAmount, '') methods.setValue(SendFormFields.CryptoSymbol, asset.symbol) methods.setValue(SendFormFields.FiatAmount, '') - methods.setValue(SendFormFields.FiatSymbol, 'USD') + methods.setValue(SendFormFields.FiatSymbol, selectedCurrency) methods.setValue(SendFormFields.AccountId, accountId) history.push(SendRoutes.Address) diff --git a/src/components/Modals/Settings/BalanceThresholdInput.tsx b/src/components/Modals/Settings/BalanceThresholdInput.tsx index c5ac829c6a7..e371ccaed91 100644 --- a/src/components/Modals/Settings/BalanceThresholdInput.tsx +++ b/src/components/Modals/Settings/BalanceThresholdInput.tsx @@ -30,7 +30,7 @@ export const BalanceThresholdInput = () => { const dispatch = useAppDispatch() const { number: { localeParts }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() const onChange = (value: string) => { dispatch(preferences.actions.setBalanceThreshold({ threshold: value })) } diff --git a/src/components/Modals/Settings/CurrencyFormat.tsx b/src/components/Modals/Settings/CurrencyFormat.tsx new file mode 100644 index 00000000000..cf948e994ec --- /dev/null +++ b/src/components/Modals/Settings/CurrencyFormat.tsx @@ -0,0 +1,82 @@ +import { ArrowBackIcon } from '@chakra-ui/icons' +import { Button, Flex, Icon, IconButton, ModalBody, ModalHeader } from '@chakra-ui/react' +import identity from 'lodash/identity' +import sortBy from 'lodash/sortBy' +import { FaCheck } from 'react-icons/fa' +import { useTranslate } from 'react-polyglot' +import { useHistory } from 'react-router-dom' +import { SlideTransition } from 'components/SlideTransition' +import { RawText } from 'components/Text' +import { CurrencyFormats, preferences } from 'state/slices/preferencesSlice/preferencesSlice' +import { selectCurrencyFormat } from 'state/slices/selectors' +import { useAppDispatch, useAppSelector } from 'state/store' + +import { currencyFormatsRepresenter } from './SettingsCommon' + +export const CurrencyFormat = () => { + const dispatch = useAppDispatch() + const currentCurrencyFormat = useAppSelector(selectCurrencyFormat) + const translate = useTranslate() + const history = useHistory() + const { goBack } = history + const formats = sortBy(CurrencyFormats, identity) + const { setCurrencyFormat } = preferences.actions + + return ( + + } + aria-label={translate('common.back')} + position='absolute' + top={2} + left={3} + fontSize='xl' + size='sm' + isRound + onClick={goBack} + /> + {translate('modals.settings.currencyFormat')} + <> + + {formats.map(currencyFormat => { + const active = currencyFormat === currentCurrencyFormat + const buttonProps = active + ? { + disabled: true, + _disabled: { opacity: 1 }, + } + : { + pl: 8, + variant: 'ghost', + onClick: () => dispatch(setCurrencyFormat({ currencyFormat })), + } + return ( + + ) + })} + + + + ) +} diff --git a/src/components/Modals/Settings/FiatCurrencies.tsx b/src/components/Modals/Settings/FiatCurrencies.tsx new file mode 100644 index 00000000000..20aace76629 --- /dev/null +++ b/src/components/Modals/Settings/FiatCurrencies.tsx @@ -0,0 +1,87 @@ +import { ArrowBackIcon } from '@chakra-ui/icons' +import { Button, Flex, Icon, IconButton, ModalBody, ModalHeader } from '@chakra-ui/react' +import { SupportedFiatCurrencies, SupportedFiatCurrenciesList } from '@shapeshiftoss/market-service' +import identity from 'lodash/identity' +import sortBy from 'lodash/sortBy' +import { FaCheck } from 'react-icons/fa' +import { useTranslate } from 'react-polyglot' +import { useHistory } from 'react-router-dom' +import { SlideTransition } from 'components/SlideTransition' +import { RawText, Text } from 'components/Text' +import { preferences } from 'state/slices/preferencesSlice/preferencesSlice' +import { selectSelectedCurrency } from 'state/slices/selectors' +import { useAppDispatch, useAppSelector } from 'state/store' + +export const FiatCurrencies = () => { + const dispatch = useAppDispatch() + const selectedCurrency = useAppSelector(selectSelectedCurrency) + const translate = useTranslate() + const history = useHistory() + const { goBack } = history + const defaultCurrency: SupportedFiatCurrencies = 'USD' + const allFiatCurrencies = sortBy(SupportedFiatCurrenciesList, item => + // keep default currency at the top of the list + item === defaultCurrency ? defaultCurrency : identity, + ) + const { setSelectedCurrency } = preferences.actions + + return ( + + } + aria-label={translate('common.back')} + position='absolute' + top={2} + left={3} + fontSize='xl' + size='sm' + isRound + onClick={goBack} + /> + {translate('modals.settings.currency')} + <> + + {allFiatCurrencies.map(currency => { + const active = currency === selectedCurrency + const buttonProps = active + ? { + disabled: true, + _disabled: { opacity: 1 }, + } + : { + pl: 8, + variant: 'ghost', + onClick: () => dispatch(setSelectedCurrency({ currency })), + } + return ( + + ) + })} + + + + ) +} diff --git a/src/components/Modals/Settings/Settings.tsx b/src/components/Modals/Settings/Settings.tsx index abc8ca86652..1644e712097 100644 --- a/src/components/Modals/Settings/Settings.tsx +++ b/src/components/Modals/Settings/Settings.tsx @@ -5,7 +5,12 @@ import { useModal } from 'hooks/useModal/useModal' import { SettingsRoutes } from './SettingsCommon' import { SettingsRouter } from './SettingsRouter' -export const entries = [SettingsRoutes.Index, SettingsRoutes.Languages] +export const entries = [ + SettingsRoutes.Index, + SettingsRoutes.Languages, + SettingsRoutes.FiatCurrencies, + SettingsRoutes.CurrencyFormat, +] const Settings = () => { /** diff --git a/src/components/Modals/Settings/SettingsCommon.ts b/src/components/Modals/Settings/SettingsCommon.ts index 06fa79949de..c6e4d20d445 100644 --- a/src/components/Modals/Settings/SettingsCommon.ts +++ b/src/components/Modals/Settings/SettingsCommon.ts @@ -1,4 +1,13 @@ +import { CurrencyFormats } from 'state/slices/preferencesSlice/preferencesSlice' + export enum SettingsRoutes { Index = '/settings/index', - Languages = '/receive/languages', + Languages = '/settings/languages', + FiatCurrencies = '/settings/fiat-currencies', + CurrencyFormat = '/settings/currency-format', +} + +export const currencyFormatsRepresenter: Record = { + [CurrencyFormats.DotDecimal]: '1,234.56', + [CurrencyFormats.CommaDecimal]: '1 234,56', } diff --git a/src/components/Modals/Settings/SettingsList.tsx b/src/components/Modals/Settings/SettingsList.tsx index b6362fbb989..041aa6f7ecd 100644 --- a/src/components/Modals/Settings/SettingsList.tsx +++ b/src/components/Modals/Settings/SettingsList.tsx @@ -11,7 +11,7 @@ import { useColorMode, useColorModeValue, } from '@chakra-ui/react' -import { FaGreaterThanEqual } from 'react-icons/fa' +import { FaCoins, FaDollarSign, FaGreaterThanEqual } from 'react-icons/fa' import { IoDocumentTextOutline, IoLockClosed } from 'react-icons/io5' import { MdChevronRight, MdLanguage } from 'react-icons/md' import { useTranslate } from 'react-polyglot' @@ -19,12 +19,17 @@ import { RouteComponentProps } from 'react-router-dom' import { SlideTransition } from 'components/SlideTransition' import { RawText } from 'components/Text' import { useModal } from 'hooks/useModal/useModal' -import { selectSelectedLocale } from 'state/slices/selectors' +import { + selectCurrencyFormat, + selectFeatureFlags, + selectSelectedCurrency, + selectSelectedLocale, +} from 'state/slices/selectors' import { useAppSelector } from 'state/store' import { getLocaleLabel } from '../../../assets/translations/utils' import { BalanceThresholdInput } from './BalanceThresholdInput' -import { SettingsRoutes } from './SettingsCommon' +import { currencyFormatsRepresenter, SettingsRoutes } from './SettingsCommon' import { SettingsListItem } from './SettingsListItem' type SettingsListProps = { @@ -37,8 +42,11 @@ export const SettingsList = ({ appHistory, ...routeProps }: SettingsListProps) = const { toggleColorMode } = useColorMode() const isLightMode = useColorModeValue(true, false) const selectedLocale = useAppSelector(selectSelectedLocale) + const selectedCurrency = useAppSelector(selectSelectedCurrency) + const selectedCurrencyFormat = useAppSelector(selectCurrencyFormat) // for both locale and currency const selectedPreferenceValueColor = useColorModeValue('blue.500', 'blue.200') + const featureFlags = useAppSelector(selectFeatureFlags) const closeModalAndNavigateTo = (linkHref: string) => { settings.close() @@ -60,6 +68,36 @@ export const SettingsList = ({ appHistory, ...routeProps }: SettingsListProps) = + {featureFlags.MultiCurrency && ( + <> + routeProps.history.push(SettingsRoutes.FiatCurrencies)} + icon={} + > + + + {selectedCurrency} + + + + + + routeProps.history.push(SettingsRoutes.CurrencyFormat)} + icon={} + > + + + {currencyFormatsRepresenter[selectedCurrencyFormat]} + + + + + + + )} routeProps.history.push(SettingsRoutes.Languages)} diff --git a/src/components/Modals/Settings/SettingsRouter.tsx b/src/components/Modals/Settings/SettingsRouter.tsx index 4de63439a8f..28cf4f417ab 100644 --- a/src/components/Modals/Settings/SettingsRouter.tsx +++ b/src/components/Modals/Settings/SettingsRouter.tsx @@ -1,6 +1,8 @@ import { AnimatePresence } from 'framer-motion' import { Route, RouteComponentProps, Switch, useLocation } from 'react-router-dom' +import { CurrencyFormat } from './CurrencyFormat' +import { FiatCurrencies } from './FiatCurrencies' import { Languages } from './Languages' import { SettingsRoutes } from './SettingsCommon' import { SettingsList } from './SettingsList' @@ -16,7 +18,9 @@ export const SettingsRouter = ({ appHistory }: { appHistory: RouteComponentProps )} /> - } /> + + + ) diff --git a/src/components/TokenRow/TokenRow.tsx b/src/components/TokenRow/TokenRow.tsx index b19c4423cfa..6b52d9dbc48 100644 --- a/src/components/TokenRow/TokenRow.tsx +++ b/src/components/TokenRow/TokenRow.tsx @@ -44,7 +44,7 @@ export function TokenRow({ }: TokenRowProps) { const { number: { localeParts }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() return ( diff --git a/src/components/Trade/TradeConfirm/AssetToAsset.tsx b/src/components/Trade/TradeConfirm/AssetToAsset.tsx index 70d4eb4d666..ea5a9b22803 100644 --- a/src/components/Trade/TradeConfirm/AssetToAsset.tsx +++ b/src/components/Trade/TradeConfirm/AssetToAsset.tsx @@ -36,7 +36,7 @@ export const AssetToAsset = ({ const buyAssetColor = '#2775CA' const { number: { toCrypto }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() const gray = useColorModeValue('white', 'gray.750') const red = useColorModeValue('white', 'red.500') const green = useColorModeValue('white', 'green.500') diff --git a/src/components/Trade/TradeConfirm/TradeConfirm.tsx b/src/components/Trade/TradeConfirm/TradeConfirm.tsx index 98a7a9c51e1..17c5ef56254 100644 --- a/src/components/Trade/TradeConfirm/TradeConfirm.tsx +++ b/src/components/Trade/TradeConfirm/TradeConfirm.tsx @@ -51,7 +51,7 @@ export const TradeConfirm = ({ history }: RouterProps) => { const { fiatRate } = location.state const { number: { toFiat }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() const { state: { isConnected }, dispatch, diff --git a/src/components/Trade/TradeInput.tsx b/src/components/Trade/TradeInput.tsx index 73aabad6aba..1cd193fabb8 100644 --- a/src/components/Trade/TradeInput.tsx +++ b/src/components/Trade/TradeInput.tsx @@ -39,7 +39,7 @@ export const TradeInput = ({ history }: RouterProps) => { } = useFormContext>() const { number: { localeParts }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() const [isSendMaxLoading, setIsSendMaxLoading] = useState(false) const [quote, buyTradeAsset, sellTradeAsset, feeAssetFiatRate] = useWatch({ name: ['quote', 'buyAsset', 'sellAsset', 'feeAssetFiatRate'], diff --git a/src/config.ts b/src/config.ts index 184c3160bb0..2e186e861d4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -43,6 +43,7 @@ const validators = { REACT_APP_FRIENDLY_CAPTCHA_SITE_KEY: str(), REACT_APP_FEATURE_YEARN: bool({ default: true }), REACT_APP_FEATURE_OSMOSIS: bool({ default: false }), + REACT_APP_FEATURE_MULTI_CURRENCY: bool({ default: false }), REACT_APP_FEATURE_WALLETCONNECT_WALLET: bool({ default: false }), REACT_APP_FEATURE_AVALANCHE: bool({ default: false }), REACT_APP_FEATURE_THOR: bool({ default: false }), diff --git a/src/hooks/useBalanceChartData/useBalanceChartData.test.ts b/src/hooks/useBalanceChartData/useBalanceChartData.test.ts index 991b90dc5f2..554898d804d 100644 --- a/src/hooks/useBalanceChartData/useBalanceChartData.test.ts +++ b/src/hooks/useBalanceChartData/useBalanceChartData.test.ts @@ -1,5 +1,5 @@ import { RebaseHistory } from '@shapeshiftoss/investor-foxy' -import { HistoryTimeframe } from '@shapeshiftoss/types' +import { HistoryData, HistoryTimeframe } from '@shapeshiftoss/types' import { ethereum, fox } from 'test/mocks/assets' import { ethereumTransactions, FOXSend } from 'test/mocks/txs' import { bn } from 'lib/bignumber/bignumber' @@ -95,9 +95,10 @@ describe('calculateBucketPrices', () => { const txs = [FOXSend] - const priceHistoryData: PriceHistoryData = { + const cryptoPriceHistoryData: PriceHistoryData = { [foxAssetId]: [{ price: 0, date: Number() }], } + const fiatPriceHistoryData: HistoryData[] = [{ price: 0, date: Number() }] const portfolioAssets: PortfolioAssets = { [foxAssetId]: fox, @@ -109,7 +110,8 @@ describe('calculateBucketPrices', () => { const calculatedBuckets = calculateBucketPrices({ assetIds, buckets, - priceHistoryData, + cryptoPriceHistoryData, + fiatPriceHistoryData, portfolioAssets, delegationTotal: '0', }) @@ -127,9 +129,10 @@ describe('calculateBucketPrices', () => { } const assetIds = [ethAssetId] const timeframe = HistoryTimeframe.YEAR - const priceHistoryData: PriceHistoryData = { + const cryptoPriceHistoryData: PriceHistoryData = { [ethAssetId]: [{ price: 0, date: Number() }], } + const fiatPriceHistoryData: HistoryData[] = [{ price: 0, date: Number() }] const portfolioAssets: PortfolioAssets = { [ethAssetId]: ethereum, } @@ -140,7 +143,8 @@ describe('calculateBucketPrices', () => { const calculatedBuckets = calculateBucketPrices({ assetIds, buckets, - priceHistoryData, + cryptoPriceHistoryData, + fiatPriceHistoryData, portfolioAssets, delegationTotal: '0', }) diff --git a/src/hooks/useBalanceChartData/useBalanceChartData.ts b/src/hooks/useBalanceChartData/useBalanceChartData.ts index d440802f37d..6b2adf5240c 100644 --- a/src/hooks/useBalanceChartData/useBalanceChartData.ts +++ b/src/hooks/useBalanceChartData/useBalanceChartData.ts @@ -30,6 +30,8 @@ import { selectAccountSpecifiers, selectAssets, selectCryptoPriceHistoryTimeframe, + selectFiatPriceHistoriesLoadingByTimeframe, + selectFiatPriceHistoryTimeframe, selectPortfolioAssets, selectPortfolioCryptoBalancesByAccountIdAboveThreshold, selectPriceHistoriesLoadingByAssetTimeframe, @@ -160,14 +162,16 @@ export const bucketEvents = ( type FiatBalanceAtBucketArgs = { bucket: Bucket portfolioAssets: PortfolioAssets - priceHistoryData: PriceHistoryData + cryptoPriceHistoryData: PriceHistoryData + fiatPriceHistoryData: HistoryData[] } type FiatBalanceAtBucket = (args: FiatBalanceAtBucketArgs) => BigNumber const fiatBalanceAtBucket: FiatBalanceAtBucket = ({ bucket, - priceHistoryData, + cryptoPriceHistoryData, + fiatPriceHistoryData, portfolioAssets, }) => { const { balance, end } = bucket @@ -175,15 +179,17 @@ const fiatBalanceAtBucket: FiatBalanceAtBucket = ({ const { crypto } = balance return Object.entries(crypto).reduce((acc, [assetId, assetCryptoBalance]) => { - const assetPriceHistoryData = priceHistoryData[assetId] + const assetPriceHistoryData = cryptoPriceHistoryData[assetId] if (!assetPriceHistoryData?.length) return acc - const price = priceAtDate({ priceHistoryData: assetPriceHistoryData, date }) const portfolioAsset = portfolioAssets[assetId] - if (!portfolioAsset) { - return acc - } + if (!portfolioAsset) return acc + const price = priceAtDate({ priceHistoryData: assetPriceHistoryData, date }) + const fiatToUsdRate = priceAtDate({ priceHistoryData: fiatPriceHistoryData, date }) const { precision } = portfolioAsset - const assetFiatBalance = assetCryptoBalance.div(bn(10).exponentiatedBy(precision)).times(price) + const assetFiatBalance = assetCryptoBalance + .div(bn(10).exponentiatedBy(precision)) + .times(price) + .times(fiatToUsdRate) return acc.plus(assetFiatBalance) }, bn(0)) } @@ -192,7 +198,8 @@ type CalculateBucketPricesArgs = { assetIds: AssetId[] buckets: Bucket[] portfolioAssets: PortfolioAssets - priceHistoryData: PriceHistoryData + cryptoPriceHistoryData: PriceHistoryData + fiatPriceHistoryData: HistoryData[] delegationTotal: string } @@ -200,7 +207,14 @@ type CalculateBucketPrices = (args: CalculateBucketPricesArgs) => Bucket[] // note - this mutates buckets export const calculateBucketPrices: CalculateBucketPrices = args => { - const { assetIds, buckets, portfolioAssets, priceHistoryData, delegationTotal } = args + const { + assetIds, + buckets, + portfolioAssets, + cryptoPriceHistoryData, + fiatPriceHistoryData, + delegationTotal, + } = args const startingBucket = buckets[buckets.length - 1] @@ -266,7 +280,12 @@ export const calculateBucketPrices: CalculateBucketPrices = args => { bucket.balance.crypto[assetId] = bnOrZero(bucket.balance.crypto[assetId]).minus(balanceDiff) }) - bucket.balance.fiat = fiatBalanceAtBucket({ bucket, priceHistoryData, portfolioAssets }) + bucket.balance.fiat = fiatBalanceAtBucket({ + bucket, + cryptoPriceHistoryData, + fiatPriceHistoryData, + portfolioAssets, + }) buckets[i] = bucket } return buckets @@ -361,13 +380,20 @@ export const useBalanceChartData: UseBalanceChartData = args => { // kick off requests for all the price histories we need useFetchPriceHistories({ assetIds, timeframe }) - const priceHistoryData = useAppSelector(state => + const cryptoPriceHistoryData = useAppSelector(state => selectCryptoPriceHistoryTimeframe(state, timeframe), ) - const priceHistoryDataLoading = useAppSelector(state => + const cryptoPriceHistoryDataLoading = useAppSelector(state => selectPriceHistoriesLoadingByAssetTimeframe(state, assetIds, timeframe), ) + const fiatPriceHistoryData = useAppSelector(state => + selectFiatPriceHistoryTimeframe(state, timeframe), + ) + const fiatPriceHistoryDataLoading = useAppSelector(state => + selectFiatPriceHistoriesLoadingByTimeframe(state, timeframe), + ) + // loading state useEffect(() => setBalanceChartDataLoading(true), [setBalanceChartDataLoading, timeframe]) @@ -376,8 +402,8 @@ export const useBalanceChartData: UseBalanceChartData = args => { // data prep const hasNoDeviceId = isNil(walletInfo?.deviceId) const hasNoAssetIds = !assetIds.length - const hasNoPriceHistoryData = isEmpty(priceHistoryData) - if (hasNoDeviceId || hasNoAssetIds || priceHistoryDataLoading || hasNoPriceHistoryData) { + const hasNoPriceHistoryData = isEmpty(cryptoPriceHistoryData) || !fiatPriceHistoryData?.length + if (hasNoDeviceId || hasNoAssetIds || hasNoPriceHistoryData || cryptoPriceHistoryDataLoading) { return setBalanceChartDataLoading(true) } @@ -390,7 +416,8 @@ export const useBalanceChartData: UseBalanceChartData = args => { const calculatedBuckets = calculateBucketPrices({ assetIds, buckets, - priceHistoryData, + cryptoPriceHistoryData, + fiatPriceHistoryData, portfolioAssets, delegationTotal, }) @@ -405,8 +432,10 @@ export const useBalanceChartData: UseBalanceChartData = args => { assets, assetIds, accountIds, - priceHistoryData, - priceHistoryDataLoading, + cryptoPriceHistoryData, + cryptoPriceHistoryDataLoading, + fiatPriceHistoryData, + fiatPriceHistoryDataLoading, txs, timeframe, balances, diff --git a/src/hooks/useFetchPriceHistories/useFetchPriceHistories.ts b/src/hooks/useFetchPriceHistories/useFetchPriceHistories.ts index f921ebc87b0..77eb6e617ba 100644 --- a/src/hooks/useFetchPriceHistories/useFetchPriceHistories.ts +++ b/src/hooks/useFetchPriceHistories/useFetchPriceHistories.ts @@ -2,7 +2,8 @@ import { AssetId } from '@shapeshiftoss/caip' import { HistoryTimeframe } from '@shapeshiftoss/types' import { useEffect } from 'react' import { marketApi } from 'state/slices/marketDataSlice/marketDataSlice' -import { useAppDispatch } from 'state/store' +import { selectSelectedCurrency } from 'state/slices/selectors' +import { useAppDispatch, useAppSelector } from 'state/store' type UseFetchPriceHistoriesArgs = { assetIds: AssetId[] @@ -13,13 +14,19 @@ type UseFetchPriceHistories = (args: UseFetchPriceHistoriesArgs) => void export const useFetchPriceHistories: UseFetchPriceHistories = ({ assetIds, timeframe }) => { const dispatch = useAppDispatch() + const symbol = useAppSelector(selectSelectedCurrency) + + const { findPriceHistoryByAssetId, findPriceHistoryByFiatSymbol } = marketApi.endpoints useEffect( () => assetIds.forEach(assetId => - dispatch(marketApi.endpoints.findPriceHistoryByAssetId.initiate({ assetId, timeframe })), + dispatch(findPriceHistoryByAssetId.initiate({ assetId, timeframe })), ), // assetIds ref changes, prevent infinite render // eslint-disable-next-line react-hooks/exhaustive-deps - [JSON.stringify(assetIds), dispatch, timeframe], + [assetIds, dispatch, timeframe], ) + useEffect(() => { + dispatch(findPriceHistoryByFiatSymbol.initiate({ symbol, timeframe })) + }, [dispatch, findPriceHistoryByFiatSymbol, symbol, timeframe]) } diff --git a/src/hooks/useLocaleFormatter/useLocaleFormatter.test.ts b/src/hooks/useLocaleFormatter/useLocaleFormatter.test.tsx similarity index 88% rename from src/hooks/useLocaleFormatter/useLocaleFormatter.test.ts rename to src/hooks/useLocaleFormatter/useLocaleFormatter.test.tsx index 024f2cc40ba..1c51b4cd063 100644 --- a/src/hooks/useLocaleFormatter/useLocaleFormatter.test.ts +++ b/src/hooks/useLocaleFormatter/useLocaleFormatter.test.tsx @@ -2,6 +2,8 @@ import 'lib/polyfills' import { renderHook, RenderHookResult } from '@testing-library/react' import { FiatTypeEnum } from 'constants/FiatTypeEnum' +import { PropsWithChildren } from 'react' +import { TestProviders } from 'test/TestProviders' import { FiatParts, NumberFormatter, NumberValue, useLocaleFormatter } from './useLocaleFormatter' @@ -14,7 +16,10 @@ function setup({ locale?: string fiat: FiatTypeEnum }): RenderHookResult, NumberFormatter> { - return renderHook(() => useLocaleFormatter({ locale, fiatType: fiat })) + const wrapper: React.FC = ({ children }) => ( + {children} + ) + return renderHook(() => useLocaleFormatter({ locale, fiatType: fiat }), { wrapper }) } describe('useLocaleFormatter', () => { @@ -327,32 +332,4 @@ describe('useLocaleFormatter', () => { }, ) }) - - describe('toSupply', () => { - const scenarios: [{ number: NumberValue }, string][] = [ - [{ number: 123.456 }, '123.45'], - [{ number: 1234.567 }, '1,234.56'], - [{ number: 12345.6789 }, '12,345'], - [{ number: 123456.789 }, '123,456'], - [{ number: 1234567.89123 }, '1.23M'], - [{ number: 12345678.9123 }, '12.34M'], - [{ number: 123456789.12345 }, '123.45M'], - [{ number: 1234567891.2345 }, '1.23 billion'], - [{ number: 12345678912.3456 }, '12.34 billion'], - [{ number: 123456789123.4567 }, '123.45 billion'], - [{ number: 1004567.89123 }, '1M'], - [{ number: 1014567.89123 }, '1.01M'], - [{ number: 10005678.9123 }, '10M'], - [{ number: 123006789.12345 }, '123M'], - [{ number: 1004567891.2345 }, '1 billion'], - [{ number: 12005678912.3456 }, '12 billion'], - [{ number: 123006789123.4567 }, '123 billion'], - ] - - it.each(scenarios)('parses %p and returns %s', async ({ number }, expected) => { - const { result } = setup({ locale: 'en-US', fiat: FiatTypeEnum.USD }) - - expect(result.current.number.toSupply(number)).toEqual(expected) - }) - }) }) diff --git a/src/hooks/useLocaleFormatter/useLocaleFormatter.ts b/src/hooks/useLocaleFormatter/useLocaleFormatter.ts index 14061029bcf..579830d590f 100644 --- a/src/hooks/useLocaleFormatter/useLocaleFormatter.ts +++ b/src/hooks/useLocaleFormatter/useLocaleFormatter.ts @@ -1,6 +1,8 @@ import { useCallback, useMemo } from 'react' import { getFiatNumberFractionDigits } from 'lib/getFiatNumberFractionDigits/getFiatNumberFractionDigits' import { logger } from 'lib/logger' +import { selectCurrencyFormat, selectSelectedCurrency } from 'state/slices/selectors' +import { useAppSelector } from 'state/store' const CRYPTO_PRECISION = 8 @@ -29,8 +31,9 @@ export type NumberFormatOptions = { maximumFractionDigits?: number minimumFractionDigits?: number notation?: 'compact' | 'standard' | 'scientific' | 'engineering' - compactDisplay?: 'short' | 'long' fiatType?: string + abbreviated?: boolean + omitDecimalTrailingZeros?: boolean } export type NumberFormatter = { @@ -44,7 +47,6 @@ export type NumberFormatter = { toParts: (value: string) => FiatParts toPercent: (number: NumberValue, options?: NumberFormatOptions) => string toString: (number: NumberValue, options?: NumberFormatOptions) => string - toSupply: (number: NumberValue, options?: NumberFormatOptions) => string } date: { toDateTime: (date: DateValue, options?: Intl.DateTimeFormatOptions) => string @@ -140,27 +142,28 @@ export const getBrowserLocales = (options = {}) => { })[0] } +type useLocaleFormatterArgs = { + locale?: string + fiatType?: string +} + /** * Set of helper functions for formatting using the user's locale * such as numbers, currencies, and dates */ -export const useLocaleFormatter = ({ - locale, - fiatType = 'USD', -}: { - locale?: string - fiatType: string -}): NumberFormatter => { - const deviceLocale = locale ?? getBrowserLocales() - +export const useLocaleFormatter = (args?: useLocaleFormatterArgs): NumberFormatter => { + const currencyFormat = useAppSelector(selectCurrencyFormat) + const deviceLocale = args?.locale ?? currencyFormat + const selectedCurrency = useAppSelector(selectSelectedCurrency) + const fiatTypeToUse = args?.fiatType ?? selectedCurrency /** * Parse a number in the current locale formatted in the selected currency. * This will determine the thousandth group separator, decimal separator, and minor units */ const localeParts = useMemo((): LocaleParts => { - return getParts(deviceLocale, fiatType) - }, [fiatType, deviceLocale]) + return getParts(deviceLocale, fiatTypeToUse) + }, [fiatTypeToUse, deviceLocale]) /** * Parse a formatted number string into a prefix, number, and postfix @@ -180,13 +183,13 @@ export const useLocaleFormatter = ({ * Helper function to abbreviate number to truncate rather than round fractions * @param {number} maximumFractionDigits - truncate fraction after this number of digits. Use 0 for no fraction. */ - function partsReducer(maximumFractionDigits: number, fiatType?: string) { + function partsReducer(maximumFractionDigits: number, omitDecimalTrailingZeros?: boolean) { return (accum: string, { type, value }: Intl.NumberFormatPart) => { let segment = value if (type === 'decimal' && maximumFractionDigits === 0) segment = '' if (type === 'fraction') { segment = value.substr(0, maximumFractionDigits) - if (!fiatType && segment && /^0*$/.test(segment)) { + if (omitDecimalTrailingZeros && segment && /^0*$/.test(segment)) { // remove trailing zeroes as well as separator character in case there are only zeroes as decimals return accum.slice(0, -1) } @@ -196,37 +199,43 @@ export const useLocaleFormatter = ({ } } - const abbreviateNumber = (number: number, fiatType?: string, options?: NumberFormatOptions) => { - const bounds = { min: 10000, max: 1000000 } - const longCompactDisplayLowerBound = 1_000_000_000 - const noDecimals = bounds.min <= number && number < bounds.max - const minDisplayValue = 0.000001 - const lessThanMin = 0 < number && minDisplayValue > number - const formatNumber = lessThanMin ? minDisplayValue : number - const minimumFractionDigits = noDecimals ? 0 : 2 - const maximumFractionDigits = Math.max( - minimumFractionDigits, - lessThanMin ? 6 : getFiatNumberFractionDigits(number), - ) - const formatter = new Intl.NumberFormat(deviceLocale, { - notation: number < bounds.min || noDecimals ? 'standard' : 'compact', - compactDisplay: fiatType || number < longCompactDisplayLowerBound ? 'short' : 'long', - style: fiatType ? 'currency' : 'decimal', - currency: fiatType, - minimumFractionDigits, - maximumFractionDigits: 10, - ...options, - }) - - const parts = formatter.formatToParts(formatNumber) - return parts.reduce(partsReducer(maximumFractionDigits, fiatType), lessThanMin ? '<' : '') - } + const abbreviateNumber = useCallback( + (number: number, fiatType?: string, options?: NumberFormatOptions) => { + const bounds = { min: 10000, max: 1000000 } + const longCompactDisplayLowerBound = 1_000_000_000 + const noDecimals = bounds.min <= number && number < bounds.max + const minDisplayValue = 0.000001 + const lessThanMin = 0 < number && minDisplayValue > number + const formatNumber = lessThanMin ? minDisplayValue : number + const minimumFractionDigits = noDecimals ? 0 : 2 + const maximumFractionDigits = Math.max( + minimumFractionDigits, + lessThanMin ? 6 : getFiatNumberFractionDigits(number), + ) + const formatter = new Intl.NumberFormat(deviceLocale, { + notation: number < bounds.min || noDecimals ? 'standard' : 'compact', + compactDisplay: fiatType || number < longCompactDisplayLowerBound ? 'short' : 'long', + style: fiatType ? 'currency' : 'decimal', + currency: fiatType, + minimumFractionDigits, + maximumFractionDigits: 10, + ...options, + }) + + const parts = formatter.formatToParts(formatNumber) + return parts.reduce( + partsReducer(maximumFractionDigits, options?.omitDecimalTrailingZeros), + lessThanMin ? '<' : '', + ) + }, + [deviceLocale], + ) /** If the number that is being formatted has a trailing decimal, add it back to the formatted number */ const showTrailingDecimal = useCallback( (num: NumberValue, formattedNum: string): string => { const parts = numberToParts(formattedNum) - const numHasDecimal = String(num).includes('.') + const numHasDecimal = String(num).includes(localeParts.decimal) const decimalWasRemoved = !parts?.number?.includes(localeParts.decimal) if (numHasDecimal && decimalWasRemoved) { parts.number = parts.number + localeParts.decimal @@ -257,7 +266,7 @@ export const useLocaleFormatter = ({ symbol = 'BTC', options?: NumberFormatOptions, ): string => { - const fractionDigits = (String(num).split('.')?.[1] ?? '').length + const fractionDigits = (String(num).split(localeParts.decimal)?.[1] ?? '').length const minimumFractionDigits = fractionDigits < CRYPTO_PRECISION ? fractionDigits : CRYPTO_PRECISION const crypto = numberToCrypto(num, symbol, { ...options, minimumFractionDigits }) @@ -269,7 +278,7 @@ export const useLocaleFormatter = ({ (value: NumberValue, options?: NumberFormatOptions): string => { try { const number = toNumber(value) - const numberFiat = options?.fiatType || fiatType + const numberFiat = options?.fiatType || fiatTypeToUse return abbreviateNumber(number, numberFiat, options) } catch (e) { // @TODO: figure out logging @@ -277,7 +286,7 @@ export const useLocaleFormatter = ({ return String(value) } }, - [fiatType, deviceLocale], // eslint-disable-line react-hooks/exhaustive-deps + [abbreviateNumber, fiatTypeToUse], ) /** @@ -288,7 +297,7 @@ export const useLocaleFormatter = ({ (num: NumberValue, options?: NumberFormatOptions): string => { try { const { fraction } = localeParts - const fractionDigits = (String(num).split('.')?.[1] ?? '').length + const fractionDigits = (String(num).split(localeParts.decimal)?.[1] ?? '').length const minimumFractionDigits = Math.min(fractionDigits, fraction) const fiat = numberToFiat(num, { ...options, minimumFractionDigits }) return showTrailingDecimal(num, fiat) @@ -302,26 +311,7 @@ export const useLocaleFormatter = ({ return String(num) } }, - [localeParts, numberToFiat], // eslint-disable-line react-hooks/exhaustive-deps - ) - - /** Format a number as a supply display value */ - const numberToSupply = useCallback( - (value: NumberValue, options?: NumberFormatOptions): string => { - try { - const number = toNumber(value) - return abbreviateNumber(number, undefined, options) - } catch (e) { - // @TODO: figure out logging - moduleLogger.error( - e, - { fn: 'numberToSupply' }, - 'Error formatting number to supply display value', - ) - return String(value) - } - }, - [deviceLocale], // eslint-disable-line react-hooks/exhaustive-deps + [localeParts, numberToFiat, showTrailingDecimal], ) const numberToPercent = (number: NumberValue, options: NumberFormatOptions = {}): string => { @@ -335,6 +325,7 @@ export const useLocaleFormatter = ({ } const numberToString = (number: NumberValue, options?: NumberFormatOptions): string => { + if (options?.abbreviated) return abbreviateNumber(toNumber(number), undefined, options) const maximumFractionDigits = options?.maximumFractionDigits ?? 8 return toNumber(number).toLocaleString(deviceLocale, { maximumFractionDigits }) } @@ -365,7 +356,6 @@ export const useLocaleFormatter = ({ toParts: numberToParts, toPercent: numberToPercent, toString: numberToString, - toSupply: numberToSupply, }, date: { toDateTime, diff --git a/src/plugins/cosmos/components/StakingInput/StakingInput.tsx b/src/plugins/cosmos/components/StakingInput/StakingInput.tsx index f19c99d5c39..480d34dbe6c 100644 --- a/src/plugins/cosmos/components/StakingInput/StakingInput.tsx +++ b/src/plugins/cosmos/components/StakingInput/StakingInput.tsx @@ -54,7 +54,7 @@ export const StakingInput = ({ }: StakingInputProps & InputGroupProps) => { const { number: { localeParts }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() return ( diff --git a/src/plugins/cosmos/components/modals/Send/Form.tsx b/src/plugins/cosmos/components/modals/Send/Form.tsx index 2f1f32524f9..1bf49178571 100644 --- a/src/plugins/cosmos/components/modals/Send/Form.tsx +++ b/src/plugins/cosmos/components/modals/Send/Form.tsx @@ -17,7 +17,7 @@ import { Address } from 'components/Modals/Send/views/Address' import { QrCodeScanner } from 'components/Modals/Send/views/QrCodeScanner' import { SelectAssetRouter } from 'components/SelectAssets/SelectAssetRouter' import { AccountSpecifier } from 'state/slices/accountSpecifiersSlice/accountSpecifiersSlice' -import { selectMarketDataById } from 'state/slices/selectors' +import { selectMarketDataById, selectSelectedCurrency } from 'state/slices/selectors' import { useAppSelector } from 'state/store' import { useFormSend } from './hooks/useFormSend/useFormSend' @@ -49,6 +49,7 @@ export const Form = ({ asset: initialAsset, accountId }: SendFormProps) => { const location = useLocation() const history = useHistory() const { handleSend } = useFormSend() + const selectedCurrency = useAppSelector(selectSelectedCurrency) const marketData = useAppSelector(state => selectMarketDataById(state, initialAsset.assetId)) const methods = useForm({ @@ -62,7 +63,7 @@ export const Form = ({ asset: initialAsset, accountId }: SendFormProps) => { cryptoAmount: '', cryptoSymbol: initialAsset?.symbol, fiatAmount: '', - fiatSymbol: 'USD', // TODO: use user preferences to get default fiat currency + fiatSymbol: selectedCurrency, }, }) @@ -71,7 +72,7 @@ export const Form = ({ asset: initialAsset, accountId }: SendFormProps) => { methods.setValue(SendFormFields.CryptoAmount, '') methods.setValue(SendFormFields.CryptoSymbol, asset.symbol) methods.setValue(SendFormFields.FiatAmount, '') - methods.setValue(SendFormFields.FiatSymbol, 'USD') + methods.setValue(SendFormFields.FiatSymbol, selectedCurrency) methods.setValue(SendFormFields.AccountId, accountId) history.push(SendRoutes.Address) diff --git a/src/plugins/foxPage/components/FoxChart.tsx b/src/plugins/foxPage/components/FoxChart.tsx index 1701b0dcecc..63e3f4745b1 100644 --- a/src/plugins/foxPage/components/FoxChart.tsx +++ b/src/plugins/foxPage/components/FoxChart.tsx @@ -20,7 +20,7 @@ export const FoxChart: React.FC = ({ assetId }) => { const [percentChange, setPercentChange] = useState(0) const { number: { toFiat }, - } = useLocaleFormatter({ fiatType: 'USD' }) + } = useLocaleFormatter() const translate = useTranslate() const marketData = useAppSelector(state => selectMarketDataById(state, assetId)) const { price } = marketData || {} diff --git a/src/plugins/foxPage/components/FoxTab.tsx b/src/plugins/foxPage/components/FoxTab.tsx index cdd9de8237a..cc79378e73c 100644 --- a/src/plugins/foxPage/components/FoxTab.tsx +++ b/src/plugins/foxPage/components/FoxTab.tsx @@ -58,8 +58,14 @@ export const FoxTab: React.FC = ({ lineHeight={'1.2'} fontSize={{ base: 'lg', md: '2xl' }} fontWeight='semibold' + maximumFractionDigits={2} + /> + - diff --git a/src/state/slices/preferencesSlice/preferencesSlice.ts b/src/state/slices/preferencesSlice/preferencesSlice.ts index 9f15563ecfa..744e4d16b04 100644 --- a/src/state/slices/preferencesSlice/preferencesSlice.ts +++ b/src/state/slices/preferencesSlice/preferencesSlice.ts @@ -9,6 +9,7 @@ dayjs.extend(localizedFormat) export type FeatureFlags = { Osmosis: boolean + MultiCurrency: boolean WalletConnectWallet: boolean Avalanche: boolean CoinbasePay: boolean @@ -21,16 +22,23 @@ export type FeatureFlags = { export type Flag = keyof FeatureFlags +export enum CurrencyFormats { + DotDecimal = 'en-US', + CommaDecimal = 'fr-FR', +} + export type Preferences = { featureFlags: FeatureFlags selectedLocale: string balanceThreshold: string selectedCurrency: SupportedFiatCurrencies + currencyFormat: CurrencyFormats } const initialState: Preferences = { featureFlags: { Osmosis: getConfig().REACT_APP_FEATURE_OSMOSIS, + MultiCurrency: getConfig().REACT_APP_FEATURE_MULTI_CURRENCY, WalletConnectWallet: getConfig().REACT_APP_FEATURE_WALLETCONNECT_WALLET, Avalanche: getConfig().REACT_APP_FEATURE_AVALANCHE, CoinbasePay: getConfig().REACT_APP_FEATURE_COINBASE_RAMP, @@ -43,6 +51,7 @@ const initialState: Preferences = { selectedLocale: simpleLocale(), balanceThreshold: '0', selectedCurrency: 'USD', + currencyFormat: CurrencyFormats.DotDecimal, } export const preferences = createSlice({ @@ -66,5 +75,8 @@ export const preferences = createSlice({ setBalanceThreshold(state, { payload }: { payload: { threshold: string } }) { state.balanceThreshold = payload.threshold }, + setCurrencyFormat(state, { payload }: { payload: { currencyFormat: CurrencyFormats } }) { + state.currencyFormat = payload.currencyFormat + }, }, }) diff --git a/src/state/slices/preferencesSlice/selectors.ts b/src/state/slices/preferencesSlice/selectors.ts index e3f437d33bd..272a27323e7 100644 --- a/src/state/slices/preferencesSlice/selectors.ts +++ b/src/state/slices/preferencesSlice/selectors.ts @@ -14,3 +14,4 @@ export const selectFeatureFlag = createCachedSelector( export const selectSelectedLocale = (state: ReduxState) => state.preferences.selectedLocale export const selectSelectedCurrency = (state: ReduxState) => state.preferences.selectedCurrency export const selectBalanceThreshold = (state: ReduxState) => state.preferences.balanceThreshold +export const selectCurrencyFormat = (state: ReduxState) => state.preferences.currencyFormat diff --git a/src/test/mocks/store.ts b/src/test/mocks/store.ts index 100d2e34eb6..2185b331290 100644 --- a/src/test/mocks/store.ts +++ b/src/test/mocks/store.ts @@ -1,5 +1,6 @@ import { ReduxState } from 'state/reducer' import { INITIAL_PRICE_HISTORY } from 'state/slices/marketDataSlice/marketDataSlice' +import { CurrencyFormats } from 'state/slices/preferencesSlice/preferencesSlice' const mockApiFactory = (reducerPath: T) => ({ queries: {}, @@ -48,6 +49,7 @@ export const mockStore: ReduxState = { preferences: { featureFlags: { Osmosis: false, + MultiCurrency: false, WalletConnectWallet: false, FoxPage: false, Avalanche: false, @@ -61,6 +63,7 @@ export const mockStore: ReduxState = { selectedLocale: 'en', balanceThreshold: '0', selectedCurrency: 'USD', + currencyFormat: CurrencyFormats.DotDecimal, // the following object is required by redux-persist _persist: { version: 0,