import { useEffect, useState, useCallback } from 'react' import { trpc } from '~/utils/trpc' import { RCTResponderProps } from '~/components/RenderIOCall' import { Field, Form, Formik } from 'formik' import IVButton from '~/components/IVButton' import IVDialog, { useDialogState } from '~/components/IVDialog' import IVInputField from '~/components/IVInputField' import MFAInput from '~/components/MFAInput' import { tryLogin } from '~/utils/auth' import GoogleIcon from '~/icons/compiled/Google' import CheckCircleIcon from '~/icons/compiled/CheckCircleOutline' import XCircleIcon from '~/icons/compiled/XCircle' function InlineConfirmNotice(props: RCTResponderProps<'CONFIRM'>) { const Icon = props.value ? CheckCircleIcon : XCircleIcon return (
{props.value ? 'Identity confirmed' : 'Identity not confirmed'}
) } function ConfirmMFA({ onRespond, transactionId, context }) { const challenge = trpc.useMutation(['auth.mfa.challenge']) const verify = trpc.useMutation(['auth.mfa.verify'], { onSuccess() { onRespond(true) }, }) const { mutate: challengeMfa } = challenge useEffect(() => { challengeMfa() }, [challengeMfa]) return ( initialValues={{ code: '', }} onSubmit={async ({ code }) => { if (!challenge.data || context === 'docs') return verify.mutate({ code, challengeId: challenge.data, transactionId, }) }} validate={({ code }) => { if (!code) { return { code: 'Please enter a code.', } } }} > {({ isValid }) => (

Your Interval account is enrolled in multi-factor authentication. Enter a verification code from your authenticator app to continue.

{verify.isError && (
Sorry, that code is invalid. Please try again.
)}
onRespond(false)} />
)} ) } function ConfirmPassword({ onRespond, email, transactionId, }: { onRespond: (response: boolean) => void email: string transactionId?: string }) { const [loading, setLoading] = useState(false) const [hasError, setHasError] = useState(false) return ( initialValues={{ password: '', }} onSubmit={async ({ password }) => { setHasError(false) setLoading(true) try { const r = await tryLogin({ email, password, transactionId, }) setLoading(false) if (r.ok) { onRespond(true) } else { setHasError(true) } } catch (error) { console.error('Error confirming password', { error }) setHasError(true) } }} validate={({ password }) => { if (!password.length) { return { password: 'Please enter your password.' } } }} > {({ isValid }) => (

Enter your Interval password to continue.

{hasError && (
Invalid password, please try again.
)}
onRespond(false)} />
)} ) } function ConfirmViaRedirect({ href, label, onRespond, theme, }: { href: string label: React.ReactNode onRespond: (ok: boolean) => void theme?: 'primary' | 'secondary' | 'danger' | 'plain' }) { return (
{ window.open(href, '_blank', 'popup,width=800,height=600') }} /> onRespond(false)} />
) } export default function ConfirmIdentity( props: RCTResponderProps<'CONFIRM_IDENTITY'> ) { const session = trpc.useQuery(['auth.session.user']) const authCheck = trpc.useQuery([ 'auth.check', { email: session.data?.email || '', transactionId: props.transaction?.id, }, ]) const confirm = trpc.useMutation(['auth.identity.confirm']) const hasMfa = trpc.useQuery(['auth.mfa.has']) const hasPassword = trpc.useQuery(['auth.password.has']) const { onUpdatePendingReturnValue } = props const dialog = useDialogState({ visible: true, // non-modals are rendered within their parent component, not a modal: props.context === 'transaction', }) const { hide } = dialog const onRespond = useCallback( (value: boolean) => { onUpdatePendingReturnValue(value) if (props.context !== 'docs') hide() }, [onUpdatePendingReturnValue, props.context, hide] ) const { mutate: doConfirm } = confirm const { refetch: refetchAuth } = authCheck useEffect(() => { if (authCheck.data?.identityConfirmed && props.transaction) { doConfirm( { transactionId: props.transaction?.id }, { onSuccess(response) { if (response === true) { onUpdatePendingReturnValue(true) } else { refetchAuth() } }, } ) } }, [ authCheck.data, onUpdatePendingReturnValue, props.transaction, doConfirm, refetchAuth, ]) // show inline result of the confirmation process if (props.shouldUseAppendUi && typeof props.value === 'boolean') { return } if (props.context !== 'docs') { if ( !props.transaction || authCheck.isLoading || confirm.isLoading || authCheck.data?.identityConfirmed ) { // avoid flash of modal until we know we need a confirmation return null } } let confirmComponent: React.ReactNode if (props.context === 'docs' || hasMfa.data) { confirmComponent = ( ) } else if (authCheck.data?.sso) { const { workosOrganizationId } = authCheck.data.sso const paramObj = { workosOrganizationId, } if (props.transaction?.id) { paramObj['transactionId'] = props.transaction.id } const params = new URLSearchParams(paramObj) confirmComponent = ( ) } else if (hasPassword.data) { confirmComponent = ( ) } else { let href = `/api/auth/sso/sign-in-with-google` if (props.transaction?.id) { const params = new URLSearchParams({ transactionId: props.transaction?.id || '', }) href += `?${params}` } confirmComponent = ( Continue with Google } onRespond={onRespond} /> ) } return (

Please confirm your identity to continue

{props.label &&
{props.label}
} {confirmComponent}
) }