From cec25ac71958f70b96aba0c6bab5440e205d70b6 Mon Sep 17 00:00:00 2001 From: Catalin Documenso Date: Tue, 6 May 2025 15:48:52 +0300 Subject: [PATCH] feat: add support for attachments in template management - Enhanced TemplateEditForm to include attachments in the template data. - Updated createDocumentFromTemplate to handle attachment creation. - Modified updateTemplate to manage attachment updates and deletions. - Integrated attachments into ZTemplateSchema and ZAddTemplateSettingsFormSchema for validation. - Improved getTemplateById to fetch attachments alongside other template data. --- .../general/template/template-edit-form.tsx | 9 +++++++ .../template/create-document-from-template.ts | 10 +++++++ .../template/get-template-by-id.ts | 1 + .../server-only/template/update-template.ts | 27 ++++++++++++++++++- packages/lib/types/template.ts | 9 +++++++ packages/prisma/schema.prisma | 2 ++ .../trpc/server/template-router/schema.ts | 11 ++++++++ .../template-flow/add-template-settings.tsx | 19 ++++++++++--- .../add-template-settings.types.tsx | 16 ++++++----- 9 files changed, 94 insertions(+), 10 deletions(-) diff --git a/apps/remix/app/components/general/template/template-edit-form.tsx b/apps/remix/app/components/general/template/template-edit-form.tsx index e7dcc86d8..89c0f2134 100644 --- a/apps/remix/app/components/general/template/template-edit-form.tsx +++ b/apps/remix/app/components/general/template/template-edit-form.tsx @@ -136,6 +136,7 @@ export const TemplateEditForm = ({ visibility: data.visibility, globalAccessAuth: data.globalAccessAuth ?? null, globalActionAuth: data.globalActionAuth ?? null, + attachments: data.attachments ?? [], }, meta: { ...data.meta, @@ -165,6 +166,14 @@ export const TemplateEditForm = ({ await Promise.all([ updateTemplateSettings({ templateId: template.id, + data: { + attachments: template.attachments?.map((attachment) => ({ + id: attachment.id, + label: attachment.label, + url: attachment.url, + formId: attachment.id, + })), + }, meta: { signingOrder: data.signingOrder, allowDictateNextSigner: data.allowDictateNextSigner, diff --git a/packages/lib/server-only/template/create-document-from-template.ts b/packages/lib/server-only/template/create-document-from-template.ts index b17a36835..ffb0b7f8f 100644 --- a/packages/lib/server-only/template/create-document-from-template.ts +++ b/packages/lib/server-only/template/create-document-from-template.ts @@ -296,6 +296,7 @@ export const createDocumentFromTemplate = async ({ fields: true, }, }, + attachments: true, templateDocumentData: true, templateMeta: true, team: { @@ -385,6 +386,15 @@ export const createDocumentFromTemplate = async ({ }), visibility: template.visibility || template.team?.teamGlobalSettings?.documentVisibility, useLegacyFieldInsertion: template.useLegacyFieldInsertion ?? false, + attachments: { + create: template.attachments.map((attachment) => ({ + type: attachment.type, + label: attachment.label, + url: attachment.url, + createdAt: attachment.createdAt, + updatedAt: attachment.updatedAt, + })), + }, documentMeta: { create: { subject: override?.subject || template.templateMeta?.subject, diff --git a/packages/lib/server-only/template/get-template-by-id.ts b/packages/lib/server-only/template/get-template-by-id.ts index e978d75bb..dd22f7031 100644 --- a/packages/lib/server-only/template/get-template-by-id.ts +++ b/packages/lib/server-only/template/get-template-by-id.ts @@ -34,6 +34,7 @@ export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOpt templateMeta: true, recipients: true, fields: true, + attachments: true, user: { select: { id: true, diff --git a/packages/lib/server-only/template/update-template.ts b/packages/lib/server-only/template/update-template.ts index dee71d678..e34e14232 100644 --- a/packages/lib/server-only/template/update-template.ts +++ b/packages/lib/server-only/template/update-template.ts @@ -1,4 +1,4 @@ -import type { DocumentVisibility, Template, TemplateMeta } from '@prisma/client'; +import type { Attachment, DocumentVisibility, Template, TemplateMeta } from '@prisma/client'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { prisma } from '@documenso/prisma'; @@ -21,6 +21,7 @@ export type UpdateTemplateOptions = { publicDescription?: string; type?: Template['type']; useLegacyFieldInsertion?: boolean; + attachments?: Pick[]; }; meta?: Partial>; }; @@ -53,6 +54,7 @@ export const updateTemplate = async ({ }, include: { templateMeta: true, + attachments: true, }, }); @@ -104,6 +106,29 @@ export const updateTemplate = async ({ publicDescription: data?.publicDescription, publicTitle: data?.publicTitle, useLegacyFieldInsertion: data?.useLegacyFieldInsertion, + attachments: { + deleteMany: { + templateId, + id: { + notIn: data?.attachments?.map((attachment) => attachment.id), + }, + }, + upsert: data?.attachments?.map((attachment) => ({ + where: { + id: attachment.id, + templateId, + }, + update: { + label: attachment.label, + url: attachment.url, + }, + create: { + id: attachment.id, + label: attachment.label, + url: attachment.url, + }, + })), + }, authOptions, templateMeta: { upsert: { diff --git a/packages/lib/types/template.ts b/packages/lib/types/template.ts index 2f36336c4..f7beec751 100644 --- a/packages/lib/types/template.ts +++ b/packages/lib/types/template.ts @@ -1,5 +1,6 @@ import type { z } from 'zod'; +import { AttachmentSchema } from '@documenso/prisma/generated/zod/modelSchema/AttachmentSchema'; import { DocumentDataSchema } from '@documenso/prisma/generated/zod/modelSchema/DocumentDataSchema'; import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema'; import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod/modelSchema/TemplateDirectLinkSchema'; @@ -62,6 +63,14 @@ export const ZTemplateSchema = TemplateSchema.pick({ }), recipients: ZRecipientLiteSchema.array(), fields: ZFieldSchema.array(), + attachments: AttachmentSchema.pick({ + id: true, + label: true, + url: true, + type: true, + }) + .array() + .optional(), }); export type TTemplate = z.infer; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index aa3c2fe85..592c29a8c 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -312,6 +312,8 @@ enum DocumentVisibility { ADMIN } +// Only "LINK" is supported for now. +// All other attachment types are not yet supported. enum AttachmentType { FILE VIDEO diff --git a/packages/trpc/server/template-router/schema.ts b/packages/trpc/server/template-router/schema.ts index 2d8481395..3217dec56 100644 --- a/packages/trpc/server/template-router/schema.ts +++ b/packages/trpc/server/template-router/schema.ts @@ -14,6 +14,7 @@ import { ZTemplateManySchema, ZTemplateSchema, } from '@documenso/lib/types/template'; +import AttachmentSchema from '@documenso/prisma/generated/zod/modelSchema/AttachmentSchema'; import { TemplateDirectLinkSchema } from '@documenso/prisma/generated/zod/modelSchema/TemplateDirectLinkSchema'; import { @@ -154,6 +155,16 @@ export const ZUpdateTemplateRequestSchema = z.object({ .optional(), type: z.nativeEnum(TemplateType).optional(), useLegacyFieldInsertion: z.boolean().optional(), + attachments: AttachmentSchema.pick({ + id: true, + label: true, + url: true, + }) + .extend({ + formId: z.string().min(1), + }) + .array() + .optional(), }) .optional(), meta: z diff --git a/packages/ui/primitives/template-flow/add-template-settings.tsx b/packages/ui/primitives/template-flow/add-template-settings.tsx index e9466f88a..f899547b7 100644 --- a/packages/ui/primitives/template-flow/add-template-settings.tsx +++ b/packages/ui/primitives/template-flow/add-template-settings.tsx @@ -124,6 +124,11 @@ export const AddTemplateSettingsFormPartial = ({ emailSettings: ZDocumentEmailSettingsSchema.parse(template?.templateMeta?.emailSettings), signatureTypes: extractTeamSignatureSettings(template?.templateMeta), }, + attachments: template.attachments?.map((attachment) => ({ + ...attachment, + id: String(attachment.id), + formId: String(attachment.id), + })), }, }); @@ -138,12 +143,20 @@ export const AddTemplateSettingsFormPartial = ({ const onAddAttachment = () => { appendAttachment({ + id: nanoid(12), formId: nanoid(12), label: '', - link: '', + url: '', }); }; + const onRemoveAttachment = (index: number) => { + removeAttachment(index); + + const updatedAttachments = attachments.filter((_, idx) => idx !== index); + form.setValue('attachments', updatedAttachments); + }; + const { stepIndex, currentStep, totalSteps, previousStep } = useStep(); const distributionMethod = form.watch('meta.distributionMethod'); @@ -622,7 +635,7 @@ export const AddTemplateSettingsFormPartial = ({
( @@ -641,7 +654,7 @@ export const AddTemplateSettingsFormPartial = ({