From 6ee896048ea9e676f457005dceeaecdc3c4ef262 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Tue, 20 Feb 2024 19:11:12 +0000 Subject: [PATCH] feat: dialog to enter custom recipients for creating document from template --- .../templates/data-table-templates.tsx | 66 +---- .../templates/use-template-dialog.tsx | 242 ++++++++++++++++++ .../server-only/template/find-templates.ts | 1 + .../trpc/server/template-router/router.ts | 1 + .../trpc/server/template-router/schema.ts | 11 + .../primitives/document-flow/add-signers.tsx | 2 +- 6 files changed, 270 insertions(+), 53 deletions(-) create mode 100644 apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx diff --git a/apps/web/src/app/(dashboard)/templates/data-table-templates.tsx b/apps/web/src/app/(dashboard)/templates/data-table-templates.tsx index 309695c88..e878d8df2 100644 --- a/apps/web/src/app/(dashboard)/templates/data-table-templates.tsx +++ b/apps/web/src/app/(dashboard)/templates/data-table-templates.tsx @@ -1,30 +1,31 @@ 'use client'; -import { useState, useTransition } from 'react'; +import { useTransition } from 'react'; import Link from 'next/link'; -import { useRouter } from 'next/navigation'; -import { AlertTriangle, Loader, Plus } from 'lucide-react'; +import { AlertTriangle, Loader } from 'lucide-react'; import { useLimits } from '@documenso/ee/server-only/limits/provider/client'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; -import type { Template } from '@documenso/prisma/client'; -import { trpc } from '@documenso/trpc/react'; +import type { Recipient, Template } from '@documenso/prisma/client'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; -import { Button } from '@documenso/ui/primitives/button'; import { DataTable } from '@documenso/ui/primitives/data-table'; import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination'; -import { useToast } from '@documenso/ui/primitives/use-toast'; import { LocaleDate } from '~/components/formatter/locale-date'; import { TemplateType } from '~/components/formatter/template-type'; import { DataTableActionDropdown } from './data-table-action-dropdown'; import { DataTableTitle } from './data-table-title'; +import { UseTemplateDialog } from './use-template-dialog'; + +type TemplateWithRecipient = Template & { + Recipient: Recipient[]; +}; type TemplatesDataTableProps = { - templates: Template[]; + templates: TemplateWithRecipient[]; perPage: number; page: number; totalPages: number; @@ -47,14 +48,6 @@ export const TemplatesDataTable = ({ const { remaining } = useLimits(); - const router = useRouter(); - - const { toast } = useToast(); - const [loadingStates, setLoadingStates] = useState<{ [key: string]: boolean }>({}); - - const { mutateAsync: createDocumentFromTemplate } = - trpc.template.createDocumentFromTemplate.useMutation(); - const onPaginationChange = (page: number, perPage: number) => { startTransition(() => { updateSearchParams({ @@ -64,28 +57,6 @@ export const TemplatesDataTable = ({ }); }; - const onUseButtonClick = async (templateId: number) => { - try { - const { id } = await createDocumentFromTemplate({ - templateId, - }); - - toast({ - title: 'Document created', - description: 'Your document has been created from the template successfully.', - duration: 5000, - }); - - router.push(`${documentRootPath}/${id}`); - } catch (err) { - toast({ - title: 'Error', - description: 'An error occurred while creating document from template.', - variant: 'destructive', - }); - } - }; - return (
{remaining.documents === 0 && ( @@ -121,22 +92,13 @@ export const TemplatesDataTable = ({ header: 'Actions', accessorKey: 'actions', cell: ({ row }) => { - const isRowLoading = loadingStates[row.original.id]; - return (
- + ; + +export type UseTemplateDialogProps = { + templateId: number; + recipients: Recipient[]; + documentRootPath: string; +}; + +export function UseTemplateDialog({ + recipients, + documentRootPath, + templateId, +}: UseTemplateDialogProps) { + const router = useRouter(); + const { toast } = useToast(); + + const { + control, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: zodResolver(ZAddRecipientsForNewDocumentSchema), + defaultValues: { + recipients: + recipients.length > 0 + ? recipients.map((recipient) => ({ + nativeId: recipient.id, + formId: String(recipient.id), + name: recipient.name, + email: recipient.email, + role: recipient.role, + })) + : [ + { + name: '', + email: '', + role: RecipientRole.SIGNER, + }, + ], + }, + }); + + const { mutateAsync: createDocumentFromTemplate, isLoading: isCreatingDocumentFromTemplate } = + trpc.template.createDocumentFromTemplate.useMutation(); + + const onSubmit = async (data: TAddRecipientsForNewDocumentSchema) => { + try { + const { id } = await createDocumentFromTemplate({ + templateId, + recipients: data.recipients, + }); + + toast({ + title: 'Document created', + description: 'Your document has been created from the template successfully.', + duration: 5000, + }); + + router.push(`${documentRootPath}/${id}`); + } catch (err) { + toast({ + title: 'Error', + description: 'An error occurred while creating document from template.', + variant: 'destructive', + }); + } + }; + + const onCreateDocumentFromTemplate = handleSubmit(onSubmit); + + const { fields: formRecipients } = useFieldArray({ + control, + name: 'recipients', + }); + + return ( + + + + + + + Document Recipients + Add the recipients to create the template with. + +
+ {formRecipients.map((recipient, index) => ( +
+
+ + + ( + + )} + /> +
+ +
+ + + ( + + )} + /> +
+ +
+ ( + + )} + /> +
+ +
+ + +
+
+ ))} +
+ + + + + + + + +
+
+ ); +} diff --git a/packages/lib/server-only/template/find-templates.ts b/packages/lib/server-only/template/find-templates.ts index d453d28a0..69b43f9b9 100644 --- a/packages/lib/server-only/template/find-templates.ts +++ b/packages/lib/server-only/template/find-templates.ts @@ -38,6 +38,7 @@ export const findTemplates = async ({ include: { templateDocumentData: true, Field: true, + Recipient: true, }, skip: Math.max(page - 1, 0) * perPage, orderBy: { diff --git a/packages/trpc/server/template-router/router.ts b/packages/trpc/server/template-router/router.ts index 7417e7d00..3f5346554 100644 --- a/packages/trpc/server/template-router/router.ts +++ b/packages/trpc/server/template-router/router.ts @@ -52,6 +52,7 @@ export const templateRouter = router({ return await createDocumentFromTemplate({ templateId, userId: ctx.user.id, + recipients: input.recipients, }); } catch (err) { throw new TRPCError({ diff --git a/packages/trpc/server/template-router/schema.ts b/packages/trpc/server/template-router/schema.ts index 3d87d4b4f..561bad109 100644 --- a/packages/trpc/server/template-router/schema.ts +++ b/packages/trpc/server/template-router/schema.ts @@ -1,5 +1,7 @@ import { z } from 'zod'; +import { RecipientRole } from '@documenso/prisma/client'; + export const ZCreateTemplateMutationSchema = z.object({ title: z.string().min(1).trim(), teamId: z.number().optional(), @@ -8,6 +10,15 @@ export const ZCreateTemplateMutationSchema = z.object({ export const ZCreateDocumentFromTemplateMutationSchema = z.object({ templateId: z.number(), + recipients: z + .array( + z.object({ + email: z.string().email(), + name: z.string(), + role: z.nativeEnum(RecipientRole), + }), + ) + .optional(), }); export const ZDuplicateTemplateMutationSchema = z.object({ diff --git a/packages/ui/primitives/document-flow/add-signers.tsx b/packages/ui/primitives/document-flow/add-signers.tsx index b1341c6ca..06e0a2af2 100644 --- a/packages/ui/primitives/document-flow/add-signers.tsx +++ b/packages/ui/primitives/document-flow/add-signers.tsx @@ -32,7 +32,7 @@ import { import { ShowFieldItem } from './show-field-item'; import type { DocumentFlowStep } from './types'; -const ROLE_ICONS: Record = { +export const ROLE_ICONS: Record = { SIGNER: , APPROVER: , CC: ,