mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-05 01:07:39 +02:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca1b87309f | ||
|
|
0b4548130d | ||
|
|
e8b3c7fed8 | ||
|
|
2ce43ec2c8 |
@@ -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 it’s a text‐input widget, enforce that read-only fields have
|
// If it’s a text‐input 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>
|
||||||
|
|||||||
@@ -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;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
? {
|
? {
|
||||||
|
|||||||
Generated
+2660
-2528
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user