From f3cde6caed3fa967b2ebd7ab00c61653a11414c5 Mon Sep 17 00:00:00 2001 From: sophian Date: Wed, 27 Sep 2023 19:23:41 -0400 Subject: [PATCH 01/31] Update transaction table styles and data fetching --- .../Portfolio/TransactionTypeChip.tsx | 4 +- .../src/components/Portfolio/Transactions.tsx | 111 ++++----------- centrifuge-app/src/utils/usePools.ts | 6 +- centrifuge-js/src/modules/pools.ts | 134 +++--------------- centrifuge-js/src/types/subquery.ts | 12 ++ 5 files changed, 67 insertions(+), 200 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx index e54a3a775d..cb0f91d18e 100644 --- a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx +++ b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx @@ -35,11 +35,11 @@ const states: { }, INVEST_EXECUTION: { label: 'Invest execution', - status: 'default', + status: 'ok', }, REDEEM_EXECUTION: { label: 'Redeem execution', - status: 'default', + status: 'info', }, TRANSFER_IN: { label: 'Transfer in', diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 3f83842cf3..b0d7b52852 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -1,90 +1,39 @@ -import { - BorrowerTransactionType, - CurrencyBalance, - InvestorTransactionType, - Pool, - SubqueryInvestorTransaction, -} from '@centrifuge/centrifuge-js' -import { formatBalance, useCentrifugeUtils } from '@centrifuge/centrifuge-react' +import { BorrowerTransactionType, CurrencyBalance, InvestorTransactionType, Pool } from '@centrifuge/centrifuge-js' +import { useCentrifugeUtils } from '@centrifuge/centrifuge-react' import { Box, Grid, IconExternalLink, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' import { Link } from 'react-router-dom' import { formatDate } from '../../utils/date' +import { formatBalanceAbbreviated } from '../../utils/formatting' import { useAddress } from '../../utils/useAddress' -import { useAllTransactions, usePool, usePoolMetadata } from '../../utils/usePools' +import { usePool, usePoolMetadata, useTransactionsByAddress } from '../../utils/usePools' import { TransactionTypeChip } from './TransactionTypeChip' -export const TRANSACTION_CARD_COLUMNS = `150px 100px 250px 150px 1fr` +export const TRANSACTION_CARD_COLUMNS = `150px 125px 200px 150px 1fr` export const TRANSACTION_CARD_GAP = 4 type AddressTransactionsProps = { count?: number } -type SubqueryBorrowerTransaction = any -type SubqueryOutstandingOrder = any - -const formatters = { - investorTransactions: ({ - timestamp, - type, - poolId, - hash, - tokenAmount, - tokenPrice, - currencyAmount, - trancheId, - }: Omit) => { - return { - date: new Date(timestamp).getTime(), - action: type, - amount: tokenAmount, - poolId, - hash, - trancheId, - } as TransactionCardProps - }, - borrowerTransactions: ({ timestamp, type, amount, poolId, hash }: SubqueryBorrowerTransaction) => - ({ - date: new Date(timestamp).getTime(), - action: type, - amount, - poolId, - hash, - } as TransactionCardProps), - outstandingOrders: ({ timestamp, investAmount, redeemAmount, poolId, hash, trancheId }: SubqueryOutstandingOrder) => - ({ - date: new Date(timestamp).getTime(), - action: 'PENDING_ORDER', - amount: investAmount.add(redeemAmount), - poolId, - hash, - trancheId, - } as TransactionCardProps), -} - export function Transactions({ count }: AddressTransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() - const formattedAddress = formatAddress(address || '') - const allTransactions = useAllTransactions(formattedAddress) - const formattedTransactions: TransactionCardProps[] = [] - - if (allTransactions) { - const { borrowerTransactions, investorTransactions, outstandingOrders } = allTransactions - - investorTransactions.forEach((transaction) => - formattedTransactions.push(formatters.investorTransactions(transaction)) - ) - borrowerTransactions.forEach((transaction) => - formattedTransactions.push(formatters.borrowerTransactions(transaction)) - ) - outstandingOrders.forEach((transaction) => formattedTransactions.push(formatters.outstandingOrders(transaction))) - } - - const transactions = formattedTransactions.slice(0, count ?? formattedTransactions.length) - - return !!transactions.length ? ( + const transactions = useTransactionsByAddress(formatAddress(address || '')) + + const investorTransactions = + transactions?.investorTransactions.map((tx) => { + return { + date: new Date(tx.timestamp).getTime(), + action: tx.type, + amount: tx.tokenAmount, + poolId: tx.poolId, + hash: tx.hash, + trancheId: tx.trancheId, + } + }) || [] + + return !!investorTransactions.slice(0, count ?? investorTransactions.length) ? ( Transaction history @@ -93,21 +42,17 @@ export function Transactions({ count }: AddressTransactionsProps) { Action - - Transaction date - + Transaction date Token - - Amount - + Amount - {transactions.map((transaction, index) => ( + {investorTransactions.slice(0, count ?? investorTransactions.length).map((transaction, index) => ( @@ -131,7 +76,9 @@ export type TransactionCardProps = { export function TransactionListItem({ date, action, amount, poolId, hash, trancheId }: TransactionCardProps) { const pool = usePool(poolId) as Pool const { data } = usePoolMetadata(pool) + console.log('🚀 ~ data:', data) const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined + console.log('🚀 ~ token:', token) const subScanUrl = import.meta.env.REACT_APP_SUBSCAN_URL if (!pool || !data) { @@ -162,10 +109,10 @@ export function TransactionListItem({ date, action, amount, poolId, hash, tranch - {!!token ? token.currency?.name : data.pool?.name} + {!!token ? token.currency?.name.split(data.pool?.name || '') : data.pool?.name} {!!token && ( - + {data?.pool?.name} )} @@ -173,14 +120,14 @@ export function TransactionListItem({ date, action, amount, poolId, hash, tranch - {formatBalance(amount, pool.currency.symbol)} + {formatBalanceAbbreviated(amount, pool.currency.symbol)} {!!subScanUrl && !!hash && ( cent.pools.getAllTransactions([address!]), + ['txByAddress', address], + (cent) => cent.pools.getTransactionsByAddress([address!]), { enabled: !!address, } diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index e830df38f8..0843e6db35 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -11,6 +11,7 @@ import { InvestorTransactionType, SubqueryBorrowerTransaction, SubqueryInvestorTransaction, + SubqueryOutstandingOrder, SubqueryPoolSnapshot, SubqueryTrancheSnapshot, } from '../types/subquery' @@ -2015,44 +2016,12 @@ export function getPoolsModule(inst: Centrifuge) { ) } - function getAllTransactions(args: [address: string]) { + function getTransactionsByAddress(args: [address: string]) { const [address] = args const $query = inst.getSubqueryObservable<{ - investorTransactions: { - nodes: { - timestamp: string - type: InvestorTransactionType - poolId: string - trancheId: string - hash: string - tokenAmount: string - tokenPrice: string - currencyAmount: string - }[] - } - borrowerTransactions: { - nodes: { - timestamp: string - type: BorrowerTransactionType - poolId: string - amount: string - hash: string - }[] - } - outstandingOrders: { - nodes: { - timestamp: string - poolId: string - trancheId: string - hash: string - redeemAmount: string - investAmount: string - tranche: { - tokenPrice: string - } - }[] - } + investorTransactions: { nodes: SubqueryInvestorTransaction[] } + outstandingOrders: { nodes: SubqueryOutstandingOrder[] } }>( `query($address: String!) { investorTransactions(filter: { @@ -2071,39 +2040,6 @@ export function getPoolsModule(inst: Centrifuge) { currencyAmount } } - - borrowerTransactions(filter: { - accountId: { - equalTo: $address - } - }) { - nodes { - timestamp - type - poolId - hash - amount - } - } - - outstandingOrders(filter: { - accountId: { - equalTo: $address - } - }) { - nodes { - timestamp - redeemAmount - investAmount - poolId - trancheId - hash - tranche { - tokenPrice - } - } - } - } `, { address, @@ -2112,55 +2048,27 @@ export function getPoolsModule(inst: Centrifuge) { return $query.pipe( mergeMap((data) => { - const investorTransactions$ = from(data?.investorTransactions.nodes || []).pipe( - mergeMap((entry) => { - return getPoolCurrency([entry.poolId]).pipe( - map((poolCurrency) => ({ - ...entry, - tokenAmount: new CurrencyBalance(entry.tokenAmount || 0, poolCurrency.decimals), - tokenPrice: new Price(entry.tokenPrice || 0), - currencyAmount: new CurrencyBalance(entry.currencyAmount || 0, poolCurrency.decimals), - trancheId: entry.trancheId.split('-')[1], - })) - ) - }), - toArray() - ) - - const borrowerTransactions$ = from(data?.borrowerTransactions.nodes || []).pipe( - mergeMap((entry) => { - return getPoolCurrency([entry.poolId]).pipe( - map((poolCurrency) => ({ - ...entry, - amount: new CurrencyBalance(entry.amount || 0, poolCurrency.decimals), - })) - ) - }), - toArray() - ) - - const outstandingOrders$ = from(data?.outstandingOrders.nodes || []).pipe( - mergeMap((entry) => { - return getPoolCurrency([entry.poolId]).pipe( - map((poolCurrency) => { - return { + const $investorTransactions = from(data?.investorTransactions.nodes || []) + // .pipe(distinct(({ hash }) => hash)) + .pipe( + mergeMap((entry) => { + return getPoolCurrency([entry.poolId]).pipe( + map((poolCurrency) => ({ ...entry, - investAmount: new CurrencyBalance(entry.investAmount || 0, poolCurrency.decimals), - redeemAmount: new CurrencyBalance(entry.redeemAmount || 0, poolCurrency.decimals), + tokenAmount: new CurrencyBalance(entry.tokenAmount || 0, poolCurrency.decimals), + tokenPrice: new Price(entry.tokenPrice || 0), + currencyAmount: new CurrencyBalance(entry.currencyAmount || 0, poolCurrency.decimals), trancheId: entry.trancheId.split('-')[1], - } - }) - ) - }), - toArray() - ) + })) + ) + }), + toArray() + ) - return forkJoin([investorTransactions$, borrowerTransactions$, outstandingOrders$]).pipe( - map(([investorTransactions, borrowerTransactions, outstandingOrders]) => { + return forkJoin([$investorTransactions]).pipe( + map(([investorTransactions]) => { return { investorTransactions, - borrowerTransactions, - outstandingOrders, } }) ) @@ -3052,7 +2960,7 @@ export function getPoolsModule(inst: Centrifuge) { getNativeCurrency, getCurrencies, getDailyTrancheStates, - getAllTransactions, + getTransactionsByAddress, getDailyTVL, } } diff --git a/centrifuge-js/src/types/subquery.ts b/centrifuge-js/src/types/subquery.ts index 9f67a8b96a..398e342ebe 100644 --- a/centrifuge-js/src/types/subquery.ts +++ b/centrifuge-js/src/types/subquery.ts @@ -82,6 +82,18 @@ export type SubqueryBorrowerTransaction = { amount?: number | null } +export type SubqueryOutstandingOrder = { + timestamp: string + poolId: string + trancheId: string // poolId-trancheId + hash: string + redeemAmount: string + investAmount: string + tranche: { + tokenPrice: string + } +} + export type SubqueryEpoch = { id: string poolId: string From 17eab72250f898c8b343b2b3433ba06494ee9643 Mon Sep 17 00:00:00 2001 From: sophian Date: Wed, 27 Sep 2023 19:42:00 -0400 Subject: [PATCH 02/31] Filter transaction types for short table --- .../src/components/Portfolio/Transactions.tsx | 27 ++++++++++--------- centrifuge-app/src/pages/Portfolio/index.tsx | 7 +++-- centrifuge-js/src/modules/pools.ts | 5 ++-- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index b0d7b52852..ef756ea628 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -14,24 +14,27 @@ export const TRANSACTION_CARD_GAP = 4 type AddressTransactionsProps = { count?: number + txTypes?: InvestorTransactionType[] } -export function Transactions({ count }: AddressTransactionsProps) { +export function Transactions({ count, txTypes }: AddressTransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() const transactions = useTransactionsByAddress(formatAddress(address || '')) const investorTransactions = - transactions?.investorTransactions.map((tx) => { - return { - date: new Date(tx.timestamp).getTime(), - action: tx.type, - amount: tx.tokenAmount, - poolId: tx.poolId, - hash: tx.hash, - trancheId: tx.trancheId, - } - }) || [] + transactions?.investorTransactions + .filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) + .map((tx) => { + return { + date: new Date(tx.timestamp).getTime(), + action: tx.type, + amount: tx.tokenAmount, + poolId: tx.poolId, + hash: tx.hash, + trancheId: tx.trancheId, + } + }) || [] return !!investorTransactions.slice(0, count ?? investorTransactions.length) ? ( @@ -76,9 +79,7 @@ export type TransactionCardProps = { export function TransactionListItem({ date, action, amount, poolId, hash, trancheId }: TransactionCardProps) { const pool = usePool(poolId) as Pool const { data } = usePoolMetadata(pool) - console.log('🚀 ~ data:', data) const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined - console.log('🚀 ~ token:', token) const subScanUrl = import.meta.env.REACT_APP_SUBSCAN_URL if (!pool || !data) { diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index 48c7ae4eb6..5eab454d08 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -50,9 +50,12 @@ function Portfolio() { - + - + diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 0843e6db35..39d826d398 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -11,7 +11,6 @@ import { InvestorTransactionType, SubqueryBorrowerTransaction, SubqueryInvestorTransaction, - SubqueryOutstandingOrder, SubqueryPoolSnapshot, SubqueryTrancheSnapshot, } from '../types/subquery' @@ -2021,7 +2020,6 @@ export function getPoolsModule(inst: Centrifuge) { const $query = inst.getSubqueryObservable<{ investorTransactions: { nodes: SubqueryInvestorTransaction[] } - outstandingOrders: { nodes: SubqueryOutstandingOrder[] } }>( `query($address: String!) { investorTransactions(filter: { @@ -2040,6 +2038,7 @@ export function getPoolsModule(inst: Centrifuge) { currencyAmount } } + } `, { address, @@ -2047,7 +2046,7 @@ export function getPoolsModule(inst: Centrifuge) { ) return $query.pipe( - mergeMap((data) => { + switchMap((data) => { const $investorTransactions = from(data?.investorTransactions.nodes || []) // .pipe(distinct(({ hash }) => hash)) .pipe( From 9e6e56750ee26f686c3998606cc668a5256bd2aa Mon Sep 17 00:00:00 2001 From: sophian Date: Wed, 27 Sep 2023 19:55:40 -0400 Subject: [PATCH 03/31] Update styles in tx history page --- .../Portfolio/TransactionTypeChip.tsx | 21 +++++++-------- .../src/components/Portfolio/Transactions.tsx | 16 ++++++----- .../pages/Portfolio/Transactions/index.tsx | 27 ++++++++++--------- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx index cb0f91d18e..07ddb6b5da 100644 --- a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx +++ b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx @@ -3,26 +3,21 @@ import * as React from 'react' import { TransactionCardProps } from './Transactions' type TransactionTypeProps = { - type: TransactionCardProps['action'] + type: TransactionCardProps['type'] } -// @ts-expect-error const states: { - [Key in TransactionCardProps['action']]: { + [Key in TransactionCardProps['type']]: { label: string status: StatusChipProps['status'] } } = { - PENDING_ORDER: { - label: 'Pending order', - status: 'default', - }, INVEST_ORDER_UPDATE: { - label: 'Invest order update', + label: 'Pending invest', status: 'default', }, REDEEM_ORDER_UPDATE: { - label: 'Redeem order update', + label: 'Pending redemption', status: 'default', }, INVEST_ORDER_CANCEL: { @@ -34,11 +29,11 @@ const states: { status: 'default', }, INVEST_EXECUTION: { - label: 'Invest execution', + label: 'Invest', status: 'ok', }, REDEEM_EXECUTION: { - label: 'Redeem execution', + label: 'Redeem', status: 'info', }, TRANSFER_IN: { @@ -73,6 +68,10 @@ const states: { label: 'Closed', status: 'default', }, + PRICED: { + label: 'Priced', + status: 'default', + }, } export function TransactionTypeChip({ type }: TransactionTypeProps) { diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index ef756ea628..84f7d1e09a 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -2,7 +2,7 @@ import { BorrowerTransactionType, CurrencyBalance, InvestorTransactionType, Pool import { useCentrifugeUtils } from '@centrifuge/centrifuge-react' import { Box, Grid, IconExternalLink, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' -import { Link } from 'react-router-dom' +import { Link, useRouteMatch } from 'react-router-dom' import { formatDate } from '../../utils/date' import { formatBalanceAbbreviated } from '../../utils/formatting' import { useAddress } from '../../utils/useAddress' @@ -21,6 +21,7 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() const transactions = useTransactionsByAddress(formatAddress(address || '')) + const match = useRouteMatch('/portfolio/transactions') const investorTransactions = transactions?.investorTransactions @@ -28,7 +29,7 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { .map((tx) => { return { date: new Date(tx.timestamp).getTime(), - action: tx.type, + type: tx.type, amount: tx.tokenAmount, poolId: tx.poolId, hash: tx.hash, @@ -39,8 +40,9 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { return !!investorTransactions.slice(0, count ?? investorTransactions.length) ? ( - Transaction history + {match ? null : 'Transaction history'} + Action @@ -62,21 +64,21 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { ))} - View all + {match ? null : View all} ) : null } export type TransactionCardProps = { date: number - action: InvestorTransactionType | BorrowerTransactionType | 'PENDING_ORDER' + type: InvestorTransactionType | BorrowerTransactionType amount: CurrencyBalance poolId: string hash: string trancheId?: string } -export function TransactionListItem({ date, action, amount, poolId, hash, trancheId }: TransactionCardProps) { +export function TransactionListItem({ date, type, amount, poolId, hash, trancheId }: TransactionCardProps) { const pool = usePool(poolId) as Pool const { data } = usePoolMetadata(pool) const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined @@ -97,7 +99,7 @@ export function TransactionListItem({ date, action, amount, poolId, hash, tranch borderBottomStyle="solid" > - + diff --git a/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx b/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx index dd9389317d..00dd7e6631 100644 --- a/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx @@ -1,6 +1,7 @@ import { Box, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' import { LayoutBase } from '../../../components/LayoutBase' +import { BasePadding } from '../../../components/LayoutBase/BasePadding' import { Transactions } from '../../../components/Portfolio/Transactions' import { useAddress } from '../../../utils/useAddress' @@ -8,19 +9,21 @@ export function TransactionsPage() { const address = useAddress() return ( - - - - Transaction history - - + + + + + Transaction history + + - {!!address ? ( - - ) : ( - You need to connect your wallet to see your transactions - )} - + {!!address ? ( + + ) : ( + You need to connect your wallet to see your transactions + )} + + ) } From d9e3cf88344352bd084046b7ac3b9205894e3e3c Mon Sep 17 00:00:00 2001 From: sophian Date: Thu, 28 Sep 2023 18:49:30 -0400 Subject: [PATCH 04/31] Addsort buttons to table and view all button --- .../Portfolio/TransactionTypeChip.tsx | 8 +- .../src/components/Portfolio/Transactions.tsx | 136 ++++++++++++++---- centrifuge-app/src/pages/Portfolio/index.tsx | 2 +- centrifuge-js/src/modules/pools.ts | 33 ++--- 4 files changed, 132 insertions(+), 47 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx index 07ddb6b5da..4efe785dc3 100644 --- a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx +++ b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx @@ -13,11 +13,11 @@ const states: { } } = { INVEST_ORDER_UPDATE: { - label: 'Pending invest', + label: 'Invest order placed', status: 'default', }, REDEEM_ORDER_UPDATE: { - label: 'Pending redemption', + label: 'Redeem order placed', status: 'default', }, INVEST_ORDER_CANCEL: { @@ -29,11 +29,11 @@ const states: { status: 'default', }, INVEST_EXECUTION: { - label: 'Invest', + label: 'Invest executed', status: 'ok', }, REDEEM_EXECUTION: { - label: 'Redeem', + label: 'Redeem executed', status: 'info', }, TRANSFER_IN: { diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 84f7d1e09a..27e436520a 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -1,10 +1,28 @@ -import { BorrowerTransactionType, CurrencyBalance, InvestorTransactionType, Pool } from '@centrifuge/centrifuge-js' +import { + BorrowerTransactionType, + CurrencyBalance, + InvestorTransactionType, + Pool, + TokenBalance, +} from '@centrifuge/centrifuge-js' import { useCentrifugeUtils } from '@centrifuge/centrifuge-react' -import { Box, Grid, IconExternalLink, Stack, Text } from '@centrifuge/fabric' +import { + AnchorButton, + Box, + Grid, + IconChevronDown, + IconChevronUp, + IconExternalLink, + IconEye, + Shelf, + Stack, + Text, +} from '@centrifuge/fabric' import * as React from 'react' -import { Link, useRouteMatch } from 'react-router-dom' +import { useRouteMatch } from 'react-router-dom' +import styled from 'styled-components' import { formatDate } from '../../utils/date' -import { formatBalanceAbbreviated } from '../../utils/formatting' +import { formatBalance } from '../../utils/formatting' import { useAddress } from '../../utils/useAddress' import { usePool, usePoolMetadata, useTransactionsByAddress } from '../../utils/usePools' import { TransactionTypeChip } from './TransactionTypeChip' @@ -22,20 +40,34 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { const address = useAddress() const transactions = useTransactionsByAddress(formatAddress(address || '')) const match = useRouteMatch('/portfolio/transactions') - - const investorTransactions = - transactions?.investorTransactions - .filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) - .map((tx) => { - return { - date: new Date(tx.timestamp).getTime(), - type: tx.type, - amount: tx.tokenAmount, - poolId: tx.poolId, - hash: tx.hash, - trancheId: tx.trancheId, - } - }) || [] + const [sortKey, setSortKey] = React.useState<'date' | 'amount'>('date') + const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>('desc') + + const investorTransactions = React.useMemo(() => { + const txs = + transactions?.investorTransactions + .filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) + .map((tx) => { + return { + date: new Date(tx.timestamp).getTime(), + type: tx.type, + poolId: tx.poolId, + hash: tx.hash, + trancheId: tx.trancheId, + amount: tx.currencyAmount, + } + }) + .sort((a, b) => { + if (sortKey === 'date') { + return new Date(b.date).getTime() - new Date(a.date).getTime() + } else if (sortKey === 'amount') { + return b.amount.toDecimal().minus(a.amount.toDecimal()).toNumber() + } else { + return 1 + } + }) || [] + return sortOrder === 'asc' ? txs : txs.reverse() + }, [sortKey, transactions, sortOrder]) return !!investorTransactions.slice(0, count ?? investorTransactions.length) ? ( @@ -46,14 +78,52 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { Action - - Transaction date + { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') + setSortKey('date') + }} + gap={1} + > + Transaction date + + + + + Token - + { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') + setSortKey('amount') + }} + gap={1} + justifyContent="flex-end" + > Amount - + + + + + @@ -64,7 +134,13 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { ))} - {match ? null : View all} + + {match ? null : ( + + View all + + )} + ) : null } @@ -72,12 +148,20 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { export type TransactionCardProps = { date: number type: InvestorTransactionType | BorrowerTransactionType - amount: CurrencyBalance + amount: CurrencyBalance | TokenBalance poolId: string hash: string trancheId?: string } +const SortButton = styled(Shelf)` + background: initial; + border: none; + cursor: pointer; + display: inline-flex; + align-items: flex-start; +` + export function TransactionListItem({ date, type, amount, poolId, hash, trancheId }: TransactionCardProps) { const pool = usePool(poolId) as Pool const { data } = usePoolMetadata(pool) @@ -112,7 +196,7 @@ export function TransactionListItem({ date, type, amount, poolId, hash, trancheI - {!!token ? token.currency?.name.split(data.pool?.name || '') : data.pool?.name} + {!!token ? token?.currency?.name.split(`${data?.pool?.name} ` || '').at(-1) : data.pool?.name} {!!token && ( @@ -123,7 +207,7 @@ export function TransactionListItem({ date, type, amount, poolId, hash, trancheI - {formatBalanceAbbreviated(amount, pool.currency.symbol)} + {formatBalance(amount.toDecimal(), pool.currency.symbol)} diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index 5eab454d08..4f7a5f2f10 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -19,7 +19,7 @@ export function PortfolioPage() { } function Portfolio() { - const address = useAddress() + const address = useAddress('substrate') const theme = useTheme() if (!address) { diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 39d826d398..625eea78f2 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -2047,25 +2047,26 @@ export function getPoolsModule(inst: Centrifuge) { return $query.pipe( switchMap((data) => { - const $investorTransactions = from(data?.investorTransactions.nodes || []) - // .pipe(distinct(({ hash }) => hash)) - .pipe( - mergeMap((entry) => { - return getPoolCurrency([entry.poolId]).pipe( - map((poolCurrency) => ({ - ...entry, - tokenAmount: new CurrencyBalance(entry.tokenAmount || 0, poolCurrency.decimals), - tokenPrice: new Price(entry.tokenPrice || 0), - currencyAmount: new CurrencyBalance(entry.currencyAmount || 0, poolCurrency.decimals), - trancheId: entry.trancheId.split('-')[1], - })) - ) - }), - toArray() - ) + const $investorTransactions = from(data?.investorTransactions.nodes || []).pipe( + mergeMap((entry) => { + return getPoolCurrency([entry.poolId]).pipe( + map((poolCurrency) => ({ + ...entry, + tokenAmount: new TokenBalance(entry.tokenAmount || 0, poolCurrency.decimals), + tokenPrice: new Price(entry.tokenPrice || 0), + currencyAmount: new CurrencyBalance(entry.currencyAmount || 0, poolCurrency.decimals), + trancheId: entry.trancheId.split('-')[1], + })) + ) + }), + toArray() + ) return forkJoin([$investorTransactions]).pipe( map(([investorTransactions]) => { + investorTransactions.sort((a, b) => { + return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() + }) return { investorTransactions, } From 532062d3905d0b9aa70c38310ec39981fcfd2f9f Mon Sep 17 00:00:00 2001 From: sophian Date: Thu, 28 Sep 2023 19:39:02 -0400 Subject: [PATCH 05/31] Add count and txTypes to cent-js --- .../src/components/Portfolio/Transactions.tsx | 13 +++++------ centrifuge-app/src/utils/usePools.ts | 6 ++--- centrifuge-js/src/modules/pools.ts | 22 +++++++++---------- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 27e436520a..de6095f98b 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -38,15 +38,14 @@ type AddressTransactionsProps = { export function Transactions({ count, txTypes }: AddressTransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() - const transactions = useTransactionsByAddress(formatAddress(address || '')) + const transactions = useTransactionsByAddress(formatAddress(address || ''), count, txTypes) const match = useRouteMatch('/portfolio/transactions') const [sortKey, setSortKey] = React.useState<'date' | 'amount'>('date') const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>('desc') - const investorTransactions = React.useMemo(() => { + const investorTransactions: TransactionCardProps[] = React.useMemo(() => { const txs = transactions?.investorTransactions - .filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) .map((tx) => { return { date: new Date(tx.timestamp).getTime(), @@ -66,10 +65,10 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { return 1 } }) || [] - return sortOrder === 'asc' ? txs : txs.reverse() + return sortOrder === 'asc' ? txs.reverse() : txs }, [sortKey, transactions, sortOrder]) - return !!investorTransactions.slice(0, count ?? investorTransactions.length) ? ( + return !!investorTransactions ? ( {match ? null : 'Transaction history'} @@ -127,7 +126,7 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { - {investorTransactions.slice(0, count ?? investorTransactions.length).map((transaction, index) => ( + {investorTransactions.map((transaction, index) => ( @@ -136,7 +135,7 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { {match ? null : ( - + View all )} diff --git a/centrifuge-app/src/utils/usePools.ts b/centrifuge-app/src/utils/usePools.ts index cfdc1ab69e..db1e5f64cb 100644 --- a/centrifuge-app/src/utils/usePools.ts +++ b/centrifuge-app/src/utils/usePools.ts @@ -1,4 +1,4 @@ -import Centrifuge, { Pool, PoolMetadata } from '@centrifuge/centrifuge-js' +import Centrifuge, { InvestorTransactionType, Pool, PoolMetadata } from '@centrifuge/centrifuge-js' import { useCentrifuge, useCentrifugeQuery, useWallet } from '@centrifuge/centrifuge-react' import { useEffect } from 'react' import { useQuery } from 'react-query' @@ -49,10 +49,10 @@ export function useMonthlyPoolStates(poolId: string, from?: Date, to?: Date) { return result } -export function useTransactionsByAddress(address?: string) { +export function useTransactionsByAddress(address?: string, count?: number, txTypes?: InvestorTransactionType[]) { const [result] = useCentrifugeQuery( ['txByAddress', address], - (cent) => cent.pools.getTransactionsByAddress([address!]), + (cent) => cent.pools.getTransactionsByAddress([address!, count, txTypes]), { enabled: !!address, } diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 625eea78f2..7b01b24ee3 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -2015,18 +2015,17 @@ export function getPoolsModule(inst: Centrifuge) { ) } - function getTransactionsByAddress(args: [address: string]) { - const [address] = args + function getTransactionsByAddress(args: [address: string, count?: number, txTypes?: InvestorTransactionType[]]) { + const [address, count, txTypes] = args const $query = inst.getSubqueryObservable<{ investorTransactions: { nodes: SubqueryInvestorTransaction[] } }>( - `query($address: String!) { - investorTransactions(filter: { - accountId: { - equalTo: $address - } - }) { + `query ($address: String) { + investorTransactions( + filter: {accountId: {equalTo: $address}} + orderBy: TIMESTAMP_DESC + ) { nodes { timestamp type @@ -2064,11 +2063,10 @@ export function getPoolsModule(inst: Centrifuge) { return forkJoin([$investorTransactions]).pipe( map(([investorTransactions]) => { - investorTransactions.sort((a, b) => { - return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() - }) return { - investorTransactions, + investorTransactions: investorTransactions + .filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) + .slice(0, count || investorTransactions.length), } }) ) From 3dff2a0062daeb468dd59a3907da70a6194af8b6 Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 29 Sep 2023 11:12:40 -0400 Subject: [PATCH 06/31] Create new history page --- centrifuge-app/src/components/Menu/index.tsx | 8 +++++ .../src/components/Portfolio/Transactions.tsx | 34 +++++++++---------- centrifuge-app/src/components/Root.tsx | 4 +-- centrifuge-app/src/pages/Transactions.tsx | 29 ++++++++++++++++ 4 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 centrifuge-app/src/pages/Transactions.tsx diff --git a/centrifuge-app/src/components/Menu/index.tsx b/centrifuge-app/src/components/Menu/index.tsx index 8d7a0fdc2d..89f774b5a8 100644 --- a/centrifuge-app/src/components/Menu/index.tsx +++ b/centrifuge-app/src/components/Menu/index.tsx @@ -1,5 +1,6 @@ import { Box, + IconClock, IconInvestments, IconNft, IconPieChart, @@ -54,6 +55,13 @@ export function Menu() { )} + {showPortfolio && address && ( + + + History + + )} + {(pools.length > 0 || config.poolCreationType === 'immediate') && ( {isLarge ? ( diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index de6095f98b..9c2782cf30 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -30,20 +30,20 @@ import { TransactionTypeChip } from './TransactionTypeChip' export const TRANSACTION_CARD_COLUMNS = `150px 125px 200px 150px 1fr` export const TRANSACTION_CARD_GAP = 4 -type AddressTransactionsProps = { +type TransactionsProps = { count?: number txTypes?: InvestorTransactionType[] } -export function Transactions({ count, txTypes }: AddressTransactionsProps) { +export function Transactions({ count, txTypes }: TransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() const transactions = useTransactionsByAddress(formatAddress(address || ''), count, txTypes) - const match = useRouteMatch('/portfolio/transactions') + const match = useRouteMatch('/history') const [sortKey, setSortKey] = React.useState<'date' | 'amount'>('date') const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>('desc') - const investorTransactions: TransactionCardProps[] = React.useMemo(() => { + const investorTransactions: TransactionListItemProps[] = React.useMemo(() => { const txs = transactions?.investorTransactions .map((tx) => { @@ -68,13 +68,13 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { return sortOrder === 'asc' ? txs.reverse() : txs }, [sortKey, transactions, sortOrder]) - return !!investorTransactions ? ( + return !!investorTransactions.length ? ( {match ? null : 'Transaction history'} - + Action {match ? null : ( - + View all )} @@ -144,7 +144,7 @@ export function Transactions({ count, txTypes }: AddressTransactionsProps) { ) : null } -export type TransactionCardProps = { +export type TransactionListItemProps = { date: number type: InvestorTransactionType | BorrowerTransactionType amount: CurrencyBalance | TokenBalance @@ -153,15 +153,7 @@ export type TransactionCardProps = { trancheId?: string } -const SortButton = styled(Shelf)` - background: initial; - border: none; - cursor: pointer; - display: inline-flex; - align-items: flex-start; -` - -export function TransactionListItem({ date, type, amount, poolId, hash, trancheId }: TransactionCardProps) { +export function TransactionListItem({ date, type, amount, poolId, hash, trancheId }: TransactionListItemProps) { const pool = usePool(poolId) as Pool const { data } = usePoolMetadata(pool) const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined @@ -225,3 +217,11 @@ export function TransactionListItem({ date, type, amount, poolId, hash, trancheI ) } + +const SortButton = styled(Shelf)` + background: initial; + border: none; + cursor: pointer; + display: inline-flex; + align-items: flex-start; +` diff --git a/centrifuge-app/src/components/Root.tsx b/centrifuge-app/src/components/Root.tsx index abddbf6a8b..1b5931c1e9 100644 --- a/centrifuge-app/src/components/Root.tsx +++ b/centrifuge-app/src/components/Root.tsx @@ -32,8 +32,8 @@ import { UpdateInvestorStatus } from '../pages/Onboarding/UpdateInvestorStatus' import { PoolDetailPage } from '../pages/Pool' import { PoolsPage } from '../pages/Pools' import { PortfolioPage } from '../pages/Portfolio' -import { TransactionsPage } from '../pages/Portfolio/Transactions' import { TokenOverviewPage } from '../pages/Tokens' +import { TransactionsPage } from '../pages/Transactions' import { pinToApi } from '../utils/pinToApi' import { DebugFlags, initialFlagsState } from './DebugFlags' import { DemoBanner } from './DemoBanner' @@ -211,7 +211,7 @@ function Routes() { - + diff --git a/centrifuge-app/src/pages/Transactions.tsx b/centrifuge-app/src/pages/Transactions.tsx new file mode 100644 index 0000000000..e4aef61b07 --- /dev/null +++ b/centrifuge-app/src/pages/Transactions.tsx @@ -0,0 +1,29 @@ +import { Box, Stack, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { LayoutBase } from '../components/LayoutBase' +import { BasePadding } from '../components/LayoutBase/BasePadding' +import { Transactions } from '../components/Portfolio/Transactions' +import { useAddress } from '../utils/useAddress' + +export function TransactionsPage() { + const address = useAddress() + return ( + + + + + + Transaction history + + + + {!!address ? ( + + ) : ( + You need to connect your wallet to see your transactions + )} + + + + ) +} From f8ae576cdf0eecf0856d6fb67ecdd7ea292eb9f1 Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 29 Sep 2023 11:17:16 -0400 Subject: [PATCH 07/31] Remove old file --- .../pages/Portfolio/Transactions/index.tsx | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 centrifuge-app/src/pages/Portfolio/Transactions/index.tsx diff --git a/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx b/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx deleted file mode 100644 index 00dd7e6631..0000000000 --- a/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Box, Stack, Text } from '@centrifuge/fabric' -import * as React from 'react' -import { LayoutBase } from '../../../components/LayoutBase' -import { BasePadding } from '../../../components/LayoutBase/BasePadding' -import { Transactions } from '../../../components/Portfolio/Transactions' -import { useAddress } from '../../../utils/useAddress' - -export function TransactionsPage() { - const address = useAddress() - return ( - - - - - - Transaction history - - - - {!!address ? ( - - ) : ( - You need to connect your wallet to see your transactions - )} - - - - ) -} From e34d67c2214f34e5768c70fe7fa0f880667f70d7 Mon Sep 17 00:00:00 2001 From: sophian Date: Tue, 10 Oct 2023 18:15:19 -0400 Subject: [PATCH 08/31] Add pagination --- .../src/components/Portfolio/Transactions.tsx | 155 ++++++++++-------- 1 file changed, 85 insertions(+), 70 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 9c2782cf30..b4c3442e14 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -14,9 +14,12 @@ import { IconChevronUp, IconExternalLink, IconEye, + Pagination, + PaginationContainer, Shelf, Stack, Text, + usePagination, } from '@centrifuge/fabric' import * as React from 'react' import { useRouteMatch } from 'react-router-dom' @@ -42,6 +45,10 @@ export function Transactions({ count, txTypes }: TransactionsProps) { const match = useRouteMatch('/history') const [sortKey, setSortKey] = React.useState<'date' | 'amount'>('date') const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>('desc') + const pagination = usePagination({ + data: transactions?.investorTransactions, + pageSize: 10, + }) const investorTransactions: TransactionListItemProps[] = React.useMemo(() => { const txs = @@ -64,83 +71,91 @@ export function Transactions({ count, txTypes }: TransactionsProps) { } else { return 1 } - }) || [] + }) + .slice((pagination.page - 1) * pagination.pageSize, pagination.page * pagination.pageSize) || [] return sortOrder === 'asc' ? txs.reverse() : txs - }, [sortKey, transactions, sortOrder]) + }, [sortKey, transactions, sortOrder, pagination]) return !!investorTransactions.length ? ( - - - {match ? null : 'Transaction history'} - + + + + {match ? null : 'Transaction history'} + - - - Action - { - setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') - setSortKey('date') - }} - gap={1} - > - Transaction date - - - - - - - Token - - { - setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') - setSortKey('amount') - }} - gap={1} - justifyContent="flex-end" - > - Amount - - - - - - - - - {investorTransactions.map((transaction, index) => ( - - - - ))} + + + Action + { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') + setSortKey('date') + }} + gap={1} + > + Transaction date + + + + + + + Token + + { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') + setSortKey('amount') + }} + gap={1} + justifyContent="flex-end" + > + Amount + + + + + + + + + {investorTransactions.map((transaction, index) => ( + + + + ))} + - - {match ? null : ( - - View all - + + + View all + + )} - - + {pagination.pageCount > 1 && ( + + + + )} + + ) : null } From 26f28f16b30c14aef4435293cd0b1e5157d0dcb9 Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 13 Oct 2023 12:57:28 -0400 Subject: [PATCH 09/31] Resolve unique key error --- fabric/src/components/Pagination/Pagination.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fabric/src/components/Pagination/Pagination.tsx b/fabric/src/components/Pagination/Pagination.tsx index 59d14bb94f..c77392f68c 100644 --- a/fabric/src/components/Pagination/Pagination.tsx +++ b/fabric/src/components/Pagination/Pagination.tsx @@ -105,7 +105,12 @@ export function Pagination({ pagination }: { pagination?: PaginationState }) { )} {pages.map((n) => ( - goToPage(n)} $active={page === n} aria-label={`Go to page ${n}`}> + goToPage(n)} + $active={page === n} + aria-label={`Go to page ${n}`} + > {n} From 67900c274faf13a4d2a5bc39ff23b4d44283081f Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 13 Oct 2023 12:58:40 -0400 Subject: [PATCH 10/31] Fetch currencies once per poolId --- centrifuge-js/src/modules/pools.ts | 49 +++++++++++++++--------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 6fbf803b79..70df2401e8 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -1,8 +1,8 @@ import { StorageKey, u32 } from '@polkadot/types' import { Codec } from '@polkadot/types-codec/types' import BN from 'bn.js' -import { combineLatest, EMPTY, expand, firstValueFrom, forkJoin, from, Observable, of, startWith } from 'rxjs' -import { combineLatestWith, filter, map, mergeMap, repeatWhen, switchMap, take, toArray } from 'rxjs/operators' +import { combineLatest, EMPTY, expand, firstValueFrom, from, Observable, of, startWith } from 'rxjs' +import { combineLatestWith, filter, map, repeatWhen, switchMap, take } from 'rxjs/operators' import { calculateOptimalSolution, SolverResult } from '..' import { Centrifuge } from '../Centrifuge' import { Account, TransactionOptions } from '../types' @@ -2032,31 +2032,30 @@ export function getPoolsModule(inst: Centrifuge) { return $query.pipe( switchMap((data) => { - const $investorTransactions = from(data?.investorTransactions.nodes || []).pipe( - mergeMap((entry) => { - return getPoolCurrency([entry.poolId]).pipe( - map((poolCurrency) => ({ - ...entry, - tokenAmount: new TokenBalance(entry.tokenAmount || 0, poolCurrency.decimals), - tokenPrice: new Price(entry.tokenPrice || 0), - currencyAmount: new CurrencyBalance(entry.currencyAmount || 0, poolCurrency.decimals), - trancheId: entry.trancheId.split('-')[1], - })) - ) - }), - toArray() - ) - - return forkJoin([$investorTransactions]).pipe( - map(([investorTransactions]) => { - return { - investorTransactions: investorTransactions - .filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) - .slice(0, count || investorTransactions.length), - } + const poolIds = new Set(data?.investorTransactions.nodes.map((e) => e.poolId)) ?? [] + const $poolCurrencies = Array.from(poolIds).map((poolId) => getPoolCurrency([poolId])) + return combineLatest([$query, ...$poolCurrencies]).pipe( + map(([data, ...currencies]) => { + return data?.investorTransactions.nodes.map((tx) => { + const currencyIndex = Array.from(poolIds).indexOf(tx.poolId) + const poolCurrency = currencies[currencyIndex] + return { + ...tx, + tokenAmount: new TokenBalance(tx.tokenAmount || 0, poolCurrency.decimals), + tokenPrice: new Price(tx.tokenPrice || 0), + currencyAmount: new CurrencyBalance(tx.currencyAmount || 0, poolCurrency.decimals), + trancheId: tx.trancheId.split('-')[1], + } + }) }) ) - }) + }), + map((investorTransactions) => ({ + investorTransactions: + investorTransactions + ?.filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) + .slice(0, count || investorTransactions.length) ?? [], + })) ) } From 87dd3e8603c6961d9009472fdd389ffa330bebdd Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 13 Oct 2023 14:46:42 -0400 Subject: [PATCH 11/31] Add CSV export --- .../src/components/Portfolio/Transactions.tsx | 151 +++++++++++------- 1 file changed, 93 insertions(+), 58 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index b4c3442e14..e28e11cee4 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -26,8 +26,9 @@ import { useRouteMatch } from 'react-router-dom' import styled from 'styled-components' import { formatDate } from '../../utils/date' import { formatBalance } from '../../utils/formatting' +import { getCSVDownloadUrl } from '../../utils/getCSVDownloadUrl' import { useAddress } from '../../utils/useAddress' -import { usePool, usePoolMetadata, useTransactionsByAddress } from '../../utils/usePools' +import { usePool, usePoolMetadata, usePools, useTransactionsByAddress } from '../../utils/usePools' import { TransactionTypeChip } from './TransactionTypeChip' export const TRANSACTION_CARD_COLUMNS = `150px 125px 200px 150px 1fr` @@ -49,6 +50,7 @@ export function Transactions({ count, txTypes }: TransactionsProps) { data: transactions?.investorTransactions, pageSize: 10, }) + const pools = usePools() const investorTransactions: TransactionListItemProps[] = React.useMemo(() => { const txs = @@ -71,12 +73,33 @@ export function Transactions({ count, txTypes }: TransactionsProps) { } else { return 1 } - }) - .slice((pagination.page - 1) * pagination.pageSize, pagination.page * pagination.pageSize) || [] + }) || [] return sortOrder === 'asc' ? txs.reverse() : txs }, [sortKey, transactions, sortOrder, pagination]) - return !!investorTransactions.length ? ( + const paginatedInvestorTransactions = React.useMemo(() => { + return investorTransactions.slice( + (pagination.page - 1) * pagination.pageSize, + pagination.page * pagination.pageSize + ) + }, [investorTransactions, pagination]) + + const csvData: any = React.useMemo(() => { + if (!investorTransactions || !investorTransactions?.length) { + return undefined + } + return investorTransactions.map((entry) => { + const pool = pools?.find((pool) => pool.id === entry.poolId) + return { + 'Transaction date': `"${formatDate(entry.date)}"`, + Action: entry.type, + Token: pool ? pool.tranches.find(({ id }) => id === entry.trancheId)?.currency.name : undefined, + Amount: pool ? `"${formatBalance(entry.amount.toDecimal(), pool.currency.symbol)}"` : undefined, + } + }) + }, [investorTransactions]) + + return !!paginatedInvestorTransactions.length ? ( @@ -135,7 +158,7 @@ export function Transactions({ count, txTypes }: TransactionsProps) { - {investorTransactions.map((transaction, index) => ( + {paginatedInvestorTransactions.map((transaction, index) => ( @@ -149,11 +172,21 @@ export function Transactions({ count, txTypes }: TransactionsProps) { )} - {pagination.pageCount > 1 && ( - - - - )} + + {pagination.pageCount > 1 && ( + + + + )} + + Export as CSV + + ) : null @@ -173,64 +206,66 @@ export function TransactionListItem({ date, type, amount, poolId, hash, trancheI const { data } = usePoolMetadata(pool) const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined const subScanUrl = import.meta.env.REACT_APP_SUBSCAN_URL + console.log('🚀 ~ subScanUrl:', subScanUrl) if (!pool || !data) { return null } - return ( - - - - - - - {formatDate(date, { - day: '2-digit', - month: '2-digit', - year: '2-digit', - })} - - - - - {!!token ? token?.currency?.name.split(`${data?.pool?.name} ` || '').at(-1) : data.pool?.name} + + + + + + + {formatDate(date, { + day: '2-digit', + month: '2-digit', + year: '2-digit', + })} - {!!token && ( - - {data?.pool?.name} + + + + {!!token ? token?.currency?.name.split(`${data?.pool?.name} ` || '').at(-1) : data.pool?.name} - )} - + {!!token && ( + + {data?.pool?.name} + + )} + - - - {formatBalance(amount.toDecimal(), pool.currency.symbol)} - - - - {!!subScanUrl && !!hash && ( - - + + + {formatBalance(amount.toDecimal(), pool.currency.symbol)} + - )} - - ) + + {!!subScanUrl && !!hash && ( + + + + )} + + + ) : null } const SortButton = styled(Shelf)` From b56b861ee1aef42124d946fa2981e8ea63f11136 Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 13 Oct 2023 14:56:27 -0400 Subject: [PATCH 12/31] Hide download button in preview --- .../src/components/Portfolio/Transactions.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index e28e11cee4..5652943b69 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -178,14 +178,16 @@ export function Transactions({ count, txTypes }: TransactionsProps) { )} - - Export as CSV - + {!match ? null : ( + + Export as CSV + + )} From cad7795a97b394f90cd6ab9cc677b9cb506f6cc0 Mon Sep 17 00:00:00 2001 From: sophian Date: Mon, 16 Oct 2023 12:35:48 -0400 Subject: [PATCH 13/31] Fix logs --- .../src/components/Portfolio/TransactionTypeChip.tsx | 6 +++--- centrifuge-app/src/components/Portfolio/Transactions.tsx | 1 - centrifuge-app/src/utils/usePools.ts | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx index 4efe785dc3..ac863c5e70 100644 --- a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx +++ b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx @@ -1,13 +1,13 @@ import { StatusChip, StatusChipProps } from '@centrifuge/fabric' import * as React from 'react' -import { TransactionCardProps } from './Transactions' +import { TransactionListItemProps } from './Transactions' type TransactionTypeProps = { - type: TransactionCardProps['type'] + type: TransactionListItemProps['type'] } const states: { - [Key in TransactionCardProps['type']]: { + [Key in TransactionListItemProps['type']]: { label: string status: StatusChipProps['status'] } diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 5652943b69..6b7c327947 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -208,7 +208,6 @@ export function TransactionListItem({ date, type, amount, poolId, hash, trancheI const { data } = usePoolMetadata(pool) const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined const subScanUrl = import.meta.env.REACT_APP_SUBSCAN_URL - console.log('🚀 ~ subScanUrl:', subScanUrl) if (!pool || !data) { return null diff --git a/centrifuge-app/src/utils/usePools.ts b/centrifuge-app/src/utils/usePools.ts index 1812d6448b..7352d25778 100644 --- a/centrifuge-app/src/utils/usePools.ts +++ b/centrifuge-app/src/utils/usePools.ts @@ -1,5 +1,5 @@ import Centrifuge, { - BorrowerTransactionType, + BorrowerTransaction, InvestorTransactionType, Loan, Pool, @@ -119,7 +119,7 @@ export function useBorrowerAssetTransactions(poolId: string, assetId: string, fr const borrowerTransactions = cent.pools.getBorrowerTransactions([poolId, from, to]) return borrowerTransactions.pipe( - map((transactions: BorrowerTransactionType[]) => + map((transactions: BorrowerTransaction[]) => transactions.filter((transaction) => transaction.loanId.split('-')[1] === assetId) ) ) From 35db8ff30de2511958cfa3c934f0c35aeafc278f Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 20 Oct 2023 17:25:18 -0400 Subject: [PATCH 14/31] Add more query keys --- centrifuge-app/src/utils/usePools.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrifuge-app/src/utils/usePools.ts b/centrifuge-app/src/utils/usePools.ts index a89182d513..6fef787ab1 100644 --- a/centrifuge-app/src/utils/usePools.ts +++ b/centrifuge-app/src/utils/usePools.ts @@ -60,7 +60,7 @@ export function useMonthlyPoolStates(poolId: string, from?: Date, to?: Date) { export function useTransactionsByAddress(address?: string, count?: number, txTypes?: InvestorTransactionType[]) { const [result] = useCentrifugeQuery( - ['txByAddress', address], + ['txByAddress', count, address, txTypes], (cent) => cent.pools.getTransactionsByAddress([address!, count, txTypes]), { enabled: !!address, From f445227aa61300b9f45f95f27a27c002c4ac2bc3 Mon Sep 17 00:00:00 2001 From: sophian Date: Mon, 23 Oct 2023 17:42:58 -0400 Subject: [PATCH 15/31] Add loading state and internal link --- .../src/components/Portfolio/Transactions.tsx | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 6b7c327947..a1bb59058a 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -20,15 +20,17 @@ import { Stack, Text, usePagination, + VisualButton, } from '@centrifuge/fabric' import * as React from 'react' -import { useRouteMatch } from 'react-router-dom' +import { Link, useRouteMatch } from 'react-router-dom' import styled from 'styled-components' import { formatDate } from '../../utils/date' import { formatBalance } from '../../utils/formatting' import { getCSVDownloadUrl } from '../../utils/getCSVDownloadUrl' import { useAddress } from '../../utils/useAddress' import { usePool, usePoolMetadata, usePools, useTransactionsByAddress } from '../../utils/usePools' +import { Spinner } from '../Spinner' import { TransactionTypeChip } from './TransactionTypeChip' export const TRANSACTION_CARD_COLUMNS = `150px 125px 200px 150px 1fr` @@ -42,12 +44,12 @@ type TransactionsProps = { export function Transactions({ count, txTypes }: TransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() - const transactions = useTransactionsByAddress(formatAddress(address || ''), count, txTypes) + const transactions = useTransactionsByAddress(formatAddress(address || '')) const match = useRouteMatch('/history') const [sortKey, setSortKey] = React.useState<'date' | 'amount'>('date') const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>('desc') const pagination = usePagination({ - data: transactions?.investorTransactions, + data: transactions?.investorTransactions || [], pageSize: 10, }) const pools = usePools() @@ -55,6 +57,8 @@ export function Transactions({ count, txTypes }: TransactionsProps) { const investorTransactions: TransactionListItemProps[] = React.useMemo(() => { const txs = transactions?.investorTransactions + .slice(0, count || transactions?.investorTransactions.length) + .filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) .map((tx) => { return { date: new Date(tx.timestamp).getTime(), @@ -75,7 +79,7 @@ export function Transactions({ count, txTypes }: TransactionsProps) { } }) || [] return sortOrder === 'asc' ? txs.reverse() : txs - }, [sortKey, transactions, sortOrder, pagination]) + }, [sortKey, transactions, sortOrder, txTypes, count]) const paginatedInvestorTransactions = React.useMemo(() => { return investorTransactions.slice( @@ -166,14 +170,16 @@ export function Transactions({ count, txTypes }: TransactionsProps) { {match ? null : ( - - - View all - - + + + + View all + + + )} - {pagination.pageCount > 1 && ( + {match && pagination.pageCount > 1 && ( @@ -191,7 +197,9 @@ export function Transactions({ count, txTypes }: TransactionsProps) { - ) : null + ) : ( + + ) } export type TransactionListItemProps = { From 176bbd0ebd8650dffae320d79629c1d703f66d72 Mon Sep 17 00:00:00 2001 From: sophian Date: Mon, 23 Oct 2023 17:57:41 -0400 Subject: [PATCH 16/31] Improve load time --- centrifuge-app/src/utils/usePools.ts | 14 ++++---------- centrifuge-js/src/modules/pools.ts | 21 +++++++++------------ 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/centrifuge-app/src/utils/usePools.ts b/centrifuge-app/src/utils/usePools.ts index 6fef787ab1..21e33a2fef 100644 --- a/centrifuge-app/src/utils/usePools.ts +++ b/centrifuge-app/src/utils/usePools.ts @@ -1,10 +1,4 @@ -import Centrifuge, { - BorrowerTransaction, - InvestorTransactionType, - Loan, - Pool, - PoolMetadata, -} from '@centrifuge/centrifuge-js' +import Centrifuge, { BorrowerTransaction, Loan, Pool, PoolMetadata } from '@centrifuge/centrifuge-js' import { useCentrifugeConsts, useCentrifugeQuery, useWallet } from '@centrifuge/centrifuge-react' import BN from 'bn.js' import { useEffect, useMemo } from 'react' @@ -58,10 +52,10 @@ export function useMonthlyPoolStates(poolId: string, from?: Date, to?: Date) { return result } -export function useTransactionsByAddress(address?: string, count?: number, txTypes?: InvestorTransactionType[]) { +export function useTransactionsByAddress(address?: string) { const [result] = useCentrifugeQuery( - ['txByAddress', count, address, txTypes], - (cent) => cent.pools.getTransactionsByAddress([address!, count, txTypes]), + ['txByAddress', address], + (cent) => cent.pools.getTransactionsByAddress([address!]), { enabled: !!address, } diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 8b454e7a4f..8a277054c1 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -2004,7 +2004,7 @@ export function getPoolsModule(inst: Centrifuge) { } function getTransactionsByAddress(args: [address: string, count?: number, txTypes?: InvestorTransactionType[]]) { - const [address, count, txTypes] = args + const [address] = args const $query = inst.getSubqueryObservable<{ investorTransactions: { nodes: SubqueryInvestorTransaction[] } @@ -2036,9 +2036,9 @@ export function getPoolsModule(inst: Centrifuge) { switchMap((data) => { const poolIds = new Set(data?.investorTransactions.nodes.map((e) => e.poolId)) ?? [] const $poolCurrencies = Array.from(poolIds).map((poolId) => getPoolCurrency([poolId])) - return combineLatest([$query, ...$poolCurrencies]).pipe( - map(([data, ...currencies]) => { - return data?.investorTransactions.nodes.map((tx) => { + return combineLatest($poolCurrencies).pipe( + map((currencies) => { + const txs = data?.investorTransactions.nodes.map((tx) => { const currencyIndex = Array.from(poolIds).indexOf(tx.poolId) const poolCurrency = currencies[currencyIndex] return { @@ -2049,15 +2049,12 @@ export function getPoolsModule(inst: Centrifuge) { trancheId: tx.trancheId.split('-')[1], } }) + return { + investorTransactions: txs || [], + } }) ) - }), - map((investorTransactions) => ({ - investorTransactions: - investorTransactions - ?.filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) - .slice(0, count || investorTransactions.length) ?? [], - })) + }) ) } @@ -2808,7 +2805,7 @@ export function getPoolsModule(inst: Centrifuge) { const update = updateData.toPrimitive() as any if (!update?.changes) return null const { changes, submittedAt } = update - + return { changes: { tranches: changes.tranches.noChange === null ? null : changes.tranches.newValue, From 1ebce79b3e1759f64c9462357becc5685936641c Mon Sep 17 00:00:00 2001 From: sophian Date: Mon, 23 Oct 2023 17:58:00 -0400 Subject: [PATCH 17/31] Add loop ids --- centrifuge-app/src/components/PoolList.tsx | 4 ++-- centrifuge-app/src/components/Portfolio/AssetAllocation.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/centrifuge-app/src/components/PoolList.tsx b/centrifuge-app/src/components/PoolList.tsx index 7fb37db57d..73e9dbb347 100644 --- a/centrifuge-app/src/components/PoolList.tsx +++ b/centrifuge-app/src/components/PoolList.tsx @@ -90,12 +90,12 @@ export function PoolList() { ? Array(6) .fill(true) .map((_, index) => ( - + )) : filteredPools.map((pool) => ( - + ))} diff --git a/centrifuge-app/src/components/Portfolio/AssetAllocation.tsx b/centrifuge-app/src/components/Portfolio/AssetAllocation.tsx index 69ec228512..f94d6e3b7e 100644 --- a/centrifuge-app/src/components/Portfolio/AssetAllocation.tsx +++ b/centrifuge-app/src/components/Portfolio/AssetAllocation.tsx @@ -57,7 +57,7 @@ export function AssetAllocation({ address }: { address: string }) { {shares.map((cell, i) => ( - <> + {i > 0 && } - + ))} From 2bf06e4d4c7b1c44febd8a57bbc012470ae65894 Mon Sep 17 00:00:00 2001 From: sophian Date: Wed, 25 Oct 2023 17:31:02 -0400 Subject: [PATCH 18/31] Fix table rendering --- centrifuge-app/.env-config/.env.development | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrifuge-app/.env-config/.env.development b/centrifuge-app/.env-config/.env.development index 504841d09b..92ef338c17 100644 --- a/centrifuge-app/.env-config/.env.development +++ b/centrifuge-app/.env-config/.env.development @@ -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= From 96f5f36c8c35f7b28563151ab0e30f48a4af408d Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 12:31:32 -0400 Subject: [PATCH 19/31] Restructure files --- .../src/components/Portfolio/Transactions.tsx | 12 +++----- .../pages/Portfolio/TransactionHistory.tsx | 20 +++++++++++++ centrifuge-app/src/pages/Portfolio/index.tsx | 2 +- centrifuge-app/src/pages/Transactions.tsx | 29 ------------------- 4 files changed, 25 insertions(+), 38 deletions(-) create mode 100644 centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx delete mode 100644 centrifuge-app/src/pages/Transactions.tsx diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 6aac1a6490..7eef8a3ce4 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -26,12 +26,12 @@ import { isAddress as isValidEVMAddress } from '@ethersproject/address' import * as React from 'react' import { Link, useRouteMatch } from 'react-router-dom' import styled from 'styled-components' +import { TransactionTypeChip } from '../../components/Portfolio/TransactionTypeChip' +import { Spinner } from '../../components/Spinner' import { formatDate } from '../../utils/date' import { getCSVDownloadUrl } from '../../utils/getCSVDownloadUrl' import { useAddress } from '../../utils/useAddress' import { usePool, usePoolMetadata, usePools, useTransactionsByAddress } from '../../utils/usePools' -import { Spinner } from '../Spinner' -import { TransactionTypeChip } from './TransactionTypeChip' export const TRANSACTION_CARD_COLUMNS = `150px 125px 200px 150px 1fr` export const TRANSACTION_CARD_GAP = 4 @@ -41,7 +41,7 @@ type TransactionsProps = { txTypes?: InvestorTransactionType[] } -export function Transactions({ count, txTypes }: TransactionsProps) { +export default function Transactions({ count, txTypes }: TransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() const formattedAddress = address && isValidEVMAddress(address) ? address : formatAddress(address || '') @@ -106,11 +106,7 @@ export function Transactions({ count, txTypes }: TransactionsProps) { return !!paginatedInvestorTransactions.length ? ( - - - {match ? null : 'Transaction history'} - - + Action diff --git a/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx b/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx new file mode 100644 index 0000000000..84b1f9c17b --- /dev/null +++ b/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx @@ -0,0 +1,20 @@ +import { Stack, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { LayoutBase } from '../../components/LayoutBase' +import { BasePadding } from '../../components/LayoutBase/BasePadding' +import Transactions from '../../components/Portfolio/Transactions' + +export default function TransactionHistoryPage() { + return ( + + + + + Transaction history + + + + + + ) +} diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index 06607963c3..49abcc8050 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -7,7 +7,7 @@ import { PoolList } from '../../components/PoolList' import { AssetAllocation } from '../../components/Portfolio/AssetAllocation' import { InvestedTokens } from '../../components/Portfolio/InvestedTokens' import { Rewards } from '../../components/Portfolio/Rewards' -import { Transactions } from '../../components/Portfolio/Transactions' +import Transactions from '../../components/Portfolio/Transactions' import { useAddress } from '../../utils/useAddress' export default function PortfolioPage() { diff --git a/centrifuge-app/src/pages/Transactions.tsx b/centrifuge-app/src/pages/Transactions.tsx deleted file mode 100644 index e4aef61b07..0000000000 --- a/centrifuge-app/src/pages/Transactions.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Box, Stack, Text } from '@centrifuge/fabric' -import * as React from 'react' -import { LayoutBase } from '../components/LayoutBase' -import { BasePadding } from '../components/LayoutBase/BasePadding' -import { Transactions } from '../components/Portfolio/Transactions' -import { useAddress } from '../utils/useAddress' - -export function TransactionsPage() { - const address = useAddress() - return ( - - - - - - Transaction history - - - - {!!address ? ( - - ) : ( - You need to connect your wallet to see your transactions - )} - - - - ) -} From fe1c47e2d1920f4680097f43951c2a080eeccf73 Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 16:17:25 -0400 Subject: [PATCH 20/31] Add pagination and csv export to DataTable --- centrifuge-app/src/components/DataTable.tsx | 165 +++++++++++++------- 1 file changed, 106 insertions(+), 59 deletions(-) diff --git a/centrifuge-app/src/components/DataTable.tsx b/centrifuge-app/src/components/DataTable.tsx index d9bdfaec2d..0d1549046a 100644 --- a/centrifuge-app/src/components/DataTable.tsx +++ b/centrifuge-app/src/components/DataTable.tsx @@ -1,9 +1,22 @@ -import { Grid, IconChevronDown, IconChevronUp, Shelf, Stack, Text } from '@centrifuge/fabric' +import { + AnchorButton, + Box, + Grid, + IconChevronDown, + IconChevronUp, + Pagination, + PaginationContainer, + Shelf, + Stack, + Text, + usePagination, +} from '@centrifuge/fabric' import css from '@styled-system/css' import BN from 'bn.js' import * as React from 'react' import { Link, LinkProps } from 'react-router-dom' import styled from 'styled-components' +import { getCSVDownloadUrl } from '../utils/getCSVDownloadUrl' import { useElementScrollSize } from '../utils/useElementScrollSize' type GroupedProps = { @@ -22,6 +35,8 @@ export type DataTableProps = { summary?: T pageSize?: number page?: number + csvExportData?: Record[] + csvExportFileName?: string } & GroupedProps export type OrderBy = 'asc' | 'desc' @@ -59,7 +74,8 @@ export const DataTable = >({ lastGroupIndex, defaultSortOrder = 'desc', pageSize = Infinity, - page = 1, + csvExportData, + csvExportFileName, }: DataTableProps) => { const [orderBy, setOrderBy] = React.useState>( defaultSortKey ? { [defaultSortKey]: defaultSortOrder } : {} @@ -76,72 +92,102 @@ export const DataTable = >({ setCurrentSortKey(sortKey) } + const pagination = usePagination({ + data, + pageSize, + }) + const sortedAndPaginatedData = React.useMemo(() => { const sortedData = sorter([...data], orderBy[currentSortKey], currentSortKey) - return sortedData.slice((page - 1) * pageSize, page * pageSize) - }, [orderBy, data, currentSortKey, page, pageSize]) + return sortedData.slice((pagination.page - 1) * pageSize, pagination.page * pageSize) + }, [orderBy, data, currentSortKey, pageSize, pagination]) const showHeader = groupIndex === 0 || !groupIndex const templateColumns = `[start] ${columns.map((col) => col.width ?? 'minmax(min-content, 1fr)').join(' ')} [end]` return ( - 0 ? scrollWidth : 'auto'} - > - {showHeader && ( - - {columns.map((col, i) => ( - - - {col?.header && typeof col.header !== 'string' && col?.sortKey && React.isValidElement(col.header) - ? React.cloneElement(col.header as React.ReactElement, { - orderBy: orderBy[col.sortKey], - onClick: () => updateSortOrder(col.sortKey), - }) - : col.header} - - - ))} - - )} - {sortedAndPaginatedData?.map((row, i) => ( - onRowClicked(row))} - key={keyField ? row[keyField] : i} - tabIndex={onRowClicked ? 0 : undefined} - templateColumns={templateColumns} + + + 0 ? scrollWidth : 'auto'} > - {columns.map((col, index) => ( - - {col.cell(row, i)} - + {showHeader && ( + + {columns.map((col, i) => ( + + + {col?.header && typeof col.header !== 'string' && col?.sortKey && React.isValidElement(col.header) + ? React.cloneElement(col.header as React.ReactElement, { + orderBy: orderBy[col.sortKey], + onClick: () => updateSortOrder(col.sortKey), + }) + : col.header} + + + ))} + + )} + {sortedAndPaginatedData?.map((row, i) => ( + onRowClicked(row))} + key={keyField ? row[keyField] : i} + tabIndex={onRowClicked ? 0 : undefined} + templateColumns={templateColumns} + > + {columns.map((col, index) => ( + + {col.cell(row, i)} + + ))} + ))} - - ))} - {/* summary row is not included in sorting */} - {summary && ( - - {columns.map((col, i) => ( - - {col.cell(summary, i)} - - ))} - - )} - {groupIndex != null && groupIndex !== lastGroupIndex && ( - - - - )} - + {/* summary row is not included in sorting */} + {summary && ( + + {columns.map((col, i) => ( + + {col.cell(summary, i)} + + ))} + + )} + {groupIndex != null && groupIndex !== lastGroupIndex && ( + + + + )} + {pagination.pageCount > 1 || csvExportData ? ( + + {pagination.pageCount > 1 && ( + + + + )} + {csvExportData && ( + + + Export as CSV + + + )} + + ) : null} + + + ) } @@ -155,7 +201,8 @@ const Row = styled('div')` grid-template-columns: ${(props) => props.templateColumns}; grid-template-columns: subgrid; grid-column: start / end; - box-shadow: ${({ theme }) => `-1px 0 0 0 ${theme.colors.borderSecondary}, 1px 0 0 0 ${theme.colors.borderSecondary}`}; + box-shadow: ${({ theme, hideBorder }) => + hideBorder ? 'none' : `-1px 0 0 0 ${theme.colors.borderSecondary}, 1px 0 0 0 ${theme.colors.borderSecondary}`}; ` const HeaderRow = styled(Row)( From fc56eb1bfd02ef6afe9e1a01bfd7daf70daa329c Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 16:19:11 -0400 Subject: [PATCH 21/31] Use DataTable for transactions --- centrifuge-app/src/components/DataTable.tsx | 156 ++++----- .../Portfolio/TransactionTypeChip.tsx | 6 +- .../src/components/Portfolio/Transactions.tsx | 331 ++++++------------ .../pages/Portfolio/TransactionHistory.tsx | 2 +- 4 files changed, 194 insertions(+), 301 deletions(-) diff --git a/centrifuge-app/src/components/DataTable.tsx b/centrifuge-app/src/components/DataTable.tsx index 0d1549046a..05e3a28bad 100644 --- a/centrifuge-app/src/components/DataTable.tsx +++ b/centrifuge-app/src/components/DataTable.tsx @@ -108,85 +108,83 @@ export const DataTable = >({ return ( - - 0 ? scrollWidth : 'auto'} - > - {showHeader && ( - - {columns.map((col, i) => ( - - - {col?.header && typeof col.header !== 'string' && col?.sortKey && React.isValidElement(col.header) - ? React.cloneElement(col.header as React.ReactElement, { - orderBy: orderBy[col.sortKey], - onClick: () => updateSortOrder(col.sortKey), - }) - : col.header} - - - ))} - - )} - {sortedAndPaginatedData?.map((row, i) => ( - onRowClicked(row))} - key={keyField ? row[keyField] : i} - tabIndex={onRowClicked ? 0 : undefined} - templateColumns={templateColumns} - > - {columns.map((col, index) => ( - - {col.cell(row, i)} - - ))} - - ))} - {/* summary row is not included in sorting */} - {summary && ( - - {columns.map((col, i) => ( - - {col.cell(summary, i)} - - ))} - - )} - {groupIndex != null && groupIndex !== lastGroupIndex && ( - - - - )} - {pagination.pageCount > 1 || csvExportData ? ( - - {pagination.pageCount > 1 && ( - - - - )} - {csvExportData && ( - - - Export as CSV - - - )} - - ) : null} - - + 0 ? scrollWidth : 'auto'} + > + {showHeader && ( + + {columns.map((col, i) => ( + + + {col?.header && typeof col.header !== 'string' && col?.sortKey && React.isValidElement(col.header) + ? React.cloneElement(col.header as React.ReactElement, { + orderBy: orderBy[col.sortKey], + onClick: () => updateSortOrder(col.sortKey), + }) + : col.header} + + + ))} + + )} + {sortedAndPaginatedData?.map((row, i) => ( + onRowClicked(row))} + key={keyField ? row[keyField] : i} + tabIndex={onRowClicked ? 0 : undefined} + templateColumns={templateColumns} + > + {columns.map((col, index) => ( + + {col.cell(row, i)} + + ))} + + ))} + {/* summary row is not included in sorting */} + {summary && ( + + {columns.map((col, i) => ( + + {col.cell(summary, i)} + + ))} + + )} + {groupIndex != null && groupIndex !== lastGroupIndex && ( + + + + )} + {pagination.pageCount > 1 || csvExportData ? ( + + {pagination.pageCount > 1 && ( + + + + )} + {csvExportData && ( + + + Export as CSV + + + )} + + ) : null} + ) } diff --git a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx index ac863c5e70..8f044b099a 100644 --- a/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx +++ b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx @@ -1,13 +1,13 @@ +import { BorrowerTransactionType, InvestorTransactionType } from '@centrifuge/centrifuge-js' import { StatusChip, StatusChipProps } from '@centrifuge/fabric' import * as React from 'react' -import { TransactionListItemProps } from './Transactions' type TransactionTypeProps = { - type: TransactionListItemProps['type'] + type: InvestorTransactionType | BorrowerTransactionType } const states: { - [Key in TransactionListItemProps['type']]: { + [Key in InvestorTransactionType | BorrowerTransactionType]: { label: string status: StatusChipProps['status'] } diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 7eef8a3ce4..a4d2bcda16 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -1,37 +1,16 @@ -import { - BorrowerTransactionType, - CurrencyBalance, - InvestorTransactionType, - Pool, - TokenBalance, -} from '@centrifuge/centrifuge-js' +import { BorrowerTransactionType, InvestorTransactionType, Token, TokenBalance } from '@centrifuge/centrifuge-js' import { formatBalance, useCentrifugeUtils } from '@centrifuge/centrifuge-react' -import { - AnchorButton, - Box, - Grid, - IconChevronDown, - IconChevronUp, - IconExternalLink, - IconEye, - Pagination, - PaginationContainer, - Shelf, - Stack, - Text, - usePagination, - VisualButton, -} from '@centrifuge/fabric' +import { Box, IconExternalLink, IconEye, Stack, Text, VisualButton } from '@centrifuge/fabric' import { isAddress as isValidEVMAddress } from '@ethersproject/address' import * as React from 'react' import { Link, useRouteMatch } from 'react-router-dom' -import styled from 'styled-components' import { TransactionTypeChip } from '../../components/Portfolio/TransactionTypeChip' import { Spinner } from '../../components/Spinner' import { formatDate } from '../../utils/date' -import { getCSVDownloadUrl } from '../../utils/getCSVDownloadUrl' +import { Dec } from '../../utils/Decimal' import { useAddress } from '../../utils/useAddress' -import { usePool, usePoolMetadata, usePools, useTransactionsByAddress } from '../../utils/usePools' +import { usePools, useTransactionsByAddress } from '../../utils/usePools' +import { Column, DataTable, SortableTableHeader } from '../DataTable' export const TRANSACTION_CARD_COLUMNS = `150px 125px 200px 150px 1fr` export const TRANSACTION_CARD_GAP = 4 @@ -41,53 +20,121 @@ type TransactionsProps = { txTypes?: InvestorTransactionType[] } +type TransactionTableData = Row[] + +type Row = { + action: InvestorTransactionType | BorrowerTransactionType + date: number + tranche: Token | undefined + tranchePrice: string + amount: TokenBalance + hash: string + poolId: string + trancheId: string +} + +const columns: Column[] = [ + { + align: 'left', + header: 'Action', + cell: ({ action }: Row) => , + width: '175px', + }, + { + align: 'left', + header: , + cell: ({ date }: Row) => ( + + {formatDate(date, { + day: '2-digit', + month: '2-digit', + year: '2-digit', + })} + + ), + width: '150px', + sortKey: 'date', + }, + { + align: 'left', + header: 'Token', + cell: ({ tranche }: Row) => ( + + {tranche?.currency.symbol} - ({tranche?.currency.name}) + + ), + width: '250px', + }, + { + align: 'right', + header: 'Token price', + cell: ({ tranche }: Row) => ( + + {formatBalance(tranche?.tokenPrice?.toDecimal() || Dec(1), tranche?.currency.symbol, 3)} + + ), + width: '125px', + }, + { + align: 'right', + header: , + cell: ({ amount, tranche }: Row) => ( + + {formatBalance(amount.toDecimal(), tranche?.currency.symbol || '')} + + ), + width: '125px', + sortKey: 'amount', + }, + { + align: 'left', + header: 'View transaction', + cell: ({ hash }: Row) => { + return ( + + + + ) + }, + width: '200px', + }, +] + export default function Transactions({ count, txTypes }: TransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() const formattedAddress = address && isValidEVMAddress(address) ? address : formatAddress(address || '') - const transactions = useTransactionsByAddress(formatAddress(address || '')) + const transactions = useTransactionsByAddress(formatAddress(formattedAddress)) const match = useRouteMatch('/history') - const [sortKey, setSortKey] = React.useState<'date' | 'amount'>('date') - const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>('desc') - const pagination = usePagination({ - data: transactions?.investorTransactions || [], - pageSize: 10, - }) const pools = usePools() - const investorTransactions: TransactionListItemProps[] = React.useMemo(() => { + const investorTransactions: TransactionTableData = React.useMemo(() => { const txs = transactions?.investorTransactions .slice(0, count || transactions?.investorTransactions.length) .filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) .map((tx) => { + const pool = pools?.find((pool) => pool.id === tx.poolId) + const tranche = pool?.tranches.find((tranche) => tranche.id === tx.trancheId) return { date: new Date(tx.timestamp).getTime(), - type: tx.type, - poolId: tx.poolId, + action: tx.type, + tranche, + tranchePrice: tranche?.tokenPrice?.toDecimal().toString() || '', + amount: tx.currencyAmount, hash: tx.hash, + poolId: tx.poolId, trancheId: tx.trancheId, - amount: tx.currencyAmount, - } - }) - .sort((a, b) => { - if (sortKey === 'date') { - return new Date(b.date).getTime() - new Date(a.date).getTime() - } else if (sortKey === 'amount') { - return b.amount.toDecimal().minus(a.amount.toDecimal()).toNumber() - } else { - return 1 } }) || [] - return sortOrder === 'asc' ? txs.reverse() : txs - }, [sortKey, transactions, sortOrder, txTypes, count]) - - const paginatedInvestorTransactions = React.useMemo(() => { - return investorTransactions.slice( - (pagination.page - 1) * pagination.pageSize, - pagination.page * pagination.pageSize - ) - }, [investorTransactions, pagination]) + return txs + }, [transactions, txTypes, count]) const csvData: any = React.useMemo(() => { if (!investorTransactions || !investorTransactions?.length) { @@ -97,75 +144,23 @@ export default function Transactions({ count, txTypes }: TransactionsProps) { const pool = pools?.find((pool) => pool.id === entry.poolId) return { 'Transaction date': `"${formatDate(entry.date)}"`, - Action: entry.type, + Action: entry.action, Token: pool ? pool.tranches.find(({ id }) => id === entry.trancheId)?.currency.name : undefined, Amount: pool ? `"${formatBalance(entry.amount.toDecimal(), pool.currency.symbol)}"` : undefined, } }) }, [investorTransactions]) - return !!paginatedInvestorTransactions.length ? ( - + return !!investorTransactions.length ? ( + - - - Action - { - setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') - setSortKey('date') - }} - gap={1} - > - Transaction date - - - - - - - Token - - { - setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') - setSortKey('amount') - }} - gap={1} - justifyContent="flex-end" - > - Amount - - - - - - - - - {paginatedInvestorTransactions.map((transaction, index) => ( - - - - ))} - - + {match ? null : ( @@ -175,109 +170,9 @@ export default function Transactions({ count, txTypes }: TransactionsProps) { )} - - {match && pagination.pageCount > 1 && ( - - - - )} - {!match ? null : ( - - Export as CSV - - )} - - + ) : ( ) } - -export type TransactionListItemProps = { - date: number - type: InvestorTransactionType | BorrowerTransactionType - amount: CurrencyBalance | TokenBalance - poolId: string - hash: string - trancheId?: string -} - -export function TransactionListItem({ date, type, amount, poolId, hash, trancheId }: TransactionListItemProps) { - const pool = usePool(poolId) as Pool - const { data } = usePoolMetadata(pool) - const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined - const subScanUrl = import.meta.env.REACT_APP_SUBSCAN_URL - - if (!pool || !data) { - return null - } - - return !!subScanUrl && !!hash ? ( - - - - - - - - {formatDate(date, { - day: '2-digit', - month: '2-digit', - year: '2-digit', - })} - - - - - {!!token ? token?.currency?.name.split(`${data?.pool?.name} ` || '').at(-1) : data.pool?.name} - - {!!token && ( - - {data?.pool?.name} - - )} - - - - - {formatBalance(amount.toDecimal(), pool.currency.symbol)} - - - - {!!subScanUrl && !!hash && ( - - - - )} - - - ) : null -} - -const SortButton = styled(Shelf)` - background: initial; - border: none; - cursor: pointer; - display: inline-flex; - align-items: flex-start; -` diff --git a/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx b/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx index 84b1f9c17b..e6fa5e9ea2 100644 --- a/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx +++ b/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx @@ -8,7 +8,7 @@ export default function TransactionHistoryPage() { return ( - + Transaction history From 84efc6b12f89f85eb13ae343e00173e34e8d8e07 Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 16:21:27 -0400 Subject: [PATCH 22/31] Add title to tx table --- .../src/components/Portfolio/Transactions.tsx | 39 +++++++++++-------- .../pages/Portfolio/TransactionHistory.tsx | 8 +--- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index a4d2bcda16..856febc84a 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -152,24 +152,29 @@ export default function Transactions({ count, txTypes }: TransactionsProps) { }, [investorTransactions]) return !!investorTransactions.length ? ( - + + + Transaction history + - - {match ? null : ( - - - - View all - - - - )} + + + {match ? null : ( + + + + View all + + + + )} + ) : ( diff --git a/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx b/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx index e6fa5e9ea2..72a0748856 100644 --- a/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx +++ b/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx @@ -1,4 +1,3 @@ -import { Stack, Text } from '@centrifuge/fabric' import * as React from 'react' import { LayoutBase } from '../../components/LayoutBase' import { BasePadding } from '../../components/LayoutBase/BasePadding' @@ -8,12 +7,7 @@ export default function TransactionHistoryPage() { return ( - - - Transaction history - - - + ) From d5e8ad10f8ca818053420263ba82390e0d63265d Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 17:15:34 -0400 Subject: [PATCH 23/31] Convert invested token table to new style --- .../components/Portfolio/InvestedTokens.tsx | 213 ++++++++++++------ .../components/Portfolio/TokenListItem.tsx | 100 -------- 2 files changed, 146 insertions(+), 167 deletions(-) delete mode 100644 centrifuge-app/src/components/Portfolio/TokenListItem.tsx diff --git a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx index 3dc3f146a0..fcd2a630fd 100644 --- a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx +++ b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx @@ -1,30 +1,143 @@ -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: ({ currency, poolId }: 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 ( + + + {icon ? ( + + ) : ( + + )} + + + + {currency.name} + + + ) + }, + width: '250px', + }, + { + align: 'left', + header: 'Token price', + cell: ({ tokenPrice }: Row) => { + return ( + + {formatBalance(tokenPrice.toDecimal() || 1, 'USDT', 3)} + + ) + }, + width: '150px', + }, + { + align: 'right', + header: , + cell: ({ currency, position }: Row) => { + return ( + + {formatBalance(position, currency.symbol)} + + ) + }, + width: '125px', + sortKey: 'position', + }, + { + align: 'right', + header: , + cell: ({ marketValue }: Row) => { + return ( + + {formatBalance(marketValue, 'USDT', 4)} + + ) + }, + width: '175px', + sortKey: 'marketValue', + }, + { + align: 'left', + header: '', // invest redeem buttons + cell: ({ canInvestRedeem, poolId }: Row) => { + const isTinlakePool = poolId.startsWith('0x') + return ( + canInvestRedeem && ( + + {isTinlakePool ? ( + + View on Tinlake + + ) : ( + <> + + + + )} + + ) + ) + }, + width: '300px', + }, +] + // 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' }) - const address = useAddress() const centBalances = useBalances(address) const { data: tinlakeBalances } = useTinlakeBalances() - - const { data: tinlakePools } = useTinlakePools() const pools = usePools() const balances = useMemo(() => { @@ -34,61 +147,27 @@ 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 ? ( Portfolio - - - - Token - - - handleSort('position')}> - Position - - - - - Token price - - - handleSort('market-value')}> - Market Value - - - - - - {balances.map((balance, index) => ( - - ))} - + ) : null diff --git a/centrifuge-app/src/components/Portfolio/TokenListItem.tsx b/centrifuge-app/src/components/Portfolio/TokenListItem.tsx deleted file mode 100644 index 2257e7beff..0000000000 --- a/centrifuge-app/src/components/Portfolio/TokenListItem.tsx +++ /dev/null @@ -1,100 +0,0 @@ -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 - - ) : ( - <> - - - - )} - - )} - - - ) -} From 579131067bc051bc27cde181497085946f9b213d Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 17:17:35 -0400 Subject: [PATCH 24/31] Adjust percision and table width --- centrifuge-app/src/components/Portfolio/InvestedTokens.tsx | 4 ++-- centrifuge-app/src/components/Portfolio/Transactions.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx index fcd2a630fd..bec4dfc61d 100644 --- a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx +++ b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx @@ -65,7 +65,7 @@ const columns: Column[] = [ cell: ({ tokenPrice }: Row) => { return ( - {formatBalance(tokenPrice.toDecimal() || 1, 'USDT', 3)} + {formatBalance(tokenPrice.toDecimal() || 1, 'USDT', 4)} ) }, @@ -129,7 +129,7 @@ const columns: Column[] = [ ) ) }, - width: '300px', + width: '325px', }, ] diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 856febc84a..8cbcbd7df6 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -70,7 +70,7 @@ const columns: Column[] = [ header: 'Token price', cell: ({ tranche }: Row) => ( - {formatBalance(tranche?.tokenPrice?.toDecimal() || Dec(1), tranche?.currency.symbol, 3)} + {formatBalance(tranche?.tokenPrice?.toDecimal() || Dec(1), tranche?.currency.symbol, 4)} ), width: '125px', From 7ee615a6071686de97cfbca876773434152788c7 Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 17:18:19 -0400 Subject: [PATCH 25/31] Fix border --- centrifuge-app/src/components/Portfolio/InvestedTokens.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx index bec4dfc61d..22f193b596 100644 --- a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx +++ b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx @@ -166,9 +166,7 @@ export const InvestedTokens = ({ canInvestRedeem = false }) => { Portfolio - - - + ) : null } From 91bd04b9d448a42882f3d248bbd9aaa69e9f0ae3 Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 17:24:57 -0400 Subject: [PATCH 26/31] Fix linting --- .../components/Portfolio/InvestedTokens.tsx | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx index 22f193b596..9b311aefa1 100644 --- a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx +++ b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx @@ -35,27 +35,8 @@ const columns: Column[] = [ { align: 'left', header: 'Token', - cell: ({ currency, poolId }: 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 ( - - - {icon ? ( - - ) : ( - - )} - - - - {currency.name} - - - ) + cell: (token: Row) => { + return }, width: '250px', }, @@ -170,3 +151,26 @@ export const InvestedTokens = ({ canInvestRedeem = false }) => { ) : null } + +const Token = ({ 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 ( + + + {icon ? ( + + ) : ( + + )} + + + + {currency.name} + + + ) +} From c34aeaaaddedf3ba0d8263b91d8e054d2a22799c Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 17:28:36 -0400 Subject: [PATCH 27/31] Fix pool list key --- centrifuge-app/src/components/PoolList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrifuge-app/src/components/PoolList.tsx b/centrifuge-app/src/components/PoolList.tsx index 2e6acbeb67..2e2caa05a1 100644 --- a/centrifuge-app/src/components/PoolList.tsx +++ b/centrifuge-app/src/components/PoolList.tsx @@ -95,7 +95,7 @@ export function PoolList() { )) : filteredPools.map((pool) => ( - + ))} From 59fa1100be18cfe831597dfeb820bda9cc63648a Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 17:31:06 -0400 Subject: [PATCH 28/31] Clean up --- centrifuge-app/src/components/Portfolio/Transactions.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 8cbcbd7df6..229cee6c5c 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -12,9 +12,6 @@ import { useAddress } from '../../utils/useAddress' import { usePools, useTransactionsByAddress } from '../../utils/usePools' import { Column, DataTable, SortableTableHeader } from '../DataTable' -export const TRANSACTION_CARD_COLUMNS = `150px 125px 200px 150px 1fr` -export const TRANSACTION_CARD_GAP = 4 - type TransactionsProps = { count?: number txTypes?: InvestorTransactionType[] @@ -136,7 +133,7 @@ export default function Transactions({ count, txTypes }: TransactionsProps) { return txs }, [transactions, txTypes, count]) - const csvData: any = React.useMemo(() => { + const csvData = React.useMemo(() => { if (!investorTransactions || !investorTransactions?.length) { return undefined } From f78c8084084c4a70249882d4916997a2083cc14d Mon Sep 17 00:00:00 2001 From: sophian Date: Fri, 27 Oct 2023 17:32:12 -0400 Subject: [PATCH 29/31] Lint fix --- centrifuge-app/src/components/Portfolio/InvestedTokens.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx index 9b311aefa1..4f54271e0b 100644 --- a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx +++ b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx @@ -36,7 +36,7 @@ const columns: Column[] = [ align: 'left', header: 'Token', cell: (token: Row) => { - return + return }, width: '250px', }, @@ -152,7 +152,7 @@ export const InvestedTokens = ({ canInvestRedeem = false }) => { ) : null } -const Token = ({ poolId, currency }: Row) => { +const TokenWithIcon = ({ poolId, currency }: Row) => { const pool = usePool(poolId, false) const { data: metadata } = usePoolMetadata(pool) const cent = useCentrifuge() From 076a9158382654c30d476e81bfba7a3efa7d9fdc Mon Sep 17 00:00:00 2001 From: Onno Visser <23527729+onnovisser@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:19:43 +0100 Subject: [PATCH 30/31] refactor --- centrifuge-app/src/components/DataTable.tsx | 167 +++++++----------- .../src/components/Portfolio/Transactions.tsx | 72 +++++--- .../pages/Portfolio/TransactionHistory.tsx | 2 +- centrifuge-app/src/pages/Portfolio/index.tsx | 4 +- .../src/components/Pagination/Pagination.tsx | 34 ++-- 5 files changed, 126 insertions(+), 153 deletions(-) diff --git a/centrifuge-app/src/components/DataTable.tsx b/centrifuge-app/src/components/DataTable.tsx index 05e3a28bad..d9bdfaec2d 100644 --- a/centrifuge-app/src/components/DataTable.tsx +++ b/centrifuge-app/src/components/DataTable.tsx @@ -1,22 +1,9 @@ -import { - AnchorButton, - Box, - Grid, - IconChevronDown, - IconChevronUp, - Pagination, - PaginationContainer, - Shelf, - Stack, - Text, - usePagination, -} from '@centrifuge/fabric' +import { Grid, IconChevronDown, IconChevronUp, Shelf, Stack, Text } from '@centrifuge/fabric' import css from '@styled-system/css' import BN from 'bn.js' import * as React from 'react' import { Link, LinkProps } from 'react-router-dom' import styled from 'styled-components' -import { getCSVDownloadUrl } from '../utils/getCSVDownloadUrl' import { useElementScrollSize } from '../utils/useElementScrollSize' type GroupedProps = { @@ -35,8 +22,6 @@ export type DataTableProps = { summary?: T pageSize?: number page?: number - csvExportData?: Record[] - csvExportFileName?: string } & GroupedProps export type OrderBy = 'asc' | 'desc' @@ -74,8 +59,7 @@ export const DataTable = >({ lastGroupIndex, defaultSortOrder = 'desc', pageSize = Infinity, - csvExportData, - csvExportFileName, + page = 1, }: DataTableProps) => { const [orderBy, setOrderBy] = React.useState>( defaultSortKey ? { [defaultSortKey]: defaultSortOrder } : {} @@ -92,100 +76,72 @@ export const DataTable = >({ setCurrentSortKey(sortKey) } - const pagination = usePagination({ - data, - pageSize, - }) - const sortedAndPaginatedData = React.useMemo(() => { const sortedData = sorter([...data], orderBy[currentSortKey], currentSortKey) - return sortedData.slice((pagination.page - 1) * pageSize, pagination.page * pageSize) - }, [orderBy, data, currentSortKey, pageSize, pagination]) + return sortedData.slice((page - 1) * pageSize, page * pageSize) + }, [orderBy, data, currentSortKey, page, pageSize]) const showHeader = groupIndex === 0 || !groupIndex const templateColumns = `[start] ${columns.map((col) => col.width ?? 'minmax(min-content, 1fr)').join(' ')} [end]` return ( - - 0 ? scrollWidth : 'auto'} - > - {showHeader && ( - - {columns.map((col, i) => ( - - - {col?.header && typeof col.header !== 'string' && col?.sortKey && React.isValidElement(col.header) - ? React.cloneElement(col.header as React.ReactElement, { - orderBy: orderBy[col.sortKey], - onClick: () => updateSortOrder(col.sortKey), - }) - : col.header} - - - ))} - - )} - {sortedAndPaginatedData?.map((row, i) => ( - onRowClicked(row))} - key={keyField ? row[keyField] : i} - tabIndex={onRowClicked ? 0 : undefined} - templateColumns={templateColumns} - > - {columns.map((col, index) => ( - - {col.cell(row, i)} - - ))} - - ))} - {/* summary row is not included in sorting */} - {summary && ( - - {columns.map((col, i) => ( - - {col.cell(summary, i)} - - ))} - - )} - {groupIndex != null && groupIndex !== lastGroupIndex && ( - - - - )} - {pagination.pageCount > 1 || csvExportData ? ( - - {pagination.pageCount > 1 && ( - - - - )} - {csvExportData && ( - - - Export as CSV - - - )} - - ) : null} - - + 0 ? scrollWidth : 'auto'} + > + {showHeader && ( + + {columns.map((col, i) => ( + + + {col?.header && typeof col.header !== 'string' && col?.sortKey && React.isValidElement(col.header) + ? React.cloneElement(col.header as React.ReactElement, { + orderBy: orderBy[col.sortKey], + onClick: () => updateSortOrder(col.sortKey), + }) + : col.header} + + + ))} + + )} + {sortedAndPaginatedData?.map((row, i) => ( + onRowClicked(row))} + key={keyField ? row[keyField] : i} + tabIndex={onRowClicked ? 0 : undefined} + templateColumns={templateColumns} + > + {columns.map((col, index) => ( + + {col.cell(row, i)} + + ))} + + ))} + {/* summary row is not included in sorting */} + {summary && ( + + {columns.map((col, i) => ( + + {col.cell(summary, i)} + + ))} + + )} + {groupIndex != null && groupIndex !== lastGroupIndex && ( + + + + )} + ) } @@ -199,8 +155,7 @@ const Row = styled('div')` grid-template-columns: ${(props) => props.templateColumns}; grid-template-columns: subgrid; grid-column: start / end; - box-shadow: ${({ theme, hideBorder }) => - hideBorder ? 'none' : `-1px 0 0 0 ${theme.colors.borderSecondary}, 1px 0 0 0 ${theme.colors.borderSecondary}`}; + box-shadow: ${({ theme }) => `-1px 0 0 0 ${theme.colors.borderSecondary}, 1px 0 0 0 ${theme.colors.borderSecondary}`}; ` const HeaderRow = styled(Row)( diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 229cee6c5c..972d9d7a88 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -1,19 +1,32 @@ import { BorrowerTransactionType, InvestorTransactionType, Token, TokenBalance } from '@centrifuge/centrifuge-js' import { formatBalance, useCentrifugeUtils } from '@centrifuge/centrifuge-react' -import { Box, IconExternalLink, IconEye, Stack, Text, VisualButton } from '@centrifuge/fabric' +import { + AnchorButton, + Box, + IconExternalLink, + IconEye, + Pagination, + PaginationProvider, + Shelf, + Stack, + Text, + usePagination, + VisualButton, +} from '@centrifuge/fabric' import { isAddress as isValidEVMAddress } from '@ethersproject/address' import * as React from 'react' -import { Link, useRouteMatch } from 'react-router-dom' +import { Link } from 'react-router-dom' import { TransactionTypeChip } from '../../components/Portfolio/TransactionTypeChip' import { Spinner } from '../../components/Spinner' import { formatDate } from '../../utils/date' import { Dec } from '../../utils/Decimal' +import { getCSVDownloadUrl } from '../../utils/getCSVDownloadUrl' import { useAddress } from '../../utils/useAddress' import { usePools, useTransactionsByAddress } from '../../utils/usePools' import { Column, DataTable, SortableTableHeader } from '../DataTable' type TransactionsProps = { - count?: number + onlyMostRecent?: boolean txTypes?: InvestorTransactionType[] } @@ -35,7 +48,6 @@ const columns: Column[] = [ align: 'left', header: 'Action', cell: ({ action }: Row) => , - width: '175px', }, { align: 'left', @@ -49,7 +61,6 @@ const columns: Column[] = [ })} ), - width: '150px', sortKey: 'date', }, { @@ -60,7 +71,6 @@ const columns: Column[] = [ {tranche?.currency.symbol} - ({tranche?.currency.name}) ), - width: '250px', }, { align: 'right', @@ -70,7 +80,6 @@ const columns: Column[] = [ {formatBalance(tranche?.tokenPrice?.toDecimal() || Dec(1), tranche?.currency.symbol, 4)} ), - width: '125px', }, { align: 'right', @@ -80,11 +89,10 @@ const columns: Column[] = [ {formatBalance(amount.toDecimal(), tranche?.currency.symbol || '')} ), - width: '125px', sortKey: 'amount', }, { - align: 'left', + align: 'center', header: 'View transaction', cell: ({ hash }: Row) => { return ( @@ -99,22 +107,20 @@ const columns: Column[] = [ ) }, - width: '200px', }, ] -export default function Transactions({ count, txTypes }: TransactionsProps) { +export function Transactions({ onlyMostRecent, txTypes }: TransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() const formattedAddress = address && isValidEVMAddress(address) ? address : formatAddress(address || '') const transactions = useTransactionsByAddress(formatAddress(formattedAddress)) - const match = useRouteMatch('/history') const pools = usePools() const investorTransactions: TransactionTableData = React.useMemo(() => { const txs = transactions?.investorTransactions - .slice(0, count || transactions?.investorTransactions.length) + .slice(0, onlyMostRecent ? 3 : transactions?.investorTransactions.length) .filter((tx) => (txTypes ? txTypes?.includes(tx.type) : tx)) .map((tx) => { const pool = pools?.find((pool) => pool.id === tx.poolId) @@ -131,7 +137,7 @@ export default function Transactions({ count, txTypes }: TransactionsProps) { } }) || [] return txs - }, [transactions, txTypes, count]) + }, [transactions?.investorTransactions, onlyMostRecent, txTypes, pools]) const csvData = React.useMemo(() => { if (!investorTransactions || !investorTransactions?.length) { @@ -142,27 +148,30 @@ export default function Transactions({ count, txTypes }: TransactionsProps) { return { 'Transaction date': `"${formatDate(entry.date)}"`, Action: entry.action, - Token: pool ? pool.tranches.find(({ id }) => id === entry.trancheId)?.currency.name : undefined, - Amount: pool ? `"${formatBalance(entry.amount.toDecimal(), pool.currency.symbol)}"` : undefined, + Token: (pool && pool.tranches.find(({ id }) => id === entry.trancheId)?.currency.name) ?? '', + Amount: (pool && `"${formatBalance(entry.amount.toDecimal(), pool.currency.symbol)}"`) ?? '', } }) - }, [investorTransactions]) + }, [investorTransactions, pools]) + + const csvUrl = React.useMemo(() => csvData && getCSVDownloadUrl(csvData), [csvData]) + + const pagination = usePagination({ data: investorTransactions, pageSize: onlyMostRecent ? 3 : 15 }) return !!investorTransactions.length ? ( - + Transaction history - + - {match ? null : ( + {onlyMostRecent ? ( @@ -170,9 +179,24 @@ export default function Transactions({ count, txTypes }: TransactionsProps) { + ) : ( + + {pagination.pageCount > 1 && ( + + + + )} + {csvUrl && ( + + + Export as CSV + + + )} + )} - + ) : ( diff --git a/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx b/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx index 72a0748856..5247028d2f 100644 --- a/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx +++ b/centrifuge-app/src/pages/Portfolio/TransactionHistory.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { LayoutBase } from '../../components/LayoutBase' import { BasePadding } from '../../components/LayoutBase/BasePadding' -import Transactions from '../../components/Portfolio/Transactions' +import { Transactions } from '../../components/Portfolio/Transactions' export default function TransactionHistoryPage() { return ( diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index 49abcc8050..1c9f49e0b8 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -7,7 +7,7 @@ import { PoolList } from '../../components/PoolList' import { AssetAllocation } from '../../components/Portfolio/AssetAllocation' import { InvestedTokens } from '../../components/Portfolio/InvestedTokens' import { Rewards } from '../../components/Portfolio/Rewards' -import Transactions from '../../components/Portfolio/Transactions' +import { Transactions } from '../../components/Portfolio/Transactions' import { useAddress } from '../../utils/useAddress' export default function PortfolioPage() { @@ -52,7 +52,7 @@ function Portfolio() { - + diff --git a/fabric/src/components/Pagination/Pagination.tsx b/fabric/src/components/Pagination/Pagination.tsx index c77392f68c..fdbd35eb92 100644 --- a/fabric/src/components/Pagination/Pagination.tsx +++ b/fabric/src/components/Pagination/Pagination.tsx @@ -84,16 +84,13 @@ export function Pagination({ pagination }: { pagination?: PaginationState }) { return ( - goToFirst()} - disabled={!canPreviousPage} - aria-label="first page" - style={{ visibility: firstShown > 1 ? 'visible' : 'hidden' }} - > - - - - + {firstShown > 1 && ( + goToFirst()} disabled={!canPreviousPage} aria-label="first page"> + + + + + )} goToPrevious()} disabled={!canPreviousPage} aria-label="previous page"> @@ -126,16 +123,13 @@ export function Pagination({ pagination }: { pagination?: PaginationState }) { - goToLast()} - disabled={!canNextPage} - aria-label="last page" - style={{ visibility: lastShown < pageCount ? 'visible' : 'hidden' }} - > - - - - + {lastShown < pageCount && ( + goToLast()} disabled={!canNextPage} aria-label="last page"> + + + + + )} ) } From 38f9ef21b0c636682f1d2dcd74c30359a4408f46 Mon Sep 17 00:00:00 2001 From: Onno Visser <23527729+onnovisser@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:38:39 +0100 Subject: [PATCH 31/31] cleanup --- .../components/Portfolio/InvestedTokens.tsx | 9 +--- .../src/components/Portfolio/sortTokens.ts | 50 ------------------- 2 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 centrifuge-app/src/components/Portfolio/sortTokens.ts diff --git a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx index 4f54271e0b..a028f3ddf5 100644 --- a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx +++ b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx @@ -38,10 +38,9 @@ const columns: Column[] = [ cell: (token: Row) => { return }, - width: '250px', + width: '2fr', }, { - align: 'left', header: 'Token price', cell: ({ tokenPrice }: Row) => { return ( @@ -50,10 +49,8 @@ const columns: Column[] = [ ) }, - width: '150px', }, { - align: 'right', header: , cell: ({ currency, position }: Row) => { return ( @@ -62,11 +59,9 @@ const columns: Column[] = [ ) }, - width: '125px', sortKey: 'position', }, { - align: 'right', header: , cell: ({ marketValue }: Row) => { return ( @@ -75,7 +70,6 @@ const columns: Column[] = [ ) }, - width: '175px', sortKey: 'marketValue', }, { @@ -110,7 +104,6 @@ const columns: Column[] = [ ) ) }, - width: '325px', }, ] diff --git a/centrifuge-app/src/components/Portfolio/sortTokens.ts b/centrifuge-app/src/components/Portfolio/sortTokens.ts deleted file mode 100644 index ed12057e09..0000000000 --- a/centrifuge-app/src/components/Portfolio/sortTokens.ts +++ /dev/null @@ -1,50 +0,0 @@ -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 -}