From 7c8a5e02f2d0b98520391feda2a09e0580fc4e71 Mon Sep 17 00:00:00 2001 From: prafull-opensignlabs Date: Mon, 22 Jan 2024 09:52:44 +0530 Subject: [PATCH] add createdocument with template Id --- apps/OpenSign/public/version.txt | 1 + .../cloud/customRoute/v1/apiV1.js | 5 +- .../v1/routes/CreateDocumentWithTemplate.js | 98 ++ microfrontends/SignDocuments/src/Routes.js | 3 + .../src/premitives/GuestLogin.js | 294 +++++ .../SignDocuments/src/premitives/GuestSign.js | 1002 +++++++++++++++++ 6 files changed, 1401 insertions(+), 2 deletions(-) create mode 100644 apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js create mode 100644 microfrontends/SignDocuments/src/premitives/GuestLogin.js create mode 100644 microfrontends/SignDocuments/src/premitives/GuestSign.js diff --git a/apps/OpenSign/public/version.txt b/apps/OpenSign/public/version.txt index e69de29bb..3c61df3f2 100644 --- a/apps/OpenSign/public/version.txt +++ b/apps/OpenSign/public/version.txt @@ -0,0 +1 @@ +v1.1.1-beta diff --git a/apps/OpenSignServer/cloud/customRoute/v1/apiV1.js b/apps/OpenSignServer/cloud/customRoute/v1/apiV1.js index 2d6d391c0..b9585b119 100644 --- a/apps/OpenSignServer/cloud/customRoute/v1/apiV1.js +++ b/apps/OpenSignServer/cloud/customRoute/v1/apiV1.js @@ -20,7 +20,7 @@ import multer from 'multer'; // import fs from 'node:fs'; import updateDocument from './routes/updateDocument.js'; import deleteDocument from './routes/deleteDocument.js'; -// import createDocumentWithTemplate from './routes/CreateDocumentWithTemplate.js'; +import createDocumentWithTemplate from './routes/CreateDocumentWithTemplate.js'; dotenv.config(); const storage = multer.memoryStorage(); @@ -49,7 +49,8 @@ app.get('/contactlist', getContactList); app.post('/createdocument', upload.array('file', 1), createDocument); // create Document with templateId -// app.post('/createdocument/:template_id', createDocumentWithTemplate); +app.post('/createdocument/:template_id', createDocumentWithTemplate); + // get Document on the basis of id app.get('/document/:document_id', getDocument); diff --git a/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js b/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js new file mode 100644 index 000000000..3ae07bac3 --- /dev/null +++ b/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js @@ -0,0 +1,98 @@ +const randomId = () => Math.floor(1000 + Math.random() * 9000); +export default async function createDocumentWithTemplate(request, response) { + const signers = request.body.Signers; + const folderId = request.body.FolderId; + const templateId = request.params.template_id; + const url = process.env.SERVER_URL; + try { + const reqToken = request.headers['x-api-token']; + if (!reqToken) { + return response.status(400).json({ error: 'Please Provide API Token' }); + } + const tokenQuery = new Parse.Query('appToken'); + tokenQuery.equalTo('token', reqToken); + const token = await tokenQuery.first({ useMasterKey: true }); + if (token !== undefined) { + // Valid Token then proceed request + const userPtr = token.get('userId'); + + const templateQyuery = new Parse.Query('contracts_Template'); + const templateRes = await templateQyuery.get(templateId, { useMasterKey: true }); + if (templateRes) { + const template = JSON.parse(JSON.stringify(templateRes)); + if (template?.Placeholders?.length > 0) { + let isValid = template?.Placeholders?.length <= signers?.length; + let updateSigners = template?.Placeholders?.every(y => + signers?.some(x => x.Role === y.Role) + ); + if (isValid && updateSigners) { + const folderPtr = { + __type: 'Pointer', + className: 'contracts_Document', + objectId: folderId, + }; + const template = JSON.parse(JSON.stringify(templateRes)); + const object = new Parse.Object('contracts_Document'); + object.set('Name', template.Name); + if (template?.Note) { + object.set('Note', template.Note); + } + if (template?.Description) { + object.set('Description', template.Description); + } + if (template?.Signers) { + object.set('Signers', template?.Signers); + } + object.set('URL', template.URL); + object.set('CreatedBy', template.CreatedBy); + object.set('ExtUserPtr', template.ExtUserPtr); + if (signers) { + const placeholders = template?.Placeholders?.map(placeholder => { + let matchingSigner = signers.find(y => y.Role === placeholder.Role); + if (matchingSigner) { + return { + ...placeholder, + email: matchingSigner.Email, + signerObjId: '', + signerPtr: {}, + }; + } else { + return { + ...placeholder, + }; + } + }); + console.log('placeholders ', placeholders); + object.set('Placeholders', placeholders); + } + if (folderId) { + object.set('Folder', folderPtr); + } + const newACL = new Parse.ACL(); + newACL.setPublicReadAccess(false); + newACL.setPublicWriteAccess(false); + newACL.setReadAccess(userPtr.id, true); + newACL.setWriteAccess(userPtr.id, true); + object.setACL(newACL); + const res = await object.save(null, { useMasterKey: true }); + return response.json({ objectId: res.id, url: url }); + } else { + return response.status(400).json({ error: 'Please provide signers properly!' }); + } + } else { + return response.status(400).json({ error: 'Please setup template properly!' }); + } + } else { + return response.status(404).json({ error: 'Invalid template id!' }); + } + } else { + return response.status(405).json({ error: 'Invalid API Token!' }); + } + } catch (err) { + console.log('err ', err); + if (err.code === 101) { + return response.status(404).json({ error: 'Invalid template id!' }); + } + return response.status(400).json({ error: 'Something went wrong!' }); + } +} diff --git a/microfrontends/SignDocuments/src/Routes.js b/microfrontends/SignDocuments/src/Routes.js index 1b1cba4d5..58039a599 100644 --- a/microfrontends/SignDocuments/src/Routes.js +++ b/microfrontends/SignDocuments/src/Routes.js @@ -10,6 +10,7 @@ import LegaDrive from "./Component/LegaDrive/LegaDrive"; import PageNotFound from "./Component/PageNotFound"; import TemplatePlaceHolder from "./Component/TemplatePlaceholder"; import Parse from "parse"; +import GuestLogin from "./premitives/GuestLogin"; Parse.serverURL = localStorage.getItem("baseUrl"); Parse.initialize(localStorage.getItem("parseAppId")); // `AppRoutes` is used to define route path of app and @@ -50,6 +51,8 @@ function AppRoutes() { } /> {/* Page Not Found */} } /> + } /> + } /> diff --git a/microfrontends/SignDocuments/src/premitives/GuestLogin.js b/microfrontends/SignDocuments/src/premitives/GuestLogin.js new file mode 100644 index 000000000..4df8a9960 --- /dev/null +++ b/microfrontends/SignDocuments/src/premitives/GuestLogin.js @@ -0,0 +1,294 @@ +import React, { useState } from "react"; +import ModalUi from './ModalUi' +import "../css/LoginPage.css"; +import loader from "../assests/loader2.gif"; +import axios from "axios"; +import { useParams } from "react-router-dom"; +import { themeColor } from "../utils/ThemeColor/backColor"; +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import GuestSign from "./GuestSign"; + +function GuestLogin({children, userdetails}) { + const { id, userMail, contactBookId, serverUrl } = useParams(); + let navigate = useNavigate(); + const [email, setEmail] = useState(userMail || "prafull.navkar@nxglabs.com"); + const [OTP, setOTP] = useState(""); + const [EnterOTP, setEnterOtp] = useState(false); + const [loading, setLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + handleServerUrl(); + }, []); + + //function generate serverUrl and parseAppId from url and save it in local storage + const handleServerUrl = () => { + //split url in array from '&' + localStorage.clear(); + // const checkSplit = serverUrl?.split("&"); + // const server = checkSplit?.[0]; + // const parseId = checkSplit?.[1]; + // const appName = checkSplit?.[2]; + + // const newServer = server.replaceAll("%2F", "/"); + // localStorage.setItem("baseUrl", newServer); + // localStorage.setItem("parseAppId", parseId); + // localStorage.setItem("_appName", appName); + + localStorage.setItem("baseUrl", "https://staging-app.opensignlabs.com/api/app/"); + localStorage.setItem("parseAppId", "opensignstgn"); + localStorage.setItem("_appName", "contracts"); + setIsLoading(false); + }; + + const handleChange = (event) => { + const { value } = event.target; + setOTP(value); + }; + + //send email OTP function + const SendOtp = async (e) => { + const serverUrl = + localStorage.getItem("baseUrl") && localStorage.getItem("baseUrl"); + const parseId = + localStorage.getItem("parseAppId") && localStorage.getItem("parseAppId"); + if (serverUrl && localStorage) { + setLoading(true); + e.preventDefault(); + setEmail(email); + + try { + let url = `${serverUrl}functions/SendOTPMailV1/`; + const headers = { + "Content-Type": "application/json", + "X-Parse-Application-Id": parseId + }; + let body = { + email: email.toString() + }; + let Otp = await axios.post(url, body, { headers: headers }); + + if (Otp) { + setLoading(false); + setEnterOtp(true); + } + } catch (error) { + alert("something went wrong!"); + } + } else { + alert("something went wrong!"); + } + }; + + //verify OTP send on via email + const VerifyOTP = async (e) => { + e.preventDefault(); + const serverUrl = + localStorage.getItem("baseUrl") && localStorage.getItem("baseUrl"); + const parseId = + localStorage.getItem("parseAppId") && localStorage.getItem("parseAppId"); + if (OTP) { + setLoading(true); + try { + let url = `${serverUrl}functions/AuthLoginAsMail/`; + const headers = { + "Content-Type": "application/json", + "X-Parse-Application-Id": parseId + }; + let body = { + email: email, + otp: OTP + }; + let user = await axios.post(url, body, { headers: headers }); + if (user.data.result === "Invalid Otp") { + alert("Invalid Otp"); + setLoading(false); + } else if (user.data.result === "user not found!") { + alert("User not found!"); + setLoading(false); + } else { + let _user = user.data.result; + const parseId = localStorage.getItem("parseAppId"); + localStorage.setItem("UserInformation", JSON.stringify(_user)); + localStorage.setItem( + `Parse/${parseId}/currentUser`, + JSON.stringify(_user) + ); + localStorage.setItem("username", _user.name); + localStorage.setItem("accesstoken", _user.sessionToken); + //save isGuestSigner true in local to handle login flow header in mobile view + localStorage.setItem("isGuestSigner", true); + setLoading(false); + // uKymQWaYL6 + navigate( + `/loadmf/signmicroapp/recipientSignPdf/${id}/${contactBookId}` + ); + } + } catch (error) {} + } else { + alert("Please Enter OTP!"); + } + }; + + return ( +
+ +<> + {isLoading ? ( +
+ no img + + {isLoading.message} + +
+ ) : ( +
+
+
+ sign img +
+
+ + {!EnterOTP ? ( +
+
+ Welcome Back ! +
+ + Verification code is sent to your email + +
+
+ +
+
+
+
+ {loading ? ( + + ) : ( + + )} +
+
+
+ ) : ( +
+
+ Welcome Back ! +
+ You will get a OTP via Email +
+
+ + + +
+
+
+
+ {loading ? ( + + ) : ( + + )} +
+
+
+ )} +
+ )} + +
+
+
+ ); +} + +export default GuestLogin; diff --git a/microfrontends/SignDocuments/src/premitives/GuestSign.js b/microfrontends/SignDocuments/src/premitives/GuestSign.js new file mode 100644 index 000000000..99bc0e11f --- /dev/null +++ b/microfrontends/SignDocuments/src/premitives/GuestSign.js @@ -0,0 +1,1002 @@ +import React, { useState, useRef, useEffect } from "react"; +import { themeColor } from "../utils/ThemeColor/backColor"; +import { PDFDocument } from "pdf-lib"; +import "../css/signature.css"; +import axios from "axios"; +import loader from "../assests/loader2.gif"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { useParams } from "react-router-dom"; +import SignPad from "../Component/component/signPad"; +import RenderAllPdfPage from "../Component/component/renderAllPdfPage"; +import { + contractDocument, + multiSignEmbed, + embedDocId, + pdfNewWidthFun, + signPdfFun, + onImageSelect, + onSaveSign, + onSaveImage, + addDefaultSignatureImg +} from "../utils/Utils"; +import Loader from "../Component/component/loader"; +import HandleError from "../Component/component/HandleError"; +import Nodata from "../Component/component/Nodata"; +import Header from "../Component/component/header"; +import RenderPdf from "../Component/component/renderPdf"; +import CustomModal from "../Component/component/CustomModal"; +import Title from "../Component/component/Title"; +import DefaultSignature from "../Component/component/defaultSignature"; +import ModalUi from "./ModalUi"; + +function GuestSign() { + const { docId } = useParams(); + const [pdfDetails, setPdfDetails] = useState([]); + const [signedSigners, setSignedSigners] = useState([]); + const [unsignedSigners, setUnSignedSigners] = useState([]); + const [isSignPad, setIsSignPad] = useState(false); + const [pdfUrl, setPdfUrl] = useState(); + const [allPages, setAllPages] = useState(null); + const numPages = 1; + const [pageNumber, setPageNumber] = useState(1); + const [image, setImage] = useState(null); + const [isImageSelect, setIsImageSelect] = useState(false); + const [signature, setSignature] = useState(); + const [isStamp, setIsStamp] = useState(false); + const [signKey, setSignKey] = useState(); + const [imgWH, setImgWH] = useState({}); + const imageRef = useRef(null); + const [handleError, setHandleError] = useState(); + const [isLoading, setIsLoading] = useState({ + isLoad: true, + message: "This might take some time" + }); + const [defaultSignImg, setDefaultSignImg] = useState(); + const [isDocId, setIsDocId] = useState(false); + const [pdfNewWidth, setPdfNewWidth] = useState(); + const [pdfOriginalWidth, setPdfOriginalWidth] = useState(); + const [signerPos, setSignerPos] = useState([]); + const [noData, setNoData] = useState(false); + 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 [defaultSignAlert, setDefaultSignAlert] = useState({ + isShow: false, + alertMessage: "" + }); + const [isCompleted, setIsCompleted] = useState({ + isCertificate: false, + isModal: false + }); + const [pdfLoadFail, setPdfLoadFail] = useState({ + status: false, + type: "load" + }); + const [isSigned, setIsSigned] = useState(false); + const [isExpired, setIsExpired] = useState(false); + const [alreadySign, setAlreadySign] = useState(false); + const [containerWH, setContainerWH] = useState({}); + const divRef = useRef(null); + const isMobile = window.innerWidth < 767; + const rowLevel = + localStorage.getItem("rowlevel") && + JSON.parse(localStorage.getItem("rowlevel")); + + const objectId = + rowLevel && rowLevel.id + ? rowLevel.id + : rowLevel?.objectId && rowLevel.objectId; + const documentId = "uKymQWaYL6"//docId ? docId : objectId && objectId; + + const senderUser = + localStorage.getItem( + `Parse/${localStorage.getItem("parseAppId")}/currentUser` + ) && + localStorage.getItem( + `Parse/${localStorage.getItem("parseAppId")}/currentUser` + ); + const jsonSender = JSON.parse(senderUser); + + useEffect(() => { + if (documentId) { + getDocumentDetails(); + } + }, []); + useEffect(() => { + if (divRef.current) { + const pdfWidth = pdfNewWidthFun(divRef); + setPdfNewWidth(pdfWidth); + setContainerWH({ + width: divRef.current.offsetWidth, + height: divRef.current.offsetHeight + }); + } + }, [divRef.current]); + + //function for get document details for perticular signer with signer'object id + const getDocumentDetails = async () => { + //getting document details + const documentData = await contractDocument(documentId); + if (documentData && documentData.length > 0) { + 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 getCurrentSigner = + getSigners && + getSigners.filter( + (data) => data.UserId.objectId === jsonSender.objectId + ); + + const currUserId = getCurrentSigner[0] + ? getCurrentSigner[0].objectId + : ""; + setSignerObjectId(currUserId); + if (documentData[0].SignedUrl) { + setPdfUrl(documentData[0].SignedUrl); + } else { + setPdfUrl(documentData[0].URL); + } + if (isCompleted) { + setIsSigned(true); + const data = { + isCertificate: true, + isModal: true + }; + setAlreadySign(true); + setIsCompleted(data); + } else if (declined) { + const currentDecline = { + currnt: "another", + isDeclined: true + }; + setIsDecline(currentDecline); + } else if (currDate > expireUpdateDate) { + setIsExpired(true); + } + + if (documentData.length > 0) { + const checkDocIdExist = + documentData[0].AuditTrail && + documentData[0].AuditTrail.length > 0 && + documentData[0].AuditTrail.filter( + (data) => data.Activity === "Signed" + ); + + const checkAlreadySign = + documentData[0].AuditTrail && + documentData[0].AuditTrail.length > 0 && + documentData[0].AuditTrail.filter( + (data) => + data.UserPtr.objectId === currUserId && data.Activity === "Signed" + ); + if ( + checkAlreadySign && + checkAlreadySign[0] && + checkAlreadySign.length > 0 + ) { + setAlreadySign(true); + } + + let signers = []; + let unSignedSigner = []; + + //check document is signed or not + if (checkDocIdExist && checkDocIdExist.length > 0) { + setIsDocId(true); + const signerRes = documentData[0].Signers; + //comparison auditTrail user details with signers user details + for (let i = 0; i < signerRes.length; i++) { + const signerId = signerRes[i].objectId; + + let isSignedSignature = false; + for (let j = 0; j < checkDocIdExist.length; j++) { + const signedExist = + checkDocIdExist[j] && checkDocIdExist[j].UserPtr.objectId; + //checking signerObjId and auditTrail User objId + // if match then add signed data in signer array and break loop + + if (signerId === signedExist) { + signers.push({ ...signerRes[i], ...signerRes[i] }); + isSignedSignature = true; + break; + } + // if does not match then add unsigned data in unSignedSigner array + } + if (!isSignedSignature) { + unSignedSigner.push({ ...signerRes[i], ...signerRes[i] }); + } + } + setSignedSigners(signers); + setUnSignedSigners(unSignedSigner); + + setSignerPos(documentData[0].Placeholders); + } else { + let unsigned = []; + for (let i = 0; i < documentData.length; i++) { + unsigned.push(documentData[i].Signers); + } + setUnSignedSigners(unsigned[0]); + setSignerPos(documentData[0].Placeholders); + } + setPdfDetails(documentData); + setIsUiLoading(false); + } else { + alert("No data found!"); + } + } else if ( + documentData === "Error: Something went wrong!" || + (documentData.result && documentData.result.error) + ) { + const loadObj = { + isLoad: false + }; + setHandleError("Error: Something went wrong!"); + setIsLoading(loadObj); + } else { + setNoData(true); + const loadObj = { + isLoad: false + }; + setIsLoading(loadObj); + setIsUiLoading(false); + } +if(jsonSender?.objectId){ await axios + .get( + `${localStorage.getItem("baseUrl")}classes/${localStorage.getItem( + "_appName" + )}_Signature?where={"UserId": {"__type": "Pointer","className": "_User", "objectId":"${ + jsonSender?.objectId + }"}}`, + { + headers: { + "Content-Type": "application/json", + "X-Parse-Application-Id": localStorage.getItem("parseAppId"), + "X-Parse-Session-Token": localStorage.getItem("accesstoken") + } + } + ) + .then((Listdata) => { + const json = Listdata.data; + const res = json.results; + + if (res[0] && res.length > 0) { + setDefaultSignImg(res[0].ImageURL); + } + const loadObj = { + isLoad: false + }; + setIsLoading(loadObj); + }) + .catch((err) => { + const loadObj = { + isLoad: false + }; + setHandleError("Error: Something went wrong!"); + setIsLoading(loadObj); + });} + }; + + //function for embed signature or image url in pdf + async function embedImages() { + const checkUser = signerPos.filter( + (data) => data.signerObjId === signerObjectId + ); + if (checkUser && checkUser.length > 0) { + let checkSignUrl = []; + const checkSign = checkUser[0].placeHolder.filter( + (data, ind) => data.pos + ); + for (let i = 0; i < checkSign.length; i++) { + const posData = checkSign[i].pos.filter((pos) => !pos.SignUrl); + if (posData && posData.length > 0) { + checkSignUrl.push(posData); + } + } + + if (checkSignUrl && checkSignUrl.length > 0) { + setIsAlert({ + isShow: true, + alertMessage: "Please complete your signature!" + }); + } else { + setIsUiLoading(true); + const pngUrl = checkUser[0].placeHolder; + // Load a PDFDocument from the existing PDF bytes + const existingPdfBytes = await fetch(pdfUrl).then((res) => + res.arrayBuffer() + ); + const pdfDoc = await PDFDocument.load(existingPdfBytes, { + ignoreEncryption: true + }); + // let pdfBase64; + // //else if signature is more than one then embed all sign with the use of pdf-lib + // else if (pngUrl.length > 0 && pngUrl[0].pos.length > 0) { + const flag = false; + //embed document's object id to all pages in pdf document + if (!isDocId) { + await embedDocId(pdfDoc, documentId, allPages); + } + //embed multi signature in pdf + const pdfBytes = await multiSignEmbed( + pngUrl, + pdfDoc, + pdfOriginalWidth, + flag, + containerWH + ); + //function for call to embed signature in pdf and get digital signature pdf + try { + const res = await signPdfFun( + pdfBytes, + documentId, + signerObjectId, + pdfOriginalWidth, + pngUrl, + containerWH, + setIsAlert + ); + if (res && res.status === "success") { + setPdfUrl(res.data); + setIsSigned(true); + setSignedSigners([]); + setUnSignedSigners([]); + getDocumentDetails(); + } else { + setIsAlert({ + isShow: true, + alertMessage: "something went wrong" + }); + } + } catch (err) { + setIsAlert({ + isShow: true, + alertMessage: "something went wrong" + }); + } + } + + setIsSignPad(false); + // } + } else { + setIsAlert({ + isShow: true, + alertMessage: "something went wrong" + }); + } + } + + //function for get pdf page details + const pageDetails = async (pdf) => { + const load = { + status: true + }; + setPdfLoadFail(load); + pdf.getPage(1).then((pdfPage) => { + const pageWidth = pdfPage.view[2]; + + setPdfOriginalWidth(pageWidth); + }); + }; + + //function for change page + function changePage(offset) { + setPageNumber((prevPageNumber) => prevPageNumber + offset); + } + + const getFirstLetter = (name) => { + const firstLetter = name.charAt(0); + return firstLetter; + }; + //function for image upload or update + const onImageChange = (event) => { + if (event.target.files && event.target.files[0]) { + onImageSelect(event, setImgWH, setImage); + } + }; + //function for upload stamp image + const saveImage = () => { + //get current signers placeholder position data + const currentSigner = signerPos.filter( + (data) => data.signerObjId === signerObjectId + ); + //get current pagenumber placeholder index + const getIndex = currentSigner[0].placeHolder.findIndex((object) => { + return object.pageNumber === pageNumber; + }); + //get current signer placeholder position data + const placeholderPosition = currentSigner[0].placeHolder; + //function of save image and get updated position with image url + const getUpdatePosition = onSaveImage( + placeholderPosition, + getIndex, + signKey, + imgWH, + image + ); + + //replace updated placeholder position with old data + placeholderPosition.splice( + 0, + placeholderPosition.length, + ...getUpdatePosition + ); + //get current signers placeholder position data index number in array + const indexofSigner = signerPos.findIndex((object) => { + return object.signerObjId === signerObjectId; + }); + //update current signers data with new placeholder position array data + setSignerPos((prevState) => { + const newState = [...prevState]; // Create a copy of the state + newState.splice(indexofSigner, 1, ...currentSigner); // Modify the copy + return newState; // Update the state with the modified copy + }); + }; + + //function for save button to save signature or image url + const saveSign = (isDefaultSign, width, height) => { + const isTypeText = width && height ? true : false; + const signatureImg = isDefaultSign ? defaultSignImg : signature; + let imgWH = { width: width ? width : "", height: height ? height : "" }; + setIsSignPad(false); + setIsImageSelect(false); + setImage(); + + //get current signers placeholder position data + const currentSigner = signerPos.filter( + (data) => data.signerObjId === signerObjectId + ); + //get current pagenumber placeholder index + const getIndex = currentSigner[0].placeHolder.findIndex((object) => { + return object.pageNumber === pageNumber; + }); + + //set default signature image width and height + if (isDefaultSign) { + const img = new Image(); + img.src = defaultSignImg; + if (img.complete) { + imgWH = { + width: img.width, + height: img.height + }; + } + } + //get current signer placeholder position data + const placeholderPosition = currentSigner[0].placeHolder; + //function of save signature image and get updated position with signature image url + const getUpdatePosition = onSaveSign( + placeholderPosition, + getIndex, + signKey, + signatureImg, + imgWH, + isDefaultSign, + isTypeText + ); + + const updateSignerData = currentSigner.map((obj, ind) => { + if (obj.signerObjId === signerObjectId) { + return { ...obj, placeHolder: getUpdatePosition }; + } + return obj; + }); + + const index = signerPos.findIndex( + (data) => data.signerObjId === signerObjectId + ); + setSignerPos((prevState) => { + const newState = [...prevState]; + newState.splice(index, 1, ...updateSignerData); + return newState; + }); + }; + + const checkSignerBackColor = (obj) => { + const data = signerPos.filter((data) => data.signerObjId === obj.objectId); + return data && data.length > 0 && data[0].blockColor; + }; + + //function for set decline true on press decline button + const declineDoc = async () => { + setIsDecline({ isDeclined: false }); + const data = { + IsDeclined: true + }; + setIsUiLoading(true); + + await axios + .put( + `${localStorage.getItem("baseUrl")}classes/${localStorage.getItem( + "_appName" + )}_Document/${documentId}`, + data, + { + headers: { + "Content-Type": "application/json", + "X-Parse-Application-Id": localStorage.getItem("parseAppId"), + "X-Parse-Session-Token": localStorage.getItem("accesstoken") + } + } + ) + .then((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); + }); + }; + //function to add default signature for all requested placeholder of sign + const addDefaultSignature = () => { + //get current signers placeholder position data + const currentSignerPosition = signerPos.filter( + (data) => data.signerObjId === signerObjectId + ); + //function for save default signature url for all placeholder position + const updatePlace = addDefaultSignatureImg( + currentSignerPosition[0].placeHolder, + defaultSignImg + ); + + const updatesignerPos = signerPos.map((x) => + x.signerObjId === signerObjectId ? { ...x, placeHolder: updatePlace } : x + ); + setSignerPos(updatesignerPos); + setDefaultSignAlert({ + isShow: false, + alertMessage: "" + }); + }; + + return ( + + + {isLoading.isLoad ? ( + <Loader isLoading={isLoading} /> + ) : handleError ? ( + <HandleError handleError={handleError} /> + ) : noData ? ( + <Nodata /> + ) : ( + <div> + {isUiLoading && ( + <div + style={{ + position: "absolute", + height: "100vh", + width: "100%", + display: "flex", + justifyContent: "center", + flexDirection: "column", + alignItems: "center", + zIndex: "20", + backgroundColor: "#e6f2f2", + opacity: 0.8 + }} + > + <img + alt="no img" + src={loader} + style={{ width: "100px", height: "100px" }} + /> + <span style={{ fontSize: "13px", fontWeight: "bold" }}> + This might take some time + </span> + </div> + )} + + <div className="signatureContainer" ref={divRef}> + <ModalUi + headerColor={"#dc3545"} + isOpen={isAlert.isShow} + title={"Alert message"} + handleClose={() => { + setIsAlert({ + isShow: false, + alertMessage: "" + }); + }} + > + <div style={{ height: "100%", padding: 20 }}> + <p>{isAlert.alertMessage}</p> + + <div + style={{ + height: "1px", + backgroundColor: "#9f9f9f", + width: "100%", + marginTop: "15px", + marginBottom: "15px" + }} + ></div> + + <button + onClick={() => { + setIsAlert({ + isShow: false, + alertMessage: "" + }); + }} + type="button" + className="finishBtn cancelBtn" + > + Ok + </button> + </div> + </ModalUi> + {/* this modal is used to show decline alert */} + <CustomModal + containerWH={containerWH} + show={isDecline.isDeclined} + headMsg="Document Declined" + bodyMssg={ + isDecline.currnt === "Sure" ? ( + <p className="pTagBody"> + Are you sure want to decline this document ? + </p> + ) : isDecline.currnt === "YouDeclined" ? ( + <p className="pTagBody">You have declined this document!</p> + ) : ( + isDecline.currnt === "another" && ( + <p className="pTagBody"> + You cannot sign this document as it has been declined by + one or more person(s). + </p> + ) + ) + } + footerMessage={isDecline.currnt === "Sure"} + declineDoc={declineDoc} + setIsDecline={setIsDecline} + /> + {/* this modal is used for show expired alert */} + <CustomModal + containerWH={containerWH} + show={isExpired} + headMsg="Document Expired!" + bodyMssg="This Document is no longer available." + /> + <ModalUi + headerColor={defaultSignImg ? themeColor() : "#dc3545"} + isOpen={defaultSignAlert.isShow} + title={"Auto sign"} + handleClose={() => { + setDefaultSignAlert({ + isShow: false, + alertMessage: "" + }); + }} + > + <div style={{ height: "100%", padding: 20 }}> + <p>{defaultSignAlert.alertMessage}</p> + + <div + style={{ + height: "1px", + backgroundColor: "#9f9f9f", + width: "100%", + marginTop: "15px", + marginBottom: "15px" + }} + ></div> + {defaultSignImg ? ( + <> + <button + onClick={() => addDefaultSignature()} + style={{ + background: themeColor() + }} + type="button" + className="finishBtn" + > + Yes + </button> + <button + onClick={() => { + setDefaultSignAlert({ + isShow: false, + alertMessage: "" + }); + }} + type="button" + className="finishBtn cancelBtn" + > + Close + </button> + </> + ) : ( + <button + onClick={() => { + setIsAlert({ + isShow: false, + alertMessage: "" + }); + }} + type="button" + className="finishBtn cancelBtn" + > + Ok + </button> + )} + </div> + </ModalUi> + {/* this component used to render all pdf pages in left side */} + <RenderAllPdfPage + signPdfUrl={pdfDetails[0].URL || "https://api.printnode.com/static/test/pdf/multipage.pdf"} + allPages={allPages} + setAllPages={setAllPages} + setPageNumber={setPageNumber} + pageNumber={pageNumber} + /> + + {/* pdf render view */} + <div + style={{ + marginLeft: !isMobile && pdfOriginalWidth > 500 && "20px", + marginRight: !isMobile && pdfOriginalWidth > 500 && "20px" + }} + > + {/* this modal is used show this document is already sign */} + <ModalUi + isOpen={isCompleted.isModal} + title={"Sign Documents"} + handleClose={() => { + setIsCompleted({ isModal: false, isCertificate: true }); + }} + > + <div style={{ height: "100%", padding: 20 }}> + <p>This document has been signed by all Signers.</p> + + <div + style={{ + height: "1px", + backgroundColor: "#9f9f9f", + width: "100%", + marginTop: "15px", + marginBottom: "15px" + }} + ></div> + <button + type="button" + className="finishBtn cancelBtn" + onClick={() => + setIsCompleted({ isModal: false, isCertificate: true }) + } + > + Close + </button> + </div> + </ModalUi> + + {/* this component is used for signature pad modal */} + <SignPad + isSignPad={isSignPad} + isStamp={isStamp} + setIsImageSelect={setIsImageSelect} + setIsSignPad={setIsSignPad} + setImage={setImage} + isImageSelect={isImageSelect} + imageRef={imageRef} + onImageChange={onImageChange} + setSignature={setSignature} + image={image} + onSaveImage={saveImage} + onSaveSign={saveSign} + defaultSign={defaultSignImg} + /> + {/* pdf header which contain funish back button */} + <Header + isPdfRequestFiles={true} + pageNumber={pageNumber} + allPages={allPages} + changePage={changePage} + pdfDetails={pdfDetails} + signerPos={signerPos} + isSigned={isSigned} + isCompleted={isCompleted} + embedImages={embedImages} + isShowHeader={true} + setIsDecline={setIsDecline} + decline={true} + currentSigner={currentSigner} + pdfUrl={pdfUrl} + alreadySign={alreadySign} + /> + {containerWH && ( + <RenderPdf + pageNumber={pageNumber} + pdfOriginalWidth={pdfOriginalWidth} + pdfNewWidth={pdfNewWidth} + setIsSignPad={setIsSignPad} + setIsStamp={setIsStamp} + setSignKey={setSignKey} + pdfDetails={pdfDetails} + signerPos={signerPos} + successEmail={false} + pdfUrl={pdfUrl} + numPages={numPages} + pageDetails={pageDetails} + pdfRequest={true} + signerObjectId={signerObjectId} + signedSigners={signedSigners} + setCurrentSigner={setCurrentSigner} + setPdfLoadFail={setPdfLoadFail} + pdfLoadFail={pdfLoadFail} + setSignerPos={setSignerPos} + containerWH={containerWH} + /> + )} + </div> + <div> + <div className="signerComponent"> + <div + style={{ maxHeight: window.innerHeight - 70 + "px" }} + className="autoSignScroll" + > + {signedSigners.length > 0 && ( + <> + <div + style={{ + background: themeColor() + }} + className="signedStyle" + > + Signed by + </div> + <div style={{ marginTop: "2px" }}> + {signedSigners.map((obj, ind) => { + return ( + <div + style={{ + display: "flex", + flexDirection: "row", + alignItems: "center", + padding: "10px 0", + background: checkSignerBackColor(obj) + }} + key={ind} + > + <div + className="signerStyle" + style={{ + background: "#abd1d0", + width: 30, + height: 30, + display: "flex", + borderRadius: 30 / 2, + justifyContent: "center", + alignItems: "center", + margin: "0 10px 0 5px" + }} + > + <span + style={{ + fontSize: "12px", + textAlign: "center", + fontWeight: "bold", + color: "black", + textTransform: "uppercase" + }} + > + {getFirstLetter(obj.Name)} + </span> + </div> + <div + style={{ + display: "flex", + flexDirection: "column" + }} + > + <span className="userName">{obj.Name}</span> + <span className="useEmail">{obj.Email}</span> + </div> + </div> + ); + })} + </div> + </> + )} + + {unsignedSigners.length > 0 && ( + <> + <div + style={{ + background: themeColor(), + color: "white", + padding: "5px", + fontFamily: "sans-serif", + marginTop: signedSigners.length > 0 && "20px" + }} + > + Yet to sign + </div> + <div style={{ marginTop: "5px" }}> + {unsignedSigners.map((obj, ind) => { + return ( + <div + style={{ + display: "flex", + flexDirection: "row", + alignItems: "center", + padding: "10px 0", + background: checkSignerBackColor(obj) + }} + key={ind} + > + <div + className="signerStyle" + style={{ + background: "#abd1d0", + width: 30, + height: 30, + display: "flex", + borderRadius: 30 / 2, + justifyContent: "center", + alignItems: "center", + margin: "0 10px 0 5px" + }} + > + <span + style={{ + fontSize: "12px", + textAlign: "center", + fontWeight: "bold", + color: "black", + textTransform: "uppercase" + }} + > + {getFirstLetter(obj.Name)} + </span> + </div> + <div + style={{ + display: "flex", + flexDirection: "column" + }} + > + <span className="userName">{obj.Name}</span> + <span className="useEmail">{obj.Email}</span> + </div> + <hr /> + </div> + ); + })} + </div> + </> + )} + {defaultSignImg && !alreadySign && ( + <DefaultSignature + themeColor={themeColor} + defaultSignImg={defaultSignImg} + setDefaultSignImg={setDefaultSignImg} + userObjectId={signerObjectId} + setIsLoading={setIsLoading} + xyPostion={signerPos} + setDefaultSignAlert={setDefaultSignAlert} + /> + )} + </div> + </div> + </div> + </div> + </div> + )} + </DndProvider> + ); +} + +export default GuestSign;