mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 15:53:02 +10:00
fix: add document visibility to template (#1566)
Adds the visibility property to templates
This commit is contained in:
@ -166,6 +166,7 @@ export const EditTemplateForm = ({
|
|||||||
data: {
|
data: {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
externalId: data.externalId || null,
|
externalId: data.externalId || null,
|
||||||
|
visibility: data.visibility,
|
||||||
globalAccessAuth: data.globalAccessAuth ?? null,
|
globalAccessAuth: data.globalAccessAuth ?? null,
|
||||||
globalActionAuth: data.globalActionAuth ?? null,
|
globalActionAuth: data.globalActionAuth ?? null,
|
||||||
},
|
},
|
||||||
@ -296,6 +297,7 @@ export const EditTemplateForm = ({
|
|||||||
<AddTemplateSettingsFormPartial
|
<AddTemplateSettingsFormPartial
|
||||||
key={recipients.length}
|
key={recipients.length}
|
||||||
template={template}
|
template={template}
|
||||||
|
currentTeamMemberRole={team?.currentTeamMember?.role}
|
||||||
documentFlow={documentFlow.settings}
|
documentFlow={documentFlow.settings}
|
||||||
recipients={recipients}
|
recipients={recipients}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||||
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
|
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
|
||||||
import { seedTeam } from '@documenso/prisma/seed/teams';
|
import { seedTeam } from '@documenso/prisma/seed/teams';
|
||||||
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
|
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
|
||||||
@ -157,3 +159,109 @@ test('[TEMPLATE_FLOW]: add settings', async ({ page }) => {
|
|||||||
await expect(page.getByLabel('Title')).toHaveValue('New Title');
|
await expect(page.getByLabel('Title')).toHaveValue('New Title');
|
||||||
await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account');
|
await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('[TEMPLATE_FLOW] add document visibility settings', async ({ page }) => {
|
||||||
|
const { owner, ...team } = await seedTeam({
|
||||||
|
createTeamMembers: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const template = await seedBlankTemplate(owner, {
|
||||||
|
createTemplateOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: owner.email,
|
||||||
|
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set document visibility.
|
||||||
|
await page.getByTestId('documentVisibilitySelectValue').click();
|
||||||
|
await page.getByLabel('Managers and above').click();
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toContainText(
|
||||||
|
'Managers and above',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save the settings by going to the next step.
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
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/${template.id}/edit`);
|
||||||
|
|
||||||
|
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible();
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toContainText(
|
||||||
|
'Managers and above',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[TEMPLATE_FLOW] team member visibility permissions', async ({ page }) => {
|
||||||
|
const team = await seedTeam({
|
||||||
|
createTeamMembers: 2, // Create an additional member to test different roles
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.teamMember.update({
|
||||||
|
where: {
|
||||||
|
id: team.members[1].id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
role: TeamMemberRole.MANAGER,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const owner = team.owner;
|
||||||
|
const managerUser = team.members[1].user;
|
||||||
|
const memberUser = team.members[2].user;
|
||||||
|
|
||||||
|
const template = await seedBlankTemplate(owner, {
|
||||||
|
createTemplateOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test as manager
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: managerUser.email,
|
||||||
|
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manager should be able to set visibility to managers and above
|
||||||
|
await page.getByTestId('documentVisibilitySelectValue').click();
|
||||||
|
await page.getByLabel('Managers and above').click();
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toContainText(
|
||||||
|
'Managers and above',
|
||||||
|
);
|
||||||
|
await expect(page.getByText('Admins only')).toBeDisabled();
|
||||||
|
|
||||||
|
// Save and verify
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
await expect(page.getByRole('heading', { name: 'Add Placeholders' })).toBeVisible();
|
||||||
|
|
||||||
|
// Test as regular member
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: memberUser.email,
|
||||||
|
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Regular member should not be able to modify visibility when set to managers and above
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
|
||||||
|
|
||||||
|
// Create a new template with 'everyone' visibility
|
||||||
|
const everyoneTemplate = await seedBlankTemplate(owner, {
|
||||||
|
createTemplateOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: 'EVERYONE',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Navigate to the new template
|
||||||
|
await page.goto(`/t/${team.url}/templates/${everyoneTemplate.id}/edit`);
|
||||||
|
|
||||||
|
// Regular member should be able to see but not modify visibility
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toBeDisabled();
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toContainText('Everyone');
|
||||||
|
});
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import path from 'path';
|
|||||||
|
|
||||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentDataType } from '@documenso/prisma/client';
|
import { DocumentDataType, TeamMemberRole } from '@documenso/prisma/client';
|
||||||
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
|
import { seedUserSubscription } from '@documenso/prisma/seed/subscriptions';
|
||||||
import { seedTeam } from '@documenso/prisma/seed/teams';
|
import { seedTeam } from '@documenso/prisma/seed/teams';
|
||||||
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
|
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
|
||||||
@ -529,3 +529,90 @@ test('[TEMPLATE]: should create a document from a template using template docume
|
|||||||
);
|
);
|
||||||
expect(document.documentData.type).toEqual(templateWithData.templateDocumentData.type);
|
expect(document.documentData.type).toEqual(templateWithData.templateDocumentData.type);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('[TEMPLATE]: should persist document visibility when creating from template', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const { owner, ...team } = await seedTeam({
|
||||||
|
createTeamMembers: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const template = await seedBlankTemplate(owner, {
|
||||||
|
createTemplateOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: owner.email,
|
||||||
|
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set template title and visibility
|
||||||
|
await page.getByLabel('Title').fill('TEMPLATE_WITH_VISIBILITY');
|
||||||
|
await page.getByTestId('documentVisibilitySelectValue').click();
|
||||||
|
await page.getByLabel('Managers and above').click();
|
||||||
|
await expect(page.getByTestId('documentVisibilitySelectValue')).toContainText(
|
||||||
|
'Managers and above',
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
|
||||||
|
|
||||||
|
// Add a signer
|
||||||
|
await page.getByPlaceholder('Email').fill('recipient@documenso.com');
|
||||||
|
await page.getByPlaceholder('Name').fill('Recipient');
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Continue' }).click();
|
||||||
|
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Save template' }).click();
|
||||||
|
|
||||||
|
// Test creating document as team manager
|
||||||
|
await prisma.teamMember.update({
|
||||||
|
where: {
|
||||||
|
id: team.members[1].id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
role: TeamMemberRole.MANAGER,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const managerUser = team.members[1].user;
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: managerUser.email,
|
||||||
|
redirectPath: `/t/${team.url}/templates`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Use Template' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Create as draft' }).click();
|
||||||
|
|
||||||
|
// Review that the document was created with the correct visibility
|
||||||
|
await page.waitForURL(/documents/);
|
||||||
|
|
||||||
|
const documentId = Number(page.url().split('/').pop());
|
||||||
|
|
||||||
|
const document = await prisma.document.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: documentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(document.title).toEqual('TEMPLATE_WITH_VISIBILITY');
|
||||||
|
expect(document.visibility).toEqual('MANAGER_AND_ABOVE');
|
||||||
|
expect(document.teamId).toEqual(team.id);
|
||||||
|
|
||||||
|
// Test that regular member cannot create document from restricted template
|
||||||
|
const memberUser = team.members[2].user;
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: memberUser.email,
|
||||||
|
redirectPath: `/t/${team.url}/templates`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Template should not be visible to regular member
|
||||||
|
await expect(page.getByRole('button', { name: 'Use Template' })).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|||||||
@ -67,6 +67,8 @@ test('[DIRECT_TEMPLATES]: create direct link for template', async ({ page }) =>
|
|||||||
await page.getByRole('button', { name: 'Enable direct link signing' }).click();
|
await page.getByRole('button', { name: 'Enable direct link signing' }).click();
|
||||||
await page.getByRole('button', { name: 'Create one automatically' }).click();
|
await page.getByRole('button', { name: 'Create one automatically' }).click();
|
||||||
await expect(page.getByRole('heading', { name: 'Direct Link Signing' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Direct Link Signing' })).toBeVisible();
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
await page.getByTestId('btn-dialog-close').click();
|
await page.getByTestId('btn-dialog-close').click();
|
||||||
|
|
||||||
// Expect badge to appear.
|
// Expect badge to appear.
|
||||||
|
|||||||
@ -213,7 +213,7 @@ export const createDocumentFromTemplate = async ({
|
|||||||
globalAccessAuth: templateAuthOptions.globalAccessAuth,
|
globalAccessAuth: templateAuthOptions.globalAccessAuth,
|
||||||
globalActionAuth: templateAuthOptions.globalActionAuth,
|
globalActionAuth: templateAuthOptions.globalActionAuth,
|
||||||
}),
|
}),
|
||||||
visibility: template.team?.teamGlobalSettings?.documentVisibility,
|
visibility: template.visibility || template.team?.teamGlobalSettings?.documentVisibility,
|
||||||
documentMeta: {
|
documentMeta: {
|
||||||
create: {
|
create: {
|
||||||
subject: override?.subject || template.templateMeta?.subject,
|
subject: override?.subject || template.templateMeta?.subject,
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
|
import { match } from 'ts-pattern';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import type { Prisma, Template } from '@documenso/prisma/client';
|
import {
|
||||||
|
DocumentVisibility,
|
||||||
|
type Prisma,
|
||||||
|
TeamMemberRole,
|
||||||
|
type Template,
|
||||||
|
} from '@documenso/prisma/client';
|
||||||
import {
|
import {
|
||||||
DocumentDataSchema,
|
DocumentDataSchema,
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
@ -12,6 +18,7 @@ import {
|
|||||||
TemplateSchema,
|
TemplateSchema,
|
||||||
} from '@documenso/prisma/generated/zod';
|
} from '@documenso/prisma/generated/zod';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params';
|
import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params';
|
||||||
|
|
||||||
export type FindTemplatesOptions = {
|
export type FindTemplatesOptions = {
|
||||||
@ -52,28 +59,58 @@ export const findTemplates = async ({
|
|||||||
page = 1,
|
page = 1,
|
||||||
perPage = 10,
|
perPage = 10,
|
||||||
}: FindTemplatesOptions): Promise<TFindTemplatesResponse> => {
|
}: FindTemplatesOptions): Promise<TFindTemplatesResponse> => {
|
||||||
let whereFilter: Prisma.TemplateWhereInput = {
|
const whereFilter: Prisma.TemplateWhereInput[] = [];
|
||||||
userId,
|
|
||||||
teamId: null,
|
if (teamId === undefined) {
|
||||||
type,
|
whereFilter.push({ userId, teamId: null });
|
||||||
};
|
}
|
||||||
|
|
||||||
if (teamId !== undefined) {
|
if (teamId !== undefined) {
|
||||||
whereFilter = {
|
const teamMember = await prisma.teamMember.findFirst({
|
||||||
team: {
|
where: {
|
||||||
id: teamId,
|
userId,
|
||||||
members: {
|
teamId,
|
||||||
some: {
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
|
if (!teamMember) {
|
||||||
|
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||||
|
message: 'You are not a member of this team.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
whereFilter.push(
|
||||||
|
{ teamId },
|
||||||
|
{
|
||||||
|
OR: [
|
||||||
|
match(teamMember.role)
|
||||||
|
.with(TeamMemberRole.ADMIN, () => ({
|
||||||
|
visibility: {
|
||||||
|
in: [
|
||||||
|
DocumentVisibility.EVERYONE,
|
||||||
|
DocumentVisibility.MANAGER_AND_ABOVE,
|
||||||
|
DocumentVisibility.ADMIN,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.with(TeamMemberRole.MANAGER, () => ({
|
||||||
|
visibility: {
|
||||||
|
in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE],
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.otherwise(() => ({ visibility: DocumentVisibility.EVERYONE })),
|
||||||
|
{ userId, teamId },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [data, count] = await Promise.all([
|
const [data, count] = await Promise.all([
|
||||||
prisma.template.findMany({
|
prisma.template.findMany({
|
||||||
where: whereFilter,
|
where: {
|
||||||
|
type,
|
||||||
|
AND: whereFilter,
|
||||||
|
},
|
||||||
include: {
|
include: {
|
||||||
templateDocumentData: true,
|
templateDocumentData: true,
|
||||||
team: {
|
team: {
|
||||||
@ -103,7 +140,9 @@ export const findTemplates = async ({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
prisma.template.count({
|
prisma.template.count({
|
||||||
where: whereFilter,
|
where: {
|
||||||
|
AND: whereFilter,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { z } from 'zod';
|
|||||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import type { Template, TemplateMeta } from '@documenso/prisma/client';
|
import type { DocumentVisibility, Template, TemplateMeta } from '@documenso/prisma/client';
|
||||||
import { TemplateSchema } from '@documenso/prisma/generated/zod';
|
import { TemplateSchema } from '@documenso/prisma/generated/zod';
|
||||||
|
|
||||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
@ -19,6 +19,7 @@ export type UpdateTemplateSettingsOptions = {
|
|||||||
data: {
|
data: {
|
||||||
title?: string;
|
title?: string;
|
||||||
externalId?: string | null;
|
externalId?: string | null;
|
||||||
|
visibility?: DocumentVisibility;
|
||||||
globalAccessAuth?: TDocumentAccessAuthTypes | null;
|
globalAccessAuth?: TDocumentAccessAuthTypes | null;
|
||||||
globalActionAuth?: TDocumentActionAuthTypes | null;
|
globalActionAuth?: TDocumentActionAuthTypes | null;
|
||||||
publicTitle?: string;
|
publicTitle?: string;
|
||||||
@ -110,6 +111,7 @@ export const updateTemplateSettings = async ({
|
|||||||
title: data.title,
|
title: data.title,
|
||||||
externalId: data.externalId,
|
externalId: data.externalId,
|
||||||
type: data.type,
|
type: data.type,
|
||||||
|
visibility: data.visibility,
|
||||||
publicDescription: data.publicDescription,
|
publicDescription: data.publicDescription,
|
||||||
publicTitle: data.publicTitle,
|
publicTitle: data.publicTitle,
|
||||||
authOptions,
|
authOptions,
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Template" ADD COLUMN "visibility" "DocumentVisibility" NOT NULL DEFAULT 'EVERYONE';
|
||||||
@ -654,19 +654,20 @@ model TemplateMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Template {
|
model Template {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
externalId String?
|
externalId String?
|
||||||
type TemplateType @default(PRIVATE)
|
type TemplateType @default(PRIVATE)
|
||||||
title String
|
title String
|
||||||
userId Int
|
userId Int
|
||||||
teamId Int?
|
teamId Int?
|
||||||
|
visibility DocumentVisibility @default(EVERYONE)
|
||||||
authOptions Json?
|
authOptions Json?
|
||||||
templateMeta TemplateMeta?
|
templateMeta TemplateMeta?
|
||||||
templateDocumentDataId String
|
templateDocumentDataId String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
publicTitle String @default("")
|
publicTitle String @default("")
|
||||||
publicDescription String @default("")
|
publicDescription String @default("")
|
||||||
|
|
||||||
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||||
templateDocumentData DocumentData @relation(fields: [templateDocumentDataId], references: [id], onDelete: Cascade)
|
templateDocumentData DocumentData @relation(fields: [templateDocumentDataId], references: [id], onDelete: Cascade)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
|||||||
import {
|
import {
|
||||||
DocumentDistributionMethod,
|
DocumentDistributionMethod,
|
||||||
DocumentSigningOrder,
|
DocumentSigningOrder,
|
||||||
|
DocumentVisibility,
|
||||||
TemplateType,
|
TemplateType,
|
||||||
} from '@documenso/prisma/client';
|
} from '@documenso/prisma/client';
|
||||||
|
|
||||||
@ -84,6 +85,7 @@ export const ZUpdateTemplateSettingsMutationSchema = z.object({
|
|||||||
data: z.object({
|
data: z.object({
|
||||||
title: z.string().min(1).optional(),
|
title: z.string().min(1).optional(),
|
||||||
externalId: z.string().nullish(),
|
externalId: z.string().nullish(),
|
||||||
|
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||||
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullable().optional(),
|
||||||
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
globalActionAuth: ZDocumentActionAuthTypesSchema.nullable().optional(),
|
||||||
publicTitle: z.string().trim().min(1).max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH).optional(),
|
publicTitle: z.string().trim().min(1).max(MAX_TEMPLATE_PUBLIC_TITLE_LENGTH).optional(),
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { Trans } from '@lingui/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { InfoIcon } from 'lucide-react';
|
import { InfoIcon } from 'lucide-react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||||
import { DOCUMENT_DISTRIBUTION_METHODS } from '@documenso/lib/constants/document';
|
import { DOCUMENT_DISTRIBUTION_METHODS } from '@documenso/lib/constants/document';
|
||||||
@ -14,6 +15,7 @@ import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
|||||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||||
|
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
|
||||||
import { DocumentDistributionMethod, type Field, type Recipient } from '@documenso/prisma/client';
|
import { DocumentDistributionMethod, type Field, type Recipient } from '@documenso/prisma/client';
|
||||||
import type { TemplateWithData } from '@documenso/prisma/types/template';
|
import type { TemplateWithData } from '@documenso/prisma/types/template';
|
||||||
import {
|
import {
|
||||||
@ -25,6 +27,10 @@ import {
|
|||||||
DocumentGlobalAuthActionTooltip,
|
DocumentGlobalAuthActionTooltip,
|
||||||
} from '@documenso/ui/components/document/document-global-auth-action-select';
|
} from '@documenso/ui/components/document/document-global-auth-action-select';
|
||||||
import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper';
|
import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper';
|
||||||
|
import {
|
||||||
|
DocumentVisibilitySelect,
|
||||||
|
DocumentVisibilityTooltip,
|
||||||
|
} from '@documenso/ui/components/document/document-visibility-select';
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
@ -66,6 +72,7 @@ export type AddTemplateSettingsFormProps = {
|
|||||||
isEnterprise: boolean;
|
isEnterprise: boolean;
|
||||||
isDocumentPdfLoaded: boolean;
|
isDocumentPdfLoaded: boolean;
|
||||||
template: TemplateWithData;
|
template: TemplateWithData;
|
||||||
|
currentTeamMemberRole?: TeamMemberRole;
|
||||||
onSubmit: (_data: TAddTemplateSettingsFormSchema) => void;
|
onSubmit: (_data: TAddTemplateSettingsFormSchema) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -76,6 +83,7 @@ export const AddTemplateSettingsFormPartial = ({
|
|||||||
isEnterprise,
|
isEnterprise,
|
||||||
isDocumentPdfLoaded,
|
isDocumentPdfLoaded,
|
||||||
template,
|
template,
|
||||||
|
currentTeamMemberRole,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: AddTemplateSettingsFormProps) => {
|
}: AddTemplateSettingsFormProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
@ -89,6 +97,7 @@ export const AddTemplateSettingsFormPartial = ({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
title: template.title,
|
title: template.title,
|
||||||
externalId: template.externalId || undefined,
|
externalId: template.externalId || undefined,
|
||||||
|
visibility: template.visibility || '',
|
||||||
globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined,
|
globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined,
|
||||||
globalActionAuth: documentAuthOption?.globalActionAuth || undefined,
|
globalActionAuth: documentAuthOption?.globalActionAuth || undefined,
|
||||||
meta: {
|
meta: {
|
||||||
@ -110,6 +119,16 @@ export const AddTemplateSettingsFormPartial = ({
|
|||||||
const distributionMethod = form.watch('meta.distributionMethod');
|
const distributionMethod = form.watch('meta.distributionMethod');
|
||||||
const emailSettings = form.watch('meta.emailSettings');
|
const emailSettings = form.watch('meta.emailSettings');
|
||||||
|
|
||||||
|
const canUpdateVisibility = match(currentTeamMemberRole)
|
||||||
|
.with(TeamMemberRole.ADMIN, () => true)
|
||||||
|
.with(
|
||||||
|
TeamMemberRole.MANAGER,
|
||||||
|
() =>
|
||||||
|
template.visibility === DocumentVisibility.EVERYONE ||
|
||||||
|
template.visibility === DocumentVisibility.MANAGER_AND_ABOVE,
|
||||||
|
)
|
||||||
|
.otherwise(() => false);
|
||||||
|
|
||||||
// We almost always want to set the timezone to the user's local timezone to avoid confusion
|
// We almost always want to set the timezone to the user's local timezone to avoid confusion
|
||||||
// when the document is signed.
|
// when the document is signed.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -210,6 +229,30 @@ export const AddTemplateSettingsFormPartial = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{currentTeamMemberRole && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="visibility"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel className="flex flex-row items-center">
|
||||||
|
Document visibility
|
||||||
|
<DocumentVisibilityTooltip />
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<DocumentVisibilitySelect
|
||||||
|
canUpdateVisibility={canUpdateVisibility}
|
||||||
|
currentTeamMemberRole={currentTeamMemberRole}
|
||||||
|
{...field}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="meta.distributionMethod"
|
name="meta.distributionMethod"
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
} from '@documenso/lib/types/document-auth';
|
} from '@documenso/lib/types/document-auth';
|
||||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||||
|
import { DocumentVisibility } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { ZMapNegativeOneToUndefinedSchema } from '../document-flow/add-settings.types';
|
import { ZMapNegativeOneToUndefinedSchema } from '../document-flow/add-settings.types';
|
||||||
import { DocumentDistributionMethod } from '.prisma/client';
|
import { DocumentDistributionMethod } from '.prisma/client';
|
||||||
@ -16,6 +17,7 @@ import { DocumentDistributionMethod } from '.prisma/client';
|
|||||||
export const ZAddTemplateSettingsFormSchema = z.object({
|
export const ZAddTemplateSettingsFormSchema = z.object({
|
||||||
title: z.string().trim().min(1, { message: "Title can't be empty" }),
|
title: z.string().trim().min(1, { message: "Title can't be empty" }),
|
||||||
externalId: z.string().optional(),
|
externalId: z.string().optional(),
|
||||||
|
visibility: z.nativeEnum(DocumentVisibility).optional(),
|
||||||
globalAccessAuth: ZMapNegativeOneToUndefinedSchema.pipe(
|
globalAccessAuth: ZMapNegativeOneToUndefinedSchema.pipe(
|
||||||
ZDocumentAccessAuthTypesSchema.optional(),
|
ZDocumentAccessAuthTypesSchema.optional(),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user