Skip to content
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

feat: template #52

Merged
merged 12 commits into from
Dec 21, 2023
Merged

feat: template #52

merged 12 commits into from
Dec 21, 2023

Conversation

SergeWilfried
Copy link
Owner

@SergeWilfried SergeWilfried commented Dec 21, 2023

Summary by CodeRabbit

  • New Features

    • Introduced the ability to create, edit, duplicate, and delete templates in the dashboard.
    • Added new template management components for a more streamlined user experience.
    • Implemented a new search functionality with a search bar in the Hero component.
    • Enhanced document flow with the addition of template fields and placeholder recipients.
  • Enhancements

    • Search results now include a templateId property for better integration with new features.
    • Improved navigation with updated links and icons in the desktop navigation bar.
    • Added new dialog components for various user actions like duplicating and deleting templates.
  • Bug Fixes

    • Corrected filtering logic to exclude non-signed recipients from actions.
    • Fixed an issue with keyboard event handling in the CommandMenu component.
  • Documentation

    • Updated constants with consistent use of single quotes for the SIGN property.
  • Refactor

    • Modified import statements to improve code clarity and maintainability.
    • Changed FindDocumentsOptions from an interface to a type for better type handling.
  • Style

    • Adjusted styles and classNames for components such as DialogPortal and headers for consistency.
  • Tests

    • Enhanced test coverage for new components and functionalities related to templates and document management.
  • Chores

    • Performed database schema updates to support new template features.
  • Revert

    • No reverts in this release.

Copy link

vercel bot commented Dec 21, 2023

Someone is attempting to deploy a commit to a Personal Account owned by @SergeWilfried on Vercel.

@SergeWilfried first needs to authorize it.

Copy link

coderabbitai bot commented Dec 21, 2023

Walkthrough

Walkthrough

The updates span across the application to integrate template functionality, impacting client-side components and server-side logic. Changes include new React components for template management, modifications to Prisma schema for database migrations, and the addition of template-related functions and schemas in server-side utilities. The focus is on creating, duplicating, editing, and deleting templates, and associated changes to documents and recipients.

Changes

File Path Change Summary
apps/marketing/.../client.tsx, .../singleplayer-router/router.ts Added templateId property with a default value of null.
apps/marketing/.../single-player-mode-success.tsx, .../constants.ts Updated imports and modified constants for single-player mode.
apps/web/.../documents/... Added router.push in duplicate-document-dialog.tsx and filtered non-signed recipients in data-table-action-dropdown.tsx.
apps/web/.../templates/... Introduced components for template actions, editing, listing, and dialogs for creating, duplicating, and deleting templates.
apps/web/.../layout/..., .../formatter/template-type.tsx Updated navigation, header styles, added new menu items, and a component for template types.
packages/lib/server-only/... Added server-side functionality for template and field management, and document creation from templates.
packages/prisma/... Database schema changes for templates and updates to Prisma models.
packages/trpc/server/... Added and updated routers and schemas for field and template management.
packages/ui/primitives/... Introduced components, types, and schemas for document and template flows.

🐇 To code, to build, a wondrous feat,
🌟 Templates dance, the changes greet.
With every line, the rabbits hop,
🎉 A grand update, the code does top!

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on X ?


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • You can reply to a review comment made by CodeRabbit.
  • You can tag CodeRabbit on specific lines of code or files in the PR by tagging @coderabbitai in a comment.
  • You can tag @coderabbitai in a PR comment and ask one-off questions about the PR and the codebase. Use quoted replies to pass the context for follow-up questions.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

