feat: template recipients on blur

This commit is contained in:
Catalin Pit
2024-08-23 16:00:01 +03:00
parent da9287440b
commit b885aae511
5 changed files with 121 additions and 7 deletions

View File

@ -261,6 +261,7 @@ export const EditTemplateForm = ({
onSubmit={onAddTemplatePlaceholderFormSubmit} onSubmit={onAddTemplatePlaceholderFormSubmit}
isEnterprise={isEnterprise} isEnterprise={isEnterprise}
isDocumentPdfLoaded={isDocumentPdfLoaded} isDocumentPdfLoaded={isDocumentPdfLoaded}
template={template}
/> />
<AddTemplateFieldsFormPartial <AddTemplateFieldsFormPartial

View File

@ -12,6 +12,7 @@ import {
ZAddTemplateSignersMutationSchema, ZAddTemplateSignersMutationSchema,
ZCompleteDocumentWithTokenMutationSchema, ZCompleteDocumentWithTokenMutationSchema,
ZRemoveSignerMutationSchema, ZRemoveSignerMutationSchema,
ZRemoveTemplateSignerMutationSchema,
} from './schema'; } from './schema';
export const recipientRouter = router({ export const recipientRouter = router({
@ -72,6 +73,12 @@ export const recipientRouter = router({
} }
}), }),
removeTemplateSigner: authenticatedProcedure
.input(ZRemoveTemplateSignerMutationSchema)
.mutation(async ({ input, ctx }) => {
// TODO: Implement
}),
removeSigner: authenticatedProcedure removeSigner: authenticatedProcedure
.input(ZRemoveSignerMutationSchema) .input(ZRemoveSignerMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {

View File

@ -75,3 +75,13 @@ export const ZCompleteDocumentWithTokenMutationSchema = z.object({
export type TCompleteDocumentWithTokenMutationSchema = z.infer< export type TCompleteDocumentWithTokenMutationSchema = z.infer<
typeof ZCompleteDocumentWithTokenMutationSchema typeof ZCompleteDocumentWithTokenMutationSchema
>; >;
export const ZRemoveTemplateSignerMutationSchema = z.object({
templateId: z.number(),
teamId: z.number().optional(),
recipientId: z.number(),
});
export type TRemoveTemplateSignerMutationSchema = z.infer<
typeof ZRemoveTemplateSignerMutationSchema
>;

View File

@ -250,7 +250,7 @@ export const AddSignersFormPartial = ({
console.error(e); console.error(e);
toast({ toast({
title: 'Error', title: 'Error',
description: 'An error occurred while updating the document settings.', description: 'An error occurred while updating the document recipient.',
variant: 'destructive', variant: 'destructive',
}); });
} }

View File

@ -2,17 +2,22 @@
import React, { useEffect, useId, useMemo, useState } from 'react'; import React, { useEffect, useId, useMemo, useState } from 'react';
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Link2Icon, Plus, Trash } from 'lucide-react'; import { Link2Icon, Plus, Trash } from 'lucide-react';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import { useFieldArray, useForm } from 'react-hook-form'; import { useFieldArray, useForm } from 'react-hook-form';
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth'; import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
import { nanoid } from '@documenso/lib/universal/id'; import { nanoid } from '@documenso/lib/universal/id';
import { generateRecipientPlaceholder } from '@documenso/lib/utils/templates'; import { generateRecipientPlaceholder } from '@documenso/lib/utils/templates';
import type { TemplateDirectLink } from '@documenso/prisma/client'; import type { TemplateDirectLink } from '@documenso/prisma/client';
import { type Field, type Recipient, RecipientRole } from '@documenso/prisma/client'; import { type Field, type Recipient, RecipientRole } from '@documenso/prisma/client';
import type { TemplateWithDetails } from '@documenso/prisma/types/template';
import { trpc } from '@documenso/trpc/react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out'; import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import { RecipientActionAuthSelect } from '@documenso/ui/components/recipient/recipient-action-auth-select'; import { RecipientActionAuthSelect } from '@documenso/ui/components/recipient/recipient-action-auth-select';
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select'; import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
@ -20,6 +25,7 @@ import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
import { Input } from '@documenso/ui/primitives/input'; import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Checkbox } from '../checkbox'; import { Checkbox } from '../checkbox';
import { import {
@ -44,6 +50,7 @@ export type AddTemplatePlaceholderRecipientsFormProps = {
templateDirectLink: TemplateDirectLink | null; templateDirectLink: TemplateDirectLink | null;
isEnterprise: boolean; isEnterprise: boolean;
isDocumentPdfLoaded: boolean; isDocumentPdfLoaded: boolean;
template: TemplateWithDetails;
onSubmit: (_data: TAddTemplatePlacholderRecipientsFormSchema) => void; onSubmit: (_data: TAddTemplatePlacholderRecipientsFormSchema) => void;
}; };
@ -55,7 +62,10 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
fields, fields,
isDocumentPdfLoaded, isDocumentPdfLoaded,
onSubmit, onSubmit,
template,
}: AddTemplatePlaceholderRecipientsFormProps) => { }: AddTemplatePlaceholderRecipientsFormProps) => {
const { toast } = useToast();
const router = useRouter();
const initialId = useId(); const initialId = useId();
const { data: session } = useSession(); const { data: session } = useSession();
@ -67,6 +77,39 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
const { currentStep, totalSteps, previousStep } = useStep(); const { currentStep, totalSteps, previousStep } = useStep();
const utils = trpc.useUtils();
const { mutateAsync: addTemplateSigners } = trpc.recipient.addTemplateSigners.useMutation({
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
onSuccess: (newData) => {
utils.template.getTemplateWithDetailsById.setData(
{
id: template.id,
},
(oldData) => ({ ...(oldData || template), ...newData }),
);
},
});
const { mutateAsync: removeTemplateSigner } = trpc.recipient.removeTemplateSigner.useMutation({
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
onSuccess: (deletedRecipient) => {
console.log('deletedRecipient', deletedRecipient);
utils.template.getTemplateWithDetailsById.setData(
{
id: template.id,
},
(oldData) => {
if (!oldData) return template;
return {
...oldData,
recipients: oldData.recipients.filter((r) => r.id !== deletedRecipient.id),
};
},
);
},
});
const generateDefaultFormSigners = () => { const generateDefaultFormSigners = () => {
if (recipients.length === 0) { if (recipients.length === 0) {
return [ return [
@ -154,10 +197,6 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
setPlaceholderRecipientCount((count) => count + 1); setPlaceholderRecipientCount((count) => count + 1);
}; };
const onRemoveSigner = (index: number) => {
removeSigner(index);
};
const isSignerDirectRecipient = ( const isSignerDirectRecipient = (
signer: TAddTemplatePlacholderRecipientsFormSchema['signers'][number], signer: TAddTemplatePlacholderRecipientsFormSchema['signers'][number],
): boolean => { ): boolean => {
@ -167,6 +206,58 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
); );
}; };
const handleOnBlur = async (index: number) => {
try {
const currentSigner = form.getValues(`signers.${index}`);
if (!currentSigner.email) {
return;
}
console.log('Updating signer', currentSigner);
await addTemplateSigners({
templateId: template.id,
teamId: template.teamId ?? undefined,
signers: form.getValues('signers'),
});
router.refresh();
} catch (e) {
console.error(e);
toast({
title: 'Error',
description: 'An error occurred while updating the template recipient.',
variant: 'destructive',
});
}
};
const handleRemoveSigner = async (index: number) => {
const signer = signers[index];
console.log('signer', signer);
if (!signer) {
return;
}
removeSigner(index);
if (signer.nativeId) {
await removeTemplateSigner({
templateId: template.id,
teamId: template.teamId ?? undefined,
recipientId: signer.nativeId,
});
toast({
title: 'Signer removed',
description: 'The signer has been removed from the document.',
});
}
};
return ( return (
<> <>
<DocumentFlowFormContainerHeader <DocumentFlowFormContainerHeader
@ -216,6 +307,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
signers[index].email === user?.email || signers[index].email === user?.email ||
isSignerDirectRecipient(signer) isSignerDirectRecipient(signer)
} }
onBlur={() => void handleOnBlur(index)}
/> />
</FormControl> </FormControl>
@ -246,6 +338,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
signers[index].email === user?.email || signers[index].email === user?.email ||
isSignerDirectRecipient(signer) isSignerDirectRecipient(signer)
} }
onBlur={() => void handleOnBlur(index)}
/> />
</FormControl> </FormControl>
@ -281,7 +374,10 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
<FormControl> <FormControl>
<RecipientRoleSelect <RecipientRoleSelect
{...field} {...field}
onValueChange={field.onChange} onValueChange={(value) => {
field.onChange(value);
void handleOnBlur(index);
}}
disabled={isSubmitting} disabled={isSubmitting}
hideCCRecipients={isSignerDirectRecipient(signer)} hideCCRecipients={isSignerDirectRecipient(signer)}
/> />
@ -313,7 +409,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
type="button" type="button"
className="col-span-1 mt-auto inline-flex h-10 w-10 items-center justify-center text-slate-500 hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50" className="col-span-1 mt-auto inline-flex h-10 w-10 items-center justify-center text-slate-500 hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
disabled={isSubmitting || signers.length === 1} disabled={isSubmitting || signers.length === 1}
onClick={() => onRemoveSigner(index)} onClick={() => void handleRemoveSigner(index)}
> >
<Trash className="h-5 w-5" /> <Trash className="h-5 w-5" />
</button> </button>