mirror of
https://github.com/documenso/documenso.git
synced 2025-11-20 03:32:14 +10:00
Merge branch 'main' into exp/autoplace-fields
This commit is contained in:
@ -28,9 +28,13 @@ import {
|
||||
seedPendingDocument,
|
||||
} from '@documenso/prisma/seed/documents';
|
||||
import { seedBlankFolder } from '@documenso/prisma/seed/folders';
|
||||
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
|
||||
import { seedBlankTemplate, seedTemplate } from '@documenso/prisma/seed/templates';
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
import type { TCreateEnvelopeItemsPayload } from '@documenso/trpc/server/envelope-router/create-envelope-items.types';
|
||||
import type {
|
||||
TUseEnvelopePayload,
|
||||
TUseEnvelopeResponse,
|
||||
} from '@documenso/trpc/server/envelope-router/use-envelope.types';
|
||||
|
||||
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
|
||||
|
||||
@ -3074,6 +3078,82 @@ test.describe('Document API V2', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Envelope use endpoint', () => {
|
||||
test('should block unauthorized access to envelope use endpoint', async ({ request }) => {
|
||||
const doc = await seedTemplate({
|
||||
title: 'Team template 1',
|
||||
userId: userA.id,
|
||||
teamId: teamA.id,
|
||||
internalVersion: 2,
|
||||
});
|
||||
|
||||
const payload: TUseEnvelopePayload = {
|
||||
envelopeId: doc.id,
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
|
||||
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/use`, {
|
||||
headers: { Authorization: `Bearer ${tokenB}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeFalsy();
|
||||
expect(res.status()).toBe(404);
|
||||
});
|
||||
|
||||
test('should allow authorized access to envelope use endpoint', async ({ page, request }) => {
|
||||
const doc = await seedTemplate({
|
||||
title: 'Team template 1',
|
||||
userId: userA.id,
|
||||
teamId: teamA.id,
|
||||
internalVersion: 2,
|
||||
});
|
||||
|
||||
const payload: TUseEnvelopePayload = {
|
||||
envelopeId: doc.id,
|
||||
distributeDocument: true,
|
||||
recipients: [
|
||||
{
|
||||
id: doc.recipients[0].id,
|
||||
email: doc.recipients[0].email,
|
||||
name: 'New Name',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
|
||||
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/use`, {
|
||||
headers: { Authorization: `Bearer ${tokenA}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const data: TUseEnvelopeResponse = await res.json();
|
||||
|
||||
const createdEnvelope = await prisma.envelope.findFirst({
|
||||
where: {
|
||||
id: data.id,
|
||||
},
|
||||
include: {
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(createdEnvelope).toBeDefined();
|
||||
expect(createdEnvelope?.recipients.length).toBe(1);
|
||||
expect(createdEnvelope?.recipients[0].email).toBe(doc.recipients[0].email);
|
||||
expect(createdEnvelope?.recipients[0].name).toBe('New Name');
|
||||
expect(createdEnvelope?.recipients[0].token).toBe(data.recipients[0].token);
|
||||
expect(createdEnvelope?.recipients[0].token).not.toBe(doc.recipients[0].token);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Envelope distribute endpoint', () => {
|
||||
test('should block unauthorized access to envelope distribute endpoint', async ({
|
||||
request,
|
||||
@ -3925,8 +4005,12 @@ test.describe('Document API V2', () => {
|
||||
|
||||
// 3 Files because seed creates one automatically.
|
||||
expect(envelopeItems.length).toBe(3);
|
||||
expect(envelopeItems[1].title).toBe('field-meta-1.pdf');
|
||||
expect(envelopeItems[2].title).toBe('field-meta-2.pdf');
|
||||
|
||||
const isFieldMeta1 = envelopeItems.find((item) => item.title === 'field-meta-1.pdf');
|
||||
const isFieldMeta2 = envelopeItems.find((item) => item.title === 'field-meta-2.pdf');
|
||||
|
||||
expect(isFieldMeta1).toBeDefined();
|
||||
expect(isFieldMeta2).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
export type TemplateCustomMessageBodyProps = {
|
||||
text?: string;
|
||||
};
|
||||
|
||||
export const TemplateCustomMessageBody = ({ text }: TemplateCustomMessageBodyProps) => {
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = text
|
||||
.trim()
|
||||
.replace(/\r\n?/g, '\n')
|
||||
.replace(/\n\s*\n+/g, '\n\n')
|
||||
.replace(/\n{2,}/g, '\n\n');
|
||||
|
||||
const paragraphs = normalized.split('\n\n');
|
||||
|
||||
return paragraphs.map((paragraph, i) => (
|
||||
<p
|
||||
key={`p-${i}`}
|
||||
className="whitespace-pre-line break-words font-sans text-base text-slate-400"
|
||||
>
|
||||
{paragraph.split('\n').map((line, j) => (
|
||||
<React.Fragment key={`line-${i}-${j}`}>
|
||||
{j > 0 && <br />}
|
||||
{line}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</p>
|
||||
));
|
||||
};
|
||||
|
||||
export default TemplateCustomMessageBody;
|
||||
@ -8,6 +8,7 @@ import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-
|
||||
|
||||
import { Body, Container, Head, Hr, Html, Img, Link, Preview, Section, Text } from '../components';
|
||||
import { useBranding } from '../providers/branding';
|
||||
import { TemplateCustomMessageBody } from '../template-components/template-custom-message-body';
|
||||
import type { TemplateDocumentInviteProps } from '../template-components/template-document-invite';
|
||||
import { TemplateDocumentInvite } from '../template-components/template-document-invite';
|
||||
import { TemplateFooter } from '../template-components/template-footer';
|
||||
@ -105,7 +106,7 @@ export const DocumentInviteEmailTemplate = ({
|
||||
|
||||
<Text className="mt-2 text-base text-slate-400">
|
||||
{customBody ? (
|
||||
<pre className="font-sans text-base text-slate-400">{customBody}</pre>
|
||||
<TemplateCustomMessageBody text={customBody} />
|
||||
) : (
|
||||
<Trans>
|
||||
{inviterName} has invited you to {action} the document "{documentName}".
|
||||
|
||||
@ -170,10 +170,15 @@ export const useEditorFields = ({
|
||||
);
|
||||
|
||||
const setFieldId = (formId: string, id: number) => {
|
||||
const index = localFields.findIndex((field) => field.formId === formId);
|
||||
const { fields } = form.getValues();
|
||||
|
||||
const index = fields.findIndex((field) => field.formId === formId);
|
||||
|
||||
if (index !== -1) {
|
||||
form.setValue(`fields.${index}.id`, id);
|
||||
update(index, {
|
||||
...fields[index],
|
||||
id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -51,21 +51,21 @@ export const DOCUMENT_SIGNATURE_TYPES = {
|
||||
[DocumentSignatureType.DRAW]: {
|
||||
label: msg({
|
||||
message: `Draw`,
|
||||
context: `Draw signatute type`,
|
||||
context: `Draw signature`,
|
||||
}),
|
||||
value: DocumentSignatureType.DRAW,
|
||||
},
|
||||
[DocumentSignatureType.TYPE]: {
|
||||
label: msg({
|
||||
message: `Type`,
|
||||
context: `Type signatute type`,
|
||||
context: `Type signature`,
|
||||
}),
|
||||
value: DocumentSignatureType.TYPE,
|
||||
},
|
||||
[DocumentSignatureType.UPLOAD]: {
|
||||
label: msg({
|
||||
message: `Upload`,
|
||||
context: `Upload signatute type`,
|
||||
context: `Upload signature`,
|
||||
}),
|
||||
value: DocumentSignatureType.UPLOAD,
|
||||
},
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const SUPPORTED_LANGUAGE_CODES = ['de', 'en', 'fr', 'es', 'it', 'pl'] as const;
|
||||
export const SUPPORTED_LANGUAGE_CODES = [
|
||||
'de',
|
||||
'en',
|
||||
'fr',
|
||||
'es',
|
||||
'it',
|
||||
'pl',
|
||||
'ja',
|
||||
'ko',
|
||||
'zh',
|
||||
] as const;
|
||||
|
||||
export const ZSupportedLanguageCodeSchema = z.enum(SUPPORTED_LANGUAGE_CODES).catch('en');
|
||||
|
||||
@ -54,6 +64,18 @@ export const SUPPORTED_LANGUAGES: Record<string, SupportedLanguage> = {
|
||||
short: 'pl',
|
||||
full: 'Polish',
|
||||
},
|
||||
ja: {
|
||||
short: 'ja',
|
||||
full: 'Japanese',
|
||||
},
|
||||
ko: {
|
||||
short: 'ko',
|
||||
full: 'Korean',
|
||||
},
|
||||
zh: {
|
||||
short: 'zh',
|
||||
full: 'Chinese',
|
||||
},
|
||||
} satisfies Record<SupportedLanguageCodes, SupportedLanguage>;
|
||||
|
||||
export const isValidLanguageCode = (code: unknown): code is SupportedLanguageCodes =>
|
||||
|
||||
@ -44,7 +44,7 @@ export const resendDocument = async ({
|
||||
recipients,
|
||||
teamId,
|
||||
requestMetadata,
|
||||
}: ResendDocumentOptions): Promise<void> => {
|
||||
}: ResendDocumentOptions) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
where: {
|
||||
id: userId,
|
||||
@ -103,7 +103,7 @@ export const resendDocument = async ({
|
||||
).recipientSigningRequest;
|
||||
|
||||
if (!isRecipientSigningRequestEmailEnabled) {
|
||||
return;
|
||||
return envelope;
|
||||
}
|
||||
|
||||
const { branding, emailLanguage, organisationType, senderEmail, replyToEmail } =
|
||||
@ -230,4 +230,6 @@ export const resendDocument = async ({
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
return envelope;
|
||||
};
|
||||
|
||||
@ -31,26 +31,16 @@ export const viewedDocument = async ({
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
envelope: {
|
||||
include: {
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { envelope } = recipient;
|
||||
|
||||
await prisma.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_VIEWED,
|
||||
envelopeId: envelope.id,
|
||||
envelopeId: recipient.envelopeId,
|
||||
user: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
@ -86,7 +76,7 @@ export const viewedDocument = async ({
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
|
||||
envelopeId: envelope.id,
|
||||
envelopeId: recipient.envelopeId,
|
||||
user: {
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
@ -103,6 +93,16 @@ export const viewedDocument = async ({
|
||||
});
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: {
|
||||
id: recipient.envelopeId,
|
||||
},
|
||||
include: {
|
||||
documentMeta: true,
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
await triggerWebhook({
|
||||
event: WebhookTriggerEvents.DOCUMENT_OPENED,
|
||||
data: ZWebhookDocumentSchema.parse(mapEnvelopeToWebhookDocumentPayload(envelope)),
|
||||
|
||||
@ -373,3 +373,52 @@ export const FIELD_META_DEFAULT_VALUES: Record<FieldType, TFieldMetaSchema> = {
|
||||
[FieldType.CHECKBOX]: FIELD_CHECKBOX_META_DEFAULT_VALUES,
|
||||
[FieldType.DROPDOWN]: FIELD_DROPDOWN_META_DEFAULT_VALUES,
|
||||
} as const;
|
||||
|
||||
export const ZEnvelopeFieldAndMetaSchema = z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal(FieldType.SIGNATURE),
|
||||
fieldMeta: ZSignatureFieldMeta.optional().default(FIELD_SIGNATURE_META_DEFAULT_VALUES),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.FREE_SIGNATURE),
|
||||
fieldMeta: z.undefined(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.INITIALS),
|
||||
fieldMeta: ZInitialsFieldMeta.optional().default(FIELD_INITIALS_META_DEFAULT_VALUES),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.NAME),
|
||||
fieldMeta: ZNameFieldMeta.optional().default(FIELD_NAME_META_DEFAULT_VALUES),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.EMAIL),
|
||||
fieldMeta: ZEmailFieldMeta.optional().default(FIELD_EMAIL_META_DEFAULT_VALUES),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.DATE),
|
||||
fieldMeta: ZDateFieldMeta.optional().default(FIELD_DATE_META_DEFAULT_VALUES),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.TEXT),
|
||||
fieldMeta: ZTextFieldMeta.optional().default(FIELD_TEXT_META_DEFAULT_VALUES),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.NUMBER),
|
||||
fieldMeta: ZNumberFieldMeta.optional().default(FIELD_NUMBER_META_DEFAULT_VALUES),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.RADIO),
|
||||
fieldMeta: ZRadioFieldMeta.optional().default(FIELD_RADIO_META_DEFAULT_VALUES),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.CHECKBOX),
|
||||
fieldMeta: ZCheckboxFieldMeta.optional().default(FIELD_CHECKBOX_META_DEFAULT_VALUES),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(FieldType.DROPDOWN),
|
||||
fieldMeta: ZDropdownFieldMeta.optional().default(FIELD_DROPDOWN_META_DEFAULT_VALUES),
|
||||
}),
|
||||
]);
|
||||
|
||||
type TEnvelopeFieldAndMeta = z.infer<typeof ZEnvelopeFieldAndMetaSchema>;
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Envelope_folderId_idx" ON "Envelope"("folderId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Envelope_teamId_idx" ON "Envelope"("teamId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Envelope_userId_idx" ON "Envelope"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EnvelopeAttachment_envelopeId_idx" ON "EnvelopeAttachment"("envelopeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EnvelopeItem_envelopeId_idx" ON "EnvelopeItem"("envelopeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Field_envelopeItemId_idx" ON "Field"("envelopeItemId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "OrganisationGroup_organisationId_idx" ON "OrganisationGroup"("organisationId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "OrganisationGroupMember_groupId_idx" ON "OrganisationGroupMember"("groupId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "OrganisationGroupMember_organisationMemberId_idx" ON "OrganisationGroupMember"("organisationMemberId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Session_sessionToken_idx" ON "Session"("sessionToken");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Team_organisationId_idx" ON "Team"("organisationId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "TeamGroup_teamId_idx" ON "TeamGroup"("teamId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "TeamGroup_organisationGroupId_idx" ON "TeamGroup"("organisationGroupId");
|
||||
@ -319,6 +319,9 @@ model Session {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([sessionToken])
|
||||
}
|
||||
|
||||
enum DocumentStatus {
|
||||
@ -426,6 +429,10 @@ model Envelope {
|
||||
documentMeta DocumentMeta @relation(fields: [documentMetaId], references: [id])
|
||||
|
||||
envelopeAttachments EnvelopeAttachment[]
|
||||
|
||||
@@index([folderId])
|
||||
@@index([teamId])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model EnvelopeItem {
|
||||
@ -444,6 +451,7 @@ model EnvelopeItem {
|
||||
field Field[]
|
||||
|
||||
@@unique([documentDataId])
|
||||
@@index([envelopeId])
|
||||
}
|
||||
|
||||
model DocumentAuditLog {
|
||||
@ -526,6 +534,8 @@ model EnvelopeAttachment {
|
||||
|
||||
envelopeId String
|
||||
envelope Envelope @relation(fields: [envelopeId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([envelopeId])
|
||||
}
|
||||
|
||||
enum ReadStatus {
|
||||
@ -613,6 +623,7 @@ model Field {
|
||||
fieldMeta Json? /// [FieldMeta] @zod.custom.use(ZFieldMetaNotOptionalSchema)
|
||||
|
||||
@@index([envelopeId])
|
||||
@@index([envelopeItemId])
|
||||
@@index([recipientId])
|
||||
}
|
||||
|
||||
@ -728,6 +739,8 @@ model OrganisationGroup {
|
||||
organisationGroupMembers OrganisationGroupMember[]
|
||||
|
||||
teamGroups TeamGroup[]
|
||||
|
||||
@@index([organisationId])
|
||||
}
|
||||
|
||||
model OrganisationGroupMember {
|
||||
@ -740,6 +753,8 @@ model OrganisationGroupMember {
|
||||
organisationMemberId String
|
||||
|
||||
@@unique([organisationMemberId, groupId])
|
||||
@@index([groupId])
|
||||
@@index([organisationMemberId])
|
||||
}
|
||||
|
||||
model TeamGroup {
|
||||
@ -754,6 +769,8 @@ model TeamGroup {
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([teamId, organisationGroupId])
|
||||
@@index([teamId])
|
||||
@@index([organisationGroupId])
|
||||
}
|
||||
|
||||
enum OrganisationGroupType {
|
||||
@ -865,6 +882,8 @@ model Team {
|
||||
|
||||
teamGlobalSettingsId String @unique
|
||||
teamGlobalSettings TeamGlobalSettings @relation(fields: [teamGlobalSettingsId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([organisationId])
|
||||
}
|
||||
|
||||
model TeamEmail {
|
||||
|
||||
@ -110,7 +110,7 @@ export const seedTemplate = async (options: SeedTemplateOptions) => {
|
||||
data: {
|
||||
id: prefixedId('envelope'),
|
||||
secondaryId: templateId.formattedTemplateId,
|
||||
internalVersion: 1,
|
||||
internalVersion: options.internalVersion ?? 1,
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title,
|
||||
envelopeItems: {
|
||||
@ -143,6 +143,7 @@ export const seedTemplate = async (options: SeedTemplateOptions) => {
|
||||
documentData: true,
|
||||
},
|
||||
},
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -41,10 +41,14 @@ export const createAttachmentRoute = authenticatedProcedure
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
});
|
||||
|
||||
await createAttachment({
|
||||
const attachment = await createAttachment({
|
||||
envelopeId: envelope.id,
|
||||
teamId,
|
||||
userId,
|
||||
data,
|
||||
});
|
||||
|
||||
return {
|
||||
id: attachment.id,
|
||||
};
|
||||
});
|
||||
|
||||
@ -8,7 +8,9 @@ export const ZCreateAttachmentRequestSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZCreateAttachmentResponseSchema = z.void();
|
||||
export const ZCreateAttachmentResponseSchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export type TCreateAttachmentRequest = z.infer<typeof ZCreateAttachmentRequestSchema>;
|
||||
export type TCreateAttachmentResponse = z.infer<typeof ZCreateAttachmentResponseSchema>;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { deleteAttachment } from '@documenso/lib/server-only/envelope-attachment/delete-attachment';
|
||||
|
||||
import { ZGenericSuccessResponse } from '../../schema';
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZDeleteAttachmentRequestSchema,
|
||||
@ -33,4 +34,6 @@ export const deleteAttachmentRoute = authenticatedProcedure
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../../schema';
|
||||
|
||||
export const ZDeleteAttachmentRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export const ZDeleteAttachmentResponseSchema = z.void();
|
||||
export const ZDeleteAttachmentResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TDeleteAttachmentRequest = z.infer<typeof ZDeleteAttachmentRequestSchema>;
|
||||
export type TDeleteAttachmentResponse = z.infer<typeof ZDeleteAttachmentResponseSchema>;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { updateAttachment } from '@documenso/lib/server-only/envelope-attachment/update-attachment';
|
||||
|
||||
import { ZGenericSuccessResponse } from '../../schema';
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZUpdateAttachmentRequestSchema,
|
||||
@ -34,4 +35,6 @@ export const updateAttachmentRoute = authenticatedProcedure
|
||||
teamId,
|
||||
data,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../../schema';
|
||||
|
||||
export const ZUpdateAttachmentRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
data: z.object({
|
||||
@ -8,7 +10,7 @@ export const ZUpdateAttachmentRequestSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateAttachmentResponseSchema = z.void();
|
||||
export const ZUpdateAttachmentResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TUpdateAttachmentRequest = z.infer<typeof ZUpdateAttachmentRequestSchema>;
|
||||
export type TUpdateAttachmentResponse = z.infer<typeof ZUpdateAttachmentResponseSchema>;
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
||||
|
||||
import { ZGenericSuccessResponse } from '../schema';
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteDocumentRequestSchema,
|
||||
ZDeleteDocumentResponseSchema,
|
||||
deleteDocumentMeta,
|
||||
} from './delete-document.types';
|
||||
import { ZGenericSuccessResponse } from './schema';
|
||||
|
||||
export const deleteDocumentRoute = authenticatedProcedure
|
||||
.meta(deleteDocumentMeta)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import { ZSuccessResponseSchema } from './schema';
|
||||
|
||||
export const deleteDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
|
||||
@ -19,5 +19,6 @@ export const downloadDocumentRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
|
||||
// This endpoint is purely for V2 API, which is implemented in the Hono remix server.
|
||||
throw new Error('NOT_IMPLEMENTED');
|
||||
});
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||
|
||||
import { ZGenericSuccessResponse } from '../schema';
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZRedistributeDocumentRequestSchema,
|
||||
ZRedistributeDocumentResponseSchema,
|
||||
redistributeDocumentMeta,
|
||||
} from './redistribute-document.types';
|
||||
import { ZGenericSuccessResponse } from './schema';
|
||||
|
||||
export const redistributeDocumentRoute = authenticatedProcedure
|
||||
.meta(redistributeDocumentMeta)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import { ZSuccessResponseSchema } from './schema';
|
||||
|
||||
export const redistributeDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
|
||||
@ -1,19 +1,6 @@
|
||||
import { DocumentVisibility } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Required for empty responses since we currently can't 201 requests for our openapi setup.
|
||||
*
|
||||
* Without this it will throw an error in Speakeasy SDK when it tries to parse an empty response.
|
||||
*/
|
||||
export const ZSuccessResponseSchema = z.object({
|
||||
success: z.literal(true),
|
||||
});
|
||||
|
||||
export const ZGenericSuccessResponse = {
|
||||
success: true,
|
||||
} satisfies z.infer<typeof ZSuccessResponseSchema>;
|
||||
|
||||
export const ZDocumentTitleSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
|
||||
@ -21,33 +21,38 @@ import {
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { RecipientRole } from '@documenso/prisma/client';
|
||||
import { DocumentSigningOrder } from '@documenso/prisma/generated/types';
|
||||
|
||||
import { ZDocumentExternalIdSchema, ZDocumentTitleSchema } from '../document-router/schema';
|
||||
import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
||||
|
||||
export const ZCreateEmbeddingDocumentRequestSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
documentDataId: z.string(),
|
||||
externalId: ZDocumentExternalIdSchema.optional(),
|
||||
recipients: z
|
||||
.array(
|
||||
ZCreateRecipientSchema.extend({
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
|
||||
.optional(),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
email: z.string().email(),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
// We have an any cast so any changes here you need to update it in the embeding document edit page
|
||||
// Search: "map<any>" to find it
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { DocumentSigningOrder, FieldType, RecipientRole } from '@prisma/client';
|
||||
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
@ -21,30 +21,33 @@ import {
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import { ZDocumentTitleSchema } from '../document-router/schema';
|
||||
|
||||
const ZFieldSchema = z.object({
|
||||
type: z.nativeEnum(FieldType),
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
fieldMeta: ZFieldMetaSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateEmbeddingTemplateRequestSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
documentDataId: z.string(),
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
email: z.string().email(),
|
||||
name: z.string().optional(),
|
||||
role: z.nativeEnum(RecipientRole).optional(),
|
||||
email: z.union([z.string().length(0), z.string().email()]),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
fields: z.array(ZFieldSchema).optional(),
|
||||
// We have an any cast so any changes here you need to update it in the embeding document edit page
|
||||
// Search: "map<any>" to find it
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
meta: z
|
||||
|
||||
@ -32,7 +32,7 @@ export const ZUpdateEmbeddingDocumentRequestSchema = z.object({
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
email: z.string().toLowerCase().email().min(1),
|
||||
email: z.string().email(),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
|
||||
@ -3,7 +3,6 @@ import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embeddin
|
||||
import { updateEnvelope } from '@documenso/lib/server-only/envelope/update-envelope';
|
||||
import { setFieldsForTemplate } from '@documenso/lib/server-only/field/set-fields-for-template';
|
||||
import { setTemplateRecipients } from '@documenso/lib/server-only/recipient/set-template-recipients';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
|
||||
import { procedure } from '../trpc';
|
||||
import {
|
||||
@ -53,11 +52,6 @@ export const updateEmbeddingTemplateRoute = procedure
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
const recipientsWithClientId = recipients.map((recipient) => ({
|
||||
...recipient,
|
||||
clientId: nanoid(),
|
||||
}));
|
||||
|
||||
const { recipients: updatedRecipients } = await setTemplateRecipients({
|
||||
userId: apiToken.userId,
|
||||
teamId: apiToken.teamId ?? undefined,
|
||||
@ -65,7 +59,7 @@ export const updateEmbeddingTemplateRoute = procedure
|
||||
type: 'templateId',
|
||||
id: templateId,
|
||||
},
|
||||
recipients: recipientsWithClientId.map((recipient) => ({
|
||||
recipients: recipients.map((recipient) => ({
|
||||
id: recipient.id,
|
||||
email: recipient.email,
|
||||
name: recipient.name ?? '',
|
||||
@ -74,8 +68,8 @@ export const updateEmbeddingTemplateRoute = procedure
|
||||
})),
|
||||
});
|
||||
|
||||
const fields = recipientsWithClientId.flatMap((recipient) => {
|
||||
const recipientId = updatedRecipients.find((r) => r.email === recipient.email)?.id;
|
||||
const fields = recipients.flatMap((recipient) => {
|
||||
const recipientId = updatedRecipients.find((r) => r.id === recipient.id)?.id;
|
||||
|
||||
if (!recipientId) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
@ -86,8 +80,6 @@ export const updateEmbeddingTemplateRoute = procedure
|
||||
return (recipient.fields ?? []).map((field) => ({
|
||||
...field,
|
||||
recipientId,
|
||||
// !: Temp property to be removed once we don't link based on signer email
|
||||
signerEmail: recipient.email,
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ import {
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { ZFieldAndMetaSchema, ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import { ZDocumentTitleSchema } from '../document-router/schema';
|
||||
|
||||
@ -44,11 +44,25 @@ export const ZUpdateEmbeddingTemplateRequestSchema = z.object({
|
||||
recipients: z.array(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
email: z.string().email(),
|
||||
name: z.string().optional(),
|
||||
role: z.nativeEnum(RecipientRole).optional(),
|
||||
email: z.union([z.string().length(0), z.string().email()]),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
signingOrder: z.number().optional(),
|
||||
fields: z.array(ZFieldSchema).optional(),
|
||||
// We have an any cast so any changes here you need to update it in the embeding document edit page
|
||||
// Search: "map<any>" to find it
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
id: z.number().optional(),
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
envelopeItemId: z.string(),
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
meta: z
|
||||
|
||||
@ -71,7 +71,7 @@ export const createSubscriptionRoute = authenticatedProcedure
|
||||
}
|
||||
|
||||
const returnUrl = isPersonalLayoutMode
|
||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing`
|
||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing-personal`
|
||||
: `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`;
|
||||
|
||||
const redirectUrl = await createCheckoutSession({
|
||||
|
||||
@ -12,7 +12,7 @@ import { ZManageSubscriptionRequestSchema } from './manage-subscription.types';
|
||||
export const manageSubscriptionRoute = authenticatedProcedure
|
||||
.input(ZManageSubscriptionRequestSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId } = input;
|
||||
const { organisationId, isPersonalLayoutMode } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@ -93,9 +93,13 @@ export const manageSubscriptionRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const returnUrl = isPersonalLayoutMode
|
||||
? `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing-personal`
|
||||
: `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`;
|
||||
|
||||
const redirectUrl = await getPortalSession({
|
||||
customerId,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/o/${organisation.url}/settings/billing`,
|
||||
returnUrl,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@ -2,4 +2,5 @@ import { z } from 'zod';
|
||||
|
||||
export const ZManageSubscriptionRequestSchema = z.object({
|
||||
organisationId: z.string().describe('The organisation to manage the subscription for'),
|
||||
isPersonalLayoutMode: z.boolean().optional(),
|
||||
});
|
||||
|
||||
@ -21,10 +21,14 @@ export const createAttachmentRoute = authenticatedProcedure
|
||||
input: { envelopeId, label: data.label },
|
||||
});
|
||||
|
||||
await createAttachment({
|
||||
const attachment = await createAttachment({
|
||||
envelopeId,
|
||||
teamId,
|
||||
userId,
|
||||
data,
|
||||
});
|
||||
|
||||
return {
|
||||
id: attachment.id,
|
||||
};
|
||||
});
|
||||
|
||||
@ -20,7 +20,9 @@ export const ZCreateAttachmentRequestSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZCreateAttachmentResponseSchema = z.void();
|
||||
export const ZCreateAttachmentResponseSchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export type TCreateAttachmentRequest = z.infer<typeof ZCreateAttachmentRequestSchema>;
|
||||
export type TCreateAttachmentResponse = z.infer<typeof ZCreateAttachmentResponseSchema>;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { deleteAttachment } from '@documenso/lib/server-only/envelope-attachment/delete-attachment';
|
||||
|
||||
import { ZGenericSuccessResponse } from '../../schema';
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZDeleteAttachmentRequestSchema,
|
||||
@ -26,4 +27,6 @@ export const deleteAttachmentRoute = authenticatedProcedure
|
||||
userId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../../schema';
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const deleteAttachmentMeta: TrpcRouteMeta = {
|
||||
@ -16,7 +17,7 @@ export const ZDeleteAttachmentRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export const ZDeleteAttachmentResponseSchema = z.void();
|
||||
export const ZDeleteAttachmentResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TDeleteAttachmentRequest = z.infer<typeof ZDeleteAttachmentRequestSchema>;
|
||||
export type TDeleteAttachmentResponse = z.infer<typeof ZDeleteAttachmentResponseSchema>;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { updateAttachment } from '@documenso/lib/server-only/envelope-attachment/update-attachment';
|
||||
|
||||
import { ZGenericSuccessResponse } from '../../schema';
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZUpdateAttachmentRequestSchema,
|
||||
@ -27,4 +28,6 @@ export const updateAttachmentRoute = authenticatedProcedure
|
||||
teamId,
|
||||
data,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../../schema';
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const updateAttachmentMeta: TrpcRouteMeta = {
|
||||
@ -20,7 +21,7 @@ export const ZUpdateAttachmentRequestSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateAttachmentResponseSchema = z.void();
|
||||
export const ZUpdateAttachmentResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TUpdateAttachmentRequest = z.infer<typeof ZUpdateAttachmentRequestSchema>;
|
||||
export type TUpdateAttachmentResponse = z.infer<typeof ZUpdateAttachmentResponseSchema>;
|
||||
|
||||
@ -11,6 +11,7 @@ export const createEnvelopeItemsMeta: TrpcRouteMeta = {
|
||||
method: 'POST',
|
||||
path: '/envelope/item/create-many',
|
||||
summary: 'Create envelope items',
|
||||
contentTypes: ['multipart/form-data'],
|
||||
description: 'Create multiple envelope items for an envelope',
|
||||
tags: ['Envelope Items'],
|
||||
},
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
ZClampedFieldWidthSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { ZEnvelopeFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import { zodFormData } from '../../utils/zod-form-data';
|
||||
import {
|
||||
@ -55,7 +55,7 @@ export const ZCreateEnvelopePayloadSchema = z.object({
|
||||
recipients: z
|
||||
.array(
|
||||
ZCreateRecipientSchema.extend({
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
fields: ZEnvelopeFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
identifier: z
|
||||
.union([z.string(), z.number()])
|
||||
|
||||
@ -5,6 +5,7 @@ import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-
|
||||
import { canEnvelopeItemsBeModified } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { ZGenericSuccessResponse } from '../schema';
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteEnvelopeItemRequestSchema,
|
||||
@ -100,4 +101,6 @@ export const deleteEnvelopeItemRoute = authenticatedProcedure
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const deleteEnvelopeItemMeta: TrpcRouteMeta = {
|
||||
@ -17,7 +18,7 @@ export const ZDeleteEnvelopeItemRequestSchema = z.object({
|
||||
envelopeItemId: z.string(),
|
||||
});
|
||||
|
||||
export const ZDeleteEnvelopeItemResponseSchema = z.void();
|
||||
export const ZDeleteEnvelopeItemResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TDeleteEnvelopeItemRequest = z.infer<typeof ZDeleteEnvelopeItemRequestSchema>;
|
||||
export type TDeleteEnvelopeItemResponse = z.infer<typeof ZDeleteEnvelopeItemResponseSchema>;
|
||||
|
||||
@ -6,6 +6,7 @@ import { deleteDocument } from '@documenso/lib/server-only/document/delete-docum
|
||||
import { deleteTemplate } from '@documenso/lib/server-only/template/delete-template';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { ZGenericSuccessResponse } from '../schema';
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteEnvelopeRequestSchema,
|
||||
@ -65,4 +66,6 @@ export const deleteEnvelopeRoute = authenticatedProcedure
|
||||
}),
|
||||
)
|
||||
.exhaustive();
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const deleteEnvelopeMeta: TrpcRouteMeta = {
|
||||
@ -15,7 +16,7 @@ export const ZDeleteEnvelopeRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
});
|
||||
|
||||
export const ZDeleteEnvelopeResponseSchema = z.void();
|
||||
export const ZDeleteEnvelopeResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TDeleteEnvelopeRequest = z.infer<typeof ZDeleteEnvelopeRequestSchema>;
|
||||
export type TDeleteEnvelopeResponse = z.infer<typeof ZDeleteEnvelopeResponseSchema>;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { updateDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { formatSigningLink } from '@documenso/lib/utils/recipients';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@ -44,7 +45,7 @@ export const distributeEnvelopeRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
await sendDocument({
|
||||
const envelope = await sendDocument({
|
||||
userId: ctx.user.id,
|
||||
id: {
|
||||
type: 'envelopeId',
|
||||
@ -53,4 +54,18 @@ export const distributeEnvelopeRoute = authenticatedProcedure
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
id: envelope.id,
|
||||
recipients: envelope.recipients.map((recipient) => ({
|
||||
id: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
signingUrl: formatSigningLink(recipient.token),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
@ -2,7 +2,9 @@ import { z } from 'zod';
|
||||
|
||||
import { ZDocumentMetaUpdateSchema } from '@documenso/lib/types/document-meta';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import { ZRecipientWithSigningUrlSchema } from './schema';
|
||||
|
||||
export const distributeEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
@ -30,7 +32,10 @@ export const ZDistributeEnvelopeRequestSchema = z.object({
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
export const ZDistributeEnvelopeResponseSchema = z.void();
|
||||
export const ZDistributeEnvelopeResponseSchema = ZSuccessResponseSchema.extend({
|
||||
id: z.string().describe('The ID of the envelope that was sent.'),
|
||||
recipients: ZRecipientWithSigningUrlSchema.array(),
|
||||
});
|
||||
|
||||
export type TDistributeEnvelopeRequest = z.infer<typeof ZDistributeEnvelopeRequestSchema>;
|
||||
export type TDistributeEnvelopeResponse = z.infer<typeof ZDistributeEnvelopeResponseSchema>;
|
||||
|
||||
@ -19,5 +19,6 @@ export const downloadEnvelopeItemRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
|
||||
// This endpoint is purely for V2 API, which is implemented in the Hono remix server.
|
||||
throw new Error('NOT_IMPLEMENTED');
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@ import type { TrpcRouteMeta } from '../trpc';
|
||||
export const downloadEnvelopeItemMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/envelopeItem/{envelopeItemId}/download',
|
||||
path: '/envelope/item/{envelopeItemId}/download',
|
||||
summary: 'Download an envelope item',
|
||||
description: 'Download an envelope item by its ID',
|
||||
tags: ['Envelope Items'],
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { ZEnvelopeFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
@ -16,14 +16,13 @@ export const createEnvelopeFieldsMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/envelope/field/create-many',
|
||||
contentTypes: ['multipart/form-data'],
|
||||
summary: 'Create envelope fields',
|
||||
description: 'Create multiple fields for an envelope',
|
||||
tags: ['Envelope Fields'],
|
||||
},
|
||||
};
|
||||
|
||||
const ZCreateFieldSchema = ZFieldAndMetaSchema.and(
|
||||
const ZCreateFieldSchema = ZEnvelopeFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
recipientId: z.number().describe('The ID of the recipient to create the field for'),
|
||||
envelopeItemId: z
|
||||
|
||||
@ -7,6 +7,7 @@ import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-
|
||||
import { canRecipientFieldsBeModified } from '@documenso/lib/utils/recipients';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { ZGenericSuccessResponse } from '../../schema';
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZDeleteEnvelopeFieldRequestSchema,
|
||||
@ -115,4 +116,6 @@ export const deleteEnvelopeFieldRoute = authenticatedProcedure
|
||||
|
||||
return deletedField;
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../../schema';
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const deleteEnvelopeFieldMeta: TrpcRouteMeta = {
|
||||
@ -16,7 +17,7 @@ export const ZDeleteEnvelopeFieldRequestSchema = z.object({
|
||||
fieldId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteEnvelopeFieldResponseSchema = z.void();
|
||||
export const ZDeleteEnvelopeFieldResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TDeleteEnvelopeFieldRequest = z.infer<typeof ZDeleteEnvelopeFieldRequestSchema>;
|
||||
export type TDeleteEnvelopeFieldResponse = z.infer<typeof ZDeleteEnvelopeFieldResponseSchema>;
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { ZEnvelopeFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
@ -22,7 +22,7 @@ export const updateEnvelopeFieldsMeta: TrpcRouteMeta = {
|
||||
},
|
||||
};
|
||||
|
||||
const ZUpdateFieldSchema = ZFieldAndMetaSchema.and(
|
||||
const ZUpdateFieldSchema = ZEnvelopeFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
id: z.number().describe('The ID of the field to update.'),
|
||||
envelopeItemId: z
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { deleteEnvelopeRecipient } from '@documenso/lib/server-only/recipient/delete-envelope-recipient';
|
||||
|
||||
import { ZGenericSuccessResponse } from '../../schema';
|
||||
import { authenticatedProcedure } from '../../trpc';
|
||||
import {
|
||||
ZDeleteEnvelopeRecipientRequestSchema,
|
||||
@ -27,4 +28,6 @@ export const deleteEnvelopeRecipientRoute = authenticatedProcedure
|
||||
recipientId,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../../schema';
|
||||
import type { TrpcRouteMeta } from '../../trpc';
|
||||
|
||||
export const deleteEnvelopeRecipientMeta: TrpcRouteMeta = {
|
||||
@ -16,7 +17,7 @@ export const ZDeleteEnvelopeRecipientRequestSchema = z.object({
|
||||
recipientId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteEnvelopeRecipientResponseSchema = z.void();
|
||||
export const ZDeleteEnvelopeRecipientResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TDeleteEnvelopeRecipientRequest = z.infer<typeof ZDeleteEnvelopeRecipientRequestSchema>;
|
||||
export type TDeleteEnvelopeRecipientResponse = z.infer<
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||
import { formatSigningLink } from '@documenso/lib/utils/recipients';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@ -22,7 +23,7 @@ export const redistributeEnvelopeRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
|
||||
await resendDocument({
|
||||
const envelope = await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
id: {
|
||||
@ -32,4 +33,18 @@ export const redistributeEnvelopeRoute = authenticatedProcedure
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
id: envelope.id,
|
||||
recipients: envelope.recipients.map((recipient) => ({
|
||||
id: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
signingUrl: formatSigningLink(recipient.token),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSuccessResponseSchema } from '../schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import { ZRecipientWithSigningUrlSchema } from './schema';
|
||||
|
||||
export const redistributeEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
@ -21,7 +23,10 @@ export const ZRedistributeEnvelopeRequestSchema = z.object({
|
||||
.describe('The IDs of the recipients to redistribute the envelope to.'),
|
||||
});
|
||||
|
||||
export const ZRedistributeEnvelopeResponseSchema = z.void();
|
||||
export const ZRedistributeEnvelopeResponseSchema = ZSuccessResponseSchema.extend({
|
||||
id: z.string().describe('The ID of the envelope that was redistributed.'),
|
||||
recipients: ZRecipientWithSigningUrlSchema.array(),
|
||||
});
|
||||
|
||||
export type TRedistributeEnvelopeRequest = z.infer<typeof ZRedistributeEnvelopeRequestSchema>;
|
||||
export type TRedistributeEnvelopeResponse = z.infer<typeof ZRedistributeEnvelopeResponseSchema>;
|
||||
|
||||
@ -8,6 +8,7 @@ import { createEnvelopeItemsRoute } from './create-envelope-items';
|
||||
import { deleteEnvelopeRoute } from './delete-envelope';
|
||||
import { deleteEnvelopeItemRoute } from './delete-envelope-item';
|
||||
import { distributeEnvelopeRoute } from './distribute-envelope';
|
||||
import { downloadEnvelopeItemRoute } from './download-envelope-item';
|
||||
import { duplicateEnvelopeRoute } from './duplicate-envelope';
|
||||
import { createEnvelopeFieldsRoute } from './envelope-fields/create-envelope-fields';
|
||||
import { deleteEnvelopeFieldRoute } from './envelope-fields/delete-envelope-field';
|
||||
@ -46,6 +47,7 @@ export const envelopeRouter = router({
|
||||
createMany: createEnvelopeItemsRoute,
|
||||
updateMany: updateEnvelopeItemsRoute,
|
||||
delete: deleteEnvelopeItemRoute,
|
||||
download: downloadEnvelopeItemRoute,
|
||||
},
|
||||
recipient: {
|
||||
get: getEnvelopeRecipientRoute,
|
||||
|
||||
16
packages/trpc/server/envelope-router/schema.ts
Normal file
16
packages/trpc/server/envelope-router/schema.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import RecipientSchema from '@documenso/prisma/generated/zod/modelSchema/RecipientSchema';
|
||||
|
||||
// Common schemas between envelope routes.
|
||||
|
||||
export const ZRecipientWithSigningUrlSchema = RecipientSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
token: true,
|
||||
role: true,
|
||||
signingOrder: true,
|
||||
}).extend({
|
||||
signingUrl: z.string().describe('The URL which the recipient uses to sign the document.'),
|
||||
});
|
||||
@ -6,6 +6,7 @@ import { sendDocument } from '@documenso/lib/server-only/document/send-document'
|
||||
import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
|
||||
import { createDocumentFromTemplate } from '@documenso/lib/server-only/template/create-document-from-template';
|
||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import { formatSigningLink } from '@documenso/lib/utils/recipients';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@ -26,7 +27,7 @@ export const useEnvelopeRoute = authenticatedProcedure
|
||||
const {
|
||||
envelopeId,
|
||||
externalId,
|
||||
recipients,
|
||||
recipients = [],
|
||||
distributeDocument,
|
||||
customDocumentData = [],
|
||||
folderId,
|
||||
@ -166,5 +167,14 @@ export const useEnvelopeRoute = authenticatedProcedure
|
||||
|
||||
return {
|
||||
id: createdEnvelope.id,
|
||||
recipients: createdEnvelope.recipients.map((recipient) => ({
|
||||
id: recipient.id,
|
||||
name: recipient.name,
|
||||
email: recipient.email,
|
||||
token: recipient.token,
|
||||
role: recipient.role,
|
||||
signingOrder: recipient.signingOrder,
|
||||
signingUrl: formatSigningLink(recipient.token),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
@ -19,6 +19,7 @@ import { ZFieldMetaPrefillFieldsSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import { zodFormData } from '../../utils/zod-form-data';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import { ZRecipientWithSigningUrlSchema } from './schema';
|
||||
|
||||
export const useEnvelopeMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
@ -44,7 +45,8 @@ export const ZUseEnvelopePayloadSchema = z.object({
|
||||
signingOrder: z.number().optional(),
|
||||
}),
|
||||
)
|
||||
.describe('The information of the recipients to create the document with.'),
|
||||
.describe('The information of the recipients to create the document with.')
|
||||
.optional(),
|
||||
distributeDocument: z
|
||||
.boolean()
|
||||
.describe('Whether to create the document as pending and distribute it to recipients.')
|
||||
@ -114,6 +116,7 @@ export const ZUseEnvelopeRequestSchema = zodFormData({
|
||||
|
||||
export const ZUseEnvelopeResponseSchema = z.object({
|
||||
id: z.string().describe('The ID of the created envelope.'),
|
||||
recipients: ZRecipientWithSigningUrlSchema.array(),
|
||||
});
|
||||
|
||||
export type TUseEnvelopePayload = z.infer<typeof ZUseEnvelopePayloadSchema>;
|
||||
|
||||
@ -10,7 +10,7 @@ import { setFieldsForTemplate } from '@documenso/lib/server-only/field/set-field
|
||||
import { signFieldWithToken } from '@documenso/lib/server-only/field/sign-field-with-token';
|
||||
import { updateEnvelopeFields } from '@documenso/lib/server-only/field/update-envelope-fields';
|
||||
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../document-router/schema';
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../schema';
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
import {
|
||||
ZCreateDocumentFieldRequestSchema,
|
||||
|
||||
@ -7,6 +7,7 @@ import { getFolderBreadcrumbs } from '@documenso/lib/server-only/folder/get-fold
|
||||
import { getFolderById } from '@documenso/lib/server-only/folder/get-folder-by-id';
|
||||
import { updateFolder } from '@documenso/lib/server-only/folder/update-folder';
|
||||
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../schema';
|
||||
import { authenticatedProcedure, router } from '../trpc';
|
||||
import {
|
||||
ZCreateFolderRequestSchema,
|
||||
@ -16,10 +17,8 @@ import {
|
||||
ZFindFoldersInternalResponseSchema,
|
||||
ZFindFoldersRequestSchema,
|
||||
ZFindFoldersResponseSchema,
|
||||
ZGenericSuccessResponse,
|
||||
ZGetFoldersResponseSchema,
|
||||
ZGetFoldersSchema,
|
||||
ZSuccessResponseSchema,
|
||||
ZUpdateFolderRequestSchema,
|
||||
ZUpdateFolderResponseSchema,
|
||||
} from './schema';
|
||||
|
||||
@ -5,19 +5,6 @@ import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/typ
|
||||
import { DocumentVisibility } from '@documenso/prisma/generated/types';
|
||||
import FolderSchema from '@documenso/prisma/generated/zod/modelSchema/FolderSchema';
|
||||
|
||||
/**
|
||||
* Required for empty responses since we currently can't 201 requests for our openapi setup.
|
||||
*
|
||||
* Without this it will throw an error in Speakeasy SDK when it tries to parse an empty response.
|
||||
*/
|
||||
export const ZSuccessResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
});
|
||||
|
||||
export const ZGenericSuccessResponse = {
|
||||
success: true,
|
||||
} satisfies z.infer<typeof ZSuccessResponseSchema>;
|
||||
|
||||
export const ZFolderSchema = FolderSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
|
||||
@ -9,7 +9,7 @@ import { setDocumentRecipients } from '@documenso/lib/server-only/recipient/set-
|
||||
import { setTemplateRecipients } from '@documenso/lib/server-only/recipient/set-template-recipients';
|
||||
import { updateEnvelopeRecipients } from '@documenso/lib/server-only/recipient/update-envelope-recipients';
|
||||
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../document-router/schema';
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../schema';
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
import { findRecipientSuggestionsRoute } from './find-recipient-suggestions';
|
||||
import {
|
||||
|
||||
14
packages/trpc/server/schema.ts
Normal file
14
packages/trpc/server/schema.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Required for empty responses since we currently can't 201 requests for our openapi setup.
|
||||
*
|
||||
* Without this it will throw an error in Speakeasy SDK when it tries to parse an empty response.
|
||||
*/
|
||||
export const ZSuccessResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
});
|
||||
|
||||
export const ZGenericSuccessResponse = {
|
||||
success: true,
|
||||
} satisfies z.infer<typeof ZSuccessResponseSchema>;
|
||||
@ -28,7 +28,7 @@ import { mapFieldToLegacyField } from '@documenso/lib/utils/fields';
|
||||
import { mapRecipientToLegacyRecipient } from '@documenso/lib/utils/recipients';
|
||||
import { mapEnvelopeToTemplateLite } from '@documenso/lib/utils/templates';
|
||||
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../document-router/schema';
|
||||
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../schema';
|
||||
import { authenticatedProcedure, maybeAuthenticatedProcedure, router } from '../trpc';
|
||||
import {
|
||||
ZBulkSendTemplateMutationSchema,
|
||||
@ -177,7 +177,19 @@ export const templateRouter = router({
|
||||
|
||||
const { payload, file } = input;
|
||||
|
||||
const { title, folderId } = payload;
|
||||
const {
|
||||
title,
|
||||
folderId,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
type,
|
||||
meta,
|
||||
attachments,
|
||||
} = payload;
|
||||
|
||||
const { id: templateDocumentDataId } = await putNormalizedPdfFileServerSide(file);
|
||||
|
||||
@ -194,13 +206,22 @@ export const templateRouter = router({
|
||||
data: {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title,
|
||||
folderId,
|
||||
envelopeItems: [
|
||||
{
|
||||
documentDataId: templateDocumentDataId,
|
||||
},
|
||||
],
|
||||
folderId,
|
||||
externalId: externalId ?? undefined,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
templateType: type,
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
},
|
||||
meta,
|
||||
attachments,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
|
||||
@ -79,16 +79,6 @@ export const ZTemplateMetaUpsertSchema = z.object({
|
||||
allowDictateNextSigner: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const ZCreateTemplatePayloadSchema = z.object({
|
||||
title: z.string().min(1).trim(),
|
||||
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),
|
||||
@ -234,6 +224,13 @@ export const ZCreateTemplateResponseSchema = z.object({
|
||||
id: z.number(),
|
||||
});
|
||||
|
||||
export const ZCreateTemplatePayloadSchema = ZCreateTemplateV2RequestSchema;
|
||||
|
||||
export const ZCreateTemplateMutationSchema = zodFormData({
|
||||
payload: zfd.json(ZCreateTemplatePayloadSchema),
|
||||
file: zfd.file(),
|
||||
});
|
||||
|
||||
export const ZUpdateTemplateRequestSchema = z.object({
|
||||
templateId: z.number(),
|
||||
data: z
|
||||
@ -280,7 +277,7 @@ export const ZBulkSendTemplateMutationSchema = z.object({
|
||||
sendImmediately: z.boolean(),
|
||||
});
|
||||
|
||||
export type TCreateTemplatePayloadSchema = z.infer<typeof ZCreateTemplatePayloadSchema>;
|
||||
export type TCreateTemplatePayloadSchema = z.input<typeof ZCreateTemplatePayloadSchema>;
|
||||
export type TCreateTemplateMutationSchema = z.infer<typeof ZCreateTemplateMutationSchema>;
|
||||
export type TDuplicateTemplateMutationSchema = z.infer<typeof ZDuplicateTemplateMutationSchema>;
|
||||
export type TDeleteTemplateMutationSchema = z.infer<typeof ZDeleteTemplateMutationSchema>;
|
||||
|
||||
@ -24,11 +24,17 @@ const getMultipartBody = async (req: Request) => {
|
||||
|
||||
const data: Record<string, unknown> = {};
|
||||
|
||||
for (const key of formData.keys()) {
|
||||
const values = formData.getAll(key);
|
||||
for (const [key, value] of formData.entries()) {
|
||||
// !: Handles cases where our generated SDKs send key[] syntax for arrays.
|
||||
const normalizedKey = key.endsWith('[]') ? key.slice(0, -2) : key;
|
||||
|
||||
// Return array for multiple values, single value otherwise (matches URL-encoded behavior)
|
||||
data[key] = values.length > 1 ? values : values[0];
|
||||
if (data[normalizedKey] === undefined) {
|
||||
data[normalizedKey] = value;
|
||||
} else if (Array.isArray(data[normalizedKey])) {
|
||||
data[normalizedKey].push(value);
|
||||
} else {
|
||||
data[normalizedKey] = [data[normalizedKey], value];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import type { SelectProps } from '@radix-ui/react-select';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
@ -27,6 +28,8 @@ export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVi
|
||||
{ currentTeamMemberRole, isTeamSettings = false, disabled, canUpdateVisibility, ...props },
|
||||
ref,
|
||||
) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const isAdmin = currentTeamMemberRole === TeamMemberRole.ADMIN;
|
||||
const isManager = currentTeamMemberRole === TeamMemberRole.MANAGER;
|
||||
const canEdit = isTeamSettings || canUpdateVisibility;
|
||||
@ -34,7 +37,7 @@ export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVi
|
||||
return (
|
||||
<Select {...props} disabled={!canEdit || disabled}>
|
||||
<SelectTrigger ref={ref} className="bg-background text-muted-foreground">
|
||||
<SelectValue data-testid="documentVisibilitySelectValue" placeholder="Everyone" />
|
||||
<SelectValue data-testid="documentVisibilitySelectValue" placeholder={t`Everyone`} />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
|
||||
@ -419,7 +419,7 @@ export const AddSettingsFormPartial = ({
|
||||
void handleAutoSave();
|
||||
}}
|
||||
className="bg-background w-full"
|
||||
emptySelectionPlaceholder="Select signature types"
|
||||
emptySelectionPlaceholder={t`Select signature types`}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { validateFields as validateDateFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
|
||||
import { type TDateFieldMeta as DateFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
@ -25,7 +23,7 @@ export const DateFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: DateFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { t } = useLingui();
|
||||
|
||||
// const handleInput = (field: keyof DateFieldMeta, value: string | boolean) => {
|
||||
// if (field === 'fontSize') {
|
||||
@ -67,7 +65,7 @@ export const DateFieldAdvancedSettings = ({
|
||||
id="fontSize"
|
||||
type="number"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Field font size`)}
|
||||
placeholder={t`Field font size`}
|
||||
value={fieldState.fontSize}
|
||||
onChange={(e) => handleInput('fontSize', e.target.value)}
|
||||
min={8}
|
||||
@ -85,7 +83,7 @@ export const DateFieldAdvancedSettings = ({
|
||||
onValueChange={(value) => handleInput('textAlign', value)}
|
||||
>
|
||||
<SelectTrigger className="bg-background mt-2">
|
||||
<SelectValue placeholder="Select text align" />
|
||||
<SelectValue placeholder={t`Select text align`} />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { validateFields as validateEmailFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
|
||||
import { type TEmailFieldMeta as EmailFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
@ -25,7 +23,7 @@ export const EmailFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: EmailFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { t } = useLingui();
|
||||
|
||||
const handleInput = (field: keyof EmailFieldMeta, value: string | boolean) => {
|
||||
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
|
||||
@ -49,7 +47,7 @@ export const EmailFieldAdvancedSettings = ({
|
||||
id="fontSize"
|
||||
type="number"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Field font size`)}
|
||||
placeholder={t`Field font size`}
|
||||
value={fieldState.fontSize}
|
||||
onChange={(e) => handleInput('fontSize', e.target.value)}
|
||||
min={8}
|
||||
@ -67,7 +65,7 @@ export const EmailFieldAdvancedSettings = ({
|
||||
onValueChange={(value) => handleInput('textAlign', value)}
|
||||
>
|
||||
<SelectTrigger className="bg-background mt-2">
|
||||
<SelectValue placeholder="Select text align" />
|
||||
<SelectValue placeholder={t`Select text align`} />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { validateFields as validateInitialsFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
|
||||
import { type TInitialsFieldMeta as InitialsFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
@ -20,7 +18,7 @@ export const InitialsFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: InitialsFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { t } = useLingui();
|
||||
|
||||
const handleInput = (field: keyof InitialsFieldMeta, value: string | boolean) => {
|
||||
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
|
||||
@ -44,7 +42,7 @@ export const InitialsFieldAdvancedSettings = ({
|
||||
id="fontSize"
|
||||
type="number"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Field font size`)}
|
||||
placeholder={t`Field font size`}
|
||||
value={fieldState.fontSize}
|
||||
onChange={(e) => handleInput('fontSize', e.target.value)}
|
||||
min={8}
|
||||
@ -62,7 +60,7 @@ export const InitialsFieldAdvancedSettings = ({
|
||||
onValueChange={(value) => handleInput('textAlign', value)}
|
||||
>
|
||||
<SelectTrigger className="bg-background mt-2">
|
||||
<SelectValue placeholder="Select text align" />
|
||||
<SelectValue placeholder={t`Select text align`} />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { validateFields as validateNameFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
|
||||
import { type TNameFieldMeta as NameFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
@ -25,7 +23,7 @@ export const NameFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: NameFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { t } = useLingui();
|
||||
|
||||
const handleInput = (field: keyof NameFieldMeta, value: string | boolean) => {
|
||||
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
|
||||
@ -49,7 +47,7 @@ export const NameFieldAdvancedSettings = ({
|
||||
id="fontSize"
|
||||
type="number"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Field font size`)}
|
||||
placeholder={t`Field font size`}
|
||||
value={fieldState.fontSize}
|
||||
onChange={(e) => handleInput('fontSize', e.target.value)}
|
||||
min={8}
|
||||
@ -67,7 +65,7 @@ export const NameFieldAdvancedSettings = ({
|
||||
onValueChange={(value) => handleInput('textAlign', value)}
|
||||
>
|
||||
<SelectTrigger className="bg-background mt-2">
|
||||
<SelectValue placeholder="Select text align" />
|
||||
<SelectValue placeholder={t`Select text align`} />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||
@ -32,7 +30,7 @@ export const NumberFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: NumberFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { t } = useLingui();
|
||||
|
||||
const [showValidation, setShowValidation] = useState(false);
|
||||
|
||||
@ -68,7 +66,7 @@ export const NumberFieldAdvancedSettings = ({
|
||||
<Input
|
||||
id="label"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Label`)}
|
||||
placeholder={t`Label`}
|
||||
value={fieldState.label}
|
||||
onChange={(e) => handleFieldChange('label', e.target.value)}
|
||||
/>
|
||||
@ -80,7 +78,7 @@ export const NumberFieldAdvancedSettings = ({
|
||||
<Input
|
||||
id="placeholder"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Placeholder`)}
|
||||
placeholder={t`Placeholder`}
|
||||
value={fieldState.placeholder}
|
||||
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
|
||||
/>
|
||||
@ -92,7 +90,7 @@ export const NumberFieldAdvancedSettings = ({
|
||||
<Input
|
||||
id="value"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Value`)}
|
||||
placeholder={t`Value`}
|
||||
value={fieldState.value}
|
||||
onChange={(e) => handleInput('value', e.target.value)}
|
||||
/>
|
||||
@ -106,7 +104,7 @@ export const NumberFieldAdvancedSettings = ({
|
||||
onValueChange={(val) => handleInput('numberFormat', val)}
|
||||
>
|
||||
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
|
||||
<SelectValue placeholder={_(msg`Field format`)} />
|
||||
<SelectValue placeholder={t`Field format`} />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
{numberFormatValues.map((item, index) => (
|
||||
@ -126,7 +124,7 @@ export const NumberFieldAdvancedSettings = ({
|
||||
id="fontSize"
|
||||
type="number"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Field font size`)}
|
||||
placeholder={t`Field font size`}
|
||||
value={fieldState.fontSize}
|
||||
onChange={(e) => handleInput('fontSize', e.target.value)}
|
||||
min={8}
|
||||
@ -144,7 +142,7 @@ export const NumberFieldAdvancedSettings = ({
|
||||
onValueChange={(value) => handleInput('textAlign', value)}
|
||||
>
|
||||
<SelectTrigger className="bg-background mt-2">
|
||||
<SelectValue placeholder="Select text align" />
|
||||
<SelectValue placeholder={t`Select text align`} />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
@ -198,7 +196,7 @@ export const NumberFieldAdvancedSettings = ({
|
||||
<Input
|
||||
id="minValue"
|
||||
className="bg-background mt-2"
|
||||
placeholder="E.g. 0"
|
||||
placeholder={t`E.g. 0`}
|
||||
value={fieldState.minValue ?? ''}
|
||||
onChange={(e) => handleInput('minValue', e.target.value)}
|
||||
/>
|
||||
@ -210,7 +208,7 @@ export const NumberFieldAdvancedSettings = ({
|
||||
<Input
|
||||
id="maxValue"
|
||||
className="bg-background mt-2"
|
||||
placeholder="E.g. 100"
|
||||
placeholder={t`E.g. 100`}
|
||||
value={fieldState.maxValue ?? ''}
|
||||
onChange={(e) => handleInput('maxValue', e.target.value)}
|
||||
/>
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
||||
import { type TTextFieldMeta as TextFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
@ -27,7 +25,7 @@ export const TextFieldAdvancedSettings = ({
|
||||
handleFieldChange,
|
||||
handleErrors,
|
||||
}: TextFieldAdvancedSettingsProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { t } = useLingui();
|
||||
|
||||
const handleInput = (field: keyof TextFieldMeta, value: string | boolean) => {
|
||||
const text = field === 'text' ? String(value) : (fieldState.text ?? '');
|
||||
@ -58,7 +56,7 @@ export const TextFieldAdvancedSettings = ({
|
||||
<Input
|
||||
id="label"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Field label`)}
|
||||
placeholder={t`Field label`}
|
||||
value={fieldState.label}
|
||||
onChange={(e) => handleFieldChange('label', e.target.value)}
|
||||
/>
|
||||
@ -70,7 +68,7 @@ export const TextFieldAdvancedSettings = ({
|
||||
<Input
|
||||
id="placeholder"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Field placeholder`)}
|
||||
placeholder={t`Field placeholder`}
|
||||
value={fieldState.placeholder}
|
||||
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
|
||||
/>
|
||||
@ -83,7 +81,7 @@ export const TextFieldAdvancedSettings = ({
|
||||
<Textarea
|
||||
id="text"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Add text to the field`)}
|
||||
placeholder={t`Add text to the field`}
|
||||
value={fieldState.text}
|
||||
onChange={(e) => handleInput('text', e.target.value)}
|
||||
/>
|
||||
@ -98,7 +96,7 @@ export const TextFieldAdvancedSettings = ({
|
||||
type="number"
|
||||
min={0}
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Field character limit`)}
|
||||
placeholder={t`Field character limit`}
|
||||
value={fieldState.characterLimit}
|
||||
onChange={(e) => handleInput('characterLimit', e.target.value)}
|
||||
/>
|
||||
@ -112,7 +110,7 @@ export const TextFieldAdvancedSettings = ({
|
||||
id="fontSize"
|
||||
type="number"
|
||||
className="bg-background mt-2"
|
||||
placeholder={_(msg`Field font size`)}
|
||||
placeholder={t`Field font size`}
|
||||
value={fieldState.fontSize}
|
||||
onChange={(e) => handleInput('fontSize', e.target.value)}
|
||||
min={8}
|
||||
@ -136,7 +134,7 @@ export const TextFieldAdvancedSettings = ({
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="bg-background mt-2">
|
||||
<SelectValue placeholder="Select text align" />
|
||||
<SelectValue placeholder={t`Select text align`} />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
@ -43,7 +44,7 @@ type MultiSelectComboboxProps<T = OptionValue> = {
|
||||
* - Clear all button
|
||||
*/
|
||||
export function MultiSelectCombobox<T = OptionValue>({
|
||||
emptySelectionPlaceholder = 'Select values...',
|
||||
emptySelectionPlaceholder = t`Select values...`,
|
||||
enableClearAllButton,
|
||||
enableSearch = true,
|
||||
className,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
@ -34,8 +34,8 @@ export const RecipientSelector = ({
|
||||
const { _ } = useLingui();
|
||||
const [showRecipientsSelector, setShowRecipientsSelector] = useState(false);
|
||||
|
||||
const recipientsByRole = useCallback(() => {
|
||||
const recipientsByRole: Record<RecipientRole, Recipient[]> = {
|
||||
const recipientsByRole = useMemo(() => {
|
||||
const recipientsWithRole: Record<RecipientRole, Recipient[]> = {
|
||||
CC: [],
|
||||
VIEWER: [],
|
||||
SIGNER: [],
|
||||
@ -44,14 +44,14 @@ export const RecipientSelector = ({
|
||||
};
|
||||
|
||||
recipients.forEach((recipient) => {
|
||||
recipientsByRole[recipient.role].push(recipient);
|
||||
recipientsWithRole[recipient.role].push(recipient);
|
||||
});
|
||||
|
||||
return recipientsByRole;
|
||||
return recipientsWithRole;
|
||||
}, [recipients]);
|
||||
|
||||
const recipientsByRoleToDisplay = useCallback(() => {
|
||||
return Object.entries(recipientsByRole())
|
||||
const recipientsByRoleToDisplay = useMemo(() => {
|
||||
return Object.entries(recipientsByRole)
|
||||
.filter(
|
||||
([role]) =>
|
||||
role !== RecipientRole.CC &&
|
||||
@ -71,6 +71,28 @@ export const RecipientSelector = ({
|
||||
);
|
||||
}, [recipientsByRole]);
|
||||
|
||||
const getRecipientLabel = useCallback(
|
||||
(recipient: Recipient) => {
|
||||
if (recipient.name && recipient.email) {
|
||||
return `${recipient.name} (${recipient.email})`;
|
||||
}
|
||||
|
||||
if (recipient.name) {
|
||||
return recipient.name;
|
||||
}
|
||||
|
||||
if (recipient.email) {
|
||||
return recipient.email;
|
||||
}
|
||||
|
||||
// Since objects are basically pointers we can use `indexOf` rather than `findIndex`
|
||||
const index = recipients.indexOf(recipient);
|
||||
|
||||
return `Recipient ${index + 1}`;
|
||||
},
|
||||
[recipients, selectedRecipient],
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={showRecipientsSelector} onOpenChange={setShowRecipientsSelector}>
|
||||
<PopoverTrigger asChild>
|
||||
@ -89,16 +111,12 @@ export const RecipientSelector = ({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{selectedRecipient?.email && (
|
||||
{selectedRecipient && (
|
||||
<span className="flex-1 truncate text-left">
|
||||
{selectedRecipient?.name} ({selectedRecipient?.email})
|
||||
{getRecipientLabel(selectedRecipient)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!selectedRecipient?.email && (
|
||||
<span className="flex-1 truncate text-left">{selectedRecipient?.email}</span>
|
||||
)}
|
||||
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@ -113,7 +131,7 @@ export const RecipientSelector = ({
|
||||
</span>
|
||||
</CommandEmpty>
|
||||
|
||||
{recipientsByRoleToDisplay().map(([role, roleRecipients], roleIndex) => (
|
||||
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
|
||||
<CommandGroup key={roleIndex}>
|
||||
<div className="text-muted-foreground mb-1 ml-2 mt-2 text-xs font-medium">
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
|
||||
@ -154,13 +172,7 @@ export const RecipientSelector = ({
|
||||
'text-foreground/80': recipient.id === selectedRecipient?.id,
|
||||
})}
|
||||
>
|
||||
{recipient.name && (
|
||||
<span title={`${recipient.name} (${recipient.email})`}>
|
||||
{recipient.name} ({recipient.email})
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!recipient.name && <span title={recipient.email}>{recipient.email}</span>}
|
||||
{getRecipientLabel(recipient)}
|
||||
</span>
|
||||
|
||||
<div className="ml-auto flex items-center justify-center">
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
export type SignaturePadTypeProps = {
|
||||
@ -9,6 +11,7 @@ export type SignaturePadTypeProps = {
|
||||
};
|
||||
|
||||
export const SignaturePadType = ({ className, value, onChange }: SignaturePadTypeProps) => {
|
||||
const { t } = useLingui();
|
||||
// Colors don't actually work for text.
|
||||
const [selectedColor, setSelectedColor] = useState('black');
|
||||
|
||||
@ -16,7 +19,7 @@ export const SignaturePadType = ({ className, value, onChange }: SignaturePadTyp
|
||||
<div className={cn('flex h-full w-full items-center justify-center', className)}>
|
||||
<input
|
||||
data-testid="signature-pad-type-input"
|
||||
placeholder="Type your signature"
|
||||
placeholder={t`Type your signature`}
|
||||
className="font-signature w-full bg-transparent px-4 text-center text-7xl text-black placeholder:text-4xl focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:text-white"
|
||||
// style={{ color: selectedColor }}
|
||||
value={value}
|
||||
|
||||
@ -147,21 +147,21 @@ export const SignaturePad = ({
|
||||
{drawSignatureEnabled && (
|
||||
<TabsTrigger value="draw">
|
||||
<SignatureIcon className="mr-2 size-4" />
|
||||
<Trans>Draw</Trans>
|
||||
<Trans context="Draw signature">Draw</Trans>
|
||||
</TabsTrigger>
|
||||
)}
|
||||
|
||||
{typedSignatureEnabled && (
|
||||
<TabsTrigger value="text">
|
||||
<KeyboardIcon className="mr-2 size-4" />
|
||||
<Trans>Type</Trans>
|
||||
<Trans context="Type signature">Type</Trans>
|
||||
</TabsTrigger>
|
||||
)}
|
||||
|
||||
{uploadSignatureEnabled && (
|
||||
<TabsTrigger value="image">
|
||||
<UploadCloudIcon className="mr-2 size-4" />
|
||||
<Trans>Upload</Trans>
|
||||
<Trans context="Upload signature">Upload</Trans>
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { DocumentVisibility, TeamMemberRole } from '@prisma/client';
|
||||
import { DocumentDistributionMethod, type Field, type Recipient } from '@prisma/client';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
@ -425,7 +424,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
void handleAutoSave();
|
||||
}}
|
||||
className="bg-background w-full"
|
||||
emptySelectionPlaceholder="Select signature types"
|
||||
emptySelectionPlaceholder={t`Select signature types`}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user