diff --git a/apps/OpenSign/public/locales/en/translation.json b/apps/OpenSign/public/locales/en/translation.json index bb0531210..6077d59dd 100644 --- a/apps/OpenSign/public/locales/en/translation.json +++ b/apps/OpenSign/public/locales/en/translation.json @@ -369,7 +369,8 @@ "copy-type": { "All pages": "All pages", "All pages but last": "All pages but last", - "All pages but first": "All pages but first" + "All pages but first": "All pages but first", + "Beside to widget":"Beside to widget" }, "options": "Options", "minimun-check": "Minimun check", @@ -612,5 +613,8 @@ "additional-quicksend": "Please purchase add-on quick send", "quantityofquicksend": "Quantity of Quick send", "quotaerrquicksend": "Quota Reached, You don't have enough credits.", - "buycredits": "Buy credits" -} \ No newline at end of file + "buycredits": "Buy credits", + "rotate-right":"Rotate right", + "rotate-left":"Rotate left", + "rotate-alert-mssg":"All widgets on this page will be lost. Are you sure you want to proceed?" +} diff --git a/apps/OpenSign/public/locales/fr/translation.json b/apps/OpenSign/public/locales/fr/translation.json index 8cf77b179..53b329712 100644 --- a/apps/OpenSign/public/locales/fr/translation.json +++ b/apps/OpenSign/public/locales/fr/translation.json @@ -369,7 +369,8 @@ "copy-type": { "All pages": "Toutes les pages", "All pages but last": "Toutes les pages sauf la dernière", - "All pages but first": "Toutes les pages sauf la première" + "All pages but first": "Toutes les pages sauf la première", + "Beside to widget": "À côté du widget" }, "options": "Possibilités", "minimun-check": "Vérification minimale", @@ -612,6 +613,8 @@ "additional-quicksend":"Veuillez acheter le module complémentaire quick send", "quantityofquicksend": "Quantité de Quick send", "quotaerrquicksend": "Quota atteint, vous n'avez pas assez de crédits.", - "buycredits": "Acheter des crédits" - - } + "buycredits": "Acheter des crédits", + "rotate-right" :"Faire pivoter à droite", + "rotate-left" :"Faire pivoter à gauche", + "rotate-alert-mssg" :"Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?" +} diff --git a/apps/OpenSign/src/components/Header.js b/apps/OpenSign/src/components/Header.js index 5bc4c360b..0c912ef09 100644 --- a/apps/OpenSign/src/components/Header.js +++ b/apps/OpenSign/src/components/Header.js @@ -227,11 +227,7 @@ const Header = ({ showSidebar }) => { {t("change-password")} -
  • { - handleConsoleRedirect(); - }} - > +
  • handleConsoleRedirect()}> Console diff --git a/apps/OpenSign/src/components/RotateAlert.js b/apps/OpenSign/src/components/RotateAlert.js new file mode 100644 index 000000000..a7448b6f1 --- /dev/null +++ b/apps/OpenSign/src/components/RotateAlert.js @@ -0,0 +1,38 @@ +import React from "react"; +import ModalUi from "../primitives/ModalUi"; +import { useTranslation } from "react-i18next"; + +function RotateAlert(props) { + const { t } = useTranslation(); + return ( + props.setIsRotate({ status: false, degree: 0 })} + > + {" "} +
    +

    {t("rotate-alert-mssg")}

    +
    +
    + + +
    +
    +
    + ); +} + +export default RotateAlert; diff --git a/apps/OpenSign/src/components/pdf/PdfHeader.js b/apps/OpenSign/src/components/pdf/PdfHeader.js index f4976d4f2..c8e833ce4 100644 --- a/apps/OpenSign/src/components/pdf/PdfHeader.js +++ b/apps/OpenSign/src/components/pdf/PdfHeader.js @@ -34,7 +34,11 @@ function Header({ setIsEmail, completeBtnTitle, setIsEditTemplate, - isPublicTemplate + isPublicTemplate, + clickOnZoomIn, + clickOnZoomOut, + handleRotationFun, + isDisableRotate }) { const { t } = useTranslation(); const filterPrefill = @@ -73,7 +77,7 @@ function Header({ allPages={allPages} changePage={changePage} /> - {pdfUrl && alreadySign ? ( + {isCompleted && alreadySign ? (
    @@ -184,13 +188,7 @@ function Header({ ) : (
    { - if (!pdfUrl) { - embedWidgetsData(); - } else if (isPdfRequestFiles) { - embedWidgetsData(); - } - }} + onClick={() => embedWidgetsData()} className="border-none font-[650] text-[14px] op-link op-link-primary no-underline" > {t("finish")} @@ -207,7 +205,7 @@ function Header({ handleDownloadPdf( pdfDetails, - pdfUrl, + pdfDetails[0]?.URL, setIsDownloading ) } @@ -226,6 +224,43 @@ function Header({ > {t("download")} + {!isDisableRotate && ( + <> + handleRotationFun(90)} + > + + + {t("rotate-right")} + + + handleRotationFun(-90)} + > + + + {t("rotate-left")} + + + + )} + + clickOnZoomIn()} + > + + {t("zoom-in")} + + clickOnZoomOut()} + > + + {t("zoom-out")} + @@ -423,9 +458,7 @@ function Header({ @@ -442,11 +475,7 @@ function Header({ diff --git a/apps/OpenSign/src/components/pdf/PdfZoom.js b/apps/OpenSign/src/components/pdf/PdfZoom.js index c12868e01..c315a16da 100644 --- a/apps/OpenSign/src/components/pdf/PdfZoom.js +++ b/apps/OpenSign/src/components/pdf/PdfZoom.js @@ -3,44 +3,37 @@ import { useTranslation } from "react-i18next"; function PdfZoom(props) { const { t } = useTranslation(); - const onClickZoomIn = () => { - props.setScale(props.scale + 0.1 * props.scale); - props.setZoomPercent(props.zoomPercent + 10); - }; - const onClickZoomOut = () => { - if (props.zoomPercent > 0) { - if (props.zoomPercent === 10) { - props.setScale(1); - } else { - props.setScale(props.scale - 0.1 * props.scale); - } - props.setZoomPercent(props.zoomPercent - 10); - } - }; - const handleReset = () => { - props.setScale(1); - props.setZoomPercent(0); - }; + return ( - // md:mt-[41px] xl:mt-[63px]mt=[] - + props.clickOnZoomIn()} title={t("zoom-in")} > - - - + {!props?.isDisableRotate && ( + <> + props.handleRotationFun(90)} + title={t("rotate-right")} + > + + + props.handleRotationFun(-90)} + > + + + + )} props.clickOnZoomOut()} style={{ cursor: props.zoomPercent > 0 ? "pointer" : "default" }} diff --git a/apps/OpenSign/src/components/pdf/PlaceholderCopy.js b/apps/OpenSign/src/components/pdf/PlaceholderCopy.js index 64c85c413..a10bde125 100644 --- a/apps/OpenSign/src/components/pdf/PlaceholderCopy.js +++ b/apps/OpenSign/src/components/pdf/PlaceholderCopy.js @@ -1,12 +1,21 @@ import React, { useState } from "react"; import ModalUi from "../../primitives/ModalUi"; -import { randomId, textWidget } from "../../constant/Utils"; +import { + handleCopyNextToWidget, + randomId, + textWidget +} from "../../constant/Utils"; import { useTranslation } from "react-i18next"; function PlaceholderCopy(props) { const { t } = useTranslation(); - const copyType = ["All pages", "All pages but last", "All pages but first"]; - const [selectCopyType, setSelectCopyType] = useState(""); + const copyType = [ + { id: 1, type: "All pages" }, + { id: 2, type: "All pages but last" }, + { id: 3, type: "All pages but first" }, + { id: 4, type: "Beside to widget" } + ]; + const [selectCopyType, setSelectCopyType] = useState(1); //function for get copy placeholder position const getCopyPlaceholderPosition = ( type, @@ -20,7 +29,7 @@ function PlaceholderCopy(props) { (data) => data.xPosition !== currentPlaceholder.xPosition ); //copy all page placeholder at requested position except first page - if (newPageNumber === 1 && type === "first") { + if (newPageNumber === 1 && type === 3) { if (filterPosition && filterPosition.length > 0) { return { pageNumber: newPageNumber, @@ -29,7 +38,7 @@ function PlaceholderCopy(props) { } } //copy all page placeholder at requested position except last page - else if (newPageNumber === props.allPages && type === "last") { + else if (newPageNumber === props.allPages && type === 2) { if (existPlaceholderPosition) { return { pageNumber: newPageNumber, @@ -172,20 +181,60 @@ function PlaceholderCopy(props) { //function for getting selected type placeholder copy const handleApplyCopy = () => { - if (selectCopyType === "All pages") { - copyPlaceholder("all"); - } else if (selectCopyType === "All pages but last") { - copyPlaceholder("last"); - } else if (selectCopyType === "All pages but first") { - copyPlaceholder("first"); + if (selectCopyType === 4) { + const signerPosition = props.xyPostion; + let currentXYposition; + const signerId = props.signerObjId ? props.signerObjId : props.Id; + if (signerId) { + const filterSignerPosition = signerPosition.filter( + (item) => item?.Id === signerId + ); + currentXYposition = filterSignerPosition[0].placeHolder.filter( + (data) => data.pageNumber === props?.pageNumber + ); + //get current placeholder position data which user want to copy + currentXYposition = currentXYposition[0].pos.find( + (position) => position.key === props.signKey + ); + //function to create new widget next to just widget + handleCopyNextToWidget( + currentXYposition, + props.widgetType, + props.xyPostion, + props.pageNumber, + props.setXyPostion, + props?.Id + ); + } else { + const getIndex = props?.xyPostion.findIndex( + (data) => data.pageNumber === props.pageNumber + ); + const placeholderPosition = props?.xyPostion[getIndex]; + //get current placeholder position data which user want to copy + currentXYposition = placeholderPosition.pos.find( + (pos) => pos.key === props.signKey + ); + //function to create new widget next to just widget + handleCopyNextToWidget( + currentXYposition, + props.widgetType, + props.xyPostion, + getIndex, + props.setXyPostion + ); + } + } else { + copyPlaceholder(selectCopyType); } }; const handleUniqueId = () => { - if (props.widgetType === textWidget) { + const signerId = props.signerObjId ? props.signerObjId : props.Id; + if (signerId && props.widgetType === textWidget) { props.setUniqueId(props?.tempSignerId); props.setTempSignerId(""); } props.setIsPageCopy(false); + setSelectCopyType(1); }; return ( setSelectCopyType(data)} - checked={selectCopyType === data} + value={data.id} + onChange={() => setSelectCopyType(data.id)} + checked={selectCopyType === data.id} /> - {t(`copy-type.${data}`)} + {t(`copy-type.${data.type}`)}
    ); diff --git a/apps/OpenSign/src/components/pdf/RenderAllPdfPage.js b/apps/OpenSign/src/components/pdf/RenderAllPdfPage.js index d54f9afd6..381f19be4 100644 --- a/apps/OpenSign/src/components/pdf/RenderAllPdfPage.js +++ b/apps/OpenSign/src/components/pdf/RenderAllPdfPage.js @@ -12,7 +12,8 @@ function RenderAllPdfPage({ pageNumber, signerPos, signerObjectId, - containerWH + containerWH, + pdfRotateBase64 }) { const { t } = useTranslation(); const [signPageNumber, setSignPageNumber] = useState([]); @@ -66,6 +67,7 @@ function RenderAllPdfPage({ ) ); }; + const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`; return (
    @@ -78,7 +80,7 @@ function RenderAllPdfPage({ {Array.from(new Array(allPages), (el, index) => (
    {successEmail && ( {t("success-email-alert")} )} {isMobile ? ( -
    - {containerWH?.width && - pdfOriginalWH.length > 0 && - (pdfRequest - ? signerPos.map((data, key) => { - return ( - - {checkSignedSigners(data)} - - ); - }) - : placeholder // placeholder mobile - ? signerPos.map((data, ind) => { +
    + {containerWH?.width && + pdfOriginalWH.length > 0 && + (pdfRequest + ? signerPos.map((data, key) => { return ( - - {data.placeHolder.map((placeData, index) => { - return ( - - {placeData.pageNumber === pageNumber && - placeData.pos.map((pos) => { - return ( - - - - ); - })} - - ); - })} + + {checkSignedSigners(data)} ); }) - : xyPostion.map((data, ind) => { - return ( - - {data.pageNumber === pageNumber && - data.pos.map((pos) => { + : placeholder // placeholder mobile + ? signerPos.map((data, ind) => { + return ( + + {data.placeHolder.map((placeData, index) => { return ( - pos && ( - - ) + + {placeData.pageNumber === pageNumber && + placeData.pos.map((pos) => { + return ( + + + + ); + })} + ); })} - - ); - }))} + + ); + }) + : xyPostion.map((data, ind) => { + return ( + + {data.pageNumber === pageNumber && + data.pos.map((pos) => { + return ( + pos && ( + + ) + ); + })} + + ); + }))} - {/* this component for render pdf document is in middle of the component */} -
    { - setPdfLoad(false); - }} + onLoadError={() => setPdfLoad(false)} loading={t("loading-doc")} onLoadSuccess={pageDetails} // ref={pdfRef}' @@ -417,9 +433,14 @@ function RenderPdf({ setSelectWidgetId(""); } }} - file={pdfDetails[0]?.SignedUrl || pdfDetails[0]?.URL} + file={ + (pdfRotateBase64 && pdfDataBase64) || + pdfDetails[0]?.SignedUrl || + pdfDetails[0].URL + } >
    -
    + ) : ( { - setPdfLoad(false); - }} + onLoadError={() => setPdfLoad(false)} loading={t("loading-doc")} onLoadSuccess={pageDetails} onClick={() => { @@ -606,7 +625,11 @@ function RenderPdf({ } }} // ref={pdfRef} - file={pdfDetails[0]?.SignedUrl || pdfDetails[0].URL} + file={ + (pdfRotateBase64 && pdfDataBase64) || + pdfDetails[0]?.SignedUrl || + pdfDetails[0].URL + } > { - props.handleVerifyBtn(); - }} + onClick={() => props.handleVerifyBtn()} > {t("send-otp")} diff --git a/apps/OpenSign/src/constant/Utils.js b/apps/OpenSign/src/constant/Utils.js index eed96f8ca..08b92c975 100644 --- a/apps/OpenSign/src/constant/Utils.js +++ b/apps/OpenSign/src/constant/Utils.js @@ -1,7 +1,7 @@ import axios from "axios"; import moment from "moment"; import React from "react"; -import { rgb } from "pdf-lib"; +import { PDFDocument, rgb, degrees } from "pdf-lib"; import Parse from "parse"; import { appInfo } from "./appinfo"; import { saveAs } from "file-saver"; @@ -658,7 +658,12 @@ export const randomId = () => { return randomDigit; }; -export const createDocument = async (template, placeholders, signerData) => { +export const createDocument = async ( + template, + placeholders, + signerData, + pdfUrl +) => { if (template && template.length > 0) { const Doc = template[0]; @@ -681,7 +686,7 @@ export const createDocument = async (template, placeholders, signerData) => { } const data = { Name: Doc.Name, - URL: Doc.URL, + URL: pdfUrl, SignedUrl: Doc.SignedUrl, SentToOthers: Doc.SentToOthers, Description: Doc.Description, @@ -867,7 +872,6 @@ export const onChangeInput = ( } } else { let getXYdata = xyPostion[index].pos; - const updatePosition = getXYdata.map((positionData) => { if (positionData.key === signKey) { if (dateFormat) { @@ -1077,13 +1081,18 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => { const textContent = documentId && `OpenSign™ DocumentId: ${documentId} `; const pages = pdfDoc.getPages(); const page = pages[i]; - page.drawText(textContent, { - x: 10, - y: page.getHeight() - 10, - size: fontSize, + const getObj = compensateRotation( + page.getRotation().angle, + 10, + 5, + 1, + page.getSize(), + fontSize, + rgb(0.5, 0.5, 0.5), font, - color: rgb(0.5, 0.5, 0.5) - }); + page + ); + page.drawText(textContent, getObj); } }; @@ -1849,29 +1858,26 @@ export const handleCopyNextToWidget = ( setXyPostion, userId ) => { - const isSigners = xyPostion.some((data) => data.signerPtr); let filterSignerPos; //get position of previous widget and create new widget next to that widget on same data except // xPosition and key - let newpos = position; + let newposition = position; const calculateXPosition = parseInt(position.xPosition) + defaultWidthHeight(widgetType).width + resizeBorderExtraWidth(); const newId = randomId(); - newpos = { ...newpos, xPosition: calculateXPosition, key: newId }; + newposition = { ...newposition, xPosition: calculateXPosition, key: newId }; //if condition to create widget in request-sign flow - if (isSigners) { - if (userId) { - filterSignerPos = xyPostion.filter((data) => data.Id === userId); - } - const getPlaceHolder = filterSignerPos[0]?.placeHolder; - const getPageNumer = getPlaceHolder.filter( + if (userId) { + filterSignerPos = xyPostion.filter((data) => data.Id === userId); + const getPlaceHolder = filterSignerPos && filterSignerPos[0]?.placeHolder; + const getPageNumer = getPlaceHolder?.filter( (data) => data.pageNumber === index ); - const getXYdata = getPageNumer[0].pos; - getXYdata.push(newpos); - if (getPageNumer.length > 0) { + const getXYdata = getPageNumer && getPageNumer[0]?.pos; + getXYdata.push(newposition); + if (getPageNumer && getPageNumer.length > 0) { const newUpdateSignPos = getPlaceHolder.map((obj) => { if (obj.pageNumber === index) { return { ...obj, pos: getXYdata }; @@ -1889,9 +1895,8 @@ export const handleCopyNextToWidget = ( setXyPostion(newUpdateSigner); } } else { - // else condition to create widget in sign-yourself flow - let getXYdata = xyPostion[index].pos; - getXYdata.push(newpos); + let getXYdata = xyPostion[index]?.pos || []; + getXYdata.push(newposition); const updatePlaceholder = xyPostion.map((obj, ind) => { if (ind === index) { return { ...obj, pos: getXYdata }; @@ -2192,9 +2197,11 @@ function compensateRotation( font, page ) { + // Ensure pageRotation is between 0 and 360 degrees + pageRotation = ((pageRotation % 360) + 360) % 360; let rotationRads = (pageRotation * Math.PI) / 180; - //These coords are now from bottom/left + // Coordinates are from bottom-left let coordsFromBottomLeft = { x: x / scale }; if (pageRotation === 90 || pageRotation === 270) { coordsFromBottomLeft.y = dimensions.width - (y + fontSize) / scale; @@ -2204,6 +2211,7 @@ function compensateRotation( let drawX = null; let drawY = null; + if (pageRotation === 90) { drawX = coordsFromBottomLeft.x * Math.cos(rotationRads) - @@ -2229,8 +2237,8 @@ function compensateRotation( coordsFromBottomLeft.x * Math.sin(rotationRads) + coordsFromBottomLeft.y * Math.cos(rotationRads) + dimensions.height; - } else { - //no rotation + } else if (pageRotation === 0 || pageRotation === 360) { + // No rotation or full rotation drawX = coordsFromBottomLeft.x; drawY = coordsFromBottomLeft.y; } @@ -2334,3 +2342,114 @@ export const getDefaultSignature = async (objectId) => { }; } }; + +//function to rotate pdf page +export async function rotatePdfPage( + url, + rotateDegree, + pageNumber, + pdfRotateBase64 +) { + let file; + + //condition to handle pdf base64 in arrayBuffer format + if (pdfRotateBase64) { + file = base64ToArrayBuffer(pdfRotateBase64); + } else { + //condition to handle pdf url in arrayBuffer format + file = await convertPdfArrayBuffer(url); + } + // Load the existing PDF + const pdfDoc = await PDFDocument.load(file); + // Get the page according to page number + const page = pdfDoc.getPage(pageNumber); + //get current page rotation angle + const currentRotation = page.getRotation().angle; + // Apply the rotation in the counterclockwise direction + let newRotation = (currentRotation + rotateDegree) % 360; + // Adjust for negative angles to keep within 0-359 range + if (newRotation < 0) { + newRotation += 360; + } + page.setRotation(degrees(newRotation)); + const pdfbase64 = await pdfDoc.saveAsBase64({ useObjectStreams: false }); + //convert base64 to arraybuffer is used in pdf-lib + //pdfbase64 is used to show pdf rotated format + const arrayBuffer = base64ToArrayBuffer(pdfbase64); + //`base64` is used to show pdf + return { arrayBuffer: arrayBuffer, base64: pdfbase64 }; +} +function base64ToArrayBuffer(base64) { + // Decode the base64 string to a binary string + const binaryString = atob(base64); + // Create a new ArrayBuffer with the same length as the binary string + const len = binaryString.length; + const bytes = new Uint8Array(len); + // Convert the binary string to a byte array + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + // Return the ArrayBuffer + return bytes.buffer; +} + +export const convertBase64ToFile = async (pdfName, pdfBase64) => { + const fileName = sanitizeFileName(pdfName) + ".pdf"; + const pdfFile = new Parse.File(fileName, { base64: pdfBase64 }); + // Save the Parse File if needed + const pdfData = await pdfFile.save(); + const pdfUrl = pdfData.url(); + return pdfUrl; +}; +export const onClickZoomIn = (scale, zoomPercent, setScale, setZoomPercent) => { + setScale(scale + 0.1 * scale); + setZoomPercent(zoomPercent + 10); +}; +export const onClickZoomOut = ( + zoomPercent, + scale, + setZoomPercent, + setScale +) => { + if (zoomPercent > 0) { + if (zoomPercent === 10) { + setScale(1); + } else { + setScale(scale - 0.1 * scale); + } + setZoomPercent(zoomPercent - 10); + } +}; + +//function to use remove widgets from current page when user want to rotate page +export const handleRemoveWidgets = ( + setSignerPos, + signerPos, + pageNumber, + setIsRotate +) => { + const updatedSignerPos = signerPos.map((placeholderObj) => { + return { + ...placeholderObj, + placeHolder: placeholderObj?.placeHolder?.filter( + (data) => data?.pageNumber !== pageNumber + ) + }; + }); + setSignerPos(updatedSignerPos); + setIsRotate({ + status: false, + degree: 0 + }); +}; +//function to show warning when user rotate page and there are some already widgets on that page +export const handleRotateWarning = (signerPos, pageNumber) => { + const placeholderExist = signerPos?.some((placeholderObj) => + placeholderObj?.placeHolder?.some((data) => data?.pageNumber === pageNumber) + ); + if (placeholderExist) { + return true; + } else { + return false; + } +}; diff --git a/apps/OpenSign/src/pages/PdfRequestFiles.js b/apps/OpenSign/src/pages/PdfRequestFiles.js index 6147f21dc..8bde5629a 100644 --- a/apps/OpenSign/src/pages/PdfRequestFiles.js +++ b/apps/OpenSign/src/pages/PdfRequestFiles.js @@ -32,7 +32,9 @@ import { handleToPrint, handleDownloadCertificate, openInNewTab, - getDefaultSignature + getDefaultSignature, + onClickZoomIn, + onClickZoomOut } from "../constant/Utils"; import LoaderWithMsg from "../primitives/LoaderWithMsg"; import HandleError from "../primitives/HandleError"; @@ -122,7 +124,6 @@ function PdfRequestFiles(props) { const [otp, setOtp] = useState(""); const [contractName, setContractName] = useState(""); const [zoomPercent, setZoomPercent] = useState(0); - const [totalZoomPercent, setTotalZoomPercent] = useState(); const [scale, setScale] = useState(1); const [uniqueId, setUniqueId] = useState(""); const [isPublicTemplate, setIsPublicTemplate] = useState(false); @@ -132,6 +133,7 @@ function PdfRequestFiles(props) { const [res, setRes] = useState({}); const [documentId, setDocumentId] = useState(""); const [isPublicContact, setIsPublicContact] = useState(false); + const [pdfArrayBuffer, setPdfArrayBuffer] = useState(""); const isHeader = useSelector((state) => state.showHeader); const divRef = useRef(null); @@ -140,17 +142,18 @@ function PdfRequestFiles(props) { let isGuestSignFlow = false; let sendmail; let getDocId = ""; - const route = !props.templateId && window.location.pathname; + const route = !props.templateId && window.location.pathname; //'/load/recipientSignPdf/TOAVuhXbfw/fPAKdK1qgX' + //window.location.search = ?sendmail=false const getQuery = !props.templateId && window.location?.search && - window.location?.search?.split("?"); + window.location?.search?.split("?"); //['','sendmail=false'] + //'sendmail=false' if (getQuery) { - sendmail = getQuery[1].split("=")[1]; + sendmail = getQuery[1].split("=")[1]; //false } - const checkSplit = route && route?.split("/"); - + const checkSplit = route && route?.split("/"); // ['', 'load', 'recipientSignPdf', 'TOAVuhXbfw', 'fPAKdK1qgX'] if (checkSplit && checkSplit.length > 4) { isGuestSignFlow = true; getDocId = checkSplit[3]; @@ -298,6 +301,20 @@ function PdfRequestFiles(props) { : []; if (documentData && documentData.length > 0) { + const url = + documentData[0] && + (documentData[0]?.SignedUrl || documentData[0]?.URL); + if (url) { + //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(t("something-went-wrong-mssg")); + } else { + setPdfArrayBuffer(arrayBuffer); + } + } else { + setHandleError(t("something-went-wrong-mssg")); + } setIsPublicTemplate(true); const getPublicRole = documentData[0]?.PublicRole[0]; const getUniqueIdDetails = documentData[0]?.Placeholders.find( @@ -361,6 +378,20 @@ function PdfRequestFiles(props) { //getting document details const documentData = await contractDocument(documentId || docId); if (documentData && documentData.length > 0) { + const url = + documentData[0] && + (documentData[0]?.SignedUrl || documentData[0]?.URL); + if (url) { + //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(t("something-went-wrong-mssg")); + } else { + setPdfArrayBuffer(arrayBuffer); + } + } else { + setHandleError(t("something-went-wrong-mssg")); + } setExtUserId(documentData[0]?.ExtUserPtr?.objectId); const isCompleted = documentData[0].IsCompleted && documentData[0].IsCompleted; @@ -817,30 +848,9 @@ function PdfRequestFiles(props) { setIsUiLoading(true); // `widgets` is Used to return widgets details with page number of current user const widgets = checkUser?.[0]?.placeHolder; - let pdfArrBuffer; - //`contractDocument` function used to get updated SignedUrl - //resolved issue of sign document by multiple signers simultaneously - const documentData = await contractDocument(documentId); - if (documentData && documentData.length > 0) { - const url = documentData[0]?.SignedUrl || documentData[0]?.URL; - //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(t("invalid-document")); - } else { - pdfArrBuffer = arrayBuffer; - } - } else if ( - documentData === "Error: Something went wrong!" || - (documentData.result && documentData.result.error) - ) { - setHandleError(t("something-went-wrong-mssg")); - } else { - setHandleError(t("document-not-found")); - } // Load a PDFDocument from the existing PDF bytes - const existingPdfBytes = pdfArrBuffer; + const existingPdfBytes = pdfArrayBuffer; try { const pdfDoc = await PDFDocument.load(existingPdfBytes); const isSignYourSelfFlow = false; @@ -1316,7 +1326,6 @@ function PdfRequestFiles(props) { const handleDontShow = (isChecked) => { setIsDontShow(isChecked); }; - // console.log("signerpos", signerPos); //function to close tour and save tour status const closeRequestSignTour = async () => { setRequestSignTour(true); @@ -1591,6 +1600,12 @@ function PdfRequestFiles(props) { }); }; + const clickOnZoomIn = () => { + onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent); + }; + const clickOnZoomOut = () => { + onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale); + }; return ( @@ -1712,9 +1727,7 @@ function PdfRequestFiles(props) { <ModalUi isOpen={isPublicContact} title={isOtp ? t("verify-email-1") : t("contact-details")} - handleClose={() => { - handleCloseOtp(); - }} + handleClose={() => handleCloseOtp()} > <div className="h-full p-[20px]"> {isOtp ? ( @@ -1742,9 +1755,7 @@ function PdfRequestFiles(props) { <div className="flex gap-2"> <button className="op-btn op-btn-ghost" - onClick={() => { - handleCloseOtp(); - }} + onClick={() => handleCloseOtp()} > {t("cancel")} </button> @@ -1834,9 +1845,7 @@ function PdfRequestFiles(props) { <div className="flex gap-2"> <button className="op-btn op-btn-ghost" - onClick={() => { - handleCloseOtp(); - }} + onClick={() => handleCloseOtp()} > {t("close")} </button> @@ -1914,20 +1923,18 @@ function PdfRequestFiles(props) { {/* pdf render view */} <div className=" w-full md:w-[57%] flex mr-4"> <PdfZoom - setScale={setScale} - scale={scale} - containerWH={containerWH} - setZoomPercent={setZoomPercent} - zoomPercent={zoomPercent} + clickOnZoomIn={clickOnZoomIn} + clickOnZoomOut={clickOnZoomOut} + isDisableRotate={true} /> <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 })); - }} + handleClose={() => + setIsCompleted((prev) => ({ ...prev, isModal: false })) + } reduceWidth={ !isCompleted?.message && "md:min-w-[440px] md:max-w-[400px]" @@ -2059,15 +2066,11 @@ function PdfRequestFiles(props) { currentSigner={currentSigner} pdfUrl={pdfUrl} alreadySign={alreadySign} - totalZoomPercent={totalZoomPercent} - setTotalZoomPercent={setTotalZoomPercent} - setScale={setScale} - scale={scale} - pdfOriginalWH={pdfOriginalWH} containerWH={containerWH} - setZoomPercent={setZoomPercent} - zoomPercent={zoomPercent} isPublicTemplate={isPublicTemplate} + clickOnZoomIn={clickOnZoomIn} + clickOnZoomOut={clickOnZoomOut} + isDisableRotate={true} /> <div ref={divRef} data-tut="pdfArea" className="h-[95%]"> @@ -2101,7 +2104,6 @@ function PdfRequestFiles(props) { divRef={divRef} setIsResize={setIsResize} isResize={isResize} - setTotalZoomPercent={setTotalZoomPercent} setScale={setScale} scale={scale} uniqueId={uniqueId} @@ -2181,9 +2183,7 @@ function PdfRequestFiles(props) { <ModalUi isOpen={validateAlert} title={t("validation-alert")} - handleClose={() => { - setValidateAlert(false); - }} + handleClose={() => setValidateAlert(false)} > <div className="h-[100%] p-[20px]"> <p>{t("validation-alert-1")}</p> diff --git a/apps/OpenSign/src/pages/PlaceHolderSign.js b/apps/OpenSign/src/pages/PlaceHolderSign.js index 1cf79a7e2..25b61f4ad 100644 --- a/apps/OpenSign/src/pages/PlaceHolderSign.js +++ b/apps/OpenSign/src/pages/PlaceHolderSign.js @@ -35,7 +35,13 @@ import { fetchSubscription, convertPdfArrayBuffer, getContainerScale, - openInNewTab + openInNewTab, + convertBase64ToFile, + onClickZoomIn, + onClickZoomOut, + rotatePdfPage, + handleRemoveWidgets, + handleRotateWarning } from "../constant/Utils"; import RenderPdf from "../components/pdf/RenderPdf"; import { useNavigate } from "react-router-dom"; @@ -56,6 +62,7 @@ import PdfZoom from "../components/pdf/PdfZoom"; import LottieWithLoader from "../primitives/DotLottieReact"; import { paidUrl } from "../json/plansArr"; import { useTranslation } from "react-i18next"; +import RotateAlert from "../components/RotateAlert"; function PlaceHolderSign() { const { t } = useTranslation(); @@ -133,6 +140,10 @@ function PlaceHolderSign() { const [pdfArrayBuffer, setPdfArrayBuffer] = useState(""); const isHeader = useSelector((state) => state.showHeader); const [activeMailAdapter, setActiveMailAdapter] = useState(""); + const [isRotate, setIsRotate] = useState({ + status: false, + degree: 0 + }); const [isAlreadyPlace, setIsAlreadyPlace] = useState({ status: false, message: "" @@ -141,6 +152,8 @@ function PlaceHolderSign() { const [isCustomize, setIsCustomize] = useState(false); const [zoomPercent, setZoomPercent] = useState(0); const [scale, setScale] = useState(1); + + const [pdfRotateBase64, setPdfRotatese64] = useState(""); const isMobile = window.innerWidth < 767; const [, drop] = useDrop({ accept: "BOX", @@ -837,11 +850,6 @@ function PlaceHolderSign() { const handleMouseLeave = () => { setSignBtnPosition([xySignature]); }; - - function sanitizeFileName(fileName) { - // Remove spaces and invalid characters - return fileName.replace(/[^a-zA-Z0-9._-]/g, ""); - } //embed prefill label widget data const embedPrefilllData = async () => { const prefillExist = signerPos.filter((data) => data.Role === "prefill"); @@ -853,7 +861,7 @@ function PlaceHolderSign() { }); const isSignYourSelfFlow = false; try { - const pdfBytes = await multiSignEmbed( + const pdfBase64 = await multiSignEmbed( placeholder, pdfDoc, isSignYourSelfFlow, @@ -861,19 +869,26 @@ function PlaceHolderSign() { pdfOriginalWH, containerWH ); - - const fileName = sanitizeFileName(pdfDetails[0].Name) + ".pdf"; - const pdfFile = new Parse.File(fileName, { base64: pdfBytes }); - - // Save the Parse File if needed - const pdfData = await pdfFile.save(); - const pdfUrl = pdfData.url(); + const pdfUrl = await convertBase64ToFile(pdfDetails[0].Name, pdfBase64); const tenantId = localStorage.getItem("TenantId"); - const buffer = atob(pdfBytes); + const buffer = atob(pdfBase64); SaveFileSize(buffer.length, pdfUrl, tenantId); return pdfUrl; } catch (e) { - console.log("error", e); + console.log("error to convertBase64ToFile in placeholder flow", e); + } + } else if (pdfRotateBase64) { + try { + const pdfUrl = await convertBase64ToFile( + pdfDetails[0].Name, + pdfRotateBase64 + ); + const tenantId = localStorage.getItem("TenantId"); + const buffer = atob(pdfRotateBase64); + SaveFileSize(buffer.length, pdfUrl, tenantId); + return pdfUrl; + } catch (e) { + console.log("error to convertBase64ToFile in placeholder flow", e); } } else { return pdfDetails[0].URL; @@ -1710,6 +1725,39 @@ function PlaceHolderSign() { }); } }; + const clickOnZoomIn = () => { + onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent); + }; + const clickOnZoomOut = () => { + onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale); + }; + //`handleRotationFun` function is used to roatate pdf particular page + const handleRotationFun = async (rotateDegree) => { + const isRotate = handleRotateWarning(signerPos, pageNumber); + if (isRotate) { + setIsRotate({ status: true, degree: rotateDegree }); + } else { + const urlDetails = await rotatePdfPage( + pdfDetails[0].URL, + rotateDegree, + pageNumber - 1, + pdfRotateBase64 + ); + setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer); + setPdfRotatese64(urlDetails.base64); + } + }; + const handleRemovePlaceholder = async () => { + handleRemoveWidgets(setSignerPos, signerPos, pageNumber, setIsRotate); + const urlDetails = await rotatePdfPage( + pdfDetails[0].URL, + isRotate.degree, + pageNumber - 1, + pdfRotateBase64 + ); + setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer); + setPdfRotatese64(urlDetails.base64); + }; return ( <> <Title title={state?.title ? state.title : "New Document"} /> @@ -1758,15 +1806,14 @@ function PlaceHolderSign() { setPageNumber={setPageNumber} setSignBtnPosition={setSignBtnPosition} pageNumber={pageNumber} + pdfRotateBase64={pdfRotateBase64} /> {/* pdf render view */} <div className=" w-full md:w-[57%] flex mr-4"> <PdfZoom - setScale={setScale} - scale={scale} - containerWH={containerWH} - setZoomPercent={setZoomPercent} - zoomPercent={zoomPercent} + clickOnZoomIn={clickOnZoomIn} + clickOnZoomOut={clickOnZoomOut} + handleRotationFun={handleRotationFun} /> <div className=" w-full md:w-[95%] "> {/* this modal is used show alert set placeholder for all signers before send mail */} @@ -1894,9 +1941,7 @@ function PlaceHolderSign() { > {isCurrUser && ( <button - onClick={() => { - handleRecipientSign(); - }} + onClick={() => handleRecipientSign()} type="button" className="op-btn op-btn-primary mr-1" > @@ -1921,17 +1966,13 @@ function PlaceHolderSign() { <ModalUi isOpen={isShowEmail} title={t("signers-alert")} - handleClose={() => { - setIsShowEmail(false); - }} + handleClose={() => setIsShowEmail(false)} > <div className="h-[100%] p-[20px]"> <p>{t("placeholder-alert-7")}</p> <div className="w-full h-[1px] bg-[#9f9f9f] my-[15px]"></div> <button - onClick={() => { - setIsShowEmail(false); - }} + onClick={() => setIsShowEmail(false)} type="button" className="op-btn op-btn-primary" > @@ -2012,9 +2053,16 @@ function PlaceHolderSign() { alertSendEmail={alertSendEmail} isShowHeader={true} currentSigner={true} + handleRotationFun={handleRotationFun} + clickOnZoomIn={clickOnZoomIn} + clickOnZoomOut={clickOnZoomOut} /> - <div ref={divRef} data-tut="pdfArea" className="h-[95%]"> + <div + ref={divRef} + data-tut="pdfArea" + className="h-full md:h-[95%]" + > {containerWH && ( <RenderPdf pageNumber={pageNumber} @@ -2056,6 +2104,7 @@ function PlaceHolderSign() { setScale={setScale} scale={scale} setIsSelectId={setIsSelectId} + pdfRotateBase64={pdfRotateBase64} fontSize={fontSize} setFontSize={setFontSize} fontColor={fontColor} @@ -2202,6 +2251,11 @@ function PlaceHolderSign() { fontColor={fontColor} setFontColor={setFontColor} /> + <RotateAlert + isRotate={isRotate.status} + setIsRotate={setIsRotate} + handleRemoveWidgets={handleRemovePlaceholder} + /> </DndProvider> </> ); diff --git a/apps/OpenSign/src/pages/SignyourselfPdf.js b/apps/OpenSign/src/pages/SignyourselfPdf.js index bf94527eb..a65fdc159 100644 --- a/apps/OpenSign/src/pages/SignyourselfPdf.js +++ b/apps/OpenSign/src/pages/SignyourselfPdf.js @@ -34,7 +34,10 @@ import { changeImageWH, handleSendOTP, getContainerScale, - getDefaultSignature + getDefaultSignature, + onClickZoomIn, + onClickZoomOut, + rotatePdfPage } from "../constant/Utils"; import { useParams } from "react-router-dom"; import Tour from "reactour"; @@ -53,6 +56,7 @@ import VerifyEmail from "../components/pdf/VerifyEmail"; import PdfZoom from "../components/pdf/PdfZoom"; import Loader from "../primitives/Loader"; import { useTranslation } from "react-i18next"; +import RotateAlert from "../components/RotateAlert"; //For signYourself inProgress section signer can add sign and complete doc sign. function SignYourSelf() { const { t } = useTranslation(); @@ -123,6 +127,11 @@ function SignYourSelf() { const [zoomPercent, setZoomPercent] = useState(0); const isHeader = useSelector((state) => state.showHeader); const [scale, setScale] = useState(1); + const [pdfRotateBase64, setPdfRotatese64] = useState(""); + const [isRotate, setIsRotate] = useState({ + status: false, + degree: 0 + }); const divRef = useRef(null); const nodeRef = useRef(null); const [, drop] = useDrop({ @@ -206,6 +215,7 @@ function SignYourSelf() { return () => clearTimeout(timer); // eslint-disable-next-line react-hooks/exhaustive-deps }, [divRef.current, isHeader]); + //function for get document details for perticular signer with signer'object id const getDocumentDetails = async (showComplete) => { try { @@ -794,6 +804,7 @@ function SignYourSelf() { const signedpdf = JSON.parse(JSON.stringify(resSignPdf)); setPdfUrl(signedpdf); getDocumentDetails(false); + setPdfRotatese64(""); } }; @@ -1160,6 +1171,56 @@ function SignYourSelf() { handleTextSettingModal(false); } }; + const clickOnZoomIn = () => { + onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent); + }; + const clickOnZoomOut = () => { + onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale); + }; + //`handleRotationFun` function is used to roatate pdf particular page + const handleRotationFun = async (rotateDegree) => { + const isPlaceholder = handleRotateWarning(); + if (isPlaceholder) { + setIsRotate({ status: true, degree: rotateDegree }); + } else { + const urlDetails = await rotatePdfPage( + pdfUrl || pdfDetails[0].URL, + rotateDegree, + pageNumber - 1, + pdfRotateBase64 + ); + setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer); + setPdfRotatese64(urlDetails.base64); + } + }; + + //function to show warning when user rotate page and there are some already widgets on that page + const handleRotateWarning = () => { + const placeholderData = xyPostion?.filter( + (data) => data.pageNumber === pageNumber + ); + if (placeholderData && placeholderData.length > 0) { + return true; + } else { + return false; + } + }; + //function to use remove widgets from current page when user want to rotate page + const handleRemoveWidgets = async () => { + const updatePlaceholder = xyPostion.filter( + (data) => data.pageNumber !== pageNumber + ); + const urlDetails = await rotatePdfPage( + pdfUrl || pdfDetails[0].URL, + isRotate.degree, + pageNumber - 1, + pdfRotateBase64 + ); + setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer); + setPdfRotatese64(urlDetails.base64); + setXyPostion(updatePlaceholder); + setIsRotate({ status: false, degree: 0 }); + }; return ( <DndProvider backend={HTML5Backend}> <Title title={"Self Sign"} /> @@ -1219,14 +1280,13 @@ function SignYourSelf() { setSignBtnPosition={setSignBtnPosition} pageNumber={pageNumber} containerWH={containerWH} + pdfRotateBase64={pdfRotateBase64} /> <div className=" w-full md:w-[57%] flex mr-4"> <PdfZoom - setScale={setScale} - scale={scale} - containerWH={containerWH} - setZoomPercent={setZoomPercent} - zoomPercent={zoomPercent} + clickOnZoomIn={clickOnZoomIn} + clickOnZoomOut={clickOnZoomOut} + handleRotationFun={handleRotationFun} /> <div className="w-full md:w-[95%]"> <ModalUi @@ -1245,9 +1305,7 @@ function SignYourSelf() { <ModalUi isOpen={showAlreadySignDoc.status} title={t("document-signed")} - handleClose={() => { - setShowAlreadySignDoc({ status: false }); - }} + handleClose={() => setShowAlreadySignDoc({ status: false })} > <div className="p-[20px] h-full"> <p>{showAlreadySignDoc.mssg}</p> @@ -1284,6 +1342,7 @@ function SignYourSelf() { allPages={allPages} pageNumber={pageNumber} signKey={signKey} + widgetType={widgetType} /> {/* this is modal of signature pad */} <SignPad @@ -1334,12 +1393,12 @@ function SignYourSelf() { isSignYourself={true} setIsEmail={setIsEmail} isCompleted={isCompleted} + handleRotationFun={handleRotationFun} + clickOnZoomIn={clickOnZoomIn} + clickOnZoomOut={clickOnZoomOut} + widgetsDetails={xyPostion} /> - <div - ref={divRef} - data-tut="reactourSecond" - className="h-full md:h-[95%]" - > + <div ref={divRef} data-tut="reactourSecond" className="h-full"> {containerWH?.width && containerWH?.height && ( <RenderPdf pageNumber={pageNumber} @@ -1378,6 +1437,7 @@ function SignYourSelf() { handleTextSettingModal={handleTextSettingModal} setScale={setScale} scale={scale} + pdfRotateBase64={pdfRotateBase64} fontSize={fontSize} setFontSize={setFontSize} fontColor={fontColor} @@ -1421,9 +1481,7 @@ function SignYourSelf() { <ModalUi isOpen={validateAlert} title={t("validation-alert")} - handleClose={() => { - setValidateAlert(false); - }} + handleClose={() => setValidateAlert(false)} > <div className="p-[20px] h-full"> <p>{t("validate-alert-mssg")}</p> @@ -1438,6 +1496,11 @@ function SignYourSelf() { </button> </div> </ModalUi> + <RotateAlert + isRotate={isRotate.status} + setIsRotate={setIsRotate} + handleRemoveWidgets={handleRemoveWidgets} + /> <TextFontSetting isTextSetting={isTextSetting} setIsTextSetting={setIsTextSetting} diff --git a/apps/OpenSign/src/pages/TemplatePlaceholder.js b/apps/OpenSign/src/pages/TemplatePlaceholder.js index a0c5476b0..073f5d7c9 100644 --- a/apps/OpenSign/src/pages/TemplatePlaceholder.js +++ b/apps/OpenSign/src/pages/TemplatePlaceholder.js @@ -26,7 +26,13 @@ import { radioButtonWidget, fetchSubscription, getContainerScale, - openInNewTab + openInNewTab, + convertBase64ToFile, + rotatePdfPage, + onClickZoomOut, + onClickZoomIn, + handleRemoveWidgets, + handleRotateWarning } from "../constant/Utils"; import RenderPdf from "../components/pdf/RenderPdf"; import "../styles/AddUser.css"; @@ -43,6 +49,7 @@ import { useSelector } from "react-redux"; import PdfZoom from "../components/pdf/PdfZoom"; import { paidUrl } from "../json/plansArr"; import { useTranslation } from "react-i18next"; +import RotateAlert from "../components/RotateAlert"; const TemplatePlaceholder = () => { const { t } = useTranslation(); const navigate = useNavigate(); @@ -63,6 +70,10 @@ const TemplatePlaceholder = () => { const [isSendAlert, setIsSendAlert] = useState(false); const [isCreateDocModal, setIsCreateDocModal] = useState(false); const [isSubscribe, setIsSubscribe] = useState(false); + const [isRotate, setIsRotate] = useState({ + status: false, + degree: 0 + }); const [isLoading, setIsLoading] = useState({ isLoad: true, message: t("loading-mssg") @@ -93,6 +104,7 @@ const TemplatePlaceholder = () => { const [isNameModal, setIsNameModal] = useState(false); const [isTextSetting, setIsTextSetting] = useState(false); const [pdfLoad, setPdfLoad] = useState(false); + const [pdfRotateBase64, setPdfRotatese64] = useState(""); const color = [ "#93a3db", "#e6c3db", @@ -731,6 +743,7 @@ const TemplatePlaceholder = () => { handleSaveTemplate(); } }; + const handleSaveTemplate = async () => { if (signersdata?.length) { const loadObj = { @@ -752,6 +765,17 @@ const TemplatePlaceholder = () => { } }); } + let pdfUrl = pdfDetails[0]?.URL; + if (pdfRotateBase64) { + try { + pdfUrl = await convertBase64ToFile( + pdfDetails[0].Name, + pdfRotateBase64 + ); + } catch (e) { + console.log("error to convertBase64ToFile in placeholder flow", e); + } + } try { const data = { Placeholders: signerPos, @@ -762,7 +786,8 @@ const TemplatePlaceholder = () => { SendinOrder: pdfDetails[0]?.SendinOrder || false, AutomaticReminders: pdfDetails[0]?.AutomaticReminders, RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery), - NextReminderDate: pdfDetails[0]?.NextReminderDate + NextReminderDate: pdfDetails[0]?.NextReminderDate, + URL: pdfUrl }; const updateTemplate = new Parse.Object("contracts_Template"); updateTemplate.id = templateId; @@ -902,8 +927,21 @@ const TemplatePlaceholder = () => { const handleCreateDocModal = async () => { setIsCreateDocModal(false); setIsCreateDoc(true); + let pdfUrl = pdfDetails[0]?.URL; + if (pdfRotateBase64) { + try { + pdfUrl = await convertBase64ToFile(pdfDetails[0].Name, pdfRotateBase64); + } catch (e) { + console.log("error to convertBase64ToFile in placeholder flow", e); + } + } // handle create document - const res = await createDocument(pdfDetails, signerPos, signersdata); + const res = await createDocument( + pdfDetails, + signerPos, + signersdata, + pdfUrl + ); if (res.status === "success") { navigate(`/placeHolderSign/${res.id}`, { state: { title: "Use Template" } @@ -1289,6 +1327,38 @@ const TemplatePlaceholder = () => { setIsCheckbox(false); }; + const clickOnZoomIn = () => { + onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent); + }; + const clickOnZoomOut = () => { + onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale); + }; + //`handleRotationFun` function is used to roatate pdf particular page + const handleRotationFun = async (rotateDegree) => { + const isRotate = handleRotateWarning(signerPos, pageNumber); + if (isRotate) { + setIsRotate({ status: true, degree: rotateDegree }); + } else { + const urlDetails = await rotatePdfPage( + pdfDetails[0].URL, + rotateDegree, + pageNumber - 1, + pdfRotateBase64 + ); + setPdfRotatese64(urlDetails.base64); + } + }; + const handleRemovePlaceholder = async () => { + handleRemoveWidgets(setSignerPos, signerPos, pageNumber, setIsRotate); + const urlDetails = await rotatePdfPage( + pdfDetails[0].URL, + isRotate.degree, + pageNumber - 1, + pdfRotateBase64 + ); + setPdfRotatese64(urlDetails.base64); + }; + return ( <> <Title title={"Template"} /> @@ -1336,11 +1406,9 @@ const TemplatePlaceholder = () => { {/* pdf render view */} <div className=" w-full md:w-[57%] flex mr-4"> <PdfZoom - setScale={setScale} - scale={scale} - containerWH={containerWH} - setZoomPercent={setZoomPercent} - zoomPercent={zoomPercent} + clickOnZoomIn={clickOnZoomIn} + clickOnZoomOut={clickOnZoomOut} + handleRotationFun={handleRotationFun} /> <div className="w-full md:w-[95%]"> {/* this modal is used show alert set placeholder for all signers before send mail */} @@ -1483,6 +1551,9 @@ const TemplatePlaceholder = () => { currentSigner={true} setIsEditTemplate={handleEditTemplateModal} dataTut4="reactourFour" + handleRotationFun={handleRotationFun} + clickOnZoomIn={clickOnZoomIn} + clickOnZoomOut={clickOnZoomOut} /> <div ref={divRef} @@ -1528,6 +1599,7 @@ const TemplatePlaceholder = () => { setScale={setScale} scale={scale} setIsSelectId={setIsSelectId} + pdfRotateBase64={pdfRotateBase64} fontSize={fontSize} setFontSize={setFontSize} fontColor={fontColor} @@ -1671,6 +1743,11 @@ const TemplatePlaceholder = () => { fontColor={fontColor} setFontColor={setFontColor} /> + <RotateAlert + isRotate={isRotate.status} + setIsRotate={setIsRotate} + handleRemoveWidgets={handleRemovePlaceholder} + /> </DndProvider> </> ); diff --git a/apps/OpenSign/src/primitives/AddContact.js b/apps/OpenSign/src/primitives/AddContact.js index 46155a5c3..c25024c85 100644 --- a/apps/OpenSign/src/primitives/AddContact.js +++ b/apps/OpenSign/src/primitives/AddContact.js @@ -46,109 +46,125 @@ const AddContact = (props) => { e.preventDefault(); e.stopPropagation(); setIsLoader(true); - try { - const contactQuery = new Parse.Object("contracts_Contactbook"); - contactQuery.set("Name", name); - if (phone) { - contactQuery.set("Phone", phone); - } - contactQuery.set("Email", email); - contactQuery.set("UserRole", "contracts_Guest"); - - if (localStorage.getItem("TenantId")) { - contactQuery.set("TenantId", { - __type: "Pointer", - className: "partners_Tenant", - objectId: localStorage.getItem("TenantId") - }); - } - + if (localStorage.getItem("TenantId")) { try { - const _users = Parse.Object.extend("User"); - const _user = new _users(); - _user.set("name", name); - _user.set("username", email); - _user.set("email", email); - _user.set("password", email); - if (phone) { - _user.set("phone", phone); - } + const user = Parse.User.current(); + const query = new Parse.Query("contracts_Contactbook"); + query.equalTo("CreatedBy", user); + query.notEqualTo("IsDeleted", true); + query.equalTo("Email", email); + const res = await query.first(); + if (!res) { + const contactQuery = new Parse.Object("contracts_Contactbook"); + contactQuery.set("Name", name); + if (phone) { + contactQuery.set("Phone", phone); + } + contactQuery.set("Email", email); + contactQuery.set("UserRole", "contracts_Guest"); - const user = await _user.save(); - if (user) { - const currentUser = Parse.User.current(); - contactQuery.set( - "CreatedBy", - Parse.User.createWithoutData(currentUser.id) - ); - - contactQuery.set("UserId", user); - const acl = new Parse.ACL(); - acl.setPublicReadAccess(true); - acl.setPublicWriteAccess(true); - acl.setReadAccess(currentUser.id, true); - acl.setWriteAccess(currentUser.id, true); - - contactQuery.setACL(acl); - - const res = await contactQuery.save(); - - const parseData = JSON.parse(JSON.stringify(res)); - props.details(parseData); - if (props.closePopup) { - props.closePopup(); + if (localStorage.getItem("TenantId")) { + contactQuery.set("TenantId", { + __type: "Pointer", + className: "partners_Tenant", + objectId: localStorage.getItem("TenantId") + }); } + try { + const _users = Parse.Object.extend("User"); + const _user = new _users(); + _user.set("name", name); + _user.set("username", email); + _user.set("email", email); + _user.set("password", email); + if (phone) { + _user.set("phone", phone); + } + + const user = await _user.save(); + if (user) { + const currentUser = Parse.User.current(); + contactQuery.set( + "CreatedBy", + Parse.User.createWithoutData(currentUser.id) + ); + + contactQuery.set("UserId", user); + const acl = new Parse.ACL(); + acl.setPublicReadAccess(true); + acl.setPublicWriteAccess(true); + acl.setReadAccess(currentUser.id, true); + acl.setWriteAccess(currentUser.id, true); + + contactQuery.setACL(acl); + + const res = await contactQuery.save(); + + const parseData = JSON.parse(JSON.stringify(res)); + props.details(parseData); + if (props.closePopup) { + props.closePopup(); + } + + setIsLoader(false); + // Reset the form fields + setAddYourself(false); + setName(""); + setPhone(""); + setEmail(""); + } + } catch (err) { + console.log("err ", err); + if (err.code === 202) { + const params = { email: email }; + const userRes = await Parse.Cloud.run("getUserId", params); + const currentUser = Parse.User.current(); + contactQuery.set( + "CreatedBy", + Parse.User.createWithoutData(currentUser.id) + ); + + contactQuery.set("UserId", { + __type: "Pointer", + className: "_User", + objectId: userRes.id + }); + const acl = new Parse.ACL(); + acl.setPublicReadAccess(true); + acl.setPublicWriteAccess(true); + acl.setReadAccess(currentUser.id, true); + acl.setWriteAccess(currentUser.id, true); + + contactQuery.setACL(acl); + const res = await contactQuery.save(); + + const parseData = JSON.parse(JSON.stringify(res)); + if (props.details) { + props.details(parseData); + } + + if (props.closePopup) { + props.closePopup(); + } + setIsLoader(false); + // Reset the form fields + setAddYourself(false); + setName(""); + setPhone(""); + setEmail(""); + } + } + } else { + alert(t("add-signer-alert")); setIsLoader(false); - // Reset the form fields - setAddYourself(false); - setName(""); - setPhone(""); - setEmail(""); } } catch (err) { - console.log("err ", err); - if (err.code === 202) { - const params = { email: email }; - const userRes = await Parse.Cloud.run("getUserId", params); - const currentUser = Parse.User.current(); - contactQuery.set( - "CreatedBy", - Parse.User.createWithoutData(currentUser.id) - ); - - contactQuery.set("UserId", { - __type: "Pointer", - className: "_User", - objectId: userRes.id - }); - const acl = new Parse.ACL(); - acl.setPublicReadAccess(true); - acl.setPublicWriteAccess(true); - acl.setReadAccess(currentUser.id, true); - acl.setWriteAccess(currentUser.id, true); - - contactQuery.setACL(acl); - const res = await contactQuery.save(); - - const parseData = JSON.parse(JSON.stringify(res)); - if (props.details) { - props.details(parseData); - } - - if (props.closePopup) { - props.closePopup(); - } - setIsLoader(false); - // Reset the form fields - setAddYourself(false); - setName(""); - setPhone(""); - setEmail(""); - } + // console.log("err", err); + setIsLoader(false); + alert(t("something-went-wrong-mssg")); } - } catch (err) { - // console.log("err", err); + } else { setIsLoader(false); alert(t("something-went-wrong-mssg")); } diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 983d213bd..169fe93d0 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -1306,9 +1306,7 @@ const ReportTable = (props) => { <ModalUi isOpen title={t("public-url")} - handleClose={() => { - setIsPublicProfile({}); - }} + handleClose={() => setIsPublicProfile({})} > <div className="m-[20px]"> {publicUserName ? ( diff --git a/apps/OpenSign/src/script/locales/en/translation.json b/apps/OpenSign/src/script/locales/en/translation.json index 074d421be..461c4615c 100644 --- a/apps/OpenSign/src/script/locales/en/translation.json +++ b/apps/OpenSign/src/script/locales/en/translation.json @@ -604,5 +604,8 @@ "Add-seats": "Add Seats", "user-name-exist": "user name already exist", "format": "format", - "select-date-format": "Select a date format" + "select-date-format": "Select a date format", + "rotate-right": "Rotate right", + "rotate-left": "Rotate left", + "rotate-alert-mssg": "All widgets on this page will be lost. Are you sure you want to proceed?" } diff --git a/apps/OpenSign/src/script/locales/fr/translation.json b/apps/OpenSign/src/script/locales/fr/translation.json index 88bfcfa6a..1b1671b85 100644 --- a/apps/OpenSign/src/script/locales/fr/translation.json +++ b/apps/OpenSign/src/script/locales/fr/translation.json @@ -603,5 +603,8 @@ "Select": "sélectionner", "Add-seats": "Ajouter des sièges", "format": "format", - "select-date-format": "Sélectionnez un format de date" + "select-date-format": "Sélectionnez un format de date", + "rotate-right": "Faire pivoter à droite", + "rotate-left": "Faire pivoter à gauche", + "rotate-alert-mssg" :"Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?" }