mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-08-17 21:25:54 +02:00
Merge pull request #1458 from OpenSignLabs/uncompatible_pdf
feat: send email notification to document owner when signer sign document
This commit is contained in:
Generated
+1061
-248
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@lottiefiles/dotlottie-react": "^0.9.3",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.1.4",
|
||||
"@radix-ui/themes": "^3.1.6",
|
||||
"@react-pdf/renderer": "^4.0.0",
|
||||
"@reduxjs/toolkit": "^2.3.0",
|
||||
"axios": "^1.7.7",
|
||||
@@ -34,13 +34,13 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.0.3",
|
||||
"react-i18next": "^15.1.1",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-pdf": "^9.1.1",
|
||||
"react-quill-new": "^3.3.3",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-rnd": "^10.4.13",
|
||||
"react-router-dom": "^6.27.0",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.8.3",
|
||||
@@ -54,7 +54,7 @@
|
||||
"regex-parser": "^2.3.0",
|
||||
"serve": "^14.2.4",
|
||||
"styled-components": "^5.3.0",
|
||||
"web-vitals": "^4.2.3",
|
||||
"web-vitals": "^4.2.4",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -103,17 +103,17 @@
|
||||
"babel-loader": "^9.2.1",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"commitizen": "^4.3.1",
|
||||
"concurrently": "^9.0.1",
|
||||
"concurrently": "^9.1.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.14",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.37.1",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"lint-staged": "^15.2.10",
|
||||
"mini-css-extract-plugin": "^2.9.1",
|
||||
"postcss": "^8.4.48",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-quick": "^4.0.0",
|
||||
"tailwindcss": "^3.4.14",
|
||||
|
||||
@@ -674,14 +674,14 @@
|
||||
"document-logs":"Document logs",
|
||||
"server-down": "Unable to connect to the OpenSign server. If you are self-hosting OpenSign, please ensure that all the steps in the documentation have been followed correctly. If you're running OpenSign locally, you might be accessing it through an incorrect port number.",
|
||||
"admin-exists": "Admin already exists. Please login to the application using admin credentials in order to manage users.",
|
||||
"enable-tour":"Enable Tour",
|
||||
"istourenabled-help":{
|
||||
"enable-tour":"Enable Tour",
|
||||
"istourenabled-help":{
|
||||
"p1":"This will enable a guided tour for signers, providing instructions during the signing process.",
|
||||
"p2":"This will disable the guided tour, ensuring a faster, uninterrupted signing experience.",
|
||||
"p3":"Tip: If your signers are familiar with OpenSign and frequently use it, you may want to disable the guided tour for a more seamless experience. Choose the option that best fits your document signing needs."
|
||||
},
|
||||
"form-title-1":"Document Flow Setting",
|
||||
"form-title-2":"Security Settings",
|
||||
"form-title-1":"Document Flow Setting",
|
||||
"form-title-2":"Security Settings",
|
||||
"public-tour-message":"The template needs to be public before you can generate a shareable link.",
|
||||
"add-user-template": "You need to add a role before you can add fields for it.",
|
||||
"pdf-uncompatible": "This pdf is not compatible with opensign, please contact <support@opensignlabs.com>",
|
||||
@@ -697,5 +697,10 @@
|
||||
"l2": "Enter your name and choose from various font styles to create a signature.",
|
||||
"l3": "Upload an existing digital version of your signature.",
|
||||
"l4": "Use a pre-set signature style saved under 'My Signature' in your settings."
|
||||
},
|
||||
"notify-on-signatures": "Notify on signatures",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "The document creator will receive an email notification whenever a signer signs the document.",
|
||||
"note": "Note: Notification email will not be sent for the last signer, as the document creator will receive a completion email instead."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,24 +678,29 @@
|
||||
"add-user-template": "Es necesario agregar un rol antes de poder agregarle campos.",
|
||||
"pdf-uncompatible": "Este pdf no es compatible con opensign, comuníquese con <support@opensignlabs.com>",
|
||||
"enable-tour": "Habilitar recorrido",
|
||||
"istourenabled-help":{
|
||||
"p1": "Esto permitirá realizar una visita guiada para los firmantes, brindando instrucciones durante el proceso de firma.",
|
||||
"p2": "Esto desactivará la visita guiada, lo que garantizará una experiencia de firma más rápida e ininterrumpida.",
|
||||
"p3": "Consejo: si sus firmantes están familiarizados con OpenSign y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
|
||||
"istourenabled-help":{
|
||||
"p1": "Esto permitirá realizar una visita guiada para los firmantes, brindando instrucciones durante el proceso de firma.",
|
||||
"p2": "Esto desactivará la visita guiada, lo que garantizará una experiencia de firma más rápida e ininterrumpida.",
|
||||
"p3": "Consejo: si sus firmantes están familiarizados con OpenSign y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
|
||||
},
|
||||
"form-title-1": "Configuración del flujo de documentos",
|
||||
"form-title-2": "Configuración de seguridad",
|
||||
"text-field-tour":"Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
|
||||
"attach-signer-tour" :"Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
|
||||
"allowed-signature-types": "Tipos de firma permitidos",
|
||||
"at-least-one-signature-type": "Se debe habilitar al menos un tipo de firma.",
|
||||
"expect-default-one-signature-type": "Habilite un tipo de firma adicional aparte del predeterminado.",
|
||||
"add-signer-note": "Nota - Este contacto se agregará a su lista de contactos",
|
||||
"allowed-signature-types-help": {
|
||||
"form-title-1": "Configuración del flujo de documentos",
|
||||
"form-title-2": "Configuración de seguridad",
|
||||
"text-field-tour":"Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
|
||||
"attach-signer-tour" :"Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
|
||||
"allowed-signature-types": "Tipos de firma permitidos",
|
||||
"at-least-one-signature-type": "Se debe habilitar al menos un tipo de firma.",
|
||||
"expect-default-one-signature-type": "Habilite un tipo de firma adicional aparte del predeterminado.",
|
||||
"add-signer-note": "Nota - Este contacto se agregará a su lista de contactos",
|
||||
"allowed-signature-types-help": {
|
||||
"p1": "Esta preferencia de firma controla qué opciones de firma están disponibles para sus firmantes. Solo los tipos de firma que seleccione aparecerán en el widget de firma cuando se firme un documento. Por ejemplo, si elige sólo la opción 'Draw' en las preferencias, su firmante verá sólo la opción 'Draw' en el widget de firma, mientras que las otras tres opciones no estarán disponibles.",
|
||||
"l1": "Cree una firma manuscrita directamente en la pantalla usando su dedo o un lápiz.",
|
||||
"l2": "Ingrese su nombre y elija entre varios estilos de fuente para crear una firma.",
|
||||
"l3": "Cargue una versión digital existente de su firma.",
|
||||
"l4": "Utilice un estilo de firma preestablecido guardado en 'Mi firma' en su configuración."
|
||||
},
|
||||
"notify-on-signatures": "Notificar sobre firmas",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "El creador del documento recibirá una notificación por correo electrónico cada vez que un firmante firme el documento.",
|
||||
"note": "Nota: No se enviará un correo electrónico de notificación al último firmante, ya que el creador del documento recibirá un correo electrónico de finalización."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,5 +696,10 @@
|
||||
"l2": "Entrez votre nom et choisissez parmi différents styles de police pour créer une signature.",
|
||||
"l3": "Téléchargez une version numérique existante de votre signature.",
|
||||
"l4": "Utilisez un style de signature prédéfini enregistré sous 'Ma signature' dans vos paramètres."
|
||||
},
|
||||
"notify-on-signatures": "Notifier les signatures",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "Le créateur du document recevra une notification par e-mail chaque fois qu'un signataire signera le document.",
|
||||
"note": "Remarque : L'e-mail de notification ne sera pas envoyé pour le dernier signataire, car le créateur du document recevra à la place un e-mail de fin."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,10 @@ const Header = ({ showSidebar, setIsMenu }) => {
|
||||
const extUser = await Parse.Cloud.run("getUserDetails");
|
||||
const MonthlyFreeEmails = extUser?.get("MonthlyFreeEmails") || 0;
|
||||
setEmailUsed(MonthlyFreeEmails);
|
||||
if (extUser) {
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in while fetching monthlyfreeEmails", err);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false",
|
||||
IsTourEnabled: template?.IsTourEnabled
|
||||
? `${template?.IsTourEnabled}`
|
||||
: "false"
|
||||
: "false",
|
||||
NotifyOnSignatures:
|
||||
template?.NotifyOnSignatures !== undefined
|
||||
? template?.NotifyOnSignatures
|
||||
: false
|
||||
});
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
useEffect(() => {
|
||||
@@ -64,6 +68,11 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
AutomaticReminders: !formData.AutomaticReminders
|
||||
}));
|
||||
};
|
||||
|
||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||
const handleNotifySignChange = (value) => {
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
||||
};
|
||||
return (
|
||||
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
||||
<div className="text-base-content">
|
||||
@@ -120,27 +129,29 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label className="text-[13px]">{t("send-in-order")}</label>
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="SendinOrder"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.SendinOrder === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("no")}</div>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="SendinOrder"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.SendinOrder === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isEnableSubscription && (
|
||||
@@ -156,7 +167,7 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe
|
||||
? "cursor-pointer "
|
||||
? "cursor-pointer"
|
||||
: "pointer-events-none opacity-50"
|
||||
} relative block items-center mb-0`}
|
||||
>
|
||||
@@ -221,39 +232,40 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1 `}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsEnableOTP"
|
||||
checked={formData.IsEnableOTP === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1 `}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsEnableOTP"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsEnableOTP === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsEnableOTP"
|
||||
checked={formData.IsEnableOTP === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1 `}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsEnableOTP"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsEnableOTP === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
<span>
|
||||
@@ -283,27 +295,83 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className={` flex items-center gap-2 ml-2 mb-1 `}>
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsTourEnabled"
|
||||
checked={formData.IsTourEnabled === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsTourEnabled"
|
||||
checked={formData.IsTourEnabled === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsTourEnabled"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsTourEnabled === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={` flex items-center gap-2 ml-2 mb-1 `}>
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsTourEnabled"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsTourEnabled === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription ? "" : "text-gray-300"
|
||||
} block`}
|
||||
>
|
||||
{t("notify-on-signatures")}
|
||||
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
<Tooltip id="nos-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
||||
<p className="font-bold">{t("notify-on-signatures")}</p>
|
||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||
<p>{t("notify-on-signatures-help.note")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription
|
||||
? ""
|
||||
: "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={formData.NotifyOnSignatures === true}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription
|
||||
? ""
|
||||
: "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(false)}
|
||||
checked={formData.NotifyOnSignatures === false}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-[1rem] flex justify-start">
|
||||
|
||||
@@ -7,10 +7,7 @@ const DROPBOX_SCRIPT_ID = "dropboxjs";
|
||||
|
||||
export default function DropboxChooser({ children, onSuccess, onCancel }) {
|
||||
useDropScript(DROPBOX_SDK_URL, {
|
||||
attrs: {
|
||||
id: DROPBOX_SCRIPT_ID,
|
||||
"data-app-key": DROPBOX_APP_KEY
|
||||
}
|
||||
attrs: { id: DROPBOX_SCRIPT_ID, "data-app-key": DROPBOX_APP_KEY }
|
||||
});
|
||||
|
||||
const options = useMemo(
|
||||
|
||||
@@ -705,6 +705,9 @@ export const createDocument = async (
|
||||
const SignatureType = Doc?.SignatureType
|
||||
? { SignatureType: Doc?.SignatureType }
|
||||
: {};
|
||||
const NotifyOnSignatures = Doc?.NotifyOnSignatures
|
||||
? { NotifyOnSignatures: Doc?.NotifyOnSignatures }
|
||||
: {};
|
||||
const data = {
|
||||
Name: Doc.Name,
|
||||
URL: pdfUrl,
|
||||
@@ -730,7 +733,8 @@ export const createDocument = async (
|
||||
IsEnableOTP: Doc?.IsEnableOTP || false,
|
||||
IsTourEnabled: Doc?.IsTourEnabled || false,
|
||||
FileAdapterId: Doc?.FileAdapterId || "",
|
||||
...SignatureType
|
||||
...SignatureType,
|
||||
...NotifyOnSignatures
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -68,7 +68,8 @@ const Forms = (props) => {
|
||||
remindOnceInEvery: 5,
|
||||
autoreminder: false,
|
||||
IsEnableOTP: "false",
|
||||
IsTourEnabled: "false"
|
||||
IsTourEnabled: "false",
|
||||
NotifyOnSignatures: ""
|
||||
});
|
||||
const [fileupload, setFileUpload] = useState("");
|
||||
const [fileload, setfileload] = useState(false);
|
||||
@@ -102,21 +103,25 @@ const Forms = (props) => {
|
||||
if (isEnableSubscription) {
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe.isValid);
|
||||
if (subscribe.isValid && extUserData.NotifyOnSignatures !== undefined) {
|
||||
setFormData((obj) => ({
|
||||
...obj,
|
||||
NotifyOnSignatures: extUserData.NotifyOnSignatures
|
||||
}));
|
||||
} else if (subscribe.isValid) {
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: true }));
|
||||
}
|
||||
} else {
|
||||
setIsSubscribe(true);
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: true }));
|
||||
}
|
||||
};
|
||||
|
||||
function getFileAsArrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = (e) => {
|
||||
resolve(e.target.result);
|
||||
};
|
||||
|
||||
reader.onerror = (e) => {
|
||||
reject(e.target.error);
|
||||
};
|
||||
|
||||
reader.onload = (e) => resolve(e.target.result);
|
||||
reader.onerror = (e) => reject(e.target.error);
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
@@ -525,7 +530,6 @@ const Forms = (props) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
const dropboxCancel = async () => {};
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -555,8 +559,14 @@ const Forms = (props) => {
|
||||
const IsEnableOTP =
|
||||
formData?.IsEnableOTP === "false" ? false : true;
|
||||
object.set("IsEnableOTP", IsEnableOTP);
|
||||
if (isSubscribe && formData.NotifyOnSignatures !== undefined) {
|
||||
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
|
||||
}
|
||||
} else {
|
||||
object.set("IsEnableOTP", false);
|
||||
if (formData.NotifyOnSignatures !== undefined) {
|
||||
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
|
||||
}
|
||||
}
|
||||
}
|
||||
object.set("URL", fileupload);
|
||||
@@ -639,7 +649,7 @@ const Forms = (props) => {
|
||||
setIsReset(true);
|
||||
setSigners([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
setFormData({
|
||||
let obj = {
|
||||
Name: "",
|
||||
Description: "",
|
||||
Note:
|
||||
@@ -654,7 +664,8 @@ const Forms = (props) => {
|
||||
autoreminder: false,
|
||||
IsEnableOTP: "false",
|
||||
IsTourEnabled: "true"
|
||||
});
|
||||
};
|
||||
setFormData(obj);
|
||||
setFileUpload("");
|
||||
setpercentage(0);
|
||||
setTimeout(() => setIsReset(false), 50);
|
||||
@@ -772,6 +783,12 @@ const Forms = (props) => {
|
||||
const handleAutoReminder = () => {
|
||||
setFormData((prev) => ({ ...prev, autoreminder: !formData.autoreminder }));
|
||||
};
|
||||
|
||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||
const handleNotifySignChange = (value) => {
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="shadow-md rounded-box my-[2px] p-3 bg-base-100 text-base-content">
|
||||
<Title title={props?.title} />
|
||||
@@ -865,10 +882,7 @@ const Forms = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
{process.env.REACT_APP_DROPBOX_API_KEY && (
|
||||
<DropboxChooser
|
||||
onSuccess={dropboxSuccess}
|
||||
onCancel={dropboxCancel}
|
||||
/>
|
||||
<DropboxChooser onSuccess={dropboxSuccess} />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
@@ -890,10 +904,7 @@ const Forms = (props) => {
|
||||
required
|
||||
/>
|
||||
{process.env.REACT_APP_DROPBOX_API_KEY && (
|
||||
<DropboxChooser
|
||||
onSuccess={dropboxSuccess}
|
||||
onCancel={dropboxCancel}
|
||||
/>
|
||||
<DropboxChooser onSuccess={dropboxSuccess} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -1215,7 +1226,7 @@ const Forms = (props) => {
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe
|
||||
? "cursor-pointer "
|
||||
? "cursor-pointer"
|
||||
: "pointer-events-none opacity-50"
|
||||
} relative block items-center mb-0 mt-1.5`}
|
||||
>
|
||||
@@ -1251,7 +1262,7 @@ const Forms = (props) => {
|
||||
)}
|
||||
<span className="font-[400] mt-2">{t("form-title-2")}</span>
|
||||
{isEnableSubscription && (
|
||||
<div className="text-xs mt-4">
|
||||
<div className="text-xs mt-3">
|
||||
<label className="block">
|
||||
<span className={isSubscribe ? "" : " text-gray-300"}>
|
||||
{t("isenable-otp")}{" "}
|
||||
@@ -1262,7 +1273,7 @@ const Forms = (props) => {
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>{" "}
|
||||
</a>
|
||||
{!isSubscribe && isEnableSubscription && (
|
||||
<Upgrade />
|
||||
)}
|
||||
@@ -1340,7 +1351,7 @@ const Forms = (props) => {
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>{" "}
|
||||
</a>
|
||||
</span>
|
||||
<Tooltip id="istourenabled-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
@@ -1364,7 +1375,7 @@ const Forms = (props) => {
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className={` flex items-center gap-2 ml-2 mb-1 `}>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
@@ -1375,7 +1386,7 @@ const Forms = (props) => {
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div className={` flex items-center gap-2 ml-2 mb-1 `}>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
@@ -1388,6 +1399,58 @@ const Forms = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe ? "" : "text-gray-300"
|
||||
} block`}
|
||||
>
|
||||
{t("notify-on-signatures")}
|
||||
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
<Tooltip id="nos-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
||||
<p className="font-bold">
|
||||
{t("notify-on-signatures")}
|
||||
</p>
|
||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||
<p>{t("notify-on-signatures-help.note")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={formData.NotifyOnSignatures === true}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(false)}
|
||||
checked={formData.NotifyOnSignatures === false}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -5,12 +5,15 @@ import { useTranslation } from "react-i18next";
|
||||
import Loader from "../primitives/Loader";
|
||||
import Tooltip from "../primitives/Tooltip";
|
||||
import {
|
||||
checkIsSubscribed,
|
||||
getTenantDetails,
|
||||
handleSignatureType,
|
||||
signatureTypes
|
||||
} from "../constant/Utils";
|
||||
import Parse from "parse";
|
||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import Upgrade from "../primitives/Upgrade";
|
||||
const Preferences = () => {
|
||||
const { t } = useTranslation();
|
||||
const [isalert, setIsAlert] = useState({ type: "success", msg: "" });
|
||||
@@ -18,11 +21,19 @@ const Preferences = () => {
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [signatureType, setSignatureType] = useState([]);
|
||||
const [errMsg, setErrMsg] = useState("");
|
||||
const [isNotifyOnSignatures, setIsNotifyOnSignatures] = useState();
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
useEffect(() => {
|
||||
fetchSignType();
|
||||
}, []);
|
||||
|
||||
const fetchSignType = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe.isValid);
|
||||
} else {
|
||||
setIsSubscribe(true);
|
||||
}
|
||||
setIsTopLoader(true);
|
||||
try {
|
||||
const user = JSON.parse(
|
||||
@@ -36,6 +47,11 @@ const Preferences = () => {
|
||||
const getUser = await Parse.Cloud.run("getUserDetails");
|
||||
if (getUser) {
|
||||
const _getUser = JSON.parse(JSON.stringify(getUser));
|
||||
const notifyOnSignatures =
|
||||
_getUser?.NotifyOnSignatures !== undefined
|
||||
? _getUser?.NotifyOnSignatures
|
||||
: "";
|
||||
setIsNotifyOnSignatures(notifyOnSignatures);
|
||||
if (tenantSignTypes?.length > 0) {
|
||||
const signatureType = _getUser?.SignatureType || signatureTypes;
|
||||
const updatedSignatureType = await handleSignatureType(
|
||||
@@ -69,39 +85,59 @@ const Preferences = () => {
|
||||
// `handleSave` is used save updated value signature type
|
||||
const handleSave = async () => {
|
||||
setIsLoader(true);
|
||||
const enabledSignTypes = signatureType?.filter((x) => x.enabled);
|
||||
const isDefaultSignTypeOnly =
|
||||
enabledSignTypes?.length === 1 && enabledSignTypes[0]?.name === "default";
|
||||
if (enabledSignTypes.length === 0) {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("at-least-one-signature-type")
|
||||
});
|
||||
} else if (isDefaultSignTypeOnly) {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("expect-default-one-more-signature-type")
|
||||
});
|
||||
} else {
|
||||
if (signatureType.length > 0 || isNotifyOnSignatures) {
|
||||
let params = {};
|
||||
if (signatureType.length > 0) {
|
||||
const enabledSignTypes = signatureType?.filter((x) => x.enabled);
|
||||
const isDefaultSignTypeOnly =
|
||||
enabledSignTypes?.length === 1 &&
|
||||
enabledSignTypes[0]?.name === "default";
|
||||
if (enabledSignTypes.length === 0) {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("at-least-one-signature-type")
|
||||
});
|
||||
} else if (isDefaultSignTypeOnly) {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("expect-default-one-more-signature-type")
|
||||
});
|
||||
} else {
|
||||
params = { ...params, SignatureType: signatureType };
|
||||
}
|
||||
}
|
||||
params = { ...params, NotifyOnSignatures: isNotifyOnSignatures };
|
||||
try {
|
||||
const updateRes = await Parse.Cloud.run("updatesignaturetype", {
|
||||
SignatureType: signatureType
|
||||
});
|
||||
const updateRes = await Parse.Cloud.run("updatepreferences", params);
|
||||
if (updateRes) {
|
||||
setIsAlert({ type: "success", msg: "Saved successfully." });
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.NotifyOnSignatures = isNotifyOnSignatures;
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Error updating signature type", err);
|
||||
setIsAlert({ type: "danger", msg: err.message });
|
||||
}
|
||||
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
setIsLoader(false);
|
||||
}
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
setIsLoader(false);
|
||||
};
|
||||
|
||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||
const handleNotifySignChange = (value) => {
|
||||
setIsNotifyOnSignatures(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title title={"API Token"} />
|
||||
<Title title={t("Preferences")} />
|
||||
{isalert.msg && <Alert type={isalert.type}>{isalert.msg}</Alert>}
|
||||
{isTopLoader ? (
|
||||
<div className="flex justify-center items-center h-screen">
|
||||
@@ -200,6 +236,64 @@ const Preferences = () => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe ? "" : "text-gray-300"
|
||||
} text-[14px] mb-[0.7rem] font-medium`}
|
||||
>
|
||||
{t("notify-on-signatures")}
|
||||
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
<ReactTooltip id="nos-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
||||
<p className="font-bold">{t("notify-on-signatures")}</p>
|
||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||
<p>{t("notify-on-signatures-help.note")}</p>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={isNotifyOnSignatures === true}
|
||||
/>
|
||||
<div className="text-[13px] font-medium cursor-default capitalize">
|
||||
{t("yes")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(false)}
|
||||
checked={isNotifyOnSignatures === false}
|
||||
/>
|
||||
<div className="text-[13px] font-medium cursor-default capitalize">
|
||||
{t("no")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<button
|
||||
className="op-btn op-btn-primary"
|
||||
onClick={handleSave}
|
||||
|
||||
@@ -763,7 +763,11 @@ const TemplatePlaceholder = () => {
|
||||
IsEnableOTP: pdfDetails[0]?.IsEnableOTP === true ? true : false,
|
||||
IsTourEnabled: pdfDetails[0]?.IsTourEnabled === true ? true : false,
|
||||
URL: pdfUrl,
|
||||
SignatureType: signatureType
|
||||
SignatureType: signatureType,
|
||||
NotifyOnSignatures:
|
||||
pdfDetails[0]?.NotifyOnSignatures !== undefined
|
||||
? pdfDetails[0]?.NotifyOnSignatures
|
||||
: false
|
||||
};
|
||||
const updateTemplate = new Parse.Object("contracts_Template");
|
||||
updateTemplate.id = templateId;
|
||||
|
||||
@@ -288,6 +288,10 @@ const ReportTable = (props) => {
|
||||
updatedSignatureType.length > 0
|
||||
? { SignatureType: updatedSignatureType }
|
||||
: {};
|
||||
const NotifyOnSignatures =
|
||||
Doc.NotifyOnSignatures !== undefined
|
||||
? { NotifyOnSignatures: Doc.NotifyOnSignatures }
|
||||
: {};
|
||||
let placeholdersArr = [];
|
||||
if (Doc.Placeholders?.length > 0) {
|
||||
placeholdersArr = Doc.Placeholders;
|
||||
@@ -315,7 +319,8 @@ const ReportTable = (props) => {
|
||||
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5,
|
||||
IsEnableOTP: Doc?.IsEnableOTP || false,
|
||||
FileAdapterId: Doc?.FileAdapterId || "",
|
||||
...SignatureType
|
||||
...SignatureType,
|
||||
...NotifyOnSignatures
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(
|
||||
|
||||
@@ -697,5 +697,10 @@
|
||||
"l2": "Enter your name and choose from various font styles to create a signature.",
|
||||
"l3": "Upload an existing digital version of your signature.",
|
||||
"l4": "Use a pre-set signature style saved under 'My Signature' in your settings."
|
||||
},
|
||||
"notify-on-signatures": "Notify on signatures",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "The document creator will receive an email notification whenever a signer signs the document.",
|
||||
"note": "Note: Notification email will not be sent for the last signer, as the document creator will receive a completion email instead."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,5 +697,10 @@
|
||||
"l2": "Ingrese su nombre y elija entre varios estilos de fuente para crear una firma.",
|
||||
"l3": "Cargue una versión digital existente de su firma.",
|
||||
"l4": "Utilice un estilo de firma preestablecido guardado en 'Mi firma' en su configuración."
|
||||
},
|
||||
"notify-on-signatures": "Notificar sobre firmas",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "El creador del documento recibirá una notificación por correo electrónico cada vez que un firmante firme el documento.",
|
||||
"note": "Nota: No se enviará un correo electrónico de notificación al último firmante, ya que el creador del documento recibirá un correo electrónico de finalización."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,5 +696,10 @@
|
||||
"l2": "Entrez votre nom et choisissez parmi différents styles de police pour créer une signature.",
|
||||
"l3": "Téléchargez une version numérique existante de votre signature.",
|
||||
"l4": "Utilisez un style de signature prédéfini enregistré sous 'Ma signature' dans vos paramètres."
|
||||
},
|
||||
"notify-on-signatures": "Notifier les signatures",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "Le créateur du document recevra une notification par e-mail chaque fois qu'un signataire signera le document.",
|
||||
"note": "Remarque : L'e-mail de notification ne sera pas envoyé pour le dernier signataire, car le créateur du document recevra à la place un e-mail de fin."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ import updateTourStatus from './parsefunction/updateTourStatus.js';
|
||||
import saveTemplate from './parsefunction/saveTemplate.js';
|
||||
import updateToPublicTemplate from './parsefunction/updateToPublicTemplate.js';
|
||||
import updateSignatureType from './parsefunction/updatesignaturetype.js';
|
||||
import updatePreferences from './parsefunction/updatePreferences.js';
|
||||
|
||||
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
|
||||
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
|
||||
@@ -180,3 +181,4 @@ Parse.Cloud.define('isuserincontactbook', isUserInContactBook);
|
||||
Parse.Cloud.define('updatetourstatus', updateTourStatus);
|
||||
Parse.Cloud.define('updatetopublictemplate', updateToPublicTemplate);
|
||||
Parse.Cloud.define('updatesignaturetype', updateSignatureType);
|
||||
Parse.Cloud.define('updatepreferences', updatePreferences);
|
||||
|
||||
@@ -252,6 +252,7 @@ async function batchQuery(userId, Documents, Ip, parseConfig) {
|
||||
IsTourEnabled: x?.IsTourEnabled || false,
|
||||
FileAdapterId: x?.FileAdapterId || '',
|
||||
...(x?.SignatureType ? { SignatureType: x?.SignatureType } : {}),
|
||||
...(x?.NotifyOnSignatures ? { NotifyOnSignatures: x?.NotifyOnSignatures } : {}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -95,6 +95,53 @@ async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
|
||||
}
|
||||
}
|
||||
|
||||
// `sendNotifyMail` is used to send notification mail of signer signed the document
|
||||
async function sendNotifyMail(doc, signUser, mailProvider) {
|
||||
try {
|
||||
const auditTrailCount = doc?.AuditTrail?.filter(x => x.Activity === 'Signed')?.length || 0;
|
||||
const signersCount = doc?.Signers?.length;
|
||||
const remaingsign = signersCount - auditTrailCount;
|
||||
if (remaingsign > 1 && doc?.NotifyOnSignatures) {
|
||||
const sender = doc.ExtUserPtr;
|
||||
const pdfName = doc.Name;
|
||||
const creatorName = doc.ExtUserPtr.Name;
|
||||
const creatorEmail = doc.ExtUserPtr.Email;
|
||||
const signerName = signUser.Name;
|
||||
const signerEmail = signUser.Email;
|
||||
const mailLogo = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
|
||||
const viewDocUrl = `${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`;
|
||||
const subject = `Document "${pdfName}" has been signed by ${signerName}`;
|
||||
const body =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow:rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'>" +
|
||||
`<div><img src=${mailLogo} height='50' style='padding:20px' /></div><div style='padding:2px;font-family:system-ui;background-color:#47a3ad;'>` +
|
||||
`<p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed by ${signerName}</p>` +
|
||||
`</div><div style='padding:20px;font-family:system-ui;font-size:14px'><p>Dear ${creatorName},</p>` +
|
||||
`<p>${pdfName} has been signed by ${signerName} "${signerEmail}" successfully</p>` +
|
||||
`<p><a href=${viewDocUrl} target=_blank>View Document</a></p>` +
|
||||
`</div></div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ${creatorEmail} directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>`;
|
||||
|
||||
const params = {
|
||||
extUserId: sender.objectId,
|
||||
from: 'OpenSign™',
|
||||
recipient: creatorEmail,
|
||||
subject: subject,
|
||||
pdfName: pdfName,
|
||||
html: body,
|
||||
mailProvider: mailProvider,
|
||||
};
|
||||
await axios.post(serverUrl + '/functions/sendmailv3', params, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Master-Key': masterKEY,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in sendnotifymail', err);
|
||||
}
|
||||
}
|
||||
|
||||
// `sendCompletedMail` is used to send copy of completed document mail
|
||||
async function sendCompletedMail(obj) {
|
||||
const url = obj.doc?.SignedUrl;
|
||||
@@ -447,6 +494,7 @@ async function PDF(req) {
|
||||
sign // sign base64
|
||||
);
|
||||
sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser);
|
||||
sendNotifyMail(_resDoc, signUser, mailProvider);
|
||||
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
|
||||
if (updatedDoc && updatedDoc.isCompleted) {
|
||||
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl };
|
||||
|
||||
@@ -362,6 +362,8 @@ export default function reportJson(id, userId) {
|
||||
'SharedWith.Name',
|
||||
'SendinOrder',
|
||||
'FileAdapterId',
|
||||
'SignatureType',
|
||||
'NotifyOnSignatures',
|
||||
],
|
||||
};
|
||||
default:
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
export default async function updatePreferences(request) {
|
||||
if (!request?.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
}
|
||||
const SignatureType = request.params.SignatureType || [];
|
||||
const NotifyOnSignatures = request.params.NotifyOnSignatures;
|
||||
if (SignatureType?.length > 0 || NotifyOnSignatures !== undefined) {
|
||||
try {
|
||||
const orgQuery = new Parse.Query('contracts_Users');
|
||||
orgQuery.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: request.user.id,
|
||||
});
|
||||
const resUser = await orgQuery.first({ useMasterKey: true });
|
||||
if (resUser) {
|
||||
const newOrg = new Parse.Object('contracts_Users');
|
||||
newOrg.id = resUser.id;
|
||||
if (NotifyOnSignatures !== undefined) {
|
||||
newOrg.set('NotifyOnSignatures', NotifyOnSignatures);
|
||||
}
|
||||
if (SignatureType.length > 0) {
|
||||
const enabledSignTypes = SignatureType?.filter(x => x.enabled);
|
||||
const isDefaultSignTypeOnly =
|
||||
enabledSignTypes?.length === 1 && enabledSignTypes[0]?.name === 'default';
|
||||
|
||||
if (enabledSignTypes.length === 0) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
'At least one signature type should be enabled.'
|
||||
);
|
||||
} else if (isDefaultSignTypeOnly) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
'At least one signature type other than the default should be enabled.'
|
||||
);
|
||||
} else {
|
||||
newOrg.set('SignatureType', SignatureType);
|
||||
}
|
||||
}
|
||||
const updateUserRes = await newOrg.save(null, { useMasterKey: true });
|
||||
if (updateUserRes) {
|
||||
const _updateUserRes = JSON.parse(JSON.stringify(updateUserRes));
|
||||
return _updateUserRes;
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Premission denied.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in addorganization', err);
|
||||
const code = err?.code || 400;
|
||||
const msg = err?.message || 'Something went wrong.';
|
||||
throw new Parse.Error(code, msg);
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide parameters.');
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
*
|
||||
* @param {Parse} Parse
|
||||
*/
|
||||
exports.up = async Parse => {
|
||||
const docSchema = new Parse.Schema('contracts_Document');
|
||||
docSchema.addBoolean('NotifyOnSignatures');
|
||||
await docSchema.update();
|
||||
|
||||
const templateSchema = new Parse.Schema('contracts_Template');
|
||||
templateSchema.addBoolean('NotifyOnSignatures');
|
||||
await templateSchema.update();
|
||||
|
||||
const extUserSchema = new Parse.Schema('contracts_Users');
|
||||
extUserSchema.addBoolean('NotifyOnSignatures');
|
||||
return extUserSchema.update();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Parse} Parse
|
||||
*/
|
||||
exports.down = async Parse => {
|
||||
const docSchema = new Parse.Schema('contracts_Document');
|
||||
docSchema.deleteField('NotifyOnSignatures');
|
||||
await docSchema.update();
|
||||
|
||||
const templateSchema = new Parse.Schema('contracts_Template');
|
||||
templateSchema.deleteField('NotifyOnSignatures');
|
||||
await templateSchema.update();
|
||||
|
||||
const extUserSchema = new Parse.Schema('contracts_Users');
|
||||
docSchema.deleteField('NotifyOnSignatures');
|
||||
return extUserSchema.update();
|
||||
};
|
||||
Generated
+474
-569
File diff suppressed because it is too large
Load Diff
@@ -18,15 +18,15 @@
|
||||
"watch": "nodemon index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.685.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.688.0",
|
||||
"@aws-sdk/client-s3": "^3.691.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.691.0",
|
||||
"@parse/fs-files-adapter": "^3.0.0",
|
||||
"@parse/s3-files-adapter": "^3.0.0",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@signpdf/placeholder-pdf-lib": "^3.2.4",
|
||||
"@signpdf/signer-p12": "^3.2.4",
|
||||
"@signpdf/signpdf": "^3.2.4",
|
||||
"aws-sdk": "^2.1691.0",
|
||||
"aws-sdk": "^2.1692.0",
|
||||
"axios": "^1.7.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
@@ -35,7 +35,7 @@
|
||||
"generate-api-key": "^1.0.2",
|
||||
"googleapis": "^144.0.0",
|
||||
"mailgun.js": "^10.2.3",
|
||||
"mongoose": "^8.8.0",
|
||||
"mongoose": "^8.8.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"multer-s3": "^3.0.1",
|
||||
"node-forge": "^1.3.1",
|
||||
|
||||
Reference in New Issue
Block a user