-
Notifications
You must be signed in to change notification settings - Fork 1k
Feat: Templates functionality #1743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@AshishViradiya153 is attempting to deploy a commit to the mftsio Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughThis update introduces a dataroom template system, enabling users to browse, preview, and create datarooms from pre-defined templates. New React components and SWR hooks manage template fetching, display, and creation workflows, while backend API endpoints support template listing and duplication. UI changes include new tabs, modals, and animated transitions. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI (BrowseTemplates/DataroomsPage)
participant SWR Hook
participant API /templates
participant API /duplicate-template
User->>UI (BrowseTemplates/DataroomsPage): Visit Templates tab or Browse Templates
UI (BrowseTemplates/DataroomsPage)->>SWR Hook: Fetch templates
SWR Hook->>API /templates: GET /api/teams/{teamId}/datarooms/templates
API /templates-->>SWR Hook: Return template list
SWR Hook-->>UI (BrowseTemplates/DataroomsPage): Provide templates
User->>UI (TemplateCard/UseTemplateModal): Click "Use Template"
UI (UseTemplateModal)->>API /duplicate-template: POST /api/teams/{teamId}/datarooms/duplicate-template
API /duplicate-template-->>UI (UseTemplateModal): Return new dataroom
UI (UseTemplateModal)->>SWR Hook: Mutate datarooms cache
UI (UseTemplateModal)-->>User: Show success, navigate to new dataroom
sequenceDiagram
participant User
participant WelcomePage
participant DataroomChoice
participant API /datarooms/index
participant API /datarooms/trial
User->>WelcomePage: Visit /welcome
WelcomePage->>DataroomChoice: Render choice UI
User->>DataroomChoice: Select "Create from Scratch"
DataroomChoice->>API /datarooms/index: POST create dataroom
API /datarooms/index-->>DataroomChoice: Return new dataroom
DataroomChoice-->>WelcomePage: Navigate to upload or created screen
User->>DataroomChoice: Select "Start Trial"
DataroomChoice->>API /datarooms/trial: POST trial
API /datarooms/trial-->>DataroomChoice: Return success/teamId
DataroomChoice-->>WelcomePage: Navigate to next welcome step
sequenceDiagram
participant User
participant DataroomCreated
participant API /links
User->>DataroomCreated: View dataroom created confirmation
User->>DataroomCreated: Click "Create Shareable Link"
DataroomCreated->>API /links: POST create link
API /links-->>DataroomCreated: Return link info
DataroomCreated-->>User: Show link options
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
npm error Exit handler never called! ✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (9)
pages/api/teams/[teamId]/datarooms/index.ts (1)
104-104
: Remove debug logging before production.The
console.log("teamthis", team)
statement should be removed as it's debug code that shouldn't be in production.- console.log("teamthis", team);
pages/api/teams/[teamId]/datarooms/templates.ts (1)
9-13
: Consider making template IDs configurable.The hardcoded template IDs work for the initial implementation, but consider moving them to environment variables or database configuration for better maintainability as the template system grows.
pages/api/teams/[teamId]/datarooms/duplicate-template.ts (2)
28-32
: Consider using environment variables for template configuration.The hardcoded template IDs make the system inflexible and require code changes to add new templates. Consider moving these to environment variables or a database configuration table.
-// Template dataroom IDs from Papermark Templates account -const TEMPLATE_DATAROOM_IDS = [ - "cmclsvtli0001jp04xhvtsbc8", - "cmcnpybt1000sjx04zz2p34kn", -]; +// Template dataroom IDs from Papermark Templates account +const TEMPLATE_DATAROOM_IDS = process.env.TEMPLATE_DATAROOM_IDS?.split(',') || [ + "cmclsvtli0001jp04xhvtsbc8", + "cmcnpybt1000sjx04zz2p34kn", +];
58-88
: Optimize recursive folder fetching to prevent N+1 queries.The current implementation makes individual database queries for each folder, which can lead to performance issues with deeply nested folder structures. Consider using a single query to fetch all folders and then organize them hierarchically in memory.
- // Recursive function to fetch folder contents - async function getFolderContents( - folderId: string, - ): Promise<DataroomFolderWithContents> { - const folder = await prisma.dataroomFolder.findUnique({ - where: { id: folderId }, - include: { - documents: true, - childFolders: { - include: { documents: true }, - }, - }, - }); - - if (!folder) { - throw new Error(`Folder with id ${folderId} not found`); - } - - const childFolders = await Promise.all( - folder.childFolders.map(async (childFolder) => { - const nestedContents = await getFolderContents(childFolder.id); - return nestedContents; - }), - ); - - return { - ...folder, - documents: folder.documents, - childFolders: childFolders, - }; - } + // Fetch all folders in a single query and organize hierarchically + const allFolders = await prisma.dataroomFolder.findMany({ + where: { dataroomId: templateId }, + include: { documents: true }, + }); + + // Build folder hierarchy + const folderMap = new Map<string, DataroomFolderWithContents>(); + const rootFolders: DataroomFolderWithContents[] = []; + + // Initialize all folders + allFolders.forEach(folder => { + folderMap.set(folder.id, { + ...folder, + documents: folder.documents, + childFolders: [], + }); + }); + + // Organize into hierarchy + allFolders.forEach(folder => { + const folderWithContents = folderMap.get(folder.id)!; + if (folder.parentId) { + const parent = folderMap.get(folder.parentId); + if (parent) { + parent.childFolders.push(folderWithContents); + } + } else { + rootFolders.push(folderWithContents); + } + });components/datarooms/use-template-modal.tsx (5)
52-56
: Simplify state management with proper prop handling.The component maintains both controlled and uncontrolled state which can lead to confusion. Consider using a custom hook to manage the modal state more cleanly.
- const [isOpen, setIsOpen] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [selectedTemplate, setSelectedTemplate] = - useState<DataroomTemplate | null>(template || null); - const [dataroomName, setDataroomName] = useState(""); + const [isOpen, setIsOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [selectedTemplate, setSelectedTemplate] = useState<DataroomTemplate | null>(template || null); + const [dataroomName, setDataroomName] = useState(""); + + // Reset selected template when template prop changes + useEffect(() => { + setSelectedTemplate(template || null); + }, [template]);
95-100
: Simplify modal state management.The modal state logic is overly complex with multiple conditionals. Consider using a more straightforward approach.
- const modalOpen = open !== undefined ? open : isOpen; - if (open !== undefined && setOpen) { - setOpen(false); - } else { - setIsOpen(false); - } + // Close modal using the appropriate setter + handleOpenChange(false);
126-127
: Extract modal state logic into a custom hook.The modal state management pattern is repeated and could be extracted into a reusable hook for better maintainability.
// Create a custom hook for modal state management function useModalState(controlledOpen?: boolean, controlledSetOpen?: (open: boolean) => void) { const [internalOpen, setInternalOpen] = useState(false); const isControlled = controlledOpen !== undefined; const open = isControlled ? controlledOpen : internalOpen; const setOpen = isControlled && controlledSetOpen ? controlledSetOpen : setInternalOpen; return { open, setOpen }; } // Then use it in the component: const { open: modalOpen, setOpen: handleOpenChange } = useModalState(open, setOpen);
168-184
: Add loading state for individual templates.The template selection cards don't provide visual feedback when clicked, which can feel unresponsive. Consider adding a loading state or immediate visual feedback.
<div className="grid gap-3"> {templates.map((tmpl) => ( <Card key={tmpl.id} - className="cursor-pointer transition-all duration-200 hover:border-primary/50 hover:bg-muted/50" + className="cursor-pointer transition-all duration-200 hover:border-primary/50 hover:bg-muted/50 focus:outline-none focus:ring-2 focus:ring-primary/50" onClick={() => setSelectedTemplate(tmpl)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setSelectedTemplate(tmpl); + } + }} + tabIndex={0} + role="button" + aria-label={`Select template: ${tmpl.name}`} >
201-210
: Improve accessibility for the name input.The input field lacks proper accessibility attributes and could benefit from better user guidance.
<Label htmlFor="dataroom-name">Dataroom Name</Label> <Input id="dataroom-name" placeholder={`${selectedTemplate.name}`} value={dataroomName} onChange={(e) => setDataroomName(e.target.value)} + aria-describedby="dataroom-name-help" /> - <p className="text-xs text-muted-foreground"> + <p id="dataroom-name-help" className="text-xs text-muted-foreground"> Leave empty to use default name: "{selectedTemplate.name} " </p>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
components/datarooms/template-card.tsx
(1 hunks)components/datarooms/use-template-modal.tsx
(1 hunks)components/view/dataroom/nav-dataroom.tsx
(1 hunks)components/welcome/browse-templates.tsx
(1 hunks)components/welcome/dataroom-choice.tsx
(1 hunks)components/welcome/dataroom-created.tsx
(1 hunks)components/welcome/dataroom-trial.tsx
(1 hunks)lib/swr/use-dataroom-templates.ts
(1 hunks)lib/utils.ts
(1 hunks)pages/api/teams/[teamId]/datarooms/duplicate-template.ts
(1 hunks)pages/api/teams/[teamId]/datarooms/index.ts
(2 hunks)pages/api/teams/[teamId]/datarooms/templates.ts
(1 hunks)pages/api/teams/[teamId]/datarooms/trial.ts
(1 hunks)pages/datarooms/[id]/branding/index.tsx
(2 hunks)pages/datarooms/index.tsx
(5 hunks)pages/room_ppreview_demo.tsx
(2 hunks)pages/welcome.tsx
(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
components/datarooms/template-card.tsx (5)
lib/swr/use-dataroom-templates.ts (1)
DataroomTemplate
(7-9)components/ui/card.tsx (4)
Card
(80-80)CardHeader
(81-81)CardTitle
(83-83)CardContent
(85-85)lib/utils.ts (1)
DEFAULT_BANNER_IMAGE
(752-752)components/datarooms/use-template-modal.tsx (1)
UseTemplateModal
(39-232)components/ui/button.tsx (1)
Button
(71-71)
components/welcome/browse-templates.tsx (3)
lib/swr/use-dataroom-templates.ts (1)
useDataroomTemplates
(11-29)lib/constants.ts (1)
STAGGER_CHILD_VARIANTS
(8-15)components/datarooms/template-card.tsx (1)
TemplateCard
(10-50)
components/welcome/dataroom-choice.tsx (2)
context/team-context.tsx (1)
useTeam
(85-85)lib/constants.ts (1)
STAGGER_CHILD_VARIANTS
(8-15)
pages/api/teams/[teamId]/datarooms/templates.ts (2)
pages/api/auth/[...nextauth].ts (1)
authOptions
(32-215)lib/types.ts (1)
CustomUser
(17-17)
lib/swr/use-dataroom-templates.ts (2)
context/team-context.tsx (1)
useTeam
(85-85)lib/utils.ts (1)
fetcher
(36-50)
pages/api/teams/[teamId]/datarooms/index.ts (1)
ee/limits/server.ts (1)
getLimits
(58-141)
pages/welcome.tsx (3)
components/welcome/dataroom-choice.tsx (1)
DataroomChoice
(14-135)components/welcome/browse-templates.tsx (1)
BrowseTemplates
(11-88)components/welcome/dataroom-created.tsx (1)
DataroomCreated
(21-157)
🪛 Biome (1.9.4)
pages/datarooms/index.tsx
[error] 151-191: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (27)
lib/utils.ts (1)
752-752
: Excellent refactoring to centralize the banner image constant.This eliminates duplication across multiple files and provides a single source of truth for the default banner image path.
components/view/dataroom/nav-dataroom.tsx (1)
9-9
: Good refactoring to use the centralized constant.The import correctly references the newly centralized
DEFAULT_BANNER_IMAGE
from the utils module, eliminating local duplication.pages/room_ppreview_demo.tsx (1)
7-7
: Consistent with the centralization pattern.Properly imports the centralized
DEFAULT_BANNER_IMAGE
constant, maintaining consistency across the codebase.pages/datarooms/[id]/branding/index.tsx (1)
29-34
: Well-structured import consolidation.The import statement cleanly groups related utilities including the centralized
DEFAULT_BANNER_IMAGE
, improving code organization and consistency.pages/api/teams/[teamId]/datarooms/trial.ts (1)
112-112
: All frontend consumers updated to handle the new response formatBoth
components/welcome/dataroom-trial.tsx
andcomponents/datarooms/dataroom-trial-modal.tsx
now do:const { success, teamId } = await response.json();—and handle errors via the returned
message
. No remaining consumers expect the old dataroom payload.components/welcome/dataroom-trial.tsx (3)
80-84
: LGTM! API response structure update is consistent.The change from expecting
dataroomId
tosuccess
andteamId
aligns with the updated trial API that no longer creates a dataroom entity directly but activates the trial instead.
89-89
: Analytics payload updated appropriately.The analytics now captures
teamId
instead of dataroom-specific information, which is consistent with the trial activation flow where no dataroom is created yet.
96-96
: Navigation flow improvement aligns with new UX.Redirecting to the dataroom-choice screen allows users to choose between creating from scratch or using templates, which is a better user experience than directly creating a dataroom.
pages/welcome.tsx (3)
12-15
: New component imports are properly organized.The imports for the new template-related components are correctly added and will support the enhanced dataroom creation flow.
45-49
: Skip button logic is well-designed.Hiding the skip button for template-related flows (
dataroom-trial
,dataroom-choice
,browse-templates
) makes sense as these are guided workflows that shouldn't be skipped.
90-102
: Conditional rendering for new components is correct.The template-related components are properly integrated with appropriate query parameter checks. The
DataroomCreated
component correctly requires both type and dataroomId parameters.components/datarooms/template-card.tsx (3)
10-16
: Well-defined component interface.The TypeScript interface clearly defines the required
template
prop and optionalonTemplateCreated
callback, promoting type safety and clear API design.
22-27
: Proper fallback handling for banner images.The implementation correctly uses the template's brand banner if available, falling back to the default banner image. This ensures a consistent UI even when templates don't have custom banners.
37-46
: Modal integration follows controlled component pattern.The UseTemplateModal is properly integrated with controlled open state and passes through the necessary props including the template and callback handler.
components/welcome/browse-templates.tsx (3)
57-70
: Excellent loading state implementation.The skeleton placeholders during template loading provide a great user experience. The grid layout matches the actual template cards, maintaining visual consistency.
72-84
: Proper conditional rendering and data handling.The component correctly checks for both templates existence and length before rendering, preventing potential runtime errors. The grid layout is responsive and well-structured.
15-17
: Navigation handler is clean and purposeful.The
handleTemplateCreated
function properly navigates to the dataroom-created screen with the necessary dataroomId parameter, maintaining the flow consistency.pages/api/teams/[teamId]/datarooms/index.ts (4)
86-87
: Trial plan support properly implemented.Adding
"free+drtrial"
and other trial variants to the allowed plans list correctly enables dataroom creation for trial users while maintaining plan restrictions.
96-102
: Query optimization improves performance.Using Prisma's
_count
include is more efficient than a separate count query, reducing database round trips while maintaining the same functionality.
109-116
: Trial limit enforcement is correctly implemented.The logic properly identifies trial plans using the
includes("drtrial")
check and enforces a 1-dataroom limit with appropriate error messaging. The error response format is consistent with other API endpoints.
120-120
: Proper exclusion of trial plans from general limits.Adding the
!isTrial
condition ensures that trial plans are handled by their specific limit check rather than the general limits system, maintaining clear separation of concerns.lib/swr/use-dataroom-templates.ts (1)
11-29
: LGTM! Clean SWR hook implementation.The hook follows SWR best practices with conditional fetching, proper type definitions, and appropriate return interface. The 120-second deduplication interval is reasonable for template data that doesn't change frequently.
components/welcome/dataroom-choice.tsx (1)
20-70
: Excellent error handling and state management.The
createFromScratch
function demonstrates robust error handling with proper loading states, toast notifications, and analytics tracking. The trial limit handling and navigation flow are well-implemented.pages/api/teams/[teamId]/datarooms/templates.ts (1)
15-73
: Well-structured API route with proper security.The implementation follows API best practices with proper authentication, authorization, error handling, and appropriate HTTP status codes. The team membership verification ensures data access security.
pages/datarooms/index.tsx (1)
139-252
: Excellent tabbed interface implementation.The tabbed layout with proper loading states, skeleton UI, and empty state handling provides a great user experience. The template integration is well-structured.
components/welcome/dataroom-created.tsx (2)
36-80
: Robust link creation with excellent error handling.The
createLink
function demonstrates proper error handling, loading states, and analytics tracking. The source attribution in analytics ("template_onboarding") provides valuable context for tracking.
82-91
: Clean conditional rendering pattern.The conditional rendering based on
showLinkOptions
andlinkId
provides a smooth user flow transition to the link configuration interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
pages/api/teams/[teamId]/datarooms/duplicate-template.ts (1)
203-225
: Duplicate limits fetching still present despite previous review.The code still calls
getLimits()
twice - once implicitly suggested in previous comments and once explicitly at line 213. The trial check uses a hardcoded limit of 1, while regular plans usegetLimits()
.Consider consolidating the limits logic:
+ const limits = await getLimits({ teamId, userId }); const isTrial = team.plan.includes("drtrial"); + const dataroomLimit = isTrial ? 1 : limits?.datarooms; - if (isTrial && team._count.datarooms >= 1) { + if (team._count.datarooms >= dataroomLimit) { return res.status(403).json({ message: "You've reached the limit of datarooms. Consider upgrading your plan.", - info: "trial_limit_reached", + info: isTrial ? "trial_limit_reached" : "limit_reached", }); } - const limits = await getLimits({ teamId, userId }); - - if (limits && team._count.datarooms >= limits.datarooms && !isTrial) { - console.log( - "Dataroom limit reached", - limits.datarooms, - team._count.datarooms, - ); - return res.status(400).json({ - message: - "You've reached the limit of datarooms. Consider upgrading your plan.", - }); - }
🧹 Nitpick comments (1)
pages/api/teams/[teamId]/datarooms/duplicate-template.ts (1)
260-264
: Generic error handling could be more informative.The catch block returns a generic error message regardless of the specific failure. Consider differentiating between different types of errors for better debugging and user experience.
} catch (error) { console.error("Request error", error); - res.status(500).json({ message: "Error creating dataroom from template" }); + if (error.message.includes("Template dataroom") || error.message.includes("not found")) { + res.status(404).json({ message: "Template not found" }); + } else if (error.message.includes("Failed to create") || error.message.includes("Failed to duplicate")) { + res.status(500).json({ message: "Error duplicating template contents" }); + } else { + res.status(500).json({ message: "Error creating dataroom from template" }); + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
pages/api/teams/[teamId]/datarooms/duplicate-template.ts
(1 hunks)pages/api/teams/[teamId]/datarooms/templates.ts
(1 hunks)pages/datarooms/index.tsx
(5 hunks)prisma/migrations/20250706215107_add_is_template_to_dataroom/migration.sql
(1 hunks)prisma/schema/dataroom.prisma
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- prisma/migrations/20250706215107_add_is_template_to_dataroom/migration.sql
🚧 Files skipped from review as they are similar to previous changes (2)
- pages/api/teams/[teamId]/datarooms/templates.ts
- pages/datarooms/index.tsx
🔇 Additional comments (5)
prisma/schema/dataroom.prisma (1)
30-30
: LGTM! Schema change supports template functionality effectively.The
isTemplate
field addition is well-designed with a sensible default value that ensures existing datarooms remain unaffected while enabling the new template functionality.pages/api/teams/[teamId]/datarooms/duplicate-template.ts (4)
18-27
: Well-structured type definitions for template duplication.The interface definitions properly extend the Prisma types to include nested relationships needed for template duplication. The recursive structure for
DataroomFolderWithContents
correctly models the folder hierarchy.
29-94
: Efficient recursive template fetching with proper error handling.The
fetchTemplateDataroomContents
function correctly:
- Validates the template exists with
isTemplate: true
- Recursively fetches nested folder contents
- Filters root-level documents appropriately
- Handles errors with meaningful messages
The implementation efficiently loads the complete template structure in a single operation.
114-137
: Improved error handling addresses previous review concerns.The error handling for document creation has been enhanced compared to the previous review comments. The code now:
- Logs specific error details for failed document creations
- Throws an error if any document creation fails, ensuring transaction rollback
- Provides clear error messages for debugging
This addresses the previous concern about silently ignoring failures.
231-258
: Excellent use of database transaction for atomicity.The implementation correctly uses
prisma.$transaction()
to ensure all operations (dataroom creation, document duplication, and folder duplication) are performed atomically. This addresses the previous review concern about data consistency.The transaction structure is well-organized and will properly roll back all changes if any step fails.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Chores