diff --git a/centrifuge-app/src/assets/images/prime_page_image.svg b/centrifuge-app/src/assets/images/prime_page_image.svg
new file mode 100644
index 0000000000..a64d1f53e1
--- /dev/null
+++ b/centrifuge-app/src/assets/images/prime_page_image.svg
@@ -0,0 +1,9 @@
+
diff --git a/centrifuge-app/src/components/AssetSummary.tsx b/centrifuge-app/src/components/AssetSummary.tsx
index a3cb456af4..bfaa4f4021 100644
--- a/centrifuge-app/src/components/AssetSummary.tsx
+++ b/centrifuge-app/src/components/AssetSummary.tsx
@@ -1,4 +1,4 @@
-import { Shelf, Stack, Text } from '@centrifuge/fabric'
+import { Box, Shelf, Stack, Text } from '@centrifuge/fabric'
import * as React from 'react'
import { useTheme } from 'styled-components'
@@ -7,6 +7,7 @@ type Props = {
label: React.ReactNode
value: React.ReactNode
heading: boolean
+ children?: React.ReactNode
}[]
children?: React.ReactNode
}
@@ -22,12 +23,17 @@ export function AssetSummary({ data, children }: Props) {
mx={[2, 2, 2, 2, 5]}
>
- {data?.map(({ label, value, heading }, index) => (
+ {data?.map(({ label, value, heading, children }, index) => (
{label}
- {value}
+
+
+ {value}
+
+ {children && children}
+
))}
{children}
diff --git a/centrifuge-app/src/components/BackButton.tsx b/centrifuge-app/src/components/BackButton.tsx
new file mode 100644
index 0000000000..e47b636cb0
--- /dev/null
+++ b/centrifuge-app/src/components/BackButton.tsx
@@ -0,0 +1,54 @@
+import { Box, IconArrowLeft, Text } from '@centrifuge/fabric'
+import { ReactNode } from 'react'
+import styled from 'styled-components'
+import { RouterLinkButton } from './RouterLinkButton'
+
+const StyledRouterLinkButton = styled(RouterLinkButton)`
+ margin-left: 14px;
+ border-radius: 50%;
+ margin: 0px;
+ padding: 0px;
+ width: fit-content;
+ margin-left: 30px;
+ border: 4px solid transparent;
+ width: 30px;
+ height: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ > span {
+ width: 34px;
+ border: 4px solid transparent;
+ }
+ &:hover {
+ background-color: ${({ theme }) => theme.colors.backgroundSecondary};
+ span {
+ color: ${({ theme }) => theme.colors.textPrimary};
+ }
+ }
+`
+
+export const BackButton = ({
+ to,
+ children,
+ label,
+ align,
+}: {
+ to: string
+ children?: ReactNode
+ label: string
+ align?: string
+}) => {
+ return (
+
+ } variant="tertiary" />
+
+
+ {label}
+
+ {children}
+
+
+ )
+}
diff --git a/centrifuge-app/src/components/Charts/SimpleLineChart.tsx b/centrifuge-app/src/components/Charts/SimpleLineChart.tsx
index 561af846c2..cea919dff6 100644
--- a/centrifuge-app/src/components/Charts/SimpleLineChart.tsx
+++ b/centrifuge-app/src/components/Charts/SimpleLineChart.tsx
@@ -1,11 +1,19 @@
import { CurrencyBalance, CurrencyMetadata } from '@centrifuge/centrifuge-js'
-import { Card, Shelf, Stack, Text } from '@centrifuge/fabric'
+import { Box, Card, Select, Shelf, Stack, Text } from '@centrifuge/fabric'
+import { useState } from 'react'
import { CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'
import { useTheme } from 'styled-components'
import { formatDate } from '../../utils/date'
import { formatBalance, formatBalanceAbbreviated } from '../../utils/formatting'
import { TooltipContainer, TooltipTitle } from './Tooltip'
+const rangeFilters = [
+ { value: '30d', label: '30 days' },
+ { value: '90d', label: '90 days' },
+ { value: 'ytd', label: 'Year to date' },
+ { value: 'all', label: 'All' },
+]
+
type ChartData = {
name: string
yAxis: Number
@@ -14,21 +22,65 @@ type ChartData = {
interface Props {
data: ChartData[]
currency?: CurrencyMetadata
+ tooltip?: (value: any) => { value: any; label: string }
+ withFilters?: boolean
+ filters: {
+ type: 'default' | { value: string; label: string }
+ title: string
+ legend: [
+ {
+ label: string
+ color: string
+ value: string
+ }
+ ]
+ }
}
-export const SimpleLineChart = ({ data, currency }: Props) => {
+const Dot = ({ color }: { color: string }) => (
+
+)
+
+// We use the chart for pages: asset, prime, portfolio
+export const SimpleLineChart = ({ data, currency, tooltip, filters }: Props) => {
const theme = useTheme()
const chartColor = theme.colors.accentPrimary
+ const [range, setRange] = useState<(typeof rangeFilters)[number]>(rangeFilters[0])
+
+ const toggleRange = (e: React.ChangeEvent) => {
+ const value = e.target.value
+ const range = rangeFilters.find((range) => range.value === value)
+ setRange(range ?? rangeFilters[0])
+ }
+
+ console.log(data)
+
return (
+ {filters && {filters.title}}
+ {filters && (
+
+ {filters.legend.map((legend) => (
+
+
+
+
+ {legend.label}
+
+
+ {legend.value}
+
+ ))}
+
+
+ )}
{!data.length && (
No data available
)}
-
{data?.length ? (
@@ -70,6 +122,15 @@ export const SimpleLineChart = ({ data, currency }: Props) => {
{formatDate(payload[0].payload.name)}
{payload.map(({ value }, index) => {
+ if (tooltip) {
+ const { label, value: val } = tooltip(payload[0].payload)
+ return (
+
+ {label}
+ {val}
+
+ )
+ }
return (
Value
diff --git a/centrifuge-app/src/pages/Loan/index.tsx b/centrifuge-app/src/pages/Loan/index.tsx
index ae322dbb85..51a34b8ed6 100644
--- a/centrifuge-app/src/pages/Loan/index.tsx
+++ b/centrifuge-app/src/pages/Loan/index.tsx
@@ -7,23 +7,11 @@ import {
PricingInfo,
TinlakeLoan,
} from '@centrifuge/centrifuge-js'
-import {
- Box,
- Button,
- Card,
- Drawer,
- Grid,
- IconArrowLeft,
- Shelf,
- Spinner,
- Stack,
- Text,
- truncate,
-} from '@centrifuge/fabric'
+import { Box, Button, Card, Drawer, Grid, Shelf, Spinner, Stack, Text, truncate } from '@centrifuge/fabric'
import * as React from 'react'
import { useParams } from 'react-router'
-import styled from 'styled-components'
import { AssetSummary } from '../../../src/components/AssetSummary'
+import { BackButton } from '../../../src/components/BackButton'
import { SimpleLineChart } from '../../../src/components/Charts/SimpleLineChart'
import { LoanLabel, getLoanLabelStatus } from '../../../src/components/LoanLabel'
import { Dec } from '../../../src/utils/Decimal'
@@ -33,7 +21,6 @@ import { LayoutSection } from '../../components/LayoutBase/LayoutSection'
import { LoadBoundary } from '../../components/LoadBoundary'
import { PageSection } from '../../components/PageSection'
import { TransactionHistoryTable } from '../../components/PoolOverview/TransactionHistory'
-import { RouterLinkButton } from '../../components/RouterLinkButton'
import { Tooltips } from '../../components/Tooltips'
import { nftMetadataSchema } from '../../schemas'
import { LoanTemplate } from '../../types'
@@ -62,28 +49,6 @@ function isTinlakeLoan(loan: LoanType | TinlakeLoan): loan is TinlakeLoan {
return loan.poolId.startsWith('0x')
}
-const StyledRouterLinkButton = styled(RouterLinkButton)`
- margin-left: 14px;
- border-radius: 50%;
- margin: 0px;
- padding: 0px;
- width: fit-content;
- margin-left: 30px;
- border: 4px solid transparent;
-
- > span {
- width: 34px;
- border: 4px solid transparent;
- }
- &:hover {
- background-color: ${({ theme }) => theme.colors.backgroundSecondary};
- border: ${({ theme }) => `4px solid ${theme.colors.backgroundTertiary}`};
- span {
- color: ${({ theme }) => theme.colors.textPrimary};
- }
- }
-`
-
const positiveNetflows = ['DEPOSIT_FROM_INVESTMENTS', 'INCREASE_DEBT']
function ActionButtons({ loan }: { loan: LoanType }) {
@@ -219,15 +184,9 @@ function Loan() {
return (
-
-
-
-
- {name}
-
- {loan && }
-
-
+
+ {loan && }
+
}
-function PrimeDetail() {
+const PrimeDetail = () => {
+ const theme = useTheme()
const { dao: daoSlug } = useParams<{ dao: string }>()
const { data: DAOs, isLoading } = useDAOConfig()
const dao = DAOs?.find((d) => d.slug === daoSlug)
@@ -25,30 +30,75 @@ function PrimeDetail() {
? utils.evmToSubstrateAddress(dao.address, dao.network)
: addressToHex(dao.address))
+ const tokens = useHoldings(centAddress)
+ const currentPortfolioValue = tokens.reduce((sum, token) => sum.add(token.position.mul(token.tokenPrice)), Dec(0))
+ const transactions = useTransactionsByAddress(centAddress)
+ const dailyPortfolioValue = useDailyPortfolioValue(centAddress ?? '', 90)
+
+ const chartData = dailyPortfolioValue?.map((day) => ({
+ name: day.dateInMilliseconds,
+ yAxis: day.portfolioValue.toNumber(),
+ }))
+
+ const filters = {
+ type: 'default',
+ title: 'Overview',
+ legend: [
+ {
+ label: 'Portfolio value',
+ color: 'textGold',
+ value: '1,523.00',
+ },
+ ],
+ }
+
+ const valueFormatter = (value: any) => {
+ return { value: formatBalanceAbbreviated(value.yAxis, '', 2), label: 'Portfolio Value' }
+ }
+
return !isLoading && dao && centAddress ? (
- <>
-
-
-
-
- Prime
-
- {' '}
- / {dao.name} Investments
-
-
-
- {dao.name} Investments
-
-
-
-
+
+
+
+
+ +25
+
+
+ Since inception
+
+
+ ),
+ },
+ {
+ label: 'Realized P&L',
+ value: formatBalance(0, config.baseCurrency),
+ heading: false,
+ },
+ {
+ label: 'Unrealized P&L',
+ value: formatBalance(0, config.baseCurrency),
+ heading: false,
+ },
+ ]}
+ />
+
+
+
+
+ {/*
-
- >
+ */}
+
) : null
}
diff --git a/centrifuge-app/src/pages/Prime/index.tsx b/centrifuge-app/src/pages/Prime/index.tsx
index 7ffbccba0d..ef555e2965 100644
--- a/centrifuge-app/src/pages/Prime/index.tsx
+++ b/centrifuge-app/src/pages/Prime/index.tsx
@@ -1,6 +1,9 @@
import { CurrencyBalance, addressToHex } from '@centrifuge/centrifuge-js'
import { useCentrifugeUtils, useGetNetworkName } from '@centrifuge/centrifuge-react'
-import { AnchorButton, Box, IconExternalLink, Shelf, Text, TextWithPlaceholder } from '@centrifuge/fabric'
+import { Box, Grid, IconExternalLink, IconGlobe, Shelf, Text, TextWithPlaceholder } from '@centrifuge/fabric'
+import { useTheme } from 'styled-components'
+import { AnchorTextLink } from '../../../src/components/TextLink'
+import primePageImage from '../../assets/images/prime_page_image.svg'
import { Column, DataTable, FilterableTableHeader, SortableTableHeader } from '../../components/DataTable'
import { LayoutSection } from '../../components/LayoutBase/LayoutSection'
import { formatDate } from '../../utils/date'
@@ -15,29 +18,59 @@ export default function PrimePage() {
}
function Prime() {
+ const theme = useTheme()
return (
<>
-
- Centrifuge Prime
-
-
- Centrifuge Prime was built to meet the needs of large decentralized organizations and protocols. Through
- Centrifuge Prime, DeFi native organizations can integrate with the largest financial markets in the world
- and take advantage of real yields from real economic activity - all onchain. Assets tailored to your needs,
- processes adapted to your governance, and all through decentralized rails.
-
-
-
-
+
+
- Go to website
-
+
+
+
+ Centrifuge Prime
+
+
+
+
+ Centrifuge Prime was built to meet the needs of large decentralized organizations and protocols. Through
+ Centrifuge Prime, DeFi native organizations can integrate with the largest financial markets in the world
+ and take advantage of real yields from real economic activity - all onchain. Assets tailored to your
+ needs, processes adapted to your governance, and all through decentralized rails.
+
+
+
+ Go to website
+
+
+
+
+
+
+
+
+
>
)
@@ -134,30 +167,6 @@ function DaoPortfoliosTable() {
trancheBalances[trancheId] = { balance, tokenPrice }
}
})
- // const trancheBalances = !!account
- // ? Object.fromEntries(
- // account.trancheBalances.nodes.map((tranche: any) => {
- // const pool = pools?.find((p) => p.id === tranche.poolId)
- // const decimals = pool?.currency.decimals ?? 18
- // const tokenPrice = pool?.tranches.find((t) => tranche.trancheId.endsWith(t.id))?.tokenPrice?.toFloat() ?? 1
- // let balance = new CurrencyBalance(
- // new BN(tranche.claimableTrancheTokens).add(new BN(tranche.pendingRedeemTrancheTokens)),
- // decimals
- // ).toFloat()
-
- // const subqueryCurrencies = account?.currencyBalances.nodes.filter(
- // (b: any) => b.currency.trancheId && b.currency.trancheId === tranche.trancheId
- // )
- // if (subqueryCurrencies.length) {
- // balance += subqueryCurrencies.reduce(
- // (acc: number, cur: any) => acc + new CurrencyBalance(cur.amount, decimals).toFloat(),
- // 0
- // )
- // }
- // return [tranche.trancheId.split('-')[1], { balance, tokenPrice }]
- // })
- // )
- // : {}
const totalValue = Object.values(trancheBalances)?.reduce(
(acc, { balance, tokenPrice }) => acc + balance * tokenPrice,
0
@@ -184,6 +193,7 @@ function DaoPortfoliosTable() {
{row.name}
),
+ width: '2fr',
},
{
align: 'left',
@@ -196,8 +206,10 @@ function DaoPortfoliosTable() {
/>
),
cell: (row: Row) => {getNetworkName(row.network)},
+ width: '2fr',
},
{
+ align: 'left',
header: ,
cell: (row: Row) => (
@@ -205,6 +217,7 @@ function DaoPortfoliosTable() {
),
sortKey: 'value',
+ width: '2fr',
},
{
align: 'left',
@@ -219,7 +232,10 @@ function DaoPortfoliosTable() {
]
return (
-
+
+
+ Portfolios
+
`/prime/${row.slug}`}
/>
-
+
)
}
diff --git a/fabric/src/theme/tokens/colors.ts b/fabric/src/theme/tokens/colors.ts
index ba4cf42d8a..84c6e9d42e 100644
--- a/fabric/src/theme/tokens/colors.ts
+++ b/fabric/src/theme/tokens/colors.ts
@@ -2,7 +2,7 @@ export const black = '#252B34'
export const gold = '#FFC012'
export const grayScale = {
- 10: '#cfcfcf33',
+ 10: '#F9FAFB',
50: '#F6F6F6',
100: '#E7E7E7',
300: '#CFCFCF',
diff --git a/fabric/src/theme/tokens/theme.ts b/fabric/src/theme/tokens/theme.ts
index 73f8be47db..51071ea433 100644
--- a/fabric/src/theme/tokens/theme.ts
+++ b/fabric/src/theme/tokens/theme.ts
@@ -30,6 +30,7 @@ const colors = {
backgroundInput: 'white',
backgroundThumbnail: grayScale[500],
backgroundInverted: grayScale[800],
+ backgroundLight: grayScale[10],
borderPrimary: grayScale[100],
borderSecondary: 'rgba(207, 207, 207, 0.50)',
diff --git a/fabric/src/theme/types.ts b/fabric/src/theme/types.ts
index 52d0023606..9a2623b3ca 100644
--- a/fabric/src/theme/types.ts
+++ b/fabric/src/theme/types.ts
@@ -27,7 +27,8 @@ type BackgroundColorName = `background${
| 'Thumbnail'
| 'AccentPrimary'
| 'AccentSecondary'
- | 'Inverted'}`
+ | 'Inverted'
+ | 'Light'}`
type ButtonColorName =
| `${'background' | 'text' | 'border'}Button${'Primary' | 'Secondary' | 'Tertiary' | 'Inverted'}${
| ''