Skip to content

Commit

Permalink
feat: composition table
Browse files Browse the repository at this point in the history
  • Loading branch information
JP Angelle committed Oct 5, 2023
1 parent 5c05021 commit 69851bb
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 79 deletions.
2 changes: 1 addition & 1 deletion centrifuge-app/src/components/PoolCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { useRouteMatch } from 'react-router'
import { useTheme } from 'styled-components'
import { formatBalance, formatPercentage } from '../../utils/formatting'
import { Eththumbnail } from '../EthThumbnail'
import { Anchor, Ellipsis, Root } from '../ListItemCardStyles'
import { PoolStatus, PoolStatusKey } from './PoolStatus'
import { Anchor, Ellipsis, Root } from './styles'

const columns_base = 'minmax(150px, 2fr) minmax(100px, 1fr) 140px 70px 150px'
const columns_extended = 'minmax(200px, 2fr) minmax(100px, 1fr) 140px 100px 150px'
Expand Down
2 changes: 1 addition & 1 deletion centrifuge-app/src/components/PoolFilter/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SortButtonProps } from '../SortButton'
import { FilterMenuProps } from './FilterMenu'
import { SortButtonProps } from './SortButton'

export const SEARCH_KEYS = {
SORT_BY: 'sort-by',
Expand Down
2 changes: 1 addition & 1 deletion centrifuge-app/src/components/PoolFilter/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Grid, Text } from '@centrifuge/fabric'
import * as React from 'react'
import { COLUMNS, COLUMN_GAPS, PoolCardProps } from '../PoolCard'
import { SortButton } from '../SortButton'
import { poolFilterConfig } from './config'
import { FilterMenu } from './FilterMenu'
import { SortButton } from './SortButton'

