diff --git a/apps/OpenSign/src/components/pdf/VerifyEmail.js b/apps/OpenSign/src/components/pdf/VerifyEmail.js new file mode 100644 index 000000000..1b40614a1 --- /dev/null +++ b/apps/OpenSign/src/components/pdf/VerifyEmail.js @@ -0,0 +1,85 @@ +import React from "react"; +import { rejectBtn, submitBtn, themeColor } from "../../constant/const"; + +function VerifyEmail(props) { + return ( +
+
+
+ OTP verification +
+ {props.isVerifyModal ? ( +
{ + props.setIsVerifyModal(false); + props.handleVerifyEmail(e); + }} + > +
+ + props.setOtp(e.target.value)} + /> +
+
+
+ + +
+
+ ) : props.otpLoader ? ( +
+
+
+ ) : ( +
+

Please verify your email !

+
+
+ +
+
+ )} +
+
+ ); +} + +export default VerifyEmail; diff --git a/apps/OpenSign/src/constant/Utils.js b/apps/OpenSign/src/constant/Utils.js index ab4f4e9a6..63289af0b 100644 --- a/apps/OpenSign/src/constant/Utils.js +++ b/apps/OpenSign/src/constant/Utils.js @@ -2008,3 +2008,18 @@ export const convertPdfArrayBuffer = async (url) => { return "Error"; } }; + +//`handleSendOTP` function is used to send otp on user's email using `SendOTPMailV1` cloud function +export const handleSendOTP = async (email) => { + try { + let url = `${localStorage.getItem("baseUrl")}functions/SendOTPMailV1`; + const headers = { + "Content-Type": "application/json", + "X-Parse-Application-Id": localStorage.getItem("parseAppId") + }; + const body = { email: email }; + await axios.post(url, body, { headers: headers }); + } catch (error) { + alert(error.message); + } +}; diff --git a/apps/OpenSign/src/pages/Form.js b/apps/OpenSign/src/pages/Form.js index fd3e521f0..8441cb817 100644 --- a/apps/OpenSign/src/pages/Form.js +++ b/apps/OpenSign/src/pages/Form.js @@ -14,7 +14,8 @@ import { SaveFileSize } from "../constant/saveFileSize"; import { getFileName, toDataUrl } from "../constant/Utils"; import { PDFDocument } from "pdf-lib"; import axios from "axios"; -import { isEnableSubscription } from "../constant/const"; +import { isEnableSubscription, submitBtn } from "../constant/const"; +import ModalUi from "../primitives/ModalUi"; // `Form` render all type of Form on this basis of their provided in path function Form() { @@ -43,7 +44,9 @@ const Forms = (props) => { Description: "", Note: "", TimeToCompleteDays: 15, - SendinOrder: "false" + SendinOrder: "false", + password: "", + file: "" }); const [fileupload, setFileUpload] = useState(""); const [fileload, setfileload] = useState(false); @@ -52,6 +55,8 @@ const Forms = (props) => { const [isAlert, setIsAlert] = useState(false); const [isSubmit, setIsSubmit] = useState(false); const [isErr, setIsErr] = useState(""); + const [isPassword, setIsPassword] = useState(false); + const [isDecrypting, setIsDecrypting] = useState(false); const handleStrInput = (e) => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; @@ -80,6 +85,7 @@ const Forms = (props) => { setpercentage(0); try { let files = e.target.files; + setFormData((prev) => ({ ...prev, file: e.target.files[0] })); if (typeof files[0] !== "undefined") { const mb = Math.round(files[0].size / Math.pow(1024, 2)); if (mb > maxFileSize) { @@ -96,16 +102,83 @@ const Forms = (props) => { await PDFDocument.load(res); handleFileUpload(files[0]); } catch (err) { - alert(`Currently encrypted pdf files are not supported.`); - setFileUpload(""); - e.target.value = ""; - console.log("err ", err); - try { - await Parse.Cloud.run("encryptedpdf", { - email: Parse.User.current().getEmail() - }); - } catch (err) { - console.log("err in sending posthog encryptedpdf", err); + if (err?.message?.includes("is encrypted")) { + try { + await Parse.Cloud.run("encryptedpdf", { + email: Parse.User.current().getEmail() + }); + } catch (err) { + console.log("err in sending posthog encryptedpdf", err); + } + // console.log("err ", err); + try { + setIsDecrypting(true); + const fileName = files?.[0].name; + const size = files?.[0].size; + const name = sanitizeFileName(fileName); + const url = "https://ai.nxglabs.in/decryptpdf"; //"https://ai.nxglabs.in/decryptpdf"; // + let formData = new FormData(); + formData.append("file", files[0]); + formData.append("password", ""); + const config = { + headers: { + "content-type": "multipart/form-data" + // sessiontoken: Parse.User.current().getSessionToken() + }, + responseType: "blob" + }; + const response = await axios.post(url, formData, config); + const pdfBlob = new Blob([response.data], { + type: "application/pdf" + }); + const pdfFile = new File([pdfBlob], name, { + type: "application/pdf" + }); + setIsDecrypting(false); + setfileload(true); + + // Upload the file to Parse Server + const parseFile = new Parse.File( + name, + pdfFile, + "application/pdf" + ); + + await parseFile.save({ + progress: (progressValue, loaded, total, { type }) => { + if (type === "upload" && progressValue !== null) { + const percentCompleted = Math.round( + (loaded * 100) / total + ); + setpercentage(percentCompleted); + } + } + }); + + // Retrieve the URL of the uploaded file + if (parseFile.url()) { + console.log("parseFile.url() ", parseFile.url()); + setFileUpload(parseFile.url()); + setfileload(false); + const tenantId = localStorage.getItem("TenantId"); + SaveFileSize(size, parseFile.url(), tenantId); + return parseFile.url(); + } + } catch (err) { + console.log("Error uploading file: ", err?.response); + setfileload(false); + setpercentage(0); + if (err?.response?.status === 401) { + setIsPassword(true); + } else { + setIsDecrypting(false); + e.target.value = ""; + } + } + } else { + console.log("err ", err); + setFileUpload(""); + e.target.value = ""; } } } else { @@ -174,7 +247,7 @@ const Forms = (props) => { if (isEnableSubscription) { try { setfileload(true); - const url = "http://tools.opensignlabs.com/docxtopdf"; + const url = "https://ai.nxglabs.in/docxtopdf"; let formData = new FormData(); formData.append("file", files[0]); const config = { @@ -387,7 +460,68 @@ const Forms = (props) => { setpercentage(0); setTimeout(() => setIsReset(false), 50); }; + const handlePasswordSubmit = async (e) => { + e.preventDefault(); + setIsPassword(false); + setfileload(true); + try { + const fileName = formData?.file?.name; + const size = formData?.file?.size; + const name = sanitizeFileName(fileName); + const url = "https://ai.nxglabs.in/decryptpdf"; // + let Data = new FormData(); + Data.append("file", formData?.file); + Data.append("password", formData.password); + const config = { + headers: { + "content-type": "multipart/form-data" + // sessiontoken: Parse.User.current().getSessionToken() + }, + responseType: "blob" + }; + const response = await axios.post(url, Data, config); + const pdfBlob = new Blob([response.data], { + type: "application/pdf" + }); + const pdfFile = new File([pdfBlob], name, { + type: "application/pdf" + }); + setIsDecrypting(false); + // Upload the file to Parse Server + const parseFile = new Parse.File(name, pdfFile, "application/pdf"); + await parseFile.save({ + progress: (progressValue, loaded, total, { type }) => { + if (type === "upload" && progressValue !== null) { + const percentCompleted = Math.round((loaded * 100) / total); + setpercentage(percentCompleted); + } + } + }); + + // Retrieve the URL of the uploaded file + if (parseFile.url()) { + setFormData((prev) => ({ ...prev, password: "" })); + console.log("parseFile.url() ", parseFile.url()); + setFileUpload(parseFile.url()); + setfileload(false); + const tenantId = localStorage.getItem("TenantId"); + SaveFileSize(size, parseFile.url(), tenantId); + return parseFile.url(); + } + } catch (err) { + console.log("Error uploading file: ", err?.response); + setfileload(false); + setpercentage(0); + setFormData((prev) => ({ ...prev, password: "" })); + if (err?.response?.status === 401) { + setIsPassword(true); + } else { + setIsDecrypting(false); + e.target.value = ""; + } + } + }; return (
@@ -411,173 +545,208 @@ const Forms = (props) => { ></div> </div> ) : ( - <form onSubmit={handleSubmit}> - <h1 className="text-[20px] font-semibold mb-4">{props?.title}</h1> - {fileload && ( - <div className="flex items-center gap-x-2"> - <div className="h-2 rounded-full w-[200px] md:w-[400px] bg-gray-200"> - <div - className="h-2 rounded-full bg-blue-500" - style={{ width: `${percentage}%` }} - ></div> - </div> - <span className="text-black text-sm">{percentage}%</span> - </div> - )} - <div className="text-xs"> - <label className="block"> - {`File (pdf, png, jpg, jpeg${ - isEnableSubscription ? ", docx)" : ")" - }`} - <span className="text-red-500 text-[13px]">*</span> - </label> - {fileupload.length > 0 ? ( - <div className="flex gap-2 justify-center items-center"> - <div className="flex justify-between items-center px-2 py-2 w-full font-bold rounded border-[1px] border-[#ccc] text-gray-500 bg-white text-[13px]"> - <div className="break-all"> - file selected : {getFileName(fileupload)} - </div> - <div - onClick={() => setFileUpload("")} - className="cursor-pointer px-[10px] text-[20px] font-bold bg-white text-red-500" - > - <i className="fa-solid fa-xmark"></i> - </div> - </div> - {process.env.REACT_APP_DROPBOX_API_KEY && ( - <DropboxChooser - onSuccess={dropboxSuccess} - onCancel={dropboxCancel} - /> - )} - </div> - ) : ( - <div className="flex gap-2 justify-center items-center"> + <> + <ModalUi isOpen={isPassword} title={"Enter Pdf Password"}> + <form onSubmit={handlePasswordSubmit}> + <div className="px-6 py-3"> + {/* <label className="mb-2">Enter OTP</label> */} <input - type="file" - className="bg-white px-2 py-1.5 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" - onChange={(e) => handleFileInput(e)} - accept={ - isEnableSubscription - ? "application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/png,image/jpeg" - : "application/pdf,image/png,image/jpeg" - } + type="text" + name="password" + value={formData.password} + onChange={(e) => handleStrInput(e)} + className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" + placeholder="Enter pdf password" required /> - {process.env.REACT_APP_DROPBOX_API_KEY && ( - <DropboxChooser - onSuccess={dropboxSuccess} - onCancel={dropboxCancel} - /> - )} + </div> + <hr /> + <div className="px-6 my-3"> + <button type="submit" className={submitBtn}> + Submit + </button> + </div> + </form> + </ModalUi> + <form onSubmit={handleSubmit}> + <h1 className="text-[20px] font-semibold mb-4">{props?.title}</h1> + {fileload && ( + <div className="flex items-center gap-x-2"> + <div className="h-2 rounded-full w-[200px] md:w-[400px] bg-gray-200"> + <div + className="h-2 rounded-full bg-blue-500" + style={{ width: `${percentage}%` }} + ></div> + </div> + <span className="text-black text-sm">{percentage}%</span> </div> )} - </div> - <div className="text-xs mt-2"> - <label className="block"> - {props.title === "New Template" - ? "Template Title" - : "Document Title"} - <span className="text-red-500 text-[13px]">*</span> - </label> - <input - name="Name" - className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" - value={formData.Name} - onChange={(e) => handleStrInput(e)} - required - /> - </div> - <div className="text-xs mt-2"> - <label className="block">Description</label> - <input - name="Description" - className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" - value={formData.Description} - onChange={(e) => handleStrInput(e)} - /> - </div> - {props.signers && ( - <SignersInput onChange={handleSigners} isReset={isReset} required /> - )} - <div className="text-xs mt-2"> - <label className="block"> - Note<span className="text-red-500 text-[13px]">*</span> - </label> - <input - name="Note" - className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" - value={formData.Note} - onChange={(e) => handleStrInput(e)} - required - /> - </div> - <SelectFolder - onSuccess={handleFolder} - folderCls={props.Cls} - isReset={isReset} - /> - - {props.title === "Request Signatures" && ( + {isDecrypting && ( + <div className="flex items-center gap-x-2"> + <span className="text-black text-sm"> + Decrypting pdf please wait... + </span> + </div> + )} + <div className="text-xs"> + <label className="block"> + {`File (pdf, png, jpg, jpeg${ + isEnableSubscription ? ", docx)" : ")" + }`} + <span className="text-red-500 text-[13px]">*</span> + </label> + {fileupload.length > 0 ? ( + <div className="flex gap-2 justify-center items-center"> + <div className="flex justify-between items-center px-2 py-2 w-full font-bold rounded border-[1px] border-[#ccc] text-gray-500 bg-white text-[13px]"> + <div className="break-all"> + file selected : {getFileName(fileupload)} + </div> + <div + onClick={() => setFileUpload("")} + className="cursor-pointer px-[10px] text-[20px] font-bold bg-white text-red-500" + > + <i className="fa-solid fa-xmark"></i> + </div> + </div> + {process.env.REACT_APP_DROPBOX_API_KEY && ( + <DropboxChooser + onSuccess={dropboxSuccess} + onCancel={dropboxCancel} + /> + )} + </div> + ) : ( + <div className="flex gap-2 justify-center items-center"> + <input + type="file" + className="bg-white px-2 py-1.5 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" + onChange={(e) => handleFileInput(e)} + accept={ + isEnableSubscription + ? "application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/png,image/jpeg" + : "application/pdf,image/png,image/jpeg" + } + required + /> + {process.env.REACT_APP_DROPBOX_API_KEY && ( + <DropboxChooser + onSuccess={dropboxSuccess} + onCancel={dropboxCancel} + /> + )} + </div> + )} + </div> <div className="text-xs mt-2"> <label className="block"> - Time To Complete (Days) + {props.title === "New Template" + ? "Template Title" + : "Document Title"} <span className="text-red-500 text-[13px]">*</span> </label> <input - type="number" - name="TimeToCompleteDays" + name="Name" className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" - value={formData.TimeToCompleteDays} + value={formData.Name} onChange={(e) => handleStrInput(e)} required /> </div> - )} - {props.title !== "Sign Yourself" && ( <div className="text-xs mt-2"> - <label className="block">Send In Order</label> - <div className="flex items-center gap-2 ml-2 mb-1"> + <label className="block">Description</label> + <input + name="Description" + className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" + value={formData.Description} + onChange={(e) => handleStrInput(e)} + /> + </div> + {props.signers && ( + <SignersInput + onChange={handleSigners} + isReset={isReset} + required + /> + )} + <div className="text-xs mt-2"> + <label className="block"> + Note<span className="text-red-500 text-[13px]">*</span> + </label> + <input + name="Note" + className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" + value={formData.Note} + onChange={(e) => handleStrInput(e)} + required + /> + </div> + <SelectFolder + onSuccess={handleFolder} + folderCls={props.Cls} + isReset={isReset} + /> + + {props.title === "Request Signatures" && ( + <div className="text-xs mt-2"> + <label className="block"> + Time To Complete (Days) + <span className="text-red-500 text-[13px]">*</span> + </label> <input - type="radio" - value={"true"} - name="SendinOrder" - checked={formData.SendinOrder === "true"} - className="" - onChange={handleStrInput} + type="number" + name="TimeToCompleteDays" + className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" + value={formData.TimeToCompleteDays} + onChange={(e) => handleStrInput(e)} + required /> - <div className="text-center">Yes</div> </div> - <div className="flex items-center gap-2 ml-2 mb-1"> - <input - type="radio" - value={"false"} - name="SendinOrder" - checked={formData.SendinOrder === "false"} - onChange={handleStrInput} - /> - <div className="text-center">No</div> + )} + {props.title !== "Sign Yourself" && ( + <div className="text-xs mt-2"> + <label className="block">Send In Order</label> + <div className="flex items-center gap-2 ml-2 mb-1"> + <input + type="radio" + value={"true"} + name="SendinOrder" + checked={formData.SendinOrder === "true"} + className="" + onChange={handleStrInput} + /> + <div className="text-center">Yes</div> + </div> + <div className="flex items-center gap-2 ml-2 mb-1"> + <input + type="radio" + value={"false"} + name="SendinOrder" + checked={formData.SendinOrder === "false"} + onChange={handleStrInput} + /> + <div className="text-center">No</div> + </div> + </div> + )} + <div className="flex items-center mt-3 gap-2 text-white"> + <button + className={`${ + isSubmit && "cursor-progress" + } bg-[#1ab6ce] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 focus:outline-none`} + type="submit" + disabled={isSubmit} + > + Next + </button> + <div + className="cursor-pointer bg-[#188ae2] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 text-center ml-[2px] focus:outline-none" + onClick={() => handleReset()} + > + Reset </div> </div> - )} - <div className="flex items-center mt-3 gap-2 text-white"> - <button - className={`${ - isSubmit && "cursor-progress" - } bg-[#1ab6ce] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 focus:outline-none`} - type="submit" - disabled={isSubmit} - > - Next - </button> - <div - className="cursor-pointer bg-[#188ae2] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 text-center ml-[2px] focus:outline-none" - onClick={() => handleReset()} - > - Reset - </div> - </div> - </form> + </form> + </> )} </div> ); diff --git a/apps/OpenSign/src/pages/PdfRequestFiles.js b/apps/OpenSign/src/pages/PdfRequestFiles.js index 50be8ced4..4b4cb0781 100644 --- a/apps/OpenSign/src/pages/PdfRequestFiles.js +++ b/apps/OpenSign/src/pages/PdfRequestFiles.js @@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from "react"; import { isEnableSubscription, themeColor } from "../constant/const"; import { PDFDocument } from "pdf-lib"; import "../styles/signature.css"; +import Parse from "parse"; import axios from "axios"; import loader from "../assets/images/loader2.gif"; import { DndProvider } from "react-dnd"; @@ -25,7 +26,8 @@ import { replaceMailVaribles, fetchSubscription, convertPdfArrayBuffer, - contractUsers + contractUsers, + handleSendOTP } from "../constant/Utils"; import Loader from "../primitives/LoaderWithMsg"; import HandleError from "../primitives/HandleError"; @@ -35,6 +37,7 @@ import PdfDeclineModal from "../primitives/PdfDeclineModal"; import Title from "../components/Title"; import DefaultSignature from "../components/pdf/DefaultSignature"; import ModalUi from "../primitives/ModalUi"; +import VerifyEmail from "../components/pdf/VerifyEmail"; function PdfRequestFiles() { const { docId } = useParams(); @@ -57,10 +60,12 @@ function PdfRequestFiles() { const imageRef = useRef(null); const [handleError, setHandleError] = useState(); const [selectWidgetId, setSelectWidgetId] = useState(""); + const [otpLoader, setOtpLoader] = useState(false); 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(); @@ -100,6 +105,9 @@ function PdfRequestFiles() { const [isSubscriptionExpired, setIsSubscriptionExpired] = useState(false); const [extUserId, setExtUserId] = useState(""); const [pdfArrayBuffer, setPdfArrayBuffer] = useState(""); + const [isEmailVerified, setIsEmailVerified] = useState(true); + const [isVerifyModal, setIsVerifyModal] = useState(false); + const [otp, setOtp] = useState(""); const divRef = useRef(null); const isMobile = window.innerWidth < 767; const rowLevel = @@ -139,6 +147,44 @@ function PdfRequestFiles() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [divRef.current]); + //function to use resend otp for email verification + const handleResend = async (e) => { + e.preventDefault(); + setOtpLoader(true); + await handleSendOTP(Parse.User.current().getEmail()); + setOtpLoader(false); + alert("OTP sent on you email"); + }; + //`handleVerifyEmail` function is used to verify email with otp + const handleVerifyEmail = async (e) => { + e.preventDefault(); + setOtpLoader(true); + try { + const resEmail = await Parse.Cloud.run("verifyemail", { + otp: otp, + email: Parse.User.current().getEmail() + }); + if (resEmail?.message === "Email is verified.") { + setIsEmailVerified(true); + } else if (resEmail?.message === "Email is already verified.") { + setIsEmailVerified(true); + } + setOtp(""); + alert(resEmail.message); + setIsVerifyModal(false); + // handleRecipientSign(); + } catch (error) { + alert(error.message); + } finally { + setOtpLoader(false); + } + }; + + //`handleVerifyBtn` function is used to send otp on user mail + const handleVerifyBtn = async () => { + setIsVerifyModal(true); + await handleSendOTP(Parse.User.current().getEmail()); + }; async function checkIsSubscribed(extUserId, contactId) { const isGuestSign = location.pathname.includes("/load/") || false; const res = await fetchSubscription(extUserId, contactId, isGuestSign); @@ -238,6 +284,33 @@ function PdfRequestFiles() { setExpiredDate(expireDateFormat); } + const isGuestSign = location.pathname.includes("/load/"); + if ( + !isGuestSign && + !isCompleted && + !declined && + currDate < expireUpdateDate + ) { + const currentUser = JSON.parse(JSON.stringify(Parse.User.current())); + let isEmailVerified; + isEmailVerified = currentUser?.emailVerified; + if (isEmailVerified) { + setIsEmailVerified(isEmailVerified); + } else { + try { + const userQuery = new Parse.Query(Parse.User); + const user = await userQuery.get(currentUser.objectId, { + sessionToken: localStorage.getItem("accesstoken") + }); + if (user) { + isEmailVerified = user?.get("emailVerified"); + setIsEmailVerified(isEmailVerified); + } + } catch (e) { + setHandleError("Error: Something went wrong!"); + } + } + } if (documentData.length > 0) { const checkDocIdExist = documentData[0].AuditTrail && @@ -1179,6 +1252,18 @@ function PdfRequestFiles() { headMsg="Document Expired!" bodyMssg={`This document expired on ${expiredDate} and is no longer available to sign.`} /> + {!isEmailVerified && ( + <VerifyEmail + isVerifyModal={isVerifyModal} + setIsVerifyModal={setIsVerifyModal} + handleVerifyEmail={handleVerifyEmail} + setOtp={setOtp} + otp={otp} + otpLoader={otpLoader} + handleVerifyBtn={handleVerifyBtn} + handleResend={handleResend} + /> + )} <ModalUi headerColor={defaultSignImg ? themeColor : "#dc3545"} diff --git a/apps/OpenSign/src/pages/PlaceHolderSign.js b/apps/OpenSign/src/pages/PlaceHolderSign.js index d161c032b..1eaddc43c 100644 --- a/apps/OpenSign/src/pages/PlaceHolderSign.js +++ b/apps/OpenSign/src/pages/PlaceHolderSign.js @@ -251,6 +251,7 @@ function PlaceHolderSign() { navigate(`/subscription`); } } + //function for get document details const getDocumentDetails = async () => { fetchTenantDetails(); diff --git a/apps/OpenSign/src/pages/SignyourselfPdf.js b/apps/OpenSign/src/pages/SignyourselfPdf.js index 2b276d776..6ad106d93 100644 --- a/apps/OpenSign/src/pages/SignyourselfPdf.js +++ b/apps/OpenSign/src/pages/SignyourselfPdf.js @@ -1,6 +1,7 @@ import React, { useState, useRef, useEffect } from "react"; import { PDFDocument } from "pdf-lib"; import "../styles/signature.css"; +import Parse from "parse"; import { isEnableSubscription, themeColor } from "../constant/const"; import axios from "axios"; import Loader from "../primitives/LoaderWithMsg"; @@ -31,7 +32,8 @@ import { checkIsSubscribed, convertPdfArrayBuffer, fetchImageBase64, - changeImageWH + changeImageWH, + handleSendOTP } from "../constant/Utils"; import { useParams } from "react-router-dom"; import Tour from "reactour"; @@ -44,6 +46,7 @@ import TourContentWithBtn from "../primitives/TourContentWithBtn"; import Title from "../components/Title"; import ModalUi from "../primitives/ModalUi"; import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption"; +import VerifyEmail from "../components/pdf/VerifyEmail"; //For signYourself inProgress section signer can add sign and complete doc sign. function SignYourSelf() { @@ -91,6 +94,7 @@ function SignYourSelf() { const [containerWH, setContainerWH] = useState({}); const [isPageCopy, setIsPageCopy] = useState(false); const [selectWidgetId, setSelectWidgetId] = useState(""); + const [otpLoader, setOtpLoader] = useState(false); const [showAlreadySignDoc, setShowAlreadySignDoc] = useState({ status: false }); @@ -107,6 +111,9 @@ function SignYourSelf() { const [isCompleted, setIsCompleted] = useState(false); const [pdfArrayBuffer, setPdfArrayBuffer] = useState(""); const [activeMailAdapter, setActiveMailAdapter] = useState(""); + const [isEmailVerified, setIsEmailVerified] = useState(true); + const [isVerifyModal, setIsVerifyModal] = useState(false); + const [otp, setOtp] = useState(""); const divRef = useRef(null); const nodeRef = useRef(null); const [, drop] = useDrop({ @@ -226,6 +233,29 @@ function SignYourSelf() { setSignBtnPosition([]); } } + + if (!isCompleted) { + //check current user email verified or not + const currentUser = JSON.parse(JSON.stringify(Parse.User.current())); + let isEmailVerified; + isEmailVerified = currentUser?.emailVerified; + if (isEmailVerified) { + setIsEmailVerified(isEmailVerified); + } else { + try { + const userQuery = new Parse.Query(Parse.User); + const user = await userQuery.get(currentUser.objectId, { + sessionToken: localStorage.getItem("accesstoken") + }); + if (user) { + isEmailVerified = user?.get("emailVerified"); + setIsEmailVerified(isEmailVerified); + } + } catch (e) { + setHandleError("Error: Something went wrong!"); + } + } + } } else if ( documentData === "Error: Something went wrong!" || (documentData.result && documentData.result.error) @@ -533,6 +563,44 @@ function SignYourSelf() { setSelectWidgetId(key); setSignKey(key); }; + + //`handleResend` function is used to resend otp for email verification + const handleResend = async (e) => { + e.preventDefault(); + setOtpLoader(true); + await handleSendOTP(Parse.User.current().getEmail()); + setOtpLoader(false); + alert("OTP sent on you email"); + }; + //`handleVerifyEmail` function is used to verify email with otp + const handleVerifyEmail = async (e) => { + e.preventDefault(); + setOtpLoader(true); + try { + const resEmail = await Parse.Cloud.run("verifyemail", { + otp: otp, + email: Parse.User.current().getEmail() + }); + if (resEmail?.message === "Email is verified.") { + setIsEmailVerified(true); + } else if (resEmail?.message === "Email is already verified.") { + setIsEmailVerified(true); + } + setOtp(""); + alert(resEmail.message); + setIsVerifyModal(false); + // handleRecipientSign(); + } catch (error) { + alert(error.message); + } finally { + setOtpLoader(false); + } + }; + //`handleVerifyBtn` function is used to send otp on user mail + const handleVerifyBtn = async () => { + setIsVerifyModal(true); + await handleSendOTP(Parse.User.current().getEmail()); + }; //function for send placeholder's co-ordinate(x,y) position embed signature url or stamp url async function embedWidgetsData() { let showAlert = false; @@ -1037,6 +1105,18 @@ function SignYourSelf() { )} <div className="signatureContainer" ref={divRef}> + {!isEmailVerified && ( + <VerifyEmail + isVerifyModal={isVerifyModal} + setIsVerifyModal={setIsVerifyModal} + handleVerifyEmail={handleVerifyEmail} + setOtp={setOtp} + otp={otp} + otpLoader={otpLoader} + handleVerifyBtn={handleVerifyBtn} + handleResend={handleResend} + /> + )} {/* this component used for UI interaction and show their functionality */} {pdfLoadFail && !checkTourStatus && ( <Tour diff --git a/apps/OpenSign/src/pages/UserProfile.js b/apps/OpenSign/src/pages/UserProfile.js index 69b491468..3d3f6f483 100644 --- a/apps/OpenSign/src/pages/UserProfile.js +++ b/apps/OpenSign/src/pages/UserProfile.js @@ -9,7 +9,7 @@ import axios from "axios"; import PremiumAlertHeader from "../primitives/PremiumAlertHeader"; import Tooltip from "../primitives/Tooltip"; import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const"; -import { checkIsSubscribed } from "../constant/Utils"; +import { checkIsSubscribed, handleSendOTP } from "../constant/Utils"; import Upgrade from "../primitives/Upgrade"; import ModalUi from "../primitives/ModalUi"; @@ -53,9 +53,26 @@ function UserProfile() { if (HeaderDocId) { setIsDisableDocId(HeaderDocId); } - const isEmailVerified = Parse.User.current()?.attributes?.emailVerified; - setIsEmailVerified(isEmailVerified); - setIsLoader(false); + const currentUser = JSON.parse(JSON.stringify(Parse.User.current())); + let isEmailVerified = currentUser?.emailVerified || false; + if (isEmailVerified) { + setIsEmailVerified(isEmailVerified); + setIsLoader(false); + } else { + try { + const userQuery = new Parse.Query(Parse.User); + const user = await userQuery.get(currentUser.objectId, { + sessionToken: localStorage.getItem("accesstoken") + }); + if (user) { + isEmailVerified = user?.get("emailVerified"); + setIsEmailVerified(isEmailVerified); + setIsLoader(false); + } + } catch (e) { + alert("something went wrong!"); + } + } }; const handleSubmit = async (e) => { e.preventDefault(); @@ -185,27 +202,15 @@ function UserProfile() { const handleDisableDocId = () => { setIsDisableDocId((prevChecked) => !prevChecked); }; + //`handleVerifyBtn` function is used to send otp on user mail const handleVerifyBtn = async () => { setIsVerifyModal(true); - await handleSendOTP(); + await handleSendOTP(Parse.User.current().getEmail()); }; const handleCloseVerifyModal = async () => { setIsVerifyModal(false); }; - const handleSendOTP = async () => { - try { - let url = `${parseBaseUrl}functions/SendOTPMailV1`; - const headers = { - "Content-Type": "application/json", - "X-Parse-Application-Id": parseAppId - }; - const body = { email: Parse.User.current().getEmail() }; - await axios.post(url, body, { headers: headers }); - } catch (error) { - alert(error.message); - } - }; - + //`handleVerifyEmail` function is used to verify email with otp const handleVerifyEmail = async (e) => { e.preventDefault(); setOtpLoader(true); @@ -228,7 +233,8 @@ function UserProfile() { setOtpLoader(false); } }; - const handleReset = async (e) => { + //function to use resend otp for email verification + const handleResend = async (e) => { e.preventDefault(); setOtpLoader(true); await handleSendOTP(); @@ -497,10 +503,11 @@ function UserProfile() { <div className="px-6 py-3"> <label className="mb-2">Enter OTP</label> <input + required type="tel" pattern="[0-9]{4}" className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs" - placeholder="Enter OTP sent on mail" + placeholder="Enter OTP received over email" value={otp} onChange={(e) => setOtp(e.target.value)} /> @@ -512,7 +519,7 @@ function UserProfile() { </button> <button className={`${rejectBtn} ml-2`} - onClick={(e) => handleReset(e)} + onClick={(e) => handleResend(e)} > Resend </button> diff --git a/apps/OpenSignServer/index.js b/apps/OpenSignServer/index.js index 2d51c739f..d6ba7ad9b 100644 --- a/apps/OpenSignServer/index.js +++ b/apps/OpenSignServer/index.js @@ -166,7 +166,6 @@ app.use(cors()); app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ limit: '50mb', extended: true })); app.use(function (req, res, next) { - console.log('ip', getUserIP(req)); req.headers['x-real-ip'] = getUserIP(req); next(); });