mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
feat: rework the name field
This commit is contained in:
@ -79,7 +79,6 @@ export const SignDirectTemplateForm = ({
|
||||
const { currentStep, totalSteps, previousStep } = useStep();
|
||||
|
||||
const onSignField = (value: TSignFieldWithTokenMutationSchema) => {
|
||||
console.log('onSignField value:', value.value);
|
||||
setLocalFields(
|
||||
localFields.map((field) => {
|
||||
if (field.id !== value.fieldId) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useTransition } from 'react';
|
||||
import { useEffect, useState, useTransition } from 'react';
|
||||
|
||||
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 { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||
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 { useRequiredDocumentAuthContext } from './document-auth-provider';
|
||||
@ -23,14 +30,16 @@ export type NameFieldProps = {
|
||||
field: FieldWithSignature;
|
||||
recipient: Recipient;
|
||||
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 { toast } = useToast();
|
||||
|
||||
const { fullName: providedFullName } = useRequiredSigningContext();
|
||||
const { fullName: providedFullName, setFullName: setProvidedFullName } =
|
||||
useRequiredSigningContext();
|
||||
|
||||
const { executeActionAuthProcedure } = useRequiredDocumentAuthContext();
|
||||
|
||||
@ -39,15 +48,47 @@ export const NameField = ({ field, recipient, onSignField }: NameFieldProps) =>
|
||||
const { mutateAsync: signFieldWithToken, isLoading: isSignFieldWithTokenLoading } =
|
||||
trpc.field.signFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||
|
||||
const { isLoading: isRemoveSignedFieldWithTokenLoading } =
|
||||
trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||
const {
|
||||
mutateAsync: removeSignedFieldWithToken,
|
||||
isLoading: isRemoveSignedFieldWithTokenLoading,
|
||||
} = trpc.field.removeSignedFieldWithToken.useMutation(DO_NOT_INVALIDATE_QUERY_ON_MUTATION);
|
||||
|
||||
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) => {
|
||||
try {
|
||||
const value = name || providedFullName;
|
||||
|
||||
if (!value) {
|
||||
setShowFullNameModal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: TSignFieldWithTokenMutationSchema = {
|
||||
token: recipient.token,
|
||||
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(() => {
|
||||
if (!field.inserted) {
|
||||
if (!field.inserted && providedFullName) {
|
||||
void executeActionAuthProcedure({
|
||||
onReauthFormSubmit: async (authOptions) => await onSign(authOptions),
|
||||
actionTarget: field.type,
|
||||
@ -91,7 +158,13 @@ export const NameField = ({ field, recipient, onSignField }: NameFieldProps) =>
|
||||
}, [field]);
|
||||
|
||||
return (
|
||||
<SigningFieldContainer field={field} type="Name">
|
||||
<SigningFieldContainer
|
||||
field={field}
|
||||
onPreSign={onPreSign}
|
||||
onSign={onSign}
|
||||
onRemove={onRemove}
|
||||
type="Name"
|
||||
>
|
||||
{isLoading && (
|
||||
<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" />
|
||||
@ -109,6 +182,51 @@ export const NameField = ({ field, recipient, onSignField }: NameFieldProps) =>
|
||||
{field.customText}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -173,18 +173,14 @@ export const SigningFieldContainer = ({
|
||||
</button>
|
||||
)}
|
||||
|
||||
{type !== 'Checkbox' &&
|
||||
!automatedFields &&
|
||||
field.inserted &&
|
||||
!loading &&
|
||||
!readOnlyField && (
|
||||
<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"
|
||||
onClick={onRemoveSignedFieldClick}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
)}
|
||||
{type !== 'Checkbox' && field.inserted && !loading && !readOnlyField && (
|
||||
<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"
|
||||
onClick={onRemoveSignedFieldClick}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
)}
|
||||
|
||||
{children}
|
||||
</FieldRootContainer>
|
||||
|
||||
Reference in New Issue
Block a user