import { useLingui } from '@lingui/react'; import { Plural, Trans } from '@lingui/react/macro'; import { DocumentStatus, TeamMemberRole } from '@prisma/client'; import { ChevronLeft, Clock9, Users2 } from 'lucide-react'; import { Link, redirect } from 'react-router'; import { match } from 'ts-pattern'; import { getSession } from '@documenso/auth/server/lib/utils/get-session'; import { useSession } from '@documenso/lib/client-only/providers/session'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; 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 { type TGetTeamByUrlResponse, getTeamByUrl } from '@documenso/lib/server-only/team/get-team'; import { DocumentVisibility } from '@documenso/lib/types/document-visibility'; import { formatDocumentsPath } from '@documenso/lib/utils/teams'; import { DocumentReadOnlyFields } from '@documenso/ui/components/document/document-read-only-fields'; import { Badge } from '@documenso/ui/primitives/badge'; import { Button } from '@documenso/ui/primitives/button'; import { Card, CardContent } from '@documenso/ui/primitives/card'; import { PDFViewer } from '@documenso/ui/primitives/pdf-viewer'; import { DocumentHistorySheet } from '~/components/general/document/document-history-sheet'; import { DocumentPageViewButton } from '~/components/general/document/document-page-view-button'; import { DocumentPageViewDropdown } from '~/components/general/document/document-page-view-dropdown'; import { DocumentPageViewInformation } from '~/components/general/document/document-page-view-information'; import { DocumentPageViewRecentActivity } from '~/components/general/document/document-page-view-recent-activity'; import { DocumentPageViewRecipients } from '~/components/general/document/document-page-view-recipients'; import { DocumentRecipientLinkCopyDialog } from '~/components/general/document/document-recipient-link-copy-dialog'; import { DocumentStatus as DocumentStatusComponent, FRIENDLY_STATUS_MAP, } from '~/components/general/document/document-status'; import { StackAvatarsWithTooltip } from '~/components/general/stack-avatars-with-tooltip'; import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader'; import type { Route } from './+types/documents.$id._index'; export async function loader({ params, request }: Route.LoaderArgs) { const { user } = await getSession(request); let team: TGetTeamByUrlResponse | null = null; if (params.teamUrl) { team = await getTeamByUrl({ userId: user.id, teamUrl: params.teamUrl }); } const { id } = params; const documentId = Number(id); const documentRootPath = formatDocumentsPath(team?.url); if (!documentId || Number.isNaN(documentId)) { throw redirect(documentRootPath); } const document = await getDocumentById({ documentId, userId: user.id, teamId: team?.id, }).catch(() => null); if (document?.teamId && !team?.url) { throw redirect(documentRootPath); } if (document?.folderId) { throw redirect(documentRootPath); } const documentVisibility = document?.visibility; const currentTeamMemberRole = team?.currentTeamMember?.role; const isRecipient = document?.recipients.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); } if (!document || !document.documentData || (team && !canAccessDocument)) { throw redirect(documentRootPath); } if (team && !canAccessDocument) { throw redirect(documentRootPath); } // Todo: Get full document instead? const [recipients, fields] = await Promise.all([ getRecipientsForDocument({ documentId, teamId: team?.id, userId: user.id, }), getFieldsForDocument({ documentId, userId: user.id, teamId: team?.id, }), ]); const documentWithRecipients = { ...document, recipients, }; return superLoaderJson({ document: documentWithRecipients, documentRootPath, fields, }); } export default function DocumentPage() { const loaderData = useSuperLoaderData(); const { _ } = useLingui(); const { user } = useSession(); const { document, documentRootPath, fields } = loaderData; const { recipients, documentData, documentMeta } = document; // This was a feature flag. Leave to false since it's not ready. const isDocumentHistoryEnabled = false; return (
{document.status === DocumentStatus.PENDING && ( )} Documents

{document.title}

{recipients.length > 0 && (
{recipients.length} Recipient(s)
)} {document.deletedAt && ( Document deleted )}
{isDocumentHistoryEnabled && (
)}
{document.status !== DocumentStatus.COMPLETED && ( recipient.id)} /> )}

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

{match(document.status) .with(DocumentStatus.COMPLETED, () => ( This document has been signed by all recipients )) .with(DocumentStatus.REJECTED, () => ( This document has been rejected by a recipient )) .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. */}
); }