diff --git a/apps/web/src/app/(dashboard)/dashboard/page.tsx b/apps/web/src/app/(dashboard)/dashboard/page.tsx index a9d650eb6..8e242b2fd 100644 --- a/apps/web/src/app/(dashboard)/dashboard/page.tsx +++ b/apps/web/src/app/(dashboard)/dashboard/page.tsx @@ -22,14 +22,14 @@ import { LocaleDate } from '~/components/formatter/locale-date'; import { UploadDocument } from './upload-document'; export default async function DashboardPage() { - const session = await getRequiredServerComponentSession(); + const user = await getRequiredServerComponentSession(); const [stats, results] = await Promise.all([ getStats({ - userId: session.id, + user, }), findDocuments({ - userId: session.id, + userId: user.id, perPage: 10, }), ]); diff --git a/apps/web/src/app/(dashboard)/inbox/page.tsx b/apps/web/src/app/(dashboard)/inbox/page.tsx deleted file mode 100644 index badb421c9..000000000 --- a/apps/web/src/app/(dashboard)/inbox/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import Inbox from '~/components/(dashboard)/inbox/inbox'; - -export default function InboxPage() { - return ( -
-

Inbox

-

Documents which you have been requested to sign.

- -
- -
-
- ); -} diff --git a/apps/web/src/components/(dashboard)/inbox/inbox-content.tsx b/apps/web/src/components/(dashboard)/inbox/inbox-content.tsx deleted file mode 100644 index f7e263f1f..000000000 --- a/apps/web/src/components/(dashboard)/inbox/inbox-content.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { TemplateDocumentCompleted } from '@documenso/email/template-components/template-document-completed'; -import { TemplateDocumentInvite } from '@documenso/email/template-components/template-document-invite'; -import { DocumentWithRecipientAndSender } from '@documenso/prisma/types/document'; -import { cn } from '@documenso/ui/lib/utils'; - -import { formatInboxDate } from './inbox.utils'; - -export type InboxContentProps = { - document: DocumentWithRecipientAndSender; -}; - -export default function InboxContent({ document }: InboxContentProps) { - const inboxDocumentStatusIndicator = ( - -
- - {document.recipient.signingStatus === 'SIGNED' ? 'Signed' : 'Pending'} -
- ); - - return ( -
-
-
-

{document.subject}

-

- {document.sender.name} <{document.sender.email}> -

-
- -
- {/* Todo: This needs to be updated to when the document was sent to the recipient when that value is available. */} -

{formatInboxDate(document.created)}

- - {inboxDocumentStatusIndicator} -
-
- -
- {inboxDocumentStatusIndicator} -
- - {/* Todo: get correct URLs */} -
- {document.recipient.signingStatus === 'NOT_SIGNED' && ( - - )} - - {document.recipient.signingStatus === 'SIGNED' && ( - - )} -
-
- ); -} diff --git a/apps/web/src/components/(dashboard)/inbox/inbox.actions.ts b/apps/web/src/components/(dashboard)/inbox/inbox.actions.ts deleted file mode 100644 index 38b50a8b3..000000000 --- a/apps/web/src/components/(dashboard)/inbox/inbox.actions.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use server'; - -import { z } from 'zod'; - -import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; -import { prisma } from '@documenso/prisma'; - -export async function updateRecipientReadStatus(recipientId: number, documentId: number) { - z.number().parse(recipientId); - z.number().parse(documentId); - - const { email } = await getRequiredServerComponentSession(); - - await prisma.recipient.update({ - where: { - id: recipientId, - documentId, - email, - }, - data: { - readStatus: 'OPENED', - }, - }); -} diff --git a/apps/web/src/components/(dashboard)/inbox/inbox.tsx b/apps/web/src/components/(dashboard)/inbox/inbox.tsx deleted file mode 100644 index c76ba3d94..000000000 --- a/apps/web/src/components/(dashboard)/inbox/inbox.tsx +++ /dev/null @@ -1,352 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; - -import { usePathname, useRouter, useSearchParams } from 'next/navigation'; - -import { Inbox as InboxIcon } from 'lucide-react'; -import { z } from 'zod'; - -import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; -import { SigningStatus } from '@documenso/prisma/client'; -import { DocumentWithRecipientAndSender } from '@documenso/prisma/types/document'; -import { trpc } from '@documenso/trpc/react'; -import { cn } from '@documenso/ui/lib/utils'; -import { Input } from '@documenso/ui/primitives/input'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@documenso/ui/primitives/select'; -import { Skeleton } from '@documenso/ui/primitives/skeleton'; - -import { useDebouncedValue } from '~/hooks/use-debounced-value'; - -import InboxContent from './inbox-content'; -import { updateRecipientReadStatus } from './inbox.actions'; -import { formatInboxDate } from './inbox.utils'; - -export const ZInboxSearchParamsSchema = z.object({ - filter: z - .union([z.literal('SIGNED'), z.literal('NOT_SIGNED'), z.undefined()]) - .catch(() => undefined), - id: z - .string() - .optional() - .catch(() => undefined), - query: z - .string() - .optional() - .catch(() => undefined), -}); - -export type InboxProps = { - className?: string; -}; - -const numberOfSkeletons = 3; - -export default function Inbox(props: InboxProps) { - const { className } = props; - - const pathname = usePathname(); - const searchParams = useSearchParams(); - const router = useRouter(); - const updateSearchParams = useUpdateSearchParams(); - - const parsedSearchParams = ZInboxSearchParamsSchema.parse(Object.fromEntries(searchParams ?? [])); - - const [searchQuery, setSearchQuery] = useState(() => parsedSearchParams.query || ''); - - const [readStatusState, setReadStatusState] = useState<{ - [documentId: string]: 'ERROR' | 'UPDATED' | 'UPDATING'; - }>({}); - - const [isInitialLoad, setIsInitialLoad] = useState(true); - - const debouncedSearchQuery = useDebouncedValue(searchQuery, 500); - - const { - data, - error, - fetchNextPage, - fetchPreviousPage, - hasNextPage, - hasPreviousPage, - isFetching, - isFetchingNextPage, - isFetchingPreviousPage, - refetch, - } = trpc.document.searchInboxDocuments.useInfiniteQuery( - { - query: parsedSearchParams.query, - filter: parsedSearchParams.filter, - }, - { - getPreviousPageParam: (firstPage) => - firstPage.currentPage > 1 ? firstPage.currentPage - 1 : undefined, - getNextPageParam: (lastPage) => - lastPage.currentPage < lastPage.totalPages ? lastPage.currentPage + 1 : undefined, - }, - ); - - /** - * The current documents in the inbox after filters and queries have been applied. - */ - const inboxDocuments = (data?.pages ?? []).flatMap((page) => page.data); - - /** - * The currently selected document in the inbox. - */ - const selectedDocument: DocumentWithRecipientAndSender | null = - inboxDocuments.find((item) => item.id.toString() === parsedSearchParams.id) ?? null; - - /** - * Remove the ID from the query if it is not found in the result. - */ - useEffect(() => { - if (!selectedDocument && parsedSearchParams.id && data) { - updateSearchParams({ - id: null, - }); - } - }, [data, selectedDocument, parsedSearchParams.id]); - - /** - * Handle debouncing the seach query. - */ - useEffect(() => { - if (!pathname) { - return; - } - - const params = new URLSearchParams(searchParams?.toString()); - - params.set('query', debouncedSearchQuery); - - if (debouncedSearchQuery === '') { - params.delete('query'); - } - - router.push(`${pathname}?${params.toString()}`); - }, [debouncedSearchQuery]); - - useEffect(() => { - if (!isFetching) { - setIsInitialLoad(false); - } - }, [isFetching]); - - const updateReadStatusState = (documentId: number, value: (typeof readStatusState)[string]) => { - setReadStatusState({ - ...readStatusState, - [documentId]: value, - }); - }; - - /** - * Handle selecting the selected document to display and updating the read status if required. - */ - const onSelectedDocumentChange = (value: DocumentWithRecipientAndSender) => { - if (!pathname) { - return; - } - - // Update the read status. - if ( - value.recipient.readStatus === 'NOT_OPENED' && - readStatusState[value.id] !== 'UPDATED' && - readStatusState[value.id] !== 'UPDATING' - ) { - updateReadStatusState(value.id, 'UPDATING'); - - updateRecipientReadStatus(value.recipient.id, value.id) - .then(() => { - updateReadStatusState(value.id, 'UPDATED'); - }) - .catch(() => { - updateReadStatusState(value.id, 'ERROR'); - }); - } - - const params = new URLSearchParams(searchParams?.toString()); - - params.set('id', value.id.toString()); - - router.push(`${pathname}?${params.toString()}`); - }; - - if (error) { - return ( -
-

Something went wrong while loading your inbox.

- -
- ); - } - - return ( -
-
- {/* Header with search and filter options. */} -
- setSearchQuery(e.target.value)} - /> - -
- -
-
- -
- {/* Handle rendering no items found. */} - {!isFetching && inboxDocuments.length === 0 && ( -
-

No documents found.

-
- )} - - {hasPreviousPage && !isFetchingPreviousPage && ( - - )} - -
    - {/* Handle rendering skeleton on first load. */} - {isFetching && - isInitialLoad && - !data && - Array.from({ length: numberOfSkeletons }).map((_, i) => ( -
  • - - -
    -
    - - -
    - - - - -
    -
  • - ))} - - {/* Handle rendering list of inbox documents. */} - {inboxDocuments.map((item, i) => ( -
  • - - - {/* Mobile inbox content. */} - {selectedDocument?.id === item.id && ( -
    - -
    - )} -
  • - ))} -
- - {hasNextPage && !isFetchingNextPage && ( - - )} -
-
- - {/* Desktop inbox content. */} -
- {selectedDocument ? ( - - ) : ( -
- -
- )} -
-
- ); -} diff --git a/apps/web/src/components/(dashboard)/inbox/inbox.utils.ts b/apps/web/src/components/(dashboard)/inbox/inbox.utils.ts deleted file mode 100644 index 9a6aff9f3..000000000 --- a/apps/web/src/components/(dashboard)/inbox/inbox.utils.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { DateTime } from 'luxon'; - -/** - * Format the provided date into a readable string for inboxes. - * - * @param dateValue The date or date string - * @returns The date in the current locale, or the date formatted as HH:MM AM/PM if the provided date is after 12:00AM of the current date - */ -export const formatInboxDate = (dateValue: string | Date): string => { - const date = - typeof dateValue === 'string' ? DateTime.fromISO(dateValue) : DateTime.fromJSDate(dateValue); - - const startOfTheDay = DateTime.now().startOf('day'); - - if (date >= startOfTheDay) { - return date.toFormat('h:mma'); - } - - return date.toLocaleString({ - ...DateTime.DATE_SHORT, - year: '2-digit', - }); -}; diff --git a/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx b/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx index 6d0e629b4..2c6165a05 100644 --- a/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx +++ b/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx @@ -2,19 +2,17 @@ import { HTMLAttributes } from 'react'; -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; - import { cn } from '@documenso/ui/lib/utils'; export type DesktopNavProps = HTMLAttributes; export const DesktopNav = ({ className, ...props }: DesktopNavProps) => { - const pathname = usePathname(); + // const pathname = usePathname(); return ( ); }; diff --git a/apps/web/src/components/(dashboard)/layout/header.tsx b/apps/web/src/components/(dashboard)/layout/header.tsx index c10fa9e5e..88dc5d7a4 100644 --- a/apps/web/src/components/(dashboard)/layout/header.tsx +++ b/apps/web/src/components/(dashboard)/layout/header.tsx @@ -4,11 +4,8 @@ import { HTMLAttributes } from 'react'; import Link from 'next/link'; -import { Menu } from 'lucide-react'; - import { User } from '@documenso/prisma/client'; import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; import { Logo } from '~/components/branding/logo'; @@ -23,7 +20,7 @@ export const Header = ({ className, user, ...props }: HeaderProps) => { return (
{
- + */}
diff --git a/packages/lib/server-only/document/find-documents.ts b/packages/lib/server-only/document/find-documents.ts index 5a2d695ae..c9c8eaf6c 100644 --- a/packages/lib/server-only/document/find-documents.ts +++ b/packages/lib/server-only/document/find-documents.ts @@ -2,7 +2,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 { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { FindResultSet } from '../../types/find-result-set'; @@ -160,111 +159,3 @@ export const findDocuments = async ({ totalPages: Math.ceil(count / perPage), } satisfies FindResultSet; }; - -export interface FindDocumentsWithRecipientAndSenderOptions { - email: string; - query?: string; - signingStatus?: SigningStatus; - page?: number; - perPage?: number; - orderBy?: { - column: keyof Omit; - direction: 'asc' | 'desc'; - }; -} - -export const findDocumentsWithRecipientAndSender = async ({ - email, - query, - signingStatus, - page = 1, - perPage = 20, - orderBy, -}: FindDocumentsWithRecipientAndSenderOptions): Promise< - FindResultSet -> => { - const orderByColumn = orderBy?.column ?? 'created'; - const orderByDirection = orderBy?.direction ?? 'desc'; - - const filters: Prisma.DocumentWhereInput = { - Recipient: { - some: { - email, - signingStatus, - }, - }, - }; - - if (query) { - filters.OR = [ - { - User: { - email: { - contains: query, - mode: 'insensitive', - }, - }, - // Todo: Add filter for `Subject`. - }, - ]; - } - - const [data, count] = await Promise.all([ - prisma.document.findMany({ - select: { - id: true, - created: true, - title: true, - status: true, - userId: true, - User: { - select: { - id: true, - name: true, - email: true, - }, - }, - Recipient: { - where: { - email, - signingStatus, - }, - }, - }, - where: { - ...filters, - }, - skip: Math.max(page - 1, 0) * perPage, - take: perPage, - orderBy: { - [orderByColumn]: orderByDirection, - }, - }), - prisma.document.count({ - where: { - ...filters, - }, - }), - ]); - - return { - data: data.map((item) => { - const { User, Recipient, ...rest } = item; - - const subject = undefined; // Todo. - const description = undefined; // Todo. - - return { - ...rest, - sender: User, - recipient: Recipient[0], - subject: subject ?? 'Please sign this document', - description: description ?? `${User.name} has invited you to sign "${item.title}"`, - }; - }), - count, - currentPage: Math.max(page, 1), - perPage, - totalPages: Math.ceil(count / perPage), - }; -}; diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts index 1f790dc24..f20643327 100644 --- a/packages/trpc/server/document-router/router.ts +++ b/packages/trpc/server/document-router/router.ts @@ -1,45 +1,17 @@ import { TRPCError } from '@trpc/server'; -import { findDocumentsWithRecipientAndSender } from '@documenso/lib/server-only/document/find-documents'; import { sendDocument } from '@documenso/lib/server-only/document/send-document'; import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document'; import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document'; import { authenticatedProcedure, router } from '../trpc'; import { - ZSearchInboxDocumentsParamsSchema, ZSendDocumentMutationSchema, ZSetFieldsForDocumentMutationSchema, ZSetRecipientsForDocumentMutationSchema, } from './schema'; export const documentRouter = router({ - searchInboxDocuments: authenticatedProcedure - .input(ZSearchInboxDocumentsParamsSchema) - .query(async ({ input, ctx }) => { - try { - const { filter, query, cursor: page } = input; - - return await findDocumentsWithRecipientAndSender({ - email: ctx.session.email, - query, - signingStatus: filter, - orderBy: { - column: 'created', - direction: 'desc', - }, - page, - }); - } catch (err) { - console.error(err); - - throw new TRPCError({ - code: 'BAD_REQUEST', - message: 'Something went wrong. Please try again later.', - }); - } - }), - setRecipientsForDocument: authenticatedProcedure .input(ZSetRecipientsForDocumentMutationSchema) .mutation(async ({ input, ctx }) => {