Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CentrifugeApp: Portfolio transaction table #1605

Merged
merged 40 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f3cde6c
Update transaction table styles and data fetching
sophialittlejohn Sep 27, 2023
17eab72
Filter transaction types for short table
sophialittlejohn Sep 27, 2023
9e6e567
Update styles in tx history page
sophialittlejohn Sep 27, 2023
d9e3cf8
Addsort buttons to table and view all button
sophialittlejohn Sep 28, 2023
532062d
Add count and txTypes to cent-js
sophialittlejohn Sep 28, 2023
3dff2a0
Create new history page
sophialittlejohn Sep 29, 2023
f8ae576
Remove old file
sophialittlejohn Sep 29, 2023
2689edc
Merge branch 'main' into cent-app/portfolio-transaction-table
hieronx Oct 2, 2023
1b78717
Merge branch 'main' of github.com:centrifuge/apps into cent-app/portf…
sophialittlejohn Oct 10, 2023
e34d67c
Add pagination
sophialittlejohn Oct 10, 2023
26f28f1
Resolve unique key error
sophialittlejohn Oct 13, 2023
67900c2
Fetch currencies once per poolId
sophialittlejohn Oct 13, 2023
8d10135
Merge branch 'main' into cent-app/portfolio-transaction-table
sophialittlejohn Oct 13, 2023
87dd3e8
Add CSV export
sophialittlejohn Oct 13, 2023
b56b861
Hide download button in preview
sophialittlejohn Oct 13, 2023
058368a
Merge branch 'main' into cent-app/portfolio-transaction-table
sophialittlejohn Oct 16, 2023
cad7795
Fix logs
sophialittlejohn Oct 16, 2023
afebaf3
Merge branch 'cent-app/portfolio-transaction-table' of github.com:cen…
sophialittlejohn Oct 16, 2023
0adadef
Merge branch 'main' of github.com:centrifuge/apps into cent-app/portf…
sophialittlejohn Oct 20, 2023
35db8ff
Add more query keys
sophialittlejohn Oct 20, 2023
f445227
Add loading state and internal link
sophialittlejohn Oct 23, 2023
176bbd0
Improve load time
sophialittlejohn Oct 23, 2023
1ebce79
Add loop ids
sophialittlejohn Oct 23, 2023
e505fff
Merge branch 'main' of github.com:centrifuge/apps into cent-app/portf…
sophialittlejohn Oct 25, 2023
2bf06e4
Fix table rendering
sophialittlejohn Oct 25, 2023
0f16c50
Merge branch 'main' of github.com:centrifuge/apps into cent-app/portf…
sophialittlejohn Oct 27, 2023
96f5f36
Restructure files
sophialittlejohn Oct 27, 2023
fe1c47e
Add pagination and csv export to DataTable
sophialittlejohn Oct 27, 2023
fc56eb1
Use DataTable for transactions
sophialittlejohn Oct 27, 2023
84efc6b
Add title to tx table
sophialittlejohn Oct 27, 2023
d5e8ad1
Convert invested token table to new style
sophialittlejohn Oct 27, 2023
5791310
Adjust percision and table width
sophialittlejohn Oct 27, 2023
7ee615a
Fix border
sophialittlejohn Oct 27, 2023
91bd04b
Fix linting
sophialittlejohn Oct 27, 2023
c34aeaa
Fix pool list key
sophialittlejohn Oct 27, 2023
59fa110
Clean up
sophialittlejohn Oct 27, 2023
f78c808
Lint fix
sophialittlejohn Oct 27, 2023
076a915
refactor
onnovisser Oct 30, 2023
38f9ef2
cleanup
onnovisser Oct 30, 2023
f185db4
Merge commit 'b973b1de7c359182ad994c86fbac88a9eccf3a7a' into cent-app…
onnovisser Oct 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion centrifuge-app/.env-config/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ REACT_APP_PINNING_API_URL=https://europe-central2-peak-vista-185616.cloudfunctio
REACT_APP_POOL_CREATION_TYPE=immediate
REACT_APP_RELAY_WSS_URL=wss://fullnode-relay.development.cntrfg.com
REACT_APP_SUBQUERY_URL=https://api.subquery.network/sq/centrifuge/pools-development
REACT_APP_SUBSCAN_URL=
REACT_APP_SUBSCAN_URL=https://centrifuge.subscan.io
REACT_APP_TINLAKE_NETWORK=goerli
REACT_APP_INFURA_KEY=bf808e7d3d924fbeb74672d9341d0550
REACT_APP_WHITELISTED_ACCOUNTS=
Expand Down
8 changes: 8 additions & 0 deletions centrifuge-app/src/components/Menu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Box,
IconClock,
IconGlobe,
IconInvestments,
IconNft,
Expand Down Expand Up @@ -61,6 +62,13 @@ export function Menu() {
</PageLink>
)}

