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.
This commit is contained in:
Catalin Documenso
2025-05-06 15:48:52 +03:00
parent d10ec437cf
commit cec25ac719
9 changed files with 94 additions and 10 deletions

View File

@ -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,

View File

@ -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,

View File

@ -34,6 +34,7 @@ export const getTemplateById = async ({ id, userId, teamId }: GetTemplateByIdOpt
templateMeta: true,
recipients: true,
fields: true,
attachments: true,
user: {
select: {
id: true,

View File

@ -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<Attachment, 'id' | 'label' | 'url'>[];
};
meta?: Partial<Omit<TemplateMeta, 'id' | 'templateId'>>;
};
@ -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: {

View File

@ -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<typeof ZTemplateSchema>;

View File

@ -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

View File

@ -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

View File

@ -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 = ({
<div className="flex-1">
<FormField
control={form.control}
name={`attachments.${index}.link`}
name={`attachments.${index}.url`}
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row items-center">
@ -641,7 +654,7 @@ export const AddTemplateSettingsFormPartial = ({
<div className="flex-none pt-8">
<button
onClick={() => removeAttachment(index)}
onClick={() => onRemoveAttachment(index)}
className="hover:bg-muted rounded-md"
>
<Trash className="h-4 w-4" />

View File

@ -13,6 +13,7 @@ import {
} from '@documenso/lib/types/document-auth';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import { AttachmentSchema } from '@documenso/prisma/generated/zod/modelSchema/AttachmentSchema';
import {
ZDocumentMetaDateFormatSchema,
ZDocumentMetaTimezoneSchema,
@ -55,13 +56,16 @@ export const ZAddTemplateSettingsFormSchema = z.object({
message: msg`At least one signature type must be enabled`.id,
}),
}),
attachments: z.array(
z.object({
attachments: AttachmentSchema.pick({
id: true,
label: true,
url: true,
})
.extend({
formId: z.string().min(1),
label: z.string(),
link: z.string(),
}),
),
})
.array()
.optional(),
});
export type TAddTemplateSettingsFormSchema = z.infer<typeof ZAddTemplateSettingsFormSchema>;