feat: add document view page

This commit is contained in:
Ephraim Atta-Duncan
2025-01-30 10:12:25 +00:00
parent edfb1f2157
commit d82a759acd
5 changed files with 181 additions and 0 deletions

View File

@ -0,0 +1,44 @@
'use client';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Download } from 'lucide-react';
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
import type { Document, DocumentData } from '@documenso/prisma/client';
import { Button } from '@documenso/ui/primitives/button';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type DocumentDownloadButtonProps = {
document: Document & {
documentData: DocumentData;
};
};
export const DocumentDownloadButton = ({ document }: DocumentDownloadButtonProps) => {
const { toast } = useToast();
const { _ } = useLingui();
const onDownloadClick = async () => {
try {
if (!document) {
throw new Error('No document available');
}
await downloadPDF({ documentData: document.documentData, fileName: document.title });
} catch (err) {
toast({
title: _(msg`Something went wrong`),
description: _(msg`An error occurred while downloading your document.`),
variant: 'destructive',
});
}
};
return (
<Button className="w-full" onClick={onDownloadClick}>
<Download className="-ml-1 mr-2 inline h-4 w-4" />
<Trans>Download</Trans>
</Button>
);
};

View File

@ -0,0 +1,35 @@
import React from 'react';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
import { getTeams } from '@documenso/lib/server-only/team/get-teams';
import { Header as AuthenticatedHeader } from '~/components/(dashboard)/layout/header';
import { NextAuthProvider } from '~/providers/next-auth';
export type SigningLayoutProps = {
children: React.ReactNode;
};
export default async function SigningLayout({ children }: SigningLayoutProps) {
await setupI18nSSR();
const { user, session } = await getServerComponentSession();
let teams: TGetTeamsResponse = [];
if (user && session) {
teams = await getTeams({ userId: user.id });
}
return (
<NextAuthProvider session={session}>
<div className="min-h-screen">
{user && <AuthenticatedHeader user={user} teams={teams} />}
<main className="mb-8 mt-8 px-4 md:mb-12 md:mt-12 md:px-8">{children}</main>
</div>
</NextAuthProvider>
);
}

View File

@ -0,0 +1,68 @@
import { notFound } from 'next/navigation';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { getDocumentByAccessToken } from '@documenso/lib/server-only/document/get-document-by-access-token';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
import { DocumentDownloadButton } from './document-download-button';
export type DocumentAccessPageProps = {
params: {
token?: string;
};
};
export default async function DocumentAccessPage({ params: { token } }: DocumentAccessPageProps) {
await setupI18nSSR();
if (!token) {
return notFound();
}
const { document } = await getDocumentByAccessToken({ token });
const { documentData, documentMeta } = document;
return (
<div className="mx-auto w-full max-w-screen-xl md:px-8">
<h1
className="mt-4 block max-w-[20rem] truncate text-2xl font-semibold md:max-w-[30rem] md:text-3xl"
title={document.title}
>
{document.title}
</h1>
<div className="mt-8 grid grid-cols-12 gap-y-8 lg:gap-x-8 lg:gap-y-0">
<Card
className="col-span-12 rounded-xl before:rounded-xl lg:col-span-7 xl:col-span-8"
gradient
>
<CardContent className="p-2">
<LazyPDFViewer
key={documentData.id}
documentData={documentData}
document={document}
password={documentMeta?.password}
/>
</CardContent>
</Card>
<div className="col-span-12 lg:col-span-5 xl:col-span-4">
<section className="border-border bg-widget flex flex-col rounded-xl border pb-4 pt-6">
<div className="flex flex-row items-center justify-between px-4">
<h3 className="text-foreground text-2xl font-semibold">Download document</h3>
</div>
<p className="text-muted-foreground mt-2 px-4 text-sm">
Download the document as a PDF file.
</p>
<div className="mt-4 border-t px-4 pt-4">
<DocumentDownloadButton document={document} />
</div>
</section>
</div>
</div>
</div>
);
}

View File

@ -210,6 +210,13 @@ export const run = async ({
},
});
await tx.documentAccessToken.create({
data: {
token: nanoid(32),
documentId: document.id,
},
});
await tx.documentAuditLog.create({
data: createDocumentAuditLogData({
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,

View File

@ -0,0 +1,27 @@
import { prisma } from '@documenso/prisma';
export type GetDocumentByAccessTokenOptions = {
token: string;
};
export const getDocumentByAccessToken = async ({ token }: GetDocumentByAccessTokenOptions) => {
if (!token) {
throw new Error('Missing token');
}
const result = await prisma.documentAccessToken.findFirstOrThrow({
where: {
token,
},
include: {
document: {
include: {
documentData: true,
documentMeta: true,
},
},
},
});
return result;
};