type PoolFilterProps = {
pools?: PoolCardProps[]
Expand Down
125 changes: 53 additions & 72 deletions centrifuge-app/src/components/Portfolio/InvestedTokens.tsx
Original file line number Diff line number Diff line change
@@ -1,89 +1,70 @@
import { AccountTokenBalance, Pool } from '@centrifuge/centrifuge-js'
import { formatBalance, useBalances } from '@centrifuge/centrifuge-react'
import { useAddress, useBalances } from '@centrifuge/centrifuge-react'
import { Box, Grid, Stack, Text } from '@centrifuge/fabric'
import * as React from 'react'
import { useAddress } from '../../utils/useAddress'
import { usePool } from '../../utils/usePools'
import { useMemo } from 'react'
import { useLocation } from 'react-router'
import { useTinlakeBalances } from '../../utils/tinlake/useTinlakeBalances'
import { useTinlakePools } from '../../utils/tinlake/useTinlakePools'
import { usePools } from '../../utils/usePools'
import { SortButton } from '../SortButton'
import { sortTokens } from './sortTokens'
import { TokenListItem } from './TokenListItem'

const TOKEN_ITEM_COLUMNS = `250px 200px 100px 150px 1FR`
const TOKEN_ITEM_GAP = 4
export const InvestedTokens = () => {
const { search } = useLocation()

export function InvestedTokens() {
const address = useAddress()
const balances = useBalances(address)
const centBalances = useBalances(address)
const { data: tinlakeBalances } = useTinlakeBalances()

return !!balances?.tranches && !!balances?.tranches.length ? (
<>
<Box as="article">
<Text as="h2" variant="heading2">
Portfolio Composition
</Text>
</Box>
<Stack gap={1}>
<Grid gridTemplateColumns={TOKEN_ITEM_COLUMNS} gap={TOKEN_ITEM_GAP} px={2}>
const { data: tinlakePools } = useTinlakePools()
const pools = usePools()

const balances = useMemo(() => {
return [
...(centBalances?.tranches || []),
...(tinlakeBalances?.tranches.filter((tranche) => !tranche.balance.isZero) || []),
]
}, [centBalances, tinlakeBalances])

const sortedTokens =
balances.length && pools && tinlakePools
? sortTokens(
balances,
{
centPools: pools,
tinlakePools: tinlakePools.pools,
},
new URLSearchParams(search)
)
: []

return sortedTokens.length ? (
<Stack as="article" gap={2}>
<Text as="h2" variant="heading2">
Portfolio composition
</Text>

<Box overflow="auto">
<Grid gridTemplateColumns="150px 150px 150px 150px" gap={3} alignItems="start" px={2}>
<Text as="span" variant="body3">
Token
</Text>
<Text as="button" variant="body3">
Position
</Text>

<SortButton label="Position" searchKey="position" justifySelf="start" />

<Text as="span" variant="body3">
Token price
</Text>
<Text as="button" variant="body3">
Market value
</Text>

<SortButton label="Market Value" searchKey="market-value" justifySelf="start" />
</Grid>

<Stack as="ul" role="list" gap={1}>
{balances.tranches.map((tranche, index) => (
<Box key={`${tranche.trancheId}${index}`} as="li">
<TokenListItem {...tranche} />
</Box>
<Stack as="ul" role="list" gap={1} py={1}>
{balances.map((balance, index) => (
<TokenListItem key={index} {...balance} />
))}
</Stack>
</Stack>
</>
</Box>
</Stack>
) : null
}

type TokenCardProps = AccountTokenBalance
export function TokenListItem({ balance, currency, poolId, trancheId }: TokenCardProps) {
const pool = usePool(poolId) as Pool
const isTinlakePool = poolId?.startsWith('0x')

if (isTinlakePool) {
return null
}

const tranche = pool.tranches.find(({ id }) => id === trancheId)

return (
<Grid
gridTemplateColumns={TOKEN_ITEM_COLUMNS}
gap={TOKEN_ITEM_GAP}
padding={2}
borderStyle="solid"
borderWidth={1}
borderColor="borderSecondary"
>
<Text as="span" variant="body2">
{currency.name}
</Text>

<Text as="span" variant="body2">
{formatBalance(balance, tranche?.currency.symbol)}
</Text>

<Text as="span" variant="body2">
{tranche?.tokenPrice ? formatBalance(tranche.tokenPrice.toDecimal(), tranche.currency.symbol, 4) : '-'}
</Text>

<Text as="span" variant="body2">
{tranche?.tokenPrice
? formatBalance(balance.toDecimal().mul(tranche.tokenPrice.toDecimal()), tranche.currency.symbol, 4)
: '-'}
</Text>
</Grid>
)
}
96 changes: 96 additions & 0 deletions centrifuge-app/src/components/Portfolio/TokenListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { AccountTokenBalance, PoolMetadata } from '@centrifuge/centrifuge-js'
import { formatBalance } from '@centrifuge/centrifuge-react'
import {
AnchorButton,
Box,
Button,
Grid,
IconExternalLink,
IconMinus,
IconPlus,
Shelf,
Text,
Thumbnail,
} from '@centrifuge/fabric'
import { useTheme } from 'styled-components'
import { usePool } from '../../utils/usePools'
import { Eththumbnail } from '../EthThumbnail'
import { Ellipsis, Root } from '../ListItemCardStyles'

export type TokenCardProps = AccountTokenBalance

export function TokenListItem({ balance, currency, poolId, trancheId }: TokenCardProps) {
const { sizes } = useTheme()
const pool = usePool(poolId, false)

const isTinlakePool = poolId.startsWith('0x')

const trancheInfo = pool?.tranches.find(({ id }) => id === trancheId)
const icon = (trancheInfo?.poolMetadata as PoolMetadata).tranches?.[trancheId].icon?.uri

return (
<Root as="article">
<Grid gridTemplateColumns="150px 150px 150px 150px 1fr" gap={3} p={2} alignItems="center">
<Grid as="header" gridTemplateColumns={`${sizes.iconMedium}px 1fr`} alignItems="center" gap={2}>
<Eththumbnail show={isTinlakePool}>
{icon ? (
<Box as="img" src={icon} alt="" height="iconMedium" width="iconMedium" />
) : (
<Thumbnail type="pool" label="LP" size="small" />
)}
</Eththumbnail>

<Text as="h2" variant="body2" color="textPrimary">
<Ellipsis>{currency.name}</Ellipsis>
</Text>
</Grid>

<Text as="span" variant="body2" color="textPrimary">
<Ellipsis>{formatBalance(balance, currency.symbol)}</Ellipsis>
</Text>

<Text as="span" variant="body2" color="textPrimary">
<Ellipsis>
{trancheInfo?.tokenPrice
? formatBalance(trancheInfo.tokenPrice.toDecimal(), trancheInfo.currency.symbol, 4)
: '-'}
</Ellipsis>
</Text>

<Text as="span" variant="body2" color="textPrimary">
<Ellipsis>
{trancheInfo?.tokenPrice
? formatBalance(
balance.toDecimal().mul(trancheInfo.tokenPrice.toDecimal()),
trancheInfo.currency.symbol,
4
)
: '-'}
</Ellipsis>
</Text>

<Shelf gap={2} justifySelf="end">
{isTinlakePool ? (
<AnchorButton
variant="tertiary"
icon={IconExternalLink}
href="https://legacy.tinlake.centrifuge.io/portfolio"
target="_blank"
>
View on Tinlake
</AnchorButton>
) : (
<>
<Button variant="tertiary" icon={IconMinus}>
Redeem
</Button>
<Button variant="tertiary" icon={IconPlus}>
Invest
</Button>
</>
)}
</Shelf>
</Grid>
</Root>
)
}
50 changes: 50 additions & 0 deletions centrifuge-app/src/components/Portfolio/sortTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Pool } from '@centrifuge/centrifuge-js'
import { TinlakePool } from '../../utils/tinlake/useTinlakePools'
import { TokenCardProps } from './TokenListItem'

export const sortTokens = (
tranches: TokenCardProps[],
pools: {
centPools: Pool[]
tinlakePools: TinlakePool[]
},
searchParams: URLSearchParams
) => {
const sortDirection = searchParams.get('sort')
const sortBy = searchParams.get('sort-by')

if (sortBy === 'market-value') {
tranches.sort((trancheA, trancheB) => {
const valueA = sortMarketValue(trancheA, pools)
const valueB = sortMarketValue(trancheB, pools)

return sortDirection === 'asc' ? valueA - valueB : valueB - valueA
})
}

if (sortBy === 'position' || (!sortDirection && !sortBy)) {
tranches.sort(({ balance: balanceA }, { balance: balanceB }) =>
sortDirection === 'asc'
? balanceA.toDecimal().toNumber() - balanceB.toDecimal().toNumber()
: balanceB.toDecimal().toNumber() - balanceA.toDecimal().toNumber()
)
}

return tranches
}

const sortMarketValue = (
tranche: TokenCardProps,
pools: {
centPools: Pool[]
tinlakePools: TinlakePool[]
}
) => {
const pool = tranche.poolId.startsWith('0x')
? pools.tinlakePools?.find((p) => p.id.toLowerCase() === tranche.poolId.toLowerCase())
: pools.centPools?.find((p) => p.id === tranche.poolId)

const poolTranche = pool?.tranches.find(({ id }) => id === tranche.trancheId)

return poolTranche?.tokenPrice ? tranche.balance.toDecimal().mul(poolTranche.tokenPrice.toDecimal()).toNumber() : 0
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { IconChevronDown, IconChevronUp, Stack, Tooltip } from '@centrifuge/fabric'
import * as React from 'react'
import { useHistory, useLocation } from 'react-router-dom'
import { SEARCH_KEYS } from './config'
import { FilterButton } from './styles'
import { SortBy } from './types'
import { SEARCH_KEYS } from './PoolFilter/config'
import { FilterButton } from './PoolFilter/styles'

export type SortButtonProps = {
label: string
searchKey: SortBy
searchKey: string
tooltip?: string
justifySelf?: 'start' | 'end'
}
Expand Down

0 comments on commit 69851bb

Please sign in to comment.