diff --git a/centrifuge-app/src/components/PoolCard/styles.tsx b/centrifuge-app/src/components/ListItemCardStyles.tsx similarity index 100% rename from centrifuge-app/src/components/PoolCard/styles.tsx rename to centrifuge-app/src/components/ListItemCardStyles.tsx diff --git a/centrifuge-app/src/components/PoolCard/index.tsx b/centrifuge-app/src/components/PoolCard/index.tsx index 6da4194d7d..0663d99afc 100644 --- a/centrifuge-app/src/components/PoolCard/index.tsx +++ b/centrifuge-app/src/components/PoolCard/index.tsx @@ -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' diff --git a/centrifuge-app/src/components/PoolFilter/config.ts b/centrifuge-app/src/components/PoolFilter/config.ts index cf607b7f72..59389d913d 100644 --- a/centrifuge-app/src/components/PoolFilter/config.ts +++ b/centrifuge-app/src/components/PoolFilter/config.ts @@ -1,5 +1,5 @@ +import { SortButtonProps } from '../SortButton' import { FilterMenuProps } from './FilterMenu' -import { SortButtonProps } from './SortButton' export const SEARCH_KEYS = { SORT_BY: 'sort-by', diff --git a/centrifuge-app/src/components/PoolFilter/index.tsx b/centrifuge-app/src/components/PoolFilter/index.tsx index a3fc4d1ede..07da68fe63 100644 --- a/centrifuge-app/src/components/PoolFilter/index.tsx +++ b/centrifuge-app/src/components/PoolFilter/index.tsx @@ -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[] diff --git a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx index e953a29ce3..eb4edc32df 100644 --- a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx +++ b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx @@ -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 ? ( - <> - - - Portfolio Composition - - - - + 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 ? ( + + + Portfolio composition + + + + Token - - Position - + + + Token price - - Market value - + + - - {balances.tranches.map((tranche, index) => ( - - - + + {balances.map((balance, index) => ( + ))} - - + + ) : 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 ( - - - {currency.name} - - - - {formatBalance(balance, tranche?.currency.symbol)} - - - - {tranche?.tokenPrice ? formatBalance(tranche.tokenPrice.toDecimal(), tranche.currency.symbol, 4) : '-'} - - - - {tranche?.tokenPrice - ? formatBalance(balance.toDecimal().mul(tranche.tokenPrice.toDecimal()), tranche.currency.symbol, 4) - : '-'} - - - ) -} diff --git a/centrifuge-app/src/components/Portfolio/TokenListItem.tsx b/centrifuge-app/src/components/Portfolio/TokenListItem.tsx new file mode 100644 index 0000000000..59cad67eff --- /dev/null +++ b/centrifuge-app/src/components/Portfolio/TokenListItem.tsx @@ -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 ( + + + + + {icon ? ( + + ) : ( + + )} + + + + {currency.name} + + + + + {formatBalance(balance, currency.symbol)} + + + + + {trancheInfo?.tokenPrice + ? formatBalance(trancheInfo.tokenPrice.toDecimal(), trancheInfo.currency.symbol, 4) + : '-'} + + + + + + {trancheInfo?.tokenPrice + ? formatBalance( + balance.toDecimal().mul(trancheInfo.tokenPrice.toDecimal()), + trancheInfo.currency.symbol, + 4 + ) + : '-'} + + + + + {isTinlakePool ? ( + + View on Tinlake + + ) : ( + <> + + + + )} + + + + ) +} diff --git a/centrifuge-app/src/components/Portfolio/sortTokens.ts b/centrifuge-app/src/components/Portfolio/sortTokens.ts new file mode 100644 index 0000000000..cea663cc38 --- /dev/null +++ b/centrifuge-app/src/components/Portfolio/sortTokens.ts @@ -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 +} diff --git a/centrifuge-app/src/components/PoolFilter/SortButton.tsx b/centrifuge-app/src/components/SortButton.tsx similarity index 95% rename from centrifuge-app/src/components/PoolFilter/SortButton.tsx rename to centrifuge-app/src/components/SortButton.tsx index ba31ffe790..84ea210acc 100644 --- a/centrifuge-app/src/components/PoolFilter/SortButton.tsx +++ b/centrifuge-app/src/components/SortButton.tsx @@ -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' }