diff --git a/apps/web/components/editor/recipient-selector.tsx b/apps/web/components/editor/recipient-selector.tsx index d38bb2d1b..b5a0a494b 100644 --- a/apps/web/components/editor/recipient-selector.tsx +++ b/apps/web/components/editor/recipient-selector.tsx @@ -66,7 +66,7 @@ export default function RecipientSelector(props: any) { selected ? "font-semibold" : "font-normal", "ml-3 block truncate" )}> - {`${selectedRecipient?.name} <${selectedRecipient?.email}>`} + {`${recipient?.name} <${recipient?.email}>`} diff --git a/apps/web/pages/api/documents/[id]/sign.ts b/apps/web/pages/api/documents/[id]/sign.ts index ef68e1a78..537d7ee72 100644 --- a/apps/web/pages/api/documents/[id]/sign.ts +++ b/apps/web/pages/api/documents/[id]/sign.ts @@ -73,6 +73,13 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { }, }); + const signedRecipients = await prisma.recipient.findMany({ + where: { + documentId: recipient.documentId, + signingStatus: SigningStatus.SIGNED, + }, + }); + // Don't check for inserted, because currently no "sign again" scenarios exist and // this is probably the expected behaviour in unclean states. const nonSignatureFields = await prisma.field.findMany({ @@ -126,7 +133,11 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { }); document.document = documentWithInserts; - if (documentOwner) await sendSigningDoneMail(recipient, document, documentOwner); + if (documentOwner) await sendSigningDoneMail(document, documentOwner); + + for (const signer of signedRecipients) { + await sendSigningDoneMail(document, signer); + } } return res.status(200).end(); diff --git a/apps/web/pages/documents.tsx b/apps/web/pages/documents.tsx index b1ef1fce7..eaad896a5 100644 --- a/apps/web/pages/documents.tsx +++ b/apps/web/pages/documents.tsx @@ -291,6 +291,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => { event.stopPropagation(); router.push("/documents/" + document.id); }} + disabled={document.status === "COMPLETED"} /> { }, { title: props.document.title, - href: NEXT_PUBLIC_WEBAPP_URL + "/documents/" + props.document.id, + href: + props.document.status !== DocumentStatus.COMPLETED + ? NEXT_PUBLIC_WEBAPP_URL + "/documents/" + props.document.id + : NEXT_PUBLIC_WEBAPP_URL + "/documents/" + props.document.id + "/recipients", }, { title: "Recipients", @@ -88,37 +91,45 @@ const RecipientsPage: NextPageWithLayout = (props: any) => { href={"/api/documents/" + props.document.id}> Download - - + {props.document.status !== DocumentStatus.COMPLETED && ( + <> + + + + )}

Signers

- The people who will sign the document. + {props.document.status !== DocumentStatus.COMPLETED + ? "The people who will sign the document." + : "The people who signed the document."}

