feat: quick send feature to create & send multiple documents at a time

This commit is contained in:
prafull-opensignlabs
2024-05-24 16:29:03 +05:30
parent 890c3d2b1f
commit 6c77bbfe75
9 changed files with 575 additions and 442 deletions
+28 -24
View File
@@ -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,
@@ -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 && (
+178 -162
View File
@@ -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 (
<div style={{ padding: "2rem", background: "white" }}>
{isLoading ? (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100vh",
flexDirection: "column"
}}
>
<img
alt="no img"
src={loader}
style={{ width: "80px", height: "80px" }}
/>
<div className="flex flex-col justify-center items-center h-[100vh]">
<img className="w-[80px] h-[80px]" alt="loader" src={loader} />
<span style={{ fontSize: "13px", color: "gray" }}>
{isLoading.message}
</span>
</div>
) : (
<div
style={{
margin: "10px",
border: "0.5px solid #c5c7c9",
padding: "30px",
boxShadow: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"
}}
>
<div className="main_head">
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
{appLogo && (
<img
src={appLogo}
className="object-contain h-full"
alt="logo"
/>
)}
</div>
<div className="m-1 md:m-2 border-[0.5px] border-[#c5c7c9] p-[30px] shadow-md">
<div className="md:w-[250px] md:h-[66px] inline-block overflow-hidden mt-2 mb-11">
{appLogo && (
<img src={appLogo} className="object-contain h-full" alt="logo" />
)}
</div>
{!EnterOTP ? (
<div className="row">
<div className="col-sm-6 KLO">
<span className="welcomeText">Welcome Back !</span>
<br />
<span className="KNLO">
Verification code is sent to your email
</span>
<div className="card card-box" style={{ borderRadius: "0px" }}>
<div className="card-body">
{contactId ? (
<>
{!EnterOTP ? (
<div className="w-full md:w-[50%]">
<h1 className="text-2xl md:text-[30px]">Welcome Back!</h1>
<legend className="text-[12px] text-[#878787] mt-2">
Verification code is sent to your email
</legend>
<div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
<input
type="email"
name="mobile"
value={email}
className="outline-none px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs disabled:bg-[#e0e5f5]"
disabled
className="loginInput"
/>
<br />
</div>
<div className="mt-4">
<button
className="w-[100px] text-white text-sm font-medium py-1 rounded-sm shadow-md hover:shadow-lg focus:outline-none"
style={{ background: themeColor }}
onClick={(e) => handleSendOTPBtn(e)}
disabled={loading}
>
{loading ? "Loading..." : "Send OTP"}
</button>
</div>
</div>
<div className="btnContainer">
{loading ? (
<button
type="button"
style={{
background: themeColor,
color: "white"
}}
className="verifyBtn"
disabled
>
<span
className="spinner-border spinner-border-sm "
role="status"
aria-hidden="true"
></span>
Loading...
</button>
) : (
<button
className="verifyBtn"
style={{
background: themeColor,
color: "white",
marginLeft: "0px !important"
}}
onClick={(e) => SendOtp(e)}
>
Send OTP
</button>
)}
</div>
</div>
</div>
) : (
<div className="row">
<div className="col-sm-6 KLO">
<span className="welcomeText">Welcome Back !</span>
<br />
<span className="KNLO">You will get a OTP via Email</span>
<div className="card card-box">
<div className="card-body">
<label>Enter Verification Code</label>
) : (
<form className="w-full md:w-[50%]" onSubmit={VerifyOTP}>
<h1 className="text-2xl md:text-[30px]">Welcome Back!</h1>
<legend className="text-[12px] text-[#878787] mt-2">
You will get a OTP via Email
</legend>
<div className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
<p>Enter Verification Code</p>
<input
type="number"
className="loginInput"
className="mt-1 outline-none px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs bg-[#e0e5f5]"
name="OTP"
value={OTP}
onChange={handleChange}
onChange={(e) => setOTP(e.target.value)}
/>
<br />
</div>
</div>
<div>
{loading ? (
<div className="mt-4">
<button
style={{
background: themeColor,
color: "white"
}}
className="verifyBtn"
type="button"
disabled
style={{ background: themeColor }}
className="w-[100px] text-white text-sm font-medium py-1 rounded-sm shadow-md hover:shadow-lg focus:outline-none"
type="submit"
disabled={loading}
>
<span
className="spinner-border spinner-border-sm "
role="status"
aria-hidden="true"
></span>
Loading...
{loading ? "Loading..." : "Verify"}
</button>
) : (
<button
type="button"
onClick={(e) => VerifyOTP(e)}
style={{
background: themeColor,
color: "white"
}}
className="verifyBtn"
>
Verify
</button>
)}
</div>
</form>
)}
</>
) : (
<div className="w-full md:w-[50%]">
<h1 className="text-2xl md:text-[30px]">Welcome</h1>
<legend className="text-[12px] text-[#878787] mt-2">
Provide your details
</legend>
<form
className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md"
onSubmit={handleUserData}
>
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
Name
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={contact.name}
onChange={handleInputChange}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded disabled:bg-[#e0e5f5] focus:outline-none text-xs"
disabled={loading}
required
/>
</div>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
Email
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={contact.email}
onChange={handleInputChange}
className="px-3 py-2 w-full border-[1px] border-gray-300 disabled:bg-[#e0e5f5] rounded focus:outline-none text-xs"
required
disabled
/>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
Phone
</label>
<input
type="text"
name="phone"
value={contact.phone}
onChange={handleInputChange}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
disabled={loading}
/>
</div>
<div className="mt-4 flex justify-start">
<button
type="submit"
style={{ backgroundColor: modalSubmitBtnColor }}
className="mr-2 px-[20px] py-1.5 text-white rounded shadow-md text-center focus:outline-none "
disabled={loading}
>
{loading ? "Loading..." : "Next"}
</button>
</div>
</form>
</div>
)}
</div>
+5 -1
View File
@@ -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!") {
@@ -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) => {
</div>
);
};
// `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" && (
<div className="absolute -right-2 top-5 bg-white rounded shadow z-[20] overflow-hidden">
<div className="absolute -right-2 top-5 bg-white text-nowrap rounded shadow z-[20] overflow-hidden">
{act.subaction?.map((subact) => (
<div
key={subact.btnId}
className="hover:bg-gray-300 cursor-pointer px-2 py-1.5 flex justify-start items-center text-black"
className="hover:bg-gray-300 cursor-pointer px-2 py-1.5 flex justify-start items-center text-black"
onClick={() =>
handleActionBtn(subact, item)
}
@@ -898,17 +925,34 @@ const ReportTable = (props) => {
</div>
</ModalUi>
)}
{isBulkSend[`${item.objectId}`] && (
{isBulkSend[item.objectId] && (
<ModalUi
isOpen
title={"Quick send"}
handleClose={() => setIsBulkSend({})}
>
<BulkSendUi
Placeholders={placeholders}
item={templateDeatils}
handleClose={handleQuickSendClose}
/>
{isLoader[item.objectId] ? (
<div className="w-full h-[100px] md:h-[100px] rounded-b-md flex justify-center items-center bg-black bg-opacity-30 z-30">
<div
style={{ fontSize: "45px", color: "#3dd3e0" }}
className="loader-37"
></div>
</div>
) : (
<>
{isDocErr ? (
<div className="text-black bg-white w-full h-[80px] md:h-[100px] text-sm md:text-xl flex justify-center items-center">
Please add Signers or Roles in template
</div>
) : (
<BulkSendUi
Placeholders={placeholders}
item={templateDeatils}
handleClose={handleQuickSendClose}
/>
)}
</>
)}
</ModalUi>
)}
{isShare[item.objectId] && (
@@ -1158,16 +1202,6 @@ const ReportTable = (props) => {
closePopup={handleContactFormModal}
/>
</ModalUi>
<ModalUi
headColor={"#dc3545"}
isOpen={isDocErr}
title={"Receipent required"}
handleClose={() => setIsDocErr(false)}
>
<div style={{ height: "100%", padding: 20 }}>
<p>Please add receipent in template!</p>
</div>
</ModalUi>
</div>
</div>
);
-197
View File
@@ -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;
}
}
+2
View File
@@ -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);
@@ -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,
@@ -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;
}
}