From 6c77bbfe751eede8d00a89235e1467dd3e69d0fb Mon Sep 17 00:00:00 2001 From: prafull-opensignlabs Date: Fri, 24 May 2024 16:29:03 +0530 Subject: [PATCH] feat: quick send feature to create & send multiple documents at a time --- apps/OpenSign/src/components/BulkSendUi.js | 52 +-- .../shared/fields/SuggestionInput.js | 2 +- apps/OpenSign/src/pages/GuestLogin.js | 340 +++++++++--------- apps/OpenSign/src/pages/PdfRequestFiles.js | 6 +- .../src/primitives/GetReportDisplay.js | 124 ++++--- apps/OpenSign/src/styles/loginPage.css | 197 ---------- apps/OpenSignServer/cloud/main.js | 2 + .../cloud/parsefunction/createBatchDocs.js | 18 +- .../cloud/parsefunction/linkContactToDoc.js | 276 ++++++++++++++ 9 files changed, 575 insertions(+), 442 deletions(-) delete mode 100644 apps/OpenSign/src/styles/loginPage.css create mode 100644 apps/OpenSignServer/cloud/parsefunction/linkContactToDoc.js diff --git a/apps/OpenSign/src/components/BulkSendUi.js b/apps/OpenSign/src/components/BulkSendUi.js index ee93c505a..3b84ac5ea 100644 --- a/apps/OpenSign/src/components/BulkSendUi.js +++ b/apps/OpenSign/src/components/BulkSendUi.js @@ -8,6 +8,8 @@ const BulkSendUi = (props) => { const formRef = useRef(null); const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false); const [isSubmit, setIsSubmit] = useState(false); + const [allowedForm, setAllowedForm] = useState(0); + const allowedSigners = 50; useEffect(() => { if (scrollOnNextUpdate && formRef.current) { formRef.current.scrollIntoView({ @@ -37,45 +39,48 @@ const BulkSendUi = (props) => { } }); setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]); + const totalForms = Math.floor(allowedSigners / users?.length); + setAllowedForm(totalForms); } })(); // eslint-disable-next-line }, []); const handleInputChange = (index, signer, fieldIndex) => { - console.log("index", index); - console.log("signer", signer); - console.log("fieldIndex", fieldIndex); - const newForms = [...forms]; newForms[index].fields[fieldIndex].email = signer?.Email ? signer?.Email : signer || ""; newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : ""; - console.log("newForms[index] ", newForms[index]); setForms(newForms); }; const handleAddForm = (e) => { e.preventDefault(); - if (props?.Placeholders.length > 0) { - let newForm = []; - props?.Placeholders?.forEach((element) => { - if (!element.signerObjId) { - newForm = [ - ...newForm, - { - fieldId: element.Id, - email: "", - label: element.Role, - signer: {} - } - ]; - } - }); - setForms([...forms, { Id: formId, fields: newForm }]); + // Check if the quick send limit has been reached + if (forms?.length < allowedForm) { + if (props?.Placeholders.length > 0) { + let newForm = []; + props?.Placeholders?.forEach((element) => { + if (!element.signerObjId) { + newForm = [ + ...newForm, + { + fieldId: element.Id, + email: "", + label: element.Role, + signer: {} + } + ]; + } + }); + setForms([...forms, { Id: formId, fields: newForm }]); + } + setFormId(formId + 1); + setScrollOnNextUpdate(true); + } else { + // If the limit has been reached, throw an error with the appropriate message + alert("Quick send reached limit."); } - setFormId(formId + 1); - setScrollOnNextUpdate(true); }; const handleRemoveForm = (index) => { @@ -102,7 +107,6 @@ const BulkSendUi = (props) => { ); // If a matching field is found, update the email value in the placeholder const signer = field?.signer?.objectId ? field.signer : {}; - console.log("signer ", signer); if (field) { return { ...placeholder, diff --git a/apps/OpenSign/src/components/shared/fields/SuggestionInput.js b/apps/OpenSign/src/components/shared/fields/SuggestionInput.js index 37f2c0b12..6efaf66a8 100644 --- a/apps/OpenSign/src/components/shared/fields/SuggestionInput.js +++ b/apps/OpenSign/src/components/shared/fields/SuggestionInput.js @@ -67,7 +67,7 @@ const SuggestionInput = (props) => { value={inputValue} onChange={handleInputChange} placeholder="Enter text..." - className="w-full border border-gray-300 p-2 text-black rounded" + className="w-full border-[1px] border-gray-400 p-2 text-black rounded" required={props.required} /> {showSuggestions && ( diff --git a/apps/OpenSign/src/pages/GuestLogin.js b/apps/OpenSign/src/pages/GuestLogin.js index c0d4bfc19..6647e1ee5 100644 --- a/apps/OpenSign/src/pages/GuestLogin.js +++ b/apps/OpenSign/src/pages/GuestLogin.js @@ -1,16 +1,20 @@ import React, { useState, useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import "../styles/loginPage.css"; import loader from "../assets/images/loader2.gif"; import axios from "axios"; -import { isEnableSubscription, themeColor } from "../constant/const"; +import { + isEnableSubscription, + modalSubmitBtnColor, + themeColor +} from "../constant/const"; import { contractUsers, getAppLogo } from "../constant/Utils"; import logo from "../assets/images/logo.png"; import { appInfo } from "../constant/appinfo"; +import Parse from "parse"; function GuestLogin() { const { id, userMail, contactBookId, base64url } = useParams(); - let navigate = useNavigate(); + const navigate = useNavigate(); const [email, setEmail] = useState(userMail); const [OTP, setOTP] = useState(""); const [EnterOTP, setEnterOtp] = useState(false); @@ -20,9 +24,9 @@ function GuestLogin() { const [documentId, setDocumentId] = useState(id); const [contactId, setContactId] = useState(contactBookId); const [sendmail, setSendmail] = useState(); + const [contact, setContact] = useState({ name: "", phone: "", email: "" }); useEffect(() => { handleServerUrl(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -53,54 +57,51 @@ function GuestLogin() { //split url in array from '/' const checkSplit = decodebase64.split("/"); setDocumentId(checkSplit[0]); + setContact((prev) => ({ ...prev, email: checkSplit[1] })); setEmail(checkSplit[1]); - setContactId(checkSplit[2]); + const contactId = checkSplit?.[2]; setSendmail(checkSplit[3]); + if (!contactId) { + const params = { email: checkSplit[1], docId: checkSplit[0] }; + try { + const linkContactRes = await Parse.Cloud.run( + "linkcontacttodoc", + params + ); + // console.log("linkContactRes ", linkContactRes); + setContactId(linkContactRes?.contactId); + } catch (err) { + console.log("Err in link ext contact", err); + } + } else { + setContactId(checkSplit[2]); + } } 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(), - docId: documentId - }; - let Otp = await axios.post(url, body, { headers: headers }); - - if (Otp) { - setLoading(false); - setEnterOtp(true); - } - } catch (error) { - alert("something went wrong!"); + const SendOtp = async () => { + setLoading(true); + setEmail(email); + try { + const params = { email: email.toString(), docId: documentId }; + const Otp = await Parse.Cloud.run("SendOTPMailV1", params); + if (Otp) { + setLoading(false); + setEnterOtp(true); } - } else { + } catch (error) { alert("something went wrong!"); } }; + const handleSendOTPBtn = async (e) => { + e.preventDefault(); + await SendOtp(); + }; + //verify OTP send on via email const VerifyOTP = async (e) => { e.preventDefault(); @@ -164,156 +165,171 @@ function GuestLogin() { alert("Please Enter OTP!"); } }; - + const handleUserData = async (e) => { + e.preventDefault(); + const params = { ...contact, docId: documentId }; + try { + const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params); + // console.log("linkContactRes ", linkContactRes); + setContactId(linkContactRes.contactId); + setLoading(true); + setEnterOtp(true); + await SendOtp(); + } catch (err) { + setLoading(false); + alert("something went wron, please try agian later."); + console.log("Err in link ext contact", err); + } + }; + const handleInputChange = (e) => { + setContact((prev) => ({ ...prev, [e.target.name]: e.target.value })); + }; return (
{isLoading ? ( -
- no img +
+ loader {isLoading.message}
) : ( -
-
-
- {appLogo && ( - logo - )} -
+
+
+ {appLogo && ( + logo + )}
- - {!EnterOTP ? ( -
-
- Welcome Back ! -
- - Verification code is sent to your email - -
-
+ {contactId ? ( + <> + {!EnterOTP ? ( +
+

Welcome Back!

+ + Verification code is sent to your email + +
-
+
+
+
-
- {loading ? ( - - ) : ( - - )} -
-
-
- ) : ( -
-
- Welcome Back ! -
- You will get a OTP via Email -
-
- + ) : ( +
+

Welcome Back!

+ + You will get a OTP via Email + +
+

Enter Verification Code

setOTP(e.target.value)} /> - -
-
-
- {loading ? ( +
- ) : ( - - )} +
+ + )} + + ) : ( +
+

Welcome

+ + Provide your details + +
+
+ +
-
+
+ + +
+
+ + +
+
+ +
+
)}
diff --git a/apps/OpenSign/src/pages/PdfRequestFiles.js b/apps/OpenSign/src/pages/PdfRequestFiles.js index 7565d8669..a1387a5f2 100644 --- a/apps/OpenSign/src/pages/PdfRequestFiles.js +++ b/apps/OpenSign/src/pages/PdfRequestFiles.js @@ -41,6 +41,7 @@ import DefaultSignature from "../components/pdf/DefaultSignature"; import ModalUi from "../primitives/ModalUi"; import VerifyEmail from "../components/pdf/VerifyEmail"; import TourContentWithBtn from "../primitives/TourContentWithBtn"; +import { appInfo } from "../constant/appinfo"; function useQuery() { return new URLSearchParams(useLocation().search); } @@ -460,7 +461,10 @@ function PdfRequestFiles() { } else { //else condition to check current user exist in contracts_Users class and check tour message status //if not then check user exist in contracts_Contactbook class and check tour message status - const currentUser = JSON.parse(JSON.stringify(Parse.User.current())); + const localuser = localStorage.getItem( + `Parse/${appInfo.appId}/currentUser` + ); + const currentUser = JSON.parse(JSON.stringify(localuser)); const currentUserEmail = currentUser.email; const res = await contractUsers(currentUserEmail); if (res === "Error: Something went wrong!") { diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index f97402c81..9e5b01a63 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -43,7 +43,7 @@ const ReportTable = (props) => { const [isBulkSend, setIsBulkSend] = useState({}); const [templateDeatils, setTemplateDetails] = useState({}); const [placeholders, setPlaceholders] = useState([]); - const [isErr, setIsErr] = useState(false); + const [isLoader, setIsLoader] = useState({}); const startIndex = (currentPage - 1) * props.docPerPage; const { isMoreDocs, setIsNextRecord } = props; @@ -217,7 +217,6 @@ const ReportTable = (props) => { }; const handleActionBtn = (act, item) => { - console.log("item", item); if (act.action === "redirect") { handleURL(item, act); } else if (act.action === "delete") { @@ -560,7 +559,6 @@ const ReportTable = (props) => { }; const handleResendMail = async (e, doc, user) => { e.preventDefault(); - console.log("first"); setActLoader({ [user.objectId]: true }); const url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`; const headers = { @@ -618,44 +616,73 @@ const ReportTable = (props) => {
); }; + // `handleQuickSendClose` is trigger when bulk send component trigger close event const handleQuickSendClose = (status, count) => { setIsBulkSend({}); setIsAlert(true); if (status === "success") { if (count > 1) { - setAlertMsg(count + " Documents sent successfully!"); + setAlertMsg({ + type: "success", + message: count + " Document sent successfully!" + }); + setTimeout(() => setIsAlert(false), 1500); } else { - setAlertMsg(count + " Document sent successfully!"); + setAlertMsg({ + type: "success", + message: count + " Document sent successfully!" + }); + setTimeout(() => setIsAlert(false), 1500); } } else { - setIsAlert(true); - setIsErr(true); + setAlertMsg({ + type: "danger", + message: "Something went wrong, Please try again later!" + }); + setTimeout(() => setIsAlert(false), 1500); } }; + // `handleBulkSend` is used to open modal as well as fetch template + // and show Ui on the basis template response const handleBulkSend = async (template) => { - const params = { - templateId: template.objectId, - include: ["Placeholders.signerPtr"] - }; - const axiosRes = await axios.post( - `${localStorage.getItem("baseUrl")}functions/getTemplate`, - params, - { - headers: { - "Content-Type": "application/json", - "X-Parse-Application-Id": localStorage.getItem("parseAppId"), - sessionToken: localStorage.getItem("accesstoken") + setIsBulkSend({ [template.objectId]: true }); + setIsLoader({ [template.objectId]: true }); + try { + const params = { + templateId: template.objectId, + include: ["Placeholders.signerPtr"] + }; + const axiosRes = await axios.post( + `${localStorage.getItem("baseUrl")}functions/getTemplate`, + params, + { + headers: { + "Content-Type": "application/json", + "X-Parse-Application-Id": localStorage.getItem("parseAppId"), + sessionToken: localStorage.getItem("accesstoken") + } } + ); + const templateRes = axiosRes.data && axiosRes.data.result; + if (templateRes?.Placeholders?.length > 0) { + setPlaceholders(templateRes?.Placeholders); + setTemplateDetails(templateRes); + setIsLoader({}); + } else { + setIsLoader(false); + setIsDocErr(true); } - ); - const templateRes = axiosRes.data && axiosRes.data.result; - if (templateRes?.Placeholders?.length > 0) { - setPlaceholders(templateRes?.Placeholders); - setTemplateDetails(templateRes); - setIsBulkSend({ [templateRes.objectId]: true }); - } else { - setIsDocErr(true); + } catch (err) { + console.log("err in fetch template in bulk modal", err); + setIsBulkSend({}); + setIsDocErr(false); + setIsAlert(true); + setAlertMsg({ + type: "danger", + message: "Something went wrong, Please try again later!" + }); + setTimeout(() => setIsAlert(false), 1500); } }; return ( @@ -842,11 +869,11 @@ const ReportTable = (props) => { )} {isOption[item.objectId] && act.action === "option" && ( -
+
{act.subaction?.map((subact) => (
handleActionBtn(subact, item) } @@ -898,17 +925,34 @@ const ReportTable = (props) => {
)} - {isBulkSend[`${item.objectId}`] && ( + {isBulkSend[item.objectId] && ( setIsBulkSend({})} > - + {isLoader[item.objectId] ? ( +
+
+
+ ) : ( + <> + {isDocErr ? ( +
+ Please add Signers or Roles in template +
+ ) : ( + + )} + + )}
)} {isShare[item.objectId] && ( @@ -1158,16 +1202,6 @@ const ReportTable = (props) => { closePopup={handleContactFormModal} /> - setIsDocErr(false)} - > -
-

Please add receipent in template!

-
-
); diff --git a/apps/OpenSign/src/styles/loginPage.css b/apps/OpenSign/src/styles/loginPage.css deleted file mode 100644 index 0c18fe779..000000000 --- a/apps/OpenSign/src/styles/loginPage.css +++ /dev/null @@ -1,197 +0,0 @@ -.loginInput { - padding: 10px; - border-radius: 4px; - width: 100%; - font-size: 0.75rem; - border: 1px solid #e2dddd; - background-color: #e0e5f5; - color: black; - padding-left: 1rem !important; - padding-right: 1rem !important; - padding-top: 0.5rem !important; - font-family: inherit; - font-feature-settings: inherit; - font-variation-settings: inherit; - font-weight: inherit; - overflow: visible; -} -.loginInput:focus { - outline: none; - border: none; -} -.verifyBtn { - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18); - padding: 3px 30px; - color: white; - font-weight: 500 !important; - font-size: 14px !important; - border: none; -} -.verifyBtn:focus { - border: none; - outline: none; -} - -.verifyBtn:hover { - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18); - color: white; -} -.fakeimg { - height: 200px; - background: #aaa; -} - -.GTRY { - border: 1px solid #dee2e6; - margin-top: 75px; - padding-bottom: 20px; - background-color: #fff; -} - -.main_head { - width: 100%; - height: 66px; - background-color: #fff; - margin-bottom: 52px; - padding-top: 10px; -} - -.main-logo { - width: 250px; - height: 66px; - display: inline-block; -} - -.card-box { - background: #fff; - min-height: 50px; - box-shadow: 0 20px 20px rgba(0, 0, 0, 0.1); - position: relative; - margin-bottom: 20px; - transition: 0.5s; - border: 1px solid #f2f2f2; - border-radius: 7px; - margin-top: 10px; -} - -.LKJH { - display: flex; -} - -.GTR { - text-align: left; -} - -.JUI { - text-align: right; -} - -.btn-info { - color: #fff; - background-color: #15b4e9; - border-color: #15b4e9; -} - -.btn-info:hover { - color: #fff; - background-color: #15b4e9; - border-color: #15b4e9; -} - -.btn-reg { - color: #15b4e9; - background-color: #ffffff; - border-color: #15b4e9; -} - -.btn-reg:hover { - color: #15b4e9; - background-color: #ffffff; - border-color: #15b4e9; -} - -.SYTU { - display: flex; -} - -.MLKI { - float: right; - margin-bottom: 12px; -} - -.login-btn { - width: 100% !important; - display: block !important; -} - -@media only screen and (max-width: 667px) { - .MLKI { - float: inherit; - } - - .login-btn { - width: 210px !important; - } -} - -.MLKI a { - color: #212529; -} - -.KNLO { - color: #878787; - font-size: 13px; -} - -.form-check-label { - margin-left: 17px; - margin-bottom: 12px; -} -.welcomeText { - font-size: 30px; -} - -@media only screen and (max-width: 600px) { - .KLO1 { - display: none; - } - - .MLKI a { - font-size: 12px; - } - - .form-check-label { - font-size: 12px; - margin-bottom: 14px; - } - - .MKUY { - text-align: center; - padding-top: 10px; - } - - .main_head { - margin-bottom: 12px; - padding-top: 10px; - border-bottom: 1px dashed #eeeeee; - } -} -@media screen and (max-width: 366px) { - .main-logo { - width: 150px; - height: 46px; - display: inline-block; - } - .welcomeText { - font-size: 20px; - } - .KNLO { - color: #878787; - font-size: 10px; - } - .btnContainer { - display: flex; - justify-content: center; - align-items: center; - } -} diff --git a/apps/OpenSignServer/cloud/main.js b/apps/OpenSignServer/cloud/main.js index c0c2ae78c..32a047a3f 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -38,6 +38,7 @@ import VerifyEmail from './parsefunction/VerifyEmail.js'; import encryptedpdf from './parsefunction/encryptedPdf.js'; import { getSignedUrl } from './parsefunction/getSignedUrl.js'; import createBatchDocs from './parsefunction/createBatchDocs.js'; +import linkContactToDoc from './parsefunction/linkContactToDoc.js'; // This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic. Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); @@ -86,3 +87,4 @@ Parse.Cloud.define('verifyemail', VerifyEmail); Parse.Cloud.define('encryptedpdf', encryptedpdf); Parse.Cloud.define('getsignedurl', getSignedUrl); Parse.Cloud.define('batchdocuments', createBatchDocs); +Parse.Cloud.define('linkcontacttodoc', linkContactToDoc); diff --git a/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js b/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js index 1fb8e783e..b18f70a91 100644 --- a/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js +++ b/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js @@ -31,9 +31,9 @@ async function sendMail(document, sessionToken) { const hostUrl = baseUrl.origin; let encodeBase64; if (objectId) { - encodeBase64 = btoa(`${document.objectId}}/${signerMail[i].signerPtr.Email}/${objectId}`); + encodeBase64 = btoa(`${document.objectId}/${signerMail[i].signerPtr.Email}/${objectId}`); } else { - encodeBase64 = btoa(`${document.objectId}}/${signerMail[i].email}`); + encodeBase64 = btoa(`${document.objectId}/${signerMail[i].email}`); } let signPdf = `${hostUrl}/login/${encodeBase64}`; const openSignUrl = 'https://www.opensignlabs.com/'; @@ -93,18 +93,12 @@ export default async function createBatchDocs(request) { try { const requests = Documents.map(x => { const Signers = x.Signers; - const allSigner = x?.Placeholders?.map(item => { - const signer = Signers?.find(e => item?.signerPtr?.objectId === e?.objectId); - if (signer) { - return signer; - } else { - return item?.signerPtr?.objectId && item?.signerPtr; - } - }); + const allSigner = x?.Placeholders?.map( + item => Signers?.find(e => item?.signerPtr?.objectId === e?.objectId) || item?.signerPtr + ).filter(signer => Object.keys(signer).length > 0); const date = new Date(); const isoDate = date.toISOString(); let Acl = { [x.CreatedBy.objectId]: { read: true, write: true } }; - // console.log('allSigner ', allSigner); if (allSigner && allSigner.length > 0) { allSigner.forEach(x => { const obj = { [x.CreatedBy.objectId]: { read: true, write: true } }; @@ -146,7 +140,7 @@ export default async function createBatchDocs(request) { objectId: y.objectId, })), ACL: Acl, - RemindOnceInEvery: x.RemindOnceInEvery, + RemindOnceInEvery: x.RemindOnceInEvery || 5, AutomaticReminders: x.AutomaticReminders || false, TimeToCompleteDays: x.TimeToCompleteDays || 15, OriginIp: Ip, diff --git a/apps/OpenSignServer/cloud/parsefunction/linkContactToDoc.js b/apps/OpenSignServer/cloud/parsefunction/linkContactToDoc.js new file mode 100644 index 000000000..5b0f03442 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/linkContactToDoc.js @@ -0,0 +1,276 @@ +// `saveRoleContact` is used to save user in contracts_Guest role and create contact +const saveRoleContact = async contact => { + try { + const Role = new Parse.Query(Parse.Role); + const guestRole = await Role.equalTo('name', 'contracts_Guest').first(); + if (guestRole) { + // Check if the user is already in the role + const relation = guestRole.relation('users'); + const usersInRoleQuery = relation.query(); + usersInRoleQuery.equalTo('objectId', contact.UserId.objectId); + const usersInRole = await usersInRoleQuery.find(); + if (usersInRole.length > 0) { + console.log('User already added to Guest role.'); + } else { + relation.add({ __type: 'Pointer', className: '_User', id: contact.UserId.objectId }); + await guestRole.save(null, { useMasterKey: true }); + // console.log('User added to Guest role successfully.'); + } + } + } catch (err) { + console.log('err in role save', err); + } + const contactQuery = new Parse.Object('contracts_Contactbook'); + contactQuery.set('Name', contact.Name); + contactQuery.set('Email', contact.Email); + if (contact?.Phone) { + contactQuery.set('Phone', contact.Phone); + } + contactQuery.set('CreatedBy', contact.CreatedBy); + contactQuery.set('UserId', contact.UserId); + contactQuery.set('UserRole', 'contracts_Guest'); + contactQuery.set('TenantId', contact.TenantId); + contactQuery.set('IsDeleted', false); + const acl = new Parse.ACL(); + acl.setReadAccess(contact.CreatedBy.objectId, true); + acl.setWriteAccess(contact.CreatedBy.objectId, true); + acl.setReadAccess(contact.UserId.objectId, true); + acl.setWriteAccess(contact.UserId.objectId, true); + contactQuery.setACL(acl); + const contactRes = await contactQuery.save(); + if (contactRes) { + return contactRes; + } +}; + +// `linkContactToDoc` cloud function is used to create contact, add this contact in contracts_Guest role and +// save contact pointer in placeholder, signers and ACL of Document +export default async function linkContactToDoc(req) { + const email = req.params.email; + const docId = req.params.docId; + const name = req.params.name; + const phone = req.params.phone; + try { + if (docId) { + // Execute the query to get the document with the specified 'docId' + const docQuery = new Parse.Query('contracts_Document'); + docQuery.include('ExtUserPtr'); + const docRes = await docQuery.get(docId, { useMasterKey: true }); + // Check if the document was found; if not, throw an error indicating the document was not found + if (!docRes) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.'); + } + const _docRes = JSON.parse(JSON.stringify(docRes)); + const Placeholders = _docRes?.Placeholders || []; + const index = Placeholders?.findIndex(x => x.email && x.email === email); + if (index !== -1) { + // `signerObjectId` holds the value of `signerObjId` from the `Placeholders` array at the current index. + // This value is used to check if `signerObjId` is present or not. + const signerObjectId = Placeholders[index]?.signerObjId; + if (signerObjectId) { + return { contactId: signerObjectId }; + } + // Execute the query to check if a contact already exists in the 'contracts_Contactbook' class + const contactCls = new Parse.Query('contracts_Contactbook'); + contactCls.equalTo('Email', email); + contactCls.equalTo('CreatedBy', _docRes.CreatedBy); + contactCls.notEqualTo('IsDeleted', true); + const existContact = await contactCls.first({ useMasterKey: true }); + if (existContact) { + //update contact in placeholder, signers and update ACl in provide document + const updateDoc = new Parse.Object('contracts_Document'); + updateDoc.id = docId; + const signers = _docRes?.Signers || []; + signers.push({ + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: existContact.id, + }); + updateDoc.set('Signers', signers); + + Placeholders[index] = { + ...Placeholders[index], + signerObjId: existContact.id, + signerPtr: { + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: existContact.id, + }, + }; + updateDoc.set('Placeholders', Placeholders); + const Acl = docRes.getACL(); + Acl.setReadAccess(existContact.get('CreatedBy').id, true); + Acl.setWriteAccess(existContact.get('CreatedBy').id, true); + updateDoc.setACL(Acl); + // const parseData = JSON.parse(JSON.stringify(res)); + const resDoc = await updateDoc.save(null, { useMasterKey: true }); + if (resDoc) { + return { contactId: existContact.id }; + } + } else { + // Execute the query to check if a user already exists in the 'contracts_Users' class + const extUserQuery = new Parse.Query('contracts_Users'); + extUserQuery.equalTo('Email', email); + const extUser = await extUserQuery.first({ useMasterKey: true }); + if (extUser) { + const _extUser = JSON.parse(JSON.stringify(extUser)); + const contact = { + UserId: _extUser.UserId, + Name: _extUser.Name, + Email: email, + Phone: _extUser?.Phone ? _extUser.Phone : '', + CreatedBy: _docRes.CreatedBy, + TenantId: _docRes.ExtUserPtr.TenantId, + }; + // if user present on platform create contact on the basis of extended user details + const contactRes = await saveRoleContact(contact); + //update contact in placeholder, signers and update ACl in provide document + const updateDoc = new Parse.Object('contracts_Document'); + updateDoc.id = docId; + const signers = _docRes?.Signers || []; + signers.push({ + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: contactRes.id, + }); + updateDoc.set('Signers', signers); + + Placeholders[index] = { + ...Placeholders[index], + signerObjId: contactRes.id, + signerPtr: { + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: contactRes.id, + }, + }; + updateDoc.set('Placeholders', Placeholders); + const Acl = docRes.getACL(); + Acl.setReadAccess(_extUser.UserId.objectId, true); + Acl.setWriteAccess(_extUser.UserId.objectId, true); + updateDoc.setACL(Acl); + // const parseData = JSON.parse(JSON.stringify(res)); + const resDoc = await updateDoc.save(null, { useMasterKey: true }); + if (resDoc) { + return { contactId: contactRes.id }; + } + } else if (name) { + try { + // Execute the query to check if a user already exists in the '_User' class + const userQuery = new Parse.Query(Parse.User); + userQuery.equalTo('email', email); + const userRes = await userQuery.first({ useMasterKey: true }); + if (userRes) { + const contact = { + UserId: { __type: 'Pointer', className: '_User', objectId: userRes.id }, + Name: name, + Email: email, + Phone: phone, + CreatedBy: _docRes.CreatedBy, + TenantId: _docRes.ExtUserPtr.TenantId, + }; + // Create new contract on the basis provided contact details by user and userId from _User class + const contactRes = await saveRoleContact(contact); + //update contact in placeholder, signers and update ACl in provide document + const updateDoc = new Parse.Object('contracts_Document'); + updateDoc.id = docId; + const signers = _docRes?.Signers || []; + signers.push({ + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: contactRes.id, + }); + updateDoc.set('Signers', signers); + + Placeholders[index] = { + ...Placeholders[index], + signerObjId: contactRes.id, + signerPtr: { + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: contactRes.id, + }, + }; + updateDoc.set('Placeholders', Placeholders); + const Acl = docRes.getACL(); + Acl.setReadAccess(userRes.id, true); + Acl.setWriteAccess(userRes.id, true); + updateDoc.setACL(Acl); + // const parseData = JSON.parse(JSON.stringify(res)); + const resDoc = await updateDoc.save(null, { useMasterKey: true }); + if (resDoc) { + return { contactId: contactRes.id }; + } + } else { + // create new user in _User class on the basis of details provide by user + const _users = Parse.Object.extend('User'); + const _user = new _users(); + _user.set('name', name); + _user.set('username', email); + _user.set('email', email); + if (phone) { + _user.set('phone', phone); + _user.set('password', phone); + } else { + _user.set('password', email); + } + const newUserRes = await _user.save(); + const contact = { + UserId: { __type: 'Pointer', className: '_User', objectId: newUserRes.id }, + Name: name, + Email: email, + Phone: phone, + CreatedBy: _docRes.CreatedBy, + TenantId: _docRes.ExtUserPtr.TenantId, + }; + // Create new contract on the basis provided contact details by user and userId from _User class + const contactRes = await saveRoleContact(contact); + //update contact in placeholder, signers and update ACl in provide document + const updateDoc = new Parse.Object('contracts_Document'); + updateDoc.id = docId; + const signers = _docRes?.Signers || []; + signers.push({ + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: contactRes.id, + }); + updateDoc.set('Signers', signers); + + Placeholders[index] = { + ...Placeholders[index], + signerObjId: contactRes.id, + signerPtr: { + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: contactRes.id, + }, + }; + updateDoc.set('Placeholders', Placeholders); + const Acl = docRes.getACL(); + Acl.setReadAccess(newUserRes.id, true); + Acl.setWriteAccess(newUserRes.id, true); + updateDoc.setACL(Acl); + // const parseData = JSON.parse(JSON.stringify(res)); + const resDoc = await updateDoc.save(null, { useMasterKey: true }); + if (resDoc) { + return { contactId: contactRes.id }; + } + } + } catch (err) { + console.log('Err', err); + } + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); + } + } + } else { + throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'unauthorized'); + } + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.'); + } + } catch (err) { + console.log('err in linkcontacttodoc', err); + throw err; + } +}