Skip to content

Commit

Permalink
Add pool details section UI
Browse files Browse the repository at this point in the history
  • Loading branch information
kattylucy committed Nov 15, 2024
1 parent df6d996 commit 4d6c428
Show file tree
Hide file tree
Showing 10 changed files with 646 additions and 137 deletions.
418 changes: 418 additions & 0 deletions centrifuge-app/src/pages/IssuerCreatePool/PoolDetailsSection.tsx

Large diffs are not rendered by default.

49 changes: 23 additions & 26 deletions centrifuge-app/src/pages/IssuerCreatePool/PoolStructureSection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { PoolMetadataInput } from '@centrifuge/centrifuge-js'
import {
Box,
Button,
Checkbox,
CurrencyInput,
Grid,
Expand All @@ -12,6 +11,7 @@ import {
TextInput,
} from '@centrifuge/fabric'
import { Field, FieldProps, useFormikContext } from 'formik'
import * as React from 'react'
import styled, { useTheme } from 'styled-components'
import { FieldWithErrorMessage } from '../../../src/components/FieldWithErrorMessage'
import { Tooltips, tooltipText } from '../../../src/components/Tooltips'
Expand All @@ -32,16 +32,15 @@ export const StyledGrid = styled(Grid)`
padding: 40px;
border: ${({ theme }) => `1px solid ${theme.colors.borderPrimary}`};
border-radius: 8px;
gap: 24px;
@media (max-width: ${({ theme }) => theme.breakpoints.S}) {
padding: 12px;
}
`

export const StyledBox = styled(StyledGrid)``

const Line = () => {
export const Line = () => {
const theme = useTheme()
return <Box border={`.5px solid ${theme.colors.borderPrimary}`} width="100%" mt={2} mb={2} />
return <Box border={`.5px solid ${theme.colors.borderPrimary}`} width="100%" />
}

const ASSET_CLASSES = Object.keys(config.assetClasses).map((key) => ({
Expand All @@ -57,6 +56,7 @@ const CheckboxOption = ({
icon,
sublabel,
id,
height,
}: {
name: string
label: string
Expand All @@ -65,8 +65,10 @@ const CheckboxOption = ({
disabled?: boolean
icon?: React.ReactNode
id?: keyof typeof tooltipText
height?: number
}) => {
const theme = useTheme()

return (
<Box
backgroundColor="white"
Expand All @@ -76,8 +78,8 @@ const CheckboxOption = ({
border={`1px solid ${theme.colors.borderPrimary}`}
display="flex"
flexDirection={icon ? 'row' : 'column'}
justifyContent={icon ? 'space-between' : 'flex-start'}
height={icon ? 64 : null}
justifyContent={icon ? 'space-between' : 'center'}
height={height || 70}
alignItems={icon ? 'center' : 'flex-start'}
>
<Field name={name} validate={validate[name]}>
Expand Down Expand Up @@ -109,8 +111,6 @@ export const PoolStructureSection = () => {
const form = useFormikContext<PoolMetadataInput>()
const { values } = form

console.log(values)

const subAssetClasses =
config.assetClasses[form.values.assetClass]?.map((label) => ({
label,
Expand Down Expand Up @@ -142,16 +142,18 @@ export const PoolStructureSection = () => {
<Text variant="heading2" fontWeight={700}>
Pool Structure
</Text>
<StyledGrid gridTemplateColumns={['1fr', '1fr 1fr']} gap={3} mt={2}>
<StyledGrid gridTemplateColumns={['1fr', '1fr', '1fr 1fr']} gap={3} mt={2}>
<Box>
<Text variant="body2">Pool type *</Text>
<CheckboxOption
height={113}
name="poolStructure"
label="Revolving pool"
value="revolving"
sublabel="Dynamic and flexible pools that allow continuous inflows and outflows of assets. Investors can add or withdraw funds at any time, and the pool remains active indefinitely."
/>
<CheckboxOption
height={113}
name="poolStructure"
label="Static pool (coming soon)"
value="static"
Expand Down Expand Up @@ -255,7 +257,7 @@ export const PoolStructureSection = () => {
<Box mt={4} mb={3}>
<Text>Tranches</Text>
{Array.from({ length: values.trancheStructure }).map((_, index) => (
<StyledBox key={index} mt={3}>
<StyledGrid key={index} mt={3}>
<Text variant="heading3">Tranche {index + 1}</Text>
<Line />
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gap={3}>
Expand Down Expand Up @@ -292,6 +294,7 @@ export const PoolStructureSection = () => {
</Field>
</Box>
</Box>

<Box>
<Field name={`tranches.${index}.symbolName`} validate={validate.symbolName}>
{({ field, form, meta }: FieldProps) => (
Expand All @@ -303,18 +306,18 @@ export const PoolStructureSection = () => {
placeholder="4-12 characters"
minLength={4}
maxLength={12}
// disabled={isUpdating}
/>
)}
</Field>

<Box mt={3}>
{index === 0 ? (
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gap={3}>
<Field name={`tranches.${index}.apy`} validate={validate.subAssetClass}>
<Field name={`tranches.${index}.apy`} validate={validate.apy}>
{({ field, meta, form }: FieldProps) => (
<Select
label={<Tooltips type="apy" label={<Text variant="heading4">APY</Text>} />}
onChange={(event) => form.setFieldValue('subAssetClass', event.target.value)}
onChange={(event) => form.setFieldValue('apy', event.target.value)}
onBlur={field.onBlur}
errorMessage={meta.touched && meta.error ? meta.error : undefined}
value={field.value}
Expand All @@ -328,7 +331,7 @@ export const PoolStructureSection = () => {
as={NumberInput}
placeholder="0.00"
symbol="%"
name={`tranches.${index}.apy`}
name={`tranches.${index}.interestRate`}
validate={validate.interestRate}
/>
</Box>
Expand All @@ -347,10 +350,11 @@ export const PoolStructureSection = () => {
)}
</Box>
</Box>
{index === 1 || index === 2 ? (

{(index === 1 || index === 2) && (
<>
<Box>
<Field name={`tranches.${index}.minInvestment`} validate={validate.minInvestment}>
<Field name={`tranches.${index}.interestRate`} validate={validate.interestRate}>
{({ field, form, meta }: FieldProps) => (
<CurrencyInput
{...field}
Expand Down Expand Up @@ -392,18 +396,11 @@ export const PoolStructureSection = () => {
</Field>
</Box>
</>
) : null}
)}
</Grid>
</StyledBox>
</StyledGrid>
))}
</Box>
<Box />
<Line />
<Box display="flex" justifyContent="flex-end" pt={2} pb={4}>
<Button style={{ width: 163 }} small>
Next
</Button>
</Box>
</Box>
)
}
40 changes: 32 additions & 8 deletions centrifuge-app/src/pages/IssuerCreatePool/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Box, Step, Stepper, Text } from '@centrifuge/fabric'
import { Box, Button, Step, Stepper, Text } from '@centrifuge/fabric'
import { Form, FormikProvider, useFormik } from 'formik'
import { useRef, useState } from 'react'
import styled, { useTheme } from 'styled-components'
import { useIsAboveBreakpoint } from '../../../src/utils/useIsAboveBreakpoint'
import { PoolStructureSection } from './PoolStructureSection'
import { PoolDetailsSection } from './PoolDetailsSection'
import { Line, PoolStructureSection } from './PoolStructureSection'
import { initialValues } from './types'
import { validateValues } from './validate'

Expand All @@ -14,10 +15,6 @@ const StyledBox = styled(Box)`
}
`

const PoolDetails = () => {
return <div></div>
}

const PoolSetup = () => {
return <div></div>
}
Expand All @@ -26,7 +23,7 @@ const IssuerCreatePoolPage = () => {
const theme = useTheme()
const formRef = useRef<HTMLFormElement>(null)
const isSmall = useIsAboveBreakpoint('S')
const [step, setStep] = useState(1)
const [step, setStep] = useState(2)

const form = useFormik({
initialValues: initialValues,
Expand All @@ -35,6 +32,17 @@ const IssuerCreatePoolPage = () => {
onSubmit: () => console.log('a'),
})

const { validateForm, errors } = form

Check warning on line 35 in centrifuge-app/src/pages/IssuerCreatePool/index.tsx

View workflow job for this annotation

GitHub Actions / build-app

'errors' is assigned a value but never used

Check warning on line 35 in centrifuge-app/src/pages/IssuerCreatePool/index.tsx

View workflow job for this annotation

GitHub Actions / ff-prod / build-app

'errors' is assigned a value but never used

const handleNextStep = async () => {
const currentErrors = await validateForm()

// Ensure the form is valid before proceeding to the next step
if (Object.keys(currentErrors).length === 0) {
setStep((prevStep) => prevStep + 1)
}
}

return (
<>
<FormikProvider value={form}>
Expand All @@ -56,8 +64,24 @@ const IssuerCreatePoolPage = () => {
</Box>
<StyledBox padding="48px 80px 0px 80px">
{step === 1 && <PoolStructureSection />}
{step === 2 && <PoolDetails />}
{step === 2 && <PoolDetailsSection />}
{step === 3 && <PoolSetup />}
<Line />
<Box display="flex" justifyContent="flex-end" mt={2} mb={4}>
{step !== 1 && (
<Button
style={{ width: 163, marginRight: 8 }}
small
onClick={() => setStep(step - 1)}
variant="inverted"
>
Previous
</Button>
)}
<Button style={{ width: 163 }} small onClick={handleNextStep}>
{step === 3 ? 'Create pool' : 'Next'}
</Button>
</Box>
</StyledBox>
</Form>
</FormikProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { FeeTypes, PoolMetadataInput } from '@centrifuge/centrifuge-js'
import { isTestEnv } from '../../../src/config'
import { isTestEnv } from '../../config'

export interface Tranche {
tokenName: string
symbolName: string
interestRate: number | ''
minRiskBuffer: number | ''
minInvestment: number | ''
apy: string
interestRate: number | ''
}
export interface WriteOffGroupInput {
days: number | ''
Expand All @@ -17,9 +18,10 @@ export interface WriteOffGroupInput {
export const createEmptyTranche = (trancheName: string): Tranche => ({
tokenName: trancheName,
symbolName: '',
interestRate: trancheName === 'Junior' ? '' : 0,
interestRate: 0,
minRiskBuffer: trancheName === 'Junior' ? '' : 0,
minInvestment: 1000,
apy: '90d',
})

export type CreatePoolValues = Omit<
Expand Down Expand Up @@ -62,7 +64,7 @@ export type CreatePoolValues = Omit<

export const initialValues: CreatePoolValues = {
// pool structure
poolStructure: '',
poolStructure: 'revolving',
trancheStructure: 1,
assetClass: 'Private credit',
assetDenomination: '',
Expand Down
28 changes: 16 additions & 12 deletions centrifuge-app/src/pages/IssuerCreatePool/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,32 @@ export const MB = 1024 ** 2
export const validate = {
nftImage: combine(imageFile(), maxFileSize(1 * MB)),

// pool structure
poolStructure: required(),
trancheStructure: required(),
assetClass: required(),
subAssetClass: required(),
currency: required(),

// tranches
tokenName: combine(required(), maxLength(100)),
symbolName: combine(required(), maxLength(12)),
minInvestment: combine(required(), nonNegativeNumber(), max(Number.MAX_SAFE_INTEGER)),
interestRate: combine(required(), positiveNumber(), max(Number.MAX_SAFE_INTEGER)),
minRiskBuffer: combine(required(), positiveNumber(), max(100)),
maxPriceVariation: combine(required(), min(0), max(10000)),
maturityDate: combine(required(), maturityDate()),
apy: required(),

// pool details
poolName: combine(required(), maxLength(100)),
poolIcon: combine(required(), mimeType('image/svg+xml', 'Icon must be an SVG file')),
assetClass: required(),
subAssetClass: required(),
maxReserve: combine(required(), nonNegativeNumber(), max(Number.MAX_SAFE_INTEGER)),
investorType: required(),
poolType: required(),

epochHours: combine(required(), nonNegativeNumber(), integer(), max(24 * 7 /* 1 week */)),
epochMinutes: combine(required(), nonNegativeNumber(), integer(), max(59)),
currency: required(),

issuerName: combine(required(), maxLength(100)),
issuerRepName: combine(required(), maxLength(100)),
Expand All @@ -53,15 +66,6 @@ export const validate = {
issuerDetailTitle: combine(required(), maxLength(50)),
issuerDetailBody: combine(required(), maxLength(3000)),

// tranches
tokenName: combine(required(), maxLength(100)),
symbolName: combine(required(), maxLength(12)),
minInvestment: combine(required(), nonNegativeNumber(), max(Number.MAX_SAFE_INTEGER)),
interestRate: combine(required(), positiveNumber(), max(Number.MAX_SAFE_INTEGER)),
minRiskBuffer: combine(required(), positiveNumber(), max(100)),
maxPriceVariation: combine(required(), min(0), max(10000)),
maturityDate: combine(required(), maturityDate()),

// risk groups
groupName: maxLength(30),
advanceRate: combine(required(), positiveNumber(), max(100)),
Expand Down
Loading

0 comments on commit 4d6c428

Please sign in to comment.