Compare commits

...
16 Commits
Author SHA1 Message Date
AndrewandGitHub 096d5962a3 Merge pull request #1221 from OpenSignLabs/validation 2024-09-19 11:30:35 +05:30
prafull-opensignlabs 9ea36f94a7 fix: enableOTP not working properly in template 2024-09-19 11:28:13 +05:30
AndrewandGitHub 2b66df992f Merge pull request #1220 from OpenSignLabs/validation 2024-09-19 10:27:18 +05:30
prafull-opensignlabs ff3c31975e refactor: remove extra space 2024-09-19 10:26:35 +05:30
AndrewandGitHub beaa5eca69 Merge pull request #1219 from OpenSignLabs/validation 2024-09-18 21:17:16 +05:30
prafull-opensignlabs b98040faac fix: document decline event not trigger on revoke 2024-09-18 21:15:31 +05:30
AndrewandGitHub fbbff0df61 Merge pull request #1218 from OpenSignLabs/validation 2024-09-18 19:57:46 +05:30
prafull-opensignlabs 07d5f393c0 fix: document viewed event not trigger 2024-09-18 19:56:47 +05:30
AndrewandGitHub 8220b15a29 Merge pull request #1217 from OpenSignLabs/validation
refactor: add enableOTP in allowedkeys in update doc
2024-09-18 19:27:42 +05:30
prafull-opensignlabs 7cf638b2be refactor: add enableOTP in allowedkeys in update doc 2024-09-18 19:22:41 +05:30
prafull-opensignlabsandGitHub 30fb8f31e3 Merge pull request #1216 from OpenSignLabs/validation
fix: blank page appear when try to add typed signature
2024-09-18 18:10:37 +05:30
prafull-opensignlabs ec4d875547 fix: blank page appear when try to add typed signature 2024-09-18 18:09:18 +05:30
AndrewandGitHub 43eee63df2 Merge pull request #1215 from OpenSignLabs/validation 2024-09-18 14:36:30 +05:30
prafull-opensignlabs 4caf9f598a refactor: change variable from isDisableOTP to isEnableOTP 2024-09-18 14:32:45 +05:30
AndrewandGitHub 107eaca8f0 Merge pull request #1213 from OpenSignLabs/validation
feat: add disable OTP feature to directly sign document without verification
2024-09-17 18:31:15 +05:30
prafull-opensignlabs cc11848346 feat: add disable OTP feature to directly sign document without verification 2024-09-17 18:27:21 +05:30
40 changed files with 1251 additions and 739 deletions
@@ -638,6 +638,14 @@
"unauthorized-modal":"You don't have permission to perform this action, please contact {{adminEmail}}.", "unauthorized-modal":"You don't have permission to perform this action, please contact {{adminEmail}}.",
"sent-this-month":"Sent this month", "sent-this-month":"Sent this month",
"available-seats":"Available seats", "available-seats":"Available seats",
"buy-users":"Buy more users" "buy-users":"Buy more users",
"isenable-otp": "Enable OTP verification",
"isenable-otp-help": {
"p1": "Would you like to enable the verification process using a one-time password (OTP)?",
"p2": "Selecting this option will enable OTP verification. Users will receive a verification code via email, which they must enter to sign the document.",
"p3": "Selecting this option will disable OTP verification, allowing users to sign the document directly without additional steps.",
"p4": "Please choose the option that best suits your document signing requirements."
},
"advanced-options":"Advanced options",
"hide-advanced-options":"Hide Advanced options"
} }
@@ -637,5 +637,15 @@
"unauthorized-modal":"Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.", "unauthorized-modal":"Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
"sent-this-month":"envoyé ce mois-ci", "sent-this-month":"envoyé ce mois-ci",
"available-seats":"Disponible sièges", "available-seats":"Disponible sièges",
"buy-users":"Acheter plus d'utilisateurs" "buy-users":"Acheter plus d'utilisateurs",
"isenable-otp": "Activer la vérification OTP",
"isenable-otp-help": {
"p1": "Souhaitez-vous activer le processus de vérification à l'aide d'un mot de passe à usage unique (OTP)?",
"p2": "La sélection de cette option activera la vérification OTP. Les utilisateurs recevront un code de vérification par e-mail, qu'ils devront saisir pour signer le document.",
"p3": "La sélection de cette option désactivera la vérification OTP, permettant aux utilisateurs de signer le document directement sans étapes supplémentaires.",
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
},
"advanced-options":"Options avancées",
"hide-advanced-options": "Masquer les options avancées"
} }
@@ -3,6 +3,8 @@ import { checkIsSubscribed, getFileName } from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade"; import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const"; import { isEnableSubscription } from "../../constant/const";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Tooltip } from "react-tooltip";
// import SelectFolder from "../../premitives/SelectFolder"; // import SelectFolder from "../../premitives/SelectFolder";
const EditTemplate = ({ template, onSuccess }) => { const EditTemplate = ({ template, onSuccess }) => {
@@ -14,7 +16,8 @@ const EditTemplate = ({ template, onSuccess }) => {
Description: template?.Description || "", Description: template?.Description || "",
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false", SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
AutomaticReminders: template?.AutomaticReminders || false, AutomaticReminders: template?.AutomaticReminders || false,
RemindOnceInEvery: template?.RemindOnceInEvery || 5 RemindOnceInEvery: template?.RemindOnceInEvery || 5,
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false"
}); });
const [isSubscribe, setIsSubscribe] = useState(false); const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => { useEffect(() => {
@@ -41,7 +44,7 @@ const EditTemplate = ({ template, onSuccess }) => {
e.stopPropagation(); e.stopPropagation();
const isChecked = formData.SendinOrder === "true" ? true : false; const isChecked = formData.SendinOrder === "true" ? true : false;
const AutoReminder = formData?.AutomaticReminders || false; const AutoReminder = formData?.AutomaticReminders || false;
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
let reminderDate = {}; let reminderDate = {};
if (AutoReminder) { if (AutoReminder) {
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery); const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
@@ -49,7 +52,12 @@ const EditTemplate = ({ template, onSuccess }) => {
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery); ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
reminderDate = { NextReminderDate: ReminderDate }; reminderDate = { NextReminderDate: ReminderDate };
} }
const data = { ...formData, SendinOrder: isChecked, ...reminderDate }; const data = {
...formData,
SendinOrder: isChecked,
IsEnableOTP: IsEnableOTP,
...reminderDate
};
onSuccess(data); onSuccess(data);
}; };
const handleAutoReminder = () => { const handleAutoReminder = () => {
@@ -184,6 +192,70 @@ const EditTemplate = ({ template, onSuccess }) => {
/> />
</div> </div>
)} )}
{isEnableSubscription && (
<div className="text-xs mt-2">
<label className="block">
<span className={isSubscribe ? "" : " text-gray-300"}>
{t("isenable-otp")}{" "}
<a data-tooltip-id="isenableotp-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<Tooltip id="isenableotp-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("isenable-otp")}</p>
<p>{t("isenable-otp-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}: </span>
<span>{t("isenable-otp-help.p2")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("isenable-otp-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("isenable-otp-help.p4")}</p>
</div>
</Tooltip>
</label>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="IsEnableOTP"
checked={formData.IsEnableOTP === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"false"}
name="IsEnableOTP"
className="op-radio op-radio-xs"
checked={formData.IsEnableOTP === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
)}
<div className="mt-[1rem] flex justify-start"> <div className="mt-[1rem] flex justify-start">
<button type="submit" className="op-btn op-btn-primary"> <button type="submit" className="op-btn op-btn-primary">
{t("submit")} {t("submit")}
@@ -154,7 +154,9 @@ function EmailComponent({
<div className="flex flex-row"> <div className="flex flex-row">
{!isAndroid && ( {!isAndroid && (
<button <button
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)} onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
className="op-btn op-btn-neutral op-btn-sm text-[15px]" className="op-btn op-btn-neutral op-btn-sm text-[15px]"
> >
<i className="fa-light fa-print" aria-hidden="true"></i> <i className="fa-light fa-print" aria-hidden="true"></i>
@@ -142,7 +142,7 @@ function Header({
<DropdownMenu.Item <DropdownMenu.Item
className="DropdownMenuItem" className="DropdownMenuItem"
onClick={(e) => onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading) handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
} }
> >
<div className="flex flex-row"> <div className="flex flex-row">
@@ -342,7 +342,9 @@ function Header({
alreadySign ? ( alreadySign ? (
<div className="flex flex-row"> <div className="flex flex-row">
<button <button
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)} onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
type="button" type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow" className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
> >
@@ -459,7 +461,9 @@ function Header({
</button> </button>
)} )}
<button <button
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)} onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
type="button" type="button"
className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow" className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
> >
+10 -8
View File
@@ -31,8 +31,8 @@ function SignPad({
const [isTab, setIsTab] = useState("draw"); const [isTab, setIsTab] = useState("draw");
const [isSignImg, setIsSignImg] = useState(""); const [isSignImg, setIsSignImg] = useState("");
const [signValue, setSignValue] = useState(""); const [signValue, setSignValue] = useState("");
const [textWidth, setTextWidth] = useState(null); const [textWidth, setTextWidth] = useState(0);
const [textHeight, setTextHeight] = useState(null); const [textHeight, setTextHeight] = useState(0);
const [signatureType, setSignatureType] = useState("draw"); const [signatureType, setSignatureType] = useState("draw");
const fontOptions = [ const fontOptions = [
{ value: "Fasthand" }, { value: "Fasthand" },
@@ -51,7 +51,7 @@ function SignPad({
`Parse/${localStorage.getItem("parseAppId")}/currentUser` `Parse/${localStorage.getItem("parseAppId")}/currentUser`
); );
const jsonSender = JSON.parse(senderUser); const jsonSender = JSON.parse(senderUser);
const currentUserName = jsonSender && jsonSender.name; const currentUserName = jsonSender && jsonSender?.name;
//function for clear signature image //function for clear signature image
const handleClear = () => { const handleClear = () => {
@@ -137,7 +137,7 @@ function SignPad({
(isTab === "draw" && isSignImg) || (isTab === "draw" && isSignImg) ||
(isTab === "image" && image) || (isTab === "image" && image) ||
(isTab === "mysignature" && isDefaultSign) || (isTab === "mysignature" && isDefaultSign) ||
(isTab === "type" && textWidth) (isTab === "type" && signValue)
? false ? false
: image : image
? false ? false
@@ -169,10 +169,10 @@ function SignPad({
} }
} }
const trimmedName = currentUserName && currentUserName.trim(); const trimmedName = currentUserName && currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0); const firstCharacter = trimmedName?.charAt(0);
const userName = isInitial ? firstCharacter : currentUserName; const userName = isInitial ? firstCharacter : currentUserName;
setSignValue(userName); setSignValue(userName || "");
setFontSelect("Fasthand"); setFontSelect("Fasthand");
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -200,8 +200,10 @@ function SignPad({
canvasRef.current.fromDataURL(isSignImg); canvasRef.current.fromDataURL(isSignImg);
} }
if (isTab === "type") { if (isTab === "type") {
const trimmedName = signValue ? signValue.trim() : currentUserName.trim(); const trimmedName = signValue
const firstCharacter = trimmedName.charAt(0); ? signValue?.trim()
: currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = isInitial ? firstCharacter : signValue; const userName = isInitial ? firstCharacter : signValue;
setSignValue(userName); setSignValue(userName);
convertToImg(fontSelect, userName); convertToImg(fontSelect, userName);
+13 -7
View File
@@ -708,7 +708,8 @@ export const createDocument = async (
Signers: signers, Signers: signers,
SendinOrder: Doc?.SendinOrder || false, SendinOrder: Doc?.SendinOrder || false,
AutomaticReminders: Doc?.AutomaticReminders || false, AutomaticReminders: Doc?.AutomaticReminders || false,
RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5) RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5),
IsEnableOTP: Doc?.IsEnableOTP || false
}; };
try { try {
@@ -1741,9 +1742,7 @@ export const contactBook = async (objectId) => {
//function for getting document details from contract_Documents class //function for getting document details from contract_Documents class
export const contractDocument = async (documentId) => { export const contractDocument = async (documentId) => {
const data = { const data = { docId: documentId };
docId: documentId
};
const documentDeatils = await axios const documentDeatils = await axios
.post(`${localStorage.getItem("baseUrl")}functions/getDocument`, data, { .post(`${localStorage.getItem("baseUrl")}functions/getDocument`, data, {
headers: { headers: {
@@ -2047,11 +2046,12 @@ export const handleDownloadPdf = async (
) => { ) => {
const pdfName = pdfDetails[0] && pdfDetails[0].Name; const pdfName = pdfDetails[0] && pdfDetails[0].Name;
setIsDownloading("pdf"); setIsDownloading("pdf");
const docId = !pdfDetails?.[0]?.IsEnableOTP ? pdfDetails?.[0]?.objectId : "";
try { try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl }); // const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post( const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`, `${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl }, { url: pdfUrl, docId: docId },
{ {
headers: { headers: {
"content-type": "Application/json", "content-type": "Application/json",
@@ -2075,16 +2075,22 @@ export const sanitizeFileName = (pdfName) => {
return pdfName.replace(/ /g, "_"); return pdfName.replace(/ /g, "_");
}; };
//function for print digital sign pdf //function for print digital sign pdf
export const handleToPrint = async (event, pdfUrl, setIsDownloading) => { export const handleToPrint = async (
event,
pdfUrl,
setIsDownloading,
pdfDetails
) => {
event.preventDefault(); event.preventDefault();
setIsDownloading("pdf"); setIsDownloading("pdf");
const docId = !pdfDetails?.[0]?.IsEnableOTP ? pdfDetails?.[0]?.objectId : "";
try { try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl }); // const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
//`localStorage.getItem("baseUrl")` is also use in public-profile flow for public-sign //`localStorage.getItem("baseUrl")` is also use in public-profile flow for public-sign
//if we give this `appInfo.baseUrl` as a base url then in public-profile it will create base url of it's window.location.origin ex- opensign.me which is not base url //if we give this `appInfo.baseUrl` as a base url then in public-profile it will create base url of it's window.location.origin ex- opensign.me which is not base url
const axiosRes = await axios.post( const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`, `${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl }, { url: pdfUrl, docId: docId },
{ {
headers: { headers: {
"content-type": "Application/json", "content-type": "Application/json",
+209 -108
View File
@@ -61,7 +61,8 @@ const Forms = (props) => {
password: "", password: "",
file: "", file: "",
remindOnceInEvery: 5, remindOnceInEvery: 5,
autoreminder: false autoreminder: false,
IsEnableOTP: "false"
}); });
const [fileupload, setFileUpload] = useState(""); const [fileupload, setFileUpload] = useState("");
const [fileload, setfileload] = useState(false); const [fileload, setfileload] = useState(false);
@@ -74,6 +75,7 @@ const Forms = (props) => {
const [isDecrypting, setIsDecrypting] = useState(false); const [isDecrypting, setIsDecrypting] = useState(false);
const [isCorrectPass, setIsCorrectPass] = useState(true); const [isCorrectPass, setIsCorrectPass] = useState(true);
const [isSubscribe, setIsSubscribe] = useState(false); const [isSubscribe, setIsSubscribe] = useState(false);
const [isAdvanceOpt, setIsAdvanceOpt] = useState(false);
const handleStrInput = (e) => { const handleStrInput = (e) => {
setIsCorrectPass(true); setIsCorrectPass(true);
setFormData({ ...formData, [e.target.name]: e.target.value }); setFormData({ ...formData, [e.target.name]: e.target.value });
@@ -137,7 +139,6 @@ const Forms = (props) => {
} catch (err) { } catch (err) {
console.log("err in sending posthog encryptedpdf", err); console.log("err in sending posthog encryptedpdf", err);
} }
// console.log("err ", err);
try { try {
setIsDecrypting(true); setIsDecrypting(true);
const size = files?.[0].size; const size = files?.[0].size;
@@ -402,6 +403,13 @@ const Forms = (props) => {
object.set("SendinOrder", isChecked); object.set("SendinOrder", isChecked);
object.set("AutomaticReminders", formData.autoreminder); object.set("AutomaticReminders", formData.autoreminder);
object.set("RemindOnceInEvery", parseInt(formData.remindOnceInEvery)); object.set("RemindOnceInEvery", parseInt(formData.remindOnceInEvery));
if (isEnableSubscription) {
const IsEnableOTP =
formData?.IsEnableOTP === "false" ? false : true;
object.set("IsEnableOTP", IsEnableOTP);
} else {
object.set("IsEnableOTP", false);
}
} }
object.set("URL", fileupload); object.set("URL", fileupload);
object.set("CreatedBy", Parse.User.createWithoutData(currentUser.id)); object.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
@@ -428,7 +436,17 @@ const Forms = (props) => {
setFormData({ setFormData({
Name: "", Name: "",
Description: "", Description: "",
Note: "" Note:
props.title === "Sign Yourself"
? "Note to myself"
: "Please review and sign this document",
TimeToCompleteDays: 15,
SendinOrder: "true",
password: "",
file: "",
remindOnceInEvery: 5,
autoreminder: false,
IsEnableOTP: "false"
}); });
setFileUpload(""); setFileUpload("");
setpercentage(0); setpercentage(0);
@@ -477,7 +495,8 @@ const Forms = (props) => {
password: "", password: "",
file: "", file: "",
remindOnceInEvery: 5, remindOnceInEvery: 5,
autoreminder: false autoreminder: false,
IsEnableOTP: "false"
}); });
setFileUpload(""); setFileUpload("");
setpercentage(0); setpercentage(0);
@@ -641,7 +660,7 @@ const Forms = (props) => {
)} )}
<div className="text-xs"> <div className="text-xs">
<label className="block"> <label className="block">
{`${t("report-heading.File")} (${t("file-type")} ${ {`${t("report-heading.File")} (${t("file-type")}${
isEnableSubscription ? ", docx)" : ")" isEnableSubscription ? ", docx)" : ")"
}`} }`}
<span className="text-red-500 text-[13px]">*</span> <span className="text-red-500 text-[13px]">*</span>
@@ -754,120 +773,73 @@ const Forms = (props) => {
isReset={isReset} isReset={isReset}
/> />
)} )}
{props.title === "Request Signatures" && ( {props.title !== "Sign Yourself" && (
<div className="text-xs mt-2"> <div className="text-xs mt-2">
<label className="block"> <label className="block">
{t("time-to-complete")} {t("send-in-order")}
<span className="text-red-500 text-[13px]">*</span> <a data-tooltip-id="sendInOrder-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<Tooltip id="sendInOrder-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("send-in-order")}</p>
<p>{t("send-in-order-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}: </span>
<span>{t("send-in-order-help.p2")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("send-in-order-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("send-in-order-help.p4")}</p>
</div>
</Tooltip>
</label> </label>
<input <div className="flex items-center gap-2 ml-2 mb-1">
type="number" <input
name="TimeToCompleteDays" type="radio"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" value={"true"}
value={formData.TimeToCompleteDays} className="op-radio op-radio-xs"
onChange={(e) => handleStrInput(e)} name="SendinOrder"
onInvalid={(e) => checked={formData.SendinOrder === "true"}
e.target.setCustomValidity(t("input-required")) onChange={handleStrInput}
} />
onInput={(e) => e.target.setCustomValidity("")} <div className="text-center">{t("yes")}</div>
required </div>
/> <div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"false"}
name="SendinOrder"
className="op-radio op-radio-xs"
checked={formData.SendinOrder === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div> </div>
)} )}
{props.title !== "Sign Yourself" && ( {isAdvanceOpt && (
<> <div className={` overflow-y-auto z-[500] transition-all`}>
<div className="text-xs mt-2"> {props.title === "Request Signatures" && (
<label className="block">
{t("send-in-order")}
<a data-tooltip-id="sendInOrder-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<Tooltip id="sendInOrder-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("send-in-order")}</p>
<p>{t("send-in-order-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}:</span>
<span>{t("send-in-order-help.p2")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("send-in-order-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("send-in-order-help.p4")}</p>
</div>
</Tooltip>
</label>
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="SendinOrder"
checked={formData.SendinOrder === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"false"}
name="SendinOrder"
className="op-radio op-radio-xs"
checked={formData.SendinOrder === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
{isEnableSubscription && (
<div className="text-xs mt-2">
<span
className={
isSubscribe
? "font-semibold"
: "font-semibold text-gray-300"
}
>
{t("auto-reminder")}
{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<label
className={`${
isSubscribe
? "cursor-pointer "
: "pointer-events-none opacity-50"
} relative block items-center mb-0`}
>
<input
type="checkbox"
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white"
checked={formData.autoreminder}
onChange={handleAutoReminder}
/>
</label>
</div>
)}
{formData?.autoreminder === true && (
<div className="text-xs mt-2"> <div className="text-xs mt-2">
<label className="block"> <label className="block">
{t("remind-once")} {t("time-to-complete")}
<span className="text-red-500 text-[13px]">*</span> <span className="text-red-500 text-[13px]">*</span>
</label> </label>
<input <input
type="number" type="number"
value={formData.remindOnceInEvery} name="TimeToCompleteDays"
name="remindOnceInEvery"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
onChange={handleStrInput} value={formData.TimeToCompleteDays}
onChange={(e) => handleStrInput(e)}
onInvalid={(e) => onInvalid={(e) =>
e.target.setCustomValidity(t("input-required")) e.target.setCustomValidity(t("input-required"))
} }
@@ -876,7 +848,136 @@ const Forms = (props) => {
/> />
</div> </div>
)} )}
</> {props.title !== "Sign Yourself" && (
<>
{isEnableSubscription && (
<div className="text-xs mt-2">
<span className={isSubscribe ? "" : " text-gray-300"}>
{t("auto-reminder")}{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<label
className={`${
isSubscribe
? "cursor-pointer "
: "pointer-events-none opacity-50"
} relative block items-center mb-0 mt-1.5`}
>
<input
type="checkbox"
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white"
checked={formData.autoreminder}
onChange={handleAutoReminder}
/>
</label>
</div>
)}
{formData?.autoreminder === true && (
<div className="text-xs mt-2">
<label className="block">
{t("remind-once")}
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
value={formData.remindOnceInEvery}
name="remindOnceInEvery"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
onChange={handleStrInput}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
)}
{isEnableSubscription && (
<div className="text-xs mt-2">
<label className="block">
<span className={isSubscribe ? "" : " text-gray-300"}>
{t("isenable-otp")}{" "}
<a
data-tooltip-id="isenableotp-tooltip"
className="ml-1"
>
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
{!isSubscribe && isEnableSubscription && (
<Upgrade />
)}
</span>
<Tooltip id="isenableotp-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("isenable-otp")}</p>
<p>{t("isenable-otp-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">
{t("yes")}:{" "}
</span>
<span>{t("isenable-otp-help.p2")}</span>
</li>
<li>
<span className="font-bold">
{t("no")}:{" "}
</span>
<span>{t("isenable-otp-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("isenable-otp-help.p4")}</p>
</div>
</Tooltip>
</label>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="IsEnableOTP"
checked={formData.IsEnableOTP === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"false"}
name="IsEnableOTP"
className="op-radio op-radio-xs"
checked={formData.IsEnableOTP === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
)}
</>
)}
</div>
)}
{props.title !== "Sign Yourself" && (
<div
onClick={() => setIsAdvanceOpt(!isAdvanceOpt)}
className={`mt-2.5 op-link op-link-primary text-sm`}
>
{isAdvanceOpt
? t("hide-advanced-options")
: t("advanced-options")}
</div>
)} )}
<div className="flex items-center mt-3 gap-2"> <div className="flex items-center mt-3 gap-2">
<button <button
+36 -12
View File
@@ -28,6 +28,28 @@ function GuestLogin() {
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: "" }); const [contact, setContact] = useState({ name: "", phone: "", email: "" });
const navigateToDoc = async (docId, contactId) => {
try {
const docDetails = await Parse.Cloud.run("getDocument", {
docId: docId
});
if (!docDetails.error) {
if (sendmail === "false") {
navigate(
`/load/recipientSignPdf/${docId}/${contactId}?sendmail=${sendmail}`
);
} else {
navigate(`/load/recipientSignPdf/${docId}/${contactId}`);
}
return true;
} else {
return false;
}
} catch (err) {
console.log("err while getting doc", err);
return false;
}
};
useEffect(() => { useEffect(() => {
handleServerUrl(); handleServerUrl();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -70,13 +92,14 @@ function GuestLogin() {
"linkcontacttodoc", "linkcontacttodoc",
params params
); );
// console.log("linkContactRes ", linkContactRes);
setContactId(linkContactRes?.contactId); setContactId(linkContactRes?.contactId);
await navigateToDoc(checkSplit[0], linkContactRes?.contactId);
} catch (err) { } catch (err) {
console.log("Err in link ext contact", err); console.log("Err in link ext contact", err);
} }
} else { } else {
setContactId(checkSplit[2]); setContactId(checkSplit[2]);
await navigateToDoc(checkSplit[0], checkSplit[2]);
} }
} }
@@ -114,15 +137,12 @@ function GuestLogin() {
if (OTP) { if (OTP) {
setLoading(true); setLoading(true);
try { try {
let url = `${serverUrl}functions/AuthLoginAsMail/`; let url = `${serverUrl}functions/AuthLoginAsMail`;
const headers = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": parseId "X-Parse-Application-Id": parseId
}; };
let body = { let body = { email: email, otp: OTP };
email: email,
otp: OTP
};
let user = await axios.post(url, body, { headers: headers }); let user = await axios.post(url, body, { headers: headers });
if (user.data.result === "Invalid Otp") { if (user.data.result === "Invalid Otp") {
alert(t("invalid-otp")); alert(t("invalid-otp"));
@@ -140,7 +160,6 @@ function GuestLogin() {
`Parse/${parseId}/currentUser`, `Parse/${parseId}/currentUser`,
JSON.stringify(_user) JSON.stringify(_user)
); );
// console.log("contractUserDetails ", contractUserDetails);
if (contractUserDetails && contractUserDetails.length > 0) { if (contractUserDetails && contractUserDetails.length > 0) {
localStorage.setItem( localStorage.setItem(
"Extand_Class", "Extand_Class",
@@ -174,10 +193,15 @@ function GuestLogin() {
try { try {
setLoading(true); setLoading(true);
const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params); const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params);
// console.log("linkContactRes ", linkContactRes);
setContactId(linkContactRes.contactId); setContactId(linkContactRes.contactId);
setEnterOtp(true); const IsEnableOTP = await navigateToDoc(
await SendOtp(); documentId,
linkContactRes.contactId
);
if (!IsEnableOTP) {
setEnterOtp(true);
await SendOtp();
}
} catch (err) { } catch (err) {
setLoading(false); setLoading(false);
alert(t("something-went-wrong-mssg")); alert(t("something-went-wrong-mssg"));
@@ -208,7 +232,7 @@ function GuestLogin() {
<div className="w-full md:w-[50%] text-base-content"> <div className="w-full md:w-[50%] text-base-content">
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1> <h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
<legend className="text-[12px] text-[#878787] mt-2 mb-1"> <legend className="text-[12px] text-[#878787] mt-2 mb-1">
{t("guest-email-alert")} {t("get-otp-alert")}
</legend> </legend>
<div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 op-card shadow-md"> <div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 op-card shadow-md">
<input <input
@@ -236,7 +260,7 @@ function GuestLogin() {
> >
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1> <h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
<legend className="text-[12px] text-[#878787] mt-2"> <legend className="text-[12px] text-[#878787] mt-2">
{t("get-verification-code-2")} {t("guest-email-alert")}
</legend> </legend>
<div className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 op-card my-2 shadow-md"> <div className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 op-card my-2 shadow-md">
<p className="text-sm">{t("enter-verification-code")}</p> <p className="text-sm">{t("enter-verification-code")}</p>
+196 -175
View File
@@ -142,23 +142,23 @@ function PdfRequestFiles(props) {
let isGuestSignFlow = false; let isGuestSignFlow = false;
let sendmail; let sendmail;
let getDocId = ""; let getDocId = "";
const route = !props.templateId && window.location.pathname; //'/load/recipientSignPdf/TOAVuhXbfw/fPAKdK1qgX' let contactBookId = "";
//window.location.search = ?sendmail=false const route = !props.templateId && window.location.pathname;
const getQuery = const getQuery = !props.templateId && window.location?.search?.split("?"); //['','sendmail=false']
!props.templateId &&
window.location?.search &&
window.location?.search?.split("?"); //['','sendmail=false']
//'sendmail=false'
if (getQuery) { if (getQuery) {
sendmail = getQuery[1].split("=")[1]; //false sendmail = getQuery?.[1]?.split("=")[1]; //false
} }
const checkSplit = route && route?.split("/"); // ['', 'load', 'recipientSignPdf', 'TOAVuhXbfw', 'fPAKdK1qgX']
if (checkSplit && checkSplit.length > 4) { const routeId = route && route?.split("/"); // ['', 'load', 'recipientSignPdf', ':docId', ':contactBookId']
if (routeId && routeId.length > 4) {
// this condition will be occur only in guest flow in which load routeId will be include
isGuestSignFlow = true; isGuestSignFlow = true;
getDocId = checkSplit[3]; getDocId = routeId[3];
contactBookId = routeId[4];
} else { } else {
getDocId = checkSplit[2]; // this condition will be occur only in if user is logged in and load routeId will be exclude
getDocId = routeId[2];
contactBookId = routeId?.[3] || "";
} }
let getDocumentId = getDocId || documentId; let getDocumentId = getDocId || documentId;
useEffect(() => { useEffect(() => {
@@ -372,9 +372,10 @@ function PdfRequestFiles(props) {
`Parse/${localStorage.getItem("parseAppId")}/currentUser` `Parse/${localStorage.getItem("parseAppId")}/currentUser`
); );
const jsonSender = JSON.parse(senderUser); const jsonSender = JSON.parse(senderUser);
// `currUserId` will be contactId or extUserId
let currUserId; let currUserId;
//getting document details //getting document details
const documentData = await contractDocument(documentId || docId); const documentData = await contractDocument(docId);
if (documentData && documentData.length > 0) { if (documentData && documentData.length > 0) {
const url = const url =
documentData[0] && documentData[0] &&
@@ -399,13 +400,13 @@ function PdfRequestFiles(props) {
const expireUpdateDate = new Date(expireDate).getTime(); const expireUpdateDate = new Date(expireDate).getTime();
const currDate = new Date().getTime(); const currDate = new Date().getTime();
const getSigners = documentData[0].Signers; const getSigners = documentData[0].Signers;
const getCurrentSigner = const getCurrentSigner = getSigners?.find(
getSigners && (data) => data.UserId.objectId === jsonSender?.objectId
getSigners.filter( );
(data) => data.UserId.objectId === jsonSender?.objectId
);
currUserId = getCurrentSigner[0] ? getCurrentSigner[0].objectId : ""; currUserId = getCurrentSigner?.objectId
? getCurrentSigner.objectId
: contactBookId || "";
if (isEnableSubscription) { if (isEnableSubscription) {
await checkIsSubscribed( await checkIsSubscribed(
documentData[0]?.ExtUserPtr?.objectId, documentData[0]?.ExtUserPtr?.objectId,
@@ -428,10 +429,7 @@ function PdfRequestFiles(props) {
setIsCelebration(true); setIsCelebration(true);
setTimeout(() => setIsCelebration(false), 5000); setTimeout(() => setIsCelebration(false), 5000);
} else if (declined) { } else if (declined) {
const currentDecline = { const currentDecline = { currnt: "another", isDeclined: true };
currnt: "another",
isDeclined: true
};
setIsDecline(currentDecline); setIsDecline(currentDecline);
} else if (currDate > expireUpdateDate) { } else if (currDate > expireUpdateDate) {
const expireDateFormat = moment(new Date(expireDate)).format( const expireDateFormat = moment(new Date(expireDate)).format(
@@ -457,36 +455,24 @@ function PdfRequestFiles(props) {
} }
} }
} }
const audittrailData = const audittrailData = documentData?.[0]?.AuditTrail?.filter(
documentData[0].AuditTrail && (data) => data.Activity === "Signed"
documentData[0].AuditTrail.length > 0 && );
documentData[0].AuditTrail.filter(
(data) => data.Activity === "Signed"
);
const checkAlreadySign = const checkAlreadySign =
documentData[0].AuditTrail && documentData?.[0]?.AuditTrail?.some(
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter(
(data) => (data) =>
data.UserPtr?.objectId === currUserId && data?.UserPtr?.objectId === currUserId &&
data.Activity === "Signed" data.Activity === "Signed"
); ) || false;
if ( if (checkAlreadySign) {
checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0
) {
setAlreadySign(true); setAlreadySign(true);
} else { } else {
const obj = documentData?.[0]; const obj = documentData?.[0];
setSendInOrder(obj?.SendinOrder || false); setSendInOrder(obj?.SendinOrder || false);
if ( if (
obj && obj &&
obj.Signers && obj?.Signers?.length > 0 &&
obj.Signers.length > 0 && obj?.Placeholders?.length > 0
obj.Placeholders &&
obj.Placeholders.length > 0
) { ) {
const params = { const params = {
event: "viewed", event: "viewed",
@@ -502,7 +488,10 @@ function PdfRequestFiles(props) {
email: x?.Email, email: x?.Email,
phone: x?.Phone phone: x?.Phone
})), })),
viewedBy: jsonSender.email, viewedBy:
documentData?.[0].Signers?.find(
(x) => x.objectId === currUserId
)?.Email || jsonSender?.email,
viewedAt: new Date(), viewedAt: new Date(),
createdAt: documentData?.[0].createdAt createdAt: documentData?.[0].createdAt
} }
@@ -583,40 +572,51 @@ function PdfRequestFiles(props) {
//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 //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 //then stop to display tour message
if ( if (
(checkAlreadySign && checkAlreadySign ||
checkAlreadySign[0] &&
checkAlreadySign.length > 0) ||
!currUserId || !currUserId ||
declined || declined ||
currDate > expireUpdateDate currDate > expireUpdateDate
) { ) {
setRequestSignTour(true); setRequestSignTour(true);
} else { } else {
//else condition to check current user exist in contracts_Users class and check tour message status const isEnableOTP = documentData?.[0]?.IsEnableOTP || false;
//if not then check user exist in contracts_Contactbook class and check tour message statu if (!isEnableOTP) {
const res = await contractUsers(); try {
if (res === "Error: Something went wrong!") { const resContact = await axios.post(
setHandleError(t("something-went-wrong-mssg")); `${localStorage.getItem("baseUrl")}functions/getcontact`,
} else if (res[0] && res?.length) { {
setContractName("_Users"); contactId: currUserId
currUserId = res[0].objectId; },
setSignerUserId(currUserId); {
const tourData = res[0].TourStatus && res[0].TourStatus; headers: {
if (tourData && tourData.length > 0) { "Content-Type": "application/json",
const checkTourRequest = tourData.filter( "X-Parse-Application-Id": localStorage.getItem("parseAppId")
(data) => data?.requestSign }
}
); );
setTourStatus(tourData); const contact = resContact?.data?.result;
setRequestSignTour(checkTourRequest[0]?.requestSign || false); setContractName("_Contactbook");
setSignerUserId(contact?.objectId);
const tourData = contact?.TourStatus && contact?.TourStatus;
if (tourData && tourData.length > 0) {
const checkTourRequest =
tourData?.some((data) => data?.requestSign) || false;
setTourStatus(tourData);
setRequestSignTour(checkTourRequest);
}
} catch (err) {
console.log("err while getting tourstatus", err);
} }
} else if (res?.length === 0) { } else {
const res = await contactBook(currUserId); //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 res = await contractUsers();
if (res === "Error: Something went wrong!") { if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg")); setHandleError(t("something-went-wrong-mssg"));
} else if (res[0] && res.length) { } else if (res[0] && res?.length) {
setContractName("_Contactbook"); setContractName("_Users");
const objectId = res[0].objectId; currUserId = res[0].objectId;
setSignerUserId(objectId); setSignerUserId(currUserId);
const tourData = res[0].TourStatus && res[0].TourStatus; const tourData = res[0].TourStatus && res[0].TourStatus;
if (tourData && tourData.length > 0) { if (tourData && tourData.length > 0) {
const checkTourRequest = tourData.filter( const checkTourRequest = tourData.filter(
@@ -625,8 +625,25 @@ function PdfRequestFiles(props) {
setTourStatus(tourData); setTourStatus(tourData);
setRequestSignTour(checkTourRequest[0]?.requestSign || false); setRequestSignTour(checkTourRequest[0]?.requestSign || false);
} }
} else if (res.length === 0) { } else if (res?.length === 0) {
setHandleError(t("user-not-exist")); const res = await contactBook(currUserId);
if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
} else if (res[0] && res.length) {
setContractName("_Contactbook");
const objectId = res[0].objectId;
setSignerUserId(objectId);
const tourData = res[0].TourStatus && res[0].TourStatus;
if (tourData && tourData.length > 0) {
const checkTourRequest = tourData.filter(
(data) => data?.requestSign
);
setTourStatus(tourData);
setRequestSignTour(checkTourRequest[0]?.requestSign || false);
}
} else if (res.length === 0) {
setHandleError(t("user-not-exist"));
}
} }
} }
} }
@@ -643,12 +660,12 @@ function PdfRequestFiles(props) {
setIsUiLoading({ isLoad: false }); setIsUiLoading({ isLoad: false });
} }
//function to get default signatur eof current user from `contracts_Signature` class //function to get default signatur eof current user from `contracts_Signature` class
const defaultSignRes = await getDefaultSignature(jsonSender.objectId); const defaultSignRes = await getDefaultSignature(jsonSender?.objectId);
if (defaultSignRes?.status === "success") { if (defaultSignRes?.status === "success") {
setDefaultSignImg(defaultSignRes?.res?.defaultSignature); const sign = defaultSignRes?.res?.defaultSignature || "";
setMyInitial(defaultSignRes?.res?.defaultInitial); const initials = defaultSignRes?.res?.defaultInitial || "";
} else if (defaultSignRes?.status === "error") { setDefaultSignImg(sign);
setHandleError("Error: Something went wrong!"); setMyInitial(initials);
} }
setIsLoading({ isLoad: false }); setIsLoading({ isLoad: false });
} catch (err) { } catch (err) {
@@ -664,20 +681,30 @@ function PdfRequestFiles(props) {
`Parse/${localStorage.getItem("parseAppId")}/currentUser` `Parse/${localStorage.getItem("parseAppId")}/currentUser`
); );
let currentUser = JSON.parse(localuser); let currentUser = JSON.parse(localuser);
let isEmailVerified = currentUser?.emailVerified;
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
//if emailVerified data is not present in local user details then fetch again in _User class //if emailVerified data is not present in local user details then fetch again in _User class
if (!currentUser?.emailVerified) { if (isEnableOTP) {
const userQuery = new Parse.Query(Parse.User); try {
const getUser = await userQuery.get(currentUser?.objectId, { if (!currentUser?.emailVerified) {
sessionToken: currentUser?.sessionToken const userQuery = new Parse.Query(Parse.User);
}); const getUser = await userQuery.get(currentUser?.objectId, {
if (getUser) { sessionToken:
currentUser = JSON.parse(JSON.stringify(getUser)); currentUser?.sessionToken || localStorage.getItem("accesstoken")
});
if (getUser) {
currentUser = JSON.parse(JSON.stringify(getUser));
}
}
isEmailVerified = currentUser?.emailVerified;
setIsEmailVerified(isEmailVerified);
} catch (err) {
console.log("err in get email verification ", err);
setHandleError(t("something-went-wrong-mssg"));
} }
} }
let isEmailVerified = currentUser?.emailVerified;
//check if isEmailVerified then go on next step //check if isEmailVerified then go on next step
if (isEmailVerified) { if (!isEnableOTP || isEmailVerified) {
setIsEmailVerified(isEmailVerified);
try { try {
const checkUser = signerPos.filter( const checkUser = signerPos.filter(
(data) => data.signerObjId === signerObjectId (data) => data.signerObjId === signerObjectId
@@ -858,17 +885,9 @@ function PdfRequestFiles(props) {
// console.log("pdfte", pdfBytes); // console.log("pdfte", pdfBytes);
//get ExistUserPtr object id of user class to get tenantDetails //get ExistUserPtr object id of user class to get tenantDetails
const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId; const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId;
//get ExistUserPtr email to get userDetails let activeMailAdapter =
const res = await contractUsers(); pdfDetails?.[0]?.ExtUserPtr?.active_mail_adapter;
let activeMailAdapter = "";
if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
setIsLoading({ isLoad: false });
} else if (!res || res?.length === 0) {
activeMailAdapter = "";
} else if (res[0] && res.length) {
activeMailAdapter = res[0]?.active_mail_adapter;
}
//function for call to embed signature in pdf and get digital signature pdf //function for call to embed signature in pdf and get digital signature pdf
const resSign = await signPdfFun( const resSign = await signPdfFun(
pdfBytes, pdfBytes,
@@ -880,13 +899,13 @@ function PdfRequestFiles(props) {
widgets widgets
); );
if (resSign && resSign.status === "success") { if (resSign && resSign.status === "success") {
setPdfUrl(res.data); setPdfUrl(resSign.data);
setIsSigned(true); setIsSigned(true);
setSignedSigners([]); setSignedSigners([]);
setUnSignedSigners([]); setUnSignedSigners([]);
getDocumentDetails(true); getDocumentDetails(documentId, true);
const index = pdfDetails?.[0].Signers.findIndex( const index = pdfDetails?.[0]?.Signers.findIndex(
(x) => x.Email === currentUser?.email (x) => x.objectId === signerObjectId
); );
const newIndex = index + 1; const newIndex = index + 1;
const usermail = { const usermail = {
@@ -1054,21 +1073,6 @@ function PdfRequestFiles(props) {
alertMessage: t("something-went-wrong-mssg") alertMessage: t("something-went-wrong-mssg")
}); });
} }
} else {
//else verify users email
try {
const userQuery = new Parse.Query(Parse.User);
const user = await userQuery.get(currentUser?.objectId, {
sessionToken: localStorage.getItem("accesstoken")
});
if (user) {
isEmailVerified = user?.get("emailVerified");
setIsEmailVerified(isEmailVerified);
}
} catch (e) {
console.log("error in save user's emailVerified in user class");
setHandleError(t("something-went-wrong-mssg"));
}
} }
} }
@@ -1216,31 +1220,26 @@ function PdfRequestFiles(props) {
); );
const jsonSender = JSON.parse(senderUser); const jsonSender = JSON.parse(senderUser);
setIsDecline({ isDeclined: false }); setIsDecline({ isDeclined: false });
const data = {
IsDeclined: true,
DeclineReason: reason,
DeclineBy: {
__type: "Pointer",
className: "_User",
objectId: jsonSender?.objectId
}
};
setIsUiLoading(true); setIsUiLoading(true);
const email =
pdfDetails?.[0].Signers?.find((x) => x.objectId === signerObjectId)
?.Email || jsonSender?.email;
const userId =
pdfDetails?.[0].Signers?.find((x) => x.objectId === signerObjectId)
?.UserId?.objectId || jsonSender?.objectId;
const params = {
docId: pdfDetails?.[0].objectId,
reason: reason,
userId: userId
};
await axios await axios
.put( .post(`${localStorage.getItem("baseUrl")}functions/declinedoc`, params, {
`${localStorage.getItem( headers: {
"baseUrl" "Content-Type": "application/json",
)}classes/contracts_Document/${documentId}`, "X-Parse-Application-Id": localStorage.getItem("parseAppId"),
data, "X-Parse-Session-Token": localStorage.getItem("accesstoken")
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
} }
) })
.then(async (result) => { .then(async (result) => {
const res = result.data; const res = result.data;
if (res) { if (res) {
@@ -1260,7 +1259,8 @@ function PdfRequestFiles(props) {
email: x?.Email, email: x?.Email,
phone: x?.Phone phone: x?.Phone
})), })),
declinedBy: jsonSender.email, declinedBy: email,
declinedReason: reason,
declinedAt: new Date(), declinedAt: new Date(),
createdAt: pdfDetails?.[0].createdAt createdAt: pdfDetails?.[0].createdAt
} }
@@ -1314,38 +1314,58 @@ function PdfRequestFiles(props) {
const closeRequestSignTour = async () => { const closeRequestSignTour = async () => {
setRequestSignTour(true); setRequestSignTour(true);
if (isDontShow) { if (isDontShow) {
let updatedTourStatus = []; const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
if (tourStatus.length > 0) { if (!isEnableOTP) {
updatedTourStatus = [...tourStatus]; try {
const requestSignIndex = tourStatus.findIndex( await axios.post(
(obj) => obj["requestSign"] === false || obj["requestSign"] === true `${localStorage.getItem("baseUrl")}functions/updatecontacttour`,
); {
if (requestSignIndex !== -1) { contactId: signerObjectId
updatedTourStatus[requestSignIndex] = { requestSign: true }; },
} else { {
updatedTourStatus.push({ requestSign: true }); headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
}
}
);
} catch (e) {
console.log("update tour messages error", e);
} }
} else { } else {
updatedTourStatus = [{ requestSign: true }]; let updatedTourStatus = [];
} if (tourStatus.length > 0) {
try { updatedTourStatus = [...tourStatus];
await axios.put( const requestSignIndex = tourStatus.findIndex(
`${localStorage.getItem( (obj) => obj["requestSign"] === false || obj["requestSign"] === true
"baseUrl" );
)}classes/contracts${contractName}/${signerUserId}`, if (requestSignIndex !== -1) {
{ updatedTourStatus[requestSignIndex] = { requestSign: true };
TourStatus: updatedTourStatus } else {
}, updatedTourStatus.push({ requestSign: true });
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
} }
); } else {
} catch (e) { updatedTourStatus = [{ requestSign: true }];
console.log("update tour messages error", e); }
try {
await axios.put(
`${localStorage.getItem(
"baseUrl"
)}classes/contracts${contractName}/${signerUserId}`,
{
TourStatus: updatedTourStatus
},
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
} catch (e) {
console.log("update tour messages error", e);
}
} }
} }
}; };
@@ -1691,11 +1711,7 @@ function PdfRequestFiles(props) {
{/* this modal is used to show decline alert */} {/* this modal is used to show decline alert */}
<PdfDeclineModal <PdfDeclineModal
show={isDecline.isDeclined} show={isDecline.isDeclined}
headMsg={ headMsg={t("document-declined")}
pdfDetails[0]?.IsDeclined
? t("document-declined")
: t("document-decline")
}
bodyMssg={ bodyMssg={
isDecline.currnt === "Sure" isDecline.currnt === "Sure"
? t("decline-alert-1") ? t("decline-alert-1")
@@ -1938,7 +1954,12 @@ function PdfRequestFiles(props) {
<div className="flex mt-4 gap-1 px-[15px]"> <div className="flex mt-4 gap-1 px-[15px]">
<button <button
onClick={(e) => onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading) handleToPrint(
e,
pdfUrl,
setIsDownloading,
pdfDetails
)
} }
type="button" type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-neutral" className="font-[500] text-[13px] mr-[5px] op-btn op-btn-neutral"
@@ -779,6 +779,7 @@ const TemplatePlaceholder = () => {
AutomaticReminders: pdfDetails[0]?.AutomaticReminders, AutomaticReminders: pdfDetails[0]?.AutomaticReminders,
RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery), RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery),
NextReminderDate: pdfDetails[0]?.NextReminderDate, NextReminderDate: pdfDetails[0]?.NextReminderDate,
IsEnableOTP: pdfDetails[0]?.IsEnableOTP === true ? true : false,
URL: pdfUrl URL: pdfUrl
}; };
const updateTemplate = new Parse.Object("contracts_Template"); const updateTemplate = new Parse.Object("contracts_Template");
@@ -266,7 +266,8 @@ const ReportTable = (props) => {
Signers: signers, Signers: signers,
SendinOrder: Doc?.SendinOrder || false, SendinOrder: Doc?.SendinOrder || false,
AutomaticReminders: Doc?.AutomaticReminders || false, AutomaticReminders: Doc?.AutomaticReminders || false,
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5 RemindOnceInEvery: Doc?.RemindOnceInEvery || 5,
IsEnableOTP: Doc?.IsEnableOTP || false
}; };
try { try {
const res = await axios.post( const res = await axios.post(
@@ -515,7 +516,6 @@ const ReportTable = (props) => {
.then(async (result) => { .then(async (result) => {
const res = result.data; const res = result.data;
if (res) { if (res) {
setReason("");
setActLoader({}); setActLoader({});
setIsAlert(true); setIsAlert(true);
setAlertMsg({ setAlertMsg({
@@ -527,7 +527,43 @@ const ReportTable = (props) => {
(x) => x.objectId !== item.objectId (x) => x.objectId !== item.objectId
); );
props.setList(upldatedList); props.setList(upldatedList);
const params = {
event: "declined",
body: {
objectId: item.objectId,
file: item?.SignedUrl || item?.URL,
name: item?.Name,
note: item?.Note || "",
description: item?.Description || "",
signers: item?.Signers?.map((x) => ({
name: x?.Name,
email: x?.Email,
phone: x?.Phone
})),
declinedBy: jsonSender?.email,
declinedReason: reason,
declinedAt: new Date(),
createdAt: item?.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")
}
}
);
} catch (err) {
console.log("Err ", err);
}
} }
setReason("");
}) })
.catch((err) => { .catch((err) => {
console.log("err", err); console.log("err", err);
@@ -41,8 +41,8 @@ function CustomModal(props) {
className="op-btn op-btn-primary mr-2 px-6" className="op-btn op-btn-primary mr-2 px-6"
type="button" type="button"
onClick={() => { onClick={() => {
setReason("");
props.declineDoc(reason); props.declineDoc(reason);
setReason("");
}} }}
> >
{t("yes")} {t("yes")}
@@ -41,7 +41,7 @@
"upgrade-now": "Upgrade now", "upgrade-now": "Upgrade now",
"upgrade-to": "Upgrade to", "upgrade-to": "Upgrade to",
"plan": "Plan", "plan": "Plan",
"subscribe-card-teamplan":"Unlock the full power of collaboration! Create unlimited organizations, teams, and hierarchies. Share templates seamlessly across teams and assign custom user roles. Elevate your workflow today!", "subscribe-card-teamplan": "Unlock the full power of collaboration! Create unlimited organizations, teams, and hierarchies. Share templates seamlessly across teams and assign custom user roles. Elevate your workflow today!",
"subscribe-card-plan": "Unlock premium features starting at just {{premiumPrice}}/month. Enjoy enhanced performance and only {{addonPrice}} per additional credit after your included premium credits.", "subscribe-card-plan": "Unlock premium features starting at just {{premiumPrice}}/month. Enjoy enhanced performance and only {{addonPrice}} per additional credit after your included premium credits.",
"user-name-limit-char": "To have a username less than 8 character please subscribe", "user-name-limit-char": "To have a username less than 8 character please subscribe",
"tour-content": "Don't show this again", "tour-content": "Don't show this again",
@@ -614,27 +614,35 @@
"select-date-format": "Select a date format", "select-date-format": "Select a date format",
"quantity-of-credits": "Quantity of premium credits", "quantity-of-credits": "Quantity of premium credits",
"remaining-credits": "Premium credits available:", "remaining-credits": "Premium credits available:",
"remaining-credits-help":"Use premium credits for API document signing, bulk sending, or embedding OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.", "remaining-credits-help": "Use premium credits for API document signing, bulk sending, or embedding OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
"additional-credits": "Please purchase premium credits", "additional-credits": "Please purchase premium credits",
"quota-err-quicksend": "Quota Reached, You don't have enough credits.", "quota-err-quicksend": "Quota Reached, You don't have enough credits.",
"buy-credits": "Buy Premium Credits", "buy-credits": "Buy Premium Credits",
"rotate-right":"Rotate right", "rotate-right": "Rotate right",
"rotate-left":"Rotate left", "rotate-left": "Rotate left",
"rotate-alert-mssg":"All widgets on this page will be lost. Are you sure you want to proceed?", "rotate-alert-mssg": "All widgets on this page will be lost. Are you sure you want to proceed?",
"templateid":"Template-Id", "templateid": "Template-Id",
"bulk-send-subcription-alert":"Please upgrade to Professional or Team plan to use bulk send.", "bulk-send-subcription-alert": "Please upgrade to Professional or Team plan to use bulk send.",
"generate-test-token":"Generate Test Token", "generate-test-token": "Generate Test Token",
"regenerate-test-token":"Regenerate Test Token", "regenerate-test-token": "Regenerate Test Token",
"help-test-token":"This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once youve completed your testing, you can upgrade to one of our paid plans to generate a production token.", "help-test-token": "This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once youve completed your testing, you can upgrade to one of our paid plans to generate a production token.",
"help-api-token":"This token can be used to access the production APIs at the {{origin}}/api/v1 endpoint. It can only be generated on one of our paid plans.", "help-api-token": "This token can be used to access the production APIs at the {{origin}}/api/v1 endpoint. It can only be generated on one of our paid plans.",
"quota-mail-info-head":"Monthly request signatures email limit", "quota-mail-info-head": "Monthly request signatures email limit",
"quota-mail-info": "You can send upto 15 signature request emails every month. Upgrade now to send unlimited signing requests directly.", "quota-mail-info": "You can send upto 15 signature request emails every month. Upgrade now to send unlimited signing requests directly.",
"quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.", "quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.",
"quota-mail-tip":"Tip: You can still sign <1>unlimited documents</1> by manually sharing the signing request links.", "quota-mail-tip": "Tip: You can still sign <1>unlimited documents</1> by manually sharing the signing request links.",
"quota-mail-head":"Quota Reached", "quota-mail-head": "Quota Reached",
"unauthorized-modal":"You don't have permission to perform this action, please contact {{adminEmail}}.", "unauthorized-modal": "You don't have permission to perform this action, please contact {{adminEmail}}.",
"sent-this-month":"Sent this month", "sent-this-month": "Sent this month",
"available-seats":"Available seats", "available-seats": "Available seats",
"buy-users":"Buy more users" "buy-users": "Buy more users",
"isenable-otp": "Enable OTP verification",
"isenable-otp-help": {
"p1": "Would you like to enable the verification process using a one-time password (OTP)?",
"p2": "Selecting this option will enable OTP verification. Users will receive a verification code via email, which they must enter to sign the document.",
"p3": "Selecting this option will disable OTP verification, allowing users to sign the document directly without additional steps.",
"p4": "Please choose the option that best suits your document signing requirements."
},
"advanced-options": "Advanced options",
"hide-advanced-options": "Hide Advanced options"
} }
@@ -42,7 +42,7 @@
"upgrade-to": "Mettre à niveau vers", "upgrade-to": "Mettre à niveau vers",
"pro": "PRO", "pro": "PRO",
"plan": "Offre", "plan": "Offre",
"subscribe-card-teamplan":"Libérez toute la puissance de la collaboration ! Créez un nombre illimité d'organisations, d'équipes et de hiérarchies. Partagez des modèles de manière transparente entre les équipes et attribuez des rôles d'utilisateur personnalisés. Améliorez votre flux de travail dès aujourd'hui !", "subscribe-card-teamplan": "Libérez toute la puissance de la collaboration ! Créez un nombre illimité d'organisations, d'équipes et de hiérarchies. Partagez des modèles de manière transparente entre les équipes et attribuez des rôles d'utilisateur personnalisés. Améliorez votre flux de travail dès aujourd'hui !",
"subscribe-card-plan": "Débloquez des fonctionnalités premium à partir de seulement {{premiumPrice}}/mois. Bénéficiez de performances améliorées et de seulement {{addonPrice}} par crédit supplémentaire après vos crédits premium inclus.", "subscribe-card-plan": "Débloquez des fonctionnalités premium à partir de seulement {{premiumPrice}}/mois. Bénéficiez de performances améliorées et de seulement {{addonPrice}} par crédit supplémentaire après vos crédits premium inclus.",
"user-name-limit-char": "Pour avoir un nom d'utilisateur de moins de 8 caractères s'il vous plaît s'abonner", "user-name-limit-char": "Pour avoir un nom d'utilisateur de moins de 8 caractères s'il vous plaît s'abonner",
"tour-content": "Ne plus afficher", "tour-content": "Ne plus afficher",
@@ -611,29 +611,37 @@
"Add-seats": "Ajouter des sièges", "Add-seats": "Ajouter des sièges",
"format": "format", "format": "format",
"select-date-format": "Sélectionnez un format de date", "select-date-format": "Sélectionnez un format de date",
"quantity-of-credits":"Quantité de crédits de prime", "quantity-of-credits": "Quantité de crédits de prime",
"remaining-credits":"Crédits premium disponibles :", "remaining-credits": "Crédits premium disponibles :",
"additional-credits":"Veuillez acheter des crédits premium", "additional-credits": "Veuillez acheter des crédits premium",
"remaining-credits-help":"Utilisez des crédits premium pour la signature de documents API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits achetés supplémentaires.", "remaining-credits-help": "Utilisez des crédits premium pour la signature de documents API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits achetés supplémentaires.",
"quota-err-quicksend": "Quota atteint, vous n'avez pas assez de crédits.", "quota-err-quicksend": "Quota atteint, vous n'avez pas assez de crédits.",
"buy-credits": "Acheter des crédits premium", "buy-credits": "Acheter des crédits premium",
"rotate-right" :"Faire pivoter à droite", "rotate-right": "Faire pivoter à droite",
"rotate-left" :"Faire pivoter à gauche", "rotate-left": "Faire pivoter à gauche",
"rotate-alert-mssg" :"Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?", "rotate-alert-mssg": "Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?",
"templateid":"ID de modèle", "templateid": "ID de modèle",
"bulk-send-subcription-alert":"Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.", "bulk-send-subcription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
"generate-test-token": "Générer jeton de test", "generate-test-token": "Générer jeton de test",
"regenerate-test-token":"Régénérer le jeton de test", "regenerate-test-token": "Régénérer le jeton de test",
"help-test-token":"Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à lun de nos forfaits payants pour générer un jeton de production.", "help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à lun de nos forfaits payants pour générer un jeton de production.",
"help-api-token":"Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.", "help-api-token": "Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
"quota-mail-info-head":"Limite mensuelle d'e-mails de signatures de demandes", "quota-mail-info-head": "Limite mensuelle d'e-mails de signatures de demandes",
"quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.", "quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.",
"quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.", "quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.",
"quota-mail-tip":"Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.", "quota-mail-tip": "Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.",
"quota-mail-head":"Quota atteint", "quota-mail-head": "Quota atteint",
"unauthorized-modal":"Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.", "unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
"sent-this-month":"envoyé ce mois-ci", "sent-this-month": "envoyé ce mois-ci",
"available-seats":"Disponible sièges", "available-seats": "Disponible sièges",
"buy-users":"Acheter plus d'utilisateurs" "buy-users": "Acheter plus d'utilisateurs",
"isenable-otp": "Activer la vérification OTP",
"isenable-otp-help": {
"p1": "Souhaitez-vous activer le processus de vérification à l'aide d'un mot de passe à usage unique (OTP)?",
"p2": "La sélection de cette option activera la vérification OTP. Les utilisateurs recevront un code de vérification par e-mail, qu'ils devront saisir pour signer le document.",
"p3": "La sélection de cette option désactivera la vérification OTP, permettant aux utilisateurs de signer le document directement sans étapes supplémentaires.",
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
},
"advanced-options": "Options avancées",
"hide-advanced-options": "Masquer les options avancées"
} }
@@ -196,6 +196,9 @@ export default async function createDocumentWithTemplate(request, response) {
if (TimeToCompleteDays) { if (TimeToCompleteDays) {
object.set('TimeToCompleteDays', TimeToCompleteDays); object.set('TimeToCompleteDays', TimeToCompleteDays);
} }
const enableOTP = request.body?.enableOTP === true ? true : false;
const IsEnableOTP = enableOTP || template?.IsEnableOTP || false;
object.set('IsEnableOTP', IsEnableOTP);
object.set('CreatedBy', template.CreatedBy); object.set('CreatedBy', template.CreatedBy);
object.set('ExtUserPtr', { object.set('ExtUserPtr', {
__type: 'Pointer', __type: 'Pointer',
@@ -63,6 +63,7 @@ export default async function createDocumentwithCoordinate(request, response) {
const email_body = request.body.email_body; const email_body = request.body.email_body;
const sendInOrder = request.body.sendInOrder || false; const sendInOrder = request.body.sendInOrder || false;
const TimeToCompleteDays = request.body.timeToCompleteDays || 15; const TimeToCompleteDays = request.body.timeToCompleteDays || 15;
const IsEnableOTP = request.body?.enableOTP === true ? true : false;
// console.log('fileData ', fileData); // console.log('fileData ', fileData);
const protocol = customAPIurl(); const protocol = customAPIurl();
const baseUrl = new URL(process.env.PUBLIC_URL); const baseUrl = new URL(process.env.PUBLIC_URL);
@@ -162,6 +163,7 @@ export default async function createDocumentwithCoordinate(request, response) {
if (TimeToCompleteDays) { if (TimeToCompleteDays) {
object.set('TimeToCompleteDays', TimeToCompleteDays); object.set('TimeToCompleteDays', TimeToCompleteDays);
} }
object.set('IsEnableOTP', IsEnableOTP);
object.set('IsSendMail', send_email); object.set('IsSendMail', send_email);
let contact = []; let contact = [];
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
@@ -9,6 +9,7 @@ export default async function createTemplate(request, response) {
const SendinOrder = request.body.sendInOrder || false; const SendinOrder = request.body.sendInOrder || false;
const fileData = request.files?.[0] ? request.files[0].buffer : null; const fileData = request.files?.[0] ? request.files[0].buffer : null;
const baseUrl = new URL(process.env.PUBLIC_URL); const baseUrl = new URL(process.env.PUBLIC_URL);
const isEnableOTP = request.body?.enableOTP === true ? true : false;
try { try {
const reqToken = request.headers['x-api-token']; const reqToken = request.headers['x-api-token'];
@@ -65,6 +66,7 @@ export default async function createTemplate(request, response) {
object.set('URL', fileUrl); object.set('URL', fileUrl);
object.set('CreatedBy', userPtr); object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr); object.set('ExtUserPtr', extUserPtr);
object.set('IsEnableOTP', isEnableOTP);
if (SendinOrder) { if (SendinOrder) {
object.set('SendinOrder', SendinOrder); object.set('SendinOrder', SendinOrder);
} }
@@ -17,6 +17,8 @@ export default async function createTemplatewithCoordinate(request, response) {
const base64File = request.body.file; const base64File = request.body.file;
const fileData = request.files?.[0] ? request.files[0].buffer : null; const fileData = request.files?.[0] ? request.files[0].buffer : null;
const SendinOrder = request.body.sendInOrder || false; const SendinOrder = request.body.sendInOrder || false;
const isEnableOTP = request.body?.enableOTP === true ? true : false;
// console.log('fileData ', fileData); // console.log('fileData ', fileData);
const protocol = customAPIurl(); const protocol = customAPIurl();
@@ -80,6 +82,7 @@ export default async function createTemplatewithCoordinate(request, response) {
object.set('URL', fileUrl); object.set('URL', fileUrl);
object.set('CreatedBy', userPtr); object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr); object.set('ExtUserPtr', extUserPtr);
object.set('IsEnableOTP', isEnableOTP);
let contact = []; let contact = [];
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let parseSigners; let parseSigners;
@@ -12,6 +12,8 @@ export default async function draftDocument(request, response) {
const send_email = request.body.send_email || true; const send_email = request.body.send_email || true;
const fileData = request.files?.[0] ? request.files[0].buffer : null; const fileData = request.files?.[0] ? request.files[0].buffer : null;
const SendinOrder = request.body.sendInOrder || false; const SendinOrder = request.body.sendInOrder || false;
const isEnableOTP = request.body?.enableOTP === true ? true : false;
// console.log('fileData ', fileData); // console.log('fileData ', fileData);
const protocol = customAPIurl(); const protocol = customAPIurl();
const baseUrl = new URL(process.env.PUBLIC_URL); const baseUrl = new URL(process.env.PUBLIC_URL);
@@ -83,6 +85,7 @@ export default async function draftDocument(request, response) {
object.set('CreatedBy', userPtr); object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr); object.set('ExtUserPtr', extUserPtr);
object.set('IsSendMail', send_email); object.set('IsSendMail', send_email);
object.set('IsEnableOTP', isEnableOTP);
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let parseSigners; let parseSigners;
if (base64File) { if (base64File) {
@@ -62,6 +62,7 @@ export default async function getDocument(request, response) {
document?.Signers?.map(y => ({ name: y?.Name, email: y?.Email, phone: y?.Phone })) || document?.Signers?.map(y => ({ name: y?.Name, email: y?.Email, phone: y?.Phone })) ||
[], [],
sendInOrder: document?.SendinOrder || false, sendInOrder: document?.SendinOrder || false,
enableOTP: document?.IsEnableOTP || false,
createdAt: document.createdAt, createdAt: document.createdAt,
updatedAt: document.updatedAt, updatedAt: document.updatedAt,
}); });
@@ -100,6 +100,7 @@ export default async function getDocumentList(request, response) {
x?.Signers?.map(y => ({ name: y?.Name, email: y?.Email, phone: y?.Phone })) || x?.Signers?.map(y => ({ name: y?.Name, email: y?.Email, phone: y?.Phone })) ||
[], [],
sendInOrder: x?.SendinOrder || false, sendInOrder: x?.SendinOrder || false,
enableOTP: x?.IsEnableOTP || false,
createdAt: x.createdAt, createdAt: x.createdAt,
updatedAt: x.updatedAt, updatedAt: x.updatedAt,
})); }));
@@ -61,6 +61,7 @@ export default async function getTemplate(request, response) {
), ),
})) || [], })) || [],
sendInOrder: template?.SendinOrder || false, sendInOrder: template?.SendinOrder || false,
enableOTP: template?.IsEnableOTP || false,
createdAt: template.createdAt, createdAt: template.createdAt,
updatedAt: template.updatedAt, updatedAt: template.updatedAt,
}); });
@@ -88,6 +88,7 @@ export default async function getTemplatetList(request, response) {
), ),
})) || [], })) || [],
sendInOrder: template?.SendinOrder || false, sendInOrder: template?.SendinOrder || false,
enableOTP: template?.IsEnableOTP || false,
createdAt: template.createdAt, createdAt: template.createdAt,
updatedAt: template.updatedAt, updatedAt: template.updatedAt,
})); }));
@@ -10,7 +10,7 @@ export default async function updateDocument(request, response) {
const token = await tokenQuery.first({ useMasterKey: true }); const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) { if (token !== undefined) {
// Valid Token then proceed request // Valid Token then proceed request
const allowedKeys = ['name', 'note', 'description', 'folderId']; const allowedKeys = ['name', 'note', 'description', 'folderId', 'enableOTP'];
const objectKeys = Object.keys(request.body); const objectKeys = Object.keys(request.body);
const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0; const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0;
const parseUser = JSON.parse(JSON.stringify(token)); const parseUser = JSON.parse(JSON.stringify(token));
@@ -48,6 +48,10 @@ export default async function updateDocument(request, response) {
objectId: request?.body?.folderId, objectId: request?.body?.folderId,
}); });
} }
if (request.body?.enableOTP) {
updateQuery.set('IsEnableOTP', request.body?.enableOTP);
}
const updatedRes = await updateQuery.save(null, { useMasterKey: true }); const updatedRes = await updateQuery.save(null, { useMasterKey: true });
if (updatedRes) { if (updatedRes) {
if (request.posthog) { if (request.posthog) {
@@ -10,7 +10,7 @@ export default async function updateTemplate(request, response) {
const token = await tokenQuery.first({ useMasterKey: true }); const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) { if (token !== undefined) {
// Valid Token then proceed request // Valid Token then proceed request
const allowedKeys = ['name', 'note', 'description', 'folderId']; const allowedKeys = ['name', 'note', 'description', 'folderId', 'enableOTP'];
const objectKeys = Object.keys(request.body); const objectKeys = Object.keys(request.body);
const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0; const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0;
const parseUser = JSON.parse(JSON.stringify(token)); const parseUser = JSON.parse(JSON.stringify(token));
@@ -48,6 +48,9 @@ export default async function updateTemplate(request, response) {
objectId: request?.body?.folderId, objectId: request?.body?.folderId,
}); });
} }
if (request.body?.enableOTP) {
updateQuery.set('IsEnableOTP', request.body?.enableOTP);
}
const updatedRes = await updateQuery.save(null, { useMasterKey: true }); const updatedRes = await updateQuery.save(null, { useMasterKey: true });
if (updatedRes) { if (updatedRes) {
if (request.posthog) { if (request.posthog) {
+6
View File
@@ -65,6 +65,9 @@ import ExtUserAftersave from './parsefunction/ExtUserAftersave.js';
import ExtUserAfterdelete from './parsefunction/ExtUserAfterdelete.js'; import ExtUserAfterdelete from './parsefunction/ExtUserAfterdelete.js';
import AllowedCredits from './parsefunction/AllowedCredits.js'; import AllowedCredits from './parsefunction/AllowedCredits.js';
import BuyCredits from './parsefunction/BuyCredits.js'; import BuyCredits from './parsefunction/BuyCredits.js';
import getContact from './parsefunction/getContact.js';
import updateContactTour from './parsefunction/updateContactTour.js';
import declinedocument from './parsefunction/declinedocument.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);
@@ -142,3 +145,6 @@ Parse.Cloud.define('allowedusers', AllowedUsers);
Parse.Cloud.define('buyaddonusers', BuyAddonUsers); Parse.Cloud.define('buyaddonusers', BuyAddonUsers);
Parse.Cloud.define('allowedcredits', AllowedCredits); Parse.Cloud.define('allowedcredits', AllowedCredits);
Parse.Cloud.define('buycredits', BuyCredits); Parse.Cloud.define('buycredits', BuyCredits);
Parse.Cloud.define('getcontact', getContact);
Parse.Cloud.define('updatecontacttour', updateContactTour);
Parse.Cloud.define('declinedoc', declinedocument);
@@ -62,6 +62,7 @@ const createDocumentFromTemplate = async (template, existContact, index) => {
object.set('CreatedBy', template?.CreatedBy); object.set('CreatedBy', template?.CreatedBy);
object.set('ExtUserPtr', template?.ExtUserPtr); object.set('ExtUserPtr', template?.ExtUserPtr);
object.set('OriginIp', template?.OriginIp || ''); object.set('OriginIp', template?.OriginIp || '');
object.set('IsEnableOTP', template?.IsEnableOTP || false);
let signers = template?.Signers || []; let signers = template?.Signers || [];
const signerobj = { const signerobj = {
__type: 'Pointer', __type: 'Pointer',
@@ -7,99 +7,101 @@ export default async function callWebhook(request) {
const contactId = request.params.contactId; const contactId = request.params.contactId;
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID; const appId = process.env.APP_ID;
const userRes = await axios.get(serverUrl + '/users/me', { try {
headers: { const docQuery = new Parse.Query('contracts_Document');
'X-Parse-Application-Id': appId, const docRes = await docQuery.get(docId, { useMasterKey: true });
'X-Parse-Session-Token': request.headers['sessiontoken'], const isEnableOTP = docRes?.get('IsEnableOTP') || false;
}, let userId;
}); if (isEnableOTP) {
const userRes = await axios.get(serverUrl + '/users/me', {
const userId = userRes.data && userRes.data.objectId; headers: {
if (userId) { 'X-Parse-Application-Id': appId,
if (event === 'viewed' && contactId) { 'X-Parse-Session-Token': request.headers['sessiontoken'],
const docQuery = new Parse.Query('contracts_Document'); },
const res = await docQuery.get(docId, { useMasterKey: true }); });
if (res) { userId = userRes.data && userRes.data.objectId;
const _res = res.toJSON(); }
const userPtr = { if (!isEnableOTP || userId) {
__type: 'Pointer', if (event === 'viewed' && contactId) {
className: 'contracts_Contactbook', if (docRes) {
objectId: contactId, const _docRes = docRes.toJSON();
}; const userPtr = {
const date = new Date().toISOString(); __type: 'Pointer',
const obj = { className: 'contracts_Contactbook',
UserPtr: userPtr, objectId: contactId,
SignedUrl: _res.SignedUrl, };
Activity: 'Viewed', const date = new Date().toISOString();
ipAddress: request.headers['x-real-ip'], const obj = {
ViewedOn: date, UserPtr: userPtr,
}; SignedUrl: _docRes.SignedUrl,
const isUserExist = _res?.AuditTrail?.some( Activity: 'Viewed',
x => x.UserPtr.objectId === contactId && x?.ViewedOn ipAddress: request.headers['x-real-ip'],
); ViewedOn: date,
if (!isUserExist) { };
const updateDoc = new Parse.Object('contracts_Document'); const isUserExist = _docRes?.AuditTrail?.some(
updateDoc.id = res.id; x => x.UserPtr.objectId === contactId && x?.ViewedOn
if (_res?.AuditTrail && _res?.AuditTrail?.length > 0) { );
updateDoc.set('AuditTrail', [..._res?.AuditTrail, obj]); if (!isUserExist) {
} else { const updateDoc = new Parse.Object('contracts_Document');
updateDoc.set('AuditTrail', [obj]); updateDoc.id = docRes.id;
if (_docRes?.AuditTrail && _docRes?.AuditTrail?.length > 0) {
updateDoc.set('AuditTrail', [..._docRes?.AuditTrail, obj]);
} else {
updateDoc.set('AuditTrail', [obj]);
}
await updateDoc.save(null, { useMasterKey: true });
} }
await updateDoc.save(null, { useMasterKey: true });
} }
} }
} const extendcls = new Parse.Query('contracts_Users');
const docQuery = new Parse.Query('contracts_Document'); extendcls.equalTo('objectId', docRes.get('ExtUserPtr')?.id);
const resDoc = await docQuery.get(docId, { useMasterKey: true }); // extendcls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const extendcls = new Parse.Query('contracts_Users'); const resExt = await extendcls.first({ useMasterKey: true });
extendcls.equalTo('objectId', resDoc.get('ExtUserPtr')?.id); if (resExt) {
// extendcls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId }); const extUser = JSON.parse(JSON.stringify(resExt));
const res = await extendcls.first({ useMasterKey: true }); if (extUser?.Webhook) {
if (res) { const params = { event: event, ...body };
const extUser = JSON.parse(JSON.stringify(res)); await axios
if (extUser?.Webhook) { .post(extUser?.Webhook, params, {
const params = { headers: { 'Content-Type': 'application/json' },
event: event, })
...body, .then(res => {
}; try {
await axios const webhook = new Parse.Object('contracts_Webhook');
.post(extUser?.Webhook, params, { webhook.set('Log', res?.status);
headers: { 'Content-Type': 'application/json' }, webhook.set('UserId', {
}) __type: 'Pointer',
.then(res => { className: '_User',
try { objectId: userId,
// console.log('res ', res); });
const webhook = new Parse.Object('contracts_Webhook'); webhook.save(null, { useMasterKey: true });
webhook.set('Log', res?.status); } catch (err) {
webhook.set('UserId', { console.log('err save in contracts_Webhook', err.message);
__type: 'Pointer', }
className: '_User', })
objectId: userId, .catch(err => {
}); console.log('Err send data to webhook', err.message);
webhook.save(null, { useMasterKey: true }); try {
} catch (err) { const webhook = new Parse.Object('contracts_Webhook');
console.log('err save in contracts_Webhook', err.message); webhook.set('Log', err?.status);
} webhook.set('UserId', {
}) __type: 'Pointer',
.catch(err => { className: '_User',
console.log('Err send data to webhook', err.message); objectId: userId,
try { });
const webhook = new Parse.Object('contracts_Webhook'); webhook.save(null, { useMasterKey: true });
webhook.set('Log', err?.status); } catch (err) {
webhook.set('UserId', { console.log('err save in contracts_Webhook', err.message);
__type: 'Pointer', }
className: '_User', });
objectId: userId, }
}); return { message: 'webhook called!' };
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
});
} }
return { message: 'webhook called!' }; } else {
return { message: 'User not found!' };
} }
} else { } catch (err) {
return { message: 'User not found!' }; console.log('Err in callwebhook', err);
return { message: 'Something went wrong!' };
} }
} }
@@ -199,6 +199,7 @@ export default async function createBatchDocs(request) {
TimeToCompleteDays: x.TimeToCompleteDays || 15, TimeToCompleteDays: x.TimeToCompleteDays || 15,
OriginIp: Ip, OriginIp: Ip,
DocSentAt: { __type: 'Date', iso: isoDate }, DocSentAt: { __type: 'Date', iso: isoDate },
IsEnableOTP: x?.IsEnableOTP || false,
}, },
}; };
}); });
@@ -0,0 +1,38 @@
export default async function declinedocument(request) {
const docId = request.params.docId;
const reason = request.params?.reason || '';
const declineBy = {
__type: 'Pointer',
className: '_User',
objectId: request.params?.userId,
};
try {
const docCls = new Parse.Query('contracts_Document');
const updateDoc = await docCls.get(docId, { useMasterKey: true });
if (updateDoc) {
const isEnableOTP = updateDoc?.get('IsEnableOTP') || false;
if (!isEnableOTP) {
updateDoc.set('IsDeclined', true);
updateDoc.set('DeclineReason', reason);
updateDoc.set('DeclineBy', declineBy);
await updateDoc.save(null, { useMasterKey: true });
return 'document declined';
} else {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
updateDoc.set('IsDeclined', true);
updateDoc.set('DeclineReason', reason);
updateDoc.set('DeclineBy', declineBy);
await updateDoc.save(null, { useMasterKey: true });
return 'document declined';
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
} catch (err) {
console.log('err while decling doc', err);
throw err;
}
}
@@ -0,0 +1,11 @@
export default async function getContact(request) {
const contactId = request.params.contactId;
try {
const contactCls = new Parse.Query('contracts_Contactbook');
const contactRes = await contactCls.get(contactId, { useMasterKey: true });
return contactRes;
} catch (err) {
console.log('Err in contracts_Contactbook class ', err);
throw err;
}
}
@@ -6,14 +6,7 @@ export default async function getDocument(request) {
const docId = request.params.docId; const docId = request.params.docId;
try { try {
const userRes = await axios.get(serverUrl + '/users/me', { if (docId) {
headers: {
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
const userId = userRes.data && userRes.data.objectId;
if (docId && userId) {
try { try {
const query = new Parse.Query('contracts_Document'); const query = new Parse.Query('contracts_Document');
query.equalTo('objectId', docId); query.equalTo('objectId', docId);
@@ -26,11 +19,32 @@ export default async function getDocument(request) {
query.notEqualTo('IsArchive', true); query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true }); const res = await query.first({ useMasterKey: true });
if (res) { if (res) {
const acl = res.getACL(); const IsEnableOTP = res?.get('IsEnableOTP') || false;
if (acl && acl.getReadAccess(userId)) { if (!IsEnableOTP) {
return res; return res;
} else { } else {
return { error: "You don't have access of this document!" }; if (request?.headers?.['sessiontoken']) {
try {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
const userId = userRes.data && userRes.data?.objectId;
const acl = res.getACL();
if (userId && acl && acl.getReadAccess(userId)) {
return res;
} else {
return { error: "You don't have access of this document!" };
}
} catch (err) {
console.log('err user in not authenticated', err);
return { error: "You don't have access of this document!" };
}
} else {
return { error: "You don't have access of this document!" };
}
} }
} else { } else {
return { error: "You don't have access of this document!" }; return { error: "You don't have access of this document!" };
@@ -28,15 +28,42 @@ export default function getPresignedUrl(url) {
export async function getSignedUrl(request) { export async function getSignedUrl(request) {
try { try {
const docId = request.params.docId || '';
const url = request.params.url; const url = request.params.url;
if (!request?.user) { if (docId) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); try {
const query = new Parse.Query('contracts_Document');
query.equalTo('objectId', docId);
query.notEqualTo('IsEnableOTP', true);
query.include('CreatedBy');
query.include('Signers');
query.include('AuditTrail.UserPtr');
query.include('Placeholders');
query.include('DeclineBy');
query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true });
if (res) {
if (useLocal !== 'true') {
const presignedUrl = getPresignedUrl(url);
return presignedUrl;
} else {
return url;
}
}
} catch (err) {
console.log('Err in presigned url', err);
throw err;
}
} else { } else {
if (useLocal !== 'true') { if (!request?.user) {
const presignedUrl = getPresignedUrl(url); throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
return presignedUrl;
} else { } else {
return url; if (useLocal !== 'true') {
const presignedUrl = getPresignedUrl(url);
return presignedUrl;
} else {
return url;
}
} }
} }
} catch (err) { } catch (err) {
@@ -50,37 +50,24 @@ export default async function getSubscription(request) {
} }
} else if (contactId) { } else if (contactId) {
try { try {
const userRes = await axios.get(serverUrl + '/users/me', { const contactCls = new Parse.Query('contracts_Contactbook');
headers: { const contactUser = await contactCls.get(contactId, { useMasterKey: true });
'X-Parse-Application-Id': appId, if (contactUser) {
'X-Parse-Session-Token': request.headers['sessiontoken'], const subscriptionCls = new Parse.Query('contracts_Subscriptions');
}, subscriptionCls.equalTo('TenantId', {
}); __type: 'Pointer',
className: 'partners_Tenant',
const userId = userRes.data && userRes.data.objectId; objectId: contactUser.get('TenantId').id,
if (userId) { });
const contactCls = new Parse.Query('contracts_Contactbook'); subscriptionCls.descending('createdAt');
const contactUser = await contactCls.get(contactId, { useMasterKey: true }); const subcripitions = await subscriptionCls.first({ useMasterKey: true });
if (contactUser) { if (subcripitions) {
const subscriptionCls = new Parse.Query('contracts_Subscriptions'); const _subcripitions = JSON.parse(JSON.stringify(subcripitions));
subscriptionCls.equalTo('TenantId', { if (_subcripitions.PlanCode === 'freeplan') {
__type: 'Pointer', return { status: 'success', result: { isSubscribed: false, plan: 'freeplan' } };
className: 'partners_Tenant', } else if (_subcripitions?.Next_billing_date?.iso) {
objectId: contactUser.get('TenantId').id, if (new Date(_subcripitions.Next_billing_date.iso) > new Date()) {
}); return { status: 'success', result: { isSubscribed: true } };
subscriptionCls.descending('createdAt');
const subcripitions = await subscriptionCls.first({ useMasterKey: true });
if (subcripitions) {
const _subcripitions = JSON.parse(JSON.stringify(subcripitions));
if (_subcripitions.PlanCode === 'freeplan') {
return { status: 'success', result: { isSubscribed: false, plan: 'freeplan' } };
} else if (_subcripitions?.Next_billing_date?.iso) {
if (new Date(_subcripitions.Next_billing_date.iso) > new Date()) {
return { status: 'success', result: { isSubscribed: true } };
} else {
return { status: 'success', result: { isSubscribed: false } };
}
} else { } else {
return { status: 'success', result: { isSubscribed: false } }; return { status: 'success', result: { isSubscribed: false } };
} }
@@ -88,10 +75,10 @@ export default async function getSubscription(request) {
return { status: 'success', result: { isSubscribed: false } }; return { status: 'success', result: { isSubscribed: false } };
} }
} else { } else {
return { status: 'error', result: 'User not found!' }; return { status: 'success', result: { isSubscribed: false } };
} }
} else { } else {
return { status: 'error', result: 'Invalid session token!' }; return { status: 'error', result: 'User not found!' };
} }
} catch (err) { } catch (err) {
console.log('Err in get subscription2', err.message); console.log('Err in get subscription2', err.message);
@@ -32,6 +32,7 @@ export default async function GenerateCertificate(docDetails) {
const OriginIp = docDetails?.OriginIp || ''; const OriginIp = docDetails?.OriginIp || '';
const company = docDetails?.ExtUserPtr?.Company || ''; const company = docDetails?.ExtUserPtr?.Company || '';
const createdAt = docDetails?.DocSentAt?.iso || docDetails.createdAt; const createdAt = docDetails?.DocSentAt?.iso || docDetails.createdAt;
const IsEnableOTP = docDetails?.IsEnableOTP || false;
const auditTrail = const auditTrail =
docDetails?.Signers?.length > 0 docDetails?.Signers?.length > 0
? docDetails.AuditTrail.map(x => { ? docDetails.AuditTrail.map(x => {
@@ -348,22 +349,22 @@ export default async function GenerateCertificate(docDetails) {
font: timesRomanFont, font: timesRomanFont,
color: textValueColor, color: textValueColor,
}); });
page.drawText('Security level :', { if (IsEnableOTP) {
x: half + 55, page.drawText('Security level :', {
y: yPosition4 + 10, x: half + 55,
size: timeText, y: yPosition4 + 10,
font: timesRomanFont, size: timeText,
color: textKeyColor, font: timesRomanFont,
}); color: textKeyColor,
});
page.drawText(`Email, OTP Auth`, { page.drawText('Email, OTP Auth', {
x: half + 125, x: half + 125,
y: yPosition4 + 10, y: yPosition4 + 10,
size: timeText, size: timeText,
font: timesRomanFont, font: timesRomanFont,
color: textValueColor, color: textValueColor,
}); });
}
page.drawText('Signature :', { page.drawText('Signature :', {
x: 30, x: 30,
y: yPosition5, y: yPosition5,
+146 -143
View File
@@ -272,157 +272,160 @@ const sendMailsaveCertifcate = async (doc, P12Buffer, url, isCustomMail, mailPro
*/ */
async function PDF(req) { async function PDF(req) {
try { try {
if (!req?.user) { const userIP = req.headers['x-real-ip']; // client IPaddress
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); 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 IsEnableOTP = resDoc?.get('IsEnableOTP') || false;
// if `IsEnableOTP` is false then we don't have to check authentication
if (IsEnableOTP) {
if (!req?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
}
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 { } else {
const userIP = req.headers['x-real-ip']; // client IPaddress className = 'contracts_Users';
const docId = req.params.docId; signUser = _resDoc.ExtUserPtr;
const reqUserId = req.params.userId; }
const isCustomMail = req.params.isCustomCompletionMail || false;
const mailProvider = req.params.mailProvider || ''; const username = signUser.Name;
const sign = req.params.signature || ''; const userEmail = signUser.Email;
// below bode is used to get info of docId if (req.params.pdfFile) {
const docQuery = new Parse.Query('contracts_Document'); // `PdfBuffer` used to create buffer from pdf file
docQuery.include('ExtUserPtr,Signers'); let PdfBuffer = Buffer.from(req.params.pdfFile, 'base64');
docQuery.equalTo('objectId', docId); // `P12Buffer` used to create buffer from p12 certificate
const resDoc = await docQuery.first({ useMasterKey: true }); const pfxFile = process.env.PFX_BASE64;
if (!resDoc) { // const P12Buffer = fs.readFileSync();
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.'); const P12Buffer = Buffer.from(pfxFile, 'base64');
} const p12Cert = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
const _resDoc = resDoc?.toJSON(); const UserPtr = { __type: 'Pointer', className: className, objectId: signUser.objectId };
let signUser; const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP };
let className; let updateAuditTrail;
// `reqUserId` is send throught pdfrequest signing flow if (_resDoc.AuditTrail && _resDoc.AuditTrail.length > 0) {
if (reqUserId) { updateAuditTrail = [..._resDoc.AuditTrail, obj];
// 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 { } else {
className = 'contracts_Users'; updateAuditTrail = [obj];
signUser = _resDoc.ExtUserPtr;
} }
const username = signUser.Name; const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
const userEmail = signUser.Email; let isCompleted = false;
if (req.params.pdfFile) { if (_resDoc.Signers && _resDoc.Signers.length > 0) {
// `PdfBuffer` used to create buffer from pdf file if (auditTrail.length === _resDoc.Signers.length) {
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: userIP };
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; 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
userIP, // client ipAddress,
_resDoc, // auditTrail, signers, etc data
className, // className based on flow
sign // sign base64
);
sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser);
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
if (updatedDoc && updatedDoc.isCompleted) {
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail };
sendMailsaveCertifcate(
doc,
P12Buffer,
data.imageUrl,
isCustomMail,
mailProvider,
_resDoc?.CreatedBy?.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 { } else {
const error = new Error('Pdf file not present!'); isCompleted = true;
error.code = 400; // Set the error code (e.g., 400 for bad request)
throw error;
} }
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
userIP, // client ipAddress,
_resDoc, // auditTrail, signers, etc data
className, // className based on flow
sign // sign base64
);
sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser);
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
if (updatedDoc && updatedDoc.isCompleted) {
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail };
sendMailsaveCertifcate(
doc,
P12Buffer,
data.imageUrl,
isCustomMail,
mailProvider,
_resDoc?.CreatedBy?.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) { } catch (err) {
console.log('Err in signpdf', err); console.log('Err in signpdf', err);
@@ -0,0 +1,33 @@
export default async function updateContactTour(request) {
const contactId = request.params.contactId;
try {
const contactCls = new Parse.Query('contracts_Contactbook');
const contactRes = await contactCls.get(contactId, { useMasterKey: true });
if (contactRes) {
const _contactRes = JSON.parse(JSON.stringify(contactRes));
const tourStatus = _contactRes?.TourStatus?.length > 0 ? _contactRes.TourStatus : [];
let updatedTourStatus = [];
if (tourStatus.length > 0) {
updatedTourStatus = [...tourStatus];
const requestSignIndex = tourStatus.findIndex(
obj => obj['requestSign'] === false || obj['requestSign'] === true
);
if (requestSignIndex !== -1) {
updatedTourStatus[requestSignIndex] = { requestSign: true };
} else {
updatedTourStatus.push({ requestSign: true });
}
} else {
updatedTourStatus = [{ requestSign: true }];
}
contactRes.set('TourStatus', updatedTourStatus);
const updateRes = await contactRes.save(null, { useMasterKey: true });
return updateRes;
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'contact not found.');
}
} catch (err) {
console.log('Err in contracts_Contactbook class ', err);
throw err;
}
}
@@ -0,0 +1,29 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
const templateSchema = new Parse.Schema('contracts_Template');
templateSchema.addBoolean('IsEnableOTP');
await templateSchema.update();
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.addBoolean('IsEnableOTP');
return schema.update();
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
const templateSchema = new Parse.Schema('contracts_Template');
templateSchema.deleteField('IsEnableOTP');
await templateSchema.update();
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.deleteField('IsEnableOTP');
return schema.update();
};
+108 -76
View File
@@ -1770,11 +1770,12 @@
} }
}, },
"node_modules/@google-cloud/firestore": { "node_modules/@google-cloud/firestore": {
"version": "7.9.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.10.0.tgz",
"integrity": "sha512-c4ALHT3G08rV7Zwv8Z2KG63gZh66iKdhCBeDfCpIkLrjX6EAjTD/szMdj14M+FnQuClZLFfW5bAgoOjfNmLtJg==", "integrity": "sha512-VFNhdHvfnmqcHHs6YhmSNHHxQqaaD64GwiL0c+e1qz85S8SWZPC2XFRf8p9yHRTF40Kow424s1KBU9f0fdQa+Q==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@opentelemetry/api": "^1.3.0",
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"google-gax": "^4.3.3", "google-gax": "^4.3.3",
@@ -1866,9 +1867,9 @@
} }
}, },
"node_modules/@graphql-tools/merge/node_modules/@graphql-tools/utils": { "node_modules/@graphql-tools/merge/node_modules/@graphql-tools/utils": {
"version": "10.5.2", "version": "10.5.4",
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.5.2.tgz", "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.5.4.tgz",
"integrity": "sha512-VZpw7wxwmQGcCGt8epw6fDb8LkoySbTJ/MU565ibKivPqCkH96XK36Et/N0RlRCYGN6QAXn5UIaSbOYYHrnpAA==", "integrity": "sha512-XHnyCWSlg1ccsD8s0y6ugo5GZ5TpkTiFVNPSYms5G0s6Z/xTuSmiLBfeqgkfaCwLmLaQnRCmNDL2JRnqc2R5bQ==",
"dependencies": { "dependencies": {
"@graphql-typed-document-node/core": "^3.1.1", "@graphql-typed-document-node/core": "^3.1.1",
"cross-inspect": "1.0.1", "cross-inspect": "1.0.1",
@@ -1900,9 +1901,9 @@
} }
}, },
"node_modules/@graphql-tools/schema/node_modules/@graphql-tools/utils": { "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/utils": {
"version": "10.5.2", "version": "10.5.4",
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.5.2.tgz", "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.5.4.tgz",
"integrity": "sha512-VZpw7wxwmQGcCGt8epw6fDb8LkoySbTJ/MU565ibKivPqCkH96XK36Et/N0RlRCYGN6QAXn5UIaSbOYYHrnpAA==", "integrity": "sha512-XHnyCWSlg1ccsD8s0y6ugo5GZ5TpkTiFVNPSYms5G0s6Z/xTuSmiLBfeqgkfaCwLmLaQnRCmNDL2JRnqc2R5bQ==",
"dependencies": { "dependencies": {
"@graphql-typed-document-node/core": "^3.1.1", "@graphql-typed-document-node/core": "^3.1.1",
"cross-inspect": "1.0.1", "cross-inspect": "1.0.1",
@@ -1936,9 +1937,9 @@
} }
}, },
"node_modules/@grpc/grpc-js": { "node_modules/@grpc/grpc-js": {
"version": "1.11.1", "version": "1.11.2",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.11.1.tgz", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.11.2.tgz",
"integrity": "sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==", "integrity": "sha512-DWp92gDD7/Qkj7r8kus6/HCINeo3yPZWZ3paKgDgsbKbSpoxKg1yvN8xe2Q8uE3zOsPe3bX8FQX2+XValq2yTw==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@grpc/proto-loader": "^0.7.13", "@grpc/proto-loader": "^0.7.13",
@@ -2625,6 +2626,15 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@opentelemetry/api": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"optional": true,
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@parse/fs-files-adapter": { "node_modules/@parse/fs-files-adapter": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/@parse/fs-files-adapter/-/fs-files-adapter-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@parse/fs-files-adapter/-/fs-files-adapter-3.0.0.tgz",
@@ -3771,11 +3781,11 @@
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.2.0", "version": "22.5.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz",
"integrity": "sha512-bm6EG6/pCpkxDf/0gDNDdtDILMOHgaQBVOJGdwsqClnxA3xL6jtMv76rLBc006RVMWbmaf0xbmom4Z/5o2nRkQ==", "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==",
"dependencies": { "dependencies": {
"undici-types": "~6.13.0" "undici-types": "~6.19.2"
} }
}, },
"node_modules/@types/node-fetch": { "node_modules/@types/node-fetch": {
@@ -3793,9 +3803,9 @@
"integrity": "sha512-4tgJ1Z3elF/tOMpA8JLVuR9spt9Ynsf7+JjqsQ2IqtiPJtcLoHoXcT6qU4E10cPFqyXX5HDm9QwIzZhBSkLxsw==" "integrity": "sha512-4tgJ1Z3elF/tOMpA8JLVuR9spt9Ynsf7+JjqsQ2IqtiPJtcLoHoXcT6qU4E10cPFqyXX5HDm9QwIzZhBSkLxsw=="
}, },
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.15", "version": "6.9.16",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz",
"integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A=="
}, },
"node_modules/@types/range-parser": { "node_modules/@types/range-parser": {
"version": "1.2.7", "version": "1.2.7",
@@ -4157,9 +4167,9 @@
} }
}, },
"node_modules/aws4": { "node_modules/aws4": {
"version": "1.13.1", "version": "1.13.2",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
"integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==" "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.7.7", "version": "1.7.7",
@@ -4270,9 +4280,9 @@
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.2", "version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"content-type": "~1.0.5", "content-type": "~1.0.5",
@@ -4282,7 +4292,7 @@
"http-errors": "2.0.0", "http-errors": "2.0.0",
"iconv-lite": "0.4.24", "iconv-lite": "0.4.24",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"qs": "6.11.0", "qs": "6.13.0",
"raw-body": "2.5.2", "raw-body": "2.5.2",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "1.0.0" "unpipe": "1.0.0"
@@ -4305,6 +4315,20 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}, },
"node_modules/body-parser/node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/bowser": { "node_modules/bowser": {
"version": "2.11.0", "version": "2.11.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
@@ -5797,29 +5821,6 @@
"express": "^4 || ^5" "express": "^4 || ^5"
} }
}, },
"node_modules/express/node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/express/node_modules/debug": { "node_modules/express/node_modules/debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -6142,11 +6143,11 @@
} }
}, },
"node_modules/firebase-admin/node_modules/@types/node": { "node_modules/firebase-admin/node_modules/@types/node": {
"version": "20.14.15", "version": "20.16.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.15.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
"integrity": "sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==", "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~6.19.2"
} }
}, },
"node_modules/firebase-admin/node_modules/long": { "node_modules/firebase-admin/node_modules/long": {
@@ -6154,11 +6155,6 @@
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
}, },
"node_modules/firebase-admin/node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/flat-cache": { "node_modules/flat-cache": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
@@ -6600,9 +6596,9 @@
} }
}, },
"node_modules/google-gax": { "node_modules/google-gax": {
"version": "4.3.9", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.3.9.tgz", "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz",
"integrity": "sha512-tcjQr7sXVGMdlvcG25wSv98ap1dtF4Z6mcV0rztGIddOcezw4YMb/uTXg72JPrLep+kXcVjaJjg6oo3KLf4itQ==", "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.10.9", "@grpc/grpc-js": "^1.10.9",
@@ -7845,9 +7841,9 @@
} }
}, },
"node_modules/loglevel": { "node_modules/loglevel": {
"version": "1.9.1", "version": "1.9.2",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
"integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
"engines": { "engines": {
"node": ">= 0.6.0" "node": ">= 0.6.0"
}, },
@@ -8518,9 +8514,9 @@
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
}, },
"node_modules/node-abi": { "node_modules/node-abi": {
"version": "3.65.0", "version": "3.67.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.67.0.tgz",
"integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", "integrity": "sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==",
"dependencies": { "dependencies": {
"semver": "^7.3.5" "semver": "^7.3.5"
}, },
@@ -9414,6 +9410,42 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/parse-server/node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/parse-server/node_modules/body-parser/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/parse-server/node_modules/body-parser/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/parse-server/node_modules/bson": { "node_modules/parse-server/node_modules/bson": {
"version": "5.5.1", "version": "5.5.1",
"resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz",
@@ -10417,9 +10449,9 @@
} }
}, },
"node_modules/protobufjs": { "node_modules/protobufjs": {
"version": "7.3.2", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
"integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -10475,9 +10507,9 @@
"dev": true "dev": true
}, },
"node_modules/pump": { "node_modules/pump": {
"version": "3.0.0", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
"dependencies": { "dependencies": {
"end-of-stream": "^1.1.0", "end-of-stream": "^1.1.0",
"once": "^1.3.1" "once": "^1.3.1"
@@ -11970,9 +12002,9 @@
"dev": true "dev": true
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.13.0", "version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==" "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
}, },
"node_modules/unpipe": { "node_modules/unpipe": {
"version": "1.0.0", "version": "1.0.0",