Eisgnature Migrations, webhook handling, and clean up.

This commit is contained in:
Patrick Fic
2026-03-25 15:24:14 -07:00
parent e17b57c705
commit d4c7298334
23 changed files with 615 additions and 67 deletions

View File

@@ -1,5 +1,5 @@
import { EmbedUpdateDocumentV1 } from "@documenso/embed-react";
import { Modal } from "antd";
import { Modal, notification } from "antd";
import axios from "axios";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -7,6 +7,7 @@ import { createStructuredSelector } from "reselect";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectEsignature } from "../../redux/modals/modals.selectors";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { useState } from "react";
const mapStateToProps = createStructuredSelector({
esignatureModal: selectEsignature,
@@ -22,13 +23,14 @@ export function EsignatureModalContainer({ esignatureModal, toggleModalVisible,
const { t } = useTranslation();
const { open, context } = esignatureModal;
const { token, envelopeId, documentId, jobid } = context;
const [distributing, setDistributing] = useState(false);
return (
<Modal
open={open}
title={t("jobs.labels.esignature")}
onOk={async () => {
try {
setDistributing(true);
const distResult = await axios.post("/esign/distribute", {
documentId,
envelopeId,
@@ -39,7 +41,12 @@ export function EsignatureModalContainer({ esignatureModal, toggleModalVisible,
toggleModalVisible();
} catch (error) {
console.error("Error distributing document:", error);
notification.error({
message: t("esignature.distribute_error"),
description: error?.response?.data?.message || error.message
});
}
setDistributing(false);
}}
onCancel={async () => {
try {
@@ -51,11 +58,16 @@ export function EsignatureModalContainer({ esignatureModal, toggleModalVisible,
toggleModalVisible();
} catch (error) {
console.error("Error cancelling document:", error);
notification.error({
message: t("esignature.cancel_error"),
description: error?.response?.data?.message || error.message
});
}
}}
okButtonProps={{ title: "Distribute by Email" }}
width="90%"
okButtonProps={{ loading: distributing }}
okText={t("esignature.actions.distribute")}
destroyOnHidden
width={800}
>
<div style={{ height: "600px", width: "100%" }}>
{token ? (

View File

@@ -1,6 +1,6 @@
import { SyncOutlined } from "@ant-design/icons";
import { useQuery } from "@apollo/client/react";
import { Button, Card, Col, Row, Tag } from "antd";
import { Button, Card, Checkbox, Col, Row, Space, Tag } from "antd";
import ResponsiveTable from "../responsive-table/responsive-table.component";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -12,6 +12,8 @@ import { DateTimeFormatter } from "../../utils/DateFormatter";
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
import axios from "axios";
import { useNotification } from "../../contexts/Notifications/notificationContext";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -23,6 +25,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobAuditTrail);
export function JobAuditTrail({ bodyshop, jobId }) {
const { t } = useTranslation();
const notification = useNotification();
const { loading, data, refetch } = useQuery(QUERY_AUDIT_TRAIL, {
variables: { jobid: jobId },
skip: !jobId,
@@ -53,6 +56,125 @@ export function JobAuditTrail({ bodyshop, jobId }) {
)
}
];
const esigColumns = [
{
title: t("audit.fields.created_at"),
dataIndex: "created_at",
key: "created_at",
render: (text) => <DateTimeFormatter>{text}</DateTimeFormatter>
},
{
title: t("audit.fields.updated_at"),
dataIndex: "updated_at",
key: "updated_at",
render: (text) => <DateTimeFormatter>{text}</DateTimeFormatter>
},
{
title: t("audit.fields.title"),
dataIndex: "title",
key: "title",
render: (text) => (
<BlurWrapperComponent featureName="audit" bypass>
<div>{text}</div>
</BlurWrapperComponent>
)
},
{
title: t("audit.fields.external_document_id"),
dataIndex: "external_document_id",
key: "external_document_id",
render: (text) => (
<BlurWrapperComponent featureName="audit" bypass>
<div>{text}</div>
</BlurWrapperComponent>
)
},
{
title: t("audit.fields.status"),
dataIndex: "status",
key: "status",
render: (text) => (
<BlurWrapperComponent featureName="audit" bypass>
<div>{text}</div>
</BlurWrapperComponent>
)
},
{
title: t("audit.fields.opened"),
dataIndex: "opened",
key: "opened",
render: (text) => <Checkbox checked={text} disabled />
},
{
title: t("audit.fields.rejected"),
dataIndex: "rejected",
key: "rejected",
render: (text) => <Checkbox checked={text} disabled />
},
{
title: t("audit.fields.completed"),
dataIndex: "completed",
key: "completed",
render: (text) => <Checkbox checked={text} disabled />
},
{
title: t("audit.fields.completed_at"),
dataIndex: "completed_at",
key: "completed_at",
render: (text) => <DateTimeFormatter>{text}</DateTimeFormatter>
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (_text, record) => (
<Space wrap>
<Button
disabled={record.completed_at !== null || record.status === "REJECTED"}
onClick={async () => {
logImEXEvent("job_esig_delete", {});
try {
const deleteResult = await axios.post("/esign/delete", {
documentId: record.external_document_id
});
console.log("*** ~ JobAuditTrail ~ deleteResult:", deleteResult);
refetch();
} catch (error) {
console.error("Error deleting document:", error?.response?.data || error.message);
notification.error({
message: t("esignature.delete_error"),
description: error?.response?.data?.error || error.message
});
}
}}
>
{t("esignature.actions.delete")}
</Button>
<Button onClick={() => console.log(record)}>{t("esignature.actions.resend")}</Button>
<Button
onClick={() => {
axios
.post("/esign/view", {
documentId: record.external_document_id
})
.then((response) => {
window.open(response.data?.document?.downloadUrl, "_blank");
})
.catch((error) => {
console.error("Error viewing document:", error?.response?.data || error.message);
notification.error({
message: t("esignature.view_error"),
description: error?.response?.data?.message || error.message
});
});
}}
>
{t("esignature.actions.view")}
</Button>
</Space>
)
}
];
const emailColumns = [
{
title: t("audit.fields.created"),
@@ -184,6 +306,17 @@ export function JobAuditTrail({ bodyshop, jobId }) {
/>
</Card>
</Col>
<Col span={24}>
<Card title={t("jobs.labels.esignatures")}>
<ResponsiveTable
loading={loading}
columns={esigColumns}
mobileColumnKeys={["title", "status"]}
rowKey="id"
dataSource={data ? data.esignature_documents : []}
/>
</Card>
</Col>
</Row>
);
}

View File

@@ -22,6 +22,23 @@ export const QUERY_AUDIT_TRAIL = gql`
useremail
status
}
esignature_documents(where: {jobid: {_eq: $jobid}}) {
id
created_at
updated_at
jobid
external_document_id
subject
message
title
status
recipients
completed_at
opened
completed
rejected
completed_at
}
}
`;

View File

@@ -1239,6 +1239,11 @@
"unique_employee_number": "You must enter a unique employee number."
}
},
"esignature": {
"actions": {
"distribute": "Distribute"
}
},
"eula": {
"buttons": {
"accept": "Accept EULA"

View File

@@ -1239,6 +1239,11 @@
"unique_employee_number": ""
}
},
"esignature": {
"actions": {
"distribute": ""
}
},
"eula": {
"buttons": {
"accept": "Accept EULA"

View File

@@ -1239,6 +1239,11 @@
"unique_employee_number": ""
}
},
"esignature": {
"actions": {
"distribute": ""
}
},
"eula": {
"buttons": {
"accept": "Accept EULA"