import Link from 'next/link'; import { redirect } from 'next/navigation'; import { Plural, Trans } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import { ChevronLeft, Clock9, Users2 } from 'lucide-react'; import { match } from 'ts-pattern'; import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; import { getServerComponentFlag } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag'; import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-fields-for-document'; import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document'; import { DocumentVisibility } from '@documenso/lib/types/document-visibility'; import { symmetricDecrypt } from '@documenso/lib/universal/crypto'; import { formatDocumentsPath } from '@documenso/lib/utils/teams'; import { DocumentStatus } from '@documenso/prisma/client'; import type { Team, TeamEmail } from '@documenso/prisma/client'; import { TeamMemberRole } from '@documenso/prisma/client'; import { Badge } from '@documenso/ui/primitives/badge'; import { Button } from '@documenso/ui/primitives/button'; import { Card, CardContent } from '@documenso/ui/primitives/card'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip'; import { DocumentHistorySheet } from '~/components/document/document-history-sheet'; import { DocumentReadOnlyFields } from '~/components/document/document-read-only-fields'; import { DocumentRecipientLinkCopyDialog } from '~/components/document/document-recipient-link-copy-dialog'; import { DocumentStatus as DocumentStatusComponent, FRIENDLY_STATUS_MAP, } from '~/components/formatter/document-status'; import { DocumentPageViewButton } from './document-page-view-button'; import { DocumentPageViewDropdown } from './document-page-view-dropdown'; import { DocumentPageViewInformation } from './document-page-view-information'; import { DocumentPageViewRecentActivity } from './document-page-view-recent-activity'; import { DocumentPageViewRecipients } from './document-page-view-recipients'; export type DocumentPageViewProps = { params: { id: string; }; team?: Team & { teamEmail: TeamEmail | null } & { currentTeamMember: { role: TeamMemberRole } }; }; export const DocumentPageView = async ({ params, team }: DocumentPageViewProps) => { const { id } = params; const { _ } = useLingui(); const documentId = Number(id); const documentRootPath = formatDocumentsPath(team?.url); if (!documentId || Number.isNaN(documentId)) { redirect(documentRootPath); } const { user } = await getRequiredServerComponentSession(); const document = await getDocumentById({ id: documentId, userId: user.id, teamId: team?.id, }).catch(() => null); if (document?.teamId && !team?.url) { redirect(documentRootPath); } const documentVisibility = document?.visibility; const currentTeamMemberRole = team?.currentTeamMember?.role; const isRecipient = document?.Recipient.find((recipient) => recipient.email === user.email); let canAccessDocument = true; if (team && !isRecipient && document?.userId !== user.id) { canAccessDocument = match([documentVisibility, currentTeamMemberRole]) .with([DocumentVisibility.EVERYONE, TeamMemberRole.ADMIN], () => true) .with([DocumentVisibility.EVERYONE, TeamMemberRole.MANAGER], () => true) .with([DocumentVisibility.EVERYONE, TeamMemberRole.MEMBER], () => true) .with([DocumentVisibility.MANAGER_AND_ABOVE, TeamMemberRole.ADMIN], () => true) .with([DocumentVisibility.MANAGER_AND_ABOVE, TeamMemberRole.MANAGER], () => true) .with([DocumentVisibility.ADMIN, TeamMemberRole.ADMIN], () => true) .otherwise(() => false); } const isDocumentHistoryEnabled = await getServerComponentFlag( 'app_document_page_view_history_sheet', ); if (!document || !document.documentData || (team && !canAccessDocument)) { redirect(documentRootPath); } if (team && !canAccessDocument) { redirect(documentRootPath); } const { documentData, documentMeta } = document; if (documentMeta?.password) { const key = DOCUMENSO_ENCRYPTION_KEY; if (!key) { throw new Error('Missing DOCUMENSO_ENCRYPTION_KEY'); } const securePassword = Buffer.from( symmetricDecrypt({ key, data: documentMeta.password, }), ).toString('utf-8'); documentMeta.password = securePassword; } const [recipients, fields] = await Promise.all([ getRecipientsForDocument({ documentId, teamId: team?.id, userId: user.id, }), getFieldsForDocument({ documentId, userId: user.id, }), ]); const documentWithRecipients = { ...document, Recipient: recipients, }; return (
{document.status === DocumentStatus.PENDING && ( )} Documents

{document.title}

{recipients.length > 0 && (
{recipients.length} Recipient(s)
)} {document.deletedAt && ( Document deleted )}
{isDocumentHistoryEnabled && (
)}
{document.status === DocumentStatus.PENDING && ( )}

{_(FRIENDLY_STATUS_MAP[document.status].labelExtended)}

{match(document.status) .with(DocumentStatus.COMPLETED, () => ( This document has been signed by all recipients )) .with(DocumentStatus.DRAFT, () => ( This document is currently a draft and has not been sent )) .with(DocumentStatus.PENDING, () => { const pendingRecipients = recipients.filter( (recipient) => recipient.signingStatus === 'NOT_SIGNED', ); return ( ); }) .exhaustive()}

{/* Document information section. */} {/* Recipients section. */} {/* Recent activity section. */}
); };