@@ -114,6 +116,15 @@ export default function PDFSigner(props: any) {
fields={fields}
pdfUrl={`${NEXT_PUBLIC_WEBAPP_URL}/api/documents/${router.query.id}?token=${router.query.token}`}
onClick={onClick}
+ onMouseDown={function onMouseDown(e: any, page: number) {
+ if (
+ fields.filter((field) => field.type === FieldType.SIGNATURE)
+ .length === 0
+ )
+ createFieldForFreeSignature(e, page, props.recipient);
+ }}
+ onMouseUp={() => {}}
+ onDelete={onDeleteHandler}
>
>
);
@@ -127,4 +138,120 @@ export default function PDFSigner(props: any) {
return signatures.length > 0;
}
}
+
+ function createFieldForFreeSignature(
+ e: any,
+ page: number,
+ recipient: any
+ ): any {
+ var rect = e.target.getBoundingClientRect();
+ var newFieldX = e.clientX - rect.left; //x position within the element.
+ var newFieldY = e.clientY - rect.top; //y position within the element.
+ const signatureField = {
+ id: -1,
+ page: page,
+ type: FieldType.FREE_SIGNATURE,
+ positionX: newFieldX.toFixed(0),
+ positionY: newFieldY.toFixed(0),
+ Recipient: recipient,
+ };
+
+ upsertField(props.document, signatureField).then((res) => {
+ setFields(fields.concat(res));
+ setDialogField(res);
+ setOpen(true);
+ });
+
+ return signatureField;
+ }
+
+ function onDeleteHandler(id: any) {
+ const field = fields.find((e) => e.id == id);
+ const fieldIndex = fields.map((item) => item.id).indexOf(id);
+ if (fieldIndex > -1) {
+ const fieldWithoutRemoved = [...fields];
+ const removedField = fieldWithoutRemoved.splice(fieldIndex, 1);
+ setFields(fieldWithoutRemoved);
+
+ const signaturesWithoutRemoved = [...signatures];
+ const removedSignature = signaturesWithoutRemoved.splice(
+ signaturesWithoutRemoved.findIndex(function (i) {
+ return i.fieldId === id;
+ }),
+ 1
+ );
+
+ setSignatures(signaturesWithoutRemoved);
+ deleteField(field).catch((err) => {
+ setFields(fieldWithoutRemoved.concat(removedField));
+ setSignatures(signaturesWithoutRemoved.concat(removedSignature));
+ });
+ }
+ }
+
+ async function deleteField(field: any) {
+ if (!field.id) {
+ return;
+ }
+
+ try {
+ const deleted = toast.promise(
+ fetch("/api/documents/" + 0 + "/fields/" + field.id, {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(field),
+ }).then((res) => {
+ if (!res.ok) {
+ throw new Error(res.status.toString());
+ }
+ return res;
+ }),
+ {
+ loading: "Deleting...",
+ success: "Deleted.",
+ error: "Could not delete :/",
+ },
+ {
+ id: "delete",
+ style: {
+ minWidth: "200px",
+ },
+ }
+ );
+ return deleted;
+ } catch (error) {}
+ }
+
+ async function upsertField(document: any, field: any): Promise
{
+ try {
+ const created = await toast.promise(
+ fetch("/api/documents/" + document.id + "/fields", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(field),
+ }).then((res) => {
+ if (!res.ok) {
+ throw new Error(res.status.toString());
+ }
+ return res.json();
+ }),
+ {
+ loading: "Saving...",
+ success: "Saved.",
+ error: "Could not save :/",
+ },
+ {
+ id: "saving field",
+ style: {
+ minWidth: "200px",
+ },
+ }
+ );
+ return created;
+ } catch (error) {}
+ }
}
diff --git a/apps/web/components/editor/pdf-viewer.jsx b/apps/web/components/editor/pdf-viewer.jsx
index e4d338b37..2d0808e43 100644
--- a/apps/web/components/editor/pdf-viewer.jsx
+++ b/apps/web/components/editor/pdf-viewer.jsx
@@ -78,6 +78,7 @@ export default function PDFViewer(props) {
key={item.id}
field={item}
className="absolute"
+ onDelete={onDeleteHandler}
>
) : (
{
+ e.preventDefault();
+ e.stopPropagation();
+ }}
>
{
@@ -75,6 +80,8 @@ export default function ReadOnlyField(props: FieldPropsType) {
const newField = { ...field };
newField.signature = null;
setField(newField);
+ // remove not only signature but whole field if it is a freely places signature
+ if (field.type === "FREE_SIGNATURE") props.onDelete(field.id);
}}
/>
diff --git a/apps/web/pages/documents/[id]/sign.tsx b/apps/web/pages/documents/[id]/sign.tsx
index 0a62e1f7c..e38b99e22 100644
--- a/apps/web/pages/documents/[id]/sign.tsx
+++ b/apps/web/pages/documents/[id]/sign.tsx
@@ -3,10 +3,9 @@ import Head from "next/head";
import { NextPageWithLayout } from "../../_app";
import { ReadStatus } from "@prisma/client";
import PDFSigner from "../../../components/editor/pdf-signer";
-import Logo from "../../../components/logo";
import Link from "next/link";
-import { Button } from "@documenso/ui";
-import { CheckBadgeIcon, ClockIcon } from "@heroicons/react/24/outline";
+import { ClockIcon } from "@heroicons/react/24/outline";
+import { FieldType, DocumentStatus } from "@prisma/client";
const SignPage: NextPageWithLayout = (props: any) => {
return (
@@ -15,7 +14,11 @@ const SignPage: NextPageWithLayout = (props: any) => {
Sign | Documenso
{!props.expired ? (
-
+
) : (
<>
@@ -84,19 +87,8 @@ export async function getServerSideProps(context: any) {
},
});
- const unsignedFields = await prisma.field.findMany({
- where: {
- documentId: recipient.Document.id,
- recipientId: recipient.id,
- Signature: { is: null },
- },
- include: {
- Recipient: true,
- Signature: true,
- },
- });
-
- if (unsignedFields.length === 0) {
+ // Document was already signed
+ if (recipient.Document.status === DocumentStatus.COMPLETED) {
return {
redirect: {
permanent: false,
@@ -105,8 +97,31 @@ export async function getServerSideProps(context: any) {
};
}
+ // Clean up unsigned free place fields from UI from previous page visits
+ // todo refactor free sign fields to be client side only
+ await prisma.field.deleteMany({
+ where: {
+ type: { in: [FieldType.FREE_SIGNATURE] },
+ Signature: { is: null },
+ },
+ });
+
+ const unsignedFields = await prisma.field.findMany({
+ where: {
+ documentId: recipient.Document.id,
+ recipientId: recipient.id,
+ type: { in: [FieldType.SIGNATURE] },
+ Signature: { is: null },
+ },
+ include: {
+ Recipient: true,
+ Signature: true,
+ },
+ });
+
return {
props: {
+ recipient: JSON.parse(JSON.stringify(recipient)),
document: JSON.parse(JSON.stringify(recipient.Document)),
fields: JSON.parse(JSON.stringify(unsignedFields)),
expired: recipient.expired
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index 1f450eaeb..085319584 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -102,6 +102,7 @@ model Recipient {
enum FieldType {
SIGNATURE
+ FREE_SIGNATURE
DATE
TEXT
}