From 5af959bba22ef0b9e9024db7c8d33a67f9779a48 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Wed, 1 Mar 2023 15:02:32 +0700 Subject: [PATCH] feat: add token card to coins value --- src/lib/components/copy/CopyButton.tsx | 21 ++- src/lib/components/copy/CopyTemplate.tsx | 5 +- .../modal/UnsupportedTokensModal.tsx | 44 ++++-- .../components/tx-message/TxMsgDetails.tsx | 4 +- .../tx-message/msg-receipts/CoinComponent.tsx | 132 ++++++++++++++++++ .../tx-message/msg-receipts/index.tsx | 100 ++++++------- .../tx-message/msg-receipts/renderUtils.tsx | 27 +++- src/lib/types/currency/balance.ts | 7 +- 8 files changed, 262 insertions(+), 78 deletions(-) create mode 100644 src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx diff --git a/src/lib/components/copy/CopyButton.tsx b/src/lib/components/copy/CopyButton.tsx index cce539142..9f2f21602 100644 --- a/src/lib/components/copy/CopyButton.tsx +++ b/src/lib/components/copy/CopyButton.tsx @@ -1,16 +1,18 @@ import { CopyIcon } from "@chakra-ui/icons"; import { Button } from "@chakra-ui/react"; -import type { ButtonProps } from "@chakra-ui/react"; +import type { ButtonProps, TooltipProps } from "@chakra-ui/react"; import { AmpEvent, AmpTrack } from "lib/services/amplitude"; import { CopyTemplate } from "./CopyTemplate"; -interface CopyButtonProps { +interface CopyButtonProps extends ButtonProps { isDisable?: boolean; value: string; - size?: ButtonProps["size"]; copyLabel?: string; + hasIcon?: boolean; + buttonText?: string; + tooltipBgColor?: TooltipProps["bgColor"]; } export const CopyButton = ({ @@ -18,20 +20,27 @@ export const CopyButton = ({ value, size = "sm", copyLabel, + hasIcon = true, + variant = "outline-info", + buttonText = "Copy", + tooltipBgColor, + ...buttonProps }: CopyButtonProps) => ( AmpTrack(AmpEvent.USE_COPY_BUTTON)} - leftIcon={} + leftIcon={hasIcon ? : undefined} + {...buttonProps} > - Copy + {buttonText} } /> diff --git a/src/lib/components/copy/CopyTemplate.tsx b/src/lib/components/copy/CopyTemplate.tsx index 7eebca494..f54b8f852 100644 --- a/src/lib/components/copy/CopyTemplate.tsx +++ b/src/lib/components/copy/CopyTemplate.tsx @@ -1,3 +1,4 @@ +import type { TooltipProps } from "@chakra-ui/react"; import { Box, Tooltip, useClipboard } from "@chakra-ui/react"; import { useEffect } from "react"; @@ -5,12 +6,14 @@ interface CopyTemplateProps { value: string; copyLabel?: string; triggerElement: JSX.Element; + tooltipBgColor?: TooltipProps["bgColor"]; } export const CopyTemplate = ({ value, copyLabel = "Copied!", triggerElement, + tooltipBgColor = "honeydew.darker", }: CopyTemplateProps) => { const { onCopy, hasCopied, setValue } = useClipboard(value); useEffect(() => setValue(value), [value, setValue]); @@ -22,7 +25,7 @@ export const CopyTemplate = ({ label={copyLabel} placement="top" arrowSize={8} - bg="honeydew.darker" + bgColor={tooltipBgColor} > { diff --git a/src/lib/components/modal/UnsupportedTokensModal.tsx b/src/lib/components/modal/UnsupportedTokensModal.tsx index 709dfc765..a642ac8c8 100644 --- a/src/lib/components/modal/UnsupportedTokensModal.tsx +++ b/src/lib/components/modal/UnsupportedTokensModal.tsx @@ -1,3 +1,4 @@ +import type { ButtonProps } from "@chakra-ui/react"; import { Modal, ModalHeader, @@ -22,7 +23,7 @@ import { MdAttachMoney } from "react-icons/md"; import { ExplorerLink } from "../ExplorerLink"; import { Copier } from "lib/components/copy"; import type { AddressReturnType } from "lib/hooks"; -import { getAddressTypeByLength } from "lib/hooks"; +import { useGetAddressType, getAddressTypeByLength } from "lib/hooks"; import type { BalanceWithAssetInfo, Balance, Token, U, Addr } from "lib/types"; import { getTokenType, @@ -32,22 +33,34 @@ import { interface UnsupportedTokensModalProps { unsupportedAssets: BalanceWithAssetInfo[]; - address: Addr; + address?: Addr; + buttonProps?: ButtonProps; } interface UnsupportedTokenProps { balance: Balance; } +const getTokenTypeWithAddress = ( + type: Balance["type"], + addrType: AddressReturnType +) => { + if (type) return getTokenType(type); + return addrType === "contract_address" + ? getTokenType("cw20") + : getTokenType("native"); +}; + const UnsupportedToken = ({ balance }: UnsupportedTokenProps) => { + const getAddressType = useGetAddressType(); // TODO - Move this to utils const [tokenLabel, tokenType] = useMemo(() => { const label = getTokenLabel(balance.id); const type = !balance.id.includes("/") - ? getTokenType(balance.type) + ? getTokenTypeWithAddress(balance.type, getAddressType(balance.id)) : getTokenType(balance.id.split("/")[0]); return [label, type]; - }, [balance]); + }, [balance, getAddressType]); return ( { export const UnsupportedTokensModal = ({ unsupportedAssets, address, + buttonProps, }: UnsupportedTokensModalProps) => { const { currentChainName } = useWallet(); const { isOpen, onOpen, onClose } = useDisclosure(); @@ -118,7 +132,13 @@ export const UnsupportedTokensModal = ({ return ( <> - @@ -137,12 +157,14 @@ export const UnsupportedTokensModal = ({ - - - {content.header} - - - + {address && ( + + + {content.header} + + + + )} {unsupportedAssets.map((asset) => ( { const getAddressType = useGetAddressType(); - const receipts = generateReceipts(txMsgData, getAddressType) + const assetInfos = useAssetInfos(); + const receipts = generateReceipts(txMsgData, getAddressType, assetInfos) .filter(Boolean) .concat( txMsgData.log diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx new file mode 100644 index 000000000..6dba2e96d --- /dev/null +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/CoinComponent.tsx @@ -0,0 +1,132 @@ +import { Flex, Grid } from "@chakra-ui/react"; +import type { Coin } from "@cosmjs/stargate"; +import { useState } from "react"; + +import { ShowMoreButton } from "lib/components/button"; +import { UnsupportedTokensModal } from "lib/components/modal/UnsupportedTokensModal"; +import { TokenCard } from "lib/components/TokenCard"; +import type { AssetInfo, Option } from "lib/types"; + +type AssetObject = { [key: string]: AssetInfo }; + +interface CoinComponentProps< + T extends Coin | Coin[], + A extends Option | AssetObject +> { + amount: T; + assetInfos: A; +} + +const MultiCoin = ({ + amount, + assetInfos, +}: CoinComponentProps) => { + const [supportedCoins, unsupportedCoins] = [ + amount.filter((coin) => Boolean(assetInfos[coin.denom])), + amount.filter((coin) => !assetInfos[coin.denom]), + ]; + const [showMore, setShowMore] = useState(false); + + const hasSupportedCoins = supportedCoins.length > 0; + return ( + + {hasSupportedCoins && ( + + {supportedCoins.slice(0, showMore ? undefined : 2).map((coin) => { + const assetInfo = assetInfos[coin.denom]; + return ( + + ); + })} + + )} + + {supportedCoins.length > 2 && ( + setShowMore(!showMore)} + /> + )} + {unsupportedCoins && ( + { + const assetInfo = assetInfos[coin.denom]; + return { + balance: { + amount: coin.amount, + id: coin.denom, + precision: 0, + }, + assetInfo, + }; + })} + buttonProps={{ fontSize: "12px", mb: 0 }} + /> + )} + + + ); +}; +const SingleCoin = ({ + amount, + assetInfos, +}: CoinComponentProps) => { + const assetInfo = assetInfos[amount.denom]; + return assetInfo ? ( + + ) : ( + + ); +}; + +export const CoinComponent = ({ + amount, + assetInfos, +}: CoinComponentProps>) => { + if (!assetInfos) return <>{JSON.stringify(amount)}; + return Array.isArray(amount) ? ( + + ) : ( + + ); +}; diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx index c8d6d95b5..69d919941 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/index.tsx @@ -2,12 +2,14 @@ /* eslint-disable complexity */ import { Flex } from "@chakra-ui/react"; import { findAttribute } from "@cosmjs/stargate/build/logs"; +import big from "big.js"; import type { TxMsgData } from ".."; +import { CopyButton } from "lib/components/copy"; import { PermissionChip } from "lib/components/PermissionChip"; import { ViewPermissionAddresses } from "lib/components/ViewPermissionAddresses"; import type { AddressReturnType } from "lib/hooks"; -import type { TxReceipt, Option } from "lib/types"; +import type { TxReceipt, Option, AssetInfo } from "lib/types"; import { formatUTC, parseDate } from "lib/utils"; import { voteOption } from "./mapping"; @@ -22,11 +24,13 @@ import { proposalIdReceipt, validatorAddrReceipt, getGenericValueEntry, + getCoinComponent, } from "./renderUtils"; export const generateReceipts = ( { type, value, log }: TxMsgData, - getAddressType: (address: string) => AddressReturnType + getAddressType: (address: string) => AddressReturnType, + assetInfos: Option<{ [key: string]: AssetInfo }> ): Option[] => { switch (type) { // cosmwasm/wasm @@ -70,7 +74,23 @@ export const generateReceipts = ( }, { title: "Wasm Byte Code", - value: value.wasmByteCode, + html: ( + + Size:{" "} + {big(Buffer.from(value.wasmByteCode).byteLength) + .div(1024) + .toFixed(1)}{" "} + KB + + + ), }, ]; case "wasm/MsgInstantiateContract": @@ -114,7 +134,7 @@ export const generateReceipts = ( title: "Label", value: value.label, }, - attachFundsReceipt(value.funds), + attachFundsReceipt(value.funds, assetInfos), { title: "Instantiate Message", html: getCommonReceiptHtml({ @@ -163,7 +183,7 @@ export const generateReceipts = ( title: "Label", value: value.label, }, - attachFundsReceipt(value.funds), + attachFundsReceipt(value.funds, assetInfos), { title: "Instantiate Message", html: getCommonReceiptHtml({ @@ -198,7 +218,7 @@ export const generateReceipts = ( linkType: "contract_address", }), }, - attachFundsReceipt(value.funds), + attachFundsReceipt(value.funds, assetInfos), { title: "Execute Message", html: getCommonReceiptHtml({ @@ -306,10 +326,9 @@ export const generateReceipts = ( linkType: getAddressType(value.toAddress), }), }, - // Coin component { title: "Amount", - html: value.amount, + html: getCoinComponent(value.amount, assetInfos), }, ]; case "cosmos-sdk/MsgMultiSend": @@ -444,10 +463,9 @@ export const generateReceipts = ( return [validatorAddrReceipt(value.validatorAddress)]; case "cosmos-sdk/MsgFundCommunityPool": return [ - // Coin component { title: "Amount", - html: value.amount, + html: getCoinComponent(value.amount, assetInfos), }, { title: "Depositor", @@ -526,8 +544,10 @@ export const generateReceipts = ( // x/gov case "cosmos-sdk/MsgSubmitProposal": return [ - // Coin component - { title: "Initial Deposit", html: value.initialDeposit }, + { + title: "Initial Deposit", + html: getCoinComponent(value.initialDeposit, assetInfos), + }, { title: "Proposer", html: getCommonReceiptHtml({ @@ -595,8 +615,7 @@ export const generateReceipts = ( }, { title: "Amount", - // Coin - html: value.amount, + html: getCoinComponent(value.amount, assetInfos), }, ]; // x/slashing @@ -637,8 +656,7 @@ export const generateReceipts = ( }, { title: "Value", - // Coin - html: value.value, + html: getCoinComponent(value.value, assetInfos), }, ]; case "cosmos-sdk/MsgEditValidator": @@ -670,8 +688,7 @@ export const generateReceipts = ( validatorAddrReceipt(value.validatorAddress), { title: "Amount", - // Coin - html: value.amount, + html: getCoinComponent(value.amount, assetInfos), }, ]; case "cosmos-sdk/MsgBeginRedelegate": @@ -698,8 +715,7 @@ export const generateReceipts = ( }, { title: "Amount", - // Coin - html: value.amount, + html: getCoinComponent(value.amount, assetInfos), }, ]; // ibc/applications @@ -715,9 +731,7 @@ export const generateReceipts = ( }, { title: "Token", - // Coin - // html: value.token, - value: "", + html: getCoinComponent(value.token, assetInfos), }, { title: "Sender", @@ -1296,8 +1310,7 @@ export const generateReceipts = ( }, { title: "Initial Pool Liquidity", - // Coins - html: value.initialPoolLiquidity, + html: getCoinComponent(value.initialPoolLiquidity, assetInfos), }, { title: "Scaling Factors", @@ -1351,8 +1364,7 @@ export const generateReceipts = ( }, { title: "Token In Maxs", - // Coins - html: value.tokenInMaxs, + html: getCoinComponent(value.tokenInMaxs, assetInfos), }, ]; case "osmosis/gamm/exit-pool": @@ -1375,8 +1387,7 @@ export const generateReceipts = ( }, { title: "Token Out Mins", - // Coins - html: value.tokenOutMins, + html: getCoinComponent(value.tokenOutMins, assetInfos), }, ]; case "osmosis/gamm/swap-exact-amount-in": @@ -1398,8 +1409,7 @@ export const generateReceipts = ( }, { title: "Token In", - // Coin - html: value.tokenIn, + html: getCoinComponent(value.tokenIn, assetInfos), }, { title: "Token Out Min Amount", @@ -1429,8 +1439,7 @@ export const generateReceipts = ( }, { title: "Token Out", - // Coin - html: value.tokenOut, + html: getCoinComponent(value.tokenOut, assetInfos), }, ]; case "osmosis/gamm/join-swap-extern-amount-in": @@ -1449,8 +1458,7 @@ export const generateReceipts = ( }, { title: "Token In", - // Coin - html: value.tokenIn, + html: getCoinComponent(value.tokenIn, assetInfos), }, { title: "Share Out Min Amount", @@ -1527,8 +1535,7 @@ export const generateReceipts = ( }, { title: "Token Out", - // Coin - html: value.poolId, + html: getCoinComponent(value.poolId, assetInfos), }, { title: "Share In Max Amount", @@ -1559,8 +1566,7 @@ export const generateReceipts = ( }, { title: "Coins", - // Coins - html: value.coins, + html: getCoinComponent(value.coins, assetInfos), }, { title: "Start Time", @@ -1587,8 +1593,7 @@ export const generateReceipts = ( }, { title: "Rewards", - // Coins - html: value.rewards, + html: getCoinComponent(value.rewards, assetInfos), }, ]; // osmosis/lockup @@ -1608,8 +1613,7 @@ export const generateReceipts = ( }, { title: "Coins", - // Coins - html: value.coins, + html: getCoinComponent(value.coins, assetInfos), }, ]; case "osmosis/lockup/begin-unlock-tokens": @@ -1641,8 +1645,7 @@ export const generateReceipts = ( }, { title: "Coins", - // Coins - html: value.coins, + html: getCoinComponent(value.coins, assetInfos), }, ]; // ** No example @@ -1662,7 +1665,6 @@ export const generateReceipts = ( }, { title: "Duration", - // Coins value: formatUTC(parseDate(value.duration)), }, ]; @@ -1712,8 +1714,7 @@ export const generateReceipts = ( }, { title: "Coins", - // Coins - html: value.coins, + html: getCoinComponent(value.coins, assetInfos), }, validatorAddrReceipt(value.valAddr), ]; @@ -1761,8 +1762,7 @@ export const generateReceipts = ( }, { title: "Amount", - // Coin - html: value.amount, + html: getCoinComponent(value.amount, assetInfos), }, ]; case "osmosis/tokenfactory/change-admin": diff --git a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx index 0bfdc185e..4693dcbc2 100644 --- a/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx +++ b/src/lib/pages/tx-details/components/tx-message/msg-receipts/renderUtils.tsx @@ -5,9 +5,17 @@ import type { LinkType } from "lib/components/ExplorerLink"; import { ExplorerLink } from "lib/components/ExplorerLink"; import JsonReadOnly from "lib/components/json/JsonReadOnly"; import type { AddressReturnType } from "lib/hooks"; -import type { Addr, Option, TxReceipt, ValidatorAddr } from "lib/types"; +import type { + Addr, + AssetInfo, + Option, + TxReceipt, + ValidatorAddr, +} from "lib/types"; import { camelToTitle } from "lib/utils"; +import { CoinComponent } from "./CoinComponent"; + interface CommonReceiptHtmlArgs { type: "json" | "explorer"; value: Option; @@ -50,6 +58,11 @@ export const getCommonReceiptHtml = ({ ); }; +export const getCoinComponent = ( + amount: Coin | Coin[], + assetInfos: Option<{ [key: string]: AssetInfo }> +) => ; + export const getGenericValueEntry = ( entry: [string, string | object], getAddressType: (address: string) => AddressReturnType @@ -83,12 +96,14 @@ export const getGenericValueEntry = ( return { title: camelToTitle(title), ...valueObj }; }; -// Duplicated Components -// Coins component -export const attachFundsReceipt = (value: Option): TxReceipt => ({ +// Duplicated Receipt +export const attachFundsReceipt = ( + value: Option, + assetInfos: Option<{ [key: string]: AssetInfo }> +): TxReceipt => ({ title: "Attached Funds", - html: value ? ( - JSON.stringify(value) + html: value?.length ? ( + getCoinComponent(value, assetInfos) ) : ( No Attached Funds diff --git a/src/lib/types/currency/balance.ts b/src/lib/types/currency/balance.ts index ba29e2147..6b0bede51 100644 --- a/src/lib/types/currency/balance.ts +++ b/src/lib/types/currency/balance.ts @@ -3,10 +3,10 @@ import type { Option } from "lib/types"; export interface Balance { amount: string; id: string; - name: Option; + name?: Option; precision: number; - symbol: Option; - type: string; + symbol?: Option; + type?: string; price?: number; } @@ -21,6 +21,7 @@ export interface AssetInfo { slugs: string[]; symbol: string; type: string; + price: number; } export interface BalanceWithAssetInfo {