@SergeWilfried SergeWilfried self-assigned this Dec 21, 2023
@SergeWilfried SergeWilfried linked an issue Dec 21, 2023 that may be closed by this pull request
5 tasks
@SergeWilfried SergeWilfried merged commit b307bf4 into SergeWilfried:main Dec 21, 2023
7 of 10 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 18

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 2a277a5 and 48cdf43.
Files selected for processing (52)
  • apps/marketing/src/app/(marketing)/singleplayer/client.tsx (2 hunks)
  • apps/marketing/src/components/(marketing)/single-player-mode/single-player-mode-success.tsx (1 hunks)
  • apps/marketing/src/components/constants.ts (1 hunks)
  • apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/documents/duplicate-document-dialog.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/templates/[id]/page.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/templates/data-table-action-dropdown.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/templates/data-table-templates.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/templates/data-table-title.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/templates/delete-template-dialog.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/templates/duplicate-template-dialog.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/templates/empty-state.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/templates/new-template-dialog.tsx (1 hunks)
  • apps/web/src/app/(dashboard)/templates/page.tsx (1 hunks)
  • apps/web/src/components/(dashboard)/common/command-menu.tsx (1 hunks)
  • apps/web/src/components/(dashboard)/layout/desktop-nav.tsx (3 hunks)
  • apps/web/src/components/(dashboard)/layout/header.tsx (1 hunks)
  • apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx (2 hunks)
  • apps/web/src/components/formatter/template-type.tsx (1 hunks)
  • packages/lib/server-only/admin/get-recipients-stats.ts (1 hunks)
  • packages/lib/server-only/document/find-documents.ts (2 hunks)
  • packages/lib/server-only/field/get-fields-for-template.ts (1 hunks)
  • packages/lib/server-only/field/remove-signed-field-with-token.ts (1 hunks)
  • packages/lib/server-only/field/set-fields-for-document.ts (1 hunks)
  • packages/lib/server-only/field/set-fields-for-template.ts (1 hunks)
  • packages/lib/server-only/field/sign-field-with-token.ts (1 hunks)
  • packages/lib/server-only/recipient/get-recipients-for-template.ts (1 hunks)
  • packages/lib/server-only/recipient/set-recipients-for-template.ts (1 hunks)
  • packages/lib/server-only/template/create-document-from-template.ts (1 hunks)
  • packages/lib/server-only/template/create-template.ts (1 hunks)
  • packages/lib/server-only/template/delete-template.ts (1 hunks)
  • packages/lib/server-only/template/duplicate-template.ts (1 hunks)
  • packages/lib/server-only/template/get-template-by-id.ts (1 hunks)
  • packages/lib/server-only/template/get-templates.ts (1 hunks)
  • packages/prisma/migrations/20231221101005_add_templates/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (6 hunks)
  • packages/trpc/server/field-router/router.ts (2 hunks)
  • packages/trpc/server/field-router/schema.ts (1 hunks)
  • packages/trpc/server/recipient-router/router.ts (2 hunks)
  • packages/trpc/server/recipient-router/schema.ts (1 hunks)
  • packages/trpc/server/router.ts (2 hunks)
  • packages/trpc/server/singleplayer-router/router.ts (1 hunks)
  • packages/trpc/server/template-router/router.ts (1 hunks)
  • packages/trpc/server/template-router/schema.ts (1 hunks)
  • packages/ui/primitives/dialog.tsx (1 hunks)
  • packages/ui/primitives/document-dropzone.tsx (2 hunks)
  • packages/ui/primitives/document-flow/types.ts (1 hunks)
  • packages/ui/primitives/template-flow/add-template-fields.tsx (1 hunks)
  • packages/ui/primitives/template-flow/add-template-fields.types.ts (1 hunks)
  • packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx (1 hunks)
  • packages/ui/primitives/template-flow/add-template-placeholder-recipients.types.ts (1 hunks)
Files skipped from review due to trivial changes (4)
  • apps/marketing/src/components/(marketing)/single-player-mode/single-player-mode-success.tsx
  • apps/marketing/src/components/constants.ts
  • packages/lib/server-only/document/find-documents.ts
  • packages/trpc/server/recipient-router/schema.ts
Additional comments: 61
apps/web/src/app/(dashboard)/templates/empty-state.tsx (1)
  • 3-16: The component looks good and follows best practices for a presentational component. No action needed.
packages/ui/primitives/template-flow/add-template-fields.types.ts (1)
  • 5-23: The schema looks comprehensive and includes validation for the fields. Ensure that all the fields are required for the operation and that the validation rules are in line with the application's requirements.
