diff --git a/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx b/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx index ce2bc15c37..5beb7254fb 100644 --- a/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx +++ b/centrifuge-app/src/components/Charts/PoolPerformanceChart.tsx @@ -1,11 +1,12 @@ -import { AnchorButton, Box, Grid, IconDownload, Shelf, Stack, Text } from '@centrifuge/fabric' +import { AnchorButton, Box, IconDownload, Select, Shelf, Stack, Tabs, TabsItem, Text } from '@centrifuge/fabric' +import Decimal from 'decimal.js-light' import * as React from 'react' import { useParams } from 'react-router' import { Bar, CartesianGrid, ComposedChart, Line, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts' -import styled, { useTheme } from 'styled-components' +import { useTheme } from 'styled-components' import { getCSVDownloadUrl } from '../../../src/utils/getCSVDownloadUrl' import { daysBetween, formatDate } from '../../utils/date' -import { formatBalance, formatBalanceAbbreviated } from '../../utils/formatting' +import { formatBalance, formatBalanceAbbreviated, formatPercentage } from '../../utils/formatting' import { useLoans } from '../../utils/useLoans' import { useDailyPoolStates, usePool } from '../../utils/usePools' import { Tooltips } from '../Tooltips' @@ -15,25 +16,57 @@ import { getRangeNumber } from './utils' type ChartData = { day: Date nav: number - price: number | null + juniorTokenPrice: number + seniorTokenPrice?: number | null + currency?: string + seniorAPY: Decimal | null + juniorAPY: Decimal + isToday: boolean } -const RangeFilterButton = styled(Stack)` - &:hover { - cursor: pointer; - } -` +type Tranche = { + seniority: number + tokenPrice: number +} const rangeFilters = [ + { value: 'all', label: 'All' }, { value: '30d', label: '30 days' }, { value: '90d', label: '90 days' }, { value: 'ytd', label: 'Year to date' }, - { value: 'all', label: 'All' }, -] as const +] + +function calculateTranchePrices(pool: any) { + if (!pool?.tranches) return { juniorTokenPrice: 0, seniorTokenPrice: null } + + const juniorTranche = pool.tranches.find((t: Tranche) => t.seniority === 0) + const seniorTranche = pool.tranches.length > 1 ? pool.tranches.find((t: Tranche) => t.seniority === 1) : null + + const juniorTokenPrice = juniorTranche ? Number(formatBalance(juniorTranche.tokenPrice, undefined, 5, 5)) : 0 + const seniorTokenPrice = seniorTranche ? Number(formatBalance(seniorTranche.tokenPrice, undefined, 5, 5)) : null + + return { juniorTokenPrice, seniorTokenPrice } +} + +function getYieldFieldForFilter(tranche: any, filter: string) { + switch (filter) { + case '30d': + return tranche.yield30DaysAnnualized || 0 + case '90d': + return tranche.yield90DaysAnnualized || 0 + case 'ytd': + return tranche.yieldYTD || 0 + case 'all': + return tranche.yieldSinceInception || 0 + default: + return 0 + } +} function PoolPerformanceChart() { const theme = useTheme() - const chartColor = theme.colors.accentPrimary + const [selectedTabIndex, setSelectedTabIndex] = React.useState(0) + const chartColor = theme.colors.textGold const { pid: poolId } = useParams<{ pid: string }>() if (!poolId) throw new Error('Pool not found') @@ -69,22 +102,61 @@ function PoolPerformanceChart() { ? formatBalance(pool?.tranches[pool.tranches.length - 1].tokenPrice || 0, undefined, 5, 5) : null + const trancheTodayPrice = calculateTranchePrices(pool) + const data: ChartData[] = React.useMemo( () => - truncatedPoolStates?.map((day) => { + truncatedPoolStates?.map((day: any) => { const nav = day.poolState.netAssetValue.toDecimal().toNumber() - const price = (isSingleTranche && Object.values(day.tranches)[0].price?.toFloat()) || null + + const trancheKeys = Object.keys(day.tranches) + const juniorTrancheKey = trancheKeys[0] + const seniorTrancheKey = trancheKeys[1] || null + + const juniorTokenPrice = day.tranches[juniorTrancheKey]?.price?.toFloat() ?? 0 + const seniorTokenPrice = seniorTrancheKey ? day.tranches[seniorTrancheKey]?.price?.toFloat() ?? null : null + + const juniorAPY = getYieldFieldForFilter(day.tranches[juniorTrancheKey], range.value).toPercent().toNumber() + const seniorAPY = seniorTrancheKey + ? getYieldFieldForFilter(day.tranches[seniorTrancheKey], range.value).toPercent().toNumber() + : null + if (day.timestamp && new Date(day.timestamp).toDateString() === new Date().toDateString()) { - return { day: new Date(day.timestamp), nav: todayAssetValue, price: Number(todayPrice) } + const tranchePrices = calculateTranchePrices(pool) + + return { + day: new Date(day.timestamp), + nav: todayAssetValue, + juniorTokenPrice: tranchePrices.juniorTokenPrice ?? 0, + seniorTokenPrice: tranchePrices.seniorTokenPrice ?? null, + juniorAPY, + seniorAPY, + isToday: true, + } + } + + return { + day: new Date(day.timestamp), + nav: Number(nav), + juniorTokenPrice: juniorTokenPrice !== 0 ? juniorTokenPrice : null, + seniorTokenPrice: seniorTokenPrice !== 0 ? seniorTokenPrice : null, + juniorAPY, + seniorAPY, + isToday: false, } - return { day: new Date(day.timestamp), nav: Number(nav), price: Number(price) } }) || [], - [isSingleTranche, truncatedPoolStates, todayAssetValue, todayPrice] + [isSingleTranche, truncatedPoolStates, todayAssetValue, todayPrice, pool, range] ) + const todayData = data.find((day) => day.isToday) + const today = { nav: todayAssetValue, price: todayPrice, + currency: pool.currency.symbol, + juniorAPY: todayData?.juniorAPY, + seniorAPY: todayData?.seniorAPY, + ...trancheTodayPrice, } const chartData = data.slice(-rangeNumber) @@ -94,28 +166,24 @@ function PoolPerformanceChart() { return undefined } - const filteredData = chartData.map((data) => ({ - day: data.day, - tokenPrice: data.price, - })) + const filteredData = chartData.map((data) => { + const base = { + day: data.day, + nav: data.nav, + juniorTokenPrice: data.juniorTokenPrice ?? 0, + juniorAPY: data.juniorAPY, + } + if (data.seniorTokenPrice && data.seniorAPY) { + return { + ...base, + seniorTokenPrice: data.seniorTokenPrice, + seniorAPY: data.seniorAPY, + } + } else return { ...base } + }) return getCSVDownloadUrl(filteredData as any) - }, [chartData]) - - const priceRange = React.useMemo(() => { - if (!chartData) return [0, 100] - - const min = - chartData?.reduce((prev, curr) => { - return prev.price! < curr.price! ? prev : curr - }, chartData[0])?.price || 0 - - const max = - chartData?.reduce((prev, curr) => { - return prev.price! > curr.price! ? prev : curr - }, chartData[0])?.price || 1 - return [min, max] - }, [chartData]) + }, [chartData, selectedTabIndex]) if (truncatedPoolStates && truncatedPoolStates?.length < 1 && poolAge > 0) return No data available @@ -138,46 +206,31 @@ function PoolPerformanceChart() { } return ( - - - + + + Pool performance + setSelectedTabIndex(index)}> + + Price + + + APY + + Download - - - - {chartData.length > 0 && - rangeFilters.map((rangeFilter, index) => ( - - setRange(rangeFilter)}> - - {rangeFilter.label} - - - - {index !== rangeFilters.length - 1 && ( - - )} - - ))} - - - - + + {chartData?.length ? ( @@ -200,7 +253,7 @@ function PoolPerformanceChart() { formatBalanceAbbreviated(tick, '', 0)} yAxisId="left" width={80} @@ -208,11 +261,11 @@ function PoolPerformanceChart() { formatBalanceAbbreviated(tick, '', 6)} + style={{ fontSize: '10px', fill: theme.colors.textPrimary }} + tickFormatter={(tick: number) => formatBalanceAbbreviated(tick, '', 2)} yAxisId="right" orientation="right" - domain={priceRange} + domain={selectedTabIndex === 0 ? ['dataMin - 0.25', 'dataMax + 0.25'] : [0, 'dataMax + 0.25']} /> {formatDate(payload[0].payload.day)} - {payload.map(({ name, value }, index) => ( - - - {name === 'nav' ? 'NAV' : name === 'price' ? 'Token price' : 'Cash'} - - - {name === 'nav' && typeof value === 'number' - ? formatBalance(value, 'USD') - : typeof value === 'number' - ? formatBalance(value, 'USD', 6) - : '-'} - - - ))} + {payload.map(({ name, value }, index) => { + const labelMap: Record = { + nav: 'NAV', + juniorTokenPrice: 'Junior Token Price', + seniorTokenPrice: 'Senior Token Price', + juniorAPY: 'Junior APY', + seniorAPY: 'Senior APY', + default: 'Cash', + } + + const label = typeof name === 'string' ? labelMap[name] ?? labelMap.default : labelMap.default + + const formattedValue = (() => { + if (typeof value === 'undefined' || Array.isArray(value)) { + return '-' + } + + if (name === 'juniorAPY' || name === 'seniorAPY') { + return formatPercentage(value) + } + + return formatBalance( + Number(value), + name === 'nav' ? pool.currency.symbol ?? 'USD' : '', + name === 'juniorTokenPrice' || name === 'seniorTokenPrice' ? 6 : 0 + ) + })() + + return ( + + + {label} + + + {formattedValue} + + + ) + })} ) } return null }} /> - - + + + {chartData.some((d) => d.seniorTokenPrice !== null) && ( + + )} + + ) : ( @@ -255,35 +382,96 @@ function PoolPerformanceChart() { function CustomLegend({ data, + setRange, + selectedTabIndex, }: { data: { + currency: string nav: number - price: number | null + juniorTokenPrice: number + seniorTokenPrice?: number | null + juniorAPY: number + seniorAPY: number } + setRange: (value: { value: string; label: string }) => void + selectedTabIndex: number }) { - const theme = useTheme() + const Dot = ({ color }: { color: string }) => ( + + ) + + const navObj = { + color: 'backgroundTertiary', + label: `NAV ${data.currency}`, + value: formatBalance(data.nav), + type: 'nav', + show: true, + } + + const tokenData = [ + navObj, + { + color: 'textGold', + label: 'Junior token price', + value: data.juniorTokenPrice ?? 0, + type: 'singleTrancheTokenPrice', + show: true, + }, + { + color: 'textPrimary', + label: 'Senior token price', + value: data.seniorTokenPrice ?? 0, + type: 'singleTrancheTokenPrice', + show: !!data.seniorTokenPrice, + }, + ] + + const apyData = [ + navObj, + { + color: 'textGold', + label: 'Junior APY', + value: formatPercentage(data.juniorAPY ?? 0), + type: 'singleTrancheTokenPrice', + show: true, + }, + { + color: 'textPrimary', + label: 'Senior APY', + value: formatPercentage(data.seniorAPY ?? 0), + type: 'singleTrancheTokenPrice', + show: !!data.seniorAPY, + }, + ] + + const graphData = selectedTabIndex === 0 ? tokenData : apyData + + const toggleRange = (e: any) => { + const value = e.target.value + const range = rangeFilters.find((range) => range.value === value) + setRange(range ?? rangeFilters[0]) + } return ( - - - - - {formatBalance(data.nav, 'USD')} - - {data.price && ( - - - {data.price ? formatBalance(data.price, 'USD', 6) : '-'} - - )} - - + + + {graphData.map((item: any, index: any) => { + if (!item.show) return + return ( + + + + + + {item.value} + + ) + })} + + + { + const selectedValue = event.target.value + fmk.setFieldValue(`issuerCategories.${index}.type`, selectedValue) + if (selectedValue !== 'other') { + fmk.setFieldValue(`issuerCategories.${index}.customType`, '') + } + }} + onBlur={field.onBlur} + errorMessage={meta.touched && meta.error ? meta.error : undefined} + value={field.value} + options={OPTIONS} + label="Type" + /> + + {category.type === 'other' && ( + + ) => { + fmk.setFieldValue(`issuerCategories.${index}.customType`, event.target.value) + }} + /> + + )} + + ) => { + fmk.setFieldValue(`issuerCategories.${index}.value`, event.target.value) + }} + onBlur={field.onBlur} + value={category.value} + label="Value" + /> + + + + + + ) + }} + + ) + } + )} + + )} + + ) +} diff --git a/centrifuge-app/src/pages/IssuerCreatePool/IssuerInput.tsx b/centrifuge-app/src/pages/IssuerCreatePool/IssuerInput.tsx index 456a24e85e..69fbb682e1 100644 --- a/centrifuge-app/src/pages/IssuerCreatePool/IssuerInput.tsx +++ b/centrifuge-app/src/pages/IssuerCreatePool/IssuerInput.tsx @@ -3,6 +3,7 @@ import { Field, FieldProps } from 'formik' import { FieldWithErrorMessage } from '../../components/FieldWithErrorMessage' import { Tooltips } from '../../components/Tooltips' import { isTestEnv } from '../../config' +import { CustomCategories } from './CustomCategories' import { CustomDetails } from './CustomDetails' import { validate } from './validate' @@ -132,6 +133,9 @@ export function IssuerInput({ waitingForStoredIssuer = false }: Props) { + + + ) } diff --git a/centrifuge-app/src/pages/IssuerCreatePool/index.tsx b/centrifuge-app/src/pages/IssuerCreatePool/index.tsx index 1d896377eb..33eddcef88 100644 --- a/centrifuge-app/src/pages/IssuerCreatePool/index.tsx +++ b/centrifuge-app/src/pages/IssuerCreatePool/index.tsx @@ -114,6 +114,7 @@ export type CreatePoolValues = Omit< poolType: 'open' | 'closed' investorType: string issuerShortDescription: string + issuerCategories: { type: string; value: string }[] ratingAgency: string ratingValue: string ratingReportUrl: string @@ -137,6 +138,7 @@ const initialValues: CreatePoolValues = { issuerLogo: null, issuerDescription: '', issuerShortDescription: '', + issuerCategories: [], executiveSummary: null, website: '', diff --git a/centrifuge-app/src/pages/IssuerPool/Configuration/Issuer.tsx b/centrifuge-app/src/pages/IssuerPool/Configuration/Issuer.tsx index 5f04058d27..e7edcf09d0 100644 --- a/centrifuge-app/src/pages/IssuerPool/Configuration/Issuer.tsx +++ b/centrifuge-app/src/pages/IssuerPool/Configuration/Issuer.tsx @@ -6,7 +6,7 @@ import * as React from 'react' import { useParams } from 'react-router' import { lastValueFrom } from 'rxjs' import { ButtonGroup } from '../../../components/ButtonGroup' -import { IssuerDetails, RatingDetails, ReportDetails } from '../../../components/IssuerSection' +import { IssuerDetails, PoolAnalysis, RatingDetails } from '../../../components/IssuerSection' import { PageSection } from '../../../components/PageSection' import { getFileDataURI } from '../../../utils/getFileDataURI' import { useFile } from '../../../utils/useFile' @@ -25,6 +25,7 @@ type Values = Pick< | 'issuerLogo' | 'issuerDescription' | 'issuerShortDescription' + | 'issuerCategories' | 'executiveSummary' | 'website' | 'forum' @@ -60,6 +61,7 @@ export function Issuer() { issuerLogo: logoFile ?? null, issuerDescription: metadata?.pool?.issuer?.description ?? '', issuerShortDescription: metadata?.pool?.issuer?.shortDescription ?? '', + issuerCategories: metadata?.pool?.issuer?.categories ?? [], executiveSummary: metadata?.pool?.links?.executiveSummary ? 'executiveSummary.pdf' : ('' as any), website: metadata?.pool?.links?.website ?? '', forum: metadata?.pool?.links?.forum ?? '', @@ -120,6 +122,7 @@ export function Issuer() { logo: logoChanged && logoUri ? { uri: logoUri, mime: values.issuerLogo!.type } : oldMetadata.pool.issuer.logo, shortDescription: values.issuerShortDescription, + categories: values.issuerCategories, }, links: { executiveSummary: execSummaryUri @@ -216,7 +219,7 @@ export function Issuer() { ) : ( - {metadata?.pool?.reports?.[0] && } + {metadata?.pool?.reports?.[0] && } {metadata?.pool?.rating && } )} diff --git a/centrifuge-app/src/pages/Pool/Header.tsx b/centrifuge-app/src/pages/Pool/Header.tsx index 8c6eba054d..7a1b5bd9fe 100644 --- a/centrifuge-app/src/pages/Pool/Header.tsx +++ b/centrifuge-app/src/pages/Pool/Header.tsx @@ -3,7 +3,6 @@ import { Box, Shelf, Text, TextWithPlaceholder } from '@centrifuge/fabric' import * as React from 'react' import { useLocation, useParams } from 'react-router' import { useTheme } from 'styled-components' -import { Eththumbnail } from '../../components/EthThumbnail' import { BASE_PADDING } from '../../components/LayoutBase/BasePadding' import { NavigationTabs, NavigationTabsItem } from '../../components/NavigationTabs' import { PageHeader } from '../../components/PageHeader' @@ -30,14 +29,11 @@ export function PoolDetailHeader({ actions }: Props) { return ( {metadata?.pool?.name ?? 'Unnamed pool'}} - subtitle={ - by {metadata?.pool?.issuer.name ?? 'Unknown'} - } parent={{ to: `/pools${state?.token ? '/tokens' : ''}`, label: state?.token ? 'Tokens' : 'Pools' }} icon={ - + <> {metadata?.pool?.icon ? ( - + ) : ( {(isLoading ? '' : metadata?.pool?.name ?? 'U')[0]} )} - + } border={false} actions={actions} diff --git a/centrifuge-app/src/pages/Pool/Overview/index.tsx b/centrifuge-app/src/pages/Pool/Overview/index.tsx index 9ab15172a9..6a7b3fb4bd 100644 --- a/centrifuge-app/src/pages/Pool/Overview/index.tsx +++ b/centrifuge-app/src/pages/Pool/Overview/index.tsx @@ -1,5 +1,5 @@ -import { CurrencyBalance, Price, Rate } from '@centrifuge/centrifuge-js' -import { Box, Button, Card, Grid, IconFileText, Stack, Text, TextWithPlaceholder } from '@centrifuge/fabric' +import { CurrencyBalance, Price } from '@centrifuge/centrifuge-js' +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' @@ -9,10 +9,8 @@ import { InvestRedeemDrawer } from '../../../components/InvestRedeem/InvestRedee import { IssuerDetails, ReportDetails } from '../../../components/IssuerSection' import { LayoutSection } from '../../../components/LayoutBase/LayoutSection' import { LoadBoundary } from '../../../components/LoadBoundary' -import { Cashflows } from '../../../components/PoolOverview/Cashflows' import { KeyMetrics } from '../../../components/PoolOverview/KeyMetrics' import { PoolPerformance } from '../../../components/PoolOverview/PoolPerfomance' -import { PoolStructure } from '../../../components/PoolOverview/PoolStructure' import { TrancheTokenCards } from '../../../components/PoolOverview/TrancheTokenCards' import { TransactionHistory } from '../../../components/PoolOverview/TransactionHistory' import { Spinner } from '../../../components/Spinner' @@ -23,8 +21,7 @@ import { getPoolValueLocked } from '../../../utils/getPoolValueLocked' import { useAverageMaturity } from '../../../utils/useAverageMaturity' import { useConnectBeforeAction } from '../../../utils/useConnectBeforeAction' import { useIsAboveBreakpoint } from '../../../utils/useIsAboveBreakpoint' -import { useLoans } from '../../../utils/useLoans' -import { usePool, usePoolFees, usePoolMetadata } from '../../../utils/usePools' +import { usePool, usePoolMetadata } from '../../../utils/usePools' import { PoolDetailHeader } from '../Header' export type Token = { @@ -71,11 +68,7 @@ export function PoolDetailOverview() { const isTinlakePool = poolId.startsWith('0x') const pool = usePool(poolId) - const poolFees = usePoolFees(poolId) const { data: metadata, isLoading: metadataIsLoading } = usePoolMetadata(pool) - const averageMaturity = useAverageMaturity(poolId) - const loans = useLoans(poolId) - const isMedium = useIsAboveBreakpoint('M') const pageSummaryData = [ { @@ -109,100 +102,43 @@ export function PoolDetailOverview() { id: tranche.id, capacity: tranche.capacity, tokenPrice: tranche.tokenPrice, - yield30DaysAnnualized: tranche?.yield30DaysAnnualized, + yield30DaysAnnualized: tranche?.yield30DaysAnnualized?.toString() || '', } }) .reverse() return ( - - - - }> - - - - {tokens.length > 0 && ( + + + + + }> + + + + {tokens.length > 0 && ( + }> + + + )} }> - - - )} - }> - {metadata?.pool?.reports?.length || !isTinlakePool ? ( - - - - - - - Reports - - - - - - Issuer details - - - - - ) : null} - {isTinlakePool && ( - - - Issuer details + + - - - )} - - {!isTinlakePool && ( - <> - - }> - { - return { - fee: poolFees?.find((f) => f.id === fee.id)?.amounts.percentOfNav ?? Rate.fromFloat(0), - name: fee.name, - id: fee.id, - } - }) || [] - } - /> - - {/* }> - - */} - - {isMedium && ( - }> - - + + {metadata?.pool?.reports?.length || !isTinlakePool ? ( + + - - )} + ) : null} + + + {!isTinlakePool && ( }> - - - + - - )} + )} + ) } diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 2a07f549c4..7518ef8c2a 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -684,6 +684,7 @@ export interface PoolMetadataInput { issuerLogo?: FileType | null issuerDescription: string issuerShortDescription: string + issuerCategories: { type: string; value: string; customType?: string }[] poolReport?: { authorName: string @@ -748,6 +749,7 @@ export type PoolMetadata = { email: string logo?: FileType | null shortDescription: string + categories: { type: string; value: string; customType?: string }[] } links: { executiveSummary: FileType | null @@ -1129,6 +1131,7 @@ export function getPoolsModule(inst: Centrifuge) { email: metadata.email, logo: metadata.issuerLogo, shortDescription: metadata.issuerShortDescription, + categories: metadata.issuerCategories, }, poolStructure: metadata.poolStructure, investorType: metadata.investorType, @@ -2469,13 +2472,19 @@ export function getPoolsModule(inst: Centrifuge) { }), takeLast(1), map(({ trancheSnapshots }) => { - const trancheStates: Record = {} + const trancheStates: Record< + string, + { timestamp: string; tokenPrice: Price; yield30DaysAnnualized: Perquintill }[] + > = {} trancheSnapshots?.forEach((state) => { const tid = state.tranche.trancheId const entry = { timestamp: state.timestamp, tokenPrice: new Price(state.tokenPrice), pool: state.tranche.poolId, + yield30DaysAnnualized: state.yield30DaysAnnualized + ? new Perquintill(state.yield30DaysAnnualized) + : new Perquintill(0), } if (trancheStates[tid]) { trancheStates[tid].push(entry) @@ -2665,26 +2674,26 @@ export function getPoolsModule(inst: Centrifuge) { poolCurrency.decimals ), yield7DaysAnnualized: tranche.yield7DaysAnnualized - ? new Perquintill(hexToBN(tranche.yield7DaysAnnualized)) + ? new Perquintill(tranche.yield7DaysAnnualized) : new Perquintill(0), yield30DaysAnnualized: tranche.yield30DaysAnnualized - ? new Perquintill(hexToBN(tranche.yield30DaysAnnualized)) + ? new Perquintill(tranche.yield30DaysAnnualized) : new Perquintill(0), yield90DaysAnnualized: tranche.yield90DaysAnnualized - ? new Perquintill(hexToBN(tranche.yield90DaysAnnualized)) + ? new Perquintill(tranche.yield90DaysAnnualized) : new Perquintill(0), yieldSinceInception: tranche.yieldSinceInception - ? new Perquintill(hexToBN(tranche.yieldSinceInception)) + ? new Perquintill(tranche.yieldSinceInception) : new Perquintill(0), - yieldMTD: tranche.yieldMTD ? new Perquintill(hexToBN(tranche.yieldMTD)) : new Perquintill(0), - yieldQTD: tranche.yieldQTD ? new Perquintill(hexToBN(tranche.yieldQTD)) : new Perquintill(0), - yieldYTD: tranche.yieldYTD ? new Perquintill(hexToBN(tranche.yieldYTD)) : new Perquintill(0), + yieldMTD: tranche.yieldMTD ? new Perquintill(tranche.yieldMTD) : new Perquintill(0), + yieldQTD: tranche.yieldQTD ? new Perquintill(tranche.yieldQTD) : new Perquintill(0), + yieldYTD: tranche.yieldYTD ? new Perquintill(tranche.yieldYTD) : new Perquintill(0), yieldSinceLastPeriod: tranche.yieldSinceLastPeriod - ? new Perquintill(hexToBN(tranche.yieldSinceLastPeriod)) + ? new Perquintill(tranche.yieldSinceLastPeriod) : new Perquintill(0), } }) - + console.log('🚀 ~ tranches:', tranches) return { ...state, poolState, poolValue, tranches } }) || [], trancheStates, @@ -4629,7 +4638,7 @@ export function getPoolsModule(inst: Centrifuge) { } } -function hexToBN(value?: string | number | null) { +export function hexToBN(value?: string | number | null) { if (typeof value === 'number' || value == null) return new BN(value ?? 0) return new BN(value.toString().substring(2), 'hex') } diff --git a/fabric/src/components/Select/index.tsx b/fabric/src/components/Select/index.tsx index 38fa43e539..e6e1eb8afa 100644 --- a/fabric/src/components/Select/index.tsx +++ b/fabric/src/components/Select/index.tsx @@ -17,6 +17,7 @@ export type SelectProps = React.SelectHTMLAttributes & { placeholder?: string errorMessage?: string small?: boolean + hideBorder?: boolean } const StyledSelect = styled.select` @@ -31,14 +32,11 @@ const StyledSelect = styled.select` cursor: pointer; line-height: inherit; text-overflow: ellipsis; + font-weight: 500; &:disabled { cursor: default; } - - &:focus { - color: ${({ theme }) => theme.colors.textSelected}; - } ` export function SelectInner({ @@ -79,7 +77,7 @@ export function SelectInner({ ) } -export function Select({ label, errorMessage, id, ...rest }: SelectProps) { +export function Select({ label, errorMessage, id, hideBorder, ...rest }: SelectProps) { const defaultId = React.useId() id ??= defaultId return ( @@ -89,7 +87,7 @@ export function Select({ label, errorMessage, id, ...rest }: SelectProps) { disabled={rest.disabled} errorMessage={errorMessage} inputElement={ - + } diff --git a/fabric/src/components/Tabs/index.tsx b/fabric/src/components/Tabs/index.tsx index 0e7a40e828..1d5ff62d13 100644 --- a/fabric/src/components/Tabs/index.tsx +++ b/fabric/src/components/Tabs/index.tsx @@ -29,7 +29,7 @@ export function Tabs({ selectedIndex, onChange, children }: TabsProps) { ) } -const StyledTabsItem = styled.button<{ $active?: boolean }>( +const StyledTabsItem = styled.button<{ $active?: boolean; styleOverrides?: React.CSSProperties, showBorder?: boolean }>( { display: 'flex', alignItems: 'center', @@ -43,34 +43,56 @@ const StyledTabsItem = styled.button<{ $active?: boolean }>( appearance: 'none', background: 'transparent', }, - ({ $active, theme }) => { + ({ $active, theme, styleOverrides, showBorder }) => { return css({ paddingTop: 1, paddingLeft: 2, paddingRight: 2, paddingBottom: 2, - color: 'textPrimary', - boxShadow: $active ? `inset 0 -2px 0 ${theme.colors.textGold}` : 'none', + color: $active ? 'textPrimary' : 'textSecondary', + boxShadow: $active ? `inset 0 -2px 0 ${theme.colors.textGold}` : showBorder ? `inset 0 -2px 0 ${theme.colors.textDisabled}` : 'none', + fontWeight: 400, '&:hover, &:active, &:focus-visible': { color: 'textGold', }, + ...styleOverrides, }) } ) -export type TabsItemProps = Omit, '$active' | 'ariaLabel'> - +export type TabsItemProps = Omit, '$active' | 'ariaLabel'> & { + styleOverrides?: React.CSSProperties + showBorder?: boolean +} type TabsItemPrivateProps = TabsItemProps & { active?: boolean onClick?: () => void ariaLabel?: string + styleOverrides?: React.CSSProperties + showBorder?: boolean } -export function TabsItem({ children, active, onClick, ariaLabel, ...rest }: TabsItemPrivateProps) { +export function TabsItem({ + children, + active, + onClick, + ariaLabel, + styleOverrides, + showBorder, + ...rest +}: TabsItemPrivateProps) { return ( - - + + {children} diff --git a/fabric/src/components/TextInput/index.tsx b/fabric/src/components/TextInput/index.tsx index e48312d350..5f5775b17a 100644 --- a/fabric/src/components/TextInput/index.tsx +++ b/fabric/src/components/TextInput/index.tsx @@ -49,12 +49,11 @@ export const StyledTextInput = styled.input` margin: 0; } ` - -export const StyledInputBox = styled(Shelf)` +export const StyledInputBox = styled(Shelf)<{ hideBorder?: boolean }>` width: 100%; position: relative; background: ${({ theme }) => theme.colors.backgroundPage}; - border: 1px solid ${({ theme }) => theme.colors.borderPrimary}; + border: ${({ hideBorder, theme }) => (hideBorder ? 'none' : `1px solid ${theme.colors.borderPrimary}`)}; border-radius: ${({ theme }) => theme.radii.input}px; &::before { diff --git a/fabric/src/components/Tooltip/index.tsx b/fabric/src/components/Tooltip/index.tsx index 9eb97c49a5..ae0b10090f 100644 --- a/fabric/src/components/Tooltip/index.tsx +++ b/fabric/src/components/Tooltip/index.tsx @@ -57,6 +57,7 @@ const placements: { } const Container = styled(Stack)<{ pointer: PlacementAxis }>` + background-color: ${({ theme }) => theme.colors.backgroundInverted}; filter: ${({ theme }) => `drop-shadow(${theme.shadows.cardInteractive})`}; &::before { @@ -65,7 +66,7 @@ const Container = styled(Stack)<{ pointer: PlacementAxis }>` content: ''; position: absolute; ${({ pointer }) => placements[pointer!]} - border: ${({ theme }) => `var(--size) solid ${theme.colors.backgroundPrimary}`}; + border: ${({ theme }) => `var(--size) solid ${theme.colors.backgroundInverted}`}; transform: rotate(-45deg); } ` @@ -117,7 +118,9 @@ export function Tooltip({ {title} )} - {body} + + {body} + )} /> diff --git a/fabric/src/icon-svg/IconBalanceSheet.svg b/fabric/src/icon-svg/IconBalanceSheet.svg new file mode 100644 index 0000000000..89a2407fc4 --- /dev/null +++ b/fabric/src/icon-svg/IconBalanceSheet.svg @@ -0,0 +1,3 @@ + + + diff --git a/fabric/src/icon-svg/IconCashflow.svg b/fabric/src/icon-svg/IconCashflow.svg new file mode 100644 index 0000000000..798a162bc8 --- /dev/null +++ b/fabric/src/icon-svg/IconCashflow.svg @@ -0,0 +1,3 @@ + + + diff --git a/fabric/src/icon-svg/IconMoody.svg b/fabric/src/icon-svg/IconMoody.svg new file mode 100644 index 0000000000..2499a357a6 --- /dev/null +++ b/fabric/src/icon-svg/IconMoody.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/fabric/src/icon-svg/IconProfitAndLoss.svg b/fabric/src/icon-svg/IconProfitAndLoss.svg new file mode 100644 index 0000000000..5223d3810d --- /dev/null +++ b/fabric/src/icon-svg/IconProfitAndLoss.svg @@ -0,0 +1,3 @@ + + + diff --git a/fabric/src/icon-svg/IconSp.svg b/fabric/src/icon-svg/IconSp.svg new file mode 100644 index 0000000000..fd64483382 --- /dev/null +++ b/fabric/src/icon-svg/IconSp.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/fabric/src/icon-svg/icon-arrow-right-white.svg b/fabric/src/icon-svg/icon-arrow-right-white.svg new file mode 100644 index 0000000000..d56f3c1f14 --- /dev/null +++ b/fabric/src/icon-svg/icon-arrow-right-white.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/fabric/src/theme/tokens/theme.ts b/fabric/src/theme/tokens/theme.ts index 87824b8bee..4062c1b4bd 100644 --- a/fabric/src/theme/tokens/theme.ts +++ b/fabric/src/theme/tokens/theme.ts @@ -1,4 +1,4 @@ -import { black, blackScale, blueScale, centrifugeBlue, gold, grayScale, yellowScale } from './colors' +import { black, blackScale, blueScale, gold, grayScale, yellowScale } from './colors' const statusDefault = grayScale[800] const statusInfo = blueScale[500] @@ -79,18 +79,18 @@ const colors = { borderButtonSecondaryHover: gold, borderButtonSecondaryPressed: gold, borderButtonSecondaryDisabled: 'transparent', - shadowButtonSecondary: '#A8BFFD35', + shadowButtonSecondary: 'transparent', backgroundButtonTertiary: 'transparent', backgroundButtonTertiaryFocus: 'transparent', backgroundButtonTertiaryHover: 'tranparent', backgroundButtonTertiaryPressed: 'transparent', backgroundButtonTertiaryDisabled: 'transparent', - textButtonTertiary: centrifugeBlue, - textButtonTertiaryFocus: centrifugeBlue, - textButtonTertiaryHover: grayScale[800], - textButtonTertiaryPressed: centrifugeBlue, - textButtonTertiaryDisabled: grayScale[500], + textButtonTertiary: grayScale[800], + textButtonTertiaryFocus: gold, + textButtonTertiaryHover: gold, + textButtonTertiaryPressed: gold, + textButtonTertiaryDisabled: gold, borderButtonTertiary: 'transparent', borderButtonTertiaryFocus: 'transparent', borderButtonTertiaryHover: 'transparent', @@ -102,17 +102,17 @@ const colors = { backgroundButtonInvertedHover: grayScale[100], backgroundButtonInvertedPressed: grayScale[100], backgroundButtonInvertedDisabled: grayScale[100], - textButtonInverted: centrifugeBlue, - textButtonInvertedFocus: centrifugeBlue, - textButtonInvertedHover: centrifugeBlue, - textButtonInvertedPressed: centrifugeBlue, + textButtonInverted: black, + textButtonInvertedFocus: black, + textButtonInvertedHover: black, + textButtonInvertedPressed: black, textButtonInvertedDisabled: grayScale[500], borderButtonInverted: grayScale[100], - borderButtonInvertedFocus: centrifugeBlue, - borderButtonInvertedHover: centrifugeBlue, - borderButtonInvertedPressed: centrifugeBlue, + borderButtonInvertedFocus: black, + borderButtonInvertedHover: black, + borderButtonInvertedPressed: black, borderButtonInvertedDisabled: 'transparent', - shadowButtonInverted: '#E0E7FF', + shadowButtonInverted: 'transparent', } export const colorTheme = {