feat: add formdata endpoints for documents,envelopes,templates

Adds the missing endpoints for documents, envelopes and
templates supporting file uploads in a singular request.

Also updates frontend components that would use the prior
hidden endpoints.
This commit is contained in:
Lucas Smith
2025-11-03 15:07:15 +11:00
parent c85c0cf610
commit 87aa628dc8
16 changed files with 262 additions and 106 deletions

View File

@ -7,9 +7,9 @@ import { FilePlus, Loader } from 'lucide-react';
import { useNavigate } from 'react-router';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import type { TCreateTemplatePayloadSchema } from '@documenso/trpc/server/template-router/schema';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@ -54,13 +54,17 @@ export const TemplateCreateDialog = ({ folderId }: TemplateCreateDialogProps) =>
setIsUploadingFile(true);
try {
const response = await putPdfFile(file);
const { legacyTemplateId: id } = await createTemplate({
const payload = {
title: file.name,
templateDocumentDataId: response.id,
folderId: folderId,
});
} satisfies TCreateTemplatePayloadSchema;
const formData = new FormData();
formData.append('payload', JSON.stringify(payload));
formData.append('file', file);
const { envelopeId: id } = await createTemplate(formData);
toast({
title: _(msg`Template document uploaded`),

View File

@ -16,9 +16,9 @@ import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/l
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import type { TCreateDocumentPayloadSchema } from '@documenso/trpc/server/document-router/create-document.types';
import { cn } from '@documenso/ui/lib/utils';
import { useToast } from '@documenso/ui/primitives/use-toast';
@ -62,14 +62,18 @@ export const DocumentDropZoneWrapper = ({ children, className }: DocumentDropZon
try {
setIsLoading(true);
const response = await putPdfFile(file);
const { legacyDocumentId: id } = await createDocument({
const payload = {
title: file.name,
documentDataId: response.id,
timezone: userTimezone, // Note: When migrating to v2 document upload remember to pass this through as a 'userTimezone' field.
timezone: userTimezone,
folderId: folderId ?? undefined,
});
} satisfies TCreateDocumentPayloadSchema;
const formData = new FormData();
formData.append('payload', JSON.stringify(payload));
formData.append('file', file);
const { envelopeId: id } = await createDocument(formData);
void refreshLimits();

View File

@ -13,9 +13,9 @@ import { useSession } from '@documenso/lib/client-only/providers/session';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import type { TCreateDocumentPayloadSchema } from '@documenso/trpc/server/document-router/create-document.types';
import { cn } from '@documenso/ui/lib/utils';
import { DocumentDropzone } from '@documenso/ui/primitives/document-upload';
import {
@ -73,14 +73,18 @@ export const DocumentUploadButton = ({ className }: DocumentUploadButtonProps) =
try {
setIsLoading(true);
const response = await putPdfFile(file);
const { legacyDocumentId: id } = await createDocument({
const payload = {
title: file.name,
documentDataId: response.id,
timezone: userTimezone,
folderId: folderId ?? undefined,
});
} satisfies TCreateDocumentPayloadSchema;
const formData = new FormData();
formData.append('payload', JSON.stringify(payload));
formData.append('file', file);
const { envelopeId: id } = await createDocument(formData);
void refreshLimits();

View File

@ -14,9 +14,9 @@ import { useSession } from '@documenso/lib/client-only/providers/session';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
import { TIME_ZONES } from '@documenso/lib/constants/time-zones';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import type { TCreateEnvelopePayload } from '@documenso/trpc/server/envelope-router/create-envelope.types';
import { cn } from '@documenso/ui/lib/utils';
import { DocumentDropzone } from '@documenso/ui/primitives/document-upload';
import {
@ -78,35 +78,24 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo
try {
setIsLoading(true);
const result = await Promise.all(
files.map(async (file) => {
try {
const response = await putPdfFile(file);
return {
title: file.name,
documentDataId: response.id,
};
} catch (err) {
console.error(err);
throw new Error('Failed to upload document');
}
}),
);
const envelopeItemsToCreate = result.filter(
(item): item is { title: string; documentDataId: string } => item !== undefined,
);
const { id } = await createEnvelope({
const payload = {
folderId,
type,
title: files[0].name,
items: envelopeItemsToCreate,
meta: {
timezone: userTimezone,
},
}).catch((error) => {
} satisfies TCreateEnvelopePayload;
const formData = new FormData();
formData.append('payload', JSON.stringify(payload));
for (const file of files) {
formData.append('files', file);
}
const { id } = await createEnvelope(formData).catch((error) => {
console.error(error);
throw error;

View File

@ -10,9 +10,9 @@ import { match } from 'ts-pattern';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import type { TCreateTemplatePayloadSchema } from '@documenso/trpc/server/template-router/schema';
import { cn } from '@documenso/ui/lib/utils';
import { useToast } from '@documenso/ui/primitives/use-toast';
@ -40,13 +40,17 @@ export const TemplateDropZoneWrapper = ({ children, className }: TemplateDropZon
try {
setIsLoading(true);
const documentData = await putPdfFile(file);
const { legacyTemplateId: id } = await createTemplate({
const payload = {
title: file.name,
templateDocumentDataId: documentData.id,
folderId: folderId ?? undefined,
});
} satisfies TCreateTemplatePayloadSchema;
const formData = new FormData();
formData.append('payload', JSON.stringify(payload));
formData.append('file', file);
const { envelopeId: id } = await createTemplate(formData);
toast({
title: _(msg`Template uploaded`),

View File

@ -16,11 +16,16 @@ import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-reques
import { nanoid, prefixedId } from '@documenso/lib/universal/id';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import type { TCreateEnvelopeRequest } from '@documenso/trpc/server/envelope-router/create-envelope.types';
import type { TDocumentAccessAuthTypes, TDocumentActionAuthTypes } from '../../types/document-auth';
import type {
TDocumentAccessAuthTypes,
TDocumentActionAuthTypes,
TRecipientAccessAuthTypes,
TRecipientActionAuthTypes,
} from '../../types/document-auth';
import type { TDocumentFormValues } from '../../types/document-form-values';
import type { TEnvelopeAttachmentType } from '../../types/envelope-attachment';
import type { TFieldAndMeta } from '../../types/field-meta';
import {
ZWebhookDocumentSchema,
mapEnvelopeToWebhookDocumentPayload,
@ -34,6 +39,25 @@ import { incrementDocumentId, incrementTemplateId } from '../envelope/increment-
import { getTeamSettings } from '../team/get-team-settings';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
type CreateEnvelopeRecipientFieldOptions = TFieldAndMeta & {
documentDataId: string;
page: number;
positionX: number;
positionY: number;
width: number;
height: number;
};
type CreateEnvelopeRecipientOptions = {
email: string;
name: string;
role: RecipientRole;
signingOrder?: number;
accessAuth?: TRecipientAccessAuthTypes[];
actionAuth?: TRecipientActionAuthTypes[];
fields?: CreateEnvelopeRecipientFieldOptions[];
};
export type CreateEnvelopeOptions = {
userId: number;
teamId: number;
@ -56,7 +80,7 @@ export type CreateEnvelopeOptions = {
visibility?: DocumentVisibility;
globalAccessAuth?: TDocumentAccessAuthTypes[];
globalActionAuth?: TDocumentActionAuthTypes[];
recipients?: TCreateEnvelopeRequest['recipients'];
recipients?: CreateEnvelopeRecipientOptions[];
folderId?: string;
};
attachments?: Array<{

View File

@ -1,13 +1,22 @@
import { PDFDocument } from '@cantoo/pdf-lib';
import { AppError } from '../../errors/app-error';
import { flattenAnnotations } from './flatten-annotations';
import { flattenForm, removeOptionalContentGroups } from './flatten-form';
export const normalizePdf = async (pdf: Buffer) => {
const pdfDoc = await PDFDocument.load(pdf).catch(() => null);
const pdfDoc = await PDFDocument.load(pdf).catch((e) => {
console.error(`PDF normalization error: ${e.message}`);
if (!pdfDoc) {
return pdf;
throw new AppError('INVALID_DOCUMENT_FILE', {
message: 'The document is not a valid PDF',
});
});
if (pdfDoc.isEncrypted) {
throw new AppError('INVALID_DOCUMENT_FILE', {
message: 'The document is encrypted',
});
}
removeOptionalContentGroups(pdfDoc);

View File

@ -7,6 +7,7 @@ import { env } from '@documenso/lib/utils/env';
import { AppError } from '../../errors/app-error';
import { createDocumentData } from '../../server-only/document-data/create-document-data';
import { normalizePdf } from '../../server-only/pdf/normalize-pdf';
import { uploadS3File } from './server-actions';
type File = {
@ -43,6 +44,28 @@ export const putPdfFileServerSide = async (file: File) => {
return await createDocumentData({ type, data });
};
/**
* Uploads a pdf file and normalizes it.
*/
export const putNormalizedPdfFileServerSide = async (file: File) => {
const buffer = Buffer.from(await file.arrayBuffer());
const normalized = await normalizePdf(buffer);
const fileName = file.name.endsWith('.pdf') ? file.name : `${file.name}.pdf`;
const documentData = await putFileServerSide({
name: fileName,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(normalized),
});
return await createDocumentData({
type: documentData.type,
data: documentData.data,
});
};
/**
* Uploads a file to the appropriate storage location.
*/

View File

@ -134,8 +134,8 @@ model Passkey {
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
lastUsedAt DateTime?
credentialId Bytes
credentialPublicKey Bytes
credentialId Bytes /// @zod.custom.use(z.instanceof(Uint8Array))
credentialPublicKey Bytes /// @zod.custom.use(z.instanceof(Uint8Array))
counter BigInt
credentialDeviceType String
credentialBackedUp Boolean

View File

@ -3,6 +3,7 @@ import { EnvelopeType } from '@prisma/client';
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { authenticatedProcedure } from '../trpc';
@ -16,7 +17,12 @@ export const createDocumentRoute = authenticatedProcedure
.output(ZCreateDocumentResponseSchema)
.mutation(async ({ input, ctx }) => {
const { user, teamId } = ctx;
const { title, documentDataId, timezone, folderId, attachments } = input;
const { payload, file } = input;
const { title, timezone, folderId, attachments } = payload;
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file);
ctx.logger.info({
input: {
@ -55,6 +61,7 @@ export const createDocumentRoute = authenticatedProcedure
});
return {
envelopeId: document.id,
legacyDocumentId: mapSecondaryIdToDocumentId(document.secondaryId),
};
});

View File

@ -1,23 +1,27 @@
import { z } from 'zod';
import { zfd } from 'zod-form-data';
import { ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
import { zodFormData } from '../../utils/zod-form-data';
import type { TrpcRouteMeta } from '../trpc';
import { ZDocumentTitleSchema } from './schema';
// Currently not in use until we allow passthrough documents on create.
// export const createDocumentMeta: TrpcRouteMeta = {
// openapi: {
// method: 'POST',
// path: '/document/create',
// summary: 'Create document',
// tags: ['Document'],
// },
// };
export const createDocumentMeta: TrpcRouteMeta = {
openapi: {
method: 'POST',
path: '/document/create',
contentTypes: ['multipart/form-data'],
summary: 'Create document',
description: 'Create a document using form data.',
tags: ['Document'],
},
};
export const ZCreateDocumentRequestSchema = z.object({
export const ZCreateDocumentPayloadSchema = z.object({
title: ZDocumentTitleSchema,
documentDataId: z.string().min(1),
timezone: ZDocumentMetaTimezoneSchema.optional(),
folderId: z.string().describe('The ID of the folder to create the document in').optional(),
attachments: z
@ -31,9 +35,16 @@ export const ZCreateDocumentRequestSchema = z.object({
.optional(),
});
export const ZCreateDocumentRequestSchema = zodFormData({
payload: zfd.json(ZCreateDocumentPayloadSchema),
file: zfd.file(),
});
export const ZCreateDocumentResponseSchema = z.object({
envelopeId: z.string(),
legacyDocumentId: z.number(),
});
export type TCreateDocumentPayloadSchema = z.infer<typeof ZCreateDocumentPayloadSchema>;
export type TCreateDocumentRequest = z.infer<typeof ZCreateDocumentRequestSchema>;
export type TCreateDocumentResponse = z.infer<typeof ZCreateDocumentResponseSchema>;

View File

@ -1,6 +1,7 @@
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
import { authenticatedProcedure } from '../trpc';
import {
@ -13,6 +14,9 @@ export const createEnvelopeRoute = authenticatedProcedure
.output(ZCreateEnvelopeResponseSchema)
.mutation(async ({ input, ctx }) => {
const { user, teamId } = ctx;
const { payload, files } = input;
const {
title,
type,
@ -22,10 +26,9 @@ export const createEnvelopeRoute = authenticatedProcedure
globalActionAuth,
recipients,
folderId,
items,
meta,
attachments,
} = input;
} = payload;
ctx.logger.info({
input: {
@ -45,13 +48,62 @@ export const createEnvelopeRoute = authenticatedProcedure
});
}
if (items.length > maximumEnvelopeItemCount) {
if (files.length > maximumEnvelopeItemCount) {
throw new AppError('ENVELOPE_ITEM_LIMIT_EXCEEDED', {
message: `You cannot upload more than ${maximumEnvelopeItemCount} envelope items per envelope`,
statusCode: 400,
});
}
// For each file, stream to s3 and create the document data.
const envelopeItems = await Promise.all(
files.map(async (file) => {
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file);
return {
title: file.name,
documentDataId,
};
}),
);
const recipientsToCreate = recipients?.map((recipient) => ({
email: recipient.email,
name: recipient.name,
role: recipient.role,
signingOrder: recipient.signingOrder,
accessAuth: recipient.accessAuth,
actionAuth: recipient.actionAuth,
fields: recipient.fields?.map((field) => {
let documentDataId: string | undefined = undefined;
if (typeof field.identifier === 'string') {
documentDataId = envelopeItems.find(
(item) => item.title === field.identifier,
)?.documentDataId;
}
if (typeof field.identifier === 'number') {
documentDataId = envelopeItems.at(field.identifier)?.documentDataId;
}
if (field.identifier === undefined) {
documentDataId = envelopeItems[0]?.documentDataId;
}
if (!documentDataId) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Document data not found',
});
}
return {
...field,
documentDataId,
};
}),
}));
const envelope = await createEnvelope({
userId: user.id,
teamId,
@ -63,9 +115,9 @@ export const createEnvelopeRoute = authenticatedProcedure
visibility,
globalAccessAuth,
globalActionAuth,
recipients,
recipients: recipientsToCreate,
folderId,
envelopeItems: items,
envelopeItems,
},
attachments,
meta,

View File

@ -1,5 +1,6 @@
import { EnvelopeType } from '@prisma/client';
import { z } from 'zod';
import { zfd } from 'zod-form-data';
import {
ZDocumentAccessAuthTypesSchema,
@ -17,24 +18,28 @@ import {
} from '@documenso/lib/types/field';
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
import { zodFormData } from '../../utils/zod-form-data';
import {
ZDocumentExternalIdSchema,
ZDocumentTitleSchema,
ZDocumentVisibilitySchema,
} from '../document-router/schema';
import { ZCreateRecipientSchema } from '../recipient-router/schema';
import type { TrpcRouteMeta } from '../trpc';
// Currently not in use until we allow passthrough documents on create.
// export const createEnvelopeMeta: TrpcRouteMeta = {
// openapi: {
// method: 'POST',
// path: '/envelope/create',
// summary: 'Create envelope',
// tags: ['Envelope'],
// },
// };
export const createEnvelopeMeta: TrpcRouteMeta = {
openapi: {
method: 'POST',
path: '/envelope/create',
contentTypes: ['multipart/form-data'],
summary: 'Create envelope',
description: 'Create a envelope using form data.',
tags: ['Envelope'],
},
};
export const ZCreateEnvelopeRequestSchema = z.object({
export const ZCreateEnvelopePayloadSchema = z.object({
title: ZDocumentTitleSchema,
type: z.nativeEnum(EnvelopeType),
externalId: ZDocumentExternalIdSchema.optional(),
@ -42,12 +47,6 @@ export const ZCreateEnvelopeRequestSchema = z.object({
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
formValues: ZDocumentFormValuesSchema.optional(),
items: z
.object({
title: ZDocumentTitleSchema.optional(),
documentDataId: z.string(),
})
.array(),
folderId: z
.string()
.describe(
@ -59,11 +58,12 @@ export const ZCreateEnvelopeRequestSchema = z.object({
ZCreateRecipientSchema.extend({
fields: ZFieldAndMetaSchema.and(
z.object({
documentDataId: z
.string()
identifier: z
.union([z.string(), z.number()])
.describe(
'The ID of the document data to create the field on. If empty, the first document data will be used.',
),
'Either the filename or the index of the file that was uploaded to attach the field to.',
)
.optional(),
page: ZFieldPageNumberSchema,
positionX: ZFieldPageXSchema,
positionY: ZFieldPageYSchema,
@ -88,9 +88,15 @@ export const ZCreateEnvelopeRequestSchema = z.object({
.optional(),
});
export const ZCreateEnvelopeRequestSchema = zodFormData({
payload: zfd.json(ZCreateEnvelopePayloadSchema),
files: zfd.repeatableOfType(zfd.file()),
});
export const ZCreateEnvelopeResponseSchema = z.object({
id: z.string(),
});
export type TCreateEnvelopePayload = z.infer<typeof ZCreateEnvelopePayloadSchema>;
export type TCreateEnvelopeRequest = z.infer<typeof ZCreateEnvelopeRequestSchema>;
export type TCreateEnvelopeResponse = z.infer<typeof ZCreateEnvelopeResponseSchema>;

View File

@ -21,6 +21,7 @@ import { deleteTemplateDirectLink } from '@documenso/lib/server-only/template/de
import { findTemplates } from '@documenso/lib/server-only/template/find-templates';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { toggleTemplateDirectLink } from '@documenso/lib/server-only/template/toggle-template-direct-link';
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
import { mapFieldToLegacyField } from '@documenso/lib/utils/fields';
@ -159,20 +160,27 @@ export const templateRouter = router({
* @private
*/
createTemplate: authenticatedProcedure
// .meta({ // Note before releasing this to public, update the response schema to be correct.
// openapi: {
// method: 'POST',
// path: '/template/create',
// summary: 'Create template',
// description: 'Create a new template',
// tags: ['Template'],
// },
// })
.meta({
// Note before releasing this to public, update the response schema to be correct.
openapi: {
method: 'POST',
path: '/template/create',
contentTypes: ['multipart/form-data'],
summary: 'Create template',
description: 'Create a new template',
tags: ['Template'],
},
})
.input(ZCreateTemplateMutationSchema)
.output(ZCreateTemplateResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = ctx;
const { title, templateDocumentDataId, folderId } = input;
const { payload, file } = input;
const { title, folderId } = payload;
const { id: templateDocumentDataId } = await putNormalizedPdfFileServerSide(file);
ctx.logger.info({
input: {
@ -198,6 +206,7 @@ export const templateRouter = router({
});
return {
envelopeId: envelope.id,
legacyTemplateId: mapSecondaryIdToTemplateId(envelope.secondaryId),
};
}),

View File

@ -1,5 +1,6 @@
import { DocumentSigningOrder, DocumentVisibility, TemplateType } from '@prisma/client';
import { z } from 'zod';
import { zfd } from 'zod-form-data';
import { ZDocumentSchema } from '@documenso/lib/types/document';
import {
@ -29,6 +30,7 @@ import {
} from '@documenso/lib/types/template';
import { LegacyTemplateDirectLinkSchema } from '@documenso/prisma/types/template-legacy-schema';
import { zodFormData } from '../../utils/zod-form-data';
import { ZSignFieldWithTokenMutationSchema } from '../field-router/schema';
export const MAX_TEMPLATE_PUBLIC_TITLE_LENGTH = 50;
@ -77,12 +79,16 @@ export const ZTemplateMetaUpsertSchema = z.object({
allowDictateNextSigner: z.boolean().optional(),
});
export const ZCreateTemplateMutationSchema = z.object({
export const ZCreateTemplatePayloadSchema = z.object({
title: z.string().min(1).trim(),
templateDocumentDataId: z.string().min(1),
folderId: z.string().optional(),
});
export const ZCreateTemplateMutationSchema = zodFormData({
payload: zfd.json(ZCreateTemplatePayloadSchema),
file: zfd.file(),
});
export const ZCreateDocumentFromDirectTemplateRequestSchema = z.object({
directRecipientName: z.string().max(255).optional(),
directRecipientEmail: z.string().email().max(254),
@ -218,6 +224,7 @@ export const ZCreateTemplateV2ResponseSchema = z.object({
});
export const ZCreateTemplateResponseSchema = z.object({
envelopeId: z.string(),
legacyTemplateId: z.number(),
});
@ -267,6 +274,7 @@ export const ZBulkSendTemplateMutationSchema = z.object({
sendImmediately: z.boolean(),
});
export type TCreateTemplatePayloadSchema = z.infer<typeof ZCreateTemplatePayloadSchema>;
export type TCreateTemplateMutationSchema = z.infer<typeof ZCreateTemplateMutationSchema>;
export type TDuplicateTemplateMutationSchema = z.infer<typeof ZDuplicateTemplateMutationSchema>;
export type TDeleteTemplateMutationSchema = z.infer<typeof ZDeleteTemplateMutationSchema>;

View File

@ -2,14 +2,16 @@ import type { DataTransformer } from '@trpc/server';
import SuperJSON from 'superjson';
export const dataTransformer: DataTransformer = {
serialize: (data: unknown) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
serialize: (data: any) => {
if (data instanceof FormData) {
return data;
}
return SuperJSON.serialize(data);
},
deserialize: (data: unknown) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
deserialize: (data: any) => {
return SuperJSON.deserialize(data);
},
};