From e74be56681314600ca577278df405ac23ac9707e Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 13 May 2026 12:09:18 -0400 Subject: [PATCH] feature/IO-2433-esignature - Code review fixes --- .../esignature-custom-document.component.jsx | 5 +++ .../esignature-modal.container.jsx | 6 +++ .../job-audit-trail.component.jsx | 28 +++++++----- .../column-header-checkbox.component.jsx | 12 ++--- .../notification-settings-form.component.jsx | 33 +++++++++++--- .../print-center-item.component.jsx | 4 +- .../print-center-jobs.component.jsx | 4 +- client/src/graphql/bodyshop.queries.js | 1 + .../jobs-detail.page.component.jsx | 4 +- .../pages/manage/manage.page.component.jsx | 4 +- client/src/utils/esignature.js | 7 +++ client/src/utils/jobNotificationScenarios.js | 16 +++++-- server/esign/esign-new.js | 45 ++++++++++++++++--- server/esign/webhook.js | 29 ++++++------ 14 files changed, 146 insertions(+), 52 deletions(-) create mode 100644 client/src/utils/esignature.js diff --git a/client/src/components/esignature-custom-document/esignature-custom-document.component.jsx b/client/src/components/esignature-custom-document/esignature-custom-document.component.jsx index b0c09aeea..59d5cab67 100644 --- a/client/src/components/esignature-custom-document/esignature-custom-document.component.jsx +++ b/client/src/components/esignature-custom-document/esignature-custom-document.component.jsx @@ -8,6 +8,7 @@ import { createStructuredSelector } from "reselect"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { setModalContext } from "../../redux/modals/modals.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import { hasDocumensoApiKey } from "../../utils/esignature.js"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -28,6 +29,10 @@ export function EsignatureCustomDocument({ bodyshop, jobId, setEsignatureContext const notification = useNotification(); const { t } = useTranslation(); + if (!hasDocumensoApiKey(bodyshop)) { + return null; + } + const uploadCustomDocument = async ({ file, onError, onSuccess }) => { const formData = new FormData(); formData.append("document", file); diff --git a/client/src/components/esignature-modal/esignature-modal.container.jsx b/client/src/components/esignature-modal/esignature-modal.container.jsx index 48232d258..09ce49ad6 100644 --- a/client/src/components/esignature-modal/esignature-modal.container.jsx +++ b/client/src/components/esignature-modal/esignature-modal.container.jsx @@ -9,6 +9,7 @@ import { selectEsignature } from "../../redux/modals/modals.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { useState } from "react"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import { hasDocumensoApiKey } from "../../utils/esignature.js"; const mapStateToProps = createStructuredSelector({ esignatureModal: selectEsignature, @@ -25,6 +26,11 @@ export function EsignatureModalContainer({ esignatureModal, toggleModalVisible, const { open, context } = esignatureModal; const { token, envelopeId, documentId, jobid } = context; const [distributing, setDistributing] = useState(false); + + if (!hasDocumensoApiKey(bodyshop)) { + return null; + } + return ( - - - - - + {esignatureEnabled && ( + + + + + + )} ); } diff --git a/client/src/components/notification-settings/column-header-checkbox.component.jsx b/client/src/components/notification-settings/column-header-checkbox.component.jsx index 5a857921e..1efdfe420 100644 --- a/client/src/components/notification-settings/column-header-checkbox.component.jsx +++ b/client/src/components/notification-settings/column-header-checkbox.component.jsx @@ -1,4 +1,3 @@ -import { notificationScenarios } from "../../utils/jobNotificationScenarios.js"; import { Checkbox, Form } from "antd"; import { useTranslation } from "react-i18next"; import PropTypes from "prop-types"; @@ -9,18 +8,18 @@ import PropTypes from "prop-types"; * @param form * @param disabled * @param onHeaderChange + * @param scenarioKeys * @returns {JSX.Element} * @constructor */ -const ColumnHeaderCheckbox = ({ channel, form, disabled = false, onHeaderChange }) => { +const ColumnHeaderCheckbox = ({ channel, form, disabled = false, onHeaderChange, scenarioKeys }) => { const { t } = useTranslation(); // Subscribe to all form values so that this component re-renders on changes. const formValues = Form.useWatch([], form) || {}; // Determine if all scenarios for this channel are checked. - const allChecked = - notificationScenarios.length > 0 && notificationScenarios.every((scenario) => formValues[scenario]?.[channel]); + const allChecked = scenarioKeys.length > 0 && scenarioKeys.every((scenario) => formValues[scenario]?.[channel]); const onChange = (e) => { const checked = e.target.checked; @@ -28,7 +27,7 @@ const ColumnHeaderCheckbox = ({ channel, form, disabled = false, onHeaderChange const currentValues = form.getFieldsValue(); // Update each scenario for this channel. const newValues = { ...currentValues }; - notificationScenarios.forEach((scenario) => { + scenarioKeys.forEach((scenario) => { newValues[scenario] = { ...newValues[scenario], [channel]: checked }; }); // Update form values. @@ -50,7 +49,8 @@ ColumnHeaderCheckbox.propTypes = { channel: PropTypes.oneOf(["app", "email", "fcm"]).isRequired, form: PropTypes.object.isRequired, disabled: PropTypes.bool, - onHeaderChange: PropTypes.func + onHeaderChange: PropTypes.func, + scenarioKeys: PropTypes.arrayOf(PropTypes.string).isRequired }; export default ColumnHeaderCheckbox; diff --git a/client/src/components/notification-settings/notification-settings-form.component.jsx b/client/src/components/notification-settings/notification-settings-form.component.jsx index 82ea4ad6c..250d8ae3d 100644 --- a/client/src/components/notification-settings/notification-settings-form.component.jsx +++ b/client/src/components/notification-settings/notification-settings-form.component.jsx @@ -12,12 +12,13 @@ import { UPDATE_NOTIFICATION_SETTINGS, UPDATE_NOTIFICATIONS_AUTOADD } from "../../graphql/user.queries.js"; -import { notificationScenarios, notificationScenarioDefaults } from "../../utils/jobNotificationScenarios.js"; +import { getNotificationScenarios, notificationScenarioDefaults } from "../../utils/jobNotificationScenarios.js"; import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx"; import PropTypes from "prop-types"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import ColumnHeaderCheckbox from "../notification-settings/column-header-checkbox.component.jsx"; import { useIsEmployee } from "../../utils/useIsEmployee.js"; +import { hasDocumensoApiKey } from "../../utils/esignature.js"; /** * Notifications Settings Form @@ -35,6 +36,7 @@ const NotificationSettingsForm = ({ currentUser, bodyshop }) => { const [initialAutoAdd, setInitialAutoAdd] = useState(false); const notification = useNotification(); const isEmployee = useIsEmployee(bodyshop, currentUser); + const notificationScenarios = getNotificationScenarios({ includeEsign: hasDocumensoApiKey(bodyshop) }); // Fetch notification settings and notifications_autoadd const { loading, error, data } = useQuery(QUERY_NOTIFICATION_SETTINGS, { @@ -66,7 +68,7 @@ const NotificationSettingsForm = ({ currentUser, bodyshop }) => { setInitialAutoAdd(autoAdd); setIsDirty(false); // Reset dirty state when new data loads } - }, [data, form]); + }, [data, form, notificationScenarios]); // Handle toggle of notifications_autoadd const handleAutoAddToggle = async (checked) => { @@ -137,7 +139,14 @@ const NotificationSettingsForm = ({ currentUser, bodyshop }) => { width: "80%" }, { - title: setIsDirty(true)} />, + title: ( + setIsDirty(true)} + scenarioKeys={notificationScenarios} + /> + ), dataIndex: "app", key: "app", align: "center", @@ -148,7 +157,14 @@ const NotificationSettingsForm = ({ currentUser, bodyshop }) => { ) }, { - title: setIsDirty(true)} />, + title: ( + setIsDirty(true)} + scenarioKeys={notificationScenarios} + /> + ), dataIndex: "email", key: "email", align: "center", @@ -163,7 +179,14 @@ const NotificationSettingsForm = ({ currentUser, bodyshop }) => { // Currently disabled for prod if (!import.meta.env.PROD) { columns.push({ - title: setIsDirty(true)} />, + title: ( + setIsDirty(true)} + scenarioKeys={notificationScenarios} + /> + ), dataIndex: "fcm", key: "fcm", align: "center", diff --git a/client/src/components/print-center-item/print-center-item.component.jsx b/client/src/components/print-center-item/print-center-item.component.jsx index 2cfa1b60c..0f1d69f66 100644 --- a/client/src/components/print-center-item/print-center-item.component.jsx +++ b/client/src/components/print-center-item/print-center-item.component.jsx @@ -12,6 +12,7 @@ import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import axios from "axios"; import { setModalContext } from "../../redux/modals/modals.actions.js"; +import { hasDocumensoApiKey } from "../../utils/esignature.js"; const mapStateToProps = createStructuredSelector({ printCenterModal: selectPrintCenter, @@ -41,6 +42,7 @@ export function PrintCenterItemComponent({ const [loading, setLoading] = useState(false); const { context } = printCenterModal; const notification = useNotification(); + const esignatureEnabled = hasDocumensoApiKey(bodyshop); const renderToNewWindow = async () => { setLoading(true); @@ -96,7 +98,7 @@ export function PrintCenterItemComponent({
  • {item.title} - + {esignatureEnabled && } {!technician ? ( - + {esignatureEnabled && } setSearch(e.target.value)} value={search} enterButton /> diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js index 38dd5bde4..91a230053 100644 --- a/client/src/graphql/bodyshop.queries.js +++ b/client/src/graphql/bodyshop.queries.js @@ -53,6 +53,7 @@ export const QUERY_BODYSHOP = gql` phone federal_tax_id id + documenso_api_key insurance_vendor_id logo_img_path md_ro_statuses diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index fe41869a6..ea8fc9153 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -57,6 +57,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; import { DateTimeFormat } from "../../utils/DateFormatter"; import dayjs from "../../utils/day"; +import { hasDocumensoApiKey } from "../../utils/esignature.js"; import UndefinedToNull from "../../utils/undefinedtonull"; const mapStateToProps = createStructuredSelector({ @@ -105,6 +106,7 @@ export function JobsDetailPage({ }); const notification = useNotification(); const { scenarioNotificationsOn } = useSocket(); + const esignatureEnabled = hasDocumensoApiKey(bodyshop); useEffect(() => { //form.setFieldsValue(transormJobToForm(job)); @@ -286,7 +288,7 @@ export function JobsDetailPage({ > {t("general.labels.refresh")} - + {esignatureEnabled && }