mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
## Description Support setting a document language that will control the language used for sending emails to recipients. Additional work has been done to convert all emails to using our i18n implementation so we can later add controls for sending other kinds of emails in a users target language. ## Related Issue N/A ## Changes Made - Added `<Trans>` and `msg` macros to emails - Introduced a new `renderEmailWithI18N` utility in the lib package - Updated all emails to use the `<Tailwind>` component at the top level due to rendering constraints - Updated the `i18n.server.tsx` file to not use a top level await ## Testing Performed - Configured document language and verified emails were sent in the expected language - Created a document from a template and verified that the templates language was transferred to the document
578 lines
16 KiB
TypeScript
578 lines
16 KiB
TypeScript
import { extendZodWithOpenApi } from '@anatine/zod-openapi';
|
|
import { z } from 'zod';
|
|
|
|
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
|
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
|
import '@documenso/lib/constants/time-zones';
|
|
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
|
import { ZUrlSchema } from '@documenso/lib/schemas/common';
|
|
import {
|
|
ZDocumentAccessAuthTypesSchema,
|
|
ZDocumentActionAuthTypesSchema,
|
|
ZRecipientActionAuthTypesSchema,
|
|
} from '@documenso/lib/types/document-auth';
|
|
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
|
import {
|
|
DocumentDataType,
|
|
DocumentSigningOrder,
|
|
FieldType,
|
|
ReadStatus,
|
|
RecipientRole,
|
|
SendStatus,
|
|
SigningStatus,
|
|
TeamMemberRole,
|
|
TemplateType,
|
|
} from '@documenso/prisma/client';
|
|
|
|
extendZodWithOpenApi(z);
|
|
|
|
export const ZNoBodyMutationSchema = null;
|
|
|
|
/**
|
|
* Documents
|
|
*/
|
|
export const ZGetDocumentsQuerySchema = z.object({
|
|
page: z.coerce.number().min(1).optional().default(1),
|
|
perPage: z.coerce.number().min(1).optional().default(1),
|
|
});
|
|
|
|
export type TGetDocumentsQuerySchema = z.infer<typeof ZGetDocumentsQuerySchema>;
|
|
|
|
export const ZDeleteDocumentMutationSchema = null;
|
|
|
|
export type TDeleteDocumentMutationSchema = typeof ZDeleteDocumentMutationSchema;
|
|
|
|
export const ZSuccessfulDocumentResponseSchema = z.object({
|
|
id: z.number(),
|
|
externalId: z.string().nullish(),
|
|
userId: z.number(),
|
|
teamId: z.number().nullish(),
|
|
title: z.string(),
|
|
status: z.string(),
|
|
documentDataId: z.string(),
|
|
createdAt: z.date(),
|
|
updatedAt: z.date(),
|
|
completedAt: z.date().nullable(),
|
|
});
|
|
|
|
export const ZSuccessfulGetDocumentResponseSchema = ZSuccessfulDocumentResponseSchema.extend({
|
|
recipients: z.lazy(() => z.array(ZSuccessfulRecipientResponseSchema)),
|
|
});
|
|
|
|
export type TSuccessfulGetDocumentResponseSchema = z.infer<
|
|
typeof ZSuccessfulGetDocumentResponseSchema
|
|
>;
|
|
|
|
export type TSuccessfulDocumentResponseSchema = z.infer<typeof ZSuccessfulDocumentResponseSchema>;
|
|
|
|
export const ZSendDocumentForSigningMutationSchema = z
|
|
.object({
|
|
sendEmail: z.boolean().optional().default(true),
|
|
})
|
|
.or(z.literal('').transform(() => ({ sendEmail: true })));
|
|
|
|
export type TSendDocumentForSigningMutationSchema = typeof ZSendDocumentForSigningMutationSchema;
|
|
|
|
export const ZResendDocumentForSigningMutationSchema = z.object({
|
|
recipients: z.array(z.number()),
|
|
});
|
|
|
|
export type TResendDocumentForSigningMutationSchema = z.infer<
|
|
typeof ZResendDocumentForSigningMutationSchema
|
|
>;
|
|
|
|
export const ZSuccessfulResendDocumentResponseSchema = z.object({
|
|
message: z.string(),
|
|
});
|
|
|
|
export type TResendDocumentResponseSchema = z.infer<typeof ZSuccessfulResendDocumentResponseSchema>;
|
|
|
|
export const ZUploadDocumentSuccessfulSchema = z.object({
|
|
url: z.string(),
|
|
key: z.string(),
|
|
});
|
|
|
|
export const ZDownloadDocumentSuccessfulSchema = z.object({
|
|
downloadUrl: z.string(),
|
|
});
|
|
|
|
export type TUploadDocumentSuccessfulSchema = z.infer<typeof ZUploadDocumentSuccessfulSchema>;
|
|
|
|
export const ZCreateDocumentMutationSchema = z.object({
|
|
title: z.string().min(1),
|
|
externalId: z.string().nullish(),
|
|
recipients: z.array(
|
|
z.object({
|
|
name: z.string().min(1),
|
|
email: z.string().email().min(1),
|
|
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
|
signingOrder: z.number().nullish(),
|
|
}),
|
|
),
|
|
meta: z
|
|
.object({
|
|
subject: z.string(),
|
|
message: z.string(),
|
|
timezone: z.string().default(DEFAULT_DOCUMENT_TIME_ZONE).openapi({
|
|
description:
|
|
'The timezone of the date. Must be one of the options listed in the list below.',
|
|
enum: TIME_ZONES,
|
|
}),
|
|
dateFormat: z
|
|
.string()
|
|
.default(DEFAULT_DOCUMENT_DATE_FORMAT)
|
|
.openapi({
|
|
description:
|
|
'The format of the date. Must be one of the options listed in the list below.',
|
|
enum: DATE_FORMATS.map((format) => format.value),
|
|
}),
|
|
redirectUrl: z.string(),
|
|
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
|
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
|
})
|
|
.partial(),
|
|
authOptions: z
|
|
.object({
|
|
globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(),
|
|
globalActionAuth: ZDocumentActionAuthTypesSchema.optional(),
|
|
})
|
|
.optional(),
|
|
formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
|
|
});
|
|
|
|
export type TCreateDocumentMutationSchema = z.infer<typeof ZCreateDocumentMutationSchema>;
|
|
|
|
export const ZCreateDocumentMutationResponseSchema = z.object({
|
|
uploadUrl: z.string().min(1),
|
|
documentId: z.number(),
|
|
externalId: z.string().nullish(),
|
|
recipients: z.array(
|
|
z.object({
|
|
recipientId: z.number(),
|
|
name: z.string(),
|
|
email: z.string().email().min(1),
|
|
token: z.string(),
|
|
role: z.nativeEnum(RecipientRole),
|
|
signingOrder: z.number().nullish(),
|
|
|
|
signingUrl: z.string(),
|
|
}),
|
|
),
|
|
});
|
|
|
|
export type TCreateDocumentMutationResponseSchema = z.infer<
|
|
typeof ZCreateDocumentMutationResponseSchema
|
|
>;
|
|
|
|
export const ZCreateDocumentFromTemplateMutationSchema = z.object({
|
|
title: z.string().min(1),
|
|
externalId: z.string().nullish(),
|
|
recipients: z.array(
|
|
z.object({
|
|
name: z.string().min(1),
|
|
email: z.string().email().min(1),
|
|
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
|
signingOrder: z.number().nullish(),
|
|
}),
|
|
),
|
|
meta: z
|
|
.object({
|
|
subject: z.string(),
|
|
message: z.string(),
|
|
timezone: z.string(),
|
|
dateFormat: z.string(),
|
|
redirectUrl: z.string(),
|
|
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
|
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
|
})
|
|
.partial()
|
|
.optional(),
|
|
authOptions: z
|
|
.object({
|
|
globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(),
|
|
globalActionAuth: ZDocumentActionAuthTypesSchema.optional(),
|
|
})
|
|
.optional(),
|
|
formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
|
|
});
|
|
|
|
export type TCreateDocumentFromTemplateMutationSchema = z.infer<
|
|
typeof ZCreateDocumentFromTemplateMutationSchema
|
|
>;
|
|
|
|
export const ZCreateDocumentFromTemplateMutationResponseSchema = z.object({
|
|
documentId: z.number(),
|
|
externalId: z.string().nullish(),
|
|
recipients: z.array(
|
|
z.object({
|
|
recipientId: z.number(),
|
|
name: z.string(),
|
|
email: z.string().email().min(1),
|
|
token: z.string(),
|
|
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
|
signingOrder: z.number().nullish(),
|
|
|
|
signingUrl: z.string(),
|
|
}),
|
|
),
|
|
});
|
|
|
|
export type TCreateDocumentFromTemplateMutationResponseSchema = z.infer<
|
|
typeof ZCreateDocumentFromTemplateMutationResponseSchema
|
|
>;
|
|
|
|
export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
|
|
title: z.string().optional(),
|
|
externalId: z.string().nullish(),
|
|
recipients: z
|
|
.array(
|
|
z.object({
|
|
id: z.number(),
|
|
name: z.string().optional(),
|
|
email: z.string().email().min(1),
|
|
signingOrder: z.number().nullish(),
|
|
}),
|
|
)
|
|
.refine(
|
|
(schema) => {
|
|
const emails = schema.map((signer) => signer.email.toLowerCase());
|
|
const ids = schema.map((signer) => signer.id);
|
|
|
|
return new Set(emails).size === emails.length && new Set(ids).size === ids.length;
|
|
},
|
|
{ message: 'Recipient IDs and emails must be unique' },
|
|
),
|
|
meta: z
|
|
.object({
|
|
subject: z.string(),
|
|
message: z.string(),
|
|
timezone: z.string(),
|
|
dateFormat: z.string(),
|
|
redirectUrl: ZUrlSchema,
|
|
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
|
language: z.enum(SUPPORTED_LANGUAGE_CODES).optional(),
|
|
})
|
|
.partial()
|
|
.optional(),
|
|
authOptions: z
|
|
.object({
|
|
globalAccessAuth: ZDocumentAccessAuthTypesSchema.optional(),
|
|
globalActionAuth: ZDocumentActionAuthTypesSchema.optional(),
|
|
})
|
|
.optional(),
|
|
formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
|
|
});
|
|
|
|
export type TGenerateDocumentFromTemplateMutationSchema = z.infer<
|
|
typeof ZGenerateDocumentFromTemplateMutationSchema
|
|
>;
|
|
|
|
export const ZGenerateDocumentFromTemplateMutationResponseSchema = z.object({
|
|
documentId: z.number(),
|
|
externalId: z.string().nullish(),
|
|
recipients: z.array(
|
|
z.object({
|
|
recipientId: z.number(),
|
|
name: z.string(),
|
|
email: z.string().email().min(1),
|
|
token: z.string(),
|
|
role: z.nativeEnum(RecipientRole),
|
|
signingOrder: z.number().nullish(),
|
|
|
|
signingUrl: z.string(),
|
|
}),
|
|
),
|
|
});
|
|
|
|
export type TGenerateDocumentFromTemplateMutationResponseSchema = z.infer<
|
|
typeof ZGenerateDocumentFromTemplateMutationResponseSchema
|
|
>;
|
|
|
|
export const ZCreateRecipientMutationSchema = z.object({
|
|
name: z.string().min(1),
|
|
email: z.string().email().min(1),
|
|
role: z.nativeEnum(RecipientRole).optional().default(RecipientRole.SIGNER),
|
|
signingOrder: z.number().nullish(),
|
|
authOptions: z
|
|
.object({
|
|
actionAuth: ZRecipientActionAuthTypesSchema.optional(),
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
/**
|
|
* Recipients
|
|
*/
|
|
export type TCreateRecipientMutationSchema = z.infer<typeof ZCreateRecipientMutationSchema>;
|
|
|
|
export const ZUpdateRecipientMutationSchema = ZCreateRecipientMutationSchema.partial();
|
|
|
|
export type TUpdateRecipientMutationSchema = z.infer<typeof ZUpdateRecipientMutationSchema>;
|
|
|
|
export const ZDeleteRecipientMutationSchema = null;
|
|
|
|
export type TDeleteRecipientMutationSchema = typeof ZDeleteRecipientMutationSchema;
|
|
|
|
export const ZSuccessfulRecipientResponseSchema = z.object({
|
|
id: z.number(),
|
|
// !: This handles the fact that we have null documentId's for templates
|
|
// !: while we won't need the default we must add it to satisfy typescript
|
|
documentId: z.number().nullish().default(-1),
|
|
email: z.string().email().min(1),
|
|
name: z.string(),
|
|
role: z.nativeEnum(RecipientRole),
|
|
signingOrder: z.number().nullish(),
|
|
token: z.string(),
|
|
// !: Not used for now
|
|
// expired: z.string(),
|
|
signedAt: z.date().nullable(),
|
|
readStatus: z.nativeEnum(ReadStatus),
|
|
signingStatus: z.nativeEnum(SigningStatus),
|
|
sendStatus: z.nativeEnum(SendStatus),
|
|
|
|
signingUrl: z.string(),
|
|
});
|
|
|
|
export type TSuccessfulRecipientResponseSchema = z.infer<typeof ZSuccessfulRecipientResponseSchema>;
|
|
|
|
/**
|
|
* Fields
|
|
*/
|
|
const ZCreateFieldSchema = z.object({
|
|
recipientId: z.number(),
|
|
type: z.nativeEnum(FieldType),
|
|
pageNumber: z.number(),
|
|
pageX: z.number(),
|
|
pageY: z.number(),
|
|
pageWidth: z.number(),
|
|
pageHeight: z.number(),
|
|
fieldMeta: ZFieldMetaSchema.openapi({}),
|
|
});
|
|
|
|
export const ZCreateFieldMutationSchema = z.union([
|
|
ZCreateFieldSchema,
|
|
z.array(ZCreateFieldSchema).min(1),
|
|
]);
|
|
|
|
export type TCreateFieldMutationSchema = z.infer<typeof ZCreateFieldMutationSchema>;
|
|
|
|
export const ZUpdateFieldMutationSchema = ZCreateFieldSchema.partial();
|
|
|
|
export type TUpdateFieldMutationSchema = z.infer<typeof ZUpdateFieldMutationSchema>;
|
|
|
|
export const ZDeleteFieldMutationSchema = null;
|
|
|
|
export type TDeleteFieldMutationSchema = typeof ZDeleteFieldMutationSchema;
|
|
|
|
const ZSuccessfulFieldSchema = z.object({
|
|
id: z.number(),
|
|
documentId: z.number(),
|
|
recipientId: z.number(),
|
|
type: z.nativeEnum(FieldType),
|
|
pageNumber: z.number(),
|
|
pageX: z.number(),
|
|
pageY: z.number(),
|
|
pageWidth: z.number(),
|
|
pageHeight: z.number(),
|
|
customText: z.string(),
|
|
fieldMeta: ZFieldMetaSchema,
|
|
inserted: z.boolean(),
|
|
});
|
|
|
|
export const ZSuccessfulFieldCreationResponseSchema = z.object({
|
|
fields: z.union([ZSuccessfulFieldSchema, z.array(ZSuccessfulFieldSchema)]),
|
|
documentId: z.number(),
|
|
});
|
|
|
|
export const ZSuccessfulFieldResponseSchema = z.object({
|
|
id: z.number(),
|
|
documentId: z.number(),
|
|
recipientId: z.number(),
|
|
type: z.nativeEnum(FieldType),
|
|
pageNumber: z.number(),
|
|
pageX: z.number(),
|
|
pageY: z.number(),
|
|
pageWidth: z.number(),
|
|
pageHeight: z.number(),
|
|
customText: z.string(),
|
|
fieldMeta: ZFieldMetaSchema,
|
|
inserted: z.boolean(),
|
|
});
|
|
|
|
export type TSuccessfulFieldResponseSchema = z.infer<typeof ZSuccessfulFieldResponseSchema>;
|
|
|
|
export const ZSuccessfulResponseSchema = z.object({
|
|
documents: ZSuccessfulDocumentResponseSchema.array(),
|
|
totalPages: z.number(),
|
|
});
|
|
|
|
export type TSuccessfulResponseSchema = z.infer<typeof ZSuccessfulResponseSchema>;
|
|
|
|
export const ZSuccessfulSigningResponseSchema = z
|
|
.object({
|
|
message: z.string(),
|
|
})
|
|
.and(ZSuccessfulGetDocumentResponseSchema);
|
|
|
|
export type TSuccessfulSigningResponseSchema = z.infer<typeof ZSuccessfulSigningResponseSchema>;
|
|
|
|
/**
|
|
* General
|
|
*/
|
|
export const ZAuthorizationHeadersSchema = z.object({
|
|
authorization: z.string(),
|
|
});
|
|
|
|
export type TAuthorizationHeadersSchema = z.infer<typeof ZAuthorizationHeadersSchema>;
|
|
|
|
export const ZUnsuccessfulResponseSchema = z.object({
|
|
message: z.string(),
|
|
});
|
|
|
|
export type TUnsuccessfulResponseSchema = z.infer<typeof ZUnsuccessfulResponseSchema>;
|
|
|
|
export const ZTemplateMetaSchema = z.object({
|
|
id: z.string(),
|
|
subject: z.string().nullish(),
|
|
message: z.string().nullish(),
|
|
timezone: z.string().nullish(),
|
|
dateFormat: z.string().nullish(),
|
|
templateId: z.number(),
|
|
redirectUrl: z.string().nullish(),
|
|
signingOrder: z.nativeEnum(DocumentSigningOrder).nullish().default(DocumentSigningOrder.PARALLEL),
|
|
});
|
|
|
|
export const ZTemplateSchema = z.object({
|
|
id: z.number(),
|
|
externalId: z.string().nullish(),
|
|
type: z.nativeEnum(TemplateType),
|
|
title: z.string(),
|
|
userId: z.number(),
|
|
teamId: z.number().nullish(),
|
|
templateDocumentDataId: z.string(),
|
|
createdAt: z.date(),
|
|
updatedAt: z.date(),
|
|
});
|
|
|
|
export const ZRecipientSchema = z.object({
|
|
id: z.number(),
|
|
documentId: z.number().nullish(),
|
|
templateId: z.number().nullish(),
|
|
email: z.string().email().min(1),
|
|
name: z.string(),
|
|
token: z.string(),
|
|
signingOrder: z.number().nullish(),
|
|
documentDeletedAt: z.date().nullish(),
|
|
expired: z.date().nullish(),
|
|
signedAt: z.date().nullish(),
|
|
authOptions: z.unknown(),
|
|
role: z.nativeEnum(RecipientRole),
|
|
readStatus: z.nativeEnum(ReadStatus),
|
|
signingStatus: z.nativeEnum(SigningStatus),
|
|
sendStatus: z.nativeEnum(SendStatus),
|
|
});
|
|
|
|
export const ZFieldSchema = z.object({
|
|
id: z.number(),
|
|
secondaryId: z.string(),
|
|
documentId: z.number().nullish(),
|
|
templateId: z.number().nullish(),
|
|
recipientId: z.number(),
|
|
type: z.nativeEnum(FieldType),
|
|
page: z.number(),
|
|
positionX: z.unknown(),
|
|
positionY: z.unknown(),
|
|
width: z.unknown(),
|
|
height: z.unknown(),
|
|
customText: z.string(),
|
|
inserted: z.boolean(),
|
|
});
|
|
|
|
export const ZTemplateWithDataSchema = ZTemplateSchema.extend({
|
|
templateMeta: ZTemplateMetaSchema.nullish(),
|
|
directLink: z
|
|
.object({
|
|
token: z.string(),
|
|
enabled: z.boolean(),
|
|
})
|
|
.nullable(),
|
|
templateDocumentData: z.object({
|
|
id: z.string(),
|
|
type: z.nativeEnum(DocumentDataType),
|
|
data: z.string(),
|
|
}),
|
|
Field: ZFieldSchema.pick({
|
|
id: true,
|
|
recipientId: true,
|
|
type: true,
|
|
page: true,
|
|
positionX: true,
|
|
positionY: true,
|
|
width: true,
|
|
height: true,
|
|
}).array(),
|
|
Recipient: ZRecipientSchema.pick({
|
|
id: true,
|
|
email: true,
|
|
name: true,
|
|
signingOrder: true,
|
|
authOptions: true,
|
|
role: true,
|
|
}).array(),
|
|
});
|
|
|
|
export const ZSuccessfulGetTemplateResponseSchema = ZTemplateWithDataSchema;
|
|
|
|
export const ZSuccessfulDeleteTemplateResponseSchema = ZTemplateSchema;
|
|
|
|
export const ZSuccessfulGetTemplatesResponseSchema = z.object({
|
|
templates: ZTemplateWithDataSchema.omit({
|
|
templateDocumentData: true,
|
|
templateMeta: true,
|
|
}).array(),
|
|
totalPages: z.number(),
|
|
});
|
|
|
|
export const ZGetTemplatesQuerySchema = z.object({
|
|
page: z.coerce.number().min(1).optional().default(1),
|
|
perPage: z.coerce.number().min(1).optional().default(1),
|
|
});
|
|
|
|
export const ZFindTeamMembersResponseSchema = z.object({
|
|
members: z.array(
|
|
z.object({
|
|
id: z.number(),
|
|
email: z.string().email(),
|
|
role: z.nativeEnum(TeamMemberRole),
|
|
}),
|
|
),
|
|
});
|
|
|
|
export const ZInviteTeamMemberMutationSchema = z.object({
|
|
email: z
|
|
.string()
|
|
.email()
|
|
.transform((email) => email.toLowerCase()),
|
|
role: z.nativeEnum(TeamMemberRole).optional().default(TeamMemberRole.MEMBER),
|
|
});
|
|
|
|
export const ZSuccessfulInviteTeamMemberResponseSchema = z.object({
|
|
message: z.string(),
|
|
});
|
|
|
|
export const ZUpdateTeamMemberMutationSchema = z.object({
|
|
role: z.nativeEnum(TeamMemberRole),
|
|
});
|
|
|
|
export const ZSuccessfulUpdateTeamMemberResponseSchema = z.object({
|
|
id: z.number(),
|
|
email: z.string().email(),
|
|
role: z.nativeEnum(TeamMemberRole),
|
|
});
|
|
|
|
export const ZSuccessfulRemoveTeamMemberResponseSchema = z.object({
|
|
id: z.number(),
|
|
email: z.string().email(),
|
|
role: z.nativeEnum(TeamMemberRole),
|
|
});
|