Merge pull request #1790 from OpenSignLabs/staging

v2.24.1
This commit is contained in:
prafull-opensignlabs
2025-06-06 18:17:54 +05:30
committed by GitHub
26 changed files with 3959 additions and 4140 deletions
+17 -1
View File
@@ -73,12 +73,28 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
---
### Installation
### Deploy
Note: The default MongoDB instance used in deployment is not persistant and will be cleared on every restart. To retain your data, configure and supply your own MongoDB connection URL.
#### DigitalOcean
[![Deploy on DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0)
#### Docker
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
**Command for linux/MacOS**
```
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
```
**Command for Windows (Powershell)**
```
$env:HOST_URL="https://opensign.yourdomain.com"; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml -OutFile docker-compose.yml; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile -OutFile Caddyfile; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev -OutFile .env.local_dev; Rename-Item -Path .env.local_dev -NewName .env.prod; docker compose up --force-recreate
```
**Command for Windows (CMD/Terminal)**
```
set HOST_URL=https://opensign.yourdomain.com && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && rename .env.local_dev .env.prod && docker compose up --force-recreate
```
Make sure that you have `Docker` and `git` installed before you run this command -
Please refer to the [Installation Guide](https://docs.opensignlabs.com/docs/self-host/docker/run-locally/) for detailed instructions on how to install OpenSign on your system.
+923 -192
View File
File diff suppressed because it is too large Load Diff
+20 -18
View File
@@ -4,11 +4,11 @@
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.13.5",
"@imgly/background-removal": "^1.6.0",
"@lottiefiles/dotlottie-react": "^0.14.0",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.2.1",
"@reduxjs/toolkit": "^2.8.2",
"@imgly/background-removal": "^1.6.0",
"axios": "^1.9.0",
"date-fns-tz": "^3.2.0",
"file-saver": "^2.0.5",
@@ -19,15 +19,15 @@
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"parse": "^6.1.1",
"pkijs": "^3.0.8",
"pdf-lib": "^1.17.1",
"pkijs": "^3.0.8",
"print-js": "^1.6.0",
"prismjs": "^1.30.0",
"radix-ui": "^1.4.2",
"react": "^18.3.1",
"react-bootstrap": "^2.10.10",
"react-confetti": "^6.4.0",
"react-datepicker": "^8.3.0",
"react-datepicker": "^8.4.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^9.0.0",
@@ -35,13 +35,13 @@
"react-dom": "^18.3.1",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-i18next": "^15.5.1",
"react-i18next": "^15.5.2",
"react-konva": "^18.2.10",
"react-pdf": "^9.2.1",
"react-quill-new": "^3.4.6",
"react-redux": "^9.2.0",
"react-rnd": "^10.5.2",
"react-router": "^7.6.0",
"react-router": "^7.6.1",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.10.1",
"react-signature-canvas": "^1.1.0-alpha.2",
@@ -52,7 +52,7 @@
"regex-parser": "^2.3.1",
"serve": "^14.2.4",
"styled-components": "^5.3.11",
"web-vitals": "^5.0.1",
"web-vitals": "^5.0.2",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"scripts": {
@@ -93,16 +93,17 @@
}
},
"devDependencies": {
"@babel/core": "^7.27.1",
"@babel/core": "^7.27.4",
"@babel/preset-env": "^7.27.2",
"@babel/preset-react": "^7.27.1",
"@babel/runtime-corejs2": "^7.27.1",
"@babel/runtime-corejs2": "^7.27.4",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^18.3.22",
"@vitejs/plugin-react": "^4.4.1",
"@vitejs/plugin-react-swc": "^3.9.0",
"@types/react": "^18.3.23",
"@vitejs/plugin-react": "^4.5.1",
"@vitejs/plugin-react-swc": "^3.10.1",
"@vitest/ui": "^3.2.0",
"autoprefixer": "^10.4.21",
"babel-loader": "^10.0.0",
"commitizen": "^4.3.1",
@@ -110,19 +111,20 @@
"css-loader": "^7.1.2",
"daisyui": "^4.12.24",
"dotenv": "^16.5.0",
"eslint": "^9.27.0",
"eslint-plugin-prettier": "^5.4.0",
"eslint": "^9.28.0",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-react": "^7.37.5",
"lint-staged": "^16.0.0",
"postcss": "^8.5.3",
"jsdom": "^26.1.0",
"lint-staged": "^16.1.0",
"postcss": "^8.5.4",
"prettier": "^3.5.3",
"pretty-quick": "^4.1.1",
"pretty-quick": "^4.2.2",
"rollup-plugin-node-polyfills": "^0.2.1",
"tailwindcss": "^3.4.17",
"vite": "^6.3.5",
"vite-plugin-svgr": "^4.3.0",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.4"
"vitest": "^3.2.0"
},
"engines": {
"node": "18 || 20 || 22"
@@ -1022,5 +1022,8 @@
"no-signer-info-in-pkcs7": "Keine Signaturinformationen in PKCS#7",
"could-not-parse-signer-info": "Signaturinformationen konnten nicht analysiert werden",
"not-calculated": "Nicht berechnet",
"not-found-in-signature": "Nicht in Signatur gefunden"
"not-found-in-signature": "Nicht in Signatur gefunden",
"readonly-textinput-error": "Schreibgeschütztes Text-Widget muss einen Standardwert haben oder optional sein.",
"readonly-dropdown-error": "Schreibgeschütztes Dropdown-Widget muss einen Standardwert haben oder optional sein.",
"readonly-radiobtn-error": "Schreibgeschütztes Optionsfeld-Widget muss einen Standardwert haben oder optional sein."
}
@@ -1022,5 +1022,8 @@
"no-signer-info-in-pkcs7": "No signer info in PKCS#7",
"could-not-parse-signer-info": "Could not parse signer info",
"not-calculated": "Not calculated",
"not-found-in-signature": "Not found in signature"
"not-found-in-signature": "Not found in signature",
"readonly-textinput-error": "Read-only text widget must have a default value or you can make it optional.",
"readonly-dropdown-error": "Read-only dropdown widget must have a default value or you can make it optional.",
"readonly-radiobtn-error": "Read-only radio button widget must have a default value or you can make it optional."
}
@@ -1022,5 +1022,8 @@
"no-signer-info-in-pkcs7": "No hay información del firmante en PKCS#7",
"could-not-parse-signer-info": "No se pudo analizar la información del firmante",
"not-calculated": "No calculado",
"not-found-in-signature": "No encontrado en la firma"
"not-found-in-signature": "No encontrado en la firma",
"readonly-textinput-error": "El widget de texto de solo lectura debe tener un valor predeterminado o puede hacerse opcional.",
"readonly-dropdown-error": "El widget desplegable de solo lectura debe tener un valor predeterminado o puede hacerse opcional.",
"readonly-radiobtn-error": "El widget de botón de opción de solo lectura debe tener un valor predeterminado o puede hacerse opcional."
}
@@ -1022,5 +1022,8 @@
"no-signer-info-in-pkcs7": "Aucune information sur le signataire dans PKCS#7",
"could-not-parse-signer-info": "Impossible d'analyser les informations sur le signataire",
"not-calculated": "Non calculé",
"not-found-in-signature": "Introuvable dans la signature"
"not-found-in-signature": "Introuvable dans la signature",
"readonly-textinput-error": "Le widget de texte en lecture seule doit avoir une valeur par défaut ou être rendu optionnel.",
"readonly-dropdown-error": "Le widget déroulant en lecture seule doit avoir une valeur par défaut ou être rendu optionnel.",
"readonly-radiobtn-error": "Le widget bouton radio en lecture seule doit avoir une valeur par défaut ou être rendu optionnel."
}
@@ -1022,5 +1022,8 @@
"no-signer-info-in-pkcs7": "PKCS#7 में कोई हस्ताक्षरकर्ता जानकारी नहीं है",
"could-not-parse-signer-info": "हस्ताक्षरकर्ता जानकारी पार्स नहीं की जा सकी",
"not-calculated": "गणना नहीं की गई",
"not-found-in-signature": "हस्ताक्षर में नहीं मिला"
"not-found-in-signature": "हस्ताक्षर में नहीं मिला",
"readonly-textinput-error": "रीड-ओनली टेक्स्ट विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
"readonly-dropdown-error": "रीड-ओनली ड्रॉपडाउन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
"readonly-radiobtn-error": "रीड-ओनली रेडियो बटन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।"
}
@@ -1022,5 +1022,8 @@
"no-signer-info-in-pkcs7": "Nessuna informazione firmatario in PKCS#7",
"could-not-parse-signer-info": "Impossibile analizzare le informazioni del firmatario",
"not-calculated": "Non calcolato",
"not-found-in-signature": "Non trovato nella firma"
"not-found-in-signature": "Non trovato nella firma",
"readonly-textinput-error": "Il widget di testo di sola lettura deve avere un valore predefinito oppure può essere reso opzionale.",
"readonly-dropdown-error": "Il widget a discesa di sola lettura deve avere un valore predefinito oppure può essere reso opzionale.",
"readonly-radiobtn-error": "Il widget pulsante di opzione di sola lettura deve avere un valore predefinito oppure può essere reso opzionale."
}
+5 -1
View File
@@ -30,7 +30,7 @@ const AddAdmin = lazy(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
const Preferences = lazy(() => import("./pages/Preferences"));
const Login = lazy(() => import("./pages/Login"));
const VerifyDocument = lazy(() => import("./pages/VerifyDocument"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
return (
@@ -166,6 +166,10 @@ function App() {
element={<PdfRequestFiles />}
/>
<Route path="/users" element={<UserList />} />
<Route
path="/verify-document"
element={<LazyPage Page={VerifyDocument} />}
/>
<Route
path="/preferences"
element={<LazyPage Page={Preferences} />}
+11
View File
@@ -183,6 +183,17 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
{t("change-password")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/verify-document");
}}
>
<span>
<i className="fa-light fa-check-square"></i>{" "}
{t("verify-document")}
</span>
</li>
</>
)}
<li onClick={closeDropdown}>
@@ -12,19 +12,32 @@ function AgreementSign(props) {
<div className="op-modal op-modal-open absolute z-[448]">
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box overflow-y-auto hide-scrollbar text-sm p-4">
<div className="flex flex-row items-center">
<input
data-tut="IsAgree"
className="mr-3 op-checkbox op-checkbox-m"
type="checkbox"
value={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
if (e.target.checked) {
props.setIsAgreeTour(false);
}
props.showFirstWidget();
}}
/>
<label className="inline-flex justify-center items-center cursor-pointer mb-0">
{/* 1) This div becomes the “fake” checkbox */}
<div
data-tut="IsAgree"
className={`w-6 h-6 border-2 mr-3 rounded-full flex text-center items-center justify-center ${isChecked ? "op-border-primary" : "border-red-500"}`}
>
{isChecked ? (
<span className="op-text-primary text-sm font-bold"></span>
) : (
<span className="text-red-500 text-sm font-bold">X</span>
)}
</div>
{/* 2) Visually hide the native checkbox but keep it in the DOM */}
<input
className="sr-only"
type="checkbox"
checked={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
if (e.target.checked) {
props.setIsAgreeTour(false);
}
props.showFirstWidget();
}}
/>
</label>
<div className="text-[11px] md:text-base">
<span>{t("agree-p1")}</span>
<span
@@ -103,6 +103,22 @@ function DropdownWidgetOption(props) {
? defaultCheckbox
: defaultValue;
const isDropdownOrRadio =
props?.type === "dropdown" || props?.type === radioButtonWidget;
const readOnlyWithoutValue =
isReadOnly && !defaultValue && status !== "optional";
// If its a dropdown and its read-only without a value (nor marked optional), stop here.
if (isDropdownOrRadio && readOnlyWithoutValue) {
alert(
props?.type === "dropdown"
? t("readonly-dropdown-error")
: t("readonly-radiobtn-error")
);
return;
}
// Otherwise (either not a dropdown, or a valid dropdown), do the save + reset exactly once.
props.handleSaveWidgetsOptions(
dropdownName,
dropdownOptionList,
@@ -228,7 +244,7 @@ function DropdownWidgetOption(props) {
</select>
</>
)}
{props.type !== "checkbox" && props.type !== radioButtonWidget && (
{props.type !== "checkbox" && (
<>
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
{statusArr.map((data, ind) => {
@@ -241,6 +241,7 @@ function WidgetComponent(props) {
handleDivClick={props.handleDivClick}
handleMouseLeave={props.handleMouseLeave}
signRef={signRef}
addPositionOfSignature={props.addPositionOfSignature}
/>
</div>
</div>
@@ -83,6 +83,19 @@ const WidgetNameModal = (props) => {
props.handleData(data, props.defaultdata?.type);
}
} else {
const isTextInput = props.defaultdata?.type === textInputWidget;
const { isReadOnly, defaultValue, status } = formdata;
// If its a textinput widget, enforce that read-only fields have
// either a defaultValue or an "optional" status.
if (isTextInput) {
const readOnlyWithoutValue =
isReadOnly && !defaultValue && status !== "optional";
if (readOnlyWithoutValue) {
alert(t("readonly-textinput-error"));
return;
}
}
props.handleData(formdata);
}
setFormdata({
@@ -14,7 +14,6 @@ import {
onSaveSign,
radioButtonWidget,
selectCheckbox,
signatureTypes,
textInputWidget,
textWidget,
years
@@ -85,7 +84,8 @@ function WidgetsValueModal(props) {
setXyPosition,
isSave,
setUniqueId,
tempSignerId
tempSignerId,
signatureTypes
} = props;
const [penColor, setPenColor] = useState("blue");
const [isOptional, setIsOptional] = useState(true);
@@ -400,7 +400,7 @@ function WidgetsValueModal(props) {
if (getIndex !== -1) {
setIsSignTypes(true);
const tab = signatureTypes[getIndex].name;
const tab = signatureTypes?.[getIndex].name;
if (tab === "draw") {
setIsTab("draw");
setSignatureType("draw");
@@ -427,7 +427,7 @@ function WidgetsValueModal(props) {
}
}
function isTabEnabled(tabName) {
const isEnabled = signatureTypes.find((x) => x.name === tabName)?.enabled;
const isEnabled = signatureTypes?.find((x) => x.name === tabName)?.enabled;
return isEnabled;
}
@@ -683,51 +683,64 @@ function WidgetsValueModal(props) {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isTab]);
//function for convert input text value in image
// function for convert input text value in image
const convertToImg = async (fontStyle, text, color) => {
//get text content to convert in image
const textContent = text;
const fontfamily = fontStyle
? fontStyle
: fontSelect
? fontSelect
: "Fasthand";
const fontSizeValue = "40px";
//creating span for getting text content width
// 1) Read the max widget dimensions:
const maxWidth = currWidgetsDetails.Width; // e.g. 150 px
const maxHeight = currWidgetsDetails.Height; // e.g. 40 px
// 2) Pick a “baseline” font size for measurement:
const baselineFontSizePx = 40;
const chosenFontFamily = fontStyle || fontSelect || "Fasthand";
const fillColor = color || penColor;
// 3) Create a temporary <span> (hidden) to measure the text at 40px:
const span = document.createElement("span");
span.textContent = textContent;
span.style.font = `${fontSizeValue} ${fontfamily}`; // here put your text size and font family
span.style.color = color ? color : penColor;
span.style.display = "hidden";
document.body.appendChild(span); // Replace 'container' with the ID of the container element
span.textContent = text;
span.style.font = `${baselineFontSizePx}px ${chosenFontFamily}`;
span.style.visibility = "hidden"; // keep it in the DOM so offsetWidth/Height works
span.style.whiteSpace = "nowrap"; // so we measure a single line
document.body.appendChild(span);
//create canvas to render text in canvas and convert in image
const canvasElement = document.createElement("canvas");
// Draw the text content on the canvas
const ctx = canvasElement.getContext("2d");
// Measured size at 40px:
const measuredWidth = span.offsetWidth;
const measuredHeight = span.offsetHeight;
document.body.removeChild(span);
// 4) Compute uniform scale so that 40pxsized text fits inside (maxWidth × maxHeight):
const scaleX = maxWidth / measuredWidth;
const scaleY = maxHeight / measuredHeight;
const scale = Math.min(scaleX, scaleY, 1); // never scale up beyond 1
// 5) Final text size in **CSS px**:
const finalFontSizePx = baselineFontSizePx * scale;
// 6) Create a <canvas> that is ALWAYS maxWidth × maxHeight in **CSS px**,
// but use devicePixelRatio for sharpness.
const pixelRatio = window.devicePixelRatio || 1;
const addExtraWidth = currWidgetsDetails?.type === "initials" ? 10 : 50;
const width = span.offsetWidth + addExtraWidth;
const height = span.offsetHeight;
setTextWidth(width);
setTextHeight(height);
const font = span.style["font"];
// Set the canvas dimensions to match the span
canvasElement.width = width * pixelRatio;
canvasElement.height = height * pixelRatio;
const canvas = document.createElement("canvas");
// You can customize text styles if needed
ctx.font = font;
ctx.fillStyle = color ? color : penColor; // Set the text color
// ★ Instead of using `finalTextWidth/Height`, force it to be the max box:
canvas.width = Math.ceil(maxWidth * pixelRatio);
canvas.height = Math.ceil(maxHeight * pixelRatio);
const ctx = canvas.getContext("2d");
ctx.scale(pixelRatio, pixelRatio);
// 7) Draw the text **centered** inside the full maxWidth×maxHeight box:
ctx.font = `${finalFontSizePx}px ${chosenFontFamily}`;
ctx.fillStyle = fillColor;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.scale(pixelRatio, pixelRatio);
// Draw the content of the span onto the canvas
ctx.fillText(span.textContent, width / 2, height / 2); // Adjust the x,y-coordinate as needed
//remove span tag
document.body.removeChild(span);
// Convert the canvas to image data
const dataUrl = canvasElement.toDataURL("image/png");
// ★ Center = (maxWidth/2, maxHeight/2):
const centerX = maxWidth / 2;
const centerY = maxHeight / 2;
ctx.fillText(text, centerX, centerY);
// 8) Export to a PNG data-URL:
const dataUrl = canvas.toDataURL("image/png");
setSignature(dataUrl);
};
const PenColorComponent = (props) => {
@@ -1671,8 +1684,12 @@ function WidgetsValueModal(props) {
validateExpression(regexValidation);
break;
default:
regexValidation =
currWidgetsDetails?.options?.validation?.pattern || "";
// Grab the current pattern (if it exists)
const pattern = currWidgetsDetails?.options?.validation?.pattern;
// Removed `backwordSupportPattern` (/^[a-zA-Z0-9s]+$/) — it blocked spaces and special characters.
const backwordSupportPattern =
pattern && pattern === "/^[a-zA-Z0-9s]+$/" ? "" : pattern; // If it matches exactly '/^[a-zA-Z0-9s]+$/', clear it
regexValidation = backwordSupportPattern || "";
validateExpression(regexValidation);
break;
}
+9 -5
View File
@@ -683,10 +683,14 @@ export const signPdfFun = async (
};
export const randomId = () => {
const randomBytes = crypto.getRandomValues(new Uint16Array(1));
const randomValue = randomBytes[0];
const randomDigit = 1000 + (randomValue % 9000);
return randomDigit;
// 1. Grab a cryptographically-secure 32-bit random value
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
const raw = randomBytes[0]; // 0 … 4 294 967 295
// 2. Collapse into a 90 000 000-wide band (0…89 999 999), then shift to 10 000 000…99 999 999
const eightDigit = 10_000_000 + (raw % 90_000_000);
return eightDigit;
};
export const createDocument = async (
@@ -1552,7 +1556,7 @@ export const multiSignEmbed = async (
let hasError = false;
for (let item of widgets) {
//pdfOriginalWH contained all pdf's pages width and height
//'getSize' is used to get particular pdf's page width and height
//'getSize' is used to get particular pdf's page width and height
const getSize = pdfOriginalWH.find(
(page) => page?.pageNumber === item?.pageNumber
);
+2 -2
View File
@@ -455,7 +455,7 @@ const Forms = (props) => {
setBcc([]);
setFolder({ ObjectId: "", Name: "" });
const notifySign =
extUserData?.NotifyOnSignatures
extUserData?.NotifyOnSignatures !== undefined
? extUserData?.NotifyOnSignatures
: true;
setFormData({
@@ -534,7 +534,7 @@ const Forms = (props) => {
setBcc([]);
setFolder({ ObjectId: "", Name: "" });
const notifySign =
extUserData?.NotifyOnSignatures
extUserData?.NotifyOnSignatures !== undefined
? extUserData?.NotifyOnSignatures
: true;
let obj = {
+12 -4
View File
@@ -821,7 +821,7 @@ function PdfRequestFiles(
await embedDocId(pdfOriginalWH, pdfDoc, docId);
}
}
//embed all widgets in document
//embed all widgets in document
const pdfBytes = await multiSignEmbed(
pdfOriginalWH,
widgets,
@@ -865,7 +865,10 @@ function PdfRequestFiles(
const user = usermail?.Email
? usermail
: pdfDetails?.[0]?.Signers[newIndex];
if (sendmail !== "false" && sendInOrder) {
if (
sendmail !== "false" &&
sendInOrder
) {
const requestBody =
updatedDoc.updatedPdfDetails?.[0]?.RequestBody;
const requestSubject =
@@ -1564,11 +1567,15 @@ function PdfRequestFiles(
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// Compute the pixelspace center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: widgetWidth / 4 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
@@ -2259,6 +2266,7 @@ function PdfRequestFiles(
index={pageNumber}
setUniqueId={setUniqueId}
tempSignerId={tempSignerId}
signatureTypes={signatureType}
/>
)}
<DownloadPdfZip
+9 -3
View File
@@ -193,7 +193,7 @@ function PlaceHolderSign() {
);
if (user) {
try {
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;{{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;{{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p><a href='{{signing_url}}' rel='noopener noreferrer' target='_blank'>Sign here</a></p><br><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
const defaultSubject = `{{sender_name}} has requested you to sign {{document_title}}`;
setDefaultBody(defaultRequestBody);
setDefaultSubject(defaultSubject);
@@ -524,11 +524,15 @@ function PlaceHolderSign() {
defaultWidthHeight(dragTypeValue).height * containerScale;
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// Compute the pixelspace center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: widgetWidth / 4 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
@@ -1448,6 +1452,7 @@ function PlaceHolderSign() {
...position.options,
name: dropdownName,
values: dropdownOptions,
status: status,
isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false,
defaultValue: defaultValue,
@@ -2589,6 +2594,7 @@ function PlaceHolderSign() {
isSave={true}
tempSignerId={tempSignerId}
setUniqueId={setUniqueId}
signatureTypes={signatureType}
/>
)}
<ModalUi
+1 -1
View File
@@ -218,7 +218,7 @@ const Preferences = () => {
setIsLoader(true);
const updateRes = tenantRes;
setTenantId(updateRes?.objectId);
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;{{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;{{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p><a href='{{signing_url}}' rel='noopener noreferrer' target='_blank'>Sign here</a></p><br><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
if (updateRes?.RequestBody) {
setRequestBody(updateRes?.RequestBody);
setRequestSubject(updateRes?.RequestSubject);
+9 -3
View File
@@ -37,7 +37,8 @@ import {
generatePdfName,
handleRemoveWidgets,
addWidgetSelfsignOptions,
getOriginalWH
getOriginalWH,
signatureTypes
} from "../constant/Utils";
import { useParams } from "react-router";
import Tour from "../primitives/Tour";
@@ -365,15 +366,19 @@ function SignYourSelf() {
);
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divHeight = divRef.current.getBoundingClientRect().height;
const divWidth = divRef.current.getBoundingClientRect().width;
const getWidth = widgetTypeExist
? calculateInitialWidthHeight(widgetValue).getWidth
: defaultWidthHeight(dragTypeValue).width;
const getHeight = defaultWidthHeight(dragTypeValue).height;
// Compute the pixelspace center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - getWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
xPosition: getWidth / 2 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: getHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
@@ -1330,6 +1335,7 @@ function SignYourSelf() {
currWidgetsDetails={currWidgetsDetails}
index={index}
isSave={true}
signatureTypes={signatureTypes}
/>
)}
<RotateAlert
@@ -376,11 +376,15 @@ const TemplatePlaceholder = () => {
filterSignerPos;
let placeHolder;
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// Compute the pixelspace center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: widgetWidth / 4 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
@@ -763,7 +763,7 @@ const ReportTable = (props) => {
const body =
doc?.RequestBody ||
doc?.ExtUserPtr?.TenantId?.RequestBody ||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign <b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br></body> </html>`;
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign <b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p><a href='{{signing_url}}' rel='noopener noreferrer' target='_blank'>Sign here</a></p><br><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br></body> </html>`;
const res = replaceMailVaribles(subject, body, variables);
setMail((prev) => ({ ...prev, subject: res.subject, body: res.body }));
setIsNextStep({ [user.Id]: true });
+2781 -3832
View File
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -18,10 +18,10 @@
"watch": "nodemon index.js"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.812.0",
"@aws-sdk/s3-request-presigner": "^3.812.0",
"@aws-sdk/client-s3": "^3.824.0",
"@aws-sdk/s3-request-presigner": "^3.824.0",
"@parse/fs-files-adapter": "^3.0.0",
"@parse/s3-files-adapter": "^4.1.0",
"@parse/s3-files-adapter": "^4.1.1",
"@pdf-lib/fontkit": "^1.1.1",
"@signpdf/placeholder-pdf-lib": "^3.2.6",
"@signpdf/signer-p12": "^3.2.4",
@@ -34,19 +34,19 @@
"express": "^5.1.0",
"form-data": "^4.0.2",
"generate-api-key": "^1.0.2",
"googleapis": "^148.0.0",
"mailgun.js": "^12.0.1",
"mongodb": "^6.16.0",
"multer": "^2.0.0",
"googleapis": "^149.0.0",
"mailgun.js": "^12.0.2",
"mongodb": "^6.17.0",
"multer": "^2.0.1",
"multer-s3": "^3.0.1",
"node-forge": "^1.3.1",
"nodemailer": "^7.0.3",
"parse": "^6.1.1",
"parse-dbtool": "^1.2.0",
"parse-server": "^8.2.0",
"parse-server": "^8.2.1",
"parse-server-api-mail-adapter": "^4.1.0",
"pdf-lib": "^1.17.1",
"posthog-node": "^4.17.1",
"posthog-node": "^4.18.0",
"qrcode": "^1.5.4",
"rate-limiter-flexible": "^7.1.1",
"speakeasy": "^2.0.0",
@@ -54,8 +54,8 @@
},
"type": "module",
"devDependencies": {
"@babel/eslint-parser": "^7.27.1",
"eslint": "^9.27.0",
"@babel/eslint-parser": "^7.27.5",
"eslint": "^9.28.0",
"jasmine": "^5.7.1",
"mongodb-runner": "^5.8.3",
"nodemon": "^3.1.10",