Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create pool - functionality #2545

Merged
merged 17 commits into from
Dec 4, 2024
4 changes: 2 additions & 2 deletions centrifuge-app/src/components/Menu/IssuerMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ export function IssuerMenu({ defaultOpen = false, children }: IssuerMenuProps) {
Issuer
{isLarge &&
(open ? (
<IconChevronDown size={['iconMedium', 'iconMedium', 'iconSmall']} />
<IconChevronDown size={['iconMedium', 'iconMedium', 'iconSmall']} color="white" />
) : (
<IconChevronRight size={['iconMedium', 'iconMedium', 'iconSmall']} />
<IconChevronRight size={['iconMedium', 'iconMedium', 'iconSmall']} color="white" />
))}
</Toggle>

Expand Down
2 changes: 1 addition & 1 deletion centrifuge-app/src/components/Menu/Toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const Toggle = styled(Text)<{ isActive?: boolean; stacked?: boolean }>`
width: 100%;
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)};
color: ${({ theme }) => theme.colors.textInverted};
border-radius: 4px;
background-color: ${({ isActive }) => (isActive ? LIGHT_BACKGROUND : 'transparent')};

Expand Down
4 changes: 4 additions & 0 deletions centrifuge-app/src/components/Menu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ const StyledRouterLinkButton = styled(RouterLinkButton)`
background-color: ${COLOR};
color: white;
}

