mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
feat: add document search to the command menu (#713)
This commit is contained in:
@ -57,7 +57,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
|||||||
redirect(`/sign/${token}/complete`);
|
redirect(`/sign/${token}/complete`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipientSignature = (await getRecipientSignatures({ recipientId: recipient.id }))[0];
|
const [recipientSignature] = await getRecipientSignatures({ recipientId: recipient.id });
|
||||||
|
|
||||||
if (document.deletedAt) {
|
if (document.deletedAt) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useCallback, useMemo, useState } from 'react';
|
|||||||
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { Monitor, Moon, Sun } from 'lucide-react';
|
import { Loader, Monitor, Moon, Sun } from 'lucide-react';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
DOCUMENTS_PAGE_SHORTCUT,
|
DOCUMENTS_PAGE_SHORTCUT,
|
||||||
SETTINGS_PAGE_SHORTCUT,
|
SETTINGS_PAGE_SHORTCUT,
|
||||||
} from '@documenso/lib/constants/keyboard-shortcuts';
|
} from '@documenso/lib/constants/keyboard-shortcuts';
|
||||||
|
import { trpc as trpcReact } from '@documenso/trpc/react';
|
||||||
import {
|
import {
|
||||||
CommandDialog,
|
CommandDialog,
|
||||||
CommandEmpty,
|
CommandEmpty,
|
||||||
@ -29,13 +30,20 @@ const DOCUMENTS_PAGES = [
|
|||||||
shortcut: DOCUMENTS_PAGE_SHORTCUT.replace('+', ''),
|
shortcut: DOCUMENTS_PAGE_SHORTCUT.replace('+', ''),
|
||||||
},
|
},
|
||||||
{ label: 'Draft documents', path: '/documents?status=DRAFT' },
|
{ label: 'Draft documents', path: '/documents?status=DRAFT' },
|
||||||
{ label: 'Completed documents', path: '/documents?status=COMPLETED' },
|
{
|
||||||
|
label: 'Completed documents',
|
||||||
|
path: '/documents?status=COMPLETED',
|
||||||
|
},
|
||||||
{ label: 'Pending documents', path: '/documents?status=PENDING' },
|
{ label: 'Pending documents', path: '/documents?status=PENDING' },
|
||||||
{ label: 'Inbox documents', path: '/documents?status=INBOX' },
|
{ label: 'Inbox documents', path: '/documents?status=INBOX' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const SETTINGS_PAGES = [
|
const SETTINGS_PAGES = [
|
||||||
{ label: 'Settings', path: '/settings', shortcut: SETTINGS_PAGE_SHORTCUT.replace('+', '') },
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
path: '/settings',
|
||||||
|
shortcut: SETTINGS_PAGE_SHORTCUT.replace('+', ''),
|
||||||
|
},
|
||||||
{ label: 'Profile', path: '/settings/profile' },
|
{ label: 'Profile', path: '/settings/profile' },
|
||||||
{ label: 'Password', path: '/settings/password' },
|
{ label: 'Password', path: '/settings/password' },
|
||||||
];
|
];
|
||||||
@ -53,6 +61,29 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
|
|||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [pages, setPages] = useState<string[]>([]);
|
const [pages, setPages] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const { data: searchDocumentsData, isLoading: isSearchingDocuments } =
|
||||||
|
trpcReact.document.searchDocuments.useQuery(
|
||||||
|
{
|
||||||
|
query: search,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keepPreviousData: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const searchResults = useMemo(() => {
|
||||||
|
if (!searchDocumentsData) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchDocumentsData.map((document) => ({
|
||||||
|
label: document.title,
|
||||||
|
path: `/documents/${document.id}`,
|
||||||
|
value:
|
||||||
|
document.title + ' ' + document.Recipient.map((recipient) => recipient.email).join(' '),
|
||||||
|
}));
|
||||||
|
}, [searchDocumentsData]);
|
||||||
|
|
||||||
const currentPage = pages[pages.length - 1];
|
const currentPage = pages[pages.length - 1];
|
||||||
|
|
||||||
const toggleOpen = () => {
|
const toggleOpen = () => {
|
||||||
@ -113,7 +144,13 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandDialog commandProps={{ onKeyDown: handleKeyDown }} open={open} onOpenChange={setOpen}>
|
<CommandDialog
|
||||||
|
commandProps={{
|
||||||
|
onKeyDown: handleKeyDown,
|
||||||
|
}}
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
>
|
||||||
<CommandInput
|
<CommandInput
|
||||||
value={search}
|
value={search}
|
||||||
onValueChange={setSearch}
|
onValueChange={setSearch}
|
||||||
@ -121,7 +158,17 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<CommandList>
|
<CommandList>
|
||||||
|
{isSearchingDocuments ? (
|
||||||
|
<CommandEmpty>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<span className="animate-spin">
|
||||||
|
<Loader />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</CommandEmpty>
|
||||||
|
) : (
|
||||||
<CommandEmpty>No results found.</CommandEmpty>
|
<CommandEmpty>No results found.</CommandEmpty>
|
||||||
|
)}
|
||||||
{!currentPage && (
|
{!currentPage && (
|
||||||
<>
|
<>
|
||||||
<CommandGroup heading="Documents">
|
<CommandGroup heading="Documents">
|
||||||
@ -133,6 +180,11 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
|
|||||||
<CommandGroup heading="Preferences">
|
<CommandGroup heading="Preferences">
|
||||||
<CommandItem onSelect={() => addPage('theme')}>Change theme</CommandItem>
|
<CommandItem onSelect={() => addPage('theme')}>Change theme</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
|
{searchResults.length > 0 && (
|
||||||
|
<CommandGroup heading="Your documents">
|
||||||
|
<Commands push={push} pages={searchResults} />
|
||||||
|
</CommandGroup>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{currentPage === 'theme' && <ThemeCommands setTheme={setTheme} />}
|
{currentPage === 'theme' && <ThemeCommands setTheme={setTheme} />}
|
||||||
@ -146,10 +198,14 @@ const Commands = ({
|
|||||||
pages,
|
pages,
|
||||||
}: {
|
}: {
|
||||||
push: (_path: string) => void;
|
push: (_path: string) => void;
|
||||||
pages: { label: string; path: string; shortcut?: string }[];
|
pages: { label: string; path: string; shortcut?: string; value?: string }[];
|
||||||
}) => {
|
}) => {
|
||||||
return pages.map((page) => (
|
return pages.map((page, idx) => (
|
||||||
<CommandItem key={page.path} onSelect={() => push(page.path)}>
|
<CommandItem
|
||||||
|
key={page.path + idx}
|
||||||
|
value={page.value ?? page.label}
|
||||||
|
onSelect={() => push(page.path)}
|
||||||
|
>
|
||||||
{page.label}
|
{page.label}
|
||||||
{page.shortcut && <CommandShortcut>{page.shortcut}</CommandShortcut>}
|
{page.shortcut && <CommandShortcut>{page.shortcut}</CommandShortcut>}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
|
|||||||
@ -0,0 +1,72 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
import { TEST_USERS } from '@documenso/prisma/seed/pr-713-add-document-search-to-command-menu';
|
||||||
|
|
||||||
|
test('[PR-713]: should see sent documents', async ({ page }) => {
|
||||||
|
const [user] = TEST_USERS;
|
||||||
|
|
||||||
|
await page.goto('/signin');
|
||||||
|
|
||||||
|
await page.getByLabel('Email').fill(user.email);
|
||||||
|
await page.getByLabel('Password', { exact: true }).fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Sign In' }).click();
|
||||||
|
|
||||||
|
await page.waitForURL('/documents');
|
||||||
|
|
||||||
|
await page.keyboard.press('Meta+K');
|
||||||
|
|
||||||
|
await page.getByPlaceholder('Type a command or search...').fill('sent');
|
||||||
|
await expect(page.getByRole('option', { name: '[713] Document - Sent' })).toBeVisible();
|
||||||
|
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
|
||||||
|
// signout
|
||||||
|
await page.getByTitle('Profile Dropdown').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Sign Out' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[PR-713]: should see received documents', async ({ page }) => {
|
||||||
|
const [user] = TEST_USERS;
|
||||||
|
|
||||||
|
await page.goto('/signin');
|
||||||
|
|
||||||
|
await page.getByLabel('Email').fill(user.email);
|
||||||
|
await page.getByLabel('Password', { exact: true }).fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Sign In' }).click();
|
||||||
|
|
||||||
|
await page.waitForURL('/documents');
|
||||||
|
|
||||||
|
await page.keyboard.press('Meta+K');
|
||||||
|
|
||||||
|
await page.getByPlaceholder('Type a command or search...').fill('received');
|
||||||
|
await expect(page.getByRole('option', { name: '[713] Document - Received' })).toBeVisible();
|
||||||
|
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
|
||||||
|
// signout
|
||||||
|
await page.getByTitle('Profile Dropdown').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Sign Out' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[PR-713]: should be able to search by recipient', async ({ page }) => {
|
||||||
|
const [user, recipient] = TEST_USERS;
|
||||||
|
|
||||||
|
await page.goto('/signin');
|
||||||
|
|
||||||
|
await page.getByLabel('Email').fill(user.email);
|
||||||
|
await page.getByLabel('Password', { exact: true }).fill(user.password);
|
||||||
|
await page.getByRole('button', { name: 'Sign In' }).click();
|
||||||
|
|
||||||
|
await page.waitForURL('/documents');
|
||||||
|
|
||||||
|
await page.keyboard.press('Meta+K');
|
||||||
|
|
||||||
|
await page.getByPlaceholder('Type a command or search...').fill(recipient.email);
|
||||||
|
await expect(page.getByRole('option', { name: '[713] Document - Sent' })).toBeVisible();
|
||||||
|
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
|
||||||
|
// signout
|
||||||
|
await page.getByTitle('Profile Dropdown').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Sign Out' }).click();
|
||||||
|
});
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
import { DocumentStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
export type SearchDocumentsWithKeywordOptions = {
|
||||||
|
query: string;
|
||||||
|
userId: number;
|
||||||
|
limit?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const searchDocumentsWithKeyword = async ({
|
||||||
|
query,
|
||||||
|
userId,
|
||||||
|
limit = 5,
|
||||||
|
}: SearchDocumentsWithKeywordOptions) => {
|
||||||
|
const user = await prisma.user.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const documents = await prisma.document.findMany({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
contains: query,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
userId: userId,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Recipient: {
|
||||||
|
some: {
|
||||||
|
email: {
|
||||||
|
contains: query,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
userId: userId,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: DocumentStatus.COMPLETED,
|
||||||
|
Recipient: {
|
||||||
|
some: {
|
||||||
|
email: user.email,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
contains: query,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: DocumentStatus.PENDING,
|
||||||
|
Recipient: {
|
||||||
|
some: {
|
||||||
|
email: user.email,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
contains: query,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
take: limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return documents;
|
||||||
|
};
|
||||||
@ -0,0 +1,167 @@
|
|||||||
|
import type { User } from '@prisma/client';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { hashSync } from '@documenso/lib/server-only/auth/hash';
|
||||||
|
|
||||||
|
import { prisma } from '..';
|
||||||
|
import {
|
||||||
|
DocumentDataType,
|
||||||
|
DocumentStatus,
|
||||||
|
FieldType,
|
||||||
|
Prisma,
|
||||||
|
ReadStatus,
|
||||||
|
SendStatus,
|
||||||
|
SigningStatus,
|
||||||
|
} from '../client';
|
||||||
|
|
||||||
|
//
|
||||||
|
// https://github.com/documenso/documenso/pull/713
|
||||||
|
//
|
||||||
|
|
||||||
|
const PULL_REQUEST_NUMBER = 713;
|
||||||
|
|
||||||
|
const EMAIL_DOMAIN = `pr-${PULL_REQUEST_NUMBER}.documenso.com`;
|
||||||
|
|
||||||
|
export const TEST_USERS = [
|
||||||
|
{
|
||||||
|
name: 'User 1',
|
||||||
|
email: `user1@${EMAIL_DOMAIN}`,
|
||||||
|
password: 'Password123',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User 2',
|
||||||
|
email: `user2@${EMAIL_DOMAIN}`,
|
||||||
|
password: 'Password123',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const examplePdf = fs
|
||||||
|
.readFileSync(path.join(__dirname, '../../../assets/example.pdf'))
|
||||||
|
.toString('base64');
|
||||||
|
|
||||||
|
export const seedDatabase = async () => {
|
||||||
|
const users = await Promise.all(
|
||||||
|
TEST_USERS.map(async (u) =>
|
||||||
|
prisma.user.create({
|
||||||
|
data: {
|
||||||
|
name: u.name,
|
||||||
|
email: u.email,
|
||||||
|
password: hashSync(u.password),
|
||||||
|
emailVerified: new Date(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [user1, user2] = users;
|
||||||
|
|
||||||
|
await createSentDocument(user1, [user2]);
|
||||||
|
await createReceivedDocument(user2, [user1]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSentDocument = async (sender: User, recipients: User[]) => {
|
||||||
|
const documentData = await prisma.documentData.create({
|
||||||
|
data: {
|
||||||
|
type: DocumentDataType.BYTES_64,
|
||||||
|
data: examplePdf,
|
||||||
|
initialData: examplePdf,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const document = await prisma.document.create({
|
||||||
|
data: {
|
||||||
|
title: `[${PULL_REQUEST_NUMBER}] Document - Sent`,
|
||||||
|
status: DocumentStatus.PENDING,
|
||||||
|
documentDataId: documentData.id,
|
||||||
|
userId: sender.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const recipient of recipients) {
|
||||||
|
const index = recipients.indexOf(recipient);
|
||||||
|
|
||||||
|
await prisma.recipient.create({
|
||||||
|
data: {
|
||||||
|
email: String(recipient.email),
|
||||||
|
name: String(recipient.name),
|
||||||
|
token: `sent-token-${index}`,
|
||||||
|
readStatus: ReadStatus.NOT_OPENED,
|
||||||
|
sendStatus: SendStatus.SENT,
|
||||||
|
signingStatus: SigningStatus.NOT_SIGNED,
|
||||||
|
signedAt: new Date(),
|
||||||
|
Document: {
|
||||||
|
connect: {
|
||||||
|
id: document.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Field: {
|
||||||
|
create: {
|
||||||
|
page: 1,
|
||||||
|
type: FieldType.NAME,
|
||||||
|
inserted: true,
|
||||||
|
customText: String(recipient.name),
|
||||||
|
positionX: new Prisma.Decimal(1),
|
||||||
|
positionY: new Prisma.Decimal(1),
|
||||||
|
width: new Prisma.Decimal(1),
|
||||||
|
height: new Prisma.Decimal(1),
|
||||||
|
documentId: document.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createReceivedDocument = async (sender: User, recipients: User[]) => {
|
||||||
|
const documentData = await prisma.documentData.create({
|
||||||
|
data: {
|
||||||
|
type: DocumentDataType.BYTES_64,
|
||||||
|
data: examplePdf,
|
||||||
|
initialData: examplePdf,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const document = await prisma.document.create({
|
||||||
|
data: {
|
||||||
|
title: `[${PULL_REQUEST_NUMBER}] Document - Received`,
|
||||||
|
status: DocumentStatus.PENDING,
|
||||||
|
documentDataId: documentData.id,
|
||||||
|
userId: sender.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const recipient of recipients) {
|
||||||
|
const index = recipients.indexOf(recipient);
|
||||||
|
|
||||||
|
await prisma.recipient.create({
|
||||||
|
data: {
|
||||||
|
email: String(recipient.email),
|
||||||
|
name: String(recipient.name),
|
||||||
|
token: `received-token-${index}`,
|
||||||
|
readStatus: ReadStatus.NOT_OPENED,
|
||||||
|
sendStatus: SendStatus.SENT,
|
||||||
|
signingStatus: SigningStatus.NOT_SIGNED,
|
||||||
|
signedAt: new Date(),
|
||||||
|
Document: {
|
||||||
|
connect: {
|
||||||
|
id: document.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Field: {
|
||||||
|
create: {
|
||||||
|
page: 1,
|
||||||
|
type: FieldType.NAME,
|
||||||
|
inserted: true,
|
||||||
|
customText: String(recipient.name),
|
||||||
|
positionX: new Prisma.Decimal(1),
|
||||||
|
positionY: new Prisma.Decimal(1),
|
||||||
|
width: new Prisma.Decimal(1),
|
||||||
|
height: new Prisma.Decimal(1),
|
||||||
|
documentId: document.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -8,6 +8,7 @@ import { duplicateDocumentById } from '@documenso/lib/server-only/document/dupli
|
|||||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||||
|
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
|
||||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||||
import { updateTitle } from '@documenso/lib/server-only/document/update-title';
|
import { updateTitle } from '@documenso/lib/server-only/document/update-title';
|
||||||
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
|
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
|
||||||
@ -20,6 +21,7 @@ import {
|
|||||||
ZGetDocumentByIdQuerySchema,
|
ZGetDocumentByIdQuerySchema,
|
||||||
ZGetDocumentByTokenQuerySchema,
|
ZGetDocumentByTokenQuerySchema,
|
||||||
ZResendDocumentMutationSchema,
|
ZResendDocumentMutationSchema,
|
||||||
|
ZSearchDocumentsMutationSchema,
|
||||||
ZSendDocumentMutationSchema,
|
ZSendDocumentMutationSchema,
|
||||||
ZSetFieldsForDocumentMutationSchema,
|
ZSetFieldsForDocumentMutationSchema,
|
||||||
ZSetRecipientsForDocumentMutationSchema,
|
ZSetRecipientsForDocumentMutationSchema,
|
||||||
@ -240,4 +242,23 @@ export const documentRouter = router({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
searchDocuments: authenticatedProcedure
|
||||||
|
.input(ZSearchDocumentsMutationSchema)
|
||||||
|
.query(async ({ input, ctx }) => {
|
||||||
|
const { query } = input;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const documents = await searchDocumentsWithKeyword({
|
||||||
|
query,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
});
|
||||||
|
return documents;
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'BAD_REQUEST',
|
||||||
|
message: 'We are unable to search for documents. Please try again later.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -84,3 +84,7 @@ export const ZDeleteDraftDocumentMutationSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export type TDeleteDraftDocumentMutationSchema = z.infer<typeof ZDeleteDraftDocumentMutationSchema>;
|
export type TDeleteDraftDocumentMutationSchema = z.infer<typeof ZDeleteDraftDocumentMutationSchema>;
|
||||||
|
|
||||||
|
export const ZSearchDocumentsMutationSchema = z.object({
|
||||||
|
query: z.string(),
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user