mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-08-17 21:25:54 +02:00
@@ -152,7 +152,8 @@
|
||||
"Duplicate Template": "Vorlage duplizieren",
|
||||
"Duplicate": "Duplikat",
|
||||
"daily-mail-quota": "Tägliches E-Mail-Kontingent",
|
||||
"Save as template": "Als Vorlage speichern"
|
||||
"Save as template": "Als Vorlage speichern",
|
||||
"Fix & resend": "Korrigieren und erneut senden"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nr.",
|
||||
@@ -881,5 +882,10 @@
|
||||
"contact-billing-at-opensign": "Um weitere Plätze hinzuzufügen, kontaktieren Sie bitte OpenSign™ unter <1>billing@opensignlabs.com</1> für Unterstützung.",
|
||||
"title-length-alert": "Der Titel darf höchstens 250 Zeichen lang sein.",
|
||||
"note-length-alert": "Die Notiz darf höchstens 200 Zeichen lang sein.",
|
||||
"description-length-alert": "Die Beschreibung darf höchstens 500 Zeichen lang sein."
|
||||
"description-length-alert": "Die Beschreibung darf höchstens 500 Zeichen lang sein.",
|
||||
"fix-&-resend-document": "Dokument korrigieren und erneut senden",
|
||||
"do-you-want-recreate-document?": "Dadurch wird ein Entwurf aus diesem Dokument mit allen vorhandenen Feldern erstellt. Sind Sie sicher, dass Sie dieses Dokument neu erstellen möchten?",
|
||||
"start-editing": "Bearbeitung starten",
|
||||
"unsaved-changes-discard-them?": "Sie haben ungespeicherte Änderungen. Verwerfen?",
|
||||
"yes-discard": "Ja, verwerfen"
|
||||
}
|
||||
|
||||
@@ -152,7 +152,8 @@
|
||||
"Duplicate Template": "Duplicate template",
|
||||
"Duplicate": "Duplicate",
|
||||
"daily-mail-quota": "Daily Email Quota",
|
||||
"Save as template": "Save as template"
|
||||
"Save as template": "Save as template",
|
||||
"Fix & resend": "Fix & Resend"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Sr.No",
|
||||
@@ -881,5 +882,10 @@
|
||||
"contact-billing-at-opensign": "To add more seats, please contact OpenSign™ at <1>billing@opensignlabs.com</1> for assistance",
|
||||
"title-length-alert": "Title must be at most 250 characters long.",
|
||||
"note-length-alert": "Note must be at most 200 characters long.",
|
||||
"description-length-alert": "Description must be at most 500 characters long."
|
||||
"description-length-alert": "Description must be at most 500 characters long.",
|
||||
"fix-&-resend-document": "Fix & Resend Document",
|
||||
"do-you-want-recreate-document?": "This will create a draft from this document with all fields intact. Are you sure you want to recreate this document?",
|
||||
"start-editing": "Start Editing",
|
||||
"unsaved-changes-discard-them?": "You have unsaved changes. Discard them?",
|
||||
"yes-discard": "Yes, Discard"
|
||||
}
|
||||
|
||||
@@ -152,7 +152,8 @@
|
||||
"Duplicate Template": "Plantilla duplicada",
|
||||
"Duplicate": "Duplicada",
|
||||
"daily-mail-quota": "Cuota diaria de correos electrónicos",
|
||||
"Save as template": "Guardar como plantilla"
|
||||
"Save as template": "Guardar como plantilla",
|
||||
"Fix & resend": "Corregir y reenviar"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nº",
|
||||
@@ -881,5 +882,10 @@
|
||||
"contact-billing-at-opensign": "Para agregar más asientos, comuníquese con OpenSign™ a <1>billing@opensignlabs.com</1> para obtener ayuda.",
|
||||
"title-length-alert": "El título debe tener como máximo 250 caracteres.",
|
||||
"note-length-alert": "La nota debe tener como máximo 200 caracteres.",
|
||||
"description-length-alert": "La descripción debe tener como máximo 500 caracteres."
|
||||
"description-length-alert": "La descripción debe tener como máximo 500 caracteres.",
|
||||
"fix-&-resend-document": "Corregir y reenviar el documento",
|
||||
"do-you-want-recreate-document?": "Esto creará un borrador a partir de este documento con todos los campos intactos. ¿Está seguro de que desea recrear este documento?",
|
||||
"start-editing": "Comenzar a editar",
|
||||
"unsaved-changes-discard-them?": "Tienes cambios sin guardar. ¿Deseas descartarlos?",
|
||||
"yes-discard": "Sí, descartar"
|
||||
}
|
||||
|
||||
@@ -173,7 +173,8 @@
|
||||
"Duplicate Template": "dupliquer le modèle",
|
||||
"Duplicate": "Double",
|
||||
"daily-mail-quota": "Quota d'e-mails quotidien",
|
||||
"Save as template": "Enregistrer comme modèle"
|
||||
"Save as template": "Enregistrer comme modèle",
|
||||
"Fix & resend": "Corriger et renvoyer"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||
@@ -881,5 +882,10 @@
|
||||
"contact-billing-at-opensign": "Pour ajouter plus de places, veuillez contacter OpenSign™ à l'adresse <1>billing@opensignlabs.com</1> pour obtenir de l'aide.",
|
||||
"title-length-alert": "Le titre doit comporter au maximum 250 caractères.",
|
||||
"note-length-alert": "La note doit comporter au maximum 200 caractères.",
|
||||
"description-length-alert": "La description doit comporter au maximum 500 caractères"
|
||||
"description-length-alert": "La description doit comporter au maximum 500 caractères",
|
||||
"fix-&-resend-document": "Corriger et renvoyer le document",
|
||||
"do-you-want-recreate-document?": "Cela créera un brouillon à partir de ce document avec tous les champs intacts. Êtes-vous sûr de vouloir recréer ce document ?",
|
||||
"start-editing": "Commencer l'édition",
|
||||
"unsaved-changes-discard-them?": "Vous avez des modifications non enregistrées. Les supprimer ?",
|
||||
"yes-discard": "Oui, supprimer"
|
||||
}
|
||||
|
||||
@@ -152,7 +152,8 @@
|
||||
"Duplicate Template": "Duplica modello",
|
||||
"Duplicate": "Duplica",
|
||||
"daily-mail-quota": "Quota e-mail giornaliera",
|
||||
"Save as template": "Salva come modello"
|
||||
"Save as template": "Salva come modello",
|
||||
"Fix & resend": "Correggi e reinvia"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nr.",
|
||||
@@ -881,5 +882,10 @@
|
||||
"contact-billing-at-opensign": " Per aggiungere altri posti, contattare OpenSign™ all'indirizzo <1>billing@opensignlabs.com</1> per assistenza.",
|
||||
"title-length-alert": "Il titolo può contenere al massimo 250 caratteri.",
|
||||
"note-length-alert": "La nota può contenere al massimo 200 caratteri.",
|
||||
"description-length-alert": " La descrizione può contenere al massimo 500 caratteri."
|
||||
"description-length-alert": " La descrizione può contenere al massimo 500 caratteri.",
|
||||
"fix-&-resend-document": "Correggi e reinvia il documento",
|
||||
"do-you-want-recreate-document?": "Questo creerà una bozza da questo documento con tutti i campi intatti. Sei sicuro di voler ricreare questo documento?",
|
||||
"start-editing": "Inizia a modificare",
|
||||
"unsaved-changes-discard-them?": "Hai modifiche non salvate. Vuoi scartarle?",
|
||||
"yes-discard": "Sì, scarta"
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -1,5 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import { getFileName } from "../../constant/Utils";
|
||||
import React, { useState, useRef } from "react";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
convertBase64ToFile,
|
||||
generatePdfName,
|
||||
getFileName
|
||||
} from "../../constant/Utils";
|
||||
import {
|
||||
maxDescriptionLength,
|
||||
maxNoteLength,
|
||||
@@ -8,10 +13,22 @@ import {
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import SignersInput from "../shared/fields/SignersInput";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { SaveFileSize } from "../../constant/saveFileSize";
|
||||
|
||||
const EditTemplate = ({ template, onSuccess }) => {
|
||||
const EditTemplate = ({
|
||||
title,
|
||||
handleClose,
|
||||
pdfbase64,
|
||||
template,
|
||||
onSuccess,
|
||||
setPdfArrayBuffer,
|
||||
setPdfBase64Url
|
||||
}) => {
|
||||
const appName = "OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const inputFileRef = useRef(null);
|
||||
const [formData, setFormData] = useState({
|
||||
Name: template?.Name || "",
|
||||
Note: template?.Note || "",
|
||||
@@ -32,6 +49,31 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
AllowModifications: template?.AllowModifications || false,
|
||||
TimeToCompleteDays: template?.TimeToCompleteDays || 15
|
||||
});
|
||||
const [isUpdate, setIsUpdate] = useState(false);
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
const [uploadPdf, setUploadPdf] = useState({
|
||||
name: "",
|
||||
base64: "",
|
||||
url: ""
|
||||
});
|
||||
const handleDrop = (e) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer.files[0];
|
||||
handleFile(file);
|
||||
};
|
||||
|
||||
const handleFile = (file) => {
|
||||
if (file && file.type === "application/pdf") {
|
||||
handleReplaceFileValdition(file);
|
||||
// You can handle the file here
|
||||
} else {
|
||||
alert("Only pdf files are allowed.");
|
||||
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||
}
|
||||
};
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// `isValidURL` is used to check valid webhook url
|
||||
function isValidURL(value) {
|
||||
@@ -44,9 +86,63 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
}
|
||||
|
||||
const handleStrInput = (e) => {
|
||||
setIsUpdate(true);
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
const getPdfMetadataHash = async (pdfBytes) => {
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||
const pages = pdfDoc.getPages();
|
||||
const metaString = pages
|
||||
.map((page, index) => {
|
||||
const { width, height } = page.getSize();
|
||||
return `${index + 1}:${Math.round(width)}x${Math.round(height)}`;
|
||||
})
|
||||
.join("|");
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(metaString);
|
||||
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
||||
return Array.from(new Uint8Array(hashBuffer))
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
};
|
||||
|
||||
const handleFileInput = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
handleReplaceFileValdition(file);
|
||||
};
|
||||
const handleReplaceFileValdition = async (file) => {
|
||||
try {
|
||||
const basePdfBytes = base64ToArrayBuffer(pdfbase64);
|
||||
const expectedHash = await getPdfMetadataHash(basePdfBytes);
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = async (event) => {
|
||||
const uploadedPdfBytes = event.target.result;
|
||||
const uploadedHash = await getPdfMetadataHash(uploadedPdfBytes);
|
||||
|
||||
if (expectedHash === uploadedHash) {
|
||||
const arrayBuffer = uploadedPdfBytes;
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
const binaryString = Array.from(uint8Array)
|
||||
.map((b) => String.fromCharCode(b))
|
||||
.join("");
|
||||
const base64 = btoa(binaryString);
|
||||
const pdfName = generatePdfName(16);
|
||||
setIsUpdate(true);
|
||||
setUploadPdf((prev) => ({ ...prev, name: pdfName, base64: base64 }));
|
||||
// alert("✅ PDFs match (based on page number, width, height)");
|
||||
} else {
|
||||
alert("❌ PDF do NOT match based on page number, width, height");
|
||||
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
} catch (err) {
|
||||
alert("Error: " + err.message);
|
||||
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||
}
|
||||
};
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -67,6 +163,19 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
alert(t("description-length-alert"));
|
||||
return;
|
||||
}
|
||||
let pdfUrl;
|
||||
if (uploadPdf?.base64) {
|
||||
pdfUrl = await convertBase64ToFile(uploadPdf.name, uploadPdf.base64);
|
||||
setUploadPdf((prev) => ({ ...prev, url: pdfUrl }));
|
||||
const pdfBuffer = base64ToArrayBuffer(uploadPdf.base64);
|
||||
setPdfArrayBuffer && setPdfArrayBuffer(pdfBuffer);
|
||||
setPdfBase64Url && setPdfBase64Url(uploadPdf.base64);
|
||||
const tenantId =
|
||||
localStorage.getItem("TenantId") ||
|
||||
template?.ExtUserPtr?.TenantId?.objectId;
|
||||
const buffer = atob(uploadPdf.base64);
|
||||
SaveFileSize(buffer.length, pdfUrl, tenantId);
|
||||
}
|
||||
const isChecked = formData.SendinOrder === "true" ? true : false;
|
||||
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
|
||||
const AutoReminder = formData?.AutomaticReminders || false;
|
||||
@@ -88,6 +197,7 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
}
|
||||
const data = {
|
||||
...formData,
|
||||
...(pdfUrl ? { URL: pdfUrl } : {}),
|
||||
SendinOrder: isChecked,
|
||||
IsEnableOTP: IsEnableOTP,
|
||||
IsTourEnabled: isTourEnabled,
|
||||
@@ -99,6 +209,7 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
|
||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||
const handleNotifySignChange = (value) => {
|
||||
setIsUpdate(true);
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
||||
};
|
||||
const handleBcc = (data) => {
|
||||
@@ -108,226 +219,304 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
Name: item?.label,
|
||||
Email: item?.email
|
||||
}));
|
||||
setIsUpdate(true);
|
||||
setFormData((prev) => ({ ...prev, Bcc: trimEmail }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditTemplateClose = () => {
|
||||
if (isUpdate) {
|
||||
setShowConfirm(true);
|
||||
} else {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
const discardChanges = () => {
|
||||
setShowConfirm(false);
|
||||
handleClose();
|
||||
};
|
||||
return (
|
||||
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
||||
<div className="text-base-content">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("report-heading.File")}
|
||||
</label>
|
||||
<div className="op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs">
|
||||
{getFileName(template.URL)}
|
||||
</div>
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={isUpdate ? `${title} (unsaved)` : title}
|
||||
handleClose={handleEditTemplateClose}
|
||||
>
|
||||
<ModalUi isOpen={showConfirm} showClose={false}>
|
||||
<div className="p-[20px]">
|
||||
<p className="text-base font-normal text-base-content py-[5px] md:py-[6px] px-[5px]">
|
||||
{t("unsaved-changes-discard-them?")}
|
||||
</p>
|
||||
<div className="flex items-center mt-2.5 gap-2 md:gap-3 text-white">
|
||||
<button
|
||||
className="op-btn op-btn-primary px-6"
|
||||
onClick={discardChanges}
|
||||
>
|
||||
{t("yes-discard")}
|
||||
</button>
|
||||
<button
|
||||
className="op-btn op-btn-secondary px-4 md:px-6"
|
||||
onClick={() => setShowConfirm(false)}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("Title")}
|
||||
<span className="text-[13px] text-[red]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Name"
|
||||
value={formData.Name}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="Note" className="text-[13px]">
|
||||
{t("report-heading.Note")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Note"
|
||||
id="Note"
|
||||
value={formData.Note}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="Description" className="text-[13px]">
|
||||
{t("description")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Description"
|
||||
id="Description"
|
||||
value={formData.Description}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label className="text-[13px]">{t("send-in-order")}</label>
|
||||
<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>
|
||||
</ModalUi>
|
||||
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
||||
<div className="text-base-content">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("report-heading.File")}
|
||||
</label>
|
||||
<div
|
||||
className="border-[1.5px] border-dashed border-gray-300 rounded-lg px-4 py-6 text-center text-gray-500 bg-white cursor-pointer hover:border-base-content transition"
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
onClick={() => inputFileRef?.current?.click()}
|
||||
>
|
||||
<label
|
||||
htmlFor="fileUpload"
|
||||
className="cursor-pointer text-center mb-0"
|
||||
>
|
||||
Browse or drag & drop a new file to replace the existng one
|
||||
</label>
|
||||
</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>
|
||||
<input
|
||||
ref={inputFileRef}
|
||||
type="file"
|
||||
className="hidden"
|
||||
accept="application/pdf"
|
||||
onChange={(e) => handleFileInput(e)}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
/>
|
||||
{uploadPdf?.name && (
|
||||
<div
|
||||
onClick={() => inputFileRef?.current?.click()}
|
||||
className="mt-2 cursor-pointer op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs"
|
||||
>
|
||||
selected:{" "}
|
||||
{uploadPdf?.url
|
||||
? `${uploadPdf?.name}.pdf`
|
||||
: getFileName(template.URL)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("Title")}
|
||||
<span className="text-[13px] text-[red]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Name"
|
||||
value={formData.Name}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="Note" className="text-[13px]">
|
||||
{t("report-heading.Note")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Note"
|
||||
id="Note"
|
||||
value={formData.Note}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="Description" className="text-[13px]">
|
||||
{t("description")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Description"
|
||||
id="Description"
|
||||
value={formData.Description}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label className="text-[13px]">{t("send-in-order")}</label>
|
||||
<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>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<label className="block">
|
||||
<span>
|
||||
{t("enable-tour")}
|
||||
<a data-tooltip-id="istourenabled-tooltip" className="ml-1">
|
||||
<div className="text-xs mt-3">
|
||||
<label className="block">
|
||||
<span>
|
||||
{t("enable-tour")}
|
||||
<a data-tooltip-id="istourenabled-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>{" "}
|
||||
</span>
|
||||
<Tooltip id="istourenabled-tooltip" className="z-50">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("enable-tour")}</p>
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">{t("yes")}: </span>
|
||||
<span>{t("istourenabled-help.p1")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">{t("no")}: </span>
|
||||
<span>{t("istourenabled-help.p2")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p>{t("istourenabled-help.p3", { appName: appName })}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<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>
|
||||
<div className="text-xs mt-3">
|
||||
<label>
|
||||
{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>{" "}
|
||||
</span>
|
||||
<Tooltip id="istourenabled-tooltip" className="z-50">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("enable-tour")}</p>
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">{t("yes")}: </span>
|
||||
<span>{t("istourenabled-help.p1")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">{t("no")}: </span>
|
||||
<span>{t("istourenabled-help.p2")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
<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>
|
||||
<p>{t("istourenabled-help.p3", { appName: appName })}</p>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className={`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={`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>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<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>
|
||||
<div className="text-xs mt-3">
|
||||
<label>
|
||||
{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>{" "}
|
||||
<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={`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={`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 className="text-xs mt-3">
|
||||
<SignersInput
|
||||
label={t("Bcc")}
|
||||
initialData={template?.Bcc}
|
||||
onChange={handleBcc}
|
||||
helptextZindex={50}
|
||||
helpText={t("bcc-help")}
|
||||
isCaptureAllData
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<SignersInput
|
||||
label={t("Bcc")}
|
||||
initialData={template?.Bcc}
|
||||
onChange={handleBcc}
|
||||
helptextZindex={50}
|
||||
helpText={t("bcc-help")}
|
||||
isCaptureAllData
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">Redirect Url</label>
|
||||
<input
|
||||
name="RedirectUrl"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
value={formData.RedirectUrl}
|
||||
onChange={handleStrInput}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
{t("time-to-complete")}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="TimeToCompleteDays"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
value={formData.TimeToCompleteDays}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-[1rem] flex justify-start">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("submit")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">Redirect Url</label>
|
||||
<input
|
||||
name="RedirectUrl"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
value={formData.RedirectUrl}
|
||||
onChange={handleStrInput}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
{t("time-to-complete")}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="TimeToCompleteDays"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
value={formData.TimeToCompleteDays}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-[1rem] flex justify-start">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("submit")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const DateFormatSelector = (props) => {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="flex flex-col md:flex-row md:gap-4 mt-[0.75rem] text-[12px]">
|
||||
<div className="flex flex-row gap-4 mt-[0.75rem] text-[12px]">
|
||||
<div className="flex items-center gap-2 ml-2">
|
||||
<input
|
||||
type="radio"
|
||||
|
||||
@@ -219,7 +219,6 @@ export default function reportJson(id) {
|
||||
btnId: "44534",
|
||||
hoverLabel: "option",
|
||||
btnColor: "",
|
||||
restrictBtn: true,
|
||||
textColor: "black",
|
||||
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
|
||||
action: "option",
|
||||
@@ -228,9 +227,18 @@ export default function reportJson(id) {
|
||||
btnId: "0940",
|
||||
btnLabel: "Save as template",
|
||||
hoverLabel: "Save as template",
|
||||
restrictBtn: true,
|
||||
btnIcon: "fa-light fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "saveastemplate"
|
||||
},
|
||||
{
|
||||
btnId: "8440",
|
||||
btnLabel: "Fix & resend",
|
||||
hoverLabel: "Fix & resend",
|
||||
btnIcon: "fa-light fa-paper-plane",
|
||||
redirectUrl: "",
|
||||
action: "recreatedocument"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ const Preferences = () => {
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<h1 className="ml-4 mt-3 mb-2 font-semibold">
|
||||
<h1 className="ml-4 mt-3 text-lg mb-2 font-semibold text-base-content">
|
||||
{appName} {t("Preferences")}{" "}
|
||||
<span>
|
||||
<Tooltip message={`${appName} ${t("Preferences")}`} />
|
||||
@@ -362,7 +362,9 @@ const Preferences = () => {
|
||||
role="tab"
|
||||
className={`${
|
||||
activeTab === ind ? "op-tab-active" : ""
|
||||
} op-tab text-xs md:text-base pb-2 md:pb-0 }`}
|
||||
} op-tab text-xs md:text-base pb-2 md:pb-0 transition-all `}
|
||||
aria-selected={activeTab === ind}
|
||||
aria-controls={`panel-${tabData.title}`}
|
||||
>
|
||||
<i className={tabData.icon}></i>
|
||||
<span className="ml-1 md:ml-2">{tabData.title}</span>
|
||||
@@ -370,275 +372,330 @@ const Preferences = () => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center md:justify-start mt-3 md:mt-4 break-all">
|
||||
<div
|
||||
id="panel-general"
|
||||
className="px-6 pt-4 pb-6"
|
||||
aria-labelledby="tab-general"
|
||||
role="tabpanel"
|
||||
>
|
||||
{activeTab === 0 ? (
|
||||
<div className="ml-4 mt-1 mb-2 flex flex-col">
|
||||
<div className="mb-[0.75rem]">
|
||||
<label
|
||||
className="mb-[0.7rem] text-[12px]"
|
||||
htmlFor="signaturetype"
|
||||
>
|
||||
<span className="font-medium text-[14px]">
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 md:gap-x-8">
|
||||
{/* Left Column - Signature Settings */}
|
||||
<div className="md:col-span-5 flex flex-col">
|
||||
{/* Signature Types Section */}
|
||||
<div className="mb-6">
|
||||
<label
|
||||
className="text-[14px] mb-[0.7rem] font-medium"
|
||||
htmlFor="signaturetype"
|
||||
>
|
||||
{t("allowed-signature-types")}
|
||||
</span>
|
||||
<a data-tooltip-id="signtypes-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="signtypes-tooltip"
|
||||
className="z-[999]"
|
||||
>
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">
|
||||
{t("allowed-signature-types")}
|
||||
</p>
|
||||
<p>{t("allowed-signature-types-help.p1")}</p>
|
||||
<div className="p-[5px] ml-2">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">Draw: </span>
|
||||
<span>
|
||||
{t("allowed-signature-types-help.l1")}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">Type: </span>
|
||||
<span>
|
||||
{t("allowed-signature-types-help.l2")}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">Upload: </span>
|
||||
<span>
|
||||
{t("allowed-signature-types-help.l3")}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">Default: </span>
|
||||
<span>
|
||||
{t("allowed-signature-types-help.l4")}
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<div className="ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
|
||||
{signatureType.map((type, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
<a
|
||||
data-tooltip-id="signtypes-tooltip"
|
||||
className="ml-1"
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
name="signaturetype"
|
||||
onChange={() => handleCheckboxChange(i)}
|
||||
checked={type.enabled}
|
||||
/>
|
||||
<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="signtypes-tooltip"
|
||||
className="z-[999]"
|
||||
>
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">
|
||||
{t("allowed-signature-types")}
|
||||
</p>
|
||||
<p>{t("allowed-signature-types-help.p1")}</p>
|
||||
<div className="p-[5px] ml-2">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">Draw: </span>
|
||||
<span>
|
||||
{t("allowed-signature-types-help.l1")}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">Type: </span>
|
||||
<span>
|
||||
{t("allowed-signature-types-help.l2")}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">Upload: </span>
|
||||
<span>
|
||||
{t("allowed-signature-types-help.l3")}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">Default: </span>
|
||||
<span>
|
||||
{t("allowed-signature-types-help.l4")}
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row gap-3 mb-2">
|
||||
{signatureType.map((type, i) => (
|
||||
<div
|
||||
className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default capitalize"
|
||||
title={`Enabling this allow signers to ${type.name} signature`}
|
||||
key={i}
|
||||
className="flex flex-row gap-2 items-center"
|
||||
>
|
||||
{type?.name === "typed" ? "type" : type?.name}
|
||||
<input
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
id={`signature-type-${type.name}`}
|
||||
name="signaturetype"
|
||||
onChange={() => handleCheckboxChange(i)}
|
||||
checked={type.enabled}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`signature-type-${type.name}`}
|
||||
className="text-sm font-medium text-base-content hover:underline underline-offset-2 cursor-pointer capitalize mb-0"
|
||||
title={`Enabling this allows signers to ${type.name} signature`}
|
||||
>
|
||||
{type?.name === "typed" ? "type" : type?.name}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<label className="mb-[0.7rem] text-[12px]">
|
||||
<span className="text-[14px] font-medium">
|
||||
|
||||
{/* Notify on Signatures Section */}
|
||||
<div className="mb-6">
|
||||
<label className="text-[14px] mb-[0.7rem] font-medium">
|
||||
{t("notify-on-signatures")}
|
||||
</span>
|
||||
<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]">
|
||||
<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>
|
||||
<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]">
|
||||
<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>
|
||||
</label>
|
||||
<div className="flex flex-row gap-6">
|
||||
<div className={"flex items-center gap-2"}>
|
||||
<input
|
||||
id="notify-yes"
|
||||
className="op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={isNotifyOnSignatures === true}
|
||||
/>
|
||||
<label
|
||||
htmlFor="notify-yes"
|
||||
className="text-sm text-base-content cursor-pointer mb-0"
|
||||
>
|
||||
{t("yes")}
|
||||
</label>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className={"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] cursor-default capitalize">
|
||||
{t("yes")}
|
||||
</div>
|
||||
</div>
|
||||
<div className={"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] cursor-default capitalize">
|
||||
{t("no")}
|
||||
<div className={"flex items-center gap-2"}>
|
||||
<input
|
||||
id="notify-no"
|
||||
className="op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(false)}
|
||||
checked={isNotifyOnSignatures === false}
|
||||
/>
|
||||
<label
|
||||
htmlFor="notify-no"
|
||||
className="text-sm text-base-content cursor-pointer mb-0"
|
||||
>
|
||||
{t("no")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<TimezoneSelector
|
||||
timezone={timezone}
|
||||
setTimezone={setTimezone}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<DateFormatSelector
|
||||
timezone={timezone}
|
||||
dateFormat={dateFormat}
|
||||
is12HourTime={is12HourTime}
|
||||
setIs12HourTime={setIs12HourTime}
|
||||
setDateFormat={setDateFormat}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.75rem] text-[12px]">
|
||||
<label className="block mb-[0.7rem]">
|
||||
<span className="text-[14px] font-medium">
|
||||
|
||||
{/* Send in Order Section */}
|
||||
<div className="mb-6">
|
||||
<label className="text-[14px] mb-[0.7rem] font-medium">
|
||||
{t("send-in-order")}
|
||||
</span>
|
||||
<a
|
||||
data-tooltip-id="sendInOrder-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="sendInOrder-tooltip"
|
||||
className="z-[999]"
|
||||
>
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("send-in-order")}</p>
|
||||
<p>{t("send-in-order-help.p1")}</p>
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
{t("yes")}:{" "}
|
||||
</span>
|
||||
<span>{t("send-in-order-help.p2")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">{t("no")}: </span>
|
||||
<span>{t("send-in-order-help.p3")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
<a
|
||||
data-tooltip-id="sendInOrder-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="sendInOrder-tooltip"
|
||||
className="z-[999]"
|
||||
>
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("send-in-order")}</p>
|
||||
<p>{t("send-in-order-help.p1")}</p>
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
{t("yes")}:{" "}
|
||||
</span>
|
||||
<span>{t("send-in-order-help.p2")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
{t("no")}:{" "}
|
||||
</span>
|
||||
<span>{t("send-in-order-help.p3")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p>{t("send-in-order-help.p4")}</p>
|
||||
</div>
|
||||
<p>{t("send-in-order-help.p4")}</p>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<div className="flex flex-row gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
id="order-yes"
|
||||
type="radio"
|
||||
value={true}
|
||||
className="op-radio op-radio-xs"
|
||||
name="SendinOrder"
|
||||
checked={sendinOrder}
|
||||
onChange={handleSendinOrderInput}
|
||||
/>
|
||||
<label
|
||||
htmlFor="order-yes"
|
||||
className="text-sm text-base-content cursor-pointer mb-0"
|
||||
>
|
||||
{t("yes")}
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
id="order-no"
|
||||
type="radio"
|
||||
value={false}
|
||||
name="SendinOrder"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={!sendinOrder}
|
||||
onChange={handleSendinOrderInput}
|
||||
/>
|
||||
<label
|
||||
htmlFor="order-no"
|
||||
className="text-sm text-base-content cursor-pointer mb-0"
|
||||
>
|
||||
{t("no")}
|
||||
</label>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<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="SendinOrder"
|
||||
checked={sendinOrder}
|
||||
onChange={handleSendinOrderInput}
|
||||
/>
|
||||
<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="SendinOrder"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={!sendinOrder}
|
||||
onChange={handleSendinOrderInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.75rem] text-[12px]">
|
||||
<label className="block mb-[0.7rem]">
|
||||
<span className="text-[14px] font-medium">
|
||||
|
||||
{/* Enable Tour Section */}
|
||||
<div className="mb-6">
|
||||
<label className="text-[14px] mb-[0.7rem] font-medium">
|
||||
{t("enable-tour")}
|
||||
</span>
|
||||
<a
|
||||
data-tooltip-id="istourenabled-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="istourenabled-tooltip"
|
||||
className="z-[999]"
|
||||
>
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("enable-tour")}</p>
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
{t("yes")}:{" "}
|
||||
</span>
|
||||
<span>{t("istourenabled-help.p1")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">{t("no")}: </span>
|
||||
<span>{t("istourenabled-help.p2")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
<a
|
||||
data-tooltip-id="istourenabled-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="istourenabled-tooltip"
|
||||
className="z-[999]"
|
||||
>
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("enable-tour")}</p>
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
{t("yes")}:{" "}
|
||||
</span>
|
||||
<span>{t("istourenabled-help.p1")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
{t("no")}:{" "}
|
||||
</span>
|
||||
<span>{t("istourenabled-help.p2")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p>
|
||||
{t("istourenabled-help.p3", {
|
||||
appName: appName
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
{t("istourenabled-help.p3", {
|
||||
appName: appName
|
||||
})}
|
||||
</p>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<div className="flex flex-row gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
id="tour-yes"
|
||||
type="radio"
|
||||
value={true}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsTourEnabled"
|
||||
checked={isTourEnabled}
|
||||
onChange={handleTourInput}
|
||||
/>
|
||||
<label
|
||||
htmlFor="tour-yes"
|
||||
className="text-sm text-base-content cursor-pointer mb-0"
|
||||
>
|
||||
{t("yes")}
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
id="tour-no"
|
||||
type="radio"
|
||||
value={false}
|
||||
name="IsTourEnabled"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={!isTourEnabled}
|
||||
onChange={handleTourInput}
|
||||
/>
|
||||
<label
|
||||
htmlFor="tour-no"
|
||||
className="text-sm text-base-content cursor-pointer mb-0"
|
||||
>
|
||||
{t("no")}
|
||||
</label>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<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={isTourEnabled}
|
||||
onChange={handleTourInput}
|
||||
/>
|
||||
<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={!isTourEnabled}
|
||||
onChange={handleTourInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
|
||||
{/* Right Column - Timezone & Date Settings */}
|
||||
<div className="md:col-span-7 flex flex-col">
|
||||
<div className="mb-6">
|
||||
<TimezoneSelector
|
||||
timezone={timezone}
|
||||
setTimezone={setTimezone}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<DateFormatSelector
|
||||
timezone={timezone}
|
||||
dateFormat={dateFormat}
|
||||
is12HourTime={is12HourTime}
|
||||
setIs12HourTime={setIs12HourTime}
|
||||
setDateFormat={setDateFormat}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save Button - Full Width */}
|
||||
<div className="md:col-span-12 flex justify-start mt-2">
|
||||
<button
|
||||
className="op-btn op-btn-primary w-[110px]"
|
||||
onClick={handleSave}
|
||||
@@ -648,7 +705,7 @@ const Preferences = () => {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col mx-4 mb-4">
|
||||
<div className="flex flex-col mb-4">
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-[14px] mb-[0.7rem] font-medium">
|
||||
{t("request-email")}
|
||||
@@ -660,7 +717,7 @@ const Preferences = () => {
|
||||
>
|
||||
<div className="text-lg font-normal">
|
||||
<label className="text-sm">
|
||||
{t("subject")}
|
||||
{t("subject")}{" "}
|
||||
<Tooltip
|
||||
id={"request-sub-tooltip"}
|
||||
message={`${t(
|
||||
@@ -682,7 +739,7 @@ const Preferences = () => {
|
||||
</div>
|
||||
<div className="text-lg font-normal py-2">
|
||||
<label className="text-sm mt-3">
|
||||
{t("body")}
|
||||
{t("body")}{" "}
|
||||
<Tooltip
|
||||
id={"request-body-tooltip"}
|
||||
message={`${t(
|
||||
@@ -730,7 +787,7 @@ const Preferences = () => {
|
||||
>
|
||||
<div className="text-lg font-normal">
|
||||
<label className="text-sm">
|
||||
{t("subject")}
|
||||
{t("subject")}{" "}
|
||||
<Tooltip
|
||||
id={"complete-sub-tooltip"}
|
||||
message={`${t(
|
||||
@@ -752,7 +809,7 @@ const Preferences = () => {
|
||||
</div>
|
||||
<div className="text-lg font-normal py-2">
|
||||
<label className="text-sm mt-3">
|
||||
{t("body")}
|
||||
{t("body")}{" "}
|
||||
<Tooltip
|
||||
id={"complete-body-tooltip"}
|
||||
message={`${t(
|
||||
|
||||
@@ -733,20 +733,18 @@ const TemplatePlaceholder = () => {
|
||||
let isLabel = false;
|
||||
let unfilledTextWidgetId = "";
|
||||
//condition is used to check text widget data is empty or have response
|
||||
if (getPrefill) {
|
||||
if (prefillPlaceholder) {
|
||||
prefillPlaceholder.map((data) => {
|
||||
if (!isLabel) {
|
||||
const unfilledTextWidgets = data.pos.find(
|
||||
(position) => !position.options.response
|
||||
);
|
||||
if (unfilledTextWidgets) {
|
||||
isLabel = true;
|
||||
unfilledTextWidgetId = unfilledTextWidgets.key;
|
||||
}
|
||||
if (getPrefill && prefillPlaceholder) {
|
||||
prefillPlaceholder.map((data) => {
|
||||
if (!isLabel) {
|
||||
const unfilledTextWidgets = data.pos.find(
|
||||
(position) => !position.options.response
|
||||
);
|
||||
if (unfilledTextWidgets) {
|
||||
isLabel = true;
|
||||
unfilledTextWidgetId = unfilledTextWidgets.key;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (getPrefill && isLabel) {
|
||||
setUnSignedWidgetId(unfilledTextWidgetId);
|
||||
@@ -1250,6 +1248,7 @@ const TemplatePlaceholder = () => {
|
||||
? { RedirectUrl: updateTemplate?.[0]?.RedirectUrl }
|
||||
: {};
|
||||
const data = {
|
||||
...(updateTemplate?.[0]?.URL ? { URL: updateTemplate?.[0]?.URL } : {}),
|
||||
Name: updateTemplate?.[0]?.Name || "",
|
||||
Note: updateTemplate?.[0]?.Note || "",
|
||||
Description: updateTemplate?.[0]?.Description || "",
|
||||
@@ -1964,16 +1963,17 @@ const TemplatePlaceholder = () => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ModalUi
|
||||
title={t("edit-template")}
|
||||
isOpen={isEditTemplate}
|
||||
handleClose={handleEditTemplateModal}
|
||||
>
|
||||
{isEditTemplate && (
|
||||
<EditTemplate
|
||||
title={t("edit-template")}
|
||||
handleClose={handleEditTemplateModal}
|
||||
pdfbase64={pdfBase64Url}
|
||||
template={pdfDetails?.[0]}
|
||||
onSuccess={handleEditTemplateForm}
|
||||
setPdfArrayBuffer={setPdfArrayBuffer}
|
||||
setPdfBase64Url={setPdfBase64Url}
|
||||
/>
|
||||
</ModalUi>
|
||||
)}
|
||||
<WidgetNameModal
|
||||
signatureType={signatureType}
|
||||
widgetName={widgetName}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import pad from "../assets/images/pad.svg";
|
||||
import recreatedoc from "../assets/images/recreatedoc.png";
|
||||
import { Link, useLocation, useNavigate } from "react-router";
|
||||
import axios from "axios";
|
||||
import ModalUi from "./ModalUi";
|
||||
@@ -19,7 +20,8 @@ import {
|
||||
getTenantDetails,
|
||||
handleSignatureType,
|
||||
replaceMailVaribles,
|
||||
signatureTypes
|
||||
signatureTypes,
|
||||
openInNewTab
|
||||
} from "../constant/Utils";
|
||||
import EditorToolbar, {
|
||||
module1,
|
||||
@@ -412,6 +414,8 @@ const ReportTable = (props) => {
|
||||
setIsModal({ [`edit_${item.objectId}`]: true });
|
||||
} else if (act.action === "saveastemplate") {
|
||||
setIsModal({ [`saveastemplate_${item.objectId}`]: true });
|
||||
} else if (act.action === "recreatedocument") {
|
||||
setIsModal({ [`recreatedocument_${item.objectId}`]: true });
|
||||
} else if (act.action) {
|
||||
setIsModal({ [`extendexpiry_${item.objectId}`]: true });
|
||||
}
|
||||
@@ -1352,6 +1356,24 @@ const ReportTable = (props) => {
|
||||
setUserDetails({});
|
||||
};
|
||||
|
||||
const handleRecreateDoc = async (item) => {
|
||||
setActLoader({ [item.objectId]: true });
|
||||
try {
|
||||
const res = await Parse.Cloud.run("recreatedoc", {
|
||||
docId: item.objectId
|
||||
});
|
||||
if (res) {
|
||||
openInNewTab(`/placeHolderSign/${res.objectId}`, "_self");
|
||||
}
|
||||
} catch (err) {
|
||||
handleCloseModal();
|
||||
showAlert("danger", err.message);
|
||||
// showAlert("danger", t("something-went-wrong-mssg"));
|
||||
console.log("Err while create duplicate template", err);
|
||||
} finally {
|
||||
setActLoader({});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{Object.keys(actLoader)?.length > 0 && (
|
||||
@@ -1826,6 +1848,45 @@ const ReportTable = (props) => {
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{isModal["recreatedocument_" + item.objectId] && (
|
||||
<ModalUi isOpen handleClose={handleCloseModal}>
|
||||
{actLoader[item.objectId] && (
|
||||
<div className="absolute h-full w-full flex justify-center items-center rounded-box bg-black/30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||
{t("fix-&-resend-document")}
|
||||
</h3>
|
||||
<div className="p-[15px] md:p-[20px]">
|
||||
<div className="text-lg font-normal text-center">
|
||||
<img
|
||||
src={recreatedoc}
|
||||
alt="recreate-doc"
|
||||
className="mx-auto w-[200px] h-auto"
|
||||
/>
|
||||
<p className="text-sm md:text-base md:px-2 mt-2">
|
||||
{t("do-you-want-recreate-document?")}
|
||||
</p>
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-2.5" />
|
||||
<div className="flex items-center justify-center mt-[14px] md:mt-[16px] gap-2 text-white">
|
||||
<button
|
||||
onClick={() => handleRecreateDoc(item)}
|
||||
className="op-btn op-btn-primary focus:outline-none text-sm relative px-4"
|
||||
>
|
||||
{t("start-editing")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCloseModal}
|
||||
className="op-btn op-btn-secondary focus:outline-none text-sm relative px-8"
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
{isModal["saveastemplate_" + item.objectId] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
FROM node:22.14.0
|
||||
|
||||
# install java
|
||||
RUN wget https://download.oracle.com/java/23/latest/jdk-23_linux-x64_bin.deb \
|
||||
RUN wget https://downloads.opensign.me/jdk-23_linux-x64_bin.deb \
|
||||
&& dpkg -i jdk-23_linux-x64_bin.deb
|
||||
|
||||
# Set the working directory inside the container
|
||||
|
||||
Binary file not shown.
@@ -2,6 +2,8 @@ import dotenv from 'dotenv';
|
||||
import { format, toZonedTime } from 'date-fns-tz';
|
||||
import { getSignedLocalUrl } from './cloud/parsefunction/getSignedUrl.js';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import crypto from 'node:crypto';
|
||||
import axios from 'axios';
|
||||
dotenv.config();
|
||||
|
||||
export const cloudServerUrl = 'http://localhost:8080/app';
|
||||
@@ -148,6 +150,7 @@ export const useLocal = process.env.USE_LOCAL ? process.env.USE_LOCAL.toLowerCas
|
||||
export const smtpsecure = process.env.SMTP_PORT && process.env.SMTP_PORT !== '465' ? false : true;
|
||||
export const smtpenable =
|
||||
process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false;
|
||||
export const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
// `generateId` is used to unique Id for fileAdapter
|
||||
export function generateId(length) {
|
||||
@@ -297,3 +300,39 @@ export function formatDateTime(date, dateFormat, timeZone, is12Hour) {
|
||||
? format(zonedDate, `${selectFormat(dateFormat)}, ${timeFormat} 'GMT' XXX`, { timeZone })
|
||||
: formatTimeInTimezone(date, timeZone);
|
||||
}
|
||||
|
||||
// Utility: Convert base64 to buffer
|
||||
export const base64ToBuffer = base64 => Buffer.from(base64, 'base64');
|
||||
|
||||
// Utility: Generate SHA-256 hash from PDF page metadata
|
||||
const getPdfMetadataHash = async pdfBytes => {
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||
const metaString = pdfDoc
|
||||
.getPages()
|
||||
.map((page, index) => {
|
||||
const { width, height } = page.getSize();
|
||||
return `${index + 1}:${Math.round(width)}x${Math.round(height)}`;
|
||||
})
|
||||
.join('|');
|
||||
|
||||
return crypto.createHash('sha256').update(metaString).digest('hex');
|
||||
};
|
||||
// Utility: Validate if uploaded file matches original template PDF
|
||||
export const handleReplaceFileValidation = async (baseFileUrl, newFileBase64) => {
|
||||
try {
|
||||
const { data } = await axios.get(baseFileUrl, { responseType: 'arraybuffer' });
|
||||
const basePdfBytes = Buffer.from(data);
|
||||
const uploadedPdfBytes = base64ToBuffer(newFileBase64);
|
||||
|
||||
const baseHash = await getPdfMetadataHash(basePdfBytes);
|
||||
const uploadedHash = await getPdfMetadataHash(uploadedPdfBytes);
|
||||
|
||||
if (baseHash === uploadedHash) {
|
||||
return { base64: newFileBase64 };
|
||||
}
|
||||
return { error: 'PDFs do NOT match based on page number, width, and height' };
|
||||
} catch (err) {
|
||||
console.error('Validation Error:', err.message);
|
||||
return { error: err.message };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,6 +51,7 @@ import editContact from './parsefunction/editContact.js';
|
||||
import forwardDoc from './parsefunction/ForwardDoc.js';
|
||||
import saveAsTemplate from './parsefunction/saveAsTemplate.js';
|
||||
import updateTenant from './parsefunction/updateTenant.js';
|
||||
import recreateDocument from './parsefunction/recreateDocument.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);
|
||||
@@ -112,3 +113,4 @@ Parse.Cloud.define('editcontact', editContact);
|
||||
Parse.Cloud.define('forwarddoc', forwardDoc);
|
||||
Parse.Cloud.define('saveastemplate', saveAsTemplate);
|
||||
Parse.Cloud.define('updatetenant', updateTenant);
|
||||
Parse.Cloud.define('recreatedoc', recreateDocument);
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { exec } from 'child_process';
|
||||
|
||||
export default function DigitalSign(pdf, pfx, details) {
|
||||
const signcmd = `java -jar PDFDigitalSigner.jar "${pdf}" "${pfx.name}" "${pfx.passphrase}" "${details.name}" "${details.location}" "${details.reason}"`;
|
||||
// const signcmd = `java -jar PDFDigitalSigner.jar "${pdf}" keystore.pfx opensign "${details.name}" "${details.location}" "${details.reason}"`;
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(signcmd, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`Error: ${error.message}`);
|
||||
}
|
||||
if (stderr) {
|
||||
reject(`stderr: ${stderr}`);
|
||||
}
|
||||
// Resolve the promise with the output
|
||||
resolve(stdout.trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function retryAsync(fn, args = [], retries = 3, delay = 2000) {
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
try {
|
||||
const response = await fn(...args); // Try executing the async function
|
||||
if (response) return { response, attempt }; // Stop retrying if response is received
|
||||
} catch (error) {
|
||||
if (attempt < retries) {
|
||||
console.log(`Attempt ${attempt} failed. Retrying in ${delay / 1000} seconds...\n`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay)); // Wait before retrying
|
||||
} else {
|
||||
return { error, attempt };
|
||||
// throw new Error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,16 @@ const masterKEY = process.env.MASTER_KEY;
|
||||
const eSignName = 'OpenSign';
|
||||
const eSigncontact = 'hello@opensignlabs.com';
|
||||
|
||||
async function unlinkFile(path) {
|
||||
if (fs.existsSync(path)) {
|
||||
try {
|
||||
fs.unlinkSync(path);
|
||||
} catch (err) {
|
||||
console.log('Err in unlink file: ', path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `updateDoc` is used to create url in from pdfFile
|
||||
async function uploadFile(pdfName, filepath) {
|
||||
try {
|
||||
@@ -33,8 +43,8 @@ async function uploadFile(pdfName, filepath) {
|
||||
return { imageUrl: fileUrl };
|
||||
} catch (err) {
|
||||
console.log('Err ', err);
|
||||
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
|
||||
fs.unlinkSync(filepath);
|
||||
// below line of code is used to remove exported signed pdf file from exports folder
|
||||
unlinkFile(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,10 +250,10 @@ async function sendCompletedMail(obj) {
|
||||
});
|
||||
// console.log('res', res.data.result);
|
||||
if (res.data?.result?.status !== 'success') {
|
||||
fs.unlinkSync(`./exports/signed_certificate_${doc.objectId}.pdf`);
|
||||
unlinkFile(`./exports/signed_certificate_${doc.objectId}.pdf`);
|
||||
}
|
||||
} catch (err) {
|
||||
fs.unlinkSync(`./exports/signed_certificate_${doc.objectId}.pdf`);
|
||||
unlinkFile(`./exports/signed_certificate_${doc.objectId}.pdf`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +298,7 @@ async function sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, file
|
||||
sendCompletedMail({ isCustomMail, doc, mailProvider, filename });
|
||||
}
|
||||
saveFileUsage(CertificateBuffer.length, file.imageUrl, doc?.CreatedBy?.objectId);
|
||||
fs.unlinkSync(pfx.name);
|
||||
unlinkFile(pfx.name);
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -438,10 +448,10 @@ async function PDF(req) {
|
||||
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl };
|
||||
sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, `signed_${name}`);
|
||||
} else {
|
||||
fs.unlinkSync(pfxname);
|
||||
unlinkFile(pfxname);
|
||||
}
|
||||
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
|
||||
fs.unlinkSync(signedFilePath);
|
||||
// below code is used to remove exported signed pdf file from exports folder
|
||||
unlinkFile(signedFilePath);
|
||||
// console.log(`New Signed PDF created called: ${filePath}`);
|
||||
if (updatedDoc.message === 'success') {
|
||||
return { status: 'success', data: data.imageUrl };
|
||||
@@ -466,7 +476,7 @@ async function PDF(req) {
|
||||
} catch (err) {
|
||||
console.log('err in saving debugginglog', err);
|
||||
}
|
||||
fs.unlinkSync(pfxname);
|
||||
unlinkFile(pfxname);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
export default async function recreateDocument(request) {
|
||||
const { docId } = request.params;
|
||||
if (!docId) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Missing docId parameter');
|
||||
}
|
||||
if (!request.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User not aunthenticated');
|
||||
}
|
||||
|
||||
try {
|
||||
const docQuery = new Parse.Query('contracts_Document');
|
||||
docQuery.equalTo('objectId', docId);
|
||||
const doc = await docQuery.first({ useMasterKey: true });
|
||||
if (!doc) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found');
|
||||
}
|
||||
const _docRes = doc?.toJSON();
|
||||
const { objectId, SignedUrl, AuditTrail, ACL, DeclineBy, DeclineReason, ...docRes } = _docRes;
|
||||
const createDoc = new Parse.Object('contracts_Document');
|
||||
Object.entries(docRes).forEach(([key, value]) => {
|
||||
if (key === 'IsDeclined') {
|
||||
createDoc.set(key, false);
|
||||
} else {
|
||||
createDoc.set(key, value);
|
||||
}
|
||||
// console.log(`${key}: ${value}`);
|
||||
});
|
||||
const createDocRes = await createDoc.save(null, { useMasterKey: true });
|
||||
// console.log('createDocRes', createDocRes);
|
||||
const newDoc = JSON.parse(JSON.stringify(createDocRes));
|
||||
return { objectId: newDoc.objectId, createdAt: newDoc.createdAt, updatedAt: newDoc.updatedAt };
|
||||
} catch (err) {
|
||||
console.log('err in recreate document', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user