feat: rework the name field

This commit is contained in:
Catalin Pit
2024-08-13 16:03:35 +03:00
parent d6e51ce5e6
commit 6505a40067
3 changed files with 134 additions and 21 deletions

View File

@ -79,7 +79,6 @@ export const SignDirectTemplateForm = ({
const { currentStep, totalSteps, previousStep } = useStep(); const { currentStep, totalSteps, previousStep } = useStep();
const onSignField = (value: TSignFieldWithTokenMutationSchema) => { const onSignField = (value: TSignFieldWithTokenMutationSchema) => {
console.log('onSignField value:', value.value);
setLocalFields( setLocalFields(
localFields.map((field) => { localFields.map((field) => {
if (field.id !== value.fieldId) { if (field.id !== value.fieldId) {

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useEffect, useTransition } from 'react'; import { useEffect, useState, useTransition } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@ -12,7 +12,14 @@ import type { TRecipientActionAuth } from '@documenso/lib/types/document-auth';
import { type Recipient } from '@documenso/prisma/client'; import { type Recipient } from '@documenso/prisma/client';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import type { TSignFieldWithTokenMutationSchema } from '@documenso/trpc/server/field-router/schema'; import type {
TRemovedSignedFieldWithTokenMutationSchema,
TSignFieldWithTokenMutationSchema,
} from '@documenso/trpc/server/field-router/schema';
import { Button } from '@documenso/ui/primitives/button';
import { Dialog, DialogContent, DialogFooter, DialogTitle } from '@documenso/ui/primitives/dialog';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { useRequiredDocumentAuthContext } from './document-auth-provider'; import { useRequiredDocumentAuthContext } from './document-auth-provider';
@ -23,14 +30,16 @@ export type NameFieldProps = {
field: FieldWithSignature; field: FieldWithSignature;
recipient: Recipient; recipient: Recipient;
onSignField?: (value: TSignFieldWithTokenMutationSchema) => Promise<void> | void; onSignField?: (value: TSignFieldWithTokenMutationSchema) => Promise<void> | void;
onUnsignField?: (value: TRemovedSignedFieldWithTokenMutationSchema) => Promise<void> | void;
}; };
export const NameField = ({ field, recipient, onSignField }: NameFieldProps) => { export const NameField = ({ field, recipient, onSignField, onUnsignField }: NameFieldProps) => {
const router = useRouter(); const router = useRouter();
const { toast } = useToast(); const { toast } = useToast();
const { fullName: providedFullName } = useRequiredSigningContext(); const { fullName: providedFullName, setFullName: setProvidedFullName } =
useRequiredSigningContext();
const { executeActionAuthProcedure } = useRequiredDocumentAuthContext(); const { executeActionAuthProcedure } = useRequiredDocumentAuthContext();
@ -39,15 +48,47 @@ export const NameField = ({ field, recipient, onSignField }: NameFieldProps) =>
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } = const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION); trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const { isLoading: isRemoveSignedFieldWithTokenLoading } = const {
trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION); mutateAsync: removeSignedFieldWithToken,
isLoading: isRemoveSignedFieldWithTokenLoading,
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending; const isLoading = isSignFieldWithTokenLoading || isRemoveSignedFieldWithTokenLoading || isPending;
const [showFullNameModal, setShowFullNameModal] = useState(false);
const [localFullName, setLocalFullName] = useState('');
const onPreSign = () => {
if (!providedFullName) {
setShowFullNameModal(true);
return false;
}
return true;
};
/**
* When the user clicks the sign button in the dialog where they enter their full name.
*/
const onDialogSignClick = () => {
setShowFullNameModal(false);
setProvidedFullName(localFullName);
void executeActionAuthProcedure({
onReauthFormSubmit: async (authOptions) => await onSign(authOptions, localFullName),
actionTarget: field.type,
});
};
const onSign = async (authOptions?: TRecipientActionAuth, name?: string) => { const onSign = async (authOptions?: TRecipientActionAuth, name?: string) => {
try { try {
const value = name || providedFullName; const value = name || providedFullName;
if (!value) {
setShowFullNameModal(true);
return;
}
const payload: TSignFieldWithTokenMutationSchema = { const payload: TSignFieldWithTokenMutationSchema = {
token: recipient.token, token: recipient.token,
fieldId: field.id, fieldId: field.id,
@ -81,8 +122,34 @@ export const NameField = ({ field, recipient, onSignField }: NameFieldProps) =>
} }
}; };
const onRemove = async () => {
try {
const payload: TRemovedSignedFieldWithTokenMutationSchema = {
token: recipient.token,
fieldId: field.id,
};
if (onUnsignField) {
await onUnsignField(payload);
return;
}
await removeSignedFieldWithToken(payload);
startTransition(() => router.refresh());
} catch (err) {
console.error(err);
toast({
title: 'Error',
description: 'An error occurred while removing the signature.',
variant: 'destructive',
});
}
};
useEffect(() => { useEffect(() => {
if (!field.inserted) { if (!field.inserted && providedFullName) {
void executeActionAuthProcedure({ void executeActionAuthProcedure({
onReauthFormSubmit: async (authOptions) => await onSign(authOptions), onReauthFormSubmit: async (authOptions) => await onSign(authOptions),
actionTarget: field.type, actionTarget: field.type,
@ -91,7 +158,13 @@ export const NameField = ({ field, recipient, onSignField }: NameFieldProps) =>
}, [field]); }, [field]);
return ( return (
<SigningFieldContainer field={field} type="Name"> <SigningFieldContainer
field={field}
onPreSign={onPreSign}
onSign={onSign}
onRemove={onRemove}
type="Name"
>
{isLoading && ( {isLoading && (
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md"> <div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" /> <Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
@ -109,6 +182,51 @@ export const NameField = ({ field, recipient, onSignField }: NameFieldProps) =>
{field.customText} {field.customText}
</p> </p>
)} )}
<Dialog open={showFullNameModal} onOpenChange={setShowFullNameModal}>
<DialogContent>
<DialogTitle>
Sign as {recipient.name}{' '}
<div className="text-muted-foreground">({recipient.email})</div>
</DialogTitle>
<div>
<Label htmlFor="signature">Full Name</Label>
<Input
type="text"
className="mt-2"
value={localFullName}
onChange={(e) => setLocalFullName(e.target.value.trimStart())}
/>
</div>
<DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4">
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
variant="secondary"
onClick={() => {
setShowFullNameModal(false);
setLocalFullName('');
}}
>
Cancel
</Button>
<Button
type="button"
className="flex-1"
disabled={!localFullName}
onClick={() => onDialogSignClick()}
>
Sign
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</SigningFieldContainer> </SigningFieldContainer>
); );
}; };

View File

@ -173,11 +173,7 @@ export const SigningFieldContainer = ({
</button> </button>
)} )}
{type !== 'Checkbox' && {type !== 'Checkbox' && field.inserted && !loading && !readOnlyField && (
!automatedFields &&
field.inserted &&
!loading &&
!readOnlyField && (
<button <button
className="text-destructive bg-background/50 absolute inset-0 z-10 flex h-full w-full items-center justify-center rounded-md text-sm opacity-0 duration-200 group-hover:opacity-100" className="text-destructive bg-background/50 absolute inset-0 z-10 flex h-full w-full items-center justify-center rounded-md text-sm opacity-0 duration-200 group-hover:opacity-100"
onClick={onRemoveSignedFieldClick} onClick={onRemoveSignedFieldClick}