mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-08-17 21:25:54 +02:00
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "open_sign",
|
||||
"version": "2.26.0",
|
||||
"version": "2.30.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.9.0",
|
||||
@@ -110,7 +110,7 @@
|
||||
"concurrently": "^9.2.1",
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.24",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"jsdom": "^27.0.0",
|
||||
|
||||
@@ -386,7 +386,8 @@
|
||||
"dropdown": "Dropdown",
|
||||
"radio button": "Radiobutton",
|
||||
"image": "Bild",
|
||||
"email": "E-Mail"
|
||||
"email": "E-Mail",
|
||||
"number": "Zahl"
|
||||
},
|
||||
"fields": "Felder",
|
||||
"recipients": "Empfänger",
|
||||
@@ -443,6 +444,19 @@
|
||||
"maximum-check": "Maximale Anzahl",
|
||||
"cell-count": "Zellzahl",
|
||||
"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",
|
||||
"read-only": "Ist schreibgeschützt",
|
||||
"hide-labels": "Labels ausblenden",
|
||||
|
||||
@@ -386,7 +386,8 @@
|
||||
"dropdown": "dropdown",
|
||||
"radio button": "radio button",
|
||||
"image": "image",
|
||||
"email": "email"
|
||||
"email": "email",
|
||||
"number": "number"
|
||||
},
|
||||
"fields": "Fields",
|
||||
"recipients": "Recipients",
|
||||
@@ -444,6 +445,19 @@
|
||||
"maximum-check": "Maximum check",
|
||||
"cell-count": "Cell count",
|
||||
"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",
|
||||
"read-only": "read only",
|
||||
"hide-labels": "Hide labels",
|
||||
|
||||
@@ -386,7 +386,8 @@
|
||||
"dropdown": "desplegable",
|
||||
"radio button": "botón de radio",
|
||||
"image": "imagen",
|
||||
"email": "correo"
|
||||
"email": "correo",
|
||||
"number": "número"
|
||||
},
|
||||
"fields": "Campos",
|
||||
"recipients": "Destinatarios",
|
||||
@@ -444,6 +445,19 @@
|
||||
"maximum-check": "Chequeo máximo",
|
||||
"cell-count": "recuento de células",
|
||||
"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",
|
||||
"read-only": "Es de solo lectura",
|
||||
"hide-labels": "Esconder etiquetas",
|
||||
|
||||
@@ -386,7 +386,8 @@
|
||||
"dropdown": "dérouler",
|
||||
"radio button": "bouton radio",
|
||||
"image": "image",
|
||||
"email": "e-mail"
|
||||
"email": "e-mail",
|
||||
"number": "nombre"
|
||||
},
|
||||
"fields": "Des champs",
|
||||
"recipients": "Destinataires",
|
||||
@@ -443,6 +444,19 @@
|
||||
"maximum-check": "Contrôle maximum",
|
||||
"cell-count": "numération cellulaire",
|
||||
"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",
|
||||
"read-only": "Est en lecture seule",
|
||||
"hide-labels": "Masquer les étiquettes",
|
||||
|
||||
@@ -386,7 +386,8 @@
|
||||
"dropdown": "ड्रॉपडाउन",
|
||||
"radio button": "रेडियो बटन",
|
||||
"image": "छवि",
|
||||
"email": "ईमेल"
|
||||
"email": "ईमेल",
|
||||
"number": "संख्या"
|
||||
},
|
||||
"fields": "क्षेत्र",
|
||||
"recipients": "प्राप्तकर्ता",
|
||||
@@ -443,6 +444,19 @@
|
||||
"maximum-check": "अधिकतम जाँच",
|
||||
"cell-count": "सेल्स संख्या",
|
||||
"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": "चुनें",
|
||||
"read-only": "सिर्फ पढ़ने के लिए है",
|
||||
"hide-labels": "लेबल छिपाएँ",
|
||||
|
||||
@@ -386,7 +386,8 @@
|
||||
"dropdown": "menu a tendina",
|
||||
"radio button": "pulsante di opzione",
|
||||
"image": "immagine",
|
||||
"email": "email"
|
||||
"email": "email",
|
||||
"number": "numero"
|
||||
},
|
||||
"fields": "Campi",
|
||||
"recipients": "Destinatari",
|
||||
@@ -443,6 +444,19 @@
|
||||
"maximum-check": "Controllo massimo",
|
||||
"cell-count": "conteggio delle cellule",
|
||||
"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",
|
||||
"read-only": "È di sola lettura",
|
||||
"hide-labels": "Nascondi etichette",
|
||||
|
||||
@@ -71,11 +71,9 @@ function DriveBody(props) {
|
||||
});
|
||||
props.setPdfData(updatedData);
|
||||
props.sortingData(null, null, updatedData);
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem(
|
||||
"baseUrl"
|
||||
)}classes/contracts_Document/${docId}`,
|
||||
try {
|
||||
await axios.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${docId}`,
|
||||
updateName,
|
||||
{
|
||||
headers: {
|
||||
@@ -84,18 +82,14 @@ function DriveBody(props) {
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
// const res = result.data;
|
||||
// console.log("res", res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Err ", err);
|
||||
props.setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
);
|
||||
} catch (err) {
|
||||
console.log("Err ", err);
|
||||
props.setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -245,7 +239,6 @@ function DriveBody(props) {
|
||||
)
|
||||
|
||||
.then((Listdata) => {
|
||||
// console.log("Listdata ", Listdata);
|
||||
const res = Listdata.data;
|
||||
if (res) {
|
||||
const updatedData = props.pdfData.filter(
|
||||
|
||||
@@ -31,7 +31,6 @@ function EmailComponent({
|
||||
setIsLoading(true);
|
||||
const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
|
||||
const sendmail = await Parse.Cloud.run("forwarddoc", params);
|
||||
console.log("sendmail ", sendmail);
|
||||
if (sendmail?.status === "success") {
|
||||
setSuccessEmail(true);
|
||||
setIsEmail(false);
|
||||
|
||||
@@ -267,7 +267,8 @@ function Placeholder(props) {
|
||||
(props.isSignYourself || props.isSelfSign)
|
||||
) {
|
||||
props.handleTextSettingModal(true);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
props?.handleNameModal && props?.handleNameModal(true);
|
||||
}
|
||||
|
||||
@@ -305,13 +306,7 @@ function Placeholder(props) {
|
||||
) {
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
}
|
||||
|
||||
//checking widget's type and open widget copy modal for required widgets
|
||||
if (
|
||||
["signature", textInputWidget, textWidget, "stamp", "initials"].includes(
|
||||
props.pos.type
|
||||
)
|
||||
) {
|
||||
if (props?.data?.Role !== "prefill") {
|
||||
props.setIsPageCopy(true);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
} else {
|
||||
@@ -326,7 +321,7 @@ function Placeholder(props) {
|
||||
props.data && props.data?.Id
|
||||
);
|
||||
//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);
|
||||
dispatch(
|
||||
setPrefillImg({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState, forwardRef } from "react";
|
||||
import { useEffect, useMemo, useState, forwardRef } from "react";
|
||||
import {
|
||||
getMonth,
|
||||
getYear,
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
years,
|
||||
selectCheckbox,
|
||||
checkRegularExpress,
|
||||
isBase64
|
||||
isBase64,
|
||||
getSignerPages
|
||||
} from "../../constant/Utils";
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
@@ -19,6 +20,7 @@ import { useTranslation } from "react-i18next";
|
||||
import CellsWidget from "./CellsWidget";
|
||||
import { useSelector } from "react-redux";
|
||||
import Loader from "../../primitives/Loader";
|
||||
|
||||
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";
|
||||
const widgetCls =
|
||||
@@ -65,18 +67,21 @@ function PlaceholderType(props) {
|
||||
props?.pos?.options?.defaultValue ||
|
||||
[]
|
||||
);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// keep displayed value in sync with the stored response
|
||||
setwidgetValue(widgetData);
|
||||
}
|
||||
if (props.pos?.options?.hint) {
|
||||
setHint(props.pos?.options.hint);
|
||||
} 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
|
||||
}, [props.pos]);
|
||||
}, [props.pos, widgetData, type]);
|
||||
|
||||
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -28,6 +28,8 @@ import {
|
||||
} from "../../redux/reducers/widgetSlice";
|
||||
import * as utils from "../../utils";
|
||||
|
||||
const widgetTitle = "font-medium";
|
||||
|
||||
const ShowTextWidget = ({ position, handleWidgetDetails }) => {
|
||||
const inputRef = useRef(null);
|
||||
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) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
// Track already loaded image keys so they don't increment multiple times
|
||||
const loadedSet = useRef(new Set());
|
||||
const initializedRef = useRef(false); // prevent rerun on state updates
|
||||
const prefillImg = useSelector((state) => state.widget.prefillImg);
|
||||
const [image, setImage] = useState(null);
|
||||
const [imageLoaders, setImageLoaders] = useState({});
|
||||
const [currentWidget, setCurrentWidget] = useState("");
|
||||
@@ -61,7 +127,6 @@ function PrefillWidgetModal(props) {
|
||||
const [loadedImages, setLoadedImages] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const years = range(1950, getYear(new Date()) + 16, 1);
|
||||
const widgetTitle = "font-medium";
|
||||
const isAnyLoaderActive = Object.values(imageLoaders).some(
|
||||
(val) => val === true
|
||||
);
|
||||
@@ -93,6 +158,7 @@ function PrefillWidgetModal(props) {
|
||||
|
||||
return flatArray || [];
|
||||
}, [props.prefillData]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(resetWidgetState([]));
|
||||
}, []);
|
||||
@@ -163,67 +229,7 @@ function PrefillWidgetModal(props) {
|
||||
}
|
||||
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) => (
|
||||
<div
|
||||
style={{ fontFamily: "Arial, sans-serif" }}
|
||||
@@ -273,7 +279,6 @@ function PrefillWidgetModal(props) {
|
||||
image.src,
|
||||
image.imgType
|
||||
);
|
||||
setImageLoaders({});
|
||||
if (imageUrl) {
|
||||
return imageUrl;
|
||||
}
|
||||
@@ -414,6 +419,7 @@ function PrefillWidgetModal(props) {
|
||||
// (tracks how many images have finished loading)
|
||||
setLoadedImages((prev) => prev + 1);
|
||||
}
|
||||
setImageLoaders({});
|
||||
};
|
||||
const handleWidgetType = (position) => {
|
||||
switch (position?.type) {
|
||||
@@ -432,9 +438,9 @@ function PrefillWidgetModal(props) {
|
||||
className="mt-[2px] op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
checked={selectCheckbox(ind, position)}
|
||||
onChange={(e) => {
|
||||
handleCheckboxValue(e.target.checked, ind, position);
|
||||
}}
|
||||
onChange={(e) =>
|
||||
handleCheckboxValue(e.target.checked, ind, position)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`checkbox-${position.key + ind}`}
|
||||
@@ -467,9 +473,7 @@ function PrefillWidgetModal(props) {
|
||||
value={
|
||||
position?.options?.response || position?.options?.defaultValue
|
||||
}
|
||||
onChange={(e) => {
|
||||
handleWidgetDetails(position, e.target.value);
|
||||
}}
|
||||
onChange={(e) => handleWidgetDetails(position, e.target.value)}
|
||||
>
|
||||
{/* Default/Title option */}
|
||||
<option value="" disabled hidden>
|
||||
@@ -519,9 +523,7 @@ function PrefillWidgetModal(props) {
|
||||
)}
|
||||
closeOnScroll={true}
|
||||
selected={handleDate(position)}
|
||||
onChange={(date) => {
|
||||
handleOnDateChange(date, position);
|
||||
}}
|
||||
onChange={(date) => handleOnDateChange(date, position)}
|
||||
customInput={<ExampleCustomInput />}
|
||||
dateFormat={position?.options?.validation?.format || "MM/dd/yyyy"}
|
||||
/>
|
||||
@@ -531,7 +533,10 @@ function PrefillWidgetModal(props) {
|
||||
return (
|
||||
<ImageComponent
|
||||
position={position}
|
||||
docId={props?.docId}
|
||||
imageLoaders={imageLoaders}
|
||||
onImageChange={onImageChange}
|
||||
handleImageLoaded={handleImageLoaded}
|
||||
handleClearImage={handleClearImage}
|
||||
/>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
@@ -549,9 +554,7 @@ function PrefillWidgetModal(props) {
|
||||
className="mt-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
checked={handleRadioCheck(data, position)}
|
||||
onChange={() => {
|
||||
handleWidgetDetails(position, data);
|
||||
}}
|
||||
onChange={() => handleWidgetDetails(position, data)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`radio-${position.key + ind}`}
|
||||
@@ -697,9 +700,9 @@ function PrefillWidgetModal(props) {
|
||||
multiValueLabel: () => "mb-[2px]",
|
||||
menu: () =>
|
||||
"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: () =>
|
||||
"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: () =>
|
||||
"p-2 bg-base-200 rounded-lg m-1 p-2"
|
||||
}}
|
||||
@@ -710,7 +713,7 @@ function PrefillWidgetModal(props) {
|
||||
</div>
|
||||
<button
|
||||
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>
|
||||
</button>
|
||||
|
||||
@@ -271,6 +271,7 @@ function RenderPdf(props) {
|
||||
setIsReqSignTourDisabled={props.setIsReqSignTourDisabled}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={props?.currWidgetsDetails}
|
||||
handleNameModal={props?.handleNameModal}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
@@ -484,6 +485,8 @@ function RenderPdf(props) {
|
||||
setIsReqSignTourDisabled={
|
||||
props.setIsReqSignTourDisabled
|
||||
}
|
||||
unSignedWidgetId={props.unSignedWidgetId}
|
||||
handleNameModal={props.handleNameModal}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -91,14 +91,14 @@ function WidgetComponent(props) {
|
||||
name,
|
||||
jobTitle,
|
||||
company,
|
||||
email,
|
||||
date,
|
||||
text,
|
||||
cells,
|
||||
checkbox,
|
||||
dropdown,
|
||||
radioButton,
|
||||
image,
|
||||
email
|
||||
image
|
||||
];
|
||||
const getWidgetArray = widgets;
|
||||
const newUpdateSigner = getWidgetArray.map((obj, ind) => {
|
||||
@@ -109,7 +109,7 @@ function WidgetComponent(props) {
|
||||
// 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(
|
||||
(data) =>
|
||||
![
|
||||
@@ -121,7 +121,7 @@ function WidgetComponent(props) {
|
||||
"checkbox"
|
||||
].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(
|
||||
(data) =>
|
||||
![
|
||||
@@ -263,7 +263,12 @@ function WidgetComponent(props) {
|
||||
)}
|
||||
</span>
|
||||
</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
|
||||
updateWidgets={handleWidgetType}
|
||||
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 "../../styles/AddUser.css";
|
||||
import RegexParser from "regex-parser";
|
||||
@@ -21,11 +21,30 @@ const WidgetNameModal = (props) => {
|
||||
hint: "",
|
||||
textvalidate: "",
|
||||
isReadOnly: false,
|
||||
cellCount: 5
|
||||
cellCount: 5,
|
||||
});
|
||||
const [isValid, setIsValid] = useState(true);
|
||||
const statusArr = ["Required", "Optional"];
|
||||
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 type = props.defaultdata?.type;
|
||||
@@ -54,13 +73,13 @@ const WidgetNameModal = (props) => {
|
||||
? props.defaultdata?.options?.validation?.pattern
|
||||
: props.defaultdata?.options?.validation?.type || "",
|
||||
isReadOnly: props.defaultdata?.options?.isReadOnly || false,
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5,
|
||||
});
|
||||
} else {
|
||||
setFormdata({
|
||||
...formdata,
|
||||
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 || [];
|
||||
setSignatureType(defaultSignatureType);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.defaultdata]);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (props.handleData) {
|
||||
if (["signature", "initials"].includes(props.defaultdata?.type)) {
|
||||
if (isSignOrInitials) {
|
||||
const enabledSignTypes = signatureType?.filter((x) => x.enabled);
|
||||
const isDefaultSignTypeOnly =
|
||||
enabledSignTypes?.length === 1 &&
|
||||
@@ -87,9 +107,11 @@ const WidgetNameModal = (props) => {
|
||||
props.handleData(data, props.defaultdata?.type);
|
||||
}
|
||||
} else {
|
||||
const isTextInput = [textInputWidget, cellsWidget].includes(
|
||||
props.defaultdata?.type
|
||||
);
|
||||
|
||||
const isTextInput = [
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
].includes(props.defaultdata?.type);
|
||||
const { isReadOnly, defaultValue, status } = formdata;
|
||||
// If it’s a text‐input widget, enforce that read-only fields have
|
||||
// either a defaultValue or an "optional" status.
|
||||
@@ -110,7 +132,7 @@ const WidgetNameModal = (props) => {
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: "",
|
||||
cellCount: 5
|
||||
cellCount: 5,
|
||||
});
|
||||
setSignatureType(signTypes);
|
||||
}
|
||||
@@ -148,10 +170,11 @@ const WidgetNameModal = (props) => {
|
||||
} else {
|
||||
setIsValid(true);
|
||||
}
|
||||
const val =
|
||||
props.defaultdata?.type === cellsWidget
|
||||
? e.target.value.slice(0, formdata.cellCount)
|
||||
: e.target.value;
|
||||
|
||||
const val = isCellWidget
|
||||
? e.target.value.slice(0, formdata.cellCount)
|
||||
: e.target.value;
|
||||
|
||||
setFormdata({ ...formdata, [e.target.name]: val });
|
||||
};
|
||||
|
||||
@@ -160,7 +183,7 @@ const WidgetNameModal = (props) => {
|
||||
case "email":
|
||||
return "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/";
|
||||
case "number":
|
||||
return "/^\\d+$/";
|
||||
return "^\\d+(?:\\.\\d+)?$"; // "/^\\d+$/";
|
||||
case "text":
|
||||
//allow space in text regex
|
||||
return "/^[a-zA-Z ]+$/";
|
||||
@@ -184,23 +207,19 @@ const WidgetNameModal = (props) => {
|
||||
<ModalUi
|
||||
isOpen={props.isOpen}
|
||||
handleClose={props.handleClose && props.handleClose}
|
||||
title={
|
||||
["signature", "initials"].includes(props.defaultdata?.type)
|
||||
? t("signature-setting")
|
||||
: t("widget-info")
|
||||
}
|
||||
title={isSignOrInitials ? t("signature-setting") : t("widget-info")}
|
||||
>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className={`${
|
||||
[textInputWidget, cellsWidget].includes(props.defaultdata?.type)
|
||||
? "pt-0"
|
||||
: ["signature", "initials"].includes(props.defaultdata?.type)
|
||||
: isSignOrInitials
|
||||
? "pt-2"
|
||||
: ""
|
||||
} p-[20px] text-base-content`}
|
||||
>
|
||||
{!["signature", "initials"].includes(props.defaultdata?.type) && (
|
||||
{!isSignOrInitials && (
|
||||
<div className="mb-[0.75rem] text-[13px]">
|
||||
<label htmlFor="name">
|
||||
{t("name")}
|
||||
@@ -217,7 +236,7 @@ const WidgetNameModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{props.defaultdata?.type === cellsWidget && (
|
||||
{isCellWidget && (
|
||||
<div className="mb-[0.75rem] text-[13px]">
|
||||
<label htmlFor="cellCount">{t("cell-count")}</label>
|
||||
<input
|
||||
@@ -231,7 +250,10 @@ const WidgetNameModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{[textInputWidget, cellsWidget].includes(props.defaultdata?.type) &&
|
||||
{[
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
].includes(props.defaultdata?.type) &&
|
||||
props?.roleName !== "prefill" && (
|
||||
<>
|
||||
<div className="mb-[0.75rem]">
|
||||
@@ -244,11 +266,7 @@ const WidgetNameModal = (props) => {
|
||||
value={formdata.defaultValue}
|
||||
onChange={(e) => handledefaultChange(e)}
|
||||
autoComplete="off"
|
||||
maxLength={
|
||||
props.defaultdata?.type === cellsWidget
|
||||
? formdata.cellCount
|
||||
: undefined
|
||||
}
|
||||
maxLength={isCellWidget ? formdata.cellCount : undefined}
|
||||
onBlur={() => {
|
||||
if (isValid === false) {
|
||||
setFormdata({ ...formdata, defaultValue: "" });
|
||||
@@ -262,7 +280,7 @@ const WidgetNameModal = (props) => {
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-exclamation-circle text-[15px]"
|
||||
className="fa-light fa-exclamation-circle text-[15px] mr-1"
|
||||
style={{ color: "#fab005" }}
|
||||
></i>
|
||||
{t("invalid-default-value")}
|
||||
@@ -271,7 +289,8 @@ const WidgetNameModal = (props) => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!["signature", "initials"].includes(props.defaultdata?.type) &&
|
||||
{!props?.isSelfSign &&
|
||||
!isSignOrInitials &&
|
||||
props?.roleName !== "prefill" && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<div className="flex flex-row gap-[10px] mb-[0.5rem]">
|
||||
@@ -302,9 +321,10 @@ const WidgetNameModal = (props) => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{[textInputWidget, cellsWidget].includes(
|
||||
props.defaultdata?.type
|
||||
) &&
|
||||
{[
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
].includes(props.defaultdata?.type) &&
|
||||
props?.roleName !== "prefill" && (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
@@ -330,7 +350,7 @@ const WidgetNameModal = (props) => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{["signature", "initials"].includes(props.defaultdata?.type) && (
|
||||
{isSignOrInitials && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="signaturetype" className="text-[14px] mb-[0.7rem]">
|
||||
{t("allowed-signature-types")}
|
||||
@@ -358,7 +378,7 @@ const WidgetNameModal = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{props?.roleName !== "prefill" && (
|
||||
{!props?.isSelfSign && props?.roleName !== "prefill" && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="hint" className="text-[13px]">
|
||||
{t("hint")}
|
||||
@@ -372,15 +392,7 @@ const WidgetNameModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
].includes(props.defaultdata?.type) && (
|
||||
{showFontControls && (
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-3 mb-3">
|
||||
<div className="flex items-center gap-2 ">
|
||||
<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 { useTranslation } from "react-i18next";
|
||||
import { removeBackground } from "@imgly/background-removal";
|
||||
@@ -23,7 +23,8 @@ import {
|
||||
convertBase64ToFile,
|
||||
generatePdfName,
|
||||
getDefaultDate,
|
||||
getDefaultFormat
|
||||
getDefaultFormat,
|
||||
getSignerPages
|
||||
} from "../../constant/Utils";
|
||||
import CellsWidget from "./CellsWidget";
|
||||
import DatePicker from "react-datepicker";
|
||||
@@ -45,7 +46,9 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import { emailRegex } from "../../constant/const";
|
||||
import RegexParser from "regex-parser";
|
||||
import { saveToMySign } from "../../utils/widgetUtils";
|
||||
import {
|
||||
saveToMySign
|
||||
} from "../../utils/widgetUtils";
|
||||
|
||||
const fontOptions = [
|
||||
{ value: "Fasthand" },
|
||||
@@ -111,12 +114,28 @@ function WidgetsValueModal(props) {
|
||||
props?.kiosk_signer || JSON.parse(localStorage.getItem("kiosk_signer"));
|
||||
const jsonSender = senderUser && JSON.parse(senderUser);
|
||||
const currentUserName = jsonSender && jsonSender?.name;
|
||||
const type = currWidgetsDetails?.type;
|
||||
const widgetTypeTranslation = t(`widgets-name.${currWidgetsDetails?.type}`);
|
||||
const [widgetValue, setWidgetValue] = useState(
|
||||
currWidgetsDetails.type !== "checkbox" &&
|
||||
(currWidgetsDetails?.options?.response ||
|
||||
currWidgetsDetails?.options?.defaultValue)
|
||||
);
|
||||
|
||||
const [widgetValue, setWidgetValue] = useState(() => {
|
||||
if (currWidgetsDetails.type === "checkbox") {
|
||||
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 count = currWidgetsDetails?.options?.cellCount || 5;
|
||||
const val =
|
||||
@@ -192,7 +211,8 @@ function WidgetsValueModal(props) {
|
||||
} else {
|
||||
setHint(currWidgetsDetails?.type);
|
||||
}
|
||||
} else if (isSignOrInitials) {
|
||||
}
|
||||
else if (isSignOrInitials) {
|
||||
setIsTab(currWidgetsDetails?.signatureType);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -877,9 +897,10 @@ function WidgetsValueModal(props) {
|
||||
const handleOnchangeTextBox = (e) => {
|
||||
// hide any prior validation error while typing
|
||||
setIsShowValidation(false);
|
||||
setWidgetValue(e.target.value);
|
||||
let value = e.target.value;
|
||||
setWidgetValue(value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
value,
|
||||
currWidgetsDetails,
|
||||
xyPosition,
|
||||
props.index,
|
||||
@@ -1718,13 +1739,14 @@ function WidgetsValueModal(props) {
|
||||
} else if (!isSave) {
|
||||
const widgetsPosition = xyPosition?.find((data) => data.Id === uniqueId);
|
||||
let nextWidgetDetails = null;
|
||||
const editableWidgets = widgetsPosition?.placeHolder.flatMap((page) =>
|
||||
page.pos
|
||||
.filter((widget) => !widget.options?.isReadOnly)
|
||||
.map((widget) => ({
|
||||
widget,
|
||||
pageNumber: page.pageNumber
|
||||
}))
|
||||
const editableWidgets = (widgetsPosition?.placeHolder ?? []).flatMap(
|
||||
({ pos = [], pageNumber }) =>
|
||||
pos
|
||||
.filter(
|
||||
({ options }) =>
|
||||
!options?.isReadOnly
|
||||
)
|
||||
.map((widget) => ({ widget, pageNumber }))
|
||||
);
|
||||
//get current index of widget
|
||||
const currentIndex = editableWidgets.findIndex(
|
||||
@@ -1926,13 +1948,11 @@ function WidgetsValueModal(props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-center">
|
||||
{!["checkbox", "dropdown", "date", "radio"].includes(
|
||||
currWidgetsDetails?.type
|
||||
) ? (
|
||||
{showClearbtn ? (
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm text-base-content mr-1"
|
||||
onClick={() => handleClear()}
|
||||
onClick={handleClear}
|
||||
>
|
||||
{t("clear")}
|
||||
</button>
|
||||
|
||||
@@ -3,15 +3,20 @@ import { isMobile } from "../../constant/Utils";
|
||||
// `getWidgetType` is used to load ui of widget in side list
|
||||
const getWidgetType = (item, widgetName) => {
|
||||
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="flex justify-start items-center text-[13px] ml-1">
|
||||
{!isMobile && <i className="fa-light fa-grip-vertical ml-[3px]"></i>}
|
||||
<span className="md:inline-block text-center text-[15px] ml-[5px] font-semibold pr-1 md:pr-0">
|
||||
<div className="flex justify-start items-center ml-1 overflow-hidden">
|
||||
{!isMobile && (
|
||||
<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}
|
||||
</span>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import fontkit from "@pdf-lib/fontkit";
|
||||
import { themeColor } from "./const";
|
||||
import { format, toZonedTime } from "date-fns-tz";
|
||||
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 fontColorArr = ["red", "black", "blue", "yellow"];
|
||||
@@ -327,6 +327,7 @@ export const widgets = [
|
||||
{ type: "name", icon: "fa-light fa-user", iconSize: "21px" },
|
||||
{ type: "job title", icon: "fa-light fa-address-card", iconSize: "17px" },
|
||||
{ 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: textWidget, icon: "fa-light fa-text-width", iconSize: "20px" },
|
||||
{ type: cellsWidget, icon: "fa-light fa-table-cells", iconSize: "20px" },
|
||||
@@ -337,8 +338,7 @@ export const widgets = [
|
||||
iconSize: "19px"
|
||||
},
|
||||
{ type: radioButtonWidget, icon: "fa-light fa-circle-dot", iconSize: "20px" },
|
||||
{ type: "image", icon: "fa-light fa-image", iconSize: "20px" },
|
||||
{ type: "email", icon: "fa-light fa-envelope", iconSize: "20px" }
|
||||
{ type: "image", icon: "fa-light fa-image", iconSize: "20px" }
|
||||
];
|
||||
|
||||
export const getDate = (dateformat) => {
|
||||
@@ -410,6 +410,35 @@ export const changeDateToMomentFormat = (format) => {
|
||||
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 = (
|
||||
type,
|
||||
signer,
|
||||
@@ -423,15 +452,9 @@ export const addWidgetOptions = (
|
||||
return count + page.pos.filter((item) => item.type === type).length;
|
||||
}, 0);
|
||||
const count = countSameWidget + 1;
|
||||
defaultOpt = {
|
||||
name: `${type}-${count}`,
|
||||
status: "required"
|
||||
};
|
||||
defaultOpt = { name: `${type}-${count}`, status: "required" };
|
||||
} else {
|
||||
defaultOpt = {
|
||||
name: `${type}-1`,
|
||||
status: "required"
|
||||
};
|
||||
defaultOpt = { name: `${type}-1`, status: "required" };
|
||||
}
|
||||
switch (type) {
|
||||
case "signature":
|
||||
@@ -439,11 +462,7 @@ export const addWidgetOptions = (
|
||||
case "stamp":
|
||||
return defaultOpt;
|
||||
case "checkbox":
|
||||
return {
|
||||
...defaultOpt,
|
||||
isReadOnly: false,
|
||||
isHideLabel: false
|
||||
};
|
||||
return { ...defaultOpt, isReadOnly: false, isHideLabel: false };
|
||||
case textInputWidget:
|
||||
return { ...defaultOpt, isReadOnly: false };
|
||||
case cellsWidget:
|
||||
@@ -457,20 +476,11 @@ export const addWidgetOptions = (
|
||||
case "initials":
|
||||
return defaultOpt;
|
||||
case "name":
|
||||
return {
|
||||
...defaultOpt,
|
||||
defaultValue: widgetValue ? widgetValue : ""
|
||||
};
|
||||
return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
|
||||
case "company":
|
||||
return {
|
||||
...defaultOpt,
|
||||
defaultValue: widgetValue ? widgetValue : ""
|
||||
};
|
||||
return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
|
||||
case "job title":
|
||||
return {
|
||||
...defaultOpt,
|
||||
defaultValue: widgetValue ? widgetValue : ""
|
||||
};
|
||||
return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
|
||||
case "date": {
|
||||
const dateFormat = signer?.DateFormat
|
||||
? selectFormat(signer?.DateFormat)
|
||||
@@ -518,15 +528,9 @@ export const addWidgetSelfsignOptions = (
|
||||
return count + page.pos.filter((item) => item.type === type).length;
|
||||
}, 0);
|
||||
const count = countSameWidget + 1;
|
||||
defaultOpt = {
|
||||
name: `${type}-${count}`,
|
||||
status: "required"
|
||||
};
|
||||
defaultOpt = { name: `${type}-${count}`, status: "required" };
|
||||
} else {
|
||||
defaultOpt = {
|
||||
name: `${type}-1`,
|
||||
status: "required"
|
||||
};
|
||||
defaultOpt = { name: `${type}-1`, status: "required" };
|
||||
}
|
||||
switch (type) {
|
||||
case "signature":
|
||||
@@ -1077,9 +1081,11 @@ export const onChangeInput = (
|
||||
return obj;
|
||||
});
|
||||
|
||||
const recalculatedPages = applyNumberFormulasToPages(newUpdateSignPos);
|
||||
|
||||
const newUpdateSigner = xyPosition.map((obj) => {
|
||||
if (obj.Id === userId) {
|
||||
return { ...obj, placeHolder: newUpdateSignPos };
|
||||
return { ...obj, placeHolder: recalculatedPages };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
@@ -1125,7 +1131,7 @@ export const onChangeInput = (
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
setXyPosition(updatePlaceholder);
|
||||
setXyPosition(applyNumberFormulasToPages(updatePlaceholder));
|
||||
}
|
||||
};
|
||||
//function to increase height of text area on press enter
|
||||
@@ -1220,13 +1226,13 @@ export const calculateInitialWidthHeight = (widgetData) => {
|
||||
export const widgetDataValue = (type, value) => {
|
||||
switch (type) {
|
||||
case "name":
|
||||
return value?.Name;
|
||||
return value?.Name || value?.name;
|
||||
case "company":
|
||||
return value?.Company;
|
||||
return value?.Company || value?.company;
|
||||
case "job title":
|
||||
return value?.JobTitle;
|
||||
return value?.JobTitle || value?.jobTitle;
|
||||
case "email":
|
||||
return value?.Email;
|
||||
return value?.Email || value?.email;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -1850,6 +1856,16 @@ export const embedWidgetsToDoc = async (
|
||||
console.log("error in image", e);
|
||||
}
|
||||
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
|
||||
try {
|
||||
let img;
|
||||
@@ -1898,33 +1914,13 @@ export const embedWidgetsToDoc = async (
|
||||
return y;
|
||||
}
|
||||
} else {
|
||||
const WidgetsTypeTextExist = [
|
||||
textWidget,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"date",
|
||||
"email"
|
||||
].includes(position.type);
|
||||
const yPosition = WidgetsTypeTextExist ? resizePos + 6 : resizePos;
|
||||
const yPosition = isTextTypeWidget ? resizePos + 6 : resizePos;
|
||||
return yPosition;
|
||||
}
|
||||
};
|
||||
const color = position?.options?.fontColor;
|
||||
const updateColorInRgb = getWidgetsFontColor(color);
|
||||
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") {
|
||||
// Determine layout mode: 'vertical' (default) or 'horizontal'
|
||||
// const isHorizontal = position.layout === "horizontal";
|
||||
@@ -2022,7 +2018,7 @@ export const embedWidgetsToDoc = async (
|
||||
textContent = position.options?.response;
|
||||
}
|
||||
} else if (position?.options?.defaultValue) {
|
||||
textContent = position?.options?.defaultValue;
|
||||
textContent = position?.options?.defaultValue?.toString();
|
||||
}
|
||||
if (position.type === cellsWidget) {
|
||||
const cellCount =
|
||||
@@ -2049,15 +2045,16 @@ export const embedWidgetsToDoc = async (
|
||||
if (ch) page.drawText(ch, textPosition);
|
||||
}
|
||||
} else {
|
||||
const fixedWidth = widgetWidth; // Set your fixed width
|
||||
const isNewOnEnterLineExist = textContent.includes("\n");
|
||||
const fixedWidth = widgetWidth; // Set your fixed
|
||||
textContent = textContent?.toString();
|
||||
const isNewOnEnterLineExist = textContent?.includes("\n");
|
||||
|
||||
// Function to break text into lines based on the fixed width
|
||||
const NewbreakTextIntoLines = (textContent, width) => {
|
||||
const lines = [];
|
||||
let currentLine = "";
|
||||
|
||||
for (const word of textContent.split(" ")) {
|
||||
for (const word of textContent?.split(" ")) {
|
||||
//get text line width
|
||||
const lineWidth = font.widthOfTextAtSize(
|
||||
`${currentLine} ${word}`,
|
||||
@@ -2242,7 +2239,6 @@ export const embedWidgetsToDoc = async (
|
||||
width: widgetWidth,
|
||||
height: widgetHeight
|
||||
};
|
||||
|
||||
const imageOptions = getWidgetPosition(page, signature, 1, getSize);
|
||||
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
|
||||
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 = [];
|
||||
for (let i = 0; i < xyPosition.length; i++) {
|
||||
// let getIMGWH;
|
||||
const getXYdata = xyPosition[i].pos;
|
||||
const getPageNo = xyPosition[i].pageNumber;
|
||||
const getPosData = getXYdata;
|
||||
|
||||
const addSign = getPosData.map((position) => {
|
||||
// getIMGWH = calculateImgAspectRatio(imgWH, position);
|
||||
if (position.type) {
|
||||
if (position?.type === type) {
|
||||
return {
|
||||
...position,
|
||||
SignUrl: defaultSignImg,
|
||||
// Width: getIMGWH.newWidth,
|
||||
// Height: getIMGWH.newHeight,
|
||||
ImageType: "default",
|
||||
options: {
|
||||
...position.options,
|
||||
response: defaultSignImg
|
||||
}
|
||||
options: { ...position.options, response: defaultSignImg }
|
||||
};
|
||||
}
|
||||
} else if (position && !position.isStamp) {
|
||||
return {
|
||||
...position,
|
||||
SignUrl: defaultSignImg,
|
||||
// Width: getIMGWH.newWidth,
|
||||
// Height: getIMGWH.newHeight,
|
||||
ImageType: "default",
|
||||
options: {
|
||||
...position.options,
|
||||
response: defaultSignImg
|
||||
}
|
||||
options: { ...position.options, response: defaultSignImg }
|
||||
};
|
||||
}
|
||||
return position;
|
||||
});
|
||||
|
||||
const newXypos = {
|
||||
pageNumber: getPageNo,
|
||||
pos: addSign
|
||||
};
|
||||
const newXypos = { pageNumber: getPageNo, pos: addSign };
|
||||
xyDefaultPos.push(newXypos);
|
||||
}
|
||||
return xyDefaultPos;
|
||||
@@ -3359,10 +3333,7 @@ export const handleHeighlightWidget = (
|
||||
...data,
|
||||
pos: data.pos.map((position) => {
|
||||
if (position.key === key) {
|
||||
return {
|
||||
...position,
|
||||
zIndex: highestZIndex + 1
|
||||
};
|
||||
return { ...position, zIndex: highestZIndex + 1 };
|
||||
}
|
||||
return position;
|
||||
})
|
||||
|
||||
@@ -4,6 +4,10 @@ const parseAppId = process.env.REACT_APP_APPID
|
||||
? process.env.REACT_APP_APPID
|
||||
: "opensign";
|
||||
const serverUrl = serverUrl_fn();
|
||||
const commonheader = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
};
|
||||
export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
|
||||
//checking server url and save file's size
|
||||
const tenantPtr = {
|
||||
@@ -29,7 +33,6 @@ export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
|
||||
);
|
||||
const response = res.data.results;
|
||||
let data;
|
||||
// console.log("response", response);
|
||||
if (response && response.length > 0) {
|
||||
data = {
|
||||
usedStorage: response[0].usedStorage
|
||||
@@ -39,20 +42,12 @@ export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
|
||||
await axios.put(
|
||||
`${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
}
|
||||
{ headers: commonheader }
|
||||
);
|
||||
} else {
|
||||
data = { usedStorage: size, PartnersTenant: tenantPtr };
|
||||
await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
headers: commonheader
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -69,14 +64,9 @@ const saveDataFile = async (size, imageUrl, tenantPtr, UserId) => {
|
||||
TenantPtr: tenantPtr,
|
||||
...(UserId ? { UserId: UserId } : {})
|
||||
};
|
||||
|
||||
// console.log("data save",file, data)
|
||||
try {
|
||||
await axios.post(`${serverUrl}/classes/partners_DataFiles`, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
headers: commonheader
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("err in save usage ", err);
|
||||
|
||||
@@ -49,7 +49,7 @@ import {
|
||||
widgetDataValue,
|
||||
getOriginalWH,
|
||||
handleCheckResponse,
|
||||
convertJpegToPng
|
||||
convertJpegToPng,
|
||||
} from "../constant/Utils";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
|
||||
@@ -722,9 +722,7 @@ function PlaceHolderSign() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((url) => {
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((url) => {
|
||||
if (url.key === keyValue) {
|
||||
return {
|
||||
...url,
|
||||
@@ -1455,9 +1453,9 @@ function PlaceHolderSign() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
|
||||
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (currWidgetsDetails?.type === radioButtonWidget) {
|
||||
if (addOption) {
|
||||
@@ -1486,12 +1484,8 @@ function PlaceHolderSign() {
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
defaultValue: defaultValue,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1525,12 +1519,8 @@ function PlaceHolderSign() {
|
||||
layout: layout,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1543,12 +1533,8 @@ function PlaceHolderSign() {
|
||||
status: status,
|
||||
values: dropdownOptions,
|
||||
defaultValue: defaultValue,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
...(isReadOnly ? { isReadOnly: isReadOnly || false } : {})
|
||||
}
|
||||
};
|
||||
@@ -1563,9 +1549,11 @@ function PlaceHolderSign() {
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
const recalculatedPlaceholders =
|
||||
utils.applyNumberFormulasToPages(newUpdateSignPos);
|
||||
const newUpdateSigner = signerPos.map((obj) => {
|
||||
if (obj.Id === uniqueId) {
|
||||
return { ...obj, placeHolder: newUpdateSignPos };
|
||||
return { ...obj, placeHolder: recalculatedPlaceholders };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
@@ -1597,9 +1585,9 @@ function PlaceHolderSign() {
|
||||
);
|
||||
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
|
||||
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (position.type === textInputWidget) {
|
||||
return {
|
||||
@@ -1612,12 +1600,8 @@ function PlaceHolderSign() {
|
||||
defaultValue: defaultdata?.defaultValue || "",
|
||||
validation:
|
||||
{},
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
isReadOnly: defaultdata?.isReadOnly || false
|
||||
}
|
||||
};
|
||||
@@ -1636,16 +1620,13 @@ function PlaceHolderSign() {
|
||||
),
|
||||
validation:
|
||||
{},
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
isReadOnly: defaultdata?.isReadOnly || false
|
||||
}
|
||||
};
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
}
|
||||
else if (["signature"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
@@ -1663,12 +1644,8 @@ function PlaceHolderSign() {
|
||||
status: defaultdata.status,
|
||||
defaultValue: defaultdata.defaultValue,
|
||||
hint: defaultdata?.hint || "",
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2535,6 +2512,8 @@ function PlaceHolderSign() {
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
roleName={roleName}
|
||||
widgetsSource={signerPos}
|
||||
activeSignerId={uniqueId}
|
||||
/>
|
||||
<RotateAlert
|
||||
showRotateAlert={showRotateAlert.status}
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
|
||||
import WidgetNameModal from "../components/pdf/WidgetNameModal";
|
||||
import CellsSettingModal from "../components/pdf/CellsSettingModal";
|
||||
import { applyNumberFormulasToPages } from "../utils";
|
||||
//For signYourself inProgress section signer can add sign and complete doc sign.
|
||||
function SignYourSelf() {
|
||||
const { t } = useTranslation();
|
||||
@@ -140,6 +141,8 @@ function SignYourSelf() {
|
||||
const [isDownloadModal, setIsDownloadModal] = useState(false);
|
||||
const [isResize, setIsResize] = useState(false);
|
||||
const [isUploadPdf, setIsUploadPdf] = useState(false);
|
||||
const [unSignedWidgetId, setUnSignedWidgetId] = useState("");
|
||||
const [widgetsTour, setWidgetsTour] = useState(false);
|
||||
|
||||
const [owner, setOwner] = useState({});
|
||||
const [, drop] = useDrop({
|
||||
@@ -586,9 +589,23 @@ function SignYourSelf() {
|
||||
}
|
||||
if (!isEnableOTP || isEmailVerified) {
|
||||
let showAlert = false,
|
||||
isSignatureExist = false;
|
||||
widgetKey,
|
||||
tourPageNumber;
|
||||
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++) {
|
||||
tourPageNumber = xyPosition[i]?.pageNumber;
|
||||
const requiredWidgets = xyPosition[i].pos.filter(
|
||||
(position) => position.type !== "checkbox"
|
||||
);
|
||||
@@ -597,34 +614,24 @@ function SignYourSelf() {
|
||||
for (let i = 0; i < requiredWidgets?.length; i++) {
|
||||
checkSigned = requiredWidgets[i]?.options?.response;
|
||||
if (!checkSigned) {
|
||||
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
|
||||
const checkSignUrl = requiredWidgets[i]?.SignUrl;
|
||||
let checkDefaultSigned =
|
||||
requiredWidgets[i]?.options?.defaultValue;
|
||||
if (!checkSignUrl && !checkDefaultSigned && !showAlert) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredWidgets[i]?.key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//condition to check exist signature widget or not
|
||||
if (!isSignatureExist) {
|
||||
isSignatureExist = xyPosition[i].pos.some(
|
||||
(data) => data?.type === "signature"
|
||||
);
|
||||
if (showAlert) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (xyPosition.length === 0 || !isSignatureExist) {
|
||||
setIsAlert({
|
||||
header: t("fields-required"),
|
||||
isShow: true,
|
||||
alertMessage: t("signature-widget-alert-1")
|
||||
});
|
||||
return;
|
||||
} else if (showAlert) {
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("signature-widget-alert-2")
|
||||
});
|
||||
if (showAlert) {
|
||||
setUnSignedWidgetId(widgetKey);
|
||||
setPageNumber(tourPageNumber);
|
||||
setWidgetsTour(true);
|
||||
return;
|
||||
} else {
|
||||
setIsUiLoading(true);
|
||||
@@ -771,9 +778,7 @@ function SignYourSelf() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (filterDropPos?.length > 0) {
|
||||
const getXYdata = xyPosition[index].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSign = getPosData.map((url) => {
|
||||
const addSign = xyPosition[index]?.pos?.map((url) => {
|
||||
if (url.key === dragKey) {
|
||||
return {
|
||||
...url,
|
||||
@@ -958,11 +963,9 @@ function SignYourSelf() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const widgetLayout =
|
||||
currWidgetsDetails?.type === "checkbox" ? { layout: layout } : {};
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (addOption) {
|
||||
return {
|
||||
@@ -1024,9 +1027,7 @@ function SignYourSelf() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
return {
|
||||
...position,
|
||||
@@ -1083,6 +1084,8 @@ function SignYourSelf() {
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
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.type === cellsWidget) {
|
||||
const count = parseInt(
|
||||
@@ -1096,20 +1099,19 @@ function SignYourSelf() {
|
||||
name: defaultdata?.name || position.options?.name || "Cells",
|
||||
cellCount: count,
|
||||
defaultValue: (defaultdata?.defaultValue || "").slice(0, count),
|
||||
fontSize: newFontSize || position.options?.fontSize || 12,
|
||||
fontColor:
|
||||
newFontColor || position.options?.fontColor || "black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || position.options?.name,
|
||||
fontSize: newFontSize || position.options?.fontSize || 12,
|
||||
fontColor:
|
||||
newFontColor || position.options?.fontColor || "black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1119,7 +1121,7 @@ function SignYourSelf() {
|
||||
const updateXYposition = xyPosition.map((obj, ind) =>
|
||||
ind === index ? { ...obj, pos: updatePos } : obj
|
||||
);
|
||||
setXyPosition(updateXYposition);
|
||||
setXyPosition(applyNumberFormulasToPages(updateXYposition));
|
||||
}
|
||||
setFontSize();
|
||||
setFontColor();
|
||||
@@ -1196,6 +1198,14 @@ function SignYourSelf() {
|
||||
setPdfBase64Url(urlDetails.base64);
|
||||
setShowRotateAlert({ status: false, degree: 0 });
|
||||
};
|
||||
const WidgetTourConfig = [
|
||||
{
|
||||
selector: '[data-tut="IsSigned"]',
|
||||
content: t("signature-validate-alert-2"),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
return (
|
||||
<>
|
||||
{isLoading.isLoad ? (
|
||||
@@ -1241,6 +1251,14 @@ function SignYourSelf() {
|
||||
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 */}
|
||||
<RenderAllPdfPage
|
||||
allPages={allPages}
|
||||
@@ -1408,6 +1426,7 @@ function SignYourSelf() {
|
||||
divRef={divRef}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
isShowModal={isShowModal}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1459,6 +1478,8 @@ function SignYourSelf() {
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
widgetsSource={xyPosition}
|
||||
isSelfSign={true}
|
||||
/>
|
||||
<CellsSettingModal
|
||||
isOpen={isCellsSetting}
|
||||
|
||||
@@ -662,9 +662,7 @@ const TemplatePlaceholder = () => {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((url) => {
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((url) => {
|
||||
if (url.key === keyValue) {
|
||||
return {
|
||||
...url,
|
||||
@@ -756,7 +754,7 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (pdfDetails?.[0]?.CreatedBy?.objectId !== user?.objectId) return;
|
||||
if (pdfDetails?.[0]?.CreatedBy?.objectId !== user?.objectId || isPrefillModal) return;
|
||||
const timer = setTimeout(() => {
|
||||
autosavedetails();
|
||||
}, 2000);
|
||||
@@ -1319,10 +1317,9 @@ const TemplatePlaceholder = () => {
|
||||
);
|
||||
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
|
||||
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (currWidgetsDetails?.type === radioButtonWidget) {
|
||||
if (addOption) {
|
||||
@@ -1351,12 +1348,8 @@ const TemplatePlaceholder = () => {
|
||||
defaultValue: defaultValue,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1390,12 +1383,8 @@ const TemplatePlaceholder = () => {
|
||||
isReadOnly: isReadOnly || false,
|
||||
defaultValue: defaultValue,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1408,12 +1397,8 @@ const TemplatePlaceholder = () => {
|
||||
status: status,
|
||||
values: dropdownOptions,
|
||||
defaultValue: defaultValue,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
...(isReadOnly ? { isReadOnly: isReadOnly || false } : {})
|
||||
}
|
||||
};
|
||||
@@ -1428,9 +1413,11 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
const recalculatedPlaceholders =
|
||||
utils.applyNumberFormulasToPages(newUpdateSignPos);
|
||||
const newUpdateSigner = signerPos.map((obj) => {
|
||||
if (obj.Id === uniqueId) {
|
||||
return { ...obj, placeHolder: newUpdateSignPos };
|
||||
return { ...obj, placeHolder: recalculatedPlaceholders };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
@@ -1463,9 +1450,9 @@ const TemplatePlaceholder = () => {
|
||||
);
|
||||
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
|
||||
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (position.type === textInputWidget) {
|
||||
return {
|
||||
@@ -1479,12 +1466,8 @@ const TemplatePlaceholder = () => {
|
||||
isReadOnly: defaultdata?.isReadOnly || false,
|
||||
validation:
|
||||
{},
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
} else if (position.type === cellsWidget) {
|
||||
@@ -1503,15 +1486,12 @@ const TemplatePlaceholder = () => {
|
||||
validation:
|
||||
{},
|
||||
isReadOnly: defaultdata?.isReadOnly || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
}
|
||||
else if (["signature"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
@@ -1529,12 +1509,8 @@ const TemplatePlaceholder = () => {
|
||||
status: defaultdata.status,
|
||||
defaultValue: defaultdata.defaultValue,
|
||||
hint: defaultdata?.hint || "",
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2125,6 +2101,8 @@ const TemplatePlaceholder = () => {
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
roleName={roleName}
|
||||
widgetsSource={signerPos}
|
||||
activeSignerId={uniqueId}
|
||||
/>
|
||||
{isPrefillModal && (
|
||||
<PrefillWidgetModal
|
||||
@@ -2140,7 +2118,6 @@ const TemplatePlaceholder = () => {
|
||||
navigatePageToDoc={navigatePageToDoc}
|
||||
setIsNewContact={setIsNewContact}
|
||||
isNewContact={isNewContact}
|
||||
documentFlow={"template"}
|
||||
docId={pdfDetails[0]?.objectId}
|
||||
isSubmit={isUseTemplate}
|
||||
/>
|
||||
|
||||
@@ -14,7 +14,7 @@ const Tooltip = ({
|
||||
onClick={() =>
|
||||
handleOnlickHelp ? handleOnlickHelp() : openInNewTab(url)
|
||||
}
|
||||
className={"text-center cursor-pointer"}
|
||||
className="text-center cursor-pointer focus:outline-none"
|
||||
>
|
||||
<sup>
|
||||
<i
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { generateTitleFromFilename, getBase64FromUrl } from "../constant/Utils";
|
||||
import { generateTitleFromFilename } from "../constant/Utils";
|
||||
import { base64StringtoFile, uploadFile } from "./fileUtils";
|
||||
import { sanitizeFileName } from "./sanitizeFileName";
|
||||
import Parse from "parse";
|
||||
@@ -41,3 +41,40 @@ export const hasSignatureWidget = (s) =>
|
||||
(s.placeHolder ?? []).some((ph) =>
|
||||
(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 multer from 'multer';
|
||||
import libre from 'libreoffice-convert';
|
||||
import util from 'node:util';
|
||||
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({
|
||||
destination(req, file, cb) {
|
||||
cb(null, 'exports');
|
||||
},
|
||||
filename(req, file, cb) {
|
||||
cb(null, file.originalname);
|
||||
// -------------------- Timeout helper (async version = no TS hint) --------------------
|
||||
/**
|
||||
* @template T
|
||||
* @param {Promise<T>} promise
|
||||
* @param {number} ms
|
||||
* @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) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
for (let i = 0; i < length; i++) result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
return result;
|
||||
}
|
||||
|
||||
export default async function docxtopdf(req, res) {
|
||||
const serverUrl = cloudServerUrl;
|
||||
const appId = serverAppId;
|
||||
const parseAppKey = { 'X-Parse-Application-Id': serverAppId };
|
||||
const masterKey = process.env.MASTER_KEY;
|
||||
const inputPath = req.file.path;
|
||||
const name = generatePdfName(16);
|
||||
const fileName = `${name}.pdf`;
|
||||
const outputPath = './exports/output.pdf';
|
||||
|
||||
const masterHeader = { ...parseAppKey, 'X-Parse-Master-Key': masterKey };
|
||||
const sessionHeader = { ...parseAppKey, 'X-Parse-Session-Token': req.headers['sessiontoken'] };
|
||||
|
||||
if (!req.file || !req.file.buffer) {
|
||||
return res.status(400).json({ error: 'No file uploaded.' });
|
||||
}
|
||||
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': req.headers['sessiontoken'],
|
||||
},
|
||||
});
|
||||
const userId = JSON.stringify({
|
||||
UserId: {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userRes.data.objectId,
|
||||
},
|
||||
// ---- Auth: current user ----
|
||||
const userRes = await axios.get(`${serverUrl}/users/me`, { headers: sessionHeader });
|
||||
|
||||
// ---- contracts_Users ----
|
||||
const whereUser = JSON.stringify({
|
||||
UserId: { __type: 'Pointer', className: '_User', objectId: userRes.data.objectId },
|
||||
});
|
||||
const resUser = await axios.get(
|
||||
serverUrl + `/classes/contracts_Users?where=${userId}&limit=1&include=TenantId`,
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
`${serverUrl}/classes/contracts_Users?where=${whereUser}&limit=1&include=TenantId`,
|
||||
{ headers: masterHeader }
|
||||
);
|
||||
|
||||
if (resUser?.data?.results?.length > 0) {
|
||||
const tenantId = resUser.data.results[0].TenantId?.objectId;
|
||||
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 });
|
||||
}
|
||||
if (!resUser?.data?.results?.length) {
|
||||
return res.status(403).json({ error: 'User not linked to tenant.' });
|
||||
}
|
||||
|
||||
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) {
|
||||
[inputPath, outputPath].forEach(p => fs.existsSync(p) && fs.unlinkSync(p));
|
||||
const msg =
|
||||
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 =
|
||||
'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 });
|
||||
|
||||
@@ -59,6 +59,7 @@ export default async function saveAsTemplate(request) {
|
||||
options: {
|
||||
...p.options,
|
||||
status: 'required',
|
||||
response: '',
|
||||
...(p?.options?.defaultValue ? { defaultValue: '' } : {}),
|
||||
},
|
||||
})),
|
||||
|
||||
@@ -110,6 +110,7 @@ export const config = {
|
||||
appName: appName,
|
||||
allowClientClassCreation: false,
|
||||
allowExpiredAuthDataToken: false,
|
||||
enableInsecureAuthAdapters: false,
|
||||
encodeParseObjectInCloudFunction: true,
|
||||
...(isMailAdapter === true
|
||||
? {
|
||||
|
||||
Generated
+2511
-2508
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "open_sign_server",
|
||||
"version": "2.26.0",
|
||||
"description": "An example Parse API server using the parse-server module",
|
||||
"version": "2.30.0",
|
||||
"description": "opensign server using the parse-server module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ParsePlatform/parse-server-example"
|
||||
"url": "https://github.com/OpenSignLabs/OpenSign"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
@@ -18,8 +18,8 @@
|
||||
"watch": "nodemon index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.896.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.896.0",
|
||||
"@aws-sdk/client-s3": "^3.901.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.901.0",
|
||||
"@parse/fs-files-adapter": "^3.0.0",
|
||||
"@parse/s3-files-adapter": "^4.2.0",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
@@ -31,27 +31,27 @@
|
||||
"coherentpdf": "^2.5.5",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"dotenv": "^17.2.2",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.1.0",
|
||||
"form-data": "^4.0.4",
|
||||
"generate-api-key": "^1.0.2",
|
||||
"googleapis": "^159.0.0",
|
||||
"googleapis": "^161.0.0",
|
||||
"libreoffice-convert": "^1.7.0",
|
||||
"mailgun.js": "^12.1.0",
|
||||
"mongodb": "^6.20.0",
|
||||
"multer": "^2.0.2",
|
||||
"multer-s3": "^3.0.1",
|
||||
"node-forge": "^1.3.1",
|
||||
"nodemailer": "^7.0.6",
|
||||
"nodemailer": "^7.0.9",
|
||||
"p-limit": "^7.1.1",
|
||||
"parse": "^6.1.1",
|
||||
"parse-dbtool": "^1.2.0",
|
||||
"parse-server": "^8.2.4",
|
||||
"parse-server": "^8.2.5",
|
||||
"parse-server-api-mail-adapter": "^4.1.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"posthog-node": "^5.9.1",
|
||||
"posthog-node": "^5.9.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"rate-limiter-flexible": "^7.4.0",
|
||||
"rate-limiter-flexible": "^8.0.1",
|
||||
"sharp": "^0.34.4",
|
||||
"speakeasy": "^2.0.0",
|
||||
"ws": "^8.18.3"
|
||||
@@ -59,8 +59,8 @@
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.28.4",
|
||||
"eslint": "^9.36.0",
|
||||
"jasmine": "^5.11.0",
|
||||
"eslint": "^9.37.0",
|
||||
"jasmine": "^5.12.0",
|
||||
"mongodb-runner": "^5.10.0",
|
||||
"nodemon": "^3.1.10",
|
||||
"nyc": "^17.1.0",
|
||||
@@ -74,4 +74,4 @@
|
||||
"engines": {
|
||||
"node": "18 || 20 || 22"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user