Merge branch 'main' into fix-#41-db-migration-Signature_recipientId_fkey

This commit is contained in:
Timur Ercan
2023-04-11 15:34:32 +02:00
committed by GitHub
7 changed files with 128 additions and 100 deletions

View File

@ -66,7 +66,7 @@ export default function RecipientSelector(props: any) {
selected ? "font-semibold" : "font-normal", selected ? "font-semibold" : "font-normal",
"ml-3 block truncate" "ml-3 block truncate"
)}> )}>
{`${selectedRecipient?.name} <${selectedRecipient?.email}>`} {`${recipient?.name} <${recipient?.email}>`}
</span> </span>
</div> </div>

View File

@ -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 // Don't check for inserted, because currently no "sign again" scenarios exist and
// this is probably the expected behaviour in unclean states. // this is probably the expected behaviour in unclean states.
const nonSignatureFields = await prisma.field.findMany({ const nonSignatureFields = await prisma.field.findMany({
@ -126,7 +133,11 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
}); });
document.document = documentWithInserts; 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(); return res.status(200).end();

View File

@ -291,6 +291,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
event.stopPropagation(); event.stopPropagation();
router.push("/documents/" + document.id); router.push("/documents/" + document.id);
}} }}
disabled={document.status === "COMPLETED"}
/> />
<IconButton <IconButton
icon={ArrowDownTrayIcon} icon={ArrowDownTrayIcon}

View File

@ -34,7 +34,10 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
}, },
{ {
title: props.document.title, 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", title: "Recipients",
@ -88,37 +91,45 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
href={"/api/documents/" + props.document.id}> href={"/api/documents/" + props.document.id}>
Download Download
</Button> </Button>
<Button {props.document.status !== DocumentStatus.COMPLETED && (
icon={PencilSquareIcon} <>
disabled={props.document.status === DocumentStatus.COMPLETED} <Button
color={props.document.status === DocumentStatus.COMPLETED ? "primary" : "secondary"} icon={PencilSquareIcon}
className="mr-2" disabled={props.document.status === DocumentStatus.COMPLETED}
href={breadcrumbItems[1].href}> color={
Edit Document props.document.status === DocumentStatus.COMPLETED ? "primary" : "secondary"
</Button> }
<Button className="mr-2"
className="min-w-[125px]" href={breadcrumbItems[1].href}>
color="primary" Edit Document
icon={PaperAirplaneIcon} </Button>
onClick={() => { <Button
setOpen(true); className="min-w-[125px]"
}} color="primary"
disabled={ icon={PaperAirplaneIcon}
(formValues.length || 0) === 0 || onClick={() => {
!formValues.some( setOpen(true);
(r: any) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT" }}
) || disabled={
loading (formValues.length || 0) === 0 ||
}> !formValues.some(
Send (r: any) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
</Button> ) ||
loading
}>
Send
</Button>
</>
)}
</div> </div>
</div> </div>
<div className="mt-10 overflow-hidden rounded-md bg-white p-4 shadow sm:p-6"> <div className="mt-10 overflow-hidden rounded-md bg-white p-4 shadow sm:p-6">
<div className="border-b border-gray-200 pb-3 sm:pb-5"> <div className="border-b border-gray-200 pb-3 sm:pb-5">
<h3 className="text-lg font-medium leading-6 text-gray-900 ">Signers</h3> <h3 className="text-lg font-medium leading-6 text-gray-900 ">Signers</h3>
<p className="mt-2 max-w-4xl text-sm text-gray-500"> <p className="mt-2 max-w-4xl text-sm text-gray-500">
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."}
</p> </p>
</div> </div>
<FormProvider {...form}> <FormProvider {...form}>
@ -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"> 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 Not Sent
</span> </span>
) : ( ) : null}
""
)}
{item.sendStatus === "SENT" && item.readStatus !== "OPENED" ? ( {item.sendStatus === "SENT" && item.readStatus !== "OPENED" ? (
<span id="sent_icon"> <span id="sent_icon">
<span <span
@ -226,9 +235,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
<CheckIcon className="mr-1 inline h-5" /> Sent <CheckIcon className="mr-1 inline h-5" /> Sent
</span> </span>
</span> </span>
) : ( ) : null}
""
)}
{item.readStatus === "OPENED" && item.signingStatus === "NOT_SIGNED" ? ( {item.readStatus === "OPENED" && item.signingStatus === "NOT_SIGNED" ? (
<span id="read_icon"> <span id="read_icon">
<span <span
@ -239,77 +246,77 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
Seen Seen
</span> </span>
</span> </span>
) : ( ) : null}
""
)}
{item.signingStatus === "SIGNED" ? ( {item.signingStatus === "SIGNED" ? (
<span id="signed_icon"> <span id="signed_icon">
<span <span
id="sent_icon" id="sent_icon"
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"> 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">
<CheckBadgeIcon className="mr-1 inline h-5"></CheckBadgeIcon> <CheckBadgeIcon className="mr-1 inline h-5"></CheckBadgeIcon>
Signed Signed
</span> </span>
</span> </span>
) : ( ) : null}
""
)}
</div> </div>
</div> </div>
<div className="mr-1 flex"> {props.document.status !== DocumentStatus.COMPLETED && (
<IconButton <div className="mr-1 flex">
icon={PaperAirplaneIcon} <IconButton
disabled={ icon={PaperAirplaneIcon}
!item.id || disabled={
item.sendStatus !== "SENT" || !item.id ||
item.signingStatus === "SIGNED" || item.sendStatus !== "SENT" ||
loading item.signingStatus === "SIGNED" ||
} loading
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);
});
} }
}}> color="secondary"
Resend className="my-auto mr-4 h-9"
</IconButton> onClick={() => {
<IconButton if (confirm("Resend this signing request?")) {
icon={TrashIcon} setLoading(true);
disabled={!item.id || item.sendStatus === "SENT" || loading} sendSigningRequests(props.document, [item.id]).finally(() => {
onClick={() => { setLoading(false);
const removedItem = { ...fields }[index]; });
remove(index); }
deleteRecipient(item)?.catch((err) => { }}>
append(removedItem); Resend
}); </IconButton>
}} <IconButton
className="group-hover:text-neon-dark group-hover:disabled:text-gray-400" icon={TrashIcon}
/> disabled={!item.id || item.sendStatus === "SENT" || loading}
</div> onClick={() => {
const removedItem = { ...fields }[index];
remove(index);
deleteRecipient(item)?.catch((err) => {
append(removedItem);
});
}}
className="group-hover:text-neon-dark group-hover:disabled:text-gray-400"
/>
</div>
)}
</div> </div>
</div> </div>
</li> </li>
))} ))}
</ul> </ul>
<Button {props.document.status !== "COMPLETED" && (
icon={UserPlusIcon} <Button
className="mt-3" icon={UserPlusIcon}
onClick={() => { className="mt-3"
createOrUpdateRecipient({ onClick={() => {
id: "", createOrUpdateRecipient({
email: "", id: "",
name: "", email: "",
documentId: props.document.id, name: "",
}).then((res) => { documentId: props.document.id,
append(res); }).then((res) => {
}); append(res);
}}> });
Add Signer }}>
</Button> Add Signer
</Button>
)}
</form> </form>
</FormProvider> </FormProvider>
</div> </div>

