fix: add validation of signature widgetat in quick send flow, update SentToOthers field when document is in inProgress , resolve resend nonexist email issue

This commit is contained in:
RaktimaNXG
2024-06-07 19:07:56 +05:30
parent 2900464272
commit 5c3f967128
9 changed files with 194 additions and 127 deletions
+80 -57
View File
@@ -9,7 +9,22 @@ const BulkSendUi = (props) => {
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
const [isSubmit, setIsSubmit] = useState(false);
const [allowedForm, setAllowedForm] = useState(0);
const [isSignatureExist, setIsSignatureExist] = useState();
const allowedSigners = 50;
useEffect(() => {
signatureExist();
}, []);
//function to check atleast one signature field exist
const signatureExist = () => {
const getPlaceholder = props.item?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
};
useEffect(() => {
if (scrollOnNextUpdate && formRef.current) {
formRef.current.scrollIntoView({
@@ -165,65 +180,73 @@ const BulkSendUi = (props) => {
</div>
)}
{props.Placeholders?.length > 0 ? (
<>
{props.Placeholders?.some((x) => !x.signerObjId) ? (
<form onSubmit={handleSubmit}>
<div className=" min-h-max max-h-[250px] overflow-y-auto">
{forms?.map((form, index) => (
<div
key={form.Id}
className="p-3 rounded-xl border-[1px] border-gray-400 m-4 bg-white text-black grid grid-cols-1 md:grid-cols-2 gap-2 relative"
isSignatureExist ? (
<>
{props.Placeholders?.some((x) => !x.signerObjId) ? (
<form onSubmit={handleSubmit}>
<div className=" min-h-max max-h-[250px] overflow-y-auto">
{forms?.map((form, index) => (
<div
key={form.Id}
className="p-3 rounded-xl border-[1px] border-gray-400 m-4 bg-white text-black grid grid-cols-1 md:grid-cols-2 gap-2 relative"
>
{form?.fields?.map((field, fieldIndex) => (
<div className="flex flex-col " key={field.fieldId}>
<label>{field.label}</label>
<SuggestionInput
required
type="email"
value={field.value}
index={fieldIndex}
onChange={(signer) =>
handleInputChange(index, signer, fieldIndex)
}
/>
</div>
))}
{forms?.length > 1 && (
<button
onClick={() => handleRemoveForm(index)}
className="absolute right-3 top-1 border border-gray-300 rounded-lg px-2 py-1"
>
<i className="fa-solid fa-trash"></i>
</button>
)}
<div ref={formRef}></div>
</div>
))}
</div>
<div className="flex flex-col mx-4 mb-4 gap-3">
<button
onClick={handleAddForm}
className="bg-[#32a3ac] p-2 text-white w-full rounded-full focus:outline-none"
>
{form?.fields?.map((field, fieldIndex) => (
<div className="flex flex-col " key={field.fieldId}>
<label>{field.label}</label>
<SuggestionInput
required
type="email"
value={field.value}
index={fieldIndex}
onChange={(signer) =>
handleInputChange(index, signer, fieldIndex)
}
/>
</div>
))}
{forms?.length > 1 && (
<button
onClick={() => handleRemoveForm(index)}
className="absolute right-3 top-1 border border-gray-300 rounded-lg px-2 py-1"
>
<i className="fa-solid fa-trash"></i>
</button>
)}
<div ref={formRef}></div>
</div>
))}
<i className="fa-solid fa-plus"></i> <span>Add new</span>
</button>
<button
type="submit"
className="bg-[#32a3ac] p-2 text-white w-full rounded-full focus:outline-none"
>
<i className="fa-solid fa-paper-plane"></i>{" "}
<span>Send</span>
</button>
</div>
</form>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
All roles in this document are currently linked to contacts. To
&apos;quick send&apos; copies of this template to multiple
signers, please ensure that at least one role is not linked to
any contact.
</div>
<div className="flex flex-col mx-4 mb-4 gap-3">
<button
onClick={handleAddForm}
className="bg-[#32a3ac] p-2 text-white w-full rounded-full focus:outline-none"
>
<i className="fa-solid fa-plus"></i> <span>Add new</span>
</button>
<button
type="submit"
className="bg-[#32a3ac] p-2 text-white w-full rounded-full focus:outline-none"
>
<i className="fa-solid fa-paper-plane"></i> <span>Send</span>
</button>
</div>
</form>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
All roles in this document are currently linked to contacts. To
&apos;quick send&apos; copies of this template to multiple
signers, please ensure that at least one role is not linked to any
contact.
</div>
)}
</>
)}
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
Please ensure there&#39;s at least one signature widget added for
all recipients.
</div>
)
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
Please add at least one role to this template in order to &apos;quick
@@ -206,13 +206,9 @@ function RenderPdf({
const checkSignedSignes = (data) => {
let checkSign = [];
//condition to handle quick send flow and using normal request sign flow
if (signedSigners[0]?.Id) {
checkSign = signedSigners.filter((sign) => sign.Id === data.Id);
} else {
checkSign = signedSigners.filter(
(sign) => sign.objectId === data.signerObjId
);
}
checkSign = signedSigners.filter(
(sign) => sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
);
if (data.signerObjId === signerObjectId) {
setCurrentSigner(true);
}
+1
View File
@@ -669,6 +669,7 @@ export const createDocument = async (template, placeholders, signerData) => {
Name: Doc.Name,
URL: Doc.URL,
SignedUrl: Doc.SignedUrl,
SentToOthers: Doc.SentToOthers,
Description: Doc.Description,
Note: Doc.Note,
Placeholders: placeholdersArr,
+27 -17
View File
@@ -403,25 +403,34 @@ function PdfRequestFiles() {
let signers = [];
let unSignedSigner = [];
//`emailExist` variable to handle condition for quick send flow and show unsigned signers list
const emailExist = documentData[0].Placeholders[0]?.email;
const placeholdersOrSigners = [];
for (const placeholder of documentData[0].Placeholders) {
//`emailExist` variable to handle condition for quick send flow and show unsigned signers list
const emailExist = placeholder?.email;
if (emailExist) {
placeholdersOrSigners.push(placeholder);
} else {
const getSignerData = documentData[0].Signers.filter(
(data) => data.objectId === placeholder?.signerObjId
);
placeholdersOrSigners.push(getSignerData[0]);
}
}
//condition to check already signed document by someone
if (audittrailData && audittrailData.length > 0) {
setIsDocId(true);
const placeholdersOrSigners = emailExist
? documentData[0].Placeholders
: documentData[0].Signers;
for (const item of placeholdersOrSigners) {
const checkEmail = item?.email;
//if email exist then compare user signed by using email else signers objectId
const emailOrId = emailExist ? item.email : item.objectId;
const emailOrId = checkEmail ? item.email : item.objectId;
//`isSignedSignature` variable to handle break loop whenever it get true
let isSignedSignature = false;
//checking the signer who signed the document by using audit trail details.
//and save signedSigners and unsignedSigners details
for (const doc of audittrailData) {
const signedExist = emailExist
const signedExist = checkEmail
? doc?.UserPtr.Email
: doc?.UserPtr.objectId;
@@ -441,12 +450,7 @@ function PdfRequestFiles() {
} else {
//else condition is show there are no details in audit trail then direct push all signers details
//in unsignedsigners array
let unsigned = [];
const placeholdersOrSigners = emailExist
? documentData[0].Placeholders
: documentData[0].Signers;
unsigned.push(placeholdersOrSigners);
setUnSignedSigners(unsigned[0]);
setUnSignedSigners(placeholdersOrSigners);
setSignerPos(documentData[0].Placeholders);
}
setPdfDetails(documentData);
@@ -1000,8 +1004,10 @@ function PdfRequestFiles() {
}
const getFirstLetter = (name) => {
const firstLetter = name.charAt(0);
return firstLetter;
if (name) {
const firstLetter = name.charAt(0);
return firstLetter;
}
};
//function for image upload or update
const onImageChange = (event) => {
@@ -1123,8 +1129,12 @@ function PdfRequestFiles() {
};
const checkUserNameColor = (obj) => {
const getBackColor = checkSignerBackColor(obj);
const color = darkenColor(getBackColor, 0.4);
return color;
if (getBackColor) {
const color = darkenColor(getBackColor, 0.4);
return color;
} else {
return "#abd1d0";
}
};
//function for set decline true on press decline button
+3 -1
View File
@@ -936,6 +936,7 @@ function PlaceHolderSign() {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers,
SentToOthers: true,
ExpiryDate: {
iso: updateExpiryDate,
__type: "Date"
@@ -945,7 +946,8 @@ function PlaceHolderSign() {
data = {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers
Signers: signers,
SentToOthers: true
};
}
await axios
@@ -143,6 +143,7 @@ const ReportTable = (props) => {
Name: Doc.Name,
URL: Doc.URL,
SignedUrl: Doc.SignedUrl,
SentToOthers: Doc?.SentToOthers || false,
Description: Doc.Description,
Note: Doc.Note,
Placeholders: placeholdersArr,
@@ -338,7 +339,7 @@ const ReportTable = (props) => {
const sendMail = item?.SendMail || false;
const getUrl = (x) => {
//encode this url value `${item.objectId}/${x.Email}/${x.objectId}` to base64 using `btoa` function
if (x.objectId) {
if (x?.signerObjId) {
const encodeBase64 = btoa(
`${item.objectId}/${x.signerPtr.Email}/${x.signerPtr.objectId}/${sendMail}`
);
@@ -495,9 +496,9 @@ const ReportTable = (props) => {
// `handleSubjectChange` is used to add or change subject of resend mail
const handleSubjectChange = (subject, doc) => {
const encodeBase64 = btoa(
`${doc.objectId}/${userDetails.Email}/${userDetails.objectId}`
);
const encodeBase64 = userDetails?.objectId
? btoa(`${doc.objectId}/${userDetails.Email}/${userDetails.objectId}`)
: btoa(`${doc.objectId}/${userDetails.Email}`);
const expireDate = doc.ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString("en-US", {
@@ -511,8 +512,8 @@ const ReportTable = (props) => {
sender_name: doc.ExtUserPtr.Name,
sender_mail: doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr?.Phone || "",
receiver_name: userDetails.Name,
receiver_email: userDetails.Email,
receiver_name: userDetails?.Name,
receiver_email: userDetails?.Email,
receiver_phone: userDetails?.Phone || "",
expiry_date: localExpireDate,
company_name: doc.ExtUserPtr.Company,
@@ -524,9 +525,9 @@ const ReportTable = (props) => {
};
// `handlebodyChange` is used to add or change body of resend mail
const handlebodyChange = (body, doc) => {
const encodeBase64 = btoa(
`${doc.objectId}/${userDetails.Email}/${userDetails.objectId}`
);
const encodeBase64 = userDetails?.objectId
? btoa(`${doc.objectId}/${userDetails.Email}/${userDetails.objectId}`)
: btoa(`${doc.objectId}/${userDetails.Email}`);
const expireDate = doc.ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString("en-US", {
@@ -540,8 +541,8 @@ const ReportTable = (props) => {
sender_name: doc.ExtUserPtr.Name,
sender_mail: doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr?.Phone || "",
receiver_name: userDetails.Name,
receiver_email: userDetails.Email,
receiver_name: userDetails?.Name || "",
receiver_email: userDetails?.Email || "",
receiver_phone: userDetails?.Phone || "",
expiry_date: localExpireDate,
company_name: doc.ExtUserPtr.Company,
@@ -556,8 +557,18 @@ const ReportTable = (props) => {
// `handleNextBtn` is used to open edit mail template screen in resend mail modal
// as well as replace variable with original one
const handleNextBtn = (user, doc) => {
setUserDetails(user);
const encodeBase64 = btoa(`${doc.objectId}/${user.Email}/${user.objectId}`);
const userdata = {
Name: user?.signerPtr?.Name,
Email: user.email ? user?.email : user.signerPtr?.Email,
Phone: user?.signerPtr?.Phone,
objectId: user?.signerPtr?.objectId
};
setUserDetails(userdata);
const encodeBase64 = user.email
? btoa(`${doc.objectId}/${user.email}`)
: btoa(
`${doc.objectId}/${user.signerPtr.Email}/${user.signerPtr.objectId}`
);
const expireDate = doc.ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString("en-US", {
@@ -571,9 +582,9 @@ const ReportTable = (props) => {
sender_name: doc.ExtUserPtr.Name,
sender_mail: doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr?.Phone || "",
receiver_name: user.Name,
receiver_email: user.Email,
receiver_phone: user?.Phone || "",
receiver_name: user?.signerPtr?.Name || "",
receiver_email: user?.email ? user?.email : user?.signerPtr?.Email,
receiver_phone: user?.signerPtr?.Phone || "",
expiry_date: localExpireDate,
company_name: doc?.ExtUserPtr?.Company || "",
signing_url: `<a href=${signPdf}>Sign here</a>`
@@ -584,14 +595,14 @@ const ReportTable = (props) => {
`{{sender_name}} has requested you to sign "{{document_title}}"`;
const body =
doc?.RequestBody ||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;<b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br></body> </html>`;
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign <b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br></body> </html>`;
const res = replaceMailVaribles(subject, body, variables);
setMail((prev) => ({ ...prev, subject: res.subject, body: res.body }));
setIsNextStep({ [user.objectId]: true });
setIsNextStep({ [user.Id]: true });
};
const handleResendMail = async (e, doc, user) => {
e.preventDefault();
setActLoader({ [user.objectId]: true });
setActLoader({ [user?.Id]: true });
const url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
@@ -601,7 +612,7 @@ const ReportTable = (props) => {
let params = {
mailProvider: doc?.ExtUserPtr?.active_mail_adapter,
extUserId: doc?.ExtUserPtr?.objectId,
recipient: userDetails.Email,
recipient: userDetails?.Email,
subject: mail.subject,
from: doc?.ExtUserPtr?.Email,
html: mail.body
@@ -633,9 +644,9 @@ const ReportTable = (props) => {
}
};
const fetchUserStatus = (user, doc) => {
const audit = doc?.AuditTrail?.find(
(x) => x.UserPtr.objectId === user.objectId
);
const email = user.email ? user.email : user.signerPtr.Email;
const audit = doc?.AuditTrail?.find((x) => x.UserPtr.Email === email);
return (
<div className="flex flex-row gap-2 justify-center items-center">
<div className="flex justify-center items-center bg-gray-200 text-xs text-black shadow rounded-full w-[65px] h-[23px] cursor-default">
@@ -789,23 +800,38 @@ const ReportTable = (props) => {
const getPlaceholder = item?.Placeholders;
//condiiton to check role is exist or not
if (getPlaceholder && getPlaceholder.length > 0) {
let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
const userName = extendUser[0]?.UserName;
setIsPublicUserName(extendUser[0]?.UserName);
//condition to check user have public url or not
if (userName) {
setIsPublic((prevStates) => ({
...prevStates,
[item.objectId]: e.target.checked
}));
const getPlaceholder = item?.Placeholders;
if (getPlaceholder.length === 1) {
setSelectedPublicRole(getPlaceholder[0]?.Role);
}
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
if (checkIsSignatureExistt) {
let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
const userName = extendUser[0]?.UserName;
setIsPublicUserName(extendUser[0]?.UserName);
//condition to check user have public url or not
if (userName) {
setIsPublic((prevStates) => ({
...prevStates,
[item.objectId]: e.target.checked
}));
const getPlaceholder = item?.Placeholders;
if (getPlaceholder.length === 1) {
setSelectedPublicRole(getPlaceholder[0]?.Role);
}
setIsMakePublic({ [item.objectId]: true });
setIsMakePublic({ [item.objectId]: true });
} else {
setIsPublicProfile({ [item.objectId]: true });
}
} else {
setIsPublicProfile({ [item.objectId]: true });
setIsAlert(true);
setAlertMsg({
type: "danger",
message:
" Please ensure there's at least one signature widget added for all recipients."
});
setTimeout(() => setIsAlert(false), 5000);
}
} else {
setIsAlert(true);
@@ -813,7 +839,7 @@ const ReportTable = (props) => {
type: "danger",
message: "Please assign at least one role to make this template public."
});
setTimeout(() => setIsAlert(false), 3000);
setTimeout(() => setIsAlert(false), 5000);
}
};
@@ -1240,11 +1266,11 @@ const ReportTable = (props) => {
}}
>
<div className=" overflow-y-auto max-h-[340px] md:max-h-[400px]">
{item?.Signers.map((user) => (
<React.Fragment key={user.objectId}>
{isNextStep[user.objectId] && (
{item?.Placeholders?.map((user) => (
<React.Fragment key={user.Id}>
{isNextStep[user.Id] && (
<div className="relative ">
{actLoader[user.objectId] && (
{actLoader[user.Id] && (
<div className="absolute w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-30">
<div
style={{
@@ -1264,7 +1290,7 @@ const ReportTable = (props) => {
>
<div className="absolute right-5 text-xs z-40">
<Tooltip
id={`${user.objectId}_help`}
id={`${user.Id}_help`}
message={
"You can use following variables which will get replaced with their actual values:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}."
}
@@ -1322,7 +1348,12 @@ const ReportTable = (props) => {
{Object?.keys(isNextStep) <= 0 && (
<div className="flex justify-between items-center gap-2 my-2 px-3">
<div className="text-black">
{user.Name} {`<${user.Email}>`}
{user?.signerPtr?.Name || "-"}{" "}
{`<${
user?.email
? user.email
: user.signerPtr.Email
}>`}
</div>
<>{fetchUserStatus(user, item)}</>
</div>
@@ -179,6 +179,7 @@ export default async function createDocumentWithTemplate(request, response) {
}
object.set('URL', template.URL);
object.set('SignedUrl', template.URL);
object.set('SentToOthers', true);
if (TimeToCompleteDays) {
object.set('TimeToCompleteDays', TimeToCompleteDays);
}
@@ -141,6 +141,7 @@ export default async function createDocumentwithCoordinate(request, response) {
}
object.set('URL', fileUrl);
object.set('SignedUrl', fileUrl);
object.set('SentToOthers', true);
object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr);
if (TimeToCompleteDays) {
@@ -140,12 +140,14 @@ export default async function createBatchDocs(request) {
: { ...y, signerPtr: {}, signerObjId: '' }
),
SignedUrl: x.URL || x.SignedUrl,
SentToOthers: true,
Signers: allSigner?.map(y => ({
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.objectId,
})),
ACL: Acl,
SentToOthers: true,
RemindOnceInEvery: x.RemindOnceInEvery || 5,
AutomaticReminders: x.AutomaticReminders || false,
TimeToCompleteDays: x.TimeToCompleteDays || 15,