mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
fix: remove unused code
This commit is contained in:
@ -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,
|
||||
}),
|
||||
]);
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import Inbox from '~/components/(dashboard)/inbox/inbox';
|
||||
|
||||
export default function InboxPage() {
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
|
||||
<h1 className="text-4xl font-semibold">Inbox</h1>
|
||||
<h3>Documents which you have been requested to sign.</h3>
|
||||
|
||||
<div className="mt-8">
|
||||
<Inbox className="4xl:h-[70vh] sm:h-[40rem]" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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 = (
|
||||
<span
|
||||
className={cn(
|
||||
'ml-auto inline-flex items-center gap-x-1.5 rounded-md px-2 py-1 text-xs font-medium',
|
||||
{
|
||||
'bg-green-100 text-green-700': document.recipient.signingStatus === 'SIGNED',
|
||||
'bg-yellow-100 text-yellow-800': document.recipient.signingStatus === 'NOT_SIGNED',
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn([
|
||||
'h-1.5 w-1.5 rounded-full',
|
||||
{
|
||||
'bg-green-500': document.recipient.signingStatus === 'SIGNED',
|
||||
'bg-yellow-500': document.recipient.signingStatus === 'NOT_SIGNED',
|
||||
},
|
||||
])}
|
||||
></div>
|
||||
|
||||
{document.recipient.signingStatus === 'SIGNED' ? 'Signed' : 'Pending'}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full">
|
||||
<div className="hidden h-14 w-full flex-row items-center border-b px-4 sm:flex">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold">{document.subject}</h2>
|
||||
<p className="text-xs">
|
||||
{document.sender.name} <span className=""><{document.sender.email}></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="ml-auto flex flex-row items-center">
|
||||
{/* Todo: This needs to be updated to when the document was sent to the recipient when that value is available. */}
|
||||
<p className="mx-2 text-xs">{formatInboxDate(document.created)}</p>
|
||||
|
||||
{inboxDocumentStatusIndicator}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row justify-end px-4 pt-4 sm:hidden">
|
||||
{inboxDocumentStatusIndicator}
|
||||
</div>
|
||||
|
||||
{/* Todo: get correct URLs */}
|
||||
<div className="mx-auto mb-6 mt-0 w-full max-w-xl sm:mb-16 sm:mt-14 sm:p-4">
|
||||
{document.recipient.signingStatus === 'NOT_SIGNED' && (
|
||||
<TemplateDocumentInvite
|
||||
inviterName={document.sender.name ?? document.sender.email}
|
||||
inviterEmail={document.sender.email}
|
||||
documentName={document.title}
|
||||
signDocumentLink={'Todo'}
|
||||
assetBaseUrl={location.origin}
|
||||
/>
|
||||
)}
|
||||
|
||||
{document.recipient.signingStatus === 'SIGNED' && (
|
||||
<TemplateDocumentCompleted
|
||||
downloadLink={'Todo'}
|
||||
reviewLink={'Todo'}
|
||||
documentName={document.title}
|
||||
assetBaseUrl={location.origin}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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',
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -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 (
|
||||
<section
|
||||
className={cn('flex flex-col items-center justify-center rounded-lg border', className)}
|
||||
>
|
||||
<p className="text-neutral-500">Something went wrong while loading your inbox.</p>
|
||||
<button onClick={() => refetch()} className="text-sm text-blue-500 hover:text-blue-600">
|
||||
Click here to try again
|
||||
</button>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={cn('flex flex-col rounded-lg border sm:flex-row sm:divide-x', className)}>
|
||||
<div className="w-full sm:w-1/3">
|
||||
{/* Header with search and filter options. */}
|
||||
<div className="flex h-14 flex-row items-center justify-between border-b px-2 py-2">
|
||||
<Input
|
||||
placeholder="Search"
|
||||
defaultValue={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="ml-2">
|
||||
<Select
|
||||
defaultValue={parsedSearchParams.filter}
|
||||
onValueChange={(value) =>
|
||||
updateSearchParams({
|
||||
filter: value || null,
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="max-w-[200px] text-slate-500">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value="">All</SelectItem>
|
||||
<SelectItem value={SigningStatus['NOT_SIGNED']}>Pending</SelectItem>
|
||||
<SelectItem value={SigningStatus['SIGNED']}>Approved</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[calc(100%-3.5rem)] overflow-y-scroll">
|
||||
{/* Handle rendering no items found. */}
|
||||
{!isFetching && inboxDocuments.length === 0 && (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<p className="-mt-32 text-center text-sm text-neutral-500">No documents found.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasPreviousPage && !isFetchingPreviousPage && (
|
||||
<button
|
||||
onClick={() => fetchPreviousPage()}
|
||||
className="mx-auto w-full border-b py-2 text-center text-sm text-slate-400"
|
||||
>
|
||||
Show previous
|
||||
</button>
|
||||
)}
|
||||
|
||||
<ul>
|
||||
{/* Handle rendering skeleton on first load. */}
|
||||
{isFetching &&
|
||||
isInitialLoad &&
|
||||
!data &&
|
||||
Array.from({ length: numberOfSkeletons }).map((_, i) => (
|
||||
<li
|
||||
key={`skeleton-${i}`}
|
||||
className="hover:bg-muted/50 flex w-full cursor-pointer flex-row items-center border-b py-3 pr-4 text-left transition-colors"
|
||||
>
|
||||
<Skeleton className="mx-3 h-2 w-2 rounded-full" />
|
||||
|
||||
<div className="w-full">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<Skeleton className="h-5 w-6/12" />
|
||||
<Skeleton className="h-4 w-2/12" />
|
||||
</div>
|
||||
|
||||
<Skeleton className="my-1 h-4 w-8/12" />
|
||||
|
||||
<Skeleton className="h-4 w-4/12" />
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
|
||||
{/* Handle rendering list of inbox documents. */}
|
||||
{inboxDocuments.map((item, i) => (
|
||||
<li key={i}>
|
||||
<button
|
||||
onClick={() => onSelectedDocumentChange(item)}
|
||||
className={cn(
|
||||
'hover:bg-muted/50 flex w-full cursor-pointer flex-row items-center border-b py-3 pr-4 text-left transition-colors',
|
||||
{
|
||||
'bg-muted/50 dark:bg-muted': selectedDocument?.id === item.id,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn([
|
||||
'mx-3 h-2 w-2 rounded-full',
|
||||
{
|
||||
'bg-green-300': item.recipient.signingStatus === 'SIGNED',
|
||||
'bg-yellow-300': item.recipient.signingStatus === 'NOT_SIGNED',
|
||||
},
|
||||
])}
|
||||
></div>
|
||||
|
||||
<div className="w-full">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<p className="line-clamp-1 text-sm">{item.subject}</p>
|
||||
|
||||
{/* Todo: This needs to be updated to when the document was sent to the recipient when that value is available. */}
|
||||
<p className="whitespace-nowrap text-xs">{formatInboxDate(item.created)}</p>
|
||||
</div>
|
||||
|
||||
{item.description && (
|
||||
<p
|
||||
className={cn('my-1 text-xs text-slate-500', {
|
||||
'line-clamp-1': selectedDocument?.id !== item.id,
|
||||
})}
|
||||
>
|
||||
{item.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p className="text-xs text-slate-400">
|
||||
{item.sender.name} <span className=""><{item.sender.email}></span>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Mobile inbox content. */}
|
||||
{selectedDocument?.id === item.id && (
|
||||
<div
|
||||
className={cn('w-full sm:hidden', {
|
||||
'border-b': i !== inboxDocuments.length - 1,
|
||||
})}
|
||||
>
|
||||
<InboxContent document={selectedDocument} />
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{hasNextPage && !isFetchingNextPage && (
|
||||
<button
|
||||
onClick={() => fetchNextPage()}
|
||||
className="mx-auto w-full py-2 text-center text-sm text-slate-400"
|
||||
>
|
||||
Show more
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop inbox content. */}
|
||||
<div className="hidden sm:block sm:w-2/3">
|
||||
{selectedDocument ? (
|
||||
<InboxContent document={selectedDocument} />
|
||||
) : (
|
||||
<div className="hidden h-full items-center justify-center text-slate-300 sm:flex">
|
||||
<InboxIcon className="h-12 w-12" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -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',
|
||||
});
|
||||
};
|
||||
@ -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<HTMLDivElement>;
|
||||
|
||||
export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
const pathname = usePathname();
|
||||
// const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<div className={cn('ml-8 hidden flex-1 gap-x-6 md:flex', className)} {...props}>
|
||||
<Link
|
||||
{/* We have no other subpaths rn */}
|
||||
{/* <Link
|
||||
href="/documents"
|
||||
className={cn(
|
||||
'text-muted-foreground focus-visible:ring-ring ring-offset-background rounded-md font-medium leading-5 hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 ',
|
||||
@ -24,19 +22,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
Documents
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/inbox"
|
||||
className={cn(
|
||||
'text-muted-foreground focus-visible:ring-ring ring-offset-background rounded-md font-medium leading-5 hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 ',
|
||||
{
|
||||
'text-foreground': pathname?.startsWith('/inbox'),
|
||||
},
|
||||
)}
|
||||
>
|
||||
Inbox
|
||||
</Link>
|
||||
</Link> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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 (
|
||||
<header
|
||||
className={cn(
|
||||
'supports-backdrop-blur:bg-background/60 bg-background/95 sticky top-0 z-40 flex h-16 w-full items-center border-b backdrop-blur',
|
||||
'supports-backdrop-blur:bg-background/60 bg-background/95 sticky top-0 z-50 flex h-16 w-full items-center border-b backdrop-blur',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@ -41,9 +38,9 @@ export const Header = ({ className, user, ...props }: HeaderProps) => {
|
||||
<div className="flex gap-x-4">
|
||||
<ProfileDropdown user={user} />
|
||||
|
||||
<Button variant="outline" size="sm" className="h-10 w-10 p-0.5 md:hidden">
|
||||
{/* <Button variant="outline" size="sm" className="h-10 w-10 p-0.5 md:hidden">
|
||||
<Menu className="h-6 w-6" />
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -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<typeof maskedData>;
|
||||
};
|
||||
|
||||
export interface FindDocumentsWithRecipientAndSenderOptions {
|
||||
email: string;
|
||||
query?: string;
|
||||
signingStatus?: SigningStatus;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
orderBy?: {
|
||||
column: keyof Omit<Document, 'document'>;
|
||||
direction: 'asc' | 'desc';
|
||||
};
|
||||
}
|
||||
|
||||
export const findDocumentsWithRecipientAndSender = async ({
|
||||
email,
|
||||
query,
|
||||
signingStatus,
|
||||
page = 1,
|
||||
perPage = 20,
|
||||
orderBy,
|
||||
}: FindDocumentsWithRecipientAndSenderOptions): Promise<
|
||||
FindResultSet<DocumentWithRecipientAndSender>
|
||||
> => {
|
||||
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),
|
||||
};
|
||||
};
|
||||
|
||||
@ -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 }) => {
|
||||
|
||||
Reference in New Issue
Block a user