Skip to content

Commit

Permalink
feat: composition table (#1623)
Browse files Browse the repository at this point in the history
  • Loading branch information
JP authored Oct 25, 2023
1 parent 5872566 commit 4e248a0
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 119 deletions.
11 changes: 11 additions & 0 deletions centrifuge-app/src/components/FilterButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Text } from '@centrifuge/fabric'
import styled from 'styled-components'
import { buttonActionStyles } from './styles'

export const FilterButton = styled(Text)`
display: flex;
align-items: center;
gap: 0.3em;
${buttonActionStyles}
`
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
3 changes: 2 additions & 1 deletion centrifuge-app/src/components/PoolFilter/FilterMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Box, Checkbox, Divider, IconFilter, Menu, Popover, Stack, Tooltip } from '@centrifuge/fabric'
import * as React from 'react'
import { useHistory, useLocation } from 'react-router-dom'
import { FilterButton, QuickAction } from './styles'
import { FilterButton } from '../FilterButton'
import { QuickAction } from '../QuickAction'
import { SearchKeys } from './types'
import { toKebabCase } from './utils'

Expand Down
25 changes: 5 additions & 20 deletions centrifuge-app/src/components/PoolFilter/SortButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IconChevronDown, IconChevronUp, Stack, Tooltip } from '@centrifuge/fabric'
import { Tooltip } from '@centrifuge/fabric'
import * as React from 'react'
import { useHistory, useLocation } from 'react-router-dom'
import { FilterButton } from '../FilterButton'
import { SortChevrons } from '../SortChevrons'
import { SEARCH_KEYS } from './config'
import { FilterButton } from './styles'
import { SortBy } from './types'

export type SortButtonProps = {
Expand Down Expand Up @@ -64,7 +65,7 @@ export function SortButton({ label, searchKey, tooltip, justifySelf = 'end' }: S
<FilterButton forwardedAs="span" variant="body3">
{label}

<Inner sorting={sorting} />
<SortChevrons sorting={sorting} />
</FilterButton>
</Tooltip>
)
Expand All @@ -87,23 +88,7 @@ export function SortButton({ label, searchKey, tooltip, justifySelf = 'end' }: S
>
{label}

<Inner sorting={sorting} />
<SortChevrons sorting={sorting} />
</FilterButton>
)
}

function Inner({ sorting }: { sorting: Sorting }) {
return (
<Stack as="span" width="1em">
<IconChevronUp
size="1em"
color={sorting.isActive && sorting.direction === 'asc' ? 'textSelected' : 'textSecondary'}
/>
<IconChevronDown
size="1em"
color={sorting.isActive && sorting.direction === 'desc' ? 'textSelected' : 'textSecondary'}
style={{ marginTop: '-.4em' }}
/>
</Stack>
)
}
26 changes: 0 additions & 26 deletions centrifuge-app/src/components/PoolFilter/styles.ts

This file was deleted.

148 changes: 77 additions & 71 deletions centrifuge-app/src/components/Portfolio/InvestedTokens.tsx
Original file line number Diff line number Diff line change
@@ -1,89 +1,95 @@
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, useState } from 'react'
import { useTinlakeBalances } from '../../utils/tinlake/useTinlakeBalances'
import { useTinlakePools } from '../../utils/tinlake/useTinlakePools'
import { usePools } from '../../utils/usePools'
import { FilterButton } from '../FilterButton'
import { SortChevrons } from '../SortChevrons'
import { sortTokens } from './sortTokens'
import { TokenListItem } from './TokenListItem'

const TOKEN_ITEM_COLUMNS = `250px 200px 100px 150px 1FR`
const TOKEN_ITEM_GAP = 4
export const COLUMN_GAPS = '200px 140px 140px 140px'

export type SortOptions = {
sortBy: 'position' | 'market-value'
sortDirection: 'asc' | 'desc'
}

// TODO: change canInvestRedeem to default to true once the drawer is implemented
export const InvestedTokens = ({ canInvestRedeem = false }) => {
const [sortOptions, setSortOptions] = useState<SortOptions>({ sortBy: 'position', sortDirection: 'desc' })

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,
},
sortOptions
)
: []

const handleSort = (sortOption: SortOptions['sortBy']) => {
setSortOptions((prev) => ({
sortBy: sortOption,
sortDirection: prev.sortBy !== sortOption ? 'desc' : prev.sortDirection === 'asc' ? 'desc' : 'asc',
}))
}

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

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

<FilterButton forwardedAs="span" variant="body3" onClick={() => handleSort('position')}>
Position
</Text>
<SortChevrons
sorting={{ isActive: sortOptions.sortBy === 'position', direction: sortOptions.sortDirection }}
/>
</FilterButton>

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

<FilterButton forwardedAs="span" variant="body3" onClick={() => handleSort('market-value')}>
Market Value
<SortChevrons
sorting={{ isActive: sortOptions.sortBy === 'market-value', direction: sortOptions.sortDirection }}
/>
</FilterButton>
</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} canInvestRedeem={canInvestRedeem} {...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>
)
}
100 changes: 100 additions & 0 deletions centrifuge-app/src/components/Portfolio/TokenListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { AccountTokenBalance } from '@centrifuge/centrifuge-js'
import { formatBalance, useCentrifuge } from '@centrifuge/centrifuge-react'
import {
AnchorButton,
Box,
Button,
Grid,
IconExternalLink,
IconMinus,
IconPlus,
Shelf,
Text,
Thumbnail,
} from '@centrifuge/fabric'
import styled, { useTheme } from 'styled-components'
import { usePool, usePoolMetadata } from '../../utils/usePools'
import { Eththumbnail } from '../EthThumbnail'
import { Root } from '../ListItemCardStyles'
import { COLUMN_GAPS } from './InvestedTokens'

export type TokenCardProps = AccountTokenBalance & {
canInvestRedeem?: boolean
}

const TokenName = styled(Text)`
text-wrap: nowrap;
`

export function TokenListItem({ balance, currency, poolId, trancheId, canInvestRedeem }: TokenCardProps) {
const { sizes } = useTheme()
const pool = usePool(poolId, false)
const { data: metadata } = usePoolMetadata(pool)
const cent = useCentrifuge()

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

// @ts-expect-error known typescript issue: https://github.com/microsoft/TypeScript/issues/44373
const trancheInfo = pool?.tranches.find(({ id }) => id === trancheId)
const icon = metadata?.pool?.icon?.uri ? cent.metadata.parseMetadataUrl(metadata.pool.icon.uri) : null

return (
<Root as="article" minWidth={canInvestRedeem ? '960px' : '750px'}>
<Grid gridTemplateColumns={`${COLUMN_GAPS} 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>

<TokenName textOverflow="ellipsis" variant="body2">
{currency.name}
</TokenName>
</Grid>

<Text textOverflow="ellipsis" variant="body2">
{formatBalance(balance, currency.symbol)}
</Text>

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

<Text textOverflow="ellipsis" variant="body2">
{trancheInfo?.tokenPrice
? formatBalance(balance.toDecimal().mul(trancheInfo.tokenPrice.toDecimal()), trancheInfo.currency.symbol, 4)
: '-'}
</Text>

{canInvestRedeem && (
<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>
)
}
Loading

0 comments on commit 4e248a0

Please sign in to comment.