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 formRef = useRef(null);
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false); const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
const [isSubmit, setIsSubmit] = useState(false); const [isSubmit, setIsSubmit] = useState(false);
const [allowedForm, setAllowedForm] = useState(0);
const allowedSigners = 50;
useEffect(() => { useEffect(() => {
if (scrollOnNextUpdate && formRef.current) { if (scrollOnNextUpdate && formRef.current) {
formRef.current.scrollIntoView({ formRef.current.scrollIntoView({
@@ -37,45 +39,48 @@ const BulkSendUi = (props) => {
} }
}); });
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]); setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
const totalForms = Math.floor(allowedSigners / users?.length);
setAllowedForm(totalForms);
} }
})(); })();
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
const handleInputChange = (index, signer, fieldIndex) => { const handleInputChange = (index, signer, fieldIndex) => {
console.log("index", index);
console.log("signer", signer);
console.log("fieldIndex", fieldIndex);
const newForms = [...forms]; const newForms = [...forms];
newForms[index].fields[fieldIndex].email = signer?.Email newForms[index].fields[fieldIndex].email = signer?.Email
? signer?.Email ? signer?.Email
: signer || ""; : signer || "";
newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : ""; newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : "";
console.log("newForms[index] ", newForms[index]);
setForms(newForms); setForms(newForms);
}; };
const handleAddForm = (e) => { const handleAddForm = (e) => {
e.preventDefault(); e.preventDefault();
if (props?.Placeholders.length > 0) { // Check if the quick send limit has been reached
let newForm = []; if (forms?.length < allowedForm) {
props?.Placeholders?.forEach((element) => { if (props?.Placeholders.length > 0) {
if (!element.signerObjId) { let newForm = [];
newForm = [ props?.Placeholders?.forEach((element) => {
...newForm, if (!element.signerObjId) {
{ newForm = [
fieldId: element.Id, ...newForm,
email: "", {
label: element.Role, fieldId: element.Id,
signer: {} email: "",
} label: element.Role,
]; signer: {}
} }
}); ];
setForms([...forms, { Id: formId, fields: newForm }]); }
});
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) => { const handleRemoveForm = (index) => {
@@ -102,7 +107,6 @@ const BulkSendUi = (props) => {
); );
// If a matching field is found, update the email value in the placeholder // If a matching field is found, update the email value in the placeholder
const signer = field?.signer?.objectId ? field.signer : {}; const signer = field?.signer?.objectId ? field.signer : {};
console.log("signer ", signer);
if (field) { if (field) {
return { return {
...placeholder, ...placeholder,
@@ -67,7 +67,7 @@ const SuggestionInput = (props) => {
value={inputValue} value={inputValue}
onChange={handleInputChange} onChange={handleInputChange}
placeholder="Enter text..." 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} required={props.required}
/> />
{showSuggestions && ( {showSuggestions && (
+178 -162
View File
@@ -1,16 +1,20 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import "../styles/loginPage.css";
import loader from "../assets/images/loader2.gif"; import loader from "../assets/images/loader2.gif";
import axios from "axios"; import axios from "axios";
import { isEnableSubscription, themeColor } from "../constant/const"; import {
isEnableSubscription,
modalSubmitBtnColor,
themeColor
} from "../constant/const";
import { contractUsers, getAppLogo } from "../constant/Utils"; import { contractUsers, getAppLogo } from "../constant/Utils";
import logo from "../assets/images/logo.png"; import logo from "../assets/images/logo.png";
import { appInfo } from "../constant/appinfo"; import { appInfo } from "../constant/appinfo";
import Parse from "parse";
function GuestLogin() { function GuestLogin() {
const { id, userMail, contactBookId, base64url } = useParams(); const { id, userMail, contactBookId, base64url } = useParams();
let navigate = useNavigate(); const navigate = useNavigate();
const [email, setEmail] = useState(userMail); const [email, setEmail] = useState(userMail);
const [OTP, setOTP] = useState(""); const [OTP, setOTP] = useState("");
const [EnterOTP, setEnterOtp] = useState(false); const [EnterOTP, setEnterOtp] = useState(false);
@@ -20,9 +24,9 @@ function GuestLogin() {
const [documentId, setDocumentId] = useState(id); const [documentId, setDocumentId] = useState(id);
const [contactId, setContactId] = useState(contactBookId); const [contactId, setContactId] = useState(contactBookId);
const [sendmail, setSendmail] = useState(); const [sendmail, setSendmail] = useState();
const [contact, setContact] = useState({ name: "", phone: "", email: "" });
useEffect(() => { useEffect(() => {
handleServerUrl(); handleServerUrl();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
@@ -53,54 +57,51 @@ function GuestLogin() {
//split url in array from '/' //split url in array from '/'
const checkSplit = decodebase64.split("/"); const checkSplit = decodebase64.split("/");
setDocumentId(checkSplit[0]); setDocumentId(checkSplit[0]);
setContact((prev) => ({ ...prev, email: checkSplit[1] }));
setEmail(checkSplit[1]); setEmail(checkSplit[1]);
setContactId(checkSplit[2]); const contactId = checkSplit?.[2];
setSendmail(checkSplit[3]); 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); setIsLoading(false);
}; };
const handleChange = (event) => {
const { value } = event.target;
setOTP(value);
};
//send email OTP function //send email OTP function
const SendOtp = async (e) => { const SendOtp = async () => {
const serverUrl = setLoading(true);
localStorage.getItem("baseUrl") && localStorage.getItem("baseUrl"); setEmail(email);
const parseId = try {
localStorage.getItem("parseAppId") && localStorage.getItem("parseAppId"); const params = { email: email.toString(), docId: documentId };
if (serverUrl && localStorage) { const Otp = await Parse.Cloud.run("SendOTPMailV1", params);
setLoading(true); if (Otp) {
e.preventDefault(); setLoading(false);
setEmail(email); setEnterOtp(true);
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!");
} }
} else { } catch (error) {
alert("something went wrong!"); alert("something went wrong!");
} }
}; };
const handleSendOTPBtn = async (e) => {
e.preventDefault();
await SendOtp();
};
//verify OTP send on via email //verify OTP send on via email
const VerifyOTP = async (e) => { const VerifyOTP = async (e) => {
e.preventDefault(); e.preventDefault();
@@ -164,156 +165,171 @@ function GuestLogin() {
alert("Please Enter OTP!"); 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 ( return (
<div style={{ padding: "2rem", background: "white" }}> <div style={{ padding: "2rem", background: "white" }}>
{isLoading ? ( {isLoading ? (
<div <div className="flex flex-col justify-center items-center h-[100vh]">
style={{ <img className="w-[80px] h-[80px]" alt="loader" src={loader} />
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100vh",
flexDirection: "column"
}}
>
<img
alt="no img"
src={loader}
style={{ width: "80px", height: "80px" }}
/>
<span style={{ fontSize: "13px", color: "gray" }}> <span style={{ fontSize: "13px", color: "gray" }}>
{isLoading.message} {isLoading.message}
</span> </span>
</div> </div>
) : ( ) : (
<div <div className="m-1 md:m-2 border-[0.5px] border-[#c5c7c9] p-[30px] shadow-md">
style={{ <div className="md:w-[250px] md:h-[66px] inline-block overflow-hidden mt-2 mb-11">
margin: "10px", {appLogo && (
border: "0.5px solid #c5c7c9", <img src={appLogo} className="object-contain h-full" alt="logo" />
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> </div>
{contactId ? (
{!EnterOTP ? ( <>
<div className="row"> {!EnterOTP ? (
<div className="col-sm-6 KLO"> <div className="w-full md:w-[50%]">
<span className="welcomeText">Welcome Back !</span> <h1 className="text-2xl md:text-[30px]">Welcome Back!</h1>
<br /> <legend className="text-[12px] text-[#878787] mt-2">
<span className="KNLO"> Verification code is sent to your email
Verification code is sent to your email </legend>
</span> <div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
<div className="card card-box" style={{ borderRadius: "0px" }}>
<div className="card-body">
<input <input
type="email" type="email"
name="mobile" name="mobile"
value={email} value={email}
className="outline-none px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs disabled:bg-[#e0e5f5]"
disabled 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> </div>
<div className="btnContainer"> ) : (
{loading ? ( <form className="w-full md:w-[50%]" onSubmit={VerifyOTP}>
<button <h1 className="text-2xl md:text-[30px]">Welcome Back!</h1>
type="button" <legend className="text-[12px] text-[#878787] mt-2">
style={{ You will get a OTP via Email
background: themeColor, </legend>
color: "white" <div className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
}} <p>Enter Verification Code</p>
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>
<input <input
type="number" 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" name="OTP"
value={OTP} value={OTP}
onChange={handleChange} onChange={(e) => setOTP(e.target.value)}
/> />
<br />
</div> </div>
</div> <div className="mt-4">
<div>
{loading ? (
<button <button
style={{ style={{ background: themeColor }}
background: themeColor, className="w-[100px] text-white text-sm font-medium py-1 rounded-sm shadow-md hover:shadow-lg focus:outline-none"
color: "white" type="submit"
}} disabled={loading}
className="verifyBtn"
type="button"
disabled
> >
<span {loading ? "Loading..." : "Verify"}
className="spinner-border spinner-border-sm "
role="status"
aria-hidden="true"
></span>
Loading...
</button> </button>
) : ( </div>
<button </form>
type="button" )}
onClick={(e) => VerifyOTP(e)} </>
style={{ ) : (
background: themeColor, <div className="w-full md:w-[50%]">
color: "white" <h1 className="text-2xl md:text-[30px]">Welcome</h1>
}} <legend className="text-[12px] text-[#878787] mt-2">
className="verifyBtn" Provide your details
> </legend>
Verify <form
</button> 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> <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>
)} )}
</div> </div>
+5 -1
View File
@@ -41,6 +41,7 @@ import DefaultSignature from "../components/pdf/DefaultSignature";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import VerifyEmail from "../components/pdf/VerifyEmail"; import VerifyEmail from "../components/pdf/VerifyEmail";
import TourContentWithBtn from "../primitives/TourContentWithBtn"; import TourContentWithBtn from "../primitives/TourContentWithBtn";
import { appInfo } from "../constant/appinfo";
function useQuery() { function useQuery() {
return new URLSearchParams(useLocation().search); return new URLSearchParams(useLocation().search);
} }
@@ -460,7 +461,10 @@ function PdfRequestFiles() {
} else { } else {
//else condition to check current user exist in contracts_Users class and check tour message status //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 //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 currentUserEmail = currentUser.email;
const res = await contractUsers(currentUserEmail); const res = await contractUsers(currentUserEmail);
if (res === "Error: Something went wrong!") { if (res === "Error: Something went wrong!") {
@@ -43,7 +43,7 @@ const ReportTable = (props) => {
const [isBulkSend, setIsBulkSend] = useState({}); const [isBulkSend, setIsBulkSend] = useState({});
const [templateDeatils, setTemplateDetails] = useState({}); const [templateDeatils, setTemplateDetails] = useState({});
const [placeholders, setPlaceholders] = useState([]); const [placeholders, setPlaceholders] = useState([]);
const [isErr, setIsErr] = useState(false); const [isLoader, setIsLoader] = useState({});
const startIndex = (currentPage - 1) * props.docPerPage; const startIndex = (currentPage - 1) * props.docPerPage;
const { isMoreDocs, setIsNextRecord } = props; const { isMoreDocs, setIsNextRecord } = props;
@@ -217,7 +217,6 @@ const ReportTable = (props) => {
}; };
const handleActionBtn = (act, item) => { const handleActionBtn = (act, item) => {
console.log("item", item);
if (act.action === "redirect") { if (act.action === "redirect") {
handleURL(item, act); handleURL(item, act);
} else if (act.action === "delete") { } else if (act.action === "delete") {
@@ -560,7 +559,6 @@ const ReportTable = (props) => {
}; };
const handleResendMail = async (e, doc, user) => { const handleResendMail = async (e, doc, user) => {
e.preventDefault(); e.preventDefault();
console.log("first");
setActLoader({ [user.objectId]: true }); setActLoader({ [user.objectId]: true });
const url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`; const url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = { const headers = {
@@ -618,44 +616,73 @@ const ReportTable = (props) => {
</div> </div>
); );
}; };
// `handleQuickSendClose` is trigger when bulk send component trigger close event
const handleQuickSendClose = (status, count) => { const handleQuickSendClose = (status, count) => {
setIsBulkSend({}); setIsBulkSend({});
setIsAlert(true); setIsAlert(true);
if (status === "success") { if (status === "success") {
if (count > 1) { if (count > 1) {
setAlertMsg(count + " Documents sent successfully!"); setAlertMsg({
type: "success",
message: count + " Document sent successfully!"
});
setTimeout(() => setIsAlert(false), 1500);
} else { } else {
setAlertMsg(count + " Document sent successfully!"); setAlertMsg({
type: "success",
message: count + " Document sent successfully!"
});
setTimeout(() => setIsAlert(false), 1500);
} }
} else { } else {
setIsAlert(true); setAlertMsg({
setIsErr(true); 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 handleBulkSend = async (template) => {
const params = { setIsBulkSend({ [template.objectId]: true });
templateId: template.objectId, setIsLoader({ [template.objectId]: true });
include: ["Placeholders.signerPtr"] try {
}; const params = {
const axiosRes = await axios.post( templateId: template.objectId,
`${localStorage.getItem("baseUrl")}functions/getTemplate`, include: ["Placeholders.signerPtr"]
params, };
{ const axiosRes = await axios.post(
headers: { `${localStorage.getItem("baseUrl")}functions/getTemplate`,
"Content-Type": "application/json", params,
"X-Parse-Application-Id": localStorage.getItem("parseAppId"), {
sessionToken: localStorage.getItem("accesstoken") 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);
} }
); } catch (err) {
const templateRes = axiosRes.data && axiosRes.data.result; console.log("err in fetch template in bulk modal", err);
if (templateRes?.Placeholders?.length > 0) { setIsBulkSend({});
setPlaceholders(templateRes?.Placeholders); setIsDocErr(false);
setTemplateDetails(templateRes); setIsAlert(true);
setIsBulkSend({ [templateRes.objectId]: true }); setAlertMsg({
} else { type: "danger",
setIsDocErr(true); message: "Something went wrong, Please try again later!"
});
setTimeout(() => setIsAlert(false), 1500);
} }
}; };
return ( return (
@@ -842,11 +869,11 @@ const ReportTable = (props) => {
)} )}
{isOption[item.objectId] && {isOption[item.objectId] &&
act.action === "option" && ( 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) => ( {act.subaction?.map((subact) => (
<div <div
key={subact.btnId} 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={() => onClick={() =>
handleActionBtn(subact, item) handleActionBtn(subact, item)
} }
@@ -898,17 +925,34 @@ const ReportTable = (props) => {
</div> </div>
</ModalUi> </ModalUi>
)} )}
{isBulkSend[`${item.objectId}`] && ( {isBulkSend[item.objectId] && (
<ModalUi <ModalUi
isOpen isOpen
title={"Quick send"} title={"Quick send"}
handleClose={() => setIsBulkSend({})} handleClose={() => setIsBulkSend({})}
> >
<BulkSendUi {isLoader[item.objectId] ? (
Placeholders={placeholders} <div className="w-full h-[100px] md:h-[100px] rounded-b-md flex justify-center items-center bg-black bg-opacity-30 z-30">
item={templateDeatils} <div
handleClose={handleQuickSendClose} 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> </ModalUi>
)} )}
{isShare[item.objectId] && ( {isShare[item.objectId] && (
@@ -1158,16 +1202,6 @@ const ReportTable = (props) => {
closePopup={handleContactFormModal} closePopup={handleContactFormModal}
/> />
</ModalUi> </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>
</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 encryptedpdf from './parsefunction/encryptedPdf.js';
import { getSignedUrl } from './parsefunction/getSignedUrl.js'; import { getSignedUrl } from './parsefunction/getSignedUrl.js';
import createBatchDocs from './parsefunction/createBatchDocs.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. // 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); Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
@@ -86,3 +87,4 @@ Parse.Cloud.define('verifyemail', VerifyEmail);
Parse.Cloud.define('encryptedpdf', encryptedpdf); Parse.Cloud.define('encryptedpdf', encryptedpdf);
Parse.Cloud.define('getsignedurl', getSignedUrl); Parse.Cloud.define('getsignedurl', getSignedUrl);
Parse.Cloud.define('batchdocuments', createBatchDocs); Parse.Cloud.define('batchdocuments', createBatchDocs);
Parse.Cloud.define('linkcontacttodoc', linkContactToDoc);
@@ -31,9 +31,9 @@ async function sendMail(document, sessionToken) {
const hostUrl = baseUrl.origin; const hostUrl = baseUrl.origin;
let encodeBase64; let encodeBase64;
if (objectId) { if (objectId) {
encodeBase64 = btoa(`${document.objectId}}/${signerMail[i].signerPtr.Email}/${objectId}`); encodeBase64 = btoa(`${document.objectId}/${signerMail[i].signerPtr.Email}/${objectId}`);
} else { } else {
encodeBase64 = btoa(`${document.objectId}}/${signerMail[i].email}`); encodeBase64 = btoa(`${document.objectId}/${signerMail[i].email}`);
} }
let signPdf = `${hostUrl}/login/${encodeBase64}`; let signPdf = `${hostUrl}/login/${encodeBase64}`;
const openSignUrl = 'https://www.opensignlabs.com/'; const openSignUrl = 'https://www.opensignlabs.com/';
@@ -93,18 +93,12 @@ export default async function createBatchDocs(request) {
try { try {
const requests = Documents.map(x => { const requests = Documents.map(x => {
const Signers = x.Signers; const Signers = x.Signers;
const allSigner = x?.Placeholders?.map(item => { const allSigner = x?.Placeholders?.map(
const signer = Signers?.find(e => item?.signerPtr?.objectId === e?.objectId); item => Signers?.find(e => item?.signerPtr?.objectId === e?.objectId) || item?.signerPtr
if (signer) { ).filter(signer => Object.keys(signer).length > 0);
return signer;
} else {
return item?.signerPtr?.objectId && item?.signerPtr;
}
});
const date = new Date(); const date = new Date();
const isoDate = date.toISOString(); const isoDate = date.toISOString();
let Acl = { [x.CreatedBy.objectId]: { read: true, write: true } }; let Acl = { [x.CreatedBy.objectId]: { read: true, write: true } };
// console.log('allSigner ', allSigner);
if (allSigner && allSigner.length > 0) { if (allSigner && allSigner.length > 0) {
allSigner.forEach(x => { allSigner.forEach(x => {
const obj = { [x.CreatedBy.objectId]: { read: true, write: true } }; const obj = { [x.CreatedBy.objectId]: { read: true, write: true } };
@@ -146,7 +140,7 @@ export default async function createBatchDocs(request) {
objectId: y.objectId, objectId: y.objectId,
})), })),
ACL: Acl, ACL: Acl,
RemindOnceInEvery: x.RemindOnceInEvery, RemindOnceInEvery: x.RemindOnceInEvery || 5,
AutomaticReminders: x.AutomaticReminders || false, AutomaticReminders: x.AutomaticReminders || false,
TimeToCompleteDays: x.TimeToCompleteDays || 15, TimeToCompleteDays: x.TimeToCompleteDays || 15,
OriginIp: Ip, 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;
}
}