Compare commits

...
Author SHA1 Message Date
prafull-opensignlabsandGitHub ca1b87309f Merge pull request #2011 from OpenSignLabs/updates-18996309218
feat: handle undefine username/password for SMTP
2025-11-01 17:24:58 +05:30
prafull-opensignlabs 0b4548130d feat: handle undefine username/password for SMTP 2025-11-01 11:53:04 +00:00
prafull-opensignlabsandGitHub e8b3c7fed8 Merge pull request #1994 from OpenSignLabs/updates-18555669223
v2.30.0
2025-10-16 14:19:28 +05:30
prafull-opensignlabs 2ce43ec2c8 v2.30.0 2025-10-16 08:48:28 +00:00
36 changed files with 3506 additions and 3216 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "open_sign", "name": "open_sign",
"version": "2.26.0", "version": "2.30.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^0.9.0", "@formkit/auto-animate": "^0.9.0",
@@ -110,7 +110,7 @@
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"daisyui": "^4.12.24", "daisyui": "^4.12.24",
"eslint": "^9.34.0", "eslint": "^9.37.0",
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"jsdom": "^27.0.0", "jsdom": "^27.0.0",
@@ -386,7 +386,8 @@
"dropdown": "Dropdown", "dropdown": "Dropdown",
"radio button": "Radiobutton", "radio button": "Radiobutton",
"image": "Bild", "image": "Bild",
"email": "E-Mail" "email": "E-Mail",
"number": "Zahl"
}, },
"fields": "Felder", "fields": "Felder",
"recipients": "Empfänger", "recipients": "Empfänger",
@@ -443,6 +444,19 @@
"maximum-check": "Maximale Anzahl", "maximum-check": "Maximale Anzahl",
"cell-count": "Zellzahl", "cell-count": "Zellzahl",
"default-value": "Standardwert", "default-value": "Standardwert",
"formula": "Formel",
"formula-placeholder": "Erstellen Sie eine Formel mit Zahlen-Widgets",
"formula-no-widgets": "Für diesen Unterzeichner sind noch keine weiteren Zahlen-Widgets vorhanden.",
"supported-operators": "Unterstützte Operatoren:",
"formula-helper": "Klicken Sie auf einen Zahlen-Widget-Chip, um ihn einzufügen (z. B. {{number-1}} + {{number-2}}).",
"formula-error-letter": "Widget-Namen müssen in doppelte geschweifte Klammern gesetzt werden (z. B. {{number-1}}).",
"formula-error-characters": "Außerhalb der Klammern sind nur Zahlen sowie +, -, *, /, (, ) erlaubt.",
"formula-error-braces": "Jede öffnende {{ muss eine passende }} haben.",
"formula-error-operators": "Verwenden Sie bei Operatoren oder Klammern mindestens zwei Werte.",
"formula-error-leading-operator": "Formeln dürfen nicht mit Operatoren beginnen.",
"decimal-places": "Dezimalstellen",
"number-of-decimal-places": "Anzahl der Dezimalstellen",
"decimal-places-helper": "Auf 0 setzen für ganze Zahlen",
"select": "Auswählen", "select": "Auswählen",
"read-only": "Ist schreibgeschützt", "read-only": "Ist schreibgeschützt",
"hide-labels": "Labels ausblenden", "hide-labels": "Labels ausblenden",
@@ -386,7 +386,8 @@
"dropdown": "dropdown", "dropdown": "dropdown",
"radio button": "radio button", "radio button": "radio button",
"image": "image", "image": "image",
"email": "email" "email": "email",
"number": "number"
}, },
"fields": "Fields", "fields": "Fields",
"recipients": "Recipients", "recipients": "Recipients",
@@ -444,6 +445,19 @@
"maximum-check": "Maximum check", "maximum-check": "Maximum check",
"cell-count": "Cell count", "cell-count": "Cell count",
"default-value": "Default value", "default-value": "Default value",
"formula": "Formula",
"formula-placeholder": "Build a formula using number widgets",
"formula-no-widgets": "No other number widgets are assigned to this signer yet.",
"supported-operators": "Supported operators:",
"formula-helper": "Click a number widget chip to insert it (e.g. {{number-1}} + {{number-2}}).",
"formula-error-letter": "Wrap widget names in double curly braces (e.g. {{number-1}}).",
"formula-error-characters": "Only numbers and +, -, *, /, (, ) are allowed outside of braces.",
"formula-error-braces": "Each opening {{ must have a matching }}.",
"formula-error-operators": "Add at least two values when using operators or parentheses.",
"formula-error-leading-operator": "Formulas cannot start with operators.",
"decimal-places": "Decimal Places",
"number-of-decimal-places": "Number of decimal places",
"decimal-places-helper": "Set to 0 for whole numbers",
"select": "Select", "select": "Select",
"read-only": "read only", "read-only": "read only",
"hide-labels": "Hide labels", "hide-labels": "Hide labels",
@@ -386,7 +386,8 @@
"dropdown": "desplegable", "dropdown": "desplegable",
"radio button": "botón de radio", "radio button": "botón de radio",
"image": "imagen", "image": "imagen",
"email": "correo" "email": "correo",
"number": "número"
}, },
"fields": "Campos", "fields": "Campos",
"recipients": "Destinatarios", "recipients": "Destinatarios",
@@ -444,6 +445,19 @@
"maximum-check": "Chequeo máximo", "maximum-check": "Chequeo máximo",
"cell-count": "recuento de células", "cell-count": "recuento de células",
"default-value": "Valor por defecto", "default-value": "Valor por defecto",
"formula": "Fórmula",
"formula-placeholder": "Crea una fórmula utilizando widgets numéricos",
"formula-no-widgets": "Todavía no hay otros widgets numéricos asignados a este firmante.",
"supported-operators": "Operadores compatibles:",
"formula-helper": "Haz clic en una ficha de widget numérico para insertarla (p. ej. {{number-1}} + {{number-2}}).",
"formula-error-letter": "Envuelve los nombres de los widgets entre dobles llaves (p. ej. {{number-1}}).",
"formula-error-characters": "Fuera de las llaves solo se permiten números y +, -, *, /, (, ).",
"formula-error-braces": "Cada {{ de apertura debe tener una }} correspondiente.",
"formula-error-operators": "Cuando uses operadores o paréntesis, agrega al menos dos valores.",
"formula-error-leading-operator": "Las fórmulas no pueden comenzar con operadores.",
"decimal-places": "Decimales",
"number-of-decimal-places": "Número de decimales",
"decimal-places-helper": "Establecer en 0 para números enteros",
"select": "Seleccionar", "select": "Seleccionar",
"read-only": "Es de solo lectura", "read-only": "Es de solo lectura",
"hide-labels": "Esconder etiquetas", "hide-labels": "Esconder etiquetas",
@@ -386,7 +386,8 @@
"dropdown": "dérouler", "dropdown": "dérouler",
"radio button": "bouton radio", "radio button": "bouton radio",
"image": "image", "image": "image",
"email": "e-mail" "email": "e-mail",
"number": "nombre"
}, },
"fields": "Des champs", "fields": "Des champs",
"recipients": "Destinataires", "recipients": "Destinataires",
@@ -443,6 +444,19 @@
"maximum-check": "Contrôle maximum", "maximum-check": "Contrôle maximum",
"cell-count": "numération cellulaire", "cell-count": "numération cellulaire",
"default-value": "Valeur par défaut", "default-value": "Valeur par défaut",
"formula": "Formule",
"formula-placeholder": "Créez une formule à l'aide des widgets numériques",
"formula-no-widgets": "Aucun autre widget numérique n'est encore attribué à ce signataire.",
"supported-operators": "Opérateurs pris en charge :",
"formula-helper": "Cliquez sur une puce de widget numérique pour l'insérer (ex. {{number-1}} + {{number-2}}).",
"formula-error-letter": "Encadrez les noms de widget avec des doubles accolades (ex. {{number-1}}).",
"formula-error-characters": "En dehors des accolades, seuls les chiffres et +, -, *, /, (, ) sont autorisés.",
"formula-error-braces": "Chaque {{ ouvrante doit avoir une }} correspondante.",
"formula-error-operators": "Lorsque vous utilisez des opérateurs ou des parenthèses, ajoutez au moins deux valeurs.",
"formula-error-leading-operator": "Les formules ne peuvent pas commencer par des opérateurs.",
"decimal-places": "Décimales",
"number-of-decimal-places": "Nombre de décimales",
"decimal-places-helper": "Définir sur 0 pour les nombres entiers",
"select": "Sélectionner", "select": "Sélectionner",
"read-only": "Est en lecture seule", "read-only": "Est en lecture seule",
"hide-labels": "Masquer les étiquettes", "hide-labels": "Masquer les étiquettes",
@@ -386,7 +386,8 @@
"dropdown": "ड्रॉपडाउन", "dropdown": "ड्रॉपडाउन",
"radio button": "रेडियो बटन", "radio button": "रेडियो बटन",
"image": "छवि", "image": "छवि",
"email": "ईमेल" "email": "ईमेल",
"number": "संख्या"
}, },
"fields": "क्षेत्र", "fields": "क्षेत्र",
"recipients": "प्राप्तकर्ता", "recipients": "प्राप्तकर्ता",
@@ -443,6 +444,19 @@
"maximum-check": "अधिकतम जाँच", "maximum-check": "अधिकतम जाँच",
"cell-count": "सेल्स संख्या", "cell-count": "सेल्स संख्या",
"default-value": "डिफ़ॉल्ट मान", "default-value": "डिफ़ॉल्ट मान",
"formula": "सूत्र",
"formula-placeholder": "संख्या विजेट्स का उपयोग करके एक सूत्र बनाएं",
"formula-no-widgets": "इस हस्ताक्षरकर्ता को अभी तक कोई अन्य संख्या विजेट असाइन नहीं किया गया है।",
"supported-operators": "समर्थित ऑपरेटर:",
"formula-helper": "सूची में से संख्या विजेट चिप पर क्लिक करके उसे जोड़ें (उदा. {{number-1}} + {{number-2}})।",
"formula-error-letter": "विजेट नामों को दोहरी घुंघराली ब्रैकेट में रखें (उदा. {{number-1}})।",
"formula-error-characters": "ब्रैकेट के बाहर केवल संख्याएँ और +, -, *, /, (, ) की अनुमति है।",
"formula-error-braces": "हर खुलने वाली {{ के लिए एक मिलान करने वाली }} होना चाहिए।",
"formula-error-operators": "ऑपरेटर या कोष्ठक का उपयोग करते समय कम से कम दो मान जोड़ें।",
"formula-error-leading-operator": "सूत्र ऑपरेटर से शुरू नहीं हो सकते।",
"decimal-places": "दशमलव स्थान",
"number-of-decimal-places": "दशमलव स्थानों की संख्या",
"decimal-places-helper": "पूर्णांक के लिए 0 पर सेट करें",
"select": "चुनें", "select": "चुनें",
"read-only": "सिर्फ पढ़ने के लिए है", "read-only": "सिर्फ पढ़ने के लिए है",
"hide-labels": "लेबल छिपाएँ", "hide-labels": "लेबल छिपाएँ",
@@ -386,7 +386,8 @@
"dropdown": "menu a tendina", "dropdown": "menu a tendina",
"radio button": "pulsante di opzione", "radio button": "pulsante di opzione",
"image": "immagine", "image": "immagine",
"email": "email" "email": "email",
"number": "numero"
}, },
"fields": "Campi", "fields": "Campi",
"recipients": "Destinatari", "recipients": "Destinatari",
@@ -443,6 +444,19 @@
"maximum-check": "Controllo massimo", "maximum-check": "Controllo massimo",
"cell-count": "conteggio delle cellule", "cell-count": "conteggio delle cellule",
"default-value": "Valore predefinita", "default-value": "Valore predefinita",
"formula": "Formula",
"formula-placeholder": "Crea una formula utilizzando i widget numerici",
"formula-no-widgets": "Non ci sono ancora altri widget numerici assegnati a questo firmatario.",
"supported-operators": "Operatori supportati:",
"formula-helper": "Fai clic su una chip di widget numerico per inserirla (es. {{number-1}} + {{number-2}}).",
"formula-error-letter": "Racchiudi i nomi dei widget tra doppie parentesi graffe (es. {{number-1}}).",
"formula-error-characters": "Al di fuori delle parentesi sono consentiti solo numeri e +, -, *, /, (, ).",
"formula-error-braces": "Ogni {{ di apertura deve avere una }} corrispondente.",
"formula-error-operators": "Quando usi operatori o parentesi, aggiungi almeno due valori.",
"formula-error-leading-operator": "Le formule non possono iniziare con operatori.",
"decimal-places": "Cifre decimali",
"number-of-decimal-places": "Numero di cifre decimali",
"decimal-places-helper": "Imposta su 0 per numeri interi",
"select": "Seleziona", "select": "Seleziona",
"read-only": "È di sola lettura", "read-only": "È di sola lettura",
"hide-labels": "Nascondi etichette", "hide-labels": "Nascondi etichette",
@@ -71,11 +71,9 @@ function DriveBody(props) {
}); });
props.setPdfData(updatedData); props.setPdfData(updatedData);
props.sortingData(null, null, updatedData); props.sortingData(null, null, updatedData);
await axios try {
.put( await axios.put(
`${localStorage.getItem( `${localStorage.getItem("baseUrl")}classes/contracts_Document/${docId}`,
"baseUrl"
)}classes/contracts_Document/${docId}`,
updateName, updateName,
{ {
headers: { headers: {
@@ -84,18 +82,14 @@ function DriveBody(props) {
"X-Parse-Session-Token": localStorage.getItem("accesstoken") "X-Parse-Session-Token": localStorage.getItem("accesstoken")
} }
} }
) );
.then(() => { } catch (err) {
// const res = result.data; console.log("Err ", err);
// console.log("res", res); props.setIsAlert({
}) isShow: true,
.catch((err) => { alertMessage: t("something-went-wrong-mssg")
console.log("Err ", err);
props.setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}); });
}
} }
}; };
@@ -245,7 +239,6 @@ function DriveBody(props) {
) )
.then((Listdata) => { .then((Listdata) => {
// console.log("Listdata ", Listdata);
const res = Listdata.data; const res = Listdata.data;
if (res) { if (res) {
const updatedData = props.pdfData.filter( const updatedData = props.pdfData.filter(
@@ -31,7 +31,6 @@ function EmailComponent({
setIsLoading(true); setIsLoading(true);
const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList }; const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
const sendmail = await Parse.Cloud.run("forwarddoc", params); const sendmail = await Parse.Cloud.run("forwarddoc", params);
console.log("sendmail ", sendmail);
if (sendmail?.status === "success") { if (sendmail?.status === "success") {
setSuccessEmail(true); setSuccessEmail(true);
setIsEmail(false); setIsEmail(false);
@@ -267,7 +267,8 @@ function Placeholder(props) {
(props.isSignYourself || props.isSelfSign) (props.isSignYourself || props.isSelfSign)
) { ) {
props.handleTextSettingModal(true); props.handleTextSettingModal(true);
} else { }
else {
props?.handleNameModal && props?.handleNameModal(true); props?.handleNameModal && props?.handleNameModal(true);
} }
@@ -305,13 +306,7 @@ function Placeholder(props) {
) { ) {
props.setUniqueId(props?.data?.Id); props.setUniqueId(props?.data?.Id);
} }
if (props?.data?.Role !== "prefill") {
//checking widget's type and open widget copy modal for required widgets
if (
["signature", textInputWidget, textWidget, "stamp", "initials"].includes(
props.pos.type
)
) {
props.setIsPageCopy(true); props.setIsPageCopy(true);
props.setCurrWidgetsDetails(props.pos); props.setCurrWidgetsDetails(props.pos);
} else { } else {
@@ -326,7 +321,7 @@ function Placeholder(props) {
props.data && props.data?.Id props.data && props.data?.Id
); );
//condiiton is used to store copied prefill image base64 url in redux for display image //condiiton is used to store copied prefill image base64 url in redux for display image
if (props?.data?.Role === "prefill") { if (props?.pos?.type === "image") {
const getPrefillImg = prefillImg?.find((x) => x.id === props.pos.key); const getPrefillImg = prefillImg?.find((x) => x.id === props.pos.key);
dispatch( dispatch(
setPrefillImg({ setPrefillImg({
@@ -1,4 +1,4 @@
import { useEffect, useState, forwardRef } from "react"; import { useEffect, useMemo, useState, forwardRef } from "react";
import { import {
getMonth, getMonth,
getYear, getYear,
@@ -10,7 +10,8 @@ import {
years, years,
selectCheckbox, selectCheckbox,
checkRegularExpress, checkRegularExpress,
isBase64 isBase64,
getSignerPages
} from "../../constant/Utils"; } from "../../constant/Utils";
import DatePicker from "react-datepicker"; import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css"; import "react-datepicker/dist/react-datepicker.css";
@@ -19,6 +20,7 @@ import { useTranslation } from "react-i18next";
import CellsWidget from "./CellsWidget"; import CellsWidget from "./CellsWidget";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import Loader from "../../primitives/Loader"; import Loader from "../../primitives/Loader";
const textWidgetCls = const textWidgetCls =
"w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap"; "w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap";
const widgetCls = const widgetCls =
@@ -65,18 +67,21 @@ function PlaceholderType(props) {
props?.pos?.options?.defaultValue || props?.pos?.options?.defaultValue ||
[] []
); );
} else { }
else {
// keep displayed value in sync with the stored response // keep displayed value in sync with the stored response
setwidgetValue(widgetData); setwidgetValue(widgetData);
} }
if (props.pos?.options?.hint) { if (props.pos?.options?.hint) {
setHint(props.pos?.options.hint); setHint(props.pos?.options.hint);
} else if (props.pos?.options?.validation?.type) { } else if (props.pos?.options?.validation?.type) {
checkRegularExpress(props.pos?.options?.validation?.type, setHint); checkRegularExpress(props.pos?.options?.validation?.type, setHint);
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.pos]); }, [props.pos, widgetData, type]);
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => ( const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div <div
style={{ style={{
@@ -28,6 +28,8 @@ import {
} from "../../redux/reducers/widgetSlice"; } from "../../redux/reducers/widgetSlice";
import * as utils from "../../utils"; import * as utils from "../../utils";
const widgetTitle = "font-medium";
const ShowTextWidget = ({ position, handleWidgetDetails }) => { const ShowTextWidget = ({ position, handleWidgetDetails }) => {
const inputRef = useRef(null); const inputRef = useRef(null);
const [inputValue, setInputValue] = useState(position.options.response || ""); const [inputValue, setInputValue] = useState(position.options.response || "");
@@ -46,13 +48,77 @@ const ShowTextWidget = ({ position, handleWidgetDetails }) => {
); );
}; };
const ImageComponent = (props) => {
const { t } = useTranslation();
const prefillImg = useSelector((state) => state.widget.prefillImg);
const imageRefs = useRef({});
let imgUrl = "";
const isBase64Url = isBase64(props?.position?.SignUrl);
if (isBase64Url) {
imgUrl = props?.position?.SignUrl;
} else {
const getPrefillImg = prefillImg?.find(
(x) => x.id === props?.position?.key
);
imgUrl = getPrefillImg?.base64;
}
return (
<>
<span className={widgetTitle}>{props?.position.options?.name}</span>
{imgUrl ? (
<>
<div className="cursor-pointer op-card border-[1px] border-gray-400 flex flex-col w-full h-full justify-center items-center ">
<img
alt="print img"
ref={(el) => (imageRefs.current[props?.position.key] = el)} // Assign ref dynamicallys
src={imgUrl}
draggable="false"
className="object-contain h-full w-full aspect-[5/2]"
onLoad={() => props?.handleImageLoaded?.(props?.position.key)}
onError={() => props?.handleImageLoaded?.(props?.position.key)}
/>
</div>
<span
onClick={() => props?.handleClearImage(props?.position)}
className="flex justify-start text-blue-500 underline cursor-pointer"
>
{t("clear")}
</span>
</>
) : (
<div
className="cursor-pointer op-card border-[1px] op-border-hover flex flex-col overflow-hidden w-full h-full aspect-[5/2] justify-center items-center"
onClick={() => imageRefs.current[props?.position.key]?.click()}
>
{props?.imageLoaders[props?.position?.key] && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-white/30 z-50">
<Loader />
</div>
)}
<input
type="file"
onChange={(e) => props?.onImageChange?.(e, props?.position)}
className="filetype"
accept="image/png,image/jpeg"
ref={(el) => (imageRefs.current[props?.position.key] = el)} // Assign ref dynamically
hidden
/>
<i className="fa-light text-base-content fa-cloud-upload-alt text-[25px]"></i>
<div className="text-[10px] text-base-content">{t("upload")}</div>
</div>
)}
</>
);
};
function PrefillWidgetModal(props) { function PrefillWidgetModal(props) {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch(); const dispatch = useDispatch();
// Track already loaded image keys so they don't increment multiple times // Track already loaded image keys so they don't increment multiple times
const loadedSet = useRef(new Set()); const loadedSet = useRef(new Set());
const initializedRef = useRef(false); // prevent rerun on state updates const initializedRef = useRef(false); // prevent rerun on state updates
const prefillImg = useSelector((state) => state.widget.prefillImg);
const [image, setImage] = useState(null); const [image, setImage] = useState(null);
const [imageLoaders, setImageLoaders] = useState({}); const [imageLoaders, setImageLoaders] = useState({});
const [currentWidget, setCurrentWidget] = useState(""); const [currentWidget, setCurrentWidget] = useState("");
@@ -61,7 +127,6 @@ function PrefillWidgetModal(props) {
const [loadedImages, setLoadedImages] = useState(0); const [loadedImages, setLoadedImages] = useState(0);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const years = range(1950, getYear(new Date()) + 16, 1); const years = range(1950, getYear(new Date()) + 16, 1);
const widgetTitle = "font-medium";
const isAnyLoaderActive = Object.values(imageLoaders).some( const isAnyLoaderActive = Object.values(imageLoaders).some(
(val) => val === true (val) => val === true
); );
@@ -93,6 +158,7 @@ function PrefillWidgetModal(props) {
return flatArray || []; return flatArray || [];
}, [props.prefillData]); }, [props.prefillData]);
useEffect(() => { useEffect(() => {
dispatch(resetWidgetState([])); dispatch(resetWidgetState([]));
}, []); }, []);
@@ -163,67 +229,7 @@ function PrefillWidgetModal(props) {
} }
return date; return date;
}; };
const ImageComponent = (props) => {
const imageRefs = useRef([]);
let imgUrl = "";
const isBase64Url = isBase64(props?.position?.SignUrl);
if (isBase64Url) {
imgUrl = props?.position?.SignUrl;
} else {
const getPrefillImg = prefillImg?.find(
(x) => x.id === props?.position?.key
);
imgUrl = getPrefillImg?.base64;
}
return (
<>
<span className={widgetTitle}>{props?.position.options?.name}</span>
{imgUrl ? (
<>
<div className="cursor-pointer op-card border-[1px] border-gray-400 flex flex-col w-full h-full justify-center items-center ">
<img
alt="print img"
ref={(el) => (imageRefs.current[props?.position.key] = el)} // Assign ref dynamicallys
src={imgUrl}
draggable="false"
className="object-contain h-full w-full aspect-[5/2]"
onLoad={() => handleImageLoaded?.(props?.position.key)}
onError={() => handleImageLoaded?.(props?.position.key)}
/>
</div>
<span
onClick={() => handleClearImage(props?.position)}
className="flex justify-start text-blue-500 underline cursor-pointer"
>
{t("clear")}
</span>
</>
) : (
<div
className="cursor-pointer op-card border-[1px] op-border-hover flex flex-col overflow-hidden w-full h-full aspect-[5/2] justify-center items-center"
onClick={() => imageRefs.current[props?.position.key]?.click()}
>
{imageLoaders[props?.position?.key] && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-white/30 z-50">
<Loader />
</div>
)}
<input
type="file"
onChange={(e) => onImageChange(e, props?.position)}
className="filetype"
accept="image/png,image/jpeg"
ref={(el) => (imageRefs.current[props?.position.key] = el)} // Assign ref dynamically
hidden
/>
<i className="fa-light text-base-content fa-cloud-upload-alt text-[25px]"></i>
<div className="text-[10px] text-base-content">{t("upload")}</div>
</div>
)}
</>
);
};
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => ( const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div <div
style={{ fontFamily: "Arial, sans-serif" }} style={{ fontFamily: "Arial, sans-serif" }}
@@ -273,7 +279,6 @@ function PrefillWidgetModal(props) {
image.src, image.src,
image.imgType image.imgType
); );
setImageLoaders({});
if (imageUrl) { if (imageUrl) {
return imageUrl; return imageUrl;
} }
@@ -414,6 +419,7 @@ function PrefillWidgetModal(props) {
// (tracks how many images have finished loading) // (tracks how many images have finished loading)
setLoadedImages((prev) => prev + 1); setLoadedImages((prev) => prev + 1);
} }
setImageLoaders({});
}; };
const handleWidgetType = (position) => { const handleWidgetType = (position) => {
switch (position?.type) { switch (position?.type) {
@@ -432,9 +438,9 @@ function PrefillWidgetModal(props) {
className="mt-[2px] op-checkbox op-checkbox-xs" className="mt-[2px] op-checkbox op-checkbox-xs"
type="checkbox" type="checkbox"
checked={selectCheckbox(ind, position)} checked={selectCheckbox(ind, position)}
onChange={(e) => { onChange={(e) =>
handleCheckboxValue(e.target.checked, ind, position); handleCheckboxValue(e.target.checked, ind, position)
}} }
/> />
<label <label
htmlFor={`checkbox-${position.key + ind}`} htmlFor={`checkbox-${position.key + ind}`}
@@ -467,9 +473,7 @@ function PrefillWidgetModal(props) {
value={ value={
position?.options?.response || position?.options?.defaultValue position?.options?.response || position?.options?.defaultValue
} }
onChange={(e) => { onChange={(e) => handleWidgetDetails(position, e.target.value)}
handleWidgetDetails(position, e.target.value);
}}
> >
{/* Default/Title option */} {/* Default/Title option */}
<option value="" disabled hidden> <option value="" disabled hidden>
@@ -519,9 +523,7 @@ function PrefillWidgetModal(props) {
)} )}
closeOnScroll={true} closeOnScroll={true}
selected={handleDate(position)} selected={handleDate(position)}
onChange={(date) => { onChange={(date) => handleOnDateChange(date, position)}
handleOnDateChange(date, position);
}}
customInput={<ExampleCustomInput />} customInput={<ExampleCustomInput />}
dateFormat={position?.options?.validation?.format || "MM/dd/yyyy"} dateFormat={position?.options?.validation?.format || "MM/dd/yyyy"}
/> />
@@ -531,7 +533,10 @@ function PrefillWidgetModal(props) {
return ( return (
<ImageComponent <ImageComponent
position={position} position={position}
docId={props?.docId} imageLoaders={imageLoaders}
onImageChange={onImageChange}
handleImageLoaded={handleImageLoaded}
handleClearImage={handleClearImage}
/> />
); );
case radioButtonWidget: case radioButtonWidget:
@@ -549,9 +554,7 @@ function PrefillWidgetModal(props) {
className="mt-[2px] op-radio op-radio-xs" className="mt-[2px] op-radio op-radio-xs"
type="radio" type="radio"
checked={handleRadioCheck(data, position)} checked={handleRadioCheck(data, position)}
onChange={() => { onChange={() => handleWidgetDetails(position, data)}
handleWidgetDetails(position, data);
}}
/> />
<label <label
htmlFor={`radio-${position.key + ind}`} htmlFor={`radio-${position.key + ind}`}
@@ -697,9 +700,9 @@ function PrefillWidgetModal(props) {
multiValueLabel: () => "mb-[2px]", multiValueLabel: () => "mb-[2px]",
menu: () => menu: () =>
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content absolute z-9999", "mt-1 shadow-md rounded-lg bg-base-200 text-base-content absolute z-9999",
menuList: () => "shadow-md rounded-lg ", menuList: () => "shadow-md rounded-lg",
option: () => option: () =>
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2 ", "bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2",
noOptionsMessage: () => noOptionsMessage: () =>
"p-2 bg-base-200 rounded-lg m-1 p-2" "p-2 bg-base-200 rounded-lg m-1 p-2"
}} }}
@@ -710,7 +713,7 @@ function PrefillWidgetModal(props) {
</div> </div>
<button <button
onClick={(e) => handleCreateNew(e, field.value)} onClick={(e) => handleCreateNew(e, field.value)}
className="op-btn op-btn-accent op-btn-outline op-btn-sm " className="op-btn op-btn-accent op-btn-outline op-btn-sm"
> >
<i className="fa-light fa-plus"></i> <i className="fa-light fa-plus"></i>
</button> </button>
@@ -271,6 +271,7 @@ function RenderPdf(props) {
setIsReqSignTourDisabled={props.setIsReqSignTourDisabled} setIsReqSignTourDisabled={props.setIsReqSignTourDisabled}
calculateFontsize={calculateFontsize} calculateFontsize={calculateFontsize}
currWidgetsDetails={props?.currWidgetsDetails} currWidgetsDetails={props?.currWidgetsDetails}
handleNameModal={props?.handleNameModal}
/> />
</React.Fragment> </React.Fragment>
) )
@@ -484,6 +485,8 @@ function RenderPdf(props) {
setIsReqSignTourDisabled={ setIsReqSignTourDisabled={
props.setIsReqSignTourDisabled props.setIsReqSignTourDisabled
} }
unSignedWidgetId={props.unSignedWidgetId}
handleNameModal={props.handleNameModal}
/> />
) )
)} )}
@@ -91,14 +91,14 @@ function WidgetComponent(props) {
name, name,
jobTitle, jobTitle,
company, company,
email,
date, date,
text, text,
cells, cells,
checkbox, checkbox,
dropdown, dropdown,
radioButton, radioButton,
image, image
email
]; ];
const getWidgetArray = widgets; const getWidgetArray = widgets;
const newUpdateSigner = getWidgetArray.map((obj, ind) => { const newUpdateSigner = getWidgetArray.map((obj, ind) => {
@@ -109,7 +109,7 @@ function WidgetComponent(props) {
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
//allow only (signature,stamp,initials,text,name, job title, company,email) widget when isAllowModification true and user have session token // allow only (signature, stamp, initials, text, name, job title, company, email, cells) widget when isAllowModification true and user have session token
const modifiedWidgets = widget.filter( const modifiedWidgets = widget.filter(
(data) => (data) =>
![ ![
@@ -121,7 +121,7 @@ function WidgetComponent(props) {
"checkbox" "checkbox"
].includes(data.type) ].includes(data.type)
); );
//allow only (signature,stamp,initials,text) widget when isAllowModification true and user does not have session token // allow only (signature, stamp, initials, text, cells) widget when isAllowModification true and user does not have session token
const unlogedInUserWidgets = widget.filter( const unlogedInUserWidgets = widget.filter(
(data) => (data) =>
![ ![
@@ -263,7 +263,12 @@ function WidgetComponent(props) {
)} )}
</span> </span>
</div> </div>
<div className="p-[15px] flex flex-col pt-4" data-tut="addWidgets"> <div
className="p-[12px] grid lg:grid-cols-2 gap-x-2 lg:gap-y-1.5 pt-3"
data-tut="addWidgets"
role="list"
aria-label="Add widgets"
>
<WidgetList <WidgetList
updateWidgets={handleWidgetType} updateWidgets={handleWidgetType}
handleDivClick={props.handleDivClick} handleDivClick={props.handleDivClick}
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
import "../../styles/AddUser.css"; import "../../styles/AddUser.css";
import RegexParser from "regex-parser"; import RegexParser from "regex-parser";
@@ -21,11 +21,30 @@ const WidgetNameModal = (props) => {
hint: "", hint: "",
textvalidate: "", textvalidate: "",
isReadOnly: false, isReadOnly: false,
cellCount: 5 cellCount: 5,
}); });
const [isValid, setIsValid] = useState(true); const [isValid, setIsValid] = useState(true);
const statusArr = ["Required", "Optional"]; const statusArr = ["Required", "Optional"];
const [signatureType, setSignatureType] = useState([]); const [signatureType, setSignatureType] = useState([]);
const type = props?.defaultdata?.type;
const isCellWidget = useMemo(() => type === cellsWidget, [type]);
const isSignOrInitials = useMemo(
() => ["signature", "initials"].includes(type),
[type]
);
const showFontControls = useMemo(
() =>
[
textInputWidget,
textWidget,
cellsWidget,
"name",
"company",
"job title",
"email"
].includes(props.defaultdata?.type),
[type]
);
const handleHint = () => { const handleHint = () => {
const type = props.defaultdata?.type; const type = props.defaultdata?.type;
@@ -54,13 +73,13 @@ const WidgetNameModal = (props) => {
? props.defaultdata?.options?.validation?.pattern ? props.defaultdata?.options?.validation?.pattern
: props.defaultdata?.options?.validation?.type || "", : props.defaultdata?.options?.validation?.type || "",
isReadOnly: props.defaultdata?.options?.isReadOnly || false, isReadOnly: props.defaultdata?.options?.isReadOnly || false,
cellCount: props.defaultdata?.options?.cellCount || 5 cellCount: props.defaultdata?.options?.cellCount || 5,
}); });
} else { } else {
setFormdata({ setFormdata({
...formdata, ...formdata,
name: props.defaultdata?.options?.name || "", name: props.defaultdata?.options?.name || "",
cellCount: props.defaultdata?.options?.cellCount || 5 cellCount: props.defaultdata?.options?.cellCount || 5,
}); });
} }
@@ -68,12 +87,13 @@ const WidgetNameModal = (props) => {
const defaultSignatureType = signTypes || []; const defaultSignatureType = signTypes || [];
setSignatureType(defaultSignatureType); setSignatureType(defaultSignatureType);
} }
// eslint-disable-next-line // eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.defaultdata]); }, [props.defaultdata]);
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
if (props.handleData) { if (props.handleData) {
if (["signature", "initials"].includes(props.defaultdata?.type)) { if (isSignOrInitials) {
const enabledSignTypes = signatureType?.filter((x) => x.enabled); const enabledSignTypes = signatureType?.filter((x) => x.enabled);
const isDefaultSignTypeOnly = const isDefaultSignTypeOnly =
enabledSignTypes?.length === 1 && enabledSignTypes?.length === 1 &&
@@ -87,9 +107,11 @@ const WidgetNameModal = (props) => {
props.handleData(data, props.defaultdata?.type); props.handleData(data, props.defaultdata?.type);
} }
} else { } else {
const isTextInput = [textInputWidget, cellsWidget].includes(
props.defaultdata?.type const isTextInput = [
); textInputWidget,
cellsWidget,
].includes(props.defaultdata?.type);
const { isReadOnly, defaultValue, status } = formdata; const { isReadOnly, defaultValue, status } = formdata;
// If its a textinput widget, enforce that read-only fields have // If its a textinput widget, enforce that read-only fields have
// either a defaultValue or an "optional" status. // either a defaultValue or an "optional" status.
@@ -110,7 +132,7 @@ const WidgetNameModal = (props) => {
status: "required", status: "required",
hint: "", hint: "",
textvalidate: "", textvalidate: "",
cellCount: 5 cellCount: 5,
}); });
setSignatureType(signTypes); setSignatureType(signTypes);
} }
@@ -148,10 +170,11 @@ const WidgetNameModal = (props) => {
} else { } else {
setIsValid(true); setIsValid(true);
} }
const val =
props.defaultdata?.type === cellsWidget const val = isCellWidget
? e.target.value.slice(0, formdata.cellCount) ? e.target.value.slice(0, formdata.cellCount)
: e.target.value; : e.target.value;
setFormdata({ ...formdata, [e.target.name]: val }); setFormdata({ ...formdata, [e.target.name]: val });
}; };
@@ -160,7 +183,7 @@ const WidgetNameModal = (props) => {
case "email": case "email":
return "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/"; return "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/";
case "number": case "number":
return "/^\\d+$/"; return "^\\d+(?:\\.\\d+)?$"; // "/^\\d+$/";
case "text": case "text":
//allow space in text regex //allow space in text regex
return "/^[a-zA-Z ]+$/"; return "/^[a-zA-Z ]+$/";
@@ -184,23 +207,19 @@ const WidgetNameModal = (props) => {
<ModalUi <ModalUi
isOpen={props.isOpen} isOpen={props.isOpen}
handleClose={props.handleClose && props.handleClose} handleClose={props.handleClose && props.handleClose}
title={ title={isSignOrInitials ? t("signature-setting") : t("widget-info")}
["signature", "initials"].includes(props.defaultdata?.type)
? t("signature-setting")
: t("widget-info")
}
> >
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className={`${ className={`${
[textInputWidget, cellsWidget].includes(props.defaultdata?.type) [textInputWidget, cellsWidget].includes(props.defaultdata?.type)
? "pt-0" ? "pt-0"
: ["signature", "initials"].includes(props.defaultdata?.type) : isSignOrInitials
? "pt-2" ? "pt-2"
: "" : ""
} p-[20px] text-base-content`} } p-[20px] text-base-content`}
> >
{!["signature", "initials"].includes(props.defaultdata?.type) && ( {!isSignOrInitials && (
<div className="mb-[0.75rem] text-[13px]"> <div className="mb-[0.75rem] text-[13px]">
<label htmlFor="name"> <label htmlFor="name">
{t("name")} {t("name")}
@@ -217,7 +236,7 @@ const WidgetNameModal = (props) => {
/> />
</div> </div>
)} )}
{props.defaultdata?.type === cellsWidget && ( {isCellWidget && (
<div className="mb-[0.75rem] text-[13px]"> <div className="mb-[0.75rem] text-[13px]">
<label htmlFor="cellCount">{t("cell-count")}</label> <label htmlFor="cellCount">{t("cell-count")}</label>
<input <input
@@ -231,7 +250,10 @@ const WidgetNameModal = (props) => {
/> />
</div> </div>
)} )}
{[textInputWidget, cellsWidget].includes(props.defaultdata?.type) && {[
textInputWidget,
cellsWidget,
].includes(props.defaultdata?.type) &&
props?.roleName !== "prefill" && ( props?.roleName !== "prefill" && (
<> <>
<div className="mb-[0.75rem]"> <div className="mb-[0.75rem]">
@@ -244,11 +266,7 @@ const WidgetNameModal = (props) => {
value={formdata.defaultValue} value={formdata.defaultValue}
onChange={(e) => handledefaultChange(e)} onChange={(e) => handledefaultChange(e)}
autoComplete="off" autoComplete="off"
maxLength={ maxLength={isCellWidget ? formdata.cellCount : undefined}
props.defaultdata?.type === cellsWidget
? formdata.cellCount
: undefined
}
onBlur={() => { onBlur={() => {
if (isValid === false) { if (isValid === false) {
setFormdata({ ...formdata, defaultValue: "" }); setFormdata({ ...formdata, defaultValue: "" });
@@ -262,7 +280,7 @@ const WidgetNameModal = (props) => {
style={{ fontSize: 12 }} style={{ fontSize: 12 }}
> >
<i <i
className="fa-light fa-exclamation-circle text-[15px]" className="fa-light fa-exclamation-circle text-[15px] mr-1"
style={{ color: "#fab005" }} style={{ color: "#fab005" }}
></i> ></i>
{t("invalid-default-value")} {t("invalid-default-value")}
@@ -271,7 +289,8 @@ const WidgetNameModal = (props) => {
</div> </div>
</> </>
)} )}
{!["signature", "initials"].includes(props.defaultdata?.type) && {!props?.isSelfSign &&
!isSignOrInitials &&
props?.roleName !== "prefill" && ( props?.roleName !== "prefill" && (
<div className="mb-[0.75rem]"> <div className="mb-[0.75rem]">
<div className="flex flex-row gap-[10px] mb-[0.5rem]"> <div className="flex flex-row gap-[10px] mb-[0.5rem]">
@@ -302,9 +321,10 @@ const WidgetNameModal = (props) => {
); );
})} })}
</div> </div>
{[textInputWidget, cellsWidget].includes( {[
props.defaultdata?.type textInputWidget,
) && cellsWidget,
].includes(props.defaultdata?.type) &&
props?.roleName !== "prefill" && ( props?.roleName !== "prefill" && (
<div className="flex items-center"> <div className="flex items-center">
<input <input
@@ -330,7 +350,7 @@ const WidgetNameModal = (props) => {
)} )}
</div> </div>
)} )}
{["signature", "initials"].includes(props.defaultdata?.type) && ( {isSignOrInitials && (
<div className="mb-[0.75rem]"> <div className="mb-[0.75rem]">
<label htmlFor="signaturetype" className="text-[14px] mb-[0.7rem]"> <label htmlFor="signaturetype" className="text-[14px] mb-[0.7rem]">
{t("allowed-signature-types")} {t("allowed-signature-types")}
@@ -358,7 +378,7 @@ const WidgetNameModal = (props) => {
</div> </div>
</div> </div>
)} )}
{props?.roleName !== "prefill" && ( {!props?.isSelfSign && props?.roleName !== "prefill" && (
<div className="mb-[0.75rem]"> <div className="mb-[0.75rem]">
<label htmlFor="hint" className="text-[13px]"> <label htmlFor="hint" className="text-[13px]">
{t("hint")} {t("hint")}
@@ -372,15 +392,7 @@ const WidgetNameModal = (props) => {
/> />
</div> </div>
)} )}
{[ {showFontControls && (
textInputWidget,
textWidget,
cellsWidget,
"name",
"company",
"job title",
"email"
].includes(props.defaultdata?.type) && (
<div className="flex flex-col md:flex-row md:items-center gap-3 mb-3"> <div className="flex flex-col md:flex-row md:items-center gap-3 mb-3">
<div className="flex items-center gap-2 "> <div className="flex items-center gap-2 ">
<span className="whitespace-nowrap">{t("font-size")}: </span> <span className="whitespace-nowrap">{t("font-size")}: </span>
@@ -1,4 +1,4 @@
import { forwardRef, useEffect, useRef, useState } from "react"; import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { removeBackground } from "@imgly/background-removal"; import { removeBackground } from "@imgly/background-removal";
@@ -23,7 +23,8 @@ import {
convertBase64ToFile, convertBase64ToFile,
generatePdfName, generatePdfName,
getDefaultDate, getDefaultDate,
getDefaultFormat getDefaultFormat,
getSignerPages
} from "../../constant/Utils"; } from "../../constant/Utils";
import CellsWidget from "./CellsWidget"; import CellsWidget from "./CellsWidget";
import DatePicker from "react-datepicker"; import DatePicker from "react-datepicker";
@@ -45,7 +46,9 @@ import { useDispatch, useSelector } from "react-redux";
import Loader from "../../primitives/Loader"; import Loader from "../../primitives/Loader";
import { emailRegex } from "../../constant/const"; import { emailRegex } from "../../constant/const";
import RegexParser from "regex-parser"; import RegexParser from "regex-parser";
import { saveToMySign } from "../../utils/widgetUtils"; import {
saveToMySign
} from "../../utils/widgetUtils";
const fontOptions = [ const fontOptions = [
{ value: "Fasthand" }, { value: "Fasthand" },
@@ -111,12 +114,28 @@ function WidgetsValueModal(props) {
props?.kiosk_signer || JSON.parse(localStorage.getItem("kiosk_signer")); props?.kiosk_signer || JSON.parse(localStorage.getItem("kiosk_signer"));
const jsonSender = senderUser && JSON.parse(senderUser); const jsonSender = senderUser && JSON.parse(senderUser);
const currentUserName = jsonSender && jsonSender?.name; const currentUserName = jsonSender && jsonSender?.name;
const type = currWidgetsDetails?.type;
const widgetTypeTranslation = t(`widgets-name.${currWidgetsDetails?.type}`); const widgetTypeTranslation = t(`widgets-name.${currWidgetsDetails?.type}`);
const [widgetValue, setWidgetValue] = useState(
currWidgetsDetails.type !== "checkbox" && const [widgetValue, setWidgetValue] = useState(() => {
(currWidgetsDetails?.options?.response || if (currWidgetsDetails.type === "checkbox") {
currWidgetsDetails?.options?.defaultValue) return undefined;
); }
return (
currWidgetsDetails?.options?.response ||
currWidgetsDetails?.options?.defaultValue
);
});
const blocked = [
"checkbox",
"dropdown",
"date",
"radio",
];
const showClearbtn =
!blocked.includes(type);
const [cellsValue, setCellsValue] = useState(() => { const [cellsValue, setCellsValue] = useState(() => {
const count = currWidgetsDetails?.options?.cellCount || 5; const count = currWidgetsDetails?.options?.cellCount || 5;
const val = const val =
@@ -192,7 +211,8 @@ function WidgetsValueModal(props) {
} else { } else {
setHint(currWidgetsDetails?.type); setHint(currWidgetsDetails?.type);
} }
} else if (isSignOrInitials) { }
else if (isSignOrInitials) {
setIsTab(currWidgetsDetails?.signatureType); setIsTab(currWidgetsDetails?.signatureType);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -877,9 +897,10 @@ function WidgetsValueModal(props) {
const handleOnchangeTextBox = (e) => { const handleOnchangeTextBox = (e) => {
// hide any prior validation error while typing // hide any prior validation error while typing
setIsShowValidation(false); setIsShowValidation(false);
setWidgetValue(e.target.value); let value = e.target.value;
setWidgetValue(value);
onChangeInput( onChangeInput(
e.target.value, value,
currWidgetsDetails, currWidgetsDetails,
xyPosition, xyPosition,
props.index, props.index,
@@ -1718,13 +1739,14 @@ function WidgetsValueModal(props) {
} else if (!isSave) { } else if (!isSave) {
const widgetsPosition = xyPosition?.find((data) => data.Id === uniqueId); const widgetsPosition = xyPosition?.find((data) => data.Id === uniqueId);
let nextWidgetDetails = null; let nextWidgetDetails = null;
const editableWidgets = widgetsPosition?.placeHolder.flatMap((page) => const editableWidgets = (widgetsPosition?.placeHolder ?? []).flatMap(
page.pos ({ pos = [], pageNumber }) =>
.filter((widget) => !widget.options?.isReadOnly) pos
.map((widget) => ({ .filter(
widget, ({ options }) =>
pageNumber: page.pageNumber !options?.isReadOnly
})) )
.map((widget) => ({ widget, pageNumber }))
); );
//get current index of widget //get current index of widget
const currentIndex = editableWidgets.findIndex( const currentIndex = editableWidgets.findIndex(
@@ -1926,13 +1948,11 @@ function WidgetsValueModal(props) {
</div> </div>
</div> </div>
<div className="flex justify-between items-center text-center"> <div className="flex justify-between items-center text-center">
{!["checkbox", "dropdown", "date", "radio"].includes( {showClearbtn ? (
currWidgetsDetails?.type
) ? (
<button <button
type="button" type="button"
className="op-btn op-btn-ghost op-btn-sm text-base-content mr-1" className="op-btn op-btn-ghost op-btn-sm text-base-content mr-1"
onClick={() => handleClear()} onClick={handleClear}
> >
{t("clear")} {t("clear")}
</button> </button>
@@ -3,15 +3,20 @@ import { isMobile } from "../../constant/Utils";
// `getWidgetType` is used to load ui of widget in side list // `getWidgetType` is used to load ui of widget in side list
const getWidgetType = (item, widgetName) => { const getWidgetType = (item, widgetName) => {
return ( return (
<div className="op-btn w-fit md:w-[100%] op-btn-primary op-btn-outline op-btn-sm focus:outline-none outline outline-[1.5px] ml-[6px] md:ml-0 p-0 overflow-hidden"> <div
title={widgetName}
className="op-btn w-fit md:w-[100%] op-btn-primary op-btn-outline op-btn-sm focus:outline-none ml-[6px] outline outline-[1px] md:ml-0 p-0 overflow-hidden"
>
<div className="w-full h-full flex md:justify-between items-center"> <div className="w-full h-full flex md:justify-between items-center">
<div className="flex justify-start items-center text-[13px] ml-1"> <div className="flex justify-start items-center ml-1 overflow-hidden">
{!isMobile && <i className="fa-light fa-grip-vertical ml-[3px]"></i>} {!isMobile && (
<span className="md:inline-block text-center text-[15px] ml-[5px] font-semibold pr-1 md:pr-0"> <i className="fa-light fa-grip-vertical ml-[3px] text-[13px]"></i>
)}
<span className="md:inline-block text-center text-[11px] mx-[3px] truncate font-medium capitalize">
{widgetName} {widgetName}
</span> </span>
</div> </div>
<div className="text-[20px] op-btn op-btn-primary rounded-none w-[40px] h-full flex justify-center items-center"> <div className="text-[13px] op-btn op-btn-primary rounded-none w-[20px] h-full flex justify-center items-center">
<i className={item.icon}></i> <i className={item.icon}></i>
</div> </div>
</div> </div>
+93 -122
View File
@@ -9,7 +9,7 @@ import fontkit from "@pdf-lib/fontkit";
import { themeColor } from "./const"; import { themeColor } from "./const";
import { format, toZonedTime } from "date-fns-tz"; import { format, toZonedTime } from "date-fns-tz";
import i18n from "../i18n"; import i18n from "../i18n";
import { buildDownloadFilename } from "../utils"; import { applyNumberFormulasToPages, buildDownloadFilename } from "../utils";
export const fontsizeArr = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]; export const fontsizeArr = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28];
export const fontColorArr = ["red", "black", "blue", "yellow"]; export const fontColorArr = ["red", "black", "blue", "yellow"];
@@ -327,6 +327,7 @@ export const widgets = [
{ type: "name", icon: "fa-light fa-user", iconSize: "21px" }, { type: "name", icon: "fa-light fa-user", iconSize: "21px" },
{ type: "job title", icon: "fa-light fa-address-card", iconSize: "17px" }, { type: "job title", icon: "fa-light fa-address-card", iconSize: "17px" },
{ type: "company", icon: "fa-light fa-building", iconSize: "25px" }, { type: "company", icon: "fa-light fa-building", iconSize: "25px" },
{ type: "email", icon: "fa-light fa-envelope", iconSize: "20px" },
{ type: "date", icon: "fa-light fa-calendar-days", iconSize: "20px" }, { type: "date", icon: "fa-light fa-calendar-days", iconSize: "20px" },
{ type: textWidget, icon: "fa-light fa-text-width", iconSize: "20px" }, { type: textWidget, icon: "fa-light fa-text-width", iconSize: "20px" },
{ type: cellsWidget, icon: "fa-light fa-table-cells", iconSize: "20px" }, { type: cellsWidget, icon: "fa-light fa-table-cells", iconSize: "20px" },
@@ -337,8 +338,7 @@ export const widgets = [
iconSize: "19px" iconSize: "19px"
}, },
{ type: radioButtonWidget, icon: "fa-light fa-circle-dot", iconSize: "20px" }, { type: radioButtonWidget, icon: "fa-light fa-circle-dot", iconSize: "20px" },
{ type: "image", icon: "fa-light fa-image", iconSize: "20px" }, { type: "image", icon: "fa-light fa-image", iconSize: "20px" }
{ type: "email", icon: "fa-light fa-envelope", iconSize: "20px" }
]; ];
export const getDate = (dateformat) => { export const getDate = (dateformat) => {
@@ -410,6 +410,35 @@ export const changeDateToMomentFormat = (format) => {
return "L"; return "L";
} }
}; };
export const getSignerPages = (xyPosition = [], currentKey, signerId) => {
if (!Array.isArray(xyPosition)) {
return [];
}
const hasSigners = xyPosition.some((item) =>
Array.isArray(item?.placeHolder)
);
if (hasSigners) {
const candidates =
signerId !== undefined && signerId !== null && signerId !== ""
? xyPosition.filter(
(item) => item.Id === signerId || item.signerObjId === signerId
)
: xyPosition;
for (const signer of candidates) {
const pages = signer?.placeHolder || [];
const ownsWidget = pages.some((page) =>
(page?.pos || []).some((widget) => widget?.key === currentKey)
);
if (ownsWidget) {
return pages;
}
}
return [];
}
return xyPosition;
};
export const addWidgetOptions = ( export const addWidgetOptions = (
type, type,
signer, signer,
@@ -423,15 +452,9 @@ export const addWidgetOptions = (
return count + page.pos.filter((item) => item.type === type).length; return count + page.pos.filter((item) => item.type === type).length;
}, 0); }, 0);
const count = countSameWidget + 1; const count = countSameWidget + 1;
defaultOpt = { defaultOpt = { name: `${type}-${count}`, status: "required" };
name: `${type}-${count}`,
status: "required"
};
} else { } else {
defaultOpt = { defaultOpt = { name: `${type}-1`, status: "required" };
name: `${type}-1`,
status: "required"
};
} }
switch (type) { switch (type) {
case "signature": case "signature":
@@ -439,11 +462,7 @@ export const addWidgetOptions = (
case "stamp": case "stamp":
return defaultOpt; return defaultOpt;
case "checkbox": case "checkbox":
return { return { ...defaultOpt, isReadOnly: false, isHideLabel: false };
...defaultOpt,
isReadOnly: false,
isHideLabel: false
};
case textInputWidget: case textInputWidget:
return { ...defaultOpt, isReadOnly: false }; return { ...defaultOpt, isReadOnly: false };
case cellsWidget: case cellsWidget:
@@ -457,20 +476,11 @@ export const addWidgetOptions = (
case "initials": case "initials":
return defaultOpt; return defaultOpt;
case "name": case "name":
return { return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
...defaultOpt,
defaultValue: widgetValue ? widgetValue : ""
};
case "company": case "company":
return { return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
...defaultOpt,
defaultValue: widgetValue ? widgetValue : ""
};
case "job title": case "job title":
return { return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
...defaultOpt,
defaultValue: widgetValue ? widgetValue : ""
};
case "date": { case "date": {
const dateFormat = signer?.DateFormat const dateFormat = signer?.DateFormat
? selectFormat(signer?.DateFormat) ? selectFormat(signer?.DateFormat)
@@ -518,15 +528,9 @@ export const addWidgetSelfsignOptions = (
return count + page.pos.filter((item) => item.type === type).length; return count + page.pos.filter((item) => item.type === type).length;
}, 0); }, 0);
const count = countSameWidget + 1; const count = countSameWidget + 1;
defaultOpt = { defaultOpt = { name: `${type}-${count}`, status: "required" };
name: `${type}-${count}`,
status: "required"
};
} else { } else {
defaultOpt = { defaultOpt = { name: `${type}-1`, status: "required" };
name: `${type}-1`,
status: "required"
};
} }
switch (type) { switch (type) {
case "signature": case "signature":
@@ -1077,9 +1081,11 @@ export const onChangeInput = (
return obj; return obj;
}); });
const recalculatedPages = applyNumberFormulasToPages(newUpdateSignPos);
const newUpdateSigner = xyPosition.map((obj) => { const newUpdateSigner = xyPosition.map((obj) => {
if (obj.Id === userId) { if (obj.Id === userId) {
return { ...obj, placeHolder: newUpdateSignPos }; return { ...obj, placeHolder: recalculatedPages };
} }
return obj; return obj;
}); });
@@ -1125,7 +1131,7 @@ export const onChangeInput = (
} }
return obj; return obj;
}); });
setXyPosition(updatePlaceholder); setXyPosition(applyNumberFormulasToPages(updatePlaceholder));
} }
}; };
//function to increase height of text area on press enter //function to increase height of text area on press enter
@@ -1220,13 +1226,13 @@ export const calculateInitialWidthHeight = (widgetData) => {
export const widgetDataValue = (type, value) => { export const widgetDataValue = (type, value) => {
switch (type) { switch (type) {
case "name": case "name":
return value?.Name; return value?.Name || value?.name;
case "company": case "company":
return value?.Company; return value?.Company || value?.company;
case "job title": case "job title":
return value?.JobTitle; return value?.JobTitle || value?.jobTitle;
case "email": case "email":
return value?.Email; return value?.Email || value?.email;
default: default:
return ""; return "";
} }
@@ -1850,6 +1856,16 @@ export const embedWidgetsToDoc = async (
console.log("error in image", e); console.log("error in image", e);
} }
for (let [id, position] of widgetsPositionArr.entries()) { for (let [id, position] of widgetsPositionArr.entries()) {
const isTextTypeWidget = [
textWidget,
textInputWidget,
cellsWidget,
"name",
"company",
"job title",
"date",
"email"
].includes(position.type);
if (hasError) break; // Stop the inner loop if an error occurred if (hasError) break; // Stop the inner loop if an error occurred
try { try {
let img; let img;
@@ -1898,33 +1914,13 @@ export const embedWidgetsToDoc = async (
return y; return y;
} }
} else { } else {
const WidgetsTypeTextExist = [ const yPosition = isTextTypeWidget ? resizePos + 6 : resizePos;
textWidget,
textInputWidget,
cellsWidget,
"name",
"company",
"job title",
"date",
"email"
].includes(position.type);
const yPosition = WidgetsTypeTextExist ? resizePos + 6 : resizePos;
return yPosition; return yPosition;
} }
}; };
const color = position?.options?.fontColor; const color = position?.options?.fontColor;
const updateColorInRgb = getWidgetsFontColor(color); const updateColorInRgb = getWidgetsFontColor(color);
const fontSize = parseInt(position?.options?.fontSize || 12); const fontSize = parseInt(position?.options?.fontSize || 12);
const isTextTypeWidget = [
textWidget,
textInputWidget,
cellsWidget,
"name",
"company",
"job title",
"date",
"email"
].includes(position.type);
if (position.type === "checkbox") { if (position.type === "checkbox") {
// Determine layout mode: 'vertical' (default) or 'horizontal' // Determine layout mode: 'vertical' (default) or 'horizontal'
// const isHorizontal = position.layout === "horizontal"; // const isHorizontal = position.layout === "horizontal";
@@ -2022,7 +2018,7 @@ export const embedWidgetsToDoc = async (
textContent = position.options?.response; textContent = position.options?.response;
} }
} else if (position?.options?.defaultValue) { } else if (position?.options?.defaultValue) {
textContent = position?.options?.defaultValue; textContent = position?.options?.defaultValue?.toString();
} }
if (position.type === cellsWidget) { if (position.type === cellsWidget) {
const cellCount = const cellCount =
@@ -2049,15 +2045,16 @@ export const embedWidgetsToDoc = async (
if (ch) page.drawText(ch, textPosition); if (ch) page.drawText(ch, textPosition);
} }
} else { } else {
const fixedWidth = widgetWidth; // Set your fixed width const fixedWidth = widgetWidth; // Set your fixed
const isNewOnEnterLineExist = textContent.includes("\n"); textContent = textContent?.toString();
const isNewOnEnterLineExist = textContent?.includes("\n");
// Function to break text into lines based on the fixed width // Function to break text into lines based on the fixed width
const NewbreakTextIntoLines = (textContent, width) => { const NewbreakTextIntoLines = (textContent, width) => {
const lines = []; const lines = [];
let currentLine = ""; let currentLine = "";
for (const word of textContent.split(" ")) { for (const word of textContent?.split(" ")) {
//get text line width //get text line width
const lineWidth = font.widthOfTextAtSize( const lineWidth = font.widthOfTextAtSize(
`${currentLine} ${word}`, `${currentLine} ${word}`,
@@ -2242,7 +2239,6 @@ export const embedWidgetsToDoc = async (
width: widgetWidth, width: widgetWidth,
height: widgetHeight height: widgetHeight
}; };
const imageOptions = getWidgetPosition(page, signature, 1, getSize); const imageOptions = getWidgetPosition(page, signature, 1, getSize);
page.drawImage(img, imageOptions); page.drawImage(img, imageOptions);
} }
@@ -2370,56 +2366,34 @@ export const contractDocument = async (documentId, include) => {
//function for add default signature or image for all requested location //function for add default signature or image for all requested location
export const addDefaultSignatureImg = (xyPosition, defaultSignImg, type) => { export const addDefaultSignatureImg = (xyPosition, defaultSignImg, type) => {
let imgWH = { width: "", height: "" };
const img = new Image();
img.src = defaultSignImg;
if (img.complete) {
imgWH = { width: img.width, height: img.height };
}
let xyDefaultPos = []; let xyDefaultPos = [];
for (let i = 0; i < xyPosition.length; i++) { for (let i = 0; i < xyPosition.length; i++) {
// let getIMGWH;
const getXYdata = xyPosition[i].pos; const getXYdata = xyPosition[i].pos;
const getPageNo = xyPosition[i].pageNumber; const getPageNo = xyPosition[i].pageNumber;
const getPosData = getXYdata; const getPosData = getXYdata;
const addSign = getPosData.map((position) => { const addSign = getPosData.map((position) => {
// getIMGWH = calculateImgAspectRatio(imgWH, position);
if (position.type) { if (position.type) {
if (position?.type === type) { if (position?.type === type) {
return { return {
...position, ...position,
SignUrl: defaultSignImg, SignUrl: defaultSignImg,
// Width: getIMGWH.newWidth,
// Height: getIMGWH.newHeight,
ImageType: "default", ImageType: "default",
options: { options: { ...position.options, response: defaultSignImg }
...position.options,
response: defaultSignImg
}
}; };
} }
} else if (position && !position.isStamp) { } else if (position && !position.isStamp) {
return { return {
...position, ...position,
SignUrl: defaultSignImg, SignUrl: defaultSignImg,
// Width: getIMGWH.newWidth,
// Height: getIMGWH.newHeight,
ImageType: "default", ImageType: "default",
options: { options: { ...position.options, response: defaultSignImg }
...position.options,
response: defaultSignImg
}
}; };
} }
return position; return position;
}); });
const newXypos = { const newXypos = { pageNumber: getPageNo, pos: addSign };
pageNumber: getPageNo,
pos: addSign
};
xyDefaultPos.push(newXypos); xyDefaultPos.push(newXypos);
} }
return xyDefaultPos; return xyDefaultPos;
@@ -3006,34 +2980,34 @@ export const saveLanguageInLocal = (i18n) => {
const detectedLanguage = i18n.language || "en"; const detectedLanguage = i18n.language || "en";
localStorage.setItem("i18nextLng", detectedLanguage); localStorage.setItem("i18nextLng", detectedLanguage);
}; };
//function to get default signatur eof current user from `contracts_Signature` class
// function to get default signature of current user from `contracts_Signature` class
export const getDefaultSignature = async (objectId) => { export const getDefaultSignature = async (objectId) => {
try { try {
const query = new Parse.Query("contracts_Signature"); if (objectId) {
query.equalTo("UserId", { const result = await Parse.Cloud.run("getdefaultsignature", {
__type: "Pointer", userId: objectId
className: "_User", });
objectId: objectId if (result) {
}); const res = JSON.parse(JSON.stringify(result));
const defaultSignature = res?.ImageURL
? await getBase64FromUrl(res?.ImageURL, true)
: "";
const defaultInitial = res?.Initials
? await getBase64FromUrl(res?.Initials, true)
: "";
const result = await query.first(); return {
if (result) { status: "success",
const res = JSON.parse(JSON.stringify(result)); res: {
const defaultSignature = res?.ImageURL id: result?.id,
? await getBase64FromUrl(res?.ImageURL, true) defaultSignature: defaultSignature,
: ""; defaultInitial: defaultInitial
const defaultInitial = res?.Initials }
? await getBase64FromUrl(res?.Initials, true) };
: ""; }
} else {
return { return { status: "error" };
status: "success",
res: {
id: result?.id,
defaultSignature: defaultSignature,
defaultInitial: defaultInitial
}
};
} }
} catch (err) { } catch (err) {
console.log( console.log(
@@ -3359,10 +3333,7 @@ export const handleHeighlightWidget = (
...data, ...data,
pos: data.pos.map((position) => { pos: data.pos.map((position) => {
if (position.key === key) { if (position.key === key) {
return { return { ...position, zIndex: highestZIndex + 1 };
...position,
zIndex: highestZIndex + 1
};
} }
return position; return position;
}) })
+7 -17
View File
@@ -4,6 +4,10 @@ const parseAppId = process.env.REACT_APP_APPID
? process.env.REACT_APP_APPID ? process.env.REACT_APP_APPID
: "opensign"; : "opensign";
const serverUrl = serverUrl_fn(); const serverUrl = serverUrl_fn();
const commonheader = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
};
export const SaveFileSize = async (size, imageUrl, tenantId, userId) => { export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
//checking server url and save file's size //checking server url and save file's size
const tenantPtr = { const tenantPtr = {
@@ -29,7 +33,6 @@ export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
); );
const response = res.data.results; const response = res.data.results;
let data; let data;
// console.log("response", response);
if (response && response.length > 0) { if (response && response.length > 0) {
data = { data = {
usedStorage: response[0].usedStorage usedStorage: response[0].usedStorage
@@ -39,20 +42,12 @@ export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
await axios.put( await axios.put(
`${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`, `${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`,
data, data,
{ { headers: commonheader }
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
}
); );
} else { } else {
data = { usedStorage: size, PartnersTenant: tenantPtr }; data = { usedStorage: size, PartnersTenant: tenantPtr };
await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, { await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, {
headers: { headers: commonheader
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
}); });
} }
} catch (err) { } catch (err) {
@@ -69,14 +64,9 @@ const saveDataFile = async (size, imageUrl, tenantPtr, UserId) => {
TenantPtr: tenantPtr, TenantPtr: tenantPtr,
...(UserId ? { UserId: UserId } : {}) ...(UserId ? { UserId: UserId } : {})
}; };
// console.log("data save",file, data)
try { try {
await axios.post(`${serverUrl}/classes/partners_DataFiles`, data, { await axios.post(`${serverUrl}/classes/partners_DataFiles`, data, {
headers: { headers: commonheader
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
}); });
} catch (err) { } catch (err) {
console.log("err in save usage ", err); console.log("err in save usage ", err);
+20 -42
View File
@@ -46,10 +46,9 @@ const ManageSign = () => {
objectId: User.id objectId: User.id
}; };
try { try {
const signCls = "contracts_Signature"; const signRes = await Parse.Cloud.run("getdefaultsignature", {
const signQuery = new Parse.Query(signCls); userId: User.id
signQuery.equalTo("UserId", userId); });
const signRes = await signQuery.first();
if (signRes) { if (signRes) {
const res = signRes.toJSON(); const res = signRes.toJSON();
setId(res.objectId); setId(res.objectId);
@@ -226,44 +225,23 @@ const ManageSign = () => {
}; };
const saveEntry = async (obj) => { const saveEntry = async (obj) => {
const signCls = "contracts_Signature"; try {
const User = Parse?.User?.current()?.id; const User = Parse?.User?.current()?.id;
const userId = { __type: "Pointer", className: "_User", objectId: User }; const res = await Parse.Cloud.run("managesign", {
if (id) { signature: obj.url,
try { userId: User,
const updateSign = new Parse.Object(signCls); initials: obj.initialsUrl,
updateSign.id = id; id: id,
updateSign.set("Initials", obj.initialsUrl ? obj.initialsUrl : ""); title: obj.name
updateSign.set("ImageURL", obj.url ? obj.url : ""); });
updateSign.set("SignatureName", obj.name); setIsAlert({ type: "success", message: t("signature-saved-alert") });
updateSign.set("UserId", userId); return res;
const res = await updateSign.save(); } catch (err) {
setIsAlert({ type: "success", message: t("signature-saved-alert") }); console.log(err);
return res; setIsAlert({ type: "danger", message: `${err.message}` });
} catch (err) { } finally {
console.log(err); setIsLoader(false);
setIsAlert({ type: "danger", message: `${err.message}` }); setTimeout(() => setIsAlert({}), 2000);
} finally {
setIsLoader(false);
setTimeout(() => setIsAlert({}), 2000);
}
} else {
try {
const updateSign = new Parse.Object(signCls);
updateSign.set("Initials", obj.initialsUrl ? obj.initialsUrl : "");
updateSign.set("ImageURL", obj.url);
updateSign.set("SignatureName", obj.name);
updateSign.set("UserId", userId);
const res = await updateSign.save();
setIsAlert({ type: "success", message: t("signature-saved-alert") });
return res;
} catch (err) {
console.log(err);
setIsAlert({ type: "success", message: `${err.message}` });
} finally {
setIsLoader(false);
setTimeout(() => setIsAlert({}), 2000);
}
} }
}; };
+1 -1
View File
@@ -49,7 +49,7 @@ import {
widgetDataValue, widgetDataValue,
getOriginalWH, getOriginalWH,
handleCheckResponse, handleCheckResponse,
convertJpegToPng convertJpegToPng,
} from "../constant/Utils"; } from "../constant/Utils";
import Header from "../components/pdf/PdfHeader"; import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf"; import RenderPdf from "../components/pdf/RenderPdf";
+26 -47
View File
@@ -722,9 +722,7 @@ function PlaceHolderSign() {
(data) => data.pageNumber === pageNumber (data) => data.pageNumber === pageNumber
); );
if (getPageNumer.length > 0) { if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos; const addSignPos = getPageNumer?.[0]?.pos?.map((url) => {
const getPosData = getXYdata;
const addSignPos = getPosData.map((url) => {
if (url.key === keyValue) { if (url.key === keyValue) {
return { return {
...url, ...url,
@@ -1455,9 +1453,9 @@ function PlaceHolderSign() {
(data) => data.pageNumber === pageNumber (data) => data.pageNumber === pageNumber
); );
if (getPageNumer.length > 0) { if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos; const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
const getPosData = getXYdata; const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
const addSignPos = getPosData.map((position) => { const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
if (position.key === currWidgetsDetails?.key) { if (position.key === currWidgetsDetails?.key) {
if (currWidgetsDetails?.type === radioButtonWidget) { if (currWidgetsDetails?.type === radioButtonWidget) {
if (addOption) { if (addOption) {
@@ -1486,12 +1484,8 @@ function PlaceHolderSign() {
isReadOnly: isReadOnly || false, isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false, isHideLabel: isHideLabel || false,
defaultValue: defaultValue, defaultValue: defaultValue,
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black"
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
} }
}; };
} }
@@ -1525,12 +1519,8 @@ function PlaceHolderSign() {
layout: layout, layout: layout,
isReadOnly: isReadOnly || false, isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false, isHideLabel: isHideLabel || false,
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black"
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
} }
}; };
} }
@@ -1543,12 +1533,8 @@ function PlaceHolderSign() {
status: status, status: status,
values: dropdownOptions, values: dropdownOptions,
defaultValue: defaultValue, defaultValue: defaultValue,
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black",
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black",
...(isReadOnly ? { isReadOnly: isReadOnly || false } : {}) ...(isReadOnly ? { isReadOnly: isReadOnly || false } : {})
} }
}; };
@@ -1563,9 +1549,11 @@ function PlaceHolderSign() {
} }
return obj; return obj;
}); });
const recalculatedPlaceholders =
utils.applyNumberFormulasToPages(newUpdateSignPos);
const newUpdateSigner = signerPos.map((obj) => { const newUpdateSigner = signerPos.map((obj) => {
if (obj.Id === uniqueId) { if (obj.Id === uniqueId) {
return { ...obj, placeHolder: newUpdateSignPos }; return { ...obj, placeHolder: recalculatedPlaceholders };
} }
return obj; return obj;
}); });
@@ -1597,9 +1585,9 @@ function PlaceHolderSign() {
); );
if (getPageNumer.length > 0) { if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos; const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
const getPosData = getXYdata; const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
const addSignPos = getPosData.map((position) => { const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
if (position.key === currWidgetsDetails?.key) { if (position.key === currWidgetsDetails?.key) {
if (position.type === textInputWidget) { if (position.type === textInputWidget) {
return { return {
@@ -1612,12 +1600,8 @@ function PlaceHolderSign() {
defaultValue: defaultdata?.defaultValue || "", defaultValue: defaultdata?.defaultValue || "",
validation: validation:
{}, {},
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black",
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black",
isReadOnly: defaultdata?.isReadOnly || false isReadOnly: defaultdata?.isReadOnly || false
} }
}; };
@@ -1636,16 +1620,13 @@ function PlaceHolderSign() {
), ),
validation: validation:
{}, {},
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black",
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black",
isReadOnly: defaultdata?.isReadOnly || false isReadOnly: defaultdata?.isReadOnly || false
} }
}; };
} else if (["signature"].includes(position.type)) { }
else if (["signature"].includes(position.type)) {
return { return {
...position, ...position,
options: { options: {
@@ -1663,12 +1644,8 @@ function PlaceHolderSign() {
status: defaultdata.status, status: defaultdata.status,
defaultValue: defaultdata.defaultValue, defaultValue: defaultdata.defaultValue,
hint: defaultdata?.hint || "", hint: defaultdata?.hint || "",
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black"
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
} }
}; };
} }
@@ -2535,6 +2512,8 @@ function PlaceHolderSign() {
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
roleName={roleName} roleName={roleName}
widgetsSource={signerPos}
activeSignerId={uniqueId}
/> />
<RotateAlert <RotateAlert
showRotateAlert={showRotateAlert.status} showRotateAlert={showRotateAlert.status}
+57 -36
View File
@@ -69,6 +69,7 @@ import {
import WidgetsValueModal from "../components/pdf/WidgetsValueModal"; import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
import WidgetNameModal from "../components/pdf/WidgetNameModal"; import WidgetNameModal from "../components/pdf/WidgetNameModal";
import CellsSettingModal from "../components/pdf/CellsSettingModal"; import CellsSettingModal from "../components/pdf/CellsSettingModal";
import { applyNumberFormulasToPages } from "../utils";
//For signYourself inProgress section signer can add sign and complete doc sign. //For signYourself inProgress section signer can add sign and complete doc sign.
function SignYourSelf() { function SignYourSelf() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -140,6 +141,8 @@ function SignYourSelf() {
const [isDownloadModal, setIsDownloadModal] = useState(false); const [isDownloadModal, setIsDownloadModal] = useState(false);
const [isResize, setIsResize] = useState(false); const [isResize, setIsResize] = useState(false);
const [isUploadPdf, setIsUploadPdf] = useState(false); const [isUploadPdf, setIsUploadPdf] = useState(false);
const [unSignedWidgetId, setUnSignedWidgetId] = useState("");
const [widgetsTour, setWidgetsTour] = useState(false);
const [owner, setOwner] = useState({}); const [owner, setOwner] = useState({});
const [, drop] = useDrop({ const [, drop] = useDrop({
@@ -586,9 +589,23 @@ function SignYourSelf() {
} }
if (!isEnableOTP || isEmailVerified) { if (!isEnableOTP || isEmailVerified) {
let showAlert = false, let showAlert = false,
isSignatureExist = false; widgetKey,
tourPageNumber;
try { try {
const isSignatureExist = xyPosition?.some((p) =>
p?.pos?.some((x) => x?.type === "signature")
);
if (!isSignatureExist) {
setIsAlert({
header: t("fields-required"),
isShow: true,
alertMessage: t("signature-widget-alert-1")
});
return;
}
for (let i = 0; i < xyPosition?.length; i++) { for (let i = 0; i < xyPosition?.length; i++) {
tourPageNumber = xyPosition[i]?.pageNumber;
const requiredWidgets = xyPosition[i].pos.filter( const requiredWidgets = xyPosition[i].pos.filter(
(position) => position.type !== "checkbox" (position) => position.type !== "checkbox"
); );
@@ -597,34 +614,24 @@ function SignYourSelf() {
for (let i = 0; i < requiredWidgets?.length; i++) { for (let i = 0; i < requiredWidgets?.length; i++) {
checkSigned = requiredWidgets[i]?.options?.response; checkSigned = requiredWidgets[i]?.options?.response;
if (!checkSigned) { if (!checkSigned) {
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl; const checkSignUrl = requiredWidgets[i]?.SignUrl;
let checkDefaultSigned = let checkDefaultSigned =
requiredWidgets[i]?.options?.defaultValue; requiredWidgets[i]?.options?.defaultValue;
if (!checkSignUrl && !checkDefaultSigned && !showAlert) { if (!checkSignUrl && !checkDefaultSigned && !showAlert) {
showAlert = true; showAlert = true;
widgetKey = requiredWidgets[i]?.key;
} }
} }
} }
} }
//condition to check exist signature widget or not if (showAlert) {
if (!isSignatureExist) { break;
isSignatureExist = xyPosition[i].pos.some(
(data) => data?.type === "signature"
);
} }
} }
if (xyPosition.length === 0 || !isSignatureExist) { if (showAlert) {
setIsAlert({ setUnSignedWidgetId(widgetKey);
header: t("fields-required"), setPageNumber(tourPageNumber);
isShow: true, setWidgetsTour(true);
alertMessage: t("signature-widget-alert-1")
});
return;
} else if (showAlert) {
setIsAlert({
isShow: true,
alertMessage: t("signature-widget-alert-2")
});
return; return;
} else { } else {
setIsUiLoading(true); setIsUiLoading(true);
@@ -771,9 +778,7 @@ function SignYourSelf() {
(data) => data.pageNumber === pageNumber (data) => data.pageNumber === pageNumber
); );
if (filterDropPos?.length > 0) { if (filterDropPos?.length > 0) {
const getXYdata = xyPosition[index].pos; const addSign = xyPosition[index]?.pos?.map((url) => {
const getPosData = getXYdata;
const addSign = getPosData.map((url) => {
if (url.key === dragKey) { if (url.key === dragKey) {
return { return {
...url, ...url,
@@ -958,11 +963,9 @@ function SignYourSelf() {
(data) => data.pageNumber === pageNumber (data) => data.pageNumber === pageNumber
); );
if (getPageNumer.length > 0) { if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos;
const getPosData = getXYdata;
const widgetLayout = const widgetLayout =
currWidgetsDetails?.type === "checkbox" ? { layout: layout } : {}; currWidgetsDetails?.type === "checkbox" ? { layout: layout } : {};
const addSignPos = getPosData.map((position) => { const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
if (position.key === currWidgetsDetails?.key) { if (position.key === currWidgetsDetails?.key) {
if (addOption) { if (addOption) {
return { return {
@@ -1024,9 +1027,7 @@ function SignYourSelf() {
(data) => data.pageNumber === pageNumber (data) => data.pageNumber === pageNumber
); );
if (getPageNumer.length > 0) { if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos; const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
const getPosData = getXYdata;
const addSignPos = getPosData.map((position) => {
if (position.key === currWidgetsDetails?.key) { if (position.key === currWidgetsDetails?.key) {
return { return {
...position, ...position,
@@ -1083,6 +1084,8 @@ function SignYourSelf() {
); );
if (getPageNumer.length > 0) { if (getPageNumer.length > 0) {
const updatePos = getPageNumer[0].pos.map((position) => { const updatePos = getPageNumer[0].pos.map((position) => {
const textSize = newFontSize || position?.options?.fontSize;
const textColor = newFontColor || position?.options?.fontColor;
if (position.key === currWidgetsDetails?.key) { if (position.key === currWidgetsDetails?.key) {
if (position.type === cellsWidget) { if (position.type === cellsWidget) {
const count = parseInt( const count = parseInt(
@@ -1096,20 +1099,19 @@ function SignYourSelf() {
name: defaultdata?.name || position.options?.name || "Cells", name: defaultdata?.name || position.options?.name || "Cells",
cellCount: count, cellCount: count,
defaultValue: (defaultdata?.defaultValue || "").slice(0, count), defaultValue: (defaultdata?.defaultValue || "").slice(0, count),
fontSize: newFontSize || position.options?.fontSize || 12, fontSize: textSize || 12,
fontColor: fontColor: textColor || "black"
newFontColor || position.options?.fontColor || "black"
} }
}; };
} else { }
else {
return { return {
...position, ...position,
options: { options: {
...position.options, ...position.options,
name: defaultdata?.name || position.options?.name, name: defaultdata?.name || position.options?.name,
fontSize: newFontSize || position.options?.fontSize || 12, fontSize: textSize || 12,
fontColor: fontColor: textColor || "black"
newFontColor || position.options?.fontColor || "black"
} }
}; };
} }
@@ -1119,7 +1121,7 @@ function SignYourSelf() {
const updateXYposition = xyPosition.map((obj, ind) => const updateXYposition = xyPosition.map((obj, ind) =>
ind === index ? { ...obj, pos: updatePos } : obj ind === index ? { ...obj, pos: updatePos } : obj
); );
setXyPosition(updateXYposition); setXyPosition(applyNumberFormulasToPages(updateXYposition));
} }
setFontSize(); setFontSize();
setFontColor(); setFontColor();
@@ -1196,6 +1198,14 @@ function SignYourSelf() {
setPdfBase64Url(urlDetails.base64); setPdfBase64Url(urlDetails.base64);
setShowRotateAlert({ status: false, degree: 0 }); setShowRotateAlert({ status: false, degree: 0 });
}; };
const WidgetTourConfig = [
{
selector: '[data-tut="IsSigned"]',
content: t("signature-validate-alert-2"),
position: "top",
style: { fontSize: "13px" }
}
];
return ( return (
<> <>
{isLoading.isLoad ? ( {isLoading.isLoad ? (
@@ -1241,6 +1251,14 @@ function SignYourSelf() {
isOpen={signTour} isOpen={signTour}
/> />
)} )}
<Tour
showNumber={false}
showNavigation={false}
showNavigationNumber={false}
onRequestClose={() => setWidgetsTour(false)}
steps={WidgetTourConfig}
isOpen={widgetsTour}
/>
{/* this component used to render all pdf pages in left side */} {/* this component used to render all pdf pages in left side */}
<RenderAllPdfPage <RenderAllPdfPage
allPages={allPages} allPages={allPages}
@@ -1408,6 +1426,7 @@ function SignYourSelf() {
divRef={divRef} divRef={divRef}
currWidgetsDetails={currWidgetsDetails} currWidgetsDetails={currWidgetsDetails}
isShowModal={isShowModal} isShowModal={isShowModal}
unSignedWidgetId={unSignedWidgetId}
/> />
)} )}
</div> </div>
@@ -1459,6 +1478,8 @@ function SignYourSelf() {
setFontSize={setFontSize} setFontSize={setFontSize}
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
widgetsSource={xyPosition}
isSelfSign={true}
/> />
<CellsSettingModal <CellsSettingModal
isOpen={isCellsSetting} isOpen={isCellsSetting}
+27 -50
View File
@@ -662,9 +662,7 @@ const TemplatePlaceholder = () => {
(data) => data.pageNumber === pageNumber (data) => data.pageNumber === pageNumber
); );
if (getPageNumer.length > 0) { if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos; const addSignPos = getPageNumer?.[0]?.pos?.map((url) => {
const getPosData = getXYdata;
const addSignPos = getPosData.map((url) => {
if (url.key === keyValue) { if (url.key === keyValue) {
return { return {
...url, ...url,
@@ -756,7 +754,7 @@ const TemplatePlaceholder = () => {
} }
}; };
useEffect(() => { useEffect(() => {
if (pdfDetails?.[0]?.CreatedBy?.objectId !== user?.objectId) return; if (pdfDetails?.[0]?.CreatedBy?.objectId !== user?.objectId || isPrefillModal) return;
const timer = setTimeout(() => { const timer = setTimeout(() => {
autosavedetails(); autosavedetails();
}, 2000); }, 2000);
@@ -1319,10 +1317,9 @@ const TemplatePlaceholder = () => {
); );
if (getPageNumer.length > 0) { if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos; const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
const getPosData = getXYdata; const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
const addSignPos = getPosData.map((position) => {
if (position.key === currWidgetsDetails?.key) { if (position.key === currWidgetsDetails?.key) {
if (currWidgetsDetails?.type === radioButtonWidget) { if (currWidgetsDetails?.type === radioButtonWidget) {
if (addOption) { if (addOption) {
@@ -1351,12 +1348,8 @@ const TemplatePlaceholder = () => {
defaultValue: defaultValue, defaultValue: defaultValue,
isReadOnly: isReadOnly || false, isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false, isHideLabel: isHideLabel || false,
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black"
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
} }
}; };
} }
@@ -1390,12 +1383,8 @@ const TemplatePlaceholder = () => {
isReadOnly: isReadOnly || false, isReadOnly: isReadOnly || false,
defaultValue: defaultValue, defaultValue: defaultValue,
isHideLabel: isHideLabel || false, isHideLabel: isHideLabel || false,
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black"
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
} }
}; };
} }
@@ -1408,12 +1397,8 @@ const TemplatePlaceholder = () => {
status: status, status: status,
values: dropdownOptions, values: dropdownOptions,
defaultValue: defaultValue, defaultValue: defaultValue,
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black",
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black",
...(isReadOnly ? { isReadOnly: isReadOnly || false } : {}) ...(isReadOnly ? { isReadOnly: isReadOnly || false } : {})
} }
}; };
@@ -1428,9 +1413,11 @@ const TemplatePlaceholder = () => {
} }
return obj; return obj;
}); });
const recalculatedPlaceholders =
utils.applyNumberFormulasToPages(newUpdateSignPos);
const newUpdateSigner = signerPos.map((obj) => { const newUpdateSigner = signerPos.map((obj) => {
if (obj.Id === uniqueId) { if (obj.Id === uniqueId) {
return { ...obj, placeHolder: newUpdateSignPos }; return { ...obj, placeHolder: recalculatedPlaceholders };
} }
return obj; return obj;
}); });
@@ -1463,9 +1450,9 @@ const TemplatePlaceholder = () => {
); );
if (getPageNumer.length > 0) { if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos; const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
const getPosData = getXYdata; const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
const addSignPos = getPosData.map((position) => { const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
if (position.key === currWidgetsDetails?.key) { if (position.key === currWidgetsDetails?.key) {
if (position.type === textInputWidget) { if (position.type === textInputWidget) {
return { return {
@@ -1479,12 +1466,8 @@ const TemplatePlaceholder = () => {
isReadOnly: defaultdata?.isReadOnly || false, isReadOnly: defaultdata?.isReadOnly || false,
validation: validation:
{}, {},
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black"
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
} }
}; };
} else if (position.type === cellsWidget) { } else if (position.type === cellsWidget) {
@@ -1503,15 +1486,12 @@ const TemplatePlaceholder = () => {
validation: validation:
{}, {},
isReadOnly: defaultdata?.isReadOnly || false, isReadOnly: defaultdata?.isReadOnly || false,
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black"
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
} }
}; };
} else if (["signature"].includes(position.type)) { }
else if (["signature"].includes(position.type)) {
return { return {
...position, ...position,
options: { options: {
@@ -1529,12 +1509,8 @@ const TemplatePlaceholder = () => {
status: defaultdata.status, status: defaultdata.status,
defaultValue: defaultdata.defaultValue, defaultValue: defaultdata.defaultValue,
hint: defaultdata?.hint || "", hint: defaultdata?.hint || "",
fontSize: fontSize: textSize || 12,
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontColor: textColor || "black"
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
} }
}; };
} }
@@ -2125,6 +2101,8 @@ const TemplatePlaceholder = () => {
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
roleName={roleName} roleName={roleName}
widgetsSource={signerPos}
activeSignerId={uniqueId}
/> />
{isPrefillModal && ( {isPrefillModal && (
<PrefillWidgetModal <PrefillWidgetModal
@@ -2140,7 +2118,6 @@ const TemplatePlaceholder = () => {
navigatePageToDoc={navigatePageToDoc} navigatePageToDoc={navigatePageToDoc}
setIsNewContact={setIsNewContact} setIsNewContact={setIsNewContact}
isNewContact={isNewContact} isNewContact={isNewContact}
documentFlow={"template"}
docId={pdfDetails[0]?.objectId} docId={pdfDetails[0]?.objectId}
isSubmit={isUseTemplate} isSubmit={isUseTemplate}
/> />
+1 -1
View File
@@ -14,7 +14,7 @@ const Tooltip = ({
onClick={() => onClick={() =>
handleOnlickHelp ? handleOnlickHelp() : openInNewTab(url) handleOnlickHelp ? handleOnlickHelp() : openInNewTab(url)
} }
className={"text-center cursor-pointer"} className="text-center cursor-pointer focus:outline-none"
> >
<sup> <sup>
<i <i
+46 -11
View File
@@ -1,4 +1,4 @@
import { generateTitleFromFilename, getBase64FromUrl } from "../constant/Utils"; import { generateTitleFromFilename } from "../constant/Utils";
import { base64StringtoFile, uploadFile } from "./fileUtils"; import { base64StringtoFile, uploadFile } from "./fileUtils";
import { sanitizeFileName } from "./sanitizeFileName"; import { sanitizeFileName } from "./sanitizeFileName";
import Parse from "parse"; import Parse from "parse";
@@ -16,22 +16,20 @@ export const saveToMySign = async (widget) => {
: `${replaceSpace}__initials`; : `${replaceSpace}__initials`;
const file = base64StringtoFile(base64, fileName); const file = base64StringtoFile(base64, fileName);
const fileUrl = await uploadFile(file, User?.id); const fileUrl = await uploadFile(file, User?.id);
// below code is used to save or update default signature, initials, stamp const params = {
const signCls = new Parse.Object("contracts_Signature"); id: widget?.defaultSignId, // pass id to update existing signature/initials
if (widget?.defaultSignId) { userId: User.id
signCls.id = widget.defaultSignId; };
}
if (widget?.type === "initials") { if (widget?.type === "initials") {
signCls.set("Initials", fileUrl); params.initials = fileUrl; // save initials image url
} else if (widget?.type === "signature") { } else if (widget?.type === "signature") {
signCls.set("ImageURL", fileUrl); params.signature = fileUrl; // save signature image url
} }
signCls.set("UserId", Parse.User.createWithoutData(User.id)); const signRes = await Parse.Cloud.run("savesignature", params);
const signRes = await signCls.save();
return { base64File: base64, id: signRes?.id }; return { base64File: base64, id: signRes?.id };
} catch (err) { } catch (err) {
console.log("Err while saving signature", err); console.log("Err while saving signature", err);
return url; return err;
} }
} }
}; };
@@ -41,3 +39,40 @@ export const hasSignatureWidget = (s) =>
(s.placeHolder ?? []).some((ph) => (s.placeHolder ?? []).some((ph) =>
(ph?.pos ?? []).some((p) => p?.type === "signature") (ph?.pos ?? []).some((p) => p?.type === "signature")
); );
/**
*
* @param {Array<object>} pages
* Array of page objects. Each page is expected to have:
* - pos: Array<Widget> where Widget has shape:
* {
* key: string,
* type: string,
* options?: {
* response?: number|string,
* defaultValue?: number|string,
* formula?: string
* }
* }
*
* @returns {Array<object>}
*/
export const applyNumberFormulasToPages = (pages = []) => {
// Guard: if not an array or empty, just return as-is
if (!Array.isArray(pages) || pages.length === 0) {
return pages;
}
// Clone pages and each widget.options so we do not mutate caller's data.
// (Shallow clone is sufficient for our current usage since we only touch `options`.)
const clonedPages = pages.map((page) => ({
...page,
pos: (page?.pos || []).map((widget) => ({
...widget,
options: { ...widget.options }
}))
}));
// Return the cloned, updated pages
return clonedPages;
};
+118 -138
View File
@@ -1,169 +1,149 @@
import fs from 'node:fs';
import axios from 'axios'; import axios from 'axios';
import multer from 'multer'; import multer from 'multer';
import libre from 'libreoffice-convert'; import libre from 'libreoffice-convert';
import util from 'node:util';
import { cloudServerUrl, getSecureUrl, serverAppId } from '../../Utils.js'; import { cloudServerUrl, getSecureUrl, serverAppId } from '../../Utils.js';
libre.convertAsync = util.promisify(libre.convert); /** @returns {Promise<Buffer>} */
async function convertLibre(input, ext, opts) {
return await new Promise((resolve, reject) => {
try {
libre.convert(input, ext, opts, (err, out) => (err ? reject(err) : resolve(out)));
} catch (e) {
reject(e);
}
});
}
// -------------------- Concurrency limiter (no dependency) --------------------
const MAX_CONCURRENCY = Number(process.env.DOCX2PDF_CONCURRENCY || 2); // 1 for tiny droplets
let active = 0;
const queue = [];
function runWithLimit(task) {
return new Promise((resolve, reject) => {
const run = () => {
active++;
Promise.resolve()
.then(task)
.then(resolve, reject)
.finally(() => {
active--;
if (queue.length) queue.shift()();
});
};
if (active < MAX_CONCURRENCY) run();
else queue.push(run);
});
}
const storage = multer.diskStorage({ // -------------------- Timeout helper (async version = no TS hint) --------------------
destination(req, file, cb) { /**
cb(null, 'exports'); * @template T
}, * @param {Promise<T>} promise
filename(req, file, cb) { * @param {number} ms
cb(null, file.originalname); * @param {string} [label='operation']
* @returns {Promise<T>}
*/
export async function withTimeout(promise, ms, label = 'operation') {
let timer;
try {
const timeout = new Promise((_, reject) => {
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
});
return await Promise.race([promise, timeout]);
} finally {
if (timer) clearTimeout(timer);
}
}
// -------------------- Multer: use memory storage --------------------
const storage = multer.memoryStorage();
export const upload = multer({
storage,
fileFilter: (req, file, cb) => {
const okExt = /\.docx$/i.test(file.originalname || '');
const okMime =
file.mimetype === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
file.mimetype === 'application/octet-stream';
if (okExt && okMime) return cb(null, true);
cb(new Error('Only .docx files are supported'));
}, },
}); });
export const upload = multer({ storage });
function generatePdfName(length) { function generatePdfName(length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = ''; let result = '';
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) result += chars.charAt(Math.floor(Math.random() * chars.length));
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result; return result;
} }
export default async function docxtopdf(req, res) { export default async function docxtopdf(req, res) {
const serverUrl = cloudServerUrl; const serverUrl = cloudServerUrl;
const appId = serverAppId; const parseAppKey = { 'X-Parse-Application-Id': serverAppId };
const masterKey = process.env.MASTER_KEY; const masterKey = process.env.MASTER_KEY;
const inputPath = req.file.path;
const name = generatePdfName(16); const masterHeader = { ...parseAppKey, 'X-Parse-Master-Key': masterKey };
const fileName = `${name}.pdf`; const sessionHeader = { ...parseAppKey, 'X-Parse-Session-Token': req.headers['sessiontoken'] };
const outputPath = './exports/output.pdf';
if (!req.file || !req.file.buffer) {
return res.status(400).json({ error: 'No file uploaded.' });
}
try { try {
const userRes = await axios.get(serverUrl + '/users/me', { // ---- Auth: current user ----
headers: { const userRes = await axios.get(`${serverUrl}/users/me`, { headers: sessionHeader });
'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': req.headers['sessiontoken'], // ---- contracts_Users ----
}, const whereUser = JSON.stringify({
}); UserId: { __type: 'Pointer', className: '_User', objectId: userRes.data.objectId },
const userId = JSON.stringify({
UserId: {
__type: 'Pointer',
className: '_User',
objectId: userRes.data.objectId,
},
}); });
const resUser = await axios.get( const resUser = await axios.get(
serverUrl + `/classes/contracts_Users?where=${userId}&limit=1&include=TenantId`, `${serverUrl}/classes/contracts_Users?where=${whereUser}&limit=1&include=TenantId`,
{ { headers: masterHeader }
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Master-Key': masterKey,
},
}
); );
if (resUser?.data?.results?.length > 0) { if (!resUser?.data?.results?.length) {
const tenantId = resUser.data.results[0].TenantId?.objectId; return res.status(403).json({ error: 'User not linked to tenant.' });
const ext = '.pdf';
const outPath = `./exports/output${ext}`;
const docxBuf = fs.readFileSync(inputPath);
const pdfBuffer = await libre.convertAsync(docxBuf, ext, undefined);
fs.writeFileSync(outPath, pdfBuffer);
const file = fs.readFileSync(outPath);
const size = fs.statSync(outPath).size;
const PartnersTenant = JSON.stringify({
PartnersTenant: {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: tenantId,
},
});
const resTenantCredit = await axios.get(
serverUrl + `/classes/partners_TenantCredits?where=${PartnersTenant}&limit=1`,
{
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Master-Key': masterKey,
},
}
);
if (resTenantCredit.data?.results?.length > 0) {
const tenantCreditsId = resTenantCredit.data.results[0].objectId;
const activeFileAdapter = resUser.data.results[0].TenantId?.ActiveFileAdapter;
let fileUrl;
if (activeFileAdapter) {
const params = {
fileBase64: file.toString('base64'),
fileName,
id: activeFileAdapter,
};
const url = serverUrl + '/functions/savetofileadapter';
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': req.headers['sessiontoken'],
};
try {
const savetos3 = await axios.post(url, params, { headers });
fileUrl = savetos3?.data?.result?.url;
} catch (err) {
console.log('err in save to customfile', err);
}
} else {
const parsefile = await axios.post(serverUrl + `/files/${fileName}`, file, {
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Master-Key': masterKey,
'Content-Type': 'application/pdf',
},
});
const fileRes = getSecureUrl(parsefile.data.url);
fileUrl = fileRes.url;
}
const usedStorage = resTenantCredit.data.results[0].usedStorage
? resTenantCredit.data.results[0].usedStorage + size
: size;
await axios.put(
serverUrl + `/classes/partners_TenantCredits/${tenantCreditsId}`,
{ usedStorage },
{
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Master-Key': masterKey,
},
}
);
await axios.post(
serverUrl + '/classes/partners_DataFiles',
{
FileSize: size,
FileUrl: fileUrl,
TenantPtr: {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: tenantId,
},
UserId: {
__type: 'Pointer',
className: '_User',
objectId: userRes.data.objectId,
},
},
{
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Master-Key': masterKey,
},
}
);
[inputPath, outPath].forEach(p => fs.existsSync(p) && fs.unlinkSync(p));
return res.status(200).json({ message: 'success.', url: fileUrl });
}
} }
const tenantId = resUser.data.results[0]?.TenantId?.objectId;
if (!tenantId) {
return res.status(403).json({ error: 'Tenant not found for user.' });
}
// ---- DOCX -> PDF (buffer -> buffer), with concurrency + timeout ----
const fileName = `${generatePdfName(16)}.pdf`;
const pdfBuffer = await runWithLimit(() =>
withTimeout(convertLibre(req.file.buffer, '.pdf', undefined), 60_000, 'DOCX->PDF')
);
// ---- Upload PDF (no disk IO) ----
const activeFileAdapter = resUser.data.results[0]?.TenantId?.ActiveFileAdapter;
let fileUrl;
if (activeFileAdapter) {
const params = {
fileBase64: Buffer.from(pdfBuffer).toString('base64'),
fileName,
id: activeFileAdapter,
};
const url = serverUrl + '/functions/savetofileadapter';
const headers = { 'Content-Type': 'application/json', ...sessionHeader };
const savetos3 = await axios.post(url, params, { headers });
fileUrl = savetos3?.data?.result?.url;
if (!fileUrl) throw new Error('No URL returned from file adapter');
} else {
const parsefile = await axios.post(`${serverUrl}/files/${fileName}`, pdfBuffer, {
headers: { ...masterHeader, 'Content-Type': 'application/pdf' },
});
const fileRes = getSecureUrl(parsefile.data.url);
fileUrl = fileRes.url;
}
return res.status(200).json({ message: 'success.', url: fileUrl });
} catch (err) { } catch (err) {
[inputPath, outputPath].forEach(p => fs.existsSync(p) && fs.unlinkSync(p));
const msg = const msg =
err?.response?.data?.error || err?.response?.data || err?.message || 'Something went wrong.'; err?.response?.data?.error || err?.response?.data || err?.message || 'Something went wrong.';
console.log(`Error converting file: ${msg}`); console.log(`Error in docxtopdf: ${msg}`);
// Friendly message to the client
const message = const message =
'We are currently experiencing some issues with processing DOCX files. Please upload the PDF version or contact us on support@opensignlabs.com'; 'We are currently experiencing some issues with processing DOCX files. Please upload the PDF version or contact us on support@opensignlabs.com';
return res.status(400).json({ error: message }); return res.status(400).json({ error: message });
+6
View File
@@ -57,6 +57,9 @@ import addUser from './parsefunction/addUser.js';
import filterDocs from './parsefunction/filterDocs.js'; import filterDocs from './parsefunction/filterDocs.js';
import sendDeleteUserMail from './parsefunction/sendDeleteUserMail.js'; import sendDeleteUserMail from './parsefunction/sendDeleteUserMail.js';
import resetPassword from './parsefunction/resetPassword.js'; import resetPassword from './parsefunction/resetPassword.js';
import saveSignature from './parsefunction/saveSignature.js';
import manageSign from './parsefunction/manageSign.js';
import getSignature from './parsefunction/getSignature.js';
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic. // 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); Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
@@ -124,3 +127,6 @@ Parse.Cloud.define('adduser', addUser);
Parse.Cloud.define('filterdocs', filterDocs); Parse.Cloud.define('filterdocs', filterDocs);
Parse.Cloud.define('senddeleterequest', sendDeleteUserMail); Parse.Cloud.define('senddeleterequest', sendDeleteUserMail);
Parse.Cloud.define('resetpassword', resetPassword); Parse.Cloud.define('resetpassword', resetPassword);
Parse.Cloud.define('savesignature', saveSignature);
Parse.Cloud.define('managesign', manageSign);
Parse.Cloud.define('getdefaultsignature', getSignature);
@@ -0,0 +1,18 @@
export default async function getSignature(request) {
const { userId } = request.params;
if (!userId) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Missing userId parameter.');
}
if (userId !== request.user?.id) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot save signature for the current user.');
}
try {
const query = new Parse.Query('contracts_Signature');
query.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const result = await query.first({ useMasterKey: true });
return result;
} catch (err) {
console.error('Error fetching signature:', err);
throw err;
}
}
@@ -0,0 +1,28 @@
export default async function manageSign(request) {
const { signature, userId, initials, id, title } = request.params;
if (!userId) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Missing userId parameter.');
}
if (userId !== request.user?.id) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot save signature for the current user.');
}
const userPtr = { __type: 'Pointer', className: '_User', objectId: userId };
try {
const signatureCls = new Parse.Object('contracts_Signature');
if (id) {
signatureCls.id = id;
}
signatureCls.set('Initials', initials ? initials : '');
signatureCls.set('ImageURL', signature ? signature : '');
signatureCls.set('SignatureName', title ? title : '');
if (userPtr) {
signatureCls.set('UserId', userPtr);
}
const signRes = await signatureCls.save(null, { useMasterKey: true });
return signRes;
} catch (err) {
console.error('Error saving signature:', err);
throw err;
}
}
@@ -59,6 +59,7 @@ export default async function saveAsTemplate(request) {
options: { options: {
...p.options, ...p.options,
status: 'required', status: 'required',
response: '',
...(p?.options?.defaultValue ? { defaultValue: '' } : {}), ...(p?.options?.defaultValue ? { defaultValue: '' } : {}),
}, },
})), })),
@@ -0,0 +1,33 @@
export default async function saveSignature(request) {
const { signature, userId, initials, id, title } = request.params;
if (!userId) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Missing userId parameter.');
}
if (userId !== request.user?.id) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot save signature for the current user.');
}
const userPtr = { __type: 'Pointer', className: '_User', objectId: userId };
try {
const signatureCls = new Parse.Object('contracts_Signature');
if (id) {
signatureCls.id = id;
}
if (initials) {
signatureCls.set('Initials', initials);
}
if (signature) {
signatureCls.set('ImageURL', signature);
}
if (title) {
signatureCls.set('SignatureName', title);
}
if (userPtr) {
signatureCls.set('UserId', userPtr);
}
const signRes = await signatureCls.save(null, { useMasterKey: true });
return signRes;
} catch (err) {
throw err;
}
}
@@ -12,15 +12,23 @@ async function sendMailProvider(req, plan, monthchange) {
let mailgunClient; let mailgunClient;
let mailgunDomain; let mailgunDomain;
if (smtpenable) { if (smtpenable) {
transporterSMTP = createTransport({ let transporterConfig = {
host: process.env.SMTP_HOST, host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT || 465, port: process.env.SMTP_PORT || 465,
secure: smtpsecure, secure: smtpsecure,
auth: { };
// ✅ Add auth only if BOTH username & password exist
const smtpUser = process.env.SMTP_USERNAME;
const smtpPass = process.env.SMTP_PASS;
if (smtpUser && smtpPass) {
transporterConfig.auth = {
user: process.env.SMTP_USERNAME ? process.env.SMTP_USERNAME : process.env.SMTP_USER_EMAIL, user: process.env.SMTP_USERNAME ? process.env.SMTP_USERNAME : process.env.SMTP_USER_EMAIL,
pass: process.env.SMTP_PASS, pass: smtpPass,
}, };
}); }
transporterSMTP = createTransport(transporterConfig);
} else { } else {
if (mailgunApiKey) { if (mailgunApiKey) {
const mailgun = new Mailgun(formData); const mailgun = new Mailgun(formData);
@@ -185,7 +193,7 @@ async function sendMailProvider(req, plan, monthchange) {
} }
} }
} catch (err) { } catch (err) {
console.log('err in sendmailv3', err); console.log(`Error in sendmailv3: ${err}`);
if (fs.existsSync(testPdf)) { if (fs.existsSync(testPdf)) {
try { try {
fs.unlinkSync(testPdf); fs.unlinkSync(testPdf);
@@ -236,7 +244,7 @@ async function sendMailProvider(req, plan, monthchange) {
} }
} }
} catch (err) { } catch (err) {
console.log('err in sendmailv3', err); console.log(`Error in sendmailv3: ${err}`);
if (err) { if (err) {
return { status: 'error' }; return { status: 'error' };
} }
+15 -6
View File
@@ -61,20 +61,28 @@ let mailgunDomain;
let isMailAdapter = false; let isMailAdapter = false;
if (smtpenable) { if (smtpenable) {
try { try {
transporterMail = createTransport({ let transporterConfig = {
host: process.env.SMTP_HOST, host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT || 465, port: process.env.SMTP_PORT || 465,
secure: smtpsecure, secure: smtpsecure,
auth: { };
// ✅ Add auth only if BOTH username & password exist
const smtpUser = process.env.SMTP_USERNAME;
const smtpPass = process.env.SMTP_PASS;
if (smtpUser && smtpPass) {
transporterConfig.auth = {
user: process.env.SMTP_USERNAME ? process.env.SMTP_USERNAME : process.env.SMTP_USER_EMAIL, user: process.env.SMTP_USERNAME ? process.env.SMTP_USERNAME : process.env.SMTP_USER_EMAIL,
pass: process.env.SMTP_PASS, pass: smtpPass,
}, };
}); }
transporterMail = createTransport(transporterConfig);
await transporterMail.verify(); await transporterMail.verify();
isMailAdapter = true; isMailAdapter = true;
} catch (err) { } catch (err) {
isMailAdapter = false; isMailAdapter = false;
console.log('Please provide valid SMTP credentials'); console.log(`Please provide valid SMTP credentials: ${err}`);
} }
} else if (process.env.MAILGUN_API_KEY) { } else if (process.env.MAILGUN_API_KEY) {
try { try {
@@ -110,6 +118,7 @@ export const config = {
appName: appName, appName: appName,
allowClientClassCreation: false, allowClientClassCreation: false,
allowExpiredAuthDataToken: false, allowExpiredAuthDataToken: false,
enableInsecureAuthAdapters: false,
encodeParseObjectInCloudFunction: true, encodeParseObjectInCloudFunction: true,
...(isMailAdapter === true ...(isMailAdapter === true
? { ? {
+2660 -2528
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -1,10 +1,10 @@
{ {
"name": "open_sign_server", "name": "open_sign_server",
"version": "2.26.0", "version": "2.30.0",
"description": "An example Parse API server using the parse-server module", "description": "opensign server using the parse-server module",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/ParsePlatform/parse-server-example" "url": "https://github.com/OpenSignLabs/OpenSign"
}, },
"license": "MIT", "license": "MIT",
"main": "index.js", "main": "index.js",
@@ -18,8 +18,8 @@
"watch": "nodemon index.js" "watch": "nodemon index.js"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.896.0", "@aws-sdk/client-s3": "^3.901.0",
"@aws-sdk/s3-request-presigner": "^3.896.0", "@aws-sdk/s3-request-presigner": "^3.901.0",
"@parse/fs-files-adapter": "^3.0.0", "@parse/fs-files-adapter": "^3.0.0",
"@parse/s3-files-adapter": "^4.2.0", "@parse/s3-files-adapter": "^4.2.0",
"@pdf-lib/fontkit": "^1.1.1", "@pdf-lib/fontkit": "^1.1.1",
@@ -31,27 +31,27 @@
"coherentpdf": "^2.5.5", "coherentpdf": "^2.5.5",
"cors": "^2.8.5", "cors": "^2.8.5",
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"dotenv": "^17.2.2", "dotenv": "^17.2.3",
"express": "^5.1.0", "express": "^5.1.0",
"form-data": "^4.0.4", "form-data": "^4.0.4",
"generate-api-key": "^1.0.2", "generate-api-key": "^1.0.2",
"googleapis": "^159.0.0", "googleapis": "^161.0.0",
"libreoffice-convert": "^1.7.0", "libreoffice-convert": "^1.7.0",
"mailgun.js": "^12.1.0", "mailgun.js": "^12.1.0",
"mongodb": "^6.20.0", "mongodb": "^5.9.2",
"multer": "^2.0.2", "multer": "^2.0.2",
"multer-s3": "^3.0.1", "multer-s3": "^3.0.1",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"nodemailer": "^7.0.6", "nodemailer": "^7.0.9",
"p-limit": "^7.1.1", "p-limit": "^7.1.1",
"parse": "^6.1.1", "parse": "^6.1.1",
"parse-dbtool": "^1.2.0", "parse-dbtool": "^1.2.0",
"parse-server": "^8.2.4", "parse-server": "^8.2.5",
"parse-server-api-mail-adapter": "^4.1.0", "parse-server-api-mail-adapter": "^4.1.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"posthog-node": "^5.9.1", "posthog-node": "^5.9.3",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"rate-limiter-flexible": "^7.4.0", "rate-limiter-flexible": "^8.0.1",
"sharp": "^0.34.4", "sharp": "^0.34.4",
"speakeasy": "^2.0.0", "speakeasy": "^2.0.0",
"ws": "^8.18.3" "ws": "^8.18.3"
@@ -59,8 +59,8 @@
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.28.4", "@babel/eslint-parser": "^7.28.4",
"eslint": "^9.36.0", "eslint": "^9.37.0",
"jasmine": "^5.11.0", "jasmine": "^5.12.0",
"mongodb-runner": "^5.10.0", "mongodb-runner": "^5.10.0",
"nodemon": "^3.1.10", "nodemon": "^3.1.10",
"nyc": "^17.1.0", "nyc": "^17.1.0",