mirror of
https://github.com/documenso/documenso.git
synced 2025-11-20 03:32:14 +10:00
feat: download options for document
This commit is contained in:
@ -14,6 +14,12 @@ 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 {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@documenso/ui/primitives/dropdown-menu';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
export type DocumentPageViewButtonProps = {
|
export type DocumentPageViewButtonProps = {
|
||||||
@ -44,11 +50,19 @@ export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps
|
|||||||
|
|
||||||
const documentsPath = formatDocumentsPath(document.team?.url);
|
const documentsPath = formatDocumentsPath(document.team?.url);
|
||||||
|
|
||||||
const onDownloadClick = async () => {
|
const onDownloadClick = async ({
|
||||||
|
includeCertificate = true,
|
||||||
|
includeAuditLog = true,
|
||||||
|
}: {
|
||||||
|
includeCertificate?: boolean;
|
||||||
|
includeAuditLog?: boolean;
|
||||||
|
} = {}) => {
|
||||||
try {
|
try {
|
||||||
const documentWithData = await trpcClient.document.getDocumentById.query(
|
const documentWithData = await trpcClient.document.getDocumentById.query(
|
||||||
{
|
{
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
|
includeCertificate,
|
||||||
|
includeAuditLog,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
context: {
|
context: {
|
||||||
@ -63,7 +77,12 @@ export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps
|
|||||||
throw new Error('No document available');
|
throw new Error('No document available');
|
||||||
}
|
}
|
||||||
|
|
||||||
await downloadPDF({ documentData, fileName: documentWithData.title });
|
await downloadPDF({
|
||||||
|
documentData,
|
||||||
|
fileName: documentWithData.title,
|
||||||
|
includeCertificate,
|
||||||
|
includeAuditLog,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Something went wrong`),
|
title: _(msg`Something went wrong`),
|
||||||
@ -112,10 +131,44 @@ export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps
|
|||||||
</Button>
|
</Button>
|
||||||
))
|
))
|
||||||
.with({ isComplete: true }, () => (
|
.with({ isComplete: true }, () => (
|
||||||
<Button className="w-full" onClick={onDownloadClick}>
|
<DropdownMenu>
|
||||||
<Download className="-ml-1 mr-2 inline h-4 w-4" />
|
<DropdownMenuTrigger asChild>
|
||||||
<Trans>Download</Trans>
|
<Button className="w-full">
|
||||||
</Button>
|
<Download className="-ml-1 mr-2 inline h-4 w-4" />
|
||||||
|
<Trans>Download</Trans>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
|
<DropdownMenuContent align="end" className="w-56">
|
||||||
|
<DropdownMenuItem onClick={() => void onDownloadClick()}>
|
||||||
|
<Trans>Complete Document</Trans>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() =>
|
||||||
|
void onDownloadClick({ includeCertificate: true, includeAuditLog: false })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Trans>Without Audit Log</Trans>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() =>
|
||||||
|
void onDownloadClick({ includeCertificate: false, includeAuditLog: true })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Trans>Without Certificate</Trans>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() =>
|
||||||
|
void onDownloadClick({ includeCertificate: false, includeAuditLog: false })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Trans>Without Certificate & Audit Log</Trans>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
))
|
))
|
||||||
.otherwise(() => null);
|
.otherwise(() => null);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,9 +6,16 @@ import { downloadFile } from './download-file';
|
|||||||
type DownloadPDFProps = {
|
type DownloadPDFProps = {
|
||||||
documentData: DocumentData;
|
documentData: DocumentData;
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
|
includeCertificate?: boolean;
|
||||||
|
includeAuditLog?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const downloadPDF = async ({ documentData, fileName }: DownloadPDFProps) => {
|
export const downloadPDF = async ({
|
||||||
|
documentData,
|
||||||
|
fileName,
|
||||||
|
includeCertificate,
|
||||||
|
includeAuditLog,
|
||||||
|
}: DownloadPDFProps) => {
|
||||||
const bytes = await getFile(documentData);
|
const bytes = await getFile(documentData);
|
||||||
|
|
||||||
const blob = new Blob([bytes], {
|
const blob = new Blob([bytes], {
|
||||||
@ -17,8 +24,18 @@ export const downloadPDF = async ({ documentData, fileName }: DownloadPDFProps)
|
|||||||
|
|
||||||
const baseTitle = (fileName ?? 'document').replace(/\.pdf$/, '');
|
const baseTitle = (fileName ?? 'document').replace(/\.pdf$/, '');
|
||||||
|
|
||||||
|
let suffix = '_signed';
|
||||||
|
|
||||||
|
if (includeCertificate && includeAuditLog) {
|
||||||
|
suffix = suffix + '_with_certificate_and_audit';
|
||||||
|
} else if (includeCertificate) {
|
||||||
|
suffix = suffix + '_with_certificate';
|
||||||
|
} else if (includeAuditLog) {
|
||||||
|
suffix = suffix + '_with_audit';
|
||||||
|
}
|
||||||
|
|
||||||
downloadFile({
|
downloadFile({
|
||||||
filename: `${baseTitle}_signed.pdf`,
|
filename: `${baseTitle}${suffix}.pdf`,
|
||||||
data: blob,
|
data: blob,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
|
||||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
@ -23,6 +24,7 @@ import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/
|
|||||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||||
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
|
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
|
||||||
import { symmetricEncrypt } from '@documenso/lib/universal/crypto';
|
import { symmetricEncrypt } from '@documenso/lib/universal/crypto';
|
||||||
|
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
||||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||||
import { DocumentDataType, DocumentStatus } from '@documenso/prisma/client';
|
import { DocumentDataType, DocumentStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
@ -65,13 +67,84 @@ export const documentRouter = router({
|
|||||||
.input(ZGetDocumentByIdQuerySchema)
|
.input(ZGetDocumentByIdQuerySchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const { teamId } = ctx;
|
const { teamId } = ctx;
|
||||||
const { documentId } = input;
|
const { documentId, includeCertificate, includeAuditLog } = input;
|
||||||
|
|
||||||
return await getDocumentById({
|
const documentWithData = await getDocumentById({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
teamId,
|
teamId,
|
||||||
documentId,
|
documentId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (includeCertificate && includeAuditLog) {
|
||||||
|
return documentWithData;
|
||||||
|
} else if (includeCertificate) {
|
||||||
|
const pdfData = await getFile(documentWithData.documentData);
|
||||||
|
const pdfDoc = await PDFDocument.load(pdfData);
|
||||||
|
|
||||||
|
const totalPages = pdfDoc.getPageCount();
|
||||||
|
|
||||||
|
if (!includeAuditLog) {
|
||||||
|
pdfDoc.removePage(totalPages - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfBytes = await pdfDoc.save();
|
||||||
|
const pdfBuffer = Buffer.from(pdfBytes).toString('base64');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...documentWithData,
|
||||||
|
documentData: {
|
||||||
|
...documentWithData.documentData,
|
||||||
|
data: pdfBuffer,
|
||||||
|
initialData: documentWithData.documentData.data,
|
||||||
|
type: DocumentDataType.BYTES_64,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (includeAuditLog) {
|
||||||
|
const pdfData = await getFile(documentWithData.documentData);
|
||||||
|
const pdfDoc = await PDFDocument.load(pdfData);
|
||||||
|
|
||||||
|
const totalPages = pdfDoc.getPageCount();
|
||||||
|
|
||||||
|
if (!includeCertificate) {
|
||||||
|
pdfDoc.removePage(totalPages - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfBytes = await pdfDoc.save();
|
||||||
|
const pdfBuffer = Buffer.from(pdfBytes).toString('base64');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...documentWithData,
|
||||||
|
documentData: {
|
||||||
|
...documentWithData.documentData,
|
||||||
|
data: pdfBuffer,
|
||||||
|
initialData: documentWithData.documentData.data,
|
||||||
|
type: DocumentDataType.BYTES_64,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (!includeCertificate && !includeAuditLog) {
|
||||||
|
const pdfData = await getFile(documentWithData.documentData);
|
||||||
|
const pdfDoc = await PDFDocument.load(pdfData);
|
||||||
|
|
||||||
|
const totalPages = pdfDoc.getPageCount();
|
||||||
|
|
||||||
|
pdfDoc.removePage(totalPages - 1);
|
||||||
|
pdfDoc.removePage(totalPages - 2);
|
||||||
|
|
||||||
|
const pdfBytes = await pdfDoc.save();
|
||||||
|
const pdfBuffer = Buffer.from(pdfBytes).toString('base64');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...documentWithData,
|
||||||
|
documentData: {
|
||||||
|
...documentWithData.documentData,
|
||||||
|
data: pdfBuffer,
|
||||||
|
initialData: documentWithData.documentData.data,
|
||||||
|
type: DocumentDataType.BYTES_64,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return documentWithData;
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -141,6 +141,8 @@ 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({
|
||||||
|
|||||||
Reference in New Issue
Block a user