Skip to content

Commit

Permalink
Onboarding: Convert tax forms to optional step (#1572)
Browse files Browse the repository at this point in the history
* Give email dialogs a facelift

* Add key to investor settings to make tax form optional on a pool level

* Update onboarding frontend with taxes optional

* Remove altair pinata reference

* Add taxDoc as optional field in database

* Fix visibilty for pools that don't require tax forms

* Update step numbers

* Remove Box
  • Loading branch information
sophialittlejohn authored Sep 12, 2023
1 parent ced2ca2 commit 2e038db
Show file tree
Hide file tree
Showing 23 changed files with 290 additions and 296 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@ import { useSendVerifyEmail } from '../../pages/Onboarding/queries/useSendVerify
type Props = {
isDialogOpen: boolean
setIsDialogOpen: (isDialogOpen: boolean) => void
currentEmail: string
}

export const ConfirmResendEmailVerificationDialog = ({ isDialogOpen, setIsDialogOpen }: Props) => {
export const ConfirmResendEmailVerificationDialog = ({ isDialogOpen, setIsDialogOpen, currentEmail }: Props) => {
const { mutate: sendVerifyEmail, isLoading } = useSendVerifyEmail()

return (
<Dialog
width="25%"
width="30%"
isOpen={isLoading ? true : isDialogOpen}
onClose={() => setIsDialogOpen(false)}
title={<Text variant="heading1">Send Confirmation Email</Text>}
title={<Text variant="heading2">Send Confirmation Email</Text>}
>
<Box p={2}>
<Stack gap={4}>
<Text variant="body1">Are you sure you want to resend a confirmation email?</Text>
<Shelf justifyContent="flex-end" gap={2}>
<Box>
<Stack gap={3}>
<Text variant="body1">Are you sure you want to resend a confirmation email to {currentEmail}?</Text>
<Shelf gap={2} justifyContent="flex-end">
<Button onClick={() => setIsDialogOpen(false)} variant="secondary" disabled={isLoading}>
Cancel
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, Button, Dialog, Shelf, Stack, Text, TextInput } from '@centrifuge/fabric'
import { Button, Dialog, Shelf, Stack, Text, TextInput } from '@centrifuge/fabric'
import * as React from 'react'
import { useMutation } from 'react-query'
import { string } from 'yup'
Expand Down Expand Up @@ -49,27 +49,25 @@ export const EditOnboardingEmailAddressDialog = ({ isDialogOpen, setIsDialogOpen
width="30%"
isOpen={isLoading ? true : isDialogOpen}
onClose={() => setIsDialogOpen(false)}
title={<Text variant="heading1">Edit Email Address</Text>}
title={<Text variant="heading2">Edit Email Address</Text>}
>
<Box p={4}>
<Stack gap={4}>
<TextInput value={currentEmail} label="Current Email Address" disabled />
<TextInput value={newEmail} label="New Email Address" onChange={(event) => setNewEmail(event.target.value)} />
<Shelf justifyContent="flex-end" gap={2}>
<Button onClick={() => setIsDialogOpen(false)} variant="secondary" disabled={isLoading}>
Cancel
</Button>
<Button
onClick={() => updateEmail()}
loading={isLoading}
disabled={isLoading || !isValid}
loadingMessage="Updating"
>
Update
</Button>
</Shelf>
</Stack>
</Box>
<Stack gap={3}>
<TextInput value={currentEmail} label="Current Email Address" disabled />
<TextInput value={newEmail} label="New Email Address" onChange={(event) => setNewEmail(event.target.value)} />
<Shelf justifyContent="flex-end" gap={2}>
<Button onClick={() => setIsDialogOpen(false)} variant="secondary" disabled={isLoading}>
Cancel
</Button>
<Button
onClick={() => updateEmail()}
loading={isLoading}
disabled={isLoading || !isValid || newEmail === currentEmail}
loadingMessage="Updating"
>
Update
</Button>
</Shelf>
</Stack>
</Dialog>
)
}
9 changes: 2 additions & 7 deletions centrifuge-app/src/components/Onboarding/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function FileUpload({
}

return (
<Stack gap={1} width="100%" height={280}>
<Stack gap={1} width="100%" height={150}>
<Box
px={2}
py={1}
Expand All @@ -157,7 +157,7 @@ export function FileUpload({
tabIndex={-1}
ref={inputRef}
/>
<Stack gap={4} height="100%" justifyContent="center" alignItems="center">
<Stack gap={2} height="100%" justifyContent="center" alignItems="center">
{curFile ? (
<>
<Shelf gap={1}>
Expand Down Expand Up @@ -189,11 +189,6 @@ export function FileUpload({
) : (
<>
<Stack gap={1} alignItems="center">
<Text as="span" variant="body1" textAlign="center">
Drop a file to upload
<br />
or
</Text>
<UploadButton
forwardedAs="button"
variant="body1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type OnboardingSettingsInput = {
externalOnboardingUrl?: string
openForOnboarding: { [trancheId: string]: boolean }
podReadAccess: boolean
taxInfoRequired: boolean
}

export const OnboardingSettings = () => {
Expand Down Expand Up @@ -122,6 +123,7 @@ export const OnboardingSettings = () => {
{}
),
podReadAccess: !!poolMetadata?.onboarding?.podReadAccess || false,
taxInfoRequired: !!poolMetadata?.onboarding?.taxInfoRequired || true,
}
}, [pool, poolMetadata, centrifuge.metadata])

Expand Down Expand Up @@ -202,6 +204,7 @@ export const OnboardingSettings = () => {
kybRestrictedCountries,
externalOnboardingUrl: useExternalUrl ? values.externalOnboardingUrl : undefined,
podReadAccess: values.podReadAccess,
taxInfoRequired: values.taxInfoRequired,
},
}

Expand Down Expand Up @@ -363,6 +366,15 @@ export const OnboardingSettings = () => {
disabled={!isEditing || formik.isSubmitting || isLoading}
/>
</Stack>
<Stack gap={2}>
<Text variant="heading4">Tax document requirement</Text>
<Checkbox
label="Require investors to upload tax documents before signing the subscription agreement"
checked={formik.values.taxInfoRequired}
onChange={(e) => formik.setFieldValue('taxInfoRequired', !!e.target.checked)}
disabled={!isEditing || formik.isSubmitting || isLoading}
/>
</Stack>
<RestrictedCountriesTable isEditing={isEditing} isLoading={isLoading} formik={formik} type="KYB" />
<RestrictedCountriesTable isEditing={isEditing} isLoading={isLoading} formik={formik} type="KYC" />
</Stack>
Expand Down
83 changes: 77 additions & 6 deletions centrifuge-app/src/pages/Onboarding/SignSubscriptionAgreement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import { AnchorButton, Box, Button, Checkbox, IconDownload, Shelf, Spinner, Stac
import { useFormik } from 'formik'
import * as React from 'react'
import { boolean, object } from 'yup'
import { ActionBar, Content, ContentHeader } from '../../components/Onboarding'
import { ConfirmResendEmailVerificationDialog } from '../../components/Dialogs/ConfirmResendEmailVerificationDialog'
import { EditOnboardingEmailAddressDialog } from '../../components/Dialogs/EditOnboardingEmailAddressDialog'
import { ActionBar, Content, ContentHeader, Notification, NotificationBar } from '../../components/Onboarding'
import { OnboardingPool, useOnboarding } from '../../components/OnboardingProvider'
import { PDFViewer } from '../../components/PDFViewer'
import { ValidationToast } from '../../components/ValidationToast'
import { OnboardingUser } from '../../types'
import { usePool, usePoolMetadata } from '../../utils/usePools'
import { useSignAndSendDocuments } from './queries/useSignAndSendDocuments'
import { useSignRemark } from './queries/useSignRemark'
import { useUploadTaxInfo } from './queries/useUploadTaxInfo'
import { TaxInfo } from './TaxInfo'

type Props = {
signedAgreementUrl: string | undefined
Expand All @@ -32,19 +37,24 @@ export const SignSubscriptionAgreement = ({ signedAgreementUrl }: Props) => {
const { data: poolMetadata } = usePoolMetadata(poolData)
const centrifuge = useCentrifuge()

const isTaxDocsRequired = poolMetadata?.onboarding?.taxInfoRequired
const hasSignedAgreement = !!onboardingUser.poolSteps?.[poolId]?.[trancheId]?.signAgreement.completed
const unsignedAgreementUrl = poolMetadata?.onboarding?.tranches?.[trancheId]?.agreement?.uri
? centrifuge.metadata.parseMetadataUrl(poolMetadata.onboarding.tranches[trancheId].agreement?.uri!)
: !poolId.startsWith('0x')
? centrifuge.metadata.parseMetadataUrl(GENERIC_SUBSCRIPTION_AGREEMENT)
: null

const isEmailVerified = !!onboardingUser.globalSteps.verifyEmail.completed
const formik = useFormik({
initialValues: {
isAgreed: hasSignedAgreement,
isEmailVerified,
taxInfo: undefined,
},
validationSchema,
onSubmit: () => {
onSubmit: async (values) => {
isTaxDocsRequired && (await uploadTaxInfo(values.taxInfo))
signRemark([
`I hereby sign the subscription agreement of pool ${poolId} and tranche ${trancheId}: ${poolMetadata?.onboarding?.tranches?.[trancheId]?.agreement?.uri}`,
])
Expand All @@ -53,6 +63,7 @@ export const SignSubscriptionAgreement = ({ signedAgreementUrl }: Props) => {

const { mutate: sendDocumentsToIssuer, isLoading: isSending } = useSignAndSendDocuments()
const { execute: signRemark, isLoading: isSigningTransaction } = useSignRemark(sendDocumentsToIssuer)
const { mutate: uploadTaxInfo, isLoading: isTaxUploadLoading } = useUploadTaxInfo()

// tinlake pools without subdocs cannot accept investors
const isPoolClosedToOnboarding = poolId.startsWith('0x') && !unsignedAgreementUrl
Expand All @@ -70,6 +81,12 @@ export const SignSubscriptionAgreement = ({ signedAgreementUrl }: Props) => {

return !isPoolClosedToOnboarding && isCountrySupported ? (
<Content>
{formik.errors.isEmailVerified && <ValidationToast label={formik.errors.isEmailVerified} />}
{!hasSignedAgreement && onboardingUser.investorType === 'individual' && (
<NotificationBar>
<EmailVerificationInlineFeedback email={onboardingUser?.email as string} completed={isEmailVerified} />
</NotificationBar>
)}
<ContentHeader
title="Sign subscription agreement"
body="Read the subscription agreement and click the box below to automatically e-sign the subscription agreement. You don't need to download and sign manually."
Expand Down Expand Up @@ -129,6 +146,14 @@ export const SignSubscriptionAgreement = ({ signedAgreementUrl }: Props) => {
</AnchorButton>
)}
</Stack>
{isTaxDocsRequired && (
<TaxInfo
value={formik.values.taxInfo}
setValue={(file) => formik.setFieldValue('taxInfo', file)}
touched={formik.touched.taxInfo}
error={formik.errors.taxInfo}
/>
)}
<Checkbox
{...formik.getFieldProps('isAgreed')}
checked={formik.values.isAgreed}
Expand All @@ -137,19 +162,29 @@ export const SignSubscriptionAgreement = ({ signedAgreementUrl }: Props) => {
I hereby sign and agree to the terms of the subscription agreement
</Text>
}
disabled={isSigningTransaction || isSending || hasSignedAgreement}
disabled={isSigningTransaction || isSending || hasSignedAgreement || isTaxUploadLoading}
errorMessage={formik.errors.isAgreed}
/>

<ActionBar>
<Button onClick={() => previousStep()} variant="secondary" disabled={isSigningTransaction || isSending}>
<Button
onClick={() => previousStep()}
variant="secondary"
disabled={isSigningTransaction || isSending || isTaxUploadLoading}
>
Back
</Button>
<Button
onClick={hasSignedAgreement ? () => nextStep() : () => formik.handleSubmit()}
loadingMessage="Signing"
loading={isSigningTransaction || isSending}
disabled={isSigningTransaction || isSending}
loading={isSigningTransaction || isSending || isTaxUploadLoading}
disabled={
isSigningTransaction ||
isSending ||
isTaxUploadLoading ||
(isTaxDocsRequired && !formik.values.taxInfo) ||
!formik.values.isAgreed
}
>
{hasSignedAgreement ? 'Next' : 'Sign'}
</Button>
Expand Down Expand Up @@ -187,3 +222,39 @@ export const SignSubscriptionAgreement = ({ signedAgreementUrl }: Props) => {
</Content>
)
}

const EmailVerificationInlineFeedback = ({ email, completed }: { email: string; completed: boolean }) => {
const [isEditOnboardingEmailAddressDialogOpen, setIsEditOnboardingEmailAddressDialogOpen] = React.useState(false)
const [isConfirmResendEmailVerificationDialogOpen, setIsConfirmResendEmailVerificationDialogOpen] =
React.useState(false)

if (completed) {
return <Notification>Email address verified</Notification>
}

return (
<>
<Notification type="alert">
Please verify your email address. Email sent to {email}. If you did not receive an email,{' '}
<button onClick={() => setIsConfirmResendEmailVerificationDialogOpen(true)}>send again</button> or{' '}
<button onClick={() => setIsEditOnboardingEmailAddressDialogOpen(true)}>edit email</button>. Otherwise contact{' '}
<a href="mailto:[email protected]?subject=Onboarding email verification&body=I’m reaching out about…">
[email protected]
</a>
.
</Notification>

<EditOnboardingEmailAddressDialog
currentEmail={email}
isDialogOpen={isEditOnboardingEmailAddressDialogOpen}
setIsDialogOpen={setIsEditOnboardingEmailAddressDialogOpen}
/>

<ConfirmResendEmailVerificationDialog
isDialogOpen={isConfirmResendEmailVerificationDialogOpen}
setIsDialogOpen={setIsConfirmResendEmailVerificationDialogOpen}
currentEmail={email}
/>
</>
)
}
Loading

0 comments on commit 2e038db

Please sign in to comment.