@@ -215,9 +226,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => { className="mt-3 inline-block flex-shrink-0 rounded-full bg-yellow-200 px-2 py-0.5 text-xs font-medium text-gray-800"> Not Sent - ) : ( - "" - )} + ) : null} {item.sendStatus === "SENT" && item.readStatus !== "OPENED" ? ( { Sent - ) : ( - "" - )} + ) : null} {item.readStatus === "OPENED" && item.signingStatus === "NOT_SIGNED" ? ( { Seen - ) : ( - "" - )} + ) : null} {item.signingStatus === "SIGNED" ? ( + className="mt-3 inline-block flex-shrink-0 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800"> Signed - ) : ( - "" - )} + ) : null}
-
- { - if (confirm("Resend this signing request?")) { - setLoading(true); - sendSigningRequests(props.document, [item.id]).finally(() => { - setLoading(false); - }); + {props.document.status !== DocumentStatus.COMPLETED && ( +
+ - Resend - - { - const removedItem = { ...fields }[index]; - remove(index); - deleteRecipient(item)?.catch((err) => { - append(removedItem); - }); - }} - className="group-hover:text-neon-dark group-hover:disabled:text-gray-400" - /> -
+ color="secondary" + className="my-auto mr-4 h-9" + onClick={() => { + if (confirm("Resend this signing request?")) { + setLoading(true); + sendSigningRequests(props.document, [item.id]).finally(() => { + setLoading(false); + }); + } + }}> + Resend +
+ { + const removedItem = { ...fields }[index]; + remove(index); + deleteRecipient(item)?.catch((err) => { + append(removedItem); + }); + }} + className="group-hover:text-neon-dark group-hover:disabled:text-gray-400" + /> +
+ )} ))} - + {props.document.status !== "COMPLETED" && ( + + )} diff --git a/packages/lib/mail/sendSigningDoneMail.ts b/packages/lib/mail/sendSigningDoneMail.ts index 67c73868e..c7b35bf93 100644 --- a/packages/lib/mail/sendSigningDoneMail.ts +++ b/packages/lib/mail/sendSigningDoneMail.ts @@ -3,7 +3,7 @@ import { addDigitalSignature } from "@documenso/signing/addDigitalSignature"; import { sendMail } from "./sendMail"; import { Document as PrismaDocument } from "@prisma/client"; -export const sendSigningDoneMail = async (recipient: any, document: PrismaDocument, user: any) => { +export const sendSigningDoneMail = async (document: PrismaDocument, user: any) => { await sendMail( user.email, `Completed: "${document.title}"`, diff --git a/packages/pdf/insertTextInPDF.ts b/packages/pdf/insertTextInPDF.ts index 83487e94b..b24a920c5 100644 --- a/packages/pdf/insertTextInPDF.ts +++ b/packages/pdf/insertTextInPDF.ts @@ -12,27 +12,36 @@ export async function insertTextInPDF( ): Promise { const fontBytes = fs.readFileSync("public/fonts/Qwigley-Regular.ttf"); - const existingPdfBytes = pdfAsBase64; + const pdfDoc = await PDFDocument.load(pdfAsBase64); - const pdfDoc = await PDFDocument.load(existingPdfBytes); pdfDoc.registerFontkit(fontkit); - const customFont = await pdfDoc.embedFont(fontBytes); - const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica); + + const font = await pdfDoc.embedFont(useHandwritingFont ? fontBytes : StandardFonts.Helvetica); const pages = pdfDoc.getPages(); const pdfPage = pages[page]; + const textSize = useHandwritingFont ? 50 : 15; - const textWidth = customFont.widthOfTextAtSize(text, textSize); - const textHeight = customFont.heightAtSize(textSize); + const textWidth = font.widthOfTextAtSize(text, textSize); + const textHeight = font.heightAtSize(textSize); const fieldSize = { width: 192, height: 64 }; - const invertedYPosition = pdfPage.getHeight() - positionY - fieldSize.height; + + // Because pdf-lib use a bottom-left coordinate system, we need to invert the y position + // we then center the text in the middle by adding half the height of the text + // plus the height of the field and divide the result by 2 + const invertedYPosition = + pdfPage.getHeight() - positionY - (fieldSize.height + textHeight / 2) / 2; + + // We center the text by adding the width of the field, subtracting the width of the text + // and dividing the result by 2 + const centeredXPosition = positionX + (fieldSize.width - textWidth) / 2; pdfPage.drawText(text, { - x: positionX, + x: centeredXPosition, y: invertedYPosition, size: textSize, - font: useHandwritingFont ? customFont : helveticaFont, color: rgb(0, 0, 0), + font, }); const pdfAsUint8Array = await pdfDoc.save(); diff --git a/packages/prisma/seed.ts b/packages/prisma/seed.ts index 42812331f..d686e02ac 100644 --- a/packages/prisma/seed.ts +++ b/packages/prisma/seed.ts @@ -24,7 +24,7 @@ async function createUser(userData: { email: string; password: string }) { async function main() { console.info("Start seeding..."); const password = "123456789"; - const email = "example6@documenso.com"; + const email = "example@documenso.com"; const user = await createUser({ email: email, password: await hashPassword(password),