diff --git a/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx b/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx
index 2e3a912f90..333ef7ea79 100644
--- a/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx
+++ b/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx
@@ -22,7 +22,6 @@ type ChartData = {
currency?: string
seniorAPY: number | null | undefined
juniorAPY: number | null
- isToday: boolean
}
type GraphDataItemWithType = {
@@ -115,7 +114,7 @@ function PoolPerformanceChart() {
return true
})
- const [range, setRange] = React.useState<(typeof rangeFilters)[number]>({ value: 'all', label: 'All' })
+ const [range, setRange] = React.useState<(typeof rangeFilters)[number]>(rangeFilters[0])
const rangeNumber = getRangeNumber(range.value, poolAge) ?? 100
// querying chain for more accurate data, since data for today from subquery is not necessarily up to date
@@ -124,6 +123,16 @@ function PoolPerformanceChart() {
? formatBalance(pool?.tranches[pool.tranches.length - 1].tokenPrice || 0, undefined, 5, 5)
: null
+ const todayJuniorApy = pool?.tranches
+ ?.find((pool) => pool.seniority === 0)
+ ?.yield30DaysAnnualized?.toPercent()
+ .toNumber()
+
+ const todaySeniorApy = pool?.tranches
+ ?.find((pool) => pool.seniority === 1)
+ ?.yield30DaysAnnualized?.toPercent()
+ .toNumber()
+
const trancheTodayPrice = calculateTranchePrices(pool as Pool)
const data: ChartData[] = React.useMemo(
@@ -151,9 +160,8 @@ function PoolPerformanceChart() {
nav: todayAssetValue,
juniorTokenPrice: tranchePrices.juniorTokenPrice ?? 0,
seniorTokenPrice: tranchePrices.seniorTokenPrice ?? null,
- juniorAPY: formattedJuniorAPY,
- seniorAPY: formattedSeniorAPY,
- isToday: true,
+ juniorAPY: pool.id === '1655476167' ? 15 : todayJuniorApy,
+ seniorAPY: todaySeniorApy,
}
}
@@ -164,20 +172,17 @@ function PoolPerformanceChart() {
seniorTokenPrice: seniorTokenPrice !== 0 ? seniorTokenPrice : null,
juniorAPY: formattedJuniorAPY,
seniorAPY: formattedSeniorAPY,
- isToday: false,
}
}) || [],
- [truncatedPoolStates, todayAssetValue, pool, range]
+ [truncatedPoolStates, todayAssetValue, pool, range, todayJuniorApy, todaySeniorApy]
)
- const todayData = data.find((day) => day.isToday)
-
const today = {
nav: todayAssetValue,
price: todayPrice,
currency: pool.currency.symbol,
- juniorAPY: todayData?.juniorAPY,
- seniorAPY: todayData?.seniorAPY,
+ juniorAPY: pool.id === '1655476167' ? 15 : todayJuniorApy,
+ seniorAPY: todaySeniorApy,
...trancheTodayPrice,
}
@@ -217,10 +222,10 @@ function PoolPerformanceChart() {
Pool performance
setSelectedTabIndex(index)}>
-
+
Price
-
+
APY
@@ -258,7 +263,7 @@ function PoolPerformanceChart() {
formatBalanceAbbreviated(tick, '', 0)}
yAxisId="left"
width={80}
@@ -266,7 +271,7 @@ function PoolPerformanceChart() {
formatBalanceAbbreviated(tick, '', 2)}
yAxisId="right"
orientation="right"
@@ -421,7 +426,7 @@ function CustomLegend({
navData,
{
color: 'textGold',
- label: 'Junior token price',
+ label: data.seniorTokenPrice ? 'Junior token price' : 'Token price',
value: formatBalance(data.juniorTokenPrice ?? 0, '', 3),
type: 'singleTrancheTokenPrice',
show: true,
@@ -439,7 +444,7 @@ function CustomLegend({
navData,
{
color: 'textGold',
- label: 'Junior APY',
+ label: data.seniorAPY ? 'Junior APY' : 'APY',
value: formatPercentage(data.juniorAPY ?? 0),
show: !!data.juniorAPY,
},
@@ -470,13 +475,15 @@ function CustomLegend({
}
return (
-
+
{hasType(item) ? (
-
- ) : (
+
+
+ ) : (
+
{item.label}
)}
@@ -507,7 +514,7 @@ export const CustomTick = ({ x, y, payload }: CustomTickProps) => {
return (
)}
- } />
+ } />
{
+export const SimpleBarChart = ({ currency, data, groupBy }: SimpleBarChartProps) => {
const theme = useTheme()
+ const isSmallerBar = groupBy === 'daily' || false
const getOneDayPerMonth = () => {
const seenMonths = new Set()
@@ -56,7 +58,7 @@ export const SimpleBarChart = ({ currency, data }: SimpleBarChartProps) => {
type="category"
dataKey="name"
ticks={getOneDayPerMonth()}
- tick={}
+ tick={(props) => }
angle={45}
/>
{
fill={theme.colors.backgroundTertiary}
strokeWidth={0}
fillOpacity={1}
- maxBarSize={20}
+ barSize={isSmallerBar ? 20 : 80}
/>
diff --git a/centrifuge-app/src/components/Charts/Tooltip.tsx b/centrifuge-app/src/components/Charts/Tooltip.tsx
index fb179d23bb..bde12533b2 100644
--- a/centrifuge-app/src/components/Charts/Tooltip.tsx
+++ b/centrifuge-app/src/components/Charts/Tooltip.tsx
@@ -4,9 +4,9 @@ import { TooltipProps } from 'recharts'
import { formatDate } from '../../utils/date'
import { formatBalance, formatPercentage } from '../../utils/formatting'
-type CustomizedTooltipProps = TooltipProps & { currency: string; precision?: number }
+type CustomizedTooltipProps = TooltipProps & { currency: string; precision?: number; isRate?: boolean }
-export function CustomizedTooltip({ payload, currency, precision }: CustomizedTooltipProps) {
+export function CustomizedTooltip({ payload, currency, precision, isRate }: CustomizedTooltipProps) {
if (payload && payload?.length > 0) {
return (
@@ -15,7 +15,7 @@ export function CustomizedTooltip({ payload, currency, precision }: CustomizedTo
{typeof value !== 'number'
? formatBalance(value[1] - value[0], currency, precision)
- : unit === 'percent'
+ : unit === 'percent' || isRate
? formatPercentage(value)
: formatBalance(value, currency, precision)}
diff --git a/centrifuge-app/src/components/DataTable.tsx b/centrifuge-app/src/components/DataTable.tsx
index c852234ddd..73fc0ac58e 100644
--- a/centrifuge-app/src/components/DataTable.tsx
+++ b/centrifuge-app/src/components/DataTable.tsx
@@ -42,6 +42,7 @@ export type DataTableProps = {
defaultSortKey?: string
defaultSortOrder?: OrderBy
hoverable?: boolean
+ scrollable?: boolean
/**
* summary row is not included in sorting
*/
@@ -99,6 +100,7 @@ export const DataTable = >({
pageSize = Infinity,
page = 1,
headerStyles,
+ scrollable = false,
}: DataTableProps) => {
const [orderBy, setOrderBy] = React.useState>(
defaultSortKey ? { [defaultSortKey]: defaultSortOrder } : {}
@@ -123,9 +125,9 @@ export const DataTable = >({
const templateColumns = `[start] ${columns.map((col) => col.width ?? 'minmax(min-content, 1fr)').join(' ')} [end]`
return (
-
+
{showHeader && (
-
+
{columns.map((col, i) => (
@@ -200,7 +202,15 @@ export const DataTable = >({
)
}
-const TableGrid = styled(Grid)``
+const TableGrid = styled(Grid)<{ scrollable?: boolean }>`
+ ${({ scrollable }) =>
+ scrollable &&
+ css({
+ maxHeight: 'calc(100vh - 180px)',
+ overflowY: 'auto',
+ overflowX: 'auto',
+ })}
+`
const Row = styled('div')`
display: grid;
@@ -209,12 +219,17 @@ const Row = styled('div')`
box-shadow: ${({ theme }) => `-1px 0 0 0 ${theme.colors.borderPrimary}, 1px 0 0 0 ${theme.colors.borderPrimary}`};
`
-const HeaderRow = styled(Row)<{ styles?: any }>(({ styles }) =>
+const HeaderRow = styled(Row)<{ styles?: any; scrollable?: boolean }>(({ styles, scrollable }) =>
css({
backgroundColor: 'backgroundSecondary',
borderStyle: 'solid',
borderWidth: '1px 0',
borderColor: 'borderPrimary',
+ position: scrollable ? 'sticky' : 'static',
+ top: scrollable ? 0 : 'auto',
+ zIndex: scrollable ? 10 : 'auto',
+ borderTopLeftRadius: '8px',
+ borderTopRightRadius: '8px',
...styles,
})
)
@@ -242,6 +257,10 @@ export const DataRow = styled(Row)`
'&:focus-visible': {
boxShadow: 'inset 0 0 0 3px var(--fabric-focus)',
},
+ '&:last-child': {
+ borderBottomLeftRadius: '8px',
+ borderBottomRightRadius: '8px',
+ },
})}
`
diff --git a/centrifuge-app/src/components/InvestRedeem/InvestRedeem.tsx b/centrifuge-app/src/components/InvestRedeem/InvestRedeem.tsx
index d0c86a1b73..66ec5fc07c 100644
--- a/centrifuge-app/src/components/InvestRedeem/InvestRedeem.tsx
+++ b/centrifuge-app/src/components/InvestRedeem/InvestRedeem.tsx
@@ -1,3 +1,4 @@
+import { CurrencyBalance } from '@centrifuge/centrifuge-js'
import { ConnectionGuard, useGetNetworkName, useWallet } from '@centrifuge/centrifuge-react'
import { Network } from '@centrifuge/centrifuge-react/dist/components/WalletProvider/types'
import { useGetExplorerUrl } from '@centrifuge/centrifuge-react/dist/components/WalletProvider/utils'
@@ -25,6 +26,7 @@ import { usePool, usePoolMetadata } from '../../utils/usePools'
import { LiquidityRewardsContainer } from '../LiquidityRewards/LiquidityRewardsContainer'
import { LiquidityRewardsProvider } from '../LiquidityRewards/LiquidityRewardsProvider'
import { LoadBoundary } from '../LoadBoundary'
+import { PoolMetaDataPartial } from '../PoolList'
import { Transactions } from '../Portfolio/Transactions'
import { Spinner } from '../Spinner'
import { AnchorTextLink } from '../TextLink'
@@ -35,8 +37,18 @@ import { RedeemForm } from './RedeemForm'
export type InvestRedeemProps = {
poolId: string
trancheId: string
+ metadata?: PoolMetaDataPartial
} & InputProps
+type HeaderProps = {
+ sumUnrealizedProfitAtMarketPrice?: CurrencyBalance
+ sumRealizedProfitFifoByPeriod?: CurrencyBalance
+} & InputProps
+
+type InputProps = {
+ defaultView?: 'invest' | 'redeem'
+}
+
// @ts-ignore
const listFormatter = new Intl.ListFormat('en')
@@ -71,7 +83,7 @@ export function InvestRedeem({ poolId, trancheId, ...rest }: InvestRedeemProps)
>
-
+
{!isTinlakePool && (connectedType === 'substrate' || isEvmOnSubstrate) && }
@@ -82,10 +94,6 @@ export function InvestRedeem({ poolId, trancheId, ...rest }: InvestRedeemProps)
)
}
-type InputProps = {
- defaultView?: 'invest' | 'redeem'
-}
-
function InvestRedeemInput({ defaultView: defaultViewProp }: InputProps) {
const { state } = useInvestRedeem()
const { render: renderGmp } = useGmp()
@@ -147,10 +155,9 @@ function InvestRedeemInput({ defaultView: defaultViewProp }: InputProps) {
)
}
-function Header() {
+function Header({ sumRealizedProfitFifoByPeriod, sumUnrealizedProfitAtMarketPrice }: HeaderProps) {
const { state } = useInvestRedeem()
const { connectedType } = useWallet()
-
return (
{state.trancheCurrency?.symbol} investment overview
@@ -159,7 +166,7 @@ function Header() {
Investment position
-
+
)}
+
+
+
+ Realized P&L
+
+
+
+ {formatBalance(sumRealizedProfitFifoByPeriod ?? 0, undefined, 2, 0)}
+
+
+ {state.poolCurrency?.displayName}
+
+
+
+
+
+ Unrealized P&L
+
+
+
+ {formatBalance(sumUnrealizedProfitAtMarketPrice ?? 0, undefined, 2, 0)}
+
+
+ {state.poolCurrency?.displayName}
+
+
+
+
)
}
diff --git a/centrifuge-app/src/components/InvestRedeem/InvestRedeemCentrifugeProvider.tsx b/centrifuge-app/src/components/InvestRedeem/InvestRedeemCentrifugeProvider.tsx
index 85a5258a09..888627917b 100644
--- a/centrifuge-app/src/components/InvestRedeem/InvestRedeemCentrifugeProvider.tsx
+++ b/centrifuge-app/src/components/InvestRedeem/InvestRedeemCentrifugeProvider.tsx
@@ -24,6 +24,7 @@ export function InvestRedeemCentrifugeProvider({ poolId, trancheId, children }:
const { data: metadata, isLoading: isMetadataLoading } = usePoolMetadata(pool)
const trancheMeta = metadata?.tranches?.[trancheId]
const liquidityState = useLiquidityRewards().state
+ const [isStableLoading, setIsStableLoading] = React.useState(true)
if (!tranche) throw new Error(`Token not found. Pool id: ${poolId}, token id: ${trancheId}`)
@@ -81,10 +82,26 @@ export function InvestRedeemCentrifugeProvider({ poolId, trancheId, children }:
}, [pendingTransaction?.status])
}
+ const isDataLoading = React.useMemo(() => {
+ return balances === null || order === null || isMetadataLoading
+ }, [balances, order, isMetadataLoading])
+
+ React.useEffect(() => {
+ const timer = setTimeout(() => {
+ if (isDataLoading) {
+ setIsStableLoading(true)
+ } else {
+ setIsStableLoading(false)
+ }
+ }, 300)
+
+ return () => clearTimeout(timer)
+ }, [isDataLoading])
+
const state: InvestRedeemState = {
poolId,
trancheId,
- isDataLoading: balances == null || order == null || isMetadataLoading,
+ isDataLoading: isStableLoading,
isAllowedToInvest,
isPoolBusy: isCalculatingOrders,
isFirstInvestment: order?.submittedAt === 0 && order.investCurrency.isZero(),
diff --git a/centrifuge-app/src/components/InvestRedeem/InvestRedeemDrawer.tsx b/centrifuge-app/src/components/InvestRedeem/InvestRedeemDrawer.tsx
index b08a1d5110..2e94a167f0 100644
--- a/centrifuge-app/src/components/InvestRedeem/InvestRedeemDrawer.tsx
+++ b/centrifuge-app/src/components/InvestRedeem/InvestRedeemDrawer.tsx
@@ -1,6 +1,7 @@
-import { DailyPoolState, Perquintill } from '@centrifuge/centrifuge-js'
+import { CurrencyBalance, DailyPoolState, Perquintill, Pool } from '@centrifuge/centrifuge-js'
import { Box, Drawer, Stack, Tabs, TabsItem, Text } from '@centrifuge/fabric'
import * as React from 'react'
+import { TinlakePool } from 'src/utils/tinlake/useTinlakePools'
import { useDailyPoolStates, usePool } from '../../utils/usePools'
import { FilterOptions, PriceChart } from '../Charts/PriceChart'
import { LoadBoundary } from '../LoadBoundary'
@@ -10,6 +11,12 @@ type DailyPoolStateProps = Pick & {
apy?: Perquintill | undefined
}
+const apy = {
+ '30days': 'yield30DaysAnnualized',
+ '90days': 'yield90DaysAnnualized',
+ YTD: 'yieldYTD',
+}
+
export function InvestRedeemDrawer({
poolId,
trancheId,
@@ -25,6 +32,7 @@ export function InvestRedeemDrawer({
}) {
const [filter, setFilter] = React.useState('30days')
const [index, setIndex] = React.useState(0)
+ const pool = usePool(poolId)
const dateFrom = React.useMemo(() => {
if (filter === 'YTD') {
@@ -51,10 +59,27 @@ export function InvestRedeemDrawer({
const { poolStates: dailyPoolStates } = useDailyPoolStates(poolId, new Date(dateFrom)) || {}
+ const realizedUnrealizedValues = React.useMemo(() => {
+ const today = dailyPoolStates?.find(
+ (state) => new Date(state.timestamp).toDateString() === new Date().toDateString()
+ )
+
+ const sumRealizedProfitFifoByPeriod = new CurrencyBalance(
+ today?.sumRealizedProfitFifoByPeriod ?? 0,
+ pool.currency.decimals
+ ).toDecimal()
+ const sumUnrealizedProfitAtMarketPrice = new CurrencyBalance(
+ today?.sumUnrealizedProfitAtMarketPrice ?? 0,
+ pool.currency.decimals
+ )
+
+ return { sumRealizedProfitFifoByPeriod, sumUnrealizedProfitAtMarketPrice }
+ }, [dailyPoolStates])
+
return (
-
+
{dailyPoolStates?.length ? (
@@ -74,7 +99,7 @@ export function InvestRedeemDrawer({
{
- const apy = {
- '30days': 'yield30DaysAnnualized',
- '90days': 'yield90DaysAnnualized',
- YTD: 'yieldYTD',
- }
const tokenData =
dailyPoolStates?.map((state) => {
return {
@@ -133,7 +151,7 @@ const TokenPriceChart = React.memo(function TokenPriceChart({
return tokenData
}, [dailyPoolStates, pool?.tranches, trancheId, filter])
- if (!data.length) return
+ if (!data.length || !pool) return
return (
theme.colors.textGold};
- }
+ border-radius: 4px;
+ border-color: #91969b1a;
}
`
-const reportLinks = [
- { label: 'Balance sheet', href: '/balance-sheet', icon: },
- { label: 'Profit & loss', href: '/profit-and-loss', icon: },
- { label: 'Cash flow statement', href: '/cash-flow-statement', icon: },
-]
-
const StyledRouterTextLink = styled(RouterTextLink)`
color: white;
text-decoration: unset;
@@ -67,6 +61,21 @@ const StyledRouterTextLink = styled(RouterTextLink)`
export function ReportDetails({ metadata }: IssuerSectionProps) {
const pathname = useLocation().pathname
const report = metadata?.pool?.reports?.[0]
+ const theme = useTheme()
+
+ const reportLinks = [
+ { label: 'Balance sheet', href: '/balance-sheet', icon: },
+ {
+ label: 'Profit & loss',
+ href: '/profit-and-loss',
+ icon: ,
+ },
+ {
+ label: 'Cash flow statement',
+ href: '/cash-flow-statement',
+ icon: ,
+ },
+ ]
return (
<>
@@ -89,7 +98,7 @@ export function ReportDetails({ metadata }: IssuerSectionProps) {
>
{link.icon}
-
+
{link.label}
@@ -142,8 +151,8 @@ export function IssuerDetails({ metadata }: IssuerSectionProps) {
{metadata?.pool?.issuer.logo && (
@@ -190,14 +199,19 @@ const Links = ({ links }: { links: { label: string; href?: string; show: boolean
if (link.onClick) {
return (
-
+
{link.label}
)
}
return (
-
+
{link.label}
)
@@ -235,19 +249,23 @@ export function RatingDetails({ metadata }: IssuerSectionProps) {
export const PoolAnalysis = ({ metadata, inverted }: IssuerSectionProps & { inverted?: boolean }) => {
const report = metadata?.pool?.reports?.[0]
+ // Not sure why some pools have N/A, it should be empty but this is a fix for those pools in the meantime
+ const isEmpty = report?.author.name === 'N/A'
return report?.author?.name || report?.author?.title ? (
-
-
- Pool analysis
-
-
-
- Reviewer: {report?.author?.name || 'N/A'}
-
-
- Title: {report?.author?.title || 'N/A'}
+ isEmpty ? null : (
+
+
+ Pool analysis
+
+
+ Reviewer: {report?.author?.name || 'N/A'}
+
+
+ Title: {report?.author?.title || 'N/A'}
+
+
-
+ )
) : null
}
diff --git a/centrifuge-app/src/components/LayoutBase/LayoutSection.tsx b/centrifuge-app/src/components/LayoutBase/LayoutSection.tsx
index 2cdb7f2ccb..8c97517b0c 100644
--- a/centrifuge-app/src/components/LayoutBase/LayoutSection.tsx
+++ b/centrifuge-app/src/components/LayoutBase/LayoutSection.tsx
@@ -12,7 +12,7 @@ type Props = {
export function LayoutSection({ title, titleAddition, subtitle, headerRight, children, ...boxProps }: Props) {
return (
-
+
{(title || titleAddition || subtitle || headerRight) && (
diff --git a/centrifuge-app/src/components/LayoutBase/index.tsx b/centrifuge-app/src/components/LayoutBase/index.tsx
index db5cd30888..cd2eca400e 100644
--- a/centrifuge-app/src/components/LayoutBase/index.tsx
+++ b/centrifuge-app/src/components/LayoutBase/index.tsx
@@ -55,7 +55,8 @@ export function LayoutBase(): JSX.Element {
>
)}
-
+ {/* The ID functions so we can deactive scrolling in certain pages, example in the data page */}
+
diff --git a/centrifuge-app/src/components/LayoutBase/styles.tsx b/centrifuge-app/src/components/LayoutBase/styles.tsx
index 0a3be4b863..f838be30a6 100644
--- a/centrifuge-app/src/components/LayoutBase/styles.tsx
+++ b/centrifuge-app/src/components/LayoutBase/styles.tsx
@@ -39,6 +39,7 @@ export const Inner = styled(Grid)`
width: 100vw;
bottom: 0;
overflow-y: auto;
+ padding-right: 12px;
@media (min-width: ${({ theme }) => theme.breakpoints['M']}) and (max-width: ${({ theme }) =>
theme.breakpoints['L']}) {
@@ -51,7 +52,8 @@ export const Inner = styled(Grid)`
@media (min-width: ${({ theme }) => theme.breakpoints['L']}) {
width: 15vw;
background-color: ${({ theme }) => theme.colors.backgroundInverted};
- padding-left: 16px;
+ padding-left: 12px;
+ padding-right: 20px;
height: 100vh;
}
`
@@ -99,7 +101,7 @@ export const LogoContainer = styled(Stack)`
height: ${HEADER_HEIGHT}px;
justify-content: center;
- padding-left: 12px;
+ padding-left: 8px;
@media (min-width: ${({ theme }) => theme.breakpoints['M']}) and (max-width: ${({ theme }) =>
theme.breakpoints['L']}) {
@@ -110,7 +112,7 @@ export const LogoContainer = styled(Stack)`
@media (min-width: ${({ theme }) => theme.breakpoints['L']}) {
justify-content: start;
- padding-top: ${({ theme }) => theme.space[2]}px;
+ padding-top: ${({ theme }) => theme.space[3]}px;
}
`
@@ -146,7 +148,7 @@ export const WalletInner = styled(Stack)`
@media (min-width: ${({ theme }) => theme.breakpoints[BREAK_POINT_COLUMNS]}) {
justify-content: flex-end;
height: 50px;
- margin-right: 40px;
+ margin-right: 30px;
}
`
@@ -165,6 +167,7 @@ export const FooterContainer = styled(Box)`
position: sticky;
bottom: 0;
width: 100%;
+ padding-left: 4px;
@media (min-width: ${({ theme }) => theme.breakpoints[BREAK_POINT_COLUMNS]}) {
position: sticky;
diff --git a/centrifuge-app/src/components/LiquidityEpochSection.tsx b/centrifuge-app/src/components/LiquidityEpochSection.tsx
index eb0cd05875..ae19fa0549 100644
--- a/centrifuge-app/src/components/LiquidityEpochSection.tsx
+++ b/centrifuge-app/src/components/LiquidityEpochSection.tsx
@@ -329,6 +329,7 @@ function TinlakeEpochStatus({ pool }: { pool: TinlakePool }) {
} = useWallet()
const { refetch: refetchBalances } = useTinlakeBalances()
const { refetch: refetchInvestments } = useTinlakeInvestments(pool.id)
+ const isTinlakePool = pool.id?.startsWith('0x')
const { execute: closeEpochTx, isLoading: loadingClose } = useTinlakeTransaction(
pool.id,
@@ -469,7 +470,7 @@ function TinlakeEpochStatus({ pool }: { pool: TinlakePool }) {
}
return (
-
+ > : epochButtonElement}>
diff --git a/centrifuge-app/src/components/LiquidityTransactionsSection.tsx b/centrifuge-app/src/components/LiquidityTransactionsSection.tsx
index 3120ae8a54..b392aa5e6e 100644
--- a/centrifuge-app/src/components/LiquidityTransactionsSection.tsx
+++ b/centrifuge-app/src/components/LiquidityTransactionsSection.tsx
@@ -144,7 +144,7 @@ export default function LiquidityTransactionsSection({
diff --git a/centrifuge-app/src/components/Menu/PageLink.tsx b/centrifuge-app/src/components/Menu/PageLink.tsx
index 005f06f349..20e37873bd 100644
--- a/centrifuge-app/src/components/Menu/PageLink.tsx
+++ b/centrifuge-app/src/components/Menu/PageLink.tsx
@@ -9,13 +9,14 @@ const Root = styled(Text)<{ isActive?: boolean; stacked?: boolean }>`
${baseButton}
${primaryButton}
grid-template-columns: ${({ stacked, theme }) => (stacked ? '1fr' : `${theme.sizes.iconSmall}px 1fr`)};
- color: ${({ isActive, theme }) =>
- isActive ? theme.colors.textGold : theme.colors.textInverted}; /* Example styling */
+ color: ${({ isActive, theme }) => (isActive ? theme.colors.textGold : theme.colors.textInverted)};
font-size: 14px;
font-weight: 500;
background-color: transparent;
+ border-radius: 4px;
&:hover {
color: ${({ theme }) => theme.colors.textGold};
+ background-color: rgba(145, 150, 155, 0.13);
}
`
diff --git a/centrifuge-app/src/components/Menu/Toggle.tsx b/centrifuge-app/src/components/Menu/Toggle.tsx
index 934584b765..894ab01c63 100644
--- a/centrifuge-app/src/components/Menu/Toggle.tsx
+++ b/centrifuge-app/src/components/Menu/Toggle.tsx
@@ -9,8 +9,11 @@ export const Toggle = styled(Text)<{ isActive?: boolean; stacked?: boolean }>`
grid-template-columns: ${({ stacked, theme }) =>
stacked ? '1fr' : `${theme.sizes.iconSmall}px 1fr ${theme.sizes.iconSmall}px`};
color: ${({ isActive, theme }) => (isActive ? theme.colors.textGold : theme.colors.textInverted)};
+ border-radius: 4px;
background-color: transparent;
+
&:hover {
- color: ${({ isActive, theme }) => (isActive ? theme.colors.textGold : theme.colors.textInverted)};
+ color: ${({ theme }) => theme.colors.textGold};
+ background-color: rgba(145, 150, 155, 0.13);
}
`
diff --git a/centrifuge-app/src/components/PillButton.tsx b/centrifuge-app/src/components/PillButton.tsx
index cea7e31c8e..84731bacd9 100644
--- a/centrifuge-app/src/components/PillButton.tsx
+++ b/centrifuge-app/src/components/PillButton.tsx
@@ -14,11 +14,13 @@ const Pill = styled.button<{ variant?: 'small' | 'regular' }>(
backgroundColor: 'backgroundSecondary',
textDecoration: 'none',
borderRadius: 20,
+ lineHeight: 20,
'&:visited, &:active': {
color: 'textPrimary',
},
'&:hover': {
- color: 'textGold',
+ color: 'textInverted',
+ backgroundColor: 'textPrimary',
},
}),
({ theme }) => ({
@@ -32,11 +34,11 @@ const Pill = styled.button<{ variant?: 'small' | 'regular' }>(
? css({
borderRadius: '20px',
fontSize: '14px',
- padding: '2px 8px',
+ padding: '8px 16px',
})
: css({
borderRadius: '12px',
- padding: '2px 10px',
+ padding: '4px 12px',
fontSize: '12px',
})
)
diff --git a/centrifuge-app/src/components/PoolCard/index.tsx b/centrifuge-app/src/components/PoolCard/index.tsx
index 56a33377ab..3e0c175420 100644
--- a/centrifuge-app/src/components/PoolCard/index.tsx
+++ b/centrifuge-app/src/components/PoolCard/index.tsx
@@ -1,7 +1,8 @@
import { CurrencyBalance, Rate, Token } from '@centrifuge/centrifuge-js'
import { Box, Card, Divider, Stack, Text, Thumbnail } from '@centrifuge/fabric'
import Decimal from 'decimal.js-light'
-import styled from 'styled-components'
+import { useMemo } from 'react'
+import styled, { useTheme } from 'styled-components'
import { daysBetween } from '../../utils/date'
import { formatBalance, formatBalanceAbbreviated, formatPercentage } from '../../utils/formatting'
import { CardHeader } from '../ListItemCardStyles'
@@ -24,6 +25,10 @@ export type MetaData = {
}
}
+type TinlakeTranchesKey = 'silver' | 'blocktowerThree' | 'blocktowerFour'
+
+type TrancheWithCurrency = Pick
+
const StyledRouterTextLink = styled(RouterTextLink)`
font-size: 12px;
margin-top: 8px;
@@ -33,12 +38,14 @@ const StyledCard = styled(Card)`
width: 100%;
max-width: 100%;
height: 320px;
+
margin-right: 12px;
margin-bottom: 12px;
padding: 12px;
&:hover {
- border: 1px solid ${({ theme }) => theme.colors.backgroundInverted};
+ border: 1px solid ${({ theme }) => theme.colors.backgroundTertiary};
+ box-shadow: 0px 20px 24px -4px rgba(16, 24, 40, 0.08), 0px 8px 8px -4px rgba(16, 24, 40, 0.03);
}
@media (min-width: ${({ theme }) => theme.breakpoints['M']}) {
@@ -50,6 +57,33 @@ const StyledCard = styled(Card)`
}
`
+const tinlakeTranches = {
+ silver: {
+ Junior: '15%',
+ Senior: '7%',
+ shortDescription: ' Real estate bridge loans for fix and flip projects, maturing in 12-24 months.',
+ InvestorType: 'Qualified Investors',
+ },
+ blocktowerThree: {
+ Junior: '15%',
+ Senior: '4%',
+ shortDescription: ' Investment-grade consumer ABS, auto ABS, and CLOs under 4 years.',
+ InvestorType: 'Private',
+ },
+ blocktowerFour: {
+ Junior: '15%',
+ Senior: '4%',
+ shortDescription: 'Investment-grade consumer ABS, auto ABS, and CLOs under 4 years.',
+ InvestorType: 'Private',
+ },
+ none: {
+ Junior: '-',
+ Senior: '-',
+ shortDescription: '',
+ InvestorType: '-',
+ },
+}
+
export type PoolCardProps = {
poolId?: string
name?: string
@@ -59,7 +93,7 @@ export type PoolCardProps = {
apr?: Rate | null | undefined
status?: PoolStatusKey
iconUri?: string
- tranches?: Pick[]
+ tranches?: TrancheWithCurrency[]
metaData?: MetaData
createdAt?: string
}
@@ -76,42 +110,82 @@ export function PoolCard({
metaData,
createdAt,
}: PoolCardProps) {
+ const theme = useTheme()
const isOneTranche = tranches && tranches?.length === 1
- const renderText = (text: string) => (
-
- {text}
-
- )
+ const isTinlakePool =
+ poolId === '0x53b2d22d07E069a3b132BfeaaD275b10273d381E' ||
+ poolId === '0x90040F96aB8f291b6d43A8972806e977631aFFdE' ||
+ poolId === '0x55d86d51Ac3bcAB7ab7d2124931FbA106c8b60c7'
- const tranchesData = tranches
- ?.map((tranche) => {
- const words = tranche.currency.name.trim().split(' ')
- const metadata = metaData?.tranches[tranche.id] ?? null
- const trancheName = words[words.length - 1]
- const investmentBalance = new CurrencyBalance(
- metadata?.minInitialInvestment ?? 0,
- tranche.currency.decimals
- ).toDecimal()
-
- const daysSinceCreation = createdAt ? daysBetween(createdAt, new Date()) : 0
-
- function calculateApy() {
- if (poolId === '4139607887') return formatPercentage(5, true, {}, 1)
- if (poolId === '1655476167') return formatPercentage(15, true, {}, 1)
- if (daysSinceCreation > 30 && tranche.yield30DaysAnnualized)
- return formatPercentage(tranche.yield30DaysAnnualized, true, {}, 1)
- if (tranche.interestRatePerSec) return formatPercentage(tranche.interestRatePerSec.toAprPercent(), true, {}, 1)
- return '-'
- }
-
- return {
- name: trancheName,
- apr: calculateApy(),
- minInvestment:
- metadata && metadata.minInitialInvestment ? `$${formatBalanceAbbreviated(investmentBalance, '', 0)}` : '-',
- }
- })
- .reverse()
+ const tinlakeObjKey = () => {
+ if (name?.includes('Silver')) return 'silver'
+ else if (name?.includes('BlockTower Series 3')) return 'blocktowerThree'
+ else if (name?.includes('BlockTower Series 4')) return 'blocktowerFour'
+ else return 'none'
+ }
+
+ const getTinlakeMinInvestment = (trancheName: 'Junior' | 'Senior') => {
+ if (name?.includes('Silver') && trancheName === 'Senior') return '5K'
+ else return '-'
+ }
+
+ const renderText = (text: string, isApr?: boolean) => {
+ if (isApr && poolId === '1615768079') {
+ return (
+
+
+ {text}
+
+
+ Target
+
+
+ )
+ }
+ return (
+
+ {text}
+
+ )
+ }
+
+ const calculateApy = (tranche: TrancheWithCurrency) => {
+ const daysSinceCreation = createdAt ? daysBetween(createdAt, new Date()) : 0
+ if (poolId === '1655476167') return '15%'
+ if (poolId === '1615768079' && tranche.seniority === 0) return '8.0%'
+ if (poolId === '1615768079' && tranche.seniority === 1) return '16%'
+ if (daysSinceCreation > 30 && tranche.yield30DaysAnnualized)
+ return formatPercentage(tranche.yield30DaysAnnualized, true, {}, 1)
+ if (tranche.interestRatePerSec) {
+ return formatPercentage(tranche.interestRatePerSec.toAprPercent(), true, {}, 1)
+ }
+ return '-'
+ }
+
+ const tranchesData = useMemo(() => {
+ return tranches
+ ?.map((tranche: TrancheWithCurrency) => {
+ const key = tinlakeObjKey() as TinlakeTranchesKey
+ const words = tranche.currency.name.trim().split(' ')
+ const metadata = metaData?.tranches[tranche.id] ?? null
+ const trancheName = words[words.length - 1]
+ const investmentBalance = new CurrencyBalance(
+ metadata?.minInitialInvestment ?? 0,
+ tranche.currency.decimals
+ ).toDecimal()
+
+ return {
+ name: trancheName,
+ apr: isTinlakePool ? tinlakeTranches[key][trancheName as 'Junior' | 'Senior'] : calculateApy(tranche),
+ minInvestment: isTinlakePool
+ ? getTinlakeMinInvestment(trancheName as 'Junior' | 'Senior')
+ : metadata && metadata.minInitialInvestment
+ ? `$${formatBalanceAbbreviated(investmentBalance, '', 0)}`
+ : '-',
+ }
+ })
+ .reverse()
+ }, [calculateApy, getTinlakeMinInvestment, isTinlakePool, metaData?.tranches, tinlakeObjKey, tranches])
return (
@@ -124,7 +198,15 @@ export function PoolCard({
{iconUri ? (
-
+
) : (
)}
@@ -143,7 +225,7 @@ export function PoolCard({
padding={isOneTranche ? 0 : '8px'}
display="flex"
justifyContent="space-between"
- width={isOneTranche ? '50%' : '100%'}
+ width={isOneTranche ? '60%' : '100%'}
>
{!isOneTranche && (
@@ -158,9 +240,9 @@ export function PoolCard({
)}
- APY
+ {poolId === '1655476167' ? 'Target' : 'APY'}
- {tranchesData?.map((tranche) => renderText(`${tranche.apr}`))}
+ {tranchesData?.map((tranche) => renderText(`${tranche.apr}`, true))}
@@ -169,20 +251,26 @@ export function PoolCard({
{tranchesData?.map((tranche) => renderText(`${tranche.minInvestment}`))}
- {metaData?.pool?.issuer?.shortDescription && (
-
-
- {metaData?.pool?.issuer?.shortDescription}
-
-
- )}
-
- {assetClass && 'Asset type'}
- {assetClass ?? ''}
+ {metaData?.pool?.issuer?.shortDescription ||
+ (isTinlakePool && (
+
+
+ {isTinlakePool
+ ? tinlakeTranches[tinlakeObjKey()].shortDescription
+ : metaData?.pool?.issuer?.shortDescription}
+
+
+ ))}
+
+ Asset type
+ {assetClass ?? '-'}
- {metaData?.pool?.investorType && 'Investor Type'}
- {metaData?.pool?.investorType ?? ''}
+ Investor type
+
+ {' '}
+ {isTinlakePool ? tinlakeTranches[tinlakeObjKey()].InvestorType : metaData?.pool?.investorType ?? '-'}
+
diff --git a/centrifuge-app/src/components/PoolList.tsx b/centrifuge-app/src/components/PoolList.tsx
index 9ebe37ece1..2d4c3b9a74 100644
--- a/centrifuge-app/src/components/PoolList.tsx
+++ b/centrifuge-app/src/components/PoolList.tsx
@@ -22,6 +22,19 @@ const PoolCardBox = styled(Box)`
}
`
+const StyledBox = styled(Box)`
+ background-color: transparent;
+ border: none;
+ &:hover {
+ svg {
+ color: ${({ theme }) => theme.colors.textGold};
+ }
+ div {
+ color: ${({ theme }) => theme.colors.textGold};
+ }
+ }
+`
+
const upcomingPools: PoolCardProps[] = []
export function PoolList() {
@@ -93,7 +106,7 @@ export function PoolList() {
{!metadataIsLoading && archivedPools.length > 0 && (
<>
-
+
{!showArchived && }
-
+
{showArchived && }
>
)}
@@ -140,7 +153,6 @@ export function poolsToPoolCardProps(
): PoolCardProps[] {
return pools.map((pool) => {
const metaData = typeof pool.metadata === 'string' ? metaDataById[pool.id] : pool.metadata
-
return {
poolId: pool.id,
name: metaData?.pool?.name,
@@ -151,6 +163,7 @@ export function poolsToPoolCardProps(
iconUri: metaData?.pool?.icon?.uri ? cent.metadata.parseMetadataUrl(metaData?.pool?.icon?.uri) : undefined,
tranches: pool.tranches,
metaData: metaData as MetaData,
+ createdAt: pool.createdAt ?? '',
}
})
}
diff --git a/centrifuge-app/src/components/PoolOverview/KeyMetrics.tsx b/centrifuge-app/src/components/PoolOverview/KeyMetrics.tsx
index ed169d950a..cd5f263b64 100644
--- a/centrifuge-app/src/components/PoolOverview/KeyMetrics.tsx
+++ b/centrifuge-app/src/components/PoolOverview/KeyMetrics.tsx
@@ -32,6 +32,17 @@ type Tranche = Pick & {
}
}
+type TinlakeDataKey =
+ | '0x53b2d22d07E069a3b132BfeaaD275b10273d381E'
+ | '0x55d86d51Ac3bcAB7ab7d2124931FbA106c8b60c7'
+ | '0x90040F96aB8f291b6d43A8972806e977631aFFdE'
+
+const tinlakeData = {
+ '0x53b2d22d07E069a3b132BfeaaD275b10273d381E': '7% - 15% target',
+ '0x55d86d51Ac3bcAB7ab7d2124931FbA106c8b60c7': '4% - 15% target',
+ '0x90040F96aB8f291b6d43A8972806e977631aFFdE': '4% - 15% target',
+}
+
const getTodayValue = (data: DailyTrancheStateArr | null | undefined): DailyTrancheStateArr | undefined => {
if (!data) return
if (!Object.keys(data).length) return
@@ -78,11 +89,13 @@ export const KeyMetrics = ({ poolId }: Props) => {
const thirtyDayAPY = getTodayValue(dailyTranches)
if (!thirtyDayAPY) return null
- return Object.keys(thirtyDayAPY).map((key) => {
- return thirtyDayAPY[key][0].yield30DaysAnnualized
- ? formatPercentage(thirtyDayAPY[key][0].yield30DaysAnnualized)
- : null
- })
+ return Object.keys(thirtyDayAPY)
+ .map((key) => {
+ return thirtyDayAPY[key][0].yield30DaysAnnualized
+ ? thirtyDayAPY[key][0].yield30DaysAnnualized.toPercent().toNumber()
+ : 0
+ })
+ .sort((a, b) => a - b)
}, [dailyTranches])
const minInvestmentPerTranche = useMemo(() => {
@@ -90,13 +103,19 @@ export const KeyMetrics = ({ poolId }: Props) => {
return Object.values(metadata.tranches).map((item) => {
const minInv = new CurrencyBalance(item.minInitialInvestment ?? 0, pool.currency.decimals).toDecimal()
- return item.minInitialInvestment ? formatBalanceAbbreviated(minInv, '', 0) : null
+ return item.minInitialInvestment ? minInv : null
})
}, [metadata?.tranches, pool.currency.decimals])
+ const getHardCodedApy = () => {
+ if (poolId === '1655476167') return '15%'
+ if (poolId === '1615768079') return '8% - 16%'
+ }
+
const isBT3BT4 =
- poolId.toLowerCase() === '0x90040f96ab8f291b6d43a8972806e977631affde' ||
- poolId.toLowerCase() === '0x55d86d51ac3bcab7ab7d2124931fba106c8b60c7'
+ poolId === '0x53b2d22d07E069a3b132BfeaaD275b10273d381E' ||
+ poolId === '0x90040F96aB8f291b6d43A8972806e977631aFFdE' ||
+ poolId === '0x55d86d51Ac3bcAB7ab7d2124931FbA106c8b60c7'
const metrics = [
{
@@ -104,10 +123,15 @@ export const KeyMetrics = ({ poolId }: Props) => {
value: `${capitalize(startCase(metadata?.pool?.asset?.class))} - ${metadata?.pool?.asset?.subClass}`,
},
{
- metric: '30-day APY',
- value: tranchesAPY?.length
+ metric: poolId === '1655476167' || poolId === '1615768079' ? 'Target APY' : '30-day APY',
+ value: tinlakeData[poolId as TinlakeDataKey]
+ ? tinlakeData[poolId as TinlakeDataKey]
+ : poolId === '1655476167' || poolId === '1615768079'
+ ? getHardCodedApy()
+ : tranchesAPY?.length
? tranchesAPY.map((tranche, index) => {
- return tranche && `${tranche} ${index !== tranchesAPY?.length - 1 ? '-' : ''} `
+ const formatted = formatPercentage(tranche)
+ return formatted && `${formatted} ${index !== tranchesAPY?.length - 1 ? '-' : ''}`
})
: '-',
},
@@ -122,14 +146,17 @@ export const KeyMetrics = ({ poolId }: Props) => {
{
metric: 'Min. investment',
value: minInvestmentPerTranche?.length
- ? minInvestmentPerTranche.map((tranche, index) => {
- return tranche && `${tranche} ${index !== minInvestmentPerTranche?.length - 1 ? '-' : ''} `
- })
+ ? minInvestmentPerTranche
+ .sort((a, b) => Number(a) - Number(b))
+ .map((tranche, index) => {
+ const formatted = formatBalanceAbbreviated(tranche?.toNumber() ?? 0, '', 0)
+ return tranche && `$${formatted} ${index !== minInvestmentPerTranche?.length - 1 ? '-' : ''} `
+ })
: '-',
},
{
metric: 'Investor type',
- value: metadata?.pool?.investorType ? metadata?.pool?.investorType : '-',
+ value: isBT3BT4 ? 'Private' : metadata?.pool?.investorType ?? '-',
},
...(!isTinlakePool
? [
@@ -142,7 +169,7 @@ export const KeyMetrics = ({ poolId }: Props) => {
{
metric: 'Pool structure',
- value: metadata?.pool?.poolStructure ? metadata?.pool?.poolStructure : '-',
+ value: isBT3BT4 ? 'Revolving' : metadata?.pool?.poolStructure ?? '-',
},
...(metadata?.pool?.rating?.ratingValue
? [
diff --git a/centrifuge-app/src/components/PoolOverview/TrancheTokenCards.tsx b/centrifuge-app/src/components/PoolOverview/TrancheTokenCards.tsx
index 374666961e..024f8bb187 100644
--- a/centrifuge-app/src/components/PoolOverview/TrancheTokenCards.tsx
+++ b/centrifuge-app/src/components/PoolOverview/TrancheTokenCards.tsx
@@ -7,8 +7,17 @@ import { daysBetween } from '../../utils/date'
import { formatBalance, formatPercentage } from '../../utils/formatting'
import { usePool } from '../../utils/usePools'
import { DataTable } from '../DataTable'
+import { PoolMetaDataPartial } from '../PoolList'
-export const TrancheTokenCards = ({ trancheTokens, poolId }: { trancheTokens: Token[]; poolId: string }) => {
+export const TrancheTokenCards = ({
+ trancheTokens,
+ poolId,
+ metadata,
+}: {
+ trancheTokens: Token[]
+ poolId: string
+ metadata: PoolMetaDataPartial
+}) => {
const pool = usePool(poolId)
const theme = useTheme()
const isTinlakePool = poolId.startsWith('0x')
@@ -23,6 +32,9 @@ export const TrancheTokenCards = ({ trancheTokens, poolId }: { trancheTokens: To
const columnConfig = useMemo(() => {
const calculateApy = (trancheToken: Token) => {
if (isTinlakePool && getTrancheText(trancheToken) === 'senior') return formatPercentage(trancheToken.apy)
+ if (poolId === '1655476167') return '15%'
+ if (poolId === '1615768079' && trancheToken.seniority === 0) return '8%'
+ if (poolId === '1615768079' && trancheToken.seniority === 1) return '16%'
if (daysSinceCreation < 30) return 'N/A'
return trancheToken.yield30DaysAnnualized
? formatPercentage(new Perquintill(trancheToken.yield30DaysAnnualized))
@@ -34,9 +46,10 @@ export const TrancheTokenCards = ({ trancheTokens, poolId }: { trancheTokens: To
header: 'Token',
align: 'left',
formatter: (v: any) => v,
+ width: '40%',
},
{
- header: 'APY',
+ header: poolId === '1655476167' || poolId === '1615768079' ? 'Target' : 'APY',
align: 'left',
formatter: (v: any) => (v ? calculateApy(v) : '-'),
},
@@ -48,7 +61,7 @@ export const TrancheTokenCards = ({ trancheTokens, poolId }: { trancheTokens: To
{
header: 'Token price',
align: 'left',
- formatter: (v: any) => (v ? formatBalance(v, pool?.currency.symbol, pool?.currency.decimals) : '-'),
+ formatter: (v: any) => (v ? formatBalance(v, pool?.currency.symbol, 6) : '-'),
},
...(pool.tranches.length > 1
? [
@@ -64,24 +77,23 @@ export const TrancheTokenCards = ({ trancheTokens, poolId }: { trancheTokens: To
: []),
{
header: '',
- align: 'left',
- formatter: (_: any, row: any) => {
- return
- },
+ align: 'right',
+ formatter: (_: any, row: any) => (
+
+ ),
},
]
- }, [pool, poolId, isTinlakePool, daysSinceCreation])
+ }, [pool, poolId, isTinlakePool, daysSinceCreation, metadata])
const columns = useMemo(() => {
return columnConfig.map((col, index) => {
return {
- align: col.align,
- header: col.header,
cell: (row: any) => (
{col.formatter(row.value[index], row)}
),
+ ...col,
}
})
}, [columnConfig])
diff --git a/centrifuge-app/src/components/PoolOverview/TransactionHistory.tsx b/centrifuge-app/src/components/PoolOverview/TransactionHistory.tsx
index a9c22a7f9e..38718364ef 100644
--- a/centrifuge-app/src/components/PoolOverview/TransactionHistory.tsx
+++ b/centrifuge-app/src/components/PoolOverview/TransactionHistory.tsx
@@ -23,6 +23,7 @@ type Row = {
hash: string
netFlow?: 'positive' | 'negative' | 'neutral'
label: string
+ sublabel?: string
}
export const TransactionHistory = ({
@@ -106,7 +107,7 @@ export const TransactionHistoryTable = ({
if (transaction.type === 'INCREASE_DEBT') {
return {
- label: 'Correction of',
+ label: 'Correction ↑ of',
amount: transaction.amount,
netFlow: 'positive',
}
@@ -114,7 +115,7 @@ export const TransactionHistoryTable = ({
if (transaction.type === 'DECREASE_DEBT') {
return {
- label: 'Correction of',
+ label: 'Correction ↓ of',
amount: transaction.amount,
netFlow: 'negative',
}
@@ -149,9 +150,10 @@ export const TransactionHistoryTable = ({
}
return {
- label: 'Principal payment from',
+ label: 'Sale of',
amount: transaction.principalAmount,
netFlow,
+ sublabel: 'settled into',
}
}
@@ -195,7 +197,7 @@ export const TransactionHistoryTable = ({
const tableData =
transformedTransactions.slice(0, preview ? 8 : Infinity).map((transaction) => {
- const { amount, netFlow, label } = getLabelAndAmount(transaction)
+ const { amount, netFlow, label, sublabel } = getLabelAndAmount(transaction)
return {
activeAssetId,
netFlow,
@@ -209,6 +211,7 @@ export const TransactionHistoryTable = ({
amount: amount || 0,
hash: transaction.hash,
label,
+ sublabel,
}
}) || []
@@ -226,22 +229,23 @@ export const TransactionHistoryTable = ({
{
align: 'left',
header: ,
- cell: ({ activeAssetId, assetId, assetName, fromAssetId, toAssetId, toAssetName, label }: Row) => {
+ cell: ({ assetId, assetName, toAssetId, toAssetName, label, sublabel, fromAssetName, fromAssetId }: Row) => {
const base = `${basePath}/${poolId}/assets/`
- return fromAssetId || toAssetId || activeAssetId ? (
+ const isCashTransfer = label === 'Cash transfer from'
+ return (
- {label} {assetName}{' '}
+ {label}{' '}
+
+ {isCashTransfer ? fromAssetName : assetName}
+ {' '}
{toAssetName ? (
<>
{' '}
- to {toAssetName}
+ {sublabel ? sublabel : `to`}{' '}
+ {toAssetName}
>
) : null}
- ) : (
-
- {assetName || `Asset ${assetId?.split('-')[1]}`}
-
)
},
sortKey: 'transaction',
diff --git a/centrifuge-app/src/components/Report/AssetList.tsx b/centrifuge-app/src/components/Report/AssetList.tsx
index 5a9f7c7497..0166310ce4 100644
--- a/centrifuge-app/src/components/Report/AssetList.tsx
+++ b/centrifuge-app/src/components/Report/AssetList.tsx
@@ -309,7 +309,14 @@ export function AssetList({ pool }: { pool: Pool }) {
return data.length > 0 ? (
-
+
) : (
diff --git a/centrifuge-app/src/components/Report/AssetTransactions.tsx b/centrifuge-app/src/components/Report/AssetTransactions.tsx
index 24ac1a1597..633f5f1390 100644
--- a/centrifuge-app/src/components/Report/AssetTransactions.tsx
+++ b/centrifuge-app/src/components/Report/AssetTransactions.tsx
@@ -157,7 +157,7 @@ export function AssetTransactions({ pool }: { pool: Pool }) {
return data.length > 0 ? (
-
+
) : (
diff --git a/centrifuge-app/src/components/Report/DataFilter.tsx b/centrifuge-app/src/components/Report/DataFilter.tsx
index 6ef056c6a9..6b146626b0 100644
--- a/centrifuge-app/src/components/Report/DataFilter.tsx
+++ b/centrifuge-app/src/components/Report/DataFilter.tsx
@@ -3,6 +3,7 @@ import { useGetNetworkName } from '@centrifuge/centrifuge-react'
import { AnchorButton, Box, DateInput, IconDownload, SearchInput, Select, Shelf } from '@centrifuge/fabric'
import * as React from 'react'
import { useNavigate } from 'react-router'
+import { useIsAboveBreakpoint } from '../../../src/utils/useIsAboveBreakpoint'
import { usePool } from '../../../src/utils/usePools'
import { nftMetadataSchema } from '../../schemas'
import { useBasePath } from '../../utils/useBasePath'
@@ -44,6 +45,7 @@ export function DataFilter({ poolId }: ReportFilterProps) {
const navigate = useNavigate()
const basePath = useBasePath()
const pool = usePool(poolId) as Pool
+ const isMedium = useIsAboveBreakpoint('M')
const { data: domains } = useActiveDomains(pool.id)
const getNetworkName = useGetNetworkName()
@@ -73,272 +75,196 @@ export function DataFilter({ poolId }: ReportFilterProps) {
bg="backgroundSecondary"
alignItems="flex-start"
>
-
-
-
-
- {['pool-balance', 'token-price'].includes(report) && (
+
+
- )}
-
- {report === 'asset-list' && (
-
-
- )}
- {(report === 'investor-list' || report === 'investor-tx') && (
-
-
+ )}
- {['investor-tx', 'asset-tx', 'fee-tx'].includes(report) && (
-
- {
- if (event.target.value) {
- setTxType(event.target.value)
- }
- }}
- />
-
- )}
- {['investor-tx', 'investor-list'].includes(report) && (
- <>
+ {report === 'asset-list' && (
{
- return {
- label: getNetworkName(domain.chainId),
- value: String(domain.chainId),
- }
- }),
+ { label: 'All', value: 'all' },
+ { label: 'Ongoing', value: 'ongoing' },
+ { label: 'Repaid', value: 'repaid' },
+ { label: 'Overdue', value: 'overdue' },
+ ]}
+ value={loanStatus}
+ onChange={(event) => setLoanStatus(event.target.value)}
+ />
+
+ )}
+
+ {(report === 'investor-list' || report === 'investor-tx') && (
+
+ ({
+ label: token.currency.name,
+ value: token.id,
+ })),
]}
- value={network}
- onChange={(e) => {
- const { value } = e.target
- if (value) {
- setNetwork(isNaN(Number(value)) ? value : Number(value))
+ value={activeTranche}
+ onChange={(event) => {
+ if (event.target.value) {
+ setActiveTranche(event.target.value)
}
}}
/>
-
- setAddress(e.target.value)}
+ )}
+
+ {report === 'asset-tx' && (
+
+ setLoan(event.target.value)}
+ value={loan}
+ options={[
+ { label: 'All', value: 'all' },
+ ...(loans?.map((l) => ({
+ value: l.id,
+ label: ,
+ })) ?? []),
+ ]}
/>
- >
- )}
-
-
- {!['investor-list', 'asset-list'].includes(report) && (
- <>
+ )}
+
+ {['investor-tx', 'asset-tx', 'fee-tx'].includes(report) && (
- setStartDate(e.target.value)} />
+ {
+ if (event.target.value) {
+ setTxType(event.target.value)
+ }
+ }}
+ />
- setEndDate(e.target.value)} />
- >
- )}
- {report === 'asset-list' && (
- setStartDate(e.target.value)} />
- )}
+ )}
- }
- small
- variant="inverted"
- style={{ marginTop: 28, marginLeft: 12 }}
- >
- CSV
-
+ {['investor-tx', 'investor-list'].includes(report) && (
+ <>
+
+ ({
+ label: getNetworkName(domain.chainId),
+ value: String(domain.chainId),
+ })),
+ ]}
+ value={network}
+ onChange={(e) => {
+ const { value } = e.target
+ if (value) {
+ setNetwork(isNaN(Number(value)) ? value : Number(value))
+ }
+ }}
+ />
+
+ >
+ )}
+
+
+
+
+ setStartDate(e.target.value)} />
+
+ setEndDate(e.target.value)} />
+
+ }
+ small
+ variant="inverted"
+ style={{ marginLeft: '12px', marginTop: '22px' }}
+ >
+ Download
+
+
+
+ {['investor-tx', 'investor-list'].includes(report) && (
+
+ setAddress(e.target.value)}
+ />
+
+ )}
)
}
diff --git a/centrifuge-app/src/components/Report/FeeTransactions.tsx b/centrifuge-app/src/components/Report/FeeTransactions.tsx
index d4db8adf75..34a72162a5 100644
--- a/centrifuge-app/src/components/Report/FeeTransactions.tsx
+++ b/centrifuge-app/src/components/Report/FeeTransactions.tsx
@@ -124,7 +124,7 @@ export function FeeTransactions({ pool }: { pool: Pool }) {
return data.length > 0 ? (
-
+
) : (
diff --git a/centrifuge-app/src/components/Report/InvestorList.tsx b/centrifuge-app/src/components/Report/InvestorList.tsx
index 792c32eb70..2d5e4cda5c 100644
--- a/centrifuge-app/src/components/Report/InvestorList.tsx
+++ b/centrifuge-app/src/components/Report/InvestorList.tsx
@@ -170,7 +170,7 @@ export function InvestorList({ pool }: { pool: Pool }) {
return data.length > 0 ? (
-
+
) : (
diff --git a/centrifuge-app/src/components/Report/InvestorTransactions.tsx b/centrifuge-app/src/components/Report/InvestorTransactions.tsx
index 7754d6bd24..95dea6d84a 100644
--- a/centrifuge-app/src/components/Report/InvestorTransactions.tsx
+++ b/centrifuge-app/src/components/Report/InvestorTransactions.tsx
@@ -266,7 +266,7 @@ export function InvestorTransactions({ pool }: { pool: Pool }) {
return data.length > 0 ? (
-
+
) : (
diff --git a/centrifuge-app/src/components/Report/OracleTransactions.tsx b/centrifuge-app/src/components/Report/OracleTransactions.tsx
index c36ac98451..507ae03b38 100644
--- a/centrifuge-app/src/components/Report/OracleTransactions.tsx
+++ b/centrifuge-app/src/components/Report/OracleTransactions.tsx
@@ -96,7 +96,7 @@ export function OracleTransactions({ pool }: { pool: Pool }) {
return data.length > 0 ? (
-
+
) : (
diff --git a/centrifuge-app/src/components/Report/PoolReportPage.tsx b/centrifuge-app/src/components/Report/PoolReportPage.tsx
index c2f0124750..178707993c 100644
--- a/centrifuge-app/src/components/Report/PoolReportPage.tsx
+++ b/centrifuge-app/src/components/Report/PoolReportPage.tsx
@@ -12,6 +12,7 @@ import { ReportFilter } from './ReportFilter'
export function PoolReportPage({ header }: { header: React.ReactNode }) {
const params = useParams<{ pid: string; '*': string }>()
const location = useLocation()
+ const isReportingTab = location.pathname.includes('reporting')
const { pid: poolId } = params
if (!poolId) throw new Error('Pool not found')
@@ -20,24 +21,44 @@ export function PoolReportPage({ header }: { header: React.ReactNode }) {
{header}
- {location.pathname.includes('reporting') ? : }
+ {isReportingTab ? : }
-
+
)
}
-function PoolDetailReporting({ poolId }: { poolId: string }) {
+function PoolDetailReporting({ poolId, isReportingTab }: { poolId: string; isReportingTab: boolean }) {
const pool = usePool(poolId) as Pool
+ const contentWrapperRef = React.useRef(null)
+
+ // We want to scroll within the table and not the page,
+ // this way we can keep the filters on top of the page while scrolling on the table
+ React.useEffect(() => {
+ const contentWrapper = document.getElementById('content-wrapper')
+
+ if (contentWrapper && !isReportingTab) {
+ contentWrapper.style.overflow = 'hidden'
+ }
+
+ return () => {
+ if (contentWrapper) {
+ contentWrapper.style.overflow = ''
+ }
+ }
+ }, [isReportingTab])
+
if (!poolId || !pool) {
return
}
return (
- }>
-
-
+
+ }>
+
+
+
)
}
diff --git a/centrifuge-app/src/components/Report/ReportFilter.tsx b/centrifuge-app/src/components/Report/ReportFilter.tsx
index e4fcb9c62e..b869a77c1b 100644
--- a/centrifuge-app/src/components/Report/ReportFilter.tsx
+++ b/centrifuge-app/src/components/Report/ReportFilter.tsx
@@ -30,7 +30,8 @@ const StyledButton = styled(Button)`
margin-bottom: 0;
}
& > span {
- border-color: ${({ selected, theme }) => (selected ? 'transparent' : theme.colors.backgroundInverted)};
+ border-width: 1px;
+ border-color: ${({ selected }) => (selected ? 'transparent' : '#B7B7B7')};
}
&:hover > span {
border-color: ${({ selected, theme }) => (selected ? 'transparent' : theme.colors.backgroundInverted)};
@@ -183,7 +184,7 @@ export function ReportFilter({ poolId }: ReportFilterProps) {
small
variant="inverted"
>
- CSV
+ Download
diff --git a/centrifuge-app/src/components/Report/TokenPrice.tsx b/centrifuge-app/src/components/Report/TokenPrice.tsx
index d461862ba1..7e1f2a992d 100644
--- a/centrifuge-app/src/components/Report/TokenPrice.tsx
+++ b/centrifuge-app/src/components/Report/TokenPrice.tsx
@@ -294,7 +294,7 @@ export function TokenPrice({ pool }: { pool: Pool }) {
return poolStates?.length > 0 ? (
-
+
) : (
diff --git a/centrifuge-app/src/components/Tooltips.tsx b/centrifuge-app/src/components/Tooltips.tsx
index f97ce41d58..411ab874ec 100644
--- a/centrifuge-app/src/components/Tooltips.tsx
+++ b/centrifuge-app/src/components/Tooltips.tsx
@@ -349,9 +349,10 @@ export type TooltipsProps = {
label?: string | React.ReactNode
props?: any
size?: 'med' | 'sm'
+ color?: string
} & Partial>
-export function Tooltips({ type, label: labelOverride, size = 'sm', props, ...textProps }: TooltipsProps) {
+export function Tooltips({ type, label: labelOverride, size = 'sm', props, color, ...textProps }: TooltipsProps) {
const { label, body } = type ? tooltipText[type] : { label: labelOverride, body: textProps.body }
return (
@@ -359,7 +360,7 @@ export function Tooltips({ type, label: labelOverride, size = 'sm', props, ...te
{labelOverride || label}
diff --git a/centrifuge-app/src/pages/Pool/Overview/index.tsx b/centrifuge-app/src/pages/Pool/Overview/index.tsx
index 6a7b3fb4bd..074ecee36e 100644
--- a/centrifuge-app/src/pages/Pool/Overview/index.tsx
+++ b/centrifuge-app/src/pages/Pool/Overview/index.tsx
@@ -1,9 +1,11 @@
import { CurrencyBalance, Price } from '@centrifuge/centrifuge-js'
+import { useWallet } from '@centrifuge/centrifuge-react'
import { Box, Button, Card, Grid, TextWithPlaceholder } from '@centrifuge/fabric'
import Decimal from 'decimal.js-light'
import * as React from 'react'
import { useParams } from 'react-router'
import styled, { useTheme } from 'styled-components'
+import { InvestRedeemContext, InvestRedeemProvider } from '../../../../src/components/InvestRedeem/InvestRedeemProvider'
import { InvestRedeemProps } from '../../../components/InvestRedeem/InvestRedeem'
import { InvestRedeemDrawer } from '../../../components/InvestRedeem/InvestRedeemDrawer'
import { IssuerDetails, ReportDetails } from '../../../components/IssuerSection'
@@ -20,7 +22,6 @@ import { formatBalance } from '../../../utils/formatting'
import { getPoolValueLocked } from '../../../utils/getPoolValueLocked'
import { useAverageMaturity } from '../../../utils/useAverageMaturity'
import { useConnectBeforeAction } from '../../../utils/useConnectBeforeAction'
-import { useIsAboveBreakpoint } from '../../../utils/useIsAboveBreakpoint'
import { usePool, usePoolMetadata } from '../../../utils/usePools'
import { PoolDetailHeader } from '../Header'
@@ -118,16 +119,22 @@ export function PoolDetailOverview() {
{tokens.length > 0 && (
}>
-
+
)}
}>
-
-
+
+
{metadata?.pool?.reports?.length || !isTinlakePool ? (
-
+
) : null}
@@ -144,20 +151,52 @@ export function PoolDetailOverview() {
}
export function InvestButton(props: InvestRedeemProps) {
+ const { poolId, trancheId, metadata } = props
const [open, setOpen] = React.useState(false)
const connectAndOpen = useConnectBeforeAction(() => setOpen(true))
- const isMedium = useIsAboveBreakpoint('M')
+ const { connectedType, showNetworks } = useWallet()
+
+ const getButtonText = (state: any) => {
+ if (!state.isAllowedToInvest && connectedType !== null) {
+ return 'Onboard'
+ } else if (connectedType === null) {
+ return 'Connect'
+ } else {
+ return state.isFirstInvestment ? 'Invest' : 'Invest/Redeem'
+ }
+ }
return (
<>
setOpen(false)} {...props} />
-
+
+
+ {({ state }) => {
+ if (!state) return
+ const isLoading = state?.isDataLoading
+
+ const buttonText = getButtonText(state)
+
+ return (
+
+ )
+ }}
+
+
>
)
}
diff --git a/fabric/src/components/Button/VisualButton.tsx b/fabric/src/components/Button/VisualButton.tsx
index f16acb62ee..65b9f03f99 100644
--- a/fabric/src/components/Button/VisualButton.tsx
+++ b/fabric/src/components/Button/VisualButton.tsx
@@ -93,7 +93,7 @@ export const StyledButton = styled.span(
color: $disabled ? fgDisabled : $active ? fgHover : fg,
backgroundColor: $disabled ? bgDisabled : $active ? bgHover : bg,
borderColor: $disabled ? borderDisabled : $active ? borderHover : border,
- borderWidth: 1,
+ borderWidth: 3,
borderRadius: 'button',
pointerEvents: $disabled ? 'none' : 'initial',
minHeight: $small ? 32 : 40,
@@ -171,7 +171,7 @@ export function VisualButton({
) : (
<>
{children && (
-
+
{children}
)}
diff --git a/fabric/src/components/Button/WalletButton.tsx b/fabric/src/components/Button/WalletButton.tsx
index 47a32bd635..0488669e84 100644
--- a/fabric/src/components/Button/WalletButton.tsx
+++ b/fabric/src/components/Button/WalletButton.tsx
@@ -24,16 +24,18 @@ const StyledButton = styled.button`
display: inline-block;
width: 100%;
padding: 0;
- border: none;
+ border-width: 3;
+ border-color: transparent;
appearance: none;
background-color: ${({ theme }) => theme.colors.backgroundPrimary};
outline: 0;
border-radius: 40px;
white-space: nowrap;
& > span {
+ border-width: 3;
border-color: ${({ theme }) => theme.colors.backgroundPrimary};
:hover {
- border-color: ${({ theme }) => theme.colors.backgroundPrimary};
+ border: ${({ theme }) => `3px solid ${theme.colors.borderSecondary}`};
}
}
`
diff --git a/fabric/src/components/InputUnit/index.tsx b/fabric/src/components/InputUnit/index.tsx
index 831969820d..f5f25902f4 100644
--- a/fabric/src/components/InputUnit/index.tsx
+++ b/fabric/src/components/InputUnit/index.tsx
@@ -24,7 +24,7 @@ export function InputUnit({ id, label, secondaryLabel, errorMessage, inputElemen
return (
-
+
{label && (
{label}
diff --git a/fabric/src/components/Select/index.tsx b/fabric/src/components/Select/index.tsx
index e6e1eb8afa..c3e3088bcc 100644
--- a/fabric/src/components/Select/index.tsx
+++ b/fabric/src/components/Select/index.tsx
@@ -32,7 +32,7 @@ const StyledSelect = styled.select`
cursor: pointer;
line-height: inherit;
text-overflow: ellipsis;
- font-weight: 500;
+ font-weight: 400;
&:disabled {
cursor: default;
@@ -44,6 +44,7 @@ export function SelectInner({
placeholder,
disabled,
small,
+ hideBorder,
...rest
}: Omit) {
return (
@@ -68,7 +69,12 @@ export function SelectInner({
)}
{options.map((option, index) => (
-
))}
@@ -88,7 +94,7 @@ export function Select({ label, errorMessage, id, hideBorder, ...rest }: SelectP
errorMessage={errorMessage}
inputElement={
-
+
}
/>
diff --git a/fabric/src/components/Tabs/index.tsx b/fabric/src/components/Tabs/index.tsx
index 1d5ff62d13..7c040244d5 100644
--- a/fabric/src/components/Tabs/index.tsx
+++ b/fabric/src/components/Tabs/index.tsx
@@ -9,6 +9,7 @@ export type TabsProps = {
selectedIndex: number
onChange?: (index: number) => void
children: (React.ReactElement | string | boolean | null | undefined)[]
+ variant?: 'primary' | 'secondary'
}
export function Tabs({ selectedIndex, onChange, children }: TabsProps) {
@@ -29,7 +30,12 @@ export function Tabs({ selectedIndex, onChange, children }: TabsProps) {
)
}
-const StyledTabsItem = styled.button<{ $active?: boolean; styleOverrides?: React.CSSProperties, showBorder?: boolean }>(
+const StyledTabsItem = styled.button<{
+ $active?: boolean
+ styleOverrides?: React.CSSProperties
+ showBorder?: boolean
+ variant: 'primary' | 'secondary'
+}>(
{
display: 'flex',
alignItems: 'center',
@@ -43,18 +49,22 @@ const StyledTabsItem = styled.button<{ $active?: boolean; styleOverrides?: React
appearance: 'none',
background: 'transparent',
},
- ({ $active, theme, styleOverrides, showBorder }) => {
+ ({ $active, theme, styleOverrides, showBorder, variant }) => {
return css({
paddingTop: 1,
paddingLeft: 2,
paddingRight: 2,
paddingBottom: 2,
color: $active ? 'textPrimary' : 'textSecondary',
- boxShadow: $active ? `inset 0 -2px 0 ${theme.colors.textGold}` : showBorder ? `inset 0 -2px 0 ${theme.colors.textDisabled}` : 'none',
+ boxShadow: $active
+ ? `inset 0 -2px 0 ${variant === 'secondary' ? theme.colors.textPrimary : theme.colors.textGold}`
+ : showBorder
+ ? `inset 0 -2px 0 ${theme.colors.textDisabled}`
+ : 'none',
fontWeight: 400,
'&:hover, &:active, &:focus-visible': {
- color: 'textGold',
+ color: $active ? 'textPrimary' : 'textGold',
},
...styleOverrides,
})
@@ -80,6 +90,7 @@ export function TabsItem({
ariaLabel,
styleOverrides,
showBorder,
+ variant = 'primary',
...rest
}: TabsItemPrivateProps) {
return (
@@ -90,6 +101,7 @@ export function TabsItem({
aria-label={ariaLabel}
styleOverrides={styleOverrides}
showBorder={showBorder}
+ variant={variant}
{...rest}
>
diff --git a/fabric/src/icon-svg/icon-download.svg b/fabric/src/icon-svg/icon-download.svg
index 36e3604eb9..b1b7145f29 100644
--- a/fabric/src/icon-svg/icon-download.svg
+++ b/fabric/src/icon-svg/icon-download.svg
@@ -1,4 +1,5 @@
-