&:active {
border-color: transparent;
}
}
`

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useWallet } from '@centrifuge/centrifuge-react'
import { Button } from '@centrifuge/fabric'
import { useFormikContext } from 'formik'
import { CreatePoolValues } from '.'
import { PageSection } from '../../components/PageSection'
import { MultisigForm } from '../IssuerPool/Access/MultisigForm'
import { CreatePoolValues } from './types'

export function AdminMultisigSection() {
const form = useFormikContext<CreatePoolValues>()
Expand Down
40 changes: 24 additions & 16 deletions centrifuge-app/src/pages/IssuerCreatePool/IssuerCategories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PoolMetadataInput } from '@centrifuge/centrifuge-js'
import { Box, IconButton, IconTrash, Select, Text, TextInput } from '@centrifuge/fabric'
import { Box, Grid, IconButton, IconTrash, Select, Text, TextInput } from '@centrifuge/fabric'
import { Field, FieldArray, FieldProps, useFormikContext } from 'formik'
import { AddButton } from './PoolDetailsSection'
import { StyledGrid } from './PoolStructureSection'
Expand Down Expand Up @@ -38,24 +38,32 @@ export const IssuerCategoriesSection = () => {
<FieldArray name="issuerCategories">
{({ push, remove }) => (
<>
{form.values.issuerCategories.map((_, index) => (
{form.values.issuerCategories.map((category, index) => (
<>
<Field name={`issuerCategories.${index}.type`}>
{({ field, meta }: FieldProps) => (
<Select
name={field.name}
label="Type"
onChange={(event) => form.setFieldValue(field.name, event.target.value)}
onBlur={field.onBlur}
value={field.value}
options={PROVIDERS}
placeholder="Please select..."
/>
<Grid gridTemplateColumns={['1fr', category.type === 'other' ? '1fr 1fr' : '1fr']} gap={2}>
<Field name={`issuerCategories.${index}.type`}>
{({ field, meta }: FieldProps) => (
<Select
name={field.name}
label="Type"
onChange={(event) => form.setFieldValue(field.name, event.target.value)}
onBlur={field.onBlur}
value={field.value}
options={PROVIDERS}
placeholder="Please select..."
/>
)}
</Field>
{category.type === 'other' && (
<Field name={`issuerCategories.${index}.description`}>
{({ field, meta }: FieldProps) => (
<TextInput {...field} label="Description" placeholder="Type here..." maxLength={100} />
)}
</Field>
)}
</Field>

</Grid>
<Field name={`issuerCategories.${index}.value`}>
{({ field, meta }: FieldProps) => (
{({ field }: FieldProps) => (
<TextInput
{...field}
label={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ export const PoolDetailsSection = () => {
const form = useFormikContext<PoolMetadataInput>()
const createLabel = (label: string) => `${label}${isTestEnv ? '' : '*'}`

console.log(form.values)

return (
<Box>
<Text variant="heading2" fontWeight={700}>
Expand Down Expand Up @@ -193,7 +191,6 @@ export const PoolDetailsSection = () => {
placeholder="Type here..."
maxLength={1000}
errorMessage={meta.touched && meta.error ? meta.error : undefined}
// disabled={waitingForStoredIssuer}
/>
)}
</Field>
Expand Down Expand Up @@ -299,9 +296,9 @@ export const PoolDetailsSection = () => {
onFileChange={(file) => {
form.setFieldValue('reportAuthorAvatar', file)
}}
accept="application/pdf"
label="Reviewer avatar"
placeholder="Choose file"
accept="image/*"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would limit the file types even more, e/g accept="image/png, image/jpeg, image/jpg"

small
/>
)}
Expand Down
165 changes: 123 additions & 42 deletions centrifuge-app/src/pages/IssuerCreatePool/PoolSetupSection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PoolMetadataInput } from '@centrifuge/centrifuge-js'
import { addressToHex, evmToSubstrateAddress, PoolMetadataInput } from '@centrifuge/centrifuge-js'
import { useCentEvmChainId } from '@centrifuge/centrifuge-react'
import {
Box,
Checkbox,
Expand All @@ -18,6 +19,7 @@ import { useTheme } from 'styled-components'
import { FieldWithErrorMessage } from '../../../src/components/FieldWithErrorMessage'
import { Tooltips } from '../../../src/components/Tooltips'
import { feeCategories } from '../../../src/config'
import { isEvmAddress } from '../../../src/utils/address'
import { AddButton } from './PoolDetailsSection'
import { CheckboxOption, Line, StyledGrid } from './PoolStructureSection'

Expand All @@ -28,8 +30,30 @@ const FEE_TYPES = [

const FEE_POSISTIONS = [{ label: 'Top of waterfall', value: 'Top of waterfall' }]

const TaxDocument = () => {
const form = useFormikContext<PoolMetadataInput>()

return (
<Box mt={2}>
<Text variant="heading4">Tax document requirement</Text>

<Field name="onboarding">
{({ field }: FieldProps) => (
<Checkbox
{...field}
label="Require investors to upload tax documents before signing the subscription agreement."
variant="square"
onChange={(val) => form.setFieldValue('onboarding.taxInfoRequired', val.target.checked ? true : false)}
/>
)}
</Field>
</Box>
)
}

export const PoolSetupSection = () => {
const theme = useTheme()
const chainId = useCentEvmChainId()
const form = useFormikContext<PoolMetadataInput>()
const { values } = form

Expand Down Expand Up @@ -73,7 +97,24 @@ export const PoolSetupSection = () => {
values.adminMultisig?.signers?.map((_, index) => (
<Box key={index} mt={2}>
<Field name={`adminMultisig.signers.${index}`}>
{({ field }: FieldProps) => <TextInput placeholder="Type here..." {...field} />}
{({ field, form }: FieldProps) => (
<TextInput
placeholder="Type address..."
{...field}
onChange={(val) => {
form.setFieldValue(`adminMultisig.signers.${index}`, val.target.value)
}}
onBlur={() => {
const value = form.values.adminMultisig.signers[index]
if (value) {
const transformedValue = isEvmAddress(value)
? evmToSubstrateAddress(value, chainId ?? 0)
: value
form.setFieldValue(`adminMultisig.signers.${index}`, transformedValue)
}
}}
/>
)}
</Field>
</Box>
))
Expand All @@ -88,7 +129,7 @@ export const PoolSetupSection = () => {
<Box display="flex" justifyContent="flex-end" mt={2}>
<AddButton
onClick={() => {
if (form.values.adminMultisig && form.values.adminMultisig.signers?.length <= 10) {
if (values.adminMultisig && values.adminMultisig.signers?.length <= 10) {
push('')
}
}}
Expand All @@ -103,19 +144,19 @@ export const PoolSetupSection = () => {
</Box>
<Box mt={2} mb={2}>
<StyledGrid gridTemplateColumns={['1fr', '1fr 1fr']} gap={3} mt={3}>
<Field name="subAssetClass">
<Field name="adminMultisig.threshold">
{({ field, meta, form }: FieldProps) => (
<Select
name="subAssetClass"
name="adminMultisig.threshold"
label={`Configuration change threshold (1 out of ${Math.max(
values?.adminMultisig?.signers?.length ?? 0,
1
)} managers)`}
onChange={(event) => form.setFieldValue('subAssetClass', event.target.value)}
onChange={(event) => form.setFieldValue('adminMultisig.threshold', event.target.value)}
onBlur={field.onBlur}
errorMessage={meta.touched && meta.error ? meta.error : undefined}
value={field.value}
options={form.values.adminMultisig.signers.map((_: string, i: number) => ({
options={values.adminMultisig?.signers.map((_: string, i: number) => ({
label: i + 1,
value: i + 1,
}))}
Expand All @@ -142,10 +183,27 @@ export const PoolSetupSection = () => {
Add or remove addresses that can:
</Text>
<Text variant="heading2">Originate assets and invest in the pool*</Text>
{form.values.assetOriginators?.map((_: string, index: number) => (
{values.assetOriginators?.map((_: string, index: number) => (
<Box key={index} mt={2}>
<Field name={`assetOriginators.${index}`}>
{({ field }: FieldProps) => <TextInput placeholder="Type address..." {...field} />}
{({ field, form }: FieldProps) => (
<TextInput
placeholder="Type address..."
{...field}
onChange={(val) => {
form.setFieldValue(`assetOriginators.${index}`, val.target.value)
}}
onBlur={() => {
const value = form.values.assetOriginators[index]
if (value) {
const transformedValue = isEvmAddress(value)
? evmToSubstrateAddress(value, chainId ?? 0)
: addressToHex(value)
form.setFieldValue(`assetOriginators.${index}`, transformedValue)
}
}}
/>
)}
</Field>
</Box>
))}
Expand All @@ -154,7 +212,7 @@ export const PoolSetupSection = () => {
<Box gridColumn="2 / span 1" alignSelf="end">
<AddButton
onClick={() => {
if (form.values.adminMultisig && form.values.adminMultisig.signers?.length <= 10) {
if (values.adminMultisig && values.adminMultisig.signers?.length <= 10) {
push('')
}
}}
Expand Down Expand Up @@ -207,16 +265,14 @@ export const PoolSetupSection = () => {
<FieldArray name="poolFees">
{({ push, remove }) => (
<>
{form.values.poolFees.map((_, index) => (
{values.poolFees.map((_, index) => (
<Box mt={4} mb={3} key={index}>
<StyledGrid mt={3} gap={1}>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Text variant="heading3">Pool fees {index + 1}</Text>
{form.values.poolFees.length > 1 && (
<IconButton onClick={() => remove(index)}>
<IconTrash color="textSecondary" />
</IconButton>
)}
<IconButton onClick={() => remove(index)}>
<IconTrash color="textSecondary" />
</IconButton>
</Box>
<Line />
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gap={3}>
Expand Down Expand Up @@ -325,33 +381,58 @@ export const PoolSetupSection = () => {
icon={<IconHelpCircle size="iconSmall" color={theme.colors.textSecondary} />}
/>
</Box>
<Box>
<Field name="subscriptionDocuments">
{({ field, meta, form }: FieldProps) => (
<Box>
<FileUpload
name="subscriptionDocuments"
file={field.value}
onFileChange={async (file) => {
form.setFieldTouched('poolIcon', true, false)
form.setFieldValue('poolIcon', file)
}}
label="Click to upload"
errorMessage={meta.touched && meta.error ? meta.error : undefined}
accept="application/pdf"
small
/>
</Box>
)}
</Field>
<Box mt={8}>
<Text variant="heading4">Tax document requirement</Text>
<Checkbox
label="Require investors to upload tax documents before signing the subscription agreement."
variant="square"
/>
{values.onboardingExperience === 'centrifuge' && (
<Box>
<Box>
{values.tranches.map((tranche, index) => (
<Field key={index} name={`onboarding.tranches.${tranche.tokenName}`}>
{({ field, meta }: FieldProps) => (
<Box mb={4}>
<FileUpload
name={`onboarding.${tranche.tokenName}`}
file={field.value}
onFileChange={async (file) => {
form.setFieldTouched(`onboarding.tranches.${tranche.tokenName}`, true, false)
form.setFieldValue(`onboarding.tranches.${tranche.tokenName}`, file)
}}
label={`Subscription document for ${tranche.tokenName}`}
errorMessage={meta.touched && meta.error ? meta.error : undefined}
accept="application/pdf"
small
/>
</Box>
)}
</Field>
))}
</Box>
<TaxDocument />
</Box>
</Box>
)}
{values.onboardingExperience === 'external' && (
<Box>
{values.tranches.map((tranche, index) => (
<Field key={index} name={`onboarding.tranches.${tranche.tokenName}`}>
{({ field, meta }: FieldProps) => (
<Box mb={4}>
<FieldWithErrorMessage
{...field}
as={TextInput}
name={`onboarding.tranches.${tranche.tokenName}`}
value={field.value}
label={<Text variant="heading4">Onboarding URL {tranche.tokenName}</Text>}
isUrl
placeholder="www.example.com"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
form.setFieldValue(`onboarding.tranches.${tranche.tokenName}`, e.target.value)
}
/>
</Box>
)}
</Field>
))}
<TaxDocument />
</Box>
)}
</StyledGrid>
</Box>
</Box>
Expand Down
Loading
Loading