feat: add envelope editor

This commit is contained in:
David Nguyen
2025-10-12 23:35:54 +11:00
parent bf89bc781b
commit 0da8e7dbc6
307 changed files with 24657 additions and 3681 deletions

View File

@ -1,5 +1,5 @@
import { type Page, expect, test } from '@playwright/test';
import type { Team, Template } from '@prisma/client';
import type { Envelope, Team } from '@prisma/client';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { prisma } from '@documenso/prisma';
@ -14,7 +14,7 @@ import { apiSignin } from '../fixtures/authentication';
const completeTemplateFlowWithDuplicateRecipients = async (options: {
page: Page;
team: Team;
template: Template;
template: Envelope;
}) => {
const { page, team, template } = options;
// Step 1: Settings - Continue with defaults
@ -131,20 +131,20 @@ test.describe('[TEMPLATE_FLOW]: Duplicate Recipients', () => {
// Create document
await page.getByRole('button', { name: 'Create and send' }).click();
await page.waitForURL(new RegExp(`/t/${team.url}/documents/\\d+`));
await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));
// Get the document ID from URL for database queries
const url = page.url();
const documentIdMatch = url.match(/\/documents\/(\d+)/);
const documentIdMatch = url.match(/\/documents\/envelope_(.*)/);
const documentId = documentIdMatch ? parseInt(documentIdMatch[1]) : null;
const envelopeId = documentIdMatch ? documentIdMatch[1] : null;
expect(documentId).not.toBeNull();
expect(envelopeId).not.toBeNull();
// Get recipients directly from database
const recipients = await prisma.recipient.findMany({
where: {
documentId: documentId!,
envelopeId: `envelope_${envelopeId}`,
},
});

View File

@ -2,6 +2,7 @@ import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
@ -14,7 +15,7 @@ const setupTemplateAndNavigateToFieldsStep = async (page: Page) => {
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
redirectPath: `/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}/edit`,
});
await page.getByRole('button', { name: 'Continue' }).click();
@ -85,7 +86,10 @@ test.describe('AutoSave Fields Step', () => {
await expect(async () => {
const retrievedFields = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -152,7 +156,10 @@ test.describe('AutoSave Fields Step', () => {
await expect(async () => {
const retrievedFields = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -218,7 +225,11 @@ test.describe('AutoSave Fields Step', () => {
await expect(async () => {
const retrievedFields = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -270,7 +281,10 @@ test.describe('AutoSave Fields Step', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});

View File

@ -2,6 +2,7 @@ import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
@ -16,7 +17,7 @@ const setupTemplate = async (page: Page) => {
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
redirectPath: `/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}/edit`,
});
return { user, team, template };
@ -41,7 +42,10 @@ test.describe('AutoSave Settings Step - Templates', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -65,7 +69,10 @@ test.describe('AutoSave Settings Step - Templates', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -87,7 +94,10 @@ test.describe('AutoSave Settings Step - Templates', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -109,7 +119,10 @@ test.describe('AutoSave Settings Step - Templates', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -131,7 +144,10 @@ test.describe('AutoSave Settings Step - Templates', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -154,7 +170,10 @@ test.describe('AutoSave Settings Step - Templates', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -175,7 +194,10 @@ test.describe('AutoSave Settings Step - Templates', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -197,7 +219,10 @@ test.describe('AutoSave Settings Step - Templates', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
@ -229,7 +254,10 @@ test.describe('AutoSave Settings Step - Templates', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});

View File

@ -1,8 +1,11 @@
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { EnvelopeType } from '@prisma/client';
import { getRecipientsForTemplate } from '@documenso/lib/server-only/recipient/get-recipients-for-template';
import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
import { prisma } from '@documenso/prisma';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
@ -17,7 +20,7 @@ const setupTemplateAndNavigateToSignersStep = async (page: Page) => {
await apiSignin({
page,
email: user.email,
redirectPath: `/templates/${template.id}/edit`,
redirectPath: `/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}/edit`,
});
await page.getByRole('button', { name: 'Continue' }).click();
@ -47,7 +50,7 @@ test.describe('AutoSave Signers Step - Templates', () => {
await expect(async () => {
const retrievedRecipients = await getRecipientsForTemplate({
templateId: template.id,
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
userId: user.id,
teamId: team.id,
});
@ -71,7 +74,7 @@ test.describe('AutoSave Signers Step - Templates', () => {
await expect(async () => {
const retrievedRecipients = await getRecipientsForTemplate({
templateId: template.id,
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
userId: user.id,
teamId: team.id,
});
@ -99,7 +102,7 @@ test.describe('AutoSave Signers Step - Templates', () => {
await expect(async () => {
const retrievedRecipients = await getRecipientsForTemplate({
templateId: template.id,
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
userId: user.id,
teamId: team.id,
});
@ -152,13 +155,16 @@ test.describe('AutoSave Signers Step - Templates', () => {
await expect(async () => {
const retrievedTemplate = await getTemplateById({
id: template.id,
id: {
type: 'envelopeId',
id: template.id,
},
userId: user.id,
teamId: team.id,
});
const retrievedRecipients = await getRecipientsForTemplate({
templateId: template.id,
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
userId: user.id,
teamId: team.id,
});
@ -172,3 +178,36 @@ test.describe('AutoSave Signers Step - Templates', () => {
}).toPass();
});
});
export interface GetRecipientsForTemplateOptions {
templateId: number;
userId: number;
teamId: number;
}
const getRecipientsForTemplate = async ({
templateId,
userId,
teamId,
}: GetRecipientsForTemplateOptions) => {
const { envelopeWhereInput } = await getEnvelopeWhereInput({
id: {
type: 'templateId',
id: templateId,
},
type: EnvelopeType.TEMPLATE,
userId,
teamId,
});
const recipients = await prisma.recipient.findMany({
where: {
envelope: envelopeWhereInput,
},
orderBy: {
id: 'asc',
},
});
return recipients;
};

View File

@ -15,7 +15,7 @@ test('[TEMPLATE_FLOW]: add settings', async ({ page }) => {
await apiSignin({
page,
email: user.email,
redirectPath: `/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}/edit`,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Set title.
@ -49,7 +49,7 @@ test('[TEMPLATE_FLOW] add document visibility settings', async ({ page }) => {
await apiSignin({
page,
email: user.email,
redirectPath: `/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}/edit`,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Set document visibility.
@ -64,9 +64,7 @@ test('[TEMPLATE_FLOW] add document visibility settings', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Add Placeholders' })).toBeVisible();
// Navigate back to the edit page to check that the settings are saved correctly.
await page.goto(
`/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}/edit`,
);
await page.goto(`/t/${team.url}/templates/${template.id}/edit`);
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
await expect(page.getByTestId('documentVisibilitySelectValue')).toContainText(
@ -99,7 +97,7 @@ test('[TEMPLATE_FLOW] team member visibility permissions', async ({ page }) => {
await apiSignin({
page,
email: managerUser.email,
redirectPath: `/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}/edit`,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Manager should be able to set visibility to managers and above
@ -118,12 +116,13 @@ test('[TEMPLATE_FLOW] team member visibility permissions', async ({ page }) => {
await apiSignin({
page,
email: memberUser.email,
redirectPath: `/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}/edit`,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// A regular member should not be able to see the template.
// They should be redirected to the templates page.
expect(new URL(page.url()).pathname).toBe(`/t/${team.url}/templates`);
await expect(page.getByText('Not Found').first()).toBeVisible();
await page.goto(`/t/${team.url}/templates`);
// Create a new template with 'everyone' visibility
const everyoneTemplate = await seedBlankTemplate(owner, team.id, {

View File

@ -1,6 +1,5 @@
import { expect, test } from '@playwright/test';
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
@ -80,7 +79,7 @@ test('[TEMPLATE_FLOW]: add placeholder', async ({ page }) => {
await apiSignin({
page,
email: user.email,
redirectPath: `/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}/edit`,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Save the settings by going to the next step.