feat: certificate qrcode (#1755)

Adds document access tokens and QR code functionality to enable secure
document sharing via URLs. It includes a new document access page that
allows viewing and downloading documents through tokenized links.
This commit is contained in:
Ephraim Duncan
2025-04-28 01:30:09 +00:00
committed by GitHub
parent 6a41a37bd4
commit bdb0b0ea88
26 changed files with 423 additions and 31 deletions

View File

@ -0,0 +1,106 @@
import { useEffect, useState } from 'react';
import { Trans } from '@lingui/react/macro';
import type { DocumentData } from '@prisma/client';
import { DateTime } from 'luxon';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { PDFViewer } from '@documenso/ui/primitives/pdf-viewer';
import { ShareDocumentDownloadButton } from '../share-document-download-button';
export type DocumentCertificateQRViewProps = {
documentId: number;
title: string;
documentData: DocumentData;
password?: string | null;
recipientCount?: number;
completedDate?: Date;
};
export const DocumentCertificateQRView = ({
documentId,
title,
documentData,
password,
recipientCount = 0,
completedDate,
}: DocumentCertificateQRViewProps) => {
const { data: documentUrl } = trpc.shareLink.getDocumentInternalUrlForQRCode.useQuery({
documentId,
});
const [isDialogOpen, setIsDialogOpen] = useState(() => !!documentUrl);
const formattedDate = completedDate
? DateTime.fromJSDate(completedDate).toLocaleString(DateTime.DATETIME_MED)
: '';
useEffect(() => {
if (documentUrl) {
setIsDialogOpen(true);
}
}, [documentUrl]);
return (
<div className="mx-auto w-full max-w-screen-md">
{/* Dialog for internal document link */}
{documentUrl && (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans>Document found in your account</Trans>
</DialogTitle>
<DialogDescription>
<Trans>
This document is available in your Documenso account. You can view more details,
recipients, and audit logs there.
</Trans>
</DialogDescription>
</DialogHeader>
<DialogFooter className="flex flex-row justify-end gap-2">
<Button asChild>
<a href={documentUrl} target="_blank" rel="noopener noreferrer">
<Trans>Go to document</Trans>
</a>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
<div className="flex w-full flex-col justify-between gap-4 md:flex-row md:items-end">
<div className="space-y-1">
<h1 className="text-xl font-medium">{title}</h1>
<div className="text-muted-foreground flex flex-col gap-0.5 text-sm">
<p>
<Trans>{recipientCount} recipients</Trans>
</p>
<p>
<Trans>Completed on {formattedDate}</Trans>
</p>
</div>
</div>
<ShareDocumentDownloadButton title={title} documentData={documentData} />
</div>
<div className="mt-12 w-full">
<PDFViewer key={documentData.id} documentData={documentData} password={password} />
</div>
</div>
);
};

View File

@ -0,0 +1,53 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Download } from 'lucide-react';
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
import type { DocumentData } from '@documenso/prisma/client';
import { Button } from '@documenso/ui/primitives/button';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type ShareDocumentDownloadButtonProps = {
title: string;
documentData: DocumentData;
};
export const ShareDocumentDownloadButton = ({
title,
documentData,
}: ShareDocumentDownloadButtonProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const [isDownloading, setIsDownloading] = useState(false);
const onDownloadClick = async () => {
try {
setIsDownloading(true);
await new Promise((resolve) => {
setTimeout(resolve, 4000);
});
await downloadPDF({ documentData, fileName: title });
} catch (err) {
toast({
title: _(msg`Something went wrong`),
description: _(msg`An error occurred while downloading your document.`),
variant: 'destructive',
});
} finally {
setIsDownloading(false);
}
};
return (
<Button loading={isDownloading} onClick={onDownloadClick}>
{!isDownloading && <Download className="mr-2 h-4 w-4" />}
<Trans>Download</Trans>
</Button>
);
};