diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx new file mode 100644 index 000000000..7c1d42d2b --- /dev/null +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx @@ -0,0 +1,65 @@ +'use client'; + +import Link from 'next/link'; + +import { Edit, Pencil, Share } from 'lucide-react'; +import { useSession } from 'next-auth/react'; +import { match } from 'ts-pattern'; + +import { Document, DocumentStatus, Recipient, SigningStatus, User } from '@documenso/prisma/client'; +import { Button } from '@documenso/ui/primitives/button'; + +export type DataTableActionButtonProps = { + row: Document & { + User: Pick; + Recipient: Recipient[]; + }; +}; + +export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => { + const { data: session } = useSession(); + + if (!session) { + return null; + } + + const recipient = row.Recipient.find((recipient) => recipient.email === session.user.email); + + const isOwner = row.User.id === session.user.id; + const isRecipient = !!recipient; + const isDraft = row.status === DocumentStatus.DRAFT; + const isPending = row.status === DocumentStatus.PENDING; + const isComplete = row.status === DocumentStatus.COMPLETED; + const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; + + return match({ + isOwner, + isRecipient, + isDraft, + isPending, + isComplete, + isSigned, + }) + .with({ isOwner: true, isDraft: true }, () => ( + + )) + .with({ isRecipient: true, isPending: true, isSigned: false }, () => ( + + )) + .otherwise(() => ( + + )); +}; diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx new file mode 100644 index 000000000..b1d5832f8 --- /dev/null +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx @@ -0,0 +1,133 @@ +'use client'; + +import Link from 'next/link'; + +import { + Copy, + Download, + Edit, + History, + MoreHorizontal, + Pencil, + Share, + Trash2, + XCircle, +} from 'lucide-react'; +import { useSession } from 'next-auth/react'; + +import { Document, DocumentStatus, Recipient, User } from '@documenso/prisma/client'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from '@documenso/ui/primitives/dropdown-menu'; + +export type DataTableActionDropdownProps = { + row: Document & { + User: Pick; + Recipient: Recipient[]; + }; +}; + +export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) => { + const { data: session } = useSession(); + + if (!session) { + return null; + } + + const recipient = row.Recipient.find((recipient) => recipient.email === session.user.email); + + const isOwner = row.User.id === session.user.id; + // const isRecipient = !!recipient; + // const isDraft = row.status === DocumentStatus.DRAFT; + // const isPending = row.status === DocumentStatus.PENDING; + const isComplete = row.status === DocumentStatus.COMPLETED; + // const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; + + const onDownloadClick = () => { + let decodedDocument = row.document; + + try { + decodedDocument = atob(decodedDocument); + } catch (err) { + // We're just going to ignore this error and try to download the document + console.error(err); + } + + const documentBytes = Uint8Array.from(decodedDocument.split('').map((c) => c.charCodeAt(0))); + + const blob = new Blob([documentBytes], { + type: 'application/pdf', + }); + + const link = window.document.createElement('a'); + + link.href = window.URL.createObjectURL(blob); + link.download = row.title || 'document.pdf'; + + link.click(); + + window.URL.revokeObjectURL(link.href); + }; + + return ( + + + + + + + Action + + + + + Sign + + + + + + + Edit + + + + + + Download + + + + + Duplicate + + + + + Void + + + + + Delete + + + Share + + + + Resend + + + + + Share + + + + ); +}; diff --git a/apps/web/src/app/(dashboard)/documents/data-table.tsx b/apps/web/src/app/(dashboard)/documents/data-table.tsx index 012038f4e..1d6c08e73 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table.tsx @@ -4,39 +4,28 @@ import { useTransition } from 'react'; import Link from 'next/link'; -import { - Copy, - Download, - Edit, - History, - Loader, - MoreHorizontal, - Pencil, - Share, - Trash2, - XCircle, -} from 'lucide-react'; +import { Loader } from 'lucide-react'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; import { FindResultSet } from '@documenso/lib/types/find-result-set'; -import { DocumentWithReciepient } from '@documenso/prisma/types/document-with-recipient'; -import { Button } from '@documenso/ui/primitives/button'; +import { Document, Recipient, User } from '@documenso/prisma/client'; import { DataTable } from '@documenso/ui/primitives/data-table'; import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuTrigger, -} from '@documenso/ui/primitives/dropdown-menu'; import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip'; import { DocumentStatus } from '~/components/formatter/document-status'; import { LocaleDate } from '~/components/formatter/locale-date'; +import { DataTableActionButton } from './data-table-action-button'; +import { DataTableActionDropdown } from './data-table-action-dropdown'; + export type DocumentsDataTableProps = { - results: FindResultSet; + results: FindResultSet< + Document & { + Recipient: Recipient[]; + User: Pick; + } + >; }; export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => { @@ -64,7 +53,11 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => { { header: 'Title', cell: ({ row }) => ( - + {row.original.title} ), @@ -88,52 +81,10 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => { }, { header: 'Actions', - cell: ({ row: _row }) => ( + cell: ({ row }) => (
- - - - - - - - Action - - - Sign - - - - Edit - - - - Download - - - - Duplicate - - - - Void - - - - Delete - - - Share - - - Resend - - - - Share - - - + +
), }, diff --git a/packages/lib/server-only/document/find-documents.ts b/packages/lib/server-only/document/find-documents.ts index 93b1e2089..5a2d695ae 100644 --- a/packages/lib/server-only/document/find-documents.ts +++ b/packages/lib/server-only/document/find-documents.ts @@ -3,7 +3,6 @@ import { match } from 'ts-pattern'; import { prisma } from '@documenso/prisma'; import { Document, Prisma, SigningStatus } from '@documenso/prisma/client'; import { DocumentWithRecipientAndSender } from '@documenso/prisma/types/document'; -import { DocumentWithReciepient } from '@documenso/prisma/types/document-with-recipient'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { FindResultSet } from '../../types/find-result-set'; @@ -27,7 +26,7 @@ export const findDocuments = async ({ page = 1, perPage = 10, orderBy, -}: FindDocumentsOptions): Promise> => { +}: FindDocumentsOptions) => { const user = await prisma.user.findFirstOrThrow({ where: { id: userId, @@ -145,13 +144,21 @@ export const findDocuments = async ({ }), ]); + const maskedData = data.map((doc) => ({ + ...doc, + Recipient: doc.Recipient.map((recipient) => ({ + ...recipient, + token: recipient.email === user.email ? recipient.token : '', + })), + })); + return { - data, + data: maskedData, count, currentPage: Math.max(page, 1), perPage, totalPages: Math.ceil(count / perPage), - }; + } satisfies FindResultSet; }; export interface FindDocumentsWithRecipientAndSenderOptions { diff --git a/packages/lib/types/find-result-set.ts b/packages/lib/types/find-result-set.ts index 13eab7bbd..81b16f1ca 100644 --- a/packages/lib/types/find-result-set.ts +++ b/packages/lib/types/find-result-set.ts @@ -1,5 +1,5 @@ export type FindResultSet = { - data: T[]; + data: T extends Array ? T : T[]; count: number; currentPage: number; perPage: number; diff --git a/packages/prisma/types/document-with-recipient.ts b/packages/prisma/types/document-with-recipient.ts index 208fb2b68..4ba6a9776 100644 --- a/packages/prisma/types/document-with-recipient.ts +++ b/packages/prisma/types/document-with-recipient.ts @@ -1,5 +1,5 @@ import { Document, Recipient } from '@documenso/prisma/client'; -export type DocumentWithReciepient = Document & { +export type DocumentWithRecipient = Document & { Recipient: Recipient[]; };