From 4ac313714f32deffc8ed5f53a94369e9299bae95 Mon Sep 17 00:00:00 2001 From: maro Date: Mon, 19 Feb 2024 20:14:48 +0900 Subject: [PATCH] feat: create new number/date formatters (#217) * feat: create new number/date formatters - created `formatDateRange` - created `CurrencyFormatter` - created `PercentageFormatter` - created `OverflowTooltip` to display a tooltip when the text overflows - created `AssetValueFormatter` - and more... * fix: show tooltip content without rounding * fix: wrap the text in the tooltip * refactor: use nullish coalescing operator --- src/components/Breadcrumb/index.tsx | 2 +- .../DashboardPoolTable/MobilePoolItem.tsx | 22 ++- src/components/DashboardPoolTable/index.tsx | 18 +- .../MobileTransactionItem.tsx | 33 ++-- .../DashboardTransactionTable/index.tsx | 48 +++--- src/components/SelectAssetForm/AssetItem.tsx | 7 +- src/components/Tooltip/index.tsx | 45 +++-- .../utils/AssetValueFormatter/index.tsx | 44 +++++ .../{ => utils}/ChangeRateFormatter/index.tsx | 4 +- .../utils/CurrencyFormatter/index.tsx | 34 ++++ .../{ => utils}/HoverUnderline/index.tsx | 0 .../utils/OverflowTooltip/index.tsx | 53 ++++++ .../utils/PercentageFormatter/index.tsx | 35 ++++ src/pages/Analytics/AllTokens.tsx | 12 +- src/pages/Analytics/Charts.tsx | 46 ++--- src/pages/Analytics/MobileTokenItem.tsx | 18 +- src/pages/Analytics/Recent.tsx | 2 +- src/pages/Analytics/TopMovers.tsx | 7 +- src/pages/Earn/AssetSelector.tsx | 13 +- src/pages/Earn/ImportAssetModal.tsx | 22 +-- src/pages/Earn/Lockdrop/LockdropEventItem.tsx | 28 +--- src/pages/Earn/Pools/PoolDetail/Chart.tsx | 53 ++++-- .../Earn/Pools/PoolDetail/PoolSummary.tsx | 21 +-- src/pages/Earn/Pools/PoolItem.tsx | 82 ++++----- src/pages/Earn/Pools/Provide/InputGroup.tsx | 9 +- src/pages/Earn/Pools/Provide/index.tsx | 158 +++++++++--------- src/pages/Earn/Pools/Withdraw/index.tsx | 23 +-- src/pages/Tokens/TokenDetail/Chart.tsx | 54 ++++-- src/pages/Tokens/TokenDetail/TokenSummary.tsx | 4 +- src/pages/Tokens/TokenDetail/index.tsx | 10 +- src/pages/Trade/Swap/index.tsx | 58 +++---- src/pages/Wallet/Assets.tsx | 21 +-- src/pages/Wallet/MobileAssetItem.tsx | 19 +-- src/pages/Wallet/PoolItem.tsx | 60 +++---- src/utils/index.ts | 42 ++++- 35 files changed, 654 insertions(+), 453 deletions(-) create mode 100644 src/components/utils/AssetValueFormatter/index.tsx rename src/components/{ => utils}/ChangeRateFormatter/index.tsx (88%) create mode 100644 src/components/utils/CurrencyFormatter/index.tsx rename src/components/{ => utils}/HoverUnderline/index.tsx (100%) create mode 100644 src/components/utils/OverflowTooltip/index.tsx create mode 100644 src/components/utils/PercentageFormatter/index.tsx diff --git a/src/components/Breadcrumb/index.tsx b/src/components/Breadcrumb/index.tsx index dc4861a6..1edaa594 100644 --- a/src/components/Breadcrumb/index.tsx +++ b/src/components/Breadcrumb/index.tsx @@ -1,5 +1,5 @@ import styled from "@emotion/styled"; -import HoverUnderline from "components/HoverUnderline"; +import HoverUnderline from "components/utils/HoverUnderline"; import Typography from "components/Typography"; import { Link, LinkProps } from "react-router-dom"; diff --git a/src/components/DashboardPoolTable/MobilePoolItem.tsx b/src/components/DashboardPoolTable/MobilePoolItem.tsx index 31219a52..f1fc3667 100644 --- a/src/components/DashboardPoolTable/MobilePoolItem.tsx +++ b/src/components/DashboardPoolTable/MobilePoolItem.tsx @@ -2,7 +2,7 @@ import { css } from "@emotion/react"; import styled from "@emotion/styled"; import { Numeric } from "@xpla/xpla.js"; import AssetIcon from "components/AssetIcon"; -import HoverUnderline from "components/HoverUnderline"; +import HoverUnderline from "components/utils/HoverUnderline"; import Typography from "components/Typography"; import useAssets from "hooks/useAssets"; import useModal from "hooks/useModal"; @@ -12,7 +12,8 @@ import React, { useMemo } from "react"; import { Row, Col } from "react-grid-system"; import { Link } from "react-router-dom"; import { DashboardPool } from "types/dashboard-api"; -import { formatCurrency, formatPercentage } from "utils"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; +import PercentageFormatter from "components/utils/PercentageFormatter"; interface MobilePoolItemProps { pool: DashboardPool; @@ -135,12 +136,16 @@ function MobilePoolItem({ pool, number }: MobilePoolItemProps) { {expand.isOpen && (
- {formatCurrency(pool.tvl)} + + +
)}
- {formatCurrency(pool.volume)} + + +
} @@ -148,13 +153,16 @@ function MobilePoolItem({ pool, number }: MobilePoolItemProps) {
- {formatCurrency(pool.fee)} + + +
- {!Number.isNaN(Number(pool.apr)) && - formatPercentage(Numeric.parse(pool.apr).mul(100))} + {!Number.isNaN(Number(pool.apr)) && ( + + )}
diff --git a/src/components/DashboardPoolTable/index.tsx b/src/components/DashboardPoolTable/index.tsx index 2675af95..63719a3f 100644 --- a/src/components/DashboardPoolTable/index.tsx +++ b/src/components/DashboardPoolTable/index.tsx @@ -1,5 +1,5 @@ import { css } from "@emotion/react"; -import HoverUnderline from "components/HoverUnderline"; +import HoverUnderline from "components/utils/HoverUnderline"; import Input from "components/Input"; import Pagination from "components/Pagination"; import Panel from "components/Panel"; @@ -10,12 +10,13 @@ import useAssets from "hooks/useAssets"; import { useEffect, useMemo, useState } from "react"; import { Col, Row, useScreenClass } from "react-grid-system"; import { DashboardPool } from "types/dashboard-api"; -import { formatCurrency, formatPercentage } from "utils"; import { Numeric } from "@xpla/xpla.js"; import { getBasicSortFunction } from "utils/table"; import usePairs from "hooks/usePairs"; import AssetIcon from "components/AssetIcon"; import { Link } from "react-router-dom"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; +import PercentageFormatter from "components/utils/PercentageFormatter"; import MobilePoolItem from "./MobilePoolItem"; interface DashboardPoolTableProps { @@ -235,7 +236,7 @@ function DashboardPoolTable({ width: 220, hasSort: true, render(value) { - return value && formatCurrency(value); + return value && ; }, }, { @@ -244,7 +245,7 @@ function DashboardPoolTable({ width: 220, hasSort: true, render(value) { - return value && formatCurrency(value); + return value && ; }, }, { @@ -253,7 +254,7 @@ function DashboardPoolTable({ width: 220, hasSort: true, render(value) { - return value && formatCurrency(value); + return value && ; }, }, { @@ -264,8 +265,11 @@ function DashboardPoolTable({ render(value) { return ( value && - !Number.isNaN(Number(value)) && - formatPercentage(Numeric.parse(value).mul(100)) + !Number.isNaN(Number(value)) && ( + + ) ); }, }, diff --git a/src/components/DashboardTransactionTable/MobileTransactionItem.tsx b/src/components/DashboardTransactionTable/MobileTransactionItem.tsx index bfda5211..a8bebd46 100644 --- a/src/components/DashboardTransactionTable/MobileTransactionItem.tsx +++ b/src/components/DashboardTransactionTable/MobileTransactionItem.tsx @@ -1,6 +1,6 @@ import { css } from "@emotion/react"; import styled from "@emotion/styled"; -import HoverUnderline from "components/HoverUnderline"; +import HoverUnderline from "components/utils/HoverUnderline"; import Typography from "components/Typography"; import useAssets from "hooks/useAssets"; import useModal from "hooks/useModal"; @@ -9,14 +9,13 @@ import Expand from "pages/Earn/Expand"; import React, { useMemo } from "react"; import { DashboardTransaction } from "types/dashboard-api"; import { - amountToValue, ellipsisCenter, - formatCurrency, - formatNumber, getAddressLink, getFromNow, getTransactionLink, } from "utils"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; +import AssetValueFormatter from "components/utils/AssetValueFormatter"; interface MobileTransactionItemProps { transaction: DashboardTransaction; @@ -83,30 +82,26 @@ function MobileTransactionItem({ transaction }: MobileTransactionItemProps) { <>
- {formatCurrency(transaction.totalValue)} + + +
- {formatNumber( - amountToValue( - `${transaction.asset0amount}`, - asset0?.decimals, - ) || "", - )} -  {asset0?.symbol} +
- {formatNumber( - amountToValue( - `${transaction.asset1amount}`, - asset1?.decimals, - ) || "", - )} -  {asset1?.symbol} +
diff --git a/src/components/DashboardTransactionTable/index.tsx b/src/components/DashboardTransactionTable/index.tsx index 34a7ac6e..fbfb62ed 100644 --- a/src/components/DashboardTransactionTable/index.tsx +++ b/src/components/DashboardTransactionTable/index.tsx @@ -1,5 +1,5 @@ import { css } from "@emotion/react"; -import HoverUnderline from "components/HoverUnderline"; +import HoverUnderline from "components/utils/HoverUnderline"; import Input from "components/Input"; import Pagination from "components/Pagination"; import Panel from "components/Panel"; @@ -14,15 +14,15 @@ import { useEffect, useMemo, useState } from "react"; import { Col, Row, useScreenClass } from "react-grid-system"; import { DashboardTransaction } from "types/dashboard-api"; import { - amountToValue, ellipsisCenter, - formatCurrency, - formatNumber, getAddressLink, getFromNow, getTransactionLink, } from "utils"; import { getBasicSortFunction } from "utils/table"; +import OverflowTooltip from "components/utils/OverflowTooltip"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; +import AssetValueFormatter from "components/utils/AssetValueFormatter"; import MobileTransactionItem from "./MobileTransactionItem"; interface DashboardTransactionTableProps { @@ -224,16 +224,18 @@ function DashboardTransactionTable({ data }: DashboardTransactionTableProps) { }, render(actionDisplay, row) { return ( - - {actionDisplay} - + + + {actionDisplay} + + ); }, }, @@ -243,7 +245,7 @@ function DashboardTransactionTable({ data }: DashboardTransactionTableProps) { width: 185, hasSort: true, render(value) { - return formatCurrency(`${value}`); + return value && ; }, }, { @@ -253,9 +255,11 @@ function DashboardTransactionTable({ data }: DashboardTransactionTableProps) { hasSort: true, render(amount, row) { const asset = getAsset(row.asset0); - return `${formatNumber( - amountToValue(`${amount}`, asset?.decimals) || "", - )} ${asset?.symbol}`; + return ( + amount && ( + + ) + ); }, }, { @@ -265,9 +269,11 @@ function DashboardTransactionTable({ data }: DashboardTransactionTableProps) { hasSort: true, render(amount, row) { const asset = getAsset(row.asset1); - return `${formatNumber( - amountToValue(`${amount}`, asset?.decimals) || "", - )} ${asset?.symbol}`; + return ( + amount && ( + + ) + ); }, }, { diff --git a/src/components/SelectAssetForm/AssetItem.tsx b/src/components/SelectAssetForm/AssetItem.tsx index ea48e7b6..979e34fb 100644 --- a/src/components/SelectAssetForm/AssetItem.tsx +++ b/src/components/SelectAssetForm/AssetItem.tsx @@ -8,12 +8,13 @@ import useBalance from "hooks/useBalance"; import useIsInViewport from "hooks/useIsInViewport"; import { useRef } from "react"; import { Token } from "types/api"; -import { formatNumber, cutDecimal, amountToValue, ellipsisCenter } from "utils"; +import { ellipsisCenter } from "utils"; import iconToken from "assets/icons/icon-default-token.svg"; import iconVerified from "assets/icons/icon-verified.svg"; import iconBookmark from "assets/icons/icon-bookmark-default.svg"; import iconBookmarkSelected from "assets/icons/icon-bookmark-selected.svg"; +import AssetValueFormatter from "components/utils/AssetValueFormatter"; interface WrapperProps { selected?: boolean; @@ -121,9 +122,7 @@ function Balance({ asset }: { asset?: Partial }) { padding-left: 10px; `} > - {formatNumber( - cutDecimal(amountToValue(balance || 0, asset?.decimals) || 0, 3), - )} + ); } diff --git a/src/components/Tooltip/index.tsx b/src/components/Tooltip/index.tsx index a1f19e67..0b22f935 100644 --- a/src/components/Tooltip/index.tsx +++ b/src/components/Tooltip/index.tsx @@ -1,5 +1,5 @@ import Tippy, { TippyProps } from "@tippyjs/react"; -import { useEffect } from "react"; +import { ComponentRef, forwardRef, useEffect } from "react"; import SimpleBar from "simplebar"; import { hideAll } from "tippy.js"; @@ -12,25 +12,34 @@ function handleScroll() { hideAll(); } -function Tooltip({ children, ...props }: TippyProps) { - useEffect(() => { - const simpleBar = SimpleBar.instances.get(document.body); +const Tooltip = forwardRef, TippyProps>( + function Tooltip({ children, ...props }, ref) { + useEffect(() => { + const simpleBar = SimpleBar.instances.get(document.body); - window.addEventListener("scroll", handleScroll); - try { - simpleBar.getScrollElement().addEventListener("scroll", handleScroll); - } catch (error) { - console.log(error); - } + window.addEventListener("scroll", handleScroll); + try { + simpleBar.getScrollElement().addEventListener("scroll", handleScroll); + } catch (error) { + console.log(error); + } - // No need to remove event listener since the browser does not add duplicate event listeners - }, []); + // No need to remove event listener since the browser does not add duplicate event listeners + }, []); - return ( - - {children} - - ); -} + return ( + + {children} + + ); + }, +); export default Tooltip; diff --git a/src/components/utils/AssetValueFormatter/index.tsx b/src/components/utils/AssetValueFormatter/index.tsx new file mode 100644 index 00000000..b290c0b6 --- /dev/null +++ b/src/components/utils/AssetValueFormatter/index.tsx @@ -0,0 +1,44 @@ +import Decimal from "decimal.js"; +import { Token } from "types/api"; +import { amountToValue, formatDecimals, formatNumber } from "utils"; +import { Numeric } from "@xpla/xpla.js"; +import { css } from "@emotion/react"; +import OverflowTooltip from "../OverflowTooltip"; + +interface AssetValueFormatterProps { + asset?: Partial>; + amount?: Decimal.Value; + showSymbol?: boolean; +} + +function AssetValueFormatter({ + asset, + amount, + showSymbol = true, +}: AssetValueFormatterProps) { + const parsedValue = Numeric.parse( + amountToValue(amount, asset?.decimals) || "0", + ); + const formattedValue = formatNumber(formatDecimals(parsedValue, 3)); + const valueToDisplay = + parsedValue.lt(0.001) && !parsedValue.eq(0) ? "< 0.001" : formattedValue; + return ( + + {parsedValue.toFixed()} + {showSymbol && ` ${asset?.symbol || ""}`} +
+ } + disabled={valueToDisplay !== formattedValue ? false : undefined} + > + {`${valueToDisplay}${showSymbol ? ` ${asset?.symbol || ""}` : ""}`} + + ); +} + +export default AssetValueFormatter; diff --git a/src/components/ChangeRateFormatter/index.tsx b/src/components/utils/ChangeRateFormatter/index.tsx similarity index 88% rename from src/components/ChangeRateFormatter/index.tsx rename to src/components/utils/ChangeRateFormatter/index.tsx index 2af02115..aa255843 100644 --- a/src/components/ChangeRateFormatter/index.tsx +++ b/src/components/utils/ChangeRateFormatter/index.tsx @@ -1,7 +1,7 @@ import { css, useTheme } from "@emotion/react"; import { Numeric } from "@xpla/xpla.js"; import { useMemo } from "react"; -import { formatPercentage } from "utils"; +import PercentageFormatter from "../PercentageFormatter"; interface ChangeRateFormatterProps { rate?: Numeric.Input; @@ -34,7 +34,7 @@ function ChangeRateFormatter({ > {hasBrackets && "("} {arrow} - {formatPercentage(numericRate.abs())} + {hasBrackets && ")"} ) : null; diff --git a/src/components/utils/CurrencyFormatter/index.tsx b/src/components/utils/CurrencyFormatter/index.tsx new file mode 100644 index 00000000..7d0160d4 --- /dev/null +++ b/src/components/utils/CurrencyFormatter/index.tsx @@ -0,0 +1,34 @@ +import { formatCurrency } from "utils"; +import { Numeric } from "@xpla/xpla.js"; +import { css } from "@emotion/react"; +import OverflowTooltip from "../OverflowTooltip"; + +interface CurrencyFormatterProps { + value?: Parameters[0]; +} + +function CurrencyFormatter({ value = 0 }: CurrencyFormatterProps) { + const parsedValue = Numeric.parse(value); + const formattedValue = formatCurrency(value); + const isDecimalTooLong = (formattedValue?.split(".")[1]?.length || 0) > 2; + const valueToDisplay = + isDecimalTooLong && parsedValue.lt(0.01) ? "< $0.01" : formattedValue; + return ( + + ${parsedValue.toFixed()} + + } + disabled={valueToDisplay !== formattedValue ? false : undefined} + > + {valueToDisplay} + + ); +} + +export default CurrencyFormatter; diff --git a/src/components/HoverUnderline/index.tsx b/src/components/utils/HoverUnderline/index.tsx similarity index 100% rename from src/components/HoverUnderline/index.tsx rename to src/components/utils/HoverUnderline/index.tsx diff --git a/src/components/utils/OverflowTooltip/index.tsx b/src/components/utils/OverflowTooltip/index.tsx new file mode 100644 index 00000000..97710695 --- /dev/null +++ b/src/components/utils/OverflowTooltip/index.tsx @@ -0,0 +1,53 @@ +import Tippy, { TippyProps } from "@tippyjs/react"; +import Tooltip from "components/Tooltip"; +import { + ComponentRef, + forwardRef, + useEffect, + useImperativeHandle, + useRef, + useState, +} from "react"; + +interface OverflowTooltipProps extends Omit { + children: TippyProps["children"] | string; +} + +const OverflowTooltip = forwardRef< + ComponentRef, + OverflowTooltipProps +>(function OverflowTooltip( + { disabled: disabledFromProps, content, children, ...props }, + refFromProps, +) { + const [disabled, setDisabled] = useState(false); + const tooltipRef = useRef(null); + useImperativeHandle(refFromProps, () => tooltipRef.current as Element); + + useEffect(() => { + const handleResize = () => { + const parentElement = tooltipRef.current?.parentElement; + if (parentElement) { + setDisabled(parentElement.clientWidth >= parentElement.scrollWidth); + } + }; + handleResize(); + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, [props]); + + return ( + + {typeof children === "string" ? {children} : children} + + ); +}); + +export default OverflowTooltip; diff --git a/src/components/utils/PercentageFormatter/index.tsx b/src/components/utils/PercentageFormatter/index.tsx new file mode 100644 index 00000000..e36906dd --- /dev/null +++ b/src/components/utils/PercentageFormatter/index.tsx @@ -0,0 +1,35 @@ +import Decimal from "decimal.js"; +import { formatPercentage } from "utils"; +import { Numeric } from "@xpla/xpla.js"; +import { css } from "@emotion/react"; +import OverflowTooltip from "../OverflowTooltip"; + +interface PercentageFormatterProps { + value: Decimal.Value; +} + +function PercentageFormatter({ value }: PercentageFormatterProps) { + const parsedValue = Numeric.parse(value); + const formattedValue = formatPercentage(value); + const valueToDisplay = + !parsedValue.eq(0) && parsedValue.lt(0.01) ? "<0.01%" : formattedValue; + + return ( + + {parsedValue.toFixed()}% + + } + disabled={valueToDisplay !== formattedValue ? false : undefined} + > + {valueToDisplay} + + ); +} + +export default PercentageFormatter; diff --git a/src/pages/Analytics/AllTokens.tsx b/src/pages/Analytics/AllTokens.tsx index 666c55c2..8cb5674c 100644 --- a/src/pages/Analytics/AllTokens.tsx +++ b/src/pages/Analytics/AllTokens.tsx @@ -10,12 +10,12 @@ import { useEffect, useMemo, useState } from "react"; import { Col, Row, useScreenClass } from "react-grid-system"; import useDashboard from "hooks/dashboard/useDashboard"; import useAssets from "hooks/useAssets"; -import { formatCurrency } from "utils"; import { Link } from "react-router-dom"; -import HoverUnderline from "components/HoverUnderline"; -import ChangeRateFormatter from "components/ChangeRateFormatter"; +import HoverUnderline from "components/utils/HoverUnderline"; +import ChangeRateFormatter from "components/utils/ChangeRateFormatter"; import { DashboardToken } from "types/dashboard-api"; import { getBasicSortFunction } from "utils/table"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; import MobileTokenItem from "./MobileTokenItem"; function AllTokens() { @@ -236,7 +236,7 @@ function AllTokens() { width: 190, hasSort: true, render(price) { - return formatCurrency(`${price}`); + return price && ; }, }, { @@ -262,7 +262,7 @@ function AllTokens() { width: 190, hasSort: true, render(value) { - return `${formatCurrency(`${value}`)}`; + return value && ; }, }, { @@ -271,7 +271,7 @@ function AllTokens() { width: 190, hasSort: true, render(value) { - return `${formatCurrency(`${value}`)}`; + return value && ; }, }, ]} diff --git a/src/pages/Analytics/Charts.tsx b/src/pages/Analytics/Charts.tsx index b3eaaa47..a3c0f451 100644 --- a/src/pages/Analytics/Charts.tsx +++ b/src/pages/Analytics/Charts.tsx @@ -11,6 +11,7 @@ import { formatDate, formatCurrency, getSumOfDashboardChartData, + formatDateRange, } from "utils"; import useDashboard from "hooks/dashboard/useDashboard"; import { @@ -22,6 +23,7 @@ import iconFullscreen from "assets/icons/icon-fullscreen.svg"; import useHashModal from "hooks/useHashModal"; import { useId, useMemo } from "react"; import Modal from "components/Modal"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; import useChartData from "./useChartData"; const Label = styled(Typography)``; @@ -232,23 +234,29 @@ function Charts() { } defaultCaption={ volume?.data?.[0] - ? `${formatDate(volume.data[0].t)} - ${formatDate( + ? formatDateRange( + volume.data[0].t, volume.data[volume.data.length - 1]?.t, - )}` + ) : "" } data={volume.data?.map((item) => item.v) || []} duration={volume.duration} onDurationChange={(duration) => volume.setDuration(duration)} renderTooltip={({ value, index }) => { - const [prevDate, currentDate] = [index - 1, index].map( - (i) => volume.data?.[i] && formatDate(volume.data?.[i]?.t), - ); + const [prevDate, currentDate] = [ + volume.data?.[index - 1]?.t, + volume.data?.[index]?.t, + ]; + + if (!currentDate) { + return null; + } const formattedDate = - prevDate === currentDate - ? prevDate - : [prevDate, currentDate].join(" - "); + volume.duration === "month" + ? formatDate(currentDate) + : formatDateRange(prevDate, currentDate); return (
- {formatCurrency(value)} + - {volume.duration === "month" ? currentDate : formattedDate} + {formattedDate}
); @@ -286,7 +294,7 @@ function Charts() { `} > - {formatCurrency(value)} + {tvl.data?.[index] && formatDate(tvl.data?.[index].t)} @@ -306,9 +314,10 @@ function Charts() { )} defaultCaption={ addresses?.[0] - ? `${formatDate(addresses[0].t)} - ${formatDate( + ? formatDateRange( + addresses[0].t, addresses[addresses.length - 1]?.t, - )}` + ) : "" } data={addresses?.map((item) => item.v) || []} @@ -335,9 +344,10 @@ function Charts() { )} defaultCaption={ transactions?.[0] - ? `${formatDate(transactions[0].t)} - ${formatDate( + ? formatDateRange( + transactions[0].t, transactions[transactions.length - 1]?.t, - )}` + ) : "" } data={transactions?.map((item) => item.v) || []} @@ -362,9 +372,7 @@ function Charts() { defaultValue={`${formatCurrency(getSumOfDashboardChartData(fees))}`} defaultCaption={ fees?.[0] - ? `${formatDate(fees[0].t)} - ${formatDate( - fees[fees.length - 1]?.t, - )}` + ? formatDateRange(fees[0].t, fees[fees.length - 1]?.t) : "" } data={fees?.map((item) => item.v) || []} @@ -375,7 +383,7 @@ function Charts() { `} > - {formatCurrency(value)} + {fees?.[index] && formatDate(fees?.[index].t)} diff --git a/src/pages/Analytics/MobileTokenItem.tsx b/src/pages/Analytics/MobileTokenItem.tsx index e08bbc38..78d1c48a 100644 --- a/src/pages/Analytics/MobileTokenItem.tsx +++ b/src/pages/Analytics/MobileTokenItem.tsx @@ -7,10 +7,10 @@ import { Col, Row } from "react-grid-system"; import styled from "@emotion/styled"; import { DashboardToken } from "types/dashboard-api"; import useAssets from "hooks/useAssets"; -import { formatCurrency } from "utils"; -import ChangeRateFormatter from "components/ChangeRateFormatter"; +import ChangeRateFormatter from "components/utils/ChangeRateFormatter"; import { Link } from "react-router-dom"; -import HoverUnderline from "components/HoverUnderline"; +import HoverUnderline from "components/utils/HoverUnderline"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; interface MobileTokenItemProps { number: number; @@ -133,7 +133,9 @@ function MobileTokenItem({ number, token }: MobileTokenItemProps) {
- {formatCurrency(token.price)} + + +
} @@ -147,11 +149,15 @@ function MobileTokenItem({ number, token }: MobileTokenItemProps) {
- {formatCurrency(token.volume24h)} + + +
- {formatCurrency(token.tvl)} + + +
diff --git a/src/pages/Analytics/Recent.tsx b/src/pages/Analytics/Recent.tsx index c0f73ba0..63d0dc64 100644 --- a/src/pages/Analytics/Recent.tsx +++ b/src/pages/Analytics/Recent.tsx @@ -1,6 +1,6 @@ import { css } from "@emotion/react"; import styled from "@emotion/styled"; -import ChangeRateFormatter from "components/ChangeRateFormatter"; +import ChangeRateFormatter from "components/utils/ChangeRateFormatter"; import Panel from "components/Panel"; import Typography from "components/Typography"; import { MOBILE_SCREEN_CLASS, TABLET_SCREEN_CLASS } from "constants/layout"; diff --git a/src/pages/Analytics/TopMovers.tsx b/src/pages/Analytics/TopMovers.tsx index e73f729a..0659499b 100644 --- a/src/pages/Analytics/TopMovers.tsx +++ b/src/pages/Analytics/TopMovers.tsx @@ -2,7 +2,7 @@ import { css } from "@emotion/react"; import { Numeric } from "@xpla/xpla.js"; import AssetIcon from "components/AssetIcon"; import Box from "components/Box"; -import ChangeRateFormatter from "components/ChangeRateFormatter"; +import ChangeRateFormatter from "components/utils/ChangeRateFormatter"; import Marquee from "components/Marquee"; import Panel from "components/Panel"; import Typography from "components/Typography"; @@ -12,7 +12,7 @@ import useDashboard from "hooks/dashboard/useDashboard"; import { useMemo } from "react"; import { Col, Row, useScreenClass } from "react-grid-system"; import { Link } from "react-router-dom"; -import { formatCurrency } from "utils"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; function TopMovers() { const screenClass = useScreenClass(); @@ -116,7 +116,8 @@ function TopMovers() { )} - {formatCurrency(token.price)}  + +   - {formatNumber( - formatDecimals( - amountToValue(balance, asset?.decimals) || 0, - 3, - ), - )} + diff --git a/src/pages/Earn/ImportAssetModal.tsx b/src/pages/Earn/ImportAssetModal.tsx index abfd95be..6eefe3eb 100644 --- a/src/pages/Earn/ImportAssetModal.tsx +++ b/src/pages/Earn/ImportAssetModal.tsx @@ -13,18 +13,8 @@ import { useDeferredValue, useEffect, useMemo, useState } from "react"; import ReactModal from "react-modal"; import { TokenInfo } from "types/token"; import iconDefaultToken from "assets/icons/icon-default-token.svg"; -import { - amountToValue, - cutDecimal, - formatNumber, - getIbcTokenHash, - isNativeTokenAddress, -} from "utils"; -import { - DISPLAY_DECIMAL, - MOBILE_SCREEN_CLASS, - MODAL_CLOSE_TIMEOUT_MS, -} from "constants/layout"; +import { getIbcTokenHash, isNativeTokenAddress } from "utils"; +import { MOBILE_SCREEN_CLASS, MODAL_CLOSE_TIMEOUT_MS } from "constants/layout"; import useCustomAssets from "hooks/useCustomAssets"; import { useScreenClass } from "react-grid-system"; import usePairs from "hooks/usePairs"; @@ -34,6 +24,7 @@ import imgSuccess from "assets/images/success-import.svg"; import useVerifiedAssets from "hooks/useVerifiedAssets"; import { Token } from "types/api"; import useLCDClient from "hooks/useLCDClient"; +import AssetValueFormatter from "components/utils/AssetValueFormatter"; interface ImportAssetModalProps extends ReactModal.Props { onFinish?(asset: Token): void; @@ -362,12 +353,7 @@ function ImportAssetModal({ onFinish, ...modalProps }: ImportAssetModalProps) {
- {formatNumber( - cutDecimal( - amountToValue(balance || 0, tokenInfo?.decimals) || 0, - DISPLAY_DECIMAL, - ), - )} +
diff --git a/src/pages/Earn/Lockdrop/LockdropEventItem.tsx b/src/pages/Earn/Lockdrop/LockdropEventItem.tsx index 6548e092..e2f5f1c5 100644 --- a/src/pages/Earn/Lockdrop/LockdropEventItem.tsx +++ b/src/pages/Earn/Lockdrop/LockdropEventItem.tsx @@ -43,6 +43,7 @@ import { import { useConnectedWallet } from "@xpla/wallet-provider"; import Outlink from "components/Outlink"; import TooltipWithIcon from "components/Tooltip/TooltipWithIcon"; +import AssetValueFormatter from "components/utils/AssetValueFormatter"; import Expand from "../Expand"; const Wrapper = styled(Box)<{ isNeedAction?: boolean }>` @@ -499,30 +500,19 @@ function LockdropEventItem({
{isSmallScreen && }
- {formatNumber( - formatDecimals( - amountToValue( - lockdropEvent.total_reward, - rewardAsset?.decimals, - ) || "", - 2, - ), - )} -   - {rewardAsset?.symbol} +
{isSmallScreen && }
- {formatNumber( - formatDecimals( - amountToValue(lockdropEvent.total_locked_lp, LP_DECIMALS) || - "", - 2, - ), - )} -  LP +
diff --git a/src/pages/Earn/Pools/PoolDetail/Chart.tsx b/src/pages/Earn/Pools/PoolDetail/Chart.tsx index 2e192d61..29e19126 100644 --- a/src/pages/Earn/Pools/PoolDetail/Chart.tsx +++ b/src/pages/Earn/Pools/PoolDetail/Chart.tsx @@ -9,7 +9,7 @@ import { DashboardChartDuration, DashboardChartType, } from "types/dashboard-api"; -import { formatCurrency, formatDate, formatPercentage } from "utils"; +import { formatDate, formatDateRange, getSumOfDashboardChartData } from "utils"; import Select from "pages/Earn/Pools/Select"; import { MOBILE_SCREEN_CLASS } from "constants/layout"; import { Numeric } from "@xpla/xpla.js"; @@ -17,6 +17,8 @@ import IconButton from "components/IconButton"; import iconFullscreen from "assets/icons/icon-fullscreen.svg"; import useHashModal from "hooks/useHashModal"; import Modal from "components/Modal"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; +import PercentageFormatter from "components/utils/PercentageFormatter"; import useChartData from "./useChartData"; const chartTypeTabs: { @@ -58,9 +60,11 @@ const chartValueFormatter = ( value: Numeric.Input, chartType: DashboardChartType, ) => { - return chartType === "apr" - ? formatPercentage(Numeric.parse(value).mul(100)) - : formatCurrency(value); + return chartType === "apr" ? ( + + ) : ( + + ); }; function Chart({ tokenAddress: poolAddress }: { tokenAddress: string }) { @@ -136,10 +140,16 @@ function Chart({ tokenAddress: poolAddress }: { tokenAddress: string }) { > - {chartValueFormatter( - chartData?.[chartData.length - 1]?.v.toString() || "0", - selectedChartType, + {selectedChartType === "volume" && ( + )} + {selectedChartType !== "volume" && + chartValueFormatter( + chartData?.[chartData.length - 1]?.v.toString() || "0", + selectedChartType, + )} @@ -151,10 +161,13 @@ function Chart({ tokenAddress: poolAddress }: { tokenAddress: string }) { margin-bottom: 7px; `} > - {chartData?.[0] - ? `${formatDate(chartData[0].t)} - ${formatDate( - chartData[chartData.length - 1].t, - )}` + {selectedChartType === "volume" && + formatDateRange( + chartData?.[0]?.t, + chartData?.[chartData.length - 1]?.t, + )} + {selectedChartType !== "volume" && chartData?.[0] + ? formatDate(chartData[chartData.length - 1].t) : ""} @@ -166,6 +179,20 @@ function Chart({ tokenAddress: poolAddress }: { tokenAddress: string }) { return { data: chartData?.map((item) => item.v) || [], renderTooltip({ value, index }) { + const [prevDate, currentDate] = [ + chartData?.[index - 1]?.t, + chartData?.[index]?.t, + ]; + + if (!currentDate) { + return null; + } + + const formattedDate = + duration !== "month" && selectedChartType === "volume" + ? formatDateRange(prevDate, currentDate) + : formatDate(currentDate); + return (
- {chartData?.[index] && formatDate(chartData?.[index].t)} + {formattedDate}
); }, }; - }, [chartData, selectedChartType]); + }, [chartData, duration, selectedChartType]); return ( diff --git a/src/pages/Earn/Pools/PoolDetail/PoolSummary.tsx b/src/pages/Earn/Pools/PoolDetail/PoolSummary.tsx index 2f83fe4d..e2e4800f 100644 --- a/src/pages/Earn/Pools/PoolDetail/PoolSummary.tsx +++ b/src/pages/Earn/Pools/PoolDetail/PoolSummary.tsx @@ -2,23 +2,19 @@ import { css } from "@emotion/react"; import styled from "@emotion/styled"; import { Numeric } from "@xpla/xpla.js"; import AssetIcon from "components/AssetIcon"; -import ChangeRateFormatter from "components/ChangeRateFormatter"; +import ChangeRateFormatter from "components/utils/ChangeRateFormatter"; import Hr from "components/Hr"; import Panel from "components/Panel"; import Typography from "components/Typography"; import useAssets from "hooks/useAssets"; import { Row, Col } from "react-grid-system"; -import { - amountToValue, - formatDecimals, - formatNumber, - formatPercentage, -} from "utils"; +import { amountToValue, formatDecimals, formatNumber } from "utils"; import usePool from "hooks/usePool"; import { getAddressFromAssetInfo } from "utils/dezswap"; import useDashboardPoolDetail from "hooks/dashboard/useDashboardPoolDetail"; import { Link } from "react-router-dom"; -import HoverUnderline from "components/HoverUnderline"; +import HoverUnderline from "components/utils/HoverUnderline"; +import PercentageFormatter from "components/utils/PercentageFormatter"; const Wrapper = styled.div` width: 100%; @@ -150,10 +146,11 @@ function PoolSummary({ poolAddress }: { poolAddress: string }) {
- {!Number.isNaN(Number(dashboardPoolData?.recent.apr)) && - formatPercentage( - Numeric.parse(dashboardPoolData?.recent.apr || 0).mul(100), - )} + {!Number.isNaN(Number(dashboardPoolData?.recent.apr)) && ( + + )} diff --git a/src/pages/Earn/Pools/PoolItem.tsx b/src/pages/Earn/Pools/PoolItem.tsx index 38dca36e..b5293ca5 100644 --- a/src/pages/Earn/Pools/PoolItem.tsx +++ b/src/pages/Earn/Pools/PoolItem.tsx @@ -8,14 +8,7 @@ import useBalance from "hooks/useBalance"; import useNetwork from "hooks/useNetwork"; import { useEffect, useMemo, useRef, useState } from "react"; import { Row, Col, useScreenClass, Hidden } from "react-grid-system"; -import { - formatNumber, - formatDecimals, - amountToValue, - getAddressLink, - formatCurrency, - formatPercentage, -} from "utils"; +import { formatNumber, formatDecimals, getAddressLink } from "utils"; import iconDefaultToken from "assets/icons/icon-default-token.svg"; import iconBookmark from "assets/icons/icon-bookmark-default.svg"; import iconBookmarkSelected from "assets/icons/icon-bookmark-selected.svg"; @@ -33,9 +26,12 @@ import Tooltip from "components/Tooltip"; import usePairs from "hooks/usePairs"; import Outlink from "components/Outlink"; import SimplePieChart from "components/SimplePieChart"; -import HoverUnderline from "components/HoverUnderline"; +import HoverUnderline from "components/utils/HoverUnderline"; import usePool from "hooks/usePool"; import useDashboard from "hooks/dashboard/useDashboard"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; +import AssetValueFormatter from "components/utils/AssetValueFormatter"; +import PercentageFormatter from "components/utils/PercentageFormatter"; import Expand from "../Expand"; const TableRow = styled(Box)` @@ -329,19 +325,24 @@ function PoolItem({ poolAddress, bookmarked, onBookmarkClick }: PoolItemProps) {
{isSmallScreen && } -
{formatCurrency(dashboardPool?.volume || "0")}
+
+ +
{isSmallScreen && } -
{formatCurrency(dashboardPool?.fee || "0")}
+
+ +
{isSmallScreen && }
- {!Number.isNaN(Number(dashboardPool?.apr)) && - formatPercentage( - Numeric.parse(dashboardPool?.apr || 0).mul(100), - )} + {!Number.isNaN(Number(dashboardPool?.apr)) && ( + + )}
@@ -436,13 +437,10 @@ function PoolItem({ poolAddress, bookmarked, onBookmarkClick }: PoolItemProps) { =  - {formatNumber( - formatDecimals( - amountToValue(lpBalance, LP_DECIMALS) || "0", - 3, - ), - )} -  LP + - {formatNumber( - formatDecimals( - amountToValue( - Numeric.parse(pool?.assets[0].amount || 0) - .times(userShare) - .toFixed(0), - asset1?.decimals, - ) || "0", - 3, - ), - )} -   - {asset1?.symbol} + - {formatNumber( - formatDecimals( - amountToValue( - Numeric.parse(pool?.assets[1].amount || 0) - .times(userShare) - .toFixed(0), - asset2?.decimals, - ) || "0", - 3, - ), - )} -   - {asset2?.symbol} + - {formatPercentage(userShare * 100)} + diff --git a/src/pages/Earn/Pools/Provide/InputGroup.tsx b/src/pages/Earn/Pools/Provide/InputGroup.tsx index 1ad6eabc..3686971f 100644 --- a/src/pages/Earn/Pools/Provide/InputGroup.tsx +++ b/src/pages/Earn/Pools/Provide/InputGroup.tsx @@ -13,6 +13,7 @@ import useBalance from "hooks/useBalance"; import { Numeric } from "@xpla/xpla.js"; import { UseControllerProps, useController } from "react-hook-form"; import useDashboardTokenDetail from "hooks/dashboard/useDashboardTokenDetail"; +import AssetValueFormatter from "components/utils/AssetValueFormatter"; interface InputGroupProps extends NumberInputProps { asset?: Partial | null; @@ -98,8 +99,12 @@ function InputGroup({ text-underline-offset: 3px; `} > - {formatNumber( - formatDecimals(amountToValue(balance, asset?.decimals) || 0, 3), + {asset && ( + )} diff --git a/src/pages/Earn/Pools/Provide/index.tsx b/src/pages/Earn/Pools/Provide/index.tsx index f862e22b..607192e1 100644 --- a/src/pages/Earn/Pools/Provide/index.tsx +++ b/src/pages/Earn/Pools/Provide/index.tsx @@ -52,6 +52,7 @@ import ProgressBar from "components/ProgressBar"; import Box from "components/Box"; import useInvalidPathModal from "hooks/modals/useInvalidPathModal"; import useSlippageTolerance from "hooks/useSlippageTolerance"; +import AssetValueFormatter from "components/utils/AssetValueFormatter"; export enum FormKey { asset1Value = "asset1Value", @@ -462,44 +463,43 @@ function ProvidePage() { label: "Total LP supply", tooltip: "The sum of Locked LP supply and Received LP supply.", - value: `${formatNumber( - cutDecimal( - amountToValue( - simulationResult?.share, - LP_DECIMALS, - ) || 0, - DISPLAY_DECIMAL, - ), - )} LP`, + value: ( + + ), }, { key: "lockedLp", label: "Locked LP supply", tooltip: "The amount of LP locked by contract to create a new pool.", - value: `${formatNumber( - cutDecimal( - amountToValue(LOCKED_LP_SUPPLY, LP_DECIMALS) || 0, - DISPLAY_DECIMAL, - ), - )} LP`, + value: ( + + ), }, { key: "receivedLp", label: "Received LP supply", tooltip: "The amount of LP you may get at the transaction.", - value: `${formatNumber( - cutDecimal( - amountToValue( - Numeric.parse(simulationResult?.share || 0) - .minus(LOCKED_LP_SUPPLY) - .toString(), - LP_DECIMALS, - ) || 0, - DISPLAY_DECIMAL, - ), - )} LP`, + value: ( + + ), }, { key: "fee", @@ -511,14 +511,14 @@ function ProvidePage() { the transaction. ), - value: feeAmount - ? `${formatNumber( - cutDecimal( - amountToValue(feeAmount) || "0", - DISPLAY_DECIMAL, - ), - )} ${XPLA_SYMBOL}` - : "", + value: feeAmount ? ( + + ) : ( + "" + ), }, ] : [ @@ -532,15 +532,15 @@ function ProvidePage() { you may get at the transaction. ), - value: `${formatNumber( - cutDecimal( - amountToValue( - simulationResult?.share, - LP_DECIMALS, - ) || 0, - DISPLAY_DECIMAL, - ), - )} LP`, + value: ( + + ), }, { key: "poolLiquidity1", @@ -552,22 +552,20 @@ function ProvidePage() { before adding. ), - value: `${ - formatNumber( - cutDecimal( - amountToValue( - pool?.assets?.find((a) => - "token" in a.info + value: ( + + ("token" in a.info ? a.info.token.contract_addr - : a.info.native_token.denom === - asset1?.token, - )?.amount, - asset1?.decimals, - ) || "0", - DISPLAY_DECIMAL, - ), - ) || "-" - } ${asset1?.symbol || ""}`, + : a.info.native_token.denom) === + asset1?.token, + )?.amount + } + /> + ), }, { key: "poolLiquidity2", @@ -579,22 +577,20 @@ function ProvidePage() { before adding. ), - value: `${ - formatNumber( - cutDecimal( - amountToValue( - pool?.assets?.find((a) => - "token" in a.info + value: ( + + ("token" in a.info ? a.info.token.contract_addr - : a.info.native_token.denom === - asset2?.token, - )?.amount, - asset2?.decimals, - ) || "0", - DISPLAY_DECIMAL, - ), - ) || "-" - } ${asset2?.symbol || ""}`, + : a.info.native_token.denom) === + asset2?.token, + )?.amount + } + /> + ), }, { key: "yourShare", @@ -619,14 +615,14 @@ function ProvidePage() { the transaction. ), - value: feeAmount - ? `${formatNumber( - cutDecimal( - amountToValue(feeAmount) || "0", - DISPLAY_DECIMAL, - ), - )} ${XPLA_SYMBOL}` - : "", + value: feeAmount ? ( + + ) : ( + "" + ), }, ] } diff --git a/src/pages/Earn/Pools/Withdraw/index.tsx b/src/pages/Earn/Pools/Withdraw/index.tsx index 9e97754d..1a941930 100644 --- a/src/pages/Earn/Pools/Withdraw/index.tsx +++ b/src/pages/Earn/Pools/Withdraw/index.tsx @@ -46,6 +46,7 @@ import useSettingsModal from "hooks/modals/useSettingsModal"; import useSlippageTolerance from "hooks/useSlippageTolerance"; import useInvalidPathModal from "hooks/modals/useInvalidPathModal"; import useDashboardTokenDetail from "hooks/dashboard/useDashboardTokenDetail"; +import AssetValueFormatter from "components/utils/AssetValueFormatter"; enum FormKey { lpValue = "lpValue", @@ -473,20 +474,14 @@ function WithdrawPage() { the transaction. ), - value: `${ - fee - ? formatNumber( - cutDecimal( - amountToValue( - fee.amount - ?.get(XPLA_ADDRESS) - ?.amount.toString(), - ) || 0, - DISPLAY_DECIMAL, - ), - ) - : "-" - } ${XPLA_SYMBOL}`, + value: ( + + ), }, ]} /> diff --git a/src/pages/Tokens/TokenDetail/Chart.tsx b/src/pages/Tokens/TokenDetail/Chart.tsx index bc174b7d..d593eb8f 100644 --- a/src/pages/Tokens/TokenDetail/Chart.tsx +++ b/src/pages/Tokens/TokenDetail/Chart.tsx @@ -9,13 +9,14 @@ import { DashboardTokenChartType, DashboardChartDuration, } from "types/dashboard-api"; -import { formatCurrency, formatDate } from "utils"; +import { formatDate, formatDateRange, getSumOfDashboardChartData } from "utils"; import Select from "pages/Earn/Pools/Select"; import { MOBILE_SCREEN_CLASS } from "constants/layout"; import IconButton from "components/IconButton"; import iconFullscreen from "assets/icons/icon-fullscreen.svg"; import useHashModal from "hooks/useHashModal"; import Modal from "components/Modal"; +import CurrencyFormatter from "components/utils/CurrencyFormatter"; import useChartData from "./useChartData"; const chartTypeTabs = ["Volume", "TVL", "Price"].map((label) => ({ @@ -35,11 +36,14 @@ const chartDurationOptions = ["Month", "Quarter", "Year", "All"].map( function Chart({ tokenAddress }: { tokenAddress: string }) { const screenClass = useScreenClass(); const [selectedTabIndex, setSelectedTabIndex] = useState(0); + const selectedTabValue = useMemo(() => { + return chartTypeTabs[selectedTabIndex].value; + }, [selectedTabIndex]); const { data: chartData, duration, setDuration, - } = useChartData(tokenAddress, chartTypeTabs[selectedTabIndex].value); + } = useChartData(tokenAddress, selectedTabValue); const chartModal = useHashModal(useId()); @@ -63,7 +67,7 @@ function Chart({ tokenAddress }: { tokenAddress: string }) { {screenClass === MOBILE_SCREEN_CLASS && (