packages/lib/server-only/admin/get-recipients-stats.ts (1)
  • 20-27: The accumulation logic for recipient statistics seems correct. However, ensure that the statistics are not exposing any sensitive information and are properly authorized.
packages/ui/primitives/template-flow/add-template-placeholder-recipients.types.ts (1)
  • 3-26: The schema includes a refinement to ensure unique emails, which is a good practice. Confirm that the error message and path are correctly implemented and user-friendly.
packages/trpc/server/template-router/schema.ts (1)
  • 3-26: The schemas appear to be well-defined with appropriate validation rules. Ensure that they match the expected input for the mutations they are used with.
packages/trpc/server/router.ts (1)
  • 6-12: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [9-23]

The addition of the templateRouter to the appRouter object is correct. Ensure that the templateRouter is properly configured and that all routes are secured and validated.

apps/web/src/components/formatter/template-type.tsx (1)
  • 35-49: The component looks good and follows best practices for a presentational component. No action needed.
packages/ui/primitives/document-flow/types.ts (1)
  • 24-30: The change to make the signerEmail field optional in the ZDocumentFlowFormSchema is correct. Ensure that this change is reflected in the UI and that the application logic accounts for the possibility of an absent signerEmail.
packages/trpc/server/field-router/schema.ts (1)
  • 24-41: The addition of the ZAddTemplateFieldsMutationSchema is correct. Ensure that the schema is used in the appropriate mutation and that the validation rules are consistent with the application's requirements.
apps/web/src/components/(dashboard)/layout/header.tsx (1)
  • 52-55: The change to add a margin-left style for medium-sized screens is correct. Ensure that this change is consistent with the design system and responsive layout of the application.
packages/trpc/server/recipient-router/router.ts (1)
  • 40-63: The addition of the addTemplateSigners procedure is correct. Ensure that the procedure is properly secured and that the input is validated before processing.
apps/web/src/components/(dashboard)/layout/desktop-nav.tsx (1)
  • 41-69: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [29-87]

The component should ensure that the navigation links are accessible and that the active link is correctly highlighted based on the current route.

packages/lib/server-only/field/set-fields-for-document.ts (1)
  • 5-6: The import statement for FieldType has been changed to a type import, which is correct if FieldType is only used for type annotations and not as a value.
apps/web/src/app/(dashboard)/documents/duplicate-document-dialog.tsx (1)
  • 41-43: The addition of router.push in the onSuccess callback is correct. Ensure that the user is redirected to the correct route after the document is duplicated.
packages/prisma/migrations/20231221101005_add_templates/migration.sql (16)
  • 8-8: Creation of the TemplateType enum is correct and follows SQL standards.

  • 11-11: Dropping the foreign key constraint on the Field table is a significant change. Ensure that this is intentional and that the logic relying on this constraint is updated accordingly.

  • 14-15: Adding a templateId column and making documentId nullable in the Field table is a structural change that aligns with the introduction of templates. Ensure that the application logic is updated to handle these nullable fields.

  • 18-22: The CHECK constraint added to the Field table ensures that either templateId or documentId is set, but not both. This is a good practice to maintain data integrity.

  • 25-26: Similar changes to the Recipient table as the Field table are noted here, with the addition of templateId and making documentId nullable.

  • 29-33: The CHECK constraint for the Recipient table is consistent with the Field table and maintains data integrity.

  • 36-45: The Template table is created with appropriate fields and constraints. The use of SERIAL for the id and setting defaults for timestamps are standard practices.

  • 49-49: Creating a unique index on templateDocumentDataId in the Template table ensures that each template has a unique document data identifier.

  • 52-52: Creating an index on templateId in the Field table will improve query performance for operations involving templates.

  • 55-55: Similarly, creating an index on templateId in the Recipient table will enhance performance for recipient-related queries.

  • 58-58: The unique index on ["templateId", "email"] in the Recipient table enforces the uniqueness of recipients per template, which is crucial for data integrity.

  • 61-61: Adding a foreign key constraint from Recipient to Template with cascade on delete and update is a good practice to maintain referential integrity.

  • 64-64: Adding a foreign key constraint from Field to Template with cascade on delete and update is consistent with the previous constraint and maintains referential integrity.

  • 67-67: Re-adding the foreign key constraint from Field to Recipient with set null on delete is important for maintaining relationships between fields and recipients.

  • 70-70: Adding a foreign key constraint from Template to DocumentData with cascade on delete and update ensures that templates are always associated with valid document data.

  • 73-73: Adding a foreign key constraint from Template to User with cascade on delete and update ensures that templates are always associated with a valid user.

