fix: polish

This commit is contained in:
David Nguyen
2024-03-27 18:35:20 +08:00
parent 47916e3127
commit 3c0918963d
7 changed files with 40 additions and 34 deletions

View File

@ -1,7 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Link from 'next/link';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
@ -22,6 +20,8 @@ import {
} from '@documenso/ui/primitives/form/form'; } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { EnableAuthenticatorAppDialog } from '~/components/forms/2fa/enable-authenticator-app-dialog';
import { useRequiredDocumentAuthContext } from './document-auth-provider'; import { useRequiredDocumentAuthContext } from './document-auth-provider';
export type DocumentActionAuth2FAProps = { export type DocumentActionAuth2FAProps = {
@ -58,6 +58,7 @@ export const DocumentActionAuth2FA = ({
}, },
}); });
const [is2FASetupSuccessful, setIs2FASetupSuccessful] = useState(false);
const [formErrorCode, setFormErrorCode] = useState<string | null>(null); const [formErrorCode, setFormErrorCode] = useState<string | null>(null);
const onFormSubmit = async ({ token }: T2FAAuthFormSchema) => { const onFormSubmit = async ({ token }: T2FAAuthFormSchema) => {
@ -87,17 +88,29 @@ export const DocumentActionAuth2FA = ({
token: '', token: '',
}); });
setIs2FASetupSuccessful(false);
setFormErrorCode(null); setFormErrorCode(null);
}, [open, form]);
if (!user?.twoFactorEnabled) { // eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
if (!user?.twoFactorEnabled && !is2FASetupSuccessful) {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Alert variant="warning"> <Alert variant="warning">
<AlertDescription> <AlertDescription>
{recipient.role === RecipientRole.VIEWER && actionTarget === 'DOCUMENT' <p>
? 'You need to setup 2FA to mark this document as viewed.' {recipient.role === RecipientRole.VIEWER && actionTarget === 'DOCUMENT'
: `You need to setup 2FA to ${actionVerb.toLowerCase()} this ${actionTarget.toLowerCase()}.`} ? 'You need to setup 2FA to mark this document as viewed.'
: `You need to setup 2FA to ${actionVerb.toLowerCase()} this ${actionTarget.toLowerCase()}.`}
</p>
{user?.identityProvider === 'DOCUMENSO' && (
<p className="mt-2">
By enabling 2FA, you will be required to enter a code from your authenticator app
every time you sign in.
</p>
)}
</AlertDescription> </AlertDescription>
</Alert> </Alert>
@ -106,9 +119,7 @@ export const DocumentActionAuth2FA = ({
Close Close
</Button> </Button>
<Button type="button" asChild> <EnableAuthenticatorAppDialog onSuccess={() => setIs2FASetupSuccessful(true)} />
<Link href="/settings/security">Setup 2FA</Link>
</Button>
</DialogFooter> </DialogFooter>
</div> </div>
); );

View File

@ -49,7 +49,7 @@ export const DocumentActionAuthAccount = ({
return ( return (
<fieldset disabled={isSigningOut} className="space-y-4"> <fieldset disabled={isSigningOut} className="space-y-4">
<Alert> <Alert variant="warning">
<AlertDescription> <AlertDescription>
{actionTarget === 'DOCUMENT' && recipient.role === RecipientRole.VIEWER ? ( {actionTarget === 'DOCUMENT' && recipient.role === RecipientRole.VIEWER ? (
<span> <span>

View File

@ -137,10 +137,7 @@ export const DocumentActionAuthPasskey = ({
); );
} }
if ( if (passkeyData.isInitialLoading || (passkeyData.isError && passkeyData.passkeys.length === 0)) {
passkeyData.isInitialLoading ||
(passkeyData.isRefetching && passkeyData.passkeys.length === 0)
) {
return ( return (
<div className="flex h-28 items-center justify-center"> <div className="flex h-28 items-center justify-center">
<Loader className="text-muted-foreground h-6 w-6 animate-spin" /> <Loader className="text-muted-foreground h-6 w-6 animate-spin" />
@ -171,7 +168,7 @@ export const DocumentActionAuthPasskey = ({
if (passkeyData.passkeys.length === 0) { if (passkeyData.passkeys.length === 0) {
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<Alert> <Alert variant="warning">
<AlertDescription> <AlertDescription>
{recipient.role === RecipientRole.VIEWER && actionTarget === 'DOCUMENT' {recipient.role === RecipientRole.VIEWER && actionTarget === 'DOCUMENT'
? 'You need to setup a passkey to mark this document as viewed.' ? 'You need to setup a passkey to mark this document as viewed.'

View File

@ -5,7 +5,6 @@ import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { MAXIMUM_PASSKEYS } from '@documenso/lib/constants/auth'; import { MAXIMUM_PASSKEYS } from '@documenso/lib/constants/auth';
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
import type { import type {
TDocumentAuthOptions, TDocumentAuthOptions,
TRecipientAccessAuthTypes, TRecipientAccessAuthTypes,
@ -175,9 +174,11 @@ export const DocumentAuthProvider = ({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [passkeyData.passkeys]); }, [passkeyData.passkeys]);
// Assume that a user must be logged in for any auth requirements.
const isAuthRedirectRequired = Boolean( const isAuthRedirectRequired = Boolean(
DOCUMENT_AUTH_TYPES[derivedRecipientActionAuth || '']?.isAuthRedirectRequired && derivedRecipientActionAuth &&
!preCalculatedActionAuthOptions, derivedRecipientActionAuth !== DocumentAuth.EXPLICIT_NONE &&
user?.email !== recipient.email,
); );
const refetchPasskeys = async () => { const refetchPasskeys = async () => {

View File

@ -42,10 +42,10 @@ export const SigningForm = ({ document, recipient, fields, redirectUrl }: Signin
const { mutateAsync: completeDocumentWithToken } = const { mutateAsync: completeDocumentWithToken } =
trpc.recipient.completeDocumentWithToken.useMutation(); trpc.recipient.completeDocumentWithToken.useMutation();
const { const { handleSubmit, formState } = useForm();
handleSubmit,
formState: { isSubmitting }, // Keep the loading state going if successful since the redirect may take some time.
} = useForm(); const isSubmitting = formState.isSubmitting || formState.isSubmitSuccessful;
const uninsertedFields = useMemo(() => { const uninsertedFields = useMemo(() => {
return sortFieldsByPosition(fields.filter((field) => !field.inserted)); return sortFieldsByPosition(fields.filter((field) => !field.inserted));

View File

@ -41,8 +41,13 @@ export const ZEnable2FAForm = z.object({
export type TEnable2FAForm = z.infer<typeof ZEnable2FAForm>; export type TEnable2FAForm = z.infer<typeof ZEnable2FAForm>;
export const EnableAuthenticatorAppDialog = () => { export type EnableAuthenticatorAppDialogProps = {
onSuccess?: () => void;
};
export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorAppDialogProps) => {
const { toast } = useToast(); const { toast } = useToast();
const router = useRouter(); const router = useRouter();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -79,6 +84,7 @@ export const EnableAuthenticatorAppDialog = () => {
const data = await enable2FA({ code: token }); const data = await enable2FA({ code: token });
setRecoveryCodes(data.recoveryCodes); setRecoveryCodes(data.recoveryCodes);
onSuccess?.();
toast({ toast({
title: 'Two-factor authentication enabled', title: 'Two-factor authentication enabled',
@ -89,7 +95,7 @@ export const EnableAuthenticatorAppDialog = () => {
toast({ toast({
title: 'Unable to setup two-factor authentication', title: 'Unable to setup two-factor authentication',
description: description:
'We were unable to setup two-factor authentication for your account. Please ensure that you have entered your password correctly and try again.', 'We were unable to setup two-factor authentication for your account. Please ensure that you have entered your code correctly and try again.',
variant: 'destructive', variant: 'destructive',
}); });
} }

View File

@ -4,21 +4,12 @@ import { DocumentAuth } from '../types/document-auth';
type DocumentAuthTypeData = { type DocumentAuthTypeData = {
key: TDocumentAuth; key: TDocumentAuth;
value: string; value: string;
/**
* Whether this authentication event will require the user to halt and
* redirect.
*
* Defaults to false.
*/
isAuthRedirectRequired?: boolean;
}; };
export const DOCUMENT_AUTH_TYPES: Record<string, DocumentAuthTypeData> = { export const DOCUMENT_AUTH_TYPES: Record<string, DocumentAuthTypeData> = {
[DocumentAuth.ACCOUNT]: { [DocumentAuth.ACCOUNT]: {
key: DocumentAuth.ACCOUNT, key: DocumentAuth.ACCOUNT,
value: 'Require account', value: 'Require account',
isAuthRedirectRequired: true,
}, },
[DocumentAuth.PASSKEY]: { [DocumentAuth.PASSKEY]: {
key: DocumentAuth.PASSKEY, key: DocumentAuth.PASSKEY,