From 7080a36f21d0ece5244a01193a878b979372d13c Mon Sep 17 00:00:00 2001 From: Catalin Pit Date: Wed, 10 Sep 2025 14:26:10 +0300 Subject: [PATCH] feat: use the api routes for downloading signing certificate and audit logs --- .../document-audit-log-download-button.tsx | 55 +++++++++---------- .../document-certificate-download-button.tsx | 32 +++++++---- ...teamUrl.download.audit-logs.$documentId.ts | 1 - ...eamUrl.download.certificate.$documentId.ts | 1 - 4 files changed, 45 insertions(+), 44 deletions(-) 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..d53a7f4d1 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 @@ -1,13 +1,16 @@ +import { useState } from 'react'; + import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; import { DownloadIcon } from 'lucide-react'; -import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; import { useToast } from '@documenso/ui/primitives/use-toast'; +import { useCurrentTeam } from '~/providers/team'; + export type DocumentAuditLogDownloadButtonProps = { className?: string; documentId: number; @@ -19,44 +22,34 @@ export const DocumentAuditLogDownloadButton = ({ }: DocumentAuditLogDownloadButtonProps) => { const { toast } = useToast(); const { _ } = useLingui(); - - const { mutateAsync: downloadAuditLogs, isPending } = - trpc.document.auditLog.download.useMutation(); + const [isPending, setIsPending] = useState(false); + const team = useCurrentTeam(); const onDownloadAuditLogsClick = async () => { + setIsPending(true); + try { - const { url } = await downloadAuditLogs({ documentId }); + const response = await fetch(`/api/t/${team.url}/download/audit-logs/${documentId}`); - const iframe = Object.assign(document.createElement('iframe'), { - src: url, - }); + const contentDisposition = response.headers.get('Content-Disposition'); + const filename = + contentDisposition?.split('filename="')[1]?.split('"')[0] || + `document_${documentId}_audit_logs.pdf`; - Object.assign(iframe.style, { - position: 'fixed', - top: '0', - left: '0', - width: '0', - height: '0', - }); + const blob = await response.blob(); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); - const onLoaded = () => { - if (iframe.contentDocument?.readyState === 'complete') { - iframe.contentWindow?.print(); + link.href = url; + link.download = filename; - iframe.contentWindow?.addEventListener('afterprint', () => { - document.body.removeChild(iframe); - }); - } - }; + document.body.appendChild(link); + link.click(); - // When the iframe has loaded, print the iframe and remove it from the dom - iframe.addEventListener('load', onLoaded); - - document.body.appendChild(iframe); - - onLoaded(); + document.body.removeChild(link); + URL.revokeObjectURL(url); } catch (error) { - console.error(error); + console.error('Audit logs download error:', error); toast({ title: _(msg`Something went wrong`), @@ -65,6 +58,8 @@ export const DocumentAuditLogDownloadButton = ({ ), variant: 'destructive', }); + } finally { + setIsPending(false); } }; 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 082baa440..cff841f72 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 @@ -1,3 +1,5 @@ +import { useState } from 'react'; + import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; @@ -5,11 +7,12 @@ import type { DocumentStatus } from '@prisma/client'; import { DownloadIcon } from 'lucide-react'; import { isDocumentCompleted } from '@documenso/lib/utils/document'; -import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; import { useToast } from '@documenso/ui/primitives/use-toast'; +import { useCurrentTeam } from '~/providers/team'; + export type DocumentCertificateDownloadButtonProps = { className?: string; documentId: number; @@ -23,24 +26,25 @@ export const DocumentCertificateDownloadButton = ({ }: DocumentCertificateDownloadButtonProps) => { const { toast } = useToast(); const { _ } = useLingui(); - - const { mutateAsync: downloadCertificate, isPending } = - trpc.document.downloadCertificate.useMutation(); + const [isPending, setIsPending] = useState(false); + const team = useCurrentTeam(); const onDownloadCertificatesClick = async () => { + setIsPending(true); + try { - const { pdfData, filename } = await downloadCertificate({ documentId }); + const response = await fetch(`/api/t/${team.url}/download/certificate/${documentId}`); - const byteCharacters = atob(pdfData); - const byteNumbers = new Array(byteCharacters.length); - - for (let i = 0; i < byteCharacters.length; i++) { - byteNumbers[i] = byteCharacters.charCodeAt(i); + if (!response.ok) { + throw new Error('Failed to download certificate'); } - const byteArray = new Uint8Array(byteNumbers); - const blob = new Blob([byteArray], { type: 'application/pdf' }); + const contentDisposition = response.headers.get('Content-Disposition'); + const filename = + contentDisposition?.split('filename="')[1]?.split('"')[0] || + `document_${documentId}_certificate.pdf`; + const blob = await response.blob(); const url = URL.createObjectURL(blob); const link = document.createElement('a'); @@ -53,6 +57,8 @@ export const DocumentCertificateDownloadButton = ({ document.body.removeChild(link); URL.revokeObjectURL(url); } catch (error) { + console.error('Certificate download error:', error); + toast({ title: _(msg`Something went wrong`), description: _( @@ -60,6 +66,8 @@ export const DocumentCertificateDownloadButton = ({ ), variant: 'destructive', }); + } finally { + setIsPending(false); } }; diff --git a/apps/remix/app/routes/api+/t.$teamUrl.download.audit-logs.$documentId.ts b/apps/remix/app/routes/api+/t.$teamUrl.download.audit-logs.$documentId.ts index a5831ff41..dbeb73b54 100644 --- a/apps/remix/app/routes/api+/t.$teamUrl.download.audit-logs.$documentId.ts +++ b/apps/remix/app/routes/api+/t.$teamUrl.download.audit-logs.$documentId.ts @@ -46,7 +46,6 @@ export async function loader({ request, params }: Route.LoaderArgs) { 'Content-Disposition': `attachment; filename="${filename}"`, 'Content-Length': pdfBuffer.length.toString(), 'Cache-Control': 'no-cache, no-store, must-revalidate', - Pragma: 'no-cache', Expires: '0', }, }); diff --git a/apps/remix/app/routes/api+/t.$teamUrl.download.certificate.$documentId.ts b/apps/remix/app/routes/api+/t.$teamUrl.download.certificate.$documentId.ts index df947c32b..9ee911f35 100644 --- a/apps/remix/app/routes/api+/t.$teamUrl.download.certificate.$documentId.ts +++ b/apps/remix/app/routes/api+/t.$teamUrl.download.certificate.$documentId.ts @@ -54,7 +54,6 @@ export async function loader({ request, params }: Route.LoaderArgs) { 'Content-Disposition': `attachment; filename="${filename}"`, 'Content-Length': pdfBuffer.length.toString(), 'Cache-Control': 'no-cache, no-store, must-revalidate', - Pragma: 'no-cache', Expires: '0', }, });