packages/trpc/server/template-router/router.ts (5)
  • 1-1: Importing necessary modules and functions from external libraries and internal paths is correct.

  • 16-36: The createTemplate procedure is correctly set up with authentication, input validation, and error handling. It uses a try-catch block to catch and handle errors gracefully.

  • 38-54: The createDocumentFromTemplate procedure follows the same pattern as createTemplate and is correctly implemented with appropriate error handling.

  • 56-74: The duplicateTemplate procedure is implemented consistently with the other procedures and includes proper error handling.

  • 76-93: The deleteTemplate procedure is correctly implemented with authentication and error handling. It also uses a try-catch block to handle exceptions.

packages/lib/server-only/field/set-fields-for-template.ts (4)
  • 1-1: Importing the Prisma client and type definitions is correct.

  • 4-14: Defining the Field type with optional and required properties is correct and follows TypeScript best practices.

  • 16-20: Defining the SetFieldsForTemplateOptions type with required properties is correct.

  • 22-117: The setFieldsForTemplate function is well-structured with clear error handling, transactional operations, and proper use of Prisma client methods. Ensure that the logic for handling existing fields and removed fields is thoroughly tested.

packages/trpc/server/field-router/router.ts (2)
  • 2-14: The imports are correct, and the addition of setFieldsForTemplate and ZAddTemplateFieldsMutationSchema is noted.

  • 47-66: The addTemplateFields procedure is correctly set up with authentication and input validation. It uses a try-catch block for error handling, which is good practice.

packages/ui/primitives/dialog.tsx (1)
  • 23-23: The change in the className property from z-50 to z-[9999] is a significant increase in the z-index value. Ensure that this change does not cause unintended stacking context issues with other components.
apps/web/src/app/(dashboard)/templates/data-table-templates.tsx (1)
  • 1-138: The TemplatesDataTable component is well-structured with state management, event handling, and conditional rendering. Ensure that the logic for handling loading states and routing is thoroughly tested.
apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx (2)
  • 4-7: The addition of the FileSpreadsheet import is correct and follows TypeScript import syntax.

  • 110-116: The new DropdownMenuItem for "Templates" is correctly added with a link and an icon. Ensure that the link correctly routes to the templates page.

packages/ui/primitives/document-dropzone.tsx (2)
  • 78-85: The addition of the DocumentDescription object with document and template properties is correct and follows TypeScript best practices for defining object structures.

  • 171-171: The dynamic selection of the headline based on the type property is a good use of conditional rendering.

packages/trpc/server/singleplayer-router/router.ts (1)
  • 66-66: The addition of the templateId property with a value of null to the object within the signPdf function is a structural change. Ensure that the application logic correctly handles this new nullable property.
apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx (1)
  • 101-101: The addition of the line that filters non-signed recipients from a row is correct. Ensure that this logic is consistent with the application's requirements for displaying actions based on recipient status.
apps/web/src/app/(dashboard)/templates/[id]/edit-template.tsx (1)
  • 1-156: The EditTemplateForm component is well-structured with state management, form handling, and conditional rendering. Ensure that the logic for handling form submissions and routing is thoroughly tested.
apps/web/src/components/(dashboard)/common/command-menu.tsx (1)
  • 88-91: The modification to the toggleOpen function to accept a KeyboardEvent parameter and call e.preventDefault() is correct. This change prevents the default behavior of the event, which is a common practice in event handling.
packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx (1)
  • 1-193: The AddTemplatePlaceholderRecipientsFormPartial component is correctly implemented with form handling, dynamic form fields, and animations. Ensure that the form validation and submission logic is thoroughly tested.
packages/prisma/schema.prisma (5)
  • 44-44: The addition of the Template relation to the User model is correct and follows Prisma schema conventions.

  • 157-157: The addition of the Template relation to the DocumentData model is correct.

  • 184-203: The changes to the Recipient model, including the new templateId field and Template relation, are correct. Ensure that the unique and index constraints are appropriate for the application's requirements.

  • 226-238: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [218-235]

The changes to the Field model, including the new templateId field and Template relation, are correct. The index on templateId will improve query performance.

  • 272-286: The new Template model is correctly defined with fields, relations, and a unique constraint on templateDocumentDataId.
apps/marketing/src/app/(marketing)/singleplayer/client.tsx (2)
  • 86-92: Adding templateId: null to the mapped fields ensures that the new template functionality is considered when fields are created. This change seems consistent with the PR's objective to integrate template management.

  • 149-155: The addition of templateId: null to the placeholderRecipient object aligns with the new template features. This ensures that the recipient structure is consistent with the expected data model changes.

apps/web/src/app/(dashboard)/templates/new-template-dialog.tsx (1)
  • 46-115: The NewTemplateDialog component provides a form for creating new templates, including file upload and automatic form value setting based on the uploaded file's name. Ensure that the file upload and form submission processes are thoroughly tested, especially error handling during the file upload and template creation steps.
Verification successful

The search results indicate that createTemplate and putFile are used in multiple places within the codebase. The snippets from template-router/router.ts and create-template.ts suggest that there is a try-catch block in the router that likely handles errors when calling createTemplate. Similarly, the putFile function in put-file.ts is used in various contexts, and the presence of try-catch blocks in the calling code (e.g., new-template-dialog.tsx) suggests that errors during file upload are also handled.

Given the evidence of error handling in the provided contexts, it seems that the file upload and template creation processes are designed to handle errors gracefully.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that the file upload and template creation processes handle errors gracefully.
# Expecting no unhandled exceptions or user-facing technical error messages.
rg --type typescript --type javascript -C 3 "createTemplate"
rg --type typescript --type javascript -C 3 "putFile"

Length of output: 184



Script:

#!/bin/bash
# Search for occurrences of "createTemplate" and "putFile" to verify error handling.
rg -C 3 "createTemplate"
rg -C 3 "putFile"

Length of output: 15035

packages/ui/primitives/template-flow/add-template-fields.tsx (1)
  • 62-538: The AddTemplateFieldsFormPartial component handles the addition, movement, and resizing of fields within a template. It's important to ensure that the field manipulation logic works correctly within the bounds of the document and that the state updates are handled efficiently without unnecessary re-renders.
Verification successful

The verification process has confirmed the use of useDocumentElement and useFieldArray hooks within the add-template-fields.tsx file, which are integral to the field manipulation logic. There is no direct evidence from the search results to suggest issues with field placement outside the document bounds or incorrect state updates.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that the field manipulation logic correctly restricts actions within the document bounds.
# Expecting no issues with field placement outside the document or incorrect state updates.
rg --type typescript --type javascript -C 3 "useDocumentElement"
rg --type typescript --type javascript -C 3 "useFieldArray"

Length of output: 194



Script:

#!/bin/bash
# Verify that the field manipulation logic correctly restricts actions within the document bounds.
# Expecting no issues with field placement outside the document or incorrect state updates.
rg -C 3 "useDocumentElement"
rg -C 3 "useFieldArray"

Length of output: 8852

