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

Onboarding: Convert tax forms to optional step #1572

Merged
merged 12 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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