{showPortfolio && address && (
<PageLink to="/history" stacked={!isLarge}>
<IconClock />
History
</PageLink>
)}

{(pools.length > 0 || config.poolCreationType === 'immediate') && (
<IssuerMenu defaultOpen={isLarge} stacked={!isLarge}>
{isLarge ? (
Expand Down
4 changes: 2 additions & 2 deletions centrifuge-app/src/components/PoolList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ export function PoolList() {
? Array(6)
.fill(true)
.map((_, index) => (
<Box as="li" key={index}>
<Box as="li" key={`pool-list-loading-${index}`}>
<PoolCard isLoading={true} />
</Box>
))
: filteredPools.map((pool) => (
<PoolCardBox as="li" key={pool.poolId} status={pool.status}>
<PoolCardBox as="li" key={`pool-list-${pool.name}-${pool.poolId}`} status={pool.status}>
<PoolCard {...pool} />
</PoolCardBox>
))}
Expand Down
4 changes: 2 additions & 2 deletions centrifuge-app/src/components/Portfolio/AssetAllocation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function AssetAllocation({ address }: { address: string }) {
<AssetClassChart data={shares} currency="USD" total={total.toNumber()} />
<Shelf as="ul" alignSelf="stretch" alignItems="stretch" flex={1} gap={6}>
{shares.map((cell, i) => (
<>
<Box key={`asset-allocation-${cell.name}-${i}`}>
{i > 0 && <Box width="1px" backgroundColor="borderSecondary" />}
<LabelValueStack
label={
Expand All @@ -84,7 +84,7 @@ export function AssetAllocation({ address }: { address: string }) {
}
key={i}
/>
</>
</Box>
))}
</Shelf>
</Shelf>
Expand Down
212 changes: 143 additions & 69 deletions centrifuge-app/src/components/Portfolio/InvestedTokens.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,117 @@
import { useAddress, useBalances } from '@centrifuge/centrifuge-react'
import { Box, Grid, Stack, Text } from '@centrifuge/fabric'
import { useMemo, useState } from 'react'
import { Token, TokenBalance } from '@centrifuge/centrifuge-js'
import { formatBalance, useAddress, useBalances, useCentrifuge } from '@centrifuge/centrifuge-react'
import {
AnchorButton,
Box,
Button,
Grid,
IconExternalLink,
IconMinus,
IconPlus,
Shelf,
Stack,
Text,
Thumbnail,
} from '@centrifuge/fabric'
import { useMemo } from 'react'
import { useTheme } from 'styled-components'
import { Dec } from '../../utils/Decimal'
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'
import { usePool, usePoolMetadata, usePools } from '../../utils/usePools'
import { Column, DataTable, SortableTableHeader } from '../DataTable'
import { Eththumbnail } from '../EthThumbnail'

export const COLUMN_GAPS = '200px 140px 140px 140px'

export type SortOptions = {
sortBy: 'position' | 'market-value'
sortDirection: 'asc' | 'desc'
type Row = {
currency: Token['currency']
poolId: string
trancheId: string
marketValue: TokenBalance
position: TokenBalance
tokenPrice: TokenBalance
canInvestRedeem: boolean
}

const columns: Column[] = [
{
align: 'left',
header: 'Token',
cell: (token: Row) => {
return <TokenWithIcon {...token} />
},
width: '2fr',
},
{
header: 'Token price',
cell: ({ tokenPrice }: Row) => {
return (
<Text textOverflow="ellipsis" variant="body3">
{formatBalance(tokenPrice.toDecimal() || 1, 'USDT', 4)}
</Text>
)
},
},
{
header: <SortableTableHeader label="Position" />,
cell: ({ currency, position }: Row) => {
return (
<Text textOverflow="ellipsis" variant="body3">
{formatBalance(position, currency.symbol)}
</Text>
)
},
sortKey: 'position',
},
{
header: <SortableTableHeader label="Market value" />,
cell: ({ marketValue }: Row) => {
return (
<Text textOverflow="ellipsis" variant="body3">
{formatBalance(marketValue, 'USDT', 4)}
</Text>
)
},
sortKey: 'marketValue',
},
{
align: 'left',
header: '', // invest redeem buttons
cell: ({ canInvestRedeem, poolId }: Row) => {
const isTinlakePool = poolId.startsWith('0x')
return (
canInvestRedeem && (
<Shelf gap={1} justifySelf="end">
{isTinlakePool ? (
<AnchorButton
variant="tertiary"
small
icon={IconExternalLink}
href="https://legacy.tinlake.centrifuge.io/portfolio"
target="_blank"
>
View on Tinlake
</AnchorButton>
) : (
<>
<Button small variant="tertiary" icon={IconMinus}>
Redeem
</Button>
<Button small variant="tertiary" icon={IconPlus}>
Invest
</Button>
</>
)}
</Shelf>
)
)
},
},
]

// 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' })

const address = useAddress()
const centBalances = useBalances(address)
const { data: tinlakeBalances } = useTinlakeBalances()

const { data: tinlakePools } = useTinlakePools()
const pools = usePools()

const balances = useMemo(() => {
Expand All @@ -34,62 +121,49 @@ export const InvestedTokens = ({ canInvestRedeem = false }) => {
]
}, [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',
}))
}
const tableData = balances.map((balance) => {
const pool = pools?.find((pool) => pool.id === balance.poolId)
const tranche = pool?.tranches.find((tranche) => tranche.id === balance.trancheId)
return {
currency: balance.currency,
poolId: balance.poolId,
trancheId: balance.trancheId,
position: balance.balance,
tokenPrice: tranche?.tokenPrice || Dec(1),
marketValue: tranche?.tokenPrice ? balance.balance.toDecimal().mul(tranche?.tokenPrice.toDecimal()) : Dec(0),
canInvestRedeem,
}
})

return sortedTokens.length ? (
return tableData.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>

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

<Text as="span" variant="body3">
Token price
</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} py={1}>
{balances.map((balance, index) => (
<TokenListItem key={index} canInvestRedeem={canInvestRedeem} {...balance} />
))}
</Stack>
</Box>
<DataTable columns={columns} data={tableData} />
</Stack>
) : null
}

const TokenWithIcon = ({ poolId, currency }: Row) => {
const pool = usePool(poolId, false)
const { data: metadata } = usePoolMetadata(pool)
const cent = useCentrifuge()
const { sizes } = useTheme()
const icon = metadata?.pool?.icon?.uri ? cent.metadata.parseMetadataUrl(metadata.pool.icon.uri) : null
return (
<Grid as="header" gridTemplateColumns={`${sizes.iconMedium}px 1fr`} alignItems="center" gap={2}>
<Eththumbnail show={!!poolId.startsWith('0x')}>
{icon ? (
<Box as="img" src={icon} alt="" height="iconMedium" width="iconMedium" />
) : (
<Thumbnail type="pool" label="LP" size="small" />
)}
</Eththumbnail>

<Text textOverflow="ellipsis" variant="body3">
{currency.name}
</Text>
</Grid>
)
}
Loading
Loading