diff --git a/apps/remix/app/components/general/document/document-audit-log-download-button.tsx b/apps/remix/app/components/general/document/document-audit-log-download-button.tsx index 77e90eff8..9c8246e6c 100644 --- a/apps/remix/app/components/general/document/document-audit-log-download-button.tsx +++ b/apps/remix/app/components/general/document/document-audit-log-download-button.tsx @@ -3,6 +3,8 @@ import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; import { DownloadIcon } from 'lucide-react'; +import { downloadFile } from '@documenso/lib/client-only/download-file'; +import { base64 } from '@documenso/lib/universal/base64'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; @@ -25,36 +27,15 @@ export const DocumentAuditLogDownloadButton = ({ const onDownloadAuditLogsClick = async () => { try { - const { url } = await downloadAuditLogs({ documentId }); + const { data, envelopeTitle } = await downloadAuditLogs({ documentId }); - const iframe = Object.assign(document.createElement('iframe'), { - src: url, + const buffer = new Uint8Array(base64.decode(data)); + const blob = new Blob([buffer], { type: 'application/pdf' }); + + downloadFile({ + data: blob, + filename: `${envelopeTitle} - Audit Logs.pdf`, }); - - 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); diff --git a/apps/remix/app/components/general/document/document-certificate-download-button.tsx b/apps/remix/app/components/general/document/document-certificate-download-button.tsx index 7584fe81b..67bc173c7 100644 --- a/apps/remix/app/components/general/document/document-certificate-download-button.tsx +++ b/apps/remix/app/components/general/document/document-certificate-download-button.tsx @@ -4,6 +4,8 @@ import { Trans } from '@lingui/react/macro'; import type { DocumentStatus } from '@prisma/client'; import { DownloadIcon } from 'lucide-react'; +import { downloadFile } from '@documenso/lib/client-only/download-file'; +import { base64 } from '@documenso/lib/universal/base64'; import { isDocumentCompleted } from '@documenso/lib/utils/document'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; @@ -29,36 +31,15 @@ export const DocumentCertificateDownloadButton = ({ const onDownloadCertificatesClick = async () => { try { - const { url } = await downloadCertificate({ documentId }); + const { data, envelopeTitle } = await downloadCertificate({ documentId }); - const iframe = Object.assign(document.createElement('iframe'), { - src: url, + const buffer = new Uint8Array(base64.decode(data)); + const blob = new Blob([buffer], { type: 'application/pdf' }); + + downloadFile({ + data: blob, + filename: `${envelopeTitle} - Certificate.pdf`, }); - - 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); diff --git a/packages/trpc/server/document-router/download-document-audit-logs.ts b/packages/trpc/server/document-router/download-document-audit-logs.ts index 7ce76049e..9da3c2a68 100644 --- a/packages/trpc/server/document-router/download-document-audit-logs.ts +++ b/packages/trpc/server/document-router/download-document-audit-logs.ts @@ -1,11 +1,9 @@ import { EnvelopeType } from '@prisma/client'; -import { DateTime } from 'luxon'; -import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; +import { PDF_SIZE_A4_72PPI } from '@documenso/lib/constants/pdf'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; -import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt'; import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id'; -import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope'; +import { generateAuditLogPdf } from '@documenso/lib/server-only/pdf/generate-audit-log-pdf'; import { authenticatedProcedure } from '../trpc'; import { @@ -42,12 +40,26 @@ export const downloadDocumentAuditLogsRoute = authenticatedProcedure }); } - const encrypted = encryptSecondaryData({ - data: mapSecondaryIdToDocumentId(envelope.secondaryId).toString(), - expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(), + const certificatePdf = await generateAuditLogPdf({ + envelope, + recipients: envelope.recipients, + fields: envelope.fields, + language: envelope.documentMeta.language, + envelopeOwner: { + email: envelope.user.email, + name: envelope.user.name || '', + }, + envelopeItems: envelope.envelopeItems.map((item) => item.title), + pageWidth: PDF_SIZE_A4_72PPI.width, + pageHeight: PDF_SIZE_A4_72PPI.height, }); + const result = await certificatePdf.save(); + + const base64 = Buffer.from(result).toString('base64'); + return { - url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/audit-log?d=${encrypted}`, + data: base64, + envelopeTitle: envelope.title, }; }); diff --git a/packages/trpc/server/document-router/download-document-audit-logs.types.ts b/packages/trpc/server/document-router/download-document-audit-logs.types.ts index b4cc209c2..8e0ca39ad 100644 --- a/packages/trpc/server/document-router/download-document-audit-logs.types.ts +++ b/packages/trpc/server/document-router/download-document-audit-logs.types.ts @@ -5,7 +5,8 @@ export const ZDownloadDocumentAuditLogsRequestSchema = z.object({ }); export const ZDownloadDocumentAuditLogsResponseSchema = z.object({ - url: z.string(), + data: z.string(), + envelopeTitle: z.string(), }); export type TDownloadDocumentAuditLogsRequest = z.infer< diff --git a/packages/trpc/server/document-router/download-document-certificate.ts b/packages/trpc/server/document-router/download-document-certificate.ts index e81ec5082..82fc80588 100644 --- a/packages/trpc/server/document-router/download-document-certificate.ts +++ b/packages/trpc/server/document-router/download-document-certificate.ts @@ -1,12 +1,11 @@ import { EnvelopeType } from '@prisma/client'; -import { DateTime } from 'luxon'; -import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; -import { AppError } from '@documenso/lib/errors/app-error'; -import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt'; -import { getEnvelopeById } from '@documenso/lib/server-only/envelope/get-envelope-by-id'; +import { PDF_SIZE_A4_72PPI } from '@documenso/lib/constants/pdf'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id'; +import { generateCertificatePdf } from '@documenso/lib/server-only/pdf/generate-certificate-pdf'; import { isDocumentCompleted } from '@documenso/lib/utils/document'; -import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope'; +import { prisma } from '@documenso/prisma'; import { authenticatedProcedure } from '../trpc'; import { @@ -27,7 +26,7 @@ export const downloadDocumentCertificateRoute = authenticatedProcedure }, }); - const envelope = await getEnvelopeById({ + const { envelopeWhereInput } = await getEnvelopeWhereInput({ id: { type: 'documentId', id: documentId, @@ -37,16 +36,54 @@ export const downloadDocumentCertificateRoute = authenticatedProcedure teamId, }); + const envelope = await prisma.envelope.findFirst({ + where: envelopeWhereInput, + include: { + recipients: true, + fields: { + include: { + signature: true, + }, + }, + documentMeta: true, + user: { + select: { + email: true, + name: true, + }, + }, + }, + }); + + if (!envelope) { + throw new AppError(AppErrorCode.NOT_FOUND, { + message: 'Envelope not found', + }); + } + if (!isDocumentCompleted(envelope.status)) { throw new AppError('DOCUMENT_NOT_COMPLETE'); } - const encrypted = encryptSecondaryData({ - data: mapSecondaryIdToDocumentId(envelope.secondaryId).toString(), - expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(), + const certificatePdf = await generateCertificatePdf({ + envelope, + recipients: envelope.recipients, + fields: envelope.fields, + language: envelope.documentMeta.language, + envelopeOwner: { + email: envelope.user.email, + name: envelope.user.name || '', + }, + pageWidth: PDF_SIZE_A4_72PPI.width, + pageHeight: PDF_SIZE_A4_72PPI.height, }); + const result = await certificatePdf.save(); + + const base64 = Buffer.from(result).toString('base64'); + return { - url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`, + data: base64, + envelopeTitle: envelope.title, }; }); diff --git a/packages/trpc/server/document-router/download-document-certificate.types.ts b/packages/trpc/server/document-router/download-document-certificate.types.ts index df81f1cad..ab0aeec22 100644 --- a/packages/trpc/server/document-router/download-document-certificate.types.ts +++ b/packages/trpc/server/document-router/download-document-certificate.types.ts @@ -5,7 +5,8 @@ export const ZDownloadDocumentCertificateRequestSchema = z.object({ }); export const ZDownloadDocumentCertificateResponseSchema = z.object({ - url: z.string(), + data: z.string(), + envelopeTitle: z.string(), }); export type TDownloadDocumentCertificateRequest = z.infer<