/*
Warnings:

- A unique constraint covering the columns `[templateId,email]` on the table `Recipient` will be added. If there are existing duplicate values, this will fail.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning about the unique constraint on [templateId,email] is important. Ensure that any existing duplicates are resolved before applying this migration to avoid failure.

Comment on lines +10 to +11
export const deleteTemplate = async ({ id, userId }: DeleteTemplateOptions) => {
return await prisma.template.delete({ where: { id, userId } });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the deletion of a template is properly authorized and that only the owner or an authorized user can delete the template.

Comment on lines +8 to +17
export const getTemplateById = async ({ id, userId }: GetTemplateByIdOptions) => {
return await prisma.template.findFirstOrThrow({
where: {
id,
userId,
},
include: {
templateDocumentData: true,
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding error handling for the case where the template is not found or the user is not authorized to access it.

Comment on lines +8 to +21
export const getFieldsForTemplate = async ({ templateId, userId }: GetFieldsForTemplateOptions) => {
const fields = await prisma.field.findMany({
where: {
templateId,
Template: {
userId,
},
},
orderBy: {
id: 'asc',
},
});

return fields;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if there is a need to handle scenarios where no fields are found for the given template ID, or if the user is not authorized to view them.

Comment on lines +8 to +19
export const createTemplate = async ({
title,
userId,
templateDocumentDataId,
}: CreateTemplateOptions) => {
return await prisma.template.create({
data: {
title,
userId,
templateDocumentDataId,
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the creation of a template includes all necessary validation and error handling, especially considering the direct interaction with the database.

Comment on lines +26 to +79
export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) => {
const { data: session } = useSession();

const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [isDuplicateDialogOpen, setDuplicateDialogOpen] = useState(false);

if (!session) {
return null;
}

const isOwner = row.userId === session.user.id;

return (
<DropdownMenu>
<DropdownMenuTrigger>
<MoreHorizontal className="text-muted-foreground h-5 w-5" />
</DropdownMenuTrigger>

<DropdownMenuContent className="w-52" align="start" forceMount>
<DropdownMenuLabel>Action</DropdownMenuLabel>

<DropdownMenuItem disabled={!isOwner} asChild>
<Link href={`/templates/${row.id}`}>
<Edit className="mr-2 h-4 w-4" />
Edit
</Link>
</DropdownMenuItem>

{/* <DropdownMenuItem disabled={!isOwner} onClick={async () => onDuplicateButtonClick(row.id)}> */}
<DropdownMenuItem disabled={!isOwner} onClick={() => setDuplicateDialogOpen(true)}>
<Copy className="mr-2 h-4 w-4" />
Duplicate
</DropdownMenuItem>

<DropdownMenuItem disabled={!isOwner} onClick={() => setDeleteDialogOpen(true)}>
<Trash2 className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>

<DuplicateTemplateDialog
id={row.id}
open={isDuplicateDialogOpen}
onOpenChange={setDuplicateDialogOpen}
/>

<DeleteTemplateDialog
id={row.id}
open={isDeleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
/>
</DropdownMenu>
);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component should handle the disabled state more gracefully, possibly by providing tooltips or other indicators as to why an action is disabled.

