From 14d6911c6ddbc0fd944e400d8c8a456baccd4994 Mon Sep 17 00:00:00 2001 From: sophian Date: Tue, 17 Sep 2024 16:01:31 -0400 Subject: [PATCH 1/2] Fix report url and clean up pre edit view --- .../src/components/IssuerSection.tsx | 165 +++++++++--------- .../IssuerCreatePool/PoolRatingInput.tsx | 17 ++ .../IssuerCreatePool/PoolReportsInput.tsx | 88 +++++----- .../pages/IssuerPool/Configuration/Issuer.tsx | 17 +- 4 files changed, 154 insertions(+), 133 deletions(-) create mode 100644 centrifuge-app/src/pages/IssuerCreatePool/PoolRatingInput.tsx diff --git a/centrifuge-app/src/components/IssuerSection.tsx b/centrifuge-app/src/components/IssuerSection.tsx index 260f434a0..1deda5e10 100644 --- a/centrifuge-app/src/components/IssuerSection.tsx +++ b/centrifuge-app/src/components/IssuerSection.tsx @@ -1,76 +1,48 @@ import { PoolMetadata } from '@centrifuge/centrifuge-js' import { useCentrifuge } from '@centrifuge/centrifuge-react' -import { Accordion, AnchorButton, Box, IconExternalLink, Shelf, Text } from '@centrifuge/fabric' +import { Accordion, AnchorButton, Box, IconExternalLink, Shelf, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' -import { useLocation } from 'react-router' import { ExecutiveSummaryDialog } from './Dialogs/ExecutiveSummaryDialog' import { LabelValueStack } from './LabelValueStack' import { PillButton } from './PillButton' -import { AnchorTextLink, RouterTextLink } from './TextLink' +import { AnchorTextLink } from './TextLink' type IssuerSectionProps = { metadata: Partial | undefined } -const reportLinks = [ - { label: 'Balance sheet', href: '/balance-sheet' }, - { label: 'Profit & loss', href: '/profit-and-loss' }, - { label: 'Cashflow statement', href: '/cash-flow-statement' }, - { label: 'View all', href: '/' }, -] - export function ReportDetails({ metadata }: IssuerSectionProps) { const cent = useCentrifuge() - const { pathname } = useLocation() const report = metadata?.pool?.reports?.[0] return ( - - - - - {reportLinks.map((link, i) => ( - - {link.label} - - ))} - - - } - /> + + Pool analysis + + {report && ( + <> + + {report.author.avatar?.uri && ( + + )} + {report.author.name}} /> + {report.author.title}} /> + + + + View full analysis + + + + )} - {report && ( - <> - - Pool analysis - - - {report.author.avatar?.uri && ( - - )} - - Reviewer: {report.author.name} -
- {report.author.title} -
-
- - - View full report - - - - )} -
+ ) } @@ -78,8 +50,8 @@ export function IssuerDetails({ metadata }: IssuerSectionProps) { const cent = useCentrifuge() const [isDialogOpen, setIsDialogOpen] = React.useState(false) return ( - <> - + + {metadata?.pool?.issuer.logo && ( )} - {metadata?.pool?.issuer.name} - - {metadata?.pool?.issuer.description} - - {metadata?.pool?.links.executiveSummary && ( + {metadata?.pool?.issuer.name}} /> - setIsDialogOpen(true)}> - Executive summary - - setIsDialogOpen(false)} - /> - - } + label="Legal representative" + value={{metadata?.pool?.issuer.repName}} /> - )} + {metadata?.pool?.issuer.shortDescription}} + /> + {metadata?.pool?.issuer.description}} + /> + {metadata?.pool?.links.executiveSummary && ( + + setIsDialogOpen(true)}> + Executive summary + + setIsDialogOpen(false)} + /> + + } + /> + )} + {(metadata?.pool?.links.website || metadata?.pool?.links.forum || metadata?.pool?.issuer.email) && ( } /> )} - + + ) +} + +export function RatingDetails({ metadata }: IssuerSectionProps) { + const rating = metadata?.pool?.rating + + return ( + + Pool rating + + {rating && ( + + {rating.ratingAgency}} /> + {rating.ratingValue}} /> + + )} + + + {rating?.ratingReportUrl && ( + + View full report + + )} + + ) } diff --git a/centrifuge-app/src/pages/IssuerCreatePool/PoolRatingInput.tsx b/centrifuge-app/src/pages/IssuerCreatePool/PoolRatingInput.tsx new file mode 100644 index 000000000..b23320681 --- /dev/null +++ b/centrifuge-app/src/pages/IssuerCreatePool/PoolRatingInput.tsx @@ -0,0 +1,17 @@ +import { Grid, TextInput } from '@centrifuge/fabric' +import { FieldWithErrorMessage } from '../../components/FieldWithErrorMessage' + +export function PoolRatingInput() { + return ( + + + + + + ) +} diff --git a/centrifuge-app/src/pages/IssuerCreatePool/PoolReportsInput.tsx b/centrifuge-app/src/pages/IssuerCreatePool/PoolReportsInput.tsx index 0263b1288..2002aaadb 100644 --- a/centrifuge-app/src/pages/IssuerCreatePool/PoolReportsInput.tsx +++ b/centrifuge-app/src/pages/IssuerCreatePool/PoolReportsInput.tsx @@ -1,4 +1,4 @@ -import { FileUpload, Grid, TextInput } from '@centrifuge/fabric' +import { FileUpload, Grid, Stack, Text, TextInput } from '@centrifuge/fabric' import { Field, FieldProps } from 'formik' import { FieldWithErrorMessage } from '../../components/FieldWithErrorMessage' import { combineAsync, imageFile, maxFileSize, maxImageSize } from '../../utils/validation' @@ -6,48 +6,48 @@ import { validate } from './validate' export function PoolReportsInput() { return ( - - - - - - {({ field, meta, form }: FieldProps) => ( - { - form.setFieldTouched('reportAuthorAvatar', true, false) - form.setFieldValue('reportAuthorAvatar', file) - }} - label="Reviewer avatar (JPG/PNG/SVG, max 40x40px)" - errorMessage={meta.touched && meta.error ? meta.error : undefined} - accept="image/*" - /> - )} - - - - - + + Pool analysis + + + + + + {({ field, meta, form }: FieldProps) => ( + { + form.setFieldTouched('reportAuthorAvatar', true, false) + form.setFieldValue('reportAuthorAvatar', file) + }} + label="Reviewer avatar (JPG/PNG/SVG, max 40x40px)" + errorMessage={meta.touched && meta.error ? meta.error : undefined} + accept="image/*" + /> + )} + + + ) } diff --git a/centrifuge-app/src/pages/IssuerPool/Configuration/Issuer.tsx b/centrifuge-app/src/pages/IssuerPool/Configuration/Issuer.tsx index 5b38fab96..5f04058d2 100644 --- a/centrifuge-app/src/pages/IssuerPool/Configuration/Issuer.tsx +++ b/centrifuge-app/src/pages/IssuerPool/Configuration/Issuer.tsx @@ -1,12 +1,12 @@ import { PoolMetadata } from '@centrifuge/centrifuge-js' import { useCentrifuge, useCentrifugeTransaction } from '@centrifuge/centrifuge-react' -import { Button, Stack, Text } from '@centrifuge/fabric' +import { Button, Stack } from '@centrifuge/fabric' import { Form, FormikProvider, useFormik } from 'formik' import * as React from 'react' import { useParams } from 'react-router' import { lastValueFrom } from 'rxjs' import { ButtonGroup } from '../../../components/ButtonGroup' -import { IssuerDetails, ReportDetails } from '../../../components/IssuerSection' +import { IssuerDetails, RatingDetails, ReportDetails } from '../../../components/IssuerSection' import { PageSection } from '../../../components/PageSection' import { getFileDataURI } from '../../../utils/getFileDataURI' import { useFile } from '../../../utils/useFile' @@ -15,6 +15,7 @@ import { useSuitableAccounts } from '../../../utils/usePermissions' import { usePool, usePoolMetadata } from '../../../utils/usePools' import { CreatePoolValues } from '../../IssuerCreatePool' import { IssuerInput } from '../../IssuerCreatePool/IssuerInput' +import { PoolRatingInput } from '../../IssuerCreatePool/PoolRatingInput' import { PoolReportsInput } from '../../IssuerCreatePool/PoolReportsInput' type Values = Pick< @@ -181,7 +182,7 @@ export function Issuer() {
@@ -209,18 +210,14 @@ export function Issuer() { {isEditing ? ( - Pool analysis + ) : ( - {metadata?.pool?.reports?.[0] && ( - - Pool analysis - - - )} + {metadata?.pool?.reports?.[0] && } + {metadata?.pool?.rating && } )} From 0daac01cec75922524d871bb206e7b031e8dc676 Mon Sep 17 00:00:00 2001 From: sophian Date: Tue, 17 Sep 2024 17:47:56 -0400 Subject: [PATCH 2/2] Add pool structure, fix pool name in tranche edit, fix targetAPY, --- .../src/components/IssuerSection.tsx | 16 +++++++++--- .../IssuerCreatePool/PoolRatingInput.tsx | 25 +++++++++++-------- .../pages/IssuerCreatePool/TrancheInput.tsx | 20 ++++++++++----- .../src/pages/IssuerCreatePool/index.tsx | 25 +++++++++++++++++-- .../IssuerPool/Configuration/Details.tsx | 21 ++++++++++++++-- .../Configuration/EpochAndTranches.tsx | 18 ++++++++++--- centrifuge-js/src/modules/pools.ts | 9 ++++--- 7 files changed, 103 insertions(+), 31 deletions(-) diff --git a/centrifuge-app/src/components/IssuerSection.tsx b/centrifuge-app/src/components/IssuerSection.tsx index 1deda5e10..5798a89b0 100644 --- a/centrifuge-app/src/components/IssuerSection.tsx +++ b/centrifuge-app/src/components/IssuerSection.tsx @@ -31,8 +31,12 @@ export function ReportDetails({ metadata }: IssuerSectionProps) { alt="" /> )} - {report.author.name}} /> - {report.author.title}} /> + {report.author.name && ( + {report.author.name}} /> + )} + {report.author.title && ( + {report.author.title}} /> + )} @@ -130,8 +134,12 @@ export function RatingDetails({ metadata }: IssuerSectionProps) { {rating && ( - {rating.ratingAgency}} /> - {rating.ratingValue}} /> + {rating.ratingAgency && ( + {rating.ratingAgency}} /> + )} + {rating.ratingValue && ( + {rating.ratingValue}} /> + )} )} diff --git a/centrifuge-app/src/pages/IssuerCreatePool/PoolRatingInput.tsx b/centrifuge-app/src/pages/IssuerCreatePool/PoolRatingInput.tsx index b23320681..c46c6c94d 100644 --- a/centrifuge-app/src/pages/IssuerCreatePool/PoolRatingInput.tsx +++ b/centrifuge-app/src/pages/IssuerCreatePool/PoolRatingInput.tsx @@ -1,17 +1,20 @@ -import { Grid, TextInput } from '@centrifuge/fabric' +import { Grid, Stack, Text, TextInput } from '@centrifuge/fabric' import { FieldWithErrorMessage } from '../../components/FieldWithErrorMessage' export function PoolRatingInput() { return ( - - - - - + + Pool rating + + + + + + ) } diff --git a/centrifuge-app/src/pages/IssuerCreatePool/TrancheInput.tsx b/centrifuge-app/src/pages/IssuerCreatePool/TrancheInput.tsx index 258b558ee..851d80d98 100644 --- a/centrifuge-app/src/pages/IssuerCreatePool/TrancheInput.tsx +++ b/centrifuge-app/src/pages/IssuerCreatePool/TrancheInput.tsx @@ -67,28 +67,36 @@ export function TrancheSection() { } > - + )} ) } -export function TrancheInput({ canRemove, isUpdating }: { canRemove?: boolean; isUpdating?: boolean }) { +export function TrancheInput({ + canRemove, + isUpdating, + poolName, +}: { + canRemove?: boolean + isUpdating?: boolean + poolName?: string +}) { const fmk = useFormikContext() const { values } = fmk const getTrancheName = (index: number) => { if (values.tranches.length === 1) { - return values.poolName + return poolName } switch (index) { case 0: - return `${values.poolName} Junior` + return `${poolName} Junior` case 1: - return values.tranches.length === 2 ? `${values.poolName} Senior` : `${values.poolName} Mezzanine` + return values.tranches.length === 2 ? `${poolName} Senior` : `${poolName} Mezzanine` case 2: - return `${values.poolName} Senior` + return `${poolName} Senior` default: return '' } diff --git a/centrifuge-app/src/pages/IssuerCreatePool/index.tsx b/centrifuge-app/src/pages/IssuerCreatePool/index.tsx index 0aa6dd501..1d896377e 100644 --- a/centrifuge-app/src/pages/IssuerCreatePool/index.tsx +++ b/centrifuge-app/src/pages/IssuerCreatePool/index.tsx @@ -53,6 +53,7 @@ import { truncate } from '../../utils/web3' import { AdminMultisigSection } from './AdminMultisig' import { IssuerInput } from './IssuerInput' import { PoolFeeSection } from './PoolFeeInput' +import { PoolRatingInput } from './PoolRatingInput' import { PoolReportsInput } from './PoolReportsInput' import { TrancheSection } from './TrancheInput' import { useStoredIssuer } from './useStoredIssuer' @@ -116,6 +117,7 @@ export type CreatePoolValues = Omit< ratingAgency: string ratingValue: string ratingReportUrl: string + poolStructure: string } const initialValues: CreatePoolValues = { @@ -129,7 +131,7 @@ const initialValues: CreatePoolValues = { epochMinutes: 50, // in minutes listed: !import.meta.env.REACT_APP_DEFAULT_UNLIST_POOLS, investorType: '', - + poolStructure: '', issuerName: '', issuerRepName: '', issuerLogo: null, @@ -741,14 +743,33 @@ function CreatePoolForm() { )} + + + {({ field, meta, form }: FieldProps) => ( + form.setFieldValue('poolStructure', event.target.value)} + onBlur={field.onBlur} + errorMessage={meta.touched && meta.error ? meta.error : undefined} + value={field.value} + as={TextInput} + placeholder="Revolving" + /> + )} + + - + + + + diff --git a/centrifuge-app/src/pages/IssuerPool/Configuration/Details.tsx b/centrifuge-app/src/pages/IssuerPool/Configuration/Details.tsx index cb2642ab8..a88fb8bd1 100644 --- a/centrifuge-app/src/pages/IssuerPool/Configuration/Details.tsx +++ b/centrifuge-app/src/pages/IssuerPool/Configuration/Details.tsx @@ -20,7 +20,10 @@ import { usePool, usePoolMetadata } from '../../../utils/usePools' import { CreatePoolValues } from '../../IssuerCreatePool' import { validate } from '../../IssuerCreatePool/validate' -type Values = Pick & { +type Values = Pick< + CreatePoolValues, + 'poolName' | 'poolIcon' | 'assetClass' | 'subAssetClass' | 'investorType' | 'poolStructure' +> & { listed: boolean } @@ -56,6 +59,7 @@ export function Details() { subAssetClass: metadata?.pool?.asset?.subClass ?? '', listed: metadata?.pool?.listed ?? false, investorType: metadata?.pool?.investorType ?? '', + poolStructure: metadata?.pool?.poolStructure ?? '', }), [metadata, iconFile] ) @@ -94,6 +98,7 @@ export function Details() { }, investorType: values.investorType, listed: values.listed, + poolStructure: values.poolStructure, }, pod: { ...oldMetadata.pod, @@ -231,6 +236,13 @@ export function Details() { placeholder="Institutional" maxLength={100} /> + {((isDemo && editPoolVisibility) || !isDemo) && ( {({ field }: FieldProps) => ( @@ -254,7 +266,12 @@ export function Details() { - + {metadata?.pool?.investorType && ( + + )} + {metadata?.pool?.poolStructure && ( + + )} )} diff --git a/centrifuge-app/src/pages/IssuerPool/Configuration/EpochAndTranches.tsx b/centrifuge-app/src/pages/IssuerPool/Configuration/EpochAndTranches.tsx index b976eb8c5..defeb9633 100644 --- a/centrifuge-app/src/pages/IssuerPool/Configuration/EpochAndTranches.tsx +++ b/centrifuge-app/src/pages/IssuerPool/Configuration/EpochAndTranches.tsx @@ -88,6 +88,7 @@ export function EpochAndTranches() { metadata?.tranches?.[tranche.id]?.minInitialInvestment ?? 0, pool?.currency.decimals ).toFloat(), + targetAPY: metadata?.tranches?.[tranche.id]?.targetAPY, } return row }) ?? [], @@ -155,6 +156,10 @@ export function EpochAndTranches() { values.tranches[i].minInvestment, pool.currency.decimals ).toString(), + targetAPY: + i === 0 && values.tranches[i].targetAPY + ? Rate.fromPercent(values.tranches[i].targetAPY).toString() + : undefined, }, ]) ), @@ -168,7 +173,8 @@ export function EpochAndTranches() { t1.tokenName !== t2.tokenName || t1.symbolName !== t2.symbolName || t1.interestRate !== t2.interestRate || - t1.minRiskBuffer !== t2.minRiskBuffer + t1.minRiskBuffer !== t2.minRiskBuffer || + t1.targetAPY !== t2.targetAPY ) }) @@ -178,6 +184,7 @@ export function EpochAndTranches() { { tokenName: values.tranches[0].tokenName, tokenSymbol: values.tranches[0].symbolName, + targetAPY: values.tranches[0].targetAPY ? Rate.fromPercent(values.tranches[0]?.targetAPY) : undefined, }, // most junior tranche ...nonJuniorTranches.map((tranche) => ({ interestRatePerSec: Rate.fromAprPercent(tranche.interestRate), @@ -226,7 +233,8 @@ export function EpochAndTranches() { t1.tokenName !== t2.tokenName || t1.symbolName !== t2.symbolName || t1.interestRate !== t2.interestRate || - t1.minRiskBuffer !== t2.minRiskBuffer + t1.minRiskBuffer !== t2.minRiskBuffer || + t1.targetAPY !== t2.targetAPY ) }) const epochSeconds = ((form.values.epochHours as number) * 60 + (form.values.epochMinutes as number)) * 60 @@ -321,7 +329,11 @@ export function EpochAndTranches() { Tranches - {isEditing ? : } + {isEditing ? ( + + ) : ( + + )} diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index d75242634..d7c9b3bf4 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -645,7 +645,7 @@ interface TrancheFormValues { interestRate: number | '' minRiskBuffer: number | '' minInvestment: number | '' - targetAPY?: number | '' + targetAPY?: string | '' } export type IssuerDetail = { @@ -676,6 +676,7 @@ export interface PoolMetadataInput { epochMinutes: number | '' listed?: boolean investorType: string + poolStructure: string // issuer issuerName: string @@ -732,6 +733,7 @@ export type PoolMetadata = { subClass: string } investorType: string + poolStructure: string poolFees?: { id: number name: string @@ -770,6 +772,7 @@ export type PoolMetadata = { { icon?: FileType | null minInitialInvestment?: string + targetAPY?: string // only junior tranche (index: 0) has targetAPY } > loanTemplates?: { @@ -1104,10 +1107,9 @@ export function getPoolsModule(inst: Centrifuge) { const tranchesById: PoolMetadata['tranches'] = {} metadata.tranches.forEach((tranche, index) => { - const targetAPY = tranche?.targetAPY ? { targetAPY: tranche.targetAPY } : {} tranchesById[computeTrancheId(index, poolId)] = { minInitialInvestment: CurrencyBalance.fromFloat(tranche.minInvestment, currencyDecimals).toString(), - ...targetAPY, + targetAPY: tranche.targetAPY, } }) @@ -1128,6 +1130,7 @@ export function getPoolsModule(inst: Centrifuge) { logo: metadata.issuerLogo, shortDescription: metadata.issuerShortDescription, }, + poolStructure: metadata.poolStructure, investorType: metadata.investorType, links: { executiveSummary: metadata.executiveSummary,