fix: merge changes from staging

This commit is contained in:
RaktimaNXG
2024-05-29 16:07:05 +05:30
23 changed files with 990 additions and 716 deletions
+18 -29
View File
@@ -60,7 +60,7 @@
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.34.1",
"lint-staged": "^15.2.4",
"lint-staged": "^15.2.5",
"postcss": "^8.4.38",
"prettier": "^2.8.8",
"pretty-quick": "^3.3.1",
@@ -15562,21 +15562,21 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"node_modules/lint-staged": {
"version": "15.2.4",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.4.tgz",
"integrity": "sha512-3F9KRQIS2fVDGtCkBp4Bx0jswjX7zUcKx6OF0ZeY1prksUyKPRIIUqZhIUYAstJfvj6i48VFs4dwVIbCYwvTYQ==",
"version": "15.2.5",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.5.tgz",
"integrity": "sha512-j+DfX7W9YUvdzEZl3Rk47FhDF6xwDBV5wwsCPw6BwWZVPYJemusQmvb9bRsW23Sqsaa+vRloAWogbK4BUuU2zA==",
"dev": true,
"dependencies": {
"chalk": "5.3.0",
"commander": "12.1.0",
"debug": "4.3.4",
"execa": "8.0.1",
"lilconfig": "3.1.1",
"listr2": "8.2.1",
"micromatch": "4.0.6",
"pidtree": "0.6.0",
"string-argv": "0.3.2",
"yaml": "2.4.2"
"chalk": "~5.3.0",
"commander": "~12.1.0",
"debug": "~4.3.4",
"execa": "~8.0.1",
"lilconfig": "~3.1.1",
"listr2": "~8.2.1",
"micromatch": "~4.0.7",
"pidtree": "~0.6.0",
"string-argv": "~0.3.2",
"yaml": "~2.4.2"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
@@ -16399,28 +16399,17 @@
}
},
"node_modules/micromatch": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.6.tgz",
"integrity": "sha512-Y4Ypn3oujJYxJcMacVgcs92wofTHxp9FzfDpQON4msDefoC0lb3ETvQLOdLcbhSwU1bz8HrL/1sygfBIHudrkQ==",
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^4.0.2"
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/micromatch/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+1 -1
View File
@@ -90,7 +90,7 @@
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.34.1",
"lint-staged": "^15.2.4",
"lint-staged": "^15.2.5",
"postcss": "^8.4.38",
"prettier": "^2.8.8",
"pretty-quick": "^3.3.1",
+61 -43
View File
@@ -164,54 +164,72 @@ const BulkSendUi = (props) => {
></div>
</div>
)}
<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>
))}
{index > 0 && (
{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"
>
{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={() => handleRemoveForm(index)}
className="absolute right-3 top-1 border border-gray-300 rounded-lg px-2 py-1"
onClick={handleAddForm}
className="bg-[#32a3ac] p-2 text-white w-full rounded-full focus:outline-none"
>
<i className="fa-solid fa-trash"></i>
<i className="fa-solid fa-plus"></i> <span>Add new</span>
</button>
)}
<div ref={formRef}></div>
<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 add at least one role to this template in order to &apos;quick
send&apos; copies of it to multiple signers.
</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>
)}
</>
);
};
+44 -15
View File
@@ -40,7 +40,7 @@ function Header({
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
const isMobile = window.innerWidth < 767;
const navigate = useNavigate();
const [isCertificate, setIsCertificate] = useState(false);
const [isDownloading, setIsDownloading] = useState("");
const isGuestSigner = localStorage.getItem("isGuestSigner");
//for go to previous page
function previousPage() {
@@ -131,7 +131,9 @@ function Header({
>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleDownloadPdf(pdfDetails, pdfUrl)}
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<div
style={{
@@ -153,7 +155,7 @@ function Header({
onClick={() =>
handleDownloadCertificate(
pdfDetails,
setIsCertificate
setIsDownloading
)
}
>
@@ -194,7 +196,9 @@ function Header({
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={(e) => handleToPrint(e, pdfUrl)}
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading)
}
>
<div
style={{
@@ -277,7 +281,11 @@ function Header({
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl)
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
>
<i
@@ -366,7 +374,7 @@ function Header({
<button
type="button"
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsCertificate)
handleDownloadCertificate(pdfDetails, setIsDownloading)
}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
>
@@ -379,7 +387,7 @@ function Header({
)}
<button
onClick={(e) => handleToPrint(e, pdfUrl)}
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
>
@@ -390,7 +398,9 @@ function Header({
<button
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
onClick={() => handleDownloadPdf(pdfDetails, pdfUrl)}
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<i className="fa fa-download py-[3px]" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">Download</span>
@@ -440,7 +450,11 @@ function Header({
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl)
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
>
<i
@@ -462,7 +476,7 @@ function Header({
<button
type="button"
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsCertificate)
handleDownloadCertificate(pdfDetails, setIsDownloading)
}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
>
@@ -474,7 +488,7 @@ function Header({
</button>
)}
<button
onClick={(e) => handleToPrint(e, pdfUrl)}
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
>
@@ -484,7 +498,9 @@ function Header({
<button
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
onClick={() => handleDownloadPdf(pdfDetails, pdfUrl)}
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<i className="fa fa-download py-[3px]" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">Download</span>
@@ -524,12 +540,25 @@ function Header({
)}
</div>
)}
{isDownloading === "pdf" && (
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<div
style={{ fontSize: "45px", color: "#3dd3e0" }}
className="loader-37"
></div>
</div>
)}
<ModalUi
isOpen={isCertificate}
title={"Generating certificate"}
handleClose={() => setIsCertificate(false)}
isOpen={isDownloading === "certificate"}
title={
isDownloading === "certificate"
? "Generating certificate"
: "PDF Download"
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center">
{isDownloading === "certificate"}{" "}
<p>
Your completion certificate is being generated. Please wait
momentarily.
+19 -21
View File
@@ -211,27 +211,25 @@ function RenderPdf({
setCurrentSigner(true);
}
const handleAllUserName = (Id, Role, type) => {
return pdfDetails[0].Signers.map((signerData, key) => {
return (
signerData.objectId === data.signerObjId && (
<React.Fragment key={key}>
{signerData?.Name && (
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
{signerData.Name}
</div>
)}
{type && (
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
)}
{Role && (
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
{`(${Role})`}
</div>
)}
</React.Fragment>
)
);
});
return (
<React.Fragment>
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
{
pdfDetails[0].Signers?.find(
(signer) => signer.objectId === data.signerObjId
)?.Name
}
</div>
{type && (
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
)}
{Role && (
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
{`(${Role})`}
</div>
)}
</React.Fragment>
);
};
return (
@@ -66,7 +66,7 @@ const SuggestionInput = (props) => {
type={props?.type || "text"}
value={inputValue}
onChange={handleInputChange}
placeholder="Enter text..."
placeholder="Enter Email..."
className="w-full border-[1px] border-gray-400 p-2 text-black rounded"
required={props.required}
/>
+18 -8
View File
@@ -2034,8 +2034,13 @@ export const handleSendOTP = async (email) => {
}
};
//handle download signed pdf
export const handleDownloadPdf = async (pdfDetails, pdfUrl) => {
export const handleDownloadPdf = async (
pdfDetails,
pdfUrl,
setIsDownloading
) => {
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
setIsDownloading("pdf");
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post(
@@ -2051,8 +2056,10 @@ export const handleDownloadPdf = async (pdfDetails, pdfUrl) => {
);
const url = axiosRes.data.result;
saveAs(url, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
setIsDownloading("");
} catch (err) {
console.log("err in getsignedurl", err);
setIsDownloading("");
alert("something went wrong, please try again later.");
}
};
@@ -2062,9 +2069,9 @@ export const sanitizeFileName = (pdfName) => {
return pdfName.replace(/ /g, "_");
};
//function for print digital sign pdf
export const handleToPrint = async (event, pdfUrl) => {
export const handleToPrint = async (event, pdfUrl, setIsDownloading) => {
event.preventDefault();
setIsDownloading("pdf");
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post(
@@ -2094,10 +2101,13 @@ export const handleToPrint = async (event, pdfUrl) => {
const blob = new Blob([byteArray], { type: "application/pdf" });
const blobUrl = URL.createObjectURL(blob);
window.open(blobUrl, "_blank");
setIsDownloading("");
} else {
printModule({ printable: pdf, type: "pdf", base64: true });
setIsDownloading("");
}
} catch (err) {
setIsDownloading("");
console.log("err in getsignedurl", err);
alert("something went wrong, please try again later.");
}
@@ -2106,7 +2116,7 @@ export const handleToPrint = async (event, pdfUrl) => {
//handle download signed pdf
export const handleDownloadCertificate = async (
pdfDetails,
setIsCertificate
setIsDownloading
) => {
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
try {
@@ -2117,7 +2127,7 @@ export const handleDownloadCertificate = async (
console.log("err in download in certificate", err);
}
} else {
setIsCertificate(true);
setIsDownloading("certificate");
try {
const data = {
docId: pdfDetails[0]?.objectId
@@ -2139,9 +2149,9 @@ export const handleDownloadCertificate = async (
await fetch(doc?.CertificateUrl);
const certificateUrl = doc?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
setIsCertificate(false);
setIsDownloading("");
} else {
setIsCertificate(true);
setIsDownloading("certificate");
}
}
} catch (err) {
@@ -2150,7 +2160,7 @@ export const handleDownloadCertificate = async (
}
}
};
export async function findContact(value) {
export async function findContact(value) {
try {
const currentUser = Parse.User.current();
const contactbook = new Parse.Query("contracts_Contactbook");
+13 -10
View File
@@ -3,7 +3,7 @@ export default function reportJson(id) {
const head = ["Sr.No", "Title", "Note", "Folder", "File", "Owner", "Signers"];
const contactbook = ["Sr.No", "Title", "Email", "Phone"];
const dashboardReportHead = ["Title", "File", "Owner", "Signers"];
const templateReport = ["Sr.No", "Title", "File", "Owner", "Signers"];
switch (id) {
// draft documents report
case "ByHuevtCFY":
@@ -326,7 +326,7 @@ export default function reportJson(id) {
case "6TeaPr321t":
return {
reportName: "Templates",
heading: head,
heading: templateReport,
actions: [
{
btnId: "2234",
@@ -341,6 +341,17 @@ export default function reportJson(id) {
message:
"Click the 'Use' button to create a new document from an existing template. "
},
{
btnId: "1631",
btnLabel: "Quick send",
hoverLabel: "Quick send",
btnIcon: "fa-solid fa-envelope",
redirectUrl: "",
action: "bulksend",
selector: "tourbulksend",
message:
"To quickly create and send multiple documents using an existing template, click the 'Quick Send' button."
},
{
btnId: "2234",
hoverLabel: "option",
@@ -360,14 +371,6 @@ export default function reportJson(id) {
redirectUrl: "template",
action: "redirect"
},
{
btnId: "1631",
btnLabel: "Quick send",
hoverLabel: "Quick send",
btnIcon: "fa-solid fa-envelope",
redirectUrl: "",
action: "bulksend"
},
{
btnId: "1834",
btnLabel: "Delete",
+1 -1
View File
@@ -169,10 +169,10 @@ function GuestLogin() {
e.preventDefault();
const params = { ...contact, docId: documentId };
try {
setLoading(true);
const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params);
// console.log("linkContactRes ", linkContactRes);
setContactId(linkContactRes.contactId);
setLoading(true);
setEnterOtp(true);
await SendOtp();
} catch (err) {
+194 -182
View File
@@ -95,7 +95,7 @@ function PdfRequestFiles() {
const [expiredDate, setExpiredDate] = useState("");
const [signerUserId, setSignerUserId] = useState();
const [isDontShow, setIsDontShow] = useState(false);
const [isCertificate, setIsCertificate] = useState(false);
const [isDownloading, setIsDownloading] = useState("");
const [defaultSignAlert, setDefaultSignAlert] = useState({
isShow: false,
alertMessage: ""
@@ -278,6 +278,10 @@ function PdfRequestFiles() {
};
setAlreadySign(true);
setIsCompleted(data);
setIsCelebration(true);
setTimeout(() => {
setIsCelebration(false);
}, 5000);
} else if (declined) {
const currentDecline = {
currnt: "another",
@@ -290,6 +294,13 @@ function PdfRequestFiles() {
);
setIsExpired(true);
setExpiredDate(expireDateFormat);
} // Check if the current signer is not a last signer and handle the complete message.
else if (isNextUser) {
setIsCompleted({
isModal: true,
message:
"You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document will be sent to the owner over email once it is signed by all signers."
});
}
const isGuestSign = location.pathname.includes("/load/");
@@ -319,164 +330,157 @@ function PdfRequestFiles() {
}
}
}
if (documentData.length > 0) {
const checkDocIdExist =
documentData[0].AuditTrail &&
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter(
(data) => data.Activity === "Signed"
);
const checkDocIdExist =
documentData[0].AuditTrail &&
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter((data) => data.Activity === "Signed");
const checkAlreadySign =
documentData[0].AuditTrail &&
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter(
(data) =>
data.UserPtr.objectId === currUserId && data.Activity === "Signed"
);
const checkAlreadySign =
documentData[0].AuditTrail &&
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter(
(data) =>
data.UserPtr.objectId === currUserId && data.Activity === "Signed"
);
if (
checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0
) {
setAlreadySign(true);
} else {
const obj = documentData?.[0];
setSendInOrder(obj?.SendinOrder || false);
if (
checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0
obj &&
obj.Signers &&
obj.Signers.length > 0 &&
obj.Placeholders &&
obj.Placeholders.length > 0
) {
setAlreadySign(true);
} else {
const obj = documentData?.[0];
setSendInOrder(obj?.SendinOrder || false);
if (
obj &&
obj.Signers &&
obj.Signers.length > 0 &&
obj.Placeholders &&
obj.Placeholders.length > 0
) {
const params = {
event: "viewed",
contactId: currUserId,
body: {
objectId: documentData?.[0].objectId,
file: documentData?.[0]?.SignedUrl || documentData?.[0]?.URL,
name: documentData?.[0].Name,
note: documentData?.[0].Note || "",
description: documentData?.[0].Description || "",
signers: documentData?.[0].Signers?.map((x) => ({
name: x?.Name,
email: x?.Email,
phone: x?.Phone
})),
viewedBy: jsonSender.email,
viewedAt: new Date(),
createdAt: documentData?.[0].createdAt
}
};
const params = {
event: "viewed",
contactId: currUserId,
body: {
objectId: documentData?.[0].objectId,
file: documentData?.[0]?.SignedUrl || documentData?.[0]?.URL,
name: documentData?.[0].Name,
note: documentData?.[0].Note || "",
description: documentData?.[0].Description || "",
signers: documentData?.[0].Signers?.map((x) => ({
name: x?.Name,
email: x?.Email,
phone: x?.Phone
})),
viewedBy: jsonSender.email,
viewedAt: new Date(),
createdAt: documentData?.[0].createdAt
}
};
try {
await axios.post(
`${localStorage.getItem("baseUrl")}functions/callwebhook`,
params,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id":
localStorage.getItem("parseAppId"),
sessiontoken: localStorage.getItem("accesstoken")
}
try {
await axios.post(
`${localStorage.getItem("baseUrl")}functions/callwebhook`,
params,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessiontoken: localStorage.getItem("accesstoken")
}
);
} catch (err) {
console.log("Err ", err);
}
}
}
let signers = [];
let unSignedSigner = [];
//check document is signed or not
if (checkDocIdExist && checkDocIdExist.length > 0) {
setIsDocId(true);
const signerRes = documentData[0].Signers;
//comparison auditTrail user details with signers user details
for (let i = 0; i < signerRes.length; i++) {
const signerId = signerRes[i].objectId;
let isSignedSignature = false;
for (let j = 0; j < checkDocIdExist.length; j++) {
const signedExist =
checkDocIdExist[j] && checkDocIdExist[j].UserPtr.objectId;
//checking signerObjId and auditTrail User objId
// if match then add signed data in signer array and break loop
if (signerId === signedExist) {
signers.push({ ...signerRes[i], ...signerRes[i] });
isSignedSignature = true;
break;
}
// if does not match then add unsigned data in unSignedSigner array
}
if (!isSignedSignature) {
unSignedSigner.push({ ...signerRes[i], ...signerRes[i] });
}
);
} catch (err) {
console.log("Err ", err);
}
setSignedSigners(signers);
setUnSignedSigners(unSignedSigner);
}
}
setSignerPos(documentData[0].Placeholders);
} else {
let unsigned = [];
for (let i = 0; i < documentData.length; i++) {
unsigned.push(documentData[i].Signers);
let signers = [];
let unSignedSigner = [];
//check document is signed or not
if (checkDocIdExist && checkDocIdExist.length > 0) {
setIsDocId(true);
const signerRes = documentData[0].Signers;
//comparison auditTrail user details with signers user details
for (let i = 0; i < signerRes.length; i++) {
const signerId = signerRes[i].objectId;
let isSignedSignature = false;
for (let j = 0; j < checkDocIdExist.length; j++) {
const signedExist =
checkDocIdExist[j] && checkDocIdExist[j].UserPtr.objectId;
//checking signerObjId and auditTrail User objId
// if match then add signed data in signer array and break loop
if (signerId === signedExist) {
signers.push({ ...signerRes[i], ...signerRes[i] });
isSignedSignature = true;
break;
}
// if does not match then add unsigned data in unSignedSigner array
}
setUnSignedSigners(unsigned[0]);
setSignerPos(documentData[0].Placeholders);
}
setPdfDetails(documentData);
// Check if the current signer is not a last signer and handle the complete message.
if (isNextUser) {
const getSignedAuditTrail = documentData[0].AuditTrail.filter(
(data) => data.Activity === "Signed"
);
const isLastSigner =
getSignedAuditTrail?.length === documentData?.[0]?.Signers?.length;
if (!isLastSigner) {
setIsCompleted({
isModal: true,
message:
"You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document will be sent to the owner over email once it is signed by all signers."
});
} else {
setIsCelebration(true);
setTimeout(() => {
setIsCelebration(false);
}, 5000);
if (!isSignedSignature) {
unSignedSigner.push({ ...signerRes[i], ...signerRes[i] });
}
}
//checking if condition current user already sign or owner does not exist as a signer or document has been declined by someone or document has been expired
//then stop to display tour message
if (
(checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0) ||
!currUserId ||
declined ||
currDate > expireUpdateDate
) {
setRequestSignTour(false);
} 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 localuser = localStorage.getItem(
`Parse/${appInfo.appId}/currentUser`
);
const currentUser = JSON.parse(JSON.stringify(localuser));
const currentUserEmail = currentUser.email;
const res = await contractUsers(currentUserEmail);
setSignedSigners(signers);
setUnSignedSigners(unSignedSigner);
setSignerPos(documentData[0].Placeholders);
} else {
let unsigned = [];
for (let i = 0; i < documentData.length; i++) {
unsigned.push(documentData[i].Signers);
}
setUnSignedSigners(unsigned[0]);
setSignerPos(documentData[0].Placeholders);
}
setPdfDetails(documentData);
//checking if condition current user already sign or owner does not exist as a signer or document has been declined by someone or document has been expired
//then stop to display tour message
if (
(checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0) ||
!currUserId ||
declined ||
currDate > expireUpdateDate
) {
setRequestSignTour(false);
} 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 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!") {
setHandleError("Error: Something went wrong!");
} else if (res[0] && res?.length) {
setContractName("_Users");
currUserId = res[0].objectId;
setSignerUserId(currUserId);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
const checkTourRequestSign = tourstatus.filter(
(data) => data.requestSign
);
if (checkTourRequestSign && checkTourRequestSign.length > 0) {
setRequestSignTour(checkTourRequestSign[0].requestSign);
}
}
} else if (res?.length === 0) {
const res = await contactBook(currUserId);
if (res === "Error: Something went wrong!") {
setHandleError("Error: Something went wrong!");
} else if (res[0] && res?.length) {
setContractName("_Users");
currUserId = res[0].objectId;
setSignerUserId(currUserId);
} else if (res[0] && res.length) {
setContractName("_Contactbook");
const objectId = res[0].objectId;
setSignerUserId(objectId);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
@@ -487,33 +491,12 @@ function PdfRequestFiles() {
setRequestSignTour(checkTourRequestSign[0].requestSign);
}
}
} else if (res?.length === 0) {
const res = await contactBook(currUserId);
if (res === "Error: Something went wrong!") {
setHandleError("Error: Something went wrong!");
} else if (res[0] && res.length) {
setContractName("_Contactbook");
const objectId = res[0].objectId;
setSignerUserId(objectId);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
const checkTourRequestSign = tourstatus.filter(
(data) => data.requestSign
);
if (checkTourRequestSign && checkTourRequestSign.length > 0) {
setRequestSignTour(checkTourRequestSign[0].requestSign);
}
}
} else if (res.length === 0) {
setHandleError("Error: User does not exist!");
}
} else if (res.length === 0) {
setHandleError("Error: User does not exist!");
}
}
setIsUiLoading(false);
} else {
alert("No data found!");
}
setIsUiLoading(false);
} else if (
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
@@ -804,7 +787,10 @@ function PdfRequestFiles() {
(x) => x.Email === jsonSender.email
);
const newIndex = index + 1;
const user = pdfDetails?.[0].Signers[newIndex];
const usermail = {
Email: pdfDetails?.[0]?.Placeholders[newIndex]?.email || ""
};
const user = usermail || pdfDetails?.[0]?.Signers[newIndex];
if (sendmail !== "false" && sendInOrder) {
const requestBody = pdfDetails?.[0]?.RequestBody;
const requestSubject = pdfDetails?.[0]?.RequestSubject;
@@ -828,19 +814,26 @@ function PdfRequestFiles() {
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
let url = `${localStorage.getItem(
"baseUrl"
)}functions/sendmailv3/`;
)}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id":
localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const objectId = user.objectId;
const objectId = user?.objectId;
const hostUrl = window.location.origin;
//encode this url value `${pdfDetails?.[0].objectId}/${user.Email}/${objectId}` to base64 using `btoa` function
const encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${user.Email}/${objectId}`
);
let encodeBase64;
if (objectId) {
encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${user.Email}/${objectId}`
);
} else {
encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${user.Email}`
);
}
let signPdf = `${hostUrl}/login/${encodeBase64}`;
const openSignUrl =
"https://www.opensignlabs.com/contact-us";
@@ -1605,7 +1598,7 @@ function PdfRequestFiles() {
onClick={() =>
handleDownloadCertificate(
pdfDetails,
setIsCertificate
setIsDownloading
)
}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
@@ -1619,7 +1612,9 @@ function PdfRequestFiles() {
</span>
</button>
<button
onClick={(e) => handleToPrint(e, pdfUrl)}
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading)
}
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
>
@@ -1633,7 +1628,11 @@ function PdfRequestFiles() {
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl)
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
>
<i
@@ -1648,12 +1647,25 @@ function PdfRequestFiles() {
)}
</div>
</ModalUi>
{isDownloading === "pdf" && (
<div className="fixed z-[1000] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<div
style={{ fontSize: "45px", color: "#3dd3e0" }}
className="loader-37"
></div>
</div>
)}
<ModalUi
isOpen={isCertificate}
title={"Generating certificate"}
handleClose={() => setIsCertificate(false)}
isOpen={isDownloading === "certificate"}
title={
isDownloading === "certificate"
? "Generating certificate"
: "PDF Download"
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center">
{isDownloading === "certificate"}{" "}
<p>
Your completion certificate is being generated. Please
wait momentarily.
+11 -2
View File
@@ -16,8 +16,17 @@ const PlanSubscriptions = () => {
const navigate = useNavigate();
const [yearlyVisible, setYearlyVisible] = useState(false);
const [isLoader, setIsLoader] = useState(true);
const userDetails = JSON.parse(localStorage.getItem("userDetails"));
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
const user = {
name: extUser?.Name,
email: extUser?.Email,
company: extUser?.Company,
phone: extUser?.Phone
};
const userDetails = JSON.parse(localStorage.getItem("userDetails")) || user;
// console.log("userDetails ", userDetails);
const name =
userDetails && userDetails.name
? "first_name=" + encodeURIComponent(userDetails.name)
@@ -26,7 +26,6 @@ const ReportTable = (props) => {
const [currentPage, setCurrentPage] = useState(1);
const [actLoader, setActLoader] = useState({});
const [isAlert, setIsAlert] = useState(false);
const [isDocErr, setIsDocErr] = useState(false);
const [isContactform, setIsContactform] = useState(false);
const [isDeleteModal, setIsDeleteModal] = useState({});
const [isRevoke, setIsRevoke] = useState({});
@@ -50,6 +49,7 @@ const ReportTable = (props) => {
const [selectedPublicRole, setSelectedPublicRole] = useState("");
const [isCelebration, setIsCelebration] = useState(false);
const [isViewShare, setIsViewShare] = useState({});
const startIndex = (currentPage - 1) * props.docPerPage;
const { isMoreDocs, setIsNextRecord } = props;
// For loop is used to calculate page numbers visible below table
@@ -191,7 +191,6 @@ const ReportTable = (props) => {
setActLoader({});
}
} else {
setIsDocErr(true);
setActLoader({});
}
} else {
@@ -327,14 +326,18 @@ 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
const encodeBase64 = btoa(
`${item.objectId}/${x.Email}/${x.objectId}/${sendMail}`
);
return `${host}/login/${encodeBase64}`;
if (x.objectId) {
const encodeBase64 = btoa(
`${item.objectId}/${x.signerPtr.Email}/${x.signerPtr.objectId}/${sendMail}`
);
return `${host}/login/${encodeBase64}`;
} else {
const encodeBase64 = btoa(`${item.objectId}/${x.email}`);
return `${host}/login/${encodeBase64}`;
}
};
const urls = item.Signers.map((x) => ({
email: x.Email,
const urls = item?.Placeholders?.map((x) => ({
email: x.email ? x.email : x.signerPtr.Email,
url: getUrl(x)
}));
setShareUrls(urls);
@@ -355,9 +358,9 @@ const ReportTable = (props) => {
await axios
.put(
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
"_appName"
)}_Document/${item.objectId}`,
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${
item.objectId
}`,
data,
{
headers: {
@@ -593,9 +596,15 @@ const ReportTable = (props) => {
};
try {
const res = await axios.post(url, params, { headers: headers });
if (res) {
if (res?.data?.result?.status === "success") {
setIsAlert(true);
setAlertMsg({ type: "success", message: "Mail sent successfully." });
} else {
setIsAlert(true);
setAlertMsg({
type: "danger",
message: "Something went wrong, please try again later!"
});
}
} catch (err) {
console.log("err in sendmail", err);
@@ -682,18 +691,12 @@ const ReportTable = (props) => {
}
);
const templateRes = axiosRes.data && axiosRes.data.result;
if (templateRes?.Placeholders?.length > 0) {
setPlaceholders(templateRes?.Placeholders);
setTemplateDetails(templateRes);
setIsLoader({});
} else {
setIsLoader(false);
setIsDocErr(true);
}
setPlaceholders(templateRes?.Placeholders);
setTemplateDetails(templateRes);
setIsLoader({});
} catch (err) {
console.log("err in fetch template in bulk modal", err);
setIsBulkSend({});
setIsDocErr(false);
setIsAlert(true);
setAlertMsg({
type: "danger",
@@ -766,6 +769,9 @@ const ReportTable = (props) => {
}
};
const handleViewSigners = (item) => {
setIsViewShare({ [item.objectId]: true });
};
return (
<div className="relative">
{Object.keys(actLoader)?.length > 0 && (
@@ -925,7 +931,17 @@ const ReportTable = (props) => {
{formatRow(item?.ExtUserPtr)}
</td>
<td className="px-4 py-2">
{item?.Signers ? formatRow(item?.Signers) : "-"}
{/* {item?.Signers ? formatRow(item?.Signers) : "-"} */}
{item?.Placeholders ? (
<button
onClick={() => handleViewSigners(item)}
className="text-[blue] hover:underline focus:outline-none"
>
View
</button>
) : (
"-"
)}
</td>
<td className="px-2 py-2 text-white flex flex-row gap-x-2 gap-y-1 justify-center items-center">
{props.actions?.length > 0 &&
@@ -955,19 +971,21 @@ const ReportTable = (props) => {
)}
{isOption[item.objectId] &&
act.action === "option" && (
<div className="absolute -right-2 top-5 bg-white text-nowrap rounded shadow z-[20] overflow-hidden">
<div className="absolute -right-2 top-5 p-1.5 bg-white text-nowrap rounded shadow-md 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 rounded cursor-pointer px-2 py-1.5 flex justify-start items-center text-black"
onClick={() =>
handleActionBtn(subact, item)
}
title={subact.hoverLabel}
>
<i className={subact.btnIcon}></i>
<i
className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="ml-[4px] text-xs capitalize">
<span className="ml-[4px] text-[13px] capitalize font-medium">
{subact.btnLabel}
</span>
)}
@@ -977,6 +995,48 @@ const ReportTable = (props) => {
)}
</button>
))}
{isViewShare[item.objectId] && (
<div className="fixed z-[999] inset-0 w-full h-full bg-black bg-opacity-[75%]">
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-sm bg-white rounded shadow-md max-h-90 min-w-[90%] md:min-w-[400px] overflow-y-auto max-h-[340px] md:max-h-[400px] hide-scrollbar">
<div
className="cursor-pointer absolute text-white text-[22px] font-medium rounded-full z-50 top-1 right-3"
onClick={() => setIsViewShare({})}
>
&times;
</div>
<table className="table-auto w-full">
<thead className="text-white h-[38px] sticky top-0 bg-[#32a3ac]">
<tr>
{props.ReportName === "Templates" && (
<th className="p-2 pl-3">Roles</th>
)}
<th className="p-2">Signers</th>
</tr>
</thead>
<tbody>
{item.Placeholders.map((x, i) => (
<tr
key={i}
className="text-sm font-normal text-black odd:bg-white even:bg-gray-200"
>
{props.ReportName === "Templates" && (
<td className="text-[12px] p-2 pl-3">
{x.Role && x.Role}
</td>
)}
<td className="text-[12px] p-2 break-all">
{x.email
? x.email
: x?.signerPtr?.Email || "-"}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{isDeleteModal[item.objectId] && (
<ModalUi
isOpen
@@ -1025,19 +1085,11 @@ const ReportTable = (props) => {
></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}
/>
)}
</>
<BulkSendUi
Placeholders={placeholders}
item={templateDeatils}
handleClose={handleQuickSendClose}
/>
)}
</ModalUi>
)}
+1 -2
View File
@@ -1,4 +1,4 @@
import PDF from './parsefunction/pdf/PDF.min.js';
import PDF from './parsefunction/pdf/PDF.js';
import sendmailv3 from './parsefunction/sendMailv3.js';
import GoogleSign from './parsefunction/GoogleSign.js';
import ZohoDetails from './parsefunction/ZohoDetails.js';
@@ -40,7 +40,6 @@ import { getSignedUrl } from './parsefunction/getSignedUrl.js';
import createBatchDocs from './parsefunction/createBatchDocs.js';
import linkContactToDoc from './parsefunction/linkContactToDoc.js';
import CreatePublicTemplate from './parsefunction/CreatePublicTemplate.js';
import GetPublicUserName from './parsefunction/getPublicUserName.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);
@@ -26,12 +26,15 @@ export default async function ZohoDetails(request) {
// console.log("Access Token:", res.data);
if (res.data.access_token) {
const hostedpages = request.params.hostedpagesId;
const userData = await axios.get('https://billing.zoho.in/api/v1/hostedpages/' + hostedpages, {
headers: {
Authorization: 'Zoho-oauthtoken ' + res.data.access_token,
'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID,
},
});
const userData = await axios.get(
'https://www.zohoapis.in/billing/v1/hostedpages/' + hostedpages,
{
headers: {
Authorization: 'Zoho-oauthtoken ' + res.data.access_token,
'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID,
},
}
);
const first_name = userData.data.data.subscription.contactpersons[0].first_name || '';
const last_name = userData.data.data.subscription.contactpersons[0].last_name || '';
@@ -17,7 +17,12 @@ async function sendMail(document, sessionToken) {
year: 'numeric',
});
const sender = document.ExtUserPtr.Email;
const signerMail = document.Placeholders;
let signerMail = document.Placeholders;
if (document.SendinOrder) {
signerMail = signerMail.slice();
signerMail.splice(1);
}
for (let i = 0; i < signerMail.length; i++) {
try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
@@ -42,7 +47,8 @@ async function sendMail(document, sessionToken) {
let params = {
extUserId: document.ExtUserPtr.objectId,
recipient: objectId ? signerMail[i].signerPtr.Email : signerMail[i].email,
subject: `${document.ExtUserPtr.Name} has requested you to sign ${document.Name}`,
subject: `${document.ExtUserPtr.Name} has requested you to sign "${document.Name}"`,
mailProvider: document?.ExtUserPtr?.active_mail_adapter || '',
from: sender,
html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src='" +
@@ -160,7 +166,6 @@ export default async function createBatchDocs(request) {
createdAt: response.data[i]?.success?.createdAt,
}));
for (let i = 0; i < updateDocuments.length; i++) {
// console.log('updateDocuments ', updateDocuments);
sendMail(updateDocuments[i], sessionToken);
}
return 'success';
@@ -99,8 +99,8 @@ export default async function linkContactToDoc(req) {
};
updateDoc.set('Placeholders', Placeholders);
const Acl = docRes.getACL();
Acl.setReadAccess(existContact.get('CreatedBy').id, true);
Acl.setWriteAccess(existContact.get('CreatedBy').id, true);
Acl.setReadAccess(existContact.get('UserId').id, true);
Acl.setWriteAccess(existContact.get('UserId').id, true);
updateDoc.setACL(Acl);
// const parseData = JSON.parse(JSON.stringify(res));
const resDoc = await updateDoc.save(null, { useMasterKey: true });
@@ -0,0 +1,473 @@
import fs from 'node:fs';
import axios from 'axios';
import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
import { PDFDocument } from 'pdf-lib';
import { replaceMailVaribles, saveFileUsage } from '../../../Utils.js';
import GenerateCertificate from './GenerateCertificate.js';
const serverUrl = process.env.SERVER_URL;
const APPID = process.env.APP_ID;
const masterKEY = process.env.MASTER_KEY;
const eSignName = 'opensign';
const eSigncontact = 'hello@opensignlabs.com';
// `updateDoc` is used to create url in from pdfFile
async function uploadFile(pdfName, filepath) {
try {
const filedata = fs.readFileSync(filepath);
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
await file.save({ useMasterKey: true });
const fileUrl = file.url();
return { imageUrl: fileUrl };
} catch (err) {
console.log('Err ', err);
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
fs.unlinkSync(filepath);
}
}
// `updateDoc` is used to update signedUrl, AuditTrail, Iscompleted in document
async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
try {
const UserPtr = {
__type: 'Pointer',
className: className,
objectId: userId,
};
const obj = {
UserPtr: UserPtr,
SignedUrl: url,
Activity: 'Signed',
ipAddress: ipAddress,
SignedOn: new Date(),
Signature: sign,
};
let updateAuditTrail;
if (data.AuditTrail && data.AuditTrail.length > 0) {
const AuditTrail = JSON.parse(JSON.stringify(data.AuditTrail));
const existingIndex = AuditTrail.findIndex(
entry => entry.UserPtr.objectId === userId && entry.Activity !== 'Created'
);
existingIndex !== -1
? (AuditTrail[existingIndex] = { ...AuditTrail[existingIndex], ...obj })
: AuditTrail.push(obj);
updateAuditTrail = AuditTrail;
} else {
updateAuditTrail = [obj];
}
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
let isCompleted = false;
if (data.Signers && data.Signers.length > 0) {
if (auditTrail.length === data.Placeholders.length) {
isCompleted = true;
}
} else {
isCompleted = true;
}
const body = {
SignedUrl: url,
AuditTrail: updateAuditTrail,
IsCompleted: isCompleted,
};
const signedRes = await axios.put(serverUrl + '/classes/contracts_Document/' + docId, body, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
return { isCompleted: isCompleted, message: 'success', AuditTrail: updateAuditTrail };
} catch (err) {
console.log('update doc err ', err);
return 'err';
}
}
// `sendCompletedMail` is used to send copy of completed document mail
async function sendCompletedMail(obj) {
const url = obj.url;
const doc = obj.doc;
const sender = obj.doc.ExtUserPtr;
const pdfName = doc.Name;
const mailLogo = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
const recipient = sender.Email;
let subject = `Document "${pdfName}" has been signed by all parties`;
let body =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=" +
mailLogo +
" height='50' style='padding:20px'/> </div><div style='padding:2px;font-family:system-ui; background-color: #47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document signed successfully</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document " +
`<b>"${pdfName}"</b>` +
'. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' +
sender.Email +
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>';
if (obj?.isCustomMail) {
try {
const tenantCreditsQuery = new Parse.Query('partners_Tenant');
tenantCreditsQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: sender.UserId.objectId,
});
const res = await tenantCreditsQuery.first();
if (res) {
const _res = JSON.parse(JSON.stringify(res));
if (_res?.CompletionSubject) {
subject = _res?.CompletionSubject;
}
if (_res?.CompletionBody) {
body = _res?.CompletionBody;
}
const expireDate = doc.ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
const variables = {
document_title: pdfName,
sender_name: sender.Name,
sender_mail: sender.Email,
sender_phone: sender.Phone,
receiver_name: sender.Name,
receiver_email: sender.Email,
receiver_phone: sender.Phone,
expiry_date: localExpireDate,
company_name: sender.Company,
};
const replaceVar = replaceMailVaribles(subject, body, variables);
subject = replaceVar.subject;
body = replaceVar.body;
}
} catch (err) {
console.log('error in fetch tenant in signpdf', err.message);
}
}
const params = {
extUserId: sender.objectId,
url: url,
from: 'OpenSign™',
recipient: recipient,
subject: subject,
pdfName: pdfName,
html: body,
mailProvider: obj.mailProvider,
};
const res = await axios.post(serverUrl + '/functions/sendmailv3', params, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
// console.log('Res ', res);
}
// `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, Url, event, signUser, certificateUrl) {
let signers = [];
if (signUser) {
signers = {
name: signUser?.Name,
email: signUser?.Email,
phone: signUser?.Phone,
};
} else {
signers = doc?.Signers?.map(x => ({
name: x.Name,
email: x.Email,
phone: x.Phone,
})) || [
{
name: doc?.ExtUserPtr?.Name,
email: doc?.ExtUserPtr?.Email,
phone: doc?.ExtUserPtr?.Phone,
},
];
}
if (doc.ExtUserPtr?.Webhook) {
const time =
event === 'signed'
? { signer: signers, signedAt: new Date() }
: { signers: signers, completedAt: new Date() };
const certificate = certificateUrl ? { certificate: certificateUrl } : {};
const params = {
event: event,
objectId: doc?.objectId,
file: Url || '',
...certificate,
name: doc?.Name,
note: doc?.Note || '',
description: doc?.Description || '',
...time,
createdAt: doc?.createdAt,
};
axios
.post(doc?.ExtUserPtr?.Webhook, params, {
headers: { 'Content-Type': 'application/json' },
})
.then(res => {
try {
// console.log('res ', res);
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', res?.status);
webhook.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: doc.ExtUserPtr.UserId.objectId,
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
})
.catch(err => {
console.log('Err send data to webhook', err.message);
try {
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', err?.status);
webhook.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: doc.ExtUserPtr.UserId.objectId,
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
});
// console.log('res ', res.data);
}
}
// `sendMailsaveCertifcate` is used send completion mail and update complete status of document
const sendMailsaveCertifcate = async (doc, P12Buffer, url, isCustomMail, mailProvider, userId) => {
const certificate = await GenerateCertificate(doc);
const certificatePdf = await PDFDocument.load(certificate);
const p12 = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
// `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate
pdflibAddPlaceholder({
pdfDoc: certificatePdf,
reason: 'Digitally signed by OpenSign.',
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await certificatePdf.save();
const CertificateBuffer = Buffer.from(pdfWithPlaceholderBytes);
//`new signPDF` create new instance of CertificateBuffer and p12Buffer
const certificateOBJ = new SignPdf();
// `signedCertificate` is used to sign certificate digitally
const signedCertificate = await certificateOBJ.sign(CertificateBuffer, p12);
//below is used to save signed certificate in exports folder
fs.writeFileSync('./exports/certificate.pdf', signedCertificate);
const file = await uploadFile('certificate.pdf', './exports/certificate.pdf');
const body = { CertificateUrl: file.imageUrl };
await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
// used in API only
if (doc.IsSendMail && doc.IsSendMail === false) {
console.log("don't send mail");
} else {
const mailObj = {
url: url,
isCustomMail: isCustomMail,
doc: doc,
mailProvider: mailProvider,
};
sendCompletedMail(mailObj);
}
saveFileUsage(CertificateBuffer.length, file.imageUrl, userId);
sendDoctoWebhook(doc, url, 'completed', '', file.imageUrl);
};
/**
*
* @param docId Id of Document in which user is signing
* @param pdfFile base64 of pdfFile which you want sign
* @returns if success {status, data} else {status, message}
*/
async function PDF(req) {
try {
if (!req?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
} else {
const password = '';
const authUser = req?.user?.toJSON();
const docId = req.params.docId;
const reqUserId = req.params.userId;
const isCustomMail = req.params.isCustomCompletionMail || false;
const mailProvider = req.params.mailProvider || '';
const sign = req.params.signature || '';
// below bode is used to get info of docId
const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr,Signers');
docQuery.equalTo('objectId', docId);
const resDoc = await docQuery.first({ useMasterKey: true });
if (!resDoc) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
const _resDoc = resDoc?.toJSON();
let signUser;
let className;
// `reqUserId` is send throught pdfrequest signing flow
if (reqUserId) {
// to get contracts_Contactbook details for currentuser from reqUserId
const _contractUser = _resDoc.Signers.find(x => x.objectId === reqUserId);
if (_contractUser) {
signUser = _contractUser;
className = 'contracts_Contactbook';
}
} else {
className = 'contracts_Users';
signUser = _resDoc.ExtUserPtr;
}
const username = signUser.Name;
const userEmail = signUser.Email;
if (req.params.pdfFile) {
// `PdfBuffer` used to create buffer from pdf file
let PdfBuffer = Buffer.from(req.params.pdfFile, 'base64');
// `P12Buffer` used to create buffer from p12 certificate
const pfxFile = process.env.PFX_BASE64;
// const P12Buffer = fs.readFileSync();
const P12Buffer = Buffer.from(pfxFile, 'base64');
const p12Cert = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
const UserPtr = {
__type: 'Pointer',
className: className,
objectId: signUser.objectId,
};
const obj = {
UserPtr: UserPtr,
SignedUrl: '',
Activity: 'Signed',
ipAddress: req.headers['x-real-ip'],
};
let updateAuditTrail;
if (_resDoc.AuditTrail && _resDoc.AuditTrail.length > 0) {
updateAuditTrail = [..._resDoc.AuditTrail, obj];
} else {
updateAuditTrail = [obj];
}
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
let isCompleted = false;
if (_resDoc.Signers && _resDoc.Signers.length > 0) {
if (auditTrail.length === _resDoc.Signers.length) {
isCompleted = true;
}
} else {
isCompleted = true;
}
const randomNumber = Math.floor(Math.random() * 5000);
const name = `exported_file_${randomNumber}.pdf`;
const pdfName = `./exports/${name}`;
let pdfSize = PdfBuffer.length;
if (isCompleted) {
const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>');
if (signersName && signersName.length > 0) {
// `pdflibAddPlaceholder` is used to add code of only digitial sign without widget
const pdfDoc = await PDFDocument.load(PdfBuffer);
pdflibAddPlaceholder({
pdfDoc: pdfDoc,
reason: 'Digitally signed by OpenSign for ' + signersName?.join(', '),
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
} else {
// `pdflibAddPlaceholder` is used to add code of only digitial sign without widget (signyourself)
const pdfDoc = await PDFDocument.load(PdfBuffer);
pdflibAddPlaceholder({
pdfDoc: pdfDoc,
reason: 'Digitally signed by OpenSign for ' + username + ' <' + userEmail + '>',
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
}
//`new signPDF` create new instance of pdfBuffer and p12Buffer
const OBJ = new SignPdf();
// `signedDocs` is used to signpdf digitally
const signedDocs = await OBJ.sign(PdfBuffer, p12Cert);
//`saveUrl` is used to save signed pdf in exports folder
const saveUrl = fs.writeFileSync(pdfName, signedDocs);
pdfSize = signedDocs.length;
} else {
//`saveUrl` is used to save signed pdf in exports folder
const saveUrl = fs.writeFileSync(pdfName, PdfBuffer);
pdfSize = PdfBuffer.length;
}
// `uploadFile` is used to upload pdf to aws s3 and get it's url
const data = await uploadFile(name, pdfName);
if (data && data.imageUrl) {
// `axios` is used to update signed pdf url in contracts_Document classes for given DocId
const updatedDoc = await updateDoc(
req.params.docId, //docId
data.imageUrl, // url
signUser.objectId, // userID
req.headers['x-real-ip'], // client ipAddress,
_resDoc, // auditTrail, signers, etc data
className,
sign
);
sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser);
saveFileUsage(pdfSize, data.imageUrl, authUser.objectId);
if (updatedDoc && updatedDoc.isCompleted) {
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail };
sendMailsaveCertifcate(
doc,
P12Buffer,
data.imageUrl,
isCustomMail,
mailProvider,
authUser.objectId
);
}
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
fs.unlinkSync(pdfName);
console.log(`New Signed PDF created called: ${pdfName}`);
if (updatedDoc.message === 'success') {
return { status: 'success', data: data.imageUrl };
} else {
const error = new Error('Please provide required parameters!');
error.code = 400; // Set the error code (e.g., 400 for bad request)
throw error;
}
}
} else {
const error = new Error('Pdf file not present!');
error.code = 400; // Set the error code (e.g., 400 for bad request)
throw error;
}
}
} catch (err) {
console.log('Err in signpdf', err);
throw err;
}
}
export default PDF;
-337
View File
@@ -1,337 +0,0 @@
import fs from 'node:fs';
import axios from 'axios';
import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
import { PDFDocument } from 'pdf-lib';
import { replaceMailVaribles, saveFileUsage } from '../../../Utils.js';
import GenerateCertificate from './GenerateCertificate.js';
const serverUrl = process.env.SERVER_URL,
APPID = process.env.APP_ID,
masterKEY = process.env.MASTER_KEY,
eSignName = 'opensign',
eSigncontact = 'hello@opensignlabs.com';
async function uploadFile(e, t) {
try {
var a = fs.readFileSync(t),
r = new Parse.File(e, [...a], 'application/pdf'),
i = (await r.save({ useMasterKey: !0 }), r.url());
return { imageUrl: i };
} catch (e) {
console.log('Err ', e), fs.unlinkSync(t);
}
}
async function updateDoc(a, r, i, n, s, o, l) {
try {
var c,
d,
p = {
UserPtr: { __type: 'Pointer', className: o, objectId: i },
SignedUrl: r,
Activity: 'Signed',
ipAddress: n,
SignedOn: new Date(),
Signature: l,
};
let e;
var m = (e =
s.AuditTrail && 0 < s.AuditTrail.length
? (-1 !==
(d = (c = JSON.parse(JSON.stringify(s.AuditTrail))).findIndex(
e => e.UserPtr.objectId === i && 'Created' !== e.Activity
))
? (c[d] = { ...c[d], ...p })
: c.push(p),
c)
: [p]).filter(e => 'Signed' === e.Activity);
let t = !1;
!((s.Signers && 0 < s.Signers.length && m.length !== s.Signers.length) || !(t = !0));
var g = { SignedUrl: r, AuditTrail: e, IsCompleted: t };
await axios.put(serverUrl + '/classes/contracts_Document/' + a, g, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
return { isCompleted: t, message: 'success', AuditTrail: e };
} catch (e) {
return console.log('update doc err ', e), 'err';
}
}
async function sendCompletedMail(e) {
var t = e.url,
a = e.doc,
r = e.doc.ExtUserPtr,
i = a.Name,
n = r.Email;
let s = `Document "${i}" has been signed by all parties`,
o =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=https://qikinnovation.ams3.digitaloceanspaces.com/logo.png height='50' style='padding:20px'/> </div><div style='padding:2px;font-family:system-ui; background-color: #47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document signed successfully</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document " +
`<b>"${i}"</b>` +
'. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' +
r.Email +
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>';
if (e?.isCustomMail)
try {
var l,
c,
d,
p,
m,
g = new Parse.Query('partners_Tenant');
g.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: r.UserId.objectId });
const f = await g.first();
f &&
((l = JSON.parse(JSON.stringify(f)))?.CompletionSubject && (s = l?.CompletionSubject),
l?.CompletionBody && (o = l?.CompletionBody),
(c = a.ExpiryDate.iso),
(d = new Date(c).toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
})),
(p = {
document_title: i,
sender_name: r.Name,
sender_mail: r.Email,
sender_phone: r.Phone,
receiver_name: r.Name,
receiver_email: r.Email,
receiver_phone: r.Phone,
expiry_date: d,
company_name: r.Company,
}),
(m = replaceMailVaribles(s, o, p)),
(s = m.subject),
(o = m.body));
} catch (e) {
console.log('error in fetch tenant in signpdf', e.message);
}
g = {
extUserId: r.objectId,
url: t,
from: 'OpenSign™',
recipient: n,
subject: s,
pdfName: i,
html: o,
mailProvider: e.mailProvider,
};
await axios.post(serverUrl + '/functions/sendmailv3', g, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
}
async function sendDoctoWebhook(a, e, t, r, i) {
let n = [];
(n = r
? { name: r?.Name, email: r?.Email, phone: r?.Phone }
: a?.Signers?.map(e => ({ name: e.Name, email: e.Email, phone: e.Phone })) || [
{ name: a?.ExtUserPtr?.Name, email: a?.ExtUserPtr?.Email, phone: a?.ExtUserPtr?.Phone },
]),
a.ExtUserPtr?.Webhook &&
((r =
'signed' === t
? { signer: n, signedAt: new Date() }
: { signers: n, completedAt: new Date() }),
(t = {
event: t,
objectId: a?.objectId,
file: e || '',
...(i ? { certificate: i } : {}),
name: a?.Name,
note: a?.Note || '',
description: a?.Description || '',
...r,
createdAt: a?.createdAt,
}),
axios
.post(a?.ExtUserPtr?.Webhook, t, { headers: { 'Content-Type': 'application/json' } })
.then(e => {
try {
var t = new Parse.Object('contracts_Webhook');
t.set('Log', e?.status),
t.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: a.ExtUserPtr.UserId.objectId,
}),
t.save(null, { useMasterKey: !0 });
} catch (e) {
console.log('err save in contracts_Webhook', e.message);
}
})
.catch(e => {
console.log('Err send data to webhook', e.message);
try {
var t = new Parse.Object('contracts_Webhook');
t.set('Log', e?.status),
t.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: a.ExtUserPtr.UserId.objectId,
}),
t.save(null, { useMasterKey: !0 });
} catch (e) {
console.log('err save in contracts_Webhook', e.message);
}
}));
}
const sendMailsaveCertifcate = async (e, t, a, r, i, n) => {
var s = await GenerateCertificate(e),
s = await PDFDocument.load(s),
t = new P12Signer(t, { passphrase: process.env.PASS_PHRASE || null }),
s =
(pdflibAddPlaceholder({
pdfDoc: s,
reason: 'Digitally signed by OpenSign.',
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15e3,
}),
await s.save()),
s = Buffer.from(s),
t = await new SignPdf().sign(s, t),
t =
(fs.writeFileSync('./exports/certificate.pdf', t),
await uploadFile('certificate.pdf', './exports/certificate.pdf')),
o = { CertificateUrl: t.imageUrl };
await axios.put(serverUrl + '/classes/contracts_Document/' + e.objectId, o, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
}),
e.IsSendMail && !1 === e.IsSendMail
? console.log("don't send mail")
: sendCompletedMail({ url: a, isCustomMail: r, doc: e, mailProvider: i }),
saveFileUsage(s.length, t.imageUrl, n),
sendDoctoWebhook(e, a, 'completed', '', t.imageUrl);
};
async function PDF(s) {
try {
if (!s?.user)
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
{
var o = s?.user?.toJSON(),
e = s.params.docId;
const C = s.params.userId;
var l = s.params.isCustomCompletionMail || !1,
c = s.params.mailProvider || '',
d = s.params.signature || '',
t = new Parse.Query('contracts_Document'),
a =
(t.include('ExtUserPtr,Signers'),
t.equalTo('objectId', e),
await t.first({ useMasterKey: !0 }));
if (!a) throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
var r,
p = a?.toJSON();
let i, n;
C
? ((r = p.Signers.find(e => e.objectId === C)),
console.log('_contractUser ', r),
r && ((i = r), (n = 'contracts_Contactbook')))
: ((n = 'contracts_Users'), (i = p.ExtUserPtr));
var m,
g = i.Name,
f = i.Email;
if (!s.params.pdfFile) throw (((m = new Error('Pdf file not present!')).code = 400), m);
{
let e = Buffer.from(s.params.pdfFile, 'base64');
var u = process.env.PFX_BASE64,
h = Buffer.from(u, 'base64'),
y = new P12Signer(h, { passphrase: process.env.PASS_PHRASE || null }),
P = {
UserPtr: { __type: 'Pointer', className: n, objectId: i.objectId },
SignedUrl: '',
Activity: 'Signed',
ipAddress: s.headers['x-real-ip'],
};
let t;
var v = (t = p.AuditTrail && 0 < p.AuditTrail.length ? [...p.AuditTrail, P] : [P]).filter(
e => 'Signed' === e.Activity
);
let a = !1;
!((p.Signers && 0 < p.Signers.length && v.length !== p.Signers.length) || !(a = !0));
var S,
b,
w,
U,
I,
_,
E = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
D = './exports/' + E;
let r = e.length;
r = (
a
? ((S = p.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
(e =
S && 0 < S.length
? ((b = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: b,
reason: 'Digitally signed by OpenSign for ' + S?.join(', '),
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15e3,
}),
(w = await b.save()),
Buffer.from(w))
: ((U = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: U,
reason: 'Digitally signed by OpenSign for ' + g + ' <' + f + '>',
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15e3,
}),
(I = await U.save()),
Buffer.from(I))),
(_ = await new SignPdf().sign(e, y)),
fs.writeFileSync(D, _),
_)
: (fs.writeFileSync(D, e), e)
).length;
var A = await uploadFile(E, D);
if (A && A.imageUrl) {
var x,
j,
N = await updateDoc(
s.params.docId,
A.imageUrl,
i.objectId,
s.headers['x-real-ip'],
p,
n,
d
);
if (
(sendDoctoWebhook(p, A.imageUrl, 'signed', i),
saveFileUsage(r, A.imageUrl, o.objectId),
N &&
N.isCompleted &&
((x = { ...p, AuditTrail: N.AuditTrail }),
sendMailsaveCertifcate(x, h, A.imageUrl, l, c, o.objectId)),
fs.unlinkSync(D),
console.log('New Signed PDF created called: ' + D),
'success' === N.message)
)
return { status: 'success', data: A.imageUrl };
throw (((j = new Error('Please provide required parameters!')).code = 400), j);
}
}
}
} catch (e) {
throw (console.log('Err in signpdf', e), e);
}
}
export default PDF;
@@ -27,6 +27,7 @@ export default function reportJson(id, userId) {
'Signers.Name',
'Signers.Email',
'Signers.Phone',
'Placeholders',
],
};
@@ -67,6 +68,7 @@ export default function reportJson(id, userId) {
'Signers.Phone',
'Signers.UserId',
'AuditTrail',
'Placeholders',
],
};
// In progess report
@@ -75,7 +77,7 @@ export default function reportJson(id, userId) {
reportName: 'In-progress documents',
params: {
Type: { $ne: 'Folder' },
Signers: { $exists: true, $ne: [] },
SignedUrl: { $ne: null },
Placeholders: { $ne: null },
IsCompleted: { $ne: true },
IsDeclined: { $ne: true },
@@ -102,6 +104,7 @@ export default function reportJson(id, userId) {
'AuditTrail.UserPtr',
'ExpiryDate',
'SendMail',
'Placeholders',
],
};
// completed documents report
@@ -130,6 +133,7 @@ export default function reportJson(id, userId) {
'Signers.Email',
'Signers.Phone',
'TimeToCompleteDays',
'Placeholders',
],
};
@@ -157,6 +161,7 @@ export default function reportJson(id, userId) {
'Signers.Name',
'Signers.Email',
'Signers.Phone',
'Placeholders',
],
};
// Expired Documents report
@@ -187,6 +192,7 @@ export default function reportJson(id, userId) {
'Signers.Name',
'Signers.Email',
'Signers.Phone',
'Placeholders',
],
};
// Recently sent for signatures report show on dashboard
@@ -195,7 +201,7 @@ export default function reportJson(id, userId) {
reportName: 'Recently sent for signatures',
params: {
Type: { $ne: 'Folder' },
Signers: { $exists: true, $ne: [] },
SignedUrl: { $ne: null },
Placeholders: { $ne: null },
IsCompleted: { $ne: true },
IsDeclined: { $ne: true },
@@ -220,6 +226,7 @@ export default function reportJson(id, userId) {
'AuditTrail',
'AuditTrail.UserPtr',
'ExpiryDate',
'Placeholders',
],
};
// Recent signature requests report show on dashboard
@@ -257,6 +264,7 @@ export default function reportJson(id, userId) {
'AuditTrail',
'Signers.Email',
'Signers.Phone',
'Placeholders',
],
};
// Drafts report show on dashboard
@@ -284,6 +292,7 @@ export default function reportJson(id, userId) {
'Signers.Name',
'Signers.Email',
'Signers.Phone',
'Placeholders',
],
};
// contact book report
@@ -7,6 +7,7 @@ exports.up = async Parse => {
const className = 'contracts_Template';
const schema = new Parse.Schema(className);
schema.addArray('PublicRole');
schema.addBoolean('IsPublic');
// TODO: Set the schema here
// Example:
@@ -24,6 +25,7 @@ exports.down = async Parse => {
const className = 'contracts_Template';
const schema = new Parse.Schema(className);
schema.deleteField('PublicRole');
schema.deleteField('IsPublic');
// TODO: Set the schema here
// Example:
+1 -1
View File
@@ -101,7 +101,7 @@ export const config = {
masterKey: process.env.MASTER_KEY, //Add your master key here. Keep it secret!
masterKeyIps: ['0.0.0.0/0', '::/0'], // '::1'
serverURL: 'http://localhost:8080/app', // Don't forget to change to https if needed
verifyUserEmails: isMailAdapter === true ? true : false,
verifyUserEmails: false,
publicServerURL: process.env.SERVER_URL || 'http://localhost:8080/app',
// Your apps name. This will appear in the subject and body of the emails that are sent.
appName: 'Opensign',
+8 -8
View File
@@ -38,11 +38,11 @@
"posthog-node": "^4.0.1"
},
"devDependencies": {
"@babel/eslint-parser": "^7.24.5",
"@babel/eslint-parser": "^7.24.6",
"eslint": "^9.3.0",
"jasmine": "^5.1.0",
"mongodb-runner": "^5.6.2",
"nodemon": "^3.1.0",
"nodemon": "^3.1.1",
"nyc": "15.1.0",
"prettier": "^3.2.5",
"uglify-js": "^3.17.4"
@@ -1303,9 +1303,9 @@
}
},
"node_modules/@babel/eslint-parser": {
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.5.tgz",
"integrity": "sha512-gsUcqS/fPlgAw1kOtpss7uhY6E9SFFANQ6EFX5GTvzUwaV0+sGaZWk6xq22MOdeT9wfxyokW3ceCUvOiRtZciQ==",
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.6.tgz",
"integrity": "sha512-Q1BfQX42zXHx732PLW0w4+Y3wJjoZKEMaatFUEAmQ7Z+jCXxinzeqX9bvv2Q8xNPes/H6F0I23oGkcgjaItmLw==",
"dev": true,
"dependencies": {
"@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1",
@@ -8365,9 +8365,9 @@
}
},
"node_modules/nodemon": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
"integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.1.tgz",
"integrity": "sha512-k43xGaDtaDIcufn0Fc6fTtsdKSkV/hQzoQFigNH//GaKta28yoKVYXCnV+KXRqfT/YzsFaQU9VdeEG+HEyxr6A==",
"dev": true,
"dependencies": {
"chokidar": "^3.5.2",
+2 -2
View File
@@ -48,11 +48,11 @@
},
"type": "module",
"devDependencies": {
"@babel/eslint-parser": "^7.24.5",
"@babel/eslint-parser": "^7.24.6",
"eslint": "^9.3.0",
"jasmine": "^5.1.0",
"mongodb-runner": "^5.6.2",
"nodemon": "^3.1.0",
"nodemon": "^3.1.1",
"nyc": "15.1.0",
"prettier": "^3.2.5",
"uglify-js": "^3.17.4"