From 43b2b4d6bf6b1dfb7d2216acbfc3799bb85030b2 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Tue, 25 Apr 2023 14:07:59 +0700 Subject: [PATCH 1/2] feat: pool header and pool assets for pool details page --- CHANGELOG.md | 1 + src/lib/app-provider/hooks/useAddress.ts | 4 +- src/lib/components/CopyLink.tsx | 34 +++++- src/lib/components/ExplorerLink.tsx | 4 +- src/lib/components/LabelText.tsx | 12 +- src/lib/components/TooltipComponent.tsx | 21 ++++ src/lib/components/copy/Copier.tsx | 7 +- .../pools/components/PoolAssetDetail.tsx | 80 ------------- .../pools/components/PoolDetailHeader.tsx | 12 +- src/lib/pages/pools/components/PoolTop.tsx | 5 - .../pool-details/JsonModalButton.tsx | 75 ++++++++++++ .../pool-details/header/PoolHeader.tsx | 89 ++++++++++++++ .../pool-details/header/PoolInfo.tsx | 110 ++++++++++++++++++ .../pool-details/header/PoolLogo.tsx | 65 +++++++++++ .../components/pool-details/header/index.tsx | 56 +++++++++ .../pool-details/pool-assets/index.tsx | 61 ++++++++++ .../table/PoolAssetsTableHeader.tsx | 15 ++- .../pool-assets/table/PoolAssetsTableRow.tsx | 107 +++++++++++++++++ .../pool-details/pool-assets/table/index.tsx | 37 ++++++ .../table}/PoolRelatedTxs.tsx | 0 .../supportedSection/AllocationBadge.tsx | 8 +- .../components/table/PoolAssetsTable.tsx | 48 -------- .../components/table/PoolAssetsTableRow.tsx | 87 -------------- src/lib/pages/pools/data.ts | 25 ++-- src/lib/pages/pools/poolId.tsx | 46 +++----- src/lib/services/poolService.ts | 9 +- src/lib/types/asset.ts | 1 + src/lib/types/currency/token.ts | 5 + src/lib/types/pool.ts | 13 ++- src/lib/utils/assetValue.ts | 1 + src/lib/utils/formatter/formatPercentValue.ts | 21 ---- src/lib/utils/formatter/index.ts | 2 +- src/lib/utils/formatter/percentage.ts | 31 +++++ 33 files changed, 776 insertions(+), 316 deletions(-) delete mode 100644 src/lib/pages/pools/components/PoolAssetDetail.tsx delete mode 100644 src/lib/pages/pools/components/PoolTop.tsx create mode 100644 src/lib/pages/pools/components/pool-details/JsonModalButton.tsx create mode 100644 src/lib/pages/pools/components/pool-details/header/PoolHeader.tsx create mode 100644 src/lib/pages/pools/components/pool-details/header/PoolInfo.tsx create mode 100644 src/lib/pages/pools/components/pool-details/header/PoolLogo.tsx create mode 100644 src/lib/pages/pools/components/pool-details/header/index.tsx create mode 100644 src/lib/pages/pools/components/pool-details/pool-assets/index.tsx rename src/lib/pages/pools/components/{ => pool-details/pool-assets}/table/PoolAssetsTableHeader.tsx (60%) create mode 100644 src/lib/pages/pools/components/pool-details/pool-assets/table/PoolAssetsTableRow.tsx create mode 100644 src/lib/pages/pools/components/pool-details/pool-assets/table/index.tsx rename src/lib/pages/pools/components/{ => pool-details/table}/PoolRelatedTxs.tsx (100%) delete mode 100644 src/lib/pages/pools/components/table/PoolAssetsTable.tsx delete mode 100644 src/lib/pages/pools/components/table/PoolAssetsTableRow.tsx delete mode 100644 src/lib/utils/formatter/formatPercentValue.ts create mode 100644 src/lib/utils/formatter/percentage.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ab08bb045..f35e4074e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#296](https://github.com/alleslabs/celatone-frontend/pull/296) Add pool header and pool assets section for pool details page - [#295](https://github.com/alleslabs/celatone-frontend/pull/295) Add expand/collapse all for unsupported pool list - [#277](https://github.com/alleslabs/celatone-frontend/pull/277) Wire up data for pool navigation page - [#275](https://github.com/alleslabs/celatone-frontend/pull/275) Add Pool navigation and pool detail data diff --git a/src/lib/app-provider/hooks/useAddress.ts b/src/lib/app-provider/hooks/useAddress.ts index 3f3ffbeac..38313f2e7 100644 --- a/src/lib/app-provider/hooks/useAddress.ts +++ b/src/lib/app-provider/hooks/useAddress.ts @@ -38,7 +38,7 @@ const addressLengthMap: { export const getAddressTypeByLength = ( chainName: string, - address: Option + address: Option ): AddressReturnType => address ? addressLengthMap[chainName]?.[address.length] ?? "invalid_address" @@ -75,7 +75,7 @@ const validateAddress = ( export const useGetAddressType = () => { const { currentChainName, currentChainRecord } = useWallet(); return useCallback( - (address: Option): AddressReturnType => { + (address: Option): AddressReturnType => { const addressType = getAddressTypeByLength(currentChainName, address); if ( !address || diff --git a/src/lib/components/CopyLink.tsx b/src/lib/components/CopyLink.tsx index a1e187f9a..d16302ef6 100644 --- a/src/lib/components/CopyLink.tsx +++ b/src/lib/components/CopyLink.tsx @@ -1,21 +1,40 @@ +import type { FlexProps, IconProps } from "@chakra-ui/react"; import { Flex, Text, Tooltip, useClipboard } from "@chakra-ui/react"; import { useWallet } from "@cosmos-kit/react"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { AmpTrackCopier } from "lib/services/amplitude"; import { CustomIcon } from "./icon"; -interface CopyLinkProps { +interface CopyLinkProps extends FlexProps { value: string; amptrackSection?: string; type: string; + showCopyOnHover?: boolean; } -export const CopyLink = ({ value, amptrackSection, type }: CopyLinkProps) => { +export const CopyLink = ({ + value, + amptrackSection, + type, + showCopyOnHover = false, + ...flexProps +}: CopyLinkProps) => { const { address } = useWallet(); const { onCopy, hasCopied } = useClipboard(value); const [isHover, setIsHover] = useState(false); + + const displayIcon = useMemo(() => { + if (showCopyOnHover) { + if (isHover) { + return "flex"; + } + return "none"; + } + return undefined; + }, [showCopyOnHover, isHover]); + return ( { cursor="pointer" onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)} + {...flexProps} > { > {value === address ? `${value} (Me)` : value} - + ); diff --git a/src/lib/components/ExplorerLink.tsx b/src/lib/components/ExplorerLink.tsx index d63d0c089..c6938ee83 100644 --- a/src/lib/components/ExplorerLink.tsx +++ b/src/lib/components/ExplorerLink.tsx @@ -185,7 +185,9 @@ export const ExplorerLink = ({ {...componentProps} > {readOnly ? ( - {textValue} + + {textValue} + ) : ( <> ( - - {label} - + + + {label} + + {tooltipText && } + {typeof children === "string" ? ( {children} ) : ( diff --git a/src/lib/components/TooltipComponent.tsx b/src/lib/components/TooltipComponent.tsx index c09c32e60..c385b72f1 100644 --- a/src/lib/components/TooltipComponent.tsx +++ b/src/lib/components/TooltipComponent.tsx @@ -1,6 +1,8 @@ import type { TooltipProps } from "@chakra-ui/react"; import { Tooltip } from "@chakra-ui/react"; +import { CustomIcon } from "./icon"; + export const TooltipComponent = ({ placement, ...tooltipProps @@ -13,3 +15,22 @@ export const TooltipComponent = ({ {...tooltipProps} /> ); + +interface TooltipInfoProps extends Omit { + iconVariant?: "default" | "solid"; +} + +export const TooltipInfo = ({ + iconVariant = "default", + ...tooltipProps +}: TooltipInfoProps) => ( + +
+ +
+
+); diff --git a/src/lib/components/copy/Copier.tsx b/src/lib/components/copy/Copier.tsx index d9e8c385b..9b967a796 100644 --- a/src/lib/components/copy/Copier.tsx +++ b/src/lib/components/copy/Copier.tsx @@ -1,4 +1,4 @@ -import type { LayoutProps } from "@chakra-ui/react"; +import type { IconProps, LayoutProps } from "@chakra-ui/react"; import { CustomIcon } from "../icon"; import { AmpTrackCopier } from "lib/services/amplitude"; @@ -10,7 +10,7 @@ interface CopierProps { value: string; copyLabel?: string; display?: LayoutProps["display"]; - ml?: string; + ml?: IconProps["ml"]; amptrackSection?: string; } @@ -31,10 +31,11 @@ export const Copier = ({ className="copier" display={display} cursor="pointer" + m={0} marginLeft={ml} onClick={() => AmpTrackCopier(amptrackSection, type)} name="copy" - boxSize="12px" + boxSize={4} /> } /> diff --git a/src/lib/pages/pools/components/PoolAssetDetail.tsx b/src/lib/pages/pools/components/PoolAssetDetail.tsx deleted file mode 100644 index c9c80b42f..000000000 --- a/src/lib/pages/pools/components/PoolAssetDetail.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Flex, Heading, Badge, Text, Box } from "@chakra-ui/react"; -import type { Big } from "big.js"; -import big from "big.js"; -import Link from "next/link"; - -import { CustomIcon } from "lib/components/icon"; -import { Loading } from "lib/components/Loading"; -import { useAssetInfos } from "lib/services/assetService"; -import type { TokenWithValue, USD } from "lib/types"; -import type { PoolDetail } from "lib/types/pool"; -import { formatPrice } from "lib/utils"; - -import { PoolAssetsTable } from "./table/PoolAssetsTable"; - -interface PoolAssetDetailProps { - assets: TokenWithValue[]; - pool_type: PoolDetail["type"]; - // weight?: Option; - // scaling_factors?: Option; -} -export const PoolAssetDetail = ({ - assets, - pool_type, -}: // weight, -// scaling_factors, -PoolAssetDetailProps) => { - const { assetInfos } = useAssetInfos(); - - if (!assetInfos) return ; - - const liquidity = assets.reduce( - (total, asset) => total.add(asset.value ?? big(0)) as USD, - big(0) as USD - ); - - return ( - - - - - Pools - - - {assets.length} - - - - - Total Liquidity: - - - {formatPrice(liquidity)} - - - - - - - What is asset weights and allocations? - - - - - Read more - - - - - - - ); -}; diff --git a/src/lib/pages/pools/components/PoolDetailHeader.tsx b/src/lib/pages/pools/components/PoolDetailHeader.tsx index c3283646b..aba698ad7 100644 --- a/src/lib/pages/pools/components/PoolDetailHeader.tsx +++ b/src/lib/pages/pools/components/PoolDetailHeader.tsx @@ -17,7 +17,6 @@ import { Tooltip, } from "@chakra-ui/react"; import { useWallet } from "@cosmos-kit/react"; -import Big from "big.js"; import { getAddressTypeByLength } from "lib/app-provider"; import { BackButton } from "lib/components/button"; @@ -25,8 +24,7 @@ import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; import JsonReadOnly from "lib/components/json/JsonReadOnly"; import type { PoolDetail } from "lib/types/pool"; -import { jsonPrettify, truncate } from "lib/utils"; -import { formatPercentValue } from "lib/utils/formatter/formatPercentValue"; +import { formatRatio, jsonPrettify, truncate } from "lib/utils"; import { PoolHeader } from "./PoolHeader"; @@ -148,9 +146,7 @@ export const PoolDetailHeader = ({ pool }: PoolDetailHeaderProp) => {
- - {formatPercentValue(Big(pool.swapFee).times(100))} - + {formatRatio(pool.swapFee)} @@ -167,9 +163,7 @@ export const PoolDetailHeader = ({ pool }: PoolDetailHeaderProp) => { - - {formatPercentValue(Big(pool.exitFee).times(100))} - + {formatRatio(pool.exitFee)} Future Governor diff --git a/src/lib/pages/pools/components/PoolTop.tsx b/src/lib/pages/pools/components/PoolTop.tsx deleted file mode 100644 index 930910d44..000000000 --- a/src/lib/pages/pools/components/PoolTop.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Flex } from "@chakra-ui/react"; - -export const PoolTop = () => { - return jj; -}; diff --git a/src/lib/pages/pools/components/pool-details/JsonModalButton.tsx b/src/lib/pages/pools/components/pool-details/JsonModalButton.tsx new file mode 100644 index 000000000..c95f10810 --- /dev/null +++ b/src/lib/pages/pools/components/pool-details/JsonModalButton.tsx @@ -0,0 +1,75 @@ +import { + Button, + Heading, + Modal, + ModalContent, + ModalCloseButton, + ModalHeader, + ModalOverlay, + useDisclosure, + ModalBody, + Box, +} from "@chakra-ui/react"; + +import { CustomIcon } from "lib/components/icon"; +import JsonReadOnly from "lib/components/json/JsonReadOnly"; +import { jsonPrettify } from "lib/utils"; + +interface JsonModalButtonProps { + jsonString: string; + modalHeader: string; +} + +export const JsonModalButton = ({ + jsonString, + modalHeader, +}: JsonModalButtonProps) => { + const { isOpen, onOpen, onClose } = useDisclosure(); + return ( + <> + + + + + + + + {modalHeader} + + + + + + + + + + + + ); +}; diff --git a/src/lib/pages/pools/components/pool-details/header/PoolHeader.tsx b/src/lib/pages/pools/components/pool-details/header/PoolHeader.tsx new file mode 100644 index 000000000..a184bb2f0 --- /dev/null +++ b/src/lib/pages/pools/components/pool-details/header/PoolHeader.tsx @@ -0,0 +1,89 @@ +import { Flex, Heading, Text, Image } from "@chakra-ui/react"; +import type Big from "big.js"; + +import { DotSeparator } from "lib/components/DotSeperator"; +import type { AssetInfosOpt } from "lib/services/assetService"; +import type { PoolDetail, TokenWithValue } from "lib/types"; +import { getTokenLabel } from "lib/utils"; + +import { PoolLogo } from "./PoolLogo"; + +interface PoolHeaderProps { + pool: PoolDetail; + assetInfos: AssetInfosOpt; +} + +export const PoolHeader = ({ pool, assetInfos }: PoolHeaderProps) => { + const { poolLiquidity, id: poolId, type: poolType, isSuperfluid } = pool; + return ( + + + + span": { + display: "none", + }, + }} + > + {poolLiquidity.slice(0, 4).map((item) => ( + + {assetInfos?.[item.denom]?.symbol ?? getTokenLabel(item.denom)} + + / + + + ))} + {poolLiquidity.length > 4 && ( + + + / + + + {poolLiquidity.length - 4} + + )} + + + + #{poolId} + + {poolType && ( + + + + + + {poolType === "Balancer" + ? "Balancer Pool" + : "StableSwap Pool"} + + + + )} + {isSuperfluid && ( + + + + + + Superfluid + + + + )} + + + + ); +}; diff --git a/src/lib/pages/pools/components/pool-details/header/PoolInfo.tsx b/src/lib/pages/pools/components/pool-details/header/PoolInfo.tsx new file mode 100644 index 000000000..da416023d --- /dev/null +++ b/src/lib/pages/pools/components/pool-details/header/PoolInfo.tsx @@ -0,0 +1,110 @@ +import { Flex, Text } from "@chakra-ui/react"; +import type Big from "big.js"; + +import { JsonModalButton } from "../JsonModalButton"; +import { useGetAddressType } from "lib/app-provider"; +import { CopyLink } from "lib/components/CopyLink"; +import { ExplorerLink } from "lib/components/ExplorerLink"; +import { LabelText } from "lib/components/LabelText"; +import type { PoolDetail, TokenWithValue } from "lib/types"; +import { formatRatio } from "lib/utils"; + +interface PoolInfoProps { + pool: PoolDetail; +} + +export const PoolInfo = ({ pool }: PoolInfoProps) => { + const getAddressType = useGetAddressType(); + const futurePoolGovernorType = getAddressType(pool.futurePoolGovernor); + return ( + + + + + + + + + + + + {formatRatio(pool.swapFee)} + + + {formatRatio(pool.exitFee)} + + + {futurePoolGovernorType !== "invalid_address" ? ( + + ) : ( + + {pool.futurePoolGovernor ?? "N/A"} + + )} + + {pool.smoothWeightChangeParams && ( + + + + )} + {pool.scalingFactors && ( + + + + )} + + ); +}; diff --git a/src/lib/pages/pools/components/pool-details/header/PoolLogo.tsx b/src/lib/pages/pools/components/pool-details/header/PoolLogo.tsx new file mode 100644 index 000000000..1aa656913 --- /dev/null +++ b/src/lib/pages/pools/components/pool-details/header/PoolLogo.tsx @@ -0,0 +1,65 @@ +import { Flex, Image } from "@chakra-ui/react"; +import type Big from "big.js"; + +import { getUndefinedTokenIcon } from "../../../utils"; +import type { AssetInfosOpt } from "lib/services/assetService"; +import type { TokenWithValue } from "lib/types"; +import type { PoolDetail } from "lib/types/pool"; + +interface PoolLogoProps { + poolLiquidity: PoolDetail["poolLiquidity"]; + assetInfos: AssetInfosOpt; +} + +export const PoolLogo = ({ poolLiquidity, assetInfos }: PoolLogoProps) => { + return ( + img:not(:first-of-type), > div": { + marginLeft: "-12px", + }, + }} + width="96px" + alignItems="center" + justifyContent="center" + > + {poolLiquidity.length > 3 ? ( + <> + {poolLiquidity.slice(0, 2).map((asset, i) => ( + + ))} + + +{poolLiquidity.length - 2} + + + ) : ( + poolLiquidity.map((item, i) => ( + + )) + )} + + ); +}; diff --git a/src/lib/pages/pools/components/pool-details/header/index.tsx b/src/lib/pages/pools/components/pool-details/header/index.tsx new file mode 100644 index 000000000..31408a12c --- /dev/null +++ b/src/lib/pages/pools/components/pool-details/header/index.tsx @@ -0,0 +1,56 @@ +import { Button, Flex } from "@chakra-ui/react"; +import { useWallet } from "@cosmos-kit/react"; +import type Big from "big.js"; + +import { getPoolUrl } from "lib/app-fns/explorer"; +import { useLCDEndpoint } from "lib/app-provider"; +import { CustomIcon } from "lib/components/icon"; +import { openNewTab } from "lib/hooks"; +import type { AssetInfosOpt } from "lib/services/assetService"; +import type { PoolDetail, TokenWithValue } from "lib/types"; + +import { PoolHeader } from "./PoolHeader"; +import { PoolInfo } from "./PoolInfo"; + +interface PoolTopSectionProps { + pool: PoolDetail; + assetInfos: AssetInfosOpt; +} + +export const PoolTopSection = ({ pool, assetInfos }: PoolTopSectionProps) => { + const { currentChainName } = useWallet(); + const lcdEndpoint = useLCDEndpoint(); + + const openPoolLcd = () => + openNewTab(`${lcdEndpoint}/osmosis/gamm/v1beta1/pools/${pool.id}`); + const openOsmosisPool = () => + openNewTab(`${getPoolUrl(currentChainName)}/${pool.id}`); + return ( + <> + + + + + + + + + + ); +}; diff --git a/src/lib/pages/pools/components/pool-details/pool-assets/index.tsx b/src/lib/pages/pools/components/pool-details/pool-assets/index.tsx new file mode 100644 index 000000000..7cdf18fdc --- /dev/null +++ b/src/lib/pages/pools/components/pool-details/pool-assets/index.tsx @@ -0,0 +1,61 @@ +import { Flex, Heading, Badge, Text } from "@chakra-ui/react"; +import type { Big } from "big.js"; +import big from "big.js"; +import Link from "next/link"; + +import { CustomIcon } from "lib/components/icon"; +import type { PoolDetail, TokenWithValue, USD } from "lib/types"; +import { formatPrice } from "lib/utils"; + +import { PoolAssetsTable } from "./table"; + +interface PoolAssetsProps { + pool: PoolDetail; +} + +export const PoolAssets = ({ pool }: PoolAssetsProps) => { + const totalLiquidity = pool.poolLiquidity.reduce( + (totalVal, token) => totalVal.add(token.value ?? 0), + big(0) + ) as USD; + return ( + <> + + + + Pool Assets + + + {pool.poolLiquidity.length} + + + {pool.isSupported && ( + + Total Liquidity: + + {formatPrice(totalLiquidity)} + + + )} + + + + + What is asset weight and allocation? + + + + + Read more + + + + + + + ); +}; diff --git a/src/lib/pages/pools/components/table/PoolAssetsTableHeader.tsx b/src/lib/pages/pools/components/pool-details/pool-assets/table/PoolAssetsTableHeader.tsx similarity index 60% rename from src/lib/pages/pools/components/table/PoolAssetsTableHeader.tsx rename to src/lib/pages/pools/components/pool-details/pool-assets/table/PoolAssetsTableHeader.tsx index c79a16f4a..48d751563 100644 --- a/src/lib/pages/pools/components/table/PoolAssetsTableHeader.tsx +++ b/src/lib/pages/pools/components/pool-details/pool-assets/table/PoolAssetsTableHeader.tsx @@ -5,21 +5,26 @@ import { TableHeader } from "lib/components/table/tableComponents"; import type { PoolDetail } from "lib/types"; interface PoolHeaderProps { - pool_type: PoolDetail["type"]; + poolType: PoolDetail["type"]; + isSupported: PoolDetail["isSupported"]; templateColumns: GridProps["templateColumns"]; } export const PoolAssetsTableHeader = ({ - pool_type, + poolType, + isSupported, templateColumns, }: PoolHeaderProps) => ( - + div": { color: "text.dark" } }} + > Asset - {pool_type === "Stableswap" ? "Scaling Factor" : "Weight (%)"} + {poolType === "Stableswap" ? "Scaling Factor" : "Weight (%)"} - Value Allocation + {isSupported && "Allocation"} Amount ); diff --git a/src/lib/pages/pools/components/pool-details/pool-assets/table/PoolAssetsTableRow.tsx b/src/lib/pages/pools/components/pool-details/pool-assets/table/PoolAssetsTableRow.tsx new file mode 100644 index 000000000..94b27c6fd --- /dev/null +++ b/src/lib/pages/pools/components/pool-details/pool-assets/table/PoolAssetsTableRow.tsx @@ -0,0 +1,107 @@ +import type { GridProps } from "@chakra-ui/react"; +import { Flex, Grid, Image, Text } from "@chakra-ui/react"; +import type { Big } from "big.js"; + +import { Copier } from "lib/components/copy"; +import { TableRow } from "lib/components/table/tableComponents"; +import { TooltipInfo } from "lib/components/TooltipComponent"; +import { getUndefinedTokenIcon } from "lib/pages/pools/utils"; +import type { USD, PoolDetail, TokenWithValue, Ratio } from "lib/types"; +import { + formatInteger, + formatPrice, + formatUTokenWithPrecision, + getTokenLabel, + formatRatio, +} from "lib/utils"; + +interface PoolAssetsTableRowProps { + pool: PoolDetail; + token: TokenWithValue; + templateColumns: GridProps["templateColumns"]; + totalLiquidity: USD; + liquidityIndex: number; +} + +export const PoolAssetsTableRow = ({ + pool: { type: poolType, weight, scalingFactors, isSupported }, + token, + templateColumns, + totalLiquidity, + liquidityIndex, +}: PoolAssetsTableRowProps) => { + const allocation = token.value + ? formatRatio(token.value.div(totalLiquidity) as Ratio) + : undefined; + const tokenWeight = weight?.find( + (w) => w.denom === token.denom + )?.percentWeight; + const scalingFactor = scalingFactors + ? formatInteger(scalingFactors[liquidityIndex]) + : undefined; + return ( + + + + + + + + {token.symbol ?? getTokenLabel(token.denom)} + + + + + + + {isSupported && token.price && ( + + {formatPrice(token.price)} + + )} + + + + + {poolType === "Stableswap" ? scalingFactor : tokenWeight} + + + {isSupported && allocation} + + + + + {formatUTokenWithPrecision( + token.amount, + token.precision ?? 0, + false + )} + + {isSupported && token.value && ( + + ({formatPrice(token.value)}) + + )} + + + + ); +}; diff --git a/src/lib/pages/pools/components/pool-details/pool-assets/table/index.tsx b/src/lib/pages/pools/components/pool-details/pool-assets/table/index.tsx new file mode 100644 index 000000000..497ecd9fa --- /dev/null +++ b/src/lib/pages/pools/components/pool-details/pool-assets/table/index.tsx @@ -0,0 +1,37 @@ +import type { Big } from "big.js"; + +import { TableContainer } from "lib/components/table/tableComponents"; +import type { PoolDetail, TokenWithValue, USD } from "lib/types"; + +import { PoolAssetsTableHeader } from "./PoolAssetsTableHeader"; +import { PoolAssetsTableRow } from "./PoolAssetsTableRow"; + +interface PoolAssetsTableProps { + pool: PoolDetail; + totalLiquidity: USD; +} + +const TEMPLATE_COLUMNS = "minmax(300px, 1fr) 144px 144px minmax(300px, 1fr)"; + +export const PoolAssetsTable = ({ + pool, + totalLiquidity, +}: PoolAssetsTableProps) => ( + + + {pool.poolLiquidity.map((token, idx) => ( + + ))} + +); diff --git a/src/lib/pages/pools/components/PoolRelatedTxs.tsx b/src/lib/pages/pools/components/pool-details/table/PoolRelatedTxs.tsx similarity index 100% rename from src/lib/pages/pools/components/PoolRelatedTxs.tsx rename to src/lib/pages/pools/components/pool-details/table/PoolRelatedTxs.tsx diff --git a/src/lib/pages/pools/components/supportedSection/AllocationBadge.tsx b/src/lib/pages/pools/components/supportedSection/AllocationBadge.tsx index e9fa20151..f96ae6cec 100644 --- a/src/lib/pages/pools/components/supportedSection/AllocationBadge.tsx +++ b/src/lib/pages/pools/components/supportedSection/AllocationBadge.tsx @@ -3,11 +3,11 @@ import type { Big } from "big.js"; import big from "big.js"; import { getUndefinedTokenIcon } from "../../utils"; -import type { Option, Token, U, USD } from "lib/types"; +import type { Option, Ratio, Token, U, USD } from "lib/types"; import { getTokenLabel, - formatPercentValue, formatUTokenWithPrecision, + formatRatio, } from "lib/utils"; interface AllocationBadgeProps { @@ -31,8 +31,8 @@ export const AllocationBadge = ({ liquidity, mode, }: AllocationBadgeProps) => { - const formattedValue = formatPercentValue( - (value ?? big(0)).div(liquidity).times(100) + const formattedValue = formatRatio( + (value ?? big(0)).div(liquidity) as Ratio ); const formattedAmount = denom ? formatUTokenWithPrecision(amount, precision ?? 0) diff --git a/src/lib/pages/pools/components/table/PoolAssetsTable.tsx b/src/lib/pages/pools/components/table/PoolAssetsTable.tsx deleted file mode 100644 index 14cf0ac2f..000000000 --- a/src/lib/pages/pools/components/table/PoolAssetsTable.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { Big } from "big.js"; - -import { TableContainer } from "lib/components/table/tableComponents"; -import type { PoolDetail, TokenWithValue, USD } from "lib/types"; - -// import type { Option, Proposal } from "lib/types"; -import { PoolAssetsTableHeader } from "./PoolAssetsTableHeader"; -import { PoolAssetsTableRow } from "./PoolAssetsTableRow"; -// import { PoolAssetsTableRow } from "./PoolAssetsTableRow"; - -interface PoolAssetsTableProps { - assets: TokenWithValue[]; - pool_type: PoolDetail["type"]; - // weight?: Option; - // scaling_factors?: Option; - total_liquidity: USD; -} - -export const PoolAssetsTable = ({ - assets, - pool_type, - // weight, - // scaling_factors, - total_liquidity, -}: PoolAssetsTableProps) => { - // if (isLoading) return ; - // if (!proposals?.length) return emptyState; - - const templateColumns = "minmax(300px, 1fr) 144px 144px minmax(300px, 1fr)"; - - return ( - - - {assets.map((asset) => ( - - ))} - - ); -}; diff --git a/src/lib/pages/pools/components/table/PoolAssetsTableRow.tsx b/src/lib/pages/pools/components/table/PoolAssetsTableRow.tsx deleted file mode 100644 index 2c3672375..000000000 --- a/src/lib/pages/pools/components/table/PoolAssetsTableRow.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import type { GridProps } from "@chakra-ui/react"; -import { Flex, Skeleton, Grid, Image, Text } from "@chakra-ui/react"; -import type { Big } from "big.js"; - -import { getUndefinedTokenIcon } from "../../utils"; -import { TableRow } from "lib/components/table/tableComponents"; -import { useAssetInfos } from "lib/services/assetService"; -import type { USD, PoolDetail, TokenWithValue } from "lib/types"; -import { - d2Formatter, - formatPrice, - formatUTokenWithPrecision, - getTokenLabel, -} from "lib/utils"; - -interface PoolAssetsTableRowProps { - pool_type: PoolDetail["type"]; - asset: TokenWithValue; - templateColumns: GridProps["templateColumns"]; - total_liquidity: USD; -} - -export const PoolAssetsTableRow = ({ - pool_type, - asset, - templateColumns, - total_liquidity, -}: PoolAssetsTableRowProps) => { - const { assetInfos } = useAssetInfos(); - if (!assetInfos) - return ( - - ); - const assetInfo = assetInfos[asset.denom]; - - return ( - - - - - - - {asset.symbol || getTokenLabel(asset.denom)} - - - {formatPrice(assetInfo.price as USD)} - - - - - - {pool_type === "Stableswap" ? ( - - stable scaling - - ) : ( - - balancer weight - - )} - - - - - {d2Formatter(total_liquidity, "0.00")} - - - - - - {formatUTokenWithPrecision( - asset.amount, - assetInfo.precision, - false - )} - - - {asset.value?.toString() ?? 0} - - - - - ); -}; diff --git a/src/lib/pages/pools/data.ts b/src/lib/pages/pools/data.ts index fa68acf04..ec14f8dc1 100644 --- a/src/lib/pages/pools/data.ts +++ b/src/lib/pages/pools/data.ts @@ -10,9 +10,10 @@ import type { PoolDetail, PoolTypeFilter, PoolWeight, + Ratio, TokenWithValue, } from "lib/types"; -import { coinToTokenWithValue } from "lib/utils"; +import { coinToTokenWithValue, formatRatio } from "lib/utils"; export const usePools = ( isSupported: boolean, @@ -50,7 +51,7 @@ export const usePools = ( }; export const usePool = ( - poolId: number + poolId: Option ): { pool: Option>; isLoading: boolean } => { const { assetInfos, isLoading: isLoadingAssetInfos } = useAssetInfos(); const { data: pool, isLoading: isLoadingPoolInfo } = usePoolByPoolId(poolId); @@ -60,7 +61,10 @@ export const usePool = ( pool: undefined, isLoading: isLoadingAssetInfos || isLoadingPoolInfo, }; - + const totalPoolWeight = pool.weight?.reduce( + (acc, curr) => acc.add(curr.weight), + big(0) + ); return { pool: { id: pool.id, @@ -77,10 +81,17 @@ export const usePool = ( exitFee: pool.exitFee, futurePoolGovernor: pool.futurePoolGovernor, weight: - pool.weight?.map((weight) => ({ - denom: weight.denom, - weight: big(weight.weight), - })) ?? null, + pool.weight?.map>((weight) => { + const bigWeight = big(weight.weight); + return { + denom: weight.denom, + weight: bigWeight, + percentWeight: + totalPoolWeight && totalPoolWeight.gt(0) + ? formatRatio(bigWeight.div(totalPoolWeight) as Ratio) + : null, + }; + }) ?? null, smoothWeightChangeParams: pool.smoothWeightChangeParams, scalingFactors: pool.scalingFactors, scalingFactorController: pool.scalingFactorController, diff --git a/src/lib/pages/pools/poolId.tsx b/src/lib/pages/pools/poolId.tsx index 242e8df49..ae1c49d5b 100644 --- a/src/lib/pages/pools/poolId.tsx +++ b/src/lib/pages/pools/poolId.tsx @@ -1,46 +1,32 @@ import { useRouter } from "next/router"; +import { useInternalNavigate } from "lib/app-provider"; +import { BackButton } from "lib/components/button"; import { Loading } from "lib/components/Loading"; import PageContainer from "lib/components/PageContainer"; -import { EmptyState } from "lib/components/state"; -import type { Option, PoolDetail } from "lib/types"; +import { useAssetInfos } from "lib/services/assetService"; import { getFirstQueryParam } from "lib/utils"; -import { PoolAssetDetail } from "./components/PoolAssetDetail"; -import { PoolDetailHeader } from "./components/PoolDetailHeader"; -import { PoolRelatedTxs } from "./components/PoolRelatedTxs"; +import { PoolTopSection } from "./components/pool-details/header"; +import { PoolAssets } from "./components/pool-details/pool-assets"; +import { PoolRelatedTxs } from "./components/pool-details/table/PoolRelatedTxs"; import { usePool } from "./data"; -interface PoolIdBodyProps { - pool: Option; - isLoading: boolean; -} -const PoolIdBody = ({ pool, isLoading }: PoolIdBodyProps) => { - if (isLoading) return ; - if (!pool) - return ; - return ( - <> - - - - - ); -}; - export const PoolId = () => { const router = useRouter(); - const poolIdParam = getFirstQueryParam(router.query.poolId); + const navigate = useInternalNavigate(); + const { assetInfos } = useAssetInfos(); + const poolId = Number(getFirstQueryParam(router.query.poolId)); + const { pool, isLoading } = usePool(poolId); - const { pool, isLoading } = usePool(Number(poolIdParam)); + if (isLoading) return ; + if (!pool) return navigate({ pathname: `/pool` }); return ( - + + + + ); }; diff --git a/src/lib/services/poolService.ts b/src/lib/services/poolService.ts index 340275e16..bafb242b1 100644 --- a/src/lib/services/poolService.ts +++ b/src/lib/services/poolService.ts @@ -12,7 +12,7 @@ import { getPoolListByDenomsCount, getPoolListCount, } from "lib/query"; -import type { Pool, PoolDetail, PoolTypeFilter } from "lib/types"; +import type { Option, Pool, PoolDetail, PoolTypeFilter } from "lib/types"; import { isPositiveInt } from "lib/utils"; import { usePoolExpression } from "./expression/poolExpression"; @@ -133,11 +133,12 @@ export const usePoolListCountQuery = ({ }; export const usePoolByPoolId = ( - poolId: number + poolId: Option ): UseQueryResult> => { const { indexerGraphClient } = useCelatoneApp(); const queryFn = useCallback(async () => { + if (!poolId) throw new Error("Pool ID is undefined."); return indexerGraphClient .request(getPoolByPoolId, { poolId, @@ -165,5 +166,7 @@ export const usePoolByPoolId = ( ); }, [poolId, indexerGraphClient]); - return useQuery(["pool_by_pool_id", poolId, indexerGraphClient], queryFn); + return useQuery(["pool_by_pool_id", poolId, indexerGraphClient], queryFn, { + enabled: Boolean(poolId), + }); }; diff --git a/src/lib/types/asset.ts b/src/lib/types/asset.ts index 352ec6411..080ba86db 100644 --- a/src/lib/types/asset.ts +++ b/src/lib/types/asset.ts @@ -8,5 +8,6 @@ export interface TokenWithValue { symbol: Option; logo: Option; precision: Option; + price?: Option; value: Option>; } diff --git a/src/lib/types/currency/token.ts b/src/lib/types/currency/token.ts index 031fcdcdd..8c7649382 100644 --- a/src/lib/types/currency/token.ts +++ b/src/lib/types/currency/token.ts @@ -10,6 +10,11 @@ export type Token = T & NominalType; export type USD = T & NominalType<"usd">; +// Percentage +export type Ratio = T & NominalType<"ratio">; + +export type Percent = T & NominalType<"percent">; + export interface ChainGasPrice { denom: string; gasPrice: U; diff --git a/src/lib/types/pool.ts b/src/lib/types/pool.ts index 31a1868e3..63bc7243c 100644 --- a/src/lib/types/pool.ts +++ b/src/lib/types/pool.ts @@ -1,13 +1,20 @@ import type { Coin } from "@cosmjs/stargate"; import type { Big } from "big.js"; -import type { Addr, ContractAddr, Option, TokenWithValue } from "lib/types"; +import type { + Addr, + ContractAddr, + Option, + Ratio, + TokenWithValue, +} from "lib/types"; export type PoolTypeFilter = "All" | "Balancer" | "Stableswap"; export interface PoolWeight { denom: string; weight: T; + percentWeight: string | null; } export interface Pool< @@ -27,8 +34,8 @@ export interface PoolDetail< blockHeight: Option; creator: Option; poolAddress: ContractAddr; - swapFee: number; - exitFee: number; + swapFee: Ratio; + exitFee: Ratio; futurePoolGovernor: string | null; smoothWeightChangeParams: object | null; scalingFactors: string[] | null; diff --git a/src/lib/utils/assetValue.ts b/src/lib/utils/assetValue.ts index 78a923f53..93b84aab4 100644 --- a/src/lib/utils/assetValue.ts +++ b/src/lib/utils/assetValue.ts @@ -48,6 +48,7 @@ export const coinToTokenWithValue = ( symbol: assetInfo?.symbol, logo: assetInfo?.logo, precision: assetInfo?.precision, + price: assetInfo ? (big(assetInfo.price).toFixed() as USD) : undefined, value: assetInfo ? calculateAssetValue( toToken(tokenAmount, assetInfo.precision), diff --git a/src/lib/utils/formatter/formatPercentValue.ts b/src/lib/utils/formatter/formatPercentValue.ts deleted file mode 100644 index 2de27290c..000000000 --- a/src/lib/utils/formatter/formatPercentValue.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { Big } from "big.js"; -import big from "big.js"; - -import { d2Formatter, d6Formatter } from "./token"; - -export const formatPercentValue = (value: Big): string => { - const x = big(value); - const lowestThreshold = 0.000001; - - const d2 = d2Formatter(x, "0.00"); - const d6 = d6Formatter(x, "0.00"); - - if (x.eq(0) || x.gte(1)) { - return `${d2}%`; - } - - if (x.lt(lowestThreshold)) { - return `<${lowestThreshold}%`; - } - return `${d6}%`; -}; diff --git a/src/lib/utils/formatter/index.ts b/src/lib/utils/formatter/index.ts index 9bce96c57..31c589ba3 100644 --- a/src/lib/utils/formatter/index.ts +++ b/src/lib/utils/formatter/index.ts @@ -5,7 +5,7 @@ export * from "./convertTitle"; export * from "./camelToSnake"; export * from "./snakeToCamel"; export * from "./formatBalanceWithDenom"; -export * from "./formatPercentValue"; +export * from "./percentage"; export * from "./tokenType"; export * from "./token"; export * from "./text"; diff --git a/src/lib/utils/formatter/percentage.ts b/src/lib/utils/formatter/percentage.ts new file mode 100644 index 000000000..0b1e99fde --- /dev/null +++ b/src/lib/utils/formatter/percentage.ts @@ -0,0 +1,31 @@ +import type { Big, BigSource } from "big.js"; +import big from "big.js"; + +import type { Percent, Ratio } from "lib/types"; + +import { d2Formatter, d6Formatter } from "./token"; + +export const formatPercent = (value: Percent): string => { + const x = big(value); + const lowestThreshold = 0.000001; + + const d2 = d2Formatter(x, "0.00"); + const d6 = d6Formatter(x, "0.00"); + + if (x.eq(0) || x.gte(1)) { + return `${d2}%`; + } + + if (x.lt(lowestThreshold)) { + return `<${lowestThreshold}%`; + } + return `${d6}%`; +}; + +/** + * @param ratio + * @returns formatted percentage string + * @description Use this function to format ratio (e.g. 0.55, 0.14789) to percentage string (e.g. 55.55%, 0.123456%) + */ +export const formatRatio = (ratio: Ratio): string => + formatPercent(big(ratio).times(100) as Percent); From da9c484c2372bd3d04a84e50d10cfb5ea4331fd4 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Tue, 9 May 2023 16:43:12 +0700 Subject: [PATCH 2/2] fix: import spacing --- src/lib/components/LabelText.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/components/LabelText.tsx b/src/lib/components/LabelText.tsx index c74fe36d8..109dde2e0 100644 --- a/src/lib/components/LabelText.tsx +++ b/src/lib/components/LabelText.tsx @@ -2,6 +2,7 @@ import type { FlexProps } from "@chakra-ui/react"; import { Flex, Text } from "@chakra-ui/react"; import { TooltipInfo } from "./TooltipComponent"; + interface LabelTextProps extends FlexProps { label: string; tooltipText?: string;