diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index 1701289af..7f2e670e3 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -480,4 +480,5 @@ .esignature-embed { width: 100%; height: 100%; +border-width: 0; } \ No newline at end of file diff --git a/client/src/components/esignature-modal/esignature-modal.container.jsx b/client/src/components/esignature-modal/esignature-modal.container.jsx index 0c6553d88..be82394e0 100644 --- a/client/src/components/esignature-modal/esignature-modal.container.jsx +++ b/client/src/components/esignature-modal/esignature-modal.container.jsx @@ -1,11 +1,11 @@ -import { Button, Modal } from "antd"; +import { EmbedUpdateDocumentV1 } from "@documenso/embed-react"; +import { Modal } from "antd"; +import axios from "axios"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { selectEsignature } from "../../redux/modals/modals.selectors"; -import { EmbedUpdateDocumentV1 } from "@documenso/embed-react"; -import axios from "axios"; import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ @@ -26,23 +26,42 @@ export function EsignatureModalContainer({ esignatureModal, toggleModalVisible, { - toggleModalVisible(); + onOk={async () => { + try { + const distResult = await axios.post("/esign/distribute", { + documentId, + envelopeId, + jobid, + bodyshopid: bodyshop.id + }); + console.log("Distribution result:", distResult); + toggleModalVisible(); + } catch (error) { + console.error("Error distributing document:", error); + } }} - onCancel={() => { - toggleModalVisible(); + onCancel={async () => { + try { + const cancelResult = await axios.post("/esign/delete", { + documentId, + envelopeId + }); + console.log("Cancel result:", cancelResult); + toggleModalVisible(); + } catch (error) { + console.error("Error cancelling document:", error); + } }} - cancelButtonProps={{ style: { display: "none" } }} + okButtonProps={{ title: "Distribute by Email" }} width="90%" destroyOnHidden > -
+
{token ? ( { console.log("Document updated:", data.documentId); @@ -51,24 +70,6 @@ export function EsignatureModalContainer({ esignatureModal, toggleModalVisible, ) : (
No token...
)} -
); diff --git a/server/esign/esign-new.js b/server/esign/esign-new.js index f8900bec9..5c976e4de 100644 --- a/server/esign/esign-new.js +++ b/server/esign/esign-new.js @@ -43,8 +43,25 @@ async function distributeDocument(req, res) { } } -async function newEsignDocument(req, res) { +async function deleteDocument(req, res) { + try { + const { documentId } = req.body; + //TODO: This needs to be hardened to prevent deleting other people's documents, completed ones, etc. + const deleteResult = await documenso.documents.delete({ + documentId + }); + res.json({ success: true, deleteResult }); + } catch (error) { + console.error("Error deleting document:", error?.data); + logger.log(`esig-delete-error`, "ERROR", "esig", "api", { + message: error.message, stack: error.stack, + body: req.body + }); + res.status(500).json({ error: "An error occurred while deleting the document." }); + } +} +async function newEsignDocument(req, res) { try { const client = req.userGraphQLClient; const { bodyshop } = req.body @@ -58,7 +75,7 @@ async function newEsignDocument(req, res) { const createDocumentResponse = await documenso.documents.create({ payload: { title: esigData?.title, - externalId: req.body.jobid, + externalId: `${req.body.jobid}|${req.user?.email}`, //Have to pass the uploaded by later on. Limited to 255 chars. recipients: [ { email: "patrick@imexsystems.ca",//jobData.ownr_ea, @@ -264,7 +281,8 @@ const fetchContextData = async ({ templateObject, jsrAuth, req, }) => { module.exports = { newEsignDocument, - distributeDocument + distributeDocument, + deleteDocument } diff --git a/server/esign/webhook.js b/server/esign/webhook.js index b863522f2..3c83a07ae 100644 --- a/server/esign/webhook.js +++ b/server/esign/webhook.js @@ -85,8 +85,14 @@ async function handleDocumentCompleted(payload = sampleComplete) { //Check if the bodyshop is on image proxy or not try { + //Split the external id to get the uploaded user. + const [jobid, uploaded_by] = payload.externalId.split("|"); + + if (!jobid || !uploaded_by) { + throw new Error(`Invalid externalId format. Expected "jobid|uploaded_by", got "${payload.externalId}"`); + } const { jobs_by_pk } = await client.request(QUERY_META_FOR_ESIG_COMPLETION, { - jobid: payload.externalId + jobid }); const document = await documenso.document.documentDownload({ documentId: payload.id, @@ -97,25 +103,24 @@ async function handleDocumentCompleted(payload = sampleComplete) { const buffer = Buffer.from(arrayBuffer); + let key = `${jobs_by_pk.bodyshop.id}/${jobs_by_pk.id}/${replaceAccents(document.filename).replace(/[^A-Z0-9]+/gi, "_")}-${new Date().getTime()}.pdf`; if (jobs_by_pk?.bodyshop?.uselocalmediaserver) { //LMS not yet implemented. } else { //S3 Upload - let key = `${jobs_by_pk.bodyshop.id}/${jobs_by_pk.id}/${replaceAccents(document.filename).replace(/[^A-Z0-9]+/gi, "_")}-${new Date().getTime()}.pdf`; - const uploadResult = await uploadFileBuffer({ key, buffer, contentType: "application/pdf" }); if (!uploadResult.success) { logger.log(`esig-webhook-s3-upload-error`, "ERROR", "redis", "api", { message: uploadResult.message, stack: uploadResult.stack, - jobid: payload.externalId, + jobid: jobid, documentId: payload.id }); } else { logger.log(`esig-webhook-s3-upload-success`, "INFO", "redis", "api", { - jobid: payload.externalId, + jobid: jobid, documentId: payload.id, s3Key: key, bucket: uploadResult.bucket @@ -125,7 +130,7 @@ async function handleDocumentCompleted(payload = sampleComplete) { jobid: jobs_by_pk.id, bodyshopid: jobs_by_pk.bodyshop.id, operation: `Esignature document with title ${payload.title} (ID: ${payload.documentMeta.id}) has been completed.`, - useremail: "patrick@imex.dev", //TODO: Figure out the hardcoded bypass. + useremail: uploaded_by, type: 'esig-complete' } }) @@ -133,12 +138,12 @@ async function handleDocumentCompleted(payload = sampleComplete) { await client.request(INSERT_ESIGNATURE_DOCUMENT, { docInput: { jobid: jobs_by_pk.id, - uploaded_by: "patrick@imex.dev", //TODO: Figure out the hard coded bypass. + uploaded_by: uploaded_by, key, type: "application/pdf", extension: "pdf", bodyshopid: jobs_by_pk.bodyshop.id, - size: buffer.length, //Leftover from Cloudinary. We don't do any optimization on upload, so it will always be file.size. + size: buffer.length, takenat: new Date().toISOString(), } }) diff --git a/server/routes/esignRoutes.js b/server/routes/esignRoutes.js index 264979ba3..059f49666 100644 --- a/server/routes/esignRoutes.js +++ b/server/routes/esignRoutes.js @@ -3,13 +3,14 @@ const router = express.Router(); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); -const { newEsignDocument, distributeDocument } = require("../esign/esign-new"); +const { newEsignDocument, distributeDocument, deleteDocument } = require("../esign/esign-new"); const { esignWebhook } = require("../esign/webhook"); //router.use(validateFirebaseIdTokenMiddleware); router.post("/new", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, newEsignDocument); router.post("/distribute", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, distributeDocument); +router.post("/delete", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, deleteDocument); router.post("/webhook", esignWebhook);