Comment on lines +23 to +81
export default async function TemplatePage({ params }: TemplatePageProps) {
const { id } = params;

const templateId = Number(id);

if (!templateId || Number.isNaN(templateId)) {
redirect('/documents');
}

const { user } = await getRequiredServerComponentSession();

const template = await getTemplateById({
id: templateId,
userId: user.id,
}).catch(() => null);

if (!template || !template.templateDocumentData) {
redirect('/documents');
}

const { templateDocumentData } = template;

const [templateRecipients, templateFields] = await Promise.all([
getRecipientsForTemplate({
templateId,
userId: user.id,
}),
getFieldsForTemplate({
templateId,
userId: user.id,
}),
]);

return (
<div className="mx-auto max-w-screen-xl px-4 md:px-8">
<Link href="/templates" className="flex items-center text-[#7AC455] hover:opacity-80">
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
Templates
</Link>

<h1 className="mt-4 truncate text-2xl font-semibold md:text-3xl" title={template.title}>
{template.title}
</h1>

<div className="mt-2.5 flex items-center gap-x-6">
<TemplateType inheritColor type={template.type} className="text-muted-foreground" />
</div>

<EditTemplateForm
className="mt-8"
template={template}
user={user}
recipients={templateRecipients}
fields={templateFields}
documentData={templateDocumentData}
/>
</div>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server-side logic should include error handling for scenarios where the template is not found, the user session is not found, or the user is not authorized to view the template.

Comment on lines +21 to +84
export const DeleteTemplateDialog = ({ id, open, onOpenChange }: DeleteTemplateDialogProps) => {
const router = useRouter();

const { toast } = useToast();

const { mutateAsync: deleteTemplate, isLoading } = trpcReact.template.deleteTemplate.useMutation({
onSuccess: () => {
router.refresh();

toast({
title: 'Template deleted',
description: 'Your template has been successfully deleted.',
duration: 5000,
});

onOpenChange(false);
},
});

const onDeleteTemplate = async () => {
try {
await deleteTemplate({ id });
} catch {
toast({
title: 'Something went wrong',
description: 'This template could not be deleted at this time. Please try again.',
variant: 'destructive',
duration: 7500,
});
}
};

return (
<Dialog open={open} onOpenChange={(value) => !isLoading && onOpenChange(value)}>
<DialogContent>
<DialogHeader>
<DialogTitle>Do you want to delete this template?</DialogTitle>

<DialogDescription>
Please note that this action is irreversible. Once confirmed, your template will be
permanently deleted.
</DialogDescription>
</DialogHeader>

<DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4">
<Button
type="button"
variant="secondary"
onClick={() => onOpenChange(false)}
className="flex-1"
>
Cancel
</Button>

<Button type="button" loading={isLoading} onClick={onDeleteTemplate} className="flex-1">
Delete
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component should handle errors more gracefully, possibly by providing more detailed error messages or guidance on what the user should do next.

Comment on lines +15 to +96
export const setRecipientsForTemplate = async ({
userId,
templateId,
recipients,
}: SetRecipientsForTemplateOptions) => {
const template = await prisma.template.findFirst({
where: {
id: templateId,
userId,
},
});

if (!template) {
throw new Error('Template not found');
}

const normalizedRecipients = recipients.map((recipient) => ({
...recipient,
email: recipient.email.toLowerCase(),
}));

const existingRecipients = await prisma.recipient.findMany({
where: {
templateId,
},
});

const removedRecipients = existingRecipients.filter(
(existingRecipient) =>
!normalizedRecipients.find(
(recipient) =>
recipient.id === existingRecipient.id || recipient.email === existingRecipient.email,
),
);

const linkedRecipients = normalizedRecipients.map((recipient) => {
const existing = existingRecipients.find(
(existingRecipient) =>
existingRecipient.id === recipient.id || existingRecipient.email === recipient.email,
);

return {
...recipient,
_persisted: existing,
};
});

const persistedRecipients = await prisma.$transaction(
// Disabling as wrapping promises here causes type issues
// eslint-disable-next-line @typescript-eslint/promise-function-async
linkedRecipients.map((recipient) =>
prisma.recipient.upsert({
where: {
id: recipient._persisted?.id ?? -1,
templateId,
},
update: {
name: recipient.name,
email: recipient.email,
templateId,
},
create: {
name: recipient.name,
email: recipient.email,
token: nanoid(),
templateId,
},
}),
),
);

if (removedRecipients.length > 0) {
await prisma.recipient.deleteMany({
where: {
id: {
in: removedRecipients.map((recipient) => recipient.id),
},
},
});
}

return persistedRecipients;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function should include comprehensive error handling for scenarios such as template not found, unauthorized access, and database operation failures.

Comment on lines 34 to 42
const { Document: document, Recipient: recipient } = field;

if (!document) {
throw new Error(`Document not found for field ${field.id}`);
}

if (document.status === DocumentStatus.COMPLETED) {
throw new Error(`Document ${document.id} has already been completed`);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function should include comprehensive error handling for scenarios such as document not found, document completed, document deleted, recipient already signed, and field already inserted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat: Document template
4 participants