Conversation
- API: new /api/teams/[teamId]/datarooms/[id]/search endpoint that searches both documents (by document name) and folders (by folder name) within a dataroom, returning path breadcrumbs for each result - SWR: add useDataroomSearch hook with types for search results - UI: add SearchBoxPersisted to both root and subfolder document pages; when a search query is active, display DataroomSearchResults instead of the normal folder/document list with reorder/DnD - Search results show folders and documents separately with counts, each with clickable breadcrumb paths showing their location in the dataroom hierarchy - Empty state shown when no results match the query Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
|
Cursor Agent can help with this pull request. Just |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR introduces dataroom search functionality by adding a new API search endpoint, custom hook for fetching search results with folder path enrichment, a search results display component with breadcrumb navigation, and integrating search UI into existing dataroom document pages. Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@pages/api/teams/`[teamId]/datarooms/[id]/search.ts:
- Around line 38-50: You're currently authorizing by team membership using
prisma.team.findUnique but not verifying the dataroom belongs to that team;
before performing the two searches (those that use dataroomId), query the
dataroom record with the provided dataroomId and teamId (e.g., via
prisma.dataroom.findFirst/findUnique where id = dataroomId and teamId = teamId)
and return 401/403 if not found, then proceed with the existing search logic so
only datarooms tied to the authorized team can be enumerated.
- Around line 124-160: The breadcrumb map is only populated when documents have
folderId, so folder results miss paths; change the folderId collection logic to
include folder matches as well (include IDs from documents and from the returned
folders array) and load allFoldersInDataroom whenever either set is non-empty
(use the combined folderIds length > 0). Also update how you compute folder
breadcrumbs for folder results: call buildBreadcrumb(folder.id) (not
folder.parentId) so the folder's own ancestry is included; references:
folderIds, allFoldersInDataroom, folderMap, buildBreadcrumb, documents,
foldersWithPath.
In `@pages/datarooms/`[id]/documents/[...name].tsx:
- Around line 41-45: The search branch is treating API errors and an unresolved
dataroom id as "no matches"; update the render logic that uses useDataroomSearch
(searchDocuments, searchFolders, isSearching, error) and DataroomSearchResults
so it first checks for search error (error) and that dataroom?.id is
defined/resolved before rendering results or lists; if error is present show the
error state (or error UI) and if dataroom?.id is undefined defer rendering (or
show loading), and pass a non-null dataroom id to DataroomSearchResults only
after resolution—apply the same guard to the other block referenced (lines
139-175) that forwards dataroom?.id!.
In `@pages/datarooms/`[id]/documents/index.tsx:
- Around line 40-44: The search branch currently ignores useDataroomSearch()'s
error and passes dataroom?.id! into DataroomSearchResults, causing backend
failures and unresolved dataroom ids to appear as empty results; update the
rendering logic that uses useDataroomSearch (references: searchDocuments,
searchFolders, isSearching, error) and DataroomSearchResults to: first check
error and render an error state (or early return) when error is present, and
ensure dataroom.id is a concrete value (use useDataroom() result rather than
dataroom?.id!) before passing it to child components; apply the same
guard/early-return pattern to the other search rendering block that mirrors this
logic.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bef6c6f9-98d3-4e9c-b666-b990c1b2351e
📒 Files selected for processing (5)
components/datarooms/dataroom-search-results.tsxlib/swr/use-dataroom.tspages/api/teams/[teamId]/datarooms/[id]/search.tspages/datarooms/[id]/documents/[...name].tsxpages/datarooms/[id]/documents/index.tsx
| try { | ||
| const team = await prisma.team.findUnique({ | ||
| where: { | ||
| id: teamId, | ||
| users: { | ||
| some: { userId }, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| if (!team) { | ||
| return res.status(401).end("Unauthorized"); | ||
| } |
There was a problem hiding this comment.
Bind the authorization check to the dataroom.
This route proves the caller belongs to teamId, but the two searches below are scoped only by dataroomId. A user who belongs to any team can pair that team's id with another team's dataroom id and enumerate foreign document/folder names. Please verify that dataroomId belongs to the authorized team before running the search.
Also applies to: 52-122
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pages/api/teams/`[teamId]/datarooms/[id]/search.ts around lines 38 - 50,
You're currently authorizing by team membership using prisma.team.findUnique but
not verifying the dataroom belongs to that team; before performing the two
searches (those that use dataroomId), query the dataroom record with the
provided dataroomId and teamId (e.g., via prisma.dataroom.findFirst/findUnique
where id = dataroomId and teamId = teamId) and return 401/403 if not found, then
proceed with the existing search logic so only datarooms tied to the authorized
team can be enumerated.
| // Build breadcrumb paths for documents that are in folders | ||
| const folderIds = [ | ||
| ...new Set(documents.filter((d) => d.folderId).map((d) => d.folderId!)), | ||
| ]; | ||
|
|
||
| const allFoldersInDataroom = | ||
| folderIds.length > 0 | ||
| ? await prisma.dataroomFolder.findMany({ | ||
| where: { dataroomId }, | ||
| select: { id: true, name: true, path: true, parentId: true }, | ||
| }) | ||
| : []; | ||
|
|
||
| const folderMap = new Map( | ||
| allFoldersInDataroom.map((f) => [f.id, f]), | ||
| ); | ||
|
|
||
| const buildBreadcrumb = (folderId: string): string[] => { | ||
| const names: string[] = []; | ||
| let currentId: string | null = folderId; | ||
| while (currentId) { | ||
| const folder = folderMap.get(currentId); | ||
| if (!folder) break; | ||
| names.unshift(folder.name); | ||
| currentId = folder.parentId; | ||
| } | ||
| return names; | ||
| }; | ||
|
|
||
| const documentsWithPath = documents.map((doc) => ({ | ||
| ...doc, | ||
| folderPath: doc.folderId ? buildBreadcrumb(doc.folderId) : [], | ||
| })); | ||
|
|
||
| const foldersWithPath = folders.map((folder) => ({ | ||
| ...folder, | ||
| folderPath: folder.parentId ? buildBreadcrumb(folder.parentId) : [], |
There was a problem hiding this comment.
Include folder matches when building the breadcrumb map.
allFoldersInDataroom is only loaded when a matched document has a folderId, but foldersWithPath also calls buildBreadcrumb(). If a search returns nested folders and no documents, every folder result gets folderPath: [], so the breadcrumb UI disappears.
Suggested fix
- const folderIds = [
- ...new Set(documents.filter((d) => d.folderId).map((d) => d.folderId!)),
- ];
+ const folderIds = new Set<string>();
+ for (const doc of documents) {
+ if (doc.folderId) folderIds.add(doc.folderId);
+ }
+ for (const folder of folders) {
+ if (folder.parentId) folderIds.add(folder.parentId);
+ }
const allFoldersInDataroom =
- folderIds.length > 0
+ folderIds.size > 0
? await prisma.dataroomFolder.findMany({
where: { dataroomId },
select: { id: true, name: true, path: true, parentId: true },
})
: [];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Build breadcrumb paths for documents that are in folders | |
| const folderIds = [ | |
| ...new Set(documents.filter((d) => d.folderId).map((d) => d.folderId!)), | |
| ]; | |
| const allFoldersInDataroom = | |
| folderIds.length > 0 | |
| ? await prisma.dataroomFolder.findMany({ | |
| where: { dataroomId }, | |
| select: { id: true, name: true, path: true, parentId: true }, | |
| }) | |
| : []; | |
| const folderMap = new Map( | |
| allFoldersInDataroom.map((f) => [f.id, f]), | |
| ); | |
| const buildBreadcrumb = (folderId: string): string[] => { | |
| const names: string[] = []; | |
| let currentId: string | null = folderId; | |
| while (currentId) { | |
| const folder = folderMap.get(currentId); | |
| if (!folder) break; | |
| names.unshift(folder.name); | |
| currentId = folder.parentId; | |
| } | |
| return names; | |
| }; | |
| const documentsWithPath = documents.map((doc) => ({ | |
| ...doc, | |
| folderPath: doc.folderId ? buildBreadcrumb(doc.folderId) : [], | |
| })); | |
| const foldersWithPath = folders.map((folder) => ({ | |
| ...folder, | |
| folderPath: folder.parentId ? buildBreadcrumb(folder.parentId) : [], | |
| // Build breadcrumb paths for documents that are in folders | |
| const folderIds = new Set<string>(); | |
| for (const doc of documents) { | |
| if (doc.folderId) folderIds.add(doc.folderId); | |
| } | |
| for (const folder of folders) { | |
| if (folder.parentId) folderIds.add(folder.parentId); | |
| } | |
| const allFoldersInDataroom = | |
| folderIds.size > 0 | |
| ? await prisma.dataroomFolder.findMany({ | |
| where: { dataroomId }, | |
| select: { id: true, name: true, path: true, parentId: true }, | |
| }) | |
| : []; | |
| const folderMap = new Map( | |
| allFoldersInDataroom.map((f) => [f.id, f]), | |
| ); | |
| const buildBreadcrumb = (folderId: string): string[] => { | |
| const names: string[] = []; | |
| let currentId: string | null = folderId; | |
| while (currentId) { | |
| const folder = folderMap.get(currentId); | |
| if (!folder) break; | |
| names.unshift(folder.name); | |
| currentId = folder.parentId; | |
| } | |
| return names; | |
| }; | |
| const documentsWithPath = documents.map((doc) => ({ | |
| ...doc, | |
| folderPath: doc.folderId ? buildBreadcrumb(doc.folderId) : [], | |
| })); | |
| const foldersWithPath = folders.map((folder) => ({ | |
| ...folder, | |
| folderPath: folder.parentId ? buildBreadcrumb(folder.parentId) : [], |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pages/api/teams/`[teamId]/datarooms/[id]/search.ts around lines 124 - 160,
The breadcrumb map is only populated when documents have folderId, so folder
results miss paths; change the folderId collection logic to include folder
matches as well (include IDs from documents and from the returned folders array)
and load allFoldersInDataroom whenever either set is non-empty (use the combined
folderIds length > 0). Also update how you compute folder breadcrumbs for folder
results: call buildBreadcrumb(folder.id) (not folder.parentId) so the folder's
own ancestry is included; references: folderIds, allFoldersInDataroom,
folderMap, buildBreadcrumb, documents, foldersWithPath.
| const { | ||
| documents: searchDocuments, | ||
| folders: searchFolders, | ||
| isLoading: isSearching, | ||
| } = useDataroomSearch({ query: searchQuery }); |
There was a problem hiding this comment.
Don’t treat search errors as “no matches” here either.
This page has the same new failure mode as the root documents page: useDataroomSearch().error is ignored, so API failures fall through to DataroomSearchResults with empty arrays and render the empty state. The same switch also forwards dataroom?.id!, which can still be undefined on a direct ?search= load. Please gate the branch on error and a resolved dataroom id before rendering results or list content.
Also applies to: 139-175
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pages/datarooms/`[id]/documents/[...name].tsx around lines 41 - 45, The
search branch is treating API errors and an unresolved dataroom id as "no
matches"; update the render logic that uses useDataroomSearch (searchDocuments,
searchFolders, isSearching, error) and DataroomSearchResults so it first checks
for search error (error) and that dataroom?.id is defined/resolved before
rendering results or lists; if error is present show the error state (or error
UI) and if dataroom?.id is undefined defer rendering (or show loading), and pass
a non-null dataroom id to DataroomSearchResults only after resolution—apply the
same guard to the other block referenced (lines 139-175) that forwards
dataroom?.id!.
| const { | ||
| documents: searchDocuments, | ||
| folders: searchFolders, | ||
| isLoading: isSearching, | ||
| } = useDataroomSearch({ query: searchQuery }); |
There was a problem hiding this comment.
Don’t collapse search failures into the empty state.
useDataroomSearch() exposes error, but this branch ignores it and falls through to DataroomSearchResults with empty arrays, which will show “No documents or folders found” for backend failures. This block also still forwards dataroom?.id!, so a direct ?search= load can hand an invalid id to the child components before useDataroom() resolves. Please guard on error and a concrete dataroom id before rendering either branch.
Also applies to: 132-166
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pages/datarooms/`[id]/documents/index.tsx around lines 40 - 44, The search
branch currently ignores useDataroomSearch()'s error and passes dataroom?.id!
into DataroomSearchResults, causing backend failures and unresolved dataroom ids
to appear as empty results; update the rendering logic that uses
useDataroomSearch (references: searchDocuments, searchFolders, isSearching,
error) and DataroomSearchResults to: first check error and render an error state
(or early return) when error is present, and ensure dataroom.id is a concrete
value (use useDataroom() result rather than dataroom?.id!) before passing it to
child components; apply the same guard/early-return pattern to the other search
rendering block that mirrors this logic.
Adds a search box to the admin data room to enable searching for both documents and folders across the entire dataroom.
Summary by CodeRabbit