Skip to content

Commit

Permalink
Merge branch 'main' of github.com:centrifuge/apps into runtime-upgrad…
Browse files Browse the repository at this point in the history
…e-1038
  • Loading branch information
sophialittlejohn committed Feb 26, 2024
2 parents 3be1570 + da6762d commit 1bd4d79
Show file tree
Hide file tree
Showing 22 changed files with 260 additions and 97 deletions.
2 changes: 1 addition & 1 deletion centrifuge-app/.env-config/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ REACT_APP_ONBOARDING_API_URL=https://europe-central2-centrifuge-production-x.clo
REACT_APP_PINNING_API_URL=https://europe-central2-centrifuge-production-x.cloudfunctions.net/pinning-api-production
REACT_APP_POOL_CREATION_TYPE=propose
REACT_APP_RELAY_WSS_URL=wss://rpc.polkadot.io
REACT_APP_SUBQUERY_URL=https://api.subquery.network/sq/centrifuge/pools-centrifuge
REACT_APP_SUBQUERY_URL=https://api.subquery.network/sq/centrifuge/pools
REACT_APP_SUBSCAN_URL=https://centrifuge.subscan.io
REACT_APP_TINLAKE_NETWORK=mainnet
REACT_APP_INFURA_KEY=bf808e7d3d924fbeb74672d9341d0550
Expand Down
3 changes: 3 additions & 0 deletions centrifuge-app/src/assets/images/currency-dollar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions centrifuge-app/src/components/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,19 @@ type GroupedProps = {

export type DataTableProps<T = any> = {
data: Array<T>
/**
* pinnedData is not included in sorting and will be pinned to the top of the table in the order provided
* */
pinnedData?: Array<T>
columns: Column[]
keyField?: string
onRowClicked?: (row: T) => string | LinkProps['to']
defaultSortKey?: string
defaultSortOrder?: OrderBy
hoverable?: boolean
/**
* summary row is not included in sorting
*/
summary?: T
pageSize?: number
page?: number
Expand Down Expand Up @@ -88,6 +95,7 @@ const sorter = <T extends Record<string, any>>(data: Array<T>, order: OrderBy, s

export const DataTable = <T extends Record<string, any>>({
data,
pinnedData,
columns,
keyField,
onRowClicked,
Expand Down Expand Up @@ -140,6 +148,21 @@ export const DataTable = <T extends Record<string, any>>({
))}
</HeaderRow>
)}
{pinnedData?.map((row, i) => (
<DataRow
hoverable={hoverable}
as={onRowClicked ? Link : 'div'}
to={onRowClicked && (() => onRowClicked(row))}
key={keyField ? row[keyField] : i}
tabIndex={onRowClicked ? 0 : undefined}
>
{columns.map((col, index) => (
<DataCol variant="body2" align={col?.align} key={index}>
{col.cell(row, i)}
</DataCol>
))}
</DataRow>
))}
{sortedAndPaginatedData?.map((row, i) => (
<DataRow
hoverable={hoverable}
Expand Down
5 changes: 4 additions & 1 deletion centrifuge-app/src/components/LoanLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { StatusChip } from '@centrifuge/fabric'
import * as React from 'react'
import { daysBetween } from '../utils/date'

type LabelStatus = 'default' | 'info' | 'ok' | 'warning' | 'critical'
type LabelStatus = 'default' | 'info' | 'ok' | 'warning' | 'critical' | ''

interface Props {
loan: Loan | TinlakeLoan
Expand All @@ -12,6 +12,7 @@ interface Props {
export function getLoanLabelStatus(l: Loan | TinlakeLoan, isExternalAssetRepaid?: boolean): [LabelStatus, string] {
const today = new Date()
today.setUTCHours(0, 0, 0, 0)
if (!l.status) return ['', '']
if (l.status === 'Active' && (l as ActiveLoan).writeOffStatus) return ['critical', 'Write-off']
if (l.status === 'Closed' || isExternalAssetRepaid) return ['ok', 'Repaid']
if (
Expand Down Expand Up @@ -44,7 +45,9 @@ const LoanLabel: React.FC<Props> = ({ loan }) => {
: null

const isExternalAssetRepaid = currentFace?.isZero() && loan.status === 'Active'
const isCashAsset = 'valuationMethod' in loan.pricing && loan.pricing?.valuationMethod === 'cash'
const [status, text] = getLoanLabelStatus(loan, isExternalAssetRepaid)
if (!status || isCashAsset) return null
return <StatusChip status={status}>{text}</StatusChip>
}

Expand Down
111 changes: 98 additions & 13 deletions centrifuge-app/src/components/LoanList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Loan, TinlakeLoan } from '@centrifuge/centrifuge-js'
import { CurrencyBalance, Loan, Rate, TinlakeLoan } from '@centrifuge/centrifuge-js'
import {
Box,
IconChevronRight,
Expand All @@ -11,8 +11,11 @@ import {
Thumbnail,
usePagination,
} from '@centrifuge/fabric'
import get from 'lodash/get'
import * as React from 'react'
import { useParams, useRouteMatch } from 'react-router'
import currencyDollar from '../assets/images/currency-dollar.svg'
import usdcLogo from '../assets/images/usdc-logo.svg'
import { formatNftAttribute } from '../pages/Loan/utils'
import { nftMetadataSchema } from '../schemas'
import { LoanTemplate, LoanTemplateAttribute } from '../types'
Expand All @@ -27,10 +30,13 @@ import { Column, DataTable, FilterableTableHeader, SortableTableHeader } from '.
import { LoadBoundary } from './LoadBoundary'
import LoanLabel, { getLoanLabelStatus } from './LoanLabel'
import { prefetchRoute } from './Root'
import { Tooltips } from './Tooltips'

type Row = (Loan | TinlakeLoan) & {
idSortKey: number
originationDateSortKey: string
maturityDate: string | null
status: 'Created' | 'Active' | 'Closed' | ''
}

type Props = {
Expand Down Expand Up @@ -64,10 +70,25 @@ export function LoanList({ loans }: Props) {
const templateIds = poolMetadata?.loanTemplates?.map((s) => s.id) ?? []
const templateId = templateIds.at(-1)
const { data: templateMetadata } = useMetadata<LoanTemplate>(templateId)
const loansWithLabelStatus = loans.map((loan) => ({
...loan,
labelStatus: getLoanStatus(loan),
}))
const loansWithLabelStatus = React.useMemo(() => {
return loans
.map((loan) => ({
...loan,
labelStatus: getLoanStatus(loan),
}))
.sort((a, b) => {
const aValuation = get(a, 'pricing.valuationMethod')
const bValuation = get(b, 'pricing.valuationMethod')
const aId = get(a, 'id') as string
const bId = get(b, 'id') as string

if (aValuation === 'cash' && bValuation !== 'cash') return -1
if (aValuation !== 'cash' && bValuation === 'cash') return 1
if (aValuation === 'cash' && bValuation === 'cash') return aId.localeCompare(bId)

return aId.localeCompare(bId)
})
}, [loans])
const filters = useFilters({
data: loansWithLabelStatus,
})
Expand Down Expand Up @@ -113,15 +134,15 @@ export function LoanList({ loans }: Props) {
return l.originationDate && (l.poolId.startsWith('0x') || l.status === 'Active')
? // @ts-expect-error
formatDate(l.originationDate)
: ''
: '-'
},
sortKey: 'originationDateSortKey',
},
]),
{
align: 'left',
header: <SortableTableHeader label="Maturity date" />,
cell: (l: Row) => (l.pricing.maturityDate ? formatDate(l.pricing.maturityDate) : ''),
cell: (l: Row) => (l?.maturityDate ? formatDate(l.maturityDate) : '-'),
sortKey: 'maturityDate',
},
{
Expand All @@ -144,7 +165,7 @@ export function LoanList({ loans }: Props) {
},
{
header: '',
cell: () => <IconChevronRight size={24} color="textPrimary" />,
cell: (l: Row) => (l.status ? <IconChevronRight size={24} color="textPrimary" /> : ''),
width: '52px',
},
].filter(Boolean) as Column[]
Expand All @@ -161,10 +182,33 @@ export function LoanList({ loans }: Props) {
!loan?.totalBorrowed?.isZero()
? loan.originationDate
: '',
maturityDate: loan.pricing.maturityDate,
maturityDate:
'valuationMethod' in loan.pricing && loan.pricing.valuationMethod === 'cash' ? null : loan.pricing.maturityDate,
...loan,
}))

const pinnedData: Row[] = [
{
id: 'reserve',
// @ts-expect-error
status: '',
poolId: pool.id,
pricing: {
valuationMethod: 'discountedCashFlow',
maxBorrowAmount: 'upToTotalBorrowed',
value: CurrencyBalance.fromFloat(0, 18),
maturityDate: '',
maturityExtensionDays: 0,
advanceRate: Rate.fromFloat(0),
interestRate: Rate.fromFloat(0),
},
asset: { collectionId: '', nftId: '' },
totalBorrowed: CurrencyBalance.fromFloat(0, 18),
totalRepaid: CurrencyBalance.fromFloat(0, 18),
outstandingDebt: CurrencyBalance.fromFloat(0, 18),
},
]

const pagination = usePagination({ data: rows, pageSize: 20 })

return (
Expand All @@ -175,9 +219,11 @@ export function LoanList({ loans }: Props) {
<DataTable
data={rows}
columns={columns}
defaultSortKey="idSortKey"
pinnedData={pinnedData}
defaultSortOrder="desc"
onRowClicked={(row) => `${basePath}/${poolId}/assets/${row.id}`}
onRowClicked={(row) =>
row.status ? `${basePath}/${poolId}/assets/${row.id}` : `${basePath}/${poolId}/assets`
}
pageSize={20}
page={pagination.page}
/>
Expand Down Expand Up @@ -216,14 +262,49 @@ export function AssetName({ loan }: { loan: Pick<Row, 'id' | 'poolId' | 'asset'>
const isTinlakePool = loan.poolId.startsWith('0x')
const nft = useCentNFT(loan.asset.collectionId, loan.asset.nftId, false, isTinlakePool)
const { data: metadata, isLoading } = useMetadata(nft?.metadataUri, nftMetadataSchema)
if (loan.id === 'reserve') {
return (
<Shelf gap="1" alignItems="center" justifyContent="center" style={{ whiteSpace: 'nowrap', maxWidth: '100%' }}>
<Shelf height="24px" width="24px" alignItems="center" justifyContent="center">
<Box as="img" src={usdcLogo} alt="" height="13px" width="13px" />
</Shelf>
<TextWithPlaceholder
isLoading={isLoading}
width={12}
variant="body2"
style={{ overflow: 'hidden', maxWidth: '300px', textOverflow: 'ellipsis' }}
>
<Tooltips type="onchainReserve" label={<Text variant="body2">Onchain reserve</Text>} />
</TextWithPlaceholder>
</Shelf>
)
}

if (loan.status === 'Active' && 'valuationMethod' in loan.pricing && loan.pricing.valuationMethod === 'cash') {
return (
<Shelf gap="1" alignItems="center" justifyContent="center" style={{ whiteSpace: 'nowrap', maxWidth: '100%' }}>
<Shelf height="24px" width="24px" alignItems="center" justifyContent="center">
<Box as="img" src={currencyDollar} alt="" height="13px" width="13px" />
</Shelf>
<TextWithPlaceholder
isLoading={isLoading}
width={12}
variant="body2"
style={{ overflow: 'hidden', maxWidth: '300px', textOverflow: 'ellipsis' }}
>
<Tooltips type="onchainReserve" label={<Text variant="body2">Bank account</Text>} />
</TextWithPlaceholder>
</Shelf>
)
}

return (
<Shelf gap="1" style={{ whiteSpace: 'nowrap', maxWidth: '100%' }}>
<Shelf gap="1" alignItems="center" justifyContent="center" style={{ whiteSpace: 'nowrap', maxWidth: '100%' }}>
<Thumbnail type="asset" label={loan.id} />
<TextWithPlaceholder
isLoading={isLoading}
width={12}
variant="body2"
fontWeight={600}
style={{ overflow: 'hidden', maxWidth: '300px', textOverflow: 'ellipsis' }}
>
{metadata?.name}
Expand Down Expand Up @@ -261,6 +342,10 @@ function Amount({ loan }: { loan: Row }) {

return formatBalance(l.outstandingDebt, pool?.currency.symbol)

// @ts-expect-error
case '':
return formatBalance(pool.reserve.total, pool?.currency.symbol)

default:
return ''
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { BorrowerTransactionType, InvestorTransactionType } from '@centrifuge/centrifuge-js'
import { AssetTransactionType, InvestorTransactionType } from '@centrifuge/centrifuge-js'
import { StatusChip } from '@centrifuge/fabric'
import * as React from 'react'
import { formatTransactionsType } from '../Report/utils'

type TransactionTypeProps = {
type: InvestorTransactionType | BorrowerTransactionType
type: InvestorTransactionType | AssetTransactionType
trancheTokenSymbol: string
poolCurrencySymbol: string
currencyAmount: number | null
Expand Down
4 changes: 2 additions & 2 deletions centrifuge-app/src/components/Portfolio/Transactions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BorrowerTransactionType, InvestorTransactionType, Pool, Token, TokenBalance } from '@centrifuge/centrifuge-js'
import { AssetTransactionType, InvestorTransactionType, Pool, Token, TokenBalance } from '@centrifuge/centrifuge-js'
import { formatBalance } from '@centrifuge/centrifuge-react'
import {
AnchorButton,
Expand Down Expand Up @@ -30,7 +30,7 @@ type TransactionsProps = {
}

type Row = {
action: InvestorTransactionType | BorrowerTransactionType
action: InvestorTransactionType | AssetTransactionType
date: number
tranche?: Token
tranchePrice: string
Expand Down
2 changes: 1 addition & 1 deletion centrifuge-app/src/components/Report/AssetTransactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function AssetTransactions({ pool }: { pool: Pool }) {
return transactions?.map((tx) => ({
name: '',
value: [
tx.loanId.split('-').at(-1)!,
tx.assetId.split('-').at(-1)!,
tx.epochId.split('-').at(-1)!,
formatDate(tx.timestamp.toString()),
formatAssetTransactionType(tx.type),
Expand Down
12 changes: 6 additions & 6 deletions centrifuge-app/src/components/Report/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BorrowerTransactionType, InvestorTransactionType } from '@centrifuge/centrifuge-js/dist/types/subquery'
import { AssetTransactionType, InvestorTransactionType } from '@centrifuge/centrifuge-js/dist/types/subquery'
import { Text } from '@centrifuge/fabric'
import { copyToClipboard } from '../../utils/copyToClipboard'
import { truncate } from '../../utils/web3'
Expand Down Expand Up @@ -48,7 +48,7 @@ export function formatInvestorTransactionsType({
}

const assetTransactionTypes: {
[key in BorrowerTransactionType]: string
[key in AssetTransactionType]: string
} = {
CREATED: 'Created',
PRICED: 'Priced',
Expand All @@ -57,9 +57,9 @@ const assetTransactionTypes: {
CLOSED: 'Closed',
}

export function formatAssetTransactionType(type: BorrowerTransactionType) {
export function formatAssetTransactionType(type: AssetTransactionType) {
if (!assetTransactionTypes[type]) {
console.warn(`Type '${type}' is not assignable to type 'BorrowerTransactionType'`)
console.warn(`Type '${type}' is not assignable to type 'AssetTransactionType'`)
return type
}

Expand All @@ -72,7 +72,7 @@ export function formatTransactionsType({
poolCurrencySymbol,
currencyAmount,
}: {
type: InvestorTransactionType | BorrowerTransactionType
type: InvestorTransactionType | AssetTransactionType
trancheTokenSymbol: string
poolCurrencySymbol: string
currencyAmount: number | null
Expand All @@ -89,7 +89,7 @@ export function formatTransactionsType({
})
}

function isAssetType(type: InvestorTransactionType | BorrowerTransactionType): type is BorrowerTransactionType {
function isAssetType(type: InvestorTransactionType | AssetTransactionType): type is AssetTransactionType {
return ['CREATED', 'PRICED', 'BORROWED', 'REPAID', 'CLOSED'].includes(type)
}

Expand Down
12 changes: 12 additions & 0 deletions centrifuge-app/src/components/Tooltips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,18 @@ export const tooltipText = {
label: 'Pool type',
body: 'An open pool can have multiple unrelated token holders and can onboard third party investors. A closed pool has very limited distributions and is not available for investment on the app.',
},
totalNav: {
label: "Total NAV",
body: "The total Net Asset Value (NAV) reflects the combined present value of assets, cash held in the onchain reserve of the pool, and cash in the bank account designated as offchain cash."
} ,
onchainReserve: {
label: "Onchain reserve",
body: "The onchain reserve represents the amount of available liquidity in the pool available for asset originations and redemptions."
},
offchainCash: {
label: "Offchain cash",
body: "Offchain cash represents funds held in a traditional bank account or custody account."
}
}

export type TooltipsProps = {
Expand Down
Loading

0 comments on commit 1bd4d79

Please sign in to comment.