Compare commits

..

3 Commits

Author SHA1 Message Date
Mythie
59c1e55233 v1.9.1-rc.3 2025-02-25 08:03:13 +11:00
Mythie
2fbaf56c06 fix: early adopters can use platform features 2025-02-25 07:54:28 +11:00
David Nguyen
70320cd24b chore: update API documentation 2025-02-25 02:35:11 +11:00
33 changed files with 144 additions and 703 deletions

View File

@@ -21,14 +21,20 @@ Check out the [API V1 documentation](https://app.documenso.com/api/v1/openapi) f
## API V2 - Beta ## API V2 - Beta
Our new API V2 is currently in Beta. The new API features typed SDKs for TypeScript, Python and Go and example code for many more. <Callout type="warning">API V2 is currently beta, and will be subject to breaking changes</Callout>
<Callout type="warning"> Check out the [API V2 documentation](https://documen.so/api-v2-docs) for details about the API endpoints, request parameters, response formats, and authentication methods.
NOW IN BETA: [API V2 Documentation](https://documen.so/api-v2-docs)
</Callout> Our new API V2 supports the following typed SDKs:
- [TypeScript](https://github.com/documenso/sdk-typescript)
- [Python](https://github.com/documenso/sdk-python)
- [Go](https://github.com/documenso/sdk-go)
🚀 [V2 Announcement](https://documen.so/sdk-blog) 🚀 [V2 Announcement](https://documen.so/sdk-blog)
📖 [Documentation](https://documen.so/api-v2-docs)
💬 [Leave Feedback](https://documen.so/sdk-feedback) 💬 [Leave Feedback](https://documen.so/sdk-feedback)
🔔 [Breaking Changes](https://documen.so/sdk-breaking) 🔔 [Breaking Changes](https://documen.so/sdk-breaking)

View File

@@ -1,6 +1,6 @@
{ {
"name": "@documenso/web", "name": "@documenso/web",
"version": "1.9.1-rc.2", "version": "1.9.1-rc.3",
"private": true, "private": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {

View File

@@ -14,12 +14,6 @@ import type { Document, Recipient, Team, User } from '@documenso/prisma/client';
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client'; import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
import { trpc as trpcClient } from '@documenso/trpc/client'; import { trpc as trpcClient } from '@documenso/trpc/client';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import {
SplitButton,
SplitButtonAction,
SplitButtonDropdown,
SplitButtonDropdownItem,
} from '@documenso/ui/primitives/split-button';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
export type DocumentPageViewButtonProps = { export type DocumentPageViewButtonProps = {
@@ -31,9 +25,7 @@ export type DocumentPageViewButtonProps = {
team?: Pick<Team, 'id' | 'url'>; team?: Pick<Team, 'id' | 'url'>;
}; };
export const DocumentPageViewButton = ({ export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps) => {
document: activeDocument,
}: DocumentPageViewButtonProps) => {
const { data: session } = useSession(); const { data: session } = useSession();
const { toast } = useToast(); const { toast } = useToast();
const { _ } = useLingui(); const { _ } = useLingui();
@@ -42,27 +34,25 @@ export const DocumentPageViewButton = ({
return null; return null;
} }
const recipient = activeDocument.recipients.find( const recipient = document.recipients.find((recipient) => recipient.email === session.user.email);
(recipient) => recipient.email === session.user.email,
);
const isRecipient = !!recipient; const isRecipient = !!recipient;
const isPending = activeDocument.status === DocumentStatus.PENDING; const isPending = document.status === DocumentStatus.PENDING;
const isComplete = activeDocument.status === DocumentStatus.COMPLETED; const isComplete = document.status === DocumentStatus.COMPLETED;
const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
const role = recipient?.role; const role = recipient?.role;
const documentsPath = formatDocumentsPath(activeDocument.team?.url); const documentsPath = formatDocumentsPath(document.team?.url);
const onDownloadClick = async () => { const onDownloadClick = async () => {
try { try {
const documentWithData = await trpcClient.document.getDocumentById.query( const documentWithData = await trpcClient.document.getDocumentById.query(
{ {
documentId: activeDocument.id, documentId: document.id,
}, },
{ {
context: { context: {
teamId: activeDocument.team?.id?.toString(), teamId: document.team?.id?.toString(),
}, },
}, },
); );
@@ -73,10 +63,7 @@ export const DocumentPageViewButton = ({
throw new Error('No document available'); throw new Error('No document available');
} }
await downloadPDF({ await downloadPDF({ documentData, fileName: documentWithData.title });
documentData,
fileName: documentWithData.title,
});
} catch (err) { } catch (err) {
toast({ toast({
title: _(msg`Something went wrong`), title: _(msg`Something went wrong`),
@@ -86,100 +73,6 @@ export const DocumentPageViewButton = ({
} }
}; };
const onDownloadAuditLogClick = async () => {
try {
const { url } = await trpcClient.document.downloadAuditLogs.mutate({
documentId: activeDocument.id,
});
const iframe = Object.assign(document.createElement('iframe'), {
src: url,
});
Object.assign(iframe.style, {
position: 'fixed',
top: '0',
left: '0',
width: '0',
height: '0',
});
const onLoaded = () => {
if (iframe.contentDocument?.readyState === 'complete') {
iframe.contentWindow?.print();
iframe.contentWindow?.addEventListener('afterprint', () => {
document.body.removeChild(iframe);
});
}
};
// When the iframe has loaded, print the iframe and remove it from the dom
iframe.addEventListener('load', onLoaded);
document.body.appendChild(iframe);
onLoaded();
} catch (error) {
console.error(error);
toast({
title: _(msg`Something went wrong`),
description: _(
msg`Sorry, we were unable to download the audit logs. Please try again later.`,
),
variant: 'destructive',
});
}
};
const onDownloadSigningCertificateClick = async () => {
try {
const { url } = await trpcClient.document.downloadCertificate.mutate({
documentId: activeDocument.id,
});
const iframe = Object.assign(document.createElement('iframe'), {
src: url,
});
Object.assign(iframe.style, {
position: 'fixed',
top: '0',
left: '0',
width: '0',
height: '0',
});
const onLoaded = () => {
if (iframe.contentDocument?.readyState === 'complete') {
iframe.contentWindow?.print();
iframe.contentWindow?.addEventListener('afterprint', () => {
document.body.removeChild(iframe);
});
}
};
// When the iframe has loaded, print the iframe and remove it from the dom
iframe.addEventListener('load', onLoaded);
document.body.appendChild(iframe);
onLoaded();
} catch (error) {
console.error(error);
toast({
title: _(msg`Something went wrong`),
description: _(
msg`Sorry, we were unable to download the certificate. Please try again later.`,
),
variant: 'destructive',
});
}
};
return match({ return match({
isRecipient, isRecipient,
isPending, isPending,
@@ -213,27 +106,16 @@ export const DocumentPageViewButton = ({
)) ))
.with({ isComplete: false }, () => ( .with({ isComplete: false }, () => (
<Button className="w-full" asChild> <Button className="w-full" asChild>
<Link href={`${documentsPath}/${activeDocument.id}/edit`}> <Link href={`${documentsPath}/${document.id}/edit`}>
<Trans>Edit</Trans> <Trans>Edit</Trans>
</Link> </Link>
</Button> </Button>
)) ))
.with({ isComplete: true }, () => ( .with({ isComplete: true }, () => (
<SplitButton className="flex w-full"> <Button className="w-full" onClick={onDownloadClick}>
<SplitButtonAction className="w-full" onClick={() => void onDownloadClick()}> <Download className="-ml-1 mr-2 inline h-4 w-4" />
<Download className="-ml-1 mr-2 inline h-4 w-4" /> <Trans>Download</Trans>
<Trans>Download</Trans> </Button>
</SplitButtonAction>
<SplitButtonDropdown>
<SplitButtonDropdownItem onClick={() => void onDownloadAuditLogClick()}>
<Trans>Only Audit Log</Trans>
</SplitButtonDropdownItem>
<SplitButtonDropdownItem onClick={() => void onDownloadSigningCertificateClick()}>
<Trans>Only Signing Certificate</Trans>
</SplitButtonDropdownItem>
</SplitButtonDropdown>
</SplitButton>
)) ))
.otherwise(() => null); .otherwise(() => null);
}; };

View File

@@ -187,8 +187,6 @@ export const EditDocumentForm = ({
title: data.title, title: data.title,
externalId: data.externalId || null, externalId: data.externalId || null,
visibility: data.visibility, visibility: data.visibility,
includeSigningCertificate: data.includeSigningCertificate,
includeAuditTrailLog: data.includeAuditTrailLog,
globalAccessAuth: data.globalAccessAuth ?? null, globalAccessAuth: data.globalAccessAuth ?? null,
globalActionAuth: data.globalActionAuth ?? null, globalActionAuth: data.globalActionAuth ?? null,
}, },

View File

@@ -41,7 +41,6 @@ const ZTeamDocumentPreferencesFormSchema = z.object({
includeSenderDetails: z.boolean(), includeSenderDetails: z.boolean(),
typedSignatureEnabled: z.boolean(), typedSignatureEnabled: z.boolean(),
includeSigningCertificate: z.boolean(), includeSigningCertificate: z.boolean(),
includeAuditTrailLog: z.boolean(),
}); });
type TTeamDocumentPreferencesFormSchema = z.infer<typeof ZTeamDocumentPreferencesFormSchema>; type TTeamDocumentPreferencesFormSchema = z.infer<typeof ZTeamDocumentPreferencesFormSchema>;
@@ -73,7 +72,6 @@ export const TeamDocumentPreferencesForm = ({
includeSenderDetails: settings?.includeSenderDetails ?? false, includeSenderDetails: settings?.includeSenderDetails ?? false,
typedSignatureEnabled: settings?.typedSignatureEnabled ?? true, typedSignatureEnabled: settings?.typedSignatureEnabled ?? true,
includeSigningCertificate: settings?.includeSigningCertificate ?? true, includeSigningCertificate: settings?.includeSigningCertificate ?? true,
includeAuditTrailLog: settings?.includeAuditTrailLog ?? false,
}, },
resolver: zodResolver(ZTeamDocumentPreferencesFormSchema), resolver: zodResolver(ZTeamDocumentPreferencesFormSchema),
}); });
@@ -88,7 +86,6 @@ export const TeamDocumentPreferencesForm = ({
includeSenderDetails, includeSenderDetails,
includeSigningCertificate, includeSigningCertificate,
typedSignatureEnabled, typedSignatureEnabled,
includeAuditTrailLog,
} = data; } = data;
await updateTeamDocumentPreferences({ await updateTeamDocumentPreferences({
@@ -99,7 +96,6 @@ export const TeamDocumentPreferencesForm = ({
includeSenderDetails, includeSenderDetails,
typedSignatureEnabled, typedSignatureEnabled,
includeSigningCertificate, includeSigningCertificate,
includeAuditTrailLog,
}, },
}); });
@@ -304,37 +300,6 @@ export const TeamDocumentPreferencesForm = ({
)} )}
/> />
<FormField
control={form.control}
name="includeAuditTrailLog"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>
<Trans>Include the Audit Trail Log in the Document</Trans>
</FormLabel>
<div>
<FormControl className="block">
<Switch
ref={field.ref}
name={field.name}
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</div>
<FormDescription>
<Trans>
Controls whether the audit trail log will be included in the document when it is
downloaded. The audit trail log can still be downloaded from the logs page
separately.
</Trans>
</FormDescription>
</FormItem>
)}
/>
<div className="flex flex-row justify-end space-x-4"> <div className="flex flex-row justify-end space-x-4">
<Button type="submit" loading={form.formState.isSubmitting}> <Button type="submit" loading={form.formState.isSubmitting}>
<Trans>Save</Trans> <Trans>Save</Trans>

View File

@@ -49,7 +49,7 @@ export type EmbedDirectTemplateClientPageProps = {
fields: Field[]; fields: Field[];
metadata?: DocumentMeta | TemplateMeta | null; metadata?: DocumentMeta | TemplateMeta | null;
hidePoweredBy?: boolean; hidePoweredBy?: boolean;
isPlatformOrEnterprise?: boolean; allowWhiteLabelling?: boolean;
}; };
export const EmbedDirectTemplateClientPage = ({ export const EmbedDirectTemplateClientPage = ({
@@ -60,7 +60,7 @@ export const EmbedDirectTemplateClientPage = ({
fields, fields,
metadata, metadata,
hidePoweredBy = false, hidePoweredBy = false,
isPlatformOrEnterprise = false, allowWhiteLabelling = false,
}: EmbedDirectTemplateClientPageProps) => { }: EmbedDirectTemplateClientPageProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
@@ -288,7 +288,7 @@ export const EmbedDirectTemplateClientPage = ({
document.documentElement.classList.add('dark-mode-disabled'); document.documentElement.classList.add('dark-mode-disabled');
} }
if (isPlatformOrEnterprise) { if (allowWhiteLabelling) {
injectCss({ injectCss({
css: data.css, css: data.css,
cssVars: data.cssVars, cssVars: data.cssVars,

View File

@@ -2,6 +2,7 @@ import { notFound } from 'next/navigation';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { isCommunityPlan as isUserCommunityPlan } from '@documenso/ee/server-only/util/is-community-plan';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform'; import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
@@ -55,12 +56,16 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
documentAuth: template.authOptions, documentAuth: template.authOptions,
}); });
const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([ const [isPlatformDocument, isEnterpriseDocument, isCommunityPlan] = await Promise.all([
isDocumentPlatform(template), isDocumentPlatform(template),
isUserEnterprise({ isUserEnterprise({
userId: template.userId, userId: template.userId,
teamId: template.teamId ?? undefined, teamId: template.teamId ?? undefined,
}), }),
isUserCommunityPlan({
userId: template.userId,
teamId: template.teamId ?? undefined,
}),
]); ]);
const isAccessAuthValid = match(derivedRecipientAccessAuth) const isAccessAuthValid = match(derivedRecipientAccessAuth)
@@ -106,7 +111,7 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem
fields={fields} fields={fields}
metadata={template.templateMeta} metadata={template.templateMeta}
hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy} hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy}
isPlatformOrEnterprise={isPlatformDocument || isEnterpriseDocument} allowWhiteLabelling={isCommunityPlan || isPlatformDocument || isEnterpriseDocument}
/> />
</RecipientProvider> </RecipientProvider>
</DocumentAuthProvider> </DocumentAuthProvider>

View File

@@ -51,7 +51,7 @@ export type EmbedSignDocumentClientPageProps = {
metadata?: DocumentMeta | TemplateMeta | null; metadata?: DocumentMeta | TemplateMeta | null;
isCompleted?: boolean; isCompleted?: boolean;
hidePoweredBy?: boolean; hidePoweredBy?: boolean;
isPlatformOrEnterprise?: boolean; allowWhitelabelling?: boolean;
allRecipients?: RecipientWithFields[]; allRecipients?: RecipientWithFields[];
}; };
@@ -64,7 +64,7 @@ export const EmbedSignDocumentClientPage = ({
metadata, metadata,
isCompleted, isCompleted,
hidePoweredBy = false, hidePoweredBy = false,
isPlatformOrEnterprise = false, allowWhitelabelling = false,
allRecipients = [], allRecipients = [],
}: EmbedSignDocumentClientPageProps) => { }: EmbedSignDocumentClientPageProps) => {
const { _ } = useLingui(); const { _ } = useLingui();
@@ -212,7 +212,7 @@ export const EmbedSignDocumentClientPage = ({
document.documentElement.classList.add('dark-mode-disabled'); document.documentElement.classList.add('dark-mode-disabled');
} }
if (isPlatformOrEnterprise) { if (allowWhitelabelling) {
injectCss({ injectCss({
css: data.css, css: data.css,
cssVars: data.cssVars, cssVars: data.cssVars,

View File

@@ -2,6 +2,7 @@ import { notFound } from 'next/navigation';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { isCommunityPlan as isUserCommunityPlan } from '@documenso/ee/server-only/util/is-community-plan';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform'; import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
@@ -62,12 +63,16 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
return <EmbedPaywall />; return <EmbedPaywall />;
} }
const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([ const [isPlatformDocument, isEnterpriseDocument, isCommunityPlan] = await Promise.all([
isDocumentPlatform(document), isDocumentPlatform(document),
isUserEnterprise({ isUserEnterprise({
userId: document.userId, userId: document.userId,
teamId: document.teamId ?? undefined, teamId: document.teamId ?? undefined,
}), }),
isUserCommunityPlan({
userId: document.userId,
teamId: document.teamId ?? undefined,
}),
]); ]);
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({ const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
@@ -127,7 +132,7 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
metadata={document.documentMeta} metadata={document.documentMeta}
isCompleted={document.status === DocumentStatus.COMPLETED} isCompleted={document.status === DocumentStatus.COMPLETED}
hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy} hidePoweredBy={isPlatformDocument || isEnterpriseDocument || hidePoweredBy}
isPlatformOrEnterprise={isPlatformDocument || isEnterpriseDocument} allowWhitelabelling={isCommunityPlan || isPlatformDocument || isEnterpriseDocument}
allRecipients={allRecipients} allRecipients={allRecipients}
/> />
</DocumentAuthProvider> </DocumentAuthProvider>

View File

@@ -363,40 +363,6 @@ export const DocumentHistorySheet = ({
]} ]}
/> />
)) ))
.with(
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SIGNING_CERTIFICATE_UPDATED },
({ data }) => (
<DocumentHistorySheetChanges
values={[
{
key: 'Old',
value: data.from,
},
{
key: 'New',
value: data.to,
},
]}
/>
),
)
.with(
{ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_AUDIT_TRAIL_UPDATED },
({ data }) => (
<DocumentHistorySheetChanges
values={[
{
key: 'Old',
value: data.from,
},
{
key: 'New',
value: data.to,
},
]}
/>
),
)
.exhaustive()} .exhaustive()}
{isUserDetailsVisible && ( {isUserDetailsVisible && (

6
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@documenso/root", "name": "@documenso/root",
"version": "1.9.1-rc.2", "version": "1.9.1-rc.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@documenso/root", "name": "@documenso/root",
"version": "1.9.1-rc.2", "version": "1.9.1-rc.3",
"workspaces": [ "workspaces": [
"apps/*", "apps/*",
"packages/*" "packages/*"
@@ -106,7 +106,7 @@
}, },
"apps/web": { "apps/web": {
"name": "@documenso/web", "name": "@documenso/web",
"version": "1.9.1-rc.2", "version": "1.9.1-rc.3",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@documenso/api": "*", "@documenso/api": "*",

View File

@@ -1,6 +1,6 @@
{ {
"private": true, "private": true,
"version": "1.9.1-rc.2", "version": "1.9.1-rc.3",
"scripts": { "scripts": {
"build": "turbo run build", "build": "turbo run build",
"build:web": "turbo run build --filter=@documenso/web", "build:web": "turbo run build --filter=@documenso/web",

View File

@@ -0,0 +1,56 @@
import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing';
import { prisma } from '@documenso/prisma';
import type { Subscription } from '@documenso/prisma/client';
import { getCommunityPlanPriceIds } from '../stripe/get-community-plan-prices';
export type IsCommunityPlanOptions = {
userId: number;
teamId?: number;
};
/**
* Whether the user or team is on the community plan.
*/
export const isCommunityPlan = async ({
userId,
teamId,
}: IsCommunityPlanOptions): Promise<boolean> => {
let subscriptions: Subscription[] = [];
if (teamId) {
subscriptions = await prisma.team
.findFirstOrThrow({
where: {
id: teamId,
},
select: {
owner: {
include: {
subscriptions: true,
},
},
},
})
.then((team) => team.owner.subscriptions);
} else {
subscriptions = await prisma.user
.findFirstOrThrow({
where: {
id: userId,
},
select: {
subscriptions: true,
},
})
.then((user) => user.subscriptions);
}
if (subscriptions.length === 0) {
return false;
}
const communityPlanPriceIds = await getCommunityPlanPriceIds();
return subscriptionsContainsActivePlan(subscriptions, communityPlanPriceIds);
};

View File

@@ -17,7 +17,6 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({
documentLanguage: z.string(), documentLanguage: z.string(),
includeSenderDetails: z.boolean(), includeSenderDetails: z.boolean(),
includeSigningCertificate: z.boolean(), includeSigningCertificate: z.boolean(),
includeAuditTrailLog: z.boolean(),
brandingEnabled: z.boolean(), brandingEnabled: z.boolean(),
brandingLogo: z.string(), brandingLogo: z.string(),
brandingUrl: z.string(), brandingUrl: z.string(),

View File

@@ -13,7 +13,6 @@ import { signPdf } from '@documenso/signing';
import { sendCompletedEmail } from '../../../server-only/document/send-completed-email'; import { sendCompletedEmail } from '../../../server-only/document/send-completed-email';
import PostHogServerClient from '../../../server-only/feature-flags/get-post-hog-server-client'; import PostHogServerClient from '../../../server-only/feature-flags/get-post-hog-server-client';
import { getAuditLogsPdf } from '../../../server-only/htmltopdf/get-audit-logs-pdf';
import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf'; import { getCertificatePdf } from '../../../server-only/htmltopdf/get-certificate-pdf';
import { flattenAnnotations } from '../../../server-only/pdf/flatten-annotations'; import { flattenAnnotations } from '../../../server-only/pdf/flatten-annotations';
import { flattenForm } from '../../../server-only/pdf/flatten-form'; import { flattenForm } from '../../../server-only/pdf/flatten-form';
@@ -58,7 +57,6 @@ export const run = async ({
teamGlobalSettings: { teamGlobalSettings: {
select: { select: {
includeSigningCertificate: true, includeSigningCertificate: true,
includeAuditTrailLog: true,
}, },
}, },
}, },
@@ -123,36 +121,13 @@ export const run = async ({
const pdfData = await getFile(documentData); const pdfData = await getFile(documentData);
let includeSigningCertificate; const certificateData =
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
if (document.teamId) { ? await getCertificatePdf({
includeSigningCertificate = documentId,
document.team?.teamGlobalSettings?.includeSigningCertificate ?? true; language: document.documentMeta?.language,
} else { }).catch(() => null)
includeSigningCertificate = document.includeSigningCertificate ?? true; : null;
}
const certificateData = includeSigningCertificate
? await getCertificatePdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
let includeAuditTrailLog;
if (document.teamId) {
includeAuditTrailLog = document.team?.teamGlobalSettings?.includeAuditTrailLog ?? true;
} else {
includeAuditTrailLog = document.includeAuditTrailLog ?? true;
}
const auditLogData = includeAuditTrailLog
? await getAuditLogsPdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
const newDataId = await io.runTask('decorate-and-sign-pdf', async () => { const newDataId = await io.runTask('decorate-and-sign-pdf', async () => {
const pdfDoc = await PDFDocument.load(pdfData); const pdfDoc = await PDFDocument.load(pdfData);
@@ -175,16 +150,6 @@ export const run = async ({
}); });
} }
if (auditLogData) {
const auditLog = await PDFDocument.load(auditLogData);
const auditLogPages = await pdfDoc.copyPages(auditLog, auditLog.getPageIndices());
auditLogPages.forEach((page) => {
pdfDoc.addPage(page);
});
}
for (const field of fields) { for (const field of fields) {
if (field.inserted) { if (field.inserted) {
await insertFieldInPDF(pdfDoc, field); await insertFieldInPDF(pdfDoc, field);

View File

@@ -124,8 +124,6 @@ export const createDocument = async ({
team?.teamGlobalSettings?.documentVisibility, team?.teamGlobalSettings?.documentVisibility,
userTeamRole ?? TeamMemberRole.MEMBER, userTeamRole ?? TeamMemberRole.MEMBER,
), ),
includeSigningCertificate: team?.teamGlobalSettings?.includeSigningCertificate ?? true,
includeAuditTrailLog: team?.teamGlobalSettings?.includeAuditTrailLog ?? true,
formValues, formValues,
source: DocumentSource.DOCUMENT, source: DocumentSource.DOCUMENT,
documentMeta: { documentMeta: {

View File

@@ -22,7 +22,6 @@ import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { getFile } from '../../universal/upload/get-file'; import { getFile } from '../../universal/upload/get-file';
import { putPdfFile } from '../../universal/upload/put-file'; import { putPdfFile } from '../../universal/upload/put-file';
import { fieldsContainUnsignedRequiredField } from '../../utils/advanced-fields-helpers'; import { fieldsContainUnsignedRequiredField } from '../../utils/advanced-fields-helpers';
import { getAuditLogsPdf } from '../htmltopdf/get-audit-logs-pdf';
import { getCertificatePdf } from '../htmltopdf/get-certificate-pdf'; import { getCertificatePdf } from '../htmltopdf/get-certificate-pdf';
import { flattenAnnotations } from '../pdf/flatten-annotations'; import { flattenAnnotations } from '../pdf/flatten-annotations';
import { flattenForm } from '../pdf/flatten-form'; import { flattenForm } from '../pdf/flatten-form';
@@ -62,7 +61,6 @@ export const sealDocument = async ({
teamGlobalSettings: { teamGlobalSettings: {
select: { select: {
includeSigningCertificate: true, includeSigningCertificate: true,
includeAuditTrailLog: true,
}, },
}, },
}, },
@@ -111,36 +109,13 @@ export const sealDocument = async ({
// !: Need to write the fields onto the document as a hard copy // !: Need to write the fields onto the document as a hard copy
const pdfData = await getFile(documentData); const pdfData = await getFile(documentData);
let includeSigningCertificate; const certificateData =
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
if (document.teamId) { ? await getCertificatePdf({
includeSigningCertificate = documentId,
document.team?.teamGlobalSettings?.includeSigningCertificate ?? true; language: document.documentMeta?.language,
} else { }).catch(() => null)
includeSigningCertificate = document.includeSigningCertificate ?? true; : null;
}
const certificateData = includeSigningCertificate
? await getCertificatePdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
let includeAuditTrailLog;
if (document.teamId) {
includeAuditTrailLog = document.team?.teamGlobalSettings?.includeAuditTrailLog ?? true;
} else {
includeAuditTrailLog = document.includeAuditTrailLog ?? true;
}
const auditLogData = includeAuditTrailLog
? await getAuditLogsPdf({
documentId,
language: document.documentMeta?.language,
}).catch(() => null)
: null;
const doc = await PDFDocument.load(pdfData); const doc = await PDFDocument.load(pdfData);
@@ -159,16 +134,6 @@ export const sealDocument = async ({
}); });
} }
if (auditLogData) {
const auditLog = await PDFDocument.load(auditLogData);
const auditLogPages = await doc.copyPages(auditLog, auditLog.getPageIndices());
auditLogPages.forEach((page) => {
doc.addPage(page);
});
}
for (const field of fields) { for (const field of fields) {
await insertFieldInPDF(doc, field); await insertFieldInPDF(doc, field);
} }

View File

@@ -21,8 +21,6 @@ export type UpdateDocumentOptions = {
title?: string; title?: string;
externalId?: string | null; externalId?: string | null;
visibility?: DocumentVisibility | null; visibility?: DocumentVisibility | null;
includeSigningCertificate?: boolean;
includeAuditTrailLog?: boolean;
globalAccessAuth?: TDocumentAccessAuthTypes | null; globalAccessAuth?: TDocumentAccessAuthTypes | null;
globalActionAuth?: TDocumentActionAuthTypes | null; globalActionAuth?: TDocumentActionAuthTypes | null;
}; };
@@ -158,12 +156,6 @@ export const updateDocument = async ({
documentGlobalActionAuth === undefined || documentGlobalActionAuth === newGlobalActionAuth; documentGlobalActionAuth === undefined || documentGlobalActionAuth === newGlobalActionAuth;
const isDocumentVisibilitySame = const isDocumentVisibilitySame =
data.visibility === undefined || data.visibility === document.visibility; data.visibility === undefined || data.visibility === document.visibility;
const isIncludeSigningCertificateSame =
data.includeSigningCertificate === undefined ||
data.includeSigningCertificate === document.includeSigningCertificate;
const isIncludeAuditTrailLogSame =
data.includeAuditTrailLog === undefined ||
data.includeAuditTrailLog === document.includeAuditTrailLog;
const auditLogs: CreateDocumentAuditLogDataResponse[] = []; const auditLogs: CreateDocumentAuditLogDataResponse[] = [];
@@ -243,34 +235,6 @@ export const updateDocument = async ({
); );
} }
if (!isIncludeSigningCertificateSame) {
auditLogs.push(
createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SIGNING_CERTIFICATE_UPDATED,
documentId,
metadata: requestMetadata,
data: {
from: String(document.includeSigningCertificate),
to: String(data.includeSigningCertificate || false),
},
}),
);
}
if (!isIncludeAuditTrailLogSame) {
auditLogs.push(
createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_AUDIT_TRAIL_UPDATED,
documentId,
metadata: requestMetadata,
data: {
from: String(document.includeAuditTrailLog),
to: String(data.includeAuditTrailLog || false),
},
}),
);
}
// Early return if nothing is required. // Early return if nothing is required.
if (auditLogs.length === 0) { if (auditLogs.length === 0) {
return document; return document;
@@ -290,8 +254,6 @@ export const updateDocument = async ({
title: data.title, title: data.title,
externalId: data.externalId, externalId: data.externalId,
visibility: data.visibility as DocumentVisibility, visibility: data.visibility as DocumentVisibility,
includeSigningCertificate: data.includeSigningCertificate,
includeAuditTrailLog: data.includeAuditTrailLog,
authOptions, authOptions,
}, },
}); });

View File

@@ -1,74 +0,0 @@
import { DateTime } from 'luxon';
import type { Browser } from 'playwright';
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
import { type SupportedLanguageCodes, isValidLanguageCode } from '../../constants/i18n';
import { encryptSecondaryData } from '../crypto/encrypt';
export type GetAuditLogsPdfParams = {
documentId: number;
// eslint-disable-next-line @typescript-eslint/ban-types
language?: SupportedLanguageCodes | (string & {});
};
export const getAuditLogsPdf = async ({ documentId, language }: GetAuditLogsPdfParams) => {
const { chromium } = await import('playwright');
const encryptedId = encryptSecondaryData({
data: documentId.toString(),
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
});
let browser: Browser;
if (process.env.NEXT_PRIVATE_BROWSERLESS_URL) {
// !: Use CDP rather than the default `connect` method to avoid coupling to the playwright version.
// !: Previously we would have to keep the playwright version in sync with the browserless version to avoid errors.
browser = await chromium.connectOverCDP(process.env.NEXT_PRIVATE_BROWSERLESS_URL);
} else {
browser = await chromium.launch();
}
if (!browser) {
throw new Error(
'Failed to establish a browser, please ensure you have either a Browserless.io url or chromium browser installed',
);
}
const browserContext = await browser.newContext();
const page = await browserContext.newPage();
const lang = isValidLanguageCode(language) ? language : 'en';
await page.context().addCookies([
{
name: 'language',
value: lang,
url: NEXT_PUBLIC_WEBAPP_URL(),
},
]);
try {
await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/audit-log?d=${encryptedId}`, {
waitUntil: 'networkidle',
timeout: 10_000,
});
const result = await page.pdf({
format: 'A4',
});
await browserContext.close();
void browser.close();
return result;
} catch (error) {
await browserContext.close();
void browser.close();
throw error;
}
};

View File

@@ -17,7 +17,6 @@ export type UpdateTeamDocumentSettingsOptions = {
includeSenderDetails: boolean; includeSenderDetails: boolean;
typedSignatureEnabled: boolean; typedSignatureEnabled: boolean;
includeSigningCertificate: boolean; includeSigningCertificate: boolean;
includeAuditTrailLog: boolean;
}; };
}; };
@@ -37,7 +36,6 @@ export const updateTeamDocumentSettings = async ({
documentLanguage, documentLanguage,
includeSenderDetails, includeSenderDetails,
includeSigningCertificate, includeSigningCertificate,
includeAuditTrailLog,
typedSignatureEnabled, typedSignatureEnabled,
} = settings; } = settings;
@@ -63,7 +61,6 @@ export const updateTeamDocumentSettings = async ({
includeSenderDetails, includeSenderDetails,
typedSignatureEnabled, typedSignatureEnabled,
includeSigningCertificate, includeSigningCertificate,
includeAuditTrailLog,
}, },
update: { update: {
documentVisibility, documentVisibility,
@@ -71,7 +68,6 @@ export const updateTeamDocumentSettings = async ({
includeSenderDetails, includeSenderDetails,
typedSignatureEnabled, typedSignatureEnabled,
includeSigningCertificate, includeSigningCertificate,
includeAuditTrailLog,
}, },
}); });
}; };

View File

@@ -29,9 +29,7 @@ export const ZDocumentAuditLogTypeSchema = z.enum([
'DOCUMENT_FIELD_INSERTED', // When a field is inserted (signed/approved/etc) by a recipient. 'DOCUMENT_FIELD_INSERTED', // When a field is inserted (signed/approved/etc) by a recipient.
'DOCUMENT_FIELD_UNINSERTED', // When a field is uninserted by a recipient. 'DOCUMENT_FIELD_UNINSERTED', // When a field is uninserted by a recipient.
'DOCUMENT_FIELD_PREFILLED', // When a field is prefilled by an assistant. 'DOCUMENT_FIELD_PREFILLED', // When a field is prefilled by an assistant.
'DOCUMENT_VISIBILITY_UPDATED', // When the document visibility scope is updated. 'DOCUMENT_VISIBILITY_UPDATED', // When the document visibility scope is updated
'DOCUMENT_SIGNING_CERTIFICATE_UPDATED', // When the include signing certificate is updated.
'DOCUMENT_AUDIT_TRAIL_UPDATED', // When the include audit trail is updated.
'DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED', // When the global access authentication is updated. 'DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED', // When the global access authentication is updated.
'DOCUMENT_GLOBAL_AUTH_ACTION_UPDATED', // When the global action authentication is updated. 'DOCUMENT_GLOBAL_AUTH_ACTION_UPDATED', // When the global action authentication is updated.
'DOCUMENT_META_UPDATED', // When the document meta data is updated. 'DOCUMENT_META_UPDATED', // When the document meta data is updated.
@@ -399,16 +397,6 @@ export const ZDocumentAuditLogEventDocumentVisibilitySchema = z.object({
data: ZGenericFromToSchema, data: ZGenericFromToSchema,
}); });
export const ZDocumentAuditLogEventDocumentSigningCertificateUpdatedSchema = z.object({
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SIGNING_CERTIFICATE_UPDATED),
data: ZGenericFromToSchema,
});
export const ZDocumentAuditLogEventDocumentAuditTrailUpdatedSchema = z.object({
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_AUDIT_TRAIL_UPDATED),
data: ZGenericFromToSchema,
});
/** /**
* Event: Document global authentication access updated. * Event: Document global authentication access updated.
*/ */
@@ -586,8 +574,6 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
ZDocumentAuditLogEventDocumentFieldUninsertedSchema, ZDocumentAuditLogEventDocumentFieldUninsertedSchema,
ZDocumentAuditLogEventDocumentFieldPrefilledSchema, ZDocumentAuditLogEventDocumentFieldPrefilledSchema,
ZDocumentAuditLogEventDocumentVisibilitySchema, ZDocumentAuditLogEventDocumentVisibilitySchema,
ZDocumentAuditLogEventDocumentSigningCertificateUpdatedSchema,
ZDocumentAuditLogEventDocumentAuditTrailUpdatedSchema,
ZDocumentAuditLogEventDocumentGlobalAuthAccessUpdatedSchema, ZDocumentAuditLogEventDocumentGlobalAuthAccessUpdatedSchema,
ZDocumentAuditLogEventDocumentGlobalAuthActionUpdatedSchema, ZDocumentAuditLogEventDocumentGlobalAuthActionUpdatedSchema,
ZDocumentAuditLogEventDocumentMetaUpdatedSchema, ZDocumentAuditLogEventDocumentMetaUpdatedSchema,

View File

@@ -18,8 +18,6 @@ import { ZRecipientLiteSchema } from './recipient';
*/ */
export const ZDocumentSchema = DocumentSchema.pick({ export const ZDocumentSchema = DocumentSchema.pick({
visibility: true, visibility: true,
includeSigningCertificate: true,
includeAuditTrailLog: true,
status: true, status: true,
source: true, source: true,
id: true, id: true,
@@ -84,8 +82,6 @@ export const ZDocumentLiteSchema = DocumentSchema.pick({
deletedAt: true, deletedAt: true,
teamId: true, teamId: true,
templateId: true, templateId: true,
includeSigningCertificate: true,
includeAuditTrailLog: true,
}); });
/** /**
@@ -108,8 +104,6 @@ export const ZDocumentManySchema = DocumentSchema.pick({
deletedAt: true, deletedAt: true,
teamId: true, teamId: true,
templateId: true, templateId: true,
includeSigningCertificate: true,
includeAuditTrailLog: true,
}).extend({ }).extend({
user: UserSchema.pick({ user: UserSchema.pick({
id: true, id: true,

View File

@@ -322,14 +322,6 @@ export const formatDocumentAuditLogAction = (
anonymous: msg`Document visibility updated`, anonymous: msg`Document visibility updated`,
identified: msg`${prefix} updated the document visibility`, identified: msg`${prefix} updated the document visibility`,
})) }))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SIGNING_CERTIFICATE_UPDATED }, () => ({
anonymous: msg`Document signing certificate updated`,
identified: msg`${prefix} updated the document signing certificate`,
}))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_AUDIT_TRAIL_UPDATED }, () => ({
anonymous: msg`Document audit trail updated`,
identified: msg`${prefix} updated the document audit trail`,
}))
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED }, () => ({ .with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_GLOBAL_AUTH_ACCESS_UPDATED }, () => ({
anonymous: msg`Document access auth updated`, anonymous: msg`Document access auth updated`,
identified: msg`${prefix} updated the document access auth requirements`, identified: msg`${prefix} updated the document access auth requirements`,

View File

@@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "includeAuditTrailLog" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -1,3 +0,0 @@
-- AlterTable
ALTER TABLE "Document" ADD COLUMN "includeAuditTrail" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "includeSigningCertificate" BOOLEAN NOT NULL DEFAULT true;

View File

@@ -1,9 +0,0 @@
/*
Warnings:
- You are about to drop the column `includeAuditTrail` on the `Document` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Document" DROP COLUMN "includeAuditTrail",
ADD COLUMN "includeAuditTrailLog" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -311,32 +311,30 @@ enum DocumentVisibility {
/// @zod.import(["import { ZDocumentAuthOptionsSchema } from '@documenso/lib/types/document-auth';", "import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';"]) /// @zod.import(["import { ZDocumentAuthOptionsSchema } from '@documenso/lib/types/document-auth';", "import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';"])
model Document { model Document {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
externalId String? /// @zod.string.describe("A custom external ID you can use to identify the document.") externalId String? /// @zod.string.describe("A custom external ID you can use to identify the document.")
userId Int /// @zod.number.describe("The ID of the user that created this document.") userId Int /// @zod.number.describe("The ID of the user that created this document.")
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
authOptions Json? /// [DocumentAuthOptions] @zod.custom.use(ZDocumentAuthOptionsSchema) authOptions Json? /// [DocumentAuthOptions] @zod.custom.use(ZDocumentAuthOptionsSchema)
formValues Json? /// [DocumentFormValues] @zod.custom.use(ZDocumentFormValuesSchema) formValues Json? /// [DocumentFormValues] @zod.custom.use(ZDocumentFormValuesSchema)
visibility DocumentVisibility @default(EVERYONE) visibility DocumentVisibility @default(EVERYONE)
includeSigningCertificate Boolean @default(true) title String
includeAuditTrailLog Boolean @default(false) status DocumentStatus @default(DRAFT)
title String recipients Recipient[]
status DocumentStatus @default(DRAFT) fields Field[]
recipients Recipient[] shareLinks DocumentShareLink[]
fields Field[] documentDataId String
shareLinks DocumentShareLink[] documentData DocumentData @relation(fields: [documentDataId], references: [id], onDelete: Cascade)
documentDataId String documentMeta DocumentMeta?
documentData DocumentData @relation(fields: [documentDataId], references: [id], onDelete: Cascade) createdAt DateTime @default(now())
documentMeta DocumentMeta? updatedAt DateTime @default(now()) @updatedAt
createdAt DateTime @default(now()) completedAt DateTime?
updatedAt DateTime @default(now()) @updatedAt deletedAt DateTime?
completedAt DateTime? teamId Int?
deletedAt DateTime? team Team? @relation(fields: [teamId], references: [id])
teamId Int? templateId Int?
team Team? @relation(fields: [teamId], references: [id]) template Template? @relation(fields: [templateId], references: [id], onDelete: SetNull)
templateId Int? source DocumentSource
template Template? @relation(fields: [templateId], references: [id], onDelete: SetNull)
source DocumentSource
auditLogs DocumentAuditLog[] auditLogs DocumentAuditLog[]
@@ -545,7 +543,6 @@ model TeamGlobalSettings {
includeSenderDetails Boolean @default(true) includeSenderDetails Boolean @default(true)
typedSignatureEnabled Boolean @default(true) typedSignatureEnabled Boolean @default(true)
includeSigningCertificate Boolean @default(true) includeSigningCertificate Boolean @default(true)
includeAuditTrailLog Boolean @default(false)
brandingEnabled Boolean @default(false) brandingEnabled Boolean @default(false)
brandingLogo String @default("") brandingLogo String @default("")

View File

@@ -65,7 +65,7 @@ export const documentRouter = router({
.input(ZGetDocumentByIdQuerySchema) .input(ZGetDocumentByIdQuerySchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
const { teamId } = ctx; const { teamId } = ctx;
const { documentId, includeCertificate, includeAuditLog } = input; const { documentId } = input;
return await getDocumentById({ return await getDocumentById({
userId: ctx.user.id, userId: ctx.user.id,

View File

@@ -63,16 +63,6 @@ export const ZDocumentVisibilitySchema = z
.nativeEnum(DocumentVisibility) .nativeEnum(DocumentVisibility)
.describe('The visibility of the document.'); .describe('The visibility of the document.');
export const ZDocumentIncludeSigningCertificateSchema = z
.boolean()
.default(true)
.describe('Whether to include a signing certificate in the document.');
export const ZDocumentIncludeAuditTrailSchema = z
.boolean()
.default(true)
.describe('Whether to include an audit trail in the document.');
export const ZDocumentMetaTimezoneSchema = z export const ZDocumentMetaTimezoneSchema = z
.string() .string()
.describe( .describe(
@@ -151,8 +141,6 @@ export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend(
export const ZGetDocumentByIdQuerySchema = z.object({ export const ZGetDocumentByIdQuerySchema = z.object({
documentId: z.number(), documentId: z.number(),
includeCertificate: z.boolean().default(true).optional(),
includeAuditLog: z.boolean().default(true).optional(),
}); });
export const ZDuplicateDocumentRequestSchema = z.object({ export const ZDuplicateDocumentRequestSchema = z.object({
@@ -247,8 +235,6 @@ export const ZUpdateDocumentRequestSchema = z.object({
title: ZDocumentTitleSchema.optional(), title: ZDocumentTitleSchema.optional(),
externalId: ZDocumentExternalIdSchema.nullish(), externalId: ZDocumentExternalIdSchema.nullish(),
visibility: ZDocumentVisibilitySchema.optional(), visibility: ZDocumentVisibilitySchema.optional(),
includeSigningCertificate: ZDocumentIncludeSigningCertificateSchema.optional(),
includeAuditTrailLog: ZDocumentIncludeAuditTrailSchema.optional(),
globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullish(), globalAccessAuth: ZDocumentAccessAuthTypesSchema.nullish(),
globalActionAuth: ZDocumentActionAuthTypesSchema.nullish(), globalActionAuth: ZDocumentActionAuthTypesSchema.nullish(),
}) })

View File

@@ -206,7 +206,6 @@ export const ZUpdateTeamDocumentSettingsMutationSchema = z.object({
includeSenderDetails: z.boolean().optional().default(false), includeSenderDetails: z.boolean().optional().default(false),
typedSignatureEnabled: z.boolean().optional().default(true), typedSignatureEnabled: z.boolean().optional().default(true),
includeSigningCertificate: z.boolean().optional().default(true), includeSigningCertificate: z.boolean().optional().default(true),
includeAuditTrailLog: z.boolean().optional().default(true),
}), }),
}); });

View File

@@ -42,7 +42,6 @@ import {
FormMessage, FormMessage,
} from '@documenso/ui/primitives/form/form'; } from '@documenso/ui/primitives/form/form';
import { Checkbox } from '../checkbox';
import { Combobox } from '../combobox'; import { Combobox } from '../combobox';
import { Input } from '../input'; import { Input } from '../input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../select';
@@ -93,8 +92,6 @@ export const AddSettingsFormPartial = ({
visibility: document.visibility || '', visibility: document.visibility || '',
globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined, globalAccessAuth: documentAuthOption?.globalAccessAuth || undefined,
globalActionAuth: documentAuthOption?.globalActionAuth || undefined, globalActionAuth: documentAuthOption?.globalActionAuth || undefined,
includeSigningCertificate: document.includeSigningCertificate ?? true,
includeAuditTrailLog: document.includeAuditTrailLog ?? true,
meta: { meta: {
timezone: timezone:
TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ?? TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ??
@@ -262,111 +259,6 @@ export const AddSettingsFormPartial = ({
/> />
)} )}
<FormField
control={form.control}
name="globalActionAuth"
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row items-center">
<Trans>Recipient action authentication</Trans>
<DocumentGlobalAuthActionTooltip />
</FormLabel>
<FormControl>
<DocumentGlobalAuthActionSelect {...field} onValueChange={field.onChange} />
</FormControl>
</FormItem>
)}
/>
<Accordion type="multiple" className="mt-6">
<AccordionItem value="advanced-options" className="border-none">
<AccordionTrigger className="text-foreground mb-2 rounded border px-3 py-2 text-left hover:bg-neutral-200/30 hover:no-underline">
<Trans>Certificates</Trans>
</AccordionTrigger>
<AccordionContent className="text-muted-foreground -mx-1 px-1 pt-2 text-sm leading-relaxed">
<div className="flex flex-col space-y-6">
<FormField
control={form.control}
name="includeSigningCertificate"
render={({ field }) => (
<FormItem>
<div className="flex flex-row items-center gap-4">
<FormControl>
<Checkbox
checked={field.value}
className="h-5 w-5"
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel className="m-0 flex flex-row items-center">
<Trans>Include signing certificate</Trans>{' '}
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-muted-foreground max-w-xs">
<Trans>
Including the signing certificate means that the certificate
will be attached to the document. You won't be able to remove
it. <br />
<br />
If you don't include it, you can download it individually.
</Trans>
</TooltipContent>
</Tooltip>
</FormLabel>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="includeAuditTrailLog"
render={({ field }) => (
<FormItem>
<div className="flex flex-row items-center gap-4">
<FormControl>
<Checkbox
checked={field.value}
className="h-5 w-5"
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel className="m-0 flex flex-row items-center">
<Trans>Include audit trail</Trans>{' '}
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-muted-foreground max-w-xs">
<Trans>
Including the audit trail means that the log of all actions will
be attached to the document. You won't be able to remove it.{' '}
<br />
<br />
If you don't include it, you can download it individually.
</Trans>
</TooltipContent>
</Tooltip>
</FormLabel>
</div>
<FormMessage />
</FormItem>
)}
/>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
{isDocumentEnterprise && ( {isDocumentEnterprise && (
<FormField <FormField
control={form.control} control={form.control}

View File

@@ -29,8 +29,6 @@ export const ZAddSettingsFormSchema = 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(), visibility: z.nativeEnum(DocumentVisibility).optional(),
includeSigningCertificate: z.boolean().default(true).optional(),
includeAuditTrailLog: z.boolean().default(true).optional(),
globalAccessAuth: ZMapNegativeOneToUndefinedSchema.pipe( globalAccessAuth: ZMapNegativeOneToUndefinedSchema.pipe(
ZDocumentAccessAuthTypesSchema.optional(), ZDocumentAccessAuthTypesSchema.optional(),
), ),

View File

@@ -1,83 +0,0 @@
'use client';
import * as React from 'react';
import { ChevronDown } from 'lucide-react';
import { cn } from '../lib/utils';
import { Button } from './button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from './dropdown-menu';
const SplitButtonContext = React.createContext<{
variant?: React.ComponentProps<typeof Button>['variant'];
size?: React.ComponentProps<typeof Button>['size'];
}>({});
const SplitButton = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {
variant?: React.ComponentProps<typeof Button>['variant'];
size?: React.ComponentProps<typeof Button>['size'];
}
>(({ className, children, variant = 'default', size = 'default', ...props }, ref) => {
return (
<SplitButtonContext.Provider value={{ variant, size }}>
<div ref={ref} className={cn('inline-flex', className)} {...props}>
{children}
</div>
</SplitButtonContext.Provider>
);
});
SplitButton.displayName = 'SplitButton';
const SplitButtonAction = React.forwardRef<
HTMLButtonElement,
React.ButtonHTMLAttributes<HTMLButtonElement>
>(({ className, children, ...props }, ref) => {
const { variant, size } = React.useContext(SplitButtonContext);
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn('rounded-r-none border-r-0', className)}
{...props}
>
{children}
</Button>
);
});
SplitButtonAction.displayName = 'SplitButtonAction';
const SplitButtonDropdown = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ children, ...props }, ref) => {
const { variant, size } = React.useContext(SplitButtonContext);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant={variant}
size={size}
className="rounded-l-none px-2 focus-visible:ring-offset-0"
>
<ChevronDown className="h-4 w-4" />
<span className="sr-only">More options</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" {...props} ref={ref}>
{children}
</DropdownMenuContent>
</DropdownMenu>
);
},
);
SplitButtonDropdown.displayName = 'SplitButtonDropdown';
const SplitButtonDropdownItem = DropdownMenuItem;
export { SplitButton, SplitButtonAction, SplitButtonDropdown, SplitButtonDropdownItem };