mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-03 16:28:56 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf307abe9a | ||
|
|
6c59c0ec9a | ||
|
|
32c75d1fc3 | ||
|
|
7f0f81c334 | ||
|
|
a882f07f57 | ||
|
|
74b81a5363 | ||
|
|
9b75764717 | ||
|
|
c1468112b0 | ||
|
|
950b74789c | ||
|
|
d75c1d7c0c | ||
|
|
7eacbedf59 | ||
|
|
2e5947279f | ||
|
|
ef5ac7d626 | ||
|
|
658c37cc4d | ||
|
|
3fb96fc792 | ||
|
|
71016ba5e0 | ||
|
|
c2afeed448 | ||
|
|
37fd7bbeda | ||
|
|
71fbde0099 | ||
|
|
df1c0ea66c | ||
|
|
46279efc2c | ||
|
|
eaec3cf607 | ||
|
|
ad25d73f26 | ||
|
|
481b9ac1e0 | ||
|
|
4cd3791feb | ||
|
|
7de8699437 | ||
|
|
4c162676b1 | ||
|
|
ca390bdfa6 | ||
|
|
22d064fd38 | ||
|
|
444c9bbfdf | ||
|
|
b289686d42 | ||
|
|
eeb878b340 | ||
|
|
7894d69cc9 | ||
|
|
b992d7db24 | ||
|
|
50b72f4d85 | ||
|
|
44639f66b0 | ||
|
|
ae740efced | ||
|
|
66c020d903 | ||
|
|
562a7d6fc8 | ||
|
|
090c17c2c0 | ||
|
|
70637d6143 | ||
|
|
f38e8705de | ||
|
|
ba06ff72a0 | ||
|
|
82312a1e0d | ||
|
|
1a8cf11a3e | ||
|
|
3deacbabc3 | ||
|
|
c28805add9 | ||
|
|
80fb320ae4 | ||
|
|
76c9c1fa53 | ||
|
|
f38d83dbe0 | ||
|
|
ee9411729d | ||
|
|
f6f7c6cecd | ||
|
|
a45fc0dd60 | ||
|
|
348f7810b9 | ||
|
|
45bec128a9 | ||
|
|
4da307315c | ||
|
|
c87aa415a9 | ||
|
|
b1786ec16a | ||
|
|
6ad35e979e | ||
|
|
9d996d5cb7 | ||
|
|
008e3e8783 | ||
|
|
8458906b0b | ||
|
|
6933aa309b | ||
|
|
067ea68d3b | ||
|
|
e4e8a0b721 | ||
|
|
dc392adc1d | ||
|
|
93b0d00ab0 | ||
|
|
d731951159 | ||
|
|
24bf08c14f | ||
|
|
c03819f5a9 | ||
|
|
4521d16891 | ||
|
|
f77525cd8b | ||
|
|
c934f8ccd6 | ||
|
|
4e128e5a5a | ||
|
|
b202d78059 | ||
|
|
e15136ce51 | ||
|
|
39c9211167 | ||
|
|
64864e3f54 | ||
|
|
af0bef6d29 | ||
|
|
130704b32f | ||
|
|
5b139b66a9 | ||
|
|
f9c5041af1 | ||
|
|
1e58b35db5 | ||
|
|
245bf8046b | ||
|
|
94eb5f9efe | ||
|
|
044128f36a | ||
|
|
594383fe14 | ||
|
|
90a585367e | ||
|
|
0b8382df00 | ||
|
|
a54c7e7fd7 | ||
|
|
45a2c88e90 | ||
|
|
e4526f026b | ||
|
|
11a2f6b8ec | ||
|
|
3bf35166fc | ||
|
|
cd50c8cf20 | ||
|
|
055ef3f209 | ||
|
|
964ec0d422 | ||
|
|
ec0e291604 | ||
|
|
836e952ba0 | ||
|
|
c5f39e2a24 | ||
|
|
56dd060531 | ||
|
|
960fbdb0eb | ||
|
|
f8145feebc | ||
|
|
df38d15611 | ||
|
|
bb69518674 | ||
|
|
fee53fb020 | ||
|
|
d086e60ae5 | ||
|
|
51150d4e47 | ||
|
|
1d3cd02f9d | ||
|
|
d03515efc4 | ||
|
|
30e6e4b3fd | ||
|
|
cbdd729e95 | ||
|
|
ac82209d52 | ||
|
|
d5cc6f6758 | ||
|
|
3aa211dc1a | ||
|
|
33d5eecbbf | ||
|
|
1d6c108ba4 | ||
|
|
79da0f285d | ||
|
|
e8eef32958 | ||
|
|
dc1fab5250 | ||
|
|
3e185ef796 | ||
|
|
8cf5adb273 | ||
|
|
803faed97b | ||
|
|
660eaeb714 | ||
|
|
79126700c4 | ||
|
|
6355c02485 | ||
|
|
bce028877b | ||
|
|
e47211a8c0 | ||
|
|
4a4c9eaa2d | ||
|
|
0a5653f53d | ||
|
|
5a90a0f484 | ||
|
|
a3a398bd92 | ||
|
|
7d70162137 | ||
|
|
e4c62c4eeb | ||
|
|
3eadca01fa | ||
|
|
659b7fe0f4 | ||
|
|
ade6694427 | ||
|
|
f8160665a4 | ||
|
|
5778bdecae | ||
|
|
9122c71c90 | ||
|
|
2c2065aca8 | ||
|
|
b1dd4df3c0 | ||
|
|
64fd3580ac | ||
|
|
01273777b7 | ||
|
|
23eaa1ae81 | ||
|
|
3fd0613407 | ||
|
|
d45566257f | ||
|
|
e9eb59aa7a | ||
|
|
9e3ba2cf5c | ||
|
|
81afc92c14 | ||
|
|
d3bf9e9e9b | ||
|
|
18e25f6cd9 | ||
|
|
a6d97d587f | ||
|
|
ff9a495775 | ||
|
|
b29c7d5c7f | ||
|
|
fddeba8d44 | ||
|
|
c564b5741c | ||
|
|
179c3c5dfe | ||
|
|
221f71c061 | ||
|
|
a26e65f76f | ||
|
|
9ba05d23fd | ||
|
|
0f67013005 | ||
|
|
65d5c7f337 | ||
|
|
a7dd47f1b1 | ||
|
|
350212c02f | ||
|
|
d9126d6701 | ||
|
|
51c3a0f71a | ||
|
|
a4bf9ff915 | ||
|
|
28a2a113e2 | ||
|
|
07382822bd | ||
|
|
06677d923c | ||
|
|
534dc3d3f5 | ||
|
|
a69682cebf | ||
|
|
2ae7e8ced2 | ||
|
|
e20b75cfc1 | ||
|
|
37b73e36ca | ||
|
|
cc6b64d140 | ||
|
|
4b765a8e1d | ||
|
|
4a7108e4f6 | ||
|
|
66bf577676 | ||
|
|
13e2d92ce6 | ||
|
|
4b9f22a5bc | ||
|
|
77c61c89a2 | ||
|
|
d04b79001a | ||
|
|
e14b2a2a17 | ||
|
|
6afa97a76b | ||
|
|
3a28b1cdd7 | ||
|
|
23b48838ae | ||
|
|
bddebf2758 | ||
|
|
5a68e18c48 | ||
|
|
280b812e5b | ||
|
|
082f0569e2 | ||
|
|
b01618c8ca | ||
|
|
7b0f0d5b9f | ||
|
|
1381a65c0b | ||
|
|
961ec2b2ba | ||
|
|
022cb20b64 | ||
|
|
7b4fa4fe23 | ||
|
|
c85c21ef8a | ||
|
|
09fb01862d | ||
|
|
f6ce1b45fb | ||
|
|
8fd2d4210e | ||
|
|
0f7f1f1ba2 | ||
|
|
8693ef29f6 | ||
|
|
dc67d2c23d | ||
|
|
761925fced | ||
|
|
c240eb133d | ||
|
|
9daed4a5ed | ||
|
|
ee18f82fa9 | ||
|
|
4085f8259d | ||
|
|
98f84f6cc8 | ||
|
|
da9363f588 | ||
|
|
5afb60a687 | ||
|
|
a18733ad79 | ||
|
|
400e132f11 | ||
|
|
c56830b7be | ||
|
|
f988c25491 | ||
|
|
53bd13e03b | ||
|
|
68edecae18 | ||
|
|
b421c0f1a9 | ||
|
|
3858746d64 | ||
|
|
41e8aa18a7 | ||
|
|
fb8915e9db | ||
|
|
ba2bb07aa9 | ||
|
|
74083d0606 | ||
|
|
0f2862c52e | ||
|
|
61e11b7105 | ||
|
|
d7de8648e9 | ||
|
|
c279fd6525 | ||
|
|
3599fbee21 | ||
|
|
9ad51a1d6a | ||
|
|
03e22dbf33 | ||
|
|
5693fa230b | ||
|
|
8cd90911a8 | ||
|
|
7cf820d205 | ||
|
|
4d01547fd5 | ||
|
|
6597f67f0a | ||
|
|
14ce91350e | ||
|
|
7a5f75ea79 | ||
|
|
a44cb7168f | ||
|
|
1c6b056d6c | ||
|
|
cf6759ce79 | ||
|
|
d422f46f47 | ||
|
|
85f94c8ffa | ||
|
|
91da331ce6 | ||
|
|
3db6946198 | ||
|
|
94c8caa3e6 | ||
|
|
ed1120927c | ||
|
|
79b4c38587 | ||
|
|
2cc5517bc1 | ||
|
|
eddf70f102 | ||
|
|
b6774519f9 | ||
|
|
d6bb83f997 | ||
|
|
55e8042b0a | ||
|
|
d5bc2df20f | ||
|
|
c623e868f7 | ||
|
|
174b9b9941 | ||
|
|
0eb247ae0c | ||
|
|
f96df699e4 | ||
|
|
dcaca98aec | ||
|
|
d70f4dddb6 | ||
|
|
db34c4248c | ||
|
|
1d39db6d7d | ||
|
|
654dadce93 | ||
|
|
e578981791 | ||
|
|
c4297a260e | ||
|
|
ecc453957c | ||
|
|
f14c98505a | ||
|
|
3f23bdd372 | ||
|
|
493731a6af | ||
|
|
28fd98c0db | ||
|
|
b04d2abbab | ||
|
|
387154c07b | ||
|
|
f7606bce35 | ||
|
|
f08758fd71 | ||
|
|
d3b1a37a7c | ||
|
|
4718c653f7 | ||
|
|
f9b8437981 | ||
|
|
b0c73bc63e | ||
|
|
269246659b | ||
|
|
51fe5d133e | ||
|
|
36a1123535 | ||
|
|
3c1ceb4995 | ||
|
|
53d154fa59 |
@@ -205,6 +205,33 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ugoconsonni",
|
||||
"name": "ugoconsonni",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/13661702?v=4",
|
||||
"profile": "https://github.com/ugoconsonni",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "daniel-mutwiri",
|
||||
"name": "Daniel Mutwiri",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/8936960?v=4",
|
||||
"profile": "https://github.com/daniel-mutwiri",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Zathiel",
|
||||
"name": "Zathiel",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/26553418?v=4",
|
||||
"profile": "https://github.com/Zathiel",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -134,6 +134,9 @@ We would like to thank all our contributors and users for their support and feed
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/VikramNagwal"><img src="https://avatars.githubusercontent.com/u/123088024?v=4?s=100" width="100px;" alt="Vikram"/><br /><sub><b>Vikram</b></sub></a><br /><a href="#code-VikramNagwal" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ugoconsonni"><img src="https://avatars.githubusercontent.com/u/13661702?v=4?s=100" width="100px;" alt="ugoconsonni"/><br /><sub><b>ugoconsonni</b></sub></a><br /><a href="#code-ugoconsonni" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daniel-mutwiri"><img src="https://avatars.githubusercontent.com/u/8936960?v=4?s=100" width="100px;" alt="Daniel Mutwiri"/><br /><sub><b>Daniel Mutwiri</b></sub></a><br /><a href="#code-daniel-mutwiri" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zathiel"><img src="https://avatars.githubusercontent.com/u/26553418?v=4?s=100" width="100px;" alt="Zathiel"/><br /><sub><b>Zathiel</b></sub></a><br /><a href="#code-Zathiel" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Generated
+246
-277
File diff suppressed because it is too large
Load Diff
+19
-18
@@ -3,22 +3,23 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.1",
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@radix-ui/themes": "^2.0.3",
|
||||
"@react-pdf/renderer": "^3.4.0",
|
||||
"@reduxjs/toolkit": "^2.2.1",
|
||||
"axios": "^1.6.8",
|
||||
"@react-pdf/renderer": "^3.4.4",
|
||||
"@reduxjs/toolkit": "^2.2.5",
|
||||
"axios": "^1.7.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"parse": "^4.3.1",
|
||||
"parse": "^5.1.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"print-js": "^1.6.0",
|
||||
"radix-ui": "^1.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.10.2",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-cookie": "^7.1.4",
|
||||
"react-datepicker": "^6.4.0",
|
||||
"react-datepicker": "^6.9.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dnd-multi-backend": "^8.0.3",
|
||||
@@ -28,24 +29,24 @@
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-pdf": "^7.7.1",
|
||||
"react-pdf": "^8.0.2",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-redux": "^9.1.0",
|
||||
"react-rnd": "^10.4.1",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-rnd": "^10.4.10",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.8.0",
|
||||
"react-signature-canvas": "^1.0.6",
|
||||
"react-tooltip": "^5.26.3",
|
||||
"react-tooltip": "^5.26.4",
|
||||
"react-web-share": "^2.0.2",
|
||||
"reactour": "^1.19.2",
|
||||
"reactour": "^1.19.3",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"regex-parser": "^2.3.0",
|
||||
"serve": "^14.2.1",
|
||||
"serve": "^14.2.3",
|
||||
"styled-components": "^4.4.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
"web-vitals": "^4.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "serve -s build",
|
||||
@@ -83,16 +84,16 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/runtime-corejs2": "^7.24.1",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"@babel/runtime-corejs2": "^7.24.5",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"commitizen": "^4.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"lint-staged": "^15.2.2",
|
||||
"postcss": "^8.4.37",
|
||||
"lint-staged": "^15.2.4",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.3.1",
|
||||
"tailwindcss": "^3.4.1"
|
||||
"tailwindcss": "^3.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,10 @@ function App() {
|
||||
path="/login/:id/:userMail/:contactBookId/:serverUrl"
|
||||
element={<LazyPage Page={GuestLogin} />}
|
||||
/>
|
||||
<Route
|
||||
path="/login/:base64url"
|
||||
element={<LazyPage Page={GuestLogin} />}
|
||||
/>
|
||||
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
|
||||
<Route
|
||||
path="/forgetpassword"
|
||||
|
||||
@@ -49,6 +49,7 @@ const AddSigner = (props) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsLoader(true);
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
try {
|
||||
const contactQuery = new Parse.Object("contracts_Contactbook");
|
||||
contactQuery.set("Name", name);
|
||||
@@ -56,13 +57,11 @@ const AddSigner = (props) => {
|
||||
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")
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
@@ -188,6 +187,9 @@ const AddSigner = (props) => {
|
||||
setIsLoader(false);
|
||||
alert("something went wrong!");
|
||||
}
|
||||
}else{
|
||||
alert('something went wrong, please try again later')
|
||||
}
|
||||
};
|
||||
|
||||
// Define a function to handle the "add yourself" checkbox
|
||||
|
||||
@@ -115,8 +115,8 @@ const GoogleSignInBtn = ({
|
||||
// console.log("payload ", payload);
|
||||
if (payload && payload.sessiontoken) {
|
||||
// setThirdpartyLoader(true);
|
||||
const billingDate =
|
||||
extRes.get("Next_billing_date") && extRes.get("Next_billing_date");
|
||||
// const billingDate =
|
||||
// extRes.get("Next_billing_date") && extRes.get("Next_billing_date");
|
||||
// console.log("billingDate expired", billingDate > new Date());
|
||||
const LocalUserDetails = {
|
||||
name: details.Name,
|
||||
@@ -125,7 +125,7 @@ const GoogleSignInBtn = ({
|
||||
company: extRes.get("Company")
|
||||
};
|
||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
||||
thirdpartyLoginfn(payload.sessiontoken, billingDate);
|
||||
thirdpartyLoginfn(payload.sessiontoken);
|
||||
}
|
||||
return { msg: "exist" };
|
||||
} else {
|
||||
|
||||
@@ -22,7 +22,8 @@ const RenderDebugPdf = (props) => {
|
||||
flex: 1,
|
||||
position: "relative",
|
||||
border: "1px solid grey",
|
||||
overflow: "auto"
|
||||
overflow: "auto",
|
||||
cursor: "crosshair"
|
||||
}}
|
||||
onMouseMove={props.handleMouseMoveDiv}
|
||||
>
|
||||
|
||||
@@ -17,7 +17,7 @@ function DefaultSignature({ defaultSignImg, xyPostion, setDefaultSignAlert }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-tut="reactourThird">
|
||||
<div
|
||||
style={{
|
||||
background: themeColor,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import "../../styles/AddUser.css";
|
||||
import { getFileName } from "../../constant/Utils";
|
||||
import { checkIsSubscribed, getFileName } from "../../constant/Utils";
|
||||
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
// import SelectFolder from "../../premitives/SelectFolder";
|
||||
|
||||
const EditTemplate = ({ template, onSuccess }) => {
|
||||
@@ -9,9 +12,21 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
Name: template?.Name || "",
|
||||
Note: template?.Note || "",
|
||||
Description: template?.Description || "",
|
||||
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false"
|
||||
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
|
||||
AutomaticReminders: template?.AutomaticReminders || false,
|
||||
RemindOnceInEvery: template?.RemindOnceInEvery || 5
|
||||
});
|
||||
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
useEffect(() => {
|
||||
fetchSubscription();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const fetchSubscription = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
}
|
||||
};
|
||||
const handleStrInput = (e) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
@@ -25,12 +40,26 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const isChecked = formData.SendinOrder === "true" ? true : false;
|
||||
const data = { ...formData, SendinOrder: isChecked };
|
||||
const AutoReminder = formData?.AutomaticReminders || false;
|
||||
|
||||
let reminderDate = {};
|
||||
if (AutoReminder) {
|
||||
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
|
||||
const ReminderDate = new Date(template?.createdAt);
|
||||
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
|
||||
reminderDate = { NextReminderDate: ReminderDate };
|
||||
}
|
||||
const data = { ...formData, SendinOrder: isChecked, ...reminderDate };
|
||||
onSuccess(data);
|
||||
};
|
||||
|
||||
const handleAutoReminder = () => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
AutomaticReminders: !formData.AutomaticReminders
|
||||
}));
|
||||
};
|
||||
return (
|
||||
<div className="addusercontainer">
|
||||
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
||||
<div className="form-wrapper">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div>
|
||||
@@ -128,6 +157,57 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
<div style={{ fontSize: 12 }}>No</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
{!isEnableSubscription && (
|
||||
<PremiumAlertHeader
|
||||
message={
|
||||
"Disable Auto reminder is free in beta, this feature will incur a fee later."
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={
|
||||
isSubscribe || !isEnableSubscription
|
||||
? "font-semibold"
|
||||
: "font-semibold text-gray-300"
|
||||
}
|
||||
>
|
||||
Auto reminder{" "}
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</span>
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription
|
||||
? "cursor-pointer "
|
||||
: "pointer-events-none opacity-50"
|
||||
} relative block items-center mb-0`}
|
||||
>
|
||||
<input
|
||||
checked={formData.AutomaticReminders}
|
||||
onChange={handleAutoReminder}
|
||||
type="checkbox"
|
||||
value=""
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-1 peer-focus:ring-black rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-black peer-checked:bg-blue-600 mt-2"></div>
|
||||
</label>
|
||||
</div>
|
||||
{isSubscribe && formData?.AutomaticReminders === true && (
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
Remind once in every (Days)
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.RemindOnceInEvery}
|
||||
name="RemindOnceInEvery"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
onChange={handleStrInput}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* <SelectFolder onSuccess={handleFolder} folderCls={"contracts_Template"} /> */}
|
||||
<div className="buttoncontainer">
|
||||
<button type="submit" className="submitbutton">
|
||||
|
||||
@@ -20,7 +20,7 @@ export function EmailBody(props) {
|
||||
required
|
||||
value={props.requestSubject}
|
||||
onChange={(e) => props.setRequestSubject(e.target.value)}
|
||||
placeholder="${senderName} has requested you to sign ${documentName}"
|
||||
placeholder='${senderName} has requested you to sign "${documentName}"'
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
/>
|
||||
<label className="text-sm ml-2 mt-3">
|
||||
|
||||
@@ -10,20 +10,22 @@ import loader from "../../assets/images/loader2.gif";
|
||||
function EmailComponent({
|
||||
isEmail,
|
||||
pdfUrl,
|
||||
isCeleb,
|
||||
setIsEmail,
|
||||
setSuccessEmail,
|
||||
pdfName,
|
||||
sender,
|
||||
setIsAlert,
|
||||
extUserId
|
||||
extUserId,
|
||||
activeMailAdapter
|
||||
}) {
|
||||
const [emailList, setEmailList] = useState([]);
|
||||
const [emailValue, setEmailValue] = useState();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isEmailCelebration, setIsEmailCelebration] = useState(false);
|
||||
//function for send email
|
||||
const sendEmail = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
let sendMail;
|
||||
for (let i = 0; i < emailList.length; i++) {
|
||||
try {
|
||||
@@ -39,6 +41,7 @@ function EmailComponent({
|
||||
const openSignUrl = "https://www.opensignlabs.com/contact-us";
|
||||
const themeBGcolor = themeColor;
|
||||
let params = {
|
||||
mailProvider: activeMailAdapter,
|
||||
extUserId: extUserId,
|
||||
pdfName: pdfName,
|
||||
url: pdfUrl,
|
||||
@@ -59,6 +62,10 @@ function EmailComponent({
|
||||
" target=_blank>here</a> </p></div></div></body></html>"
|
||||
};
|
||||
sendMail = await axios.post(url, params, { headers: headers });
|
||||
setIsEmailCelebration(true);
|
||||
setTimeout(() => {
|
||||
setIsEmailCelebration(false);
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
setIsLoading(false);
|
||||
@@ -242,22 +249,17 @@ function EmailComponent({
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ height: "100%", padding: 20 }}>
|
||||
{isCeleb && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
marginLeft: "50px"
|
||||
}}
|
||||
>
|
||||
{isEmailCelebration && (
|
||||
<div className="absolute w-[100%] flex justify-center items-center">
|
||||
<img
|
||||
alt="print img"
|
||||
width={300}
|
||||
height={250}
|
||||
// style={styles.gifCeleb}
|
||||
src={celebration}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p
|
||||
style={{
|
||||
fontFamily: "system-ui",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import PrevNext from "./PrevNext";
|
||||
import printModule from "print-js";
|
||||
import { getBase64FromUrl } from "../../constant/Utils";
|
||||
@@ -7,8 +7,9 @@ import "../../styles/signature.css";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { themeColor } from "../../constant/const";
|
||||
import Certificate from "./Certificate";
|
||||
import { PDFDownloadLink } from "@react-pdf/renderer";
|
||||
import axios from "axios";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { appInfo } from "../../constant/appinfo";
|
||||
|
||||
function Header({
|
||||
isPdfRequestFiles,
|
||||
@@ -39,6 +40,7 @@ function Header({
|
||||
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const navigate = useNavigate();
|
||||
const [isCertificate, setIsCertificate] = useState(false);
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
//for go to previous page
|
||||
function previousPage() {
|
||||
@@ -61,30 +63,67 @@ function Header({
|
||||
const handleToPrint = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const pdf = await getBase64FromUrl(pdfUrl);
|
||||
const isAndroidDevice = navigator.userAgent.match(/Android/i);
|
||||
const isAppleDevice =
|
||||
(/iPad|iPhone|iPod/.test(navigator.platform) ||
|
||||
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)) &&
|
||||
!window.MSStream;
|
||||
if (isAndroidDevice || isAppleDevice) {
|
||||
const byteArray = Uint8Array.from(
|
||||
atob(pdf)
|
||||
.split("")
|
||||
.map((char) => char.charCodeAt(0))
|
||||
try {
|
||||
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
|
||||
const axiosRes = await axios.post(
|
||||
`${appInfo.baseUrl}/functions/getsignedurl`,
|
||||
{ url: pdfUrl },
|
||||
{
|
||||
headers: {
|
||||
"content-type": "Application/json",
|
||||
"X-Parse-Application-Id": appInfo.appId,
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
);
|
||||
const blob = new Blob([byteArray], { type: "application/pdf" });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
window.open(blobUrl, "_blank");
|
||||
} else {
|
||||
printModule({ printable: pdf, type: "pdf", base64: true });
|
||||
const url = axiosRes.data.result;
|
||||
const pdf = await getBase64FromUrl(url);
|
||||
const isAndroidDevice = navigator.userAgent.match(/Android/i);
|
||||
const isAppleDevice =
|
||||
(/iPad|iPhone|iPod/.test(navigator.platform) ||
|
||||
(navigator.platform === "MacIntel" &&
|
||||
navigator.maxTouchPoints > 1)) &&
|
||||
!window.MSStream;
|
||||
if (isAndroidDevice || isAppleDevice) {
|
||||
const byteArray = Uint8Array.from(
|
||||
atob(pdf)
|
||||
.split("")
|
||||
.map((char) => char.charCodeAt(0))
|
||||
);
|
||||
const blob = new Blob([byteArray], { type: "application/pdf" });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
window.open(blobUrl, "_blank");
|
||||
} else {
|
||||
printModule({ printable: pdf, type: "pdf", base64: true });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in getsignedurl", err);
|
||||
alert("something went wrong, please try again later.");
|
||||
}
|
||||
};
|
||||
|
||||
//handle download signed pdf
|
||||
const handleDownloadPdf = () => {
|
||||
const handleDownloadPdf = async () => {
|
||||
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
|
||||
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
|
||||
try {
|
||||
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
|
||||
const axiosRes = await axios.post(
|
||||
`${appInfo.baseUrl}/functions/getsignedurl`,
|
||||
{ url: pdfUrl },
|
||||
{
|
||||
headers: {
|
||||
"content-type": "Application/json",
|
||||
"X-Parse-Application-Id": appInfo.appId,
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
);
|
||||
const url = axiosRes.data.result;
|
||||
saveAs(url, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
|
||||
} catch (err) {
|
||||
console.log("err in getsignedurl", err);
|
||||
alert("something went wrong, please try again later.");
|
||||
}
|
||||
};
|
||||
|
||||
const sanitizeFileName = (pdfName) => {
|
||||
@@ -102,55 +141,41 @@ function Header({
|
||||
} catch (err) {
|
||||
console.log("err in download in certificate", err);
|
||||
}
|
||||
} else {
|
||||
setIsCertificate(true);
|
||||
try {
|
||||
const data = {
|
||||
docId: pdfDetails[0]?.objectId
|
||||
};
|
||||
const docDetails = await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}functions/getDocument`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
);
|
||||
if (docDetails.data && docDetails.data.result) {
|
||||
const doc = docDetails.data.result;
|
||||
if (doc?.CertificateUrl) {
|
||||
await fetch(doc?.CertificateUrl);
|
||||
const certificateUrl = doc?.CertificateUrl;
|
||||
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
|
||||
setIsCertificate(false);
|
||||
} else {
|
||||
setIsCertificate(true);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in download in certificate", err);
|
||||
alert("something went wrong, please try again later.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const GenerateCertificate = () => {
|
||||
//after generate download certifcate pdf
|
||||
const handleDownload = (pdfBlob, fileName) => {
|
||||
if (pdfBlob) {
|
||||
const url = window.URL.createObjectURL(pdfBlob);
|
||||
// Create a temporary anchor element
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
// Append the anchor to the body
|
||||
document.body.appendChild(link);
|
||||
// Programmatically click the anchor to trigger the download
|
||||
link.click();
|
||||
// Remove the anchor from the body
|
||||
document.body.removeChild(link);
|
||||
// Release the object URL to free up resources
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PDFDownloadLink
|
||||
onClick={(e) => e.preventDefault()}
|
||||
style={{ textDecoration: "none", zIndex: "35" }}
|
||||
document={<Certificate pdfData={pdfDetails} />}
|
||||
>
|
||||
{({ blob, loading }) => (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleDownload(
|
||||
blob,
|
||||
`completion certificate-${
|
||||
pdfDetails[0] && pdfDetails[0].Name
|
||||
}.pdf`
|
||||
)
|
||||
}
|
||||
disabled={loading}
|
||||
className=" md:bg-[#08bc66] border-none focus:outline-none flex flex-row items-center md:shadow md:rounded-[3px] py-[3px] md:px-[11px] md:text-white text-black md:font-[500] text-[13px] mr-[5px]"
|
||||
>
|
||||
<i className="fa-solid fa-award py-[3px]" aria-hidden="true"></i>
|
||||
<span className="md:hidden lg:block ml-1">Certificate</span>
|
||||
</button>
|
||||
)}
|
||||
</PDFDownloadLink>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div style={{ padding: "5px 0px 5px 0px" }} className="mobileHead">
|
||||
{isMobile && isShowHeader ? (
|
||||
@@ -240,32 +265,24 @@ function Header({
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
{isCompleted && (
|
||||
<>
|
||||
{pdfDetails[0] && pdfDetails[0]?.CertificateUrl ? (
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => handleDownloadCertificate()}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
border: "none",
|
||||
backgroundColor: "#fff"
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa-solid fa-award"
|
||||
style={{ marginRight: "2px" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Certificate
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
) : (
|
||||
<DropdownMenu.Item className="DropdownMenuItem">
|
||||
<GenerateCertificate />
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
</>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => handleDownloadCertificate()}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
border: "none",
|
||||
backgroundColor: "#fff"
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa-solid fa-award"
|
||||
style={{ marginRight: "2px" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Certificate
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
{isSignYourself && (
|
||||
<DropdownMenu.Item
|
||||
@@ -309,24 +326,16 @@ function Header({
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
) : (
|
||||
<div style={{ display: "flex", justifyContent: "space-around" }}>
|
||||
<div className="flex justify-around items-center">
|
||||
{/* current signer is checking user send request and check status of pdf sign than if current
|
||||
user exist than show finish button else no
|
||||
*/}
|
||||
{currentSigner && (
|
||||
<>
|
||||
<div className="flex items-center" data-tut="reactourFifth">
|
||||
{decline && (
|
||||
<div
|
||||
onClick={() => {
|
||||
handleDeclinePdfAlert();
|
||||
}}
|
||||
style={{
|
||||
color: "red",
|
||||
border: "none",
|
||||
fontWeight: "650",
|
||||
fontSize: "16px",
|
||||
marginRight: "10px"
|
||||
}}
|
||||
onClick={() => handleDeclinePdfAlert()}
|
||||
className="text-[red] border-none font-[650] text-[14px] mr-2"
|
||||
>
|
||||
Decline
|
||||
</div>
|
||||
@@ -338,12 +347,8 @@ function Header({
|
||||
alertSendEmail();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
color: isMailSend ? "gray" : themeColor,
|
||||
border: "none",
|
||||
fontWeight: "650",
|
||||
fontSize: "16px"
|
||||
}}
|
||||
className="border-none font-[650] text-[14px]"
|
||||
style={{ color: isMailSend ? "gray" : themeColor }}
|
||||
data-tut={dataTut4}
|
||||
>
|
||||
{completeBtnTitle ? completeBtnTitle : "Send"}
|
||||
@@ -358,17 +363,43 @@ function Header({
|
||||
embedWidgetsData();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
color: themeColor,
|
||||
border: "none",
|
||||
fontWeight: "650",
|
||||
fontSize: "16px"
|
||||
}}
|
||||
style={{ color: themeColor }}
|
||||
className="border-none font-[650] text-[14px]"
|
||||
>
|
||||
Finish
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div
|
||||
className="border-none font-[650] text-[18px] px-2 ml-[4px]"
|
||||
style={{ color: themeColor }}
|
||||
>
|
||||
<i
|
||||
className="fa fa-ellipsis-v"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="bg-white shadow-md rounded px-2 py-1"
|
||||
sideOffset={5}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
|
||||
onClick={() => handleDownloadPdf()}
|
||||
>
|
||||
<i
|
||||
className="fa fa-arrow-down mr-[5px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="font-[500]">Download</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -433,34 +464,26 @@ function Header({
|
||||
{completeBtnTitle
|
||||
? completeBtnTitle
|
||||
: isMailSend
|
||||
? "Sent"
|
||||
: "Send"}
|
||||
? "Sent"
|
||||
: "Send"}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : isPdfRequestFiles ? (
|
||||
alreadySign ? (
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
<div className="flex flex-row">
|
||||
{isCompleted && (
|
||||
<>
|
||||
{pdfDetails[0] && pdfDetails[0]?.CertificateUrl ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDownloadCertificate()}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
|
||||
>
|
||||
<i
|
||||
className="fa-solid fa-award py-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="hidden lg:block ml-1">
|
||||
Certificate
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<GenerateCertificate />
|
||||
)}
|
||||
</>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDownloadCertificate()}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
|
||||
>
|
||||
<i
|
||||
className="fa-solid fa-award py-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="hidden lg:block ml-1">Certificate</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
@@ -482,7 +505,7 @@ function Header({
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex">
|
||||
<div className="flex" data-tut="reactourFifth">
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
type="button"
|
||||
@@ -493,50 +516,66 @@ function Header({
|
||||
{currentSigner && (
|
||||
<>
|
||||
<button
|
||||
style={{ background: "#de4337" }}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
|
||||
onClick={() => {
|
||||
handleDeclinePdfAlert();
|
||||
}}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#de4337]"
|
||||
onClick={() => handleDeclinePdfAlert()}
|
||||
>
|
||||
Decline
|
||||
</button>
|
||||
<button
|
||||
style={{
|
||||
background: "#188ae2"
|
||||
}}
|
||||
type="button"
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
|
||||
onClick={() => {
|
||||
embedWidgetsData();
|
||||
}}
|
||||
onClick={() => embedWidgetsData()}
|
||||
>
|
||||
Finish
|
||||
</button>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div
|
||||
className="border-none font-[650] text-[16px] py-[3px] px-[11px] shadow rounded bg-white"
|
||||
style={{ color: themeColor }}
|
||||
>
|
||||
<i
|
||||
className="fa fa-ellipsis-v"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="bg-white shadow-md rounded px-2 py-1"
|
||||
sideOffset={5}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
|
||||
onClick={() => handleDownloadPdf()}
|
||||
>
|
||||
<i
|
||||
className="fa fa-arrow-down mr-[5px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="font-[500]">Download</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
) : isCompleted ? (
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
<div className="flex flex-row">
|
||||
{isCompleted && (
|
||||
<>
|
||||
{pdfDetails[0] && pdfDetails[0]?.CertificateUrl ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDownloadCertificate()}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
|
||||
>
|
||||
<i
|
||||
className="fa-solid fa-award py-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="hidden lg:block ml-1">Certificate</span>
|
||||
</button>
|
||||
) : (
|
||||
<GenerateCertificate />
|
||||
)}
|
||||
</>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDownloadCertificate()}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
|
||||
>
|
||||
<i
|
||||
className="fa-solid fa-award py-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="hidden lg:block ml-1">Certificate</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={handleToPrint}
|
||||
@@ -575,9 +614,6 @@ function Header({
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
style={{
|
||||
background: "#188ae2"
|
||||
}}
|
||||
type="button"
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
|
||||
onClick={() => {
|
||||
@@ -592,6 +628,21 @@ function Header({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isCertificate}
|
||||
title={"Generating certificate"}
|
||||
handleClose={() => setIsCertificate(false)}
|
||||
>
|
||||
<div className="p-3 md:p-5 text-[13px] md:text-base text-center">
|
||||
<p>
|
||||
Your completion certificate is being generated. Please wait
|
||||
momentarily.
|
||||
</p>
|
||||
<p>
|
||||
If the download doesn't start shortly, click the button again.
|
||||
</p>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ function PlaceholderType(props) {
|
||||
"December"
|
||||
];
|
||||
const validateExpression = (regexValidation) => {
|
||||
if (textValue) {
|
||||
if (textValue && regexValidation) {
|
||||
let regexObject = regexValidation;
|
||||
if (props.pos?.options?.validation?.type === "regex") {
|
||||
regexObject = RegexParser(regexValidation);
|
||||
@@ -56,7 +56,7 @@ function PlaceholderType(props) {
|
||||
props.setDraggingEnabled(true);
|
||||
const validateType = props.pos?.options?.validation?.type;
|
||||
let regexValidation;
|
||||
if (validateType) {
|
||||
if (validateType && validateType !== "text") {
|
||||
switch (validateType) {
|
||||
case "email":
|
||||
regexValidation = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
@@ -66,12 +66,8 @@ function PlaceholderType(props) {
|
||||
regexValidation = /^[0-9\s]*$/;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
case "text":
|
||||
regexValidation = /^[a-zA-Z\s]+$/;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
default:
|
||||
regexValidation = props.pos?.options.validation.pattern;
|
||||
regexValidation = props.pos?.options?.validation?.pattern || "";
|
||||
validateExpression(regexValidation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import RSC from "react-scrollbars-custom";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import { themeColor } from "../../constant/const";
|
||||
@@ -9,13 +9,54 @@ function RenderAllPdfPage({
|
||||
setAllPages,
|
||||
setPageNumber,
|
||||
setSignBtnPosition,
|
||||
pageNumber
|
||||
pageNumber,
|
||||
signerPos,
|
||||
signerObjectId
|
||||
}) {
|
||||
const [signPageNumber, setSignPageNumber] = useState([]);
|
||||
const [bookmarkColor, setBookmarkColor] = useState("");
|
||||
//set all number of pages after load pdf
|
||||
function onDocumentLoad({ numPages }) {
|
||||
setAllPages(numPages);
|
||||
//check if signerPos array exist then save page number exist in signerPos array to show bookmark icon
|
||||
if (signerPos) {
|
||||
const checkUser = signerPos.filter(
|
||||
(data) => data.signerObjId === signerObjectId
|
||||
);
|
||||
setBookmarkColor(checkUser[0]?.blockColor);
|
||||
let pageNumberArr = [];
|
||||
if (checkUser?.length > 0) {
|
||||
checkUser[0]?.placeHolder?.map((data) => {
|
||||
pageNumberArr.push(data?.pageNumber);
|
||||
});
|
||||
|
||||
setSignPageNumber(pageNumberArr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//'function `addSignatureBookmark` is used to display the page where the user's signature is located.
|
||||
const addSignatureBookmark = (index) => {
|
||||
const ispageNumber = signPageNumber.includes(index + 1);
|
||||
return (
|
||||
ispageNumber && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
top: -12,
|
||||
right: -7,
|
||||
transform: "translate(50% -50%)"
|
||||
}}
|
||||
>
|
||||
<i
|
||||
style={{ color: bookmarkColor || "red" }}
|
||||
className="fa-solid fa-bookmark"
|
||||
></i>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className="showPages">
|
||||
<div
|
||||
@@ -66,7 +107,8 @@ function RenderAllPdfPage({
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
contain: "content"
|
||||
|
||||
position: "relative"
|
||||
}}
|
||||
onClick={() => {
|
||||
setPageNumber(index + 1);
|
||||
@@ -75,15 +117,25 @@ function RenderAllPdfPage({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Page
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={100}
|
||||
height={100}
|
||||
scale={1}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
/>
|
||||
{signerPos && addSignatureBookmark(index)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<Page
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={100}
|
||||
height={100}
|
||||
scale={1}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Document>
|
||||
|
||||
@@ -334,6 +334,7 @@ function RenderPdf({
|
||||
{successEmail && <Alert type={"success"}>Email sent successfully!</Alert>}
|
||||
{isMobile && scale ? (
|
||||
<div
|
||||
data-tut="reactourForth"
|
||||
style={{
|
||||
border: "0.1px solid #ebe8e8",
|
||||
marginTop: isGuestSigner && "30px"
|
||||
@@ -517,6 +518,7 @@ function RenderPdf({
|
||||
noScrollX={pdfNewWidth < pdfOriginalWidth ? false : true}
|
||||
>
|
||||
<div
|
||||
data-tut="reactourForth"
|
||||
style={{
|
||||
border: "0.1px solid #ebe8e8",
|
||||
width: pdfOriginalWidth
|
||||
|
||||
@@ -214,8 +214,8 @@ function SignPad({
|
||||
const fontfamily = fontStyle
|
||||
? fontStyle
|
||||
: fontSelect
|
||||
? fontSelect
|
||||
: "Fasthand";
|
||||
? fontSelect
|
||||
: "Fasthand";
|
||||
|
||||
//creating span for getting text content width
|
||||
const span = document.createElement("span");
|
||||
@@ -267,10 +267,10 @@ function SignPad({
|
||||
key === 0 && penColor === "blue"
|
||||
? "2px solid blue"
|
||||
: key === 1 && penColor === "red"
|
||||
? "2px solid red"
|
||||
: key === 2 && penColor === "black"
|
||||
? "2px solid black"
|
||||
: "2px solid white"
|
||||
? "2px solid red"
|
||||
: key === 2 && penColor === "black"
|
||||
? "2px solid black"
|
||||
: "2px solid white"
|
||||
}}
|
||||
onClick={() => {
|
||||
props?.convertToImg &&
|
||||
@@ -536,7 +536,6 @@ function SignPad({
|
||||
style={{
|
||||
border: "1.3px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
@@ -553,7 +552,7 @@ function SignPad({
|
||||
type="file"
|
||||
onChange={onImageChange}
|
||||
className="filetype"
|
||||
accept="image/*"
|
||||
accept="image/png,image/jpeg"
|
||||
ref={imageRef}
|
||||
hidden
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import React from "react";
|
||||
import { rejectBtn, submitBtn, themeColor } from "../../constant/const";
|
||||
|
||||
function VerifyEmail(props) {
|
||||
return (
|
||||
<div className="bg-black bg-opacity-[75%] absolute z-[999] flex flex-col items-center justify-center w-full h-full rounded">
|
||||
<div className="bg-white rounded outline-none md:w-[40%] w-[80%]">
|
||||
<div
|
||||
style={{ backgroundColor: themeColor }}
|
||||
className=" text-white p-[10px] rounded-t"
|
||||
>
|
||||
OTP verification
|
||||
</div>
|
||||
{props.isVerifyModal ? (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
props.setIsVerifyModal(false);
|
||||
props.handleVerifyEmail(e);
|
||||
}}
|
||||
>
|
||||
<div className="px-6 py-3">
|
||||
<label className="mb-2">Enter OTP</label>
|
||||
<input
|
||||
required
|
||||
type="tel"
|
||||
pattern="[0-9]{4}"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
placeholder="Enter OTP received over email"
|
||||
value={props.otp}
|
||||
onChange={(e) => props.setOtp(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="px-6 my-3">
|
||||
<button type="submit" className={submitBtn}>
|
||||
Verify
|
||||
</button>
|
||||
<button
|
||||
className={`${rejectBtn} ml-2`}
|
||||
onClick={(e) => props.handleResend(e)}
|
||||
>
|
||||
Resend
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : props.otpLoader ? (
|
||||
<div
|
||||
style={{
|
||||
height: "150px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "45px",
|
||||
color: "#3dd3e0"
|
||||
}}
|
||||
className="loader-37"
|
||||
></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-6 py-3">
|
||||
<p className="mb-2">Please verify your email !</p>
|
||||
<hr />
|
||||
<div className="px-0 mt-3">
|
||||
<button
|
||||
className={submitBtn}
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
props.handleVerifyBtn();
|
||||
}}
|
||||
>
|
||||
Send OTP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VerifyEmail;
|
||||
@@ -6,6 +6,7 @@ import { textInputWidget } from "../../constant/Utils";
|
||||
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
import Tooltip from "../../primitives/Tooltip";
|
||||
|
||||
const WidgetNameModal = (props) => {
|
||||
const [formdata, setFormdata] = useState({
|
||||
@@ -53,7 +54,11 @@ const WidgetNameModal = (props) => {
|
||||
}
|
||||
};
|
||||
const handleChange = (e) => {
|
||||
setFormdata({ ...formdata, [e.target.name]: e.target.value });
|
||||
if (e) {
|
||||
setFormdata({ ...formdata, [e.target.name]: e.target.value });
|
||||
} else {
|
||||
setFormdata({ ...formdata, textvalidate: "" });
|
||||
}
|
||||
};
|
||||
|
||||
const handledefaultChange = (e) => {
|
||||
@@ -136,16 +141,21 @@ const WidgetNameModal = (props) => {
|
||||
props.widgetName === textInputWidget) && (
|
||||
<>
|
||||
<div className="form-section">
|
||||
<label
|
||||
htmlFor="textvalidate"
|
||||
className={
|
||||
!props.isSubscribe && isEnableSubscription && "disabled"
|
||||
}
|
||||
style={{ fontSize: 13 }}
|
||||
>
|
||||
Validation
|
||||
</label>
|
||||
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
<div className="flex items-center gap-1">
|
||||
<label
|
||||
htmlFor="textvalidate"
|
||||
className={
|
||||
!props.isSubscribe && isEnableSubscription && "disabled"
|
||||
}
|
||||
style={{ fontSize: 13 }}
|
||||
>
|
||||
Validation
|
||||
</label>
|
||||
<Tooltip
|
||||
url={"https://www.w3schools.com/jsref/jsref_obj_regexp.asp"}
|
||||
/>
|
||||
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
!props.isSubscribe && isEnableSubscription && "disabled"
|
||||
@@ -161,7 +171,7 @@ const WidgetNameModal = (props) => {
|
||||
<input
|
||||
className="relative z-20 w-[87%] md:w-[92%] p-2.5 border-[1px] border-[#d1d5db] rounded-l-md outline-none text-xs"
|
||||
name="textvalidate"
|
||||
placeholder="Enter custom expression"
|
||||
placeholder="Enter custom regular expression"
|
||||
value={formdata.textvalidate}
|
||||
onChange={(e) => handleChange(e)}
|
||||
// onBlur={() => handleBlurRegex()}
|
||||
@@ -173,7 +183,10 @@ const WidgetNameModal = (props) => {
|
||||
onChange={(e) => handleChange(e)}
|
||||
onBlur={() => handleBlurRegex()}
|
||||
>
|
||||
<option disabled style={{ fontSize: "13px" }}>
|
||||
<option
|
||||
disabled={formdata?.textvalidate}
|
||||
style={{ fontSize: "13px" }}
|
||||
>
|
||||
Select...
|
||||
</option>
|
||||
{inputOpt.map((data, ind) => {
|
||||
|
||||
@@ -25,9 +25,11 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
|
||||
if (parentFolderId) {
|
||||
FolderQuery.equalTo("Folder", folderPtr);
|
||||
FolderQuery.equalTo("Type", "Folder");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
} else {
|
||||
FolderQuery.doesNotExist("Folder");
|
||||
FolderQuery.equalTo("Type", "Folder");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
}
|
||||
|
||||
const res = await FolderQuery.find();
|
||||
@@ -49,6 +51,7 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
|
||||
const exsitQuery = new Parse.Query(folderCls);
|
||||
exsitQuery.equalTo("Name", name);
|
||||
exsitQuery.equalTo("Type", "Folder");
|
||||
exsitQuery.notEqualTo("IsArchive", true);
|
||||
if (parentFolderId) {
|
||||
exsitQuery.equalTo("Folder", folderPtr);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ const FolderModal = (props) => {
|
||||
if (folderPtr) {
|
||||
FolderQuery.equalTo("Folder", folderPtr);
|
||||
FolderQuery.equalTo("Type", "Folder");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
} else {
|
||||
FolderQuery.doesNotExist("Folder");
|
||||
FolderQuery.equalTo("Type", "Folder");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
}
|
||||
|
||||
const res = await FolderQuery.find();
|
||||
|
||||
@@ -45,9 +45,11 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
|
||||
if (folderPtr) {
|
||||
FolderQuery.equalTo("Folder", folderPtr);
|
||||
FolderQuery.equalTo("Type", "Folder");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
} else {
|
||||
FolderQuery.doesNotExist("Folder");
|
||||
FolderQuery.equalTo("Type", "Folder");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
}
|
||||
|
||||
const res = await FolderQuery.find();
|
||||
@@ -169,32 +171,40 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
|
||||
{required && <span className="text-red-500 text-[13px]">*</span>}
|
||||
</label>
|
||||
</div>
|
||||
<div className="relative rounded px-[20px] py-[20px] bg-white border border-gray-200 shadow flex max-w-sm gap-8 items-center">
|
||||
<div>
|
||||
<i
|
||||
className="far fa-folder-open text-[40px] text-[#33bbff]"
|
||||
style={{ fontSize: "40px" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
<div className="font-semibold ">
|
||||
<div className="flex items-center gap-2">
|
||||
<p>
|
||||
{selectFolder && selectFolder.Name
|
||||
? selectFolder.Name
|
||||
: "OpenSign™ Drive"}
|
||||
</p>
|
||||
<div className="text-black text-sm" onClick={() => SetIsOpen(true)}>
|
||||
<i
|
||||
className="fa fa-pencil"
|
||||
title="Select Folder"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
<div className="relative max-w-sm">
|
||||
<div
|
||||
onClick={() => SetIsOpen(true)}
|
||||
className=" cursor-pointer rounded px-[20px] py-[20px] bg-white border border-gray-200 shadow flex max-w-sm gap-8 items-center"
|
||||
>
|
||||
<div>
|
||||
<i
|
||||
className="far fa-folder-open text-[40px] text-[#33bbff]"
|
||||
style={{ fontSize: "40px" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
<div className="font-semibold ">
|
||||
<div className="flex items-center gap-2">
|
||||
<p>
|
||||
{selectFolder && selectFolder.Name
|
||||
? selectFolder.Name
|
||||
: "OpenSign™ Drive"}
|
||||
</p>
|
||||
<div
|
||||
className="text-black text-sm"
|
||||
// onClick={() => SetIsOpen(true)}
|
||||
>
|
||||
<i
|
||||
className="fa fa-pencil cursor-pointer"
|
||||
title="Select Folder"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[10px] text-gray-400">
|
||||
{selectFolder && selectFolder.Name ? `(${folderPath})` : ""}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-[10px] text-gray-400">
|
||||
{selectFolder && selectFolder.Name ? `(${folderPath})` : ""}
|
||||
</p>
|
||||
</div>
|
||||
<div className="absolute top-2 right-1 cursor-pointer">
|
||||
<Tooltip
|
||||
|
||||
@@ -13,11 +13,15 @@ export const openInNewTab = (url) => {
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
};
|
||||
|
||||
export async function fetchSubscription(extUserId) {
|
||||
export async function fetchSubscription(
|
||||
extUserId,
|
||||
contactObjId,
|
||||
isGuestSign = false
|
||||
) {
|
||||
try {
|
||||
const extClass = localStorage.getItem("Extand_Class");
|
||||
let extUser;
|
||||
if (extClass) {
|
||||
if (extClass && extClass.length > 0) {
|
||||
const jsonSender = JSON.parse(extClass);
|
||||
extUser = jsonSender[0].objectId;
|
||||
} else {
|
||||
@@ -30,11 +34,19 @@ export async function fetchSubscription(extUserId) {
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const params = { extUserId: extUser };
|
||||
const params = isGuestSign
|
||||
? { contactId: contactObjId }
|
||||
: { extUserId: extUser };
|
||||
const tenatRes = await axios.post(url, params, { headers: headers });
|
||||
const plan = tenatRes.data?.result?.result?.PlanCode;
|
||||
const billingDate = tenatRes.data?.result?.result?.Next_billing_date?.iso;
|
||||
return { plan, billingDate };
|
||||
let plan, status, billingDate;
|
||||
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;
|
||||
}
|
||||
return { plan, billingDate, status };
|
||||
} catch (err) {
|
||||
console.log("Err in fetch subscription", err);
|
||||
return { plan: "", billingDate: "" };
|
||||
@@ -159,11 +171,10 @@ export const contractUsers = async (email) => {
|
||||
.then((Listdata) => {
|
||||
const json = Listdata.data;
|
||||
let data = [];
|
||||
|
||||
if (json && json.result) {
|
||||
data.push(json.result);
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Err ", err);
|
||||
@@ -528,53 +539,94 @@ export const signPdfFun = async (
|
||||
signerObjectId,
|
||||
setIsAlert,
|
||||
objectId,
|
||||
isSubscribed
|
||||
isSubscribed,
|
||||
activeMailAdapter,
|
||||
xyPosition
|
||||
) => {
|
||||
let singleSign,
|
||||
isCustomCompletionMail = false;
|
||||
|
||||
//get tenant details
|
||||
const tenantDetails = await getTenantDetails(objectId);
|
||||
if (tenantDetails && tenantDetails === "user does not exist!") {
|
||||
alert("User does not exist");
|
||||
} else {
|
||||
if (
|
||||
tenantDetails?.CompletionBody &&
|
||||
tenantDetails?.CompletionSubject &&
|
||||
(!isEnableSubscription || isSubscribed)
|
||||
) {
|
||||
isCustomCompletionMail = true;
|
||||
}
|
||||
}
|
||||
|
||||
singleSign = {
|
||||
pdfFile: base64Url,
|
||||
docId: documentId,
|
||||
userId: signerObjectId,
|
||||
isCustomCompletionMail: isCustomCompletionMail
|
||||
};
|
||||
const response = await axios
|
||||
.post(`${localStorage.getItem("baseUrl")}functions/signPdf`, singleSign, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
try {
|
||||
//get tenant details
|
||||
const tenantDetails = await getTenantDetails(objectId);
|
||||
if (tenantDetails && tenantDetails === "user does not exist!") {
|
||||
alert("User does not exist");
|
||||
} else {
|
||||
if (
|
||||
tenantDetails?.CompletionBody &&
|
||||
tenantDetails?.CompletionSubject &&
|
||||
(!isEnableSubscription || isSubscribed)
|
||||
) {
|
||||
isCustomCompletionMail = true;
|
||||
}
|
||||
})
|
||||
.then((Listdata) => {
|
||||
const json = Listdata.data;
|
||||
const res = json.result;
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Err ", err);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "something went wrong"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
// below for loop is used to get first signature of user to send if to signpdf
|
||||
// for adding it in completion certificate
|
||||
let getSignature;
|
||||
for (let item of xyPosition) {
|
||||
if (!getSignature) {
|
||||
const typeExist = item.pos.some((data) => data?.type);
|
||||
if (typeExist) {
|
||||
getSignature = item.pos.find((data) => data?.type === "signature");
|
||||
} else {
|
||||
getSignature = item.pos.find((data) => !data.isStamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let base64Sign = getSignature.SignUrl;
|
||||
//check https type signature (default signature exist) then convert in base64
|
||||
const isUrl = base64Sign.includes("https");
|
||||
if (isUrl) {
|
||||
try {
|
||||
base64Sign = await fetchImageBase64(base64Sign);
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
}
|
||||
}
|
||||
//change image width and height to 104/44 in png base64
|
||||
const getNewse64 = await changeImageWH(base64Sign);
|
||||
//remove suffiix of base64
|
||||
const suffixbase64 = getNewse64 && getNewse64.split(",").pop();
|
||||
|
||||
singleSign = {
|
||||
mailProvider: activeMailAdapter,
|
||||
pdfFile: base64Url,
|
||||
docId: documentId,
|
||||
userId: signerObjectId,
|
||||
isCustomCompletionMail: isCustomCompletionMail,
|
||||
signature: suffixbase64
|
||||
};
|
||||
const response = await axios
|
||||
.post(`${localStorage.getItem("baseUrl")}functions/signPdf`, singleSign, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
// sessionToken: localStorage.getItem("accesstoken")
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
})
|
||||
.then((Listdata) => {
|
||||
const json = Listdata.data;
|
||||
const res = json.result;
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Err ", err);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "something went wrong"
|
||||
});
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (e) {
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "something went wrong"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const randomId = () => {
|
||||
@@ -623,7 +675,9 @@ export const createDocument = async (template, placeholders, signerData) => {
|
||||
objectId: Doc.CreatedBy.objectId
|
||||
},
|
||||
Signers: signers,
|
||||
SendinOrder: Doc?.SendinOrder || false
|
||||
SendinOrder: Doc?.SendinOrder || false,
|
||||
AutomaticReminders: Doc?.AutomaticReminders || false,
|
||||
RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5)
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1111,7 +1165,6 @@ export const onImageSelect = (event, setImgWH, setImage) => {
|
||||
const imageType = event.target.files[0].type;
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(event.target.files[0]);
|
||||
|
||||
reader.onloadend = function (e) {
|
||||
let width, height;
|
||||
const image = new Image();
|
||||
@@ -1139,6 +1192,50 @@ export const onImageSelect = (event, setImgWH, setImage) => {
|
||||
};
|
||||
};
|
||||
|
||||
//convert https url to base64
|
||||
export const fetchImageBase64 = async (imageUrl) => {
|
||||
try {
|
||||
const response = await fetch(imageUrl);
|
||||
const blob = await response.blob();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(blob);
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result;
|
||||
resolve(base64data);
|
||||
};
|
||||
reader.onerror = (error) => {
|
||||
reject(error);
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error("Error converting URL to base64:", error);
|
||||
}
|
||||
};
|
||||
//function for select image and upload image
|
||||
export const changeImageWH = async (base64Image) => {
|
||||
const newWidth = 100;
|
||||
const newHeight = 40;
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = base64Image;
|
||||
img.onload = async () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = newWidth;
|
||||
canvas.height = newHeight;
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
ctx.drawImage(img, 0, 0, newWidth, newHeight);
|
||||
const resizedBase64 = canvas.toDataURL("image/png", 1);
|
||||
resolve(resizedBase64);
|
||||
};
|
||||
img.onerror = (error) => {
|
||||
reject(error);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
//function for embed multiple signature using pdf-lib
|
||||
export const multiSignEmbed = async (
|
||||
pngUrl,
|
||||
@@ -1253,8 +1350,8 @@ export const multiSignEmbed = async (
|
||||
position.type === radioButtonWidget
|
||||
? 10
|
||||
: position.type === "checkbox"
|
||||
? 10
|
||||
: newUpdateHeight;
|
||||
? 10
|
||||
: newUpdateHeight;
|
||||
const newHeight = ind ? (ind > 0 ? widgetHeight : 0) : widgetHeight;
|
||||
|
||||
if (signyourself) {
|
||||
@@ -1917,3 +2014,18 @@ export const convertPdfArrayBuffer = async (url) => {
|
||||
return "Error";
|
||||
}
|
||||
};
|
||||
|
||||
//`handleSendOTP` function is used to send otp on user's email using `SendOTPMailV1` cloud function
|
||||
export const handleSendOTP = async (email) => {
|
||||
try {
|
||||
let url = `${localStorage.getItem("baseUrl")}functions/SendOTPMailV1`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
|
||||
};
|
||||
const body = { email: email };
|
||||
await axios.post(url, body, { headers: headers });
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,6 +85,14 @@ export default function reportJson(id) {
|
||||
btnIcon: "fa-solid fa-ellipsis-vertical fa-lg",
|
||||
action: "option",
|
||||
subaction: [
|
||||
{
|
||||
btnId: "1630",
|
||||
btnLabel: "Resend",
|
||||
hoverLabel: "Resend",
|
||||
btnIcon: "fa-solid fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "resend"
|
||||
},
|
||||
{
|
||||
btnId: "1688",
|
||||
btnLabel: "Revoke",
|
||||
@@ -223,6 +231,14 @@ export default function reportJson(id) {
|
||||
btnIcon: "fa-solid fa-ellipsis-vertical fa-lg",
|
||||
action: "option",
|
||||
subaction: [
|
||||
{
|
||||
btnId: "1631",
|
||||
btnLabel: "Resend",
|
||||
hoverLabel: "Resend",
|
||||
btnIcon: "fa-solid fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "resend"
|
||||
},
|
||||
{
|
||||
btnId: "6788",
|
||||
btnLabel: "Revoke",
|
||||
|
||||
@@ -43,7 +43,7 @@ const plans = [
|
||||
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d8ad1135190dff951330360e47585a71/professional-yearly",
|
||||
target: "_self",
|
||||
benefits: [
|
||||
"Everything in OpenSign™ professional",
|
||||
"Everything in OpenSign™ free",
|
||||
"Field validations",
|
||||
"Regular expression validations",
|
||||
"Organize docs in OpenSign™ Drive",
|
||||
@@ -52,7 +52,8 @@ const plans = [
|
||||
"API Access",
|
||||
"100 API signatures included",
|
||||
"DocumentId removal from signed docs",
|
||||
"Custom email templates(coming soon)"
|
||||
"Custom email templates",
|
||||
"Auto reminders"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -28,29 +28,34 @@ const HomeLayout = () => {
|
||||
const [tourConfigs, setTourConfigs] = useState([]);
|
||||
const [, setCookie] = useCookies(["accesstoken", "main_Domain"]);
|
||||
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
// Use the session token to validate the user
|
||||
const userQuery = new Parse.Query(Parse.User);
|
||||
const user = await userQuery.get(Parse.User.current().id, {
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
});
|
||||
if (user) {
|
||||
localStorage.setItem("profileImg", user.get("ProfilePic") || "");
|
||||
checkIsSubscribed();
|
||||
} else {
|
||||
if (!tenantId) {
|
||||
setIsUserValid(false);
|
||||
} else {
|
||||
(async () => {
|
||||
try {
|
||||
// Use the session token to validate the user
|
||||
const userQuery = new Parse.Query(Parse.User);
|
||||
const user = await userQuery.get(Parse.User.current().id, {
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
});
|
||||
if (user) {
|
||||
localStorage.setItem("profileImg", user.get("ProfilePic") || "");
|
||||
checkIsSubscribed();
|
||||
} else {
|
||||
setIsUserValid(false);
|
||||
}
|
||||
} catch (error) {
|
||||
// Session token is invalid or there was an error
|
||||
setIsUserValid(false);
|
||||
}
|
||||
} catch (error) {
|
||||
// Session token is invalid or there was an error
|
||||
setIsUserValid(false);
|
||||
}
|
||||
})();
|
||||
saveCookies();
|
||||
})();
|
||||
saveCookies();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [tenantId]);
|
||||
//function to use save data in cookies storage
|
||||
const saveCookies = () => {
|
||||
const main_Domain = window.location.origin;
|
||||
|
||||
@@ -24,7 +24,7 @@ const Dashboard = () => {
|
||||
getDashboard(localStorage.getItem("PageLanding"));
|
||||
}
|
||||
} else {
|
||||
navigate("/", { replace: true, state: { from: location } });
|
||||
navigate("/", { replace: true, state: { from: "" } });
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [id]);
|
||||
|
||||
@@ -108,7 +108,7 @@ function ForgotPassword() {
|
||||
</button>
|
||||
<NavLink
|
||||
to="/"
|
||||
className="rounded-sm bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] px-4 py-3 text-xs font-semibold shadow uppercase"
|
||||
className="rounded-sm bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] px-4 py-3 text-xs font-semibold shadow uppercase cursor-pointer"
|
||||
>
|
||||
Login
|
||||
</NavLink>
|
||||
|
||||
+675
-203
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { formJson } from "../json/FormJson";
|
||||
import AddUser from "../components/AddUser";
|
||||
@@ -11,7 +11,14 @@ import SignersInput from "../components/shared/fields/SignersInput";
|
||||
import Title from "../components/Title";
|
||||
import PageNotFound from "./PageNotFound";
|
||||
import { SaveFileSize } from "../constant/saveFileSize";
|
||||
import { getFileName } from "../constant/Utils";
|
||||
import { checkIsSubscribed, getFileName, toDataUrl } from "../constant/Utils";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import axios from "axios";
|
||||
import { isEnableSubscription, submitBtn } from "../constant/const";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import Upgrade from "../primitives/Upgrade";
|
||||
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
|
||||
|
||||
// `Form` render all type of Form on this basis of their provided in path
|
||||
function Form() {
|
||||
@@ -31,6 +38,8 @@ function Form() {
|
||||
|
||||
const Forms = (props) => {
|
||||
const maxFileSize = 20;
|
||||
const abortController = new AbortController();
|
||||
const inputFileRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const [signers, setSigners] = useState([]);
|
||||
const [folder, setFolder] = useState({ ObjectId: "", Name: "" });
|
||||
@@ -39,37 +48,248 @@ const Forms = (props) => {
|
||||
Description: "",
|
||||
Note: "",
|
||||
TimeToCompleteDays: 15,
|
||||
SendinOrder: "false"
|
||||
SendinOrder: "false",
|
||||
password: "",
|
||||
file: "",
|
||||
remindOnceInEvery: 5,
|
||||
autoreminder: false
|
||||
});
|
||||
const [fileupload, setFileUpload] = useState([]);
|
||||
const [fileupload, setFileUpload] = useState("");
|
||||
const [fileload, setfileload] = useState(false);
|
||||
const [percentage, setpercentage] = useState(0);
|
||||
const [isReset, setIsReset] = useState(false);
|
||||
const [isAlert, setIsAlert] = useState(false);
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
const [isErr, setIsErr] = useState("");
|
||||
const [isPassword, setIsPassword] = useState(false);
|
||||
const [isDecrypting, setIsDecrypting] = useState(false);
|
||||
const [isCorrectPass, setIsCorrectPass] = useState(true);
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
const handleStrInput = (e) => {
|
||||
setIsCorrectPass(true);
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
useEffect(() => {
|
||||
handleReset();
|
||||
return () => abortController.abort();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.title]);
|
||||
const handleFileInput = (e) => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchSubscription();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const fetchSubscription = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
}
|
||||
};
|
||||
|
||||
function getFileAsArrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = (e) => {
|
||||
resolve(e.target.result);
|
||||
};
|
||||
|
||||
reader.onerror = (e) => {
|
||||
reject(e.target.error);
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
const handleFileInput = async (e) => {
|
||||
setpercentage(0);
|
||||
try {
|
||||
let files = e.target.files;
|
||||
setFormData((prev) => ({ ...prev, file: e.target.files[0] }));
|
||||
if (typeof files[0] !== "undefined") {
|
||||
const mb = Math.round(files[0].size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(
|
||||
`The selected file size is too large. Please select a file less than ${maxFileSize} MB`
|
||||
);
|
||||
setFileUpload([]);
|
||||
setFileUpload("");
|
||||
e.target.value = "";
|
||||
return;
|
||||
} else {
|
||||
handleFileUpload(files[0]);
|
||||
if (files?.[0]?.type === "application/pdf") {
|
||||
try {
|
||||
const res = await getFileAsArrayBuffer(files[0]);
|
||||
await PDFDocument.load(res);
|
||||
handleFileUpload(files[0]);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
await Parse.Cloud.run("encryptedpdf", {
|
||||
email: Parse.User.current().getEmail()
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("err in sending posthog encryptedpdf", err);
|
||||
}
|
||||
// console.log("err ", err);
|
||||
try {
|
||||
setIsDecrypting(true);
|
||||
const fileName = files?.[0].name;
|
||||
const size = files?.[0].size;
|
||||
const name = sanitizeFileName(fileName);
|
||||
const url = "https://ai.nxglabs.in/decryptpdf"; //"https://ai.nxglabs.in/decryptpdf"; //
|
||||
let formData = new FormData();
|
||||
formData.append("file", files[0]);
|
||||
formData.append("password", "");
|
||||
const config = {
|
||||
headers: {
|
||||
"content-type": "multipart/form-data"
|
||||
// sessiontoken: Parse.User.current().getSessionToken()
|
||||
},
|
||||
responseType: "blob"
|
||||
};
|
||||
const response = await axios.post(url, formData, config);
|
||||
const pdfBlob = new Blob([response.data], {
|
||||
type: "application/pdf"
|
||||
});
|
||||
const pdfFile = new File([pdfBlob], name, {
|
||||
type: "application/pdf"
|
||||
});
|
||||
setIsDecrypting(false);
|
||||
setfileload(true);
|
||||
|
||||
// Upload the file to Parse Server
|
||||
const parseFile = new Parse.File(
|
||||
name,
|
||||
pdfFile,
|
||||
"application/pdf"
|
||||
);
|
||||
|
||||
await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Retrieve the URL of the uploaded file
|
||||
if (parseFile.url()) {
|
||||
setFileUpload(parseFile.url());
|
||||
setfileload(false);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, parseFile.url(), tenantId);
|
||||
return parseFile.url();
|
||||
}
|
||||
} catch (err) {
|
||||
setfileload(false);
|
||||
setpercentage(0);
|
||||
if (err?.response?.status === 401) {
|
||||
setIsPassword(true);
|
||||
} else {
|
||||
console.log("Error uploading file: ", err?.response);
|
||||
setIsDecrypting(false);
|
||||
e.target.value = "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("err ", err);
|
||||
setFileUpload("");
|
||||
e.target.value = "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const isImage = files?.[0]?.type.includes("image/");
|
||||
if (isImage) {
|
||||
const image = await toDataUrl(files[0]);
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
let embedImg;
|
||||
if (files?.[0]?.type === "image/png") {
|
||||
embedImg = await pdfDoc.embedPng(image);
|
||||
} else {
|
||||
embedImg = await pdfDoc.embedJpg(image);
|
||||
}
|
||||
|
||||
// Get image dimensions
|
||||
const imageWidth = embedImg.width;
|
||||
const imageHeight = embedImg.height;
|
||||
const page = pdfDoc.addPage([imageWidth, imageHeight]);
|
||||
page.drawImage(embedImg, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: imageWidth,
|
||||
height: imageHeight
|
||||
});
|
||||
const getFile = await pdfDoc.save({
|
||||
useObjectStreams: false
|
||||
});
|
||||
setfileload(true);
|
||||
const fileName = files[0].name;
|
||||
const size = files[0].size;
|
||||
const name = sanitizeFileName(fileName);
|
||||
const pdfName = `${name?.split(".")[0]}.pdf`;
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...getFile],
|
||||
"application/pdf"
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// The response object will contain information about the uploaded file
|
||||
// You can access the URL of the uploaded file using response.url()
|
||||
setFileUpload(response.url());
|
||||
setfileload(false);
|
||||
if (response.url()) {
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, response.url(), tenantId);
|
||||
return response.url();
|
||||
}
|
||||
} catch (error) {
|
||||
e.target.value = "";
|
||||
setfileload(false);
|
||||
setpercentage(0);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
} else {
|
||||
if (isEnableSubscription) {
|
||||
try {
|
||||
setfileload(true);
|
||||
const url = "https://ai.nxglabs.in/docxtopdf";
|
||||
let formData = new FormData();
|
||||
formData.append("file", files[0]);
|
||||
const config = {
|
||||
headers: {
|
||||
"content-type": "multipart/form-data",
|
||||
sessiontoken: Parse.User.current().getSessionToken()
|
||||
},
|
||||
signal: abortController.signal
|
||||
};
|
||||
const res = await axios.post(url, formData, config);
|
||||
if (res.data) {
|
||||
setFileUpload(res.data.url);
|
||||
setfileload(false);
|
||||
}
|
||||
} catch (err) {
|
||||
e.target.value = "";
|
||||
setfileload(false);
|
||||
setpercentage(0);
|
||||
console.log("err in libreconverter ", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("Please select file.");
|
||||
@@ -162,64 +382,69 @@ const Forms = (props) => {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSubmit(true);
|
||||
try {
|
||||
const currentUser = Parse.User.current();
|
||||
const object = new Parse.Object(props.Cls);
|
||||
object.set("Name", formData?.Name);
|
||||
object.set("Description", formData?.Description);
|
||||
object.set("Note", formData?.Note);
|
||||
if (props.title === "Request Signatures") {
|
||||
object.set(
|
||||
"TimeToCompleteDays",
|
||||
parseInt(formData?.TimeToCompleteDays)
|
||||
);
|
||||
}
|
||||
if (props.title !== "Sign Yourself") {
|
||||
const isChecked = formData.SendinOrder === "false" ? false : true;
|
||||
object.set("SendinOrder", isChecked);
|
||||
}
|
||||
object.set("URL", fileupload);
|
||||
object.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
|
||||
if (folder && folder.ObjectId) {
|
||||
object.set("Folder", {
|
||||
if (fileupload) {
|
||||
setIsSubmit(true);
|
||||
try {
|
||||
const currentUser = Parse.User.current();
|
||||
const object = new Parse.Object(props.Cls);
|
||||
object.set("Name", formData?.Name);
|
||||
object.set("Description", formData?.Description);
|
||||
object.set("Note", formData?.Note);
|
||||
if (props.title === "Request Signatures") {
|
||||
object.set(
|
||||
"TimeToCompleteDays",
|
||||
parseInt(formData?.TimeToCompleteDays)
|
||||
);
|
||||
}
|
||||
if (props.title !== "Sign Yourself") {
|
||||
const isChecked = formData.SendinOrder === "false" ? false : true;
|
||||
object.set("SendinOrder", isChecked);
|
||||
object.set("AutomaticReminders", formData.autoreminder);
|
||||
object.set("RemindOnceInEvery", parseInt(formData.remindOnceInEvery));
|
||||
}
|
||||
object.set("URL", fileupload);
|
||||
object.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
|
||||
if (folder && folder.ObjectId) {
|
||||
object.set("Folder", {
|
||||
__type: "Pointer",
|
||||
className: props.Cls,
|
||||
objectId: folder.ObjectId
|
||||
});
|
||||
}
|
||||
if (signers && signers.length > 0) {
|
||||
object.set("Signers", signers);
|
||||
}
|
||||
const ExtCls = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
object.set("ExtUserPtr", {
|
||||
__type: "Pointer",
|
||||
className: props.Cls,
|
||||
objectId: folder.ObjectId
|
||||
className: "contracts_Users",
|
||||
objectId: ExtCls[0].objectId
|
||||
});
|
||||
const res = await object.save();
|
||||
if (res) {
|
||||
setSigners([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
setFormData({
|
||||
Name: "",
|
||||
Description: "",
|
||||
Note: ""
|
||||
});
|
||||
setFileUpload("");
|
||||
setpercentage(0);
|
||||
navigate(`/${props?.redirectRoute}/${res.id}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
setIsErr(true);
|
||||
} finally {
|
||||
setIsAlert(true);
|
||||
setTimeout(() => {
|
||||
setIsAlert(false);
|
||||
}, 1000);
|
||||
setIsSubmit(false);
|
||||
}
|
||||
if (signers && signers.length > 0) {
|
||||
object.set("Signers", signers);
|
||||
}
|
||||
const ExtCls = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
object.set("ExtUserPtr", {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Users",
|
||||
objectId: ExtCls[0].objectId
|
||||
});
|
||||
|
||||
const res = await object.save();
|
||||
if (res) {
|
||||
setSigners([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
setFormData({
|
||||
Name: "",
|
||||
Description: "",
|
||||
Note: ""
|
||||
});
|
||||
setFileUpload([]);
|
||||
setpercentage(0);
|
||||
navigate(`/${props?.redirectRoute}/${res.id}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
setIsErr(true);
|
||||
} finally {
|
||||
setIsAlert(true);
|
||||
setTimeout(() => {
|
||||
setIsAlert(false);
|
||||
}, 1000);
|
||||
setIsSubmit(false);
|
||||
} else {
|
||||
alert("Please wait until the PDF has been uploading.");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -249,13 +474,92 @@ const Forms = (props) => {
|
||||
? "Note to myself"
|
||||
: "Please review and sign this document",
|
||||
TimeToCompleteDays: 15,
|
||||
SendinOrder: "true"
|
||||
SendinOrder: "true",
|
||||
password: "",
|
||||
file: "",
|
||||
remindOnceInEvery: 5,
|
||||
autoreminder: false
|
||||
});
|
||||
setFileUpload([]);
|
||||
setFileUpload("");
|
||||
setpercentage(0);
|
||||
setTimeout(() => setIsReset(false), 50);
|
||||
};
|
||||
|
||||
const handlePasswordSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsPassword(false);
|
||||
setfileload(true);
|
||||
try {
|
||||
const fileName = formData?.file?.name;
|
||||
const size = formData?.file?.size;
|
||||
const name = sanitizeFileName(fileName);
|
||||
const url = "https://ai.nxglabs.in/decryptpdf"; //
|
||||
let Data = new FormData();
|
||||
Data.append("file", formData?.file);
|
||||
Data.append("password", formData.password);
|
||||
const config = {
|
||||
headers: {
|
||||
"content-type": "multipart/form-data"
|
||||
// sessiontoken: Parse.User.current().getSessionToken()
|
||||
},
|
||||
responseType: "blob"
|
||||
};
|
||||
const response = await axios.post(url, Data, config);
|
||||
const pdfBlob = new Blob([response.data], {
|
||||
type: "application/pdf"
|
||||
});
|
||||
const pdfFile = new File([pdfBlob], name, {
|
||||
type: "application/pdf"
|
||||
});
|
||||
setIsDecrypting(false);
|
||||
// Upload the file to Parse Server
|
||||
const parseFile = new Parse.File(name, pdfFile, "application/pdf");
|
||||
|
||||
await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Retrieve the URL of the uploaded file
|
||||
if (parseFile.url()) {
|
||||
setFormData((prev) => ({ ...prev, password: "" }));
|
||||
setFileUpload(parseFile.url());
|
||||
setfileload(false);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, parseFile.url(), tenantId);
|
||||
return parseFile.url();
|
||||
}
|
||||
} catch (err) {
|
||||
setfileload(false);
|
||||
setpercentage(0);
|
||||
if (err?.response?.status === 401) {
|
||||
setIsPassword(true);
|
||||
setIsCorrectPass(false);
|
||||
} else {
|
||||
console.log("Error uploading file: ", err?.response);
|
||||
setFormData((prev) => ({ ...prev, password: "" }));
|
||||
setIsDecrypting(false);
|
||||
if (inputFileRef.current) {
|
||||
inputFileRef.current.value = ""; // Set file input value to empty string
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const handeCloseModal = () => {
|
||||
setIsPassword(false);
|
||||
setFormData((prev) => ({ ...prev, file: "", password: "" }));
|
||||
setIsDecrypting(false);
|
||||
setfileload(false);
|
||||
if (inputFileRef.current) {
|
||||
inputFileRef.current.value = ""; // Set file input value to empty string
|
||||
}
|
||||
};
|
||||
const handleAutoReminder = () => {
|
||||
setFormData((prev) => ({ ...prev, autoreminder: !formData.autoreminder }));
|
||||
};
|
||||
return (
|
||||
<div className="shadow-md rounded my-2 p-3 bg-[#ffffff] md:border-[1px] md:border-gray-600/50">
|
||||
<Title title={props?.title} />
|
||||
@@ -279,166 +583,334 @@ const Forms = (props) => {
|
||||
></div>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h1 className="text-[20px] font-semibold mb-4">{props?.title}</h1>
|
||||
{fileload && (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="h-2 rounded-full w-[200px] md:w-[400px] bg-gray-200">
|
||||
<div
|
||||
className="h-2 rounded-full bg-blue-500"
|
||||
style={{ width: `${percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="text-black text-sm">{percentage}%</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xs">
|
||||
<label className="block">
|
||||
File<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
{fileupload.length > 0 ? (
|
||||
<div className="flex gap-2 justify-center items-center">
|
||||
<div className="flex justify-between items-center px-2 py-2 w-full font-bold rounded border-[1px] border-[#ccc] text-gray-500 bg-white text-[13px]">
|
||||
<div className="break-all">
|
||||
file selected : {getFileName(fileupload)}
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setFileUpload([])}
|
||||
className="cursor-pointer px-[10px] text-[20px] font-bold bg-white text-red-500"
|
||||
>
|
||||
<i className="fa-solid fa-xmark"></i>
|
||||
</div>
|
||||
</div>
|
||||
{process.env.REACT_APP_DROPBOX_API_KEY && (
|
||||
<DropboxChooser
|
||||
onSuccess={dropboxSuccess}
|
||||
onCancel={dropboxCancel}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-2 justify-center items-center">
|
||||
<>
|
||||
<ModalUi
|
||||
isOpen={isPassword}
|
||||
handleClose={() => handeCloseModal()}
|
||||
title={"Enter Pdf Password"}
|
||||
>
|
||||
<form onSubmit={handlePasswordSubmit}>
|
||||
<div className="px-6 pt-3 pb-2">
|
||||
<label className="mb-2 text-xs">Password</label>
|
||||
<input
|
||||
type="file"
|
||||
className="bg-white px-2 py-1.5 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
onChange={(e) => handleFileInput(e)}
|
||||
accept="application/pdf"
|
||||
type="text"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
placeholder="Enter pdf password"
|
||||
required
|
||||
/>
|
||||
{process.env.REACT_APP_DROPBOX_API_KEY && (
|
||||
<DropboxChooser
|
||||
onSuccess={dropboxSuccess}
|
||||
onCancel={dropboxCancel}
|
||||
/>
|
||||
{!isCorrectPass ? (
|
||||
<p className="ml-2 text-[11px] text-red-600">
|
||||
Please provide correct password
|
||||
</p>
|
||||
) : (
|
||||
<p className="ml-2 text-[11px] text-transparent text-red-600">
|
||||
.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<hr />
|
||||
<div className="px-6 my-3">
|
||||
<button type="submit" className={submitBtn}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalUi>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h1 className="text-[20px] font-semibold mb-4">{props?.title}</h1>
|
||||
{fileload && (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="h-2 rounded-full w-[200px] md:w-[400px] bg-gray-200">
|
||||
<div
|
||||
className="h-2 rounded-full bg-blue-500"
|
||||
style={{ width: `${percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="text-black text-sm">{percentage}%</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
{props.title === "New Template"
|
||||
? "Template Title"
|
||||
: "Document Title"}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
name="Name"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
value={formData.Name}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">Description</label>
|
||||
<input
|
||||
name="Description"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
value={formData.Description}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
/>
|
||||
</div>
|
||||
{props.signers && (
|
||||
<SignersInput onChange={handleSigners} isReset={isReset} required />
|
||||
)}
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
Note<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
name="Note"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
value={formData.Note}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<SelectFolder
|
||||
onSuccess={handleFolder}
|
||||
folderCls={props.Cls}
|
||||
isReset={isReset}
|
||||
/>
|
||||
|
||||
{props.title === "Request Signatures" && (
|
||||
{isDecrypting && (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<span className="text-black text-sm">
|
||||
Decrypting pdf please wait...
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xs">
|
||||
<label className="block">
|
||||
{`File (pdf, png, jpg, jpeg${
|
||||
isEnableSubscription ? ", docx)" : ")"
|
||||
}`}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
{fileupload.length > 0 ? (
|
||||
<div className="flex gap-2 justify-center items-center">
|
||||
<div className="flex justify-between items-center px-2 py-2 w-full font-bold rounded border-[1px] border-[#ccc] text-gray-500 bg-white text-[13px]">
|
||||
<div className="break-all">
|
||||
file selected : {getFileName(fileupload)}
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setFileUpload("")}
|
||||
className="cursor-pointer px-[10px] text-[20px] font-bold bg-white text-red-500"
|
||||
>
|
||||
<i className="fa-solid fa-xmark"></i>
|
||||
</div>
|
||||
</div>
|
||||
{process.env.REACT_APP_DROPBOX_API_KEY && (
|
||||
<DropboxChooser
|
||||
onSuccess={dropboxSuccess}
|
||||
onCancel={dropboxCancel}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-2 justify-center items-center">
|
||||
<input
|
||||
type="file"
|
||||
className="bg-white px-2 py-1.5 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
onChange={(e) => handleFileInput(e)}
|
||||
ref={inputFileRef}
|
||||
accept={
|
||||
isEnableSubscription
|
||||
? "application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/png,image/jpeg"
|
||||
: "application/pdf,image/png,image/jpeg"
|
||||
}
|
||||
required
|
||||
/>
|
||||
{process.env.REACT_APP_DROPBOX_API_KEY && (
|
||||
<DropboxChooser
|
||||
onSuccess={dropboxSuccess}
|
||||
onCancel={dropboxCancel}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
Time To Complete (Days)
|
||||
{props.title === "New Template"
|
||||
? "Template Title"
|
||||
: "Document Title"}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="TimeToCompleteDays"
|
||||
name="Name"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
value={formData.TimeToCompleteDays}
|
||||
value={formData.Name}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{props.title !== "Sign Yourself" && (
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">Send In Order</label>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<label className="block">Description</label>
|
||||
<input
|
||||
name="Description"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
value={formData.Description}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
/>
|
||||
</div>
|
||||
{props.signers && (
|
||||
<SignersInput
|
||||
onChange={handleSigners}
|
||||
isReset={isReset}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
Note<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
name="Note"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
value={formData.Note}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{props.title !== "New Template" && (
|
||||
<SelectFolder
|
||||
onSuccess={handleFolder}
|
||||
folderCls={props.Cls}
|
||||
isReset={isReset}
|
||||
/>
|
||||
)}
|
||||
{props.title === "Request Signatures" && (
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
Time To Complete (Days)
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "true"}
|
||||
className=""
|
||||
onChange={handleStrInput}
|
||||
type="number"
|
||||
name="TimeToCompleteDays"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
value={formData.TimeToCompleteDays}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
required
|
||||
/>
|
||||
<div className="text-center">Yes</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">No</div>
|
||||
)}
|
||||
{props.title !== "Sign Yourself" && (
|
||||
<>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
Send In Order
|
||||
<a data-tooltip-id="sendInOrder-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i
|
||||
className="fa-solid fa-question rounded-full"
|
||||
style={{
|
||||
borderColor: "#33bbff",
|
||||
color: "#33bbff",
|
||||
fontSize: 13,
|
||||
borderWidth: 1.5,
|
||||
padding: "1.5px 4px"
|
||||
}}
|
||||
></i>
|
||||
</sup>
|
||||
</a>
|
||||
<Tooltip id="sendInOrder-tooltip" className="z-50">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">Send in Order</p>
|
||||
<p>
|
||||
Choose how you want the signing requests to be sent to
|
||||
the document signers:
|
||||
</p>
|
||||
<p className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">Yes:</span>
|
||||
<span>
|
||||
{" "}
|
||||
Selecting this option will send the signing
|
||||
request to the first signer initially. Once the
|
||||
first signer completes their part, the next
|
||||
signer in the sequence will receive the request.
|
||||
This process continues until all signers have
|
||||
signed the document. This method ensures that
|
||||
the document is signed in a specific order.
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">No: </span>
|
||||
<span>
|
||||
Selecting this option will send the signing
|
||||
links to all signers simultaneously. Every
|
||||
signer can sign the document at their
|
||||
convenience, regardless of whether other signers
|
||||
have completed their signatures. This method is
|
||||
faster but does not enforce any signing order
|
||||
among the participants.
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Select the option that best suits the needs of your
|
||||
document processing.
|
||||
</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">Yes</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">No</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
{!isEnableSubscription && (
|
||||
<PremiumAlertHeader
|
||||
message={
|
||||
"Disable Auto reminder is free in beta, this feature will incur a fee later."
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={
|
||||
isSubscribe || !isEnableSubscription
|
||||
? "font-semibold"
|
||||
: "font-semibold text-gray-300"
|
||||
}
|
||||
>
|
||||
Auto reminder{" "}
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</span>
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription
|
||||
? "cursor-pointer "
|
||||
: "pointer-events-none opacity-50"
|
||||
} relative block items-center mb-0`}
|
||||
>
|
||||
<input
|
||||
checked={formData.autoreminder}
|
||||
onChange={handleAutoReminder}
|
||||
type="checkbox"
|
||||
value=""
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-1 peer-focus:ring-black rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-black peer-checked:bg-blue-600 mt-2"></div>
|
||||
</label>
|
||||
</div>
|
||||
{formData?.autoreminder === true && (
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
Remind once in every (Days)
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.remindOnceInEvery}
|
||||
name="remindOnceInEvery"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
onChange={handleStrInput}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button
|
||||
className={`${
|
||||
isSubmit && "cursor-progress"
|
||||
} bg-[#1ab6ce] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 focus:outline-none`}
|
||||
type="submit"
|
||||
disabled={isSubmit}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<div
|
||||
className="cursor-pointer bg-[#188ae2] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 text-center ml-[2px] focus:outline-none"
|
||||
onClick={() => handleReset()}
|
||||
>
|
||||
Reset
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button
|
||||
className={`${
|
||||
isSubmit && "cursor-progress"
|
||||
} bg-[#1ab6ce] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 focus:outline-none`}
|
||||
type="submit"
|
||||
disabled={isSubmit}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<div
|
||||
className="cursor-pointer bg-[#188ae2] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 text-center ml-[2px] focus:outline-none"
|
||||
onClick={() => handleReset()}
|
||||
>
|
||||
Reset
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -144,29 +144,32 @@ function GenerateToken() {
|
||||
: "w-full flex flex-col p-2 text-sm opacity-20 pointer-events-none select-none"
|
||||
}
|
||||
>
|
||||
<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-[70%] flex-col md:flex-row flex items-center gap-5 ">
|
||||
<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="">Api Token:</span>{" "}
|
||||
<span
|
||||
id="token"
|
||||
className=" md:text-end cursor-pointer"
|
||||
onClick={() => copytoclipboard(apiToken)}
|
||||
>
|
||||
{apiToken ? apiToken : "_____"}
|
||||
<span id="token" className="md:text-end py-2 md:py-0">
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
onClick={() => copytoclipboard(apiToken)}
|
||||
>
|
||||
{apiToken ? apiToken : "_____"}
|
||||
</span>
|
||||
<button
|
||||
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-2.5 py-2 focus:outline-none ml-2"
|
||||
onClick={() => copytoclipboard(apiToken)}
|
||||
>
|
||||
<i className="fa-solid fa-copy"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={apiToken ? handleModal : handleSubmit}
|
||||
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-4 py-2 text-xs md:text-base focus:outline-none"
|
||||
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white text-xs md:text-sm lg:text-base px-2 py-2 focus:outline-none"
|
||||
>
|
||||
{apiToken ? "Regenerate Token" : "Generate Token"}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className="flex items-center justify-center ">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -6,9 +6,10 @@ import axios from "axios";
|
||||
import { isEnableSubscription, themeColor } from "../constant/const";
|
||||
import { contractUsers, getAppLogo } from "../constant/Utils";
|
||||
import logo from "../assets/images/logo.png";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
|
||||
function GuestLogin() {
|
||||
const { id, userMail, contactBookId, serverUrl } = useParams();
|
||||
const { id, userMail, contactBookId, base64url } = useParams();
|
||||
let navigate = useNavigate();
|
||||
const [email, setEmail] = useState(userMail);
|
||||
const [OTP, setOTP] = useState("");
|
||||
@@ -16,7 +17,9 @@ function GuestLogin() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [appLogo, setAppLogo] = useState("");
|
||||
|
||||
const [documentId, setDocumentId] = useState(id);
|
||||
const [contactId, setContactId] = useState(contactBookId);
|
||||
const [sendmail, setSendmail] = useState();
|
||||
useEffect(() => {
|
||||
handleServerUrl();
|
||||
|
||||
@@ -36,17 +39,25 @@ function GuestLogin() {
|
||||
setAppLogo(logo);
|
||||
}
|
||||
|
||||
//split url in array from '&'
|
||||
localStorage.clear();
|
||||
const checkSplit = serverUrl.split("&");
|
||||
const server = checkSplit[0];
|
||||
const parseId = checkSplit[1];
|
||||
const appName = checkSplit[2];
|
||||
|
||||
const newServer = server.replaceAll("%2F", "/");
|
||||
const parseId = appInfo.appId;
|
||||
const newServer = `${appInfo.baseUrl}/`;
|
||||
const appName = appInfo.appname;
|
||||
localStorage.setItem("baseUrl", newServer);
|
||||
localStorage.setItem("parseAppId", parseId);
|
||||
localStorage.setItem("_appName", appName);
|
||||
//this condition is used decode base64 to string and get userEmail,documentId, contactBoookId data.
|
||||
if (!id) {
|
||||
//`atob` function is used to decode base64
|
||||
const decodebase64 = atob(base64url);
|
||||
//split url in array from '/'
|
||||
const checkSplit = decodebase64.split("/");
|
||||
setDocumentId(checkSplit[0]);
|
||||
setEmail(checkSplit[1]);
|
||||
setContactId(checkSplit[2]);
|
||||
setSendmail(checkSplit[3]);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
@@ -74,7 +85,7 @@ function GuestLogin() {
|
||||
};
|
||||
let body = {
|
||||
email: email.toString(),
|
||||
docId: id
|
||||
docId: documentId
|
||||
};
|
||||
let Otp = await axios.post(url, body, { headers: headers });
|
||||
|
||||
@@ -137,7 +148,13 @@ function GuestLogin() {
|
||||
//save isGuestSigner true in local to handle login flow header in mobile view
|
||||
localStorage.setItem("isGuestSigner", true);
|
||||
setLoading(false);
|
||||
navigate(`/load/recipientSignPdf/${id}/${contactBookId}`);
|
||||
if (sendmail === "false") {
|
||||
navigate(
|
||||
`/load/recipientSignPdf/${documentId}/${contactId}?sendmail=${sendmail}`
|
||||
);
|
||||
} else {
|
||||
navigate(`/load/recipientSignPdf/${documentId}/${contactId}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("err ", error);
|
||||
|
||||
@@ -422,7 +422,7 @@ function Login() {
|
||||
alert("Somenthing went wrong, please try again later!");
|
||||
}
|
||||
};
|
||||
const thirdpartyLoginfn = async (sessionToken, billingDate) => {
|
||||
const thirdpartyLoginfn = async (sessionToken) => {
|
||||
const baseUrl = localStorage.getItem("baseUrl");
|
||||
const parseAppId = localStorage.getItem("parseAppId");
|
||||
const res = await axios.get(baseUrl + "users/me", {
|
||||
@@ -521,7 +521,7 @@ function Login() {
|
||||
await Parse.Cloud.run("getUserDetails", {
|
||||
email: currentUser.get("email")
|
||||
}).then(
|
||||
(result) => {
|
||||
async (result) => {
|
||||
let tenentInfo = [];
|
||||
const results = [result];
|
||||
if (results) {
|
||||
@@ -571,7 +571,7 @@ function Login() {
|
||||
setState({ ...state, loading: false });
|
||||
navigate("/");
|
||||
} else {
|
||||
extendedInfo.forEach((x) => {
|
||||
extendedInfo.forEach(async (x) => {
|
||||
if (x.TenantId) {
|
||||
let obj = {
|
||||
tenentId: x.TenantId.objectId,
|
||||
@@ -608,8 +608,13 @@ function Login() {
|
||||
setThirdpartyLoader(false);
|
||||
setState({ ...state, loading: false });
|
||||
if (isEnableSubscription) {
|
||||
if (billingDate) {
|
||||
if (billingDate > new Date()) {
|
||||
const res = await fetchSubscription();
|
||||
const freeplan = res.plan;
|
||||
const billingDate = res.billingDate;
|
||||
if (freeplan === "freeplan") {
|
||||
navigate(redirectUrl);
|
||||
} else if (billingDate) {
|
||||
if (new Date(billingDate) > new Date()) {
|
||||
localStorage.removeItem("userDetails");
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
@@ -644,8 +649,13 @@ function Login() {
|
||||
setState({ ...state, loading: false });
|
||||
setThirdpartyLoader(false);
|
||||
if (isEnableSubscription) {
|
||||
if (billingDate) {
|
||||
if (billingDate > new Date()) {
|
||||
const res = await fetchSubscription();
|
||||
const freeplan = res.plan;
|
||||
const billingDate = res.billingDate;
|
||||
if (freeplan === "freeplan") {
|
||||
navigate(redirectUrl);
|
||||
} else if (billingDate) {
|
||||
if (new Date(billingDate) > new Date()) {
|
||||
localStorage.removeItem("userDetails");
|
||||
// Redirect to the appropriate URL after successful login
|
||||
navigate(redirectUrl);
|
||||
@@ -1126,34 +1136,16 @@ function Login() {
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative mt-1">
|
||||
<NavLink
|
||||
to="/forgetpassword"
|
||||
className="text-[13px] text-[#002864] hover:underline underline-offset-1 focus:outline-none cursor-pointer ml-1"
|
||||
>
|
||||
Forgot Password?
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className="flex flex-row justify-between items-center ml-4 py-2">
|
||||
<div>
|
||||
<label
|
||||
className="form-check-label inline-block cursor-pointer"
|
||||
htmlFor="rememberpassword"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input mr-1"
|
||||
value=""
|
||||
id="rememberpassword"
|
||||
/>
|
||||
<span className="text-[13px]">
|
||||
Remember Password
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<NavLink
|
||||
to="/forgetpassword"
|
||||
className="text-[13px] underline focus:outline-none focus:ring-2 focus:ring-blue-600"
|
||||
>
|
||||
Forgot Password ?
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row justify-between items-stretch gap-8 text-center text-xs font-bold mt-2">
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
@@ -11,8 +11,8 @@ import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
import Tour from "reactour";
|
||||
import axios from "axios";
|
||||
|
||||
const DriveBody = React.lazy(
|
||||
() => import("../components/opensigndrive/DriveBody")
|
||||
const DriveBody = React.lazy(() =>
|
||||
import("../components/opensigndrive/DriveBody")
|
||||
);
|
||||
const Loader = () => {
|
||||
return (
|
||||
@@ -64,6 +64,7 @@ function Opensigndrive() {
|
||||
const sortingValue = ["Name", "Date"];
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
const [tourData, setTourData] = useState();
|
||||
const [showTourFirstTIme, setShowTourFirstTime] = useState(true);
|
||||
const orderName = {
|
||||
Ascending: "Ascending",
|
||||
Descending: "Descending",
|
||||
@@ -134,7 +135,9 @@ function Opensigndrive() {
|
||||
//function for get all pdf document list
|
||||
const getPdfDocumentList = async (disbaleLoading) => {
|
||||
setLoading(true);
|
||||
checkTourStatus();
|
||||
if (showTourFirstTIme) {
|
||||
checkTourStatus();
|
||||
}
|
||||
if (!disbaleLoading) {
|
||||
setIsLoading({ isLoad: true, message: "This might take some time" });
|
||||
}
|
||||
@@ -250,17 +253,23 @@ function Opensigndrive() {
|
||||
}, [loading, sortingOrder, selectedSort]); // Add/remove scroll event listener when loading changes
|
||||
|
||||
//function for handle folder name path
|
||||
const handleRoute = (index) => {
|
||||
setPdfData([]);
|
||||
const handleRoute = (index, folderData) => {
|
||||
setSkip(0);
|
||||
// after onclick on route filter route from that index
|
||||
const updateFolderName = folderName.filter((x, i) => {
|
||||
if (i <= index) {
|
||||
return x;
|
||||
}
|
||||
});
|
||||
setFolderName(updateFolderName);
|
||||
//get route details after onclick path of folder name
|
||||
const getCurrentId = folderData[index];
|
||||
const getLastId = updateFolderName[updateFolderName.length - 1];
|
||||
setDocId(getLastId.objectId);
|
||||
//below condition is used to check if user click on same route which already open then don't change any thing
|
||||
if (docId !== getCurrentId.objectId) {
|
||||
setPdfData([]);
|
||||
setDocId(getLastId.objectId);
|
||||
}
|
||||
};
|
||||
|
||||
//function for add new folder name
|
||||
@@ -291,6 +300,7 @@ function Opensigndrive() {
|
||||
const exsitQuery = new Parse.Query(foldercls);
|
||||
exsitQuery.equalTo("Name", newFolderName);
|
||||
exsitQuery.equalTo("Type", "Folder");
|
||||
exsitQuery.notEqualTo("IsArchive", true);
|
||||
if (parentId) {
|
||||
exsitQuery.equalTo("Folder", folderPtr);
|
||||
}
|
||||
@@ -439,7 +449,7 @@ function Opensigndrive() {
|
||||
return (
|
||||
<React.Fragment key={id}>
|
||||
<span
|
||||
onClick={() => handleRoute(id)}
|
||||
onClick={() => handleRoute(id, folderData)}
|
||||
style={{
|
||||
color: "#a64b4e",
|
||||
fontWeight: "400",
|
||||
@@ -474,6 +484,7 @@ function Opensigndrive() {
|
||||
|
||||
const closeTour = async () => {
|
||||
setIsTour(false);
|
||||
setShowTourFirstTime(false);
|
||||
if (isDontShow) {
|
||||
const serverUrl = localStorage.getItem("baseUrl");
|
||||
const appId = localStorage.getItem("parseAppId");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -123,6 +123,7 @@ function PlaceHolderSign() {
|
||||
const [requestSubject, setRequestSubject] = useState("");
|
||||
const [requestBody, setRequestBody] = useState("");
|
||||
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
|
||||
const [activeMailAdapter, setActiveMailAdapter] = useState("");
|
||||
const [isAlreadyPlace, setIsAlreadyPlace] = useState({
|
||||
status: false,
|
||||
message: ""
|
||||
@@ -172,15 +173,6 @@ function PlaceHolderSign() {
|
||||
: rowLevel?.objectId && rowLevel.objectId;
|
||||
|
||||
const documentId = docId ? docId : objectId && objectId;
|
||||
const senderUser =
|
||||
localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
) &&
|
||||
localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
);
|
||||
const jsonSender = JSON.parse(senderUser);
|
||||
|
||||
useEffect(() => {
|
||||
if (documentId) {
|
||||
getDocumentDetails();
|
||||
@@ -209,16 +201,16 @@ function PlaceHolderSign() {
|
||||
|
||||
setDefaultBody(defaultRequestBody);
|
||||
setDefaultSubject(
|
||||
`{{sender_name}} has requested you to sign {{document_title}}`
|
||||
`{{sender_name}} has requested you to sign "{{document_title}}"`
|
||||
);
|
||||
} else {
|
||||
setRequestBody(defaultRequestBody);
|
||||
setRequestSubject(
|
||||
`{{sender_name}} has requested you to sign {{document_title}}`
|
||||
`{{sender_name}} has requested you to sign "{{document_title}}"`
|
||||
);
|
||||
setDefaultBody(defaultRequestBody);
|
||||
setDefaultSubject(
|
||||
`{{sender_name}}has requested you to sign {{document_title}}`
|
||||
`{{sender_name}}has requested you to sign "{{document_title}}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -405,11 +397,22 @@ function PlaceHolderSign() {
|
||||
};
|
||||
setIsLoading(loadObj);
|
||||
}
|
||||
const res = await contractUsers(jsonSender.email);
|
||||
if (res[0] && res.length) {
|
||||
const currentUserEmail = documentData[0]?.ExtUserPtr?.Email;
|
||||
const res = await contractUsers(currentUserEmail);
|
||||
if (res === "Error: Something went wrong!") {
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setHandleError("Error: Something went wrong!");
|
||||
setIsLoading(loadObj);
|
||||
} else if (res.length && res[0]?.objectId) {
|
||||
setActiveMailAdapter(res[0]?.active_mail_adapter);
|
||||
setSignerUserId(res[0].objectId);
|
||||
const tourstatus = res[0].TourStatus && res[0].TourStatus;
|
||||
if (tourstatus && tourstatus.length > 0) {
|
||||
const alreadyPlaceholder = documentData[0]?.SignedUrl;
|
||||
if (alreadyPlaceholder) {
|
||||
setCheckTourStatus(true);
|
||||
} else if (tourstatus && tourstatus.length > 0) {
|
||||
setTourStatus(tourstatus);
|
||||
const checkTourRecipients = tourstatus.filter(
|
||||
(data) => data.placeholder
|
||||
@@ -422,12 +425,6 @@ function PlaceHolderSign() {
|
||||
isLoad: false
|
||||
};
|
||||
setIsLoading(loadObj);
|
||||
} else if (res === "Error: Something went wrong!") {
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setHandleError("Error: Something went wrong!");
|
||||
setIsLoading(loadObj);
|
||||
} else if (res.length === 0) {
|
||||
setHandleError("No Data Found!");
|
||||
const loadObj = {
|
||||
@@ -836,11 +833,11 @@ function PlaceHolderSign() {
|
||||
return pdfDetails[0].URL;
|
||||
}
|
||||
};
|
||||
|
||||
const alertSendEmail = async () => {
|
||||
const filterPrefill = signerPos?.filter((data) => data.Role !== "prefill");
|
||||
const getPrefill = signerPos?.filter((data) => data.Role === "prefill");
|
||||
let isLabel = false;
|
||||
//condition is used to check text widget data is empty or have response
|
||||
if (getPrefill && getPrefill.length > 0) {
|
||||
const prefillPlaceholder = getPrefill[0].placeHolder;
|
||||
if (prefillPlaceholder) {
|
||||
@@ -851,6 +848,29 @@ function PlaceHolderSign() {
|
||||
});
|
||||
}
|
||||
}
|
||||
let isSignatureExist = true; // variable is used to check a signature widget exit or not then execute other code
|
||||
//for loop is used to check signature widget exist or not
|
||||
for (let item of filterPrefill) {
|
||||
let signatureExist = false; // Reset for each iteration
|
||||
for (let x of item.placeHolder) {
|
||||
if (!signatureExist) {
|
||||
const typeExist = x.pos.some((data) => data?.type);
|
||||
if (typeExist) {
|
||||
signatureExist = x.pos.some((data) => data?.type === "signature");
|
||||
} else {
|
||||
signatureExist = x.pos.some((data) => !data.isStamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!signatureExist) {
|
||||
isSignatureExist = false;
|
||||
const alert = {
|
||||
mssg: "sure",
|
||||
alert: true
|
||||
};
|
||||
setIsSendAlert(alert);
|
||||
}
|
||||
}
|
||||
|
||||
if (getPrefill && isLabel) {
|
||||
const alert = {
|
||||
@@ -858,23 +878,26 @@ function PlaceHolderSign() {
|
||||
alert: true
|
||||
};
|
||||
setIsSendAlert(alert);
|
||||
} else if (filterPrefill.length === signersdata.length) {
|
||||
const IsSignerNotExist = filterPrefill?.filter((x) => !x.signerObjId);
|
||||
if (IsSignerNotExist && IsSignerNotExist?.length > 0) {
|
||||
setSignerExistModal(true);
|
||||
setSelectWidgetId(IsSignerNotExist[0]?.placeHolder?.[0]?.pos?.[0]?.key);
|
||||
} else if (isSignatureExist) {
|
||||
if (filterPrefill.length === signersdata.length) {
|
||||
const IsSignerNotExist = filterPrefill?.filter((x) => !x.signerObjId);
|
||||
if (IsSignerNotExist && IsSignerNotExist?.length > 0) {
|
||||
setSignerExistModal(true);
|
||||
setSelectWidgetId(
|
||||
IsSignerNotExist[0]?.placeHolder?.[0]?.pos?.[0]?.key
|
||||
);
|
||||
} else {
|
||||
saveDocumentDetails();
|
||||
}
|
||||
} else {
|
||||
saveDocumentDetails();
|
||||
const alert = {
|
||||
mssg: "sure",
|
||||
alert: true
|
||||
};
|
||||
setIsSendAlert(alert);
|
||||
}
|
||||
} else {
|
||||
const alert = {
|
||||
mssg: "sure",
|
||||
alert: true
|
||||
};
|
||||
setIsSendAlert(alert);
|
||||
}
|
||||
};
|
||||
|
||||
//function to use save placeholder details in contracts_document
|
||||
const saveDocumentDetails = async () => {
|
||||
setIsUiLoading(true);
|
||||
@@ -973,15 +996,14 @@ function PlaceHolderSign() {
|
||||
const shareLinkList = [];
|
||||
let signerMail = signersdata;
|
||||
for (let i = 0; i < signerMail.length; i++) {
|
||||
const serverUrl = localStorage.getItem("baseUrl");
|
||||
const newServer = serverUrl.replaceAll("/", "%2F");
|
||||
const objectId = signerMail[i].objectId;
|
||||
const serverParams = `${newServer}&${localStorage.getItem(
|
||||
"parseAppId"
|
||||
)}&${localStorage.getItem("_appName")}`;
|
||||
|
||||
const hostUrl = window.location.origin;
|
||||
let signPdf = `${hostUrl}/login/${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}/${serverParams}`;
|
||||
const sendMail = false;
|
||||
//encode this url value `${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}` to base64 using `btoa` function
|
||||
const encodeBase64 = btoa(
|
||||
`${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}/${sendMail}`
|
||||
);
|
||||
let signPdf = `${hostUrl}/login/${encodeBase64}`;
|
||||
shareLinkList.push({ signerEmail: signerMail[i].Email, url: signPdf });
|
||||
}
|
||||
return shareLinkList.map((data, ind) => {
|
||||
@@ -1055,15 +1077,13 @@ function PlaceHolderSign() {
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const serverUrl = localStorage.getItem("baseUrl");
|
||||
const newServer = serverUrl.replaceAll("/", "%2F");
|
||||
const objectId = signerMail[i].objectId;
|
||||
const serverParams = `${newServer}&${localStorage.getItem(
|
||||
"parseAppId"
|
||||
)}&${localStorage.getItem("_appName")}`;
|
||||
|
||||
const hostUrl = window.location.origin;
|
||||
let signPdf = `${hostUrl}/login/${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}/${serverParams}`;
|
||||
//encode this url value `${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}` to base64 using `btoa` function
|
||||
const encodeBase64 = btoa(
|
||||
`${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}`
|
||||
);
|
||||
let signPdf = `${hostUrl}/login/${encodeBase64}`;
|
||||
const openSignUrl = "https://www.opensignlabs.com/";
|
||||
const orgName = pdfDetails[0]?.ExtUserPtr.Company
|
||||
? pdfDetails[0].ExtUserPtr.Company
|
||||
@@ -1104,11 +1124,12 @@ function PlaceHolderSign() {
|
||||
}
|
||||
|
||||
let params = {
|
||||
mailProvider: activeMailAdapter,
|
||||
extUserId: extUserId,
|
||||
recipient: signerMail[i].Email,
|
||||
subject: isCustomize
|
||||
? replaceVar?.subject
|
||||
: `${senderName} has requested you to sign ${documentName}`,
|
||||
: `${senderName} has requested you to sign "${documentName}"`,
|
||||
from: senderEmail,
|
||||
html: isCustomize
|
||||
? replaceVar?.body
|
||||
@@ -1142,40 +1163,45 @@ function PlaceHolderSign() {
|
||||
}
|
||||
if (sendMail.data.result.status === "success") {
|
||||
setMailStatus("success");
|
||||
|
||||
if (
|
||||
requestBody &&
|
||||
requestSubject &&
|
||||
isCustomize &&
|
||||
(isSubscribe || !isEnableSubscription)
|
||||
) {
|
||||
try {
|
||||
const data = {
|
||||
try {
|
||||
let data;
|
||||
if (
|
||||
requestBody &&
|
||||
requestSubject &&
|
||||
isCustomize &&
|
||||
(isSubscribe || !isEnableSubscription)
|
||||
) {
|
||||
data = {
|
||||
RequestBody: htmlReqBody,
|
||||
RequestSubject: requestSubject
|
||||
RequestSubject: requestSubject,
|
||||
SendMail: true
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
SendMail: true
|
||||
};
|
||||
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
|
||||
"_appName"
|
||||
)}_Document/${documentId}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
console.log("axois err ", err);
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
}
|
||||
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
|
||||
"_appName"
|
||||
)}_Document/${documentId}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
console.log("axois err ", err);
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
}
|
||||
|
||||
setIsSend(true);
|
||||
@@ -1696,8 +1722,8 @@ function PlaceHolderSign() {
|
||||
>
|
||||
{isSendAlert.mssg === "sure" ? (
|
||||
<span>
|
||||
Please add at least one signature field for all
|
||||
recipients.
|
||||
Please ensure there's at least one signature widget
|
||||
added for all recipients.
|
||||
</span>
|
||||
) : isSendAlert.mssg === textWidget ? (
|
||||
<p>Please confirm that you have filled the text field.</p>
|
||||
@@ -1778,10 +1804,7 @@ function PlaceHolderSign() {
|
||||
|
||||
{!isSubscribe && isEnableSubscription && (
|
||||
<div className="mt-2">
|
||||
<Upgrade
|
||||
message="Upgrade to customize Email"
|
||||
newWindow={true}
|
||||
/>
|
||||
<Upgrade message="Upgrade to customize Email" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useDispatch } from "react-redux";
|
||||
import { fetchAppInfo } from "../redux/reducers/infoReducer";
|
||||
import { showTenant } from "../redux/reducers/ShowTenant";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import { getAppLogo } from "../constant/Utils";
|
||||
import { getAppLogo, openInNewTab } from "../constant/Utils";
|
||||
const Signup = () => {
|
||||
const { width } = useWindowSize();
|
||||
const navigate = useNavigate();
|
||||
@@ -33,7 +33,7 @@ const Signup = () => {
|
||||
const [lengthValid, setLengthValid] = useState(false);
|
||||
const [caseDigitValid, setCaseDigitValid] = useState(false);
|
||||
const [specialCharValid, setSpecialCharValid] = useState(false);
|
||||
|
||||
const [isAuthorize, setIsAuthorize] = useState(false);
|
||||
const togglePasswordVisibility = () => setShowPassword(!showPassword);
|
||||
|
||||
const clearStorage = async () => {
|
||||
@@ -658,10 +658,34 @@ const Signup = () => {
|
||||
{specialCharValid ? "✓" : "✗"} Password should
|
||||
contain special character
|
||||
</p>
|
||||
{/* </>
|
||||
)} */}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-2.5 ml-1 flex flex-row items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="termsandcondition"
|
||||
checked={isAuthorize}
|
||||
onChange={(e) => setIsAuthorize(e.target.checked)}
|
||||
required
|
||||
/>
|
||||
<label
|
||||
className="text-xs cursor-pointer ml-1 mb-0"
|
||||
htmlFor="termsandcondition"
|
||||
>
|
||||
I agree to the
|
||||
</label>
|
||||
<span
|
||||
className="underline cursor-pointer ml-1"
|
||||
onClick={() =>
|
||||
openInNewTab(
|
||||
"https://www.opensignlabs.com/terms-and-conditions"
|
||||
)
|
||||
}
|
||||
>
|
||||
Terms of Service
|
||||
</span>
|
||||
<span>.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row justify-between items-stretch gap-8 text-center text-xs font-bold mt-2">
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import "../styles/signature.css";
|
||||
import Parse from "parse";
|
||||
import { isEnableSubscription, themeColor } from "../constant/const";
|
||||
import Confetti from "react-confetti";
|
||||
import axios from "axios";
|
||||
import Loader from "../primitives/LoaderWithMsg";
|
||||
import loader from "../assets/images/loader2.gif";
|
||||
@@ -29,7 +31,10 @@ import {
|
||||
textWidget,
|
||||
getTenantDetails,
|
||||
checkIsSubscribed,
|
||||
convertPdfArrayBuffer
|
||||
convertPdfArrayBuffer,
|
||||
fetchImageBase64,
|
||||
changeImageWH,
|
||||
handleSendOTP
|
||||
} from "../constant/Utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Tour from "reactour";
|
||||
@@ -42,6 +47,7 @@ import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
import Title from "../components/Title";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
|
||||
import VerifyEmail from "../components/pdf/VerifyEmail";
|
||||
|
||||
//For signYourself inProgress section signer can add sign and complete doc sign.
|
||||
function SignYourSelf() {
|
||||
@@ -65,7 +71,6 @@ function SignYourSelf() {
|
||||
const [dragKey, setDragKey] = useState();
|
||||
const [signKey, setSignKey] = useState();
|
||||
const [imgWH, setImgWH] = useState({});
|
||||
const [isCeleb, setIsCeleb] = useState(false);
|
||||
const [pdfNewWidth, setPdfNewWidth] = useState();
|
||||
const [pdfOriginalWidth, setPdfOriginalWidth] = useState();
|
||||
const [successEmail, setSuccessEmail] = useState(false);
|
||||
@@ -89,6 +94,7 @@ function SignYourSelf() {
|
||||
const [containerWH, setContainerWH] = useState({});
|
||||
const [isPageCopy, setIsPageCopy] = useState(false);
|
||||
const [selectWidgetId, setSelectWidgetId] = useState("");
|
||||
const [otpLoader, setOtpLoader] = useState(false);
|
||||
const [showAlreadySignDoc, setShowAlreadySignDoc] = useState({
|
||||
status: false
|
||||
});
|
||||
@@ -103,7 +109,12 @@ function SignYourSelf() {
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
const [extUserId, setExtUserId] = useState("");
|
||||
const [isCompleted, setIsCompleted] = useState(false);
|
||||
const [isCelebration, setIsCelebration] = useState(false);
|
||||
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
|
||||
const [activeMailAdapter, setActiveMailAdapter] = useState("");
|
||||
const [isEmailVerified, setIsEmailVerified] = useState(true);
|
||||
const [isVerifyModal, setIsVerifyModal] = useState(false);
|
||||
const [otp, setOtp] = useState("");
|
||||
const divRef = useRef(null);
|
||||
const nodeRef = useRef(null);
|
||||
const [, drop] = useDrop({
|
||||
@@ -193,13 +204,18 @@ function SignYourSelf() {
|
||||
setPdfDetails(documentData);
|
||||
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
|
||||
const url = documentData[0] && documentData[0]?.URL;
|
||||
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
|
||||
const arrayBuffer = await convertPdfArrayBuffer(url);
|
||||
if (arrayBuffer === "Error") {
|
||||
setHandleError("Error: Something went wrong!");
|
||||
if (url) {
|
||||
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
|
||||
const arrayBuffer = await convertPdfArrayBuffer(url);
|
||||
if (arrayBuffer === "Error") {
|
||||
setHandleError("Error: Something went wrong!");
|
||||
} else {
|
||||
setPdfArrayBuffer(arrayBuffer);
|
||||
}
|
||||
} else {
|
||||
setPdfArrayBuffer(arrayBuffer);
|
||||
setHandleError("Error: Something went wrong!");
|
||||
}
|
||||
|
||||
isCompleted = documentData[0].IsCompleted && documentData[0].IsCompleted;
|
||||
if (isCompleted) {
|
||||
setIsCompleted(true);
|
||||
@@ -214,8 +230,12 @@ function SignYourSelf() {
|
||||
setIsUiLoading(false);
|
||||
setIsSignPad(false);
|
||||
setIsEmail(true);
|
||||
setIsCelebration(true);
|
||||
setXyPostion([]);
|
||||
setSignBtnPosition([]);
|
||||
setTimeout(() => {
|
||||
setIsCelebration(false);
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
@@ -266,7 +286,14 @@ function SignYourSelf() {
|
||||
setIsLoading(loadObj);
|
||||
});
|
||||
const contractUsersRes = await contractUsers(jsonSender.email);
|
||||
if (contractUsersRes[0] && contractUsersRes.length > 0) {
|
||||
if (contractUsersRes === "Error: Something went wrong!") {
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setHandleError("Error: Something went wrong!");
|
||||
setIsLoading(loadObj);
|
||||
} else if (contractUsersRes[0] && contractUsersRes.length > 0) {
|
||||
setActiveMailAdapter(contractUsersRes[0]?.active_mail_adapter);
|
||||
setContractName("_Users");
|
||||
setSignerUserId(contractUsersRes[0].objectId);
|
||||
const tourstatuss =
|
||||
@@ -286,12 +313,6 @@ function SignYourSelf() {
|
||||
isLoad: false
|
||||
};
|
||||
setIsLoading(loadObj);
|
||||
} else if (contractUsersRes === "Error: Something went wrong!") {
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setHandleError("Error: Something went wrong!");
|
||||
setIsLoading(loadObj);
|
||||
} else if (contractUsersRes.length === 0) {
|
||||
const contractContactBook = await contactBook(jsonSender.objectId);
|
||||
if (contractContactBook && contractContactBook.length > 0) {
|
||||
@@ -525,76 +546,163 @@ function SignYourSelf() {
|
||||
setSignKey(key);
|
||||
};
|
||||
|
||||
//`handleResend` function is used to resend otp for email verification
|
||||
const handleResend = async (e) => {
|
||||
e.preventDefault();
|
||||
setOtpLoader(true);
|
||||
await handleSendOTP(Parse.User.current().getEmail());
|
||||
setOtpLoader(false);
|
||||
alert("OTP sent on you email");
|
||||
};
|
||||
//`handleVerifyEmail` function is used to verify email with otp
|
||||
const handleVerifyEmail = async (e) => {
|
||||
e.preventDefault();
|
||||
setOtpLoader(true);
|
||||
try {
|
||||
const resEmail = await Parse.Cloud.run("verifyemail", {
|
||||
otp: otp,
|
||||
email: Parse.User.current().getEmail()
|
||||
});
|
||||
if (resEmail?.message === "Email is verified.") {
|
||||
setIsEmailVerified(true);
|
||||
} else if (resEmail?.message === "Email is already verified.") {
|
||||
setIsEmailVerified(true);
|
||||
}
|
||||
setOtp("");
|
||||
alert(resEmail.message);
|
||||
setIsVerifyModal(false);
|
||||
// handleRecipientSign();
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
} finally {
|
||||
setOtpLoader(false);
|
||||
}
|
||||
};
|
||||
//`handleVerifyBtn` function is used to send otp on user mail
|
||||
const handleVerifyBtn = async () => {
|
||||
setIsVerifyModal(true);
|
||||
await handleSendOTP(Parse.User.current().getEmail());
|
||||
};
|
||||
//function for send placeholder's co-ordinate(x,y) position embed signature url or stamp url
|
||||
async function embedWidgetsData() {
|
||||
let showAlert = false;
|
||||
|
||||
for (let i = 0; i < xyPostion?.length; i++) {
|
||||
const requiredWidgets = xyPostion[i].pos.filter(
|
||||
(position) => position.type !== "checkbox"
|
||||
);
|
||||
if (requiredWidgets && requiredWidgets?.length > 0) {
|
||||
let checkSigned;
|
||||
for (let i = 0; i < requiredWidgets?.length; i++) {
|
||||
checkSigned = requiredWidgets[i]?.options?.response;
|
||||
if (!checkSigned) {
|
||||
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
|
||||
let checkDefaultSigned = requiredWidgets[i]?.options?.defaultValue;
|
||||
if (!checkSignUrl) {
|
||||
if (!checkDefaultSigned) {
|
||||
if (!showAlert) {
|
||||
showAlert = true;
|
||||
//check current user email is verified or not
|
||||
const currentUser = JSON.parse(JSON.stringify(Parse.User.current()));
|
||||
let isEmailVerified;
|
||||
isEmailVerified = currentUser?.emailVerified;
|
||||
if (isEmailVerified) {
|
||||
setIsEmailVerified(isEmailVerified);
|
||||
} else {
|
||||
try {
|
||||
const userQuery = new Parse.Query(Parse.User);
|
||||
const user = await userQuery.get(currentUser.objectId, {
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
});
|
||||
if (user) {
|
||||
isEmailVerified = user?.get("emailVerified");
|
||||
setIsEmailVerified(isEmailVerified);
|
||||
}
|
||||
} catch (e) {
|
||||
setHandleError("Error: Something went wrong!");
|
||||
}
|
||||
}
|
||||
if (isEmailVerified) {
|
||||
let showAlert = false,
|
||||
isSignatureExist = false;
|
||||
try {
|
||||
for (let i = 0; i < xyPostion?.length; i++) {
|
||||
const requiredWidgets = xyPostion[i].pos.filter(
|
||||
(position) => position.type !== "checkbox"
|
||||
);
|
||||
if (requiredWidgets && requiredWidgets?.length > 0) {
|
||||
let checkSigned;
|
||||
for (let i = 0; i < requiredWidgets?.length; i++) {
|
||||
checkSigned = requiredWidgets[i]?.options?.response;
|
||||
if (!checkSigned) {
|
||||
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
|
||||
let checkDefaultSigned =
|
||||
requiredWidgets[i]?.options?.defaultValue;
|
||||
if (!checkSignUrl) {
|
||||
if (!checkDefaultSigned) {
|
||||
if (!showAlert) {
|
||||
showAlert = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//condition to check exist signature widget or not
|
||||
if (!isSignatureExist) {
|
||||
isSignatureExist = xyPostion[i].pos.some(
|
||||
(data) => data?.type === "signature"
|
||||
);
|
||||
}
|
||||
}
|
||||
if (xyPostion.length === 0 || !isSignatureExist) {
|
||||
setIsAlert({
|
||||
header: "Fields required",
|
||||
isShow: true,
|
||||
alertMessage:
|
||||
"Please ensure there's at least one signature widget added"
|
||||
});
|
||||
return;
|
||||
} else if (showAlert) {
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage:
|
||||
"Please ensure all field is accurately filled and meets all requirements."
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
setIsUiLoading(true);
|
||||
const existingPdfBytes = pdfArrayBuffer;
|
||||
// Load a PDFDocument from the existing PDF bytes
|
||||
try {
|
||||
const pdfDoc = await PDFDocument.load(existingPdfBytes);
|
||||
const isSignYourSelfFlow = true;
|
||||
const extUserPtr = pdfDetails[0].ExtUserPtr;
|
||||
const HeaderDocId = extUserPtr?.HeaderDocId;
|
||||
//embed document's object id to all pages in pdf document
|
||||
if (!HeaderDocId) {
|
||||
await embedDocId(pdfDoc, documentId, allPages);
|
||||
}
|
||||
//embed multi signature in pdf
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
xyPostion,
|
||||
pdfDoc,
|
||||
pdfOriginalWidth,
|
||||
isSignYourSelfFlow,
|
||||
containerWH
|
||||
);
|
||||
// console.log("pdf", pdfBytes);
|
||||
//function for call to embed signature in pdf and get digital signature pdf
|
||||
await signPdfFun(pdfBytes, documentId);
|
||||
} catch (err) {
|
||||
setIsUiLoading(false);
|
||||
if (err && err.message.includes("is encrypted.")) {
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: `Currently encrypted pdf files are not supported.`
|
||||
});
|
||||
} else {
|
||||
console.log("err in signing", err);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: `Something went wrong.`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in embedselfsign ", err);
|
||||
setIsUiLoading(false);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "something went wrong, please try again later."
|
||||
});
|
||||
}
|
||||
}
|
||||
if (xyPostion.length === 0) {
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "Please complete your signature!"
|
||||
});
|
||||
return;
|
||||
} else if (showAlert) {
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "Please complete your signature!"
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
setIsCeleb(true);
|
||||
setTimeout(() => {
|
||||
setIsCeleb(false);
|
||||
}, 3000);
|
||||
setIsUiLoading(true);
|
||||
const existingPdfBytes = pdfArrayBuffer;
|
||||
// Load a PDFDocument from the existing PDF bytes
|
||||
const pdfDoc = await PDFDocument.load(existingPdfBytes, {
|
||||
ignoreEncryption: true
|
||||
});
|
||||
const isSignYourSelfFlow = true;
|
||||
const extUserPtr = pdfDetails[0].ExtUserPtr;
|
||||
const HeaderDocId = extUserPtr?.HeaderDocId;
|
||||
//embed document's object id to all pages in pdf document
|
||||
if (!HeaderDocId) {
|
||||
await embedDocId(pdfDoc, documentId, allPages);
|
||||
}
|
||||
//embed multi signature in pdf
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
xyPostion,
|
||||
pdfDoc,
|
||||
pdfOriginalWidth,
|
||||
isSignYourSelfFlow,
|
||||
containerWH
|
||||
);
|
||||
// console.log("pdf", pdfBytes);
|
||||
//function for call to embed signature in pdf and get digital signature pdf
|
||||
await signPdfFun(pdfBytes, documentId);
|
||||
}
|
||||
}
|
||||
// console.log("signyourself", xyPostion);
|
||||
//function for get digital signature
|
||||
const signPdfFun = async (base64Url, documentId) => {
|
||||
let isCustomCompletionMail = false;
|
||||
@@ -611,11 +719,40 @@ function SignYourSelf() {
|
||||
isCustomCompletionMail = true;
|
||||
}
|
||||
}
|
||||
// below for loop is used to get first signature of user to send if to signpdf
|
||||
// for adding it in completion certificate
|
||||
let getSignature;
|
||||
for (let item of xyPostion) {
|
||||
if (!getSignature) {
|
||||
const typeExist = item.pos.some((data) => data?.type);
|
||||
if (typeExist) {
|
||||
getSignature = item.pos.find((data) => data?.type === "signature");
|
||||
} else {
|
||||
getSignature = item.pos.find((data) => !data.isStamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
let base64Sign = getSignature.SignUrl;
|
||||
//check https type signature (default signature exist) then convert in base64
|
||||
const isUrl = base64Sign.includes("https");
|
||||
if (isUrl) {
|
||||
try {
|
||||
base64Sign = await fetchImageBase64(base64Sign);
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
}
|
||||
}
|
||||
//change image width and height to 104/44 in png base64
|
||||
const getNewse64 = await changeImageWH(base64Sign);
|
||||
//remove suffiix of base64
|
||||
const suffixbase64 = getNewse64 && getNewse64.split(",").pop();
|
||||
|
||||
let singleSign = {
|
||||
pdfFile: base64Url,
|
||||
docId: documentId,
|
||||
isCustomCompletionMail: isCustomCompletionMail
|
||||
isCustomCompletionMail: isCustomCompletionMail,
|
||||
mailProvider: activeMailAdapter,
|
||||
signature: suffixbase64
|
||||
};
|
||||
|
||||
await axios
|
||||
@@ -623,7 +760,8 @@ function SignYourSelf() {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
// sessionToken: localStorage.getItem("accesstoken")
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
})
|
||||
.then((Listdata) => {
|
||||
@@ -985,8 +1123,25 @@ function SignYourSelf() {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{isCelebration && (
|
||||
<div style={{ position: "relative", zIndex: "999" }}>
|
||||
<Confetti width={window.innerWidth} height={window.innerHeight} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="signatureContainer" ref={divRef}>
|
||||
{!isEmailVerified && (
|
||||
<VerifyEmail
|
||||
isVerifyModal={isVerifyModal}
|
||||
setIsVerifyModal={setIsVerifyModal}
|
||||
handleVerifyEmail={handleVerifyEmail}
|
||||
setOtp={setOtp}
|
||||
otp={otp}
|
||||
otpLoader={otpLoader}
|
||||
handleVerifyBtn={handleVerifyBtn}
|
||||
handleResend={handleResend}
|
||||
/>
|
||||
)}
|
||||
{/* this component used for UI interaction and show their functionality */}
|
||||
{pdfLoadFail && !checkTourStatus && (
|
||||
<Tour
|
||||
@@ -1021,7 +1176,7 @@ function SignYourSelf() {
|
||||
<ModalUi
|
||||
headerColor={"#dc3545"}
|
||||
isOpen={isAlert.isShow}
|
||||
title={"Alert"}
|
||||
title={isAlert?.header || "Alert"}
|
||||
handleClose={() => {
|
||||
setIsAlert({
|
||||
isShow: false,
|
||||
@@ -1031,28 +1186,6 @@ function SignYourSelf() {
|
||||
>
|
||||
<div style={{ height: "100%", padding: 20 }}>
|
||||
<p>{isAlert.alertMessage}</p>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: "1px",
|
||||
backgroundColor: "#9f9f9f",
|
||||
width: "100%",
|
||||
marginTop: "15px",
|
||||
marginBottom: "15px"
|
||||
}}
|
||||
></div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsAlert({
|
||||
isShow: false,
|
||||
alertMessage: ""
|
||||
});
|
||||
}}
|
||||
type="button"
|
||||
className="finishBtn cancelBtn"
|
||||
>
|
||||
Ok
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
|
||||
@@ -1131,13 +1264,13 @@ function SignYourSelf() {
|
||||
<EmailComponent
|
||||
isEmail={isEmail}
|
||||
pdfUrl={pdfUrl}
|
||||
isCeleb={isCeleb}
|
||||
setIsEmail={setIsEmail}
|
||||
pdfName={pdfDetails[0] && pdfDetails[0].Name}
|
||||
setSuccessEmail={setSuccessEmail}
|
||||
sender={jsonSender}
|
||||
setIsAlert={setIsAlert}
|
||||
extUserId={extUserId}
|
||||
activeMailAdapter={activeMailAdapter}
|
||||
/>
|
||||
{/* pdf header which contain funish back button */}
|
||||
<Header
|
||||
|
||||
@@ -36,6 +36,7 @@ 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";
|
||||
const TemplatePlaceholder = () => {
|
||||
const navigate = useNavigate();
|
||||
const { templateId } = useParams();
|
||||
@@ -704,35 +705,29 @@ const TemplatePlaceholder = () => {
|
||||
Name: pdfDetails[0]?.Name || "",
|
||||
Note: pdfDetails[0]?.Note || "",
|
||||
Description: pdfDetails[0]?.Description || "",
|
||||
SendinOrder: pdfDetails[0]?.SendinOrder || false
|
||||
SendinOrder: pdfDetails[0]?.SendinOrder || false,
|
||||
AutomaticReminders: pdfDetails[0]?.AutomaticReminders,
|
||||
RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery),
|
||||
NextReminderDate: pdfDetails[0]?.NextReminderDate
|
||||
};
|
||||
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 axios
|
||||
.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
|
||||
"_appName"
|
||||
)}_Template/${templateId}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
setIsCreateDocModal(true);
|
||||
setIsMailSend(true);
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setIsLoading(loadObj);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("axois err ", err);
|
||||
});
|
||||
setIsCreateDocModal(true);
|
||||
setIsMailSend(true);
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setIsLoading(loadObj);
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
alert("Something went wrong, please try again later.");
|
||||
console.log("error", e);
|
||||
}
|
||||
} else {
|
||||
@@ -1207,7 +1202,6 @@ const TemplatePlaceholder = () => {
|
||||
setIsCheckbox(false);
|
||||
};
|
||||
|
||||
console.log("signerpos", signerPos);
|
||||
return (
|
||||
<div>
|
||||
<Title title={"Template"} />
|
||||
|
||||
@@ -8,9 +8,10 @@ import sanitizeFileName from "../primitives/sanitizeFileName";
|
||||
import axios from "axios";
|
||||
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
|
||||
import Tooltip from "../primitives/Tooltip";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import { checkIsSubscribed } from "../constant/Utils";
|
||||
import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
|
||||
import { checkIsSubscribed, handleSendOTP } from "../constant/Utils";
|
||||
import Upgrade from "../primitives/Upgrade";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
|
||||
function UserProfile() {
|
||||
const navigate = useNavigate();
|
||||
@@ -32,12 +33,16 @@ function UserProfile() {
|
||||
const [jobTitle, setJobTitle] = useState(
|
||||
extendUser && extendUser?.[0]?.JobTitle
|
||||
);
|
||||
|
||||
const [isVerifyModal, setIsVerifyModal] = useState(false);
|
||||
const [otp, setOtp] = useState("");
|
||||
const [otpLoader, setOtpLoader] = useState(false);
|
||||
const [isEmailVerified, setIsEmailVerified] = useState(false);
|
||||
useEffect(() => {
|
||||
getUserDetail();
|
||||
}, []);
|
||||
|
||||
const getUserDetail = async () => {
|
||||
setIsLoader(true);
|
||||
const extClass = localStorage.getItem("Extand_Class");
|
||||
const jsonSender = JSON.parse(extClass);
|
||||
const HeaderDocId = jsonSender[0]?.HeaderDocId;
|
||||
@@ -48,6 +53,26 @@ function UserProfile() {
|
||||
if (HeaderDocId) {
|
||||
setIsDisableDocId(HeaderDocId);
|
||||
}
|
||||
const currentUser = JSON.parse(JSON.stringify(Parse.User.current()));
|
||||
let isEmailVerified = currentUser?.emailVerified || false;
|
||||
if (isEmailVerified) {
|
||||
setIsEmailVerified(isEmailVerified);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
try {
|
||||
const userQuery = new Parse.Query(Parse.User);
|
||||
const user = await userQuery.get(currentUser.objectId, {
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
});
|
||||
if (user) {
|
||||
isEmailVerified = user?.get("emailVerified");
|
||||
setIsEmailVerified(isEmailVerified);
|
||||
setIsLoader(false);
|
||||
}
|
||||
} catch (e) {
|
||||
alert("something went wrong!");
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -177,6 +202,45 @@ function UserProfile() {
|
||||
const handleDisableDocId = () => {
|
||||
setIsDisableDocId((prevChecked) => !prevChecked);
|
||||
};
|
||||
//`handleVerifyBtn` function is used to send otp on user mail
|
||||
const handleVerifyBtn = async () => {
|
||||
setIsVerifyModal(true);
|
||||
await handleSendOTP(Parse.User.current().getEmail());
|
||||
};
|
||||
const handleCloseVerifyModal = async () => {
|
||||
setIsVerifyModal(false);
|
||||
};
|
||||
//`handleVerifyEmail` function is used to verify email with otp
|
||||
const handleVerifyEmail = async (e) => {
|
||||
e.preventDefault();
|
||||
setOtpLoader(true);
|
||||
try {
|
||||
const resEmail = await Parse.Cloud.run("verifyemail", {
|
||||
otp: otp,
|
||||
email: Parse.User.current().getEmail()
|
||||
});
|
||||
if (resEmail?.message === "Email is verified.") {
|
||||
setIsEmailVerified(true);
|
||||
} else if (resEmail?.message === "Email is already verified.") {
|
||||
setIsEmailVerified(true);
|
||||
}
|
||||
setOtp("");
|
||||
alert(resEmail.message);
|
||||
setIsVerifyModal(false);
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
} finally {
|
||||
setOtpLoader(false);
|
||||
}
|
||||
};
|
||||
//function to use resend otp for email verification
|
||||
const handleResend = async (e) => {
|
||||
e.preventDefault();
|
||||
setOtpLoader(true);
|
||||
await handleSendOTP();
|
||||
setOtpLoader(false);
|
||||
alert("OTP sent on you email");
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title title={"Profile"} />
|
||||
@@ -310,9 +374,20 @@ function UserProfile() {
|
||||
<li className="flex justify-between items-center border-t-[1px] border-gray-300 py-2 break-all">
|
||||
<span className="font-semibold">Is Email verified:</span>{" "}
|
||||
<span>
|
||||
{UserProfile && UserProfile.emailVerified
|
||||
? "Verified"
|
||||
: "Not verified"}
|
||||
{isEmailVerified ? (
|
||||
"Verified"
|
||||
) : (
|
||||
<span>
|
||||
Not verified(
|
||||
<span
|
||||
onClick={() => handleVerifyBtn()}
|
||||
className="hover:underline text-blue-600 cursor-pointer"
|
||||
>
|
||||
verify
|
||||
</span>
|
||||
)
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
<li className="border-y-[1px] border-gray-300 break-all">
|
||||
@@ -329,7 +404,6 @@ function UserProfile() {
|
||||
url={
|
||||
"https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid"
|
||||
}
|
||||
isSubscribe={isSubscribe}
|
||||
/>{" "}
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</span>{" "}
|
||||
@@ -399,6 +473,60 @@ function UserProfile() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isVerifyModal && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={"OTP verification"}
|
||||
handleClose={handleCloseVerifyModal}
|
||||
>
|
||||
{otpLoader ? (
|
||||
<div
|
||||
style={{
|
||||
height: "150px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "45px",
|
||||
color: "#3dd3e0"
|
||||
}}
|
||||
className="loader-37"
|
||||
></div>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={(e) => handleVerifyEmail(e)}>
|
||||
<div className="px-6 py-3">
|
||||
<label className="mb-2">Enter OTP</label>
|
||||
<input
|
||||
required
|
||||
type="tel"
|
||||
pattern="[0-9]{4}"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
placeholder="Enter OTP received over email"
|
||||
value={otp}
|
||||
onChange={(e) => setOtp(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="px-6 my-3">
|
||||
<button type="submit" className={submitBtn}>
|
||||
Verify
|
||||
</button>
|
||||
<button
|
||||
className={`${rejectBtn} ml-2`}
|
||||
onClick={(e) => handleResend(e)}
|
||||
>
|
||||
Resend
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</ModalUi>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -18,7 +18,7 @@ const Alert = ({ children, type }) => {
|
||||
<>
|
||||
{children && (
|
||||
<div
|
||||
className={`z-40 fixed top-20 left-1/2 transform -translate-x-1/2 border-[1px] text-sm ${textcolor} rounded py-[.75rem] px-[1.25rem] z-50`}
|
||||
className={`z-[1000] fixed top-20 left-1/2 transform -translate-x-1/2 border-[1px] text-sm ${textcolor} rounded py-[.75rem] px-[1.25rem] z-50`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,14 @@ import Tooltip from "./Tooltip";
|
||||
import { RWebShare } from "react-web-share";
|
||||
import Tour from "reactour";
|
||||
import Parse from "parse";
|
||||
import { saveAs } from "file-saver";
|
||||
import { replaceMailVaribles } from "../constant/Utils";
|
||||
import EditorToolbar, {
|
||||
module1,
|
||||
formats
|
||||
} from "../components/pdf/EditorToolbar";
|
||||
import ReactQuill from "react-quill";
|
||||
import "react-quill/dist/quill.snow.css";
|
||||
|
||||
const ReportTable = (props) => {
|
||||
const navigate = useNavigate();
|
||||
@@ -27,6 +35,10 @@ const ReportTable = (props) => {
|
||||
const [alertMsg, setAlertMsg] = useState({ type: "success", message: "" });
|
||||
const [isTour, setIsTour] = useState(false);
|
||||
const [tourStatusArr, setTourStatusArr] = useState([]);
|
||||
const [isResendMail, setIsResendMail] = useState({});
|
||||
const [mail, setMail] = useState({ subject: "", body: "" });
|
||||
const [userDetails, setUserDetails] = useState({});
|
||||
const [isNextStep, setIsNextStep] = useState({});
|
||||
const startIndex = (currentPage - 1) * props.docPerPage;
|
||||
const { isMoreDocs, setIsNextRecord } = props;
|
||||
// For loop is used to calculate page numbers visible below table
|
||||
@@ -88,8 +100,6 @@ const ReportTable = (props) => {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// console.log("templateDeatils.data ", templateDeatils.data);
|
||||
const templateData =
|
||||
templateDeatils.data && templateDeatils.data.result;
|
||||
if (!templateData.error) {
|
||||
@@ -129,7 +139,10 @@ const ReportTable = (props) => {
|
||||
className: "_User",
|
||||
objectId: Doc.CreatedBy.objectId
|
||||
},
|
||||
Signers: signers
|
||||
Signers: signers,
|
||||
SendinOrder: Doc?.SendinOrder || false,
|
||||
AutomaticReminders: Doc?.AutomaticReminders || false,
|
||||
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(
|
||||
@@ -148,7 +161,6 @@ const ReportTable = (props) => {
|
||||
}
|
||||
);
|
||||
|
||||
// console.log("Res ", res.data);
|
||||
if (res.data && res.data.objectId) {
|
||||
setActLoader({});
|
||||
setIsAlert(true);
|
||||
@@ -209,6 +221,8 @@ const ReportTable = (props) => {
|
||||
setIsRevoke({ [item.objectId]: true });
|
||||
} else if (act.action === "option") {
|
||||
setIsOption({ [item.objectId]: !isOption[item.objectId] });
|
||||
} else if (act.action === "resend") {
|
||||
setIsResendMail({ [item.objectId]: true });
|
||||
}
|
||||
};
|
||||
// Get current list
|
||||
@@ -253,7 +267,6 @@ const ReportTable = (props) => {
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
});
|
||||
// console.log("Res ", res.data);
|
||||
if (res.data && res.data.updatedAt) {
|
||||
setActLoader({});
|
||||
setIsAlert(true);
|
||||
@@ -286,13 +299,18 @@ const ReportTable = (props) => {
|
||||
const handleShare = (item) => {
|
||||
setActLoader({ [item.objectId]: true });
|
||||
const host = window.location.origin;
|
||||
const serverUrl = process.env.REACT_APP_SERVERURL
|
||||
? process.env.REACT_APP_SERVERURL
|
||||
: window.location.origin + "/api/app";
|
||||
const baseURL = serverUrl.replace(/\//g, "%2F");
|
||||
const sendMail = item?.SendMail || false;
|
||||
const getUrl = (x) => {
|
||||
//encode this url value `${item.objectId}/${x.Email}/${x.objectId}` to base64 using `btoa` function
|
||||
const encodeBase64 = btoa(
|
||||
`${item.objectId}/${x.Email}/${x.objectId}/${sendMail}`
|
||||
);
|
||||
return `${host}/login/${encodeBase64}`;
|
||||
};
|
||||
|
||||
const urls = item.Signers.map((x) => ({
|
||||
email: x.Email,
|
||||
url: `${host}/login/${item.objectId}/${x.Email}/${x.objectId}/${baseURL}%2F&opensign&contracts`
|
||||
url: getUrl(x)
|
||||
}));
|
||||
setShareUrls(urls);
|
||||
setIsShare({ [item.objectId]: true });
|
||||
@@ -420,6 +438,177 @@ const ReportTable = (props) => {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// `handleDownload` is used to get valid doc url available in completed report
|
||||
const handleDownload = async (item) => {
|
||||
const url = item?.SignedUrl || item?.URL || "";
|
||||
if (url) {
|
||||
try {
|
||||
const signedUrl = await Parse.Cloud.run("getsignedurl", { url: url });
|
||||
saveAs(signedUrl);
|
||||
} catch (err) {
|
||||
console.log("err in getsignedurl", err);
|
||||
alert("something went wrong, please try again later.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// `handleSubjectChange` is used to add or change subject of resend mail
|
||||
const handleSubjectChange = (subject, doc) => {
|
||||
const encodeBase64 = btoa(
|
||||
`${doc.objectId}/${userDetails.Email}/${userDetails.objectId}`
|
||||
);
|
||||
const expireDate = doc.ExpiryDate.iso;
|
||||
const newDate = new Date(expireDate);
|
||||
const localExpireDate = newDate.toLocaleDateString("en-US", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric"
|
||||
});
|
||||
const signPdf = `${window.location.origin}/login/${encodeBase64}`;
|
||||
const variables = {
|
||||
document_title: doc.Name,
|
||||
sender_name: doc.ExtUserPtr.Name,
|
||||
sender_mail: doc.ExtUserPtr.Email,
|
||||
sender_phone: doc.ExtUserPtr.Phone,
|
||||
receiver_name: userDetails.Name,
|
||||
receiver_email: userDetails.Email,
|
||||
receiver_phone: userDetails.Phone,
|
||||
expiry_date: localExpireDate,
|
||||
company_name: doc.ExtUserPtr.Company,
|
||||
signing_url: `<a href=${signPdf}>Sign here</a>`
|
||||
};
|
||||
const res = replaceMailVaribles(subject, "", variables);
|
||||
|
||||
setMail((prev) => ({ ...prev, subject: res.subject }));
|
||||
};
|
||||
// `handlebodyChange` is used to add or change body of resend mail
|
||||
const handlebodyChange = (body, doc) => {
|
||||
const encodeBase64 = btoa(
|
||||
`${doc.objectId}/${userDetails.Email}/${userDetails.objectId}`
|
||||
);
|
||||
const expireDate = doc.ExpiryDate.iso;
|
||||
const newDate = new Date(expireDate);
|
||||
const localExpireDate = newDate.toLocaleDateString("en-US", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric"
|
||||
});
|
||||
const signPdf = `${window.location.origin}/login/${encodeBase64}`;
|
||||
const variables = {
|
||||
document_title: doc.Name,
|
||||
sender_name: doc.ExtUserPtr.Name,
|
||||
sender_mail: doc.ExtUserPtr.Email,
|
||||
sender_phone: doc.ExtUserPtr.Phone,
|
||||
receiver_name: userDetails.Name,
|
||||
receiver_email: userDetails.Email,
|
||||
receiver_phone: userDetails.Phone,
|
||||
expiry_date: localExpireDate,
|
||||
company_name: doc.ExtUserPtr.Company,
|
||||
signing_url: `<a href=${signPdf}>Sign here</a>`
|
||||
};
|
||||
const res = replaceMailVaribles("", body, variables);
|
||||
|
||||
if (body) {
|
||||
setMail((prev) => ({ ...prev, body: res.body }));
|
||||
}
|
||||
};
|
||||
// `handleNextBtn` is used to open edit mail template screen in resend mail modal
|
||||
// as well as replace variable with original one
|
||||
const handleNextBtn = (user, doc) => {
|
||||
setUserDetails(user);
|
||||
const encodeBase64 = btoa(`${doc.objectId}/${user.Email}/${user.objectId}`);
|
||||
const expireDate = doc.ExpiryDate.iso;
|
||||
const newDate = new Date(expireDate);
|
||||
const localExpireDate = newDate.toLocaleDateString("en-US", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric"
|
||||
});
|
||||
const signPdf = `${window.location.origin}/login/${encodeBase64}`;
|
||||
const variables = {
|
||||
document_title: doc.Name,
|
||||
sender_name: doc.ExtUserPtr.Name,
|
||||
sender_mail: doc.ExtUserPtr.Email,
|
||||
sender_phone: doc.ExtUserPtr.Phone,
|
||||
receiver_name: user.Name,
|
||||
receiver_email: user.Email,
|
||||
receiver_phone: user.Phone,
|
||||
expiry_date: localExpireDate,
|
||||
company_name: doc?.ExtUserPtr?.Company || "",
|
||||
signing_url: `<a href=${signPdf}>Sign here</a>`
|
||||
};
|
||||
|
||||
const subject =
|
||||
doc?.RequestSubject ||
|
||||
`{{sender_name}} has requested you to sign "{{document_title}}"`;
|
||||
const body =
|
||||
doc?.RequestBody ||
|
||||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign <b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br></body> </html>`;
|
||||
const res = replaceMailVaribles(subject, body, variables);
|
||||
setMail((prev) => ({ ...prev, subject: res.subject, body: res.body }));
|
||||
setIsNextStep({ [user.objectId]: true });
|
||||
};
|
||||
const handleResendMail = async (e, doc, user) => {
|
||||
e.preventDefault();
|
||||
console.log("first");
|
||||
setActLoader({ [user.objectId]: true });
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
let params = {
|
||||
mailProvider: doc?.ExtUserPtr?.active_mail_adapter,
|
||||
extUserId: doc?.ExtUserPtr?.objectId,
|
||||
recipient: userDetails.Email,
|
||||
subject: mail.subject,
|
||||
from: doc?.ExtUserPtr?.Email,
|
||||
html: mail.body
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(url, params, { headers: headers });
|
||||
if (res) {
|
||||
setIsAlert(true);
|
||||
setAlertMsg({ type: "success", message: "Mail sent successfully." });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in sendmail", err);
|
||||
setIsAlert(true);
|
||||
setAlertMsg({
|
||||
type: "danger",
|
||||
message: "Something went wrong, please try again later!"
|
||||
});
|
||||
} finally {
|
||||
setTimeout(() => setIsAlert(false), 1500);
|
||||
setIsNextStep({});
|
||||
setUserDetails({});
|
||||
setActLoader({});
|
||||
}
|
||||
};
|
||||
const fetchUserStatus = (user, doc) => {
|
||||
const audit = doc?.AuditTrail?.find(
|
||||
(x) => x.UserPtr.objectId === user.objectId
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-row gap-2 justify-center items-center">
|
||||
<div className="flex justify-center items-center bg-gray-200 text-xs text-black shadow rounded-full w-[65px] h-[23px] cursor-default">
|
||||
{audit?.Activity ? audit?.Activity : "Awaited"}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => handleNextBtn(user, doc)}
|
||||
className={`${
|
||||
audit?.Activity !== "Signed" ? "bg-[#32a3ac] shadow-md" : ""
|
||||
} flex justify-center items-center text-center text-white rounded w-[60px] h-[30px]`}
|
||||
disabled={audit?.Activity === "Signed"}
|
||||
>
|
||||
{audit?.Activity !== "Signed" && "Resend"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{Object.keys(actLoader)?.length > 0 && (
|
||||
@@ -562,18 +751,14 @@ const ReportTable = (props) => {
|
||||
</td>
|
||||
)}
|
||||
<td className="px-4 py-2">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={item?.URL}
|
||||
href={item?.URL}
|
||||
download={item?.URL}
|
||||
className="text-[blue] hover:text-[blue] hover:underline"
|
||||
<button
|
||||
onClick={() => handleDownload(item)}
|
||||
className="text-[blue] hover:text-[blue] hover:underline focus:outline-none"
|
||||
title={"Download"}
|
||||
>
|
||||
{item?.URL ? "Download" : "-"}
|
||||
</a>
|
||||
</button>
|
||||
</td>
|
||||
|
||||
<td className="px-4 py-2">
|
||||
{formatRow(item?.ExtUserPtr)}
|
||||
</td>
|
||||
@@ -740,6 +925,109 @@ const ReportTable = (props) => {
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
{isResendMail[item.objectId] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={"Resend Mail"}
|
||||
handleClose={() => {
|
||||
setIsResendMail({});
|
||||
setIsNextStep({});
|
||||
setUserDetails({});
|
||||
}}
|
||||
>
|
||||
<div className=" overflow-y-auto max-h-[340px] md:max-h-[400px]">
|
||||
{item?.Signers.map((user) => (
|
||||
<React.Fragment key={user.objectId}>
|
||||
{isNextStep[user.objectId] && (
|
||||
<div className="relative ">
|
||||
{actLoader[user.objectId] && (
|
||||
<div className="absolute w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-30">
|
||||
<div
|
||||
style={{
|
||||
fontSize: "45px",
|
||||
color: "#3dd3e0"
|
||||
}}
|
||||
className="loader-37"
|
||||
></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form
|
||||
onSubmit={(e) =>
|
||||
handleResendMail(e, item, user)
|
||||
}
|
||||
className="w-full flex flex-col gap-2 p-3 text-black relative"
|
||||
>
|
||||
<div className="absolute right-5 text-xs z-40">
|
||||
<Tooltip
|
||||
id={`${user.objectId}_help`}
|
||||
message={
|
||||
"You can use following variables which will get replaced with their actual values:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}."
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
className="text-xs ml-1"
|
||||
htmlFor="mailsubject"
|
||||
>
|
||||
Subject{" "}
|
||||
</label>
|
||||
<input
|
||||
id="mailsubject"
|
||||
className="w-full py-1.5 px-2 border-[1px] border-gray-300 rounded"
|
||||
value={mail.subject}
|
||||
onChange={(e) =>
|
||||
handleSubjectChange(
|
||||
e.target.value,
|
||||
item
|
||||
)
|
||||
}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
className="text-xs ml-1"
|
||||
htmlFor="mailbody"
|
||||
>
|
||||
Body{" "}
|
||||
</label>
|
||||
<EditorToolbar containerId="toolbar1" />
|
||||
<ReactQuill
|
||||
id="mailbody"
|
||||
theme="snow"
|
||||
value={mail.body || ""}
|
||||
placeholder="add body of email "
|
||||
modules={module1}
|
||||
formats={formats}
|
||||
onChange={(value) =>
|
||||
handlebodyChange(value, item)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-[#32a3ac] w-[70px] font-semibold text-white py-2 rounded"
|
||||
>
|
||||
Resend
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
{Object?.keys(isNextStep) <= 0 && (
|
||||
<div className="flex justify-between items-center gap-2 my-2 px-3">
|
||||
<div className="text-black">
|
||||
{user.Name} {`<${user.Email}>`}
|
||||
</div>
|
||||
<>{fetchUserStatus(user, item)}</>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ const ModalUi = ({
|
||||
return (
|
||||
<>
|
||||
{isOpen && (
|
||||
<div className="fixed z-[999] 2 top-0 left-0 w-[100%] h-[100%] bg-black bg-opacity-[75%]">
|
||||
<div className="fixed z-[999] top-0 left-0 w-[100%] h-[100%] bg-black bg-opacity-[75%]">
|
||||
<div
|
||||
className={
|
||||
styleClass
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import React from "react";
|
||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
const Tooltip = ({ id, message, url, iconColor, isSubscribe }) =>
|
||||
const Tooltip = ({ id, message, url, iconColor }) =>
|
||||
url ? (
|
||||
<button
|
||||
onClick={() => openInNewTab(url)}
|
||||
className={
|
||||
isSubscribe || !isEnableSubscription
|
||||
? "text-center"
|
||||
: "text-center opacity-20 pointer-events-none"
|
||||
}
|
||||
>
|
||||
<button onClick={() => openInNewTab(url)} className={"text-center"}>
|
||||
<sup>
|
||||
<i
|
||||
className="fa-solid fa-question rounded-full"
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
|
||||
function Upgrade({ message, newWindow }) {
|
||||
const navigation = useNavigate();
|
||||
|
||||
function Upgrade({ message }) {
|
||||
return (
|
||||
<sup>
|
||||
<span
|
||||
onClick={() => {
|
||||
if (newWindow) {
|
||||
const url = window.location.origin + "/subscription";
|
||||
openInNewTab(url);
|
||||
} else {
|
||||
navigation("/subscription");
|
||||
}
|
||||
const url = window.location.origin + "/subscription";
|
||||
openInNewTab(url);
|
||||
}}
|
||||
className="text-blue-800 text-sm cursor-pointer underline"
|
||||
>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
const appId = process.env.APP_ID;
|
||||
const serverUrl = process.env.SERVER_URL;
|
||||
@@ -243,3 +245,5 @@ export function sanitizeFileName(fileName) {
|
||||
const removedot = file.replace(/\.(?=.*\.)/g, '');
|
||||
return removedot.replace(/[^a-zA-Z0-9._-]/g, '');
|
||||
}
|
||||
|
||||
export const useLocal = process.env.USE_LOCAL ? process.env.USE_LOCAL.toLowerCase() : 'false';
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// `replaceMailVaribles` is used to replace variables from mail with there actual values
|
||||
function replaceMailVaribles(subject, body, variables) {
|
||||
let replacedSubject = subject;
|
||||
let replacedBody = body;
|
||||
|
||||
for (const variable in variables) {
|
||||
const regex = new RegExp(`{{${variable}}}`, 'g');
|
||||
if (subject) {
|
||||
replacedSubject = replacedSubject.replace(regex, variables[variable]);
|
||||
}
|
||||
if (body) {
|
||||
replacedBody = replacedBody.replace(regex, variables[variable]);
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
subject: replacedSubject,
|
||||
body: replacedBody,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
// `sendmail` is used to signing reminder mail to signer
|
||||
async function sendMail(doc, signer, mailcount) {
|
||||
const subject = `{{sender_name}} has requested you to sign "{{document_title}}"`;
|
||||
const body = `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign <b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br></body> </html>`;
|
||||
const url = `${process.env.SERVER_URL}/functions/sendmailv3`;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
};
|
||||
|
||||
const baseUrl = new URL(process.env.PUBLIC_URL);
|
||||
const encodeBase64 = btoa(`${doc.objectId}/${signer.Email}/${signer.objectId}`);
|
||||
const expireDate = doc?.ExpiryDate?.iso;
|
||||
const newDate = new Date(expireDate);
|
||||
const localExpireDate = newDate.toLocaleDateString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
const signPdf = `${baseUrl.origin}/login/${encodeBase64}`;
|
||||
const variables = {
|
||||
document_title: doc.Name,
|
||||
sender_name: doc.ExtUserPtr.Name,
|
||||
sender_mail: doc.ExtUserPtr.Email,
|
||||
sender_phone: doc.ExtUserPtr.Phone,
|
||||
receiver_name: signer.Name,
|
||||
receiver_email: signer.Email,
|
||||
receiver_phone: signer.Phone,
|
||||
expiry_date: localExpireDate,
|
||||
company_name: doc?.ExtUserPtr?.Company || '',
|
||||
signing_url: `<a href=${signPdf}>Sign here</a>`,
|
||||
};
|
||||
const mail = replaceMailVaribles(subject, body, variables);
|
||||
|
||||
let params = {
|
||||
mailProvider: doc?.ExtUserPtr?.active_mail_adapter,
|
||||
extUserId: doc?.ExtUserPtr?.objectId,
|
||||
recipient: signer.Email,
|
||||
subject: mail.subject,
|
||||
from: doc?.ExtUserPtr?.Email,
|
||||
html: mail.body,
|
||||
};
|
||||
try {
|
||||
// The axios request is used to send a signing reminder email.
|
||||
const res = await axios.post(url, params, { headers: headers });
|
||||
// console.log('res ', res.data.result);
|
||||
if (res.data.result.status === 'success') {
|
||||
mailcount += 1;
|
||||
return { status: 'success', count: mailcount };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in mail of sendreminder api', err);
|
||||
return { status: 'error' };
|
||||
}
|
||||
}
|
||||
export default async function autoReminder(request, response) {
|
||||
// The query below is used to find documents where the reminder date is less than or equal to the current date, and which have existing signers and a signed URL.
|
||||
try {
|
||||
const docQuery = new Parse.Query('contracts_Document');
|
||||
docQuery.limit(2000);
|
||||
docQuery.lessThanOrEqualTo('NextReminderDate', new Date());
|
||||
docQuery.equalTo('AutomaticReminders', true);
|
||||
docQuery.exists('NextReminderDate');
|
||||
docQuery.exists('Signers');
|
||||
docQuery.exists('SignedUrl');
|
||||
docQuery.descending('createdAt');
|
||||
docQuery.include('Signers,AuditTrail.UserPtr,ExtUserPtr');
|
||||
docQuery.notEqualTo('IsCompleted', true);
|
||||
docQuery.notEqualTo('IsDeclined', true);
|
||||
docQuery.notEqualTo('IsArchive', true);
|
||||
docQuery.greaterThanOrEqualTo('ExpiryDate', new Date());
|
||||
|
||||
const docsArr = await docQuery.find({ useMasterKey: true });
|
||||
|
||||
if (docsArr && docsArr.length > 0) {
|
||||
const _docsArr = JSON.parse(JSON.stringify(docsArr));
|
||||
let mailCount = 0;
|
||||
for (const doc of _docsArr) {
|
||||
// The reminderDate variable is used to calculate the next reminder date.
|
||||
const RemindOnceInEvery = doc?.RemindOnceInEvery || 5;
|
||||
const ReminderDate = new Date(doc?.NextReminderDate?.iso);
|
||||
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
|
||||
// The sendInOrder variable is used to determine whether to send emails in order or not.
|
||||
const SendInOrder = doc?.SendinOrder || false;
|
||||
if (SendInOrder) {
|
||||
// The auditTrail variable is used to get the count of how many users have already signed the document.
|
||||
const auditTrail = doc?.AuditTrail?.filter(x => x.Activity === 'Signed');
|
||||
const count = auditTrail?.length || 0;
|
||||
const signer = doc?.Signers?.[count];
|
||||
if (signer) {
|
||||
const mailRes = await sendMail(doc, signer, mailCount);
|
||||
if (mailRes && mailRes.status === 'success') {
|
||||
mailCount += mailRes.count;
|
||||
}
|
||||
try {
|
||||
// The code below is used to update the next reminder date of the document based on the "remind once every X days" setting.
|
||||
const updateDoc = new Parse.Object('contracts_Document');
|
||||
updateDoc.id = doc.objectId;
|
||||
updateDoc.set('NextReminderDate', ReminderDate);
|
||||
const updateRes = await updateDoc.save(null, { useMasterKey: true });
|
||||
// console.log('updateRes ', updateRes);
|
||||
} catch (err) {
|
||||
console.log('err in update document', err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The AuditTrail variable is used to check if there is any user who has already signed the document.
|
||||
const auditTrail = doc?.AuditTrail?.filter(x => x.Activity === 'Signed');
|
||||
if (auditTrail?.length > 0) {
|
||||
// The signers variable is used to get the signers who haven't signed the document.
|
||||
const signers = doc?.Signers.filter(signer => {
|
||||
const signedUser = auditTrail?.find(y => y.UserPtr.objectId === signer.objectId);
|
||||
if (!signedUser) {
|
||||
return signer;
|
||||
}
|
||||
});
|
||||
if (signers?.length > 0) {
|
||||
// The for...of loop below is used to send a signing reminder to every signer who hasn't signed the document yet.
|
||||
for (const signer of signers) {
|
||||
const mailRes = await sendMail(doc, signer, mailCount);
|
||||
if (mailRes && mailRes.status === 'success') {
|
||||
mailCount += mailRes.count;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The for...of loop below is used to send a signing reminder to every signer who hasn't signed the document yet.
|
||||
const signers = doc?.Signers;
|
||||
if (signers?.length > 0) {
|
||||
for (const signer of signers) {
|
||||
const mailRes = await sendMail(doc, signer, mailCount);
|
||||
if (mailRes && mailRes.status === 'success') {
|
||||
mailCount += mailRes.count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The code below is used to update the next reminder date of the document based on the "remind once every X days" setting.
|
||||
try {
|
||||
const updateDoc = new Parse.Object('contracts_Document');
|
||||
updateDoc.id = doc.objectId;
|
||||
updateDoc.set('NextReminderDate', ReminderDate);
|
||||
const updateRes = await updateDoc.save(null, { useMasterKey: true });
|
||||
// console.log('updateRes ', updateRes);
|
||||
} catch (err) {
|
||||
console.log('err in sendmail', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
response.json({ status: 'success', document_count: docsArr.length, mail_count: mailCount });
|
||||
} else {
|
||||
response.json({ status: 'no record found' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
const code = err?.code || 400;
|
||||
const message = err?.message || 'Somehting went wrong!';
|
||||
response.status(code).json({ error: message });
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import uploadFile from './uploadFile.js';
|
||||
import saveSubscription from './saveSubscription.js';
|
||||
import saveInvoice from './saveInvoice.js';
|
||||
import savePayments from './savePayments.js';
|
||||
import gooogleauth from './googleauth.js';
|
||||
import autoReminder from './autoReminder.js';
|
||||
export const app = express();
|
||||
|
||||
dotenv.config();
|
||||
@@ -15,8 +17,8 @@ app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||
|
||||
app.post('/file_upload', uploadFile);
|
||||
|
||||
app.post('/savesubscription', saveSubscription)
|
||||
app.post('/saveinvoice', saveInvoice)
|
||||
app.post('/savepayment', savePayments)
|
||||
|
||||
|
||||
app.post('/savesubscription', saveSubscription);
|
||||
app.post('/saveinvoice', saveInvoice);
|
||||
app.post('/savepayment', savePayments);
|
||||
app.post('/googleauth', gooogleauth);
|
||||
app.post('/sendreminder', autoReminder);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import axios from 'axios';
|
||||
const appId = process.env.APP_ID;
|
||||
const serverUrl = process.env.SERVER_URL;
|
||||
export default async function gooogleauth(request, response) {
|
||||
const code = request.body.code;
|
||||
const baseUrl = new URL(process.env.SERVER_URL);
|
||||
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': request.headers['sessiontoken'],
|
||||
},
|
||||
});
|
||||
const userId = userRes.data && userRes.data.objectId;
|
||||
if (userId) {
|
||||
const clientId = process.env.GOOGLE_CLIENT_ID;
|
||||
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
||||
const redirectUri =
|
||||
baseUrl?.hostname === 'localhost'
|
||||
? 'http://localhost:3000'
|
||||
: 'https://console.opensignlabs.com'; // Should match the redirect URI used in the authorization request
|
||||
const tokenEndpoint = 'https://oauth2.googleapis.com/token';
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('code', code);
|
||||
params.append('client_id', clientId);
|
||||
params.append('client_secret', clientSecret);
|
||||
params.append('redirect_uri', redirectUri);
|
||||
params.append('grant_type', 'authorization_code');
|
||||
|
||||
const res = await axios.post(tokenEndpoint, params);
|
||||
// console.log('oauthres ', res.data);
|
||||
const refresh_token = res.data.refresh_token;
|
||||
const extUserCls = new Parse.Query('contracts_Users');
|
||||
extUserCls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
|
||||
const extUser = await extUserCls.first({ useMasterKey: true });
|
||||
|
||||
if (extUser) {
|
||||
const extUserCls = new Parse.Object('contracts_Users');
|
||||
extUserCls.id = extUser.id;
|
||||
extUserCls.set('google_refresh_token', refresh_token);
|
||||
extUserCls.set('active_mail_adapter', 'google');
|
||||
const updateExtUser = await extUserCls.save(null, { useMasterKey: true });
|
||||
// console.log('updateExtUser ', updateExtUser);
|
||||
}
|
||||
return response.status(200).json({ status: 'success' });
|
||||
} else {
|
||||
return response.status(404).json({ message: 'user not found!' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in google auth', err);
|
||||
if (err?.response?.data?.error) {
|
||||
return response.status(404).json({ message: err.response.data.error });
|
||||
} else {
|
||||
return response.status(404).json({ message: err.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import multer from 'multer';
|
||||
import multerS3 from 'multer-s3';
|
||||
import aws from 'aws-sdk';
|
||||
import dotenv from 'dotenv';
|
||||
import { useLocal } from '../../Utils.js';
|
||||
dotenv.config();
|
||||
|
||||
function sanitizeFileName(fileName) {
|
||||
@@ -51,7 +52,7 @@ async function uploadFile(req, res) {
|
||||
const parseBaseUrl = process.env.SERVER_URL;
|
||||
const parseAppId = process.env.APP_ID;
|
||||
let fileStorage;
|
||||
if (process.env.USE_LOCAL == 'TRUE') {
|
||||
if (useLocal === 'true') {
|
||||
fileStorage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, 'files/files');
|
||||
@@ -147,7 +148,7 @@ async function uploadFile(req, res) {
|
||||
const status = 'Success';
|
||||
//res.header("Access-Control-Allow-Headers", "Content-Type");
|
||||
//res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
if (process.env.USE_LOCAL == 'TRUE') {
|
||||
if (useLocal === 'true') {
|
||||
console.log(req.file);
|
||||
var fileUrl = `${parseBaseUrl}/files/${parseAppId}/${req.file.filename}`;
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,6 @@ import getTemplatetList from './routes/getTemplateList.js';
|
||||
import updateTemplate from './routes/updateTemplate.js';
|
||||
import createContact from './routes/createContact.js';
|
||||
import multer from 'multer';
|
||||
// import fs from 'node:fs';
|
||||
import updateDocument from './routes/updateDocument.js';
|
||||
import deleteDocument from './routes/deleteDocument.js';
|
||||
import createDocumentWithTemplate from './routes/CreateDocumentWithTemplate.js';
|
||||
@@ -26,6 +25,12 @@ import deleteWebhook from './routes/deleteWebhook.js';
|
||||
import getWebhook from './routes/getWebhook.js';
|
||||
import createDocumentwithCoordinate from './routes/createDocumentwithCoordinate.js';
|
||||
import createTemplatewithCoordinate from './routes/createTemplatewithCoordinate.js';
|
||||
import resendMail from './routes/resendMail.js';
|
||||
import getFolder from './routes/getFolder.js';
|
||||
import createFolder from './routes/createFolder.js';
|
||||
import updateFolder from './routes/updateFolder.js';
|
||||
import getFolderList from './routes/getFolderList.js';
|
||||
import deleteFolder from './routes/deleteFolder.js';
|
||||
dotenv.config();
|
||||
const storage = multer.memoryStorage();
|
||||
const upload = multer({ storage: storage });
|
||||
@@ -102,3 +107,21 @@ app.post('/webhook', saveWebhook);
|
||||
|
||||
// set and update webhook
|
||||
app.delete('/webhook', deleteWebhook);
|
||||
|
||||
// resend mail
|
||||
app.post('/resendmail', resendMail);
|
||||
|
||||
// create folder
|
||||
app.post('/createfolder', createFolder);
|
||||
|
||||
// update folder
|
||||
app.put('/folder/:folder_id', updateFolder);
|
||||
|
||||
// get folder list
|
||||
app.delete('/folder/:folder_id', deleteFolder);
|
||||
|
||||
// get folder
|
||||
app.get('/folder/:folder_id', getFolder);
|
||||
|
||||
// get folder list
|
||||
app.get('/folderlist', getFolderList);
|
||||
|
||||
@@ -50,11 +50,12 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
const folderId = request.body.folderId;
|
||||
const templateId = request.params.template_id;
|
||||
const protocol = customAPIurl();
|
||||
const baseUrl = new URL(process.env.SERVER_URL);
|
||||
const baseUrl = new URL(process.env.PUBLIC_URL);
|
||||
const send_email = request.body.send_email;
|
||||
const email_subject = request.body.email_subject;
|
||||
const email_body = request.body.email_body;
|
||||
const sendInOrder = request.body.sendInOrder || false;
|
||||
const TimeToCompleteDays = request.body.timeToCompleteDays || 15;
|
||||
|
||||
try {
|
||||
const reqToken = request.headers['x-api-token'];
|
||||
@@ -88,12 +89,20 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
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 template = JSON.parse(JSON.stringify(templateRes));
|
||||
const object = new Parse.Object('contracts_Document');
|
||||
object.set('Name', template.Name);
|
||||
if (template?.Note) {
|
||||
@@ -169,6 +178,10 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
object.set('Signers', templateSigner);
|
||||
}
|
||||
object.set('URL', template.URL);
|
||||
object.set('SignedUrl', template.URL);
|
||||
if (TimeToCompleteDays) {
|
||||
object.set('TimeToCompleteDays', TimeToCompleteDays);
|
||||
}
|
||||
object.set('CreatedBy', template.CreatedBy);
|
||||
object.set('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
@@ -217,9 +230,10 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
};
|
||||
|
||||
const objectId = contactMail[i].contactPtr.objectId;
|
||||
|
||||
const hostUrl = baseUrl.origin;
|
||||
let signPdf = `${hostUrl}/login/${res.id}/${contactMail[i].email}/${objectId}/${serverParams}`;
|
||||
//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';
|
||||
@@ -266,13 +280,13 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
replaceVar = { subject: replaceVar.subject, body: email_html };
|
||||
} else if (email_body) {
|
||||
replaceVar = replaceMailVaribles(
|
||||
`${template.ExtUserPtr.Name} has requested you to sign ${template.Name}`,
|
||||
`${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}`,
|
||||
subject: `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`,
|
||||
body: email_html,
|
||||
};
|
||||
}
|
||||
@@ -320,7 +334,9 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
objectId: res.id,
|
||||
signurl: contact.map(x => ({
|
||||
email: x.email,
|
||||
url: `${baseUrl.origin}/login/${res.id}/${x.email}/${x.contactPtr.objectId}/${serverParams}`,
|
||||
url: `${baseUrl.origin}/login/${btoa(
|
||||
`${res.id}/${x.email}/${x.contactPtr.objectId}`
|
||||
)}`,
|
||||
})),
|
||||
message: 'Document sent successfully!',
|
||||
});
|
||||
|
||||
@@ -128,7 +128,7 @@ export default async function createContact(request, response) {
|
||||
userId: userRes.objectId,
|
||||
};
|
||||
await axios.post(roleurl, body, { headers: headers });
|
||||
contactQuery.set('CreatedBy', userPtr.objectId);
|
||||
contactQuery.set('CreatedBy', userPtr);
|
||||
contactQuery.set('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
|
||||
@@ -66,9 +66,10 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
const email_subject = request.body.email_subject;
|
||||
const email_body = request.body.email_body;
|
||||
const sendInOrder = request.body.sendInOrder || false;
|
||||
const TimeToCompleteDays = request.body.timeToCompleteDays || 15;
|
||||
// console.log('fileData ', fileData);
|
||||
const protocol = customAPIurl();
|
||||
const baseUrl = new URL(process.env.SERVER_URL);
|
||||
const baseUrl = new URL(process.env.PUBLIC_URL);
|
||||
const reqToken = request.headers['x-api-token'];
|
||||
if (!reqToken) {
|
||||
return response.status(400).json({ error: 'Please Provide API Token' });
|
||||
@@ -88,6 +89,15 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
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');
|
||||
@@ -130,8 +140,12 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
object.set('SendinOrder', sendInOrder);
|
||||
}
|
||||
object.set('URL', fileUrl);
|
||||
object.set('SignedUrl', fileUrl);
|
||||
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) {
|
||||
@@ -278,7 +292,9 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
|
||||
const objectId = contactMail[i].contactPtr.objectId;
|
||||
const hostUrl = baseUrl.origin;
|
||||
let signPdf = `${hostUrl}/login/${res.id}/${contactMail[i].email}/${objectId}/${serverParams}`;
|
||||
//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';
|
||||
@@ -324,13 +340,13 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
replaceVar = { subject: replaceVar.subject, body: email_html };
|
||||
} else if (email_body) {
|
||||
replaceVar = replaceMailVaribles(
|
||||
`${parseExtUser.Name} has requested you to sign ${name}`,
|
||||
`${parseExtUser.Name} has requested you to sign "${name}"`,
|
||||
email_body,
|
||||
variables
|
||||
);
|
||||
} else {
|
||||
replaceVar = {
|
||||
subject: `${parseExtUser.Name} has requested you to sign ${parseExtUser.Name}`,
|
||||
subject: `${parseExtUser.Name} has requested you to sign "${name}"`,
|
||||
body: email_html,
|
||||
};
|
||||
}
|
||||
@@ -363,7 +379,7 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
objectId: res.id,
|
||||
signurl: contact.map(x => ({
|
||||
email: x.email,
|
||||
url: `${baseUrl.origin}/login/${res.id}/${x.email}/${x.contactPtr.objectId}/${serverParams}`,
|
||||
url: `${baseUrl.origin}/login/${btoa(`${res.id}/${x.email}/${x.contactPtr.objectId}`)}`,
|
||||
})),
|
||||
message: 'Document sent successfully!',
|
||||
});
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
export default async function createFolder(request, response) {
|
||||
const apiToken = request.headers['x-api-token'];
|
||||
const folderName = request.body.folderName;
|
||||
const parentFolderId = request.body.parentFolderId;
|
||||
if (!apiToken) {
|
||||
return response.status(400).json({ error: 'Please Provide API Token' });
|
||||
}
|
||||
try {
|
||||
const tokenQuery = new Parse.Query('appToken');
|
||||
tokenQuery.equalTo('token', apiToken);
|
||||
tokenQuery.include('userId');
|
||||
const token = await tokenQuery.first({ useMasterKey: true });
|
||||
if (token !== undefined) {
|
||||
// Valid Token then proceed request
|
||||
const parseUser = JSON.parse(JSON.stringify(token));
|
||||
const userPtr = {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: parseUser.userId.objectId,
|
||||
};
|
||||
const userQuery = new Parse.Query('contracts_Users');
|
||||
userQuery.equalTo('UserId', userPtr);
|
||||
const extUser = await userQuery.first({ useMasterKey: true });
|
||||
|
||||
const folderCls = new Parse.Object('contracts_Document');
|
||||
folderCls.set('Name', folderName);
|
||||
folderCls.set('CreatedBy', userPtr);
|
||||
folderCls.set('Type', 'Folder');
|
||||
folderCls.set('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
});
|
||||
if (parentFolderId) {
|
||||
folderCls.set('Folder', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Document',
|
||||
objectId: parentFolderId,
|
||||
});
|
||||
}
|
||||
const acl = new Parse.ACL();
|
||||
acl.setReadAccess(parseUser.userId.objectId, true); // Allow read access to the userPtr
|
||||
acl.setWriteAccess(parseUser.userId.objectId, true); // Allow write access to the userPtr
|
||||
|
||||
// Set the ACL to the document
|
||||
folderCls.setACL(acl);
|
||||
|
||||
const _resFolder = await folderCls.save(null, { useMasterKey: true });
|
||||
if (_resFolder) {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_create_folder',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
const parseRes = JSON.parse(JSON.stringify(_resFolder));
|
||||
return response.json({
|
||||
objectId: parseRes.objectId,
|
||||
folderName: parseRes.Name,
|
||||
parentFolderId: parseRes?.Folder?.objectId || '',
|
||||
createdAt: parseRes.createdAt,
|
||||
updatedAt: parseRes.updatedAt,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return response.status(405).json({ error: 'Invalid API Token!' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export default async function createTemplate(request, response) {
|
||||
const base64File = request.body.file;
|
||||
const SendinOrder = request.body.sendInOrder || false;
|
||||
const fileData = request.files?.[0] ? request.files[0].buffer : null;
|
||||
const baseUrl = new URL(process.env.SERVER_URL);
|
||||
const baseUrl = new URL(process.env.PUBLIC_URL);
|
||||
|
||||
try {
|
||||
const reqToken = request.headers['x-api-token'];
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
export default async function deleteFolder(request, response) {
|
||||
const apiToken = request.headers['x-api-token'];
|
||||
const folderId = request.params.folder_id;
|
||||
if (!apiToken) {
|
||||
return response.status(400).json({ error: 'Please Provide API Token' });
|
||||
}
|
||||
try {
|
||||
const tokenQuery = new Parse.Query('appToken');
|
||||
tokenQuery.equalTo('token', apiToken);
|
||||
tokenQuery.include('userId');
|
||||
const token = await tokenQuery.first({ useMasterKey: true });
|
||||
if (token !== undefined) {
|
||||
// Valid Token then proceed request
|
||||
const parseUser = JSON.parse(JSON.stringify(token));
|
||||
const userPtr = {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: parseUser.userId.objectId,
|
||||
};
|
||||
|
||||
const obj = new Parse.Query('contracts_Document');
|
||||
obj.equalTo('objectId', folderId);
|
||||
obj.equalTo('IsArchive', true);
|
||||
obj.equalTo('CreatedBy', userPtr);
|
||||
const isFolderDeleted = await obj.first({ useMasterKey: true });
|
||||
if (!isFolderDeleted) {
|
||||
const folder = new Parse.Query('contracts_Document');
|
||||
folder.equalTo('Folder', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Document',
|
||||
objectId: folderId,
|
||||
});
|
||||
|
||||
folder.notEqualTo('IsArchive', true);
|
||||
folder.equalTo('CreatedBy', userPtr);
|
||||
folder.equalTo('Type', 'Folder');
|
||||
const isSubItems = await folder.first({ useMasterKey: true });
|
||||
// console.log('isSubItems ', isSubItems);
|
||||
if (isSubItems) {
|
||||
return response
|
||||
.status(400)
|
||||
.json({ error: 'folder is not empty, contains document or folder.' });
|
||||
} else {
|
||||
const deleteFolder = new Parse.Object('contracts_Document');
|
||||
deleteFolder.id = folderId;
|
||||
deleteFolder.set('IsArchive', true);
|
||||
const deleteRes = await deleteFolder.save(null, { useMasterKey: true });
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_delete_folder',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
return response.json({ objectId: deleteRes.id, deletedAt: deleteRes.updatedAt });
|
||||
}
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_delete_folder',
|
||||
properties: { response_code: 404 },
|
||||
});
|
||||
}
|
||||
return response.status(404).json({ error: 'folder not found.' });
|
||||
}
|
||||
} else {
|
||||
return response.status(405).json({ error: 'Invalid API Token!' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err is delete folder API', err);
|
||||
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { customAPIurl } from '../../../../Utils.js';
|
||||
import { customAPIurl, saveFileUsage } from '../../../../Utils.js';
|
||||
|
||||
// const randomId = () => Math.floor(1000 + Math.random() * 9000);
|
||||
export default async function draftDocument(request, response) {
|
||||
@@ -14,7 +14,7 @@ export default async function draftDocument(request, response) {
|
||||
const SendinOrder = request.body.sendInOrder || false;
|
||||
// console.log('fileData ', fileData);
|
||||
const protocol = customAPIurl();
|
||||
const baseUrl = new URL(process.env.SERVER_URL);
|
||||
const baseUrl = new URL(process.env.PUBLIC_URL);
|
||||
|
||||
try {
|
||||
const reqToken = request.headers['x-api-token'];
|
||||
|
||||
@@ -40,6 +40,7 @@ export default async function getDocument(request, response) {
|
||||
note: document.Note || '',
|
||||
folder: { objectId: document?.Folder?.objectId, name: document?.Folder?.Name } || '',
|
||||
file: document?.SignedUrl || document.URL,
|
||||
certificate: document?.CertificateUrl || '',
|
||||
owner: document?.ExtUserPtr?.Name,
|
||||
signers:
|
||||
document?.Placeholders?.map(y => ({
|
||||
|
||||
@@ -77,7 +77,7 @@ export default async function getDocumentList(request, response) {
|
||||
title: x.Name,
|
||||
note: x.Note || '',
|
||||
folder: { objectId: x?.Folder?.objectId, name: x?.Folder?.Name } || '',
|
||||
file: x?.SignedUrl || x.URL,
|
||||
// file: x?.SignedUrl || x.URL,
|
||||
owner: x?.ExtUserPtr?.Name,
|
||||
signers:
|
||||
x?.Placeholders?.map(y => ({
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
export default async function getFolder(request, response) {
|
||||
const apiToken = request.headers['x-api-token'];
|
||||
const folder_id = request.params.folder_id;
|
||||
if (!apiToken) {
|
||||
return response.status(400).json({ error: 'Please Provide API Token' });
|
||||
}
|
||||
try {
|
||||
const tokenQuery = new Parse.Query('appToken');
|
||||
tokenQuery.equalTo('token', apiToken);
|
||||
tokenQuery.include('userId');
|
||||
const token = await tokenQuery.first({ useMasterKey: true });
|
||||
if (token !== undefined) {
|
||||
// Valid Token then proceed request
|
||||
const parseUser = JSON.parse(JSON.stringify(token));
|
||||
const userPtr = {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: parseUser.userId.objectId,
|
||||
};
|
||||
|
||||
const folderCls = new Parse.Query('contracts_Document');
|
||||
folderCls.equalTo('CreatedBy', userPtr);
|
||||
folderCls.equalTo('objectId', folder_id);
|
||||
folderCls.equalTo('Type', 'Folder');
|
||||
folderCls.notEqualTo('IsArchive', true);
|
||||
folderCls.descending('createdAt');
|
||||
folderCls.include('Folder');
|
||||
|
||||
const res = await folderCls.first({ useMasterKey: true });
|
||||
if (res) {
|
||||
const parseRes = JSON.parse(JSON.stringify(res));
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_get_folder',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
return response.json({
|
||||
objectId: parseRes.objectId,
|
||||
folderName: parseRes.Name,
|
||||
parentFolderId: parseRes?.Folder?.objectId || '',
|
||||
parentFolderName: parseRes?.Folder?.Name || '',
|
||||
createdAt: parseRes.createdAt,
|
||||
updatedAt: parseRes.updatedAt,
|
||||
});
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_get_folder',
|
||||
properties: { response_code: 404 },
|
||||
});
|
||||
}
|
||||
return response.status(404).json({ error: 'folder not found.' });
|
||||
}
|
||||
} else {
|
||||
return response.status(405).json({ error: 'Invalid API Token!' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
export default async function getFolderList(request, response) {
|
||||
const apiToken = request.headers['x-api-token'];
|
||||
const parentFolderId = request.query?.parentFolderId || '';
|
||||
if (!apiToken) {
|
||||
return response.status(400).json({ error: 'Please Provide API Token' });
|
||||
}
|
||||
try {
|
||||
const tokenQuery = new Parse.Query('appToken');
|
||||
tokenQuery.equalTo('token', apiToken);
|
||||
tokenQuery.include('userId');
|
||||
const token = await tokenQuery.first({ useMasterKey: true });
|
||||
if (token !== undefined) {
|
||||
// Valid Token then proceed request
|
||||
const parseUser = JSON.parse(JSON.stringify(token));
|
||||
const userPtr = {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: parseUser.userId.objectId,
|
||||
};
|
||||
const limit = request?.query?.limit ? parseInt(request.query.limit) : 100;
|
||||
const skip = request?.query?.skip ? parseInt(request.query.skip) : 0;
|
||||
const folderCls = new Parse.Query('contracts_Document');
|
||||
folderCls.equalTo('CreatedBy', userPtr);
|
||||
folderCls.equalTo('Type', 'Folder');
|
||||
folderCls.notEqualTo('IsArchive', true);
|
||||
folderCls.descending('createdAt');
|
||||
folderCls.include('Folder');
|
||||
if (parentFolderId) {
|
||||
folderCls.equalTo('Folder', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Document',
|
||||
objectId: parentFolderId,
|
||||
});
|
||||
} else {
|
||||
folderCls.doesNotExist('Folder');
|
||||
}
|
||||
folderCls.descending('createdAt');
|
||||
folderCls.limit(limit);
|
||||
folderCls.skip(skip);
|
||||
const res = await folderCls.find({ useMasterKey: true });
|
||||
if (res && res.length > 0) {
|
||||
const parseRes = JSON.parse(JSON.stringify(res));
|
||||
const folderlist = parseRes.map(x => ({
|
||||
objectId: x.objectId,
|
||||
folderName: x.Name,
|
||||
parentFolderId: x?.Folder?.objectId || '',
|
||||
parentFolderName: x?.Folder?.Name || '',
|
||||
createdAt: x.createdAt,
|
||||
updatedAt: x.updatedAt,
|
||||
}));
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_get_Folder_list',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
return response.json({ result: folderlist });
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_get_Folder_list',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
return response.json({ result: [] });
|
||||
}
|
||||
} else {
|
||||
return response.status(405).json({ error: 'Invalid API Token!' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import axios from 'axios';
|
||||
import { replaceMailVaribles } from '../../../../Utils.js';
|
||||
|
||||
export default async function resendMail(request, response) {
|
||||
try {
|
||||
const reqToken = request.headers['x-api-token'];
|
||||
const docId = request.body.document_id;
|
||||
const userMail = request.body.email;
|
||||
const email_subject = request.body.email_subject;
|
||||
const email_body = request.body.email_body;
|
||||
const baseUrl = new URL(process.env.SERVER_URL);
|
||||
|
||||
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');
|
||||
const token = await tokenQuery.first({ useMasterKey: true });
|
||||
if (token !== undefined) {
|
||||
// Valid Token then proceed request
|
||||
const parseUser = JSON.parse(JSON.stringify(token));
|
||||
const userPtr = {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: parseUser.userId.objectId,
|
||||
};
|
||||
|
||||
const docQuery = new Parse.Query('contracts_Document');
|
||||
docQuery.equalTo('objectId', docId);
|
||||
docQuery.equalTo('CreatedBy', userPtr);
|
||||
docQuery.include('Signers,ExtUserPtr');
|
||||
docQuery.notEqualTo('IsCompleted', true);
|
||||
docQuery.notEqualTo('IsDeclined', true);
|
||||
docQuery.notEqualTo('IsArchive', true);
|
||||
docQuery.greaterThanOrEqualTo('ExpiryDate', new Date());
|
||||
docQuery.exists('SignedUrl');
|
||||
|
||||
const resDoc = await docQuery.first({ useMasterKey: true });
|
||||
// console.log("resDoc ",resDoc)
|
||||
if (resDoc) {
|
||||
const _resDoc = resDoc.toJSON();
|
||||
const contact = _resDoc.Signers.find(x => x.Email === userMail);
|
||||
if (contact) {
|
||||
try {
|
||||
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
|
||||
let url = `${process.env.SERVER_URL}/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 = contact.objectId;
|
||||
const hostUrl = baseUrl.origin;
|
||||
const title = _resDoc.Name;
|
||||
const receiverMail = contact.Email;
|
||||
//encode this url value `${response.id}/${receiverMail}/${objectId}` to base64 using `btoa` function
|
||||
const encodeBase64 = btoa(`${_resDoc.objectId}/${receiverMail}/${objectId}`);
|
||||
let signPdf = `${hostUrl}/login/${encodeBase64}`;
|
||||
const openSignUrl = 'https://www.opensignlabs.com/contact-us';
|
||||
const orgName = _resDoc.ExtUserPtr.Company ? _resDoc.ExtUserPtr.Company : '';
|
||||
const newDate = new Date(_resDoc.ExpiryDate.iso);
|
||||
newDate.setDate(newDate.getDate() + 15);
|
||||
const sender = _resDoc.ExtUserPtr.Email;
|
||||
const localExpireDate = newDate.toLocaleDateString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
|
||||
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;'> " +
|
||||
_resDoc.ExtUserPtr.Name +
|
||||
' has requested you to review and sign <strong> ' +
|
||||
title +
|
||||
"</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: title,
|
||||
sender_name: _resDoc.ExtUserPtr.Name,
|
||||
sender_mail: _resDoc.ExtUserPtr.Email,
|
||||
sender_phone: _resDoc.ExtUserPtr.Phone,
|
||||
receiver_name: contact.Name,
|
||||
receiver_email: contact.Email,
|
||||
receiver_phone: contact.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(
|
||||
`${_resDoc.ExtUserPtr.Name} has requested you to sign "${title}"`,
|
||||
email_body,
|
||||
variables
|
||||
);
|
||||
} else {
|
||||
replaceVar = {
|
||||
subject: `${_resDoc.ExtUserPtr.Name} has requested you to sign "${title}"`,
|
||||
body: email_html,
|
||||
};
|
||||
}
|
||||
const subject = replaceVar.subject;
|
||||
const html = replaceVar.body;
|
||||
|
||||
let params = {
|
||||
recipient: contact.Email,
|
||||
subject: subject,
|
||||
from: sender,
|
||||
html: html,
|
||||
extUserId: _resDoc.ExtUserPtr.objectId,
|
||||
};
|
||||
|
||||
const res = await axios.post(url, params, { headers: headers });
|
||||
if (res.data.result && res.data.result.status === 'success') {
|
||||
return response.json({ result: 'mail sent successfully.' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
return response.status(400).json({ error: error.message });
|
||||
}
|
||||
} else {
|
||||
return response.status(404).json({ error: 'user not found.' });
|
||||
}
|
||||
} else {
|
||||
return response.status(404).json({ error: 'document not found.' });
|
||||
}
|
||||
} else {
|
||||
return response.status(405).json({ error: 'Invalid API Token.' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in resendmail', err);
|
||||
return response.status(400).json({ error: err.message || 'Something went wrong.' });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
export default async function updateFolder(request, response) {
|
||||
const apiToken = request.headers['x-api-token'];
|
||||
const folderId = request.params.folder_id;
|
||||
const name = request.body.folderName;
|
||||
const parentFolderId = request.body.parentFolderId;
|
||||
if (!apiToken) {
|
||||
return response.status(400).json({ error: 'Please Provide API Token' });
|
||||
}
|
||||
try {
|
||||
const tokenQuery = new Parse.Query('appToken');
|
||||
tokenQuery.equalTo('token', apiToken);
|
||||
tokenQuery.include('userId');
|
||||
const token = await tokenQuery.first({ useMasterKey: true });
|
||||
if (token !== undefined) {
|
||||
// Valid Token then proceed request
|
||||
const parseUser = JSON.parse(JSON.stringify(token));
|
||||
const userPtr = {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: parseUser.userId.objectId,
|
||||
};
|
||||
|
||||
const folderCls = new Parse.Query('contracts_Document');
|
||||
folderCls.equalTo('CreatedBy', userPtr);
|
||||
folderCls.equalTo('objectId', folderId);
|
||||
folderCls.equalTo('Type', 'Folder');
|
||||
folderCls.notEqualTo('IsArchive', true);
|
||||
|
||||
const res = await folderCls.first({ useMasterKey: true });
|
||||
if (res) {
|
||||
const updateFolder = new Parse.Object('contracts_Document');
|
||||
updateFolder.id = res.id;
|
||||
updateFolder.set('Name', name);
|
||||
if (parentFolderId) {
|
||||
updateFolder.set('Folder', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Document',
|
||||
objectId: parentFolderId,
|
||||
});
|
||||
}
|
||||
const updateRes = await updateFolder.save(null, { useMasterKey: true });
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_update_folder',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
return response.json({ objectId: updateRes.id, updatedAt: updateRes.updatedAt });
|
||||
} else {
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_update_folder',
|
||||
properties: { response_code: 404 },
|
||||
});
|
||||
}
|
||||
return response.status(404).json({ error: 'folder not found.' });
|
||||
}
|
||||
} else {
|
||||
return response.status(405).json({ error: 'Invalid API Token!' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,10 @@ import getInvoices from './parsefunction/getInvoices.js';
|
||||
import getPayments from './parsefunction/getPayments.js';
|
||||
import getSubscriptions from './parsefunction/getSubscriptions.js';
|
||||
import TenantAterFind from './parsefunction/TenantAfterFind.js';
|
||||
import saveSubscriptio from './parsefunction/saveSubscription.js';
|
||||
import saveSubscription from './parsefunction/saveSubscription.js';
|
||||
import VerifyEmail from './parsefunction/VerifyEmail.js';
|
||||
import encryptedpdf from './parsefunction/encryptedPdf.js';
|
||||
import { getSignedUrl } from './parsefunction/getSignedUrl.js';
|
||||
|
||||
Parse.Cloud.define('AddUserToRole', addUserToGroups);
|
||||
Parse.Cloud.define('UserGroups', getUserGroups);
|
||||
@@ -70,4 +73,7 @@ Parse.Cloud.afterFind('contracts_Document', DocumentBeforeFind);
|
||||
Parse.Cloud.afterFind('contracts_Template', TemplateAfterFind);
|
||||
Parse.Cloud.afterFind('contracts_Signature', SignatureAfterFind);
|
||||
Parse.Cloud.afterFind('partners_Tenant', TenantAterFind);
|
||||
Parse.Cloud.define('savesubscription', saveSubscriptio);
|
||||
Parse.Cloud.define('savesubscription', saveSubscription);
|
||||
Parse.Cloud.define('verifyemail', VerifyEmail);
|
||||
Parse.Cloud.define('encryptedpdf', encryptedpdf);
|
||||
Parse.Cloud.define('getsignedurl', getSignedUrl);
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import { useLocal } from '../../Utils.js';
|
||||
import getPresignedUrl from './getSignedUrl.js';
|
||||
|
||||
async function DocumentAfterFind(request) {
|
||||
if (request.objects.length === 1) {
|
||||
if (request.objects) {
|
||||
const obj = request.objects[0];
|
||||
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
|
||||
const Url = obj?.get('URL') && obj?.get('URL');
|
||||
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
|
||||
if (SignedUrl) {
|
||||
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
|
||||
if (useLocal !== 'true') {
|
||||
if (request.objects.length === 1) {
|
||||
if (request.objects) {
|
||||
const obj = request.objects[0];
|
||||
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
|
||||
const Url = obj?.get('URL') && obj?.get('URL');
|
||||
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
|
||||
if (SignedUrl) {
|
||||
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
|
||||
}
|
||||
if (Url) {
|
||||
obj.set('URL', getPresignedUrl(Url));
|
||||
}
|
||||
if (certificateUrl) {
|
||||
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
|
||||
}
|
||||
return [obj];
|
||||
}
|
||||
if (Url) {
|
||||
obj.set('URL', getPresignedUrl(Url));
|
||||
}
|
||||
if (certificateUrl) {
|
||||
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
|
||||
}
|
||||
|
||||
return [obj];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ async function DocumentAftersave(request) {
|
||||
console.log('new entry is insert in contracts_Document');
|
||||
const createdAt = request.object.get('createdAt');
|
||||
const Folder = request.object.get('Type');
|
||||
const ip = request?.headers?.['x-real-ip'] || '';
|
||||
if (createdAt && Folder === undefined) {
|
||||
// console.log("IN If condition")
|
||||
const TimeToCompleteDays = request.object.get('TimeToCompleteDays');
|
||||
@@ -12,18 +13,30 @@ async function DocumentAftersave(request) {
|
||||
const documentQuery = new Parse.Query('contracts_Document');
|
||||
const updateQuery = await documentQuery.get(request.object.id, { useMasterKey: true });
|
||||
updateQuery.set('ExpiryDate', ExpiryDate);
|
||||
updateQuery.set('OriginIp', ip);
|
||||
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
|
||||
if (AutoReminder) {
|
||||
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
|
||||
const ReminderDate = new Date(createdAt);
|
||||
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
|
||||
updateQuery.set('NextReminderDate', ReminderDate);
|
||||
}
|
||||
await updateQuery.save(null, { useMasterKey: true });
|
||||
} else if (createdAt && Folder === 'AIDoc') {
|
||||
const TimeToCompleteDays = request.object.get('TimeToCompleteDays');
|
||||
const ExpiryDate = new Date(createdAt);
|
||||
// console.log("ExpiryDate")
|
||||
// console.log(ExpiryDate)
|
||||
ExpiryDate.setDate(ExpiryDate.getDate() + TimeToCompleteDays);
|
||||
// console.log("ExpiryDate date after update")
|
||||
// console.log(ExpiryDate)
|
||||
const documentQuery = new Parse.Query('contracts_Document');
|
||||
const updateQuery = await documentQuery.get(request.object.id, { useMasterKey: true });
|
||||
updateQuery.set('ExpiryDate', ExpiryDate);
|
||||
updateQuery.set('OriginIp', ip);
|
||||
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
|
||||
if (AutoReminder) {
|
||||
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
|
||||
const ReminderDate = new Date(createdAt);
|
||||
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
|
||||
updateQuery.set('NextReminderDate', ReminderDate);
|
||||
}
|
||||
await updateQuery.save(null, { useMasterKey: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ async function DocumentBeforesave(request) {
|
||||
} catch (error) {
|
||||
console.log('Error updating document count in contracts_users: ' + error.message);
|
||||
}
|
||||
if (document?.get('Signers') && document.get('Signers').length > 0) {
|
||||
document.set('DocSentAt', new Date());
|
||||
document.save(null, { useMasterKey: true });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in document beforesave', err.message);
|
||||
|
||||
@@ -26,7 +26,7 @@ export default async function GetTemplate(request) {
|
||||
if (res) {
|
||||
// console.log("res ",res)
|
||||
const acl = res.getACL();
|
||||
console.log("acl", acl.getReadAccess(userId))
|
||||
// console.log("acl", acl.getReadAccess(userId))
|
||||
if (acl && acl.getReadAccess(userId)) {
|
||||
return res;
|
||||
} else {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { useLocal } from '../../Utils.js';
|
||||
import getPresignedUrl from './getSignedUrl.js';
|
||||
|
||||
async function SignatureAfterFind(request) {
|
||||
if (request.objects.length === 1) {
|
||||
if (request.objects) {
|
||||
const obj = request.objects[0];
|
||||
const ImageURL = obj?.get('ImageURL') && obj?.get('ImageURL');
|
||||
const Initials = obj?.get('Initials') && obj?.get('Initials');
|
||||
|
||||
if (ImageURL) {
|
||||
obj.set('ImageURL', getPresignedUrl(ImageURL));
|
||||
if (useLocal !== 'true') {
|
||||
if (request.objects.length === 1) {
|
||||
if (request.objects) {
|
||||
const obj = request.objects[0];
|
||||
const ImageURL = obj?.get('ImageURL') && obj?.get('ImageURL');
|
||||
const Initials = obj?.get('Initials') && obj?.get('Initials');
|
||||
if (ImageURL) {
|
||||
obj.set('ImageURL', getPresignedUrl(ImageURL));
|
||||
}
|
||||
if (Initials) {
|
||||
obj.set('Initials', getPresignedUrl(Initials));
|
||||
}
|
||||
return [obj];
|
||||
}
|
||||
|
||||
if (Initials) {
|
||||
obj.set('Initials', getPresignedUrl(Initials));
|
||||
}
|
||||
|
||||
return [obj];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import { useLocal } from '../../Utils.js';
|
||||
import getPresignedUrl from './getSignedUrl.js';
|
||||
|
||||
async function TemplateAfterFind(request) {
|
||||
if (request.objects.length === 1) {
|
||||
if (request.objects) {
|
||||
const obj = request.objects[0];
|
||||
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
|
||||
const Url = obj?.get('URL') && obj?.get('URL');
|
||||
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
|
||||
if (SignedUrl) {
|
||||
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
|
||||
}
|
||||
if (Url) {
|
||||
obj.set('URL', getPresignedUrl(Url));
|
||||
}
|
||||
if (certificateUrl) {
|
||||
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
|
||||
}
|
||||
if (useLocal !== 'true') {
|
||||
if (request.objects.length === 1) {
|
||||
if (request.objects) {
|
||||
const obj = request.objects[0];
|
||||
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
|
||||
const Url = obj?.get('URL') && obj?.get('URL');
|
||||
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
|
||||
if (SignedUrl) {
|
||||
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
|
||||
}
|
||||
if (Url) {
|
||||
obj.set('URL', getPresignedUrl(Url));
|
||||
}
|
||||
if (certificateUrl) {
|
||||
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
|
||||
}
|
||||
|
||||
return [obj];
|
||||
return [obj];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,14 @@ export default async function TemplateAfterSave(request) {
|
||||
console.log('new entry is insert in contracts_Template');
|
||||
// update acl of New Document If There are signers present in array
|
||||
const signers = request.object.get('Signers');
|
||||
|
||||
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
|
||||
if (AutoReminder) {
|
||||
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
|
||||
const ReminderDate = new Date(request?.object?.get('createdAt'));
|
||||
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
|
||||
request.object.set('NextReminderDate', ReminderDate);
|
||||
await request.object.save(null, { useMasterKey: true });
|
||||
}
|
||||
if (signers && signers.length > 0) {
|
||||
await updateAclDoc(request.object.id);
|
||||
} else {
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { useLocal } from '../../Utils.js';
|
||||
import getPresignedUrl from './getSignedUrl.js';
|
||||
|
||||
async function TenantAterFind(request) {
|
||||
if (request.objects.length === 1) {
|
||||
if (request.objects) {
|
||||
const obj = request.objects[0];
|
||||
const Logo = obj?.get('Logo') && obj?.get('Logo');
|
||||
if (Logo) {
|
||||
obj.set('Logo', getPresignedUrl(Logo));
|
||||
if (useLocal !== 'true') {
|
||||
if (request.objects.length === 1) {
|
||||
if (request.objects) {
|
||||
const obj = request.objects[0];
|
||||
const Logo = obj?.get('Logo') && obj?.get('Logo');
|
||||
if (Logo) {
|
||||
obj.set('Logo', getPresignedUrl(Logo));
|
||||
}
|
||||
return [obj];
|
||||
}
|
||||
return [obj];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { useLocal } from '../../Utils.js';
|
||||
import getPresignedUrl from './getSignedUrl.js';
|
||||
|
||||
async function UserAfterFind(request) {
|
||||
if (request.objects.length === 1) {
|
||||
if (request.objects) {
|
||||
const obj = request.objects[0];
|
||||
const ProfilePic = obj?.get('ProfilePic') && obj?.get('ProfilePic');
|
||||
|
||||
if (ProfilePic) {
|
||||
obj.set('ProfilePic', getPresignedUrl(ProfilePic));
|
||||
if (useLocal !== 'true') {
|
||||
if (request.objects.length === 1) {
|
||||
if (request.objects) {
|
||||
const obj = request.objects[0];
|
||||
const ProfilePic = obj?.get('ProfilePic') && obj?.get('ProfilePic');
|
||||
if (ProfilePic) {
|
||||
obj.set('ProfilePic', getPresignedUrl(ProfilePic));
|
||||
}
|
||||
return [obj];
|
||||
}
|
||||
|
||||
return [obj];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
export default async function VerifyEmail(request) {
|
||||
try {
|
||||
if (!request?.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
} else {
|
||||
let otpN = request.params.otp;
|
||||
let otp = parseInt(otpN);
|
||||
let email = request.params.email;
|
||||
|
||||
//checking otp is correct or not which already save in defaultdata_Otp class
|
||||
const checkOtp = new Parse.Query('defaultdata_Otp');
|
||||
checkOtp.equalTo('Email', email);
|
||||
checkOtp.equalTo('OTP', otp);
|
||||
|
||||
const res = await checkOtp.first({ useMasterKey: true });
|
||||
if (res) {
|
||||
// Fetch the user by their objectId
|
||||
const isEmailVerified = request?.user?.get('emailVerified');
|
||||
if (isEmailVerified) {
|
||||
return { message: 'Email is already verified.' };
|
||||
} else {
|
||||
const userQuery = new Parse.Query(Parse.User);
|
||||
const user = await userQuery.get(request?.user.id, {
|
||||
sessionToken: request?.user.getSessionToken(),
|
||||
});
|
||||
|
||||
// Update the emailVerified field to true
|
||||
user.set('emailVerified', true);
|
||||
// Save the user object
|
||||
const res = await user.save(null, { useMasterKey: true });
|
||||
if (res) {
|
||||
return { message: 'Email is verified.' };
|
||||
} else {
|
||||
const error = new Error('Something went wrong, please try again later!');
|
||||
error.code = 400; // Set the error code (e.g., 400 for bad request)
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const error = new Error('OTP is invalid.');
|
||||
error.code = 400; // Set the error code (e.g., 400 for bad request)
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err ', err.code + ' ' + err.message);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ import axios from 'axios';
|
||||
export default async function callWebhook(request) {
|
||||
const event = request.params.event;
|
||||
const body = request.params.body;
|
||||
const docId = body.objectId;
|
||||
const contactId = request.params.contactId;
|
||||
const serverUrl = process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
@@ -13,8 +15,44 @@ export default async function callWebhook(request) {
|
||||
|
||||
const userId = userRes.data && userRes.data.objectId;
|
||||
if (userId) {
|
||||
if (event === 'viewed' && contactId) {
|
||||
const docQuery = new Parse.Query('contracts_Document');
|
||||
const res = await docQuery.get(docId, { useMasterKey: true });
|
||||
if (res) {
|
||||
const _res = res.toJSON();
|
||||
const userPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: contactId,
|
||||
};
|
||||
const date = new Date().toISOString();
|
||||
const obj = {
|
||||
UserPtr: userPtr,
|
||||
SignedUrl: _res.SignedUrl,
|
||||
Activity: 'Viewed',
|
||||
ipAddress: request.headers['x-real-ip'],
|
||||
ViewedOn: date,
|
||||
};
|
||||
const isUserExist = _res?.AuditTrail?.some(
|
||||
x => x.UserPtr.objectId === contactId && x?.ViewedOn
|
||||
);
|
||||
if (!isUserExist) {
|
||||
const updateDoc = new Parse.Object('contracts_Document');
|
||||
updateDoc.id = res.id;
|
||||
if (_res?.AuditTrail && _res?.AuditTrail?.length > 0) {
|
||||
updateDoc.set('AuditTrail', [..._res?.AuditTrail, obj]);
|
||||
} else {
|
||||
updateDoc.set('AuditTrail', [obj]);
|
||||
}
|
||||
await updateDoc.save(null, { useMasterKey: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
const docQuery = new Parse.Query('contracts_Document');
|
||||
const resDoc = await docQuery.get(docId, { useMasterKey: true });
|
||||
const extendcls = new Parse.Query('contracts_Users');
|
||||
extendcls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
|
||||
extendcls.equalTo('objectId', resDoc.get('ExtUserPtr')?.id);
|
||||
// extendcls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
|
||||
const res = await extendcls.first({ useMasterKey: true });
|
||||
if (res) {
|
||||
const extUser = JSON.parse(JSON.stringify(res));
|
||||
@@ -39,11 +77,11 @@ export default async function callWebhook(request) {
|
||||
});
|
||||
webhook.save(null, { useMasterKey: true });
|
||||
} catch (err) {
|
||||
console.log('err save in contracts_Webhook', err);
|
||||
console.log('err save in contracts_Webhook', err.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('Err send data to webhook', err);
|
||||
console.log('Err send data to webhook', err.message);
|
||||
try {
|
||||
const webhook = new Parse.Object('contracts_Webhook');
|
||||
webhook.set('Log', err?.status);
|
||||
@@ -54,7 +92,7 @@ export default async function callWebhook(request) {
|
||||
});
|
||||
webhook.save(null, { useMasterKey: true });
|
||||
} catch (err) {
|
||||
console.log('err save in contracts_Webhook', err);
|
||||
console.log('err save in contracts_Webhook', err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { PostHog } from 'posthog-node';
|
||||
const ph_project_api_key = process.env.PH_PROJECT_API_KEY;
|
||||
const client = ph_project_api_key ? new PostHog(ph_project_api_key) : '';
|
||||
export default async function encryptedpdf(request) {
|
||||
const email = request.params.email;
|
||||
if (client) {
|
||||
client?.capture({
|
||||
distinctId: email,
|
||||
event: 'encrypted_pdf_error',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
return { message: 'success' };
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import AWS from 'aws-sdk';
|
||||
import { useLocal } from '../../Utils.js';
|
||||
const credentials = {
|
||||
accessKeyId: process.env.DO_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.DO_SECRET_ACCESS_KEY,
|
||||
@@ -24,3 +25,25 @@ export default function getPresignedUrl(url) {
|
||||
});
|
||||
return presignedGETURL;
|
||||
}
|
||||
|
||||
export async function getSignedUrl(request) {
|
||||
try {
|
||||
const url = request.params.url;
|
||||
if (!request?.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
} else {
|
||||
if (useLocal !== 'true') {
|
||||
const presignedUrl = getPresignedUrl(url);
|
||||
return presignedUrl;
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('error in getsignedurl', err);
|
||||
const code = err.code || 400;
|
||||
const msg = err.message;
|
||||
const error = new Parse.Error(code, msg);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,41 +2,96 @@ import axios from 'axios';
|
||||
const serverUrl = process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
export default async function getSubscription(request) {
|
||||
const extUserId = request.params.extUserId;
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': request.headers['sessiontoken'],
|
||||
},
|
||||
});
|
||||
const userId = userRes.data && userRes.data.objectId;
|
||||
if (userId) {
|
||||
const subcriptionCls = new Parse.Query('contracts_Users');
|
||||
const exUser = await subcriptionCls.get(extUserId, { useMasterKey: true });
|
||||
if (exUser) {
|
||||
const subscriptionCls = new Parse.Query('contracts_Subscriptions');
|
||||
subscriptionCls.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: exUser.get('TenantId').id,
|
||||
});
|
||||
subscriptionCls.descending('createdAt');
|
||||
const subcripitions = await subscriptionCls.first({ useMasterKey: true });
|
||||
if (subcripitions) {
|
||||
const _subcripitions = JSON.parse(JSON.stringify(subcripitions));
|
||||
return { status: 'success', result: _subcripitions };
|
||||
const extUserId = request.params.extUserId || '';
|
||||
const contactId = request.params.contactId || '';
|
||||
|
||||
if (extUserId) {
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': request.headers['sessiontoken'],
|
||||
},
|
||||
});
|
||||
const userId = userRes.data && userRes.data.objectId;
|
||||
if (userId) {
|
||||
const extCls = new Parse.Query('contracts_Users');
|
||||
const exUser = await extCls.get(extUserId, { useMasterKey: true });
|
||||
if (exUser) {
|
||||
const subscriptionCls = new Parse.Query('contracts_Subscriptions');
|
||||
subscriptionCls.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: exUser.get('TenantId').id,
|
||||
});
|
||||
subscriptionCls.descending('createdAt');
|
||||
const subcripitions = await subscriptionCls.first({ useMasterKey: true });
|
||||
if (subcripitions) {
|
||||
const _subcripitions = JSON.parse(JSON.stringify(subcripitions));
|
||||
return { status: 'success', result: _subcripitions };
|
||||
} else {
|
||||
return { status: 'success', result: {} };
|
||||
}
|
||||
} else {
|
||||
return { status: 'success', result: {} };
|
||||
return { status: 'error', result: 'User not found!' };
|
||||
}
|
||||
} else {
|
||||
return { status: 'error', result: 'User not found!' };
|
||||
return { status: 'error', result: 'Invalid session token!' };
|
||||
}
|
||||
} else {
|
||||
return { status: 'error', result: 'Invalid session token!' };
|
||||
} catch (err) {
|
||||
console.log('Err in get subscription', err.message);
|
||||
return { status: 'error', result: err.message };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err in get subscription', err.message);
|
||||
return { status: 'error', result: err.message };
|
||||
} else if (contactId) {
|
||||
try {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': request.headers['sessiontoken'],
|
||||
},
|
||||
});
|
||||
|
||||
const userId = userRes.data && userRes.data.objectId;
|
||||
if (userId) {
|
||||
const contactCls = new Parse.Query('contracts_Contactbook');
|
||||
const contactUser = await contactCls.get(contactId, { useMasterKey: true });
|
||||
if (contactUser) {
|
||||
const subscriptionCls = new Parse.Query('contracts_Subscriptions');
|
||||
subscriptionCls.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: contactUser.get('TenantId').id,
|
||||
});
|
||||
subscriptionCls.descending('createdAt');
|
||||
const subcripitions = await subscriptionCls.first({ useMasterKey: true });
|
||||
|
||||
if (subcripitions) {
|
||||
const _subcripitions = JSON.parse(JSON.stringify(subcripitions));
|
||||
if (_subcripitions.PlanCode === 'freeplan') {
|
||||
return { status: 'success', result: { isSubscribed: false, plan: 'freeplan' } };
|
||||
} else if (_subcripitions?.Next_billing_date?.iso) {
|
||||
if (new Date(_subcripitions.Next_billing_date.iso) > new Date()) {
|
||||
return { status: 'success', result: { isSubscribed: true } };
|
||||
} else {
|
||||
return { status: 'success', result: { isSubscribed: false } };
|
||||
}
|
||||
} else {
|
||||
return { status: 'success', result: { isSubscribed: false } };
|
||||
}
|
||||
} else {
|
||||
return { status: 'success', result: { isSubscribed: false } };
|
||||
}
|
||||
} else {
|
||||
return { status: 'error', result: 'User not found!' };
|
||||
}
|
||||
} else {
|
||||
return { status: 'error', result: 'Invalid session token!' };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err in get subscription2', err.message);
|
||||
return { status: 'error', result: err.message };
|
||||
}
|
||||
} else {
|
||||
return { status: 'error', result: 'Invalid session token!' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ async function getUserDetails(request) {
|
||||
userQuery.equalTo('Email', request.params.email);
|
||||
userQuery.include('TenantId');
|
||||
userQuery.include('UserId');
|
||||
userQuery.exclude('google_refresh_token')
|
||||
if (userId) {
|
||||
userQuery.equalTo('CreatedBy', { __type: 'Pointer', className: '_User', objectId: userId });
|
||||
}
|
||||
|
||||
@@ -15,23 +15,44 @@ export default async function GenerateCertificate(docDetails) {
|
||||
const title = 25;
|
||||
const subtitle = 20;
|
||||
const text = 14;
|
||||
const timeText = 11;
|
||||
const textKeyColor = rgb(0.12, 0.12, 0.12);
|
||||
const textValueColor = rgb(0.3, 0.3, 0.3);
|
||||
const completedAt = new Date(docDetails.updatedAt);
|
||||
const completedAt = new Date();
|
||||
const completedUTCtime = completedAt.toUTCString();
|
||||
const signersCount = docDetails?.Signers?.length || 1;
|
||||
const createdAt = new Date();
|
||||
const createdUTCTime = createdAt.toUTCString();
|
||||
const createDate = 'Generated On ' + createdUTCTime;
|
||||
const generateAt = new Date();
|
||||
const generatedUTCTime = generateAt.toUTCString();
|
||||
const generatedOn = 'Generated On ' + generatedUTCTime;
|
||||
const OriginIp = docDetails?.OriginIp || '';
|
||||
const company = docDetails?.ExtUserPtr?.Company || '';
|
||||
const createdAt = docDetails?.DocSentAt?.iso || docDetails.createdAt;
|
||||
const auditTrail =
|
||||
docDetails.AuditTrail?.length > 1
|
||||
docDetails?.Signers?.length > 0
|
||||
? docDetails.AuditTrail.map(x => {
|
||||
const data = docDetails.Signers.find(y => y.objectId === x.UserPtr.objectId);
|
||||
return { ...data, ipAddress: x.ipAddress };
|
||||
return {
|
||||
...data,
|
||||
ipAddress: x.ipAddress,
|
||||
SignedOn: x?.SignedOn || generatedUTCTime,
|
||||
ViewedOn: x?.ViewedOn || x?.SignedOn || generatedUTCTime,
|
||||
Signature: x?.Signature || '',
|
||||
};
|
||||
})
|
||||
: [{ ...docDetails.ExtUserPtr, ipAddress: docDetails?.AuditTrail[0].ipAddress }];
|
||||
: [
|
||||
{
|
||||
...docDetails.ExtUserPtr,
|
||||
ipAddress: docDetails?.AuditTrail[0].ipAddress,
|
||||
SignedOn: docDetails?.AuditTrail[0]?.SignedOn || generatedUTCTime,
|
||||
ViewedOn:
|
||||
docDetails?.AuditTrail[0]?.ViewedOn ||
|
||||
docDetails?.AuditTrail[0]?.SignedOn ||
|
||||
generatedUTCTime,
|
||||
Signature: docDetails?.AuditTrail[0]?.Signature || '',
|
||||
},
|
||||
];
|
||||
|
||||
const half = width / 2;
|
||||
// Draw a border
|
||||
page.drawRectangle({
|
||||
x: startX,
|
||||
@@ -48,7 +69,7 @@ export default async function GenerateCertificate(docDetails) {
|
||||
height: 25,
|
||||
});
|
||||
|
||||
page.drawText(createDate, {
|
||||
page.drawText(generatedOn, {
|
||||
x: 320,
|
||||
y: 810,
|
||||
size: 12,
|
||||
@@ -127,8 +148,7 @@ export default async function GenerateCertificate(docDetails) {
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('Completed on :', {
|
||||
page.drawText('Created on :', {
|
||||
x: 30,
|
||||
y: 625,
|
||||
size: text,
|
||||
@@ -136,15 +156,14 @@ export default async function GenerateCertificate(docDetails) {
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(`${completedUTCtime}`, {
|
||||
x: 120,
|
||||
page.drawText(`${new Date(createdAt).toUTCString()}`, {
|
||||
x: 105,
|
||||
y: 625,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('Signers :', {
|
||||
page.drawText('Completed on :', {
|
||||
x: 30,
|
||||
y: 605,
|
||||
size: text,
|
||||
@@ -152,35 +171,102 @@ export default async function GenerateCertificate(docDetails) {
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(`${completedUTCtime}`, {
|
||||
x: 125,
|
||||
y: 605,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Signers :', {
|
||||
x: 30,
|
||||
y: 585,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(`${signersCount}`, {
|
||||
x: 80,
|
||||
y: 605,
|
||||
y: 585,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Document originator', {
|
||||
x: 30,
|
||||
y: 565,
|
||||
size: 17,
|
||||
font: timesRomanFont,
|
||||
color: titleColor,
|
||||
});
|
||||
page.drawText('Name :', {
|
||||
x: 60,
|
||||
y: 545,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText(`${docDetails.ExtUserPtr.Name}`, {
|
||||
x: 105,
|
||||
y: 545,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Email :', {
|
||||
x: 60,
|
||||
y: 525,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText(`${docDetails.ExtUserPtr.Email}`, {
|
||||
x: 105,
|
||||
y: 525,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('IP address :', {
|
||||
x: 60,
|
||||
y: 505,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText(`${OriginIp}`, {
|
||||
x: 130,
|
||||
y: 505,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawLine({
|
||||
start: { x: 30, y: 565 },
|
||||
end: { x: width - 30, y: 565 },
|
||||
start: { x: 30, y: 495 },
|
||||
end: { x: width - 30, y: 495 },
|
||||
color: rgb(0.12, 0.12, 0.12),
|
||||
thickness: 0.5,
|
||||
});
|
||||
page.drawText('Recipients', {
|
||||
x: 30,
|
||||
y: 575,
|
||||
size: subtitle,
|
||||
font: timesRomanFont,
|
||||
color: titleColor,
|
||||
});
|
||||
let yPosition1 = 550;
|
||||
let yPosition2 = 530;
|
||||
let yPosition3 = 510;
|
||||
let yPosition4 = 500;
|
||||
auditTrail.forEach(x => {
|
||||
page.drawText('Name :', {
|
||||
let yPosition1 = 475;
|
||||
let yPosition2 = 455;
|
||||
let yPosition3 = 435;
|
||||
let yPosition4 = 415;
|
||||
let yPosition5 = 395;
|
||||
let yPosition6 = 360;
|
||||
auditTrail.slice(0, 3).forEach(async (x, i) => {
|
||||
const embedPng = x.Signature ? await pdfDoc.embedPng(x.Signature) : '';
|
||||
page.drawText(`Signer ${i + 1}`, {
|
||||
x: 30,
|
||||
y: yPosition1,
|
||||
size: subtitle,
|
||||
font: timesRomanFont,
|
||||
color: titleColor,
|
||||
});
|
||||
page.drawText('Name :', {
|
||||
x: 30,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
@@ -188,15 +274,31 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText(x?.Name, {
|
||||
x: 75,
|
||||
y: yPosition1,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('Viewed on :', {
|
||||
x: half + 55,
|
||||
y: yPosition2,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(`${new Date(x.ViewedOn).toUTCString()}`, {
|
||||
x: half + 112,
|
||||
y: yPosition2,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('Email :', {
|
||||
x: 30,
|
||||
y: yPosition2,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
@@ -204,41 +306,271 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText(x?.Email, {
|
||||
x: 75,
|
||||
y: yPosition2,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('Accessed from :', {
|
||||
page.drawText('Signed on :', {
|
||||
x: half + 55,
|
||||
y: yPosition3 + 5,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(`${new Date(x.SignedOn).toUTCString()}`, {
|
||||
x: half + 108,
|
||||
y: yPosition3 + 5,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('IP address :', {
|
||||
x: 30,
|
||||
y: yPosition3,
|
||||
y: yPosition4,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(x?.ipAddress, {
|
||||
x: 125,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
x: 100,
|
||||
y: yPosition4,
|
||||
size: 13,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Security level :', {
|
||||
x: half + 55,
|
||||
y: yPosition4 + 10,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(`Email, OTP Auth`, {
|
||||
x: half + 125,
|
||||
y: yPosition4 + 10,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('Signature :', {
|
||||
x: 30,
|
||||
y: yPosition5,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawRectangle({
|
||||
x: 98,
|
||||
y: yPosition5 - 30,
|
||||
width: 104,
|
||||
height: 44,
|
||||
borderColor: rgb(0.22, 0.18, 0.47),
|
||||
borderWidth: 1,
|
||||
});
|
||||
if (embedPng) {
|
||||
page.drawImage(embedPng, {
|
||||
x: 100,
|
||||
y: yPosition5 - 27,
|
||||
width: 100,
|
||||
height: 40,
|
||||
});
|
||||
}
|
||||
page.drawLine({
|
||||
start: { x: 30, y: yPosition4 },
|
||||
end: { x: width - 30, y: yPosition4 },
|
||||
start: { x: 30, y: yPosition6 },
|
||||
end: { x: width - 30, y: yPosition6 },
|
||||
color: rgb(0.12, 0.12, 0.12),
|
||||
thickness: 0.5,
|
||||
});
|
||||
|
||||
yPosition1 = yPosition4 - 20;
|
||||
yPosition1 = yPosition6 - 20;
|
||||
yPosition2 = yPosition1 - 20;
|
||||
yPosition3 = yPosition2 - 20;
|
||||
yPosition4 = yPosition4 - 70;
|
||||
yPosition4 = yPosition3 - 20;
|
||||
yPosition5 = yPosition4 - 20;
|
||||
yPosition6 = yPosition6 - 140;
|
||||
});
|
||||
|
||||
if (auditTrail.length > 3) {
|
||||
let currentPageIndex = 1;
|
||||
let currentPage = page;
|
||||
auditTrail.slice(3).forEach(async (x, i) => {
|
||||
const embedPng = x.Signature ? await pdfDoc.embedPng(x.Signature) : '';
|
||||
|
||||
// Calculate remaining space on current page
|
||||
const remainingSpace = yPosition6;
|
||||
|
||||
// If there's not enough space for the next entry, create a new page
|
||||
if (remainingSpace < 90) {
|
||||
// Adjust the value as needed
|
||||
currentPageIndex++;
|
||||
currentPage = pdfDoc.addPage();
|
||||
currentPage.drawRectangle({
|
||||
x: startX,
|
||||
y: startY,
|
||||
width: width - 2 * startX,
|
||||
height: height - 2 * startY,
|
||||
borderColor: borderColor,
|
||||
borderWidth: 1,
|
||||
});
|
||||
yPosition1 = currentPage.getHeight() - 40;
|
||||
yPosition2 = yPosition1 - 20;
|
||||
yPosition3 = yPosition2 - 20;
|
||||
yPosition4 = yPosition3 - 20;
|
||||
yPosition5 = yPosition4 - 20;
|
||||
yPosition6 = currentPage.getHeight() - 160;
|
||||
}
|
||||
|
||||
currentPage.drawText(`Signer ${4 + i}`, {
|
||||
x: 30,
|
||||
y: yPosition1,
|
||||
size: subtitle,
|
||||
font: timesRomanFont,
|
||||
color: titleColor,
|
||||
});
|
||||
currentPage.drawText('Name :', {
|
||||
x: 30,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(x?.Name, {
|
||||
x: 75,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
currentPage.drawText('Viewed on :', {
|
||||
x: half,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(`${new Date(x.ViewedOn).toUTCString()}`, {
|
||||
x: half + 75,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
currentPage.drawText('Email :', {
|
||||
x: 30,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(x?.Email, {
|
||||
x: 75,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
currentPage.drawText('Signed on :', {
|
||||
x: half,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(`${new Date(x.SignedOn).toUTCString()}`, {
|
||||
x: half + 70,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
currentPage.drawText('IP address :', {
|
||||
x: 30,
|
||||
y: yPosition4,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(x?.ipAddress, {
|
||||
x: 100,
|
||||
y: yPosition4,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
currentPage.drawText('Security level :', {
|
||||
x: half,
|
||||
y: yPosition4,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(`Email, OTP Auth`, {
|
||||
x: half + 90,
|
||||
y: yPosition4,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
currentPage.drawText('Signature :', {
|
||||
x: 30,
|
||||
y: yPosition5,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
currentPage.drawRectangle({
|
||||
x: 98,
|
||||
y: yPosition5 - 27,
|
||||
width: 104,
|
||||
height: 44,
|
||||
borderColor: rgb(0.22, 0.18, 0.47),
|
||||
borderWidth: 1,
|
||||
});
|
||||
if (embedPng) {
|
||||
currentPage.drawImage(embedPng, {
|
||||
x: 100,
|
||||
y: yPosition5 - 25,
|
||||
width: 100,
|
||||
height: 40,
|
||||
});
|
||||
}
|
||||
|
||||
currentPage.drawLine({
|
||||
start: { x: 30, y: yPosition6 },
|
||||
end: { x: width - 30, y: yPosition6 },
|
||||
color: rgb(0.12, 0.12, 0.12),
|
||||
thickness: 0.5,
|
||||
});
|
||||
|
||||
// Update y positions for the next entry
|
||||
yPosition1 = yPosition6 - 20;
|
||||
yPosition2 = yPosition1 - 20;
|
||||
yPosition3 = yPosition2 - 20;
|
||||
yPosition4 = yPosition3 - 20;
|
||||
yPosition5 = yPosition4 - 20;
|
||||
yPosition6 = yPosition6 - 140;
|
||||
});
|
||||
}
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
return pdfBytes;
|
||||
}
|
||||
|
||||
+228
-249
@@ -1,109 +1,124 @@
|
||||
import SignPDF from './SignPDF.min.cjs';
|
||||
import fs from 'node:fs';
|
||||
import axios from 'axios';
|
||||
import { pdflibAddPlaceholder } from './customSignPdf/pdflibplaceholder.min.js';
|
||||
import { SignPdf } from '@signpdf/signpdf';
|
||||
import { P12Signer } from '@signpdf/signer-p12';
|
||||
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { replaceMailVaribles, saveFileUsage } from '../../../Utils.js';
|
||||
import GenerateCertificate from './GenerateCertificate.js';
|
||||
const serverUrl = process.env.SERVER_URL,
|
||||
APPID = process.env.APP_ID,
|
||||
masterKEY = process.env.MASTER_KEY;
|
||||
async function uploadFile(e, a) {
|
||||
masterKEY = process.env.MASTER_KEY,
|
||||
eSignName = 'opensign',
|
||||
eSigncontact = 'hello@opensignlabs.com';
|
||||
async function uploadFile(e, t) {
|
||||
try {
|
||||
var t = fs.readFileSync(a),
|
||||
s = new Parse.File(e, [...t], 'application/pdf'),
|
||||
r = (await s.save({ useMasterKey: !0 }), s.url());
|
||||
return { imageUrl: r };
|
||||
var a = fs.readFileSync(t),
|
||||
r = new Parse.File(e, [...a], 'application/pdf'),
|
||||
i = (await r.save({ useMasterKey: !0 }), r.url());
|
||||
return { imageUrl: i };
|
||||
} catch (e) {
|
||||
console.log('Err ', e), fs.unlinkSync(a);
|
||||
console.log('Err ', e), fs.unlinkSync(t);
|
||||
}
|
||||
}
|
||||
async function updateDoc(t, s, r, i, o, n) {
|
||||
async function updateDoc(a, r, i, n, s, o, l) {
|
||||
try {
|
||||
var l = {
|
||||
UserPtr: { __type: 'Pointer', className: n, objectId: r },
|
||||
SignedUrl: s,
|
||||
Activity: 'Signed',
|
||||
ipAddress: i,
|
||||
};
|
||||
var c,
|
||||
d,
|
||||
p = {
|
||||
UserPtr: { __type: 'Pointer', className: o, objectId: i },
|
||||
SignedUrl: r,
|
||||
Activity: 'Signed',
|
||||
ipAddress: n,
|
||||
SignedOn: new Date(),
|
||||
Signature: l,
|
||||
};
|
||||
let e;
|
||||
var d = (e = o.AuditTrail && 0 < o.AuditTrail.length ? [...o.AuditTrail, l] : [l]).filter(
|
||||
e => 'Signed' === e.Activity
|
||||
);
|
||||
let a = !1;
|
||||
!((o.Signers && 0 < o.Signers.length && d.length !== o.Signers.length) || !(a = !0));
|
||||
var c = { SignedUrl: s, AuditTrail: e, IsCompleted: a };
|
||||
await axios.put(serverUrl + '/classes/contracts_Document/' + t, c, {
|
||||
var m = (e =
|
||||
s.AuditTrail && 0 < s.AuditTrail.length
|
||||
? (-1 !==
|
||||
(d = (c = JSON.parse(JSON.stringify(s.AuditTrail))).findIndex(
|
||||
e => e.UserPtr.objectId === i && 'Created' !== e.Activity
|
||||
))
|
||||
? (c[d] = { ...c[d], ...p })
|
||||
: c.push(p),
|
||||
c)
|
||||
: [p]).filter(e => 'Signed' === e.Activity);
|
||||
let t = !1;
|
||||
!((s.Signers && 0 < s.Signers.length && m.length !== s.Signers.length) || !(t = !0));
|
||||
var g = { SignedUrl: r, AuditTrail: e, IsCompleted: t };
|
||||
await axios.put(serverUrl + '/classes/contracts_Document/' + a, g, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Master-Key': masterKEY,
|
||||
},
|
||||
});
|
||||
return { isCompleted: a, message: 'success', AuditTrail: e };
|
||||
return { isCompleted: t, message: 'success', AuditTrail: e };
|
||||
} catch (e) {
|
||||
return console.log('update doc err ', e), 'err';
|
||||
}
|
||||
}
|
||||
async function sendCompletedMail(e) {
|
||||
var a = e.url,
|
||||
t = e.doc,
|
||||
s = e.doc.ExtUserPtr,
|
||||
r = t.Name,
|
||||
i = s.Email;
|
||||
let o = `Document "${r}" has been signed by all parties`,
|
||||
n =
|
||||
var t = e.url,
|
||||
a = e.doc,
|
||||
r = e.doc.ExtUserPtr,
|
||||
i = a.Name,
|
||||
n = r.Email;
|
||||
let s = `Document "${i}" has been signed by all parties`,
|
||||
o =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=https://qikinnovation.ams3.digitaloceanspaces.com/logo.png height='50' style='padding:20px'/> </div><div style='padding:2px;font-family:system-ui; background-color: #47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document signed successfully</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document " +
|
||||
`<b>"${r}"</b>` +
|
||||
`<b>"${i}"</b>` +
|
||||
'. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' +
|
||||
s.Email +
|
||||
r.Email +
|
||||
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>';
|
||||
if (e?.isCustomMail)
|
||||
try {
|
||||
var l,
|
||||
d,
|
||||
c,
|
||||
d,
|
||||
p,
|
||||
m,
|
||||
g = new Parse.Query('partners_Tenant');
|
||||
g.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: s.UserId.objectId });
|
||||
const u = await g.first();
|
||||
u &&
|
||||
((l = JSON.parse(JSON.stringify(u)))?.CompletionSubject && (o = l?.CompletionSubject),
|
||||
l?.CompletionBody && (n = l?.CompletionBody),
|
||||
(d = t.ExpiryDate.iso),
|
||||
(c = new Date(d).toLocaleDateString('en-US', {
|
||||
g.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: r.UserId.objectId });
|
||||
const f = await g.first();
|
||||
f &&
|
||||
((l = JSON.parse(JSON.stringify(f)))?.CompletionSubject && (s = l?.CompletionSubject),
|
||||
l?.CompletionBody && (o = l?.CompletionBody),
|
||||
(c = a.ExpiryDate.iso),
|
||||
(d = new Date(c).toLocaleDateString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})),
|
||||
(p = {
|
||||
document_title: r,
|
||||
sender_name: s.Name,
|
||||
sender_mail: s.Email,
|
||||
sender_phone: s.Phone,
|
||||
receiver_name: s.Name,
|
||||
receiver_email: s.Email,
|
||||
receiver_phone: s.Phone,
|
||||
expiry_date: c,
|
||||
company_name: s.Company,
|
||||
document_title: i,
|
||||
sender_name: r.Name,
|
||||
sender_mail: r.Email,
|
||||
sender_phone: r.Phone,
|
||||
receiver_name: r.Name,
|
||||
receiver_email: r.Email,
|
||||
receiver_phone: r.Phone,
|
||||
expiry_date: d,
|
||||
company_name: r.Company,
|
||||
}),
|
||||
(m = replaceMailVaribles(o, n, p)),
|
||||
(o = m.subject),
|
||||
(n = m.body));
|
||||
(m = replaceMailVaribles(s, o, p)),
|
||||
(s = m.subject),
|
||||
(o = m.body));
|
||||
} catch (e) {
|
||||
console.log('error in fetch tenant in signpdf', e.message);
|
||||
}
|
||||
e = {
|
||||
extUserId: s.objectId,
|
||||
url: a,
|
||||
g = {
|
||||
extUserId: r.objectId,
|
||||
url: t,
|
||||
from: 'OpenSign™',
|
||||
recipient: i,
|
||||
subject: o,
|
||||
pdfName: r,
|
||||
html: n,
|
||||
recipient: n,
|
||||
subject: s,
|
||||
pdfName: i,
|
||||
html: o,
|
||||
mailProvider: e.mailProvider,
|
||||
};
|
||||
await axios.post(serverUrl + '/functions/sendmailv3', e, {
|
||||
await axios.post(serverUrl + '/functions/sendmailv3', g, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': APPID,
|
||||
@@ -111,44 +126,41 @@ async function sendCompletedMail(e) {
|
||||
},
|
||||
});
|
||||
}
|
||||
async function sendDoctoWebhook(t, e, a, s) {
|
||||
let r = [];
|
||||
(r = s
|
||||
? { name: s?.Name, email: s?.Email, phone: s?.Phone }
|
||||
: t?.data?.Signers?.map(e => ({ name: e.Name, email: e.Email, phone: e.Phone })) || [
|
||||
{
|
||||
name: t?.data?.ExtUserPtr?.Name,
|
||||
email: t?.data?.ExtUserPtr?.Email,
|
||||
phone: t?.data?.ExtUserPtr?.Phone,
|
||||
},
|
||||
async function sendDoctoWebhook(a, e, t, r, i) {
|
||||
let n = [];
|
||||
(n = r
|
||||
? { name: r?.Name, email: r?.Email, phone: r?.Phone }
|
||||
: a?.Signers?.map(e => ({ name: e.Name, email: e.Email, phone: e.Phone })) || [
|
||||
{ name: a?.ExtUserPtr?.Name, email: a?.ExtUserPtr?.Email, phone: a?.ExtUserPtr?.Phone },
|
||||
]),
|
||||
t.data.ExtUserPtr?.Webhook &&
|
||||
((s =
|
||||
'signed' === a
|
||||
? { signer: r, signedAt: new Date() }
|
||||
: { signers: r, completedAt: new Date() }),
|
||||
(a = {
|
||||
event: a,
|
||||
objectId: t?.data?.objectId,
|
||||
a.ExtUserPtr?.Webhook &&
|
||||
((r =
|
||||
'signed' === t
|
||||
? { signer: n, signedAt: new Date() }
|
||||
: { signers: n, completedAt: new Date() }),
|
||||
(t = {
|
||||
event: t,
|
||||
objectId: a?.objectId,
|
||||
file: e || '',
|
||||
name: t?.data?.Name,
|
||||
note: t?.data?.Note || '',
|
||||
description: t?.data?.Description || '',
|
||||
...s,
|
||||
createdAt: t?.data?.createdAt,
|
||||
...(i ? { certificate: i } : {}),
|
||||
name: a?.Name,
|
||||
note: a?.Note || '',
|
||||
description: a?.Description || '',
|
||||
...r,
|
||||
createdAt: a?.createdAt,
|
||||
}),
|
||||
await axios
|
||||
.post(t?.data?.ExtUserPtr?.Webhook, a, { headers: { 'Content-Type': 'application/json' } })
|
||||
axios
|
||||
.post(a?.ExtUserPtr?.Webhook, t, { headers: { 'Content-Type': 'application/json' } })
|
||||
.then(e => {
|
||||
try {
|
||||
var a = new Parse.Object('contracts_Webhook');
|
||||
a.set('Log', e?.status),
|
||||
a.set('UserId', {
|
||||
var t = new Parse.Object('contracts_Webhook');
|
||||
t.set('Log', e?.status),
|
||||
t.set('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: t.data.ExtUserPtr.UserId.objectId,
|
||||
objectId: a.ExtUserPtr.UserId.objectId,
|
||||
}),
|
||||
a.save(null, { useMasterKey: !0 });
|
||||
t.save(null, { useMasterKey: !0 });
|
||||
} catch (e) {
|
||||
console.log('err save in contracts_Webhook', e.message);
|
||||
}
|
||||
@@ -156,203 +168,170 @@ async function sendDoctoWebhook(t, e, a, s) {
|
||||
.catch(e => {
|
||||
console.log('Err send data to webhook', e.message);
|
||||
try {
|
||||
var a = new Parse.Object('contracts_Webhook');
|
||||
a.set('Log', e?.status),
|
||||
a.set('UserId', {
|
||||
var t = new Parse.Object('contracts_Webhook');
|
||||
t.set('Log', e?.status),
|
||||
t.set('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: t.data.ExtUserPtr.UserId.objectId,
|
||||
objectId: a.ExtUserPtr.UserId.objectId,
|
||||
}),
|
||||
a.save(null, { useMasterKey: !0 });
|
||||
t.save(null, { useMasterKey: !0 });
|
||||
} catch (e) {
|
||||
console.log('err save in contracts_Webhook', e.message);
|
||||
}
|
||||
}));
|
||||
}
|
||||
async function PDF(o) {
|
||||
const sendMailsaveCertifcate = async (e, t, a, r, i, n) => {
|
||||
var s = await GenerateCertificate(e),
|
||||
s = await PDFDocument.load(s),
|
||||
t = new P12Signer(t, { passphrase: process.env.PASS_PHRASE || null }),
|
||||
s =
|
||||
(pdflibAddPlaceholder({
|
||||
pdfDoc: s,
|
||||
reason: 'Digitally signed by OpenSign.',
|
||||
location: 'n/a',
|
||||
name: eSignName,
|
||||
contactInfo: eSigncontact,
|
||||
signatureLength: 15e3,
|
||||
}),
|
||||
await s.save()),
|
||||
s = Buffer.from(s),
|
||||
t = await new SignPdf().sign(s, t),
|
||||
t =
|
||||
(fs.writeFileSync('./exports/certificate.pdf', t),
|
||||
await uploadFile('certificate.pdf', './exports/certificate.pdf')),
|
||||
o = { CertificateUrl: t.imageUrl };
|
||||
await axios.put(serverUrl + '/classes/contracts_Document/' + e.objectId, o, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Master-Key': masterKEY,
|
||||
},
|
||||
}),
|
||||
e.IsSendMail && !1 === e.IsSendMail
|
||||
? console.log("don't send mail")
|
||||
: sendCompletedMail({ url: a, isCustomMail: r, doc: e, mailProvider: i }),
|
||||
saveFileUsage(s.length, t.imageUrl, n),
|
||||
sendDoctoWebhook(e, a, 'completed', '', t.imageUrl);
|
||||
};
|
||||
async function PDF(s) {
|
||||
try {
|
||||
var n = o.params.docId,
|
||||
e = o.params.userId,
|
||||
l = o.params.isCustomCompletionMail,
|
||||
d = await axios.get(
|
||||
serverUrl + '/classes/contracts_Document/' + n + '?include=ExtUserPtr,Signers',
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Master-Key': masterKEY,
|
||||
},
|
||||
}
|
||||
),
|
||||
c = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Session-Token': o.headers.sessiontoken,
|
||||
},
|
||||
});
|
||||
if (!c.data || !c.data.objectId) return { status: 'error', message: 'This user not allowed!' };
|
||||
if (!s?.user)
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
{
|
||||
var a,
|
||||
t,
|
||||
s,
|
||||
p = JSON.stringify({ objectId: e });
|
||||
let r, i;
|
||||
i = e
|
||||
? (a = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + p, {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Session-Token': o.headers.sessiontoken,
|
||||
},
|
||||
})).data && 0 < a.data.results.length
|
||||
? ((r = a), 'contracts_Contactbook')
|
||||
: ((r = await axios.get(serverUrl + '/classes/contracts_Users?where=' + p, {
|
||||
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
|
||||
})),
|
||||
'contracts_Users')
|
||||
: ((t = JSON.stringify({
|
||||
UserId: { __type: 'Pointer', className: '_User', objectId: c.data.objectId },
|
||||
})),
|
||||
(s = await axios.get(serverUrl + '/classes/contracts_Users?where=' + t, {
|
||||
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
|
||||
})).data && 0 < s.data.results.length
|
||||
? ((r = s), 'contracts_Users')
|
||||
: ((r = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + t, {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Session-Token': o.headers.sessiontoken,
|
||||
},
|
||||
})),
|
||||
'contracts_Contactbook'));
|
||||
var m = r.data.results[0].Name,
|
||||
g = r.data.results[0].Email;
|
||||
if (!o.params.pdfFile) return { status: 'error', message: 'Pdf file not present!' };
|
||||
var o = s?.user?.toJSON(),
|
||||
e = s.params.docId;
|
||||
const C = s.params.userId;
|
||||
var l = s.params.isCustomCompletionMail || !1,
|
||||
c = s.params.mailProvider || '',
|
||||
d = s.params.signature || '',
|
||||
t = new Parse.Query('contracts_Document'),
|
||||
a =
|
||||
(t.include('ExtUserPtr,Signers'),
|
||||
t.equalTo('objectId', e),
|
||||
await t.first({ useMasterKey: !0 }));
|
||||
if (!a) throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
|
||||
var r,
|
||||
p = a?.toJSON();
|
||||
let i, n;
|
||||
C
|
||||
? ((r = p.Signers.find(e => e.objectId === C)),
|
||||
console.log('_contractUser ', r),
|
||||
r && ((i = r), (n = 'contracts_Contactbook')))
|
||||
: ((n = 'contracts_Users'), (i = p.ExtUserPtr));
|
||||
var m,
|
||||
g = i.Name,
|
||||
f = i.Email;
|
||||
if (!s.params.pdfFile) throw (((m = new Error('Pdf file not present!')).code = 400), m);
|
||||
{
|
||||
let e = Buffer.from(o.params.pdfFile, 'base64');
|
||||
let e = Buffer.from(s.params.pdfFile, 'base64');
|
||||
var u = process.env.PFX_BASE64,
|
||||
h = Buffer.from(u, 'base64'),
|
||||
f = {
|
||||
UserPtr: { __type: 'Pointer', className: i, objectId: r.data.results[0].objectId },
|
||||
y = new P12Signer(h, { passphrase: process.env.PASS_PHRASE || null }),
|
||||
P = {
|
||||
UserPtr: { __type: 'Pointer', className: n, objectId: i.objectId },
|
||||
SignedUrl: '',
|
||||
Activity: 'Signed',
|
||||
ipAddress: o.headers['x-real-ip'],
|
||||
ipAddress: s.headers['x-real-ip'],
|
||||
};
|
||||
let a;
|
||||
var P = (a =
|
||||
d.data.AuditTrail && 0 < d.data.AuditTrail.length
|
||||
? [...d.data.AuditTrail, f]
|
||||
: [f]).filter(e => 'Signed' === e.Activity);
|
||||
let t = !1;
|
||||
!(
|
||||
(d.data.Signers && 0 < d.data.Signers.length && P.length !== d.data.Signers.length) ||
|
||||
!(t = !0)
|
||||
let t;
|
||||
var v = (t = p.AuditTrail && 0 < p.AuditTrail.length ? [...p.AuditTrail, P] : [P]).filter(
|
||||
e => 'Signed' === e.Activity
|
||||
);
|
||||
var y,
|
||||
let a = !1;
|
||||
!((p.Signers && 0 < p.Signers.length && v.length !== p.Signers.length) || !(a = !0));
|
||||
var S,
|
||||
b,
|
||||
v,
|
||||
w,
|
||||
U,
|
||||
I,
|
||||
w,
|
||||
D = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
|
||||
S = './exports/' + D;
|
||||
let s = e.length;
|
||||
s = (
|
||||
t
|
||||
? ((y = d.data.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
|
||||
_,
|
||||
E = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
|
||||
D = './exports/' + E;
|
||||
let r = e.length;
|
||||
r = (
|
||||
a
|
||||
? ((S = p.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
|
||||
(e =
|
||||
y && 0 < y.length
|
||||
S && 0 < S.length
|
||||
? ((b = await PDFDocument.load(e)),
|
||||
pdflibAddPlaceholder({
|
||||
pdfDoc: b,
|
||||
reason: 'Digitally signed by OpenSign for ' + y?.join(', '),
|
||||
reason: 'Digitally signed by OpenSign for ' + S?.join(', '),
|
||||
location: 'n/a',
|
||||
name: eSignName,
|
||||
contactInfo: eSigncontact,
|
||||
signatureLength: 15e3,
|
||||
}),
|
||||
(v = await b.save()),
|
||||
Buffer.from(v))
|
||||
(w = await b.save()),
|
||||
Buffer.from(w))
|
||||
: ((U = await PDFDocument.load(e)),
|
||||
pdflibAddPlaceholder({
|
||||
pdfDoc: U,
|
||||
reason: 'Digitally signed by OpenSign for ' + m + ' <' + g + '>',
|
||||
reason: 'Digitally signed by OpenSign for ' + g + ' <' + f + '>',
|
||||
location: 'n/a',
|
||||
name: eSignName,
|
||||
contactInfo: eSigncontact,
|
||||
signatureLength: 15e3,
|
||||
}),
|
||||
(I = await U.save()),
|
||||
Buffer.from(I))),
|
||||
(w = await new SignPDF(e, h).signPDF()),
|
||||
fs.writeFileSync(S, w),
|
||||
w)
|
||||
: (fs.writeFileSync(S, e), e)
|
||||
(_ = await new SignPdf().sign(e, y)),
|
||||
fs.writeFileSync(D, _),
|
||||
_)
|
||||
: (fs.writeFileSync(D, e), e)
|
||||
).length;
|
||||
var _,
|
||||
A,
|
||||
x,
|
||||
E,
|
||||
j,
|
||||
k,
|
||||
F,
|
||||
C,
|
||||
T,
|
||||
N = await uploadFile(D, S);
|
||||
if (N && N.imageUrl)
|
||||
return (
|
||||
(_ = await updateDoc(
|
||||
o.params.docId,
|
||||
N.imageUrl,
|
||||
r.data.results[0].objectId,
|
||||
o.headers['x-real-ip'],
|
||||
d.data,
|
||||
i
|
||||
)),
|
||||
sendDoctoWebhook(d, N.imageUrl, 'signed', r?.data.results?.[0]),
|
||||
saveFileUsage(s, N.imageUrl, c.data.objectId),
|
||||
_ &&
|
||||
_.isCompleted &&
|
||||
((A = { ...d.data, AuditTrail: _.AuditTrail }),
|
||||
(x = await GenerateCertificate(A)),
|
||||
(E = await PDFDocument.load(x)),
|
||||
pdflibAddPlaceholder({
|
||||
pdfDoc: E,
|
||||
reason: 'Digitally signed by OpenSign.',
|
||||
location: 'n/a',
|
||||
signatureLength: 15e3,
|
||||
}),
|
||||
(j = await E.save()),
|
||||
(k = Buffer.from(j)),
|
||||
(F = await new SignPDF(k, h).signPDF()),
|
||||
fs.writeFileSync('./exports/certificate.pdf', F),
|
||||
(T = {
|
||||
CertificateUrl: (C = await uploadFile(
|
||||
'certificate.pdf',
|
||||
'./exports/certificate.pdf'
|
||||
)).imageUrl,
|
||||
}),
|
||||
await axios.put(serverUrl + '/classes/contracts_Document/' + n, T, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Master-Key': masterKEY,
|
||||
},
|
||||
}),
|
||||
d.data.IsSendMail && !1 === d.data.IsSendMail
|
||||
? console.log("don't send mail")
|
||||
: sendCompletedMail({ url: N.imageUrl, isCustomMail: l, doc: d.data }),
|
||||
saveFileUsage(k.length, C.imageUrl, c.data.objectId),
|
||||
sendDoctoWebhook(d, N.imageUrl, 'completed')),
|
||||
fs.unlinkSync(S),
|
||||
console.log('New Signed PDF created called: ' + S),
|
||||
'success' === _.message
|
||||
? { status: 'success', data: N.imageUrl }
|
||||
: { status: 'error', message: 'Please provide required parameters!' }
|
||||
);
|
||||
var A = await uploadFile(E, D);
|
||||
if (A && A.imageUrl) {
|
||||
var x,
|
||||
j,
|
||||
N = await updateDoc(
|
||||
s.params.docId,
|
||||
A.imageUrl,
|
||||
i.objectId,
|
||||
s.headers['x-real-ip'],
|
||||
p,
|
||||
n,
|
||||
d
|
||||
);
|
||||
if (
|
||||
(sendDoctoWebhook(p, A.imageUrl, 'signed', i),
|
||||
saveFileUsage(r, A.imageUrl, o.objectId),
|
||||
N &&
|
||||
N.isCompleted &&
|
||||
((x = { ...p, AuditTrail: N.AuditTrail }),
|
||||
sendMailsaveCertifcate(x, h, A.imageUrl, l, c, o.objectId)),
|
||||
fs.unlinkSync(D),
|
||||
console.log('New Signed PDF created called: ' + D),
|
||||
'success' === N.message)
|
||||
)
|
||||
return { status: 'success', data: A.imageUrl };
|
||||
throw (((j = new Error('Please provide required parameters!')).code = 400), j);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return (
|
||||
console.log('Err ', e),
|
||||
'ERR_BAD_REQUEST' === e.code
|
||||
? { status: 'error', message: 'Invalid session token!' }
|
||||
: { status: 'error', message: 'Encrypted files are currently not supported!' }
|
||||
);
|
||||
throw (console.log('Err in signpdf', e), e);
|
||||
}
|
||||
}
|
||||
export default PDF;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
const signer=require("node-signpdf").default;class SignPDF{constructor(e,t){this.pdfDoc=e,this.certificate=t}async signPDF(){return signer.sign(this.pdfDoc,this.certificate, process.env.PASS_PHRASE ? {passphrase : process.env.PASS_PHRASE}: null)}static unit8ToBuffer(e){var t=Buffer.alloc(e.byteLength),r=new Uint8Array(e);for(let e=0;e<t.length;++e)t[e]=r[e];return t}}module.exports=SignPDF;
|
||||
-1
@@ -1 +0,0 @@
|
||||
class PDFAbstractReference{toString(){throw new Error("Must be implemented by subclasses")}}export default PDFAbstractReference;
|
||||
-1
@@ -1 +0,0 @@
|
||||
import PDFAbstractReference from"./PDFAbstractReference.min.js";class PDFKitReferenceMock extends PDFAbstractReference{constructor(e,t=void 0){super(),this.index=e,void 0!==t&&Object.assign(this,t)}toString(){return this.index+" 0 R"}}export default PDFKitReferenceMock;
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
const ERROR_TYPE_UNKNOWN = 1,
|
||||
ERROR_TYPE_INPUT = 2,
|
||||
ERROR_TYPE_PARSE = 3,
|
||||
ERROR_VERIFY_SIGNATURE = 4;
|
||||
class SignPdfError extends Error {
|
||||
constructor(R, E = ERROR_TYPE_UNKNOWN) {
|
||||
super(R), (this.type = E);
|
||||
}
|
||||
}
|
||||
(SignPdfError.TYPE_UNKNOWN = ERROR_TYPE_UNKNOWN),
|
||||
(SignPdfError.TYPE_INPUT = ERROR_TYPE_INPUT),
|
||||
(SignPdfError.TYPE_PARSE = ERROR_TYPE_PARSE),
|
||||
(SignPdfError.VERIFY_SIGNATURE = ERROR_VERIFY_SIGNATURE);
|
||||
export {
|
||||
ERROR_TYPE_UNKNOWN,
|
||||
ERROR_TYPE_INPUT,
|
||||
ERROR_TYPE_PARSE,
|
||||
ERROR_VERIFY_SIGNATURE,
|
||||
SignPdfError,
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
const DEFAULT_SIGNATURE_LENGTH=8192,DEFAULT_BYTE_RANGE_PLACEHOLDER="**********",SUBFILTER_ADOBE_PKCS7_DETACHED="adbe.pkcs7.detached",SUBFILTER_ADOBE_PKCS7_SHA1="adbe.pkcs7.sha1",SUBFILTER_ADOBE_X509_SHA1="adbe.x509.rsa.sha1",SUBFILTER_ETSI_CADES_DETACHED="ETSI.CAdES.detached";export{DEFAULT_SIGNATURE_LENGTH,DEFAULT_BYTE_RANGE_PLACEHOLDER,SUBFILTER_ADOBE_PKCS7_DETACHED,SUBFILTER_ADOBE_PKCS7_SHA1,SUBFILTER_ADOBE_X509_SHA1,SUBFILTER_ETSI_CADES_DETACHED};
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
import findObject from"./findObject.min.js";import getIndexFromRef from"./getIndexFromRef.min.js";const createBufferPageWithAnnotation=(e,t,n,f)=>{e=findObject(e,t.xref,n).toString();let r,o,i;r=e.indexOf("/Annots"),i=-1<r?(o=e.indexOf("]",r),(i=e.substr(r,o+1-r)).substr(0,i.length-1)):(r=e.length,o=e.length,"/Annots [");t=getIndexFromRef(t.xref,n),n=f.toString(),i+=` ${n}]`,f=e.substr(0,r);let m="";return e.length>o&&(m=e.substr(o+1)),Buffer.concat([Buffer.from(t+` 0 obj
|
||||
`),Buffer.from("<<\n"),Buffer.from(f+i+m+`
|
||||
`),Buffer.from("\n>>\nendobj\n")])};export default createBufferPageWithAnnotation;
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
import getIndexFromRef from"./getIndexFromRef.min.js";const createBufferRootWithAcroform=(r,f,o)=>{var e=getIndexFromRef(f.xref,f.rootRef);return Buffer.concat([Buffer.from(e+` 0 obj
|
||||
`),Buffer.from("<<\n"),Buffer.from(f.root+`
|
||||
`),Buffer.from("/AcroForm "+o),Buffer.from("\n>>\nendobj\n")])};export default createBufferRootWithAcroform;
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
const createBufferTrailer=(f,r,e)=>{let o=[];return o[0]="0000000000 65535 f ",e.forEach((f,r)=>{f=("0000000000"+f).slice(-10);o[r+1]=r+` 1
|
||||
${f} 00000 n `}),o=o.filter(f=>void 0!==f),Buffer.concat([Buffer.from("xref\n"),Buffer.from(r.xref.startingIndex+` 1
|
||||
`),Buffer.from(o.join("\n")),Buffer.from("\ntrailer\n"),Buffer.from("<<\n"),Buffer.from(`/Size ${r.xref.maxIndex+1}
|
||||
`),Buffer.from(`/Root ${r.rootRef}
|
||||
`),Buffer.from(r.infoRef?`/Info ${r.infoRef}
|
||||
`:""),Buffer.from(`/Prev ${r.xRefPosition}
|
||||
`),Buffer.from(">>\n"),Buffer.from("startxref\n"),Buffer.from(f.length+`
|
||||
`),Buffer.from("%%EOF")])};export default createBufferTrailer;
|
||||
@@ -1 +0,0 @@
|
||||
import getIndexFromRef from"./getIndexFromRef.min.js";const findObject=(e,t,f)=>{f=getIndexFromRef(t,f),t=t.offsets.get(f);let n=e.slice(t);return n=(n=(n=n.slice(0,n.indexOf("endobj","utf8"))).slice(n.indexOf("<<","utf8")+2)).slice(0,n.lastIndexOf(">>","utf8"))};export default findObject;
|
||||
-1
@@ -1 +0,0 @@
|
||||
import SignPdfError from"./SignPdfError.min.js";const getIndexFromRef=(r,e)=>{var[o]=e.split(" "),o=parseInt(o);if(r.offsets.has(o))return o;throw new SignPdfError(`Failed to locate object "${e}".`,SignPdfError.TYPE_PARSE)};export default getIndexFromRef;
|
||||
@@ -1 +0,0 @@
|
||||
import getPagesDictionaryRef from"./getPagesDictionaryRef.min.js";import findObject from"./findObject.min.js";export default function getPageRef(e,t,i){var n=getPagesDictionaryRef(t),e=findObject(e,t.xref,n),t=e.indexOf("/Kids"),n=e.indexOf("[",t)+1,t=e.indexOf("]",t);const f=e.slice(n,t).toString();for(var r=[],o=0;o<f.length;o++)"R"===f[o]&&r.push(o);let a={},g=0;return r.forEach((e,t)=>{a={...a,[t+1]:f.substring(g+1,e+1)},g=e+1}),i?a[i]:a[1]}
|
||||
-1
@@ -1 +0,0 @@
|
||||
import SignPdfError from"./SignPdfError.min.js";export default function getPagesDictionaryRef(r){r=/\/Pages\s+(\d+\s+\d+\s+R)/g.exec(r.root);if(null===r)throw new SignPdfError("Failed to find the pages descriptor. This is probably a problem in node-signpdf.",SignPdfError.TYPE_PARSE);return r[1]}
|
||||
@@ -1 +0,0 @@
|
||||
import PDFAbstractReference from"./PDFAbstractReference.min.js";const pad=(e,t)=>(Array(t+1).join("0")+e).slice(-t),escapableRe=/[\n\r\t\b\f()\\]/g,escapable={"\n":"\\n","\r":"\\r","\t":"\\t","\b":"\\b","\f":"\\f","\\":"\\\\","(":"\\(",")":"\\)"},swapBytes=e=>e.swap16();export default class PDFObject{static convert(t,a=null){if("string"==typeof t)return"/"+t;if(t instanceof String){let r=t,n=!1;for(let e=0,t=r.length;e<t;e+=1)if(127<r.charCodeAt(e)){n=!0;break}let e;return e=n?swapBytes(Buffer.from(``+r,"utf16le")):Buffer.from(r,"ascii"),`(${r=(r=(a?a(e):e).toString("binary")).replace(escapableRe,e=>escapable[e])})`}if(Buffer.isBuffer(t))return`<${t.toString("hex")}>`;if(t instanceof PDFAbstractReference)return t.toString();if(t instanceof Date){let e=`D:${pad(t.getUTCFullYear(),4)}${pad(t.getUTCMonth()+1,2)}${pad(t.getUTCDate(),2)}${pad(t.getUTCHours(),2)}${pad(t.getUTCMinutes(),2)}${pad(t.getUTCSeconds(),2)}Z`;return`(${e=a?(e=a(Buffer.from(e,"ascii")).toString("binary")).replace(escapableRe,e=>escapable[e]):e})`}if(Array.isArray(t))return`[${t.map(e=>PDFObject.convert(e,a)).join(" ")}]`;if("[object Object]"!=={}.toString.call(t))return"number"==typeof t?PDFObject.number(t):""+t;{const n=["<<"];return Object.entries(t).forEach(([e,t])=>{let r="";r=t&&-1!==t.toString().indexOf("<<")?t:PDFObject.convert(t,a),n.push(`/${e} `+r)}),n.push(">>"),n.join("\n")}}static number(e){if(-1e21<e&&e<1e21)return Math.round(1e6*e)/1e6;throw new Error("unsupported number: "+e)}}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import{DEFAULT_BYTE_RANGE_PLACEHOLDER,DEFAULT_SIGNATURE_LENGTH,SUBFILTER_ADOBE_PKCS7_DETACHED}from"./const.min.js";import PDFKitReferenceMock from"./PDFKitReferenceMock.min.js";import PNG from"png-js";import zlib from"zlib";const specialCharacters=["á","Á","é","É","í","Í","ó","Ó","ö","Ö","ő","Ő","ú","Ú","ű","Ű"],MARKERS=[65472,65473,65474,65475,65477,65478,65479,65480,65481,65482,65483,65484,65485,65486,65487],COLOR_SPACE_MAP={1:"DeviceGray",3:"DeviceRGB",4:"DeviceCMYK"};function getImage(e,t){let n;if(255===e[0]&&216===e[1])n=getJpgImage(t,e);else{if(137!==e[0]||"PNG"!==e.toString("ascii",1,4))throw new Error("Unknown image format.");n=getPngImage(t,e)}return n}function getAnnotationApparance(e,t,n){return e.ref({CropBox:[0,10,200,60],Type:"XObject",FormType:1,BBox:[0,10,200,60],Resources:`<</XObject <<
|
||||
/Img${t.index} ${t.index} 0 R
|
||||
>>
|
||||
/Font <<
|
||||
/f1 ${n.index} 0 R
|
||||
>>
|
||||
>>`,MediaBox:[0,10,200,60],Subtype:"Form"},null,getStream(t.index))}const getStream=e=>getConvertedText(`
|
||||
1.0 1.0 1.0 rg
|
||||
0.0 0.0 0.0 RG
|
||||
q
|
||||
q
|
||||
200 0 0 50 0 10 cm
|
||||
/Img${e} Do
|
||||
Q
|
||||
0 0 0 rg
|
||||
BT
|
||||
0 Tr
|
||||
/f1 10.0 Tf
|
||||
1.4 0 0 1 20 45.97412 Tm
|
||||
ET
|
||||
BT
|
||||
0 Tr
|
||||
/f1 10.0 Tf
|
||||
1.4 0 0 1 20 33.56006 Tm
|
||||
ET
|
||||
Q`),getFont=(e,t)=>e.ref({Type:"Font",BaseFont:t,Encoding:"WinAnsiEncoding",Subtype:"Type1"}),getJpgImage=(e,t)=>{if(65496!==t.readUInt16BE(0))throw"SOI not found in JPEG";let n=2,a;for(;n<t.length&&(a=t.readUInt16BE(n),n+=2,!MARKERS.includes(a));)n+=t.readUInt16BE(n);if(!MARKERS.includes(a))throw"Invalid JPEG.";n+=2;var r=t[n++],o=t.readUInt16BE(n),i=(n+=2,t.readUInt16BE(n)),l=(n+=2,t[n++]),l=COLOR_SPACE_MAP[l],r={Type:"XObject",Subtype:"Image",BitsPerComponent:r,Width:i,Height:o,ColorSpace:l,Filter:"DCTDecode"},i=("DeviceCMYK"===l&&(r.Decode=[1,0,1,0,1,0,1,0]),e.ref(r,null,t));return i},getPngImage=(e,t)=>{var t=new PNG(t),n=t.hasAlphaChannel,a={Type:"XObject",Subtype:"Image",BitsPerComponent:n?8:t.bits,Width:t.width,Height:t.height,Filter:"FlateDecode"};if(n||(r=e.ref({Predictor:15,Colors:t.colors,BitsPerComponent:t.bits,Columns:t.width}),a.DecodeParms=r),0===t.palette.length?a.ColorSpace=t.colorSpace:(r=e.ref({stream:new Buffer(t.palette)}),a.ColorSpace=["Indexed","DeviceRGB",t.palette.length/3-1,r]),null!=t.transparency.grayscale){var r=t.transparency.grayscale;a.Mask=[r,r]}else if(t.transparency.rgb){var o,r=t.transparency["rgb"],i=[];for(o of r)i.push(o,o);a.Mask=i}else t.transparency.indexed?loadIndexedAlphaChannel(t):n&&(splitAlphaChannel(t),r=getSmask(e,t),a.Mask=r);return e.ref(a,null,t.imgData)},loadIndexedAlphaChannel=e=>{const o=e.transparency.indexed;return e.decodePixels(n=>{var a=new Buffer(e.width*e.height);let r=0;for(let e=0,t=n.length;e<t;e++)a[r++]=o[n[e]];e.alphaChannel=zlib.deflateSync(a)})},splitAlphaChannel=d=>{d.decodePixels(t=>{let e,n;var a=d.colors,r=d.width*d.height,o=new Buffer(r*a),i=new Buffer(r);let l=n=e=0;for(var c=t.length,g=16===d.bits?1:0;l<c;){for(let e=0;e<a;e++)o[n++]=t[l++],l+=g;i[e++]=t[l++],l+=g}d.imgData=zlib.deflateSync(o),d.alphaChannel=zlib.deflateSync(i)})},getSmask=(e,t)=>{let n;return console.log("image.hasAlphaChannel ",t.hasAlphaChannel),t.hasAlphaChannel&&(console.log("image.alphaChannel ",t.alphaChannel),n=e.ref({Type:"XObject",Subtype:"Image",Height:t.height,Width:t.width,BitsPerComponent:8,Filter:"FlateDecode",ColorSpace:"DeviceGray",Decode:[0,1],stream:t.alphaChannel})),n},getConvertedText=e=>e.split("").map(e=>specialCharacters.includes(e)?getOctalCodeFromCharacter(e):e).join(""),getOctalCodeFromCharacter=e=>"\\"+e.charCodeAt(0).toString(8),pdfkitAddPlaceholder=({pdf:e,pdfBuffer:a,reason:r,contactInfo:t="emailfromp1289@gmail.com",name:n="Name from p12",location:o="Location from p12",signatureLength:i=DEFAULT_SIGNATURE_LENGTH,byteRangePlaceholder:l=DEFAULT_BYTE_RANGE_PLACEHOLDER,subFilter:c=SUBFILTER_ADOBE_PKCS7_DETACHED,sign:g})=>{var d=getFont(e,"Helvetica"),s=getFont(e,"ZapfDingbats"),p=getFont(e,"Helvetica"),f=g.Base64,f=getAnnotationApparance(e,getImage(Buffer.from(f,"base64"),e),p,g),p=e.ref({Type:"Sig",Filter:"Adobe.PPKLite",SubFilter:c,ByteRange:[0,l,l,l],Contents:Buffer.from(String.fromCharCode(0).repeat(i)),Reason:new String(r),M:new Date,ContactInfo:new String(t),Name:new String(n),Location:new String(o)}),h=a.lastIndexOf("/Type /AcroForm"),c=-1!==h;let m=[],C;if(c){let e=h;var S=h-10;let t="",n=11;for(n;n<22;n+=1){var A=a.slice(h-n,S).toString();if("\n"===A[0])break;t=A,e=h-n}l=a.slice(e),i=l.slice(0,l.indexOf("endobj")).toString(),r=(C=parseInt(t),i.slice(i.indexOf("/Fields [")+9,i.indexOf("]")));m=r.split(" ").filter((e,t)=>t%3==0).map(e=>new PDFKitReferenceMock(e))}t=g.Left,n=g.Bottom,o=t+g.Width,l=n+g.Height,i=e.ref({Type:"Annot",Subtype:"Widget",FT:"Sig",Rect:[t,n,o,l],V:p,T:new String("Signature"+(m.length+1)),F:4,P:e.page.dictionary,AP:`<</N ${f.index} 0 R>>`,DA:new String("/Helvetica 0 Tf 0 g")});e.page.dictionary.data.Annots=[i];let T;return T=c?e.ref({Type:"AcroForm",SigFlags:3,Fields:[...m,i],DR:`<</Font
|
||||
<</Helvetica ${d.index} 0 R/ZapfDingbats ${s.index} 0 R>>
|
||||
>>`},C):e.ref({Type:"AcroForm",SigFlags:3,Fields:[...m,i]}),{signature:p,form:e._root.data.AcroForm=T,widget:i}};export default pdfkitAddPlaceholder;
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
import {
|
||||
DEFAULT_BYTE_RANGE_PLACEHOLDER,
|
||||
DEFAULT_SIGNATURE_LENGTH,
|
||||
SUBFILTER_ADOBE_PKCS7_DETACHED,
|
||||
} from './const.min.js';
|
||||
import { SignPdfError } from './SignPdfError.min.js';
|
||||
import {
|
||||
PDFArray,
|
||||
PDFDict,
|
||||
PDFHexString,
|
||||
PDFName,
|
||||
PDFNumber,
|
||||
PDFInvalidObject,
|
||||
PDFString,
|
||||
} from 'pdf-lib';
|
||||
const pdflibAddPlaceholder = ({
|
||||
pdfDoc: e = void 0,
|
||||
pdfPage: o = void 0,
|
||||
reason: t,
|
||||
contactInfo: r = 'emailfromp1289@gmail.com',
|
||||
name: i = 'Name from p12',
|
||||
location: n,
|
||||
signingTime: a = void 0,
|
||||
signatureLength: P = DEFAULT_SIGNATURE_LENGTH,
|
||||
byteRangePlaceholder: F = DEFAULT_BYTE_RANGE_PLACEHOLDER,
|
||||
subFilter: D = SUBFILTER_ADOBE_PKCS7_DETACHED,
|
||||
widgetRect: f = [0, 0, 0, 0],
|
||||
appName: g = void 0,
|
||||
}) => {
|
||||
if (void 0 === e && void 0 === o)
|
||||
throw new SignPdfError('PDFDoc or PDFPage must be set.', SignPdfError.TYPE_INPUT);
|
||||
var e = e ?? o.doc,
|
||||
o = o ?? e.getPages()[0],
|
||||
m = PDFArray.withContext(e.context),
|
||||
F =
|
||||
(m.push(PDFNumber.of(0)),
|
||||
m.push(PDFName.of(F)),
|
||||
m.push(PDFName.of(F)),
|
||||
m.push(PDFName.of(F)),
|
||||
PDFHexString.of(String.fromCharCode(0).repeat(P))),
|
||||
P = g ? { App: { Name: g } } : {},
|
||||
g = e.context.obj({
|
||||
Type: 'Sig',
|
||||
Filter: 'Adobe.PPKLite',
|
||||
SubFilter: D,
|
||||
ByteRange: m,
|
||||
Contents: F,
|
||||
Reason: PDFString.of(t),
|
||||
M: PDFString.fromDate(a ?? new Date()),
|
||||
ContactInfo: PDFString.of(r),
|
||||
Name: PDFString.of(i),
|
||||
Location: PDFString.of(n),
|
||||
Prop_Build: { Filter: { Name: 'Adobe.PPKLite' }, ...P },
|
||||
}),
|
||||
D = new Uint8Array(g.sizeInBytes()),
|
||||
m = (g.copyBytesInto(D, 0), PDFInvalidObject.of(D)),
|
||||
F = e.context.register(m);
|
||||
const s = PDFArray.withContext(e.context);
|
||||
f.forEach(e => s.push(PDFNumber.of(e)));
|
||||
(t = e.context.formXObject([], { BBox: f, Resources: {} })),
|
||||
(a = e.context.obj({
|
||||
Type: 'Annot',
|
||||
Subtype: 'Widget',
|
||||
FT: 'Sig',
|
||||
Rect: s,
|
||||
V: F,
|
||||
T: PDFString.of('Signature1'),
|
||||
F: 4,
|
||||
P: o.ref,
|
||||
AP: { N: e.context.register(t) },
|
||||
})),
|
||||
(r = e.context.register(a));
|
||||
let c = o.node.lookupMaybe(PDFName.of('Annots'), PDFArray),
|
||||
d =
|
||||
((c = void 0 === c ? e.context.obj([]) : c).push(r),
|
||||
o.node.set(PDFName.of('Annots'), c),
|
||||
e.catalog.lookupMaybe(PDFName.of('AcroForm'), PDFDict));
|
||||
void 0 === d &&
|
||||
((d = e.context.obj({ Fields: [] })),
|
||||
(i = e.context.register(d)),
|
||||
e.catalog.set(PDFName.of('AcroForm'), i));
|
||||
let N;
|
||||
N = d.has(PDFName.of('SigFlags')) ? d.get(PDFName.of('SigFlags')) : PDFNumber.of(0);
|
||||
n = PDFNumber.of(3 | N.asNumber());
|
||||
d.set(PDFName.of('SigFlags'), n), d.get(PDFName.of('Fields')).push(r);
|
||||
};
|
||||
export { pdflibAddPlaceholder };
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
import PDFObject from"./pdfObject.min.js";import PDFKitReferenceMock from"./PDFKitReferenceMock.min.js";import removeTrailingNewLine from"./removeTrailingNewLine.min.js";import{DEFAULT_SIGNATURE_LENGTH,SUBFILTER_ADOBE_PKCS7_DETACHED}from"./const.min.js";import pdfkitAddPlaceholder from"./pdfkitAddPlaceholder.min.js";import getIndexFromRef from"./getIndexFromRef.min.js";import readPdf from"./readPdf.min.js";import getPageRef from"./getPageRef.min.js";import createBufferRootWithAcroform from"./createBufferRootWithAcroform.min.js";import createBufferPageWithAnnotation from"./createBufferPageWithAnnotation.min.js";import createBufferTrailer from"./createBufferTrailer.min.js";const isContainBufferRootWithAcroform=e=>{e=/\/AcroForm\s+(\d+\s\d+\sR)/g.exec(e.toString());return null!=e&&null!=e[1]&&""!==e[1]},getAssembledPdf=(e,r,f,o)=>{let t=e;return t=Buffer.concat([t,Buffer.from("\n"),Buffer.from(r+` 0 obj
|
||||
`),Buffer.from(PDFObject.convert(f))]),o&&(t=Buffer.concat([t,Buffer.from("\nstream\n"),Buffer.from(o),Buffer.from("\nendstream")])),t=Buffer.concat([t,Buffer.from("\nendobj\n")])},plainAddPlaceholder=({pdfBuffer:e,reason:r,contactInfo:f="emailfromp1289@gmail.com",name:o="Name from p12",location:t="Location from p12",signatureLength:n=DEFAULT_SIGNATURE_LENGTH,subFilter:i=SUBFILTER_ADOBE_PKCS7_DETACHED,sign:m})=>{let a=removeTrailingNewLine(e);const c=readPdf(a);var d=getPageRef(a,c,m.Page),u=getIndexFromRef(c.xref,d);const l=new Map;var s={ref:(e,r,f)=>{c.xref.maxIndex+=1;r=null!=r?r:c.xref.maxIndex;return l.set(r,a.length+1),a=getAssembledPdf(a,r,e,f),new PDFKitReferenceMock(c.xref.maxIndex)},page:{dictionary:new PDFKitReferenceMock(u,{data:{Annots:[]}})},_root:{data:{}}},{form:s,widget:e}=pdfkitAddPlaceholder({pdf:s,pdfBuffer:e,reason:r,contactInfo:f,name:o,location:t,signatureLength:n,subFilter:i,sign:m});return isContainBufferRootWithAcroform(a)||(r=getIndexFromRef(c.xref,c.rootRef),l.set(r,a.length+1),a=Buffer.concat([a,Buffer.from("\n"),createBufferRootWithAcroform(a,c,s)])),l.set(u,a.length+1),a=Buffer.concat([a,Buffer.from("\n"),createBufferPageWithAnnotation(a,c,d,e)]),a=Buffer.concat([a,Buffer.from("\n"),createBufferTrailer(a,c,l)])};export default plainAddPlaceholder;
|
||||
@@ -1 +0,0 @@
|
||||
import readRefTable from"./readRefTable.min.js";import findObject from"./findObject.min.js";const getValue=(e,t)=>{let r=e.indexOf(t);if(-1!==r)return e=e.slice(r),-1===(r=e.indexOf("/",1))&&(r=e.indexOf(">",1)),e.slice(t.length+1,r).toString().trim()},readPdf=e=>{var t=e.lastIndexOf("trailer"),r=e.slice(t,e.length-6),f=r.slice(r.lastIndexOf("startxref")+10).toString(),f=parseInt(f),i=readRefTable(e),n=getValue(r,"/Root");return{xref:i,rootRef:n,root:findObject(e,i,n).toString(),infoRef:getValue(r,"/Info"),trailerStart:t,previousXrefs:[],xRefPosition:f}};export default readPdf;
|
||||
@@ -1 +0,0 @@
|
||||
import SignPdfError from"./SignPdfError.min.js";import xrefToRefMap from"./xrefToRefMap.min.js";const getLastTrailerPosition=e=>{var r=e.lastIndexOf(Buffer.from("trailer","utf8")),r=e.slice(r,e.length-6),e=r.slice(r.lastIndexOf(Buffer.from("startxref","utf8"))+10).toString();return parseInt(e)},getXref=(e,r)=>{let t=e.slice(r);e=t.indexOf(Buffer.from("xref","utf8"));if(-1===e)throw new SignPdfError(`Could not find xref anywhere at or after ${r}.`,SignPdfError.TYPE_PARSE);if(0<e&&""!==t.slice(0,e).toString().replace(/\s*/g,""))throw new SignPdfError(`Expected xref at ${r} but found other content.`,SignPdfError.TYPE_PARSE);r=t.indexOf(Buffer.from("%%EOF","utf8"));if(-1===r)throw new SignPdfError("Expected EOF after xref and trailer but could not find one.",SignPdfError.TYPE_PARSE);if(!(r=(t=(t=(t=t.slice(0,r)).slice(e+4)).slice(t.indexOf("\n")+1)).toString().split("/Size")[1]))throw new SignPdfError("Size not found in xref table.",SignPdfError.TYPE_PARSE);if(null===(r=/^\s*(\d+)/.exec(r)))throw new SignPdfError("Failed to parse size of xref table.",SignPdfError.TYPE_PARSE);var r=parseInt(r[1]),[e,f]=t.toString().split("trailer");let n;null!=f.split("/Prev")[1]&&([,f]=/Prev (\d+)/g.exec(f),n=f);f=xrefToRefMap(e);return{size:r,prev:n,xRefContent:f}},getFullXrefTable=e=>{var r=getLastTrailerPosition(e),t=getXref(e,r);return void 0===t.prev?t.xRefContent:(e=e.slice(0,r),r=getFullXrefTable(e),new Map([...r,...t.xRefContent]))},readRefTable=e=>{e=getFullXrefTable(e);return{startingIndex:0,maxIndex:Math.max(...e.keys()),offsets:e}};export default readRefTable;export{getLastTrailerPosition,getXref,getFullXrefTable};
|
||||
-1
@@ -1 +0,0 @@
|
||||
import SignPdfError from"./SignPdfError.min.js";const sliceLastChar=(r,e)=>{return r.slice(r.length-1).toString()===e?r.slice(0,r.length-1):r},removeTrailingNewLine=r=>{if(!(r instanceof Buffer))throw new SignPdfError("PDF expected as Buffer.",SignPdfError.TYPE_INPUT);r=sliceLastChar(r,"\n");if("\n%%EOF"!==(r=sliceLastChar(r,"\r")).slice(r.length-6).toString())throw new SignPdfError("A PDF file must end with an EOF line.",SignPdfError.TYPE_PARSE);return r};export default removeTrailingNewLine;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user