View File

@ -3,7 +3,7 @@ import { addDigitalSignature } from "@documenso/signing/addDigitalSignature";
import { sendMail } from "./sendMail"; import { sendMail } from "./sendMail";
import { Document as PrismaDocument } from "@prisma/client"; 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( await sendMail(
user.email, user.email,
`Completed: "${document.title}"`, `Completed: "${document.title}"`,

View File

@ -12,27 +12,36 @@ export async function insertTextInPDF(
): Promise<string> { ): Promise<string> {
const fontBytes = fs.readFileSync("public/fonts/Qwigley-Regular.ttf"); 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); 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 pages = pdfDoc.getPages();
const pdfPage = pages[page]; const pdfPage = pages[page];
const textSize = useHandwritingFont ? 50 : 15; const textSize = useHandwritingFont ? 50 : 15;
const textWidth = customFont.widthOfTextAtSize(text, textSize); const textWidth = font.widthOfTextAtSize(text, textSize);
const textHeight = customFont.heightAtSize(textSize); const textHeight = font.heightAtSize(textSize);
const fieldSize = { width: 192, height: 64 }; 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, { pdfPage.drawText(text, {
x: positionX, x: centeredXPosition,
y: invertedYPosition, y: invertedYPosition,
size: textSize, size: textSize,
font: useHandwritingFont ? customFont : helveticaFont,
color: rgb(0, 0, 0), color: rgb(0, 0, 0),
font,
}); });
const pdfAsUint8Array = await pdfDoc.save(); const pdfAsUint8Array = await pdfDoc.save();

View File

@ -24,7 +24,7 @@ async function createUser(userData: { email: string; password: string }) {
async function main() { async function main() {
console.info("Start seeding..."); console.info("Start seeding...");
const password = "123456789"; const password = "123456789";
const email = "example6@documenso.com"; const email = "example@documenso.com";
const user = await createUser({ const user = await createUser({
email: email, email: email,
password: await hashPassword(password), password: await hashPassword(password),