Commit f9a94695 authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

feat: [info] Add token description to TDP (#7504)

* update token description for TDP

* add tooltip to fee

* show buy/sell fees

* remove token description from PDP

* remove unused styles

* update snapshots

* undo fee component testing

* isInfoTDPEnabled

* update explorer fn
parent b995f4d6
...@@ -41,10 +41,10 @@ export const LeftPanel = styled.div` ...@@ -41,10 +41,10 @@ export const LeftPanel = styled.div`
max-width: 780px; max-width: 780px;
overflow: hidden; overflow: hidden;
` `
export const RightPanel = styled.div` export const RightPanel = styled.div<{ isInfoTDPEnabled?: boolean }>`
display: none; display: none;
flex-direction: column; flex-direction: column;
gap: 20px; gap: ${({ isInfoTDPEnabled }) => (isInfoTDPEnabled ? 40 : 20)}px;
width: ${SWAP_COMPONENT_WIDTH}px; width: ${SWAP_COMPONENT_WIDTH}px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) { @media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
......
...@@ -34,11 +34,10 @@ describe('TokenDescription', () => { ...@@ -34,11 +34,10 @@ describe('TokenDescription', () => {
}) })
it('renders token information correctly with defaults', () => { it('renders token information correctly with defaults', () => {
const { asFragment } = render(<TokenDescription tokenAddress={tokenAddress} showCopy />) const { asFragment } = render(<TokenDescription tokenAddress={tokenAddress} />)
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
expect(screen.getByText('USDC')).toBeVisible() expect(screen.getByText('Info')).toBeVisible()
expect(screen.getByText('USDCoin')).toBeVisible()
expect(screen.getByText('Website')).toBeVisible() expect(screen.getByText('Website')).toBeVisible()
expect(screen.getByText('Twitter')).toBeVisible() expect(screen.getByText('Twitter')).toBeVisible()
expect(screen.getByText('Etherscan')).toBeVisible() expect(screen.getByText('Etherscan')).toBeVisible()
...@@ -46,7 +45,7 @@ describe('TokenDescription', () => { ...@@ -46,7 +45,7 @@ describe('TokenDescription', () => {
}) })
it('truncates description and shows more', async () => { it('truncates description and shows more', async () => {
const { asFragment } = render(<TokenDescription tokenAddress={tokenAddress} showCopy />) const { asFragment } = render(<TokenDescription tokenAddress={tokenAddress} />)
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
const truncatedDescription = screen.getByTestId('token-description-truncated') const truncatedDescription = screen.getByTestId('token-description-truncated')
...@@ -61,19 +60,12 @@ describe('TokenDescription', () => { ...@@ -61,19 +60,12 @@ describe('TokenDescription', () => {
expect(screen.getByText('Hide')).toBeVisible() expect(screen.getByText('Hide')).toBeVisible()
}) })
it('copy address button hidden when flagged', async () => {
const { asFragment } = render(<TokenDescription tokenAddress={tokenAddress} />)
expect(asFragment()).toMatchSnapshot()
expect(screen.queryByText('0xA0b8...eB48')).toBeNull()
})
it('no description or social buttons shown when not available', async () => { it('no description or social buttons shown when not available', async () => {
mocked(useTokenProjectQuery).mockReturnValue({ data: undefined } as unknown as QueryResult< mocked(useTokenProjectQuery).mockReturnValue({ data: undefined } as unknown as QueryResult<
TokenProjectQuery, TokenProjectQuery,
Exact<{ chain: Chain; address?: string }> Exact<{ chain: Chain; address?: string }>
>) >)
const { asFragment } = render(<TokenDescription tokenAddress={tokenAddress} showCopy />) const { asFragment } = render(<TokenDescription tokenAddress={tokenAddress} />)
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
expect(screen.getByText('No token information available')).toBeVisible() expect(screen.getByText('No token information available')).toBeVisible()
......
...@@ -4,14 +4,14 @@ import Column from 'components/Column' ...@@ -4,14 +4,14 @@ import Column from 'components/Column'
import { EtherscanLogo } from 'components/Icons/Etherscan' import { EtherscanLogo } from 'components/Icons/Etherscan'
import { Globe } from 'components/Icons/Globe' import { Globe } from 'components/Icons/Globe'
import { TwitterXLogo } from 'components/Icons/TwitterX' import { TwitterXLogo } from 'components/Icons/TwitterX'
import CurrencyLogo from 'components/Logo/CurrencyLogo'
import Row from 'components/Row' import Row from 'components/Row'
import { FOTTooltipContent } from 'components/swap/SwapLineItem'
import { NoInfoAvailable, truncateDescription, TruncateDescriptionButton } from 'components/Tokens/TokenDetails/shared' import { NoInfoAvailable, truncateDescription, TruncateDescriptionButton } from 'components/Tokens/TokenDetails/shared'
import { MouseoverTooltip, TooltipSize } from 'components/Tooltip'
import { useTokenProjectQuery } from 'graphql/data/__generated__/types-and-hooks' import { useTokenProjectQuery } from 'graphql/data/__generated__/types-and-hooks'
import { chainIdToBackendName } from 'graphql/data/util' import { chainIdToBackendName } from 'graphql/data/util'
import { useCurrency } from 'hooks/Tokens'
import { useColor } from 'hooks/useColor'
import useCopyClipboard from 'hooks/useCopyClipboard' import useCopyClipboard from 'hooks/useCopyClipboard'
import { useSwapTaxes } from 'hooks/useSwapTaxes'
import { useCallback, useReducer } from 'react' import { useCallback, useReducer } from 'react'
import { Copy } from 'react-feather' import { Copy } from 'react-feather'
import styled, { useTheme } from 'styled-components' import styled, { useTheme } from 'styled-components'
...@@ -19,10 +19,12 @@ import { BREAKPOINTS } from 'theme' ...@@ -19,10 +19,12 @@ import { BREAKPOINTS } from 'theme'
import { ClickableStyle, EllipsisStyle, ExternalLink, ThemedText } from 'theme/components' import { ClickableStyle, EllipsisStyle, ExternalLink, ThemedText } from 'theme/components'
import { opacify } from 'theme/utils' import { opacify } from 'theme/utils'
import { shortenAddress } from 'utils' import { shortenAddress } from 'utils'
import { useFormatter } from 'utils/formatNumbers'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink' import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
import { getNativeTokenDBAddress } from 'utils/nativeTokens'
const TokenInfoSection = styled(Column)` const TokenInfoSection = styled(Column)`
gap: 12px; gap: 16px;
width: 100%; width: 100%;
@media (max-width: ${BREAKPOINTS.lg - 1}px) and (min-width: ${BREAKPOINTS.sm}px) { @media (max-width: ${BREAKPOINTS.lg - 1}px) and (min-width: ${BREAKPOINTS.sm}px) {
...@@ -35,10 +37,6 @@ const TokenNameRow = styled(Row)` ...@@ -35,10 +37,6 @@ const TokenNameRow = styled(Row)`
width: 100%; width: 100%;
` `
const TokenName = styled(ThemedText.BodyPrimary)`
${EllipsisStyle}
`
const TokenButtonRow = styled(TokenNameRow)` const TokenButtonRow = styled(TokenNameRow)`
flex-wrap: wrap; flex-wrap: wrap;
` `
...@@ -73,25 +71,30 @@ const TRUNCATE_CHARACTER_COUNT = 75 ...@@ -73,25 +71,30 @@ const TRUNCATE_CHARACTER_COUNT = 75
export function TokenDescription({ export function TokenDescription({
tokenAddress, tokenAddress,
chainId = ChainId.MAINNET, chainId = ChainId.MAINNET,
showCopy = false, isNative = false,
characterCount = TRUNCATE_CHARACTER_COUNT,
}: { }: {
tokenAddress: string tokenAddress: string
chainId?: number chainId?: number
showCopy?: boolean isNative?: boolean
characterCount?: number
}) { }) {
const currency = useCurrency(tokenAddress, chainId) const color = useTheme().neutral1
const theme = useTheme() const chainName = chainIdToBackendName(chainId)
const color = useColor(currency?.wrapped, theme.surface1, theme.darkMode)
const { data: tokenQuery } = useTokenProjectQuery({ const { data: tokenQuery } = useTokenProjectQuery({
variables: { variables: {
address: tokenAddress, address: isNative ? getNativeTokenDBAddress(chainName) : tokenAddress,
chain: chainIdToBackendName(chainId), chain: chainName,
}, },
errorPolicy: 'all', errorPolicy: 'all',
}) })
const tokenProject = tokenQuery?.token?.project const tokenProject = tokenQuery?.token?.project
const description = tokenProject?.description const description = tokenProject?.description
const explorerUrl = getExplorerLink(chainId, tokenAddress, ExplorerDataType.TOKEN) const explorerUrl = getExplorerLink(
chainId,
tokenAddress,
isNative ? ExplorerDataType.NATIVE : ExplorerDataType.TOKEN
)
const [, setCopied] = useCopyClipboard() const [, setCopied] = useCopyClipboard()
const copy = useCallback(() => { const copy = useCallback(() => {
...@@ -99,19 +102,25 @@ export function TokenDescription({ ...@@ -99,19 +102,25 @@ export function TokenDescription({
}, [tokenAddress, setCopied]) }, [tokenAddress, setCopied])
const [isDescriptionTruncated, toggleIsDescriptionTruncated] = useReducer((x) => !x, true) const [isDescriptionTruncated, toggleIsDescriptionTruncated] = useReducer((x) => !x, true)
const truncatedDescription = truncateDescription(description ?? '', TRUNCATE_CHARACTER_COUNT) const truncatedDescription = truncateDescription(description ?? '', characterCount)
const shouldTruncate = !!description && description.length > TRUNCATE_CHARACTER_COUNT const shouldTruncate = !!description && description.length > characterCount
const showTruncatedDescription = shouldTruncate && isDescriptionTruncated const showTruncatedDescription = shouldTruncate && isDescriptionTruncated
const { inputTax: sellFee, outputTax: buyFee } = useSwapTaxes(tokenAddress, tokenAddress)
const { formatPercent } = useFormatter()
const { sellFeeString, buyFeeString } = {
sellFeeString: formatPercent(sellFee),
buyFeeString: formatPercent(buyFee),
}
const hasFee = Boolean(parseFloat(sellFeeString)) || Boolean(parseFloat(buyFee.toFixed(2)))
const sameFee = sellFeeString === buyFeeString
return ( return (
<TokenInfoSection> <TokenInfoSection>
<TokenNameRow> <ThemedText.HeadlineSmall>
<CurrencyLogo currency={currency} size="20px" /> <Trans>Info</Trans>
<TokenName>{currency?.name}</TokenName> </ThemedText.HeadlineSmall>
<ThemedText.BodySecondary>{currency?.symbol}</ThemedText.BodySecondary>
</TokenNameRow>
<TokenButtonRow> <TokenButtonRow>
{showCopy && ( {!isNative && (
<TokenInfoButton tokenColor={color} onClick={copy}> <TokenInfoButton tokenColor={color} onClick={copy}>
<Copy width="18px" height="18px" color={color} /> <Copy width="18px" height="18px" color={color} />
{shortenAddress(tokenAddress)} {shortenAddress(tokenAddress)}
...@@ -165,6 +174,37 @@ export function TokenDescription({ ...@@ -165,6 +174,37 @@ export function TokenDescription({
</TruncateDescriptionButton> </TruncateDescriptionButton>
)} )}
</TokenDescriptionContainer> </TokenDescriptionContainer>
{hasFee && (
<MouseoverTooltip
placement="left"
size={TooltipSize.Small}
text={
<ThemedText.Caption color="neutral2">
<FOTTooltipContent />
</ThemedText.Caption>
}
>
<Column gap="sm">
{sameFee ? (
<ThemedText.BodyPrimary>
{tokenQuery?.token?.symbol}&nbsp;
<Trans>fee:</Trans>&nbsp;{sellFeeString}
</ThemedText.BodyPrimary>
) : (
<>
<ThemedText.BodyPrimary>
{tokenQuery?.token?.symbol}&nbsp;
<Trans>buy fee:</Trans>&nbsp;{buyFeeString}
</ThemedText.BodyPrimary>{' '}
<ThemedText.BodyPrimary>
{tokenQuery?.token?.symbol}&nbsp;
<Trans>sell fee:</Trans>&nbsp;{sellFeeString}
</ThemedText.BodyPrimary>{' '}
</>
)}
</Column>
</MouseoverTooltip>
)}
</TokenInfoSection> </TokenInfoSection>
) )
} }
...@@ -43,6 +43,7 @@ import { addressesAreEquivalent } from 'utils/addressesAreEquivalent' ...@@ -43,6 +43,7 @@ import { addressesAreEquivalent } from 'utils/addressesAreEquivalent'
import { OnChangeTimePeriod } from './ChartSection' import { OnChangeTimePeriod } from './ChartSection'
import InvalidTokenDetails from './InvalidTokenDetails' import InvalidTokenDetails from './InvalidTokenDetails'
import { TokenDescription } from './TokenDescription'
const TokenSymbol = styled.span` const TokenSymbol = styled.span`
text-transform: uppercase; text-transform: uppercase;
...@@ -258,7 +259,7 @@ export default function TokenDetails({ ...@@ -258,7 +259,7 @@ export default function TokenDetails({
<TokenDetailsSkeleton /> <TokenDetailsSkeleton />
)} )}
<RightPanel onClick={() => isBlockedToken && setOpenTokenSafetyModal(true)}> <RightPanel isInfoTDPEnabled={isInfoTDPEnabled} onClick={() => isBlockedToken && setOpenTokenSafetyModal(true)}>
<div style={{ pointerEvents: isBlockedToken ? 'none' : 'auto' }}> <div style={{ pointerEvents: isBlockedToken ? 'none' : 'auto' }}>
<Swap <Swap
chainId={pageChainId} chainId={pageChainId}
...@@ -270,6 +271,14 @@ export default function TokenDetails({ ...@@ -270,6 +271,14 @@ export default function TokenDetails({
</div> </div>
{tokenWarning && <TokenSafetyMessage tokenAddress={address} warning={tokenWarning} />} {tokenWarning && <TokenSafetyMessage tokenAddress={address} warning={tokenWarning} />}
{!isInfoTDPEnabled && detailedToken && <BalanceSummary token={detailedToken} />} {!isInfoTDPEnabled && detailedToken && <BalanceSummary token={detailedToken} />}
{isInfoTDPEnabled && (
<TokenDescription
tokenAddress={address}
chainId={pageChainId}
isNative={detailedToken?.isNative}
characterCount={200}
/>
)}
</RightPanel> </RightPanel>
{!isInfoTDPEnabled && detailedToken && <MobileBalanceSummaryFooter token={detailedToken} />} {!isInfoTDPEnabled && detailedToken && <MobileBalanceSummaryFooter token={detailedToken} />}
......
...@@ -63,7 +63,7 @@ const AutoBadge = styled(ThemedText.LabelMicro).attrs({ fontWeight: 535 })` ...@@ -63,7 +63,7 @@ const AutoBadge = styled(ThemedText.LabelMicro).attrs({ fontWeight: 535 })`
} }
` `
function FOTTooltipContent() { export function FOTTooltipContent() {
return ( return (
<> <>
<Trans> <Trans>
......
import { Trans } from '@lingui/macro'
import Column from 'components/Column' import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { LoadingBubble } from 'components/Tokens/loading' import { LoadingBubble } from 'components/Tokens/loading'
import { LoadingChart } from 'components/Tokens/TokenDetails/Skeleton' import { LoadingChart } from 'components/Tokens/TokenDetails/Skeleton'
import { TokenDescription } from 'components/Tokens/TokenDetails/TokenDescription'
import { getValidUrlChainName, supportedChainIdFromGQLChain } from 'graphql/data/util' import { getValidUrlChainName, supportedChainIdFromGQLChain } from 'graphql/data/util'
import { usePoolData } from 'graphql/thegraph/PoolData' import { usePoolData } from 'graphql/thegraph/PoolData'
import NotFound from 'pages/NotFound' import NotFound from 'pages/NotFound'
import { useReducer } from 'react' import { useReducer } from 'react'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { Text } from 'rebass'
import styled from 'styled-components' import styled from 'styled-components'
import { BREAKPOINTS } from 'theme' import { BREAKPOINTS } from 'theme'
import { isAddress } from 'utils' import { isAddress } from 'utils'
...@@ -76,28 +73,6 @@ const RightColumn = styled(Column)` ...@@ -76,28 +73,6 @@ const RightColumn = styled(Column)`
} }
` `
const TokenDetailsWrapper = styled(Column)`
gap: 24px;
padding: 20px;
@media (max-width: ${BREAKPOINTS.lg - 1}px) and (min-width: ${BREAKPOINTS.sm}px) {
flex-direction: row;
flex-wrap: wrap;
padding: unset;
}
@media (max-width: ${BREAKPOINTS.sm - 1}px) {
padding: unset;
}
`
const TokenDetailsHeader = styled(Text)`
width: 100%;
font-size: 24px;
font-weight: 485;
line-height: 32px;
`
export default function PoolDetailsPage() { export default function PoolDetailsPage() {
const { poolAddress, chainName } = useParams<{ const { poolAddress, chainName } = useParams<{
poolAddress: string poolAddress: string
...@@ -152,15 +127,8 @@ export default function PoolDetailsPage() { ...@@ -152,15 +127,8 @@ export default function PoolDetailsPage() {
</Row> </Row>
))} ))}
</LinkColumn> </LinkColumn>
) : ( ) : null)}
<TokenDetailsWrapper> {/* TODO(WEB-2985) replace with new Token Links component */}
<TokenDetailsHeader>
<Trans>Info</Trans>
</TokenDetailsHeader>
{token0 && <TokenDescription tokenAddress={token0.id} chainId={chainId} />}
{token1 && <TokenDescription tokenAddress={token1.id} chainId={chainId} />}
</TokenDetailsWrapper>
))}
</RightColumn> </RightColumn>
</PageWrapper> </PageWrapper>
) )
......
...@@ -20,6 +20,7 @@ export enum ExplorerDataType { ...@@ -20,6 +20,7 @@ export enum ExplorerDataType {
TOKEN = 'token', TOKEN = 'token',
ADDRESS = 'address', ADDRESS = 'address',
BLOCK = 'block', BLOCK = 'block',
NATIVE = 'native',
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment