From f19d396d1078ae1965bc309181fd0f565679b962 Mon Sep 17 00:00:00 2001 From: Sophia Date: Tue, 24 Oct 2023 11:37:57 -0400 Subject: [PATCH 1/3] Fix connection guard for evm wallets on cent pool (#1661) --- centrifuge-app/src/pages/Pool/Overview/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrifuge-app/src/pages/Pool/Overview/index.tsx b/centrifuge-app/src/pages/Pool/Overview/index.tsx index 6ff5f01bf8..d7f8f5f8e3 100644 --- a/centrifuge-app/src/pages/Pool/Overview/index.tsx +++ b/centrifuge-app/src/pages/Pool/Overview/index.tsx @@ -66,7 +66,7 @@ export function PoolDetailSideBar({ const isTinlakePool = poolId.startsWith('0x') const tinlakeNetworks = [ethConfig.network === 'goerli' ? 5 : 1] as Network[] // TODO: fetch supported networks from centrifuge chain - const centrifugeNetworks = ['centrifuge', 1, 5] as Network[] + const centrifugeNetworks = ['centrifuge'] as Network[] return ( Date: Wed, 25 Oct 2023 13:59:06 -0400 Subject: [PATCH 2/3] swap to goldsky subgraph (#1662) --- centrifuge-app/.env-config/.env.altair | 2 +- centrifuge-app/.env-config/.env.catalyst | 2 +- centrifuge-app/.env-config/.env.demo | 2 +- centrifuge-app/.env-config/.env.development | 2 +- centrifuge-app/.env-config/.env.example | 2 +- centrifuge-app/.env-config/.env.production | 2 +- .../src/utils/tinlake/useTinlakeRewards.ts | 48 +++++++++++-------- 7 files changed, 33 insertions(+), 27 deletions(-) diff --git a/centrifuge-app/.env-config/.env.altair b/centrifuge-app/.env-config/.env.altair index aa97b424d6..19d42c3d61 100644 --- a/centrifuge-app/.env-config/.env.altair +++ b/centrifuge-app/.env-config/.env.altair @@ -17,4 +17,4 @@ REACT_APP_WHITELISTED_ACCOUNTS= REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALJqPUHFzDR2VkoQYWefPQyzjGzKznNny2smXGQpSf3aMw19 REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a -REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake +REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/1.0.2/gn diff --git a/centrifuge-app/.env-config/.env.catalyst b/centrifuge-app/.env-config/.env.catalyst index ef208e5934..e31934e161 100644 --- a/centrifuge-app/.env-config/.env.catalyst +++ b/centrifuge-app/.env-config/.env.catalyst @@ -17,4 +17,4 @@ REACT_APP_WHITELISTED_ACCOUNTS= REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=4bo2vNkwZtr2PuqppWwqya6dPC8MzxqZ4kgnAoTZyKo9Kxq8 REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a -REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake +REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/1.0.2/gn diff --git a/centrifuge-app/.env-config/.env.demo b/centrifuge-app/.env-config/.env.demo index 963c3aee0a..6aba06de00 100644 --- a/centrifuge-app/.env-config/.env.demo +++ b/centrifuge-app/.env-config/.env.demo @@ -17,4 +17,4 @@ REACT_APP_NETWORK=centrifuge REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALwmJutBq95s41U9fWnoApCUgvPqPGTh1GSmFnQh5f9fWo93 REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a -REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake +REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/1.0.2/gn diff --git a/centrifuge-app/.env-config/.env.development b/centrifuge-app/.env-config/.env.development index 504841d09b..72519c9784 100644 --- a/centrifuge-app/.env-config/.env.development +++ b/centrifuge-app/.env-config/.env.development @@ -14,7 +14,7 @@ REACT_APP_SUBSCAN_URL= REACT_APP_TINLAKE_NETWORK=goerli REACT_APP_INFURA_KEY=bf808e7d3d924fbeb74672d9341d0550 REACT_APP_WHITELISTED_ACCOUNTS= -REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake +REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/1.0.2/gn REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kAJ27w29x7gHM75xajP2yXVLjVBaKmmUTxHwgRuCoAcWaoEiz diff --git a/centrifuge-app/.env-config/.env.example b/centrifuge-app/.env-config/.env.example index 6429fbfcc4..445e23deea 100644 --- a/centrifuge-app/.env-config/.env.example +++ b/centrifuge-app/.env-config/.env.example @@ -17,4 +17,4 @@ REACT_APP_WHITELISTED_ACCOUNTS='' REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALJqPUHFzDR2VkoQYWefPQyzjGzKznNny2smXGQpSf3aMw19 REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a -REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake +REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/1.0.2/gn diff --git a/centrifuge-app/.env-config/.env.production b/centrifuge-app/.env-config/.env.production index a9c9ccc116..6c7655edd6 100644 --- a/centrifuge-app/.env-config/.env.production +++ b/centrifuge-app/.env-config/.env.production @@ -17,4 +17,4 @@ REACT_APP_WHITELISTED_ACCOUNTS='' REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-mainnet-production/latest.json REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALJqPUHFzDR2VkoQYWefPQyzjGzKznNny2smXGQpSf3aMw19 REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a -REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake +REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/1.0.2/gn diff --git a/centrifuge-app/src/utils/tinlake/useTinlakeRewards.ts b/centrifuge-app/src/utils/tinlake/useTinlakeRewards.ts index 40e152fb81..9ec2db5193 100644 --- a/centrifuge-app/src/utils/tinlake/useTinlakeRewards.ts +++ b/centrifuge-app/src/utils/tinlake/useTinlakeRewards.ts @@ -9,13 +9,15 @@ import { RewardBalance, RewardClaim, RewardDayTotals, RewardsData, UserRewardsDa async function getTinlakeUserRewards(ethAddr: string) { let rewardBalances: RewardBalance[] = [] - const response = await fetch('https://graph.centrifuge.io/tinlake', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: ` + const response = await fetch( + 'https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/1.0.2/gn', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: ` query GetRewardBalances($address: String!) { rewardBalances(where: {id: $address}) { links { @@ -28,11 +30,12 @@ async function getTinlakeUserRewards(ethAddr: string) { } } `, - variables: { - address: ethAddr.toLowerCase(), - }, - }), - }) + variables: { + address: ethAddr.toLowerCase(), + }, + }), + } + ) if (response?.ok) { const { data } = await response.json() @@ -73,13 +76,15 @@ export function useTinlakeUserRewards(ethAddr?: string | null) { async function getTinlakeRewards(): Promise { let rewardDayTotals: RewardDayTotals[] = [] - const response = await fetch('https://graph.centrifuge.io/tinlake', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: ` + const response = await fetch( + 'https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/1.0.2/gn', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: ` query GetRewardDayTotals { rewardDayTotals(first: 1, skip: 1, orderBy: id, orderDirection: desc) { dropRewardRate @@ -90,8 +95,9 @@ async function getTinlakeRewards(): Promise { } } `, - }), - }) + }), + } + ) if (response?.ok) { const { data } = await response.json() From 4e248a0a46fb2429e1ace41524bf37a105bf3e0f Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 25 Oct 2023 14:43:56 -0500 Subject: [PATCH 3/3] feat: composition table (#1623) --- .../src/components/FilterButton.tsx | 11 ++ .../styles.tsx => ListItemCardStyles.tsx} | 0 .../src/components/PoolCard/index.tsx | 2 +- .../src/components/PoolFilter/FilterMenu.tsx | 3 +- .../src/components/PoolFilter/SortButton.tsx | 25 +-- .../src/components/PoolFilter/styles.ts | 26 --- .../components/Portfolio/InvestedTokens.tsx | 148 +++++++++--------- .../components/Portfolio/TokenListItem.tsx | 100 ++++++++++++ .../src/components/Portfolio/sortTokens.ts | 50 ++++++ centrifuge-app/src/components/QuickAction.tsx | 7 + .../src/components/SortChevrons.tsx | 22 +++ centrifuge-app/src/components/styles.ts | 14 ++ 12 files changed, 289 insertions(+), 119 deletions(-) create mode 100644 centrifuge-app/src/components/FilterButton.tsx rename centrifuge-app/src/components/{PoolCard/styles.tsx => ListItemCardStyles.tsx} (100%) delete mode 100644 centrifuge-app/src/components/PoolFilter/styles.ts create mode 100644 centrifuge-app/src/components/Portfolio/TokenListItem.tsx create mode 100644 centrifuge-app/src/components/Portfolio/sortTokens.ts create mode 100644 centrifuge-app/src/components/QuickAction.tsx create mode 100644 centrifuge-app/src/components/SortChevrons.tsx create mode 100644 centrifuge-app/src/components/styles.ts diff --git a/centrifuge-app/src/components/FilterButton.tsx b/centrifuge-app/src/components/FilterButton.tsx new file mode 100644 index 0000000000..adbc29baf9 --- /dev/null +++ b/centrifuge-app/src/components/FilterButton.tsx @@ -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} +` 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/FilterMenu.tsx b/centrifuge-app/src/components/PoolFilter/FilterMenu.tsx index feadc2fc9a..c725f01af1 100644 --- a/centrifuge-app/src/components/PoolFilter/FilterMenu.tsx +++ b/centrifuge-app/src/components/PoolFilter/FilterMenu.tsx @@ -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' diff --git a/centrifuge-app/src/components/PoolFilter/SortButton.tsx b/centrifuge-app/src/components/PoolFilter/SortButton.tsx index ba31ffe790..44af2f4c7c 100644 --- a/centrifuge-app/src/components/PoolFilter/SortButton.tsx +++ b/centrifuge-app/src/components/PoolFilter/SortButton.tsx @@ -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 = { @@ -64,7 +65,7 @@ export function SortButton({ label, searchKey, tooltip, justifySelf = 'end' }: S {label} - + ) @@ -87,23 +88,7 @@ export function SortButton({ label, searchKey, tooltip, justifySelf = 'end' }: S > {label} - + ) } - -function Inner({ sorting }: { sorting: Sorting }) { - return ( - - - - - ) -} diff --git a/centrifuge-app/src/components/PoolFilter/styles.ts b/centrifuge-app/src/components/PoolFilter/styles.ts deleted file mode 100644 index f94d6c7d85..0000000000 --- a/centrifuge-app/src/components/PoolFilter/styles.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Text } from '@centrifuge/fabric' -import styled, { css } from 'styled-components' - -const sharedStyles = css` - appearance: none; - border: none; - cursor: pointer; - background-color: transparent; - border-radius: ${({ theme }) => theme.radii.tooltip}px; - - &:focus-visible { - outline: ${({ theme }) => `2px solid ${theme.colors.textSelected}`}; - outline-offset: 4px; - } -` - -export const FilterButton = styled(Text)` - display: flex; - align-items: center; - gap: 0.3em; - - ${sharedStyles} -` -export const QuickAction = styled(Text)` - ${sharedStyles} -` diff --git a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx index e953a29ce3..3dc3f146a0 100644 --- a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx +++ b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx @@ -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({ 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 ? ( - <> - - - 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, + }, + sortOptions + ) + : [] + + const handleSort = (sortOption: SortOptions['sortBy']) => { + setSortOptions((prev) => ({ + sortBy: sortOption, + sortDirection: prev.sortBy !== sortOption ? 'desc' : prev.sortDirection === 'asc' ? 'desc' : 'asc', + })) + } + + return sortedTokens.length ? ( + + + Portfolio + + + + Token - + + handleSort('position')}> Position - + + + Token price - - Market value - + + handleSort('market-value')}> + 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..2257e7beff --- /dev/null +++ b/centrifuge-app/src/components/Portfolio/TokenListItem.tsx @@ -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 ( + + + + + {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) + : '-'} + + + {canInvestRedeem && ( + + {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..ed12057e09 --- /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 { SortOptions } from './InvestedTokens' +import { TokenCardProps } from './TokenListItem' + +export const sortTokens = ( + tokens: TokenCardProps[], + pools: { + centPools: Pool[] + tinlakePools: TinlakePool[] + }, + sortOptions: SortOptions +) => { + const { sortBy, sortDirection } = sortOptions + if (sortBy === 'market-value') { + tokens.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)) { + tokens.sort(({ balance: balanceA }, { balance: balanceB }) => + sortDirection === 'asc' + ? balanceA.toDecimal().toNumber() - balanceB.toDecimal().toNumber() + : balanceB.toDecimal().toNumber() - balanceA.toDecimal().toNumber() + ) + } + + return tokens +} + +const sortMarketValue = ( + token: TokenCardProps, + pools: { + centPools: Pool[] + tinlakePools: TinlakePool[] + } +) => { + const pool = token.poolId.startsWith('0x') + ? pools.tinlakePools?.find((p) => p.id.toLowerCase() === token.poolId.toLowerCase()) + : pools.centPools?.find((p) => p.id === token.poolId) + + // @ts-expect-error known typescript issue: https://github.com/microsoft/TypeScript/issues/44373 + const poolTranche = pool?.tranches.find(({ id }) => id === token.trancheId) + + return poolTranche?.tokenPrice ? token.balance.toDecimal().mul(poolTranche.tokenPrice.toDecimal()).toNumber() : 0 +} diff --git a/centrifuge-app/src/components/QuickAction.tsx b/centrifuge-app/src/components/QuickAction.tsx new file mode 100644 index 0000000000..eb196114c9 --- /dev/null +++ b/centrifuge-app/src/components/QuickAction.tsx @@ -0,0 +1,7 @@ +import { Text } from '@centrifuge/fabric' +import styled from 'styled-components' +import { buttonActionStyles } from './styles' + +export const QuickAction = styled(Text)` + ${buttonActionStyles} +` diff --git a/centrifuge-app/src/components/SortChevrons.tsx b/centrifuge-app/src/components/SortChevrons.tsx new file mode 100644 index 0000000000..fa9674b729 --- /dev/null +++ b/centrifuge-app/src/components/SortChevrons.tsx @@ -0,0 +1,22 @@ +import { IconChevronDown, IconChevronUp, Stack } from '@centrifuge/fabric' + +type Sorting = { + isActive: boolean + direction: string | null +} + +export function SortChevrons({ sorting }: { sorting: Sorting }) { + return ( + + + + + ) +} diff --git a/centrifuge-app/src/components/styles.ts b/centrifuge-app/src/components/styles.ts new file mode 100644 index 0000000000..2203668ef8 --- /dev/null +++ b/centrifuge-app/src/components/styles.ts @@ -0,0 +1,14 @@ +import { css } from 'styled-components' + +export const buttonActionStyles = css` + appearance: none; + border: none; + cursor: pointer; + background-color: transparent; + border-radius: ${({ theme }) => theme.radii.tooltip}px; + + &:focus-visible { + outline: ${({ theme }) => `2px solid ${theme.colors.textSelected}`}; + outline-offset: 4px; + } +`