From e0e2f3e4400232b6a3284cfeab7e29009ef8078e Mon Sep 17 00:00:00 2001 From: Doug Andrade Date: Tue, 13 Jun 2023 23:28:25 -0400 Subject: [PATCH 01/25] improved loading state for /document/id --- .../(dashboard)/documents/[id]/loading.tsx | 28 +++++++++++++++++++ .../app/(dashboard)/documents/[id]/page.tsx | 2 +- .../(dashboard)/pdf-viewer/pdf-viewer.tsx | 6 ++-- .../src/components/forms/edit-document.tsx | 2 +- 4 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/app/(dashboard)/documents/[id]/loading.tsx diff --git a/apps/web/src/app/(dashboard)/documents/[id]/loading.tsx b/apps/web/src/app/(dashboard)/documents/[id]/loading.tsx new file mode 100644 index 000000000..e68b149dc --- /dev/null +++ b/apps/web/src/app/(dashboard)/documents/[id]/loading.tsx @@ -0,0 +1,28 @@ +import Link from 'next/link'; + +import { ChevronLeft, Loader } from 'lucide-react'; + +export default function Loading() { + return ( +
+ + + Documents + +

+ Loading Document... +

+
+
+
+ + +

Loading document...

+
+
+ +
+
+
+ ); +} diff --git a/apps/web/src/app/(dashboard)/documents/[id]/page.tsx b/apps/web/src/app/(dashboard)/documents/[id]/page.tsx index e84d463e4..a83e2949a 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/page.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/page.tsx @@ -38,7 +38,7 @@ export default async function DocumentPage({ params }: DocumentPageProps) {
- Dashboard + Documents

onDocumentLoaded(d)} externalLinkTarget="_blank" loading={ -
- +
+ -

Loading document...

+

Loading document...

} > diff --git a/apps/web/src/components/forms/edit-document.tsx b/apps/web/src/components/forms/edit-document.tsx index 2529f01de..25dda7695 100644 --- a/apps/web/src/components/forms/edit-document.tsx +++ b/apps/web/src/components/forms/edit-document.tsx @@ -22,7 +22,7 @@ const PDFViewer = dynamic(async () => import('~/components/(dashboard)/pdf-viewe ssr: false, loading: () => (
- +

Loading document...

From 3aea62e8980803cc1d4c60a3a1abcbff31c9a627 Mon Sep 17 00:00:00 2001 From: Mythie Date: Wed, 21 Jun 2023 23:48:22 +1000 Subject: [PATCH 02/25] fix: styling and semantic updates --- .../document-dropzone/document-dropzone.tsx | 2 +- .../(dashboard)/pdf-viewer/pdf-viewer.tsx | 12 ++---------- .../components/(dashboard)/pdf-viewer/types.ts | 2 ++ apps/web/src/components/(marketing)/callout.tsx | 16 ++++++++-------- apps/web/src/components/(marketing)/hero.tsx | 15 +++++++++++---- .../components/(marketing)/pricing-table.tsx | 17 +++++++++-------- 6 files changed, 33 insertions(+), 31 deletions(-) create mode 100644 apps/web/src/components/(dashboard)/pdf-viewer/types.ts diff --git a/apps/web/src/components/(dashboard)/document-dropzone/document-dropzone.tsx b/apps/web/src/components/(dashboard)/document-dropzone/document-dropzone.tsx index c3c53d32e..3688bdfca 100644 --- a/apps/web/src/components/(dashboard)/document-dropzone/document-dropzone.tsx +++ b/apps/web/src/components/(dashboard)/document-dropzone/document-dropzone.tsx @@ -110,7 +110,7 @@ export const DocumentDropzone = ({ className, onDrop, ...props }: DocumentDropzo )} gradient={true} degrees={120} - lightMode={theme === 'light'} + {...getRootProps()} {...props} > diff --git a/apps/web/src/components/(dashboard)/pdf-viewer/pdf-viewer.tsx b/apps/web/src/components/(dashboard)/pdf-viewer/pdf-viewer.tsx index 998a54f1a..77284a02b 100644 --- a/apps/web/src/components/(dashboard)/pdf-viewer/pdf-viewer.tsx +++ b/apps/web/src/components/(dashboard)/pdf-viewer/pdf-viewer.tsx @@ -67,16 +67,6 @@ export const PDFViewer = ({ className, document, onPageClick, ...props }: PDFVie const pageX = event.clientX - left; const pageY = event.clientY - top; - console.log({ - pageNumber, - numPages, - originalEvent: event, - pageHeight: height, - pageWidth: width, - pageX, - pageY, - }); - if (onPageClick) { onPageClick({ pageNumber, @@ -137,6 +127,8 @@ export const PDFViewer = ({ className, document, onPageClick, ...props }: PDFVie onDocumentPageClick(e, i + 1)} />
diff --git a/apps/web/src/components/(dashboard)/pdf-viewer/types.ts b/apps/web/src/components/(dashboard)/pdf-viewer/types.ts new file mode 100644 index 000000000..54c9c5d5a --- /dev/null +++ b/apps/web/src/components/(dashboard)/pdf-viewer/types.ts @@ -0,0 +1,2 @@ +export const PDF_VIEWER_CONTAINER_SELECTOR = '.react-pdf__Document'; +export const PDF_VIEWER_PAGE_SELECTOR = '.react-pdf__Page'; diff --git a/apps/web/src/components/(marketing)/callout.tsx b/apps/web/src/components/(marketing)/callout.tsx index 30a1abdbf..0c2613ec8 100644 --- a/apps/web/src/components/(marketing)/callout.tsx +++ b/apps/web/src/components/(marketing)/callout.tsx @@ -41,16 +41,16 @@ export const Callout = () => { - event('view-github')} - > - - + +

); }; diff --git a/apps/web/src/components/(marketing)/hero.tsx b/apps/web/src/components/(marketing)/hero.tsx index b406b51cc..7f7aa6d05 100644 --- a/apps/web/src/components/(marketing)/hero.tsx +++ b/apps/web/src/components/(marketing)/hero.tsx @@ -114,12 +114,19 @@ export const Hero = ({ className, ...props }: HeroProps) => { - event('view-github')}> - - + + { For small teams and individuals who need a simple solution

- event('view-github')} - > - - +

Host your own instance

From eea09dcfacbbb722f5eb60e56c8c20cc073cba47 Mon Sep 17 00:00:00 2001 From: Mythie Date: Wed, 21 Jun 2023 23:49:23 +1000 Subject: [PATCH 03/25] feat: persist fields and recipients for document editing --- apps/web/package.json | 2 + .../app/(dashboard)/documents/[id]/page.tsx | 21 +- .../components/form/form-error-message.tsx | 3 +- .../src/components/forms/edit-document.tsx | 173 +++++++-- .../forms/edit-document/add-fields.tsx | 332 ++++++++++++++++-- .../forms/edit-document/add-signers.tsx | 83 +++-- .../forms/edit-document/add-subject.tsx | 111 ++++++ .../forms/edit-document/field-item.tsx | 149 ++++++++ .../forms/edit-document/provider.tsx | 53 +++ .../components/forms/edit-document/types.ts | 44 ++- package-lock.json | 146 ++++++++ packages/lib/next-auth/auth-options.ts | 1 - packages/lib/package.json | 1 + .../field/get-fields-for-document.ts | 19 + .../field/set-fields-for-document.ts | 127 +++++++ .../recipient/get-recipients-for-document.ts | 22 ++ .../recipient/set-recipients-for-document.ts | 103 ++++++ .../migration.sql | 2 + .../migration.sql | 2 + .../migration.sql | 3 + .../migration.sql | 8 + .../migration.sql | 9 + packages/prisma/schema.prisma | 11 +- packages/trpc/package.json | 2 + .../trpc/server/document-router/router.ts | 55 +++ .../trpc/server/document-router/schema.ts | 38 ++ packages/trpc/server/router.ts | 2 + packages/ui/primitives/card.tsx | 23 +- 28 files changed, 1432 insertions(+), 113 deletions(-) create mode 100644 apps/web/src/components/forms/edit-document/add-subject.tsx create mode 100644 apps/web/src/components/forms/edit-document/field-item.tsx create mode 100644 apps/web/src/components/forms/edit-document/provider.tsx create mode 100644 packages/lib/server-only/field/get-fields-for-document.ts create mode 100644 packages/lib/server-only/field/set-fields-for-document.ts create mode 100644 packages/lib/server-only/recipient/get-recipients-for-document.ts create mode 100644 packages/lib/server-only/recipient/set-recipients-for-document.ts create mode 100644 packages/prisma/migrations/20230617040606_add_name_field/migration.sql create mode 100644 packages/prisma/migrations/20230617041623_add_email_field/migration.sql create mode 100644 packages/prisma/migrations/20230621130930_add_width_and_height_for_fields/migration.sql create mode 100644 packages/prisma/migrations/20230621131348_add_document_id_and_email_index/migration.sql create mode 100644 packages/prisma/migrations/20230621133446_migrate_field_position_to_float/migration.sql create mode 100644 packages/trpc/server/document-router/router.ts create mode 100644 packages/trpc/server/document-router/schema.ts diff --git a/apps/web/package.json b/apps/web/package.json index c7db16aa6..d493b92d9 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -21,6 +21,7 @@ "framer-motion": "^10.12.8", "lucide-react": "^0.214.0", "micro": "^10.0.1", + "nanoid": "^4.0.2", "next": "13.4.1", "next-auth": "^4.22.1", "next-plausible": "^3.7.2", @@ -32,6 +33,7 @@ "react-hook-form": "^7.43.9", "react-icons": "^4.8.0", "react-pdf": "^7.1.1", + "react-rnd": "^10.4.1", "typescript": "5.0.4", "zod": "^3.21.4" }, diff --git a/apps/web/src/app/(dashboard)/documents/[id]/page.tsx b/apps/web/src/app/(dashboard)/documents/[id]/page.tsx index a83e2949a..7f3b76909 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/page.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/page.tsx @@ -5,6 +5,8 @@ import { ChevronLeft } from 'lucide-react'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; +import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-fields-for-document'; +import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document'; import { EditDocumentForm } from '~/components/forms/edit-document'; @@ -34,6 +36,17 @@ export default async function DocumentPage({ params }: DocumentPageProps) { redirect('/documents'); } + const [recipients, fields] = await Promise.all([ + await getRecipientsForDocument({ + documentId, + userId: session.id, + }), + await getFieldsForDocument({ + documentId, + userId: session.id, + }), + ]); + return (
@@ -48,7 +61,13 @@ export default async function DocumentPage({ params }: DocumentPageProps) { {document.title} - +
); } diff --git a/apps/web/src/components/form/form-error-message.tsx b/apps/web/src/components/form/form-error-message.tsx index 059a2eb83..6fa7c32b0 100644 --- a/apps/web/src/components/form/form-error-message.tsx +++ b/apps/web/src/components/form/form-error-message.tsx @@ -1,11 +1,10 @@ import { AnimatePresence, motion } from 'framer-motion'; -import { FieldError } from 'react-hook-form'; import { cn } from '@documenso/ui/lib/utils'; export type FormErrorMessageProps = { className?: string; - error: FieldError | undefined; + error: { message?: string } | undefined; }; export const FormErrorMessage = ({ error, className }: FormErrorMessageProps) => { diff --git a/apps/web/src/components/forms/edit-document.tsx b/apps/web/src/components/forms/edit-document.tsx index 25dda7695..482c63fae 100644 --- a/apps/web/src/components/forms/edit-document.tsx +++ b/apps/web/src/components/forms/edit-document.tsx @@ -1,21 +1,22 @@ 'use client'; -import { useState } from 'react'; +import { useId, useState } from 'react'; import dynamic from 'next/dynamic'; import { zodResolver } from '@hookform/resolvers/zod'; import { Loader } from 'lucide-react'; -import { useTheme } from 'next-themes'; import { useForm } from 'react-hook-form'; -import { Document, User } from '@documenso/prisma/client'; +import { Document, Field, Recipient, User } from '@documenso/prisma/client'; +import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; import { Card, CardContent } from '@documenso/ui/primitives/card'; import { AddFieldsFormPartial } from './edit-document/add-fields'; import { AddSignersFormPartial } from './edit-document/add-signers'; +import { AddSubjectFormPartial } from './edit-document/add-subject'; import { TEditDocumentFormSchema, ZEditDocumentFormSchema } from './edit-document/types'; const PDFViewer = dynamic(async () => import('~/components/(dashboard)/pdf-viewer/pdf-viewer'), { @@ -35,57 +36,143 @@ export type EditDocumentFormProps = { className?: string; user: User; document: Document; + recipients: Recipient[]; + fields: Field[]; }; -export const EditDocumentForm = ({ className, document, user: _user }: EditDocumentFormProps) => { - const documentUrl = `data:application/pdf;base64,${document.document}`; +export const EditDocumentForm = ({ + className, + document, + recipients, + fields, + user: _user, +}: EditDocumentFormProps) => { + const initialId = useId(); const [step, setStep] = useState(0); + const [nextStepLoading, setNextStepLoading] = useState(false); + + const documentUrl = `data:application/pdf;base64,${document.document}`; + const defaultSigners = + recipients.length > 0 + ? recipients.map((recipient) => ({ + nativeId: recipient.id, + formId: `${recipient.id}-${recipient.documentId}`, + name: recipient.name, + email: recipient.email, + })) + : [ + { + formId: initialId, + name: '', + email: '', + }, + ]; + + const defaultFields = fields.map((field) => ({ + nativeId: field.id, + formId: `${field.id}-${field.documentId}`, + pageNumber: field.page, + type: field.type, + pageX: Number(field.positionX), + pageY: Number(field.positionY), + pageWidth: Number(field.width), + pageHeight: Number(field.height), + signerEmail: recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '', + })); + + const { mutateAsync: setRecipientsForDocument } = + trpc.document.setRecipientsForDocument.useMutation(); + + const { mutateAsync: setFieldsForDocument } = trpc.document.setFieldsForDocument.useMutation(); const { control, - // handleSubmit, + handleSubmit, watch, - formState: { errors, isSubmitting, isValid }, + trigger, + formState: { errors, isSubmitting }, } = useForm({ mode: 'onBlur', defaultValues: { - signers: [ - { - name: '', - email: '', - }, - ], + signers: defaultSigners, + fields: defaultFields, + email: { + subject: '', + message: '', + }, }, resolver: zodResolver(ZEditDocumentFormSchema), }); - const { theme } = useTheme(); + const signersFormValue = watch('signers'); + const fieldsFormValue = watch('fields'); + + console.log({ state: watch(), errors }); const canGoBack = step > 0; - const canGoNext = isValid && step < MAX_STEP; + const canGoNext = step < MAX_STEP; const onGoBackClick = () => setStep((prev) => Math.max(0, prev - 1)); - const onGoNextClick = () => setStep((prev) => Math.min(MAX_STEP, prev + 1)); + const onGoNextClick = async () => { + setNextStepLoading(true); + + const passes = await trigger(); + + if (step === 0) { + await setRecipientsForDocument({ + documentId: document.id, + recipients: signersFormValue.map((signer) => ({ + id: signer.nativeId ?? undefined, + name: signer.name, + email: signer.email, + })), + }).catch((err: unknown) => console.error(err)); + } + + if (step === 1) { + await setFieldsForDocument({ + documentId: document.id, + fields: fieldsFormValue.map((field) => ({ + id: field.nativeId ?? undefined, + type: field.type, + signerEmail: field.signerEmail, + pageNumber: field.pageNumber, + pageX: field.pageX, + pageY: field.pageY, + pageWidth: field.pageWidth, + pageHeight: field.pageHeight, + })), + }).catch((err: unknown) => console.error(err)); + } + + if (passes) { + setStep((prev) => Math.min(MAX_STEP, prev + 1)); + } + + console.log({ passes }); + + setNextStepLoading(false); + }; return (
- + -
-
+
+
{step === 0 && ( @@ -98,7 +185,16 @@ export const EditDocumentForm = ({ className, document, user: _user }: EditDocum watch={watch} errors={errors} isSubmitting={isSubmitting} - theme={theme || 'dark'} + /> + )} + + {step === 2 && ( + )} @@ -118,6 +214,7 @@ export const EditDocumentForm = ({ className, document, user: _user }: EditDocum
+ {step < MAX_STEP && ( + + )} + + {step === MAX_STEP && ( + + )}
-
+
); diff --git a/apps/web/src/components/forms/edit-document/add-fields.tsx b/apps/web/src/components/forms/edit-document/add-fields.tsx index edad9de68..cf48b1f1d 100644 --- a/apps/web/src/components/forms/edit-document/add-fields.tsx +++ b/apps/web/src/components/forms/edit-document/add-fields.tsx @@ -1,12 +1,14 @@ 'use client'; -import { useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { Caveat } from 'next/font/google'; import { Check, ChevronsUpDown } from 'lucide-react'; -import { Control, FieldErrors, UseFormWatch } from 'react-hook-form'; +import { nanoid } from 'nanoid'; +import { Control, FieldErrors, UseFormWatch, useFieldArray } from 'react-hook-form'; +import { FieldType } from '@documenso/prisma/client'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; import { Card, CardContent } from '@documenso/ui/primitives/card'; @@ -19,7 +21,10 @@ import { } from '@documenso/ui/primitives/command'; import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; -import { TEditDocumentFormSchema } from './types'; +import { PDF_VIEWER_PAGE_SELECTOR } from '~/components/(dashboard)/pdf-viewer/types'; + +import { FieldItem } from './field-item'; +import { FRIENDLY_FIELD_TYPE, TEditDocumentFormSchema } from './types'; const fontCaveat = Caveat({ weight: ['500'], @@ -28,30 +33,285 @@ const fontCaveat = Caveat({ variable: '--font-caveat', }); +const DEFAULT_HEIGHT_PERCENT = 5; +const DEFAULT_WIDTH_PERCENT = 15; + +const MIN_HEIGHT_PX = 60; +const MIN_WIDTH_PX = 200; + export type AddFieldsFormProps = { className?: string; control: Control; watch: UseFormWatch; errors: FieldErrors; isSubmitting: boolean; - theme: string; }; export const AddFieldsFormPartial = ({ className, - control: _control, + control: control, watch, errors: _errors, isSubmitting: _isSubmitting, - theme, }: AddFieldsFormProps) => { const signers = watch('signers'); + const fields = watch('fields'); + + const { append, remove, update } = useFieldArray({ + control, + name: 'fields', + }); const [selectedSigner, setSelectedSigner] = useState(() => signers[0]); + const [selectedField, setSelectedField] = useState(null); + + const [visible, setVisible] = useState(false); + const [coords, setCoords] = useState({ + x: 0, + y: 0, + }); + + const fieldBounds = useRef({ + height: 0, + width: 0, + }); + + const isWithinPageBounds = useCallback((event: MouseEvent) => { + if (!(event.target instanceof HTMLElement)) { + return false; + } + + const target = event.target; + const $page = + target.closest(PDF_VIEWER_PAGE_SELECTOR) ?? target.querySelector(PDF_VIEWER_PAGE_SELECTOR); + + if (!$page) { + return false; + } + + const { top, left, height, width } = $page.getBoundingClientRect(); + + if (event.clientY > top + height || event.clientY < top) { + return false; + } + + if (event.clientX > left + width || event.clientX < left) { + return false; + } + + return true; + }, []); + + const onMouseMove = useCallback( + (event: MouseEvent) => { + if (!isWithinPageBounds(event)) { + setVisible(false); + return; + } + + setVisible(true); + setCoords({ + x: event.clientX - fieldBounds.current.width / 2, + y: event.clientY - fieldBounds.current.height / 2, + }); + }, + [isWithinPageBounds], + ); + + const onMouseClick = useCallback( + (event: MouseEvent) => { + if (!selectedField) { + return; + } + + if (!(event.target instanceof HTMLElement)) { + return; + } + + const target = event.target; + + const $page = + target.closest(PDF_VIEWER_PAGE_SELECTOR) ?? + target.querySelector(PDF_VIEWER_PAGE_SELECTOR); + + if (!$page || !isWithinPageBounds(event)) { + return; + } + + const { height, width } = $page.getBoundingClientRect(); + + const top = $page.offsetTop; + const left = $page.offsetLeft; + + const pageNumber = parseInt($page.getAttribute('data-page-number') ?? '1', 10); + + // Calculate x and y as a percentage of the page width and height + let pageX = ((event.pageX - left) / width) * 100; + let pageY = ((event.pageY - top) / height) * 100; + + // Get the bounds as a percentage of the page width and height + const fieldPageWidth = (fieldBounds.current.width / width) * 100; + const fieldPageHeight = (fieldBounds.current.height / height) * 100; + + // And center it based on the bounds + pageX -= fieldPageWidth / 2; + pageY -= fieldPageHeight / 2; + + append({ + formId: nanoid(12), + type: selectedField, + pageNumber, + pageX, + pageY, + pageWidth: fieldPageWidth, + pageHeight: fieldPageHeight, + signerEmail: selectedSigner.email, + }); + + setVisible(false); + setSelectedField(null); + }, + [append, isWithinPageBounds, selectedField, selectedSigner.email], + ); + + const onFieldResize = useCallback( + (node: HTMLElement, index: number) => { + const field = fields[index]; + + const $page = document.querySelector( + `${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`, + ); + + if (!$page) { + return; + } + + const { height: pageHeight, width: pageWidth } = $page.getBoundingClientRect(); + + const pageTop = $page.offsetTop; + const pageLeft = $page.offsetLeft; + + let { top: nodeTop, left: nodeLeft } = node.getBoundingClientRect(); + const { height, width } = node.getBoundingClientRect(); + + nodeTop += window.scrollY; + nodeLeft += window.scrollX; + + // Calculate width and height as a percentage of the page width and height + const newWidth = (width / pageWidth) * 100; + const newHeight = (height / pageHeight) * 100; + + // Calculate the new position as a percentage of the page width and height + const newX = ((nodeLeft - pageLeft) / pageWidth) * 100; + const newY = ((nodeTop - pageTop) / pageHeight) * 100; + + update(index, { + ...field, + pageX: newX, + pageY: newY, + pageWidth: newWidth, + pageHeight: newHeight, + }); + }, + [fields, update], + ); + + const onFieldMove = useCallback( + (node: HTMLElement, index: number) => { + const field = fields[index]; + + const $page = document.querySelector( + `${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`, + ); + + if (!$page || !($page instanceof HTMLElement)) { + return; + } + + const { height: pageHeight, width: pageWidth } = $page.getBoundingClientRect(); + + const pageTop = $page.offsetTop; + const pageLeft = $page.offsetLeft; + + let { top: nodeTop, left: nodeLeft } = node.getBoundingClientRect(); + + nodeTop += window.scrollY; + nodeLeft += window.scrollX; + + // Calculate the new position as a percentage of the page width and height + const newX = ((nodeLeft - pageLeft) / pageWidth) * 100; + const newY = ((nodeTop - pageTop) / pageHeight) * 100; + + update(index, { + ...field, + pageX: newX, + pageY: newY, + }); + }, + [fields, update], + ); + + useEffect(() => { + if (selectedField) { + window.addEventListener('mousemove', onMouseMove); + window.addEventListener('click', onMouseClick); + } + + return () => { + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('click', onMouseClick); + }; + }, [onMouseClick, onMouseMove, selectedField]); + + useEffect(() => { + const $page = document.querySelector(PDF_VIEWER_PAGE_SELECTOR); + + if (!$page) { + return; + } + + const { height, width } = $page.getBoundingClientRect(); + + fieldBounds.current = { + height: Math.max(height * (DEFAULT_HEIGHT_PERCENT / 100), MIN_HEIGHT_PX), + width: Math.max(width * (DEFAULT_WIDTH_PERCENT / 100), MIN_WIDTH_PX), + }; + }, []); + return (
-

Edit Document

+ {selectedField && visible && ( + + + {FRIENDLY_FIELD_TYPE[selectedField]} + + + )} + + {fields.map((field, index) => ( + onFieldResize(options, index)} + onMove={(options) => onFieldMove(options, index)} + onRemove={() => remove(index)} + /> + ))} + +

Add Fields

Add all relevant fields for each recipient. @@ -62,6 +322,7 @@ export const AddFieldsFormPartial = ({ - - - @@ -106,17 +150,10 @@ export const AddSignersFormPartial = ({

+ +
- diff --git a/apps/web/src/components/forms/edit-document/add-subject.tsx b/apps/web/src/components/forms/edit-document/add-subject.tsx new file mode 100644 index 000000000..2aedb0127 --- /dev/null +++ b/apps/web/src/components/forms/edit-document/add-subject.tsx @@ -0,0 +1,111 @@ +'use client'; + +import { Control, Controller, FieldErrors, UseFormWatch } from 'react-hook-form'; + +import { cn } from '@documenso/ui/lib/utils'; +import { Input } from '@documenso/ui/primitives/input'; +import { Label } from '@documenso/ui/primitives/label'; +import { Textarea } from '@documenso/ui/primitives/textarea'; + +import { FormErrorMessage } from '~/components/form/form-error-message'; + +import { TEditDocumentFormSchema } from './types'; + +export type AddSubjectFormProps = { + className?: string; + control: Control; + watch: UseFormWatch; + errors: FieldErrors; + isSubmitting: boolean; +}; + +export const AddSubjectFormPartial = ({ + className, + control, + errors, + isSubmitting, +}: AddSubjectFormProps) => { + return ( +
+

Add Subject

+ +

+ Add the subject and message you wish to send to signers. +

+ +
+ +
+
+ + + ( + + )} + /> + + +
+ +
+ + + ( +