mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-03 08:18:50 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04c228cd36 | ||
|
|
915f8e7700 | ||
|
|
218cea00b5 | ||
|
|
f38f5ea670 | ||
|
|
791d8beb06 | ||
|
|
57a9f3aca0 | ||
|
|
ae8bf187c8 | ||
|
|
88f2ab1f8e | ||
|
|
76f5136073 | ||
|
|
0f84cd8949 | ||
|
|
adfdf7606f | ||
|
|
ce671d4294 | ||
|
|
2e7cf7a9df | ||
|
|
f699d93e44 | ||
|
|
b7a22d94db | ||
|
|
3d854756d1 | ||
|
|
251e7058f1 | ||
|
|
368eb24217 | ||
|
|
f89ef54b82 | ||
|
|
b9d9f8772b | ||
|
|
f2ee03e850 | ||
|
|
92e59a0072 | ||
|
|
5394c0b520 | ||
|
|
e4a9c6f8c2 | ||
|
|
e559d037c3 | ||
|
|
c9909a0869 | ||
|
|
988fc1bbfd | ||
|
|
1831bbcd70 | ||
|
|
ced9bbdbd5 | ||
|
|
15d57d937b | ||
|
|
555979869d | ||
|
|
4e6477297e | ||
|
|
e9e06fc53b | ||
|
|
ecb2818589 | ||
|
|
1a1bc41283 | ||
|
|
4188caed33 | ||
|
|
ffc742b460 | ||
|
|
0c04cc6ce6 | ||
|
|
4e6dfed838 | ||
|
|
fdc0524ad2 | ||
|
|
096d5962a3 | ||
|
|
9ea36f94a7 | ||
|
|
2b66df992f | ||
|
|
ff3c31975e | ||
|
|
beaa5eca69 | ||
|
|
b98040faac | ||
|
|
fbbff0df61 | ||
|
|
07d5f393c0 | ||
|
|
8220b15a29 | ||
|
|
7cf638b2be | ||
|
|
30fb8f31e3 | ||
|
|
ec4d875547 | ||
|
|
43eee63df2 | ||
|
|
4caf9f598a | ||
|
|
107eaca8f0 | ||
|
|
cc11848346 | ||
|
|
c8ecd34bd3 | ||
|
|
d4a4eb3248 | ||
|
|
f782733397 | ||
|
|
7720eddc47 | ||
|
|
71170a373a | ||
|
|
8f080080a7 | ||
|
|
71b2958c14 | ||
|
|
f69aafcbfa | ||
|
|
5f6a423697 | ||
|
|
5d245927c4 | ||
|
|
17c96fb849 | ||
|
|
b7c28cdf36 | ||
|
|
d05aa0d350 | ||
|
|
875dc086bf | ||
|
|
4719ebd52a | ||
|
|
c8a436a7df | ||
|
|
8d6f4dd64e | ||
|
|
63486927d6 | ||
|
|
c36a70f8d3 | ||
|
|
39472d6874 | ||
|
|
6207e99cc0 | ||
|
|
bdc163199d | ||
|
|
e7a04ede65 | ||
|
|
f6aee4148d | ||
|
|
6cb30a6597 | ||
|
|
34fcfc8f30 | ||
|
|
bc89b33468 | ||
|
|
af9af347e6 | ||
|
|
6f46279b52 | ||
|
|
8f93ed8b8e | ||
|
|
df91363ff4 | ||
|
|
e40e59714d | ||
|
|
a43d95afc3 | ||
|
|
8fa4b5ab22 | ||
|
|
68a030145f | ||
|
|
4f48657623 | ||
|
|
ff2f7f6586 | ||
|
|
93f33d73d9 | ||
|
|
402c176277 | ||
|
|
79b05b4a66 | ||
|
|
fb9e37e71c | ||
|
|
cd7a741d53 | ||
|
|
5fdc90b4f9 | ||
|
|
e775840abb | ||
|
|
d7df54dfa1 | ||
|
|
646d165d37 | ||
|
|
ba0a80cca4 | ||
|
|
9d59858542 | ||
|
|
aaff0a5697 | ||
|
|
1bb7cee238 | ||
|
|
d330e662d3 | ||
|
|
b530ff76c4 | ||
|
|
e59a166445 | ||
|
|
980e713735 | ||
|
|
7a5228bbab | ||
|
|
5a26e5c2ee | ||
|
|
c2dccf17ce | ||
|
|
181d6bf1e4 | ||
|
|
a07be3b5dc | ||
|
|
af2666c83d | ||
|
|
cad9929b56 | ||
|
|
3b79da527d | ||
|
|
3eaba55823 | ||
|
|
e2eddb6362 | ||
|
|
d885570dd8 | ||
|
|
f3de883e39 | ||
|
|
6da22d23a8 | ||
|
|
49461a43bb | ||
|
|
d4414f99af | ||
|
|
52f37b0a37 | ||
|
|
ce949e4029 | ||
|
|
19d3465a13 | ||
|
|
a0e1425c74 | ||
|
|
a5d8379b18 | ||
|
|
9ac487ea5b | ||
|
|
9cc9a93c3d | ||
|
|
4f2b0313de | ||
|
|
806894b24b | ||
|
|
cbb737f96b | ||
|
|
5407f8156a | ||
|
|
a0c2fd09ae | ||
|
|
48b744c8ba | ||
|
|
812b946d85 | ||
|
|
27eca2b470 | ||
|
|
ab6c88bebd | ||
|
|
d547e0552a | ||
|
|
03fc0b84cb | ||
|
|
b0825634ee | ||
|
|
9675e051bd | ||
|
|
981654a2aa | ||
|
|
8c97ea0da0 | ||
|
|
6c80911c90 | ||
|
|
740b5ea719 | ||
|
|
76c958fb7a | ||
|
|
28c53fd115 | ||
|
|
6d9fc8b3df | ||
|
|
a044f4eceb | ||
|
|
1f35efcd51 | ||
|
|
c587abddae | ||
|
|
d7f4c9a329 | ||
|
|
8a3e6f5781 | ||
|
|
cfbebc4989 | ||
|
|
99ec772d64 | ||
|
|
1f60b0bbcb | ||
|
|
c8fb9a5b09 | ||
|
|
d6f117c7bf | ||
|
|
a765668354 | ||
|
|
da053658e8 | ||
|
|
cb5bcd72e4 | ||
|
|
d4d53623ef | ||
|
|
8a31b008d2 | ||
|
|
fa4541b59f | ||
|
|
aae6b32828 | ||
|
|
7158d6735b | ||
|
|
f4b896d5e6 | ||
|
|
d2f814e6f8 | ||
|
|
87cec67e60 | ||
|
|
041c46f38e | ||
|
|
54a7628f75 | ||
|
|
7cbbc16390 | ||
|
|
14532bf6db | ||
|
|
7bfe18e718 | ||
|
|
07903a07a8 | ||
|
|
14fe3b2b98 | ||
|
|
fa2756ff3f | ||
|
|
9d4841b4dc | ||
|
|
4b74b08825 | ||
|
|
6457cfc958 | ||
|
|
30081c893b | ||
|
|
e69890a47e | ||
|
|
9d31162eec | ||
|
|
0031cc7418 | ||
|
|
57132892b5 | ||
|
|
be084d1570 | ||
|
|
03d29a9ebd | ||
|
|
e9a837b534 | ||
|
|
36c601f9fa | ||
|
|
af41234484 | ||
|
|
bcaf03a770 | ||
|
|
9429b009ce | ||
|
|
3395785cbc | ||
|
|
97c048c7af | ||
|
|
726eb2ffe6 | ||
|
|
a9f22d3a10 | ||
|
|
5a6437e371 | ||
|
|
4ffc98b371 | ||
|
|
16c73d4f91 | ||
|
|
96aff5230f | ||
|
|
b298571be8 | ||
|
|
7b7b10c9a5 | ||
|
|
56beed5ad5 | ||
|
|
8c8ef0ea53 | ||
|
|
90377d98c0 | ||
|
|
d167e474d8 | ||
|
|
0cfacfe057 | ||
|
|
fbda5231d0 | ||
|
|
bd4d333d59 | ||
|
|
07878207de | ||
|
|
b2352877b9 | ||
|
|
669ba9e8cb | ||
|
|
5fa8a76219 | ||
|
|
476dcf051c | ||
|
|
db96dbba6a | ||
|
|
e9c6509970 | ||
|
|
b26c7dfa8f | ||
|
|
3f251761d6 | ||
|
|
bc9d489ee8 | ||
|
|
271563ba9f | ||
|
|
82aba20e83 | ||
|
|
12f60c9be5 | ||
|
|
1c98a354c7 | ||
|
|
9ca7aee859 | ||
|
|
937b306128 | ||
|
|
69a2ab0ce2 | ||
|
|
b720711217 | ||
|
|
bf56348c62 | ||
|
|
f993817cb6 | ||
|
|
1c2b4794f3 | ||
|
|
523d210503 | ||
|
|
39da8bd636 | ||
|
|
fbea98bbe6 | ||
|
|
5d89f724f4 | ||
|
|
0313b6f3ab | ||
|
|
fe92111dc9 | ||
|
|
9a03bfb9e7 | ||
|
|
882db6acc9 | ||
|
|
5596468b03 | ||
|
|
eb66de1220 | ||
|
|
f97896f6a6 | ||
|
|
cfb47f880e | ||
|
|
eba87776a0 | ||
|
|
dda0e6d676 | ||
|
|
8d514644c1 | ||
|
|
0870a34391 | ||
|
|
38118986a3 | ||
|
|
1b4e5247ad | ||
|
|
3ef2eeef93 | ||
|
|
4d5682e79b | ||
|
|
24100d203f | ||
|
|
86946a2c21 | ||
|
|
54f3e4cc6f | ||
|
|
d9360b2d8c | ||
|
|
abb47118c0 | ||
|
|
9e4e55bb8d | ||
|
|
498f720507 | ||
|
|
b9bc9cb75e | ||
|
|
3b65917422 | ||
|
|
862dd78cd5 | ||
|
|
d0bc48d119 | ||
|
|
37462ba282 | ||
|
|
99e7f5862a | ||
|
|
048e529bb5 | ||
|
|
5b8b06f3a1 | ||
|
|
3c7d025802 | ||
|
|
4f8b46da14 | ||
|
|
0b386a882e | ||
|
|
6329d58a67 | ||
|
|
af0d780ac0 | ||
|
|
45c2c863ef | ||
|
|
3372e16409 | ||
|
|
a04fad9c1f | ||
|
|
a8a89dc9bd | ||
|
|
a0305a745a | ||
|
|
f06b702360 | ||
|
|
6f104c4ee6 | ||
|
|
c721580516 | ||
|
|
1fe1381fe2 | ||
|
|
870a25dad1 | ||
|
|
e4e5d5c6d7 | ||
|
|
7e45f23b6c | ||
|
|
719f61f612 | ||
|
|
908edda14b |
+1
-1
@@ -3,7 +3,7 @@
|
||||
# Note that any data in staging will get cleared from time to time and you may have to sign up again and again
|
||||
|
||||
# Set it to the URL form where the app home page will be accessed
|
||||
PUBLIC_URL=http://localhost:3000
|
||||
PUBLIC_URL=https://localhost:3001
|
||||
# Set it to true if you want to generate the Sourcemap for debugging
|
||||
GENERATE_SOURCEMAP=false
|
||||
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
|
||||
|
||||
Generated
+532
-613
File diff suppressed because it is too large
Load Diff
+16
-15
@@ -4,16 +4,17 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@lottiefiles/dotlottie-react": "^0.8.7",
|
||||
"@lottiefiles/dotlottie-react": "^0.8.9",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.1.1",
|
||||
"@radix-ui/themes": "^3.1.3",
|
||||
"@react-pdf/renderer": "^3.4.4",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"axios": "^1.7.3",
|
||||
"axios": "^1.7.5",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^23.12.2",
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-http-backend": "^2.5.2",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"nth-check": "^2.1.1",
|
||||
@@ -36,10 +37,10 @@
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-pdf": "^9.1.0",
|
||||
"react-quill-new": "^3.2.2",
|
||||
"react-quill-new": "^3.3.0",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-rnd": "^10.4.11",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.8.0",
|
||||
@@ -52,7 +53,7 @@
|
||||
"regex-parser": "^2.3.0",
|
||||
"serve": "^14.2.3",
|
||||
"styled-components": "^5.3.0",
|
||||
"web-vitals": "^4.2.2",
|
||||
"web-vitals": "^4.2.3",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -94,10 +95,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@babel/runtime-corejs2": "^7.25.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"@babel/runtime-corejs2": "^7.25.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"babel-loader": "^9.1.3",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"commitizen": "^4.3.0",
|
||||
@@ -109,12 +110,12 @@
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
"lint-staged": "^15.2.8",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"postcss": "^8.4.41",
|
||||
"lint-staged": "^15.2.10",
|
||||
"mini-css-extract-plugin": "^2.9.1",
|
||||
"postcss": "^8.4.45",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-quick": "^4.0.0",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Title from "./Title";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { copytoData } from "../constant/Utils";
|
||||
import { copytoData, fetchSubscriptionInfo } from "../constant/Utils";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import { useTranslation } from "react-i18next";
|
||||
function generatePassword(length) {
|
||||
@@ -27,15 +27,64 @@ const AddUser = (props) => {
|
||||
password: "",
|
||||
role: ""
|
||||
});
|
||||
const [amount, setAmount] = useState({ quantity: 1, price: 0 });
|
||||
const [planInfo, setPlanInfo] = useState({
|
||||
priceperUser: 0,
|
||||
price: 0,
|
||||
totalAllowedUser: 0,
|
||||
adminId: ""
|
||||
});
|
||||
const [isFormLoader, setIsFormLoader] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [teamList, setTeamList] = useState([]);
|
||||
const [allowedUser, setAllowedUser] = useState(0);
|
||||
const [err, setErr] = useState("");
|
||||
const role = ["OrgAdmin", "Editor", "User"];
|
||||
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
useEffect(() => {
|
||||
getTeamList();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const getTeamList = async () => {
|
||||
if (isEnableSubscription) {
|
||||
try {
|
||||
setIsLoader(true);
|
||||
const resSub = await fetchSubscriptionInfo();
|
||||
if (!resSub.error) {
|
||||
setPlanInfo((prev) => ({
|
||||
...prev,
|
||||
priceperUser: resSub.price,
|
||||
totalAllowedUser: resSub.totalAllowedUser,
|
||||
adminId: resSub.adminId
|
||||
}));
|
||||
setAmount((prev) => ({ ...prev, price: resSub.price }));
|
||||
const res = await Parse.Cloud.run("allowedusers");
|
||||
if (props.setFormHeader) {
|
||||
if (res > 0) {
|
||||
props.setFormHeader(t("add-user"));
|
||||
} else {
|
||||
props.setFormHeader(
|
||||
resSub.adminId !== extUser?.objectId
|
||||
? "Unauthorized"
|
||||
: t("Add-seats")
|
||||
);
|
||||
}
|
||||
}
|
||||
setAllowedUser(res);
|
||||
} else {
|
||||
setAllowedUser(0);
|
||||
}
|
||||
} catch (err) {
|
||||
setAllowedUser(0);
|
||||
console.log("Err in alloweduser", err);
|
||||
setErr(err.message);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
|
||||
const teamRes = await Parse.Cloud.run("getteams", { active: true });
|
||||
if (teamRes.length > 0) {
|
||||
@@ -67,14 +116,12 @@ const AddUser = (props) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
setIsLoader(true);
|
||||
setIsFormLoader(true);
|
||||
const res = await checkUserExist();
|
||||
if (res) {
|
||||
props.setIsAlert({ type: "danger", msg: t("user-already-exist") });
|
||||
setIsLoader(false);
|
||||
setTimeout(() => {
|
||||
props.setIsAlert({ type: "success", msg: "" });
|
||||
}, 1000);
|
||||
setIsFormLoader(false);
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1000);
|
||||
} else {
|
||||
try {
|
||||
const extUser = new Parse.Object("contracts_Users");
|
||||
@@ -157,14 +204,8 @@ const AddUser = (props) => {
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
|
||||
setIsLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
setIsFormLoader(false);
|
||||
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
@@ -204,19 +245,13 @@ const AddUser = (props) => {
|
||||
}
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
setIsLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
setIsFormLoader(false);
|
||||
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
setIsLoader(false);
|
||||
setIsFormLoader(false);
|
||||
props.setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("something-went-wrong-mssg")
|
||||
@@ -229,13 +264,7 @@ const AddUser = (props) => {
|
||||
|
||||
// Define a function to handle the "add yourself" checkbox
|
||||
const handleReset = () => {
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
@@ -245,10 +274,7 @@ const AddUser = (props) => {
|
||||
if (name === "email") {
|
||||
value = value?.toLowerCase()?.replace(/\s/g, "");
|
||||
}
|
||||
setFormdata((prev) => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
setFormdata((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const copytoclipboard = (text) => {
|
||||
@@ -256,163 +282,271 @@ const AddUser = (props) => {
|
||||
props.setIsAlert({ type: "success", msg: t("copied") });
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
|
||||
};
|
||||
const handlePricePerUser = (e) => {
|
||||
const quantity = e.target.value;
|
||||
const price = e.target?.value > 0 ? planInfo.priceperUser * quantity : 0;
|
||||
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
|
||||
};
|
||||
const handleAddOnSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsFormLoader(true);
|
||||
try {
|
||||
const resAddon = await Parse.Cloud.run("buyaddonusers", {
|
||||
users: amount.quantity
|
||||
});
|
||||
if (resAddon) {
|
||||
const _resAddon = JSON.parse(JSON.stringify(resAddon));
|
||||
if (_resAddon.status === "success") {
|
||||
setAllowedUser(amount.quantity);
|
||||
setPlanInfo((obj) => ({ ...obj, totalAllowedUser: _resAddon.addon }));
|
||||
}
|
||||
if (props.handleBuyUsers) {
|
||||
props.handleBuyUsers(amount.quantity, _resAddon.addon);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in buy addon", err);
|
||||
props.setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
} finally {
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 2000);
|
||||
setIsFormLoader(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="shadow-md rounded-box my-[1px] p-3 bg-[#ffffff]">
|
||||
<Title title={"Add User"} />
|
||||
{isLoader && (
|
||||
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50 rounded-box">
|
||||
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
|
||||
<Title title={t("add-user")} />
|
||||
{isFormLoader && (
|
||||
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto">
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */}
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("name")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={formdata.name}
|
||||
onChange={(e) => handleChange(e)}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("email")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={formdata.email}
|
||||
onChange={(e) => handleChange(e)}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("password")}
|
||||
</label>
|
||||
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
|
||||
<div className="break-all">{formdata?.password}</div>
|
||||
<i
|
||||
onClick={() => copytoclipboard(formdata?.password)}
|
||||
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
|
||||
></i>
|
||||
{!isLoader ? (
|
||||
<>
|
||||
{err ? (
|
||||
<div className="flex justify-center items-center h-[70px] text-[18px]">
|
||||
{err}
|
||||
</div>
|
||||
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
|
||||
{t("password-generateed")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("phone")}
|
||||
{/* <span className="text-[red] text-[13px]"> *</span> */}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="phone"
|
||||
placeholder="optional"
|
||||
value={formdata.phone}
|
||||
onChange={(e) => handleChange(e)}
|
||||
// required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
{isEnableSubscription && (
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Team<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<select
|
||||
value={formdata.team}
|
||||
onChange={(e) => handleChange(e)}
|
||||
name="team"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
>
|
||||
<option defaultValue={""} value={""}>
|
||||
select
|
||||
</option>
|
||||
{teamList.length > 0 &&
|
||||
teamList.map((x) => (
|
||||
<option key={x.objectId} value={x.objectId}>
|
||||
{x.Name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<div className="w-full mx-auto">
|
||||
{!isEnableSubscription || allowedUser > 0 ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
{isEnableSubscription && (
|
||||
<p className="flex justify-center mb-1">
|
||||
{t("remaing-users")}{" "}
|
||||
<span
|
||||
className={`${
|
||||
allowedUser < 2 ? "op-text-accent" : "op-text-primary"
|
||||
} font-medium ml-1`}
|
||||
>
|
||||
{allowedUser} {t("of")} {planInfo.totalAllowedUser}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("name")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={formdata.name}
|
||||
onChange={(e) => handleChange(e)}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("email")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={formdata.email}
|
||||
onChange={(e) => handleChange(e)}
|
||||
required
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="block text-xs text-gray-700 font-semibold">
|
||||
{t("password")}
|
||||
</label>
|
||||
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
|
||||
<div className="break-all">{formdata?.password}</div>
|
||||
<i
|
||||
onClick={() => copytoclipboard(formdata?.password)}
|
||||
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
|
||||
></i>
|
||||
</div>
|
||||
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
|
||||
{t("password-generateed")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("phone")}
|
||||
{/* <span className="text-[red] text-[13px]"> *</span> */}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="phone"
|
||||
placeholder="optional"
|
||||
value={formdata.phone}
|
||||
onChange={(e) => handleChange(e)}
|
||||
// required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
{isEnableSubscription && (
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("team")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<select
|
||||
name="team"
|
||||
value={formdata.team}
|
||||
onChange={(e) => handleChange(e)}
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
>
|
||||
<option defaultValue={""} value={""}>
|
||||
{t("Select")}
|
||||
</option>
|
||||
{teamList.length > 0 &&
|
||||
teamList.map((x) => (
|
||||
<option key={x.objectId} value={x.objectId}>
|
||||
{x.Name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("Role")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<select
|
||||
value={formdata.role}
|
||||
onChange={(e) => handleChange(e)}
|
||||
name="role"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
>
|
||||
<option defaultValue={""} value={""}>
|
||||
{t("Select")}
|
||||
</option>
|
||||
{role.length > 0 &&
|
||||
role.map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("submit")}
|
||||
</button>
|
||||
<div
|
||||
type="button"
|
||||
onClick={() => handleReset()}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
{t("cancel")}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<>
|
||||
{planInfo.adminId !== extUser?.objectId ? (
|
||||
<div className="mb-3 mt-1 flex justify-center text-center items-center font-medium break-all">
|
||||
{t("unauthorized-modal", {
|
||||
adminEmail: extUser?.TenantId?.EmailAddress
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleAddOnSubmit}>
|
||||
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
|
||||
{t("additional-users")}
|
||||
</p>
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label
|
||||
htmlFor="quantity"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("Quantity-of-users")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="quantity"
|
||||
value={amount.quantity}
|
||||
onChange={(e) => handlePricePerUser(e)}
|
||||
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label className="block text-xs text-gray-700 font-semibold">
|
||||
{t("Price")} (1 * {planInfo.priceperUser})
|
||||
</label>
|
||||
<div className="w-1/4 flex justify-center items-center text-sm">
|
||||
USD {amount.price}
|
||||
</div>
|
||||
</div>
|
||||
<hr className="text-base-content mb-3" />
|
||||
<button className="op-btn op-btn-primary w-full">
|
||||
{t("Proceed")}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Role<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<select
|
||||
value={formdata.role}
|
||||
onChange={(e) => handleChange(e)}
|
||||
name="role"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
>
|
||||
<option defaultValue={""} value={""}>
|
||||
select
|
||||
</option>
|
||||
{role.length > 0 &&
|
||||
role.map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
<div
|
||||
type="button"
|
||||
onClick={() => handleReset()}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full h-[200px] inset-0 flex justify-center items-center z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,9 +3,14 @@ import axios from "axios";
|
||||
import SuggestionInput from "./shared/fields/SuggestionInput";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Parse from "parse";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import { fetchSubscription } from "../constant/Utils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
const BulkSendUi = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [forms, setForms] = useState([]);
|
||||
const [formId, setFormId] = useState(2);
|
||||
const formRef = useRef(null);
|
||||
@@ -13,6 +18,22 @@ const BulkSendUi = (props) => {
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
const [allowedForm, setAllowedForm] = useState(0);
|
||||
const [isSignatureExist, setIsSignatureExist] = useState();
|
||||
const [isBulkAvailable, setIsBulkAvailable] = useState(false);
|
||||
const quantityList = [500, 1000, 5000, 50000];
|
||||
const [amount, setAmount] = useState({
|
||||
price: (75.0).toFixed(2),
|
||||
quantity: 500,
|
||||
priceperbulksend: 0.15,
|
||||
totalcredits: 0
|
||||
});
|
||||
const [isQuotaReached, setIsQuotaReached] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [isFreePlan, setIsFreePlan] = useState(false);
|
||||
const [admin, setAdmin] = useState({
|
||||
objectId: "",
|
||||
email: "",
|
||||
isAdmin: true
|
||||
});
|
||||
const allowedSigners = 50;
|
||||
useEffect(() => {
|
||||
signatureExist();
|
||||
@@ -20,14 +41,71 @@ const BulkSendUi = (props) => {
|
||||
}, []);
|
||||
|
||||
//function to check atleast one signature field exist
|
||||
const signatureExist = () => {
|
||||
const getPlaceholder = props.item?.Placeholders;
|
||||
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((holder) =>
|
||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
||||
)
|
||||
);
|
||||
setIsSignatureExist(checkIsSignatureExistt);
|
||||
const signatureExist = async () => {
|
||||
if (isEnableSubscription) {
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const subscription = await fetchSubscription();
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (
|
||||
subscription?.adminId &&
|
||||
extUser?.objectId === subscription?.adminId
|
||||
) {
|
||||
setAdmin((obj) => ({
|
||||
...obj,
|
||||
objectId: subscription?.adminId,
|
||||
isAdmin: true
|
||||
}));
|
||||
} else {
|
||||
setAdmin((obj) => ({
|
||||
...obj,
|
||||
isAdmin: false,
|
||||
email: extUser?.TenantId?.EmailAddress
|
||||
}));
|
||||
}
|
||||
setAdmin((obj) => ({ ...obj, objectId: subscription?.adminId }));
|
||||
if (subscription?.plan === "freeplan") {
|
||||
setIsFreePlan(true);
|
||||
}
|
||||
const resCredits = await Parse.Cloud.run("allowedcredits");
|
||||
if (resCredits) {
|
||||
const allowedcredits = resCredits?.allowedcredits || 0;
|
||||
const addoncredits = resCredits?.addoncredits || 0;
|
||||
const totalcredits = allowedcredits + addoncredits;
|
||||
if (totalcredits > 0) {
|
||||
if (subscription?.plan !== "freeplan") {
|
||||
setIsBulkAvailable(true);
|
||||
}
|
||||
}
|
||||
setAmount((obj) => ({ ...obj, totalcredits: totalcredits }));
|
||||
}
|
||||
const getPlaceholder = props.item?.Placeholders;
|
||||
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((holder) =>
|
||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
||||
)
|
||||
);
|
||||
setIsSignatureExist(checkIsSignatureExistt);
|
||||
setIsLoader(false);
|
||||
} catch (err) {
|
||||
setIsLoader(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
console.log("Err", err);
|
||||
}
|
||||
} else {
|
||||
setIsBulkAvailable(true);
|
||||
setAdmin((obj) => ({ ...obj, isAdmin: true }));
|
||||
const getPlaceholder = props.item?.Placeholders;
|
||||
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((holder) =>
|
||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
||||
)
|
||||
);
|
||||
setIsSignatureExist(checkIsSignatureExistt);
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (scrollOnNextUpdate && formRef.current) {
|
||||
@@ -76,29 +154,33 @@ const BulkSendUi = (props) => {
|
||||
const handleAddForm = (e) => {
|
||||
e.preventDefault();
|
||||
// Check if the quick send limit has been reached
|
||||
if (forms?.length < allowedForm) {
|
||||
if (props?.Placeholders.length > 0) {
|
||||
let newForm = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
if (!element.signerObjId) {
|
||||
newForm = [
|
||||
...newForm,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setForms([...forms, { Id: formId, fields: newForm }]);
|
||||
}
|
||||
setFormId(formId + 1);
|
||||
setScrollOnNextUpdate(true);
|
||||
if (isEnableSubscription && forms.length >= amount.totalcredits) {
|
||||
setIsQuotaReached(true);
|
||||
} else {
|
||||
// If the limit has been reached, throw an error with the appropriate message
|
||||
alert(t("quick-send-alert-4"));
|
||||
if (forms?.length < allowedForm) {
|
||||
if (props?.Placeholders.length > 0) {
|
||||
let newForm = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
if (!element.signerObjId) {
|
||||
newForm = [
|
||||
...newForm,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setForms([...forms, { Id: formId, fields: newForm }]);
|
||||
}
|
||||
setFormId(formId + 1);
|
||||
setScrollOnNextUpdate(true);
|
||||
} else {
|
||||
// If the limit has been reached, throw an error with the appropriate message
|
||||
alert(t("quick-send-alert-4"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -162,13 +244,9 @@ const BulkSendUi = (props) => {
|
||||
: [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders
|
||||
});
|
||||
Documents.push({ ...props.item, Placeholders: updatedPlaceholders });
|
||||
}
|
||||
});
|
||||
//console.log("Documents ", Documents);
|
||||
await batchQuery(Documents);
|
||||
};
|
||||
|
||||
@@ -180,9 +258,7 @@ const BulkSendUi = (props) => {
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const params = {
|
||||
Documents: JSON.stringify(Documents)
|
||||
};
|
||||
const params = { Documents: JSON.stringify(Documents) };
|
||||
try {
|
||||
const res = await axios.post(functionsUrl, params, { headers: headers });
|
||||
// console.log("res ", res);
|
||||
@@ -196,83 +272,231 @@ const BulkSendUi = (props) => {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddOnQuickSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSubmit(true);
|
||||
try {
|
||||
const resAddon = await Parse.Cloud.run("buycredits", {
|
||||
credits: amount.quantity
|
||||
});
|
||||
if (resAddon) {
|
||||
const _resAddon = JSON.parse(JSON.stringify(resAddon));
|
||||
if (_resAddon.status === "success") {
|
||||
setIsBulkAvailable(true);
|
||||
handleCloseQuotaReached();
|
||||
setAmount((obj) => ({
|
||||
...obj,
|
||||
quantity: 500,
|
||||
priceperbulksend: 0.15,
|
||||
price: (75.0).toFixed(2),
|
||||
totalcredits: obj?.totalcredits + _resAddon.addon
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in buy addon", err);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
} finally {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
const handlePricePerQuick = async (e) => {
|
||||
const quantity = e.target?.value;
|
||||
const price =
|
||||
quantity > 0
|
||||
? (Math.round(quantity * amount.priceperbulksend * 100) / 100).toFixed(
|
||||
2
|
||||
)
|
||||
: 500 * amount.priceperbulksend;
|
||||
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
|
||||
};
|
||||
const handleCloseQuotaReached = () => {
|
||||
setIsQuotaReached(false);
|
||||
};
|
||||
const handleNavigation = () => {
|
||||
navigate("/subscription");
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{isSubmit && (
|
||||
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
|
||||
{isLoader ? (
|
||||
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{props.Placeholders?.length > 0 ? (
|
||||
isSignatureExist ? (
|
||||
<>
|
||||
{props.Placeholders?.some((x) => !x.signerObjId) ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="min-h-max max-h-[250px] overflow-y-auto">
|
||||
{forms?.map((form, index) => (
|
||||
<div
|
||||
key={form.Id}
|
||||
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
|
||||
>
|
||||
{form?.fields?.map((field, fieldIndex) => (
|
||||
<div className="flex flex-col" key={field.fieldId}>
|
||||
<label>{field.label}</label>
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.value}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(index, signer, fieldIndex)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{forms?.length > 1 && (
|
||||
<button
|
||||
onClick={() => handleRemoveForm(index)}
|
||||
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
|
||||
>
|
||||
<i className="fa-light fa-trash"></i>
|
||||
</button>
|
||||
)}
|
||||
<div ref={formRef}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col mx-4 mb-4 gap-3">
|
||||
<button
|
||||
onClick={handleAddForm}
|
||||
className="op-btn op-btn-primary focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-plus"></i>{" "}
|
||||
<span>{t("add-new")}</span>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-secondary focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-paper-plane"></i>{" "}
|
||||
<span>{t("send")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-1")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-2")}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-3")}
|
||||
</div>
|
||||
<>
|
||||
{isSubmit && (
|
||||
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{isBulkAvailable ? (
|
||||
<>
|
||||
{props.Placeholders?.length > 0 ? (
|
||||
isSignatureExist ? (
|
||||
<>
|
||||
{props.Placeholders?.some((x) => !x.signerObjId) ? (
|
||||
<div>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="min-h-max max-h-[250px] overflow-y-auto">
|
||||
{forms?.map((form, index) => (
|
||||
<div
|
||||
key={form.Id}
|
||||
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
|
||||
>
|
||||
{form?.fields?.map((field, fieldIndex) => (
|
||||
<div
|
||||
className="flex flex-col"
|
||||
key={field.fieldId}
|
||||
>
|
||||
<label>{field.label}</label>
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.value}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(
|
||||
index,
|
||||
signer,
|
||||
fieldIndex
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{forms?.length > 1 && (
|
||||
<button
|
||||
onClick={() => handleRemoveForm(index)}
|
||||
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
|
||||
>
|
||||
<i className="fa-light fa-trash"></i>
|
||||
</button>
|
||||
)}
|
||||
<div ref={formRef}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col mx-4 mb-4 gap-3">
|
||||
{isEnableSubscription && (
|
||||
<button
|
||||
onClick={handleAddForm}
|
||||
className="op-btn op-btn-primary focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-plus"></i>{" "}
|
||||
<span>{t("add-new")}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-accent focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-paper-plane"></i>{" "}
|
||||
<span>{t("send")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<ModalUi
|
||||
isOpen={isQuotaReached}
|
||||
handleClose={() => handleCloseQuotaReached()}
|
||||
>
|
||||
<div className="p-4 flex justify-center items-center flex-col gap-y-3">
|
||||
<p className="text-center text-base-content">
|
||||
{t("quota-err-quicksend")}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsBulkAvailable(false)}
|
||||
className=" op-btn op-btn-primary w-[200px]"
|
||||
>
|
||||
{t("buy-credits")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-1")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-2")}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-3")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{isFreePlan ? (
|
||||
<div className="w-full h-[130px] flex flex-col justify-center items-center text-center p-4">
|
||||
<p className="text-base font-medium mb-2.5">
|
||||
{t("bulk-send-subcription-alert")}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => handleNavigation()}
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("upgrade-now")}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{admin?.isAdmin ? (
|
||||
<form onSubmit={handleAddOnQuickSubmit} className="p-3">
|
||||
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
|
||||
{t("additional-credits")}
|
||||
</p>
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label
|
||||
htmlFor="quantity"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("quantity-of-credits")}
|
||||
<span className="text-[red] text-[13px]">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={amount.quantity}
|
||||
onChange={(e) => handlePricePerQuick(e)}
|
||||
name="quantity"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs"
|
||||
required
|
||||
>
|
||||
{quantityList.length > 0 &&
|
||||
quantityList.map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label className="block text-xs text-gray-700 font-semibold">
|
||||
{t("Price")} (1 * {amount.priceperbulksend})
|
||||
</label>
|
||||
<div className="w-1/4 flex justify-center items-center text-sm">
|
||||
USD {amount.price}
|
||||
</div>
|
||||
</div>
|
||||
<hr className="text-base-content mb-3" />
|
||||
<button className="op-btn op-btn-primary w-full mt-2">
|
||||
{t("Proceed")}
|
||||
</button>
|
||||
</form>
|
||||
) : (
|
||||
<div className="mx-8 mt-4 mb-8 flex justify-center text-center items-center font-medium break-all">
|
||||
{t("unauthorized-modal", { adminEmail: admin?.email })}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -6,30 +6,42 @@ import Parse from "parse";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
import {
|
||||
checkIsSubscribed,
|
||||
checkIsSubscribedTeam,
|
||||
getAppLogo,
|
||||
openInNewTab,
|
||||
saveLanguageInLocal
|
||||
} from "../constant/Utils";
|
||||
import { isEnableSubscription, isStaging } from "../constant/const";
|
||||
import { validplan } from "../json/plansArr";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import QuotaCard from "../primitives/QuotaCard";
|
||||
|
||||
const Header = ({ showSidebar }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { width } = useWindowSize();
|
||||
let username = localStorage.getItem("username");
|
||||
const current_notification_version = "1"; // Update this with each new feature release
|
||||
// Get the dismissed version from local storage
|
||||
const dismissedVersion = localStorage.getItem("notificationDismissedVersion");
|
||||
const username = localStorage.getItem("username") || "";
|
||||
const image = localStorage.getItem("profileImg") || dp;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(true);
|
||||
const [isPro, setIsPro] = useState(false);
|
||||
const [isTeam, setIsTeam] = useState(false);
|
||||
const [isTeam, setIsTeam] = useState({ plan: "", isValid: false });
|
||||
const [applogo, setAppLogo] = useState(
|
||||
localStorage.getItem("appLogo") || " "
|
||||
);
|
||||
|
||||
const [emailUsed, setEmailUsed] = useState(0);
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const [showNotification, setShowNotification] = useState(
|
||||
(!dismissedVersion || dismissedVersion !== current_notification_version) &&
|
||||
true
|
||||
);
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen(!isOpen);
|
||||
if (width <= 768) {
|
||||
showSidebar();
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
checkSubscription();
|
||||
@@ -37,9 +49,8 @@ const Header = ({ showSidebar }) => {
|
||||
}, []);
|
||||
async function checkSubscription() {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
const getIsTeam = await checkIsSubscribedTeam();
|
||||
if (getIsSubscribe) {
|
||||
const subscribe = await checkIsSubscribed();
|
||||
if (subscribe.isValid) {
|
||||
const applogo = await getAppLogo();
|
||||
if (applogo?.logo) {
|
||||
setAppLogo(applogo?.logo);
|
||||
@@ -47,20 +58,31 @@ const Header = ({ showSidebar }) => {
|
||||
setAppLogo(localStorage.getItem("appLogo") || "");
|
||||
}
|
||||
}
|
||||
setIsPro(getIsSubscribe);
|
||||
setIsTeam(getIsTeam);
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
setIsTeam(subscribe);
|
||||
setIsSubscribe(subscribe.isValid);
|
||||
try {
|
||||
const extUser = await Parse.Cloud.run("getUserDetails");
|
||||
const MonthlyFreeEmails = extUser?.get("MonthlyFreeEmails") || 0;
|
||||
setEmailUsed(MonthlyFreeEmails);
|
||||
} catch (err) {
|
||||
console.log("err in while fetching monthlyfreeEmails", err);
|
||||
}
|
||||
} else {
|
||||
const applogo = await getAppLogo();
|
||||
if (applogo?.logo) {
|
||||
setAppLogo(applogo?.logo);
|
||||
} else {
|
||||
setAppLogo(localStorage.getItem("appLogo") || "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const closeDropdown = () => {
|
||||
const closeDropdown = async () => {
|
||||
setIsOpen(false);
|
||||
if (Parse?.User?.current()) {
|
||||
try {
|
||||
Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err", err);
|
||||
}
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
@@ -103,129 +125,174 @@ const Header = ({ showSidebar }) => {
|
||||
window.open("https://console.opensignlabs.com/");
|
||||
}
|
||||
};
|
||||
const handleNavigation = () => {
|
||||
navigate("/subscription");
|
||||
};
|
||||
const handleMailUsed = () => {
|
||||
setIsModal(!isModal);
|
||||
};
|
||||
// Handle dismissing the notification
|
||||
const dismissNotification = () => {
|
||||
// Store the current version as dismissed in local storage
|
||||
localStorage.setItem(
|
||||
"notificationDismissedVersion",
|
||||
current_notification_version
|
||||
);
|
||||
setShowNotification(false);
|
||||
};
|
||||
return (
|
||||
<div className="op-navbar bg-base-100 shadow">
|
||||
<div className="flex-none">
|
||||
<button
|
||||
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
|
||||
onClick={showSidebar}
|
||||
>
|
||||
<i className="fa-light fa-bars text-xl text-base-content"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 ml-2">
|
||||
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
||||
<img
|
||||
className="object-contain h-full w-auto"
|
||||
src={applogo}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="profile-menu" className="flex-none gap-2">
|
||||
{!isSubscribe && (
|
||||
<div>
|
||||
<button
|
||||
className="text-xs md:text-sm shadow op-btn op-btn-outline op-btn-sm md:op-btn-md op-btn-accent"
|
||||
onClick={() => navigate("/subscription")}
|
||||
<div>
|
||||
{isEnableSubscription && showNotification && (
|
||||
<div className="flex justify-between items-center shadow py-1 bg-[#CAE4FA] p-1">
|
||||
<div className="text-center text-[14px] ml-auto">
|
||||
{t("header-news")} —
|
||||
<span
|
||||
className="cursor-pointer font-medium underline text-blue-800"
|
||||
onClick={() => navigate("/profile")}
|
||||
>
|
||||
{t("upgrade-now")}
|
||||
</button>
|
||||
{" " + t("header-news-btn") + "."}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!isTeam && isPro && (
|
||||
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
{t("pro")}
|
||||
</div>
|
||||
)}
|
||||
{isTeam && (
|
||||
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] text-[13px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
{t("team")}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
{width >= 768 && (
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
|
||||
className="ml-auto mr-1 cursor-pointer"
|
||||
onClick={() => dismissNotification()}
|
||||
>
|
||||
<i className="fa-light fa-xmark"></i>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="op-navbar bg-base-100 shadow">
|
||||
<div className="flex-none">
|
||||
<button
|
||||
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
|
||||
onClick={showSidebar}
|
||||
>
|
||||
<i className="fa-light fa-bars text-xl text-base-content"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 ml-2">
|
||||
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
||||
<img
|
||||
className="w-[35px] h-[35px] object-contain"
|
||||
src={image}
|
||||
className="object-contain h-full w-auto"
|
||||
src={applogo}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{width >= 768 && (
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
className="cursor-pointer text-base-content text-sm"
|
||||
>
|
||||
{username && username}
|
||||
</div>
|
||||
<div id="profile-menu" className="flex-none gap-2">
|
||||
{!isSubscribe && (
|
||||
<div>
|
||||
<button
|
||||
className="text-xs md:text-sm shadow op-btn op-btn-outline op-btn-sm md:op-btn-md op-btn-accent"
|
||||
onClick={() => handleNavigation()}
|
||||
>
|
||||
{t("upgrade-now")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{isTeam.isValid && !validplan[isTeam.plan] && (
|
||||
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
{t("pro")}
|
||||
</div>
|
||||
)}
|
||||
{validplan[isTeam.plan] && (
|
||||
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] text-[13px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
{t("team")}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
)}
|
||||
<div className="op-dropdown op-dropdown-end" id="profile-menu">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
|
||||
>
|
||||
<i
|
||||
{width >= 768 && (
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
|
||||
>
|
||||
<img
|
||||
className="w-[35px] h-[35px] object-contain"
|
||||
src={image}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{width >= 768 && (
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
className="cursor-pointer text-base-content text-sm"
|
||||
>
|
||||
{username && username}
|
||||
</div>
|
||||
)}
|
||||
<div className="op-dropdown op-dropdown-end" id="profile-menu">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={toggleDropdown}
|
||||
className="fa-light fa-angle-down text-base-content"
|
||||
></i>
|
||||
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
|
||||
>
|
||||
<i
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={toggleDropdown}
|
||||
className="fa-light fa-angle-down text-base-content"
|
||||
></i>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52"
|
||||
>
|
||||
<li onClick={() => openInNewTab("https://docs.opensignlabs.com")}>
|
||||
<span>
|
||||
<i className="fa-light fa-book"></i> {t("docs")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/profile");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-user"></i> {t("profile")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/changepassword");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-lock"></i> {t("change-password")}
|
||||
</span>
|
||||
</li>
|
||||
{isEnableSubscription && (
|
||||
<li onClick={() => handleConsoleRedirect()}>
|
||||
<span>
|
||||
<i className="fa-light fa-id-card"></i> Console
|
||||
</span>
|
||||
</li>
|
||||
)}
|
||||
{isEnableSubscription && isTeam?.plan === "freeplan" && (
|
||||
<li className="cursor-pointer" onClick={handleMailUsed}>
|
||||
<span>
|
||||
<i className="fa-light fa-envelope"></i>
|
||||
{emailUsed}/15 {t("sent-this-month")}
|
||||
</span>
|
||||
</li>
|
||||
)}
|
||||
<li onClick={closeDropdown}>
|
||||
<span>
|
||||
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
|
||||
{t("log-out")}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52"
|
||||
>
|
||||
<li onClick={() => openInNewTab("https://docs.opensignlabs.com")}>
|
||||
<span>
|
||||
<i className="fa-light fa-book"></i> {t("docs")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/profile");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-user"></i> {t("profile")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/changepassword");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-lock"></i> {t("change-password")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
handleConsoleRedirect();
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-id-card"></i> Console
|
||||
</span>
|
||||
</li>
|
||||
<li onClick={closeDropdown}>
|
||||
<span>
|
||||
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
|
||||
{t("log-out")}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<ModalUi isOpen={isModal}>
|
||||
<QuotaCard isPaidInfo={true} handlClose={handleMailUsed} />
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -47,8 +47,10 @@ const GoogleSignInBtn = ({
|
||||
});
|
||||
});
|
||||
const clearStorage = async () => {
|
||||
if (Parse.User.current()) {
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
let baseUrl = localStorage.getItem("baseUrl");
|
||||
let appid = localStorage.getItem("parseAppId");
|
||||
@@ -160,10 +162,13 @@ const GoogleSignInBtn = ({
|
||||
alert(t("fill-required-details!"));
|
||||
}
|
||||
};
|
||||
const handleCloseModal = () => {
|
||||
const handleCloseModal = async () => {
|
||||
setIsModal(false);
|
||||
Parse.User.logOut();
|
||||
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function RotateAlert(props) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ModalUi
|
||||
isOpen={props.isRotate}
|
||||
title={t("Rotation-alert")}
|
||||
handleClose={() => props.setIsRotate({ status: false, degree: 0 })}
|
||||
>
|
||||
{" "}
|
||||
<div className="p-[20px] h-full">
|
||||
<p>{t("rotate-alert-mssg")}</p>
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => props.setIsRotate({ status: false, degree: 0 })}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost shadow-md"
|
||||
>
|
||||
{t("no")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => props.handleRemoveWidgets()}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
|
||||
export default RotateAlert;
|
||||
@@ -357,7 +357,7 @@ function DriveBody(props) {
|
||||
e.stopPropagation();
|
||||
handleMenuItemClick("Download", data);
|
||||
}}
|
||||
className="fa-light fa-download mr-[8px] op-text-primary"
|
||||
className="fa-light fa-download mr-[8px] op-text-primary cursor-pointer"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</td>
|
||||
@@ -370,7 +370,11 @@ function DriveBody(props) {
|
||||
{/* folder */}
|
||||
<div
|
||||
data-tut={props.dataTutSeventh}
|
||||
onClick={() => handleOnclikFolder(data)}
|
||||
onClick={() => {
|
||||
if (!rename) {
|
||||
handleOnclikFolder(data);
|
||||
}
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
@@ -383,7 +387,6 @@ function DriveBody(props) {
|
||||
{rename === data.objectId ? (
|
||||
<input
|
||||
onFocus={() => {
|
||||
inputRef.current.setSelectionRange(0, 0);
|
||||
const input = inputRef.current;
|
||||
if (input) {
|
||||
input.select();
|
||||
@@ -395,9 +398,8 @@ function DriveBody(props) {
|
||||
onKeyDown={(e) => handleEnterPress(e, data)}
|
||||
ref={inputRef}
|
||||
defaultValue={renameValue}
|
||||
// value={renameValue}
|
||||
onChange={(e) => setRenameValue(e.target.value)}
|
||||
className="w-[100px] border-[1.5px] border-black rounded-sm text-[10px]"
|
||||
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
|
||||
/>
|
||||
) : (
|
||||
<span className="fileName">{data.Name}</span>
|
||||
@@ -423,7 +425,11 @@ function DriveBody(props) {
|
||||
</ContextMenu.Root>
|
||||
</div>
|
||||
) : (
|
||||
<HoverCard.Root openDelay={0} closeDelay={100}>
|
||||
<HoverCard.Root
|
||||
open={rename ? false : undefined}
|
||||
openDelay={0}
|
||||
closeDelay={100}
|
||||
>
|
||||
<HoverCard.Trigger asChild>
|
||||
<div>
|
||||
<ContextMenu.Root>
|
||||
@@ -432,7 +438,11 @@ function DriveBody(props) {
|
||||
{/* pdf */}
|
||||
<div
|
||||
data-tut={props.dataTutSixth}
|
||||
onClick={() => checkPdfStatus(data)}
|
||||
onClick={() => {
|
||||
if (!rename) {
|
||||
checkPdfStatus(data);
|
||||
}
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
@@ -447,7 +457,6 @@ function DriveBody(props) {
|
||||
autoFocus={true}
|
||||
type="text"
|
||||
onFocus={() => {
|
||||
inputRef.current.setSelectionRange(0, 0);
|
||||
const input = inputRef.current;
|
||||
if (input) {
|
||||
input.select();
|
||||
@@ -457,9 +466,8 @@ function DriveBody(props) {
|
||||
onKeyDown={(e) => handleEnterPress(e, data)}
|
||||
ref={inputRef}
|
||||
defaultValue={renameValue}
|
||||
// value={renameValue}
|
||||
onChange={(e) => setRenameValue(e.target.value)}
|
||||
className="w-[100px] border-[1.5px] border-black rounded-sm text-[10px]"
|
||||
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
|
||||
/>
|
||||
) : (
|
||||
<span className="fileName">{data.Name}</span>
|
||||
|
||||
@@ -54,7 +54,7 @@ function DraftDocument() {
|
||||
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||
}
|
||||
//checking if document has completed and signyour-self flow
|
||||
else if (!signerExist && !isPlaceholder) {
|
||||
else if ((!signerExist && !isPlaceholder) || data?.IsSignyourself) {
|
||||
navigate(`/signaturePdf/${data.objectId}`);
|
||||
}
|
||||
//checking if document has declined by someone
|
||||
|
||||
@@ -4,6 +4,8 @@ import ModalUi from "../../primitives/ModalUi";
|
||||
import { radioButtonWidget } from "../../constant/Utils";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
|
||||
function DropdownWidgetOption(props) {
|
||||
const { t } = useTranslation();
|
||||
const [dropdownOptionList, setDropdownOptionList] = useState([
|
||||
@@ -330,6 +332,55 @@ function DropdownWidgetOption(props) {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex items-center mt-3 mb-3">
|
||||
<span>{t("font-size")} :</span>
|
||||
<select
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={
|
||||
props.fontSize ||
|
||||
props.currWidgetsDetails?.options?.fontSize ||
|
||||
"12"
|
||||
}
|
||||
onChange={(e) => props.setFontSize(e.target.value)}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<div className="flex flex-row gap-1 items-center ml-4 ">
|
||||
<span>{t("color")} : </span>
|
||||
<select
|
||||
value={
|
||||
props.fontColor ||
|
||||
props.currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
onChange={(e) => props.setFontColor(e.target.value)}
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
>
|
||||
{fontColorArr.map((color, ind) => {
|
||||
return (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<span
|
||||
style={{
|
||||
background:
|
||||
props.fontColor ||
|
||||
props.currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}}
|
||||
className="w-5 h-[19px] ml-1"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
{["checkbox", radioButtonWidget].includes(props.type) && (
|
||||
<div className="flex flex-row gap-5 my-2 items-center text-center">
|
||||
{!props.isSignYourself && (
|
||||
@@ -362,6 +413,7 @@ function DropdownWidgetOption(props) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`${
|
||||
props.type === "checkbox" && !props.isSignYourself
|
||||
|
||||
@@ -3,6 +3,8 @@ import { checkIsSubscribed, getFileName } from "../../constant/Utils";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
// import SelectFolder from "../../premitives/SelectFolder";
|
||||
|
||||
const EditTemplate = ({ template, onSuccess }) => {
|
||||
@@ -14,7 +16,8 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
Description: template?.Description || "",
|
||||
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
|
||||
AutomaticReminders: template?.AutomaticReminders || false,
|
||||
RemindOnceInEvery: template?.RemindOnceInEvery || 5
|
||||
RemindOnceInEvery: template?.RemindOnceInEvery || 5,
|
||||
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false"
|
||||
});
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
useEffect(() => {
|
||||
@@ -23,8 +26,8 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
}, []);
|
||||
const fetchSubscription = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe.isValid);
|
||||
}
|
||||
};
|
||||
const handleStrInput = (e) => {
|
||||
@@ -41,7 +44,7 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
e.stopPropagation();
|
||||
const isChecked = formData.SendinOrder === "true" ? true : false;
|
||||
const AutoReminder = formData?.AutomaticReminders || false;
|
||||
|
||||
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
|
||||
let reminderDate = {};
|
||||
if (AutoReminder) {
|
||||
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
|
||||
@@ -49,7 +52,12 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
|
||||
reminderDate = { NextReminderDate: ReminderDate };
|
||||
}
|
||||
const data = { ...formData, SendinOrder: isChecked, ...reminderDate };
|
||||
const data = {
|
||||
...formData,
|
||||
SendinOrder: isChecked,
|
||||
IsEnableOTP: IsEnableOTP,
|
||||
...reminderDate
|
||||
};
|
||||
onSuccess(data);
|
||||
};
|
||||
const handleAutoReminder = () => {
|
||||
@@ -184,6 +192,70 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
/>
|
||||
</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">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("submit")}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import axios from "axios";
|
||||
import { handleDownloadPdf, handleToPrint } from "../../constant/Utils";
|
||||
import { handleToPrint } from "../../constant/Utils";
|
||||
import { themeColor, emailRegex } from "../../constant/const";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
@@ -15,7 +15,8 @@ function EmailComponent({
|
||||
sender,
|
||||
setIsAlert,
|
||||
extUserId,
|
||||
activeMailAdapter
|
||||
activeMailAdapter,
|
||||
setIsDownloadModal
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [emailList, setEmailList] = useState([]);
|
||||
@@ -34,7 +35,7 @@ function EmailComponent({
|
||||
const imgPng =
|
||||
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
|
||||
|
||||
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3/`;
|
||||
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
@@ -74,26 +75,15 @@ function EmailComponent({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (sendMail && sendMail.data.result.status === "success") {
|
||||
if (sendMail?.data?.result?.status === "success") {
|
||||
setSuccessEmail(true);
|
||||
setIsEmail(false);
|
||||
setTimeout(() => {
|
||||
setSuccessEmail(false);
|
||||
setIsEmail(false);
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
}, 1500);
|
||||
|
||||
setIsLoading(false);
|
||||
} else if (sendMail && sendMail.data.result.status === "error") {
|
||||
setIsLoading(false);
|
||||
setIsEmail(false);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setIsEmail(false);
|
||||
@@ -140,6 +130,11 @@ function EmailComponent({
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleClose = () => {
|
||||
setIsEmail(false);
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{/* isEmail */}
|
||||
@@ -165,7 +160,9 @@ function EmailComponent({
|
||||
<div className="flex flex-row">
|
||||
{!isAndroid && (
|
||||
<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]"
|
||||
>
|
||||
<i className="fa-light fa-print" aria-hidden="true"></i>
|
||||
@@ -174,9 +171,10 @@ function EmailComponent({
|
||||
)}
|
||||
<button
|
||||
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
onClick={() => {
|
||||
handleClose();
|
||||
setIsDownloadModal(true);
|
||||
}}
|
||||
>
|
||||
<i className="fa-light fa-download" aria-hidden="true"></i>
|
||||
{t("download")}
|
||||
@@ -272,11 +270,7 @@ function EmailComponent({
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-2"
|
||||
onClick={() => {
|
||||
setIsEmail(false);
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
}}
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
|
||||
@@ -34,7 +34,13 @@ function Header({
|
||||
setIsEmail,
|
||||
completeBtnTitle,
|
||||
setIsEditTemplate,
|
||||
isPublicTemplate
|
||||
isPublicTemplate,
|
||||
clickOnZoomIn,
|
||||
clickOnZoomOut,
|
||||
handleRotationFun,
|
||||
isDisableRotate,
|
||||
templateId,
|
||||
setIsDownloadModal
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const filterPrefill =
|
||||
@@ -48,7 +54,6 @@ function Header({
|
||||
const currentDecline = { currnt: "Sure", isDeclined: true };
|
||||
setIsDecline(currentDecline);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex py-[5px]">
|
||||
{isMobile && isShowHeader ? (
|
||||
@@ -73,7 +78,7 @@ function Header({
|
||||
allPages={allPages}
|
||||
changePage={changePage}
|
||||
/>
|
||||
{pdfUrl && alreadySign ? (
|
||||
{isCompleted || alreadySign ? (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div className="op-link op-link-primary no-underline text-[16px] font-semibold pr-[3px] pl-[5px]">
|
||||
@@ -90,9 +95,13 @@ function Header({
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
onClick={() => {
|
||||
if (isCompleted) {
|
||||
setIsDownloadModal(true);
|
||||
} else {
|
||||
handleDownloadPdf(pdfDetails, setIsDownloading);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i
|
||||
@@ -138,7 +147,7 @@ function Header({
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, pdfUrl, setIsDownloading)
|
||||
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
|
||||
}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
@@ -184,13 +193,7 @@ function Header({
|
||||
) : (
|
||||
<div
|
||||
data-tut="reactourThird"
|
||||
onClick={() => {
|
||||
if (!pdfUrl) {
|
||||
embedWidgetsData();
|
||||
} else if (isPdfRequestFiles) {
|
||||
embedWidgetsData();
|
||||
}
|
||||
}}
|
||||
onClick={() => embedWidgetsData()}
|
||||
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
|
||||
>
|
||||
{t("finish")}
|
||||
@@ -207,30 +210,86 @@ function Header({
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="bg-white shadow-md rounded-full px-3 py-2"
|
||||
className="bg-white shadow-md rounded-md px-3 py-2"
|
||||
sideOffset={5}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
|
||||
className="DropdownMenuItem"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(
|
||||
pdfDetails,
|
||||
pdfUrl,
|
||||
setIsDownloading
|
||||
)
|
||||
handleDownloadPdf(pdfDetails, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-arrow-down mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="font-[500]">{t("download")}</span>
|
||||
<div className="flex flex-row">
|
||||
<i
|
||||
className="fa-light fa-arrow-down mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="font-[500]">
|
||||
{t("download")}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
{!isDisableRotate && (
|
||||
<>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => handleRotationFun(90)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">
|
||||
{t("rotate-right")}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => handleRotationFun(-90)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">
|
||||
{t("rotate-left")}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => clickOnZoomIn()}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">{t("zoom-in")}</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => clickOnZoomOut()}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">
|
||||
{t("zoom-out")}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
)}
|
||||
{isPublicTemplate && (
|
||||
<div
|
||||
data-tut="reactourThird"
|
||||
onClick={() => embedWidgetsData()}
|
||||
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
|
||||
>
|
||||
{t("sign-now")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -298,7 +357,9 @@ function Header({
|
||||
alreadySign ? (
|
||||
<div className="flex flex-row">
|
||||
<button
|
||||
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
|
||||
}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
|
||||
>
|
||||
@@ -326,9 +387,13 @@ function Header({
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
onClick={() => {
|
||||
if (isCompleted) {
|
||||
setIsDownloadModal(true);
|
||||
} else {
|
||||
handleDownloadPdf(pdfDetails, setIsDownloading);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-download py-[3px]"
|
||||
@@ -339,15 +404,32 @@ function Header({
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex" data-tut="reactourFifth">
|
||||
<button
|
||||
onClick={() => window.history.go(-2)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
>
|
||||
{t("back")}
|
||||
</button>
|
||||
{!templateId && (
|
||||
<button
|
||||
onClick={() => window.history.go(-2)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
>
|
||||
{t("back")}
|
||||
</button>
|
||||
)}
|
||||
{currentSigner && (
|
||||
<>
|
||||
{templateId && (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleDownloadPdf(
|
||||
pdfDetails,
|
||||
pdfUrl,
|
||||
setIsDownloading
|
||||
)
|
||||
}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
>
|
||||
<span className="hidden lg:block">{t("download")}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() => handleDeclinePdfAlert()}
|
||||
@@ -361,16 +443,18 @@ function Header({
|
||||
>
|
||||
{t("finish")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
|
||||
<span className="hidden lg:block">{t("download")}</span>
|
||||
</button>
|
||||
{!templateId && (
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
|
||||
<span className="hidden lg:block">{t("download")}</span>
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -392,7 +476,9 @@ function Header({
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
|
||||
}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
>
|
||||
@@ -402,9 +488,9 @@ function Header({
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
onClick={() => {
|
||||
setIsDownloadModal(true);
|
||||
}}
|
||||
>
|
||||
<i className="fa-light fa-download" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">{t("download")}</span>
|
||||
@@ -423,9 +509,7 @@ function Header({
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm shadow"
|
||||
onClick={() => {
|
||||
embedWidgetsData();
|
||||
}}
|
||||
onClick={() => embedWidgetsData()}
|
||||
>
|
||||
{t("sign-now")}
|
||||
</button>
|
||||
@@ -442,11 +526,7 @@ function Header({
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
|
||||
onClick={() => {
|
||||
if (!pdfUrl) {
|
||||
embedWidgetsData();
|
||||
}
|
||||
}}
|
||||
onClick={() => embedWidgetsData()}
|
||||
>
|
||||
{t("finish")}
|
||||
</button>
|
||||
|
||||
@@ -3,44 +3,37 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
function PdfZoom(props) {
|
||||
const { t } = useTranslation();
|
||||
const onClickZoomIn = () => {
|
||||
props.setScale(props.scale + 0.1 * props.scale);
|
||||
props.setZoomPercent(props.zoomPercent + 10);
|
||||
};
|
||||
const onClickZoomOut = () => {
|
||||
if (props.zoomPercent > 0) {
|
||||
if (props.zoomPercent === 10) {
|
||||
props.setScale(1);
|
||||
} else {
|
||||
props.setScale(props.scale - 0.1 * props.scale);
|
||||
}
|
||||
props.setZoomPercent(props.zoomPercent - 10);
|
||||
}
|
||||
};
|
||||
const handleReset = () => {
|
||||
props.setScale(1);
|
||||
props.setZoomPercent(0);
|
||||
};
|
||||
|
||||
return (
|
||||
// md:mt-[41px] xl:mt-[63px]mt=[]
|
||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[50px] 2xl:mt-[63px]">
|
||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[43px] 2xl:mt-[63px]">
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={onClickZoomIn}
|
||||
onClick={() => props.clickOnZoomIn()}
|
||||
title={t("zoom-in")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={handleReset}
|
||||
title={t("reset")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-arrows-rotate text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
{!props?.isDisableRotate && (
|
||||
<>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => props.handleRotationFun(90)}
|
||||
title={t("rotate-right")}
|
||||
>
|
||||
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
title={t("rotate-left")}
|
||||
onClick={() => props.handleRotationFun(-90)}
|
||||
>
|
||||
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
|
||||
onClick={onClickZoomOut}
|
||||
onClick={() => props.clickOnZoomOut()}
|
||||
style={{
|
||||
cursor: props.zoomPercent > 0 ? "pointer" : "default"
|
||||
}}
|
||||
|
||||
@@ -4,6 +4,8 @@ import PlaceholderBorder from "./PlaceholderBorder";
|
||||
import { Rnd } from "react-rnd";
|
||||
import {
|
||||
defaultWidthHeight,
|
||||
fontColorArr,
|
||||
fontsizeArr,
|
||||
getContainerScale,
|
||||
handleCopyNextToWidget,
|
||||
isTabAndMobile,
|
||||
@@ -15,6 +17,8 @@ import {
|
||||
import PlaceholderType from "./PlaceholderType";
|
||||
import moment from "moment";
|
||||
import "../../styles/opensigndrive.css";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const selectFormat = (data) => {
|
||||
switch (data) {
|
||||
@@ -89,9 +93,10 @@ const getDefaultdate = (selectedDate, format = "dd-MM-yyyy") => {
|
||||
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
||||
|
||||
function Placeholder(props) {
|
||||
const { t } = useTranslation();
|
||||
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
|
||||
const [isDraggingEnabled, setDraggingEnabled] = useState(true);
|
||||
const [isShowDateFormat, setIsShowDateFormat] = useState(false);
|
||||
const [isDateModal, setIsDateModal] = useState(false);
|
||||
const [containerScale, setContainerScale] = useState();
|
||||
const [selectDate, setSelectDate] = useState({
|
||||
date:
|
||||
@@ -111,6 +116,7 @@ function Placeholder(props) {
|
||||
: ""
|
||||
});
|
||||
const [dateFormat, setDateFormat] = useState([]);
|
||||
const [clickonWidget, setClickonWidget] = useState({});
|
||||
const [startDate, setStartDate] = useState(
|
||||
props.pos.type === "date" &&
|
||||
getDefaultdate(
|
||||
@@ -200,20 +206,6 @@ function Placeholder(props) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectDate]);
|
||||
|
||||
//it detect outside click of date dropdown menu
|
||||
useEffect(() => {
|
||||
const closeMenuOnOutsideClick = (e) => {
|
||||
if (isShowDateFormat && !e.target.closest("#menu-container")) {
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
}
|
||||
};
|
||||
document.addEventListener("click", closeMenuOnOutsideClick);
|
||||
return () => {
|
||||
// Cleanup the event listener when the component unmounts
|
||||
document.removeEventListener("click", closeMenuOnOutsideClick);
|
||||
};
|
||||
}, [isShowDateFormat]);
|
||||
|
||||
//`handleWidgetIdandPopup` is used to set current widget id and open relative popup
|
||||
const handleWidgetIdandPopup = () => {
|
||||
if (props.setSelectWidgetId) {
|
||||
@@ -338,7 +330,6 @@ function Placeholder(props) {
|
||||
|
||||
//condition for only placeholder and template flow
|
||||
if (props.data && props?.pos?.type !== textWidget) {
|
||||
props.setSignerObjId(props?.data?.signerObjId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
@@ -369,7 +360,6 @@ function Placeholder(props) {
|
||||
//and on save or cancel button of copy all page popup i have set this temp signer Id in unique id
|
||||
|
||||
if (props.data && props?.pos?.type !== textWidget) {
|
||||
props.setSignerObjId(props?.data?.signerObjId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
@@ -378,7 +368,6 @@ function Placeholder(props) {
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
} else if (props.data && props.pos.type === textWidget) {
|
||||
props.setTempSignerId(props.uniqueId);
|
||||
props.setSignerObjId(props?.data?.signerObjId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
@@ -430,7 +419,11 @@ function Placeholder(props) {
|
||||
props.setXyPostion,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
data?.format
|
||||
data?.format,
|
||||
null,
|
||||
null,
|
||||
props.fontSize || props.pos?.options?.fontSize || "12",
|
||||
props.fontColor || props.pos?.options?.fontColor || "black"
|
||||
);
|
||||
setSelectDate({ date: date, format: data?.format });
|
||||
};
|
||||
@@ -522,17 +515,12 @@ function Placeholder(props) {
|
||||
)}
|
||||
{/* setting icon only for date widgets */}
|
||||
{props.pos.type === "date" && selectDate && (
|
||||
<div
|
||||
id="menu-container"
|
||||
style={{
|
||||
zIndex: "99",
|
||||
top: "-19px",
|
||||
right: props.isPlaceholder ? "60px" : "44px"
|
||||
}}
|
||||
className={`${isShowDateFormat ? "show" : ""} dropdown icon`}
|
||||
<i
|
||||
onClick={(e) => {
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
setIsDateModal(!isDateModal);
|
||||
e.stopPropagation();
|
||||
setClickonWidget(props.pos);
|
||||
if (props.data) {
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setUniqueId(props.data.Id);
|
||||
@@ -543,8 +531,9 @@ function Placeholder(props) {
|
||||
}
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
e.stopPropagation();
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
setIsDateModal(!isDateModal);
|
||||
if (props.data) {
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setUniqueId(props.data.Id);
|
||||
@@ -554,44 +543,15 @@ function Placeholder(props) {
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-gear icon"
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
fontSize: "14px"
|
||||
}}
|
||||
></i>
|
||||
<div
|
||||
className={
|
||||
isShowDateFormat ? "dropdown-menu show" : "dropdown-menu"
|
||||
}
|
||||
aria-labelledby="dropdownMenuButton"
|
||||
aria-expanded={isShowDateFormat ? "true" : "false"}
|
||||
>
|
||||
{dateFormat.map((data, ind) => {
|
||||
return (
|
||||
<span
|
||||
key={ind}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
setSelectDate(data);
|
||||
handleSaveDate(data);
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
setSelectDate(data);
|
||||
handleSaveDate(data);
|
||||
}}
|
||||
className="dropdown-item text-[13px]"
|
||||
>
|
||||
{data?.date ? data?.date : "nodata"}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
style={{
|
||||
zIndex: "99",
|
||||
top: "-18px",
|
||||
right: props.isPlaceholder ? "50px" : "30px",
|
||||
color: "#188ae2",
|
||||
fontSize: "14px"
|
||||
}}
|
||||
className="fa-light fa-gear icon"
|
||||
></i>
|
||||
)}
|
||||
{/* copy icon for all widgets */}
|
||||
<i
|
||||
@@ -705,228 +665,320 @@ function Placeholder(props) {
|
||||
return "all-scroll";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Rnd
|
||||
id={props.pos.key}
|
||||
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
|
||||
key={props.pos.key}
|
||||
lockAspectRatio={
|
||||
![
|
||||
textWidget,
|
||||
"email",
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
textInputWidget
|
||||
].includes(props.pos.type) &&
|
||||
(props.pos.Width
|
||||
? props.pos.Width / props.pos.Height
|
||||
: defaultWidthHeight(props.pos.type).width /
|
||||
defaultWidthHeight(props.pos.type).height)
|
||||
}
|
||||
enableResizing={{
|
||||
top: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight:
|
||||
props.data && props.isNeedSign
|
||||
? props.data?.signerObjId === props.signerObjId &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.type !== radioButtonWidget
|
||||
? true
|
||||
: false
|
||||
: props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId &&
|
||||
true,
|
||||
bottomLeft: false,
|
||||
topLeft: false
|
||||
}}
|
||||
bounds="parent"
|
||||
className="signYourselfBlock"
|
||||
style={{
|
||||
border: "1px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
textAlign:
|
||||
props.pos.type !== "name" &&
|
||||
props.pos.type !== "company" &&
|
||||
props.pos.type !== "job title" &&
|
||||
"center",
|
||||
cursor: getCursor(),
|
||||
zIndex:
|
||||
props.pos.type === "date"
|
||||
? props.pos.key === props.selectWidgetId
|
||||
? 99 + 1
|
||||
: 99
|
||||
: props?.pos?.zIndex
|
||||
? props.pos.zIndex
|
||||
: "5",
|
||||
background: props.data ? props.data.blockColor : "rgb(203 233 237)"
|
||||
}}
|
||||
onDrag={() => {
|
||||
setDraggingEnabled(true);
|
||||
props.handleTabDrag && props.handleTabDrag(props.pos.key);
|
||||
}}
|
||||
size={{
|
||||
width:
|
||||
props.pos.type === radioButtonWidget || props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posWidth(props.pos, props.isSignYourself),
|
||||
height:
|
||||
props.pos.type === radioButtonWidget || props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself)
|
||||
}}
|
||||
onResizeStart={() => {
|
||||
setDraggingEnabled(true);
|
||||
props.setIsResize && props.setIsResize(true);
|
||||
}}
|
||||
onResizeStop={(e, direction, ref) => {
|
||||
props.setIsResize && props.setIsResize(false);
|
||||
props.handleSignYourselfImageResize &&
|
||||
props.handleSignYourselfImageResize(
|
||||
ref,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.setXyPostion,
|
||||
props.index,
|
||||
containerScale,
|
||||
props.scale,
|
||||
props.data && props.data.Id,
|
||||
props.isResize
|
||||
);
|
||||
}}
|
||||
disableDragging={
|
||||
props.isNeedSign
|
||||
? true
|
||||
: props.isPlaceholder && ![textWidget].includes(props.pos.type)
|
||||
? false
|
||||
: !isDraggingEnabled
|
||||
}
|
||||
onDragStop={(event, dragElement) =>
|
||||
props.handleStop &&
|
||||
props.handleStop(event, dragElement, props.data?.Id, props.pos?.key)
|
||||
}
|
||||
position={{
|
||||
x: xPos(props.pos, props.isSignYourself),
|
||||
y: yPos(props.pos, props.isSignYourself)
|
||||
}}
|
||||
onResize={(e, direction, ref) => {
|
||||
setPlaceholderBorder({
|
||||
w: ref.offsetWidth / (props.scale * containerScale),
|
||||
h: ref.offsetHeight / (props.scale * containerScale)
|
||||
});
|
||||
}}
|
||||
onClick={() => handleOnClickPlaceholder()}
|
||||
>
|
||||
{props.isShowBorder &&
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId ? (
|
||||
<BorderResize right={-12} top={-11} />
|
||||
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
|
||||
props.data?.signerObjId === props.signerObjId &&
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" ? (
|
||||
<BorderResize />
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<Rnd
|
||||
id={props.pos.key}
|
||||
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
|
||||
key={props.pos.key}
|
||||
lockAspectRatio={
|
||||
![
|
||||
textWidget,
|
||||
"email",
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
textInputWidget
|
||||
].includes(props.pos.type) &&
|
||||
(props.pos.Width
|
||||
? props.pos.Width / props.pos.Height
|
||||
: defaultWidthHeight(props.pos.type).width /
|
||||
defaultWidthHeight(props.pos.type).height)
|
||||
}
|
||||
enableResizing={{
|
||||
top: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight:
|
||||
props.data && props.isNeedSign
|
||||
? props.data?.signerObjId === props.signerObjId &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.type !== radioButtonWidget
|
||||
? true
|
||||
: false
|
||||
: props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId &&
|
||||
true,
|
||||
bottomLeft: false,
|
||||
topLeft: false
|
||||
}}
|
||||
bounds="parent"
|
||||
className="signYourselfBlock"
|
||||
style={{
|
||||
border: "1px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
textAlign:
|
||||
props.pos.type !== "name" &&
|
||||
props.pos.type !== "company" &&
|
||||
props.pos.type !== "job title" &&
|
||||
"center",
|
||||
cursor: getCursor(),
|
||||
zIndex:
|
||||
props.pos.type === "date"
|
||||
? props.pos.key === props.selectWidgetId
|
||||
? 99 + 1
|
||||
: 99
|
||||
: props?.pos?.zIndex
|
||||
? props.pos.zIndex
|
||||
: "5",
|
||||
background: props.data ? props.data.blockColor : "rgb(203 233 237)"
|
||||
}}
|
||||
onDrag={() => {
|
||||
setDraggingEnabled(true);
|
||||
props.handleTabDrag && props.handleTabDrag(props.pos.key);
|
||||
}}
|
||||
size={{
|
||||
width:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posWidth(props.pos, props.isSignYourself),
|
||||
height:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself)
|
||||
}}
|
||||
onResizeStart={() => {
|
||||
setDraggingEnabled(true);
|
||||
props.setIsResize && props.setIsResize(true);
|
||||
}}
|
||||
onResizeStop={(e, direction, ref) => {
|
||||
props.setIsResize && props.setIsResize(false);
|
||||
props.handleSignYourselfImageResize &&
|
||||
props.handleSignYourselfImageResize(
|
||||
ref,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.setXyPostion,
|
||||
props.index,
|
||||
containerScale,
|
||||
props.scale,
|
||||
props.data && props.data.Id,
|
||||
props.isResize
|
||||
);
|
||||
}}
|
||||
disableDragging={
|
||||
props.isNeedSign
|
||||
? true
|
||||
: props.isPlaceholder && ![textWidget].includes(props.pos.type)
|
||||
? false
|
||||
: !isDraggingEnabled
|
||||
}
|
||||
onDragStop={(event, dragElement) =>
|
||||
props.handleStop &&
|
||||
props.handleStop(event, dragElement, props.data?.Id, props.pos?.key)
|
||||
}
|
||||
position={{
|
||||
x: xPos(props.pos, props.isSignYourself),
|
||||
y: yPos(props.pos, props.isSignYourself)
|
||||
}}
|
||||
onResize={(e, direction, ref) => {
|
||||
setPlaceholderBorder({
|
||||
w: ref.offsetWidth / (props.scale * containerScale),
|
||||
h: ref.offsetHeight / (props.scale * containerScale)
|
||||
});
|
||||
}}
|
||||
onClick={() => handleOnClickPlaceholder()}
|
||||
>
|
||||
{props.isShowBorder &&
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId && <BorderResize />
|
||||
)}
|
||||
props.pos.key === props.selectWidgetId ? (
|
||||
<BorderResize right={-12} top={-11} />
|
||||
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
|
||||
props.data?.signerObjId === props.signerObjId &&
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" ? (
|
||||
<BorderResize />
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
) : (
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId && <BorderResize />
|
||||
)}
|
||||
|
||||
{props.isShowBorder && props.pos.key === props.selectWidgetId && (
|
||||
<PlaceholderBorder
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pos={props.pos}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
getCheckboxRenderWidth={getCheckboxRenderWidth}
|
||||
scale={props.scale}
|
||||
containerScale={containerScale}
|
||||
placeholderBorder={placeholderBorder}
|
||||
/>
|
||||
)}
|
||||
{isTabAndMobile ? (
|
||||
<div
|
||||
className="flex items-stretch"
|
||||
style={{
|
||||
left: xPos(props.pos, props.isSignYourself),
|
||||
top: yPos(props.pos, props.isSignYourself),
|
||||
width:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posWidth(props.pos, props.isSignYourself),
|
||||
height:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself),
|
||||
zIndex: "10"
|
||||
}}
|
||||
onTouchEnd={() => handleOnClickPlaceholder()}
|
||||
onClick={() => handleOnClickPlaceholder()}
|
||||
>
|
||||
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
|
||||
|
||||
<PlaceholderType
|
||||
pos={props.pos}
|
||||
xyPostion={props.xyPostion}
|
||||
index={props.index}
|
||||
setXyPostion={props.setXyPostion}
|
||||
data={props.data}
|
||||
setSignKey={props.setSignKey}
|
||||
isShowDropdown={props?.isShowDropdown}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
isSignYourself={props.isSignYourself}
|
||||
signerObjId={props.signerObjId}
|
||||
handleUserName={props.handleUserName}
|
||||
{props.isShowBorder && props.pos.key === props.selectWidgetId && (
|
||||
<PlaceholderBorder
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
|
||||
isNeedSign={props.isNeedSign}
|
||||
setSelectDate={setSelectDate}
|
||||
selectDate={selectDate}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setStartDate={setStartDate}
|
||||
startDate={startDate}
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
pos={props.pos}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
getCheckboxRenderWidth={getCheckboxRenderWidth}
|
||||
scale={props.scale}
|
||||
containerScale={containerScale}
|
||||
placeholderBorder={placeholderBorder}
|
||||
/>
|
||||
)}
|
||||
{isTabAndMobile ? (
|
||||
<div
|
||||
className="flex items-stretch"
|
||||
style={{
|
||||
left: xPos(props.pos, props.isSignYourself),
|
||||
top: yPos(props.pos, props.isSignYourself),
|
||||
width:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posWidth(props.pos, props.isSignYourself),
|
||||
height:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself),
|
||||
zIndex: "10"
|
||||
}}
|
||||
onTouchEnd={() => handleOnClickPlaceholder()}
|
||||
onClick={() => handleOnClickPlaceholder()}
|
||||
>
|
||||
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
|
||||
|
||||
<PlaceholderType
|
||||
pos={props.pos}
|
||||
xyPostion={props.xyPostion}
|
||||
index={props.index}
|
||||
setXyPostion={props.setXyPostion}
|
||||
data={props.data}
|
||||
setSignKey={props.setSignKey}
|
||||
isShowDropdown={props?.isShowDropdown}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
isSignYourself={props.isSignYourself}
|
||||
signerObjId={props.signerObjId}
|
||||
handleUserName={props.handleUserName}
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
|
||||
isNeedSign={props.isNeedSign}
|
||||
setSelectDate={setSelectDate}
|
||||
selectDate={selectDate}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setStartDate={setStartDate}
|
||||
startDate={startDate}
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
|
||||
<PlaceholderType
|
||||
pos={props.pos}
|
||||
xyPostion={props.xyPostion}
|
||||
index={props.index}
|
||||
setXyPostion={props.setXyPostion}
|
||||
data={props.data}
|
||||
setSignKey={props.setSignKey}
|
||||
isShowDropdown={props?.isShowDropdown}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
isSignYourself={props.isSignYourself}
|
||||
signerObjId={props.signerObjId}
|
||||
handleUserName={props.handleUserName}
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
|
||||
isNeedSign={props.isNeedSign}
|
||||
setSelectDate={setSelectDate}
|
||||
selectDate={selectDate}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setStartDate={setStartDate}
|
||||
startDate={startDate}
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Rnd>
|
||||
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<div className="flex flex-row items-center">
|
||||
<span>{t("format")} : </span>
|
||||
<div className="flex">
|
||||
<select
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
defaultValue={""}
|
||||
onChange={(e) => {
|
||||
const selectedIndex = e.target.value;
|
||||
e.stopPropagation();
|
||||
setSelectDate(dateFormat[selectedIndex]);
|
||||
}}
|
||||
>
|
||||
<option value="" disabled>
|
||||
{t("select-date-format")}
|
||||
</option>
|
||||
{dateFormat.map((data, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" value={ind} key={ind}>
|
||||
{data?.date ? data?.date : "nodata"}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<span className="text-xs text-gray-400 ml-1">
|
||||
{selectDate.format}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center mt-4 md:mt-2">
|
||||
<span>{t("font-size")} :</span>
|
||||
<select
|
||||
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={props.fontSize || clickonWidget.options?.fontSize || "12"}
|
||||
onChange={(e) => props.setFontSize(e.target.value)}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<div className="flex flex-row gap-1 items-center ml-2 md:ml-4 ">
|
||||
<span>{t("color")}: </span>
|
||||
<select
|
||||
value={
|
||||
props.fontColor || clickonWidget.options?.fontColor || "black"
|
||||
}
|
||||
onChange={(e) => props.setFontColor(e.target.value)}
|
||||
className="ml-[4px] md:ml[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
>
|
||||
{fontColorArr.map((color, ind) => {
|
||||
return (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<span
|
||||
style={{
|
||||
background:
|
||||
props.fontColor || props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
className="w-5 h-[19px] ml-1"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
onClick={() => {
|
||||
setIsDateModal(false);
|
||||
handleSaveDate(selectDate);
|
||||
props.setFontColor("");
|
||||
props.setFontSize("");
|
||||
}}
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
|
||||
<PlaceholderType
|
||||
pos={props.pos}
|
||||
xyPostion={props.xyPostion}
|
||||
index={props.index}
|
||||
setXyPostion={props.setXyPostion}
|
||||
data={props.data}
|
||||
setSignKey={props.setSignKey}
|
||||
isShowDropdown={props?.isShowDropdown}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
isSignYourself={props.isSignYourself}
|
||||
signerObjId={props.signerObjId}
|
||||
handleUserName={props.handleUserName}
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
|
||||
isNeedSign={props.isNeedSign}
|
||||
setSelectDate={setSelectDate}
|
||||
selectDate={selectDate}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setStartDate={setStartDate}
|
||||
startDate={startDate}
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Rnd>
|
||||
</ModalUi>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import React, { useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { randomId, textWidget } from "../../constant/Utils";
|
||||
import {
|
||||
handleCopyNextToWidget,
|
||||
randomId,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function PlaceholderCopy(props) {
|
||||
const { t } = useTranslation();
|
||||
const copyType = ["All pages", "All pages but last", "All pages but first"];
|
||||
const [selectCopyType, setSelectCopyType] = useState("");
|
||||
const copyType = [
|
||||
{ id: 1, type: "All pages" },
|
||||
{ id: 2, type: "All pages but last" },
|
||||
{ id: 3, type: "All pages but first" },
|
||||
{ id: 4, type: "Next to current widget" }
|
||||
];
|
||||
const [selectCopyType, setSelectCopyType] = useState(1);
|
||||
//function for get copy placeholder position
|
||||
const getCopyPlaceholderPosition = (
|
||||
type,
|
||||
@@ -20,7 +29,7 @@ function PlaceholderCopy(props) {
|
||||
(data) => data.xPosition !== currentPlaceholder.xPosition
|
||||
);
|
||||
//copy all page placeholder at requested position except first page
|
||||
if (newPageNumber === 1 && type === "first") {
|
||||
if (newPageNumber === 1 && type === 3) {
|
||||
if (filterPosition && filterPosition.length > 0) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
@@ -29,7 +38,7 @@ function PlaceholderCopy(props) {
|
||||
}
|
||||
}
|
||||
//copy all page placeholder at requested position except last page
|
||||
else if (newPageNumber === props.allPages && type === "last") {
|
||||
else if (newPageNumber === props.allPages && type === 2) {
|
||||
if (existPlaceholderPosition) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
@@ -172,25 +181,65 @@ function PlaceholderCopy(props) {
|
||||
|
||||
//function for getting selected type placeholder copy
|
||||
const handleApplyCopy = () => {
|
||||
if (selectCopyType === "All pages") {
|
||||
copyPlaceholder("all");
|
||||
} else if (selectCopyType === "All pages but last") {
|
||||
copyPlaceholder("last");
|
||||
} else if (selectCopyType === "All pages but first") {
|
||||
copyPlaceholder("first");
|
||||
if (selectCopyType === 4) {
|
||||
const signerPosition = props.xyPostion;
|
||||
let currentXYposition;
|
||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||
if (signerId) {
|
||||
const filterSignerPosition = signerPosition.filter(
|
||||
(item) => item?.Id === signerId
|
||||
);
|
||||
currentXYposition = filterSignerPosition[0].placeHolder.filter(
|
||||
(data) => data.pageNumber === props?.pageNumber
|
||||
);
|
||||
//get current placeholder position data which user want to copy
|
||||
currentXYposition = currentXYposition[0].pos.find(
|
||||
(position) => position.key === props.signKey
|
||||
);
|
||||
//function to create new widget next to just widget
|
||||
handleCopyNextToWidget(
|
||||
currentXYposition,
|
||||
props.widgetType,
|
||||
props.xyPostion,
|
||||
props.pageNumber,
|
||||
props.setXyPostion,
|
||||
props?.Id
|
||||
);
|
||||
} else {
|
||||
const getIndex = props?.xyPostion.findIndex(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
);
|
||||
const placeholderPosition = props?.xyPostion[getIndex];
|
||||
//get current placeholder position data which user want to copy
|
||||
currentXYposition = placeholderPosition.pos.find(
|
||||
(pos) => pos.key === props.signKey
|
||||
);
|
||||
//function to create new widget next to just widget
|
||||
handleCopyNextToWidget(
|
||||
currentXYposition,
|
||||
props.widgetType,
|
||||
props.xyPostion,
|
||||
getIndex,
|
||||
props.setXyPostion
|
||||
);
|
||||
}
|
||||
} else {
|
||||
copyPlaceholder(selectCopyType);
|
||||
}
|
||||
};
|
||||
const handleUniqueId = () => {
|
||||
if (props.widgetType === textWidget) {
|
||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||
if (signerId && props.widgetType === textWidget) {
|
||||
props.setUniqueId(props?.tempSignerId);
|
||||
props.setTempSignerId("");
|
||||
}
|
||||
props.setIsPageCopy(false);
|
||||
setSelectCopyType(1);
|
||||
};
|
||||
return (
|
||||
<ModalUi
|
||||
isOpen={props.isPageCopy}
|
||||
title={t("copy-to-all-pages")}
|
||||
title={t("copy-title")}
|
||||
handleClose={() => handleUniqueId()}
|
||||
>
|
||||
<div className="h-full p-[20px] text-base-content">
|
||||
@@ -201,11 +250,11 @@ function PlaceholderCopy(props) {
|
||||
<input
|
||||
className="mr-[8px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
value={data}
|
||||
onChange={() => setSelectCopyType(data)}
|
||||
checked={selectCopyType === data}
|
||||
value={data.id}
|
||||
onChange={() => setSelectCopyType(data.id)}
|
||||
checked={selectCopyType === data.id}
|
||||
/>
|
||||
{t(`copy-type.${data}`)}
|
||||
{t(`copy-type.${data.type}`)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -29,6 +29,8 @@ function PlaceholderType(props) {
|
||||
const [textValue, setTextValue] = useState();
|
||||
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
|
||||
const years = range(1990, getYear(new Date()) + 16, 1);
|
||||
const fontSize = (props.pos.options?.fontSize || "12") + "px";
|
||||
const fontColor = props.pos.options?.fontColor || "black";
|
||||
const months = [
|
||||
"January",
|
||||
"February",
|
||||
@@ -143,7 +145,12 @@ function PlaceholderType(props) {
|
||||
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
className={`${selectWidgetCls} text-[12px] overflow-hidden`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
className={`${selectWidgetCls} overflow-hidden`}
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
@@ -343,9 +350,13 @@ function PlaceholderType(props) {
|
||||
return (
|
||||
<div key={ind} className="flex items-center text-center gap-0.5">
|
||||
<input
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize
|
||||
}}
|
||||
className={`${
|
||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||
} flex justify-center op-checkbox op-checkbox-xs rounded-[0.350rem]`}
|
||||
} flex justify-center op-checkbox rounded-[1px] `}
|
||||
onBlur={handleInputBlur}
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
@@ -379,7 +390,15 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<label className="text-xs mb-0 text-center">{data}</label>
|
||||
<label
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
className="text-xs mb-0 text-center"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -410,20 +429,16 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
@@ -433,6 +448,10 @@ function PlaceholderType(props) {
|
||||
case "dropdown":
|
||||
return props.data?.signerObjId === props.signerObjId ? (
|
||||
<select
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
className={`${selectWidgetCls} text-[12px] bg-inherit`}
|
||||
id="myDropdown"
|
||||
value={selectOption}
|
||||
@@ -450,20 +469,41 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
>
|
||||
{/* Default/Title option */}
|
||||
<option value="" disabled hidden>
|
||||
<option
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
value=""
|
||||
disabled
|
||||
hidden
|
||||
>
|
||||
{props?.pos?.options?.name}
|
||||
</option>
|
||||
|
||||
{props.pos?.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<option key={ind} value={data}>
|
||||
<option
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
key={ind}
|
||||
value={data}
|
||||
>
|
||||
{data}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
) : (
|
||||
<div className="text-[12px] overflow-hidden">
|
||||
<div
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
className=" overflow-hidden"
|
||||
>
|
||||
{props.pos?.options?.name
|
||||
? props.pos.options.name
|
||||
: widgetTypeTraslation}
|
||||
@@ -511,20 +551,16 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
@@ -555,20 +591,16 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
overflow: "hidden",
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
@@ -599,20 +631,16 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
@@ -680,7 +708,14 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[12px] text-black items-center overflow-hidden">
|
||||
<div
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
className="items-center overflow-hidden"
|
||||
>
|
||||
<span>
|
||||
{props.selectDate
|
||||
? props.selectDate?.format
|
||||
@@ -732,10 +767,8 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
cols="50"
|
||||
@@ -743,10 +776,8 @@ function PlaceholderType(props) {
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
@@ -761,9 +792,12 @@ function PlaceholderType(props) {
|
||||
return (
|
||||
<div key={ind} className="flex items-center text-center gap-0.5">
|
||||
<input
|
||||
className={`${
|
||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||
} ${"w-[15px] h-[15px]"} flex justify-center op-radio`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize,
|
||||
marginTop: ind > 0 ? "10px" : "0px"
|
||||
}}
|
||||
className={`flex justify-center op-radio`}
|
||||
type="radio"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
@@ -778,7 +812,15 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<label className="text-xs mb-0">{data}</label>
|
||||
<label
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
className="text-xs mb-0"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -808,10 +850,8 @@ function PlaceholderType(props) {
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontFamily: "Arial, sans-serif",
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
fontSize: fontSize,
|
||||
color: fontColor
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
|
||||
@@ -16,7 +16,7 @@ const RecipientList = (props) => {
|
||||
//function for onhover signer name change background color
|
||||
const inputRef = useRef(null);
|
||||
const isWidgetExist = (Id) => {
|
||||
return props.signerPos.some((x) => x.Id === Id);
|
||||
return props.signerPos.some((x) => x.Id === Id && x.placeHolder);
|
||||
};
|
||||
|
||||
//handle drag start
|
||||
@@ -63,9 +63,7 @@ const RecipientList = (props) => {
|
||||
remainingItems.splice(index, 0, ...draggedItem);
|
||||
//set current draggable recipient details,objectId,index,contract_className ... after replace recipient list
|
||||
props?.setSignersData(remainingItems);
|
||||
props?.setSignerObjId(remainingItems[index]?.objectId || "");
|
||||
props?.setIsSelectId(index);
|
||||
props?.setContractName(remainingItems[index]?.className || "");
|
||||
props?.setUniqueId(remainingItems[index]?.Id);
|
||||
props?.setRoleName(remainingItems[index]?.Role);
|
||||
props?.setBlockColor(remainingItems[index]?.blockColor);
|
||||
@@ -121,9 +119,7 @@ const RecipientList = (props) => {
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
props.setSignerObjId(obj?.objectId || "");
|
||||
props.setIsSelectId(ind);
|
||||
props.setContractName(obj?.className || "");
|
||||
props.setUniqueId(obj.Id);
|
||||
props.setRoleName(obj.Role);
|
||||
props.setBlockColor(obj?.blockColor);
|
||||
|
||||
@@ -12,7 +12,8 @@ function RenderAllPdfPage({
|
||||
pageNumber,
|
||||
signerPos,
|
||||
signerObjectId,
|
||||
containerWH
|
||||
containerWH,
|
||||
pdfRotateBase64
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [signPageNumber, setSignPageNumber] = useState([]);
|
||||
@@ -66,6 +67,7 @@ function RenderAllPdfPage({
|
||||
)
|
||||
);
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`;
|
||||
return (
|
||||
<div ref={pageContainer} className="hidden w-[20%] bg-base-100 md:block">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
@@ -78,7 +80,7 @@ function RenderAllPdfPage({
|
||||
<Document
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={onDocumentLoad}
|
||||
file={signPdfUrl}
|
||||
file={(pdfRotateBase64 && pdfDataBase64) || signPdfUrl}
|
||||
>
|
||||
{Array.from(new Array(allPages), (el, index) => (
|
||||
<div
|
||||
@@ -95,16 +97,16 @@ function RenderAllPdfPage({
|
||||
>
|
||||
{signerPos && addSignatureBookmark(index)}
|
||||
|
||||
<div className="relative z-[1] overflow-hidden">
|
||||
<Page
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={pageWidth - 60}
|
||||
scale={1}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="relative z-[1] overflow-hidden"> */}
|
||||
<Page
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={pageWidth - 60}
|
||||
scale={1}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
/>
|
||||
{/* </div> */}
|
||||
</div>
|
||||
))}
|
||||
</Document>
|
||||
|
||||
@@ -41,7 +41,6 @@ function RenderPdf({
|
||||
setUniqueId,
|
||||
signersdata,
|
||||
setIsPageCopy,
|
||||
setSignerObjId,
|
||||
setShowDropdown,
|
||||
setIsInitial,
|
||||
setIsValidate,
|
||||
@@ -61,10 +60,16 @@ function RenderPdf({
|
||||
scale,
|
||||
setIsSelectId,
|
||||
ispublicTemplate,
|
||||
handleUserDetails
|
||||
handleUserDetails,
|
||||
pdfRotateBase64,
|
||||
fontSize,
|
||||
setFontSize,
|
||||
fontColor,
|
||||
setFontColor
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const isMobile = window.innerWidth < 767;
|
||||
|
||||
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
|
||||
@@ -248,155 +253,180 @@ function RenderPdf({
|
||||
);
|
||||
}
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
{successEmail && (
|
||||
<Alert type={"success"}>{t("success-email-alert")}</Alert>
|
||||
)}
|
||||
{isMobile ? (
|
||||
<div
|
||||
data-tut="reactourForth"
|
||||
className={`${
|
||||
isGuestSigner ? "30px" : ""
|
||||
} border-[0.1px] border-[#ebe8e8]`}
|
||||
ref={drop}
|
||||
id="container"
|
||||
<RSC
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||
height: window.innerHeight - 200,
|
||||
zIndex: 0
|
||||
}}
|
||||
className="h-full"
|
||||
noScrollY={false}
|
||||
noScrollX={scale === 1 ? true : false}
|
||||
>
|
||||
{containerWH?.width &&
|
||||
pdfOriginalWH.length > 0 &&
|
||||
(pdfRequest
|
||||
? signerPos.map((data, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: placeholder // placeholder mobile
|
||||
? signerPos.map((data, ind) => {
|
||||
<div
|
||||
data-tut="reactourForth"
|
||||
className={`${
|
||||
isGuestSigner ? "30px" : ""
|
||||
} border-[0.1px] border-[#ebe8e8] overflow-x-auto`}
|
||||
style={{
|
||||
width: containerWH?.width && containerWH?.width * scale
|
||||
}}
|
||||
ref={drop}
|
||||
id="container"
|
||||
>
|
||||
{containerWH?.width &&
|
||||
pdfOriginalWH.length > 0 &&
|
||||
(pdfRequest
|
||||
? signerPos.map((data, key) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
setSignerObjId={setSignerObjId}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
setShowDropdown={setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={setUniqueId}
|
||||
handleLinkUser={handleLinkUser}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={isDragging}
|
||||
setIsValidate={setIsValidate}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={handleNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
setIsSelectId={setIsSelectId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: xyPostion.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === pageNumber &&
|
||||
data.pos.map((pos) => {
|
||||
return (
|
||||
pos && (
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleSignYourselfImageResize
|
||||
}
|
||||
index={index}
|
||||
xyPostion={xyPostion}
|
||||
setXyPostion={setXyPostion}
|
||||
containerWH={containerWH}
|
||||
setIsSignPad={setIsSignPad}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={pdfDetails[0]}
|
||||
isDragging={isDragging}
|
||||
setIsInitial={setIsInitial}
|
||||
setWidgetType={setWidgetType}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleUserName={handleUserName}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setValidateAlert={setValidateAlert}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
: placeholder // placeholder mobile
|
||||
? signerPos?.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data?.placeHolder &&
|
||||
data?.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
setShowDropdown={setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={setUniqueId}
|
||||
handleLinkUser={handleLinkUser}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={isDragging}
|
||||
setIsValidate={setIsValidate}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={
|
||||
setSelectWidgetId
|
||||
}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={handleNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
setIsSelectId={setIsSelectId}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: !pdfDetails?.[0]?.IsCompleted &&
|
||||
xyPostion.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === pageNumber &&
|
||||
data.pos.map((pos) => {
|
||||
return (
|
||||
pos && (
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleSignYourselfImageResize
|
||||
}
|
||||
index={index}
|
||||
xyPostion={xyPostion}
|
||||
setXyPostion={setXyPostion}
|
||||
containerWH={containerWH}
|
||||
setIsSignPad={setIsSignPad}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={pdfDetails[0]}
|
||||
isDragging={isDragging}
|
||||
setIsInitial={setIsInitial}
|
||||
setWidgetType={setWidgetType}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleUserName={handleUserName}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setValidateAlert={setValidateAlert}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
|
||||
{/* this component for render pdf document is in middle of the component */}
|
||||
<div className="flex items-center justify-center">
|
||||
<Document
|
||||
onLoadError={() => {
|
||||
setPdfLoad(false);
|
||||
}}
|
||||
onLoadError={() => setPdfLoad(false)}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={pageDetails}
|
||||
// ref={pdfRef}'
|
||||
@@ -405,9 +435,14 @@ function RenderPdf({
|
||||
setSelectWidgetId("");
|
||||
}
|
||||
}}
|
||||
file={pdfDetails[0]?.SignedUrl || pdfDetails[0]?.URL}
|
||||
file={
|
||||
(pdfRotateBase64 && pdfDataBase64) ||
|
||||
pdfDetails[0]?.SignedUrl ||
|
||||
pdfDetails[0].URL
|
||||
}
|
||||
>
|
||||
<Page
|
||||
scale={scale || 1}
|
||||
key={index}
|
||||
pageNumber={pageNumber}
|
||||
width={containerWH.width}
|
||||
@@ -419,7 +454,7 @@ function RenderPdf({
|
||||
/>
|
||||
</Document>
|
||||
</div>
|
||||
</div>
|
||||
</RSC>
|
||||
) : (
|
||||
<RSC
|
||||
style={{
|
||||
@@ -453,71 +488,78 @@ function RenderPdf({
|
||||
? signerPos.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
setSignerObjId={setSignerObjId}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
setShowDropdown={setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={setUniqueId}
|
||||
handleLinkUser={handleLinkUser}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={isDragging}
|
||||
setIsValidate={setIsValidate}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={handleNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
setIsSelectId={setIsSelectId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
{data?.placeHolder &&
|
||||
data?.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
setShowDropdown={setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={setUniqueId}
|
||||
handleLinkUser={handleLinkUser}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={isDragging}
|
||||
setIsValidate={setIsValidate}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={
|
||||
setSelectWidgetId
|
||||
}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={handleNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
setIsSelectId={setIsSelectId}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: xyPostion.map((data, ind) => {
|
||||
: !pdfDetails?.[0]?.IsCompleted &&
|
||||
xyPostion?.map((data, ind) => {
|
||||
// signyourself flow
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
@@ -565,6 +607,10 @@ function RenderPdf({
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -575,9 +621,7 @@ function RenderPdf({
|
||||
|
||||
{/* this component for render pdf document is in middle of the component */}
|
||||
<Document
|
||||
onLoadError={() => {
|
||||
setPdfLoad(false);
|
||||
}}
|
||||
onLoadError={() => setPdfLoad(false)}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={pageDetails}
|
||||
onClick={() => {
|
||||
@@ -586,7 +630,11 @@ function RenderPdf({
|
||||
}
|
||||
}}
|
||||
// ref={pdfRef}
|
||||
file={pdfDetails[0]?.SignedUrl || pdfDetails[0].URL}
|
||||
file={
|
||||
(pdfRotateBase64 && pdfDataBase64) ||
|
||||
pdfDetails[0]?.SignedUrl ||
|
||||
pdfDetails[0].URL
|
||||
}
|
||||
>
|
||||
<Page
|
||||
key={index}
|
||||
|
||||
@@ -21,15 +21,15 @@ function SelectLanguage(props) {
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
!props.isProfile && " mt-3 pb-2 md:pb-0 "
|
||||
!props.isProfile && " mt-[9px] pb-2 md:pb-0 "
|
||||
} flex justify-center items-center `}
|
||||
>
|
||||
<select
|
||||
value={lang}
|
||||
onChange={handleChangeLang}
|
||||
className={`${
|
||||
!props.isProfile && " md:w-[15%] w-[50%]"
|
||||
} op-select op-select-bordered bg-white op-select-sm`}
|
||||
!props.isProfile ? " md:w-[15%] w-[50%]" : "w-[180px]"
|
||||
} op-select op-select-bordered bg-white op-select-sm `}
|
||||
>
|
||||
<option disabled selected>
|
||||
select
|
||||
|
||||
@@ -31,8 +31,8 @@ function SignPad({
|
||||
const [isTab, setIsTab] = useState("draw");
|
||||
const [isSignImg, setIsSignImg] = useState("");
|
||||
const [signValue, setSignValue] = useState("");
|
||||
const [textWidth, setTextWidth] = useState(null);
|
||||
const [textHeight, setTextHeight] = useState(null);
|
||||
const [textWidth, setTextWidth] = useState(0);
|
||||
const [textHeight, setTextHeight] = useState(0);
|
||||
const [signatureType, setSignatureType] = useState("draw");
|
||||
const fontOptions = [
|
||||
{ value: "Fasthand" },
|
||||
@@ -51,7 +51,7 @@ function SignPad({
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
);
|
||||
const jsonSender = JSON.parse(senderUser);
|
||||
const currentUserName = jsonSender && jsonSender.name;
|
||||
const currentUserName = jsonSender && jsonSender?.name;
|
||||
|
||||
//function for clear signature image
|
||||
const handleClear = () => {
|
||||
@@ -137,7 +137,7 @@ function SignPad({
|
||||
(isTab === "draw" && isSignImg) ||
|
||||
(isTab === "image" && image) ||
|
||||
(isTab === "mysignature" && isDefaultSign) ||
|
||||
(isTab === "type" && textWidth)
|
||||
(isTab === "type" && signValue)
|
||||
? false
|
||||
: image
|
||||
? false
|
||||
@@ -169,10 +169,10 @@ function SignPad({
|
||||
}
|
||||
}
|
||||
|
||||
const trimmedName = currentUserName && currentUserName.trim();
|
||||
const trimmedName = currentUserName && currentUserName?.trim();
|
||||
const firstCharacter = trimmedName?.charAt(0);
|
||||
const userName = isInitial ? firstCharacter : currentUserName;
|
||||
setSignValue(userName);
|
||||
setSignValue(userName || "");
|
||||
setFontSelect("Fasthand");
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -200,8 +200,10 @@ function SignPad({
|
||||
canvasRef.current.fromDataURL(isSignImg);
|
||||
}
|
||||
if (isTab === "type") {
|
||||
const trimmedName = signValue ? signValue.trim() : currentUserName.trim();
|
||||
const firstCharacter = trimmedName.charAt(0);
|
||||
const trimmedName = signValue
|
||||
? signValue?.trim()
|
||||
: currentUserName?.trim();
|
||||
const firstCharacter = trimmedName?.charAt(0);
|
||||
const userName = isInitial ? firstCharacter : signValue;
|
||||
setSignValue(userName);
|
||||
convertToImg(fontSelect, userName);
|
||||
|
||||
@@ -56,9 +56,7 @@ function VerifyEmail(props) {
|
||||
<button
|
||||
className="op-btn op-btn-primary"
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
props.handleVerifyBtn();
|
||||
}}
|
||||
onClick={() => props.handleVerifyBtn()}
|
||||
>
|
||||
{t("send-otp")}
|
||||
</button>
|
||||
|
||||
@@ -20,9 +20,7 @@ function WidgetComponent({
|
||||
addPositionOfSignature,
|
||||
signersdata,
|
||||
isSelectListId,
|
||||
setSignerObjId,
|
||||
setIsSelectId,
|
||||
setContractName,
|
||||
isSigners,
|
||||
dataTut,
|
||||
isMailSend,
|
||||
@@ -360,9 +358,7 @@ function WidgetComponent({
|
||||
signerPos={signerPos}
|
||||
signersdata={signersdata}
|
||||
isSelectListId={isSelectListId}
|
||||
setSignerObjId={setSignerObjId}
|
||||
setIsSelectId={setIsSelectId}
|
||||
setContractName={setContractName}
|
||||
setUniqueId={setUniqueId}
|
||||
setRoleName={setRoleName}
|
||||
handleDeleteUser={handleDeleteUser}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Select from "react-select";
|
||||
import AsyncSelect from "react-select/async";
|
||||
import AddSigner from "../../AddSigner";
|
||||
import Parse from "parse";
|
||||
import Tooltip from "../../../primitives/Tooltip";
|
||||
@@ -35,7 +35,6 @@ const AddSignerModal = ({ isOpen, children }) => {
|
||||
const SignersInput = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState(undefined);
|
||||
// const [editFormData, setEditFormData] = useState([]);
|
||||
const [selected, setSelected] = useState([]);
|
||||
const [isModal, setIsModel] = useState(false);
|
||||
const onChange = (selectedOptions) => setSelected(selectedOptions);
|
||||
@@ -46,40 +45,6 @@ const SignersInput = (props) => {
|
||||
setSelected(newValue);
|
||||
};
|
||||
|
||||
const GetSelectListData = async () => {
|
||||
try {
|
||||
const currentUser = Parse.User.current();
|
||||
const contactbook = new Parse.Query("contracts_Contactbook");
|
||||
contactbook.equalTo(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
contactbook.notEqualTo("IsDeleted", true);
|
||||
const contactRes = await contactbook.find();
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
let list = [];
|
||||
|
||||
// let _selected = [];
|
||||
res.forEach((x) => {
|
||||
let obj = {
|
||||
label: x.Name,
|
||||
value: x.objectId,
|
||||
isChecked: true
|
||||
};
|
||||
|
||||
list.push(obj);
|
||||
});
|
||||
setState(list);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("err", error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
GetSelectListData();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (props.isReset && props.isReset === true) {
|
||||
setSelected([]);
|
||||
@@ -118,7 +83,46 @@ const SignersInput = (props) => {
|
||||
setSelected([data]);
|
||||
}
|
||||
};
|
||||
const loadOptions = async (inputValue) => {
|
||||
try {
|
||||
const currentUser = Parse.User.current();
|
||||
const contactbook = new Parse.Query("contracts_Contactbook");
|
||||
contactbook.equalTo(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
if (inputValue.length > 1) {
|
||||
contactbook.matches("Name", new RegExp(inputValue, "i"));
|
||||
}
|
||||
contactbook.notEqualTo("IsDeleted", true);
|
||||
const contactRes = await contactbook.find();
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
|
||||
//and filter signers from total signer's list which already present in document's signers list
|
||||
const compareArrays = (res, signerObj) => {
|
||||
return res.filter(
|
||||
(item1) =>
|
||||
!signerObj.find((item2) => item2.objectId === item1.objectId)
|
||||
);
|
||||
};
|
||||
|
||||
//get update signer's List if signersdata is present
|
||||
const updateSignersList =
|
||||
props?.signersData && compareArrays(res, props?.signersData);
|
||||
|
||||
const result = updateSignersList ? updateSignersList : res;
|
||||
setState(result);
|
||||
return await result.map((item) => ({
|
||||
label: item.Name,
|
||||
value: item.objectId,
|
||||
isChecked: true
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("err", error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="text-xs mt-2 ">
|
||||
<label className="block relative">
|
||||
@@ -130,16 +134,20 @@ const SignersInput = (props) => {
|
||||
</label>
|
||||
<div className="flex gap-x-[5px]">
|
||||
<div className="w-full">
|
||||
<Select
|
||||
<AsyncSelect
|
||||
onSortEnd={onSortEnd}
|
||||
distance={4}
|
||||
isMulti
|
||||
cacheOptions
|
||||
defaultOptions
|
||||
options={state || []}
|
||||
value={selected}
|
||||
onChange={onChange}
|
||||
closeMenuOnSelect={false}
|
||||
required={props.required}
|
||||
loadingMessage={() => t("loading")}
|
||||
noOptionsMessage={() => t("contact-not-found")}
|
||||
loadOptions={loadOptions}
|
||||
unstyled
|
||||
classNames={{
|
||||
control: () =>
|
||||
|
||||
+347
-115
@@ -1,7 +1,7 @@
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { rgb } from "pdf-lib";
|
||||
import { PDFDocument, rgb, degrees } from "pdf-lib";
|
||||
import Parse from "parse";
|
||||
import { appInfo } from "./appinfo";
|
||||
import { saveAs } from "file-saver";
|
||||
@@ -56,18 +56,68 @@ export async function fetchSubscription(
|
||||
? { contactId: contactObjId }
|
||||
: { extUserId: extUser };
|
||||
const tenatRes = await axios.post(url, params, { headers: headers });
|
||||
let plan, status, billingDate;
|
||||
let plan, status, billingDate, adminId;
|
||||
if (isGuestSign) {
|
||||
plan = tenatRes.data?.result?.result?.plan;
|
||||
status = tenatRes.data?.result?.result?.isSubscribed;
|
||||
} else {
|
||||
plan = tenatRes.data?.result?.result?.PlanCode;
|
||||
billingDate = tenatRes.data?.result?.result?.Next_billing_date?.iso;
|
||||
const allowedUsers = tenatRes.data?.result?.result?.AllowedUsers || 0;
|
||||
adminId = tenatRes?.data?.result?.result?.ExtUserPtr?.objectId;
|
||||
localStorage.setItem("allowedUsers", allowedUsers);
|
||||
}
|
||||
return { plan, billingDate, status };
|
||||
return { plan, billingDate, status, adminId };
|
||||
} catch (err) {
|
||||
console.log("Err in fetch subscription", err);
|
||||
return { plan: "", billingDate: "" };
|
||||
return { plan: "", billingDate: "", status: "", adminId: "" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchSubscriptionInfo() {
|
||||
try {
|
||||
const Extand_Class = localStorage.getItem("Extand_Class");
|
||||
const extClass = Extand_Class && JSON.parse(Extand_Class);
|
||||
// console.log("extClass ", extClass);
|
||||
if (extClass && extClass.length > 0) {
|
||||
const extUser = extClass[0].objectId;
|
||||
const baseURL = localStorage.getItem("baseUrl");
|
||||
const url = `${baseURL}functions/getsubscriptions`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const params = { extUserId: extUser };
|
||||
const tenatRes = await axios.post(url, params, { headers: headers });
|
||||
|
||||
const price =
|
||||
tenatRes.data?.result?.result?.SubscriptionDetails?.data?.subscription
|
||||
?.plan?.price;
|
||||
const totalPrice =
|
||||
tenatRes.data?.result?.result?.SubscriptionDetails?.data?.subscription
|
||||
?.amount;
|
||||
const planId =
|
||||
tenatRes.data?.result?.result?.SubscriptionDetails?.data?.subscription
|
||||
?.subscription_id;
|
||||
const plan_code = tenatRes.data?.result?.result?.PlanCode;
|
||||
const totalAllowedUser = tenatRes.data?.result?.result?.AllowedUsers || 0;
|
||||
const adminId =
|
||||
tenatRes?.data?.result?.result?.ExtUserPtr?.objectId || "";
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
price: price,
|
||||
totalPrice: totalPrice,
|
||||
planId: planId,
|
||||
plan_code: plan_code,
|
||||
totalAllowedUser: totalAllowedUser,
|
||||
adminId: adminId
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in fetch subscription", err);
|
||||
return { status: "error", error: err };
|
||||
}
|
||||
}
|
||||
//function to get subcripition details from subscription class
|
||||
@@ -75,41 +125,22 @@ export async function checkIsSubscribed() {
|
||||
try {
|
||||
const res = await fetchSubscription();
|
||||
if (res.plan === "freeplan") {
|
||||
return false;
|
||||
} else if (res.billingDate) {
|
||||
if (new Date(res.billingDate) > new Date()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in fetch subscription", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//function to get subcripition details from subscription class
|
||||
export async function checkIsSubscribedTeam() {
|
||||
try {
|
||||
const res = await fetchSubscription();
|
||||
if (res.plan === "freeplan") {
|
||||
return false;
|
||||
return { plan: res.plan, isValid: false, adminId: res?.adminId };
|
||||
} else if (res.billingDate) {
|
||||
const plan = validplan[res.plan] || false;
|
||||
if (plan && new Date(res.billingDate) > new Date()) {
|
||||
return true;
|
||||
return { plan: res.plan, isValid: true, adminId: res?.adminId };
|
||||
} else if (new Date(res.billingDate) > new Date()) {
|
||||
return { plan: res.plan, isValid: true, adminId: res?.adminId };
|
||||
} else {
|
||||
return false;
|
||||
return { plan: res.plan, isValid: false, adminId: res?.adminId };
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
return { plan: res.plan, isValid: false, adminId: res?.adminId };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in fetch subscription", err);
|
||||
return false;
|
||||
return { plan: "no-plan", isValid: false, adminId: "" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,7 +661,12 @@ export const randomId = () => {
|
||||
return randomDigit;
|
||||
};
|
||||
|
||||
export const createDocument = async (template, placeholders, signerData) => {
|
||||
export const createDocument = async (
|
||||
template,
|
||||
placeholders,
|
||||
signerData,
|
||||
pdfUrl
|
||||
) => {
|
||||
if (template && template.length > 0) {
|
||||
const Doc = template[0];
|
||||
|
||||
@@ -653,7 +689,7 @@ export const createDocument = async (template, placeholders, signerData) => {
|
||||
}
|
||||
const data = {
|
||||
Name: Doc.Name,
|
||||
URL: Doc.URL,
|
||||
URL: pdfUrl,
|
||||
SignedUrl: Doc.SignedUrl,
|
||||
SentToOthers: Doc.SentToOthers,
|
||||
Description: Doc.Description,
|
||||
@@ -672,7 +708,8 @@ export const createDocument = async (template, placeholders, signerData) => {
|
||||
Signers: signers,
|
||||
SendinOrder: Doc?.SendinOrder || false,
|
||||
AutomaticReminders: Doc?.AutomaticReminders || false,
|
||||
RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5)
|
||||
RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5),
|
||||
IsEnableOTP: Doc?.IsEnableOTP || false
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -762,7 +799,9 @@ export const onChangeInput = (
|
||||
initial,
|
||||
dateFormat,
|
||||
isDefaultEmpty,
|
||||
isRadio
|
||||
isRadio,
|
||||
fontSize,
|
||||
fontColor
|
||||
) => {
|
||||
const isSigners = xyPostion.some((data) => data.signerPtr);
|
||||
let filterSignerPos;
|
||||
@@ -789,6 +828,8 @@ export const onChangeInput = (
|
||||
options: {
|
||||
...position.options,
|
||||
response: value,
|
||||
fontSize: fontSize,
|
||||
fontColor: fontColor,
|
||||
validation: {
|
||||
type: "date-format",
|
||||
format: dateFormat // This indicates the required date format explicitly.
|
||||
@@ -835,7 +876,6 @@ export const onChangeInput = (
|
||||
}
|
||||
} else {
|
||||
let getXYdata = xyPostion[index].pos;
|
||||
|
||||
const updatePosition = getXYdata.map((positionData) => {
|
||||
if (positionData.key === signKey) {
|
||||
if (dateFormat) {
|
||||
@@ -844,6 +884,8 @@ export const onChangeInput = (
|
||||
options: {
|
||||
...positionData.options,
|
||||
response: value,
|
||||
fontSize: fontSize,
|
||||
fontColor: fontColor,
|
||||
validation: {
|
||||
type: "date-format",
|
||||
format: dateFormat // This indicates the required date format explicitly.
|
||||
@@ -1043,13 +1085,18 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => {
|
||||
const textContent = documentId && `OpenSign™ DocumentId: ${documentId} `;
|
||||
const pages = pdfDoc.getPages();
|
||||
const page = pages[i];
|
||||
page.drawText(textContent, {
|
||||
x: 10,
|
||||
y: page.getHeight() - 10,
|
||||
size: fontSize,
|
||||
const getObj = compensateRotation(
|
||||
page.getRotation().angle,
|
||||
10,
|
||||
5,
|
||||
1,
|
||||
page.getSize(),
|
||||
fontSize,
|
||||
rgb(0.5, 0.5, 0.5),
|
||||
font,
|
||||
color: rgb(0.5, 0.5, 0.5)
|
||||
});
|
||||
page
|
||||
);
|
||||
page.drawText(textContent, getObj);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1243,6 +1290,21 @@ const calculateFontSize = (position, containerScale, signyourself) => {
|
||||
return font / containerScale;
|
||||
}
|
||||
};
|
||||
|
||||
const getWidgetsFontColor = (type) => {
|
||||
switch (type) {
|
||||
case "red":
|
||||
return rgb(1, 0, 0);
|
||||
case "black":
|
||||
return rgb(0, 0, 0);
|
||||
case "blue":
|
||||
return rgb(0, 0, 1);
|
||||
case "yellow":
|
||||
return rgb(0.9, 1, 0);
|
||||
default:
|
||||
return rgb(0, 0, 0);
|
||||
}
|
||||
};
|
||||
//function for embed multiple signature using pdf-lib
|
||||
export const multiSignEmbed = async (
|
||||
widgets,
|
||||
@@ -1351,6 +1413,9 @@ export const multiSignEmbed = async (
|
||||
return resizePos;
|
||||
}
|
||||
};
|
||||
const color = position?.options?.fontColor;
|
||||
const updateColorInRgb = getWidgetsFontColor(color);
|
||||
const fontSize = parseInt(position?.options?.fontSize || 13);
|
||||
const widgetTypeExist = [
|
||||
textWidget,
|
||||
textInputWidget,
|
||||
@@ -1361,11 +1426,11 @@ export const multiSignEmbed = async (
|
||||
"email"
|
||||
].includes(position.type);
|
||||
if (position.type === "checkbox") {
|
||||
let checkboxOptionGapFromTop, isCheck;
|
||||
let checkboxGapFromTop, isCheck;
|
||||
let y = yPos(position);
|
||||
const optionsFontSize = 13;
|
||||
const checkboxSize = 18;
|
||||
const checkboxTextGapFromLeft = 22;
|
||||
const optionsFontSize = fontSize || 13;
|
||||
const checkboxSize = fontSize;
|
||||
const checkboxTextGapFromLeft = fontSize + 5 || 22;
|
||||
if (position?.options?.values.length > 0) {
|
||||
position?.options?.values.forEach((item, ind) => {
|
||||
const checkboxRandomId = "checkbox" + randomId();
|
||||
@@ -1382,9 +1447,9 @@ export const multiSignEmbed = async (
|
||||
const checkbox = form.createCheckBox(checkboxRandomId);
|
||||
|
||||
if (ind > 0) {
|
||||
y = y + checkboxOptionGapFromTop;
|
||||
y = y + checkboxGapFromTop;
|
||||
} else {
|
||||
checkboxOptionGapFromTop = 26;
|
||||
checkboxGapFromTop = fontSize + 5 || 26;
|
||||
}
|
||||
|
||||
if (!position?.options?.isHideLabel) {
|
||||
@@ -1396,7 +1461,7 @@ export const multiSignEmbed = async (
|
||||
1,
|
||||
page.getSize(),
|
||||
optionsFontSize,
|
||||
rgb(0, 0, 0),
|
||||
updateColorInRgb,
|
||||
font,
|
||||
page
|
||||
);
|
||||
@@ -1427,20 +1492,6 @@ export const multiSignEmbed = async (
|
||||
signyourself
|
||||
);
|
||||
parseInt(fontSize);
|
||||
|
||||
const color = position?.options?.fontColor;
|
||||
let updateColorInRgb;
|
||||
if (color === "red") {
|
||||
updateColorInRgb = rgb(1, 0, 0);
|
||||
} else if (color === "black") {
|
||||
updateColorInRgb = rgb(0, 0, 0);
|
||||
} else if (color === "blue") {
|
||||
updateColorInRgb = rgb(0, 0, 1);
|
||||
} else if (color === "yellow") {
|
||||
updateColorInRgb = rgb(0.9, 1, 0);
|
||||
} else {
|
||||
updateColorInRgb = rgb(0, 0, 0);
|
||||
}
|
||||
let textContent;
|
||||
if (position?.options?.response) {
|
||||
textContent = position.options?.response;
|
||||
@@ -1519,6 +1570,8 @@ export const multiSignEmbed = async (
|
||||
y += 18; // Adjust the line height as needed
|
||||
}
|
||||
} else if (position.type === "dropdown") {
|
||||
const fontsize = parseInt(position?.options?.fontSize) || 12;
|
||||
|
||||
const dropdownRandomId = "dropdown" + randomId();
|
||||
const dropdown = form.createDropdown(dropdownRandomId);
|
||||
dropdown.addOptions(position?.options?.values);
|
||||
@@ -1527,31 +1580,41 @@ export const multiSignEmbed = async (
|
||||
} else if (position?.options?.defaultValue) {
|
||||
dropdown.select(position?.options?.defaultValue);
|
||||
}
|
||||
// Define the default appearance string
|
||||
// Example format: `/FontName FontSize Tf 0 g` where:
|
||||
// - `/FontName` is the name of the font (e.g., `/Helv` for Helvetica)
|
||||
// - `FontSize` is the size you want to set (e.g., 12)
|
||||
// - `Tf` specifies the font and size
|
||||
// - `0 g` sets the text color to black
|
||||
const defaultAppearance = `/Helv ${fontsize} Tf 0 g`;
|
||||
// Set the default appearance for the dropdown field
|
||||
dropdown.acroField.setDefaultAppearance(defaultAppearance);
|
||||
dropdown.setFontSize(fontsize);
|
||||
const dropdownObj = {
|
||||
x: xPos(position),
|
||||
y: yPos(position),
|
||||
width: widgetWidth,
|
||||
height: widgetHeight
|
||||
};
|
||||
dropdown.defaultUpdateAppearances(font);
|
||||
const dropdownOption = getWidgetPosition(page, dropdownObj, 1);
|
||||
const dropdownSelected = { ...dropdownOption, font: font };
|
||||
dropdown.defaultUpdateAppearances(font);
|
||||
dropdown.addToPage(page, dropdownSelected);
|
||||
dropdown.enableReadOnly();
|
||||
} else if (position.type === radioButtonWidget) {
|
||||
const radioRandomId = "radio" + randomId();
|
||||
const radioGroup = form.createRadioGroup(radioRandomId);
|
||||
let radioOptionGapFromTop;
|
||||
const optionsFontSize = 16;
|
||||
const radioTextGapFromLeft = 20;
|
||||
const radioSize = 18;
|
||||
const optionsFontSize = fontSize || 13;
|
||||
const radioTextGapFromLeft = fontSize + 5 || 20;
|
||||
const radioSize = fontSize;
|
||||
let y = yPos(position);
|
||||
if (position?.options?.values.length > 0) {
|
||||
position?.options?.values.forEach((item, ind) => {
|
||||
if (ind > 0) {
|
||||
y = y + radioOptionGapFromTop;
|
||||
} else {
|
||||
radioOptionGapFromTop = 25;
|
||||
radioOptionGapFromTop = fontSize + 10 || 25;
|
||||
}
|
||||
if (!position?.options?.isHideLabel) {
|
||||
// below line of code is used to embed label with radio button in pdf
|
||||
@@ -1563,7 +1626,7 @@ export const multiSignEmbed = async (
|
||||
1,
|
||||
page.getSize(),
|
||||
optionsFontSize,
|
||||
rgb(0, 0, 0),
|
||||
updateColorInRgb,
|
||||
font,
|
||||
page
|
||||
);
|
||||
@@ -1679,9 +1742,7 @@ export const contactBook = async (objectId) => {
|
||||
|
||||
//function for getting document details from contract_Documents class
|
||||
export const contractDocument = async (documentId) => {
|
||||
const data = {
|
||||
docId: documentId
|
||||
};
|
||||
const data = { docId: documentId };
|
||||
const documentDeatils = await axios
|
||||
.post(`${localStorage.getItem("baseUrl")}functions/getDocument`, data, {
|
||||
headers: {
|
||||
@@ -1799,29 +1860,26 @@ export const handleCopyNextToWidget = (
|
||||
setXyPostion,
|
||||
userId
|
||||
) => {
|
||||
const isSigners = xyPostion.some((data) => data.signerPtr);
|
||||
let filterSignerPos;
|
||||
//get position of previous widget and create new widget next to that widget on same data except
|
||||
// xPosition and key
|
||||
let newpos = position;
|
||||
let newposition = position;
|
||||
const calculateXPosition =
|
||||
parseInt(position.xPosition) +
|
||||
defaultWidthHeight(widgetType).width +
|
||||
resizeBorderExtraWidth();
|
||||
const newId = randomId();
|
||||
newpos = { ...newpos, xPosition: calculateXPosition, key: newId };
|
||||
newposition = { ...newposition, xPosition: calculateXPosition, key: newId };
|
||||
//if condition to create widget in request-sign flow
|
||||
if (isSigners) {
|
||||
if (userId) {
|
||||
filterSignerPos = xyPostion.filter((data) => data.Id === userId);
|
||||
}
|
||||
const getPlaceHolder = filterSignerPos[0]?.placeHolder;
|
||||
const getPageNumer = getPlaceHolder.filter(
|
||||
if (userId) {
|
||||
filterSignerPos = xyPostion.filter((data) => data.Id === userId);
|
||||
const getPlaceHolder = filterSignerPos && filterSignerPos[0]?.placeHolder;
|
||||
const getPageNumer = getPlaceHolder?.filter(
|
||||
(data) => data.pageNumber === index
|
||||
);
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
getXYdata.push(newpos);
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer && getPageNumer[0]?.pos;
|
||||
getXYdata.push(newposition);
|
||||
if (getPageNumer && getPageNumer.length > 0) {
|
||||
const newUpdateSignPos = getPlaceHolder.map((obj) => {
|
||||
if (obj.pageNumber === index) {
|
||||
return { ...obj, pos: getXYdata };
|
||||
@@ -1839,9 +1897,8 @@ export const handleCopyNextToWidget = (
|
||||
setXyPostion(newUpdateSigner);
|
||||
}
|
||||
} else {
|
||||
// else condition to create widget in sign-yourself flow
|
||||
let getXYdata = xyPostion[index].pos;
|
||||
getXYdata.push(newpos);
|
||||
let getXYdata = xyPostion[index]?.pos || [];
|
||||
getXYdata.push(newposition);
|
||||
const updatePlaceholder = xyPostion.map((obj, ind) => {
|
||||
if (ind === index) {
|
||||
return { ...obj, pos: getXYdata };
|
||||
@@ -1981,28 +2038,30 @@ export const fetchUrl = async (url, pdfName) => {
|
||||
console.error("Error downloading the file:", error);
|
||||
}
|
||||
};
|
||||
//handle download signed pdf
|
||||
export const handleDownloadPdf = async (
|
||||
pdfDetails,
|
||||
pdfUrl,
|
||||
setIsDownloading
|
||||
) => {
|
||||
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
|
||||
setIsDownloading("pdf");
|
||||
try {
|
||||
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
|
||||
const axiosRes = await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
|
||||
{ url: pdfUrl },
|
||||
{
|
||||
headers: {
|
||||
"content-type": "Application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
export const getSignedUrl = async (pdfUrl, docId) => {
|
||||
//use only axios here due to public template sign
|
||||
const axiosRes = await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
|
||||
{ url: pdfUrl, docId: docId || "" },
|
||||
{
|
||||
headers: {
|
||||
"content-type": "Application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
);
|
||||
const url = axiosRes.data.result;
|
||||
}
|
||||
);
|
||||
const url = axiosRes.data.result;
|
||||
return url;
|
||||
};
|
||||
//handle download signed pdf
|
||||
export const handleDownloadPdf = async (pdfDetails, setIsDownloading) => {
|
||||
const pdfName = pdfDetails[0] && pdfDetails[0]?.Name;
|
||||
const pdfUrl = pdfDetails?.[0]?.SignedUrl || pdfDetails?.[0]?.URL;
|
||||
setIsDownloading("pdf");
|
||||
const docId = !pdfDetails?.[0]?.IsEnableOTP ? pdfDetails?.[0]?.objectId : "";
|
||||
try {
|
||||
const url = await getSignedUrl(pdfUrl, docId);
|
||||
await fetchUrl(url, pdfName);
|
||||
setIsDownloading("");
|
||||
} catch (err) {
|
||||
@@ -2017,16 +2076,22 @@ export const sanitizeFileName = (pdfName) => {
|
||||
return pdfName.replace(/ /g, "_");
|
||||
};
|
||||
//function for print digital sign pdf
|
||||
export const handleToPrint = async (event, pdfUrl, setIsDownloading) => {
|
||||
export const handleToPrint = async (
|
||||
event,
|
||||
pdfUrl,
|
||||
setIsDownloading,
|
||||
pdfDetails
|
||||
) => {
|
||||
event.preventDefault();
|
||||
setIsDownloading("pdf");
|
||||
const docId = !pdfDetails?.[0]?.IsEnableOTP ? pdfDetails?.[0]?.objectId : "";
|
||||
try {
|
||||
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
|
||||
//`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
|
||||
const axiosRes = await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
|
||||
{ url: pdfUrl },
|
||||
{ url: pdfUrl, docId: docId },
|
||||
{
|
||||
headers: {
|
||||
"content-type": "Application/json",
|
||||
@@ -2066,13 +2131,18 @@ export const handleToPrint = async (event, pdfUrl, setIsDownloading) => {
|
||||
//handle download signed pdf
|
||||
export const handleDownloadCertificate = async (
|
||||
pdfDetails,
|
||||
setIsDownloading
|
||||
setIsDownloading,
|
||||
isZip
|
||||
) => {
|
||||
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
|
||||
try {
|
||||
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
|
||||
const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl;
|
||||
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
|
||||
if (isZip) {
|
||||
return certificateUrl;
|
||||
} else {
|
||||
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in download in certificate", err);
|
||||
}
|
||||
@@ -2098,7 +2168,13 @@ export const handleDownloadCertificate = async (
|
||||
if (doc?.CertificateUrl) {
|
||||
await fetch(doc?.CertificateUrl);
|
||||
const certificateUrl = doc?.CertificateUrl;
|
||||
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
|
||||
if (isZip) {
|
||||
setIsDownloading("");
|
||||
return certificateUrl;
|
||||
} else {
|
||||
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
|
||||
}
|
||||
|
||||
setIsDownloading("");
|
||||
} else {
|
||||
setIsDownloading("certificate");
|
||||
@@ -2142,9 +2218,11 @@ function compensateRotation(
|
||||
font,
|
||||
page
|
||||
) {
|
||||
// Ensure pageRotation is between 0 and 360 degrees
|
||||
pageRotation = ((pageRotation % 360) + 360) % 360;
|
||||
let rotationRads = (pageRotation * Math.PI) / 180;
|
||||
|
||||
//These coords are now from bottom/left
|
||||
// Coordinates are from bottom-left
|
||||
let coordsFromBottomLeft = { x: x / scale };
|
||||
if (pageRotation === 90 || pageRotation === 270) {
|
||||
coordsFromBottomLeft.y = dimensions.width - (y + fontSize) / scale;
|
||||
@@ -2154,6 +2232,7 @@ function compensateRotation(
|
||||
|
||||
let drawX = null;
|
||||
let drawY = null;
|
||||
|
||||
if (pageRotation === 90) {
|
||||
drawX =
|
||||
coordsFromBottomLeft.x * Math.cos(rotationRads) -
|
||||
@@ -2179,8 +2258,8 @@ function compensateRotation(
|
||||
coordsFromBottomLeft.x * Math.sin(rotationRads) +
|
||||
coordsFromBottomLeft.y * Math.cos(rotationRads) +
|
||||
dimensions.height;
|
||||
} else {
|
||||
//no rotation
|
||||
} else if (pageRotation === 0 || pageRotation === 360) {
|
||||
// No rotation or full rotation
|
||||
drawX = coordsFromBottomLeft.x;
|
||||
drawY = coordsFromBottomLeft.y;
|
||||
}
|
||||
@@ -2284,3 +2363,156 @@ export const getDefaultSignature = async (objectId) => {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
//function to rotate pdf page
|
||||
export async function rotatePdfPage(
|
||||
url,
|
||||
rotateDegree,
|
||||
pageNumber,
|
||||
pdfRotateBase64
|
||||
) {
|
||||
let file;
|
||||
|
||||
//condition to handle pdf base64 in arrayBuffer format
|
||||
if (pdfRotateBase64) {
|
||||
file = base64ToArrayBuffer(pdfRotateBase64);
|
||||
} else {
|
||||
//condition to handle pdf url in arrayBuffer format
|
||||
file = await convertPdfArrayBuffer(url);
|
||||
}
|
||||
// Load the existing PDF
|
||||
const pdfDoc = await PDFDocument.load(file);
|
||||
// Get the page according to page number
|
||||
const page = pdfDoc.getPage(pageNumber);
|
||||
//get current page rotation angle
|
||||
const currentRotation = page.getRotation().angle;
|
||||
// Apply the rotation in the counterclockwise direction
|
||||
let newRotation = (currentRotation + rotateDegree) % 360;
|
||||
// Adjust for negative angles to keep within 0-359 range
|
||||
if (newRotation < 0) {
|
||||
newRotation += 360;
|
||||
}
|
||||
page.setRotation(degrees(newRotation));
|
||||
const pdfbase64 = await pdfDoc.saveAsBase64({ useObjectStreams: false });
|
||||
//convert base64 to arraybuffer is used in pdf-lib
|
||||
//pdfbase64 is used to show pdf rotated format
|
||||
const arrayBuffer = base64ToArrayBuffer(pdfbase64);
|
||||
//`base64` is used to show pdf
|
||||
return { arrayBuffer: arrayBuffer, base64: pdfbase64 };
|
||||
}
|
||||
function base64ToArrayBuffer(base64) {
|
||||
// Decode the base64 string to a binary string
|
||||
const binaryString = atob(base64);
|
||||
// Create a new ArrayBuffer with the same length as the binary string
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
// Convert the binary string to a byte array
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
// Return the ArrayBuffer
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
export const convertBase64ToFile = async (pdfName, pdfBase64) => {
|
||||
const fileName = sanitizeFileName(pdfName) + ".pdf";
|
||||
const pdfFile = new Parse.File(fileName, { base64: pdfBase64 });
|
||||
// Save the Parse File if needed
|
||||
const pdfData = await pdfFile.save();
|
||||
const pdfUrl = pdfData.url();
|
||||
return pdfUrl;
|
||||
};
|
||||
export const onClickZoomIn = (scale, zoomPercent, setScale, setZoomPercent) => {
|
||||
setScale(scale + 0.1 * scale);
|
||||
setZoomPercent(zoomPercent + 10);
|
||||
};
|
||||
export const onClickZoomOut = (
|
||||
zoomPercent,
|
||||
scale,
|
||||
setZoomPercent,
|
||||
setScale
|
||||
) => {
|
||||
if (zoomPercent > 0) {
|
||||
if (zoomPercent === 10) {
|
||||
setScale(1);
|
||||
} else {
|
||||
setScale(scale - 0.1 * scale);
|
||||
}
|
||||
setZoomPercent(zoomPercent - 10);
|
||||
}
|
||||
};
|
||||
|
||||
//function to use remove widgets from current page when user want to rotate page
|
||||
export const handleRemoveWidgets = (
|
||||
setSignerPos,
|
||||
signerPos,
|
||||
pageNumber,
|
||||
setIsRotate
|
||||
) => {
|
||||
const updatedSignerPos = signerPos.map((placeholderObj) => {
|
||||
return {
|
||||
...placeholderObj,
|
||||
placeHolder: placeholderObj?.placeHolder?.filter(
|
||||
(data) => data?.pageNumber !== pageNumber
|
||||
)
|
||||
};
|
||||
});
|
||||
setSignerPos(updatedSignerPos);
|
||||
setIsRotate({
|
||||
status: false,
|
||||
degree: 0
|
||||
});
|
||||
};
|
||||
//function to show warning when user rotate page and there are some already widgets on that page
|
||||
export const handleRotateWarning = (signerPos, pageNumber) => {
|
||||
const placeholderExist = signerPos?.some((placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((data) => data?.pageNumber === pageNumber)
|
||||
);
|
||||
if (placeholderExist) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// `generateTitleFromFilename` to generate Title of document from file name
|
||||
export function generateTitleFromFilename(filename) {
|
||||
try {
|
||||
// Step 1: Trim whitespace
|
||||
let title = filename.trim();
|
||||
|
||||
// Step 2: Remove the file extension (everything after the last '.')
|
||||
const lastDotIndex = title.lastIndexOf(".");
|
||||
if (lastDotIndex > 0) {
|
||||
title = title.substring(0, lastDotIndex);
|
||||
}
|
||||
|
||||
// Step 3: Replace special characters (except Unicode letters, digits, spaces, and hyphens)
|
||||
title = title.replace(/[^\p{L}\p{N}\s-]/gu, " ");
|
||||
|
||||
// Step 4: Replace multiple spaces with a single space
|
||||
title = title.replace(/\s+/g, " ");
|
||||
|
||||
// Step 5: Capitalize first letter of each word (Title Case), handling Unicode characters
|
||||
title = title.replace(
|
||||
/\p{L}\S*/gu,
|
||||
(txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
|
||||
);
|
||||
|
||||
// Step 6: Restrict length of title (optional, let's say 100 characters)
|
||||
if (title.length > 100) {
|
||||
title = title.substring(0, 100).trim();
|
||||
}
|
||||
|
||||
// Step 7: Handle empty or invalid title by falling back to "Untitled Document"
|
||||
if (!title || title.length === 0) {
|
||||
return "Untitled Document";
|
||||
}
|
||||
|
||||
return title;
|
||||
} catch (error) {
|
||||
// Handle unexpected errors gracefully by returning a default title
|
||||
console.error("Error generating title from filename:", error);
|
||||
return "Untitled Document";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { serverUrl_fn } from "./appinfo";
|
||||
const parseAppId = process.env.REACT_APP_APPID
|
||||
? process.env.REACT_APP_APPID
|
||||
: "opensign";
|
||||
const serverUrl = serverUrl_fn()
|
||||
const serverUrl = serverUrl_fn();
|
||||
export const SaveFileSize = async (size, imageUrl, tenantId) => {
|
||||
//checking server url and save file's size
|
||||
const tenantPtr = {
|
||||
|
||||
@@ -12,23 +12,35 @@ body {
|
||||
}
|
||||
|
||||
.hide-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none;
|
||||
/* IE and Edge */
|
||||
scrollbar-width: none;
|
||||
/* Firefox */
|
||||
}
|
||||
|
||||
@media screen and (max-width: 766px) {
|
||||
.bBofGr {
|
||||
width: 14px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* below css class replace bootstrap background color with daisy */
|
||||
.op-bg-primary {
|
||||
@apply bg-primary;
|
||||
}
|
||||
|
||||
.op-bg-secondary {
|
||||
@apply bg-secondary;
|
||||
}
|
||||
|
||||
.op-bg-info {
|
||||
@apply bg-info;
|
||||
}
|
||||
.op-bg-success{
|
||||
|
||||
.op-bg-success {
|
||||
@apply bg-success;
|
||||
}
|
||||
|
||||
.op-bg-warning {
|
||||
@apply bg-warning;
|
||||
}
|
||||
@@ -37,25 +49,37 @@ body {
|
||||
.op-text-primary {
|
||||
@apply text-primary;
|
||||
}
|
||||
|
||||
.op-text-secondary {
|
||||
@apply text-secondary;
|
||||
}
|
||||
|
||||
.op-text-info {
|
||||
@apply text-info;
|
||||
}
|
||||
.op-text-success{
|
||||
|
||||
.op-text-accent {
|
||||
@apply text-accent;
|
||||
}
|
||||
|
||||
.op-text-success {
|
||||
@apply text-success;
|
||||
}
|
||||
|
||||
.op-text-warning {
|
||||
@apply text-warning;
|
||||
}
|
||||
.op-border-primary{
|
||||
@apply border-primary
|
||||
|
||||
.op-border-primary {
|
||||
@apply border-primary;
|
||||
}
|
||||
|
||||
/* CSS for scrollbar customization */
|
||||
* {
|
||||
scrollbar-width: thin; /* For Firefox */
|
||||
scrollbar-color: gray; /* For Firefox */
|
||||
scrollbar-width: thin;
|
||||
/* For Firefox */
|
||||
scrollbar-color: gray;
|
||||
/* For Firefox */
|
||||
}
|
||||
|
||||
/* For webkit-based browsers like Chrome, Safari */
|
||||
|
||||
@@ -10,6 +10,15 @@ export default function reportJson(id) {
|
||||
"Owner",
|
||||
"Signers"
|
||||
];
|
||||
const iphead = [
|
||||
"Sr.No",
|
||||
"Title",
|
||||
"Note",
|
||||
"Folder",
|
||||
"File",
|
||||
"Status",
|
||||
"Signers"
|
||||
];
|
||||
const contactbook = ["Sr.No", "Name", "Email", "Phone"];
|
||||
const dashboardReportHead = ["Title", "File", "Owner", "Signers"];
|
||||
const templateReport = ["Sr.No", "Title", "File", "Owner", "Signers"];
|
||||
@@ -63,7 +72,7 @@ export default function reportJson(id) {
|
||||
case "1MwEuxLEkF":
|
||||
return {
|
||||
reportName: "In-progress documents",
|
||||
heading: head,
|
||||
heading: iphead,
|
||||
actions: [
|
||||
{
|
||||
btnId: "8901",
|
||||
@@ -354,6 +363,13 @@ export default function reportJson(id) {
|
||||
redirectUrl: "template",
|
||||
action: "redirect"
|
||||
},
|
||||
{
|
||||
btnId: "2434",
|
||||
btnLabel: "Embed",
|
||||
hoverLabel: "Embed",
|
||||
btnIcon: "fa-light fa-code",
|
||||
action: "Embed"
|
||||
},
|
||||
{
|
||||
btnId: "1834",
|
||||
btnLabel: "Delete",
|
||||
|
||||
@@ -36,7 +36,7 @@ const dashboardJson = [
|
||||
queryType: "",
|
||||
class: "contracts_Document",
|
||||
query:
|
||||
'where={"Type":{"#*ne":"Folder"},"Signers":{"#*exists":true,"#*ne":[]},"Placeholders":{"#*ne":null},"IsCompleted":{"#*ne":true},"IsDeclined":{"#*ne":true},"IsArchive":{"#*ne":true},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"#UserId.objectId#"},"ExpiryDate":{"#*gt":{"__type":"#Date#","iso":"#today#"}}}&count=1',
|
||||
'where={"Type":{"#*ne":"Folder"},"Signers":{"#*exists":true,"#*ne":[]},"Placeholders":{"#*ne":null},"SignedUrl":{"#*ne":null},"IsCompleted":{"#*ne":true},"IsDeclined":{"#*ne":true},"IsArchive":{"#*ne":true},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"#UserId.objectId#"},"ExpiryDate":{"#*gt":{"__type":"#Date#","iso":"#today#"}}}&count=1',
|
||||
key: "count",
|
||||
Redirect_type: "Report",
|
||||
Redirect_id: "1MwEuxLEkF",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isStaging } from "../constant/const";
|
||||
const plans = [
|
||||
{
|
||||
planName: "OPENSIGN™ FREE",
|
||||
code: { monthly: "freeplan", yearly: "freeplan" },
|
||||
img: "free.png",
|
||||
currency: "",
|
||||
monthlyPrice: "Free",
|
||||
@@ -12,7 +13,7 @@ const plans = [
|
||||
url: "",
|
||||
target: "_blank",
|
||||
benefits: [
|
||||
"Unlimited envelopes",
|
||||
"Unlimited digital signatures",
|
||||
"Sign documents yourself",
|
||||
"Request signatures from others",
|
||||
"14 field types",
|
||||
@@ -26,12 +27,11 @@ const plans = [
|
||||
"Decline document support",
|
||||
"Email notifications",
|
||||
"Recipient authentication using OTP",
|
||||
"Bulk send (upto 5 docs)",
|
||||
"Public profiles",
|
||||
"And much more"
|
||||
],
|
||||
yearlyBenefits: [
|
||||
"Unlimited envelopes",
|
||||
"Unlimited digital signatures",
|
||||
"Sign documents yourself",
|
||||
"Request signatures from others",
|
||||
"14 field types",
|
||||
@@ -45,13 +45,15 @@ const plans = [
|
||||
"Decline document support",
|
||||
"Email notifications",
|
||||
"Recipient authentication using OTP",
|
||||
"Bulk send (upto 5 docs)",
|
||||
"Public profiles",
|
||||
"And much more"
|
||||
]
|
||||
},
|
||||
{
|
||||
planName: "OPENSIGN™ PROFESSIONAL",
|
||||
code: isStaging
|
||||
? { monthly: "pro-weekly", yearly: "pro-yearly" }
|
||||
: { monthly: "professional-monthly", yearly: "professional-yearly" },
|
||||
img: "professional.png",
|
||||
currency: "$",
|
||||
monthlyPrice: "29.99",
|
||||
@@ -73,10 +75,10 @@ const plans = [
|
||||
"Webhooks",
|
||||
"Zapier integration",
|
||||
"API Access",
|
||||
"100 API signatures included",
|
||||
"upto 100 API signatures",
|
||||
"Custom email templates",
|
||||
"Auto reminders",
|
||||
"Bulk send (upto 20 docs)",
|
||||
"Bulk send (upto 100 docs)",
|
||||
"Premium Public profile usernames",
|
||||
"Embedding (coming soon)"
|
||||
],
|
||||
@@ -88,16 +90,17 @@ const plans = [
|
||||
"Webhooks",
|
||||
"Zapier integration",
|
||||
"API Access",
|
||||
"240 API signatures included",
|
||||
"upto 240 API signatures",
|
||||
"Custom email templates",
|
||||
"Auto reminders",
|
||||
"Bulk send (upto 20 docs)",
|
||||
"Bulk send (upto 240 docs)",
|
||||
"Premium Public profile usernames",
|
||||
"Embedding (coming soon)"
|
||||
]
|
||||
},
|
||||
{
|
||||
planName: "OPENSIGN™ TEAMS",
|
||||
code: { monthly: "teams-monthly", yearly: "teams-yearly" },
|
||||
img: "teams.png",
|
||||
currency: "$",
|
||||
monthlyPrice: `39.99<sup style="font-size: 17px;">/user</sup>`,
|
||||
@@ -105,31 +108,31 @@ const plans = [
|
||||
subtitle: "Exclusive Access to advanced features.",
|
||||
btn: { text: "Subscribe", color: "op-btn-accent" },
|
||||
url: isStaging
|
||||
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/team-weekly"
|
||||
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-monthly"
|
||||
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901237d0702bfaa959406306635d80f138c/teams-monthly",
|
||||
yearlyUrl: isStaging
|
||||
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/team-weekly"
|
||||
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-yearly"
|
||||
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af9011a864994bbeeec71fcf106188630199d/teams-yearly",
|
||||
target: "_self",
|
||||
benefits: [
|
||||
"Everything in OpenSign™ professional",
|
||||
"100 API signatures included",
|
||||
"upto 100 API signatures",
|
||||
"Teams and Organizations",
|
||||
"Share Templates with teams",
|
||||
"Share Templates with individuals",
|
||||
"DocumentId removal from signed docs",
|
||||
"Bulk send (upto 50 docs)",
|
||||
"Bulk send (upto 100 docs)",
|
||||
"Request Payments (coming soon)",
|
||||
"Mobile app (coming soon)"
|
||||
],
|
||||
yearlyBenefits: [
|
||||
"Everything in OpenSign™ professional",
|
||||
"500 API signatures included",
|
||||
"upto 500 API signatures",
|
||||
"Teams and Organizations",
|
||||
"Share Templates with teams",
|
||||
"Share Templates with individuals",
|
||||
"DocumentId removal from signed docs",
|
||||
"Bulk send (upto 50 docs)",
|
||||
"Bulk send (upto 500 docs)",
|
||||
"Request Payments (coming soon)",
|
||||
"Mobile app (coming soon)"
|
||||
]
|
||||
@@ -171,4 +174,74 @@ export const validplan = {
|
||||
"enterprise-monthly": true,
|
||||
"enterprise-yearly": true
|
||||
};
|
||||
export const paidUrl = (plan) => {
|
||||
const teamperiod = {
|
||||
"team-weekly": "monthly",
|
||||
"team-yearly": "yearly",
|
||||
"teams-monthly": "monthly",
|
||||
"teams-yearly": "yearly"
|
||||
};
|
||||
const period = teamperiod[plan] || "";
|
||||
if (period) {
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
const user = {
|
||||
name: extUser?.Name,
|
||||
email: extUser?.Email,
|
||||
company: extUser?.Company,
|
||||
phone: extUser?.Phone
|
||||
};
|
||||
// console.log("userDetails ", userDetails);
|
||||
const fullname = user && user.name ? user.name.split(" ") : "";
|
||||
const firstname = fullname?.[0]
|
||||
? "first_name=" + encodeURIComponent(fullname?.[0])
|
||||
: "";
|
||||
const lastname = fullname?.[1]
|
||||
? "&last_name=" + encodeURIComponent(fullname?.[1])
|
||||
: "";
|
||||
const name = firstname ? firstname + lastname : "";
|
||||
const email =
|
||||
user && user.email ? "&email=" + encodeURIComponent(user.email) : "";
|
||||
const company =
|
||||
user && user.company
|
||||
? "&company_name=" + encodeURIComponent(user.company)
|
||||
: "";
|
||||
const phone =
|
||||
user && user.phone ? "&mobile=" + encodeURIComponent(user.phone) : "";
|
||||
const allowedUsers =
|
||||
localStorage.getItem("allowedUsers") &&
|
||||
localStorage.getItem("allowedUsers") > 0
|
||||
? localStorage.getItem("allowedUsers") - 1
|
||||
: "";
|
||||
const quantity = allowedUsers
|
||||
? `addon_code%5B0%5D=extra-teams-users-${period}&addon_quantity%5B0%5D=${allowedUsers}&`
|
||||
: "";
|
||||
|
||||
const details =
|
||||
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
|
||||
quantity +
|
||||
name +
|
||||
email +
|
||||
company +
|
||||
phone;
|
||||
|
||||
if (user) {
|
||||
localStorage.setItem("userDetails", JSON.stringify(user));
|
||||
}
|
||||
const url = {
|
||||
monthly: isStaging
|
||||
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-monthly"
|
||||
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901237d0702bfaa959406306635d80f138c/teams-monthly",
|
||||
yearly: isStaging
|
||||
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-yearly"
|
||||
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af9011a864994bbeeec71fcf106188630199d/teams-yearly"
|
||||
};
|
||||
|
||||
const planurl = url[period] + details;
|
||||
return planurl;
|
||||
} else {
|
||||
return "/subscription";
|
||||
}
|
||||
};
|
||||
export default plans;
|
||||
|
||||
@@ -95,7 +95,9 @@ const HomeLayout = () => {
|
||||
domain: updateDomain
|
||||
});
|
||||
};
|
||||
|
||||
const handleNavigation = () => {
|
||||
navigate("/subscription");
|
||||
};
|
||||
async function checkIsSubscribed() {
|
||||
if (isEnableSubscription) {
|
||||
const res = await fetchSubscription();
|
||||
@@ -107,10 +109,10 @@ const HomeLayout = () => {
|
||||
setIsUserValid(true);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
handleNavigation(res.plan);
|
||||
}
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
handleNavigation(res.plan);
|
||||
}
|
||||
} else {
|
||||
setIsUserValid(true);
|
||||
@@ -250,9 +252,9 @@ const HomeLayout = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginBtn = () => {
|
||||
const handleLoginBtn = async () => {
|
||||
try {
|
||||
Parse?.User?.logOut();
|
||||
await Parse?.User?.logOut();
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
} finally {
|
||||
|
||||
@@ -56,8 +56,10 @@ const AddAdmin = () => {
|
||||
}
|
||||
};
|
||||
const clearStorage = async () => {
|
||||
if (Parse.User.current()) {
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
const baseUrl = localStorage.getItem("baseUrl");
|
||||
const appid = localStorage.getItem("parseAppId");
|
||||
@@ -459,13 +461,13 @@ const AddAdmin = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
id="termsandcondition"
|
||||
id="subscribetoopensign"
|
||||
checked={isSubscribeNews}
|
||||
onChange={(e) => setIsSubscribeNews(e.target.checked)}
|
||||
/>
|
||||
<label
|
||||
className="text-xs cursor-pointer ml-1 mb-0"
|
||||
htmlFor="termsandcondition"
|
||||
htmlFor="subscribetoopensign"
|
||||
>
|
||||
{t("subscribe-to-opensign")}
|
||||
</label>
|
||||
|
||||
@@ -74,7 +74,7 @@ const DebugPdf = () => {
|
||||
|
||||
const inferPdfType = async (pdf) => {
|
||||
try {
|
||||
const firstPage = await pdf.getPage(2);
|
||||
const firstPage = await pdf.getPage(pdf?.numPages > 1 ? 2 : 1);
|
||||
const scale = 1;
|
||||
const { width, height } = firstPage.getViewport({ scale });
|
||||
setPdfDimension({ width: width, height: height });
|
||||
@@ -215,11 +215,7 @@ const DebugPdf = () => {
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{copied && (
|
||||
<Alert type="success">
|
||||
<span className="ml-3">Copied</span>
|
||||
</Alert>
|
||||
)}
|
||||
{copied && <Alert type="success">Copied</Alert>}
|
||||
<Title title={"Debug Pdf"} />
|
||||
{width < 800 ? (
|
||||
<HandleError handleError={"Debug PDF only availble for PC"} />
|
||||
|
||||
@@ -15,11 +15,7 @@ function ForgotPassword() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const [state, setState] = useState({
|
||||
email: "",
|
||||
password: "",
|
||||
hideNav: ""
|
||||
});
|
||||
const [state, setState] = useState({ email: "", password: "", hideNav: "" });
|
||||
const [sentStatus, setSentStatus] = useState("");
|
||||
const [image, setImage] = useState();
|
||||
|
||||
@@ -65,6 +61,11 @@ function ForgotPassword() {
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
const saveLogo = async () => {
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("err while logging out ", err);
|
||||
}
|
||||
if (isEnableSubscription) {
|
||||
const app = await getAppLogo();
|
||||
if (app?.logo) {
|
||||
|
||||
+238
-122
@@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { formJson } from "../json/FormJson";
|
||||
import AddUser from "../components/AddUser";
|
||||
import Parse from "parse";
|
||||
import DropboxChooser from "../components/shared/fields/DropboxChoose";
|
||||
import Alert from "../primitives/Alert";
|
||||
@@ -10,7 +9,12 @@ import SignersInput from "../components/shared/fields/SignersInput";
|
||||
import Title from "../components/Title";
|
||||
import PageNotFound from "./PageNotFound";
|
||||
import { SaveFileSize } from "../constant/saveFileSize";
|
||||
import { checkIsSubscribed, getFileName, toDataUrl } from "../constant/Utils";
|
||||
import {
|
||||
checkIsSubscribed,
|
||||
generateTitleFromFilename,
|
||||
getFileName,
|
||||
toDataUrl
|
||||
} from "../constant/Utils";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import axios from "axios";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
@@ -37,15 +41,11 @@ function generatePdfName(length) {
|
||||
function Form() {
|
||||
const { id } = useParams();
|
||||
|
||||
if (id === "lM0xRnM3iE") {
|
||||
return <AddUser />;
|
||||
const config = formJson[id];
|
||||
if (config) {
|
||||
return <Forms {...config} />;
|
||||
} else {
|
||||
const config = formJson[id];
|
||||
if (config) {
|
||||
return <Forms {...config} />;
|
||||
} else {
|
||||
return <PageNotFound prefix={"Form"} />;
|
||||
}
|
||||
return <PageNotFound prefix={"Form"} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ const Forms = (props) => {
|
||||
password: "",
|
||||
file: "",
|
||||
remindOnceInEvery: 5,
|
||||
autoreminder: false
|
||||
autoreminder: false,
|
||||
IsEnableOTP: "false"
|
||||
});
|
||||
const [fileupload, setFileUpload] = useState("");
|
||||
const [fileload, setfileload] = useState(false);
|
||||
@@ -79,6 +80,7 @@ const Forms = (props) => {
|
||||
const [isDecrypting, setIsDecrypting] = useState(false);
|
||||
const [isCorrectPass, setIsCorrectPass] = useState(true);
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
const [isAdvanceOpt, setIsAdvanceOpt] = useState(false);
|
||||
const handleStrInput = (e) => {
|
||||
setIsCorrectPass(true);
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
@@ -95,8 +97,8 @@ const Forms = (props) => {
|
||||
}, []);
|
||||
const fetchSubscription = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe.isValid);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -142,12 +144,11 @@ const Forms = (props) => {
|
||||
} catch (err) {
|
||||
console.log("err in sending posthog encryptedpdf", err);
|
||||
}
|
||||
// console.log("err ", err);
|
||||
try {
|
||||
setIsDecrypting(true);
|
||||
const size = files?.[0].size;
|
||||
const name = generatePdfName(16);
|
||||
const url = "https://ai.nxglabs.in/decryptpdf"; //"https://ai.nxglabs.in/decryptpdf"; //
|
||||
const url = "https://ai.nxglabs.in/decryptpdf"; //
|
||||
let formData = new FormData();
|
||||
formData.append("file", files[0]);
|
||||
formData.append("password", "");
|
||||
@@ -190,6 +191,9 @@ const Forms = (props) => {
|
||||
if (parseFile.url()) {
|
||||
setFileUpload(parseFile.url());
|
||||
setfileload(false);
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, response.url(), tenantId);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, parseFile.url(), tenantId);
|
||||
return parseFile.url();
|
||||
@@ -263,6 +267,8 @@ const Forms = (props) => {
|
||||
setfileload(false);
|
||||
if (response.url()) {
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, response.url(), tenantId);
|
||||
return response.url();
|
||||
}
|
||||
@@ -290,12 +296,17 @@ const Forms = (props) => {
|
||||
if (res.data) {
|
||||
setFileUpload(res.data.url);
|
||||
setfileload(false);
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
}
|
||||
} catch (err) {
|
||||
e.target.value = "";
|
||||
setfileload(false);
|
||||
setpercentage(0);
|
||||
console.log("err in libreconverter ", err);
|
||||
alert(
|
||||
"We are currently experiencing some issues with processing DOCX files. Please upload the PDF version or contact us on support@opensignlabs.com"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,6 +345,8 @@ const Forms = (props) => {
|
||||
setfileload(false);
|
||||
if (response.url()) {
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const title = generateTitleFromFilename(file.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, response.url(), tenantId);
|
||||
return response.url();
|
||||
}
|
||||
@@ -371,9 +384,10 @@ const Forms = (props) => {
|
||||
});
|
||||
setFileUpload(response.url());
|
||||
setfileload(false);
|
||||
|
||||
if (response.url()) {
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const title = generateTitleFromFilename(file.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, response.url(), tenantId);
|
||||
return response.url();
|
||||
}
|
||||
@@ -407,6 +421,13 @@ const Forms = (props) => {
|
||||
object.set("SendinOrder", isChecked);
|
||||
object.set("AutomaticReminders", formData.autoreminder);
|
||||
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("CreatedBy", Parse.User.createWithoutData(currentUser.id));
|
||||
@@ -433,7 +454,17 @@ const Forms = (props) => {
|
||||
setFormData({
|
||||
Name: "",
|
||||
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("");
|
||||
setpercentage(0);
|
||||
@@ -482,7 +513,8 @@ const Forms = (props) => {
|
||||
password: "",
|
||||
file: "",
|
||||
remindOnceInEvery: 5,
|
||||
autoreminder: false
|
||||
autoreminder: false,
|
||||
IsEnableOTP: "false"
|
||||
});
|
||||
setFileUpload("");
|
||||
setpercentage(0);
|
||||
@@ -534,6 +566,8 @@ const Forms = (props) => {
|
||||
setFormData((prev) => ({ ...prev, password: "" }));
|
||||
setFileUpload(parseFile.url());
|
||||
setfileload(false);
|
||||
const title = generateTitleFromFilename(formData?.file?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, parseFile.url(), tenantId);
|
||||
return parseFile.url();
|
||||
@@ -646,7 +680,7 @@ const Forms = (props) => {
|
||||
)}
|
||||
<div className="text-xs">
|
||||
<label className="block">
|
||||
{`${t("report-heading.File")} (${t("file-type")} ${
|
||||
{`${t("report-heading.File")} (${t("file-type")}${
|
||||
isEnableSubscription ? ", docx)" : ")"
|
||||
}`}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
@@ -759,120 +793,73 @@ const Forms = (props) => {
|
||||
isReset={isReset}
|
||||
/>
|
||||
)}
|
||||
{props.title === "Request Signatures" && (
|
||||
{props.title !== "Sign Yourself" && (
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
{t("time-to-complete")}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
{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>
|
||||
<input
|
||||
type="number"
|
||||
name="TimeToCompleteDays"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
value={formData.TimeToCompleteDays}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
<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>
|
||||
)}
|
||||
{props.title !== "Sign Yourself" && (
|
||||
<>
|
||||
<div className="text-xs mt-2">
|
||||
<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 && (
|
||||
{isAdvanceOpt && (
|
||||
<div className="overflow-y-auto z-[500] transition-all">
|
||||
{props.title === "Request Signatures" && (
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
{t("remind-once")}
|
||||
{t("time-to-complete")}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.remindOnceInEvery}
|
||||
name="remindOnceInEvery"
|
||||
name="TimeToCompleteDays"
|
||||
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) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
@@ -881,7 +868,136 @@ const Forms = (props) => {
|
||||
/>
|
||||
</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">
|
||||
<button
|
||||
|
||||
@@ -10,17 +10,39 @@ import Loader from "../primitives/Loader";
|
||||
import SubscribeCard from "../primitives/SubscribeCard";
|
||||
import Tour from "reactour";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Parse from "parse";
|
||||
|
||||
function GenerateToken() {
|
||||
const origin = window.location.origin;
|
||||
const { t } = useTranslation();
|
||||
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||
const parseBaseUrl = localStorage.getItem("baseUrl");
|
||||
const parseAppId = localStorage.getItem("parseAppId");
|
||||
const [apiToken, SetApiToken] = useState("");
|
||||
const [isLoader, setIsLoader] = useState(true);
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
const [isModal, setIsModal] = useState({
|
||||
generateapi: false,
|
||||
buyapis: false,
|
||||
unauthorized: false
|
||||
});
|
||||
const [isSubscribe, setIsSubscribe] = useState({
|
||||
plan: "",
|
||||
isValid: false,
|
||||
adminId: "",
|
||||
adminEmail: ""
|
||||
});
|
||||
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
|
||||
const [isTour, setIsTour] = useState(false);
|
||||
const [amount, setAmount] = useState({
|
||||
quantity: 500,
|
||||
priceperapi: 0.15,
|
||||
allowedcredits: 0,
|
||||
addoncredits: 0,
|
||||
totalcredits: 0,
|
||||
price: (75.0).toFixed(2)
|
||||
});
|
||||
const [isFormLoader, setIsFormLoader] = useState(false);
|
||||
const [apitestToken, setApiTestToken] = useState("");
|
||||
const quantityList = [500, 1000, 5000, 50000];
|
||||
useEffect(() => {
|
||||
fetchToken();
|
||||
// eslint-disable-next-line
|
||||
@@ -34,8 +56,8 @@ function GenerateToken() {
|
||||
const fetchToken = async () => {
|
||||
try {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe);
|
||||
}
|
||||
const url = parseBaseUrl + "functions/getapitoken";
|
||||
const headers = {
|
||||
@@ -45,22 +67,40 @@ function GenerateToken() {
|
||||
};
|
||||
const res = await axios.post(url, {}, { headers: headers });
|
||||
if (res) {
|
||||
SetApiToken(res.data?.result?.result);
|
||||
const resCredits = await Parse.Cloud.run("allowedcredits");
|
||||
const allowedcredits = resCredits?.allowedcredits || 0;
|
||||
const addoncredits = resCredits?.addoncredits || 0;
|
||||
const totalcredits = allowedcredits + addoncredits;
|
||||
setAmount((obj) => ({
|
||||
...obj,
|
||||
allowedcredits: allowedcredits,
|
||||
addoncredits: addoncredits,
|
||||
totalcredits: totalcredits
|
||||
}));
|
||||
SetApiToken(res?.data?.result?.result);
|
||||
}
|
||||
try {
|
||||
const body = { email: Parse?.User?.current()?.getEmail() || "" };
|
||||
const testurl = "https://sandbox.opensignlabs.com/api/gettesttoken";
|
||||
const testRes = await axios.post(testurl, body);
|
||||
setApiTestToken(testRes.data?.token);
|
||||
} catch (err) {
|
||||
console.log("err in fetch test token", err);
|
||||
}
|
||||
setIsLoader(false);
|
||||
} catch (err) {
|
||||
SetApiToken();
|
||||
setIsLoader(false);
|
||||
console.log("Err", err);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!isSubscribe && isEnableSubscription) {
|
||||
if (isSubscribe?.plan === "freeplan" && isEnableSubscription) {
|
||||
setIsTour(true);
|
||||
} else {
|
||||
setIsLoader(true);
|
||||
setIsModal(false);
|
||||
setIsModal((obj) => ({ ...obj, generateapi: false }));
|
||||
try {
|
||||
const url = parseBaseUrl + "functions/generateapitoken";
|
||||
const headers = {
|
||||
@@ -91,20 +131,114 @@ function GenerateToken() {
|
||||
const copytoclipboard = (text) => {
|
||||
copytoData(text);
|
||||
setIsAlert({ type: "success", msg: t("copied") });
|
||||
setTimeout(() => {
|
||||
setIsAlert({ type: "success", msg: "" });
|
||||
}, 1500); // Reset copied state after 1.5 seconds
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
|
||||
};
|
||||
const handleModal = () => {
|
||||
if (isSubscribe?.plan === "freeplan" && isEnableSubscription) {
|
||||
setIsTour(true);
|
||||
} else {
|
||||
setIsModal((obj) => ({ ...obj, generateapi: !obj.generateapi }));
|
||||
}
|
||||
};
|
||||
const handleModal = () => setIsModal(!isModal);
|
||||
|
||||
const handleBuyAPIsModal = () => {
|
||||
if (isSubscribe?.plan === "freeplan" && isEnableSubscription) {
|
||||
setIsTour(true);
|
||||
} else {
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (isSubscribe?.adminId && extUser?.objectId === isSubscribe?.adminId) {
|
||||
setIsModal((obj) => ({ ...obj, buyapis: !obj.buyapis }));
|
||||
} else {
|
||||
setIsSubscribe((obj) => ({
|
||||
...obj,
|
||||
adminEmail: extUser?.TenantId?.EmailAddress
|
||||
}));
|
||||
setIsModal((obj) => ({ ...obj, unauthorized: !obj.unauthorized }));
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleCloseModal = () => {
|
||||
setIsModal({ generateapi: false, buyapis: false, unauthorized: false });
|
||||
};
|
||||
const handlePricePerAPIs = (e) => {
|
||||
const quantity = e.target?.value;
|
||||
const price =
|
||||
quantity > 0
|
||||
? (Math.round(quantity * amount.priceperapi * 100) / 100).toFixed(2)
|
||||
: (Math.round(500 * amount.priceperapi * 100) / 100).toFixed(2);
|
||||
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
|
||||
};
|
||||
const handleAddOnApiSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsFormLoader(true);
|
||||
try {
|
||||
const resAddon = await Parse.Cloud.run("buycredits", {
|
||||
credits: amount.quantity
|
||||
});
|
||||
if (resAddon) {
|
||||
const _resAddon = JSON.parse(JSON.stringify(resAddon));
|
||||
if (_resAddon.status === "success") {
|
||||
setAmount((obj) => ({
|
||||
...obj,
|
||||
quantity: 500,
|
||||
priceperapi: 0.15,
|
||||
price: (75.0).toFixed(2),
|
||||
totalcredits: obj?.totalcredits + _resAddon.addon,
|
||||
addoncredits: obj?.addoncredits + _resAddon.addon
|
||||
}));
|
||||
}
|
||||
}
|
||||
setIsModal((obj) => ({ ...obj, buyapis: false }));
|
||||
} catch (err) {
|
||||
console.log("Err in buy addon", err);
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
} finally {
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
|
||||
setIsFormLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateTestToken = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoader(true);
|
||||
// setIsModal((obj) => ({ ...obj, generateapi: false }));
|
||||
try {
|
||||
const url = "https://sandbox.opensignlabs.com/api/generatetesttoken";
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
token: apitestToken || "1"
|
||||
};
|
||||
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
const body = {
|
||||
name: extUser?.Name,
|
||||
email: extUser?.Email,
|
||||
phone: extUser?.Phone,
|
||||
company: extUser?.Company,
|
||||
jobTitle: extUser?.JobTitle
|
||||
};
|
||||
const res = await axios.post(url, body, { headers: headers });
|
||||
if (res) {
|
||||
setApiTestToken(res.data?.token);
|
||||
setIsAlert({ type: "success", msg: t("token-generated") });
|
||||
} else {
|
||||
console.error("Error while generating Token");
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
}
|
||||
} catch (error) {
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
console.log("while generating Token", error);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title title={"API Token"} />
|
||||
{isAlert.msg && (
|
||||
<Alert type={isAlert.type}>
|
||||
<div className="ml-3">{isAlert.msg}</div>
|
||||
</Alert>
|
||||
)}
|
||||
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
|
||||
{isLoader ? (
|
||||
<div className="flex justify-center items-center h-screen">
|
||||
<Loader />
|
||||
@@ -115,29 +249,66 @@ function GenerateToken() {
|
||||
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
|
||||
OpenSign™ {t("API")}{" "}
|
||||
<Tooltip
|
||||
url={
|
||||
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
|
||||
}
|
||||
url="https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
|
||||
isSubscribe={true}
|
||||
/>
|
||||
</h1>
|
||||
<ul className="w-full flex flex-col p-2 text-sm">
|
||||
<li className="flex flex-col md:flex-row justify-between items-center border-t-[1px] border-gray-300 break-all py-2">
|
||||
<div className="w-full md:w-[70%] flex-col md:flex-row text-xs md:text-[15px] flex items-center gap-x-5">
|
||||
<span className="ml-1">
|
||||
<span className="font-medium">API Test Token : </span>
|
||||
<span className="text-xs">
|
||||
<Tooltip message={t("help-test-token")} />
|
||||
</span>
|
||||
</span>
|
||||
<span id="token" className="md:text-end py-2 md:py-0">
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
onClick={() => copytoclipboard(apitestToken)}
|
||||
>
|
||||
{apitestToken ? apitestToken : "_____"}
|
||||
</span>
|
||||
<button
|
||||
className="op-btn op-btn-accent op-btn-outline op-btn-sm ml-2 cursor-pointer"
|
||||
onClick={() => copytoclipboard(apitestToken)}
|
||||
>
|
||||
<i className="fa-light fa-copy"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleGenerateTestToken}
|
||||
className="op-btn op-btn-primary w-[210px] md:w-auto"
|
||||
>
|
||||
{apitestToken
|
||||
? t("regenerate-test-token")
|
||||
: t("generate-test-token")}
|
||||
</button>
|
||||
</li>
|
||||
<li className="flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2">
|
||||
<div className="w-full md:w-[70%] flex-col md:flex-row text-xs md:text-[15px] flex items-center gap-x-5">
|
||||
<span className="ml-1">{t("api-token")}:</span>{" "}
|
||||
<span className="ml-1">
|
||||
<span className="font-medium">{t("api-token")} : </span>
|
||||
<span className="text-xs">
|
||||
<Tooltip message={t("help-api-token", { origin })} />
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
id="token"
|
||||
className={`${
|
||||
isSubscribe
|
||||
? ""
|
||||
: "bg-white/20 pointer-events-none select-none"
|
||||
isSubscribe?.plan === "freeplan"
|
||||
? "bg-white/20 pointer-events-none select-none"
|
||||
: ""
|
||||
} md:text-end py-2 md:py-0`}
|
||||
>
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
onClick={() => copytoclipboard(apiToken)}
|
||||
>
|
||||
{apiToken ? apiToken : "_____"}
|
||||
{isSubscribe?.plan !== "freeplan" && apiToken
|
||||
? apiToken
|
||||
: "_____"}
|
||||
</span>
|
||||
<button
|
||||
className="op-btn op-btn-accent op-btn-outline op-btn-sm ml-2 cursor-pointer"
|
||||
@@ -149,13 +320,30 @@ function GenerateToken() {
|
||||
</div>
|
||||
<button
|
||||
onClick={apiToken ? handleModal : handleSubmit}
|
||||
className="op-btn op-btn-primary"
|
||||
className="op-btn op-btn-primary w-[210px] md:w-auto"
|
||||
>
|
||||
{apiToken ? t("regenerate-token") : t("generate-token")}
|
||||
</button>
|
||||
</li>
|
||||
<div className="text-xs md:text-[15px] my-3 w-full md:w-[70%] flex-col md:flex-row flex items-center gap-x-5">
|
||||
<span className="font-medium">
|
||||
{t("remaining-credits")}{" "}
|
||||
<span className="text-xs">
|
||||
<Tooltip
|
||||
message={t("remaining-credits-help", {
|
||||
allowedcredits: amount?.allowedcredits || 0,
|
||||
addoncredits: amount?.addoncredits || 0
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<span className="md:text-end py-2 md:py-0">
|
||||
{amount.totalcredits}
|
||||
</span>
|
||||
</div>
|
||||
<hr />
|
||||
</ul>
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="flex flex-col md:flex-row items-center justify-center gap-0 md:gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
@@ -163,13 +351,20 @@ function GenerateToken() {
|
||||
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
|
||||
)
|
||||
}
|
||||
className="op-btn op-btn-secondary mt-2 mb-3 px-8"
|
||||
className="op-btn op-btn-secondary mt-2 md:mb-3 px-8 w-[210px] md:w-auto"
|
||||
>
|
||||
{t("view-docs")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleBuyAPIsModal()}
|
||||
className="op-btn op-btn-accent mt-2 mb-3 px-8 w-[210px] md:w-auto"
|
||||
>
|
||||
{t("buy-credits")}
|
||||
</button>
|
||||
</div>
|
||||
<ModalUi
|
||||
isOpen={isModal}
|
||||
isOpen={isModal.generateapi}
|
||||
title={apiToken ? t("regenerate-token") : t("generate-token")}
|
||||
handleClose={handleModal}
|
||||
>
|
||||
@@ -194,8 +389,68 @@ function GenerateToken() {
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
{/* buy APIs */}
|
||||
<ModalUi
|
||||
isOpen={isModal.buyapis}
|
||||
title={"Buy Credits"}
|
||||
handleClose={handleCloseModal}
|
||||
>
|
||||
{isFormLoader && (
|
||||
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleAddOnApiSubmit} className="p-3">
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label
|
||||
htmlFor="quantity"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("quantity-of-credits")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<select
|
||||
value={amount.quantity}
|
||||
onChange={(e) => handlePricePerAPIs(e)}
|
||||
name="quantity"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs"
|
||||
required
|
||||
>
|
||||
{quantityList.length > 0 &&
|
||||
quantityList.map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label className="block text-xs text-gray-700 font-semibold">
|
||||
{t("Price")} (1 * {amount.priceperapi})
|
||||
</label>
|
||||
<div className="w-1/4 flex justify-center items-center text-sm">
|
||||
USD {amount.price}
|
||||
</div>
|
||||
</div>
|
||||
<hr className="text-base-content mb-3" />
|
||||
<button className="op-btn op-btn-primary w-full mt-2">
|
||||
{t("Proceed")}
|
||||
</button>
|
||||
</form>
|
||||
</ModalUi>
|
||||
<ModalUi
|
||||
isOpen={isModal.unauthorized}
|
||||
title={"Unauthorized"}
|
||||
handleClose={handleCloseModal}
|
||||
>
|
||||
<div className="m-8 flex justify-center text-center items-center font-medium break-all">
|
||||
{t("unauthorized-modal", {
|
||||
adminEmail: isSubscribe?.adminEmail
|
||||
})}
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
{!isSubscribe && isEnableSubscription && (
|
||||
{isEnableSubscription && isSubscribe?.plan === "freeplan" && (
|
||||
<div data-tut="apisubscribe">
|
||||
<SubscribeCard />
|
||||
</div>
|
||||
|
||||
@@ -28,6 +28,28 @@ function GuestLogin() {
|
||||
const [contactId, setContactId] = useState(contactBookId);
|
||||
const [sendmail, setSendmail] = useState();
|
||||
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(() => {
|
||||
handleServerUrl();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -70,13 +92,14 @@ function GuestLogin() {
|
||||
"linkcontacttodoc",
|
||||
params
|
||||
);
|
||||
// console.log("linkContactRes ", linkContactRes);
|
||||
setContactId(linkContactRes?.contactId);
|
||||
await navigateToDoc(checkSplit[0], linkContactRes?.contactId);
|
||||
} catch (err) {
|
||||
console.log("Err in link ext contact", err);
|
||||
}
|
||||
} else {
|
||||
setContactId(checkSplit[2]);
|
||||
await navigateToDoc(checkSplit[0], checkSplit[2]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,15 +137,12 @@ function GuestLogin() {
|
||||
if (OTP) {
|
||||
setLoading(true);
|
||||
try {
|
||||
let url = `${serverUrl}functions/AuthLoginAsMail/`;
|
||||
let url = `${serverUrl}functions/AuthLoginAsMail`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseId
|
||||
};
|
||||
let body = {
|
||||
email: email,
|
||||
otp: OTP
|
||||
};
|
||||
let body = { email: email, otp: OTP };
|
||||
let user = await axios.post(url, body, { headers: headers });
|
||||
if (user.data.result === "Invalid Otp") {
|
||||
alert(t("invalid-otp"));
|
||||
@@ -140,7 +160,6 @@ function GuestLogin() {
|
||||
`Parse/${parseId}/currentUser`,
|
||||
JSON.stringify(_user)
|
||||
);
|
||||
// console.log("contractUserDetails ", contractUserDetails);
|
||||
if (contractUserDetails && contractUserDetails.length > 0) {
|
||||
localStorage.setItem(
|
||||
"Extand_Class",
|
||||
@@ -174,10 +193,15 @@ function GuestLogin() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params);
|
||||
// console.log("linkContactRes ", linkContactRes);
|
||||
setContactId(linkContactRes.contactId);
|
||||
setEnterOtp(true);
|
||||
await SendOtp();
|
||||
const IsEnableOTP = await navigateToDoc(
|
||||
documentId,
|
||||
linkContactRes.contactId
|
||||
);
|
||||
if (!IsEnableOTP) {
|
||||
setEnterOtp(true);
|
||||
await SendOtp();
|
||||
}
|
||||
} catch (err) {
|
||||
setLoading(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
@@ -208,7 +232,7 @@ function GuestLogin() {
|
||||
<div className="w-full md:w-[50%] text-base-content">
|
||||
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
|
||||
<legend className="text-[12px] text-[#878787] mt-2 mb-1">
|
||||
{t("guest-email-alert")}
|
||||
{t("get-otp-alert")}
|
||||
</legend>
|
||||
<div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 op-card shadow-md">
|
||||
<input
|
||||
@@ -236,7 +260,7 @@ function GuestLogin() {
|
||||
>
|
||||
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
|
||||
<legend className="text-[12px] text-[#878787] mt-2">
|
||||
{t("get-verification-code-2")}
|
||||
{t("guest-email-alert")}
|
||||
</legend>
|
||||
<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>
|
||||
|
||||
@@ -80,7 +80,9 @@ function Login() {
|
||||
}
|
||||
setState({ ...state, [name]: value });
|
||||
};
|
||||
|
||||
const handlePaidRoute = () => {
|
||||
navigate("/subscription");
|
||||
};
|
||||
const handleSubmit = async (event) => {
|
||||
localStorage.removeItem("accesstoken");
|
||||
event.preventDefault();
|
||||
@@ -158,9 +160,9 @@ function Login() {
|
||||
JSON.stringify(LocalUserDetails)
|
||||
);
|
||||
const res = await fetchSubscription();
|
||||
const freeplan = res.plan;
|
||||
const plan = res.plan;
|
||||
const billingDate = res.billingDate;
|
||||
if (freeplan === "freeplan") {
|
||||
if (plan === "freeplan") {
|
||||
setState({ ...state, loading: false });
|
||||
navigate(redirectUrl);
|
||||
} else if (billingDate) {
|
||||
@@ -171,11 +173,11 @@ function Login() {
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
setState({ ...state, loading: false });
|
||||
navigate(`/subscription`, { replace: true });
|
||||
handlePaidRoute(plan);
|
||||
}
|
||||
} else {
|
||||
setState({ ...state, loading: false });
|
||||
navigate(`/subscription`, { replace: true });
|
||||
handlePaidRoute(plan);
|
||||
}
|
||||
} else {
|
||||
setState({ ...state, loading: false });
|
||||
@@ -330,9 +332,9 @@ function Login() {
|
||||
localStorage.setItem("pageType", menu.pageType);
|
||||
if (isEnableSubscription) {
|
||||
const res = await fetchSubscription();
|
||||
const freeplan = res.plan;
|
||||
const plan = res.plan;
|
||||
const billingDate = res.billingDate;
|
||||
if (freeplan === "freeplan") {
|
||||
if (plan === "freeplan") {
|
||||
navigate(redirectUrl);
|
||||
} else if (billingDate) {
|
||||
if (new Date(billingDate) > new Date()) {
|
||||
@@ -342,14 +344,14 @@ function Login() {
|
||||
if (isFreeplan) {
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
handlePaidRoute(plan);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isFreeplan) {
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
handlePaidRoute(plan);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -462,8 +464,8 @@ function Login() {
|
||||
localStorage.setItem("userDetails", JSON.stringify(userInfo));
|
||||
const res = await fetchSubscription();
|
||||
const billingDate = res.billingDate;
|
||||
const freeplan = res.plan;
|
||||
if (freeplan === "freeplan") {
|
||||
const plan = res.plan;
|
||||
if (plan === "freeplan") {
|
||||
navigate(redirectUrl);
|
||||
} else if (billingDate) {
|
||||
if (new Date(billingDate) > new Date()) {
|
||||
@@ -471,10 +473,10 @@ function Login() {
|
||||
// Redirect to the appropriate URL after successful login
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
handlePaidRoute(plan);
|
||||
}
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
handlePaidRoute(plan);
|
||||
}
|
||||
} else {
|
||||
// Redirect to the appropriate URL after successful login
|
||||
@@ -573,10 +575,12 @@ function Login() {
|
||||
}
|
||||
};
|
||||
|
||||
const logOutUser = () => {
|
||||
const logOutUser = async () => {
|
||||
setIsModal(false);
|
||||
if (Parse?.User?.current()) {
|
||||
Parse.User.logOut();
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
@@ -789,9 +793,7 @@ function Login() {
|
||||
</div>
|
||||
<SelectLanguage />
|
||||
{state.alertMsg && (
|
||||
<Alert type={state.alertType}>
|
||||
<div className="ml-3">{state.alertMsg}</div>
|
||||
</Alert>
|
||||
<Alert type={state.alertType}>{state.alertMsg}</Alert>
|
||||
)}
|
||||
</div>
|
||||
<ModalUi
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import checkmark from "../assets/images/checkmark.png";
|
||||
import plansArr from "../json/plansArr";
|
||||
import Title from "../components/Title";
|
||||
import Parse from "parse";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
import { openInNewTab, saveLanguageInLocal } from "../constant/Utils";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const listItemStyle = {
|
||||
@@ -53,12 +53,6 @@ const PlanSubscriptions = () => {
|
||||
? "&mobile=" + encodeURIComponent(userDetails.phone)
|
||||
: "";
|
||||
|
||||
const details =
|
||||
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
|
||||
name +
|
||||
email +
|
||||
company +
|
||||
phone;
|
||||
useEffect(() => {
|
||||
setIsLoader(false);
|
||||
detectLanguage();
|
||||
@@ -72,6 +66,32 @@ const PlanSubscriptions = () => {
|
||||
|
||||
const handleFreePlan = async (item) => {
|
||||
if (item.url) {
|
||||
const code = yearlyVisible ? item.code.yearly : item.code.monthly;
|
||||
const allowedUsers =
|
||||
localStorage.getItem("allowedUsers") &&
|
||||
localStorage.getItem("allowedUsers") > 0
|
||||
? localStorage.getItem("allowedUsers") - 1
|
||||
: "";
|
||||
const teamperiod = {
|
||||
"team-weekly": "monthly",
|
||||
"team-yearly": "yearly",
|
||||
"teams-monthly": "monthly",
|
||||
"teams-yearly": "yearly"
|
||||
};
|
||||
const period = teamperiod[code] || "";
|
||||
|
||||
const quantity =
|
||||
allowedUsers && period
|
||||
? `addon_code%5B0%5D=extra-teams-users-${period}&addon_quantity%5B0%5D=${allowedUsers}&`
|
||||
: "";
|
||||
|
||||
const details =
|
||||
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
|
||||
quantity +
|
||||
name +
|
||||
email +
|
||||
company +
|
||||
phone;
|
||||
const url = yearlyVisible ? item.yearlyUrl + details : item.url + details;
|
||||
if (user) {
|
||||
localStorage.setItem("userDetails", JSON.stringify(user));
|
||||
@@ -99,6 +119,30 @@ const PlanSubscriptions = () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await Parse?.User?.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||
let PageLanding = localStorage.getItem("PageLanding");
|
||||
let baseUrl = localStorage.getItem("baseUrl");
|
||||
let appid = localStorage.getItem("parseAppId");
|
||||
|
||||
localStorage.clear();
|
||||
saveLanguageInLocal(i18n);
|
||||
localStorage.setItem("appLogo", applogo);
|
||||
localStorage.setItem("defaultmenuid", defaultmenuid);
|
||||
localStorage.setItem("PageLanding", PageLanding);
|
||||
localStorage.setItem("userSettings", appdata);
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
|
||||
navigate("/");
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Title title={"Subscriptions"} />
|
||||
@@ -109,7 +153,13 @@ const PlanSubscriptions = () => {
|
||||
) : (
|
||||
<div className="overflow-y-auto max-h-full">
|
||||
<div className="block my-2">
|
||||
<div className="flex flex-col justify-center items-center w-full">
|
||||
<div className="flex flex-col justify-center items-center w-full relative">
|
||||
<button
|
||||
className="md:hidden op-btn op-btn-primary op-btn-sm h-[40px] w-[250px] shadow-lg"
|
||||
onClick={() => handleLogout()}
|
||||
>
|
||||
Log out
|
||||
</button>
|
||||
<div
|
||||
role="tablist"
|
||||
className="op-tabs op-tabs-boxed bg-base-100 my-2 shadow-lg transition-all"
|
||||
@@ -130,6 +180,12 @@ const PlanSubscriptions = () => {
|
||||
</a>
|
||||
</div>
|
||||
<ul className="op-card flex flex-col md:flex-row h-full bg-base-100 justify-center shadow-lg">
|
||||
<button
|
||||
className="hidden md:block -top-12 right-1 absolute w-[150px] h-[40px] op-btn op-btn-primary op-btn-sm shadow-lg"
|
||||
onClick={() => handleLogout()}
|
||||
>
|
||||
{t("log-out")}
|
||||
</button>
|
||||
{plansArr.map((item) => (
|
||||
<li
|
||||
className="flex flex-col text-center border-collapse border-[1px] border-gray-300 max-w-[250px]"
|
||||
|
||||
@@ -50,6 +50,9 @@ const SSOVerify = () => {
|
||||
console.log("err", err.message);
|
||||
}
|
||||
};
|
||||
const handlePaidRoute = () => {
|
||||
navigate("/subscription");
|
||||
};
|
||||
const checkExtUser = async (ssosign) => {
|
||||
const params = { email: ssosign?.email };
|
||||
try {
|
||||
@@ -196,10 +199,10 @@ const SSOVerify = () => {
|
||||
localStorage.removeItem("userDetails");
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
handlePaidRoute(plan);
|
||||
}
|
||||
} else {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
handlePaidRoute(plan);
|
||||
}
|
||||
} else {
|
||||
navigate(redirectUrl);
|
||||
@@ -225,10 +228,12 @@ const SSOVerify = () => {
|
||||
}
|
||||
};
|
||||
// `handleCloseModal` is triggered when the user wants to close the new user flow modal
|
||||
const handleCloseModal = () => {
|
||||
const handleCloseModal = async () => {
|
||||
setIsModal(false);
|
||||
if (Parse?.User?.current()) {
|
||||
Parse.User.logOut();
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -45,8 +45,10 @@ const Signup = () => {
|
||||
const togglePasswordVisibility = () => setShowPassword(!showPassword);
|
||||
|
||||
const clearStorage = async () => {
|
||||
if (Parse.User.current()) {
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
}
|
||||
let baseUrl = localStorage.getItem("baseUrl");
|
||||
let appid = localStorage.getItem("parseAppId");
|
||||
|
||||
@@ -4,7 +4,6 @@ import "../styles/signature.css";
|
||||
import Parse from "parse";
|
||||
import Confetti from "react-confetti";
|
||||
import axios from "axios";
|
||||
import LoaderWithMsg from "../primitives/LoaderWithMsg";
|
||||
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
@@ -34,25 +33,31 @@ import {
|
||||
changeImageWH,
|
||||
handleSendOTP,
|
||||
getContainerScale,
|
||||
getDefaultSignature
|
||||
getDefaultSignature,
|
||||
onClickZoomIn,
|
||||
onClickZoomOut,
|
||||
rotatePdfPage
|
||||
} from "../constant/Utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Tour from "reactour";
|
||||
import Signedby from "../components/pdf/Signedby";
|
||||
import HandleError from "../primitives/HandleError";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
|
||||
import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
import Title from "../components/Title";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
|
||||
import { useSelector } from "react-redux";
|
||||
import TextFontSetting from "../components/pdf/TextFontSetting";
|
||||
import VerifyEmail from "../components/pdf/VerifyEmail";
|
||||
import PdfZoom from "../components/pdf/PdfZoom";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import RotateAlert from "../components/RotateAlert";
|
||||
import DownloadPdfZip from "../primitives/DownloadPdfZip";
|
||||
import Loader from "../primitives/Loader";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
import HandleError from "../primitives/HandleError";
|
||||
import LoaderWithMsg from "../primitives/LoaderWithMsg";
|
||||
//For signYourself inProgress section signer can add sign and complete doc sign.
|
||||
function SignYourSelf() {
|
||||
const { t } = useTranslation();
|
||||
@@ -123,52 +128,39 @@ function SignYourSelf() {
|
||||
const [zoomPercent, setZoomPercent] = useState(0);
|
||||
const isHeader = useSelector((state) => state.showHeader);
|
||||
const [scale, setScale] = useState(1);
|
||||
const [pdfRotateBase64, setPdfRotatese64] = useState("");
|
||||
const [isRotate, setIsRotate] = useState({ status: false, degree: 0 });
|
||||
const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: true });
|
||||
const [isDownloadModal, setIsDownloadModal] = useState(false);
|
||||
const divRef = useRef(null);
|
||||
const nodeRef = useRef(null);
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
drop: (item, monitor) => addPositionOfSignature(item, monitor),
|
||||
collect: (monitor) => ({
|
||||
isOver: !!monitor.isOver()
|
||||
})
|
||||
collect: (monitor) => ({ isOver: !!monitor.isOver() })
|
||||
});
|
||||
const pdfRef = useRef();
|
||||
const [{ isDragSign }, dragSignature] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
id: 1,
|
||||
text: "signature"
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isDragSign: !!monitor.isDragging()
|
||||
})
|
||||
item: { id: 1, text: "signature" },
|
||||
collect: (monitor) => ({ isDragSign: !!monitor.isDragging() })
|
||||
});
|
||||
|
||||
const [{ isDragStamp }, dragStamp] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
id: 2,
|
||||
text: "stamp"
|
||||
},
|
||||
|
||||
collect: (monitor) => ({
|
||||
isDragStamp: !!monitor.isDragging()
|
||||
})
|
||||
item: { id: 2, text: "stamp" },
|
||||
collect: (monitor) => ({ isDragStamp: !!monitor.isDragging() })
|
||||
});
|
||||
|
||||
const index = xyPostion.findIndex((object) => {
|
||||
const index = xyPostion?.findIndex((object) => {
|
||||
return object.pageNumber === pageNumber;
|
||||
});
|
||||
// rowlevel={JSON.parse(localStorage.getItem("rowlevel"))}
|
||||
const rowLevel =
|
||||
localStorage.getItem("rowlevel") &&
|
||||
JSON.parse(localStorage.getItem("rowlevel"));
|
||||
|
||||
const signObjId =
|
||||
rowLevel && rowLevel?.id
|
||||
? rowLevel.id
|
||||
: rowLevel?.objectId && rowLevel.objectId;
|
||||
|
||||
const documentId = docId ? docId : signObjId && signObjId;
|
||||
const senderUser =
|
||||
localStorage.getItem(
|
||||
@@ -202,12 +194,18 @@ function SignYourSelf() {
|
||||
|
||||
// Use setTimeout to wait for the transition to complete
|
||||
const timer = setTimeout(updateSize, 100); // match the transition duration
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [divRef.current, isHeader]);
|
||||
|
||||
//function for get document details for perticular signer with signer'object id
|
||||
const getDocumentDetails = async (showComplete) => {
|
||||
try {
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe);
|
||||
} catch (err) {
|
||||
console.log("err in fetch sub", err);
|
||||
}
|
||||
try {
|
||||
let isCompleted;
|
||||
//getting document details
|
||||
@@ -216,6 +214,11 @@ function SignYourSelf() {
|
||||
if (documentData && documentData.length > 0) {
|
||||
setPdfDetails(documentData);
|
||||
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
|
||||
const placeholders =
|
||||
documentData[0]?.Placeholders?.length > 0
|
||||
? documentData[0]?.Placeholders
|
||||
: [];
|
||||
setXyPostion(placeholders);
|
||||
const url = documentData[0] && documentData[0]?.URL;
|
||||
if (url) {
|
||||
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
|
||||
@@ -228,21 +231,17 @@ function SignYourSelf() {
|
||||
} else {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
}
|
||||
isCompleted =
|
||||
documentData[0].IsCompleted && documentData[0].IsCompleted;
|
||||
isCompleted = documentData?.[0]?.IsCompleted;
|
||||
if (isCompleted) {
|
||||
setIsCelebration(true);
|
||||
setTimeout(() => {
|
||||
setIsCelebration(false);
|
||||
}, 5000);
|
||||
setTimeout(() => setIsCelebration(false), 5000);
|
||||
setIsCompleted(true);
|
||||
setPdfUrl(documentData[0].SignedUrl);
|
||||
const alreadySign = {
|
||||
status: true,
|
||||
mssg: t("document-signed-alert")
|
||||
};
|
||||
if (showComplete) {
|
||||
setShowAlreadySignDoc(alreadySign);
|
||||
setShowAlreadySignDoc({
|
||||
status: true,
|
||||
mssg: t("document-signed-alert")
|
||||
});
|
||||
} else {
|
||||
setIsUiLoading(false);
|
||||
setIsSignPad(false);
|
||||
@@ -255,17 +254,11 @@ function SignYourSelf() {
|
||||
documentData === "Error: Something went wrong!" ||
|
||||
(documentData.result && documentData.result.error)
|
||||
) {
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
setIsLoading(loadObj);
|
||||
setIsLoading({ isLoad: false });
|
||||
} else {
|
||||
setHandleError(t("no-data-avaliable"));
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setIsLoading(loadObj);
|
||||
setIsLoading({ isLoad: false });
|
||||
}
|
||||
//function to get default signatur eof current user from `contracts_Signature` class
|
||||
const defaultSignRes = await getDefaultSignature(jsonSender.objectId);
|
||||
@@ -274,17 +267,12 @@ function SignYourSelf() {
|
||||
setMyInitial(defaultSignRes?.res?.defaultInitial);
|
||||
} else if (defaultSignRes?.status === "error") {
|
||||
setHandleError("Error: Something went wrong!");
|
||||
setIsLoading({
|
||||
isLoad: false
|
||||
});
|
||||
setIsLoading({ isLoad: false });
|
||||
}
|
||||
const contractUsersRes = await contractUsers();
|
||||
if (contractUsersRes === "Error: Something went wrong!") {
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
setIsLoading(loadObj);
|
||||
setIsLoading({ isLoad: false });
|
||||
} else if (contractUsersRes[0] && contractUsersRes.length > 0) {
|
||||
setActiveMailAdapter(contractUsersRes[0]?.active_mail_adapter);
|
||||
setContractName("_Users");
|
||||
@@ -364,51 +352,32 @@ function SignYourSelf() {
|
||||
const addWidgetOptions = (type) => {
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return {
|
||||
name: "signature"
|
||||
};
|
||||
return { name: "signature" };
|
||||
case "stamp":
|
||||
return {
|
||||
name: "stamp"
|
||||
};
|
||||
return { name: "stamp" };
|
||||
case "checkbox":
|
||||
return {
|
||||
name: "checkbox"
|
||||
};
|
||||
return { name: "checkbox" };
|
||||
case textWidget:
|
||||
return {
|
||||
name: "text"
|
||||
};
|
||||
return { name: "text" };
|
||||
case "initials":
|
||||
return {
|
||||
name: "initials"
|
||||
};
|
||||
return { name: "initials" };
|
||||
case "name":
|
||||
return {
|
||||
name: "name",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: {
|
||||
type: "text",
|
||||
pattern: ""
|
||||
}
|
||||
validation: { type: "text", pattern: "" }
|
||||
};
|
||||
case "company":
|
||||
return {
|
||||
name: "company",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: {
|
||||
type: "text",
|
||||
pattern: ""
|
||||
}
|
||||
validation: { type: "text", pattern: "" }
|
||||
};
|
||||
case "job title":
|
||||
return {
|
||||
name: "job title",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: {
|
||||
type: "text",
|
||||
pattern: ""
|
||||
}
|
||||
validation: { type: "text", pattern: "" }
|
||||
};
|
||||
case "date":
|
||||
return {
|
||||
@@ -417,17 +386,12 @@ function SignYourSelf() {
|
||||
validation: { format: "MM/dd/yyyy", type: "date-format" }
|
||||
};
|
||||
case "image":
|
||||
return {
|
||||
name: "image"
|
||||
};
|
||||
return { name: "image" };
|
||||
case "email":
|
||||
return {
|
||||
name: "email",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: {
|
||||
type: "email",
|
||||
pattern: ""
|
||||
}
|
||||
validation: { type: "email", pattern: "" }
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
@@ -439,7 +403,7 @@ function SignYourSelf() {
|
||||
const key = randomId();
|
||||
let dropData = [];
|
||||
let dropObj = {};
|
||||
let filterDropPos = xyPostion.filter(
|
||||
let filterDropPos = xyPostion?.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
const dragTypeValue = item?.text ? item.text : monitor.type;
|
||||
@@ -454,29 +418,27 @@ function SignYourSelf() {
|
||||
);
|
||||
//adding and updating drop position in array when user drop signature button in div
|
||||
if (item === "onclick") {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
const getWidth = widgetTypeExist
|
||||
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth
|
||||
: dragTypeValue === "initials"
|
||||
? defaultWidthHeight(dragTypeValue).width
|
||||
: "";
|
||||
: defaultWidthHeight(dragTypeValue).width;
|
||||
const getHeight = widgetTypeExist
|
||||
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getHeight
|
||||
: dragTypeValue === "initials"
|
||||
? defaultWidthHeight(dragTypeValue).height
|
||||
: "";
|
||||
: defaultWidthHeight(dragTypeValue).height;
|
||||
|
||||
dropObj = {
|
||||
xPosition: containerWH.width / 2,
|
||||
yPosition: containerWH.height / 2,
|
||||
xPosition: getWidth / 2 + containerWH.width / 2,
|
||||
yPosition: getHeight + divHeight / 2,
|
||||
isStamp:
|
||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||
key: key,
|
||||
type: dragTypeValue,
|
||||
scale: containerScale,
|
||||
Width: getWidth / (containerScale * scale),
|
||||
Height: getHeight / (containerScale * scale),
|
||||
Width: getWidth,
|
||||
Height: getHeight,
|
||||
options: addWidgetOptions(dragTypeValue)
|
||||
};
|
||||
|
||||
dropData.push(dropObj);
|
||||
} else {
|
||||
//This method returns the offset of the current pointer (mouse) position relative to the client viewport.
|
||||
@@ -509,25 +471,18 @@ function SignYourSelf() {
|
||||
options: addWidgetOptions(dragTypeValue),
|
||||
scale: containerScale
|
||||
};
|
||||
|
||||
dropData.push(dropObj);
|
||||
}
|
||||
if (filterDropPos.length > 0) {
|
||||
const index = xyPostion.findIndex((object) => {
|
||||
if (filterDropPos?.length > 0) {
|
||||
const index = xyPostion?.findIndex((object) => {
|
||||
return object.pageNumber === pageNumber;
|
||||
});
|
||||
const updateData = filterDropPos[0].pos;
|
||||
const updateData = filterDropPos?.[0].pos;
|
||||
const newSignPos = updateData.concat(dropData);
|
||||
let xyPos = {
|
||||
pageNumber: pageNumber,
|
||||
pos: newSignPos
|
||||
};
|
||||
xyPostion.splice(index, 1, xyPos);
|
||||
let xyPos = { pageNumber: pageNumber, pos: newSignPos };
|
||||
xyPostion?.splice(index, 1, xyPos);
|
||||
} else {
|
||||
const xyPos = {
|
||||
pageNumber: pageNumber,
|
||||
pos: dropData
|
||||
};
|
||||
const xyPos = { pageNumber: pageNumber, pos: dropData };
|
||||
setXyPostion((prev) => [...prev, xyPos]);
|
||||
}
|
||||
|
||||
@@ -539,7 +494,6 @@ function SignYourSelf() {
|
||||
) {
|
||||
setIsSignPad(true);
|
||||
}
|
||||
|
||||
if (dragTypeValue === "stamp" || dragTypeValue === "image") {
|
||||
setIsStamp(true);
|
||||
} else if (dragTypeValue === "initials") {
|
||||
@@ -602,6 +556,29 @@ function SignYourSelf() {
|
||||
setIsVerifyModal(true);
|
||||
await handleSendOTP(Parse.User.current().getEmail());
|
||||
};
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (xyPostion?.length > 0) {
|
||||
autosavedetails();
|
||||
}
|
||||
}, 2000);
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [xyPostion]);
|
||||
|
||||
// `autosavedetails` is used to save doc details after every 2 sec when changes are happern in placeholder like drag-drop widgets, remove signers
|
||||
const autosavedetails = async () => {
|
||||
try {
|
||||
const docCls = new Parse.Object("contracts_Document");
|
||||
docCls.id = documentId;
|
||||
docCls.set("Placeholders", xyPostion);
|
||||
docCls.set("IsSignyourself", true);
|
||||
await docCls.save();
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
};
|
||||
//function for send placeholder's co-ordinate(x,y) position embed signature url or stamp url
|
||||
async function embedWidgetsData() {
|
||||
//check current user email is verified or not
|
||||
@@ -740,7 +717,7 @@ function SignYourSelf() {
|
||||
//function for get digital signature
|
||||
const signPdfFun = async (base64Url, documentId) => {
|
||||
let isCustomCompletionMail = false;
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
|
||||
const tenantDetails = await getTenantDetails(jsonSender.objectId);
|
||||
if (tenantDetails && tenantDetails === "user does not exist!") {
|
||||
alert(t("user-not-exist"));
|
||||
@@ -748,7 +725,7 @@ function SignYourSelf() {
|
||||
if (
|
||||
tenantDetails?.CompletionBody &&
|
||||
tenantDetails?.CompletionSubject &&
|
||||
getIsSubscribe
|
||||
isSubscribe?.isValid
|
||||
) {
|
||||
isCustomCompletionMail = true;
|
||||
}
|
||||
@@ -781,7 +758,6 @@ function SignYourSelf() {
|
||||
const getNewse64 = await changeImageWH(base64Sign);
|
||||
//remove suffiix of base64
|
||||
const suffixbase64 = getNewse64 && getNewse64.split(",").pop();
|
||||
|
||||
const params = {
|
||||
pdfFile: base64Url,
|
||||
docId: documentId,
|
||||
@@ -794,6 +770,7 @@ function SignYourSelf() {
|
||||
const signedpdf = JSON.parse(JSON.stringify(resSignPdf));
|
||||
setPdfUrl(signedpdf);
|
||||
getDocumentDetails(false);
|
||||
setPdfRotatese64("");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -814,10 +791,10 @@ function SignYourSelf() {
|
||||
containerWH
|
||||
);
|
||||
if (dragKey >= 0) {
|
||||
const filterDropPos = xyPostion.filter(
|
||||
const filterDropPos = xyPostion?.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (filterDropPos.length > 0) {
|
||||
if (filterDropPos?.length > 0) {
|
||||
const getXYdata = xyPostion[index].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSign = getPosData.map((url) => {
|
||||
@@ -888,15 +865,11 @@ function SignYourSelf() {
|
||||
setIsSignPad(false);
|
||||
setIsImageSelect(false);
|
||||
setImage();
|
||||
|
||||
if (isDefaultSign) {
|
||||
const img = new Image();
|
||||
img.src = defaultSignImg;
|
||||
if (img.complete) {
|
||||
imgWH = {
|
||||
width: img.width,
|
||||
height: img.height
|
||||
};
|
||||
imgWH = { width: img.width, height: img.height };
|
||||
}
|
||||
}
|
||||
const getUpdatePosition = onSaveSign(
|
||||
@@ -921,19 +894,11 @@ function SignYourSelf() {
|
||||
const touch = e.touches[0]; // Get the first touch point
|
||||
mouseX = touch.clientX - divRect.left;
|
||||
mouseY = touch.clientY - divRect.top;
|
||||
setSignBtnPosition([
|
||||
{
|
||||
xPos: mouseX,
|
||||
yPos: mouseY
|
||||
}
|
||||
]);
|
||||
setSignBtnPosition([{ xPos: mouseX, yPos: mouseY }]);
|
||||
} else {
|
||||
mouseX = e.clientX - divRect.left;
|
||||
mouseY = e.clientY - divRect.top;
|
||||
const xyPosition = {
|
||||
xPos: mouseX,
|
||||
yPos: mouseY
|
||||
};
|
||||
const xyPosition = { xPos: mouseX, yPos: mouseY };
|
||||
setXYSignature(xyPosition);
|
||||
}
|
||||
};
|
||||
@@ -951,9 +916,7 @@ function SignYourSelf() {
|
||||
const handleDeleteSign = (key) => {
|
||||
setCurrWidgetsDetails({});
|
||||
const updateResizeData = [];
|
||||
|
||||
let filterData = xyPostion[index].pos.filter((data) => data.key !== key);
|
||||
|
||||
//delete and update block position
|
||||
if (filterData.length > 0) {
|
||||
updateResizeData.push(filterData);
|
||||
@@ -1093,7 +1056,11 @@ function SignYourSelf() {
|
||||
name: dropdownName,
|
||||
values: dropdownOptions,
|
||||
isReadOnly: isReadOnly,
|
||||
isHideLabel: isHideLabel || false
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || "12",
|
||||
fontColor:
|
||||
fontColor || currWidgetsDetails?.options?.fontColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1111,6 +1078,8 @@ function SignYourSelf() {
|
||||
handleCloseModal();
|
||||
}
|
||||
}
|
||||
setFontSize();
|
||||
setFontColor();
|
||||
};
|
||||
const handleCloseModal = () => {
|
||||
setCurrWidgetsDetails({});
|
||||
@@ -1154,6 +1123,56 @@ function SignYourSelf() {
|
||||
handleTextSettingModal(false);
|
||||
}
|
||||
};
|
||||
const clickOnZoomIn = () => {
|
||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||
};
|
||||
const clickOnZoomOut = () => {
|
||||
onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale);
|
||||
};
|
||||
//`handleRotationFun` function is used to roatate pdf particular page
|
||||
const handleRotationFun = async (rotateDegree) => {
|
||||
const isPlaceholder = handleRotateWarning();
|
||||
if (isPlaceholder) {
|
||||
setIsRotate({ status: true, degree: rotateDegree });
|
||||
} else {
|
||||
const urlDetails = await rotatePdfPage(
|
||||
pdfUrl || pdfDetails[0].URL,
|
||||
rotateDegree,
|
||||
pageNumber - 1,
|
||||
pdfRotateBase64
|
||||
);
|
||||
setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer);
|
||||
setPdfRotatese64(urlDetails.base64);
|
||||
}
|
||||
};
|
||||
|
||||
//function to show warning when user rotate page and there are some already widgets on that page
|
||||
const handleRotateWarning = () => {
|
||||
const placeholderData = xyPostion?.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (placeholderData && placeholderData.length > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
//function to use remove widgets from current page when user want to rotate page
|
||||
const handleRemoveWidgets = async () => {
|
||||
const updatePlaceholder = xyPostion.filter(
|
||||
(data) => data.pageNumber !== pageNumber
|
||||
);
|
||||
const urlDetails = await rotatePdfPage(
|
||||
pdfUrl || pdfDetails[0].URL,
|
||||
isRotate.degree,
|
||||
pageNumber - 1,
|
||||
pdfRotateBase64
|
||||
);
|
||||
setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer);
|
||||
setPdfRotatese64(urlDetails.base64);
|
||||
setXyPostion(updatePlaceholder);
|
||||
setIsRotate({ status: false, degree: 0 });
|
||||
};
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Title title={"Self Sign"} />
|
||||
@@ -1173,10 +1192,14 @@ function SignYourSelf() {
|
||||
)}
|
||||
{isCelebration && (
|
||||
<div className="relative z-[1000]">
|
||||
<Confetti width={window.innerWidth} height={window.innerHeight} />
|
||||
<Confetti
|
||||
width={window.innerWidth}
|
||||
height={window.innerHeight}
|
||||
recycle={false} // Prevents confetti from repeating
|
||||
gravity={0.1} // Adjust the gravity to control the speed
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300">
|
||||
{!isEmailVerified && (
|
||||
<VerifyEmail
|
||||
@@ -1213,14 +1236,13 @@ function SignYourSelf() {
|
||||
setSignBtnPosition={setSignBtnPosition}
|
||||
pageNumber={pageNumber}
|
||||
containerWH={containerWH}
|
||||
pdfRotateBase64={pdfRotateBase64}
|
||||
/>
|
||||
<div className=" w-full md:w-[57%] flex mr-4">
|
||||
<PdfZoom
|
||||
setScale={setScale}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
setZoomPercent={setZoomPercent}
|
||||
zoomPercent={zoomPercent}
|
||||
clickOnZoomIn={clickOnZoomIn}
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
handleRotationFun={handleRotationFun}
|
||||
/>
|
||||
<div className="w-full md:w-[95%]">
|
||||
<ModalUi
|
||||
@@ -1239,9 +1261,7 @@ function SignYourSelf() {
|
||||
<ModalUi
|
||||
isOpen={showAlreadySignDoc.status}
|
||||
title={t("document-signed")}
|
||||
handleClose={() => {
|
||||
setShowAlreadySignDoc({ status: false });
|
||||
}}
|
||||
handleClose={() => setShowAlreadySignDoc({ status: false })}
|
||||
>
|
||||
<div className="p-[20px] h-full">
|
||||
<p>{showAlreadySignDoc.mssg}</p>
|
||||
@@ -1265,6 +1285,10 @@ function SignYourSelf() {
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
isSignYourself={true}
|
||||
handleClose={handleCloseModal}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
<PlaceholderCopy
|
||||
isPageCopy={isPageCopy}
|
||||
@@ -1274,6 +1298,7 @@ function SignYourSelf() {
|
||||
allPages={allPages}
|
||||
pageNumber={pageNumber}
|
||||
signKey={signKey}
|
||||
widgetType={widgetType}
|
||||
/>
|
||||
{/* this is modal of signature pad */}
|
||||
<SignPad
|
||||
@@ -1309,6 +1334,7 @@ function SignYourSelf() {
|
||||
setIsAlert={setIsAlert}
|
||||
extUserId={extUserId}
|
||||
activeMailAdapter={activeMailAdapter}
|
||||
setIsDownloadModal={setIsDownloadModal}
|
||||
/>
|
||||
{/* pdf header which contain funish back button */}
|
||||
<Header
|
||||
@@ -1324,12 +1350,13 @@ function SignYourSelf() {
|
||||
isSignYourself={true}
|
||||
setIsEmail={setIsEmail}
|
||||
isCompleted={isCompleted}
|
||||
handleRotationFun={handleRotationFun}
|
||||
clickOnZoomIn={clickOnZoomIn}
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
widgetsDetails={xyPostion}
|
||||
setIsDownloadModal={setIsDownloadModal}
|
||||
/>
|
||||
<div
|
||||
ref={divRef}
|
||||
data-tut="reactourSecond"
|
||||
className="h-full md:h-[95%]"
|
||||
>
|
||||
<div ref={divRef} data-tut="reactourSecond" className="h-full">
|
||||
{containerWH?.width && containerWH?.height && (
|
||||
<RenderPdf
|
||||
pageNumber={pageNumber}
|
||||
@@ -1368,6 +1395,11 @@ function SignYourSelf() {
|
||||
handleTextSettingModal={handleTextSettingModal}
|
||||
setScale={setScale}
|
||||
scale={scale}
|
||||
pdfRotateBase64={pdfRotateBase64}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1407,9 +1439,7 @@ function SignYourSelf() {
|
||||
<ModalUi
|
||||
isOpen={validateAlert}
|
||||
title={t("validation-alert")}
|
||||
handleClose={() => {
|
||||
setValidateAlert(false);
|
||||
}}
|
||||
handleClose={() => setValidateAlert(false)}
|
||||
>
|
||||
<div className="p-[20px] h-full">
|
||||
<p>{t("validate-alert-mssg")}</p>
|
||||
@@ -1424,6 +1454,17 @@ function SignYourSelf() {
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
<RotateAlert
|
||||
isRotate={isRotate.status}
|
||||
setIsRotate={setIsRotate}
|
||||
handleRemoveWidgets={handleRemoveWidgets}
|
||||
/>
|
||||
<DownloadPdfZip
|
||||
setIsDownloadModal={setIsDownloadModal}
|
||||
isDownloadModal={isDownloadModal}
|
||||
pdfDetails={pdfDetails}
|
||||
isDocId={true}
|
||||
/>
|
||||
<TextFontSetting
|
||||
isTextSetting={isTextSetting}
|
||||
setIsTextSetting={setIsTextSetting}
|
||||
|
||||
@@ -8,9 +8,10 @@ import ModalUi from "../primitives/ModalUi";
|
||||
import pad from "../assets/images/pad.svg";
|
||||
import AddTeam from "../components/AddTeam";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import { checkIsSubscribedTeam } from "../constant/Utils";
|
||||
import { checkIsSubscribed } from "../constant/Utils";
|
||||
import SubscribeCard from "../primitives/SubscribeCard";
|
||||
import Title from "../components/Title";
|
||||
import { validplan } from "../json/plansArr";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const heading = ["Sr.No", "Name", "Parent Team", "Active"];
|
||||
@@ -38,7 +39,7 @@ const TeamList = () => {
|
||||
const [isActiveModal, setIsActiveModal] = useState({});
|
||||
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
|
||||
const [isActLoader, setIsActLoader] = useState({});
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: false });
|
||||
const [isEditModal, setIsEditModal] = useState({});
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const startIndex = (currentPage - 1) * recordperPage; // user per page
|
||||
@@ -103,8 +104,8 @@ const TeamList = () => {
|
||||
try {
|
||||
setIsLoader(true);
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribedTeam();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe);
|
||||
}
|
||||
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (extUser) {
|
||||
@@ -134,7 +135,7 @@ const TeamList = () => {
|
||||
const handleFormModal = () => {
|
||||
setIsModal(!isModal);
|
||||
};
|
||||
// Get current list
|
||||
// to slice out 10 objects from array for current page
|
||||
const indexOfLastDoc = currentPage * recordperPage;
|
||||
const indexOfFirstDoc = indexOfLastDoc - recordperPage;
|
||||
const currentList = teamList?.slice(indexOfFirstDoc, indexOfLastDoc);
|
||||
@@ -145,8 +146,19 @@ const TeamList = () => {
|
||||
};
|
||||
|
||||
// Change page
|
||||
const paginateFront = () => setCurrentPage(currentPage + 1);
|
||||
const paginateBack = () => setCurrentPage(currentPage - 1);
|
||||
const paginateFront = () => {
|
||||
const lastValue = pageNumbers?.[pageNumbers?.length - 1];
|
||||
if (currentPage < lastValue) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const paginateBack = () => {
|
||||
if (startIndex > 0) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleActionBtn = (act, item) => {
|
||||
if (act.action === "edit") {
|
||||
setIsEditModal({ [item.objectId]: true });
|
||||
@@ -225,15 +237,11 @@ const TeamList = () => {
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{isSubscribe && isEnableSubscription && !isLoader && (
|
||||
{validplan[isSubscribe.plan] && isEnableSubscription && !isLoader && (
|
||||
<>
|
||||
{isAdmin ? (
|
||||
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
|
||||
{isAlert.msg && (
|
||||
<Alert type={isAlert.type}>
|
||||
<div className="ml-3">{isAlert.msg}</div>
|
||||
</Alert>
|
||||
)}
|
||||
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
|
||||
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
|
||||
<div className="font-light">
|
||||
{t("report-name.Teams")}
|
||||
@@ -470,9 +478,9 @@ const TeamList = () => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!isSubscribe && isEnableSubscription && !isLoader && (
|
||||
{isEnableSubscription && !validplan[isSubscribe.plan] && !isLoader && (
|
||||
<div data-tut="apisubscribe">
|
||||
<SubscribeCard plan={"TEAMS"} price={"20"} />
|
||||
<SubscribeCard plan="TEAMS" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -9,8 +9,6 @@ import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { useDrag, useDrop } from "react-dnd";
|
||||
import WidgetComponent from "../components/pdf/WidgetComponent";
|
||||
import Tour from "reactour";
|
||||
import LoaderWithMsg from "../primitives/LoaderWithMsg";
|
||||
import HandleError from "../primitives/HandleError";
|
||||
import SignerListPlace from "../components/pdf/SignerListPlace";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import WidgetNameModal from "../components/pdf/WidgetNameModal";
|
||||
@@ -25,22 +23,32 @@ import {
|
||||
textInputWidget,
|
||||
radioButtonWidget,
|
||||
fetchSubscription,
|
||||
getContainerScale
|
||||
getContainerScale,
|
||||
convertBase64ToFile,
|
||||
rotatePdfPage,
|
||||
onClickZoomOut,
|
||||
onClickZoomIn,
|
||||
handleRemoveWidgets,
|
||||
handleRotateWarning
|
||||
} from "../constant/Utils";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import "../styles/AddUser.css";
|
||||
import Title from "../components/Title";
|
||||
import LinkUserModal from "../primitives/LinkUserModal";
|
||||
import EditTemplate from "../components/pdf/EditTemplate";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import AddRoleModal from "../components/pdf/AddRoleModal";
|
||||
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
|
||||
import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
|
||||
import Parse from "parse";
|
||||
import { useSelector } from "react-redux";
|
||||
import PdfZoom from "../components/pdf/PdfZoom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import RotateAlert from "../components/RotateAlert";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
import HandleError from "../primitives/HandleError";
|
||||
import LoaderWithMsg from "../primitives/LoaderWithMsg";
|
||||
import LinkUserModal from "../primitives/LinkUserModal";
|
||||
|
||||
const TemplatePlaceholder = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
@@ -55,12 +63,15 @@ const TemplatePlaceholder = () => {
|
||||
const [xySignature, setXYSignature] = useState({});
|
||||
const [dragKey, setDragKey] = useState();
|
||||
const [signersdata, setSignersData] = useState([]);
|
||||
const [signerObjId, setSignerObjId] = useState();
|
||||
const [signerPos, setSignerPos] = useState([]);
|
||||
const [isSelectListId, setIsSelectId] = useState();
|
||||
const [isSendAlert, setIsSendAlert] = useState(false);
|
||||
const [isCreateDocModal, setIsCreateDocModal] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
const [isRotate, setIsRotate] = useState({
|
||||
status: false,
|
||||
degree: 0
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState({
|
||||
isLoad: true,
|
||||
message: t("loading-mssg")
|
||||
@@ -73,7 +84,6 @@ const TemplatePlaceholder = () => {
|
||||
const [tourStatus, setTourStatus] = useState([]);
|
||||
const [signerUserId, setSignerUserId] = useState();
|
||||
const [pdfOriginalWH, setPdfOriginalWH] = useState([]);
|
||||
const [contractName, setContractName] = useState("");
|
||||
const [containerWH, setContainerWH] = useState();
|
||||
const signRef = useRef(null);
|
||||
const dragRef = useRef(null);
|
||||
@@ -91,6 +101,7 @@ const TemplatePlaceholder = () => {
|
||||
const [isNameModal, setIsNameModal] = useState(false);
|
||||
const [isTextSetting, setIsTextSetting] = useState(false);
|
||||
const [pdfLoad, setPdfLoad] = useState(false);
|
||||
const [pdfRotateBase64, setPdfRotatese64] = useState("");
|
||||
const color = [
|
||||
"#93a3db",
|
||||
"#e6c3db",
|
||||
@@ -181,21 +192,25 @@ const TemplatePlaceholder = () => {
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [divRef.current, isHeader]);
|
||||
|
||||
const handleNavigation = () => {
|
||||
navigate("/subscription");
|
||||
};
|
||||
async function checkIsSubscribed() {
|
||||
const res = await fetchSubscription();
|
||||
const freeplan = res.plan;
|
||||
const plan = res.plan;
|
||||
const billingDate = res.billingDate;
|
||||
if (freeplan === "freeplan") {
|
||||
if (plan === "freeplan") {
|
||||
return true;
|
||||
} else if (billingDate) {
|
||||
if (new Date(billingDate) > new Date()) {
|
||||
setIsSubscribe(true);
|
||||
return true;
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
handleNavigation(plan);
|
||||
}
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
handleNavigation(plan);
|
||||
}
|
||||
}
|
||||
// `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present
|
||||
@@ -225,8 +240,6 @@ const TemplatePlaceholder = () => {
|
||||
setPdfDetails(documentData);
|
||||
setIsSigners(true);
|
||||
if (documentData[0].Signers && documentData[0].Signers.length > 0) {
|
||||
setSignerObjId(documentData[0].Signers[0].objectId);
|
||||
setContractName(documentData[0].Signers[0].className);
|
||||
setIsSelectId(0);
|
||||
if (
|
||||
documentData[0].Placeholders &&
|
||||
@@ -365,21 +378,19 @@ const TemplatePlaceholder = () => {
|
||||
containerWH
|
||||
);
|
||||
const key = randomId();
|
||||
let filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||
const dragTypeValue = item?.text ? item.text : monitor.type;
|
||||
const widgetWidth = defaultWidthHeight(dragTypeValue).width;
|
||||
const widgetHeight = defaultWidthHeight(dragTypeValue).height;
|
||||
let dropData = [];
|
||||
let dropData = [],
|
||||
currentPagePosition;
|
||||
let placeHolder;
|
||||
if (item === "onclick") {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
const dropObj = {
|
||||
//onclick put placeholder center on pdf
|
||||
xPosition:
|
||||
(containerWH.width / 2 - widgetWidth / 2) /
|
||||
(containerScale * scale),
|
||||
yPosition:
|
||||
(containerWH.height / 2 - widgetHeight / 2) /
|
||||
(containerScale * scale),
|
||||
xPosition: widgetWidth / 4 + containerWH.width / 2,
|
||||
yPosition: widgetHeight + divHeight / 2,
|
||||
isStamp:
|
||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||
key: key,
|
||||
@@ -430,65 +441,40 @@ const TemplatePlaceholder = () => {
|
||||
pos: dropData
|
||||
};
|
||||
}
|
||||
const { blockColor, Role } = signer;
|
||||
//adding placholder in existing signer pos array (placaholder)
|
||||
if (filterSignerPos.length > 0) {
|
||||
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
||||
let filterSignerPos = signerPos.find((data) => data.Id === uniqueId);
|
||||
const getPlaceHolder = filterSignerPos?.placeHolder;
|
||||
if (getPlaceHolder) {
|
||||
//checking exist placeholder on same page
|
||||
currentPagePosition = getPlaceHolder.find(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
}
|
||||
//checking current page has already some placeholders then update that placeholder and add upcoming placehoder position
|
||||
if (getPlaceHolder && currentPagePosition) {
|
||||
const updatePlace = getPlaceHolder.filter(
|
||||
(data) => data.pageNumber !== pageNumber
|
||||
);
|
||||
const getPageNumer = getPlaceHolder.filter(
|
||||
(data) => data.pageNumber === pageNumber
|
||||
const getPos = currentPagePosition?.pos;
|
||||
const newSignPos = getPos.concat(dropData);
|
||||
let xyPos = {
|
||||
pageNumber: pageNumber,
|
||||
pos: newSignPos
|
||||
};
|
||||
updatePlace.push(xyPos);
|
||||
const updatesignerPos = signerPos.map((x) =>
|
||||
x.Id === uniqueId ? { ...x, placeHolder: updatePlace } : x
|
||||
);
|
||||
|
||||
//add entry of position for same signer on multiple page
|
||||
if (getPageNumer.length > 0) {
|
||||
const getPos = getPageNumer[0].pos;
|
||||
const newSignPos = getPos.concat(dropData);
|
||||
let xyPos = {
|
||||
pageNumber: pageNumber,
|
||||
pos: newSignPos
|
||||
};
|
||||
updatePlace.push(xyPos);
|
||||
const updatesignerPos = signerPos.map((x) =>
|
||||
x.Id === uniqueId ? { ...x, placeHolder: updatePlace } : x
|
||||
);
|
||||
setSignerPos(updatesignerPos);
|
||||
} else {
|
||||
const updatesignerPos = signerPos.map((x) =>
|
||||
x.Id === uniqueId
|
||||
? { ...x, placeHolder: [...x.placeHolder, placeHolder] }
|
||||
: x
|
||||
);
|
||||
setSignerPos(updatesignerPos);
|
||||
}
|
||||
setSignerPos(updatesignerPos);
|
||||
} else {
|
||||
//adding new placeholder for selected signer in pos array (placeholder)
|
||||
let placeHolderPos;
|
||||
if (contractName) {
|
||||
placeHolderPos = {
|
||||
signerPtr: {
|
||||
__type: "Pointer",
|
||||
className: `${contractName}`,
|
||||
objectId: signerObjId
|
||||
},
|
||||
signerObjId: signerObjId,
|
||||
blockColor: blockColor ? blockColor : color[isSelectListId],
|
||||
placeHolder: [placeHolder],
|
||||
Role: Role ? Role : roleName,
|
||||
Id: uniqueId
|
||||
};
|
||||
} else {
|
||||
placeHolderPos = {
|
||||
signerPtr: {},
|
||||
signerObjId: "",
|
||||
blockColor: blockColor ? blockColor : color[isSelectListId],
|
||||
placeHolder: [placeHolder],
|
||||
Role: Role ? Role : roleName,
|
||||
Id: uniqueId
|
||||
};
|
||||
}
|
||||
setSignerPos((prev) => [...prev, placeHolderPos]);
|
||||
//else condition to add placeholder widgets on multiple page first time
|
||||
const updatesignerPos = signerPos.map((x) =>
|
||||
x.Id === uniqueId && x?.placeHolder
|
||||
? { ...x, placeHolder: [...x.placeHolder, placeHolder] }
|
||||
: x.Id === uniqueId
|
||||
? { ...x, placeHolder: [placeHolder] }
|
||||
: x
|
||||
);
|
||||
setSignerPos(updatesignerPos);
|
||||
}
|
||||
|
||||
if (dragTypeValue === "dropdown") {
|
||||
@@ -615,10 +601,6 @@ const TemplatePlaceholder = () => {
|
||||
//function for delete signature block
|
||||
const handleDeleteSign = (key, Id) => {
|
||||
const updateData = [];
|
||||
// const filterSignerPos = signerPos.filter(
|
||||
// (data) => data.signerObjId === signerId
|
||||
// );
|
||||
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === Id);
|
||||
|
||||
if (filterSignerPos.length > 0) {
|
||||
@@ -651,11 +633,11 @@ const TemplatePlaceholder = () => {
|
||||
|
||||
setSignerPos(newUpdateSigner);
|
||||
} else {
|
||||
const updateFilter = signerPos.filter((data) => data.Id !== Id);
|
||||
const getRemainPage = filterSignerPos[0].placeHolder.filter(
|
||||
(data) => data.pageNumber !== pageNumber
|
||||
);
|
||||
|
||||
//condition to check placeholder length is greater than 1 do not need to remove whole placeholder
|
||||
//array only resove particular widgets
|
||||
if (getRemainPage && getRemainPage.length > 0) {
|
||||
const newUpdatePos = filterSignerPos.map((obj) => {
|
||||
if (obj.Id === Id) {
|
||||
@@ -669,7 +651,16 @@ const TemplatePlaceholder = () => {
|
||||
|
||||
setSignerPos(signerupdate);
|
||||
} else {
|
||||
setSignerPos(updateFilter);
|
||||
const updatedData = signerPos.map((item) => {
|
||||
if (item.Id === Id) {
|
||||
// Create a copy of the item object and delete the placeHolder field
|
||||
const updatedItem = { ...item };
|
||||
delete updatedItem.placeHolder;
|
||||
return updatedItem;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setSignerPos(updatedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -691,19 +682,11 @@ const TemplatePlaceholder = () => {
|
||||
const touch = e.touches[0]; // Get the first touch point
|
||||
mouseX = touch.clientX - divRect.left;
|
||||
mouseY = touch.clientY - divRect.top;
|
||||
setSignBtnPosition([
|
||||
{
|
||||
xPos: mouseX,
|
||||
yPos: mouseY
|
||||
}
|
||||
]);
|
||||
setSignBtnPosition([{ xPos: mouseX, yPos: mouseY }]);
|
||||
} else {
|
||||
mouseX = e.clientX - divRect.left;
|
||||
mouseY = e.clientY - divRect.top;
|
||||
const xyPosition = {
|
||||
xPos: mouseX,
|
||||
yPos: mouseY
|
||||
};
|
||||
const xyPosition = { xPos: mouseX, yPos: mouseY };
|
||||
setXYSignature(xyPosition);
|
||||
}
|
||||
};
|
||||
@@ -712,20 +695,54 @@ const TemplatePlaceholder = () => {
|
||||
const handleMouseLeave = () => {
|
||||
setSignBtnPosition([xySignature]);
|
||||
};
|
||||
|
||||
const alertSendEmail = async () => {
|
||||
if (signerPos.length !== signersdata.length) {
|
||||
setIsSendAlert(true);
|
||||
} else {
|
||||
const isPlaceholderExist = signerPos.every((data) => data.placeHolder);
|
||||
if (isPlaceholderExist) {
|
||||
handleSaveTemplate();
|
||||
} else {
|
||||
setIsSendAlert(true);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (signerPos?.length > 0) {
|
||||
autosavedetails();
|
||||
}
|
||||
}, 2000);
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [signerPos]);
|
||||
|
||||
// `autosavedetails` is used to save template details after every 2 sec when changes are happern in placeholder like drag-drop widgets, remove signers
|
||||
const autosavedetails = async () => {
|
||||
let signers = [];
|
||||
if (signersdata?.length > 0) {
|
||||
signersdata.forEach((x) => {
|
||||
if (x.objectId) {
|
||||
const obj = {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Contactbook",
|
||||
objectId: x.objectId
|
||||
};
|
||||
signers.push(obj);
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
const templateCls = new Parse.Object("contracts_Template");
|
||||
templateCls.id = templateId;
|
||||
templateCls.set("Placeholders", signerPos);
|
||||
templateCls.set("Signers", signers);
|
||||
await templateCls.save();
|
||||
} catch (err) {
|
||||
console.log("error in autosave template", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveTemplate = async () => {
|
||||
if (signersdata?.length) {
|
||||
const loadObj = {
|
||||
isLoad: true,
|
||||
message: t("loading-mssg")
|
||||
};
|
||||
const loadObj = { isLoad: true, message: t("loading-mssg") };
|
||||
setIsLoading(loadObj);
|
||||
setIsSendAlert(false);
|
||||
let signers = [];
|
||||
@@ -741,6 +758,17 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
let pdfUrl = pdfDetails[0]?.URL;
|
||||
if (pdfRotateBase64) {
|
||||
try {
|
||||
pdfUrl = await convertBase64ToFile(
|
||||
pdfDetails[0].Name,
|
||||
pdfRotateBase64
|
||||
);
|
||||
} catch (e) {
|
||||
console.log("error to convertBase64ToFile in placeholder flow", e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const data = {
|
||||
Placeholders: signerPos,
|
||||
@@ -751,23 +779,19 @@ const TemplatePlaceholder = () => {
|
||||
SendinOrder: pdfDetails[0]?.SendinOrder || false,
|
||||
AutomaticReminders: pdfDetails[0]?.AutomaticReminders,
|
||||
RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery),
|
||||
NextReminderDate: pdfDetails[0]?.NextReminderDate
|
||||
NextReminderDate: pdfDetails[0]?.NextReminderDate,
|
||||
IsEnableOTP: pdfDetails[0]?.IsEnableOTP === true ? true : false,
|
||||
URL: pdfUrl
|
||||
};
|
||||
const updateTemplate = new Parse.Object("contracts_Template");
|
||||
updateTemplate.id = templateId;
|
||||
for (const key in data) {
|
||||
updateTemplate.set(key, data[key]);
|
||||
}
|
||||
await updateTemplate.save(null, {
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
});
|
||||
|
||||
await updateTemplate.save();
|
||||
setIsCreateDocModal(true);
|
||||
setIsMailSend(true);
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setIsLoading(loadObj);
|
||||
setIsLoading({ isLoad: false });
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
@@ -891,8 +915,21 @@ const TemplatePlaceholder = () => {
|
||||
const handleCreateDocModal = async () => {
|
||||
setIsCreateDocModal(false);
|
||||
setIsCreateDoc(true);
|
||||
let pdfUrl = pdfDetails[0]?.URL;
|
||||
if (pdfRotateBase64) {
|
||||
try {
|
||||
pdfUrl = await convertBase64ToFile(pdfDetails[0].Name, pdfRotateBase64);
|
||||
} catch (e) {
|
||||
console.log("error to convertBase64ToFile in placeholder flow", e);
|
||||
}
|
||||
}
|
||||
// handle create document
|
||||
const res = await createDocument(pdfDetails, signerPos, signersdata);
|
||||
const res = await createDocument(
|
||||
pdfDetails,
|
||||
signerPos,
|
||||
signersdata,
|
||||
pdfUrl
|
||||
);
|
||||
if (res.status === "success") {
|
||||
navigate(`/placeHolderSign/${res.id}`, {
|
||||
state: { title: "Use Template" }
|
||||
@@ -903,7 +940,6 @@ const TemplatePlaceholder = () => {
|
||||
setIsCreateDoc(false);
|
||||
}
|
||||
};
|
||||
|
||||
// `handleAddSigner` is used to open Add Role Modal
|
||||
const handleAddSigner = () => {
|
||||
setIsModalRole(true);
|
||||
@@ -914,8 +950,6 @@ const TemplatePlaceholder = () => {
|
||||
// save Role in entry in signerList and user
|
||||
const handleAddRole = (e) => {
|
||||
e.preventDefault();
|
||||
setSignerObjId("");
|
||||
setContractName("");
|
||||
const count = signersdata.length > 0 ? signersdata.length + 1 : 1;
|
||||
const Id = randomId();
|
||||
const index = signersdata.length;
|
||||
@@ -925,6 +959,15 @@ const TemplatePlaceholder = () => {
|
||||
blockColor: color[index]
|
||||
};
|
||||
setSignersData((prevArr) => [...prevArr, obj]);
|
||||
const signerPosObj = {
|
||||
signerPtr: {},
|
||||
signerObjId: "",
|
||||
blockColor: color[index],
|
||||
Role: roleName || "User " + count,
|
||||
Id: Id
|
||||
};
|
||||
|
||||
setSignerPos((prev) => [...prev, signerPosObj]);
|
||||
setIsModalRole(false);
|
||||
setRoleName("");
|
||||
setUniqueId(Id);
|
||||
@@ -956,11 +999,12 @@ const TemplatePlaceholder = () => {
|
||||
setIsMailSend(false);
|
||||
};
|
||||
|
||||
// `handleLinkUser` is used to open Add/Choose Signer Modal when user can link existing or new User with placeholder
|
||||
// `handleLinkUser` is used to open Add/Choose Signer Modal when user can link existing or new User with placeholder
|
||||
// and update entry in signersList
|
||||
const handleLinkUser = (id) => {
|
||||
setIsAddUser({ [id]: true });
|
||||
};
|
||||
// `handleAddUser` is used to adduser
|
||||
const handleAddUser = (data) => {
|
||||
const signerPtr = {
|
||||
__type: "Pointer",
|
||||
@@ -1089,7 +1133,13 @@ const TemplatePlaceholder = () => {
|
||||
status: status,
|
||||
defaultValue: defaultValue,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || "12",
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1121,7 +1171,13 @@ const TemplatePlaceholder = () => {
|
||||
},
|
||||
isReadOnly: isReadOnly || false,
|
||||
defaultValue: defaultValue,
|
||||
isHideLabel: isHideLabel || false
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || "12",
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1133,7 +1189,13 @@ const TemplatePlaceholder = () => {
|
||||
name: dropdownName,
|
||||
status: status,
|
||||
values: dropdownOptions,
|
||||
defaultValue: defaultValue
|
||||
defaultValue: defaultValue,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || "12",
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1160,6 +1222,8 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
setFontSize();
|
||||
setFontColor();
|
||||
};
|
||||
|
||||
const handleWidgetdefaultdata = (defaultdata) => {
|
||||
@@ -1250,6 +1314,7 @@ const TemplatePlaceholder = () => {
|
||||
setCurrWidgetsDetails({});
|
||||
handleNameModal();
|
||||
};
|
||||
|
||||
const handleNameModal = () => {
|
||||
setIsNameModal(false);
|
||||
setCurrWidgetsDetails({});
|
||||
@@ -1258,6 +1323,38 @@ const TemplatePlaceholder = () => {
|
||||
setIsCheckbox(false);
|
||||
};
|
||||
|
||||
const clickOnZoomIn = () => {
|
||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||
};
|
||||
const clickOnZoomOut = () => {
|
||||
onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale);
|
||||
};
|
||||
//`handleRotationFun` function is used to roatate pdf particular page
|
||||
const handleRotationFun = async (rotateDegree) => {
|
||||
const isRotate = handleRotateWarning(signerPos, pageNumber);
|
||||
if (isRotate) {
|
||||
setIsRotate({ status: true, degree: rotateDegree });
|
||||
} else {
|
||||
const urlDetails = await rotatePdfPage(
|
||||
pdfDetails[0].URL,
|
||||
rotateDegree,
|
||||
pageNumber - 1,
|
||||
pdfRotateBase64
|
||||
);
|
||||
setPdfRotatese64(urlDetails.base64);
|
||||
}
|
||||
};
|
||||
const handleRemovePlaceholder = async () => {
|
||||
handleRemoveWidgets(setSignerPos, signerPos, pageNumber, setIsRotate);
|
||||
const urlDetails = await rotatePdfPage(
|
||||
pdfDetails[0].URL,
|
||||
isRotate.degree,
|
||||
pageNumber - 1,
|
||||
pdfRotateBase64
|
||||
);
|
||||
setPdfRotatese64(urlDetails.base64);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title title={"Template"} />
|
||||
@@ -1305,11 +1402,9 @@ const TemplatePlaceholder = () => {
|
||||
{/* pdf render view */}
|
||||
<div className=" w-full md:w-[57%] flex mr-4">
|
||||
<PdfZoom
|
||||
setScale={setScale}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
setZoomPercent={setZoomPercent}
|
||||
zoomPercent={zoomPercent}
|
||||
clickOnZoomIn={clickOnZoomIn}
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
handleRotationFun={handleRotationFun}
|
||||
/>
|
||||
<div className="w-full md:w-[95%]">
|
||||
{/* this modal is used show alert set placeholder for all signers before send mail */}
|
||||
@@ -1391,6 +1486,10 @@ const TemplatePlaceholder = () => {
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleClose={handleNameModal}
|
||||
isSubscribe={isSubscribe}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
<DropdownWidgetOption
|
||||
type="checkbox"
|
||||
@@ -1402,6 +1501,10 @@ const TemplatePlaceholder = () => {
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleClose={handleNameModal}
|
||||
isSubscribe={isSubscribe}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
<DropdownWidgetOption
|
||||
type="dropdown"
|
||||
@@ -1413,6 +1516,10 @@ const TemplatePlaceholder = () => {
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleClose={handleNameModal}
|
||||
isSubscribe={isSubscribe}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
<PlaceholderCopy
|
||||
isPageCopy={isPageCopy}
|
||||
@@ -1440,6 +1547,9 @@ const TemplatePlaceholder = () => {
|
||||
currentSigner={true}
|
||||
setIsEditTemplate={handleEditTemplateModal}
|
||||
dataTut4="reactourFour"
|
||||
handleRotationFun={handleRotationFun}
|
||||
clickOnZoomIn={clickOnZoomIn}
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
/>
|
||||
<div
|
||||
ref={divRef}
|
||||
@@ -1471,7 +1581,6 @@ const TemplatePlaceholder = () => {
|
||||
signersdata={signersdata}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
setSignerObjId={setSignerObjId}
|
||||
isDragging={isDragging}
|
||||
setShowDropdown={setShowDropdown}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
@@ -1485,6 +1594,11 @@ const TemplatePlaceholder = () => {
|
||||
setScale={setScale}
|
||||
scale={scale}
|
||||
setIsSelectId={setIsSelectId}
|
||||
pdfRotateBase64={pdfRotateBase64}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1509,9 +1623,7 @@ const TemplatePlaceholder = () => {
|
||||
signerPos={signerPos}
|
||||
signersdata={signersdata}
|
||||
isSelectListId={isSelectListId}
|
||||
setSignerObjId={setSignerObjId}
|
||||
setIsSelectId={setIsSelectId}
|
||||
setContractName={setContractName}
|
||||
isSigners={isSigners}
|
||||
setIsShowEmail={setIsShowEmail}
|
||||
isMailSend={isMailSend}
|
||||
@@ -1541,10 +1653,8 @@ const TemplatePlaceholder = () => {
|
||||
setSignerPos={setSignerPos}
|
||||
signersdata={signersdata}
|
||||
isSelectListId={isSelectListId}
|
||||
setSignerObjId={setSignerObjId}
|
||||
setRoleName={setRoleName}
|
||||
setIsSelectId={setIsSelectId}
|
||||
setContractName={setContractName}
|
||||
handleAddSigner={handleAddSigner}
|
||||
setUniqueId={setUniqueId}
|
||||
handleDeleteUser={handleDeleteUser}
|
||||
@@ -1624,6 +1734,11 @@ const TemplatePlaceholder = () => {
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
<RotateAlert
|
||||
isRotate={isRotate.status}
|
||||
setIsRotate={setIsRotate}
|
||||
handleRemoveWidgets={handleRemovePlaceholder}
|
||||
/>
|
||||
</DndProvider>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@ const UpdateExistUserAdmin = () => {
|
||||
try {
|
||||
const isAdminExist = await Parse.Cloud.run("checkadminexist");
|
||||
if (isAdminExist !== "not_exist") {
|
||||
console.log("isAdminExist ", isAdminExist);
|
||||
// console.log("isAdminExist ", isAdminExist);
|
||||
setErrMsg("Admin already exists.");
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -65,12 +65,8 @@ const UpdateExistUserAdmin = () => {
|
||||
};
|
||||
return (
|
||||
<div className="h-screen flex justify-center">
|
||||
<Title title="Add admin" />
|
||||
{isAlert.msg && (
|
||||
<Alert type={isAlert.type}>
|
||||
<div className="ml-3">{isAlert.msg}</div>
|
||||
</Alert>
|
||||
)}
|
||||
<Title title={"Add Admin"} />
|
||||
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
|
||||
{loader ? (
|
||||
<div className="text-[grey] flex justify-center items-center text-lg md:text-2xl">
|
||||
<Loader />
|
||||
|
||||
+246
-116
@@ -9,8 +9,9 @@ import Tooltip from "../primitives/Tooltip";
|
||||
import AddUser from "../components/AddUser";
|
||||
import SubscribeCard from "../primitives/SubscribeCard";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import { checkIsSubscribedTeam } from "../constant/Utils";
|
||||
import { fetchSubscriptionInfo } from "../constant/Utils";
|
||||
import Title from "../components/Title";
|
||||
import { validplan } from "../json/plansArr";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"];
|
||||
// const actions = [];
|
||||
@@ -18,7 +19,11 @@ const UserList = () => {
|
||||
const { t } = useTranslation();
|
||||
const [userList, setUserList] = useState([]);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const [isModal, setIsModal] = useState({
|
||||
form: false,
|
||||
addseats: false,
|
||||
options: false
|
||||
});
|
||||
const location = useLocation();
|
||||
const isDashboard =
|
||||
location?.pathname === "/dashboard/35KBoSgoAK" ? true : false;
|
||||
@@ -26,8 +31,16 @@ const UserList = () => {
|
||||
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
|
||||
const [isActiveModal, setIsActiveModal] = useState({});
|
||||
const [isActLoader, setIsActLoader] = useState({});
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState({
|
||||
plan: "",
|
||||
isValid: false,
|
||||
priceperUser: 0
|
||||
});
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [formHeader, setFormHeader] = useState(t("add-user"));
|
||||
const [usersCount, setUserCounts] = useState({ allowed: 0, totalAllowed: 0 });
|
||||
const [amount, setAmount] = useState({ quantity: 1, price: 0 });
|
||||
const [isBuyLoader, setIsBuyLoader] = useState(false);
|
||||
const recordperPage = 10;
|
||||
const startIndex = (currentPage - 1) * recordperPage; // user per page
|
||||
|
||||
@@ -79,10 +92,13 @@ const UserList = () => {
|
||||
pages.push(lastPageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
const pageNumbers = getPaginationRange();
|
||||
// to slice out 10 objects from array for current page
|
||||
const indexOfLastDoc = currentPage * recordperPage;
|
||||
const indexOfFirstDoc = indexOfLastDoc - recordperPage;
|
||||
const currentList = userList?.slice(indexOfFirstDoc, indexOfLastDoc);
|
||||
useEffect(() => {
|
||||
fetchUserList();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -90,13 +106,43 @@ const UserList = () => {
|
||||
async function fetchUserList() {
|
||||
try {
|
||||
setIsLoader(true);
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribedTeam();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
const subscribe = await fetchSubscriptionInfo();
|
||||
if (subscribe?.plan_code?.includes("team")) {
|
||||
const isSupAdmin =
|
||||
subscribe?.adminId && extUser?.objectId === subscribe?.adminId;
|
||||
setIsSubscribe({
|
||||
plan: subscribe.plan_code,
|
||||
isValid: true,
|
||||
adminId: subscribe.adminId,
|
||||
priceperUser: subscribe.price,
|
||||
isSuperAdmin: isSupAdmin
|
||||
});
|
||||
setAmount((prev) => ({ ...prev, price: subscribe.price }));
|
||||
try {
|
||||
const res = await Parse.Cloud.run("allowedusers");
|
||||
setUserCounts((obj) => ({
|
||||
...obj,
|
||||
allowed: parseInt(res),
|
||||
totalAllowed: parseInt(subscribe?.totalAllowedUser) || 0
|
||||
}));
|
||||
} catch (err) {
|
||||
console.log("err while get users", err);
|
||||
}
|
||||
} else {
|
||||
setIsSubscribe({
|
||||
plan: subscribe.plan_code,
|
||||
isValid: false,
|
||||
adminId: subscribe.adminId
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setIsSubscribe(true);
|
||||
setIsSubscribe({ plan: "teams-yearly", isValid: true });
|
||||
}
|
||||
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
|
||||
if (extUser) {
|
||||
const admin =
|
||||
extUser?.UserRole &&
|
||||
@@ -115,26 +161,36 @@ const UserList = () => {
|
||||
console.log("Err in fetch userlist", err);
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setIsAlert({ type: "success", msg: "" });
|
||||
}, 1500);
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
const handleFormModal = () => {
|
||||
setIsModal(!isModal);
|
||||
const handleModal = (modalName) => {
|
||||
setIsModal((obj) => ({ ...obj, [modalName]: !obj[modalName] }));
|
||||
};
|
||||
|
||||
// Change page
|
||||
const paginateFront = () => setCurrentPage(currentPage + 1);
|
||||
const paginateBack = () => setCurrentPage(currentPage - 1);
|
||||
// const handleActionBtn = (act, item) => {
|
||||
// if (act.action === "delete") {
|
||||
// setIsDeleteModal({ [item.objectId]: true });
|
||||
// }
|
||||
// };
|
||||
const paginateFront = () => {
|
||||
const lastValue = pageNumbers?.[pageNumbers?.length - 1];
|
||||
if (currentPage < lastValue) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const paginateBack = () => {
|
||||
if (startIndex > 0) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUserData = (userData) => {
|
||||
setUserList((prev) => [userData, ...prev]);
|
||||
if (userData) {
|
||||
setUserCounts((obj) => ({
|
||||
...obj,
|
||||
allowed: obj?.allowed > 0 ? obj.allowed - 1 : 1
|
||||
}));
|
||||
setUserList((prev) => [userData, ...prev]);
|
||||
}
|
||||
};
|
||||
// `formatRow` is used to show data in poper manner like
|
||||
// if data is of array type then it will join array items with ","
|
||||
@@ -150,9 +206,8 @@ const UserList = () => {
|
||||
return "-";
|
||||
}
|
||||
};
|
||||
const handleClose = () => {
|
||||
setIsActiveModal({});
|
||||
};
|
||||
const handleClose = () => setIsActiveModal({});
|
||||
|
||||
const handleToggleSubmit = async (user) => {
|
||||
const index = userList.findIndex((obj) => obj.objectId === user.objectId);
|
||||
if (index !== -1) {
|
||||
@@ -184,6 +239,49 @@ const UserList = () => {
|
||||
const handleToggleBtn = (user) => {
|
||||
setIsActiveModal({ [user.objectId]: true });
|
||||
};
|
||||
|
||||
const handleAddOnSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsBuyLoader(true);
|
||||
try {
|
||||
const resAddon = await Parse.Cloud.run("buyaddonusers", {
|
||||
users: parseInt(amount.quantity)
|
||||
});
|
||||
if (resAddon) {
|
||||
const _resAddon = JSON.parse(JSON.stringify(resAddon));
|
||||
if (_resAddon.status === "success") {
|
||||
setUserCounts((obj) => ({
|
||||
...obj,
|
||||
allowed: parseInt(obj.allowed) + parseInt(amount.quantity),
|
||||
totalAllowed: parseInt(_resAddon.addon)
|
||||
}));
|
||||
setAmount((obj) => ({ ...obj, quantity: 1 }));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in buy addon", err);
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
} finally {
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
|
||||
setIsBuyLoader(false);
|
||||
handleModal("addseats");
|
||||
}
|
||||
};
|
||||
const handlePricePerUser = (e) => {
|
||||
const quantity = e.target.value;
|
||||
const price = e.target?.value > 0 ? isSubscribe.priceperUser * quantity : 0;
|
||||
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
|
||||
};
|
||||
const handleBuyUsers = (allowed, totalAllowed) => {
|
||||
if (allowed && totalAllowed) {
|
||||
setUserCounts((obj) => ({
|
||||
...obj,
|
||||
allowed: parseInt(obj.allowed) + parseInt(allowed),
|
||||
totalAllowed: parseInt(totalAllowed)
|
||||
}));
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
<Title title={isAdmin ? "Users" : "Page not found"} />
|
||||
@@ -193,34 +291,57 @@ const UserList = () => {
|
||||
</div>
|
||||
)}
|
||||
{Object.keys(isActLoader)?.length > 0 && (
|
||||
<div className="absolute w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-30 rounded-box">
|
||||
<div className="absolute w-full h-full flex justify-center items-center bg-black/30 z-30 rounded-box">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{isSubscribe && !isLoader && (
|
||||
{validplan[isSubscribe.plan] && !isLoader && (
|
||||
<>
|
||||
{isAdmin ? (
|
||||
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
|
||||
{isAlert.msg && (
|
||||
<Alert type={isAlert.type}>
|
||||
<div className="ml-3">{isAlert.msg}</div>
|
||||
</Alert>
|
||||
)}
|
||||
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
|
||||
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
|
||||
<div className="font-light">
|
||||
{t("report-name.Users")}
|
||||
{t("report-name.Users")}{" "}
|
||||
<span className="text-xs md:text-[13px] font-normal">
|
||||
<Tooltip message={t("users-from-teams")} />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleFormModal()}
|
||||
>
|
||||
<i className="fa-light fa-square-plus text-accent text-[40px]"></i>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
|
||||
<div
|
||||
className="hidden md:flex op-btn op-btn-sm h-[35px] op-btn-primary op-btn-outline text-xs mb-0.5"
|
||||
onClick={() => handleModal("addseats")}
|
||||
>
|
||||
{t("buy-users")}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleModal("form")}
|
||||
>
|
||||
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[40px]"></i>
|
||||
</div>
|
||||
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
|
||||
<div
|
||||
className="cursor-pointer relative md:hidden mb-0.5"
|
||||
onClick={() => handleModal("options")}
|
||||
>
|
||||
<i className="fa-light fa-ellipsis-vertical fa-xl"></i>
|
||||
{isModal?.options && (
|
||||
<ul className="absolute -right-3 top-auto z-[70] w-max op-menu op-menu-xs shadow bg-base-100 text-base-content rounded-box border">
|
||||
<li onClick={() => handleModal("addseats")}>
|
||||
<span className="text-[13px] capitalize font-medium">
|
||||
{t("buy-users")}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={` overflow-x-auto w-full`}>
|
||||
<div className="overflow-x-auto w-full">
|
||||
<table className="op-table border-collapse w-full">
|
||||
<thead className="text-[14px]">
|
||||
<tr className="border-y-[1px]">
|
||||
@@ -234,7 +355,7 @@ const UserList = () => {
|
||||
<tbody className="text-[12px]">
|
||||
{userList?.length > 0 && (
|
||||
<>
|
||||
{userList.map((item, index) => (
|
||||
{currentList.map((item, index) => (
|
||||
<tr className="border-y-[1px]" key={index}>
|
||||
{heading.includes("Sr.No") && (
|
||||
<th className="px-4 py-2">
|
||||
@@ -298,49 +419,6 @@ const UserList = () => {
|
||||
)}
|
||||
</td>
|
||||
)}
|
||||
{/* <td className="px-3 py-2 text-white grid grid-cols-2">
|
||||
{actions?.length > 0 &&
|
||||
actions.map((act, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleActionBtn(act, item)}
|
||||
title={act.hoverLabel}
|
||||
className={`${
|
||||
act?.btnColor ? act.btnColor : ""
|
||||
} op-btn op-btn-sm`}
|
||||
>
|
||||
<i className={act.btnIcon}></i>
|
||||
</button>
|
||||
))}
|
||||
{isDeleteModal[item.objectId] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={t("delete-user")}
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
{t( "are-you-sure")} {t("delete")} {t("this-user")}?
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-4 " />
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button
|
||||
onClick={() => handleDelete(item)}
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
{('no)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
</td> */}
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
@@ -348,34 +426,42 @@ const UserList = () => {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="op-join flex flex-wrap items-center p-2">
|
||||
{userList.length > recordperPage && (
|
||||
<button
|
||||
onClick={() => paginateBack()}
|
||||
className="op-join-item op-btn op-btn-sm"
|
||||
>
|
||||
{t("prev")}
|
||||
</button>
|
||||
)}
|
||||
{pageNumbers.map((x, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentPage(x)}
|
||||
disabled={x === "..."}
|
||||
className={`${
|
||||
x === currentPage ? "op-btn-active" : ""
|
||||
} op-join-item op-btn op-btn-sm`}
|
||||
>
|
||||
{x}
|
||||
</button>
|
||||
))}
|
||||
{userList.length > recordperPage && (
|
||||
<button
|
||||
onClick={() => paginateFront()}
|
||||
className="op-join-item op-btn op-btn-sm"
|
||||
>
|
||||
{t("next")}
|
||||
</button>
|
||||
<div className="flex flex-row justify-between items-center text-xs font-medium">
|
||||
<div className="op-join flex flex-wrap items-center p-2">
|
||||
{userList.length > recordperPage && (
|
||||
<button
|
||||
onClick={() => paginateBack()}
|
||||
className="op-join-item op-btn op-btn-sm"
|
||||
>
|
||||
{t("prev")}
|
||||
</button>
|
||||
)}
|
||||
{pageNumbers.map((x, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentPage(x)}
|
||||
disabled={x === "..."}
|
||||
className={`${
|
||||
x === currentPage ? "op-btn-active" : ""
|
||||
} op-join-item op-btn op-btn-sm`}
|
||||
>
|
||||
{x}
|
||||
</button>
|
||||
))}
|
||||
{userList.length > recordperPage && (
|
||||
<button
|
||||
onClick={() => paginateFront()}
|
||||
className="op-join-item op-btn op-btn-sm"
|
||||
>
|
||||
{t("next")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
|
||||
<div>
|
||||
{t("available-seats")}: {usersCount.allowed}/
|
||||
{usersCount.totalAllowed}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{userList?.length <= 0 && (
|
||||
@@ -397,16 +483,60 @@ const UserList = () => {
|
||||
</div>
|
||||
)}
|
||||
<ModalUi
|
||||
title={t("add-user")}
|
||||
isOpen={isModal}
|
||||
handleClose={handleFormModal}
|
||||
isOpen={isModal.form}
|
||||
title={formHeader}
|
||||
handleClose={() => handleModal("form")}
|
||||
>
|
||||
<AddUser
|
||||
setIsAlert={setIsAlert}
|
||||
handleUserData={handleUserData}
|
||||
closePopup={handleFormModal}
|
||||
handleBuyUsers={handleBuyUsers}
|
||||
closePopup={() => handleModal("form")}
|
||||
setFormHeader={setFormHeader}
|
||||
/>
|
||||
</ModalUi>
|
||||
<ModalUi
|
||||
isOpen={isModal.addseats}
|
||||
title="Add Seats"
|
||||
handleClose={() => handleModal("addseats")}
|
||||
>
|
||||
{isBuyLoader && (
|
||||
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleAddOnSubmit} className=" px-3 pb-6 pt-6">
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label
|
||||
htmlFor="quantity"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("Quantity-of-users")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="quantity"
|
||||
value={amount.quantity}
|
||||
onChange={(e) => handlePricePerUser(e)}
|
||||
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3 flex justify-between">
|
||||
<label className="block text-xs text-gray-700 font-semibold">
|
||||
{t("Price")} (1 * {isSubscribe.priceperUser})
|
||||
</label>
|
||||
<div className="w-1/4 flex justify-center items-center text-sm">
|
||||
USD {amount.price}
|
||||
</div>
|
||||
</div>
|
||||
<hr className="text-base-content mb-3" />
|
||||
<button className="op-btn op-btn-primary w-full">
|
||||
{t("Proceed")}
|
||||
</button>
|
||||
</form>
|
||||
</ModalUi>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box">
|
||||
@@ -422,9 +552,9 @@ const UserList = () => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!isSubscribe && isEnableSubscription && !isLoader && (
|
||||
{isEnableSubscription && !validplan[isSubscribe.plan] && !isLoader && (
|
||||
<div data-tut="apisubscribe">
|
||||
<SubscribeCard plan={"TEAMS"} price={"20"} />
|
||||
<SubscribeCard plan="TEAMS" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,6 @@ import Tooltip from "../primitives/Tooltip";
|
||||
import { isEnableSubscription, isStaging } from "../constant/const";
|
||||
import {
|
||||
checkIsSubscribed,
|
||||
checkIsSubscribedTeam,
|
||||
copytoData,
|
||||
handleSendOTP,
|
||||
openInNewTab
|
||||
@@ -18,10 +17,13 @@ import {
|
||||
import Upgrade from "../primitives/Upgrade";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { validplan } from "../json/plansArr";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SelectLanguage from "../components/pdf/SelectLanguage";
|
||||
import { RWebShare } from "react-web-share";
|
||||
import Alert from "../primitives/Alert";
|
||||
import Tour from "reactour";
|
||||
import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
|
||||
function UserProfile() {
|
||||
const navigate = useNavigate();
|
||||
@@ -39,7 +41,7 @@ function UserProfile() {
|
||||
const [isDisableDocId, setIsDisableDocId] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
const [isUpgrade, setIsUpgrade] = useState(false);
|
||||
const [isAlert, setIsAlert] = useState({});
|
||||
const [isAlert, setIsAlert] = useState({ type: "success", message: "" });
|
||||
const [publicUserName, setPublicUserName] = useState(
|
||||
extendUser && extendUser?.[0]?.UserName
|
||||
);
|
||||
@@ -57,7 +59,10 @@ function UserProfile() {
|
||||
const [tagLine, setTagLine] = useState(
|
||||
extendUser && extendUser?.[0]?.Tagline
|
||||
);
|
||||
const [isTeam, setIsTeam] = useState(false);
|
||||
const [isPlan, setIsPlan] = useState({ plan: "", isValid: false });
|
||||
const [tourStatus, setTourStatus] = useState([]);
|
||||
const [isProfileTour, setIsProfileTour] = useState(false);
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
const getPublicUrl = isStaging
|
||||
? `https://staging.opensign.me/${extendUser?.[0]?.UserName}`
|
||||
: `https://opensign.me/${extendUser?.[0]?.UserName}`;
|
||||
@@ -65,17 +70,27 @@ function UserProfile() {
|
||||
getUserDetail();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const getUserDetail = async () => {
|
||||
setIsLoader(true);
|
||||
const extClass = localStorage.getItem("Extand_Class");
|
||||
const jsonSender = JSON.parse(extClass);
|
||||
const tourstatuss = jsonSender[0]?.TourStatus && jsonSender[0].TourStatus;
|
||||
if (!isEnableSubscription) {
|
||||
setIsProfileTour(true);
|
||||
} else if (tourstatuss && tourstatuss.length > 0) {
|
||||
setTourStatus(tourstatuss);
|
||||
const checkTourRecipients = tourstatuss.filter(
|
||||
(data) => data.profileTour
|
||||
);
|
||||
if (checkTourRecipients && checkTourRecipients.length > 0) {
|
||||
setIsProfileTour(checkTourRecipients[0]?.profileTour);
|
||||
}
|
||||
}
|
||||
const HeaderDocId = jsonSender[0]?.HeaderDocId;
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
const getIsTeam = await checkIsSubscribedTeam();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
setIsTeam(getIsTeam);
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe.isValid);
|
||||
setIsPlan(subscribe);
|
||||
}
|
||||
if (HeaderDocId) {
|
||||
setIsDisableDocId(HeaderDocId);
|
||||
@@ -109,13 +124,8 @@ function UserProfile() {
|
||||
});
|
||||
if (res) {
|
||||
setIsLoader(false);
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
message: t("user-name-exist")
|
||||
});
|
||||
setTimeout(() => {
|
||||
setIsAlert({});
|
||||
}, 3000);
|
||||
setIsAlert({ type: "danger", message: t("user-name-exist") });
|
||||
setTimeout(() => setIsAlert({}), 3000);
|
||||
return res;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -320,17 +330,80 @@ function UserProfile() {
|
||||
setJobTitle(extendUser?.[0]?.JobTitle);
|
||||
setIsDisableDocId(extendUser?.[0]?.HeaderDocId);
|
||||
};
|
||||
const copytoclipboard = () => {
|
||||
copytoData(getPublicUrl);
|
||||
setIsAlert({
|
||||
type: "success",
|
||||
message: t("copied")
|
||||
});
|
||||
setTimeout(() => {
|
||||
setIsAlert({});
|
||||
}, 3000);
|
||||
const handlePaidRoute = () => {
|
||||
navigate("/subscription");
|
||||
};
|
||||
|
||||
const copytoclipboard = () => {
|
||||
copytoData(getPublicUrl);
|
||||
setIsAlert({ type: "success", message: t("copied") });
|
||||
setTimeout(() => setIsAlert({}), 3000);
|
||||
};
|
||||
|
||||
const tourConfig = [
|
||||
{
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={t("tour-mssg.public-template")}
|
||||
isChecked={handleDontShow}
|
||||
video="https://www.youtube.com/embed/_wB4UA7Jz5Q?si=I40CI3nVUWQzf42Y"
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
//function for update TourStatus
|
||||
const closeTour = async () => {
|
||||
try {
|
||||
setIsProfileTour(true);
|
||||
if (isDontShow) {
|
||||
let updatedTourStatus = [];
|
||||
if (tourStatus.length > 0) {
|
||||
updatedTourStatus = [...tourStatus];
|
||||
const profileIndex = tourStatus.findIndex(
|
||||
(obj) => obj["profileTour"] === false || obj["profileTour"] === true
|
||||
);
|
||||
if (profileIndex !== -1) {
|
||||
updatedTourStatus[profileIndex] = { profileTour: true };
|
||||
} else {
|
||||
updatedTourStatus.push({ profileTour: true });
|
||||
}
|
||||
} else {
|
||||
updatedTourStatus = [{ profileTour: true }];
|
||||
}
|
||||
|
||||
await axios.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/contracts_Users/${
|
||||
extendUser?.[0]?.objectId
|
||||
}`,
|
||||
{
|
||||
TourStatus: updatedTourStatus
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
);
|
||||
const res = await Parse.Cloud.run("getUserDetails");
|
||||
const json = JSON.parse(JSON.stringify([res]));
|
||||
const extRes = JSON.stringify(json);
|
||||
localStorage.setItem("Extand_Class", extRes);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("axois err ", err);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
};
|
||||
const handleDontShow = (isChecked) => {
|
||||
setIsDontShow(isChecked);
|
||||
};
|
||||
const handleOnlickHelp = () => {
|
||||
setIsProfileTour(false);
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title title={"Profile"} />
|
||||
@@ -340,12 +413,19 @@ function UserProfile() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center w-full relative">
|
||||
{isAlert && (
|
||||
{isAlert.message && (
|
||||
<Alert className="z-[1000] fixed top-[10%]" type={isAlert.type}>
|
||||
{isAlert.message}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Tour
|
||||
className="md:!min-w-[650px]"
|
||||
onRequestClose={closeTour}
|
||||
steps={tourConfig}
|
||||
isOpen={!isProfileTour}
|
||||
rounded={5}
|
||||
closeWithMask={false}
|
||||
/>
|
||||
<div className="bg-base-100 text-base-content flex flex-col justify-center shadow-md rounded-box w-[450px]">
|
||||
<div className="flex flex-col justify-center items-center my-4">
|
||||
<div className="w-[200px] h-[200px] overflow-hidden rounded-full">
|
||||
@@ -382,17 +462,6 @@ function UserProfile() {
|
||||
</div>
|
||||
</div>
|
||||
<ul className="w-full flex flex-col p-2 text-sm">
|
||||
<li
|
||||
className={`flex justify-between items-center border-t-[1px] border-gray-300 break-all ${
|
||||
editmode ? "py-1.5" : "py-2"
|
||||
}`}
|
||||
>
|
||||
<span className="font-semibold">{t("language")}:</span>{" "}
|
||||
<SelectLanguage
|
||||
isProfile={true}
|
||||
updateExtUser={updateExtUser}
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
className={`flex justify-between items-center border-t-[1px] border-gray-300 break-all ${
|
||||
editmode ? "py-1.5" : "py-2"
|
||||
@@ -403,7 +472,7 @@ function UserProfile() {
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-sm"
|
||||
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
|
||||
onChange={(e) => SetName(e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
@@ -419,7 +488,7 @@ function UserProfile() {
|
||||
{editmode ? (
|
||||
<input
|
||||
type="text"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-sm"
|
||||
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
|
||||
onChange={(e) => SetPhone(e.target.value)}
|
||||
value={Phone}
|
||||
/>
|
||||
@@ -441,7 +510,7 @@ function UserProfile() {
|
||||
<input
|
||||
type="text"
|
||||
value={company}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-sm"
|
||||
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
|
||||
onChange={(e) => setCompany(e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
@@ -458,7 +527,7 @@ function UserProfile() {
|
||||
<input
|
||||
type="text"
|
||||
value={jobTitle}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-sm"
|
||||
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
|
||||
onChange={(e) => setJobTitle(e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
@@ -486,40 +555,30 @@ function UserProfile() {
|
||||
</li>
|
||||
{isEnableSubscription && (
|
||||
<>
|
||||
<li className="flex md:flex-row flex-col md:justify-between md:items-center border-t-[1px] border-gray-300 py-2 break-all">
|
||||
<li className="flex flex-row justify-between md:items-center border-t-[1px] border-gray-300 py-2 break-all">
|
||||
<span className="font-semibold flex gap-1">
|
||||
{t("public-profile")} :{" "}
|
||||
<Tooltip
|
||||
maxWidth="max-w-[250px]"
|
||||
message={t("public-profile-help")}
|
||||
/>
|
||||
<Tooltip handleOnlickHelp={handleOnlickHelp} />
|
||||
</span>
|
||||
<div className="flex md:flex-row flex-col md:items-center">
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
{editmode || !extendUser?.[0]?.UserName ? (
|
||||
<input
|
||||
maxLength={40}
|
||||
style={{
|
||||
border:
|
||||
!isSubscribe &&
|
||||
publicUserName?.length > 0 &&
|
||||
publicUserName?.length < 9 &&
|
||||
"solid red"
|
||||
}}
|
||||
onChange={handleOnchangeUserName}
|
||||
value={publicUserName}
|
||||
disabled={!editmode}
|
||||
placeholder="enter user name"
|
||||
className="op-input op-input-bordered focus:outline-none hover:border-base-content op-input-xs"
|
||||
/>
|
||||
<>
|
||||
<input
|
||||
maxLength={40}
|
||||
onChange={handleOnchangeUserName}
|
||||
value={publicUserName}
|
||||
disabled={!editmode}
|
||||
placeholder="enter user name"
|
||||
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-row gap-1 items-center justify-between md:justify-start">
|
||||
<span
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
onClick={() => {
|
||||
openInNewTab(getPublicUrl);
|
||||
}}
|
||||
className="cursor-pointer underline hover:text-blue-800 w-[200px] md:w-[150px] whitespace-nowrap overflow-hidden text-ellipsis"
|
||||
onClick={() => openInNewTab(getPublicUrl)}
|
||||
className="cursor-pointer underline hover:text-blue-800 w-[110px] md:w-[150px] whitespace-nowrap overflow-hidden text-ellipsis"
|
||||
>
|
||||
{isStaging
|
||||
? `staging.opensign.me/${extendUser?.[0]?.UserName}`
|
||||
@@ -527,10 +586,7 @@ function UserProfile() {
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<RWebShare
|
||||
data={{
|
||||
url: getPublicUrl,
|
||||
title: "Sign url"
|
||||
}}
|
||||
data={{ url: getPublicUrl, title: "Sign url" }}
|
||||
>
|
||||
<button className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm ">
|
||||
<i className="fa-light fa-share-from-square"></i>{" "}
|
||||
@@ -547,30 +603,23 @@ function UserProfile() {
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex md:flex-row flex-col md:justify-between md:items-center border-t-[1px] border-gray-300 py-2 break-all">
|
||||
<li className="flex flex-row justify-between items-center border-t-[1px] border-gray-300 py-2 break-all">
|
||||
<span className="font-semibold flex gap-1">
|
||||
{t("tagline")} :{" "}
|
||||
<Tooltip
|
||||
maxWidth="max-w-[250px]"
|
||||
message={t("tagline-help")}
|
||||
/>
|
||||
<Tooltip handleOnlickHelp={handleOnlickHelp} />
|
||||
</span>
|
||||
<div className="flex md:flex-row flex-col md:items-center">
|
||||
{editmode ? (
|
||||
<div className="flex flex-row md:items-center gap-1">
|
||||
{editmode || !extendUser?.[0]?.Tagline ? (
|
||||
<input
|
||||
maxLength={40}
|
||||
onChange={handleOnchangeTagLine}
|
||||
value={tagLine}
|
||||
disabled={!editmode}
|
||||
placeholder="enter tagline"
|
||||
className="op-input op-input-bordered focus:outline-none hover:border-base-content op-input-xs"
|
||||
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
value={extendUser?.[0]?.Tagline}
|
||||
disabled
|
||||
placeholder="enter tagline"
|
||||
className="op-input op-input-bordered op-input-xs"
|
||||
/>
|
||||
<span>{extendUser?.[0]?.Tagline}</span>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
@@ -581,7 +630,9 @@ function UserProfile() {
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span
|
||||
className={
|
||||
isTeam ? "font-semibold" : "font-semibold text-gray-300"
|
||||
validplan[isPlan.plan]
|
||||
? "font-semibold"
|
||||
: "font-semibold text-gray-300"
|
||||
}
|
||||
>
|
||||
{t("disable-documentId")} :{" "}
|
||||
@@ -590,17 +641,21 @@ function UserProfile() {
|
||||
"https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid"
|
||||
}
|
||||
/>
|
||||
{!isTeam && isEnableSubscription && <Upgrade />}
|
||||
{!validplan[isPlan.plan] && isEnableSubscription && (
|
||||
<Upgrade />
|
||||
)}
|
||||
</span>
|
||||
<label
|
||||
className={`${
|
||||
isTeam
|
||||
validplan[isPlan.plan]
|
||||
? `${editmode ? "cursor-pointer" : ""}`
|
||||
: "pointer-events-none opacity-50"
|
||||
} relative block items-center mb-0`}
|
||||
>
|
||||
<input
|
||||
disabled={isTeam ? false : true}
|
||||
disabled={
|
||||
validplan[isPlan.plan] && editmode ? false : true
|
||||
}
|
||||
type="checkbox"
|
||||
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white"
|
||||
checked={isDisableDocId}
|
||||
@@ -610,6 +665,17 @@ function UserProfile() {
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
<li
|
||||
className={`flex justify-between items-center border-b-[1px] border-gray-300 break-all ${
|
||||
editmode ? "py-1.5" : "py-2"
|
||||
}`}
|
||||
>
|
||||
<span className="font-semibold">{t("language")}:</span>{" "}
|
||||
<SelectLanguage
|
||||
isProfile={true}
|
||||
updateExtUser={updateExtUser}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
className={`${
|
||||
@@ -620,6 +686,7 @@ function UserProfile() {
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
if (
|
||||
editmode &&
|
||||
!isSubscribe &&
|
||||
publicUserName?.length > 0 &&
|
||||
publicUserName?.length < 9
|
||||
@@ -689,6 +756,7 @@ function UserProfile() {
|
||||
)}
|
||||
</ModalUi>
|
||||
)}
|
||||
|
||||
{isUpgrade && (
|
||||
<div className="op-modal op-modal-open">
|
||||
<div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative">
|
||||
@@ -708,7 +776,7 @@ function UserProfile() {
|
||||
<p>{t("user-name-limit-char")}</p>
|
||||
<div className="op-card-actions justify-end">
|
||||
<button
|
||||
onClick={() => navigate("/subscription")}
|
||||
onClick={() => handlePaidRoute()}
|
||||
className="op-btn op-btn-accent"
|
||||
>
|
||||
{t("upgrade-now")}
|
||||
|
||||
@@ -19,7 +19,7 @@ function Webhook() {
|
||||
const [webhook, setWebhook] = useState();
|
||||
const [isLoader, setIsLoader] = useState(true);
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: true });
|
||||
const [error, setError] = useState("");
|
||||
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
|
||||
const [isTour, setIsTour] = useState(false);
|
||||
@@ -35,8 +35,8 @@ function Webhook() {
|
||||
];
|
||||
const fetchWebhook = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe);
|
||||
}
|
||||
try {
|
||||
const extRes = await Parse.Cloud.run("getUserDetails");
|
||||
@@ -89,7 +89,7 @@ function Webhook() {
|
||||
};
|
||||
|
||||
const handleModal = () => {
|
||||
if (!isSubscribe && isEnableSubscription) {
|
||||
if (!isSubscribe?.isValid && isEnableSubscription) {
|
||||
setIsTour(true);
|
||||
} else {
|
||||
setIsModal(!isModal);
|
||||
@@ -145,7 +145,7 @@ function Webhook() {
|
||||
</div>
|
||||
<ModalUi
|
||||
isOpen={isModal}
|
||||
title={"Regenerate Token"}
|
||||
title={t("add-webhook")}
|
||||
handleClose={handleModal}
|
||||
>
|
||||
{error && <Alert type="danger">{error}</Alert>}
|
||||
@@ -177,7 +177,7 @@ function Webhook() {
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
{!isSubscribe && isEnableSubscription && (
|
||||
{isEnableSubscription && !isSubscribe?.isValid && (
|
||||
<div data-tut="webhooksubscribe">
|
||||
<SubscribeCard />
|
||||
</div>
|
||||
|
||||
@@ -46,109 +46,125 @@ const AddContact = (props) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const contactQuery = new Parse.Object("contracts_Contactbook");
|
||||
contactQuery.set("Name", name);
|
||||
if (phone) {
|
||||
contactQuery.set("Phone", phone);
|
||||
}
|
||||
contactQuery.set("Email", email);
|
||||
contactQuery.set("UserRole", "contracts_Guest");
|
||||
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
contactQuery.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
}
|
||||
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
const _user = new _users();
|
||||
_user.set("name", name);
|
||||
_user.set("username", email);
|
||||
_user.set("email", email);
|
||||
_user.set("password", email);
|
||||
if (phone) {
|
||||
_user.set("phone", phone);
|
||||
}
|
||||
const user = Parse.User.current();
|
||||
const query = new Parse.Query("contracts_Contactbook");
|
||||
query.equalTo("CreatedBy", user);
|
||||
query.notEqualTo("IsDeleted", true);
|
||||
query.equalTo("Email", email);
|
||||
const res = await query.first();
|
||||
if (!res) {
|
||||
const contactQuery = new Parse.Object("contracts_Contactbook");
|
||||
contactQuery.set("Name", name);
|
||||
if (phone) {
|
||||
contactQuery.set("Phone", phone);
|
||||
}
|
||||
contactQuery.set("Email", email);
|
||||
contactQuery.set("UserRole", "contracts_Guest");
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.set("UserId", user);
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
props.details(parseData);
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
contactQuery.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
const _user = new _users();
|
||||
_user.set("name", name);
|
||||
_user.set("username", email);
|
||||
_user.set("email", email);
|
||||
_user.set("password", email);
|
||||
if (phone) {
|
||||
_user.set("phone", phone);
|
||||
}
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.set("UserId", user);
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
props.details(parseData);
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.set("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: userRes.id
|
||||
});
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.details) {
|
||||
props.details(parseData);
|
||||
}
|
||||
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert(t("add-signer-alert"));
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.set("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: userRes.id
|
||||
});
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.details) {
|
||||
props.details(parseData);
|
||||
}
|
||||
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
// console.log("err", err);
|
||||
setIsLoader(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
} catch (err) {
|
||||
// console.log("err", err);
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ const Alert = ({ children, type, className }) => {
|
||||
: "z-[1000] fixed top-20 left-1/2 transform -translate-x-1/2 text-sm"
|
||||
} `}
|
||||
>
|
||||
<div className={`op-alert ${textcolor} flex items-center`}>
|
||||
<span>{children}</span>
|
||||
<div className={`op-alert ${textcolor}`}>
|
||||
<span className="ml-3">{children}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ModalUi from "./ModalUi";
|
||||
import {
|
||||
getSignedUrl,
|
||||
handleDownloadCertificate,
|
||||
handleDownloadPdf,
|
||||
sanitizeFileName
|
||||
} from "../constant/Utils";
|
||||
import Loader from "./Loader";
|
||||
import JSZip from "jszip";
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
function DownloadPdfZip(props) {
|
||||
const { t } = useTranslation();
|
||||
const [selectType, setSelectType] = useState(1);
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const downloadType = [
|
||||
{ id: 1, label: t("download-pdf") },
|
||||
{ id: 2, label: t("pdf-certificate") }
|
||||
];
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (selectType === 1) {
|
||||
await handleDownloadPdf(props.pdfDetails, setIsDownloading);
|
||||
setSelectType(1);
|
||||
props.setIsDownloadModal(false);
|
||||
} else if (selectType === 2) {
|
||||
setIsDownloading("pdf");
|
||||
const zip = new JSZip();
|
||||
const pdfDetails = props.pdfDetails;
|
||||
const pdfName = pdfDetails?.[0]?.Name || "Document";
|
||||
const pdfUrl = pdfDetails?.[0]?.SignedUrl || "";
|
||||
|
||||
try {
|
||||
// Fetch the first PDF (Signed Document)
|
||||
const docId =
|
||||
props.isDocId && !pdfDetails?.[0]?.IsEnableOTP
|
||||
? pdfDetails?.[0]?.objectId
|
||||
: "";
|
||||
const signedUrl = await getSignedUrl(pdfUrl, docId);
|
||||
const pdf1Response = await fetch(signedUrl);
|
||||
if (!pdf1Response.ok) {
|
||||
throw new Error(`Failed to fetch PDF: ${signedUrl}`);
|
||||
}
|
||||
const pdf1Blob = await pdf1Response.blob();
|
||||
const isZip = true;
|
||||
// Fetch the Certificate (or generate its URL dynamically)
|
||||
const certificateUrl = await handleDownloadCertificate(
|
||||
pdfDetails,
|
||||
setIsDownloading,
|
||||
isZip
|
||||
);
|
||||
const pdf2Response = await fetch(certificateUrl);
|
||||
if (!pdf2Response.ok) {
|
||||
throw new Error(`Failed to fetch certificate PDF: ${certificateUrl}`);
|
||||
}
|
||||
const pdf2Blob = await pdf2Response.blob();
|
||||
// Add files to ZIP
|
||||
zip.file(
|
||||
`${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`,
|
||||
pdf1Blob
|
||||
);
|
||||
zip.file("Certificate_signed_by_OpenSign™.pdf", pdf2Blob);
|
||||
|
||||
// Generate the ZIP and trigger download
|
||||
const zipBlob = await zip.generateAsync({ type: "blob" });
|
||||
saveAs(
|
||||
zipBlob,
|
||||
`${sanitizeFileName(pdfName)}_signed_by_OpenSign™.zip`
|
||||
);
|
||||
setSelectType(1);
|
||||
props.setIsDownloadModal(false);
|
||||
setIsDownloading("");
|
||||
} catch (error) {
|
||||
console.error("Error creating ZIP file:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ModalUi
|
||||
isOpen={props.isDownloadModal}
|
||||
title={t("download-files")}
|
||||
handleClose={() => props.setIsDownloadModal(false)}
|
||||
>
|
||||
<div className="p-[20px] h-full">
|
||||
{downloadType.map((data, ind) => {
|
||||
return (
|
||||
<label
|
||||
key={ind}
|
||||
className="flex items-center gap-1 mb-2 cursor-pointer"
|
||||
>
|
||||
<input
|
||||
className="mr-[8px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
value={data.id}
|
||||
onChange={() => setSelectType(data.id)}
|
||||
checked={selectType === data.id}
|
||||
/>
|
||||
{data.label}
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
onClick={() => handleDownload()}
|
||||
type="submit"
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("download")}
|
||||
</button>
|
||||
</div>
|
||||
{isDownloading === "pdf" && (
|
||||
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isDownloading === "certificate"}
|
||||
title={
|
||||
isDownloading === "certificate"
|
||||
? t("generating-certificate")
|
||||
: t("pdf-download")
|
||||
}
|
||||
handleClose={() => setIsDownloading("")}
|
||||
>
|
||||
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
|
||||
{isDownloading === "certificate"}{" "}
|
||||
<p>{t("generate-certificate-alert")}</p>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
|
||||
export default DownloadPdfZip;
|
||||
@@ -11,9 +11,10 @@ import { RWebShare } from "react-web-share";
|
||||
import Tour from "reactour";
|
||||
import Parse from "parse";
|
||||
import {
|
||||
checkIsSubscribedTeam,
|
||||
checkIsSubscribed,
|
||||
copytoData,
|
||||
fetchUrl,
|
||||
getSignedUrl,
|
||||
replaceMailVaribles
|
||||
} from "../constant/Utils";
|
||||
import Confetti from "react-confetti";
|
||||
@@ -27,8 +28,10 @@ import BulkSendUi from "../components/BulkSendUi";
|
||||
import Loader from "./Loader";
|
||||
import Select from "react-select";
|
||||
import SubscribeCard from "./SubscribeCard";
|
||||
import { validplan } from "../json/plansArr";
|
||||
import { serverUrl_fn } from "../constant/appinfo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DownloadPdfZip from "./DownloadPdfZip";
|
||||
|
||||
const ReportTable = (props) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -68,20 +71,14 @@ const ReportTable = (props) => {
|
||||
const [publicUserName, setIsPublicUserName] = useState("");
|
||||
const [isViewShare, setIsViewShare] = useState({});
|
||||
const [isSubscribe, setIsSubscribe] = useState(true);
|
||||
const [isModal, setIsModal] = useState({});
|
||||
const [reason, setReason] = useState("");
|
||||
const [isDownloadModal, setIsDownloadModal] = useState(false);
|
||||
const Extand_Class = localStorage.getItem("Extand_Class");
|
||||
const extClass = Extand_Class && JSON.parse(Extand_Class);
|
||||
const startIndex = (currentPage - 1) * props.docPerPage;
|
||||
const { isMoreDocs, setIsNextRecord } = props;
|
||||
// For loop is used to calculate page numbers visible below table
|
||||
// Initialize pageNumbers using useMemo to avoid unnecessary re-creation
|
||||
// const pageNumbers = useMemo(() => {
|
||||
// const calculatedPageNumbers = [];
|
||||
// for (let i = 1; i <= Math.ceil(props.List.length / props.docPerPage); i++) {
|
||||
// calculatedPageNumbers.push(i);
|
||||
// }
|
||||
// return calculatedPageNumbers;
|
||||
// }, [props.List, props.docPerPage]);
|
||||
|
||||
const getPaginationRange = () => {
|
||||
const totalPageNumbers = 7; // Adjust this value to show more/less page numbers
|
||||
const pages = [];
|
||||
@@ -273,7 +270,8 @@ const ReportTable = (props) => {
|
||||
Signers: signers,
|
||||
SendinOrder: Doc?.SendinOrder || false,
|
||||
AutomaticReminders: Doc?.AutomaticReminders || false,
|
||||
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5
|
||||
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5,
|
||||
IsEnableOTP: Doc?.IsEnableOTP || false
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(
|
||||
@@ -355,10 +353,10 @@ const ReportTable = (props) => {
|
||||
handleBulkSend(item);
|
||||
} else if (act.action === "sharewith") {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribedTeam();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe);
|
||||
} else {
|
||||
setIsSubscribe(true);
|
||||
setIsSubscribe({ plan: "teams-yearly", isValid: true });
|
||||
}
|
||||
if (item?.SharedWith && item?.SharedWith.length > 0) {
|
||||
// below code is used to get existing sharewith teams and formated them as per react-select
|
||||
@@ -369,6 +367,8 @@ const ReportTable = (props) => {
|
||||
setSelectedTeam(formatedList);
|
||||
}
|
||||
setIsShareWith({ [item.objectId]: true });
|
||||
} else if (act.action === "Embed") {
|
||||
handleEmbedFunction(item);
|
||||
}
|
||||
};
|
||||
// Get current list
|
||||
@@ -377,8 +377,18 @@ const ReportTable = (props) => {
|
||||
const currentList = props.List?.slice(indexOfFirstDoc, indexOfLastDoc);
|
||||
|
||||
// Change page
|
||||
const paginateFront = () => setCurrentPage(currentPage + 1);
|
||||
const paginateBack = () => setCurrentPage(currentPage - 1);
|
||||
const paginateFront = () => {
|
||||
const lastValue = pageNumbers?.[pageNumbers?.length - 1];
|
||||
if (currentPage < lastValue) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const paginateBack = () => {
|
||||
if (startIndex > 0) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleContactFormModal = () => {
|
||||
setIsContactform(!isContactform);
|
||||
@@ -396,7 +406,7 @@ const ReportTable = (props) => {
|
||||
Templates: "contracts_Template"
|
||||
};
|
||||
try {
|
||||
const serverUrl = serverUrl_fn()
|
||||
const serverUrl = serverUrl_fn();
|
||||
const cls = clsObj[props.ReportName] || "contracts_Document";
|
||||
const url = serverUrl + `/classes/${cls}/`;
|
||||
const body =
|
||||
@@ -448,7 +458,6 @@ const ReportTable = (props) => {
|
||||
}
|
||||
setReason("");
|
||||
};
|
||||
|
||||
const handleShare = (item) => {
|
||||
setActLoader({ [item.objectId]: true });
|
||||
const host = window.location.origin;
|
||||
@@ -479,9 +488,21 @@ const ReportTable = (props) => {
|
||||
};
|
||||
//function to handle revoke/decline docment
|
||||
const handleRevoke = async (item) => {
|
||||
const senderUser = localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
);
|
||||
const jsonSender = JSON.parse(senderUser);
|
||||
setIsRevoke({});
|
||||
setActLoader({ [`${item.objectId}`]: true });
|
||||
const data = { IsDeclined: true, DeclineReason: reason };
|
||||
const data = {
|
||||
IsDeclined: true,
|
||||
DeclineReason: reason,
|
||||
DeclineBy: {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: jsonSender?.objectId
|
||||
}
|
||||
};
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${
|
||||
@@ -499,7 +520,6 @@ const ReportTable = (props) => {
|
||||
.then(async (result) => {
|
||||
const res = result.data;
|
||||
if (res) {
|
||||
setReason("");
|
||||
setActLoader({});
|
||||
setIsAlert(true);
|
||||
setAlertMsg({
|
||||
@@ -511,7 +531,43 @@ const ReportTable = (props) => {
|
||||
(x) => x.objectId !== item.objectId
|
||||
);
|
||||
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) => {
|
||||
console.log("err", err);
|
||||
@@ -596,10 +652,15 @@ const ReportTable = (props) => {
|
||||
setActLoader({ [`${item.objectId}`]: true });
|
||||
const url = item?.SignedUrl || item?.URL || "";
|
||||
const pdfName = item?.Name || "exported_file";
|
||||
const isCompleted = item?.IsCompleted || false;
|
||||
if (url) {
|
||||
try {
|
||||
const signedUrl = await Parse.Cloud.run("getsignedurl", { url: url });
|
||||
await fetchUrl(signedUrl, pdfName);
|
||||
if (isCompleted) {
|
||||
setIsDownloadModal({ [item.objectId]: true });
|
||||
} else {
|
||||
const signedUrl = await getSignedUrl(url);
|
||||
await fetchUrl(signedUrl, pdfName);
|
||||
}
|
||||
setActLoader({});
|
||||
} catch (err) {
|
||||
console.log("err in getsignedurl", err);
|
||||
@@ -725,7 +786,7 @@ const ReportTable = (props) => {
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
let params = {
|
||||
mailProvider: doc?.ExtUserPtr?.active_mail_adapter,
|
||||
mailProvider: doc?.ExtUserPtr?.active_mail_adapter || "",
|
||||
extUserId: doc?.ExtUserPtr?.objectId,
|
||||
recipient: userDetails?.Email,
|
||||
subject: mail.subject,
|
||||
@@ -790,13 +851,13 @@ const ReportTable = (props) => {
|
||||
if (count > 1) {
|
||||
setAlertMsg({
|
||||
type: "success",
|
||||
message: count + t("document-sent-alert")
|
||||
message: count + " " + t("document-sent-alert")
|
||||
});
|
||||
setTimeout(() => setIsAlert(false), 1500);
|
||||
} else {
|
||||
setAlertMsg({
|
||||
type: "success",
|
||||
message: count + t("document-sent-alert")
|
||||
message: count + " " + t("document-sent-alert")
|
||||
});
|
||||
setTimeout(() => setIsAlert(false), 1500);
|
||||
}
|
||||
@@ -838,14 +899,10 @@ const ReportTable = (props) => {
|
||||
console.log("err in fetch template in bulk modal", err);
|
||||
setIsBulkSend({});
|
||||
setIsAlert(true);
|
||||
setAlertMsg({
|
||||
type: "danger",
|
||||
message: t("something-went-wrong-mssg")
|
||||
});
|
||||
setAlertMsg({ type: "danger", message: t("something-went-wrong-mssg") });
|
||||
setTimeout(() => setIsAlert(false), 1500);
|
||||
}
|
||||
};
|
||||
|
||||
//function to make template public and set public role
|
||||
const handlePublicTemplate = async (item) => {
|
||||
if (selectedPublicRole || !props.isPublic[item.objectId]) {
|
||||
@@ -1005,9 +1062,26 @@ const ReportTable = (props) => {
|
||||
type: "success",
|
||||
message: t("copied")
|
||||
});
|
||||
setTimeout(() => setIsAlert(false), 1500);
|
||||
setCopied({ ...copied, publicprofile: true });
|
||||
setTimeout(() => {
|
||||
setIsAlert(false);
|
||||
setCopied(false);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const copyTemplateId = (templateid) => {
|
||||
copytoData(templateid);
|
||||
setIsAlert(true);
|
||||
setAlertMsg({
|
||||
type: "success",
|
||||
message: t("copied")
|
||||
});
|
||||
setCopied({ ...copied, templateid: true });
|
||||
setTimeout(() => {
|
||||
setIsAlert(false);
|
||||
setCopied(false);
|
||||
}, 1500);
|
||||
};
|
||||
const handleShowRole = (item) => {
|
||||
const getRole = item.Placeholders.find((data) => !data.signerObjId);
|
||||
return getRole?.Role;
|
||||
@@ -1046,7 +1120,13 @@ const ReportTable = (props) => {
|
||||
setTimeout(() => setIsAlert(false), 1500);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEmbedFunction = async (item) => {
|
||||
setIsPublicProfile({
|
||||
[item.objectId]: props.isPublic[item.objectId]
|
||||
});
|
||||
let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
setIsPublicUserName(extendUser[0]?.UserName || "");
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{Object.keys(actLoader)?.length > 0 && (
|
||||
@@ -1054,10 +1134,15 @@ const ReportTable = (props) => {
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div className="p-2 w-full overflow-auto bg-base-100 text-base-content op-card shadow-lg">
|
||||
<div className="p-2 w-full overflow-hidden bg-base-100 text-base-content op-card shadow-lg">
|
||||
{isCelebration && (
|
||||
<div className="relative z-[1000]">
|
||||
<Confetti width={window.innerWidth} height={window.innerHeight} />
|
||||
<Confetti
|
||||
width={window.innerWidth}
|
||||
height={window.innerHeight}
|
||||
recycle={false} // Prevents confetti from repeating
|
||||
gravity={0.1} // Adjust the gravity to control the speed
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isAlert && <Alert type={alertMsg.type}>{alertMsg.message}</Alert>}
|
||||
@@ -1097,8 +1182,8 @@ const ReportTable = (props) => {
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isDashboard && props.List?.length > 0 ? "h-[317px]" : "h-full"
|
||||
} w-full`}
|
||||
isDashboard && props.List?.length > 0 ? "min-h-[317px]" : "h-full"
|
||||
} overflow-auto w-full`}
|
||||
>
|
||||
<table className="op-table border-collapse w-full ">
|
||||
<thead className="text-[14px]">
|
||||
@@ -1134,48 +1219,50 @@ const ReportTable = (props) => {
|
||||
</td>
|
||||
<td className="px-4 py-2 ">{item?.Email || "-"}</td>
|
||||
<td className="px-4 py-2">{item?.Phone || "-"}</td>
|
||||
<td className="px-3 py-2 text-white grid grid-cols-2">
|
||||
{props.actions?.length > 0 &&
|
||||
props.actions.map((act, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleActionBtn(act, item)}
|
||||
title={t(`btnLabel.${act.hoverLabel}`)}
|
||||
className={`${
|
||||
act?.btnColor ? act.btnColor : ""
|
||||
} op-btn op-btn-sm`}
|
||||
<td className="px-3 py-2">
|
||||
<div className="text-base-content min-w-max flex flex-row gap-x-2 gap-y-1 justify-start items-center">
|
||||
{props.actions?.length > 0 &&
|
||||
props.actions.map((act, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleActionBtn(act, item)}
|
||||
title={t(`btnLabel.${act.hoverLabel}`)}
|
||||
className={`${
|
||||
act?.btnColor ? act.btnColor : ""
|
||||
} op-btn op-btn-sm`}
|
||||
>
|
||||
<i className={act.btnIcon}></i>
|
||||
</button>
|
||||
))}
|
||||
{isDeleteModal[item.objectId] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={"Delete Contact"}
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<i className={act.btnIcon}></i>
|
||||
</button>
|
||||
))}
|
||||
{isDeleteModal[item.objectId] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={"Delete Contact"}
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
{t("contact-delete-alert")}
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
{t("contact-delete-alert")}
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-4 " />
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button
|
||||
onClick={() => handleDelete(item)}
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
{t("no")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-4 " />
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button
|
||||
onClick={() => handleDelete(item)}
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
{t("no")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
</ModalUi>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
@@ -1217,11 +1304,57 @@ const ReportTable = (props) => {
|
||||
{item?.URL ? t("download") : "-"}
|
||||
</button>
|
||||
</td>
|
||||
{props.ReportName === "In-progress documents" ? (
|
||||
<td className="px-4 py-2">
|
||||
<button
|
||||
onClick={() =>
|
||||
item?.AuditTrail?.length > 0 &&
|
||||
setIsModal({ [item?.objectId]: true })
|
||||
}
|
||||
className={`${
|
||||
item?.AuditTrail?.length
|
||||
? "border-green-400"
|
||||
: "cursor-default op-border-primary"
|
||||
} focus:outline-none w-[60px] border-[2px] text-[12px] rounded-full md:self-center`}
|
||||
>
|
||||
{item?.AuditTrail?.length ? "VIEWED" : "SENT"}
|
||||
</button>
|
||||
{isModal[item.objectId] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={t("document-logs")}
|
||||
handleClose={() => setIsModal({})}
|
||||
>
|
||||
{item?.AuditTrail?.map((x, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="pl-3 first:mt-2 text-sm font-medium flex flex-col md:flex-row items-start md:gap-4 border-t-[1px] border-gray-600"
|
||||
>
|
||||
<div className="py-2 break-all font-bold md:text-[12px] md:col-span-2 w-full md:w-[210px]">
|
||||
{x?.UserPtr?.Email || "-"}
|
||||
</div>
|
||||
<button className="px-2 cursor-default border-[2px] text-[12px] border-green-400 rounded-full md:self-center">
|
||||
{x?.Activity?.toUpperCase() || "-"}
|
||||
</button>
|
||||
<div className=" text-[12px] py-2">
|
||||
{x?.Activity === "Signed"
|
||||
? new Date(x?.SignedOn)?.toUTCString()
|
||||
: new Date(
|
||||
x?.ViewedOn
|
||||
)?.toUTCString() || "-"}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</ModalUi>
|
||||
)}
|
||||
</td>
|
||||
) : (
|
||||
<td className="px-4 py-2">
|
||||
{formatRow(item?.ExtUserPtr)}
|
||||
</td>
|
||||
)}
|
||||
<td className="px-4 py-2">
|
||||
{formatRow(item?.ExtUserPtr)}
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
{item?.Placeholders ? (
|
||||
{!item?.IsSignyourself && item?.Placeholders ? (
|
||||
<button
|
||||
onClick={() => handleViewSigners(item)}
|
||||
className="op-link op-link-primary"
|
||||
@@ -1237,11 +1370,7 @@ const ReportTable = (props) => {
|
||||
<td className=" pl-[20px] py-2">
|
||||
{props.ReportName === "Templates" && (
|
||||
<div className="flex flex-row">
|
||||
<label
|
||||
className={
|
||||
"cursor-pointer relative inline-flex items-center mb-0"
|
||||
}
|
||||
>
|
||||
<label className="cursor-pointer relative inline-flex items-center mb-0">
|
||||
<input
|
||||
checked={props.isPublic?.[item.objectId]}
|
||||
onChange={(e) =>
|
||||
@@ -1317,24 +1446,84 @@ const ReportTable = (props) => {
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={t("public-url")}
|
||||
handleClose={() => {
|
||||
setIsPublicProfile({});
|
||||
}}
|
||||
handleClose={() => setIsPublicProfile({})}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
{publicUserName ? (
|
||||
<div className="font-normal text-black">
|
||||
<p>{t("public-url-copy-mssg")}</p>
|
||||
<div className=" flex items-center gap-3 mt-2 p-[2px] ">
|
||||
<span className="font-bold">
|
||||
{t("public-url")}:
|
||||
</span>
|
||||
<span
|
||||
onClick={() => copytoProfileLink()}
|
||||
className="underline underline-offset-2 w-[150px] md:w-[350px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer"
|
||||
>
|
||||
{publicUrl()}
|
||||
</span>
|
||||
<div className=" flex items-center justify-between gap-3 mt-2 p-[2px] ">
|
||||
<div className="w-[280px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
<span className="font-bold">
|
||||
{t("public-url")} :
|
||||
</span>
|
||||
<span
|
||||
onClick={() =>
|
||||
copytoProfileLink()
|
||||
}
|
||||
className="ml-[2px] underline underline-offset-2 cursor-pointer"
|
||||
>
|
||||
{publicUrl()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RWebShare
|
||||
data={{
|
||||
url: publicUrl(),
|
||||
title: "Public url"
|
||||
}}
|
||||
>
|
||||
<button className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm ">
|
||||
<i className="fa-light fa-share-from-square"></i>{" "}
|
||||
<span className="hidden md:inline-block">
|
||||
{t("btnLabel.Share")}
|
||||
</span>
|
||||
</button>
|
||||
</RWebShare>
|
||||
<button
|
||||
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
|
||||
onClick={() =>
|
||||
copytoProfileLink()
|
||||
}
|
||||
>
|
||||
<i className="fa-light fa-link"></i>{" "}
|
||||
<span className="hidden md:inline-block">
|
||||
{copied["publicprofile"]
|
||||
? t("copied")
|
||||
: t("copy")}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className=" flex items-center justify-between gap-3 mt-2 p-[2px] ">
|
||||
<div>
|
||||
<span className="font-bold">
|
||||
{t("templateid")} :
|
||||
</span>
|
||||
<span
|
||||
onClick={() =>
|
||||
copyTemplateId(item.objectId)
|
||||
}
|
||||
className="underline ml-[2px] underline-offset-2 w-[150px] md:w-[350px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer"
|
||||
>
|
||||
{item.objectId}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
|
||||
onClick={() =>
|
||||
copyTemplateId(item.objectId)
|
||||
}
|
||||
>
|
||||
<i className="fa-light fa-link"></i>{" "}
|
||||
<span className="hidden md:inline-block">
|
||||
{copied["templateid"]
|
||||
? t("copied")
|
||||
: t("copy")}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -1354,7 +1543,7 @@ const ReportTable = (props) => {
|
||||
</td>
|
||||
)}
|
||||
<td className="px-2 py-2">
|
||||
<div className="text-base-content flex flex-row gap-x-2 gap-y-1 justify-start items-center">
|
||||
<div className="text-base-content min-w-max flex flex-row gap-x-2 gap-y-1 justify-start items-center">
|
||||
{props.actions?.length > 0 &&
|
||||
props.actions.map((act, index) =>
|
||||
props.ReportName === "Templates" ? (
|
||||
@@ -1383,39 +1572,77 @@ const ReportTable = (props) => {
|
||||
<i className={act.btnIcon}></i>
|
||||
{act.btnLabel && (
|
||||
<span className="uppercase font-medium">
|
||||
{t(`btnLabel.${act.btnLabel}`)}
|
||||
{act.btnLabel.includes(
|
||||
"Quick send"
|
||||
) && isEnableSubscription
|
||||
? "Bulk Send"
|
||||
: `${t(
|
||||
`btnLabel.${act.btnLabel}`
|
||||
)}`}
|
||||
</span>
|
||||
)}
|
||||
{isOption[item.objectId] &&
|
||||
act.action === "option" && (
|
||||
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow bg-base-100 text-base-content rounded-box">
|
||||
{act.subaction?.map((subact) => (
|
||||
<li
|
||||
key={subact.btnId}
|
||||
onClick={() =>
|
||||
handleActionBtn(
|
||||
subact,
|
||||
item
|
||||
)
|
||||
}
|
||||
title={t(
|
||||
`btnLabel.${subact.btnLabel}`
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
className={`${subact.btnIcon} mr-1.5`}
|
||||
></i>
|
||||
{subact.btnLabel && (
|
||||
<span className="text-[13px] capitalize font-medium">
|
||||
{t(
|
||||
`btnLabel.${subact.btnLabel}`
|
||||
{act.subaction?.map((subact) =>
|
||||
props.isPublic[
|
||||
item.objectId
|
||||
] ? (
|
||||
<li
|
||||
key={subact.btnId}
|
||||
onClick={() =>
|
||||
handleActionBtn(
|
||||
subact,
|
||||
item
|
||||
)
|
||||
}
|
||||
title={t(
|
||||
`btnLabel.${subact.btnLabel}`
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
className={`${subact.btnIcon} mr-1.5`}
|
||||
></i>
|
||||
{subact.btnLabel && (
|
||||
<span className="text-[13px] capitalize font-medium">
|
||||
{t(
|
||||
`btnLabel.${subact.btnLabel}`
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
) : (
|
||||
subact.action !== "Embed" && (
|
||||
<li
|
||||
key={subact.btnId}
|
||||
onClick={() =>
|
||||
handleActionBtn(
|
||||
subact,
|
||||
item
|
||||
)
|
||||
}
|
||||
title={t(
|
||||
`btnLabel.${subact.btnLabel}`
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
className={`${subact.btnIcon} mr-1.5`}
|
||||
></i>
|
||||
{subact.btnLabel && (
|
||||
<span className="text-[13px] capitalize font-medium">
|
||||
{t(
|
||||
`btnLabel.${subact.btnLabel}`
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</li>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
@@ -1478,114 +1705,117 @@ const ReportTable = (props) => {
|
||||
{isShareWith[item.objectId] && (
|
||||
<div className="op-modal op-modal-open">
|
||||
<div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative">
|
||||
{isSubscribe && isEnableSubscription && (
|
||||
<>
|
||||
{item?.Signers?.length > 0 ? (
|
||||
<div className="h-[150px] flex justify-center items-center mx-2">
|
||||
<div
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
|
||||
onClick={() => setIsShareWith({})}
|
||||
>
|
||||
✕
|
||||
{validplan[isSubscribe.plan] &&
|
||||
isEnableSubscription && (
|
||||
<>
|
||||
{item?.Signers?.length > 0 ? (
|
||||
<div className="h-[150px] flex justify-center items-center mx-2">
|
||||
<div
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
|
||||
onClick={() => setIsShareWith({})}
|
||||
>
|
||||
✕
|
||||
</div>
|
||||
<div className="text-base-content text-base text-center">
|
||||
{t("share-with-alert")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-base-content text-base text-center">
|
||||
{t("share-with-alert")}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||
{t("share-with")}
|
||||
</h3>
|
||||
<div
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
|
||||
onClick={() => setIsShareWith({})}
|
||||
>
|
||||
✕
|
||||
</div>
|
||||
<form
|
||||
className="h-full w-full z-[1300] px-2 mt-3"
|
||||
onSubmit={(e) =>
|
||||
handleShareWith(e, item)
|
||||
}
|
||||
>
|
||||
<Select
|
||||
// onSortEnd={onSortEnd}
|
||||
distance={4}
|
||||
isMulti
|
||||
options={teamList}
|
||||
value={selectedTeam}
|
||||
onChange={onChange}
|
||||
closeMenuOnSelect
|
||||
required={true}
|
||||
noOptionsMessage={() =>
|
||||
t("team-not-found")
|
||||
) : (
|
||||
<>
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||
{t("share-with")}
|
||||
</h3>
|
||||
<div
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
|
||||
onClick={() => setIsShareWith({})}
|
||||
>
|
||||
✕
|
||||
</div>
|
||||
<form
|
||||
className="h-full w-full z-[1300] px-2 mt-3"
|
||||
onSubmit={(e) =>
|
||||
handleShareWith(e, item)
|
||||
}
|
||||
unstyled
|
||||
classNames={{
|
||||
control: () =>
|
||||
"op-input op-input-bordered op-input-sm border-gray-400 focus:outline-none hover:border-base-content w-full h-full text-[11px]",
|
||||
valueContainer: () =>
|
||||
"flex flex-row gap-x-[2px] gap-y-[2px] md:gap-y-0 w-full my-[2px]",
|
||||
multiValue: () =>
|
||||
"op-badge op-badge-primary h-full text-[11px]",
|
||||
multiValueLabel: () => "mb-[2px]",
|
||||
menu: () =>
|
||||
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content",
|
||||
menuList: () =>
|
||||
"shadow-md rounded-lg overflow-hidden",
|
||||
option: () =>
|
||||
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2",
|
||||
noOptionsMessage: () =>
|
||||
"p-2 bg-base-200 rounded-lg m-1 p-2"
|
||||
}}
|
||||
/>
|
||||
<button className="op-btn op-btn-primary ml-[10px] my-3">
|
||||
{t("submit")}
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!isSubscribe && isEnableSubscription && (
|
||||
<>
|
||||
<div
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-primary-content absolute right-2 top-2 z-40"
|
||||
onClick={() => setIsShareWith({})}
|
||||
>
|
||||
✕
|
||||
</div>
|
||||
<SubscribeCard
|
||||
plan={"TEAMS"}
|
||||
price={"20"}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{isSubscribe && !isEnableSubscription && (
|
||||
<>
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||
{t("share-with")}
|
||||
</h3>
|
||||
<div
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
|
||||
onClick={() => setIsShareWith({})}
|
||||
>
|
||||
✕
|
||||
</div>
|
||||
<div className="px-2 mt-3 w-full h-full">
|
||||
<div className="op-input op-input-bordered op-input-sm w-full h-full text-[13px] break-all">
|
||||
{selectedTeam?.[0]?.label}
|
||||
>
|
||||
<Select
|
||||
// onSortEnd={onSortEnd}
|
||||
distance={4}
|
||||
isMulti
|
||||
options={teamList}
|
||||
value={selectedTeam}
|
||||
onChange={onChange}
|
||||
closeMenuOnSelect
|
||||
required={true}
|
||||
noOptionsMessage={() =>
|
||||
t("team-not-found")
|
||||
}
|
||||
unstyled
|
||||
classNames={{
|
||||
control: () =>
|
||||
"op-input op-input-bordered op-input-sm border-gray-400 focus:outline-none hover:border-base-content w-full h-full text-[11px]",
|
||||
valueContainer: () =>
|
||||
"flex flex-row gap-x-[2px] gap-y-[2px] md:gap-y-0 w-full my-[2px]",
|
||||
multiValue: () =>
|
||||
"op-badge op-badge-primary h-full text-[11px]",
|
||||
multiValueLabel: () =>
|
||||
"mb-[2px]",
|
||||
menu: () =>
|
||||
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content",
|
||||
menuList: () =>
|
||||
"shadow-md rounded-lg overflow-hidden",
|
||||
option: () =>
|
||||
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2",
|
||||
noOptionsMessage: () =>
|
||||
"p-2 bg-base-200 rounded-lg m-1 p-2"
|
||||
}}
|
||||
/>
|
||||
<button className="op-btn op-btn-primary ml-[10px] my-3">
|
||||
{t("submit")}
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isEnableSubscription &&
|
||||
!validplan[isSubscribe.plan] && (
|
||||
<>
|
||||
<div
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-primary-content absolute right-2 top-2 z-40"
|
||||
onClick={() => setIsShareWith({})}
|
||||
>
|
||||
✕
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => handleShareWith(e, item)}
|
||||
className="op-btn op-btn-primary ml-[10px] my-3"
|
||||
>
|
||||
{t("submit")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<SubscribeCard plan="TEAMS" />
|
||||
</>
|
||||
)}
|
||||
{!isEnableSubscription &&
|
||||
validplan[isSubscribe.plan] && (
|
||||
<>
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||
{t("share-with")}
|
||||
</h3>
|
||||
<div
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
|
||||
onClick={() => setIsShareWith({})}
|
||||
>
|
||||
✕
|
||||
</div>
|
||||
<div className="px-2 mt-3 w-full h-full">
|
||||
<div className="op-input op-input-bordered op-input-sm w-full h-full text-[13px] break-all">
|
||||
{selectedTeam?.[0]?.label}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) =>
|
||||
handleShareWith(e, item)
|
||||
}
|
||||
className="op-btn op-btn-primary ml-[10px] my-3"
|
||||
>
|
||||
{t("submit")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1623,20 +1853,26 @@ const ReportTable = (props) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{item.Placeholders.map((x, i) => (
|
||||
<tr key={i} className="text-sm font-medium">
|
||||
{props.ReportName === "Templates" && (
|
||||
<td className="text-[12px] p-2 pl-3 w-[30%]">
|
||||
{x.Role && x.Role}
|
||||
</td>
|
||||
)}
|
||||
<td className="pl-3 text-[12px] py-2 break-all">
|
||||
{x.email
|
||||
? x.email
|
||||
: x?.signerPtr?.Email || "-"}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{item.Placeholders.map(
|
||||
(x, i) =>
|
||||
x.Role !== "prefill" && (
|
||||
<tr
|
||||
key={i}
|
||||
className="text-sm font-medium"
|
||||
>
|
||||
{props.ReportName === "Templates" && (
|
||||
<td className="text-[12px] p-2 pl-3 w-[30%]">
|
||||
{x.Role && x.Role}
|
||||
</td>
|
||||
)}
|
||||
<td className="pl-3 text-[12px] py-2 break-all">
|
||||
{x.email
|
||||
? x.email
|
||||
: x?.signerPtr?.Email || "-"}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</ModalUi>
|
||||
@@ -1672,11 +1908,15 @@ const ReportTable = (props) => {
|
||||
{isBulkSend[item.objectId] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={t("quick-send")}
|
||||
title={
|
||||
isEnableSubscription
|
||||
? "Bulk send"
|
||||
: t("quick-send")
|
||||
}
|
||||
handleClose={() => setIsBulkSend({})}
|
||||
>
|
||||
{isLoader[item.objectId] ? (
|
||||
<div className="w-full h-[100px] flex justify-center items-center bg-opacity-30 z-30">
|
||||
<div className="w-full h-[100px] flex justify-center items-center z-30">
|
||||
<Loader />
|
||||
</div>
|
||||
) : (
|
||||
@@ -1877,6 +2117,14 @@ const ReportTable = (props) => {
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
{isDownloadModal[item.objectId] && (
|
||||
<DownloadPdfZip
|
||||
setIsDownloadModal={setIsDownloadModal}
|
||||
isDownloadModal={isDownloadModal[item.objectId]}
|
||||
pdfDetails={[item]}
|
||||
isDocId={false}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
||||
@@ -21,9 +21,11 @@ const ModalUi = ({
|
||||
>
|
||||
{showHeader && (
|
||||
<>
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||
{title}
|
||||
</h3>
|
||||
{title && (
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||
{title}
|
||||
</h3>
|
||||
)}
|
||||
{showClose && (
|
||||
<button
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2"
|
||||
|
||||
@@ -2,27 +2,30 @@ import React, { useState } from "react";
|
||||
import "../styles/signature.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function CustomModal({
|
||||
show,
|
||||
headMsg,
|
||||
bodyMssg,
|
||||
footerMessage,
|
||||
declineDoc,
|
||||
setIsDecline
|
||||
}) {
|
||||
function CustomModal(props) {
|
||||
const { t } = useTranslation();
|
||||
const [reason, setReason] = useState("");
|
||||
return (
|
||||
show && (
|
||||
props.show && (
|
||||
<dialog className="op-modal op-modal-open absolute z-[448]">
|
||||
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box p-0 overflow-y-auto hide-scrollbar text-sm">
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||
{headMsg && headMsg}
|
||||
{props?.headMsg && props?.headMsg}
|
||||
</h3>
|
||||
<div className="p-[10px] px-[20px]">
|
||||
<p className="text-[15px]">{bodyMssg && bodyMssg}</p>
|
||||
<p className="text-[15px]">{props.bodyMssg && props.bodyMssg}</p>
|
||||
</div>
|
||||
{footerMessage && (
|
||||
{props.isDownloadBtn && (
|
||||
<div className="flex justify-start w-full ml-[20px] mb-3 mt-1">
|
||||
<button
|
||||
className="op-btn op-btn-primary "
|
||||
onClick={() => props.handleDownloadBtn()}
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{props.footerMessage && (
|
||||
<>
|
||||
<div className="mx-3">
|
||||
<textarea
|
||||
@@ -33,14 +36,13 @@ function CustomModal({
|
||||
onChange={(e) => setReason(e.target.value)}
|
||||
></textarea>
|
||||
</div>
|
||||
{/* <div className="h-[1px] bg-[#9f9f9f] w-full"></div> */}
|
||||
<div className="m-[15px]">
|
||||
<button
|
||||
className="op-btn op-btn-primary mr-2 px-6"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
props.declineDoc(reason);
|
||||
setReason("");
|
||||
declineDoc(reason);
|
||||
}}
|
||||
>
|
||||
{t("yes")}
|
||||
@@ -50,7 +52,7 @@ function CustomModal({
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => {
|
||||
setReason("");
|
||||
setIsDecline({ isDeclined: false });
|
||||
props.setIsDecline({ isDeclined: false });
|
||||
}}
|
||||
>
|
||||
{t("close")}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const QuotaCard = ({ isPaidInfo, handlClose }) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
return isPaidInfo ? (
|
||||
<>
|
||||
<div
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-primary-content absolute right-2 top-2 z-40"
|
||||
onClick={() => handlClose && handlClose()}
|
||||
>
|
||||
✕
|
||||
</div>
|
||||
<div className="op-card op-bg-primary text-primary-content w-full shadow-lg">
|
||||
<div className="op-card-body">
|
||||
<h2 className="op-card-title">{t("quota-mail-info-head")}</h2>
|
||||
<p>{t("quota-mail-info")}</p>
|
||||
<p>
|
||||
<Trans i18nKey={"quota-mail-tip"}>
|
||||
Tip: You can still sign <strong>unlimited documents</strong> by
|
||||
manually sharing the signing request links.
|
||||
</Trans>
|
||||
</p>
|
||||
<div className="op-card-actions justify-end">
|
||||
<button
|
||||
onClick={() => navigate("/subscription")}
|
||||
className="op-btn op-btn-accent"
|
||||
>
|
||||
{t("upgrade-now")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="op-card op-bg-primary text-primary-content w-full shadow-lg">
|
||||
<div className="op-card-body">
|
||||
<p>{t("quota-mail")}</p>
|
||||
<p>{t("quota-mail-tip")}</p>
|
||||
<div className="op-card-actions justify-end">
|
||||
<button
|
||||
onClick={() => navigate("/subscription")}
|
||||
className="op-btn op-btn-accent"
|
||||
>
|
||||
{t("upgrade-now")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuotaCard;
|
||||
@@ -5,18 +5,24 @@ import { useNavigate } from "react-router-dom";
|
||||
const SubscribeCard = ({ plan, price }) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const handlePaidRoute = () => navigate("/subscription");
|
||||
const premiumPrice = price ? "$" + price : "$9.99";
|
||||
const addonPrice = "$0.15";
|
||||
const plandName = plan ? plan.toLowerCase() : "Paid";
|
||||
return (
|
||||
<div className="op-card op-bg-primary text-primary-content w-full shadow-lg">
|
||||
<div className="op-card-body">
|
||||
<h2 className="op-card-title">
|
||||
{t("upgrade-to")} {plan ? plan : "PRO"} {t("plan")}
|
||||
{t("upgrade-to")} {plan ? plan : "Paid"} {t("plan")}
|
||||
</h2>
|
||||
<p>
|
||||
${price ? price : "29.99"}/month: {t("subscribe-card-plan")}
|
||||
{plandName?.includes("team")
|
||||
? t("subscribe-card-teamplan")
|
||||
: t("subscribe-card-plan", { premiumPrice, addonPrice })}
|
||||
</p>
|
||||
<div className="op-card-actions justify-end">
|
||||
<button
|
||||
onClick={() => navigate("/subscription")}
|
||||
onClick={() => handlePaidRoute()}
|
||||
className="op-btn op-btn-accent"
|
||||
>
|
||||
{t("upgrade-now")}
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import React from "react";
|
||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
const Tooltip = ({ id, message, url, iconColor, maxWidth }) =>
|
||||
url ? (
|
||||
<button onClick={() => openInNewTab(url)} className={"text-center"}>
|
||||
const Tooltip = ({
|
||||
id,
|
||||
message,
|
||||
url,
|
||||
iconColor,
|
||||
maxWidth,
|
||||
handleOnlickHelp
|
||||
}) =>
|
||||
url || handleOnlickHelp ? (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleOnlickHelp ? handleOnlickHelp() : openInNewTab(url)
|
||||
}
|
||||
className={"text-center cursor-pointer"}
|
||||
>
|
||||
<sup>
|
||||
<i
|
||||
className="fa-light fa-question rounded-full border-[1px] py-[1.5px] px-[4px] text-[13px]"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function TourContentWithBtn({ message, isChecked }) {
|
||||
export default function TourContentWithBtn({ message, isChecked, video }) {
|
||||
const { t } = useTranslation();
|
||||
const [isCheck, setIsCheck] = useState(false);
|
||||
|
||||
@@ -13,7 +13,18 @@ export default function TourContentWithBtn({ message, isChecked }) {
|
||||
return (
|
||||
<div>
|
||||
<p>{message}</p>
|
||||
<label className="flex items-center justify-center mb-0">
|
||||
{video && (
|
||||
<div className="flex items-center justify-center w-full md:h-[300px] my-[10px] border-[1.3px] border-gray-400 rounded-sm">
|
||||
<iframe
|
||||
className="w-full h-full"
|
||||
src={video}
|
||||
title="YouTube video player"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
)}
|
||||
<label className="flex items-center justify-center mt-3 mb-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="op-checkbox op-checkbox-xs mr-1"
|
||||
|
||||
@@ -37,9 +37,9 @@ const Validate = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleLoginBtn = () => {
|
||||
const handleLoginBtn = async () => {
|
||||
try {
|
||||
Parse.User.logOut();
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
} finally {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { combineReducers } from "redux";
|
||||
import infoReducer from "./infoReducer";
|
||||
import ShowTenant from "./ShowTenant";
|
||||
import TourStepsReducer from "./TourStepsReducer";
|
||||
import showHeader from "./showHeader";
|
||||
|
||||
export default combineReducers({
|
||||
appInfo: infoReducer,
|
||||
TourSteps: TourStepsReducer,
|
||||
ShowTenant,
|
||||
showHeader
|
||||
});
|
||||
@@ -56,7 +56,7 @@ document.head.appendChild(tailwindCssLink);
|
||||
// Create a root and render the wrapper component with initial props
|
||||
const root = ReactDOM.createRoot(document.getElementById("script-component"));
|
||||
root.render(
|
||||
<div>
|
||||
<div data-theme="opensigncss">
|
||||
<Provider store={store}>
|
||||
<PublicScriptFileWrapper initialProps={{ text: "templateId" }} />
|
||||
</Provider>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"header-news": "New feature: Public profiles are now live! Easily create and share public templates",
|
||||
"header-news-btn": "Setup Now",
|
||||
"create-account": "Create Account",
|
||||
"login": "Login",
|
||||
"language": "Language",
|
||||
"name": "Name",
|
||||
"phone": "Phone",
|
||||
"phone-optional": "optional",
|
||||
"email": "Email",
|
||||
"company": "Company",
|
||||
"job-title": "Job title",
|
||||
@@ -38,7 +41,8 @@
|
||||
"upgrade-now": "Upgrade now",
|
||||
"upgrade-to": "Upgrade to",
|
||||
"plan": "Plan",
|
||||
"subscribe-card-plan": "Sign 100 docs using API. Only $0.15/doc after that.",
|
||||
"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.",
|
||||
"user-name-limit-char": "To have a username less than 8 character please subscribe",
|
||||
"tour-content": "Don't show this again",
|
||||
"pro": "PRO",
|
||||
@@ -91,6 +95,7 @@
|
||||
"API Token": "API Token",
|
||||
"Webhook": "Webhook",
|
||||
"Teams": "Teams",
|
||||
"Team-update-successfully": "Team Updated successfully.",
|
||||
"Users": "Users"
|
||||
}
|
||||
},
|
||||
@@ -125,7 +130,8 @@
|
||||
"Share with team": "Share with team",
|
||||
"Share": "Share",
|
||||
"View": "View",
|
||||
"option": "Option"
|
||||
"option": "Option",
|
||||
"Embed": "Embed"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Sr.No",
|
||||
@@ -145,7 +151,8 @@
|
||||
"Name": "Name",
|
||||
"Status": "Status",
|
||||
"created-date": "Created Date",
|
||||
"Type": "Type"
|
||||
"Type": "Type",
|
||||
"Logs": "Logs"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "These are documents you have started but have not finalized for sending.",
|
||||
@@ -212,8 +219,8 @@
|
||||
"initials": "Initials",
|
||||
"API": "API",
|
||||
"api-token": "API Token",
|
||||
"regenerate-token": "Regenerate Token",
|
||||
"generate-token": "Generate Token",
|
||||
"regenerate-token": "Regenerate Live Token",
|
||||
"generate-token": "Generate Live Token",
|
||||
"view-docs": "View Docs",
|
||||
"generate-token-alert": "Are you sure you want to regenerate token it will expire old token?",
|
||||
"yes": "Yes",
|
||||
@@ -308,7 +315,7 @@
|
||||
"contact-details": "Contact Details",
|
||||
"verify-email": "Please verify your email !",
|
||||
"send-otp": " Send OTP",
|
||||
"otp-placeholder": "Enter OTP received over email",
|
||||
"otp-placeholder": "Enter verification code received over email",
|
||||
"loading-doc": "Loading Document..",
|
||||
"widgets-name": {
|
||||
"signature": "signature",
|
||||
@@ -358,7 +365,7 @@
|
||||
"initial-teb": "Initials",
|
||||
"signature-tab": "Signature",
|
||||
"your-signature": "Your signature",
|
||||
"copy-to-all-pages": "Copy to all pages",
|
||||
"copy-title": "Copy widget to",
|
||||
"contact-delete-alert": "Are you sure you want to delete this contact?",
|
||||
"reset-password-alert-1": "Reset password link has been sent to your email id",
|
||||
"reset-password-alert-2": "Please setup email adapter ",
|
||||
@@ -368,7 +375,8 @@
|
||||
"copy-type": {
|
||||
"All pages": "All pages",
|
||||
"All pages but last": "All pages but last",
|
||||
"All pages but first": "All pages but first"
|
||||
"All pages but first": "All pages but first",
|
||||
"Next to current widget": "Next to current widget"
|
||||
},
|
||||
"options": "Options",
|
||||
"minimun-check": "Minimun check",
|
||||
@@ -445,7 +453,7 @@
|
||||
"decline-alert-3": "You can not sign this document as it has been declined/revoked.",
|
||||
"sign here": "Sign here",
|
||||
"guest-email-alert": "verification code is sent to your email",
|
||||
"get-otp-alert": "you will get a OTP via Email",
|
||||
"get-otp-alert": "you will get a verification code via Email",
|
||||
"user-already-exist": "User already exist.",
|
||||
"server-error": "Internal server error !",
|
||||
"fill-required-details!": "Please fill required details!",
|
||||
@@ -468,7 +476,7 @@
|
||||
"role-not-found": "Role not found.",
|
||||
"do-not-access": "You don't have access, please contact the admin.",
|
||||
"add-admin": "Add admin",
|
||||
"opensign-setup": "Opensign Setup",
|
||||
"opensign-setup": "OpenSign Setup",
|
||||
"join-discord": "Join our discord server",
|
||||
"admin-already-exist": "Admin already exist.",
|
||||
"password-update-alert-1": "Password updated successfully.",
|
||||
@@ -524,7 +532,7 @@
|
||||
"filed-required-correctly": "Please fill required details correctly.",
|
||||
"admin-created": "Admin created",
|
||||
"invalid-masterkey": "Invalid masterkey",
|
||||
"opensign-Setup": "Opensign Setup",
|
||||
"opensign-Setup": "OpenSign Setup",
|
||||
"master-key": " Master key",
|
||||
"profile-update-alert": "Profile updated successfully.",
|
||||
"date": "Date",
|
||||
@@ -550,7 +558,7 @@
|
||||
"home-layout-1": "You have logged in successfully! Let's take a look.",
|
||||
"home-layout-2": "To upload documents for self-signing or to request others’ signatures, simply select the respective buttons.",
|
||||
"home-layout-3": "You are ready to start using OpenSign! If you need support feel free to contact us.",
|
||||
"generate-token": "Upgrade now to generate API token",
|
||||
"generate-token": "Upgrade now to generate production API token.",
|
||||
"opensign-drive-1": "Click on the breadcrumb links to easily navigate through the folder hierarchy and view the documents within each folder.",
|
||||
"opensign-drive-2": "Click the add button to create a new folder or document.",
|
||||
"opensign-drive-3": "Sort your documents by Date or Name using this menu.",
|
||||
@@ -565,9 +573,10 @@
|
||||
"pdf-request-file-5": "You can click 'Auto Sign All' to automatically sign at all the locations meant to be signed by you. Make sure that you review the document properly before you click this button .",
|
||||
"placeholder-sign-1": "Select a recipient from this list to add a place-holder where he is supposed to sign.The placeholder will appear in the same colour as the recipient name once you drop it on the document.",
|
||||
"placeholder-sign-2": "Clicking 'Add recipients' button will allow you to add more signers.",
|
||||
"placeholder-sign-3": "Drag or click on a field to add it to the document.",
|
||||
"placeholder-sign-4": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
|
||||
"placeholder-sign-5": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
|
||||
"placeholder-sign-3": "Click this button to add more recipients/signers to the document.",
|
||||
"placeholder-sign-4": "Drag or click on a field to add it to the document.",
|
||||
"placeholder-sign-5": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
|
||||
"placeholder-sign-6": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
|
||||
"report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like ‘HR Manager’ and ‘New Employee’. Each time you use the template, you can assign the ‘New Employee’ role to different incoming staff members, while the ‘HR Manager’ role remains constant, facilitating a seamless onboarding process for each recruit. ",
|
||||
"redirect": "Click the 'Use' button to create a new document from an existing template.",
|
||||
"bulksend": "To quickly create and send multiple documents using an existing template, click the 'Quick Send' button.",
|
||||
@@ -584,7 +593,8 @@
|
||||
"Out for signatures": "Clicking on this card will take you to a list of documents awaiting signature.",
|
||||
"Recent signature requests": "This is a list of documents that are waiting for your signature.",
|
||||
"Recently sent for signatures": "This is a list of documents you've sent to other parties for signature.",
|
||||
"Drafts": "This are documents you have started but have not finalized for sending."
|
||||
"Drafts": "This are documents you have started but have not finalized for sending.",
|
||||
"public-template": "This video demonstrates how to set up your personalized public profile, such as ‘https://opensign.me/your-username’. You’ll also learn how to customize your tagline and make your templates available for public signing."
|
||||
},
|
||||
"enter-email-plaholder": "Add an email address and hit enter",
|
||||
"success-email-alert": "Email sent successfully!",
|
||||
@@ -592,5 +602,56 @@
|
||||
"expired-on-mssg": "This document expired on {{expiredDate}} and is no longer available to sign.",
|
||||
"signature-validate-alert": "Please confirm that you have selected at least {{minRequiredCount}} checkboxes.",
|
||||
"signature-validate-alert-2": "Ensure this field is accurately filled and meets all requirements.",
|
||||
"user-name-exist": "user name already exist"
|
||||
"remaing-users": "Remaining users",
|
||||
"Role": "Role",
|
||||
"additional-users": "Please purchase add-on users.",
|
||||
"Quantity-of-users": "Quantity of users",
|
||||
"Price": "Price",
|
||||
"Total-price-for-next-time": "Total price for next time",
|
||||
"Proceed": "Proceed",
|
||||
"Select": "Select",
|
||||
"Add-seats": "Add Seats",
|
||||
"user-name-exist": "user name already exist",
|
||||
"format": "format",
|
||||
"select-date-format": "Select a date format",
|
||||
"quantity-of-credits": "Quantity of premium credits",
|
||||
"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.",
|
||||
"additional-credits": "Please purchase premium credits",
|
||||
"quota-err-quicksend": "Quota Reached, You don't have enough credits.",
|
||||
"buy-credits": "Buy Premium Credits",
|
||||
"rotate-right": "Rotate right",
|
||||
"rotate-left": "Rotate left",
|
||||
"rotate-alert-mssg": "All widgets on this page will be lost. Are you sure you want to proceed?",
|
||||
"templateid": "Template-Id",
|
||||
"bulk-send-subcription-alert": "Please upgrade to Professional or Team plan to use bulk send.",
|
||||
"generate-test-token": "Generate 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 you’ve 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.",
|
||||
"reason": "Reason",
|
||||
"decline-by": "Declined/revoked by",
|
||||
"document-declined": "Document declined",
|
||||
"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": "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-head": "Quota Reached",
|
||||
"unauthorized-modal": "You don't have permission to perform this action, please contact {{adminEmail}}.",
|
||||
"sent-this-month": "Sent this month",
|
||||
"available-seats": "Available seats",
|
||||
"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",
|
||||
"download-files": "Download files",
|
||||
"download-pdf": "Download Pdf",
|
||||
"pdf-certificate": "Download Pdf + Certificate",
|
||||
"document-logs": "Document logs"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"header-news": "Nouvelle fonctionnalité : les profils publics sont désormais en ligne ! Créez et partagez facilement des modèles publics",
|
||||
"header-news-btn": "Configurer maintenant",
|
||||
"create-account": "Créer un compte",
|
||||
"login": "Se Connecter",
|
||||
"language": "Langue",
|
||||
"name": "Nom et Prénom",
|
||||
"phone": "Téléphone",
|
||||
"phone-optional": "(facultatif)",
|
||||
"email": "E-mail",
|
||||
"company": "Organisation",
|
||||
"job-title": "Votre Fonction",
|
||||
@@ -39,10 +42,11 @@
|
||||
"upgrade-to": "Mettre à niveau vers",
|
||||
"pro": "PRO",
|
||||
"plan": "Offre",
|
||||
"subscribe-card-plan": "Signez 100 documents à l'aide de l'API. Seulement 0,15 $/doc par la suite.",
|
||||
"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.",
|
||||
"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",
|
||||
"team": "ÉQUIPE",
|
||||
"team": "Équipe",
|
||||
"docs": "Documents",
|
||||
"session-expired": "Votre session a expiré.",
|
||||
"user": "Utilisateur",
|
||||
@@ -91,6 +95,7 @@
|
||||
"API Token": "Jeton API",
|
||||
"Webhook": "Webhook",
|
||||
"Teams": "Équipe",
|
||||
"Team-update-successfully": "Équipe mise à jour avec succès.",
|
||||
"Users": "Utilisateurs"
|
||||
}
|
||||
},
|
||||
@@ -131,7 +136,8 @@
|
||||
"Name": "Nom",
|
||||
"Status": "Statut",
|
||||
"created-date": "Date de création",
|
||||
"Type": "Saisir"
|
||||
"Type": "Saisir",
|
||||
"Logs": "Journaux"
|
||||
},
|
||||
"btnLabel": {
|
||||
"sign": "Signer",
|
||||
@@ -144,7 +150,8 @@
|
||||
"Share with team": "Partager avec l'équipe",
|
||||
"Share": "Partager",
|
||||
"View": "Voir",
|
||||
"option": "Option"
|
||||
"option": "Option",
|
||||
"Embed": "Intégrer"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||
@@ -197,7 +204,7 @@
|
||||
"parent-folder": "Dossier Parent",
|
||||
"create": "Créer",
|
||||
"signers": "Signataires",
|
||||
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
|
||||
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
|
||||
"add-signer": "Ajouter un signataire",
|
||||
"contact-not-found": "Contact introuvable",
|
||||
"add-yourself": "Ajoutez-vous",
|
||||
@@ -211,8 +218,8 @@
|
||||
"initials": "Initiales",
|
||||
"API": "API",
|
||||
"api-token": "Jeton API",
|
||||
"regenerate-token": "Régénérer le jeton",
|
||||
"generate-token": "Générer un jeton",
|
||||
"regenerate-token": "Régénérer en direct jeton",
|
||||
"generate-token": "Générer en direct jeton",
|
||||
"view-docs": "Afficher les documents",
|
||||
"generate-token-alert": "Êtes-vous sûr de vouloir régénérer le jeton, votre ancien jeton sera supprimer?",
|
||||
"yes": "Oui",
|
||||
@@ -252,11 +259,11 @@
|
||||
"view": "voir",
|
||||
"make-template-public": "Rendre le modèle public",
|
||||
"make-template-private": "Rendre le modèle privé",
|
||||
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
|
||||
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
|
||||
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
|
||||
"public-role": "Rôle public",
|
||||
"public-url": "URL publique",
|
||||
"public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
|
||||
"public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
|
||||
"add-public-url-alert": "Veuillez ajouter votre URL publique et vous pourra rendre public modèle.",
|
||||
"share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.",
|
||||
"share-with": "Partager avec",
|
||||
@@ -274,14 +281,14 @@
|
||||
"copy-link": "Copier le lien",
|
||||
"copy": "Copier",
|
||||
"revoke-document": "Révoquer le document",
|
||||
"revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?",
|
||||
"revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?",
|
||||
"resend-mail": "Renvoyer le courrier",
|
||||
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
|
||||
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
|
||||
"subject": "Sujet",
|
||||
"body": "Corps",
|
||||
"add-contact": "Ajouter le contact",
|
||||
"add-signer-alert": "Le contact existe déjà !",
|
||||
"record-delete-alert": "Enregistrement supprimé avec succès !",
|
||||
"record-delete-alert": "Enregistrement supprimé avec succès !",
|
||||
"record-revoke-alert": " Enregistrement révoqué avec succès ! ",
|
||||
"mail-sent-alert": "Courriel envoyé avec succès.",
|
||||
"document-sent-alert": "Document envoyé avec succès !",
|
||||
@@ -307,7 +314,7 @@
|
||||
"contact-details": "Coordonnées",
|
||||
"verify-email": "Veuillez vérifier votre email!",
|
||||
"send-otp": "envoyer un code à usage unique",
|
||||
"otp-placeholder": "Saisissez l'OTP reçu par e-mail",
|
||||
"otp-placeholder": "Entrez le code de vérification reçu par e-mail",
|
||||
"loading-doc": "Chargement du document..",
|
||||
"widgets-name": {
|
||||
"signature": "signature",
|
||||
@@ -337,7 +344,7 @@
|
||||
"finish": "terminé",
|
||||
"mail": "Mail",
|
||||
"sign-now": "Signez maintenant",
|
||||
"successfully-signed": "Signé avec succès !",
|
||||
"successfully-signed": "Signé avec succès !",
|
||||
"email-mssg": "Les destinataires ajoutés ici recevront une copie du document signé.",
|
||||
"email-error-1": "veuillez fournir une adresse e-mail correcte",
|
||||
"email-error-2": "Vous ne pouvez envoyer qu'à dix destinataires à la fois.",
|
||||
@@ -357,8 +364,8 @@
|
||||
"initial-teb": "Initiales",
|
||||
"signature-tab": "Signature",
|
||||
"your-signature": "Votre signature",
|
||||
"copy-to-all-pages": "Copier sur toutes les pages",
|
||||
"contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?",
|
||||
"copy-title": "Copier le widget vers",
|
||||
"contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?",
|
||||
"reset-password": "Le lien de réinitialisation du mot de passe a été envoyé à votre identifiant de messagerie",
|
||||
"reset-password-alert-2": "Veuillez configurer l'adaptateur de messagerie",
|
||||
"reset-password-alert-3": "Réinitialisez votre mot de passe",
|
||||
@@ -367,7 +374,8 @@
|
||||
"copy-type": {
|
||||
"All pages": "Toutes les pages",
|
||||
"All pages but last": "Toutes les pages sauf la dernière",
|
||||
"All pages but first": "Toutes les pages sauf la première"
|
||||
"All pages but first": "Toutes les pages sauf la première",
|
||||
"Next to current widget": "À côté du widget actuel"
|
||||
},
|
||||
"options": "Possibilités",
|
||||
"minimun-check": "Vérification minimale",
|
||||
@@ -393,7 +401,7 @@
|
||||
"create-document": "Créer un document",
|
||||
"template-created-alert": "Voulez-vous créer un document en utilisant le modèle que vous vient de créer ?",
|
||||
"signers-alert": "alerte des signataires",
|
||||
"template-creation-alert-1": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
||||
"template-creation-alert-1": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
||||
"ok": "D'accord",
|
||||
"radio-group": "Groupe de radio",
|
||||
"dropdown-options": "Options déroulantes",
|
||||
@@ -409,9 +417,9 @@
|
||||
"hint": "Indice",
|
||||
"color": "couleur",
|
||||
"role-help": {
|
||||
"p1": "Que sont les rôles modèles ?",
|
||||
"p1": "Que sont les rôles modèles ?",
|
||||
"p2": "Commencez par spécifier chaque rôle nécessaire à la finalisation du document. Pensez aux parties impliquées dans le processus de signature et à leurs responsabilités. Les rôles courants incluent les RH pour les documents internes, le Client pour les accords ou le Fournisseur pour les accords commerciaux.",
|
||||
"p3": "Pourquoi pré-attacher les utilisateurs à certains rôles ?",
|
||||
"p3": "Pourquoi pré-attacher les utilisateurs à certains rôles ?",
|
||||
"p4": "Pour les rôles qui impliquent systématiquement la même personne (par exemple, la signature du PDG sur les lettres d'offre des employés), vous pouvez pré-attacher un utilisateur à un rôle dans le modèle. Cette étape est facultative mais recommandée pour des raisons d'efficacité et de cohérence entre les documents.",
|
||||
"p5": "Quand dois-je spécifier l'utilisateur attaché à chaque rôle ?",
|
||||
"p6": "Lorsque vous créez un document à partir de votre modèle, vous serez invité à attacher des utilisateurs à chaque rôle défini. Si un rôle est déjà associé à un utilisateur, celui-ci sera pré-rempli, mais vous pourrez le modifier si nécessaire avant d'envoyer le document."
|
||||
@@ -424,8 +432,8 @@
|
||||
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
|
||||
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
|
||||
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
|
||||
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
|
||||
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
||||
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
|
||||
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
||||
"email-subject": "Sujet du courriel",
|
||||
"email-body": "corps de l'e-mail",
|
||||
"email-placeholder": "Ajouter le corps de l'e-mail",
|
||||
@@ -444,14 +452,14 @@
|
||||
"decline-alert-3": "Vous ne pouvez pas signer ce document car il a été refusé/révoqué.",
|
||||
"sign here": "Signer ici",
|
||||
"guest-email-alert": "le code de vérification a été envoyé sur votre e-mail",
|
||||
"get-otp-alert": "vous recevrez un mot de passe à usage unique par e-mail",
|
||||
"get-otp-alert": "vous recevrez un code de vérification par e-mail",
|
||||
"user-already-exist": "L'utilisateur existe déjà.",
|
||||
"server-error": "Erreur interne du serveur !",
|
||||
"fill-required-details!": "Veuillez remplir les informations requises !",
|
||||
"fill-required-details!": "Veuillez remplir les informations requises !",
|
||||
"sign-up-form": "Formulaire d'inscription",
|
||||
"sign-up": "S'inscrire",
|
||||
"folder-already-exist!": "le dossier existe déjà !",
|
||||
"please-select-position!": "Veuillez sélectionner un poste !",
|
||||
"please-select-position!": "Veuillez sélectionner un poste !",
|
||||
"default-sign-alert": "Êtes-vous sûr de vouloir vous connecter automatiquement à tous les emplacements demandés ? ",
|
||||
"Your-Signature": "Votre signature",
|
||||
"auto-sign-all": "Tout signer automatiquement",
|
||||
@@ -459,15 +467,15 @@
|
||||
"pdf-download": "Téléchargement PDF",
|
||||
"generate-certificate-alert": " Votre certificat d'achèvement est en cours de génération. Veuillez patienter quelques instants. Si le téléchargement ne démarre pas sous peu, cliquez à nouveau sur le bouton. ",
|
||||
"folder-already-exist": "Le dossier existe déjà !",
|
||||
"folder-created-successfully": "Dossier créé avec succès !",
|
||||
"folder-created-successfully": "Dossier créé avec succès !",
|
||||
"fill-folder-name": "Veuillez indiquer le nom du dossier",
|
||||
"already-exists-this-username": "Un utilisateur existe déjà avec ce nom d'utilisateur !",
|
||||
"verification-code-sent": "Un e-mail de vérification a été envoyé à votre adresse e-mail !",
|
||||
"already-exists-this-username": "Un utilisateur existe déjà avec ce nom d'utilisateur !",
|
||||
"verification-code-sent": "Un e-mail de vérification a été envoyé à votre adresse e-mail !",
|
||||
"registered-user-successfully": "Utilisateur enregistré avec succès.",
|
||||
"role-not-found": "Rôle introuvable.",
|
||||
"do-not-access": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
|
||||
"add-admin": "Ajouter un administrateur",
|
||||
"opensign-setup": "Configuration d'Opensign",
|
||||
"opensign-setup": "Configuration d'OpenSign",
|
||||
"join-discord": "Rejoignez notre serveur Discord",
|
||||
"admin-already-exist": "L'administrateur existe déjà.",
|
||||
"password-update-alert-1": "Mot de passe mis à jour avec succès.",
|
||||
@@ -511,19 +519,19 @@
|
||||
"choose-password": "Choisissez un mot de passe",
|
||||
"enter-password": "Entrer le mot de passe",
|
||||
"sure": "bien sûr",
|
||||
"subscribed-alert": "Vous êtes déjà abonné au forfait !",
|
||||
"subscribed-alert": "Vous êtes déjà abonné au forfait !",
|
||||
"monthly": "Mensuel",
|
||||
"yearly-upto": "Annuel (jusqu'à 66 % de réduction)",
|
||||
"yearly-upto": "Annuel (jusqu'à 66 % de réduction)",
|
||||
"billed-yearly": "Facturé annuellement",
|
||||
"billed-monthly": "Facturé mensuellement",
|
||||
"plansubscription-1": "Hébergez-le vous-même gratuitement",
|
||||
"visit-github": "Visitez Github",
|
||||
"user-already-exist-name": "Un utilisateur existe déjà avec ce nom d'utilisateur !",
|
||||
"user-already-exist-name": "Un utilisateur existe déjà avec ce nom d'utilisateur !",
|
||||
"do-not-access-contact-admin": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
|
||||
"filed-required-correctly": "Veuillez remplir correctement les informations requises.",
|
||||
"admin-created": "Administrateur créé",
|
||||
"invalid-masterkey": "Clé principale invalide",
|
||||
"opensign-Setup": "Configuration d'Opensign",
|
||||
"opensign-Setup": "Configuration d'OpenSign",
|
||||
"master-key": "La clef maitresse",
|
||||
"profile-update-alert": "Mise à jour du profil réussie.",
|
||||
"date": "Date",
|
||||
@@ -546,10 +554,10 @@
|
||||
"discord": "Discorde"
|
||||
},
|
||||
"tour-mssg": {
|
||||
"home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.",
|
||||
"home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.",
|
||||
"home-layout-2": "Pour télécharger des documents à auto-signature ou pour demander la signature d'autres personnes, sélectionnez simplement les boutons correspondants.",
|
||||
"home-layout-3": "Vous êtes prêt à commencer à utiliser OpenSign ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.",
|
||||
"generate-token": "Mettez à niveau maintenant pour générer un jeton API",
|
||||
"home-layout-3": "Vous êtes prêt à commencer à utiliser OpenSign ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.",
|
||||
"generate-token": "Effectuez la mise à niveau maintenant pour générer un jeton API de production.",
|
||||
"opensign-drive-1": "Cliquez sur les liens du fil d'Ariane pour naviguer facilement dans la hiérarchie des dossiers et afficher les documents dans chaque dossier.",
|
||||
"opensign-drive-2": "Cliquez sur le bouton Ajouter pour créer un nouveau dossier ou document.",
|
||||
"opensign-drive-3": "Triez vos documents par date ou par nom à l'aide de ce menu.",
|
||||
@@ -564,9 +572,10 @@
|
||||
"pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.",
|
||||
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.",
|
||||
"placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.",
|
||||
"placeholder-sign-3": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
|
||||
"placeholder-sign-4": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
|
||||
"placeholder-sign-5": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
|
||||
"placeholder-sign-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
|
||||
"placeholder-sign-4": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
|
||||
"placeholder-sign-5": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
|
||||
"placeholder-sign-6": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
|
||||
"report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue",
|
||||
"redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.",
|
||||
"bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.",
|
||||
@@ -583,7 +592,8 @@
|
||||
"Out for signatures": "En cliquant sur cette carte, vous accéderez à une liste de documents en attente de signature.",
|
||||
"Recent signature requests": "Voici une liste de documents qui attendent votre signature.",
|
||||
"Recently sent for signatures": "Il s'agit d'une liste de documents que vous avez envoyés à d'autres parties pour signature.",
|
||||
"Drafts": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi."
|
||||
"Drafts": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||
"public-template": "Cette vidéo montre comment configurer votre profil public personnalisé, tel que 'https://opensign.me/your-username'. Vous apprendrez également comment personnaliser votre slogan et rendre vos modèles disponibles pour la signature publique."
|
||||
},
|
||||
"enter-email-plaholder": "Ajoutez une adresse email et appuyez sur Entrée",
|
||||
"success-email-alert": "E-mail envoyé avec succès!",
|
||||
@@ -591,5 +601,56 @@
|
||||
"expired-on-mssg": "Ce document a expiré le {{expiredDate}} et n'est plus disponible pour signature.",
|
||||
"signature-validate-alert": "Veuillez confirmer que vous avez coché au moins {{minRequiredCount}} cases.",
|
||||
"signature-validate-alert-2": "Assurez-vous que ce champ est rempli avec précision et répond à toutes les exigences.",
|
||||
"user-name-exist": "le nom d'utilisateur existe déjà"
|
||||
"user-name-exist": "le nom d'utilisateur existe déjà",
|
||||
"remaing-users": "Utilisateurs restants",
|
||||
"Role": "Rôle",
|
||||
"additional-users": "Veuillez acheter des utilisateurs supplémentaires.",
|
||||
"Quantity-of-users": "Quantité d'utilisateurs",
|
||||
"Price": "Prix",
|
||||
"Total-price-for-next-time": "Prix total pour la prochaine fois",
|
||||
"Proceed": "Procéder",
|
||||
"Select": "sélectionner",
|
||||
"Add-seats": "Ajouter des sièges",
|
||||
"format": "format",
|
||||
"select-date-format": "Sélectionnez un format de date",
|
||||
"quantity-of-credits": "Quantité de crédits de prime",
|
||||
"remaining-credits": "Crédits premium disponibles :",
|
||||
"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.",
|
||||
"quota-err-quicksend": "Quota atteint, vous n'avez pas assez de crédits.",
|
||||
"buy-credits": "Acheter des crédits premium",
|
||||
"rotate-right": "Faire pivoter à droite",
|
||||
"rotate-left": "Faire pivoter à gauche",
|
||||
"rotate-alert-mssg": "Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?",
|
||||
"templateid": "ID de modèle",
|
||||
"bulk-send-subcription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
|
||||
"generate-test-token": "Générer 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 à l’un 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.",
|
||||
"reason": "Raison",
|
||||
"decline-by": "Refusé/révoqué par",
|
||||
"document-declined": "document refusé",
|
||||
"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": "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-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",
|
||||
"unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
|
||||
"sent-this-month": "envoyé ce mois-ci",
|
||||
"available-seats": "Disponible sièges",
|
||||
"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",
|
||||
"download-files": "Télécharger des fichiers",
|
||||
"download-pdf": "Télécharger le PDF",
|
||||
"pdf-certificate": "Télécharger Pdf + Certificat",
|
||||
"document-logs": "Journaux de documents"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
export const cloudServerUrl = 'http://localhost:8080/app';
|
||||
export const appName = process.env.APP_NAME || 'OpenSign™';
|
||||
export function customAPIurl() {
|
||||
const url = new URL(cloudServerUrl);
|
||||
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
|
||||
@@ -42,36 +43,38 @@ export function replaceMailVaribles(subject, body, variables) {
|
||||
export const saveFileUsage = async (size, fileUrl, userId) => {
|
||||
//checking server url and save file's size
|
||||
try {
|
||||
const tenantQuery = new Parse.Query('partners_Tenant');
|
||||
tenantQuery.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userId,
|
||||
});
|
||||
const tenant = await tenantQuery.first();
|
||||
if (tenant) {
|
||||
const tenantPtr = { __type: 'Pointer', className: 'partners_Tenant', objectId: tenant.id };
|
||||
try {
|
||||
const tenantCredits = new Parse.Query('partners_TenantCredits');
|
||||
tenantCredits.equalTo('PartnersTenant', tenantPtr);
|
||||
const res = await tenantCredits.first({ useMasterKey: true });
|
||||
if (res) {
|
||||
const response = JSON.parse(JSON.stringify(res));
|
||||
const usedStorage = response?.usedStorage ? response.usedStorage + size : size;
|
||||
const updateCredit = new Parse.Object('partners_TenantCredits');
|
||||
updateCredit.id = res.id;
|
||||
updateCredit.set('usedStorage', usedStorage);
|
||||
await updateCredit.save(null, { useMasterKey: true });
|
||||
} else {
|
||||
const newCredit = new Parse.Object('partners_TenantCredits');
|
||||
newCredit.set('usedStorage', size);
|
||||
newCredit.set('PartnersTenant', tenantPtr);
|
||||
await newCredit.save(null, { useMasterKey: true });
|
||||
if (userId) {
|
||||
const tenantQuery = new Parse.Query('partners_Tenant');
|
||||
tenantQuery.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userId,
|
||||
});
|
||||
const tenant = await tenantQuery.first();
|
||||
if (tenant) {
|
||||
const tenantPtr = { __type: 'Pointer', className: 'partners_Tenant', objectId: tenant.id };
|
||||
try {
|
||||
const tenantCredits = new Parse.Query('partners_TenantCredits');
|
||||
tenantCredits.equalTo('PartnersTenant', tenantPtr);
|
||||
const res = await tenantCredits.first({ useMasterKey: true });
|
||||
if (res) {
|
||||
const response = JSON.parse(JSON.stringify(res));
|
||||
const usedStorage = response?.usedStorage ? response.usedStorage + size : size;
|
||||
const updateCredit = new Parse.Object('partners_TenantCredits');
|
||||
updateCredit.id = res.id;
|
||||
updateCredit.set('usedStorage', usedStorage);
|
||||
await updateCredit.save(null, { useMasterKey: true });
|
||||
} else {
|
||||
const newCredit = new Parse.Object('partners_TenantCredits');
|
||||
newCredit.set('usedStorage', size);
|
||||
newCredit.set('PartnersTenant', tenantPtr);
|
||||
await newCredit.save(null, { useMasterKey: true });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in save usage', err);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in save usage', err);
|
||||
saveDataFile(size, fileUrl, tenantPtr);
|
||||
}
|
||||
saveDataFile(size, fileUrl, tenantPtr);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in fetch tenant Id', err);
|
||||
@@ -91,7 +94,7 @@ const saveDataFile = async (size, fileUrl, tenantPtr) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateMailCount = async extUserId => {
|
||||
export const updateMailCount = async (extUserId, plan, monthchange) => {
|
||||
// Update count in contracts_Users class
|
||||
const query = new Parse.Query('contracts_Users');
|
||||
query.equalTo('objectId', extUserId);
|
||||
@@ -100,13 +103,23 @@ export const updateMailCount = async extUserId => {
|
||||
const contractUser = await query.first({ useMasterKey: true });
|
||||
if (contractUser) {
|
||||
contractUser.increment('EmailCount', 1);
|
||||
if (plan === 'freeplan') {
|
||||
if (monthchange) {
|
||||
contractUser.set('LastEmailCountReset', new Date());
|
||||
contractUser.set('MonthlyFreeEmails', 1);
|
||||
} else {
|
||||
if (contractUser?.get('MonthlyFreeEmails')) {
|
||||
contractUser.increment('MonthlyFreeEmails', 1);
|
||||
if (contractUser?.get('LastEmailCountReset')) {
|
||||
contractUser.set('LastEmailCountReset', new Date());
|
||||
}
|
||||
} else {
|
||||
contractUser.set('MonthlyFreeEmails', 1);
|
||||
contractUser.set('LastEmailCountReset', new Date());
|
||||
}
|
||||
}
|
||||
}
|
||||
await contractUser.save(null, { useMasterKey: true });
|
||||
} else {
|
||||
// Create new entry if not found
|
||||
const ContractsUsers = Parse.Object.extend('contracts_Users');
|
||||
const newContractUser = new ContractsUsers();
|
||||
newContractUser.set('EmailCount', 1);
|
||||
await newContractUser.save(null, { useMasterKey: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error updating EmailCount in contracts_Users: ' + error.message);
|
||||
@@ -210,3 +223,14 @@ export const useLocal = process.env.USE_LOCAL ? process.env.USE_LOCAL.toLowerCas
|
||||
export const smtpsecure = process.env.SMTP_PORT && process.env.SMTP_PORT !== '465' ? false : true;
|
||||
export const smtpenable =
|
||||
process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false;
|
||||
|
||||
export const planCredits = {
|
||||
'pro-weekly': 100,
|
||||
'pro-yearly': 240,
|
||||
'professional-monthly': 100,
|
||||
'professional-yearly': 240,
|
||||
'team-weekly': 100,
|
||||
'team-yearly': 500,
|
||||
'teams-monthly': 100,
|
||||
'teams-yearly': 500,
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import saveInvoice from './saveInvoice.js';
|
||||
import savePayments from './savePayments.js';
|
||||
import gooogleauth from './googleauth.js';
|
||||
import autoReminder from './autoReminder.js';
|
||||
import validateSmtp from './validateSmtp.js';
|
||||
export const app = express();
|
||||
|
||||
dotenv.config();
|
||||
@@ -22,3 +23,4 @@ app.post('/saveinvoice', saveInvoice);
|
||||
app.post('/savepayment', savePayments);
|
||||
app.post('/googleauth', gooogleauth);
|
||||
app.post('/sendreminder', autoReminder);
|
||||
app.post('/validatesmtp', validateSmtp);
|
||||
|
||||
@@ -1,10 +1,36 @@
|
||||
import { planCredits } from '../../Utils.js';
|
||||
|
||||
export default async function saveSubscription(request, response) {
|
||||
const SubscriptionId = request.body?.data?.subscription?.subscription_id;
|
||||
const body = request.body;
|
||||
const Email = request.body.data?.subscription?.customer?.email;
|
||||
const Next_billing_date = request.body?.data?.subscription?.next_billing_at;
|
||||
const planCode = request.body?.data?.subscription?.plan?.plan_code;
|
||||
|
||||
const addons = request.body?.data?.subscription?.addons || [];
|
||||
const event = request.body?.data?.event_type || '';
|
||||
const credits = planCredits?.[planCode] || 0;
|
||||
const isTeamPlan = planCode?.includes('team');
|
||||
let newAddons = 0;
|
||||
if (addons?.length > 0) {
|
||||
let allowedUsersMonthly = 0;
|
||||
let allowedUsersYearly = 0;
|
||||
addons?.forEach(item => {
|
||||
if (item.addon_code === 'extra-teams-users-monthly') {
|
||||
allowedUsersMonthly += item.quantity;
|
||||
} else if (item.addon_code === 'extra-teams-users-yearly') {
|
||||
allowedUsersYearly += item.quantity;
|
||||
} else if (item.addon_code === 'extra-users') {
|
||||
allowedUsersMonthly += item.quantity;
|
||||
}
|
||||
});
|
||||
if (allowedUsersMonthly > 0 || allowedUsersYearly > 0) {
|
||||
newAddons = allowedUsersMonthly + allowedUsersYearly + 1;
|
||||
}
|
||||
} else {
|
||||
if (planCode === 'teams-yearly' || planCode === 'teams-monthly' || planCode === 'team-weekly') {
|
||||
newAddons = 1;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const extUserCls = new Parse.Query('contracts_Users');
|
||||
extUserCls.equalTo('Email', Email);
|
||||
@@ -18,6 +44,7 @@ export default async function saveSubscription(request, response) {
|
||||
});
|
||||
const subscription = await subcriptionCls.first({ useMasterKey: true });
|
||||
if (subscription) {
|
||||
const _resSub = JSON.parse(JSON.stringify(subscription));
|
||||
const updateSubscription = new Parse.Object('contracts_Subscriptions');
|
||||
updateSubscription.id = subscription.id;
|
||||
updateSubscription.set('SubscriptionId', SubscriptionId);
|
||||
@@ -28,6 +55,81 @@ export default async function saveSubscription(request, response) {
|
||||
updateSubscription.unset('Next_billing_date');
|
||||
}
|
||||
updateSubscription.set('PlanCode', planCode);
|
||||
if (newAddons > 0) {
|
||||
updateSubscription.set('AllowedUsers', parseInt(newAddons));
|
||||
}
|
||||
let existAddon = 0;
|
||||
let allowedUsersMonthly = 0;
|
||||
let allowedUsersYearly = 0;
|
||||
_resSub.SubscriptionDetails?.data?.subscription?.addons?.forEach(item => {
|
||||
if (item.addon_code === 'extra-teams-users-monthly') {
|
||||
allowedUsersMonthly += item.quantity;
|
||||
} else if (item.addon_code === 'extra-teams-users-yearly') {
|
||||
allowedUsersYearly += item.quantity;
|
||||
} else if (item.addon_code === 'extra-users') {
|
||||
allowedUsersMonthly += item.quantity;
|
||||
}
|
||||
});
|
||||
if (allowedUsersMonthly > 0 || allowedUsersYearly > 0) {
|
||||
existAddon = allowedUsersMonthly + allowedUsersYearly + 1; // + 1 is Admin user
|
||||
} else {
|
||||
if (
|
||||
planCode === 'teams-yearly' ||
|
||||
planCode === 'teams-monthly' ||
|
||||
planCode === 'team-weekly'
|
||||
) {
|
||||
existAddon = 1; // 1 is Admin user
|
||||
}
|
||||
}
|
||||
const isSameAsPrevPlan = subscription?.get('PlanCode') === planCode;
|
||||
if (isSameAsPrevPlan) {
|
||||
const planCredits = subscription?.get('PlanCredits');
|
||||
const existAllowedCredits = subscription?.get('AllowedCredits') || 0;
|
||||
if (planCredits) {
|
||||
const oldAddons = existAddon;
|
||||
const substractedAddon = newAddons - oldAddons;
|
||||
if (isTeamPlan && substractedAddon > 0) {
|
||||
const newCredits = existAllowedCredits + substractedAddon * planCredits;
|
||||
updateSubscription.set('AllowedCredits', newCredits);
|
||||
if (event === 'subscription_created') {
|
||||
updateSubscription.set('PlanCredits', credits);
|
||||
}
|
||||
} else if (isTeamPlan) {
|
||||
const existCredits = existAddon * planCredits;
|
||||
updateSubscription.set('AllowedCredits', existCredits);
|
||||
if (event === 'subscription_created') {
|
||||
updateSubscription.set('PlanCredits', credits);
|
||||
}
|
||||
} else {
|
||||
updateSubscription.set('AllowedCredits', planCredits);
|
||||
if (event === 'subscription_created') {
|
||||
updateSubscription.set('PlanCredits', credits);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (credits > 0) {
|
||||
if (isTeamPlan) {
|
||||
const newCredits = newAddons * credits;
|
||||
updateSubscription.set('AllowedCredits', newCredits);
|
||||
updateSubscription.set('PlanCredits', credits);
|
||||
} else {
|
||||
updateSubscription.set('AllowedCredits', credits);
|
||||
updateSubscription.set('PlanCredits', credits);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (credits > 0) {
|
||||
if (isTeamPlan) {
|
||||
const newCredits = newAddons * credits;
|
||||
updateSubscription.set('AllowedCredits', newCredits);
|
||||
updateSubscription.set('PlanCredits', credits);
|
||||
} else {
|
||||
updateSubscription.set('AllowedCredits', credits);
|
||||
updateSubscription.set('PlanCredits', credits);
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateSubscription.save(null, { useMasterKey: true });
|
||||
return response.status(200).json({ status: 'update subscription!' });
|
||||
} else {
|
||||
@@ -55,6 +157,19 @@ export default async function saveSubscription(request, response) {
|
||||
createSubscription.unset('Next_billing_date');
|
||||
}
|
||||
createSubscription.set('PlanCode', planCode);
|
||||
if (newAddons > 0) {
|
||||
createSubscription.set('AllowedUsers', parseInt(newAddons));
|
||||
}
|
||||
if (credits > 0) {
|
||||
if (isTeamPlan) {
|
||||
const totalCredits = parseInt(newAddons) * credits;
|
||||
createSubscription.set('AllowedCredits', totalCredits);
|
||||
createSubscription.set('PlanCredits', credits);
|
||||
} else {
|
||||
createSubscription.set('AllowedCredits', credits);
|
||||
createSubscription.set('PlanCredits', credits);
|
||||
}
|
||||
}
|
||||
await createSubscription.save(null, { useMasterKey: true });
|
||||
return response.status(200).json({ status: 'create subscription!' });
|
||||
}
|
||||
|
||||
@@ -4,14 +4,9 @@ import { cloudServerUrl, customAPIurl, replaceMailVaribles } from '../../../../U
|
||||
// `sendDoctoWebhook` is used to send res data of document on webhook
|
||||
async function sendDoctoWebhook(doc, WebhookUrl, userId) {
|
||||
if (WebhookUrl) {
|
||||
const params = {
|
||||
event: 'created',
|
||||
...doc,
|
||||
};
|
||||
const params = { event: 'created', ...doc };
|
||||
await axios
|
||||
.post(WebhookUrl, params, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.post(WebhookUrl, params, { headers: { 'Content-Type': 'application/json' } })
|
||||
.then(res => {
|
||||
try {
|
||||
// console.log('res ', res);
|
||||
@@ -62,6 +57,7 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
if (!reqToken) {
|
||||
return response.status(400).json({ error: 'Please Provide API Token' });
|
||||
}
|
||||
|
||||
const tokenQuery = new Parse.Query('appToken');
|
||||
tokenQuery.equalTo('token', reqToken);
|
||||
tokenQuery.include('userId');
|
||||
@@ -74,304 +70,348 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
className: '_User',
|
||||
objectId: parseUser.userId.objectId,
|
||||
};
|
||||
const templateQuery = new Parse.Query('contracts_Template');
|
||||
templateQuery.include('ExtUserPtr');
|
||||
const templateRes = await templateQuery.get(templateId, { useMasterKey: true });
|
||||
if (templateRes) {
|
||||
const template = JSON.parse(JSON.stringify(templateRes));
|
||||
if (template?.Placeholders?.length > 0) {
|
||||
const emptyplaceholder = template?.Placeholders.filter(x => !x.signerObjId);
|
||||
const isValid =
|
||||
signers.length >= emptyplaceholder.length &&
|
||||
signers.length <= template?.Placeholders?.length;
|
||||
const placeholder =
|
||||
signers.length > emptyplaceholder.length ? template.Placeholders : emptyplaceholder;
|
||||
const updateSigners = placeholder.every(y => signers?.some(x => x.role === y.Role));
|
||||
// console.log('isValid ', isValid);
|
||||
if (isValid && updateSigners) {
|
||||
//Check if every item's placeholders contain at least one placeholder with type 'signature'.
|
||||
let isSignature = template?.Placeholders?.every(item =>
|
||||
item?.placeHolder.some(x => x?.pos.some(data => data?.type === 'signature'))
|
||||
);
|
||||
if (!isSignature) {
|
||||
return response
|
||||
.status(400)
|
||||
.json({ error: 'Please add at least one signature widget for all signers' });
|
||||
}
|
||||
const folderPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Document',
|
||||
objectId: folderId,
|
||||
};
|
||||
const object = new Parse.Object('contracts_Document');
|
||||
object.set('Name', template.Name);
|
||||
if (template?.Note) {
|
||||
object.set('Note', template.Note);
|
||||
}
|
||||
if (template?.Description) {
|
||||
object.set('Description', template.Description);
|
||||
}
|
||||
object.set('IsSendMail', send_email);
|
||||
if (sendInOrder) {
|
||||
object.set('SendinOrder', sendInOrder);
|
||||
} else if (template?.SendinOrder && template?.SendinOrder) {
|
||||
object.set('SendinOrder', template?.SendinOrder);
|
||||
}
|
||||
let templateSigner = template?.Signers ? template?.Signers : [];
|
||||
let contact = [];
|
||||
if (signers && signers.length > 0) {
|
||||
let parseSigners = [...signers];
|
||||
let createContactUrl = protocol + '/v1/createcontact';
|
||||
const extUsers = new Parse.Query('contracts_Users');
|
||||
extUsers.equalTo('UserId', userPtr);
|
||||
const extUser = await extUsers.first({ useMasterKey: true });
|
||||
|
||||
for (const obj of parseSigners) {
|
||||
const body = {
|
||||
name: obj?.name || '',
|
||||
email: obj?.email || '',
|
||||
phone: obj?.phone || '',
|
||||
const subscription = new Parse.Query('contracts_Subscriptions');
|
||||
subscription.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: extUser.get('TenantId').id,
|
||||
});
|
||||
subscription.include('ExtUserPtr');
|
||||
subscription.greaterThanOrEqualTo('Next_billing_date', new Date());
|
||||
const resSub = await subscription.first({ useMasterKey: true });
|
||||
if (resSub) {
|
||||
const _resSub = JSON.parse(JSON.stringify(resSub));
|
||||
const allowedCredits = _resSub?.AllowedCredits || 0;
|
||||
const addonCredits = _resSub?.AddonCredits || 0;
|
||||
const totalcredits = allowedCredits + addonCredits;
|
||||
if (totalcredits > 0) {
|
||||
const templateQuery = new Parse.Query('contracts_Template');
|
||||
templateQuery.include('ExtUserPtr');
|
||||
const templateRes = await templateQuery.get(templateId, { useMasterKey: true });
|
||||
if (templateRes) {
|
||||
const template = JSON.parse(JSON.stringify(templateRes));
|
||||
if (template?.Placeholders?.length > 0) {
|
||||
const emptyplaceholder = template?.Placeholders.filter(x => !x.signerObjId);
|
||||
const isValid =
|
||||
signers.length >= emptyplaceholder.length &&
|
||||
signers.length <= template?.Placeholders?.length;
|
||||
const placeholder =
|
||||
signers.length > emptyplaceholder.length ? template.Placeholders : emptyplaceholder;
|
||||
const updateSigners = placeholder.every(y => signers?.some(x => x.role === y.Role));
|
||||
// console.log('isValid ', isValid);
|
||||
if (isValid && updateSigners) {
|
||||
//Check if every item's placeholders contain at least one placeholder with type 'signature'.
|
||||
let isSignature = template?.Placeholders?.every(item =>
|
||||
item?.placeHolder.some(x => x?.pos.some(data => data?.type === 'signature'))
|
||||
);
|
||||
if (!isSignature) {
|
||||
return response
|
||||
.status(400)
|
||||
.json({ error: 'Please add at least one signature widget for all signers' });
|
||||
}
|
||||
const folderPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Document',
|
||||
objectId: folderId,
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(createContactUrl, body, {
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken },
|
||||
const object = new Parse.Object('contracts_Document');
|
||||
object.set('Name', template.Name);
|
||||
if (template?.Note) {
|
||||
object.set('Note', template.Note);
|
||||
}
|
||||
if (template?.Description) {
|
||||
object.set('Description', template.Description);
|
||||
}
|
||||
object.set('IsSendMail', send_email);
|
||||
if (sendInOrder) {
|
||||
object.set('SendinOrder', sendInOrder);
|
||||
} else if (template?.SendinOrder && template?.SendinOrder) {
|
||||
object.set('SendinOrder', template?.SendinOrder);
|
||||
}
|
||||
let templateSigner = template?.Signers ? template?.Signers : [];
|
||||
let contact = [];
|
||||
if (signers && signers.length > 0) {
|
||||
let parseSigners = [...signers];
|
||||
let createContactUrl = protocol + '/v1/createcontact';
|
||||
|
||||
for (const obj of parseSigners) {
|
||||
const body = {
|
||||
name: obj?.name || '',
|
||||
email: obj?.email || '',
|
||||
phone: obj?.phone || '',
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(createContactUrl, body, {
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken },
|
||||
});
|
||||
const contactPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: res.data?.objectId,
|
||||
};
|
||||
const newObj = { ...obj, contactPtr: contactPtr };
|
||||
contact.push(newObj);
|
||||
} catch (err) {
|
||||
// console.log('err ', err);
|
||||
if (err?.response?.data?.objectId) {
|
||||
const contactPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: err.response.data?.objectId,
|
||||
};
|
||||
const newObj = { ...obj, contactPtr: contactPtr };
|
||||
contact.push(newObj);
|
||||
} else {
|
||||
console.log('err ', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
const contactPtrs = contact.map(x => x.contactPtr);
|
||||
object.set('Signers', [...templateSigner, ...contactPtrs]);
|
||||
|
||||
let updatedPlaceholder = template?.Placeholders?.map(x => {
|
||||
let matchingSigner = contact.find(y => x.Role && x.Role === y.role);
|
||||
|
||||
if (matchingSigner) {
|
||||
return {
|
||||
...x,
|
||||
signerObjId: matchingSigner?.contactPtr?.objectId,
|
||||
signerPtr: matchingSigner?.contactPtr,
|
||||
};
|
||||
} else {
|
||||
return { ...x };
|
||||
}
|
||||
});
|
||||
const contactPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: res.data?.objectId,
|
||||
};
|
||||
const newObj = { ...obj, contactPtr: contactPtr };
|
||||
contact.push(newObj);
|
||||
} catch (err) {
|
||||
// console.log('err ', err);
|
||||
if (err?.response?.data?.objectId) {
|
||||
const contactPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: err.response.data?.objectId,
|
||||
};
|
||||
const newObj = { ...obj, contactPtr: contactPtr };
|
||||
contact.push(newObj);
|
||||
} else {
|
||||
console.log('err ', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
const contactPtrs = contact.map(x => x.contactPtr);
|
||||
object.set('Signers', [...templateSigner, ...contactPtrs]);
|
||||
|
||||
let updatedPlaceholder = template?.Placeholders?.map(x => {
|
||||
let matchingSigner = contact.find(y => x.Role && x.Role === y.role);
|
||||
|
||||
if (matchingSigner) {
|
||||
return {
|
||||
...x,
|
||||
signerObjId: matchingSigner?.contactPtr?.objectId,
|
||||
signerPtr: matchingSigner?.contactPtr,
|
||||
};
|
||||
object.set('Placeholders', updatedPlaceholder);
|
||||
} else {
|
||||
return {
|
||||
...x,
|
||||
};
|
||||
object.set('Signers', templateSigner);
|
||||
}
|
||||
});
|
||||
object.set('Placeholders', updatedPlaceholder);
|
||||
} else {
|
||||
object.set('Signers', templateSigner);
|
||||
}
|
||||
object.set('URL', template.URL);
|
||||
object.set('SignedUrl', template.URL);
|
||||
object.set('SentToOthers', true);
|
||||
if (TimeToCompleteDays) {
|
||||
object.set('TimeToCompleteDays', TimeToCompleteDays);
|
||||
}
|
||||
object.set('CreatedBy', template.CreatedBy);
|
||||
object.set('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: template.ExtUserPtr.objectId,
|
||||
});
|
||||
if (folderId) {
|
||||
object.set('Folder', folderPtr);
|
||||
}
|
||||
const newACL = new Parse.ACL();
|
||||
newACL.setPublicReadAccess(false);
|
||||
newACL.setPublicWriteAccess(false);
|
||||
newACL.setReadAccess(userPtr.objectId, true);
|
||||
newACL.setWriteAccess(userPtr.objectId, true);
|
||||
object.setACL(newACL);
|
||||
const res = await object.save(null, { useMasterKey: true });
|
||||
object.set('URL', template.URL);
|
||||
object.set('SignedUrl', template.URL);
|
||||
object.set('SentToOthers', true);
|
||||
if (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('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: template.ExtUserPtr.objectId,
|
||||
});
|
||||
if (folderId) {
|
||||
object.set('Folder', folderPtr);
|
||||
}
|
||||
const newACL = new Parse.ACL();
|
||||
newACL.setPublicReadAccess(false);
|
||||
newACL.setPublicWriteAccess(false);
|
||||
newACL.setReadAccess(userPtr.objectId, true);
|
||||
newACL.setWriteAccess(userPtr.objectId, true);
|
||||
object.setACL(newACL);
|
||||
const res = await object.save(null, { useMasterKey: true });
|
||||
|
||||
const newDate = new Date();
|
||||
newDate.setDate(newDate.getDate() + 15);
|
||||
const localExpireDate = newDate.toLocaleDateString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
let sender = template.ExtUserPtr.Email;
|
||||
let sendMail;
|
||||
if (send_email === false) {
|
||||
console.log("don't send mail");
|
||||
} else {
|
||||
let contactMail = contact;
|
||||
if (sendInOrder) {
|
||||
contactMail = contact.slice();
|
||||
contactMail.splice(1);
|
||||
}
|
||||
for (let i = 0; i < contactMail.length; i++) {
|
||||
try {
|
||||
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
|
||||
let url = `${cloudServerUrl}/functions/sendmailv3/`;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Master-Key': process.env.MASTER_KEY,
|
||||
};
|
||||
|
||||
const objectId = contactMail[i].contactPtr.objectId;
|
||||
const hostUrl = baseUrl.origin;
|
||||
//encode this url value `${res.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function
|
||||
const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`);
|
||||
let signPdf = `${hostUrl}/login/${encodeBase64}`;
|
||||
const openSignUrl = 'https://www.opensignlabs.com/contact-us';
|
||||
const orgName = template.ExtUserPtr.Company ? template.ExtUserPtr.Company : '';
|
||||
const themeBGcolor = '#47a3ad';
|
||||
const email_html =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src='" +
|
||||
imgPng +
|
||||
"' height='50' style='padding:20px; width:170px; height:40px;' /></div><div style='padding: 2px;font-family: system-ui;background-color:" +
|
||||
themeBGcolor +
|
||||
";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " +
|
||||
template.ExtUserPtr.Name +
|
||||
' has requested you to review and sign <strong> ' +
|
||||
template.Name +
|
||||
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
|
||||
sender +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
|
||||
orgName +
|
||||
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
|
||||
localExpireDate +
|
||||
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
|
||||
signPdf +
|
||||
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
|
||||
sender +
|
||||
' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' +
|
||||
openSignUrl +
|
||||
' target=_blank>here</a>.</p> </div></div></body> </html>';
|
||||
|
||||
let replaceVar;
|
||||
const variables = {
|
||||
document_title: template.Name,
|
||||
sender_name: template.ExtUserPtr.Name,
|
||||
sender_mail: template.ExtUserPtr.Email,
|
||||
sender_phone: template.ExtUserPtr?.Phone || '',
|
||||
receiver_name: contactMail[i].name,
|
||||
receiver_email: contactMail[i].email,
|
||||
receiver_phone: contactMail[i]?.phone || '',
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: signPdf,
|
||||
};
|
||||
if (email_subject && email_body) {
|
||||
replaceVar = replaceMailVaribles(email_subject, email_body, variables);
|
||||
} else if (email_subject) {
|
||||
replaceVar = replaceMailVaribles(email_subject, '', variables);
|
||||
replaceVar = { subject: replaceVar.subject, body: email_html };
|
||||
} else if (email_body) {
|
||||
replaceVar = replaceMailVaribles(
|
||||
`${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`,
|
||||
email_body,
|
||||
variables
|
||||
);
|
||||
} else {
|
||||
replaceVar = {
|
||||
subject: `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`,
|
||||
body: email_html,
|
||||
};
|
||||
const newDate = new Date();
|
||||
newDate.setDate(newDate.getDate() + 15);
|
||||
const localExpireDate = newDate.toLocaleDateString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
let sender = template.ExtUserPtr.Email;
|
||||
let sendMail;
|
||||
if (send_email === false) {
|
||||
console.log("don't send mail");
|
||||
} else {
|
||||
let contactMail = contact;
|
||||
if (sendInOrder) {
|
||||
contactMail = contact.slice();
|
||||
contactMail.splice(1);
|
||||
}
|
||||
for (let i = 0; i < contactMail.length; i++) {
|
||||
try {
|
||||
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
|
||||
let url = `${cloudServerUrl}/functions/sendmailv3/`;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Master-Key': process.env.MASTER_KEY,
|
||||
};
|
||||
|
||||
const subject = replaceVar.subject;
|
||||
const html = replaceVar.body;
|
||||
let params = {
|
||||
recipient: contactMail[i].email,
|
||||
subject: subject,
|
||||
from: sender,
|
||||
html: html,
|
||||
extUserId: template.ExtUserPtr.objectId,
|
||||
};
|
||||
sendMail = await axios.post(url, params, { headers: headers });
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
const objectId = contactMail[i].contactPtr.objectId;
|
||||
const hostUrl = baseUrl.origin;
|
||||
//encode this url value `${res.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function
|
||||
const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`);
|
||||
let signPdf = `${hostUrl}/login/${encodeBase64}`;
|
||||
const openSignUrl = 'https://www.opensignlabs.com/contact-us';
|
||||
const orgName = template.ExtUserPtr.Company
|
||||
? template.ExtUserPtr.Company
|
||||
: '';
|
||||
const themeBGcolor = '#47a3ad';
|
||||
const email_html =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src='" +
|
||||
imgPng +
|
||||
"' height='50' style='padding:20px; width:170px; height:40px;' /></div><div style='padding: 2px;font-family: system-ui;background-color:" +
|
||||
themeBGcolor +
|
||||
";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " +
|
||||
template.ExtUserPtr.Name +
|
||||
' has requested you to review and sign <strong> ' +
|
||||
template.Name +
|
||||
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
|
||||
sender +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
|
||||
orgName +
|
||||
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
|
||||
localExpireDate +
|
||||
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
|
||||
signPdf +
|
||||
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
|
||||
sender +
|
||||
' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' +
|
||||
openSignUrl +
|
||||
' target=_blank>here</a>.</p> </div></div></body> </html>';
|
||||
|
||||
let replaceVar;
|
||||
const variables = {
|
||||
document_title: template.Name,
|
||||
sender_name: template.ExtUserPtr.Name,
|
||||
sender_mail: template.ExtUserPtr.Email,
|
||||
sender_phone: template.ExtUserPtr?.Phone || '',
|
||||
receiver_name: contactMail[i].name,
|
||||
receiver_email: contactMail[i].email,
|
||||
receiver_phone: contactMail[i]?.phone || '',
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: signPdf,
|
||||
};
|
||||
if (email_subject && email_body) {
|
||||
replaceVar = replaceMailVaribles(email_subject, email_body, variables);
|
||||
} else if (email_subject) {
|
||||
replaceVar = replaceMailVaribles(email_subject, '', variables);
|
||||
replaceVar = { subject: replaceVar.subject, body: email_html };
|
||||
} else if (email_body) {
|
||||
replaceVar = replaceMailVaribles(
|
||||
`${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`,
|
||||
email_body,
|
||||
variables
|
||||
);
|
||||
} else {
|
||||
replaceVar = {
|
||||
subject: `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`,
|
||||
body: email_html,
|
||||
};
|
||||
}
|
||||
|
||||
const subject = replaceVar.subject;
|
||||
const html = replaceVar.body;
|
||||
let params = {
|
||||
recipient: contactMail[i].email,
|
||||
subject: subject,
|
||||
from: sender,
|
||||
html: html,
|
||||
extUserId: template.ExtUserPtr.objectId,
|
||||
mailProvider: template?.ExtUserPtr?.active_mail_adapter || '',
|
||||
};
|
||||
sendMail = await axios.post(url, params, { headers: headers });
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (sendMail.data.result.status === 'success') {
|
||||
try {
|
||||
const doc = {
|
||||
objectId: res.id,
|
||||
file: template?.URL,
|
||||
name: template?.Name,
|
||||
note: template?.Note || '',
|
||||
description: template?.Description || '',
|
||||
signers: contact?.map(x => ({
|
||||
name: x.name,
|
||||
email: x.email,
|
||||
phone: x?.phone || '',
|
||||
})),
|
||||
createdAt: res.createdAt,
|
||||
};
|
||||
if (template.ExtUserPtr && template.ExtUserPtr?.Webhook) {
|
||||
sendDoctoWebhook(doc, template.ExtUserPtr?.Webhook, userPtr?.objectId);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err', err);
|
||||
}
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document_with_templateid',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
|
||||
const subscriptionCls = new Parse.Object('contracts_Subscriptions');
|
||||
subscriptionCls.id = resSub.id;
|
||||
if (allowedCredits > 0) {
|
||||
const updateAllowedcredits = allowedCredits - 1 || 0;
|
||||
subscriptionCls.set('AllowedCredits', updateAllowedcredits);
|
||||
} else {
|
||||
const updateAddonCredits = addonCredits > 0 ? addonCredits - 1 : 0;
|
||||
subscriptionCls.set('AddonCredits', updateAddonCredits);
|
||||
}
|
||||
const resSubcription = await subscriptionCls.save(null, { useMasterKey: true });
|
||||
// console.log("resSubcription ", resSubcription)
|
||||
return response.json({
|
||||
objectId: res.id,
|
||||
signurl: contact.map(x => ({
|
||||
email: x.email,
|
||||
url: `${baseUrl.origin}/login/${btoa(
|
||||
`${res.id}/${x.email}/${x.contactPtr.objectId}`
|
||||
)}`,
|
||||
})),
|
||||
message: 'Document sent successfully!',
|
||||
});
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document_with_templateid',
|
||||
properties: { response_code: 400 },
|
||||
});
|
||||
}
|
||||
return response.status(400).json({ error: 'Please provide signers properly!' });
|
||||
}
|
||||
}
|
||||
// if (sendMail.data.result.status === 'success') {
|
||||
try {
|
||||
const doc = {
|
||||
objectId: res.id,
|
||||
file: template?.URL,
|
||||
name: template?.Name,
|
||||
note: template?.Note || '',
|
||||
description: template?.Description || '',
|
||||
signers: contact?.map(x => ({
|
||||
name: x.name,
|
||||
email: x.email,
|
||||
phone: x?.phone || '',
|
||||
})),
|
||||
createdAt: res.createdAt,
|
||||
};
|
||||
if (template.ExtUserPtr && template.ExtUserPtr?.Webhook) {
|
||||
sendDoctoWebhook(doc, template.ExtUserPtr?.Webhook, userPtr?.objectId);
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document_with_templateid',
|
||||
properties: { response_code: 400 },
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err', err);
|
||||
return response.status(400).json({ error: 'Please setup template properly!' });
|
||||
}
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document_with_templateid',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
return response.json({
|
||||
objectId: res.id,
|
||||
signurl: contact.map(x => ({
|
||||
email: x.email,
|
||||
url: `${baseUrl.origin}/login/${btoa(
|
||||
`${res.id}/${x.email}/${x.contactPtr.objectId}`
|
||||
)}`,
|
||||
})),
|
||||
message: 'Document sent successfully!',
|
||||
});
|
||||
// }
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document_with_templateid',
|
||||
properties: { response_code: 400 },
|
||||
properties: { response_code: 404 },
|
||||
});
|
||||
}
|
||||
return response.status(400).json({ error: 'Please provide signers properly!' });
|
||||
return response.status(404).json({ error: 'Invalid template id!' });
|
||||
}
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document_with_templateid',
|
||||
properties: { response_code: 400 },
|
||||
});
|
||||
}
|
||||
return response.status(400).json({ error: 'Please setup template properly!' });
|
||||
return response
|
||||
.status(429)
|
||||
.json({ error: 'Quota reached, Please buy credits and try again later.' });
|
||||
}
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document_with_templateid',
|
||||
properties: { response_code: 404 },
|
||||
});
|
||||
}
|
||||
return response.status(404).json({ error: 'Invalid template id!' });
|
||||
return response.status(400).json({
|
||||
error: 'Please purchase or renew your subscription.',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return response.status(405).json({ error: 'Invalid API Token!' });
|
||||
|
||||
@@ -12,15 +12,10 @@ import {
|
||||
// `sendDoctoWebhook` is used to send res data of document on webhook
|
||||
async function sendDoctoWebhook(doc, WebhookUrl, userId) {
|
||||
if (WebhookUrl) {
|
||||
const params = {
|
||||
event: 'created',
|
||||
...doc,
|
||||
};
|
||||
const params = { event: 'created', ...doc };
|
||||
|
||||
await axios
|
||||
.post(WebhookUrl, params, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.post(WebhookUrl, params, { headers: { 'Content-Type': 'application/json' } })
|
||||
.then(res => {
|
||||
try {
|
||||
// console.log('res ', res);
|
||||
@@ -68,6 +63,7 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
const email_body = request.body.email_body;
|
||||
const sendInOrder = request.body.sendInOrder || false;
|
||||
const TimeToCompleteDays = request.body.timeToCompleteDays || 15;
|
||||
const IsEnableOTP = request.body?.enableOTP === true ? true : false;
|
||||
// console.log('fileData ', fileData);
|
||||
const protocol = customAPIurl();
|
||||
const baseUrl = new URL(process.env.PUBLIC_URL);
|
||||
@@ -89,309 +85,350 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
className: '_User',
|
||||
objectId: parseUser.userId.objectId,
|
||||
};
|
||||
if (signers && signers.length > 0) {
|
||||
// Check if at least one signature exists among all items in the signers array
|
||||
let isSignExist = signers.every(item =>
|
||||
item.widgets.some(data => data?.type === 'signature')
|
||||
);
|
||||
if (!isSignExist) {
|
||||
return response
|
||||
.status(400)
|
||||
.json({ error: 'Please add at least one signature widget for all signers' });
|
||||
}
|
||||
let fileUrl;
|
||||
if (request.files?.[0]) {
|
||||
const base64 = fileData?.toString('base64');
|
||||
const file = new Parse.File(request.files?.[0]?.originalname, {
|
||||
base64: base64,
|
||||
});
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
} else {
|
||||
const filename = sanitizeFileName(`${name}.pdf`);
|
||||
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64File, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
}
|
||||
const contractsUser = new Parse.Query('contracts_Users');
|
||||
contractsUser.equalTo('UserId', userPtr);
|
||||
const extUser = await contractsUser.first({ useMasterKey: true });
|
||||
const parseExtUser = JSON.parse(JSON.stringify(extUser));
|
||||
const extUsers = new Parse.Query('contracts_Users');
|
||||
extUsers.equalTo('UserId', userPtr);
|
||||
const extUser = await extUsers.first({ useMasterKey: true });
|
||||
|
||||
const extUserPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
};
|
||||
|
||||
const object = new Parse.Object('contracts_Document');
|
||||
object.set('Name', name);
|
||||
|
||||
if (note) {
|
||||
object.set('Note', note);
|
||||
}
|
||||
if (description) {
|
||||
object.set('Description', description);
|
||||
}
|
||||
if (sendInOrder) {
|
||||
object.set('SendinOrder', sendInOrder);
|
||||
}
|
||||
object.set('URL', fileUrl);
|
||||
object.set('SignedUrl', fileUrl);
|
||||
object.set('SentToOthers', true);
|
||||
object.set('CreatedBy', userPtr);
|
||||
object.set('ExtUserPtr', extUserPtr);
|
||||
if (TimeToCompleteDays) {
|
||||
object.set('TimeToCompleteDays', TimeToCompleteDays);
|
||||
}
|
||||
object.set('IsSendMail', send_email);
|
||||
let contact = [];
|
||||
if (signers && signers.length > 0) {
|
||||
let parseSigners;
|
||||
if (base64File) {
|
||||
parseSigners = signers;
|
||||
} else {
|
||||
parseSigners = JSON.parse(signers);
|
||||
}
|
||||
let createContactUrl = protocol + '/v1/createcontact';
|
||||
|
||||
for (const [index, element] of parseSigners.entries()) {
|
||||
const body = {
|
||||
name: element?.name || '',
|
||||
email: element?.email || '',
|
||||
phone: element?.phone || '',
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(createContactUrl, body, {
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken },
|
||||
const subscription = new Parse.Query('contracts_Subscriptions');
|
||||
subscription.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: extUser.get('TenantId').id,
|
||||
});
|
||||
subscription.include('ExtUserPtr');
|
||||
subscription.greaterThanOrEqualTo('Next_billing_date', new Date());
|
||||
const resSub = await subscription.first({ useMasterKey: true });
|
||||
if (resSub) {
|
||||
const _resSub = JSON.parse(JSON.stringify(resSub));
|
||||
const allowedCredits = _resSub?.AllowedCredits || 0;
|
||||
const addonCredits = _resSub?.AddonCredits || 0;
|
||||
const totalcredits = allowedCredits + addonCredits;
|
||||
if (totalcredits > 0) {
|
||||
if (signers && signers.length > 0) {
|
||||
// Check if at least one signature exists among all items in the signers array
|
||||
let isSignExist = signers.every(item =>
|
||||
item.widgets.some(data => data?.type === 'signature')
|
||||
);
|
||||
if (!isSignExist) {
|
||||
return response
|
||||
.status(400)
|
||||
.json({ error: 'Please add at least one signature widget for all signers' });
|
||||
}
|
||||
let fileUrl;
|
||||
if (request.files?.[0]) {
|
||||
const base64 = fileData?.toString('base64');
|
||||
const file = new Parse.File(request.files?.[0]?.originalname, {
|
||||
base64: base64,
|
||||
});
|
||||
// console.log('res ', res.data);
|
||||
const contactPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: res.data?.objectId,
|
||||
};
|
||||
const newObj = { ...element, contactPtr: contactPtr, index: index };
|
||||
contact.push(newObj);
|
||||
} catch (err) {
|
||||
// console.log('err ', err.response);
|
||||
if (err?.response?.data?.objectId) {
|
||||
const contactPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: err.response.data?.objectId,
|
||||
};
|
||||
const newObj = { ...element, contactPtr: contactPtr, index: index };
|
||||
contact.push(newObj);
|
||||
}
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
} else {
|
||||
const filename = sanitizeFileName(`${name}.pdf`);
|
||||
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64File, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
}
|
||||
}
|
||||
object.set(
|
||||
'Signers',
|
||||
contact?.map(x => x.contactPtr)
|
||||
);
|
||||
let updatePlaceholders = contact.map(signer => {
|
||||
const placeHolder = [];
|
||||
const contractsUser = new Parse.Query('contracts_Users');
|
||||
contractsUser.equalTo('UserId', userPtr);
|
||||
const extUser = await contractsUser.first({ useMasterKey: true });
|
||||
const parseExtUser = JSON.parse(JSON.stringify(extUser));
|
||||
|
||||
for (const widget of signer.widgets) {
|
||||
const pageNumber = widget.page;
|
||||
const options = formatWidgetOptions(widget.type, widget.options);
|
||||
const page = placeHolder.find(page => page.pageNumber === pageNumber);
|
||||
const widgetData = {
|
||||
isStamp: widget.type === 'stamp' || widget.type === 'image',
|
||||
key: randomId(),
|
||||
isDrag: false,
|
||||
scale: 1,
|
||||
isMobile: false,
|
||||
zIndex: 1,
|
||||
type: widget.type === 'textbox' ? 'text input' : widget.type,
|
||||
options: options,
|
||||
Width: widget.w,
|
||||
Height: widget.h,
|
||||
xPosition: widget.x,
|
||||
yPosition: widget.y,
|
||||
};
|
||||
|
||||
if (page) {
|
||||
page.pos.push(widgetData);
|
||||
} else {
|
||||
placeHolder.push({
|
||||
pageNumber,
|
||||
pos: [widgetData],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
signerObjId: signer?.contactPtr?.objectId,
|
||||
signerPtr: signer?.contactPtr,
|
||||
Role: signer.role,
|
||||
Id: randomId(),
|
||||
blockColor: color[signer?.index],
|
||||
placeHolder,
|
||||
const extUserPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
};
|
||||
});
|
||||
object.set('Placeholders', updatePlaceholders);
|
||||
}
|
||||
if (folderId) {
|
||||
object.set('Folder', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Document',
|
||||
objectId: folderId,
|
||||
});
|
||||
}
|
||||
const newACL = new Parse.ACL();
|
||||
newACL.setPublicReadAccess(false);
|
||||
newACL.setPublicWriteAccess(false);
|
||||
newACL.setReadAccess(userPtr.objectId, true);
|
||||
newACL.setWriteAccess(userPtr.objectId, true);
|
||||
object.setACL(newACL);
|
||||
const res = await object.save(null, { useMasterKey: true });
|
||||
const doc = {
|
||||
objectId: res.id,
|
||||
file: fileUrl,
|
||||
name: name,
|
||||
note: note || '',
|
||||
description: description || '',
|
||||
signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x?.phone || '' })),
|
||||
createdAt: res.createdAt,
|
||||
};
|
||||
if (parseExtUser && parseExtUser.Webhook) {
|
||||
sendDoctoWebhook(doc, parseExtUser?.Webhook, userPtr?.objectId);
|
||||
}
|
||||
const newDate = new Date();
|
||||
newDate.setDate(newDate.getDate() + 15);
|
||||
const localExpireDate = newDate.toLocaleDateString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
let sender = parseExtUser.Email;
|
||||
if (send_email === false) {
|
||||
console.log("don't send mail");
|
||||
} else {
|
||||
let contactMail = contact;
|
||||
if (sendInOrder) {
|
||||
contactMail = contact.slice();
|
||||
contactMail.splice(1);
|
||||
}
|
||||
for (let i = 0; i < contactMail.length; i++) {
|
||||
try {
|
||||
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
|
||||
let url = `${cloudServerUrl}/functions/sendmailv3/`;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Master-Key': process.env.MASTER_KEY,
|
||||
};
|
||||
|
||||
const objectId = contactMail[i].contactPtr.objectId;
|
||||
const hostUrl = baseUrl.origin;
|
||||
//encode this url value `${response.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function
|
||||
const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`);
|
||||
let signPdf = `${hostUrl}/login/${encodeBase64}`;
|
||||
const openSignUrl = 'https://www.opensignlabs.com/contact-us';
|
||||
const orgName = parseExtUser.Company ? parseExtUser.Company : '';
|
||||
const themeBGcolor = '#47a3ad';
|
||||
const email_html =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" +
|
||||
imgPng +
|
||||
" height='50' style='padding:20px; width:170px; height:40px;' /></div> <div style='padding:2px; font-family: system-ui;background-color:" +
|
||||
themeBGcolor +
|
||||
";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " +
|
||||
parseExtUser.Name +
|
||||
' has requested you to review and sign <strong> ' +
|
||||
name +
|
||||
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
|
||||
sender +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
|
||||
orgName +
|
||||
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
|
||||
localExpireDate +
|
||||
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
|
||||
signPdf +
|
||||
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
|
||||
sender +
|
||||
' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' +
|
||||
openSignUrl +
|
||||
' target=_blank>here</a>.</p> </div></div></body> </html>';
|
||||
let replaceVar;
|
||||
const variables = {
|
||||
document_title: name,
|
||||
sender_name: parseExtUser.Name,
|
||||
sender_mail: parseExtUser.Email,
|
||||
sender_phone: parseExtUser?.Phone || '',
|
||||
receiver_name: contactMail[i].name,
|
||||
receiver_email: contactMail[i].email,
|
||||
receiver_phone: contactMail[i]?.phone || '',
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: signPdf,
|
||||
};
|
||||
if (email_subject && email_body) {
|
||||
replaceVar = replaceMailVaribles(email_subject, email_body, variables);
|
||||
} else if (email_subject) {
|
||||
replaceVar = replaceMailVaribles(email_subject, '', variables);
|
||||
replaceVar = { subject: replaceVar.subject, body: email_html };
|
||||
} else if (email_body) {
|
||||
replaceVar = replaceMailVaribles(
|
||||
`${parseExtUser.Name} has requested you to sign "${name}"`,
|
||||
email_body,
|
||||
variables
|
||||
);
|
||||
} else {
|
||||
replaceVar = {
|
||||
subject: `${parseExtUser.Name} has requested you to sign "${name}"`,
|
||||
body: email_html,
|
||||
};
|
||||
}
|
||||
const subject = replaceVar.subject;
|
||||
const html = replaceVar.body;
|
||||
const object = new Parse.Object('contracts_Document');
|
||||
object.set('Name', name);
|
||||
|
||||
let params = {
|
||||
recipient: contactMail[i].email,
|
||||
subject: subject,
|
||||
from: sender,
|
||||
html: html,
|
||||
extUserId: extUser.id,
|
||||
};
|
||||
|
||||
await axios.post(url, params, { headers: headers });
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
if (note) {
|
||||
object.set('Note', note);
|
||||
}
|
||||
if (description) {
|
||||
object.set('Description', description);
|
||||
}
|
||||
if (sendInOrder) {
|
||||
object.set('SendinOrder', sendInOrder);
|
||||
}
|
||||
object.set('URL', fileUrl);
|
||||
object.set('SignedUrl', fileUrl);
|
||||
object.set('SentToOthers', true);
|
||||
object.set('CreatedBy', userPtr);
|
||||
object.set('ExtUserPtr', extUserPtr);
|
||||
if (TimeToCompleteDays) {
|
||||
object.set('TimeToCompleteDays', TimeToCompleteDays);
|
||||
}
|
||||
object.set('IsEnableOTP', IsEnableOTP);
|
||||
object.set('IsSendMail', send_email);
|
||||
let contact = [];
|
||||
if (signers && signers.length > 0) {
|
||||
let parseSigners;
|
||||
if (base64File) {
|
||||
parseSigners = signers;
|
||||
} else {
|
||||
parseSigners = JSON.parse(signers);
|
||||
}
|
||||
let createContactUrl = protocol + '/v1/createcontact';
|
||||
|
||||
for (const [index, element] of parseSigners.entries()) {
|
||||
const body = {
|
||||
name: element?.name || '',
|
||||
email: element?.email || '',
|
||||
phone: element?.phone || '',
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(createContactUrl, body, {
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken },
|
||||
});
|
||||
// console.log('res ', res.data);
|
||||
const contactPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: res.data?.objectId,
|
||||
};
|
||||
const newObj = { ...element, contactPtr: contactPtr, index: index };
|
||||
contact.push(newObj);
|
||||
} catch (err) {
|
||||
// console.log('err ', err.response);
|
||||
if (err?.response?.data?.objectId) {
|
||||
const contactPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: err.response.data?.objectId,
|
||||
};
|
||||
const newObj = { ...element, contactPtr: contactPtr, index: index };
|
||||
contact.push(newObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
object.set(
|
||||
'Signers',
|
||||
contact?.map(x => x.contactPtr)
|
||||
);
|
||||
let updatePlaceholders = contact.map(signer => {
|
||||
const placeHolder = [];
|
||||
|
||||
for (const widget of signer.widgets) {
|
||||
const pageNumber = widget.page;
|
||||
const options = formatWidgetOptions(widget.type, widget.options);
|
||||
const page = placeHolder.find(page => page.pageNumber === pageNumber);
|
||||
const widgetData = {
|
||||
isStamp: widget.type === 'stamp' || widget.type === 'image',
|
||||
key: randomId(),
|
||||
isDrag: false,
|
||||
scale: 1,
|
||||
isMobile: false,
|
||||
zIndex: 1,
|
||||
type: widget.type === 'textbox' ? 'text input' : widget.type,
|
||||
options: options,
|
||||
Width: widget.w,
|
||||
Height: widget.h,
|
||||
xPosition: widget.x,
|
||||
yPosition: widget.y,
|
||||
};
|
||||
|
||||
if (page) {
|
||||
page.pos.push(widgetData);
|
||||
} else {
|
||||
placeHolder.push({ pageNumber, pos: [widgetData] });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
signerObjId: signer?.contactPtr?.objectId,
|
||||
signerPtr: signer?.contactPtr,
|
||||
Role: signer.role,
|
||||
Id: randomId(),
|
||||
blockColor: color[signer?.index],
|
||||
placeHolder,
|
||||
};
|
||||
});
|
||||
object.set('Placeholders', updatePlaceholders);
|
||||
}
|
||||
if (folderId) {
|
||||
object.set('Folder', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Document',
|
||||
objectId: folderId,
|
||||
});
|
||||
}
|
||||
const newACL = new Parse.ACL();
|
||||
newACL.setPublicReadAccess(false);
|
||||
newACL.setPublicWriteAccess(false);
|
||||
newACL.setReadAccess(userPtr.objectId, true);
|
||||
newACL.setWriteAccess(userPtr.objectId, true);
|
||||
object.setACL(newACL);
|
||||
const res = await object.save(null, { useMasterKey: true });
|
||||
const doc = {
|
||||
objectId: res.id,
|
||||
file: fileUrl,
|
||||
name: name,
|
||||
note: note || '',
|
||||
description: description || '',
|
||||
signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x?.phone || '' })),
|
||||
createdAt: res.createdAt,
|
||||
};
|
||||
if (parseExtUser && parseExtUser.Webhook) {
|
||||
sendDoctoWebhook(doc, parseExtUser?.Webhook, userPtr?.objectId);
|
||||
}
|
||||
const newDate = new Date();
|
||||
newDate.setDate(newDate.getDate() + 15);
|
||||
const localExpireDate = newDate.toLocaleDateString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
let sender = parseExtUser.Email;
|
||||
if (send_email === false) {
|
||||
console.log("don't send mail");
|
||||
} else {
|
||||
let contactMail = contact;
|
||||
if (sendInOrder) {
|
||||
contactMail = contact.slice();
|
||||
contactMail.splice(1);
|
||||
}
|
||||
for (let i = 0; i < contactMail.length; i++) {
|
||||
try {
|
||||
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
|
||||
let url = `${cloudServerUrl}/functions/sendmailv3/`;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Master-Key': process.env.MASTER_KEY,
|
||||
};
|
||||
|
||||
const objectId = contactMail[i].contactPtr.objectId;
|
||||
const hostUrl = baseUrl.origin;
|
||||
//encode this url value `${response.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function
|
||||
const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`);
|
||||
let signPdf = `${hostUrl}/login/${encodeBase64}`;
|
||||
const openSignUrl = 'https://www.opensignlabs.com/contact-us';
|
||||
const orgName = parseExtUser.Company ? parseExtUser.Company : '';
|
||||
const themeBGcolor = '#47a3ad';
|
||||
const email_html =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" +
|
||||
imgPng +
|
||||
" height='50' style='padding:20px; width:170px; height:40px;' /></div> <div style='padding:2px; font-family: system-ui;background-color:" +
|
||||
themeBGcolor +
|
||||
";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " +
|
||||
parseExtUser.Name +
|
||||
' has requested you to review and sign <strong> ' +
|
||||
name +
|
||||
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
|
||||
sender +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
|
||||
orgName +
|
||||
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
|
||||
localExpireDate +
|
||||
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
|
||||
signPdf +
|
||||
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
|
||||
sender +
|
||||
' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' +
|
||||
openSignUrl +
|
||||
' target=_blank>here</a>.</p> </div></div></body> </html>';
|
||||
let replaceVar;
|
||||
const variables = {
|
||||
document_title: name,
|
||||
sender_name: parseExtUser.Name,
|
||||
sender_mail: parseExtUser.Email,
|
||||
sender_phone: parseExtUser?.Phone || '',
|
||||
receiver_name: contactMail[i].name,
|
||||
receiver_email: contactMail[i].email,
|
||||
receiver_phone: contactMail[i]?.phone || '',
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: signPdf,
|
||||
};
|
||||
if (email_subject && email_body) {
|
||||
replaceVar = replaceMailVaribles(email_subject, email_body, variables);
|
||||
} else if (email_subject) {
|
||||
replaceVar = replaceMailVaribles(email_subject, '', variables);
|
||||
replaceVar = { subject: replaceVar.subject, body: email_html };
|
||||
} else if (email_body) {
|
||||
replaceVar = replaceMailVaribles(
|
||||
`${parseExtUser.Name} has requested you to sign "${name}"`,
|
||||
email_body,
|
||||
variables
|
||||
);
|
||||
} else {
|
||||
replaceVar = {
|
||||
subject: `${parseExtUser.Name} has requested you to sign "${name}"`,
|
||||
body: email_html,
|
||||
};
|
||||
}
|
||||
const subject = replaceVar.subject;
|
||||
const html = replaceVar.body;
|
||||
|
||||
let params = {
|
||||
recipient: contactMail[i].email,
|
||||
subject: subject,
|
||||
from: sender,
|
||||
html: html,
|
||||
extUserId: extUser.id,
|
||||
mailProvider: parseExtUser?.active_mail_adapter || '',
|
||||
};
|
||||
|
||||
await axios.post(url, params, { headers: headers });
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (sendMail.data.result.status === 'success') {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
|
||||
const subscriptionCls = new Parse.Object('contracts_Subscriptions');
|
||||
subscriptionCls.id = resSub.id;
|
||||
if (allowedCredits > 0) {
|
||||
const updateAllowedcredits = allowedCredits - 1 || 0;
|
||||
subscriptionCls.set('AllowedCredits', updateAllowedcredits);
|
||||
} else {
|
||||
const updateAddonCredits = addonCredits > 0 ? addonCredits - 1 : 0;
|
||||
subscriptionCls.set('AddonCredits', updateAddonCredits);
|
||||
}
|
||||
const resSubcription = await subscriptionCls.save(null, { useMasterKey: true });
|
||||
// console.log('resSubcription ', resSubcription);
|
||||
return response.json({
|
||||
objectId: res.id,
|
||||
signurl: contact.map(x => ({
|
||||
email: x.email,
|
||||
url: `${baseUrl.origin}/login/${btoa(
|
||||
`${res.id}/${x.email}/${x.contactPtr.objectId}`
|
||||
)}`,
|
||||
})),
|
||||
message: 'Document sent successfully!',
|
||||
});
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document',
|
||||
properties: { response_code: 400 },
|
||||
});
|
||||
}
|
||||
return response.status(400).json({ error: 'Please provide signers!' });
|
||||
}
|
||||
} else {
|
||||
return response
|
||||
.status(429)
|
||||
.json({ error: 'Quota reached, Please buy credits and try again later.' });
|
||||
}
|
||||
// if (sendMail.data.result.status === 'success') {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
return response.json({
|
||||
objectId: res.id,
|
||||
signurl: contact.map(x => ({
|
||||
email: x.email,
|
||||
url: `${baseUrl.origin}/login/${btoa(`${res.id}/${x.email}/${x.contactPtr.objectId}`)}`,
|
||||
})),
|
||||
message: 'Document sent successfully!',
|
||||
});
|
||||
// }
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_document',
|
||||
properties: { response_code: 400 },
|
||||
});
|
||||
}
|
||||
return response.status(400).json({ error: 'Please provide signers!' });
|
||||
return response.status(400).json({
|
||||
error: 'Please purchase or renew your subscription.',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return response.status(405).json({ error: 'Invalid API Token!' });
|
||||
|
||||
@@ -9,6 +9,7 @@ export default async function createTemplate(request, response) {
|
||||
const SendinOrder = request.body.sendInOrder || false;
|
||||
const fileData = request.files?.[0] ? request.files[0].buffer : null;
|
||||
const baseUrl = new URL(process.env.PUBLIC_URL);
|
||||
const isEnableOTP = request.body?.enableOTP === true ? true : false;
|
||||
|
||||
try {
|
||||
const reqToken = request.headers['x-api-token'];
|
||||
@@ -65,6 +66,7 @@ export default async function createTemplate(request, response) {
|
||||
object.set('URL', fileUrl);
|
||||
object.set('CreatedBy', userPtr);
|
||||
object.set('ExtUserPtr', extUserPtr);
|
||||
object.set('IsEnableOTP', isEnableOTP);
|
||||
if (SendinOrder) {
|
||||
object.set('SendinOrder', SendinOrder);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ export default async function createTemplatewithCoordinate(request, response) {
|
||||
const base64File = request.body.file;
|
||||
const fileData = request.files?.[0] ? request.files[0].buffer : null;
|
||||
const SendinOrder = request.body.sendInOrder || false;
|
||||
const isEnableOTP = request.body?.enableOTP === true ? true : false;
|
||||
|
||||
// console.log('fileData ', fileData);
|
||||
const protocol = customAPIurl();
|
||||
|
||||
@@ -80,6 +82,7 @@ export default async function createTemplatewithCoordinate(request, response) {
|
||||
object.set('URL', fileUrl);
|
||||
object.set('CreatedBy', userPtr);
|
||||
object.set('ExtUserPtr', extUserPtr);
|
||||
object.set('IsEnableOTP', isEnableOTP);
|
||||
let contact = [];
|
||||
if (signers && signers.length > 0) {
|
||||
let parseSigners;
|
||||
|
||||
@@ -12,6 +12,8 @@ export default async function draftDocument(request, response) {
|
||||
const send_email = request.body.send_email || true;
|
||||
const fileData = request.files?.[0] ? request.files[0].buffer : null;
|
||||
const SendinOrder = request.body.sendInOrder || false;
|
||||
const isEnableOTP = request.body?.enableOTP === true ? true : false;
|
||||
|
||||
// console.log('fileData ', fileData);
|
||||
const protocol = customAPIurl();
|
||||
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('ExtUserPtr', extUserPtr);
|
||||
object.set('IsSendMail', send_email);
|
||||
object.set('IsEnableOTP', isEnableOTP);
|
||||
if (signers && signers.length > 0) {
|
||||
let parseSigners;
|
||||
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 })) ||
|
||||
[],
|
||||
sendInOrder: document?.SendinOrder || false,
|
||||
enableOTP: document?.IsEnableOTP || false,
|
||||
createdAt: document.createdAt,
|
||||
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 })) ||
|
||||
[],
|
||||
sendInOrder: x?.SendinOrder || false,
|
||||
enableOTP: x?.IsEnableOTP || false,
|
||||
createdAt: x.createdAt,
|
||||
updatedAt: x.updatedAt,
|
||||
}));
|
||||
|
||||
@@ -61,6 +61,7 @@ export default async function getTemplate(request, response) {
|
||||
),
|
||||
})) || [],
|
||||
sendInOrder: template?.SendinOrder || false,
|
||||
enableOTP: template?.IsEnableOTP || false,
|
||||
createdAt: template.createdAt,
|
||||
updatedAt: template.updatedAt,
|
||||
});
|
||||
|
||||
@@ -88,6 +88,7 @@ export default async function getTemplatetList(request, response) {
|
||||
),
|
||||
})) || [],
|
||||
sendInOrder: template?.SendinOrder || false,
|
||||
enableOTP: template?.IsEnableOTP || false,
|
||||
createdAt: template.createdAt,
|
||||
updatedAt: template.updatedAt,
|
||||
}));
|
||||
|
||||
@@ -41,6 +41,7 @@ export default async function resendMail(request, response) {
|
||||
if (resDoc) {
|
||||
const _resDoc = resDoc.toJSON();
|
||||
const contact = _resDoc.Signers.find(x => x.Email === userMail);
|
||||
const activeMailAdapter = _resDoc?.ExtUserPtr?.active_mail_adapter || '';
|
||||
if (contact) {
|
||||
try {
|
||||
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
|
||||
@@ -131,6 +132,7 @@ export default async function resendMail(request, response) {
|
||||
from: sender,
|
||||
html: html,
|
||||
extUserId: _resDoc.ExtUserPtr.objectId,
|
||||
mailProvider: activeMailAdapter,
|
||||
};
|
||||
|
||||
const res = await axios.post(url, params, { headers: headers });
|
||||
|
||||
@@ -10,7 +10,7 @@ export default async function updateDocument(request, response) {
|
||||
const token = await tokenQuery.first({ useMasterKey: true });
|
||||
if (token !== undefined) {
|
||||
// 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 isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0;
|
||||
const parseUser = JSON.parse(JSON.stringify(token));
|
||||
@@ -48,6 +48,10 @@ export default async function updateDocument(request, response) {
|
||||
objectId: request?.body?.folderId,
|
||||
});
|
||||
}
|
||||
|
||||
if (request.body?.enableOTP) {
|
||||
updateQuery.set('IsEnableOTP', request.body?.enableOTP);
|
||||
}
|
||||
const updatedRes = await updateQuery.save(null, { useMasterKey: true });
|
||||
if (updatedRes) {
|
||||
if (request.posthog) {
|
||||
|
||||
@@ -10,7 +10,7 @@ export default async function updateTemplate(request, response) {
|
||||
const token = await tokenQuery.first({ useMasterKey: true });
|
||||
if (token !== undefined) {
|
||||
// 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 isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0;
|
||||
const parseUser = JSON.parse(JSON.stringify(token));
|
||||
@@ -48,6 +48,9 @@ export default async function updateTemplate(request, response) {
|
||||
objectId: request?.body?.folderId,
|
||||
});
|
||||
}
|
||||
if (request.body?.enableOTP) {
|
||||
updateQuery.set('IsEnableOTP', request.body?.enableOTP);
|
||||
}
|
||||
const updatedRes = await updateQuery.save(null, { useMasterKey: true });
|
||||
if (updatedRes) {
|
||||
if (request.posthog) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { createTransport } from 'nodemailer';
|
||||
import { appName } from '../../Utils.js';
|
||||
// Online Javascript Editor for free
|
||||
// Write, Edit and Run your Javascript code using JS Online Compiler
|
||||
|
||||
function generateOtpWithSum10() {
|
||||
let otp = [];
|
||||
let sum = 0;
|
||||
|
||||
// First, generate 3 random digits (0-9) and add them to the sum
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let digit = Math.floor(Math.random() * 10);
|
||||
otp.push(digit);
|
||||
sum += digit;
|
||||
}
|
||||
|
||||
// Calculate the last digit to ensure the sum equals 10
|
||||
let lastDigit = 10 - sum;
|
||||
|
||||
// If the last digit is not valid (e.g., greater than 9), regenerate
|
||||
if (lastDigit >= 0 && lastDigit <= 9) {
|
||||
otp.push(lastDigit);
|
||||
} else {
|
||||
return generateOtpWithSum10(); // Recursively generate again
|
||||
}
|
||||
|
||||
return otp.join('');
|
||||
}
|
||||
|
||||
export default async function validateSmtp(request, response) {
|
||||
const host = request.body.host;
|
||||
const port = request.body?.port?.toString();
|
||||
const username = request.body.username;
|
||||
const password = request.body.password;
|
||||
const secure = request.body?.secure;
|
||||
const email = request.body?.email;
|
||||
const otp = generateOtpWithSum10();
|
||||
// console.log('Generated OTP:', otp);
|
||||
|
||||
try {
|
||||
const smtpsecure = secure || port !== '465' ? false : true;
|
||||
const transporterSMTP = createTransport({
|
||||
host: host,
|
||||
port: port,
|
||||
secure: smtpsecure,
|
||||
auth: { user: username, pass: password },
|
||||
});
|
||||
const from = appName;
|
||||
const mailsender = username;
|
||||
const messageParams = {
|
||||
from: from + ' <' + mailsender + '>',
|
||||
to: email,
|
||||
subject: `Your ${appName} SMTP credentials verification code`,
|
||||
text: 'mail',
|
||||
html:
|
||||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>SMTP Verification Code</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your verification code is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>` +
|
||||
otp +
|
||||
'</p></div> </div> </div></body></html>',
|
||||
};
|
||||
|
||||
const res = await transporterSMTP.sendMail(messageParams);
|
||||
console.log('custom smtp transporter res: ', res?.response);
|
||||
if (!res.err) {
|
||||
response.status(200).json({ message: 'success' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
const code = err?.responseCode || 400;
|
||||
const message = err?.response || 'failed!';
|
||||
response.status(code).json({ error: message });
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import usersignup from './parsefunction/usersignup.js';
|
||||
import FacebookSign from './parsefunction/FacebookSign.js';
|
||||
import DocumentAftersave from './parsefunction/DocumentAftersave.js';
|
||||
import ContactbookAftersave from './parsefunction/ContactBookAftersave.js';
|
||||
// import ContractUsersAftersave from './parsefunction/ContractUsersAftersave.js';
|
||||
import sendMailOTPv1 from './parsefunction/SendMailOTPv1.js';
|
||||
import AuthLoginAsMail from './parsefunction/AuthLoginAsMail.js';
|
||||
import getUserId from './parsefunction/getUserId.js';
|
||||
@@ -60,14 +59,26 @@ import addTeam from './parsefunction/addTeam.js';
|
||||
import updateTeam from './parsefunction/updateTeam.js';
|
||||
import getOrgAdmins from './parsefunction/getOrgAdmins.js';
|
||||
import getAllUserTeamByOrg from './parsefunction/getAllUserTeamByOrg.js';
|
||||
import AllowedUsers from './parsefunction/AlllowedUsers.js';
|
||||
import BuyAddonUsers from './parsefunction/BuyAddonUsers.js';
|
||||
import ExtUserAftersave from './parsefunction/ExtUserAftersave.js';
|
||||
import ExtUserAfterdelete from './parsefunction/ExtUserAfterdelete.js';
|
||||
import AllowedCredits from './parsefunction/AllowedCredits.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';
|
||||
import addcustomsmtp from './parsefunction/addcustomsmtp.js';
|
||||
import deactivateMailAdapter from './parsefunction/deactivateMailAdapter.js';
|
||||
|
||||
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
|
||||
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
|
||||
Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave);
|
||||
// Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave);
|
||||
Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave);
|
||||
Parse.Cloud.afterSave('contracts_Teams', TeamsAftersave);
|
||||
Parse.Cloud.afterSave('contracts_Subscriptions', SubscriptionAftersave);
|
||||
Parse.Cloud.afterSave('contracts_Users', ExtUserAftersave);
|
||||
|
||||
// This beforeSave function triggers before an object is added or updated in the specified class, allowing for validation or modification.
|
||||
Parse.Cloud.beforeSave('contracts_Document', DocumentBeforesave);
|
||||
Parse.Cloud.beforeSave('contracts_Template', TemplateBeforeSave);
|
||||
@@ -79,6 +90,9 @@ Parse.Cloud.afterFind('contracts_Template', TemplateAfterFind);
|
||||
Parse.Cloud.afterFind('contracts_Signature', SignatureAfterFind);
|
||||
Parse.Cloud.afterFind('partners_Tenant', TenantAterFind);
|
||||
|
||||
// This afterDelete function triggers after an object get deleted.
|
||||
Parse.Cloud.afterDelete('contracts_Users', ExtUserAfterdelete);
|
||||
|
||||
// This define function creates a custom Cloud Function that can be called from the client-side, enabling custom business logic on the server.
|
||||
Parse.Cloud.define('signPdf', PDF);
|
||||
Parse.Cloud.define('sendmailv3', sendmailv3);
|
||||
@@ -129,3 +143,12 @@ Parse.Cloud.define('addteam', addTeam);
|
||||
Parse.Cloud.define('updateteam', updateTeam);
|
||||
Parse.Cloud.define('getorgadmins', getOrgAdmins);
|
||||
Parse.Cloud.define('getalluserteambyorg', getAllUserTeamByOrg);
|
||||
Parse.Cloud.define('allowedusers', AllowedUsers);
|
||||
Parse.Cloud.define('buyaddonusers', BuyAddonUsers);
|
||||
Parse.Cloud.define('allowedcredits', AllowedCredits);
|
||||
Parse.Cloud.define('buycredits', BuyCredits);
|
||||
Parse.Cloud.define('getcontact', getContact);
|
||||
Parse.Cloud.define('updatecontacttour', updateContactTour);
|
||||
Parse.Cloud.define('declinedoc', declinedocument);
|
||||
Parse.Cloud.define('addsmtp', addcustomsmtp);
|
||||
Parse.Cloud.define('deactivatemailadapter', deactivateMailAdapter);
|
||||
|
||||
@@ -189,34 +189,3 @@ export default async function AddAdmin(request) {
|
||||
console.log('Err ', err);
|
||||
}
|
||||
}
|
||||
|
||||
// async function saveSubscription(extUserId, UserId, tenantId, subscription) {
|
||||
// const SubscriptionId = subscription?.data?.subscription?.subscription_id || '';
|
||||
// const Next_billing_date = subscription?.data?.subscription?.next_billing_at || '';
|
||||
// const planCode = subscription?.data?.subscription?.plan?.plan_code || '';
|
||||
// try {
|
||||
// const createSubscription = new Parse.Object('contracts_Subscriptions');
|
||||
// createSubscription.set('SubscriptionId', SubscriptionId);
|
||||
// createSubscription.set('SubscriptionDetails', subscription);
|
||||
// createSubscription.set('ExtUserPtr', {
|
||||
// __type: 'Pointer',
|
||||
// className: 'contracts_Users',
|
||||
// objectId: extUserId,
|
||||
// });
|
||||
// createSubscription.set('CreatedBy', {
|
||||
// __type: 'Pointer',
|
||||
// className: '_User',
|
||||
// objectId: UserId,
|
||||
// });
|
||||
// createSubscription.set('TenantId', {
|
||||
// __type: 'Pointer',
|
||||
// className: 'partners_Tenant',
|
||||
// objectId: tenantId,
|
||||
// });
|
||||
// createSubscription.set('Next_billing_date', new Date(Next_billing_date));
|
||||
// createSubscription.set('PlanCode', planCode);
|
||||
// await createSubscription.save(null, { useMasterKey: true });
|
||||
// } catch (err) {
|
||||
// console.log('err in save subscription pgsignup', err);
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
export default async function AllowedUsers(request) {
|
||||
if (!request?.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
}
|
||||
try {
|
||||
const extUser = new Parse.Query('contracts_Users');
|
||||
extUser.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: request.user.id,
|
||||
});
|
||||
const resExtUser = await extUser.first({ useMasterKey: true });
|
||||
if (resExtUser) {
|
||||
const _resExtUser = JSON.parse(JSON.stringify(resExtUser));
|
||||
const subscription = new Parse.Query('contracts_Subscriptions');
|
||||
subscription.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: _resExtUser.TenantId.objectId,
|
||||
});
|
||||
subscription.include('ExtUserPtr');
|
||||
const resSub = await subscription.first({ useMasterKey: true });
|
||||
if (resSub) {
|
||||
const _resSub = JSON.parse(JSON.stringify(resSub));
|
||||
const count = _resSub?.UsersCount || 0;
|
||||
if (count > 0) {
|
||||
const allowedUser = resSub.get('AllowedUsers') || 0;
|
||||
const remainUsers = allowedUser - count;
|
||||
if (remainUsers > 0) {
|
||||
return remainUsers;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
const alloweduser = resSub.get('AllowedUsers') || 0;
|
||||
return alloweduser;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in allowedusers', err);
|
||||
const code = err?.code || 400;
|
||||
const msg = err?.message || 'Something went wrong.';
|
||||
throw new Parse.Error(code, msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
export default async function AllowedCredits(request) {
|
||||
if (!request?.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
}
|
||||
try {
|
||||
const extUser = new Parse.Query('contracts_Users');
|
||||
extUser.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: request.user.id,
|
||||
});
|
||||
const resExtUser = await extUser.first({ useMasterKey: true });
|
||||
if (resExtUser) {
|
||||
const _resExtUser = JSON.parse(JSON.stringify(resExtUser));
|
||||
const subscription = new Parse.Query('contracts_Subscriptions');
|
||||
subscription.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: _resExtUser.TenantId.objectId,
|
||||
});
|
||||
subscription.include('ExtUserPtr');
|
||||
const resSub = await subscription.first({ useMasterKey: true });
|
||||
if (resSub) {
|
||||
const _resSub = JSON.parse(JSON.stringify(resSub));
|
||||
const AllowedCredits = _resSub?.AllowedCredits || 0;
|
||||
const AddonCredits = _resSub?.AddonCredits || 0;
|
||||
return { allowedcredits: AllowedCredits, addoncredits: AddonCredits };
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in allowedCredits', err);
|
||||
const code = err?.code || 400;
|
||||
const msg = err?.message || 'Something went wrong.';
|
||||
throw new Parse.Error(code, msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import axios from 'axios';
|
||||
import { planCredits } from '../../Utils.js';
|
||||
export default async function BuyAddonUsers(request) {
|
||||
const users = request.params.users;
|
||||
if (!request?.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
}
|
||||
if (users) {
|
||||
try {
|
||||
const extUser = new Parse.Query('contracts_Users');
|
||||
extUser.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: request.user.id,
|
||||
});
|
||||
const resExtUser = await extUser.first({ useMasterKey: true });
|
||||
if (resExtUser) {
|
||||
const _resExtUser = JSON.parse(JSON.stringify(resExtUser));
|
||||
const subscription = new Parse.Query('contracts_Subscriptions');
|
||||
subscription.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: _resExtUser.TenantId.objectId,
|
||||
});
|
||||
subscription.include('ExtUserPtr');
|
||||
const resSub = await subscription.first({ useMasterKey: true });
|
||||
if (resSub) {
|
||||
const _resSub = JSON.parse(JSON.stringify(resSub));
|
||||
// Define the URL
|
||||
const url = 'https://accounts.zoho.in/oauth/v2/token';
|
||||
|
||||
// Convert the data to x-www-form-urlencoded format
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('refresh_token', process.env.ZOHO_REFRESH_TOKEN);
|
||||
formData.append('client_id', process.env.ZOHO_CLIENT_ID);
|
||||
formData.append('client_secret', process.env.ZOHO_CLIENT_SECRET);
|
||||
formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI);
|
||||
formData.append('grant_type', 'refresh_token');
|
||||
|
||||
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
// Make the POST request using Axios
|
||||
const res = await axios.post(url, formData, { headers });
|
||||
if (res.data.access_token) {
|
||||
const subscriptionId = _resSub.SubscriptionId;
|
||||
const price = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.price;
|
||||
const plan_code = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.plan_code;
|
||||
const addonsArr = _resSub?.SubscriptionDetails?.data?.subscription?.addons || [];
|
||||
let addon = 0;
|
||||
if (addonsArr?.length > 0) {
|
||||
let allowedUsersMonthly = 0;
|
||||
let allowedUsersYearly = 0;
|
||||
addonsArr?.forEach(item => {
|
||||
if (item.addon_code === 'extra-teams-users-monthly') {
|
||||
allowedUsersMonthly += item.quantity;
|
||||
} else if (item.addon_code === 'extra-teams-users-yearly') {
|
||||
allowedUsersYearly += item.quantity;
|
||||
} else if (item.addon_code === 'extra-users') {
|
||||
allowedUsersMonthly += item.quantity;
|
||||
}
|
||||
});
|
||||
if (allowedUsersMonthly > 0 || allowedUsersYearly > 0) {
|
||||
addon = allowedUsersMonthly + allowedUsersYearly;
|
||||
}
|
||||
}
|
||||
const quantity = parseInt(users) + parseInt(addon);
|
||||
const addoncode = plan_code.includes('yearly')
|
||||
? 'extra-teams-users-yearly'
|
||||
: 'extra-teams-users-monthly';
|
||||
const data = JSON.stringify({
|
||||
plan: { plan_code: plan_code },
|
||||
addons: [
|
||||
{
|
||||
addon_code: addoncode,
|
||||
addon_description: 'Extra users',
|
||||
price: price,
|
||||
quantity: quantity,
|
||||
},
|
||||
],
|
||||
});
|
||||
const updatedSubscription = await axios.put(
|
||||
'https://www.zohoapis.in/billing/v1/subscriptions/' + subscriptionId,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: 'Zoho-oauthtoken ' + res.data.access_token,
|
||||
'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID,
|
||||
},
|
||||
}
|
||||
);
|
||||
const subscriptionInfo = { data: updatedSubscription.data };
|
||||
const allowedUsers = quantity + 1;
|
||||
const existAllowedCredits = _resSub?.AllowedCredits || 0;
|
||||
const credits = resSub?.PlanCredits || planCredits[plan_code];
|
||||
const newAllowedCredits = users * credits;
|
||||
const totalCredits = existAllowedCredits + newAllowedCredits;
|
||||
const updateSub = new Parse.Object('contracts_Subscriptions');
|
||||
updateSub.id = resSub.id;
|
||||
updateSub.set('SubscriptionDetails', subscriptionInfo);
|
||||
updateSub.set('AllowedUsers', allowedUsers);
|
||||
updateSub.set('AllowedCredits', totalCredits);
|
||||
const resupdateSub = await updateSub.save(null, { useMasterKey: true });
|
||||
return { status: 'success', addon: allowedUsers };
|
||||
} else {
|
||||
throw new Parse.Error('400', 'Invalid access token.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
|
||||
}
|
||||
} catch (err) {
|
||||
const code = err?.response?.data?.code || err?.response?.status || err?.code || 400;
|
||||
const msg =
|
||||
err?.response?.data?.error ||
|
||||
err?.response?.data ||
|
||||
err?.message ||
|
||||
'Something went wrong.';
|
||||
console.log('err in buyaddon', code, msg);
|
||||
throw new Parse.Error(code, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import axios from 'axios';
|
||||
export default async function BuyCredits(request) {
|
||||
const credits = request.params.credits;
|
||||
if (!request?.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
}
|
||||
if (credits) {
|
||||
try {
|
||||
const extUser = new Parse.Query('contracts_Users');
|
||||
extUser.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: request.user.id,
|
||||
});
|
||||
const resExtUser = await extUser.first({ useMasterKey: true });
|
||||
if (resExtUser) {
|
||||
const _resExtUser = JSON.parse(JSON.stringify(resExtUser));
|
||||
const subscription = new Parse.Query('contracts_Subscriptions');
|
||||
subscription.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: _resExtUser.TenantId.objectId,
|
||||
});
|
||||
subscription.include('ExtUserPtr');
|
||||
const resSub = await subscription.first({ useMasterKey: true });
|
||||
if (resSub) {
|
||||
const _resSub = JSON.parse(JSON.stringify(resSub));
|
||||
// Define the URL
|
||||
const url = 'https://accounts.zoho.in/oauth/v2/token';
|
||||
|
||||
// Convert the data to x-www-form-urlencoded format
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('refresh_token', process.env.ZOHO_REFRESH_TOKEN);
|
||||
formData.append('client_id', process.env.ZOHO_CLIENT_ID);
|
||||
formData.append('client_secret', process.env.ZOHO_CLIENT_SECRET);
|
||||
formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI);
|
||||
formData.append('grant_type', 'refresh_token');
|
||||
|
||||
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
|
||||
// Make the POST request using Axios
|
||||
const res = await axios.post(url, formData, { headers });
|
||||
if (res.data.access_token) {
|
||||
const subscriptionId = _resSub.SubscriptionId;
|
||||
const plan_code = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.plan_code;
|
||||
const quantity = parseInt(credits);
|
||||
const addoncode = 'addon-credits';
|
||||
const data = JSON.stringify({
|
||||
plan: { plan_code: plan_code },
|
||||
addons: [
|
||||
{ addon_code: addoncode, addon_description: 'addon credits', quantity: quantity },
|
||||
],
|
||||
});
|
||||
const creditsUrl = `https://www.zohoapis.in/billing/v1/subscriptions/${subscriptionId}/buyonetimeaddon`;
|
||||
const resCredits = await axios.post(creditsUrl, data, {
|
||||
headers: {
|
||||
Authorization: 'Zoho-oauthtoken ' + res.data.access_token,
|
||||
'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID,
|
||||
},
|
||||
});
|
||||
// console.log('resCredits ', resCredits.data);
|
||||
if (resCredits.data) {
|
||||
const existAddonCredits = _resSub?.AddonCredits ? _resSub.AddonCredits : 0;
|
||||
const addonCredits = existAddonCredits + quantity;
|
||||
const updateSub = new Parse.Object('contracts_Subscriptions');
|
||||
updateSub.id = resSub.id;
|
||||
updateSub.set('AddonCredits', addonCredits);
|
||||
const resupdateSub = await updateSub.save(null, { useMasterKey: true });
|
||||
// console.log('resupdateSub ', resupdateSub);
|
||||
return { status: 'success', addon: quantity };
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error('400', 'Invalid access token.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
|
||||
}
|
||||
} catch (err) {
|
||||
const code = err?.response?.data?.code || err?.response?.status || err?.code || 400;
|
||||
const msg =
|
||||
err?.response?.data?.error ||
|
||||
err?.response?.data ||
|
||||
err?.message ||
|
||||
'Something went wrong.';
|
||||
console.log('err in Buyaddonusers', code, msg);
|
||||
throw new Parse.Error(code, msg);
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide parameters.');
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
async function ContractUsersAftersave(request) {
|
||||
console.log('In contracts_Users aftersave');
|
||||
|
||||
if (!request.original) {
|
||||
const shareWithTeam = request.object.get('ShareWithTeam');
|
||||
const newACL = new Parse.ACL();
|
||||
const contactbook = new Parse.Object('contracts_Contactbook');
|
||||
contactbook.set('Name', request.object.get('Name'));
|
||||
contactbook.set('Email', request.object.get('Email'));
|
||||
contactbook.set('ExtUserPtr', request.object.get('objectId'));
|
||||
if (request.object?.get('Phone')) {
|
||||
contactbook.set('Phone', request.object.get('Phone'));
|
||||
}
|
||||
contactbook.set('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: request.object.id,
|
||||
});
|
||||
contactbook.set('UserId', request.object.get('UserId'));
|
||||
|
||||
// newACL.setPublicReadAccess(false);
|
||||
// newACL.setPublicWriteAccess(false);
|
||||
const tenant_Id = request.object.get('TenantId');
|
||||
if (shareWithTeam) {
|
||||
if (tenant_Id) {
|
||||
newACL.setReadAccess('tenant_' + tenant_Id.id, true);
|
||||
newACL.setWriteAccess('tenant_' + tenant_Id.id, true);
|
||||
}
|
||||
newACL.setReadAccess(request.user, true);
|
||||
newACL.setWriteAccess(request.user, true);
|
||||
} else {
|
||||
newACL.setReadAccess(request.user, true);
|
||||
newACL.setWriteAccess(request.user, true);
|
||||
}
|
||||
|
||||
contactbook.setACL(newACL);
|
||||
contactbook.save(null, { useMasterkey: true });
|
||||
}
|
||||
}
|
||||
export default ContractUsersAftersave;
|
||||
@@ -5,7 +5,7 @@ async function DocumentBeforesave(request) {
|
||||
const oldDocument = request.original;
|
||||
|
||||
// Check if SignedUrl field has been added (transition from undefined to defined)
|
||||
if (!oldDocument?.get('SignedUrl') && document?.get('SignedUrl')) {
|
||||
if (oldDocument && !oldDocument?.get('SignedUrl') && document?.get('SignedUrl')) {
|
||||
// Update count in contracts_Users class
|
||||
const query = new Parse.Query('contracts_Users');
|
||||
query.equalTo('objectId', oldDocument.get('ExtUserPtr').id);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
async function ExtUserAfterdelete(request) {
|
||||
try {
|
||||
const extUser = request.object;
|
||||
const tenantId = extUser.get('TenantId')?.id;
|
||||
const subscription = new Parse.Query('contracts_Subscriptions');
|
||||
subscription.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: tenantId,
|
||||
});
|
||||
const resSub = await subscription.first({ useMasterKey: true });
|
||||
if (resSub) {
|
||||
const updateSub = new Parse.Object('contracts_Subscriptions');
|
||||
updateSub.id = resSub.id;
|
||||
if (resSub?.get('UsersCount') > 0) {
|
||||
updateSub.decrement('UsersCount', 1);
|
||||
} else {
|
||||
updateSub.decrement('UsersCount', 0);
|
||||
}
|
||||
updateSub.save(null, { useMasterKey: true });
|
||||
} else {
|
||||
const addSub = new Parse.Object('contracts_Subscriptions');
|
||||
addSub.set('UsersCount', 0);
|
||||
addSub.save(null, { useMasterKey: true });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in extuser afterdelete', err);
|
||||
}
|
||||
}
|
||||
|
||||
export default ExtUserAfterdelete;
|
||||
@@ -0,0 +1,44 @@
|
||||
async function ExtUserAftersave(request) {
|
||||
try {
|
||||
if (!request.original) {
|
||||
const extUser = request.object;
|
||||
const tenantId = extUser.get('TenantId')?.id;
|
||||
const subscription = new Parse.Query('contracts_Subscriptions');
|
||||
subscription.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: tenantId,
|
||||
});
|
||||
const resSub = await subscription.first({ useMasterKey: true });
|
||||
if (resSub) {
|
||||
const updateSub = new Parse.Object('contracts_Subscriptions');
|
||||
updateSub.id = resSub.id;
|
||||
updateSub.increment('UsersCount', 1);
|
||||
await updateSub.save(null, { useMasterKey: true });
|
||||
} else {
|
||||
const addSub = new Parse.Object('contracts_Subscriptions');
|
||||
addSub.set('UsersCount', 1);
|
||||
addSub.set('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
});
|
||||
addSub.set('CreatedBy', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: extUser.get('UserId').id,
|
||||
});
|
||||
addSub.set('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: extUser.get('TenantId').id,
|
||||
});
|
||||
await addSub.save(null, { useMasterKey: true });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in extuser aftersave', err);
|
||||
}
|
||||
}
|
||||
|
||||
export default ExtUserAftersave;
|
||||
@@ -62,6 +62,7 @@ const createDocumentFromTemplate = async (template, existContact, index) => {
|
||||
object.set('CreatedBy', template?.CreatedBy);
|
||||
object.set('ExtUserPtr', template?.ExtUserPtr);
|
||||
object.set('OriginIp', template?.OriginIp || '');
|
||||
object.set('IsEnableOTP', template?.IsEnableOTP || false);
|
||||
let signers = template?.Signers || [];
|
||||
const signerobj = {
|
||||
__type: 'Pointer',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { smtpenable, updateMailCount } from '../../Utils.js';
|
||||
import { appName, smtpenable, updateMailCount } from '../../Utils.js';
|
||||
async function getDocument(docId) {
|
||||
try {
|
||||
const query = new Parse.Query('contracts_Document');
|
||||
@@ -28,12 +28,12 @@ async function sendMailOTPv1(request) {
|
||||
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
|
||||
try {
|
||||
await Parse.Cloud.sendEmail({
|
||||
from: 'Opensign™' + ' <' + mailsender + '>',
|
||||
from: appName + ' <' + mailsender + '>',
|
||||
recipient: recipient,
|
||||
subject: 'Your OpenSign™ OTP',
|
||||
subject: `Your ${appName} OTP`,
|
||||
text: 'This email is a test.',
|
||||
html:
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for OpenSign™ verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>" +
|
||||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for ${appName} verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>` +
|
||||
code +
|
||||
'</p></div> </div> </div></body></html>',
|
||||
});
|
||||
|
||||
@@ -14,21 +14,38 @@ export default async function SubscribeFree(request) {
|
||||
});
|
||||
subscriptionCls.descending('createdAt');
|
||||
const subcripitions = await subscriptionCls.first({ useMasterKey: true });
|
||||
if (subcripitions?.get('PlanCode') === 'freeplan') {
|
||||
return { status: 'success', result: 'already subscribed!' };
|
||||
} else if (subcripitions?.get('Next_billing_date') < new Date()) {
|
||||
try {
|
||||
const updateSubscription = new Parse.Object('contracts_Subscriptions');
|
||||
updateSubscription.id = subcripitions.id;
|
||||
updateSubscription.set('PlanCode', 'freeplan');
|
||||
await updateSubscription.save(null, { useMasterKey: true });
|
||||
return { status: 'success', result: 'subscribed!' };
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
return { status: 'error', result: err.message };
|
||||
if (subcripitions) {
|
||||
if (subcripitions?.get('PlanCode') === 'freeplan') {
|
||||
return { status: 'success', result: 'already subscribed!' };
|
||||
} else if (subcripitions?.get('Next_billing_date') < new Date()) {
|
||||
try {
|
||||
const updateSubscription = new Parse.Object('contracts_Subscriptions');
|
||||
updateSubscription.id = subcripitions.id;
|
||||
updateSubscription.set('PlanCode', 'freeplan');
|
||||
updateSubscription.set('AllowedCredits', 0);
|
||||
updateSubscription.set('PlanCredits', 0);
|
||||
await updateSubscription.save(null, { useMasterKey: true });
|
||||
return { status: 'success', result: 'subscribed!' };
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
return { status: 'error', result: err.message };
|
||||
}
|
||||
} else if (subcripitions?.get('Next_billing_date') > new Date()) {
|
||||
return { status: 'success', result: 'already subscribed!' };
|
||||
} else {
|
||||
try {
|
||||
const updateSubscription = new Parse.Object('contracts_Subscriptions');
|
||||
updateSubscription.id = subcripitions.id;
|
||||
updateSubscription.set('PlanCode', 'freeplan');
|
||||
updateSubscription.set('AllowedCredits', 0);
|
||||
updateSubscription.set('PlanCredits', 0);
|
||||
await updateSubscription.save(null, { useMasterKey: true });
|
||||
return { status: 'success', result: 'subscribed!' };
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
return { status: 'error', result: err.message };
|
||||
}
|
||||
}
|
||||
} else if (subcripitions?.get('Next_billing_date') > new Date()) {
|
||||
return { status: 'success', result: 'already subscribed!' };
|
||||
} else {
|
||||
try {
|
||||
const createSubscription = new Parse.Object('contracts_Subscriptions');
|
||||
@@ -50,6 +67,8 @@ export default async function SubscribeFree(request) {
|
||||
objectId: extUser.get('TenantId').id,
|
||||
});
|
||||
}
|
||||
createSubscription.set('AllowedCredits', 0);
|
||||
createSubscription.set('PlanCredits', 0);
|
||||
await createSubscription.save(null, { useMasterKey: true });
|
||||
return { status: 'success', result: 'subscribed!' };
|
||||
} catch (err) {
|
||||
|
||||
@@ -18,9 +18,7 @@ export default async function ZohoDetails(request) {
|
||||
formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI);
|
||||
formData.append('grant_type', 'refresh_token');
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
};
|
||||
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
// Make the POST request using Axios
|
||||
const res = await axios.post(url, formData, { headers });
|
||||
// console.log("Access Token:", res.data);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user