Files
OpenSign/apps/OpenSign/src/pages/PdfRequestFiles.jsx
T
2026-08-10 15:59:21 +00:00

2314 lines
90 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useRef, useEffect, useCallback } from "react";
import { PDFDocument } from "pdf-lib";
import "../styles/signature.css";
import Parse from "parse";
import axios from "axios";
import { useDispatch, useSelector } from "react-redux";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import Tour from "../primitives/Tour";
import Confetti from "react-confetti";
import moment from "moment";
import {
setSaveSignCheckbox,
setMyInitial,
setDefaultSignImg,
resetWidgetState,
setPrefillImg,
setTypedSignFont,
setMyStamp
} from "../redux/reducers/widgetSlice";
import {
contractDocument,
embedWidgetsToDoc,
embedDocId,
pdfNewWidthFun,
signPdfFun,
addDefaultSignatureImg,
replaceMailVaribles,
convertPdfArrayBuffer,
contractUsers,
contactBook,
handleToPrint,
handleDownloadCertificate,
getDefaultSignature,
onClickZoomIn,
onClickZoomOut,
fetchUrl,
signatureTypes,
handleSignatureType,
getTenantDetails,
getBase64FromUrl,
openInNewTab,
getContainerScale,
randomId,
defaultWidthHeight,
addWidgetOptions,
textWidget,
mailTemplate,
updateDateWidgetsRes,
widgetDataValue,
getOriginalWH,
handleCheckResponse,
convertJpegToPng,
} from "../constant/Utils";
import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf";
import DefaultSignature from "../components/pdf/DefaultSignature";
import SignerListComponent from "../components/pdf/SignerListComponent";
import PdfTools from "../components/pdf/PdfTools";
import { useTranslation } from "react-i18next";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
import Loader from "../primitives/Loader";
import PdfDeclineModal from "../primitives/PdfDeclineModal";
import { serverUrl_fn } from "../constant/appinfo";
import AgreementSign from "../components/pdf/AgreementSign";
import WidgetComponent from "../components/pdf/WidgetComponent";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TextFontSetting from "../components/pdf/TextFontSetting";
import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
import * as utils from "../utils";
import { useWindowSize } from "../hook/useWindowSize";
import { useScroll } from "../context/ScrollPdfContext";
function PdfRequestFiles(
) {
const { t } = useTranslation();
const dispatch = useDispatch();
const windowSize = useWindowSize();
const { scrollRef } = useScroll();
const prefillImg = useSelector((state) => state.widget.prefillImg);
const isShowModal = useSelector((state) => state.widget.isShowModal);
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
const myInitial = useSelector((state) => state.widget.myInitial);
const appName =
"OpenSign™";
const [pdfDetails, setPdfDetails] = useState([]);
const [signedSigners, setSignedSigners] = useState([]);
const [unsignedSigners, setUnSignedSigners] = useState([]);
const [pdfUrl, setPdfUrl] = useState();
const [allPages, setAllPages] = useState(null);
const numPages = 1;
const [pageNumber, setPageNumber] = useState(1);
const [handleError, setHandleError] = useState();
const [isCelebration, setIsCelebration] = useState(false);
const [isReqSignTourDisabled, setIsReqSignTourDisabled] = useState(true);
const [tourStatus, setTourStatus] = useState([]);
const [isDontShowCheckbox, setIsDontShowCheckbox] = useState(false);
const [isLoading, setIsLoading] = useState({
isLoad: true,
message: t("loading-mssg")
});
const [isDocId, setIsDocId] = useState(false);
const [pdfNewWidth, setPdfNewWidth] = useState();
const [pdfOriginalWH, setPdfOriginalWH] = useState([]);
const [signerPos, setSignerPos] = useState([]);
const [signerObjectId, setSignerObjectId] = useState();
const [isUiLoading, setIsUiLoading] = useState(false);
const [isDecline, setIsDecline] = useState({ isDeclined: false });
const [currentSigner, setCurrentSigner] = useState(false);
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [unSignedWidgetId, setUnSignedWidgetId] = useState("");
const [expiredDate, setExpiredDate] = useState("");
const [isResize, setIsResize] = useState(false);
const [signerUserId, setSignerUserId] = useState();
const [isDontShow, setIsDontShow] = useState(true);
const [isDownloading, setIsDownloading] = useState("");
const [defaultSignAlert, setDefaultSignAlert] = useState({
isShow: false,
alertMessage: ""
});
const [isCompleted, setIsCompleted] = useState({
isCertificate: false,
isModal: false
});
const [pdfLoad, setPdfLoad] = useState(false);
const [isSigned, setIsSigned] = useState(false);
const [isExpired, setIsExpired] = useState(false);
const [alreadySign, setAlreadySign] = useState(false);
const [containerWH, setContainerWH] = useState({ width: 0, height: 0 });
const [widgetsTour, setWidgetsTour] = useState(false);
const [minRequiredCount, setminRequiredCount] = useState();
const [sendInOrder, setSendInOrder] = useState(false);
const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
const [extUserId, setExtUserId] = useState("");
const [contractName, setContractName] = useState("");
const [zoomPercent, setZoomPercent] = useState(0);
const [scale, setScale] = useState(1);
const [uniqueId, setUniqueId] = useState("");
const [documentId, setDocumentId] = useState("");
const isSidebar = useSelector((state) => state.sidebar.isOpen);
const divRef = useRef(null);
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [signatureType, setSignatureType] = useState([]);
const [pdfBase64Url, setPdfBase64Url] = useState("");
const [isAgree, setIsAgree] = useState(false);
const [redirectTimeLeft, setRedirectTimeLeft] = useState(5);
const [isredirectCanceled, setIsredirectCanceled] = useState(true);
const [isDragging, setIsDragging] = useState(false);
const [dragKey, setDragKey] = useState();
const [signBtnPosition, setSignBtnPosition] = useState([]);
const [xySignature, setXYSignature] = useState({});
const [zIndex, setZIndex] = useState(1);
const [fontSize, setFontSize] = useState();
const [fontColor, setFontColor] = useState();
const [isTextSetting, setIsTextSetting] = useState(false);
const [isPageCopy, setIsPageCopy] = useState(false);
const [assignedWidgetId, setAssignedWidgetId] = useState([]);
const [showSignPagenumber, setShowSignPagenumber] = useState([]);
const [owner, setOwner] = useState({});
const [tenantMailTemplate, setTenantMailTemplate] = useState({
body: "",
subject: ""
});
const isMobile = window.innerWidth < 767;
let isGuestSignFlow = false;
let sendmail;
let getDocId = "";
let contactBookId = "";
const route =
window.location.pathname;
const getQuery =
window.location?.search?.split("?"); //['','sendmail=false']
if (getQuery) {
sendmail = getQuery?.[1]?.split("=")[1]; //false
}
const routeId = route && route?.split("/"); // ['', 'load', 'recipientSignPdf', ':docId', ':contactBookId']
if (routeId && routeId.length > 4) {
// this condition will be occur only in guest flow in which load routeId will be include
isGuestSignFlow = true;
getDocId = routeId[3];
contactBookId = routeId[4];
} else {
// this condition will be occur only in if user is logged in and load routeId will be exclude
getDocId = routeId[2];
contactBookId = routeId?.[3] || "";
}
let getDocumentId = getDocId || documentId;
useEffect(() => {
dispatch(resetWidgetState([]));
if (getDocumentId) {
setDocumentId(getDocumentId);
getDocumentDetails(getDocumentId);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
getDocumentId
]);
useEffect(() => {
const updateSize = () => {
if (divRef.current) {
const pdfWidth = pdfNewWidthFun(divRef);
setPdfNewWidth(pdfWidth);
setContainerWH({
width: divRef.current.offsetWidth,
height: divRef.current.offsetHeight
});
}
};
// Use setTimeout to wait for the transition to complete
const timer = setTimeout(updateSize, 150); // match the transition duration
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current, isSidebar, windowSize?.width]);
const redirectUrl = pdfDetails?.[0]?.RedirectUrl || "";
useEffect(() => {
if (isredirectCanceled) return; // Stop the redirect timer if canceled
if (redirectUrl) {
if (redirectTimeLeft === 0) {
openInNewTab(redirectUrl, "_self"); // Replace with your target URL
}
const timer = setTimeout(() => {
setRedirectTimeLeft((prev) => prev - 1); // Decrement the timer
}, 1000);
return () => clearTimeout(timer); // Cleanup the timer
}
}, [redirectTimeLeft, isredirectCanceled, redirectUrl]);
const fetchTenantDetails = async (contactId) => {
const user = JSON.parse(
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
)
);
try {
const tenantDetails = await getTenantDetails(
user?.objectId, // userId
contactId // contactId
);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert(t("user-not-exist"));
} else if (tenantDetails) {
const signatureType = tenantDetails?.SignatureType || [];
const filterSignTypes = signatureType?.filter(
(x) => x.enabled === true
);
if (tenantDetails?.RequestBody) {
setTenantMailTemplate({
body: tenantDetails?.RequestBody,
subject: tenantDetails?.RequestSubject
});
}
return filterSignTypes;
}
} catch (e) {
alert(t("user-not-exist"));
}
};
//function for get document details for perticular signer with signer'object id
const getDocumentDetails = async (
docId,
isNextUser,
isSuccessPage = false,
) => {
try {
const senderUser = localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = JSON.parse(senderUser);
const contactId = jsonSender?.objectId
? ""
: contactBookId ||
signerObjectId ||
"";
const tenantSignTypes = await fetchTenantDetails(contactId);
// `currUserId` will be contactId or extUserId
let currUserId;
//getting document details
const documentData = await contractDocument(docId);
// Filter out 'prefill' roles from the Placeholder array
const filteredPlaceholder = documentData[0]?.Placeholders?.filter(
(data) => data.Role !== "prefill"
);
// Reassign the updated Placeholder back to the documentData array
documentData[0] = {
...documentData[0],
Placeholders: filteredPlaceholder
};
if (documentData && documentData.length > 0) {
const prefillImg = await utils?.savePrefillImg(
documentData[0]?.Placeholders
);
if (prefillImg && Array.isArray(prefillImg)) {
prefillImg.forEach((img) => {
dispatch(setPrefillImg(img));
});
}
const userSignatureType =
documentData[0]?.ExtUserPtr?.SignatureType || signatureTypes;
const docSignTypes =
documentData?.[0]?.SignatureType || userSignatureType;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
docSignTypes
);
setSignatureType(updatedSignatureType);
const updatedPdfDetails = [...documentData];
updatedPdfDetails[0].SignatureType = updatedSignatureType;
setPdfDetails(updatedPdfDetails);
const url =
documentData[0] &&
(documentData[0]?.SignedUrl || documentData[0]?.URL);
if (url) {
const base64Pdf = await getBase64FromUrl(url);
if (base64Pdf) {
setPdfBase64Url(base64Pdf);
}
} else {
setHandleError(t("something-went-wrong-mssg"));
}
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
setOwner(documentData?.[0]?.ExtUserPtr);
const isCompleted =
documentData[0].IsCompleted && documentData[0].IsCompleted;
const expireDate = documentData[0]?.ExpiryDate?.iso;
const declined =
documentData[0].IsDeclined && documentData[0].IsDeclined;
const expireUpdateDate = new Date(expireDate).getTime();
const currDate = new Date().getTime();
const getSigners = documentData[0].Signers;
const isTourEnabled =
documentData[0]?.IsTourEnabled === true ? true : false;
const getCurrentSigner = getSigners?.find(
(data) => data.UserId.objectId === jsonSender?.objectId
);
currUserId = getCurrentSigner?.objectId
? getCurrentSigner.objectId
: contactBookId ||
signerObjectId ||
""; //signerObjectId is contactBookId refer from public template flow
if (currUserId) {
setSignerObjectId(currUserId);
}
if (documentData[0].SignedUrl) {
setPdfUrl(documentData[0].SignedUrl);
} else {
setPdfUrl(documentData[0].URL);
}
if (isCompleted) {
setIsSigned(true);
setAlreadySign(true);
setIsCelebration(true);
setTimeout(() => setIsCelebration(false), 5000);
if (!isSuccessPage) {
const data = { isCertificate: true, isModal: true };
setIsCompleted(data);
}
} else if (declined) {
const currentDecline = { currnt: "another", isDeclined: true };
setIsDecline(currentDecline);
} else if (currDate > expireUpdateDate) {
const expireDateFormat = moment(new Date(expireDate)).format(
"MMM DD, YYYY"
);
setIsExpired(true);
setExpiredDate(expireDateFormat);
} // Check if the current signer is not a last signer and handle the complete message.
else if (isNextUser) {
setIsCelebration(true);
setTimeout(() => setIsCelebration(false), 5000);
if (!isSuccessPage) {
setIsCompleted({
isModal: true,
message: t("document-signed-alert-1")
});
}
} else {
if (currUserId) {
const checkCurrentUser = documentData[0]?.Placeholders?.find(
(data) => data?.signerObjId === currUserId
);
if (checkCurrentUser) {
const widgetId = [];
// Viewers and approvers may not have any placeholders/widgets
// attached, so guard against an undefined placeHolder array.
for (let placeholder of checkCurrentUser.placeHolder || []) {
for (let item of placeholder.pos || []) {
widgetId.push(item.key);
}
}
setAssignedWidgetId(widgetId);
setUniqueId(checkCurrentUser.Id);
setCurrentSigner(true);
}
}
}
// Approved entries also count toward audit trail completion summary.
const audittrailData = documentData?.[0]?.AuditTrail?.filter(
(data) => data.Activity === "Signed"
);
const checkAlreadySign =
documentData?.[0]?.AuditTrail?.some(
(data) =>
data?.UserPtr?.objectId === currUserId &&
(data.Activity === "Signed"
)
) || false;
if (checkAlreadySign) {
setAlreadySign(true);
} else {
const obj = documentData?.[0];
setSendInOrder(obj?.SendinOrder || false);
if (
obj?.Signers?.length &&
obj?.Placeholders?.length &&
currUserId !== "undefined"
) {
const params = {
event: "viewed",
contactId: currUserId,
body: {
objectId: documentData?.[0].objectId,
}
};
try {
await axios.post(
`${localStorage.getItem("baseUrl")}functions/triggerevent`,
params,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id":
localStorage.getItem("parseAppId"),
sessiontoken: localStorage.getItem("accesstoken")
}
}
);
} catch (err) {
console.log("Err ", err);
}
}
}
let signers = [];
let unSignedSigner = [];
const removePrefill = documentData[0]?.Placeholders.filter(
(x) => x.Role !== "prefill"
);
const placeholdersOrSigners = [];
for (const placeholder of removePrefill) {
//`emailExist` variable to handle condition for quick send flow and show unsigned signers list
const signerIdExist = placeholder?.signerObjId;
if (signerIdExist) {
const getSignerData = documentData[0].Signers.find(
(data) => data.objectId === placeholder?.signerObjId
);
placeholdersOrSigners.push({
...getSignerData,
});
} else {
placeholdersOrSigners.push(placeholder);
}
}
//condition to check already signed document by someone
if (audittrailData && audittrailData.length > 0) {
setIsDocId(true);
for (const item of placeholdersOrSigners) {
const checkEmail = item?.email;
//if email exist then compare user signed by using email else signers objectId
const emailOrId = checkEmail ? item.email : item?.objectId;
//`isSignedSignature` variable to handle break loop whenever it get true
let isSignedSignature = false;
//checking the signer who signed the document by using audit trail details.
//and save signedSigners and unsignedSigners details
for (const doc of audittrailData) {
const signedExist = checkEmail
? doc?.UserPtr?.Email
: doc?.UserPtr?.objectId;
if (emailOrId === signedExist) {
signers.push({ ...item });
isSignedSignature = true;
break;
}
}
if (!isSignedSignature) {
unSignedSigner.push({ ...item });
}
}
setSignedSigners(signers);
setUnSignedSigners(unSignedSigner);
} else {
//else condition is show there are no details in audit trail then direct push all signers details
//in unsignedsigners array
setUnSignedSigners(placeholdersOrSigners);
}
setSignerPos(updateDateWidgetsRes(documentData[0], currUserId));
setPdfDetails(documentData);
//checking if condition current user already sign or owner does not exist as a signer or document has been declined by someone or document has been expired
//then stop to display tour message
if (
checkAlreadySign ||
!currUserId ||
declined ||
currDate > expireUpdateDate
) {
setIsReqSignTourDisabled(true);
} else {
const isEnableOTP = documentData?.[0]?.IsEnableOTP || false;
const sessionToken = localStorage.getItem("accesstoken");
if (sessionToken) {
//condition to check current user exist in contracts_Users class and check tour message status
//if not then check user exist in contracts_Contactbook class and check tour message status
const res = await contractUsers();
if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
} else if (res[0] && res?.length) {
setContractName("_Users");
currUserId = res[0].objectId;
setSignerUserId(currUserId);
storeSignerDetails(res?.[0]);
handleTourStatus(isTourEnabled, res?.[0]?.TourStatus);
//function to get default signatur of current user from `contracts_Signature` class
const defaultSignRes = await getDefaultSignature(
jsonSender?.objectId
);
if (defaultSignRes?.status === "success") {
dispatch(
setSaveSignCheckbox({
isVisible: true,
signId: defaultSignRes?.res?.id
})
);
const sign = defaultSignRes?.res?.defaultSignature || "";
const initials = defaultSignRes?.res?.defaultInitial || "";
dispatch(setDefaultSignImg(sign));
dispatch(setMyInitial(initials));
dispatch(setMyStamp(defaultSignRes?.res?.defaultStamp));
} else {
dispatch(setSaveSignCheckbox({ isVisible: true }));
}
} else if (res?.length === 0) {
const res = await contactBook(currUserId);
if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
} else if (res[0] && res.length) {
setContractName("_Contactbook");
const objectId = res[0].objectId;
storeSignerDetails(res?.[0]);
setSignerUserId(objectId);
handleTourStatus(isTourEnabled, res?.[0]?.TourStatus);
} else if (res.length === 0) {
setHandleError(t("user-not-exist"));
}
}
} else if (!isEnableOTP) {
try {
const resContact = await axios.post(
`${localStorage.getItem("baseUrl")}functions/getcontact`,
{ contactId: currUserId },
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
}
}
);
const contact = resContact?.data?.result;
storeSignerDetails(contact);
setContractName("_Contactbook");
setSignerUserId(contact?.objectId);
handleTourStatus(isTourEnabled, contact?.TourStatus);
} catch (error) {
console.log("err while getting tourstatus", error);
}
}
}
setIsUiLoading(false);
setIsLoading({ isLoad: false });
return {
updatedPdfDetails,
};
} else if (
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
) {
if (documentData?.result?.error?.includes("deleted")) {
setHandleError(t("document-deleted"));
} else {
setHandleError(t("something-went-wrong-mssg"));
}
setIsLoading({ isLoad: false });
console.log("err in getDocument cloud function ");
} else {
setHandleError(t("no-data"));
setIsUiLoading(false);
setIsLoading({ isLoad: false });
}
setIsLoading({ isLoad: false });
} catch (err) {
console.log("Error: error in getDocumentDetails", err);
setHandleError(t("something-went-wrong-mssg"));
setIsLoading({ isLoad: false });
}
};
// `storeSignerDetails` is used to stor signer details to set in text type widgets
const storeSignerDetails = (signer) => {
localStorage.setItem(
"signer",
JSON.stringify({
Name: signer?.Name,
Email: signer?.Email,
UserId: signer?.UserId?.objectId,
JobTitle: signer?.JobTitle,
Company: signer?.Company
})
);
};
// `handleTourStatus` is used to disable/enable tour
const handleTourStatus = (isTourEnabled, tourData) => {
if (isTourEnabled) {
if (tourData && tourData.length > 0) {
const checkTourRequest =
tourData?.some((data) => data?.requestSign) || false;
setTourStatus(tourData);
setIsDontShowCheckbox(!checkTourRequest);
setIsReqSignTourDisabled(checkTourRequest);
} else {
setIsReqSignTourDisabled(false);
}
} else {
setIsReqSignTourDisabled(true);
}
};
//function for embed signature or image url in pdf
async function embedWidgetsData(
) {
let contactId =
signerObjectId;
let docId =
documentId;
const addExtraDays = pdfDetails[0]?.TimeToCompleteDays
? pdfDetails[0].TimeToCompleteDays
: 15;
let updateExpiryDate;
updateExpiryDate = new Date();
updateExpiryDate.setDate(updateExpiryDate.getDate() + addExtraDays);
const expiry = updateExpiryDate || pdfDetails?.[0].ExpiryDate.iso;
//for emailVerified data checking first in localstorage
const localuser = localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
let currentUser = JSON.parse(localuser);
let isEmailVerified = currentUser?.emailVerified;
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
//if emailVerified data is not present in local user details then fetch again in _User class
if (isEnableOTP) {
try {
if (!currentUser?.emailVerified) {
const userQuery = new Parse.Query(Parse.User);
const getUser = await userQuery.get(currentUser?.objectId, {
sessionToken:
currentUser?.sessionToken || localStorage.getItem("accesstoken")
});
if (getUser) {
currentUser = JSON.parse(JSON.stringify(getUser));
}
}
isEmailVerified = currentUser?.emailVerified;
} catch (err) {
console.log("err in get email verification ", err);
setHandleError(t("something-went-wrong-mssg"));
setIsUiLoading(false);
}
}
//check if isEmailVerified then go on next step
if (!isEnableOTP || isEmailVerified) {
try {
const checkUser = signerPos.filter(
(data) => data.signerObjId === signerObjectId
);
if (checkUser && checkUser.length > 0) {
const status = handleCheckResponse(checkUser, setminRequiredCount);
if (status?.showAlert) {
setUnSignedWidgetId(status?.widgetKey);
setPageNumber(status?.tourPageNumber);
setWidgetsTour(true);
setIsUiLoading(false);
} else {
// `widgets` is Used to return widgets details with page number of current user
const widgets = checkUser?.[0]?.placeHolder;
let pdfArrBuffer;
const isSignYourSelfFlow = false;
//`contractDocument` function used to get updated SignedUrl
// to resolve issue of widgets get remove automatically when more than 1 signers try to sign doc at a time
const documentData = await contractDocument(docId);
if (documentData && documentData.length > 0) {
const url = documentData[0]?.SignedUrl || documentData[0]?.URL;
const isDeclined = documentData?.[0]?.IsDeclined || false;
if (isDeclined) {
setIsDecline({
isDeclined,
currnt: "another",
email: documentData?.[0]?.DeclineBy?.email || "",
reason: documentData?.[0]?.DeclineReason || ""
});
setIsUiLoading(false);
return;
}
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
const arrayBuffer = await convertPdfArrayBuffer(url);
if (arrayBuffer === "Error") {
setHandleError("Error: invalid document!");
} else {
pdfArrBuffer = arrayBuffer;
}
const prefillDetails = signerPos.find(
(x) => x.Role === "prefill"
);
//condition to embed prefill widgets details in pdf in public signing flow
if (prefillDetails && props.templateId) {
const prefillwidgets = prefillDetails?.placeHolder;
const pdfDoc = await PDFDocument.load(pdfArrBuffer);
const pdfbase64 = await embedWidgetsToDoc(
prefillwidgets,
pdfDoc,
isSignYourSelfFlow,
scale,
prefillImg
);
//convert base64 to arraybuffer is used in pdf-lib
pdfArrBuffer = base64ToArrayBuffer(pdfbase64);
}
} else if (
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
) {
if (documentData?.result?.error?.includes("deleted")) {
setHandleError(t("document-deleted"));
} else {
setHandleError(t("something-went-wrong-mssg"));
}
} else {
setHandleError("Document not Found!");
}
try {
const pdfDoc = await PDFDocument.load(pdfArrBuffer);
const extUserPtr = pdfDetails[0].ExtUserPtr;
const HeaderDocId = extUserPtr?.HeaderDocId;
//embed document's object id to all pages in pdf document
if (!HeaderDocId) {
if (!isDocId) {
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
await embedDocId(pdfOriginalWH, pdfDoc, docId);
}
}
//embed all widgets in document
const pdfBytes = await embedWidgetsToDoc(
widgets,
pdfDoc,
isSignYourSelfFlow,
scale
);
//get ExistUserPtr object id of user class to get tenantDetails
if (!pdfBytes?.error) {
const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId;
//function for call to embed signature in pdf and get digital signature pdf
const resSign = await signPdfFun(
pdfBytes,
docId,
contactId,
objectId,
widgets,
"Signed"
);
if (resSign && resSign.status === "success") {
dispatch(setTypedSignFont("Fasthand"));
setPdfUrl(resSign.data);
setIsSigned(true);
setSignedSigners([]);
setUnSignedSigners([]);
const isSuccessRoute = pdfDetails?.[0]?.RedirectUrl
? false
: window.location?.pathname?.includes("load");
const updatedDoc = await getDocumentDetails(
docId,
true,
isSuccessRoute,
contactId
);
const index =
updatedDoc.updatedPdfDetails?.[0]?.Signers.findIndex(
(x) => x.objectId === contactId
);
const removePrefill =
updatedDoc.updatedPdfDetails?.[0]?.Placeholders?.filter(
(x) => x.Role !== "prefill"
);
// Skip viewer placeholders when computing the next signer
// to notify (viewers do not gate sequential signing).
let newIndex = index + 1;
const usermail = {
Email: removePrefill[newIndex]?.email || ""
};
const user = usermail?.Email
? usermail
: updatedDoc.updatedPdfDetails?.[0]?.Signers[newIndex];
if (
sendmail !== "false" &&
sendInOrder
) {
const mailBody =
tenantMailTemplate?.body;
const mailSubject =
tenantMailTemplate?.subject;
const requestBody =
updatedDoc.updatedPdfDetails?.[0]?.RequestBody ||
mailBody;
const requestSubject =
updatedDoc.updatedPdfDetails?.[0]?.RequestSubject ||
mailSubject;
if (user) {
const expireDate = expiry;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString(
"en-US",
{ day: "numeric", month: "long", year: "numeric" }
);
let senderEmail =
pdfDetails?.[0]?.SenderMail ||
pdfDetails?.[0]?.ExtUserPtr?.Email;
let senderPhone = pdfDetails?.[0]?.ExtUserPtr?.Phone;
const senderName =
pdfDetails?.[0]?.SenderName ||
pdfDetails?.[0].ExtUserPtr.Name;
const documentName = pdfDetails?.[0].Name;
try {
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id":
localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const objectId = user?.objectId;
const hostUrl = window.location.origin;
//encode this url value `${pdfDetails?.[0].objectId}/${user.Email}/${objectId}` to base64 using `btoa` function
let encodeBase64;
if (objectId) {
encodeBase64 = btoa(
`${docId}/${user.Email}/${objectId}`
);
} else {
encodeBase64 = btoa(`${docId}/${user.Email}`);
}
let signPdf =
`${hostUrl}/login/${encodeBase64}`;
const orgName = pdfDetails[0]?.ExtUserPtr.Company
? pdfDetails[0].ExtUserPtr.Company
: "";
let replaceVar;
if (
requestBody &&
requestSubject
) {
const replacedRequestBody = requestBody.replace(
/"/g,
"'"
);
const htmlReqBody =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
replacedRequestBody +
"</body></html>";
const variables = {
document_title: documentName,
note: pdfDetails?.[0]?.Note,
sender_name: senderName,
sender_mail: senderEmail,
sender_phone: senderPhone,
receiver_name: user?.Name || "",
receiver_email: user.Email,
receiver_phone: user?.Phone || "",
expiry_date: localExpireDate,
company_name: orgName,
signing_url: signPdf
};
replaceVar = replaceMailVaribles(
requestSubject,
htmlReqBody,
variables
);
}
const mailparam = {
note: pdfDetails?.[0]?.Note || "",
senderName: senderName,
senderMail: senderEmail,
title: documentName,
organization: orgName,
localExpireDate: localExpireDate,
signingUrl: signPdf
};
let params = {
replyto: senderEmail || "",
extUserId: extUserId,
recipient: user.Email,
subject: replaceVar?.subject
? replaceVar?.subject
: mailTemplate(mailparam).subject,
from:
pdfDetails?.[0]?.SenderName ||
senderEmail,
html: replaceVar?.body
? replaceVar?.body
: mailTemplate(mailparam).body
};
await axios.post(url, params, { headers: headers });
} catch (error) {
console.log("error", error);
}
}
}
if (!isSuccessRoute) {
setIsredirectCanceled(false);
} else {
const url =
updatedDoc.updatedPdfDetails?.[0]?.SignedUrl ||
updatedDoc.updatedPdfDetails?.[0]?.URL;
const fileAdapter =
"";
const isCompleted = updatedDoc.updatedPdfDetails?.[0]
?.IsCompleted
? `&completed=true`
: "";
const params =
`docid=${updatedDoc.updatedPdfDetails[0].objectId}&docurl=${encodeURIComponent(url)}${isCompleted}${fileAdapter}`;
window.location.href = `/success?${params}`;
}
} else {
setIsUiLoading(false);
setIsAlert({
title: "Error",
isShow: true,
alertMessage: resSign.message
});
}
} else {
setIsUiLoading(false);
setIsAlert({
title: "Error",
isShow: true,
alertMessage: t("pdf-uncompatible", { appName: appName })
});
}
} catch (err) {
setIsUiLoading(false);
if (err && err.message.includes("is encrypted.")) {
setIsAlert({
isShow: true,
alertMessage: t("encrypted-pdf-not-support")
});
} else {
console.log("err in request signing", err);
setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
}
}
} else {
setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
setIsUiLoading(false);
}
} catch (err) {
console.log("err in embedsign", err);
setIsUiLoading(false);
setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
}
}
const handleSignPdf = async () => {
setIsUiLoading(true);
await embedWidgetsData();
};
//function for save x and y position and show signature tab on that position
const handleTabDrag = (key) => {
setDragKey(key);
setIsDragging(true);
};
//function for set and update x and y postion after drag and drop signature tab
const handleStop = (event, dragElement, signerId, key, widgetPageNumber) => {
if (!isResize && isDragging) {
const effectivePageNumber = widgetPageNumber || pageNumber;
let updateSignPos = [...signerPos];
const signerObjId = signerId ? signerId : uniqueId;
const keyValue = key ? key : dragKey;
const containerScale = getContainerScale(
pdfOriginalWH,
effectivePageNumber,
containerWH
);
if (keyValue >= 0) {
let filterSignerPos = [];
if (signerObjId) {
//get current signerObjId placeholder details
filterSignerPos = updateSignPos.filter(
(data) => data.Id === signerObjId && data.Role !== "prefill"
);
}
if (filterSignerPos.length > 0) {
const getPlaceHolder = filterSignerPos[0]?.placeHolder;
//get position of current pagenumber
const getPageNumer = getPlaceHolder?.filter(
(data) => data.pageNumber === effectivePageNumber
);
if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos;
const addSignPos = getXYdata.map((url) => {
//add new position after drag widgets
if (url.key === keyValue) {
return {
...url,
xPosition: dragElement.x / containerScale,
yPosition: dragElement.y / containerScale
};
}
return url;
});
//update new position of current page number
const newUpdateSignPos = getPlaceHolder.map((obj) => {
if (obj.pageNumber === effectivePageNumber) {
return { ...obj, pos: addSignPos };
}
return obj;
});
//update new placeholder of current signer
const newUpdateSigner = updateSignPos.map((obj) => {
if (signerObjId) {
if (obj.Id === uniqueId && obj.Role !== "prefill") {
return { ...obj, placeHolder: newUpdateSignPos };
}
}
return obj;
});
setSignerPos(newUpdateSigner);
}
}
}
}
setTimeout(() => setIsDragging(false), 200);
};
const handleTextSettingModal = (value) => {
setIsTextSetting(value);
};
const handleSaveFontSize = () => {
const widgetPageNumber = currWidgetsDetails?.pageNumber || pageNumber;
const filterSignerPos = signerPos?.filter((data) => data.Id === uniqueId);
if (filterSignerPos) {
const placehoder = filterSignerPos[0]?.placeHolder;
const getPageNumer = placehoder.filter(
(data) => data.pageNumber === widgetPageNumber
);
if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos;
const getPosData = getXYdata;
const updateSignPos = getPosData.map((position) => {
if (position.key === currWidgetsDetails?.key) {
return {
...position,
options: {
...position.options,
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor:
fontColor || currWidgetsDetails?.options?.fontColor || "black"
}
};
}
return position;
});
//update new position of current page number
const newUpdateSignPos = placehoder.map((obj) => {
if (obj.pageNumber === widgetPageNumber) {
return { ...obj, pos: updateSignPos };
}
return obj;
});
//update new placeholder of current signer
const newUpdateSigner = signerPos.map((obj) => {
if (obj.Id === uniqueId) {
return { ...obj, placeHolder: newUpdateSignPos };
}
return obj;
});
setSignerPos(newUpdateSigner);
setFontSize();
setFontColor();
handleTextSettingModal(false);
}
}
};
//function for update TourStatus
const closeTour = async () => {
setWidgetsTour(false);
};
const tourConfig = [
{
selector: '[data-tut="IsSigned"]',
content: minRequiredCount
? t("signature-validate-alert", { minRequiredCount })
: t("signature-validate-alert-2"),
position: "top",
styles: { fontSize: "13px" }
}
];
//function for get pdf page details
const pageDetails = async (pdf) => {
const pdfWHObj = await getOriginalWH(pdf);
setPdfOriginalWH(pdfWHObj);
setPdfLoad(true);
};
//function for change page
function changePage(offset) {
setPageNumber((prevPageNumber) => prevPageNumber + offset);
}
//function for set decline true on press decline button
const declineDoc = async (reason) => {
const senderUser = localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = JSON.parse(senderUser);
setIsDecline({ isDeclined: false });
setIsUiLoading(true);
const userId =
pdfDetails?.[0].Signers?.find((x) => x.objectId === signerObjectId)
?.UserId?.objectId || jsonSender?.objectId;
const params = {
docId: pdfDetails?.[0].objectId,
reason: reason,
userId: userId,
};
await axios
.post(`${localStorage.getItem("baseUrl")}functions/declinedoc`, params, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
})
.then(async (result) => {
const res = result.data;
if (res) {
const currentDecline = { currnt: "YouDeclined", isDeclined: true };
setIsDecline(currentDecline);
setIsUiLoading(false);
}
})
.catch((err) => {
console.log("error updating field is decline ", err);
setIsUiLoading(false);
setIsAlert({
title: "Error",
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
});
};
//function to add default signature for all requested placeholder of sign
const addDefaultSignature = async () => {
const type = defaultSignAlert?.type;
//get current signers placeholder position data
const currentSignerPosition = signerPos?.filter(
(data) => data.signerObjId === signerObjectId
);
const defaultSign = type === "signature" ? defaultSignImg : myInitial;
const placeHolder = currentSignerPosition[0].placeHolder;
const filename = type === "signature" ? "mysign" : "myinitials";
const signImg = await convertJpegToPng(defaultSign, filename);
//function for save default signature url for all placeholder position
const updatePlace = addDefaultSignatureImg(placeHolder, signImg, type);
const updatesignerPos = signerPos.map((x) =>
x.signerObjId === signerObjectId ? { ...x, placeHolder: updatePlace } : x
);
setSignerPos(updatesignerPos);
setDefaultSignAlert({ isShow: false, alertMessage: "" });
};
const handleDontShow = (isChecked) => {
setIsDontShow(isChecked);
};
//function to close tour and save tour status
const closeRequestSignTour = async () => {
setIsReqSignTourDisabled(true);
if (isDontShow) {
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
const sessionToken = localStorage.getItem("accesstoken");
if (!isEnableOTP && !sessionToken) {
try {
await axios.post(
`${localStorage.getItem("baseUrl")}functions/updatecontacttour`,
{ contactId: signerObjectId },
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
}
}
);
} catch (e) {
console.log("update tour messages error", e);
}
} else {
let updatedTourStatus = [];
if (tourStatus.length > 0) {
updatedTourStatus = [...tourStatus];
const requestSignIndex = tourStatus.findIndex(
(obj) => obj["requestSign"] === false || obj["requestSign"] === true
);
if (requestSignIndex !== -1) {
updatedTourStatus[requestSignIndex] = { requestSign: true };
} else {
updatedTourStatus.push({ requestSign: true });
}
} else {
updatedTourStatus = [{ requestSign: true }];
}
try {
await axios.put(
`${localStorage.getItem(
"baseUrl"
)}classes/contracts${contractName}/${signerUserId}`,
{
TourStatus: updatedTourStatus
},
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
} catch (e) {
console.log("update tour messages error", e);
}
}
}
};
const formatArrayToString = (arr) => {
if (arr.length === 0) return ""; // Handle empty array
if (arr.length === 1) return `${arr[0]}`; // Handle single-element array
const lastElement = arr.pop(); // Remove and store the last element
return `${arr.join(", ")} ${t("and")} ${lastElement}`; // Format the string
};
const requestSignTourFunction = () => {
const pagenumbers = formatArrayToString(showSignPagenumber);
const tourConfig = [
{
selector: '[data-tut="IsSigned"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-6", { pagenumbers })}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
position: "top",
styles: { fontSize: "13px" }
},
{
selector: '[data-tut="reactourFirst"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-1")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
position: "top",
styles: { fontSize: "13px" }
},
{
selector: '[data-tut="pdfArea"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-2")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
position: "top",
styles: { fontSize: "13px" }
},
{
selector: '[data-tut="pdftools"]',
content: () => (
<TourContentWithBtn
message={t("pdf-tools-tour")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
position: "top",
styles: { fontSize: "13px" }
},
{
selector: '[data-tut="reactourFifth"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-3")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
position: "top",
styles: { fontSize: "13px" }
}
];
const signedByStep = {
selector: '[data-tut="reactourSecond"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-4")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
position: "top",
styles: { fontSize: "13px" }
};
//checking if signed by user component exist then add signed by tour step
const signedBy =
signedSigners.length > 0
? [...tourConfig.slice(0, 0), signedByStep, ...tourConfig.slice(0)]
: tourConfig;
//checking if default signature component exist then add defaultSign tour step
const defaultSignStep = {
selector: '[data-tut="reactourThird"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-5")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
position: "top",
styles: { fontSize: "13px" }
};
//checking if AllowModifications is true then add allow widgets panel tour step
const allModifyWidgets = {
selector: '[data-tut="reactourFourth"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.allowModify-widgets")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
position: "top",
styles: { fontSize: "13px" }
};
//handle signed by panel index if signed by exist then 2 else 1 to add tour step
const index = signedSigners.length > 0 ? 3 : 2;
let defaultSignTour = defaultSignImg
? [...signedBy.slice(0, index), defaultSignStep, ...signedBy.slice(index)]
: signedBy;
//handle index when AllowModifications is true and defaultSignImg & signedSigners both exist or only
//signedSigners or defaultSignImg exist then adjust index
const modifyIndex =
signedSigners > 0 && defaultSignImg
? 4
: signedSigners > 0 || defaultSignImg
? 3
: 2;
if (pdfDetails[0]?.AllowModifications) {
defaultSignTour = [
...defaultSignTour.slice(0, modifyIndex),
allModifyWidgets,
...defaultSignTour.slice(modifyIndex)
];
}
let mobileTour;
if (isMobile) {
mobileTour = tourConfig.filter((_, ind) => ind !== 1);
}
return (
<Tour
onRequestClose={closeRequestSignTour}
steps={isMobile ? mobileTour : defaultSignTour}
isOpen={true}
/>
);
};
const handleCloseTour = () => {
closeTour();
setIsReqSignTourDisabled(true);
};
const clickOnZoomIn = () => {
onClickZoomIn(scale, setScale, scrollRef);
};
const clickOnZoomOut = () => {
onClickZoomOut(scale, setScale, scrollRef);
};
const handleDownloadBtn = async () => {
const url = pdfDetails?.[0]?.SignedUrl || pdfDetails?.[0]?.URL;
const isCompleted = pdfDetails?.[0]?.IsCompleted || false;
const name =
pdfDetails?.[0]?.Name?.length > 100
? pdfDetails?.[0]?.Name?.slice(0, 100)
: pdfDetails?.[0]?.Name || "Document";
const formatId = pdfDetails?.[0]?.ExtUserPtr?.DownloadFilenameFormat;
const docName = utils?.buildDownloadFilename(formatId, {
docName: name,
isSigned: isCompleted
});
await fetchUrl(url, docName);
};
const handleDeclineMssg = () => {
const user = pdfDetails[0]?.DeclineBy?.email || isDecline?.email;
const reason = pdfDetails[0]?.DeclineReason || isDecline?.reason;
return (
<div>
{t("decline-alert-3")}
<div className="mt-2">
{" "}
<span className="font-medium">{t("decline-by")}</span> : {user}
</div>
<div className="mt-2">
{" "}
<span className="font-medium">{t("reason")}</span> : {reason}{" "}
</div>
</div>
);
};
const handleExpiry = async (expiryDate) => {
setIsUiLoading(true);
const doc = pdfDetails?.[0];
const oldExpiryDate = new Date(doc?.ExpiryDate?.iso);
const newExpiryDate = new Date(expiryDate);
if (newExpiryDate > oldExpiryDate) {
const updateExpiryDate = new Date(expiryDate).toISOString();
const expiryIsoFormat = { iso: updateExpiryDate, __type: "Date" };
try {
const serverUrl = serverUrl_fn();
const url = serverUrl + `/classes/contracts_Document/`;
const body = { ExpiryDate: expiryIsoFormat };
const res = await axios.put(url + doc.objectId, body, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
});
if (res.data && res.data.updatedAt) {
setIsExpired(false);
let doc = pdfDetails?.[0];
doc.ExpiryDate = expiryIsoFormat;
setPdfDetails([doc]);
}
} catch (err) {
console.log("err", err);
} finally {
setIsUiLoading(false);
}
} else {
setIsUiLoading(false);
alert(t("expiry-date-error"));
}
};
// `handleRedirectCancel` is used to cancel redirecting to redirectUrl
const handleRedirectCancel = () => {
setIsredirectCanceled(true);
};
//function for capture position on hover or touch widgets
const handleDivClick = (e) => {
const isTouchEvent = e.type.startsWith("touch");
const divRect = e.currentTarget.getBoundingClientRect();
let mouseX, mouseY;
if (isTouchEvent) {
const touch = e.touches[0]; // Get the first touch point
mouseX = touch.clientX - divRect.left;
mouseY = touch.clientY - divRect.top;
setSignBtnPosition([{ xPos: mouseX, yPos: mouseY }]);
} else {
mouseX = e.clientX - divRect.left;
mouseY = e.clientY - divRect.top;
setXYSignature({ xPos: mouseX, yPos: mouseY });
}
};
//function for capture position of x and y on hover signature button last position
const handleMouseLeave = () => {
setSignBtnPosition([xySignature]);
};
//function for setting position after drop signature button over pdf
const addPositionOfSignature = (item, monitor) => {
getSignerPos(item, monitor);
};
const getSignerPos = (item, monitor) => {
if (uniqueId) {
const posZIndex = zIndex + 1;
setZIndex(posZIndex);
const key = randomId();
const containerScale = getContainerScale(
pdfOriginalWH,
pageNumber,
containerWH
);
let filterSignerPos,
currentPagePosition,
dropData = [],
dropObj,
placeHolder;
filterSignerPos = signerPos?.find((data) => data.Id === uniqueId);
const dragTypeValue = item?.text ? item.text : monitor.type;
const widgetWidth =
defaultWidthHeight(dragTypeValue).width * containerScale;
const widgetHeight =
defaultWidthHeight(dragTypeValue).height * containerScale;
const extUser = localStorage.getItem("Extand_Class");
let parseUser =
(extUser && JSON.parse(extUser)[0]) ||
JSON.parse(localStorage.getItem("signer"));
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// Use the current page container (id="container") so that the
// height reflects one page, not the entire multi-page document.
const containerEl =
document.getElementById("container") || divRef.current;
const divWidth = containerEl.getBoundingClientRect().width;
const divHeight = containerEl.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: xPosition_Final,
yPosition:
(divHeight / 2 - widgetHeight / 2) / (containerScale * scale),
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
key: key,
scale: containerScale,
zIndex: posZIndex,
type: dragTypeValue,
options: addWidgetOptions(
dragTypeValue,
owner,
filterSignerPos?.placeHolder,
null,
widgetValue
),
Width: widgetWidth / (containerScale * scale),
Height: widgetHeight / (containerScale * scale)
};
dropData.push(dropObj);
placeHolder = { pageNumber: pageNumber, pos: dropData };
} else {
const offset = monitor.getClientOffset();
//This method returns the offset of the current pointer (mouse) position relative to the client viewport.
const containerRect = document
.getElementById("container")
.getBoundingClientRect();
//`containerRect.left`, The distance from the left of the viewport to the left side of the element.
//`containerRect.top` The distance from the top of the viewport to the top of the element.
const x = offset.x - containerRect.left;
const y = offset.y - containerRect.top;
let getXPosition = signBtnPosition[0] ? x - signBtnPosition[0].xPos : x;
let getYPosition = signBtnPosition[0] ? y - signBtnPosition[0].yPos : y;
// to avoid negative position values (half portion of widget should not be out of pdf container)
if (getXPosition < 0) {
getXPosition = 0;
}
if (getYPosition < 0) {
getYPosition = 0;
}
dropObj = {
xPosition: getXPosition / (containerScale * scale),
yPosition: getYPosition / (containerScale * scale),
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
key: key,
scale: containerScale,
zIndex: posZIndex,
type: dragTypeValue,
options: addWidgetOptions(
dragTypeValue,
owner,
filterSignerPos?.placeHolder,
null,
widgetValue
),
Width: widgetWidth / (containerScale * scale),
Height: widgetHeight / (containerScale * scale)
};
dropData.push(dropObj);
placeHolder = { pageNumber: pageNumber, pos: dropData };
}
const getPlaceHolder = filterSignerPos?.placeHolder;
if (getPlaceHolder) {
//checking exist placeholder on same page
currentPagePosition = getPlaceHolder?.find(
(data) => data.pageNumber === pageNumber
);
}
//checking current page has already some placeholders then update that placeholder and add upcoming placehoder position
if (getPlaceHolder && currentPagePosition) {
const updatePlace = getPlaceHolder.filter(
(data) => data.pageNumber !== pageNumber
);
const getPos = currentPagePosition?.pos;
const newSignPos = getPos.concat(dropData);
newSignPos.sort((a, b) => a.yPosition - b.yPosition);
let xyPos = { pageNumber: pageNumber, pos: newSignPos };
updatePlace.push(xyPos);
const updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId ? { ...x, placeHolder: updatePlace } : x
);
setSignerPos(updatesignerPos);
} else {
//else condition to add placeholder widgets on multiple page first time
const updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId && x?.placeHolder
? {
...x,
placeHolder: [...x.placeHolder, placeHolder].sort(
(a, b) => a.pageNumber - b.pageNumber
)
}
: x.Id === uniqueId
? {
...x,
placeHolder: [placeHolder]
}
: x
);
setSignerPos(updatesignerPos);
}
if (
[textWidget, "name", "company", "job title", "email"].includes(
dragTypeValue
)
) {
setFontSize(12);
setFontColor("black");
}
setCurrWidgetsDetails({ ...dropObj, pageNumber: pageNumber });
}
};
//function for delete signature block
const handleDeleteWidget = (key, Id, widgetPageNumber) => {
const effectivePageNumber = widgetPageNumber || pageNumber;
const updateData = [];
const filterSignerPos = signerPos?.filter((data) => data.Id === Id);
if (filterSignerPos.length > 0) {
const getPlaceHolder = filterSignerPos[0].placeHolder;
const getPageNumer = getPlaceHolder.filter(
(data) => data.pageNumber === effectivePageNumber
);
if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos.filter(
(data) => data.key !== key
);
//condition to check on same has multiple widgets so do not delete all widgets
if (getXYdata.length > 0) {
updateData.push(getXYdata);
const newUpdatePos = getPlaceHolder.map((obj) => {
if (obj.pageNumber === effectivePageNumber) {
return { ...obj, pos: updateData[0] };
}
return obj;
});
const newUpdateSigner = signerPos.map((obj) => {
if (obj.Id === Id) {
return { ...obj, placeHolder: newUpdatePos };
}
return obj;
});
setSignerPos(newUpdateSigner);
} else {
const getRemainPage = filterSignerPos[0]?.placeHolder?.filter(
(data) => data.pageNumber !== effectivePageNumber
);
//condition to check placeholder length is greater than 1 do not need to remove whole placeholder
//array only resove particular widgets
if (getRemainPage && getRemainPage.length > 0) {
const newUpdatePos = filterSignerPos.map((obj) => {
if (obj.Id === Id) {
return { ...obj, placeHolder: getRemainPage };
}
return obj;
});
let signerupdate = [];
signerupdate = signerPos?.filter((data) => data.Id !== Id);
signerupdate.push(newUpdatePos[0]);
setSignerPos(signerupdate);
} else {
const updatedData = signerPos?.map((item) => {
if (item.Id === Id) {
// Create a copy of the item object and delete the placeHolder field
const updatedItem = { ...item };
delete updatedItem.placeHolder;
return updatedItem;
}
return item;
});
setSignerPos(updatedData);
}
}
}
}
};
//function to get first widget and page number to assign currect signer and tour message
const showFirstWidget = () => {
if (!isReqSignTourDisabled) {
const getCurrentUserPlaceholder = signerPos?.find(
(x) => x.Id === uniqueId
);
const placeholder = getCurrentUserPlaceholder.placeHolder;
//checking minimum pagnumber of existing widgets and throw tour message on that page
const getPosition = placeholder.reduce(
(min, obj) => (obj.pageNumber < min.pageNumber ? obj : min),
placeholder[0]
);
const getWidgetId = getPosition.pos[0].key;
setUnSignedWidgetId(getWidgetId);
setPageNumber(getPosition.pageNumber);
let pagenumber = [];
for (let item of getCurrentUserPlaceholder.placeHolder) {
pagenumber.push(item.pageNumber);
}
// Sort the pagenumber in ascending order
const sortedPagenumber = [...pagenumber].sort((a, b) => a - b);
setShowSignPagenumber(sortedPagenumber);
}
};
const RedirectNotice = () => {
return !isredirectCanceled && redirectUrl ? (
<div
className="flex flex-row gap-1 items-center justify-center mb-3"
aria-live="polite"
>
<p>{t("redirecting-you-in", { redirectTimeLeft })}</p>
<button
onClick={handleRedirectCancel}
className="underline cursor-pointer op-text-primary focus:outline-none ml-2"
>
{t("cancel")}
</button>
</div>
) : (
<></>
);
};
const handleTourHelp = () => {
setIsReqSignTourDisabled(false);
};
const getSignerEmail = () => {
const isSignerAssigned = unsignedSigners[0]?.objectId;
let email = formData[0]?.email;
if (isSignerAssigned) {
email = unsignedSigners[0]?.Email;
}
return email;
};
const handleDownload = async () => {
setIsCompleted((prev) => ({ ...prev, isModal: false }));
setIsDownloadModal(true);
};
return (
<>
{isLoading.isLoad ? (
<LoaderWithMsg isLoading={isLoading} />
) : handleError ? (
<HandleError handleError={handleError} />
) : (
<div>
{!isAgree &&
currentSigner &&
!isExpired &&
!alreadySign &&
!isCompleted?.isCertificate &&
!isDecline?.isDeclined &&
(
<AgreementSign
setIsAgree={setIsAgree}
showFirstWidget={showFirstWidget}
/>
)}
{isCelebration && (
<div className="relative z-[1000]">
<Confetti
width={window.innerWidth}
height={window.innerHeight}
recycle={false} // Prevents confetti from repeating
gravity={0.1} // Adjust the gravity to control the speed
/>
</div>
)}
<div
style={{
pointerEvents:
isExpired ||
(isDecline.isDeclined && isDecline.currnt === "another")
? "none"
: "auto"
}}
className={`${
isGuestSignFlow
? "border-[0.5px] border-gray-300"
: "op-card"
} relative overflow-hidden flex flex-col md:flex-row justify-between bg-base-300`}
>
{isUiLoading && (
<div className="absolute h-full w-full flex flex-col justify-center items-center z-[999] bg-[#e6f2f2]/80">
<Loader />
<span className="text-[13px]">{t("loading-mssg")}</span>
</div>
)}
{!isReqSignTourDisabled &&
isAgree &&
signerObjectId &&
!alreadySign &&
requestSignTourFunction()}
<Tour
key={`tour-min-required-${minRequiredCount || 0}`}
showNumber={false}
showNavigation={false}
showNavigationNumber={false}
onRequestClose={closeTour}
steps={tourConfig}
isOpen={widgetsTour}
/>
{/* this modal is used to show decline alert */}
<PdfDeclineModal
show={isDecline.isDeclined}
headMsg={t("document-declined")}
bodyMssg={
isDecline.currnt === "Sure"
? t("decline-alert-1")
: isDecline.currnt === "YouDeclined"
? t("decline-alert-2")
: isDecline.currnt === "another" && handleDeclineMssg()
}
footerMessage={isDecline.currnt === "Sure"}
declineDoc={declineDoc}
setIsDecline={setIsDecline}
/>
{/* this modal is used for show expired alert */}
<PdfDeclineModal
show={isExpired}
doc={pdfDetails?.[0]}
headMsg={t("expired-doc-title")}
bodyMssg={t("expired-on-mssg", { expiredDate })}
isDownloadBtn={true}
handleDownloadBtn={handleDownloadBtn}
handleExpiry={handleExpiry}
/>
<ModalUi
isOpen={defaultSignAlert.isShow}
title={t("auto-sign-all")}
handleClose={() =>
setDefaultSignAlert({ isShow: false, alertMessage: "" })
}
>
<div className="h-full p-[20px]">
<p>{defaultSignAlert.alertMessage}</p>
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
{defaultSignImg ? (
<>
<button
onClick={() => addDefaultSignature()}
type="button"
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={() =>
setDefaultSignAlert({
isShow: false,
alertMessage: ""
})
}
type="button"
className="op-btn op-btn-secondary ml-1"
>
{t("close")}
</button>
</>
) : (
<button
onClick={() =>
setIsAlert({ isShow: false, alertMessage: "" })
}
type="button"
className="op-btn op-btn-primary"
>
{t("ok")}
</button>
)}
</div>
</ModalUi>
{/* this component used to render all pdf pages in left side */}
<RenderAllPdfPage
signerPos={signerPos}
id={uniqueId}
allPages={allPages}
setAllPages={setAllPages}
setPageNumber={setPageNumber}
pageNumber={pageNumber}
containerWH={containerWH}
pdfBase64Url={pdfBase64Url}
signedUrl={pdfDetails?.[0]?.SignedUrl || ""}
/>
{/* pdf render view */}
<div className="w-full md:w-[57%] flex mr-4">
<PdfTools
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
isDisableEditTools={true}
allPages={allPages}
setAllPages={setAllPages}
setPageNumber={setPageNumber}
setIsTour={() => setIsReqSignTourDisabled(true)}
/>
<PlaceholderCopy
isPageCopy={isPageCopy}
setIsPageCopy={setIsPageCopy}
xyPosition={signerPos}
setXyPosition={setSignerPos}
allPages={allPages}
pageNumber={currWidgetsDetails?.pageNumber || pageNumber}
signKey={currWidgetsDetails?.key}
Id={uniqueId}
widgetType={currWidgetsDetails?.type}
setUniqueId={setUniqueId}
pdfOriginalWH={pdfOriginalWH}
/>
<div className=" w-full md:w-[95%] ">
{/* this modal is used show this document is already sign */}
<ModalUi
isOpen={isCompleted.isModal}
title={t("document-signed")}
handleClose={() =>
setIsCompleted((prev) => ({ ...prev, isModal: false }))
}
reduceWidth={
!isCompleted?.message &&
"md:min-w-[440px] md:max-w-[400px]"
}
>
<div className="h-full p-[20px] text-base-content">
{isCompleted?.message ? (
<>
<p className="mb-2">{isCompleted?.message}</p>
<RedirectNotice />
</>
) : (
<div className="px-[15px]">
<span>{t("document-signed-alert-4")}</span>
</div>
)}
{!isCompleted?.message && (
<div className="flex flex-col mt-3 gap-1 px-[10px] justify-center items-center">
<RedirectNotice />
<div className={`${!redirectUrl ? "m-2" : ""}`}>
<button
onClick={(e) =>
handleToPrint(e, setIsDownloading, pdfDetails)
}
type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-neutral"
>
<i
className="fa-light fa-print"
aria-hidden="true"
></i>
<span className="hidden lg:block">
{t("print")}
</span>
</button>
<button
type="button"
onClick={() =>
handleDownloadCertificate(
pdfDetails,
setIsDownloading
)
}
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-secondary"
>
<i
className="fa-light fa-award mx-[3px] lg:mx-0"
aria-hidden="true"
></i>
<span className="hidden lg:block">
{t("certificate")}
</span>
</button>
<button
type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-primary"
onClick={handleDownload}
>
<i
className="fa-light fa-download"
aria-hidden="true"
></i>
<span className="hidden lg:block">
{t("download")}
</span>
</button>
</div>
</div>
)}
</div>
</ModalUi>
{isDownloading === "pdf" && (
<div className="fixed z-[1000] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<ModalUi
isOpen={
isDownloading === "certificate" ||
isDownloading === "certificate_err"
}
title={
isDownloading === "certificate" ||
isDownloading === "certificate_err"
? t("generating-certificate")
: t("pdf-download")
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
{isDownloading === "certificate" ? (
<p>{t("generate-certificate-alert")}</p>
) : (
<p>{t("generate-certificate-err")}</p>
)}
</div>
</ModalUi>
{/* pdf header which contain funish back button */}
<Header
isPdfRequestFiles={
true
}
pageNumber={pageNumber}
allPages={allPages}
changePage={changePage}
pdfDetails={pdfDetails}
signerPos={signerPos}
isSigned={isSigned}
isCompleted={isCompleted.isCertificate}
embedWidgetsData={
handleSignPdf
}
isShowHeader={true}
setIsDecline={setIsDecline}
decline={true}
currentSigner={currentSigner}
alreadySign={alreadySign}
containerWH={containerWH}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
isDisablePdfEditTools={true}
setIsDownloadModal={setIsDownloadModal}
pdfBase64={pdfBase64Url}
isGuestSignFlow={isGuestSignFlow}
/>
<div
ref={divRef}
data-tut="pdfArea"
className="h-fit"
onClick={() => setIsReqSignTourDisabled(true)}
>
{containerWH?.width && (
<RenderPdf
setIsPageCopy={setIsPageCopy}
pageNumber={pageNumber}
setPageNumber={setPageNumber}
pdfOriginalWH={pdfOriginalWH}
pdfNewWidth={pdfNewWidth}
pdfDetails={pdfDetails}
signerPos={signerPos}
successEmail={false}
pdfUrl={pdfUrl}
numPages={numPages}
pageDetails={pageDetails}
pdfRequest={true}
signerObjectId={signerObjectId}
signedSigners={signedSigners}
setPdfLoad={setPdfLoad}
pdfLoad={pdfLoad}
setSignerPos={setSignerPos}
containerWH={containerWH}
unSignedWidgetId={unSignedWidgetId}
setCurrWidgetsDetails={setCurrWidgetsDetails}
divRef={divRef}
setIsResize={setIsResize}
isResize={isResize}
setScale={setScale}
scale={scale}
uniqueId={uniqueId}
pdfBase64Url={pdfBase64Url}
isAgree={isAgree}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
isDragging={isDragging}
isAlllowModify={pdfDetails[0]?.AllowModifications}
setUniqueId={setUniqueId}
handleDeleteWidget={handleDeleteWidget}
handleTextSettingModal={handleTextSettingModal}
assignedWidgetId={assignedWidgetId}
setIsReqSignTourDisabled={handleCloseTour}
currWidgetsDetails={currWidgetsDetails}
isShowModal={isShowModal}
signBtnPosition={signBtnPosition}
addPositionOfSignature={addPositionOfSignature}
closeWidgetTour={closeTour}
/>
)}
</div>
</div>
</div>
<div className="w-full md:w-[23%] bg-base-100 overflow-y-auto hide-scrollbar ">
<div className={`max-h-screen`}>
<div className="w-full hidden md:inline-block">
{signedSigners.length > 0 && (
<>
<div
data-tut="reactourSecond"
className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300"
>
<span>{t("signed-by")}</span>
</div>
<div className="mt-[2px]">
{signedSigners.map((obj, ind) => {
return (
<div key={ind}>
<SignerListComponent
ind={ind}
obj={obj}
signerPos={signerPos}
/>
</div>
);
})}
</div>
</>
)}
{unsignedSigners.length > 0 &&
(() => {
return (
<>
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
<span>
{t("yet-to-sign")}
<sup onClick={handleTourHelp}>
<i className="ml-1 cursor-pointer fa-light fa-question rounded-full border-[1px] border-base-content text-[11px] py-[1px] px-[3px]"></i>
</sup>
</span>
</div>
<div
data-tut="reactourFirst"
className="mt-[5px]"
>
{unsignedSigners.map((obj, ind) => (
<div key={ind}>
<SignerListComponent
ind={ind}
obj={obj}
signerPos={signerPos}
/>
</div>
))}
</div>
</>
);
})()}
{(defaultSignImg || myInitial) &&
!alreadySign &&
currentSigner && (
<DefaultSignature
userObjectId={signerObjectId}
setIsLoading={setIsLoading}
xyPosition={signerPos}
uniqueId={uniqueId}
setDefaultSignAlert={setDefaultSignAlert}
isDefault={
signatureType?.find((x) => x.name === "default")
?.enabled || false
}
isAgree={isAgree}
/>
)}
</div>
{pdfDetails[0]?.AllowModifications &&
currentSigner &&
!alreadySign &&
(
<div data-tut="reactourFourth">
<WidgetComponent
pdfUrl={pdfUrl}
handleDivClick={handleDivClick}
handleMouseLeave={handleMouseLeave}
xyPosition={signerPos}
addPositionOfSignature={addPositionOfSignature}
isAlllowModify={true}
/>
</div>
)}
</div>
</div>
</div>
</div>
)}
{currentSigner && isShowModal[currWidgetsDetails?.key] && (
<WidgetsValueModal
key={currWidgetsDetails?.key}
xyPosition={signerPos}
pageNumber={pageNumber}
setXyPosition={setSignerPos}
uniqueId={uniqueId}
setPageNumber={setPageNumber}
finishDocument={handleSignPdf}
setCurrWidgetsDetails={setCurrWidgetsDetails}
currWidgetsDetails={currWidgetsDetails}
index={currWidgetsDetails?.pageNumber || pageNumber}
setUniqueId={setUniqueId}
signatureTypes={signatureType}
allowCellResize={false}
penColors={pdfDetails?.[0]?.PenColors}
/>
)}
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal}
pdfDetails={pdfDetails}
isDocId={true}
pdfBase64={pdfBase64Url}
/>
<ModalUi
isOpen={isAlert.isShow}
title={isAlert?.title || t("alert-message")}
handleClose={() => setIsAlert({ isShow: false, alertMessage: "" })}
>
<div className="h-full p-[20px]">
<p>{isAlert.alertMessage}</p>
<button
onClick={() => setIsAlert({ isShow: false, alertMessage: "" })}
type="button"
className="op-btn op-btn-primary mt-3 px-4"
>
{t("close")}
</button>
</div>
</ModalUi>
<TextFontSetting
isTextSetting={isTextSetting}
setIsTextSetting={setIsTextSetting}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
handleSaveFontSize={handleSaveFontSize}
currWidgetsDetails={currWidgetsDetails}
/>
</>
);
}
export default PdfRequestFiles;