mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-03 16:28:56 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e073e83ad6 | ||
|
|
69140d2139 | ||
|
|
5518d6932e | ||
|
|
f14e301b6c | ||
|
|
901ddf35bb | ||
|
|
9aa3166528 | ||
|
|
a90e679228 | ||
|
|
90b5e3c860 | ||
|
|
74f6612271 | ||
|
|
5c3f967128 | ||
|
|
2900464272 | ||
|
|
302f38d472 | ||
|
|
0be1816046 | ||
|
|
feb162e03e | ||
|
|
a793fc3722 | ||
|
|
ffdabe4a00 | ||
|
|
ba1c3145f3 | ||
|
|
4898c4a851 | ||
|
|
85aa6b2947 | ||
|
|
3b02d2bcdb | ||
|
|
ed01f4a7a0 | ||
|
|
1f395cf25f | ||
|
|
cf98e73d13 | ||
|
|
10112ac20d | ||
|
|
9a0183274f | ||
|
|
6b2e16c21d | ||
|
|
3c51baf1c9 | ||
|
|
3f4be1c4f3 | ||
|
|
62b36f58f7 | ||
|
|
ba3591c7bc | ||
|
|
1ec11b8733 | ||
|
|
ab89d3b71f | ||
|
|
1811173648 | ||
|
|
414e2d819c | ||
|
|
0769101f28 | ||
|
|
5dc86119d3 | ||
|
|
4999dc2b18 | ||
|
|
962d36c088 | ||
|
|
d071986aac | ||
|
|
afb4de9d0c | ||
|
|
bc5c8cac33 | ||
|
|
be93e6a214 | ||
|
|
37748057e3 | ||
|
|
eb3bb77692 | ||
|
|
df0ce3f086 | ||
|
|
0a99b5181e | ||
|
|
8a5d3a0368 | ||
|
|
c00406e401 | ||
|
|
aeead23c86 | ||
|
|
83fe88911f | ||
|
|
527bca32d2 | ||
|
|
9bf4940c2e | ||
|
|
a653df6935 | ||
|
|
dfc6da388c | ||
|
|
3e2c822bbc | ||
|
|
1a20841a94 | ||
|
|
c6be81923b | ||
|
|
6b47f6a27b | ||
|
|
33b0111ed0 | ||
|
|
48fd9e52b3 | ||
|
|
d85074b127 | ||
|
|
5ada9a411b | ||
|
|
6f50de1ef1 | ||
|
|
612150cf4b | ||
|
|
634db83e8e | ||
|
|
b32c8068ef | ||
|
|
ae464274b0 | ||
|
|
c5ceba8d57 | ||
|
|
903d626c27 | ||
|
|
e5b4fb123f | ||
|
|
116893c1b4 | ||
|
|
592254a9a3 | ||
|
|
f52c6e24fe | ||
|
|
70ae1e24fc | ||
|
|
c491595fc8 | ||
|
|
6875135ede | ||
|
|
662049d878 | ||
|
|
1764d7f9ba | ||
|
|
07933b56b1 | ||
|
|
f6ba4db62d | ||
|
|
5bd36f91d9 | ||
|
|
c199c3019e | ||
|
|
facbc8e76e | ||
|
|
c3c6f6bab3 | ||
|
|
f5e216fedc | ||
|
|
c40a923388 | ||
|
|
a86f4fd9b7 | ||
|
|
59f6d022cf | ||
|
|
b7beeee260 | ||
|
|
31c861cbe4 | ||
|
|
6a9408b7b4 | ||
|
|
a9fd264a01 | ||
|
|
6c77bbfe75 | ||
|
|
890c3d2b1f | ||
|
|
2433ad5957 | ||
|
|
fffd907d63 | ||
|
|
bf307abe9a | ||
|
|
08a97707b7 | ||
|
|
d99fecd46e | ||
|
|
6c59c0ec9a | ||
|
|
32c75d1fc3 | ||
|
|
7f0f81c334 | ||
|
|
a882f07f57 | ||
|
|
74b81a5363 | ||
|
|
9b75764717 | ||
|
|
c1468112b0 | ||
|
|
950b74789c | ||
|
|
7e4f12e41a | ||
|
|
d75c1d7c0c | ||
|
|
7eacbedf59 | ||
|
|
2e5947279f | ||
|
|
ef5ac7d626 | ||
|
|
658c37cc4d | ||
|
|
ffc8e4b23e | ||
|
|
3fb96fc792 | ||
|
|
71016ba5e0 | ||
|
|
cd1a6fdaf6 | ||
|
|
c2afeed448 | ||
|
|
37fd7bbeda | ||
|
|
71fbde0099 | ||
|
|
ca6cb60d78 | ||
|
|
df1c0ea66c | ||
|
|
46279efc2c | ||
|
|
aca2848406 | ||
|
|
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 | ||
|
|
498e0c6be6 | ||
|
|
b4d73b2b44 | ||
|
|
f14c98505a | ||
|
|
3f23bdd372 | ||
|
|
440d253124 | ||
|
|
2b6bf7bcaa | ||
|
|
cb6d9e9b9b | ||
|
|
ebeeedc96c | ||
|
|
5cecf3390b | ||
|
|
9890d568fd | ||
|
|
06bcae64be | ||
|
|
fb017c4bcd | ||
|
|
01d3edd262 | ||
|
|
41a025715e | ||
|
|
306768fbd2 | ||
|
|
d82da53258 | ||
|
|
673245df02 | ||
|
|
42549e5792 | ||
|
|
493731a6af | ||
|
|
cc8b4b7157 | ||
|
|
4cbd7e74f7 | ||
|
|
b300a5bb39 | ||
|
|
5422fefc54 | ||
|
|
248b0cac0b | ||
|
|
a388a7c345 | ||
|
|
fc564ca37b | ||
|
|
37b2e4c97a | ||
|
|
a3bc9817e7 | ||
|
|
8a216e6c0d | ||
|
|
613b1cfe07 | ||
|
|
ee77534e35 | ||
|
|
737b33db1e | ||
|
|
ac214d0467 | ||
|
|
f8b21e7a93 | ||
|
|
7e6df13b71 | ||
|
|
3e584cec1a | ||
|
|
bc75dd109f | ||
|
|
6b3abed782 | ||
|
|
f163958cd0 | ||
|
|
ecd461b96c | ||
|
|
1371c2c09a | ||
|
|
3121f35158 | ||
|
|
3c0db9473c | ||
|
|
4f2b72dd4d | ||
|
|
7d9af1db95 | ||
|
|
0e38783560 | ||
|
|
6880ae35a8 | ||
|
|
9a07c67b69 | ||
|
|
33bfdeef15 | ||
|
|
a404247604 | ||
|
|
c48579daa7 | ||
|
|
509f8f9a07 | ||
|
|
5b85d5b277 | ||
|
|
bbf3170398 | ||
|
|
c172a7c55c | ||
|
|
90f3e0066b | ||
|
|
b6e028a2c0 | ||
|
|
11384192ba | ||
|
|
9df63265fb | ||
|
|
ff6a8ce755 | ||
|
|
60c1aa253a | ||
|
|
ad2fec0d66 | ||
|
|
0451a6cae0 | ||
|
|
0d5f8981fe | ||
|
|
cfd8be0eee | ||
|
|
86e5ef6ef5 | ||
|
|
1af47c8425 | ||
|
|
f0395cb2c7 | ||
|
|
598aa2975f | ||
|
|
0742291216 | ||
|
|
94eeceeaf9 | ||
|
|
970fbecb7b | ||
|
|
361e082930 | ||
|
|
3f9b6eb5a9 | ||
|
|
2265bee61f | ||
|
|
6390706d8d | ||
|
|
37e03c1026 | ||
|
|
c6fa6c710d | ||
|
|
2e921e30b6 | ||
|
|
686a773d34 | ||
|
|
b03e1b8f9d | ||
|
|
f1ceae794f | ||
|
|
5a8bcb8f9a | ||
|
|
ce70da7d8e | ||
|
|
db03dfa641 | ||
|
|
7e70a5e111 | ||
|
|
f5f18f1630 | ||
|
|
fa6856c649 | ||
|
|
3b4946e47b | ||
|
|
05fe68d458 | ||
|
|
2f3cdca821 | ||
|
|
4d8a54a6c5 | ||
|
|
4fe5bdaad8 | ||
|
|
b95ede5273 | ||
|
|
5b2684a9f4 | ||
|
|
d779720f26 | ||
|
|
3cf8029978 | ||
|
|
0bb857a942 | ||
|
|
103d32edcb | ||
|
|
eca6ad374b | ||
|
|
d2efe88266 | ||
|
|
450e5ec7f7 | ||
|
|
3bbbda91b8 | ||
|
|
a48db306bb | ||
|
|
2a60b30c75 | ||
|
|
19cfa9d27d | ||
|
|
0fc12c957e | ||
|
|
6fd332f8b4 | ||
|
|
39f7ccecaa | ||
|
|
3bd306809b | ||
|
|
04e9d13206 | ||
|
|
b24739d61b | ||
|
|
9dda211605 | ||
|
|
93708a6875 | ||
|
|
e78f6f6df4 | ||
|
|
ccaaca75ba | ||
|
|
dedff36b35 | ||
|
|
4d561ca19e | ||
|
|
f70e9e2a3f | ||
|
|
e4a3ce405e | ||
|
|
c3be2a1fef | ||
|
|
8b2f7f1e95 | ||
|
|
55465a035f | ||
|
|
d30105299c | ||
|
|
78b038da13 | ||
|
|
d230ad2655 | ||
|
|
367c824918 | ||
|
|
5f8affb1bb | ||
|
|
3bd23f83cf | ||
|
|
ab1dbecab2 | ||
|
|
44c6aa9b0b | ||
|
|
ad922c16c7 | ||
|
|
87ee2e881e | ||
|
|
3659a1de55 | ||
|
|
cc49a2fd4e | ||
|
|
babd43e710 | ||
|
|
8dde38edf1 | ||
|
|
6c16d00031 | ||
|
|
99f444a8a1 | ||
|
|
f0149f6fd8 | ||
|
|
bdb7d9be8f | ||
|
|
27dcd7fefd | ||
|
|
a2467ef1a6 | ||
|
|
824b0a5407 | ||
|
|
ac8195a52d | ||
|
|
e0920bf5d8 | ||
|
|
4a5f030d87 | ||
|
|
ba839b0fc8 | ||
|
|
27130fd223 | ||
|
|
19ba9e3091 | ||
|
|
8c64a89f08 | ||
|
|
1c3d2d5c9a | ||
|
|
b36df32b19 | ||
|
|
155a3eee14 | ||
|
|
7bb46782a3 | ||
|
|
40961936d0 | ||
|
|
db310b0d73 | ||
|
|
f0e9c6b3e4 | ||
|
|
28fd98c0db | ||
|
|
97bd375de3 | ||
|
|
e2d450f9aa | ||
|
|
add01cb02a | ||
|
|
cea1653805 | ||
|
|
9d81fce956 | ||
|
|
79e3762b36 | ||
|
|
aa04a957ed | ||
|
|
d31910aefa | ||
|
|
96a4a2e7fa | ||
|
|
e8aaab99f9 | ||
|
|
c61be7c35b | ||
|
|
0ee85f53da | ||
|
|
748e2196da | ||
|
|
58199a934a | ||
|
|
b04d2abbab | ||
|
|
7eec822a65 | ||
|
|
6c21028523 | ||
|
|
099a3cc46d | ||
|
|
5adaffc684 | ||
|
|
d588caf6d4 | ||
|
|
5930fd940d | ||
|
|
741f66b019 | ||
|
|
de26c2d719 | ||
|
|
881ac12b03 | ||
|
|
1932dda6d6 | ||
|
|
46159b7214 | ||
|
|
c57c15a4cb | ||
|
|
724fa7c27e | ||
|
|
2762c3e170 | ||
|
|
74bfd39abc | ||
|
|
3115ff3e2a | ||
|
|
adbf1fe1e0 | ||
|
|
fe2f3c5f7c | ||
|
|
387154c07b | ||
|
|
8a01efee4c | ||
|
|
eafb26d462 | ||
|
|
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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ DO_SECRET_ACCESS_KEY=
|
||||
# Digital ocean spaces region or AWS s3 region
|
||||
DO_REGION=us-west
|
||||
# local storage
|
||||
USE_LOCAL=TRUE
|
||||
USE_LOCAL=true
|
||||
|
||||
# Email mailgun or set SMTP_ENABLE true to use SMTP config (The app will not initialize if any of these 3 variables are not set) *********************************************************************************************************************
|
||||
MAILGUN_API_KEY=
|
||||
|
||||
@@ -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><a href="#security-Zathiel" title="Security">🛡️</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Generated
+4222
-3563
File diff suppressed because it is too large
Load Diff
+27
-19
@@ -3,21 +3,24 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.1",
|
||||
"@radix-ui/themes": "^2.0.3",
|
||||
"@react-pdf/renderer": "^3.4.0",
|
||||
"@reduxjs/toolkit": "^2.2.1",
|
||||
"axios": "^1.6.8",
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@radix-ui/themes": "^3.0.5",
|
||||
"@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",
|
||||
"nth-check": "^2.1.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-datepicker": "^6.4.0",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-cookie": "^7.1.4",
|
||||
"react-datepicker": "^6.9.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dnd-multi-backend": "^8.0.3",
|
||||
@@ -27,22 +30,24 @@
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-pdf": "^7.7.1",
|
||||
"react-redux": "^9.1.0",
|
||||
"react-rnd": "^10.4.1",
|
||||
"react-pdf": "^9.0.0",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-rnd": "^10.4.11",
|
||||
"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",
|
||||
"reactour": "^1.19.2",
|
||||
"react-tooltip": "^5.26.4",
|
||||
"react-web-share": "^2.0.2",
|
||||
"reactour": "^1.19.4",
|
||||
"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",
|
||||
@@ -80,16 +85,19 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/runtime-corejs2": "^7.24.1",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"@babel/runtime-corejs2": "^7.24.6",
|
||||
"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.5",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.3.1",
|
||||
"tailwindcss": "^3.4.1"
|
||||
"tailwindcss": "^3.4.3"
|
||||
},
|
||||
"overrides": {
|
||||
"nth-check": "$nth-check"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<!-- <link rel="icon" type="image/png"
|
||||
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAALlJREFUaEPtmN0NwjAMBpNxYDKYiM1Yp90g93CKStH1NbIdfz921Dker2Pc+Js1cDF7MXAxASMGYkAigBI6vh9ZwoXP53uZoAYcvhwdA3mAVbI2aSb+9zFKU4IURB6j/HoPUIEa2G3iGIAhQQDlAUIoE2diabIklISS0NoFPebo77RF6OenEF3QntOm128he0GKrwHyACFoz2MgBqSGkpAEcHs47oHtN5AFakACqMNjQEMoE8SABFCHn4HE2zGHSLeEAAAAAElFTkSuQmCC"
|
||||
sizes="40x40" /> -->
|
||||
@@ -15,7 +15,7 @@
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
|
||||
@@ -15,6 +15,8 @@ import DraftDocument from "./components/pdf/DraftDocument";
|
||||
import PlaceHolderSign from "./pages/PlaceHolderSign";
|
||||
import PdfRequestFiles from "./pages/PdfRequestFiles";
|
||||
import LazyPage from "./primitives/LazyPage";
|
||||
import { isEnableSubscription } from "./constant/const";
|
||||
import SSOVerify from "./pages/SSOVerify";
|
||||
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
|
||||
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
|
||||
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
|
||||
@@ -27,8 +29,7 @@ const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
|
||||
const ManageSign = lazy(() => import("./pages/Managesign"));
|
||||
const GenerateToken = lazy(() => import("./pages/GenerateToken"));
|
||||
const Webhook = lazy(() => import("./pages/Webhook"));
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
|
||||
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.mjs`;
|
||||
const Loader = () => {
|
||||
return (
|
||||
<div
|
||||
@@ -108,12 +109,16 @@ 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"
|
||||
element={<LazyPage Page={ForgetPassword} />}
|
||||
/>
|
||||
{process.env.REACT_APP_ENABLE_SUBSCRIPTION && (
|
||||
{isEnableSubscription && (
|
||||
<>
|
||||
<Route
|
||||
path="/pgsignup"
|
||||
@@ -176,6 +181,7 @@ function App() {
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/sso" element={<SSOVerify />} />
|
||||
<Route path="*" element={<PageNotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -23,7 +23,7 @@ const AddSigner = (props) => {
|
||||
);
|
||||
if (savedUserDetails && addYourself) {
|
||||
setName(savedUserDetails.name);
|
||||
setPhone(savedUserDetails.phone);
|
||||
setPhone(savedUserDetails?.phone || "");
|
||||
setEmail(savedUserDetails.email);
|
||||
}
|
||||
}, [addYourself]);
|
||||
@@ -49,144 +49,162 @@ const AddSigner = (props) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const contactQuery = new Parse.Object("contracts_Contactbook");
|
||||
contactQuery.set("Name", name);
|
||||
contactQuery.set("Phone", phone);
|
||||
contactQuery.set("Email", email);
|
||||
contactQuery.set("UserRole", "contracts_Guest");
|
||||
|
||||
if (localStorage.getItem("TenetId")) {
|
||||
contactQuery.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenetId")
|
||||
});
|
||||
}
|
||||
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
const _user = new _users();
|
||||
_user.set("name", name);
|
||||
_user.set("username", email);
|
||||
_user.set("email", email);
|
||||
_user.set("phone", phone);
|
||||
_user.set("password", phone);
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const body = {
|
||||
appName: localStorage.getItem("_appName"),
|
||||
roleName: "contracts_Guest",
|
||||
userId: user.id
|
||||
};
|
||||
await axios.post(roleurl, body, { headers: headers });
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.set("UserId", user);
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.details) {
|
||||
props.details({
|
||||
value: parseData[props.valueKey],
|
||||
label: parseData[props.displayKey]
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
props.handleUserData(parseData);
|
||||
const user = Parse.User.current();
|
||||
const query = new Parse.Query("contracts_Contactbook");
|
||||
query.equalTo("CreatedBy", user);
|
||||
query.notEqualTo("IsDeleted", true);
|
||||
query.equalTo("Email", email);
|
||||
const res = await query.first();
|
||||
// console.log(res);
|
||||
if (!res) {
|
||||
const contactQuery = new Parse.Object("contracts_Contactbook");
|
||||
contactQuery.set("Name", name);
|
||||
if (phone) {
|
||||
contactQuery.set("Phone", phone);
|
||||
}
|
||||
contactQuery.set("Email", email);
|
||||
contactQuery.set("UserRole", "contracts_Guest");
|
||||
|
||||
contactQuery.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
const _user = new _users();
|
||||
_user.set("name", name);
|
||||
_user.set("username", email);
|
||||
_user.set("email", email);
|
||||
_user.set("password", email);
|
||||
if (phone) {
|
||||
_user.set("phone", phone);
|
||||
}
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const body = {
|
||||
appName: localStorage.getItem("_appName"),
|
||||
roleName: "contracts_Guest",
|
||||
userId: user.id
|
||||
};
|
||||
await axios.post(roleurl, body, { headers: headers });
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.set("UserId", user);
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.details) {
|
||||
props.details({
|
||||
value: parseData[props.valueKey],
|
||||
label: parseData[props.displayKey]
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const body = {
|
||||
appName: localStorage.getItem("_appName"),
|
||||
roleName: "contracts_Guest",
|
||||
userId: userRes.id
|
||||
};
|
||||
await axios.post(roleurl, body, { headers: headers });
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.set("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: userRes.id
|
||||
});
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.details) {
|
||||
props.details({
|
||||
value: parseData[props.valueKey],
|
||||
label: parseData[props.displayKey]
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("Contact already exist!");
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const body = {
|
||||
appName: localStorage.getItem("_appName"),
|
||||
roleName: "contracts_Guest",
|
||||
userId: userRes.id
|
||||
};
|
||||
await axios.post(roleurl, body, { headers: headers });
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.set("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: userRes.id
|
||||
});
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.details) {
|
||||
props.details({
|
||||
value: parseData[props.valueKey],
|
||||
label: parseData[props.displayKey]
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
console.log("err in fetch contact", err);
|
||||
setIsLoader(false);
|
||||
alert("something went wrong, please try again later");
|
||||
}
|
||||
} catch (err) {
|
||||
// console.log("err", err);
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
alert("something went wrong!");
|
||||
alert("something went wrong, please try again later");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -264,10 +282,10 @@ const AddSigner = (props) => {
|
||||
type="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onChange={(e) => setEmail(e.target.value?.toLowerCase())}
|
||||
required
|
||||
disabled={addYourself}
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs lowercase"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
@@ -276,14 +294,12 @@ const AddSigner = (props) => {
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Phone
|
||||
<span style={{ color: "red", fontSize: 13 }}> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="phone"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
required
|
||||
disabled={addYourself}
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
/>
|
||||
|
||||
@@ -48,11 +48,11 @@ const AddUser = (props) => {
|
||||
contactQuery.set("Email", email);
|
||||
contactQuery.set("UserRole", "contracts_User");
|
||||
|
||||
if (localStorage.getItem("TenetId")) {
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
contactQuery.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenetId")
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
}
|
||||
|
||||
@@ -62,8 +62,10 @@ const AddUser = (props) => {
|
||||
_user.set("name", name);
|
||||
_user.set("username", email);
|
||||
_user.set("email", email);
|
||||
_user.set("phone", phone);
|
||||
_user.set("password", phone);
|
||||
_user.set("password", email);
|
||||
if (phone) {
|
||||
_user.set("phone", phone);
|
||||
}
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import axios from "axios";
|
||||
import SuggestionInput from "./shared/fields/SuggestionInput";
|
||||
|
||||
const BulkSendUi = (props) => {
|
||||
const [forms, setForms] = useState([]);
|
||||
const [formId, setFormId] = useState(2);
|
||||
const formRef = useRef(null);
|
||||
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
const [allowedForm, setAllowedForm] = useState(0);
|
||||
const [isSignatureExist, setIsSignatureExist] = useState();
|
||||
const allowedSigners = 50;
|
||||
useEffect(() => {
|
||||
signatureExist();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//function to check atleast one signature field exist
|
||||
const signatureExist = () => {
|
||||
const getPlaceholder = props.item?.Placeholders;
|
||||
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((holder) =>
|
||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
||||
)
|
||||
);
|
||||
setIsSignatureExist(checkIsSignatureExistt);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (scrollOnNextUpdate && formRef.current) {
|
||||
formRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "end",
|
||||
inline: "nearest"
|
||||
});
|
||||
setScrollOnNextUpdate(false);
|
||||
}
|
||||
}, [forms, scrollOnNextUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
(() => {
|
||||
if (props?.Placeholders?.length > 0) {
|
||||
let users = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
if (!element.signerObjId) {
|
||||
users = [
|
||||
...users,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
|
||||
const totalForms = Math.floor(allowedSigners / users?.length);
|
||||
setAllowedForm(totalForms);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
const handleInputChange = (index, signer, fieldIndex) => {
|
||||
const newForms = [...forms];
|
||||
newForms[index].fields[fieldIndex].email = signer?.Email
|
||||
? signer?.Email
|
||||
: signer || "";
|
||||
newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : "";
|
||||
setForms(newForms);
|
||||
};
|
||||
|
||||
const handleAddForm = (e) => {
|
||||
e.preventDefault();
|
||||
// Check if the quick send limit has been reached
|
||||
if (forms?.length < allowedForm) {
|
||||
if (props?.Placeholders.length > 0) {
|
||||
let newForm = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
if (!element.signerObjId) {
|
||||
newForm = [
|
||||
...newForm,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setForms([...forms, { Id: formId, fields: newForm }]);
|
||||
}
|
||||
setFormId(formId + 1);
|
||||
setScrollOnNextUpdate(true);
|
||||
} else {
|
||||
// If the limit has been reached, throw an error with the appropriate message
|
||||
alert("Quick send reached limit.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveForm = (index) => {
|
||||
const updatedForms = forms.filter((_, i) => i !== index);
|
||||
setForms(updatedForms);
|
||||
};
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSubmit(true);
|
||||
|
||||
// Create a copy of Placeholders array from props.item
|
||||
let Placeholders = [...props.item.Placeholders];
|
||||
// Initialize an empty array to store updated documents
|
||||
let Documents = [];
|
||||
|
||||
// Loop through each form
|
||||
forms.forEach((form) => {
|
||||
// Map through the copied Placeholders array to update email values
|
||||
const updatedPlaceholders = Placeholders.map((placeholder) => {
|
||||
// Find the field in the current form that matches the placeholder Id
|
||||
const field = form.fields.find(
|
||||
(element) => parseInt(element.fieldId) === placeholder.Id
|
||||
);
|
||||
// If a matching field is found, update the email value in the placeholder
|
||||
const signer = field?.signer?.objectId ? field.signer : {};
|
||||
if (field) {
|
||||
return {
|
||||
...placeholder,
|
||||
email: field.email,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
}
|
||||
// If no matching field is found, keep the placeholder as is
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Push a new document object with updated Placeholders into the Documents array
|
||||
Documents.push({ ...props.item, Placeholders: updatedPlaceholders });
|
||||
});
|
||||
// console.log("Documents ", Documents);
|
||||
await batchQuery(Documents);
|
||||
};
|
||||
|
||||
const batchQuery = async (Documents) => {
|
||||
const serverUrl = localStorage.getItem("baseUrl");
|
||||
const functionsUrl = `${serverUrl}functions/batchdocuments`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const params = {
|
||||
Documents: JSON.stringify(Documents)
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(functionsUrl, params, { headers: headers });
|
||||
// console.log("res ", res);
|
||||
if (res.data && res.data.result) {
|
||||
props.handleClose("success", Documents?.length);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err ", err);
|
||||
props.handleClose("error", 0);
|
||||
} finally {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isSubmit && (
|
||||
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-40">
|
||||
<div
|
||||
style={{
|
||||
fontSize: "45px",
|
||||
color: "#3dd3e0"
|
||||
}}
|
||||
className="loader-37 "
|
||||
></div>
|
||||
</div>
|
||||
)}
|
||||
{props.Placeholders?.length > 0 ? (
|
||||
isSignatureExist ? (
|
||||
<>
|
||||
{props.Placeholders?.some((x) => !x.signerObjId) ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className=" min-h-max max-h-[250px] overflow-y-auto">
|
||||
{forms?.map((form, index) => (
|
||||
<div
|
||||
key={form.Id}
|
||||
className="p-3 rounded-xl border-[1px] border-gray-400 m-4 bg-white text-black grid grid-cols-1 md:grid-cols-2 gap-2 relative"
|
||||
>
|
||||
{form?.fields?.map((field, fieldIndex) => (
|
||||
<div className="flex flex-col " key={field.fieldId}>
|
||||
<label>{field.label}</label>
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.value}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(index, signer, fieldIndex)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{forms?.length > 1 && (
|
||||
<button
|
||||
onClick={() => handleRemoveForm(index)}
|
||||
className="absolute right-3 top-1 border border-gray-300 rounded-lg px-2 py-1"
|
||||
>
|
||||
<i className="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
)}
|
||||
<div ref={formRef}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col mx-4 mb-4 gap-3">
|
||||
<button
|
||||
onClick={handleAddForm}
|
||||
className="bg-[#32a3ac] p-2 text-white w-full rounded-full focus:outline-none"
|
||||
>
|
||||
<i className="fa-solid fa-plus"></i> <span>Add new</span>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-[#32a3ac] p-2 text-white w-full rounded-full focus:outline-none"
|
||||
>
|
||||
<i className="fa-solid fa-paper-plane"></i>{" "}
|
||||
<span>Send</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
All roles in this document are currently linked to contacts. To
|
||||
'quick send' copies of this template to multiple
|
||||
signers, please ensure that at least one role is not linked to
|
||||
any contact.
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
Please ensure there's at least one signature widget added for
|
||||
all recipients.
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
Please add at least one role to this template in order to 'quick
|
||||
send' copies of it to multiple signers.
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BulkSendUi;
|
||||
@@ -4,23 +4,49 @@ import FullScreenButton from "./FullScreenButton";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Parse from "parse";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
import { checkIsSubscribed, getAppLogo, openInNewTab } from "../constant/Utils";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
|
||||
const Header = ({ showSidebar }) => {
|
||||
const navigation = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
const { width } = useWindowSize();
|
||||
let applogo = localStorage.getItem("appLogo") || "";
|
||||
let username = localStorage.getItem("username");
|
||||
const image = localStorage.getItem("profileImg") || dp;
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(true);
|
||||
const [isPro, setIsPro] = useState(false);
|
||||
const [applogo, setAppLogo] = useState(
|
||||
localStorage.getItem("appLogo") || " "
|
||||
);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
useEffect(() => {
|
||||
checkSubscription();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
async function checkSubscription() {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
if (getIsSubscribe) {
|
||||
const applogo = await getAppLogo();
|
||||
if (applogo) {
|
||||
setAppLogo(applogo);
|
||||
} else {
|
||||
setAppLogo(localStorage.getItem("appLogo") || "");
|
||||
}
|
||||
}
|
||||
setIsPro(getIsSubscribe);
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
}
|
||||
}
|
||||
|
||||
const closeDropdown = () => {
|
||||
setIsOpen(false);
|
||||
Parse.User.logOut();
|
||||
if (Parse?.User?.current()) {
|
||||
Parse.User.logOut();
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let appName = localStorage.getItem("appName");
|
||||
@@ -43,7 +69,7 @@ const Header = ({ showSidebar }) => {
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
|
||||
navigation("/");
|
||||
navigate("/");
|
||||
};
|
||||
|
||||
//handle to close profile drop down menu onclick screen
|
||||
@@ -83,6 +109,21 @@ const Header = ({ showSidebar }) => {
|
||||
id="profile-menu"
|
||||
className="flex justify-between items-center gap-x-3"
|
||||
>
|
||||
{!isSubscribe && (
|
||||
<div>
|
||||
<button
|
||||
className="text-xs bg-[#002864] p-2 text-white rounded shadow"
|
||||
onClick={() => navigate("/subscription")}
|
||||
>
|
||||
Upgrade Now
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{isPro && (
|
||||
<div className="w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
PRO
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
@@ -123,7 +164,7 @@ const Header = ({ showSidebar }) => {
|
||||
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigation("/profile");
|
||||
navigate("/profile");
|
||||
}}
|
||||
>
|
||||
<i className="fa-regular fa-user"></i> Profile
|
||||
@@ -132,11 +173,19 @@ const Header = ({ showSidebar }) => {
|
||||
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigation("/changepassword");
|
||||
navigate("/changepassword");
|
||||
}}
|
||||
>
|
||||
<i className="fa-solid fa-lock"></i> Change Password
|
||||
</li>
|
||||
<li
|
||||
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
|
||||
onClick={() => {
|
||||
window.open("https://console.opensignlabs.com/");
|
||||
}}
|
||||
>
|
||||
<i className="fa-regular fa-id-card"></i> Console
|
||||
</li>
|
||||
<li
|
||||
className="hover:bg-gray-100 rounded-b-lg py-1 px-2 cursor-pointer font-normal"
|
||||
onClick={closeDropdown}
|
||||
|
||||
@@ -49,7 +49,7 @@ const LoginFacebook = ({
|
||||
const extRes = await Parse.Cloud.run("getUserDetails", params);
|
||||
// console.log("extRes ", extRes);
|
||||
if (extRes) {
|
||||
const params = { ...details, Phone: extRes.get("Phone") };
|
||||
const params = { ...details, Phone: extRes?.get("Phone") || "" };
|
||||
const payload = await Parse.Cloud.run("facebooksign", params);
|
||||
// console.log("payload ", payload);
|
||||
if (payload && payload.sessiontoken) {
|
||||
@@ -59,7 +59,7 @@ const LoginFacebook = ({
|
||||
const LocalUserDetails = {
|
||||
name: details.Name,
|
||||
email: details.Email,
|
||||
phone: extRes.get("Phone"),
|
||||
phone: extRes?.get("Phone") || "",
|
||||
company: extRes.get("Company")
|
||||
};
|
||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
||||
@@ -75,7 +75,7 @@ const LoginFacebook = ({
|
||||
const handleSubmitbtn = async () => {
|
||||
if (userDetails.Phone && userDetails.Company) {
|
||||
setThirdpartyLoader(true);
|
||||
const params = { ...fBDetails, Phone: userDetails.Phone };
|
||||
const params = { ...fBDetails, Phone: userDetails?.Phone || "" };
|
||||
const payload = await Parse.Cloud.run("facebooksign", params);
|
||||
// console.log("payload ", payload);
|
||||
|
||||
@@ -85,7 +85,7 @@ const LoginFacebook = ({
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
// "passsword":userDetails.Phone,
|
||||
phone: userDetails.Phone,
|
||||
phone: userDetails?.Phone || "",
|
||||
role: "contracts_User",
|
||||
company: userDetails.Company
|
||||
}
|
||||
@@ -96,7 +96,7 @@ const LoginFacebook = ({
|
||||
const LocalUserDetails = {
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
phone: userDetails.Phone,
|
||||
phone: userDetails?.Phone || "",
|
||||
company: userDetails.Company
|
||||
};
|
||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
||||
|
||||
@@ -110,22 +110,22 @@ const GoogleSignInBtn = ({
|
||||
const extRes = await Parse.Cloud.run("getUserDetails", params);
|
||||
// console.log("extRes ", extRes);
|
||||
if (extRes) {
|
||||
const params = { ...details, Phone: extRes.get("Phone") };
|
||||
const params = { ...details, Phone: extRes?.get("Phone") || "" };
|
||||
const payload = await Parse.Cloud.run("googlesign", params);
|
||||
// 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,
|
||||
email: details.Gmail,
|
||||
phone: extRes.get("Phone"),
|
||||
phone: extRes?.get("Phone") || "",
|
||||
company: extRes.get("Company")
|
||||
};
|
||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
||||
thirdpartyLoginfn(payload.sessiontoken, billingDate);
|
||||
thirdpartyLoginfn(payload.sessiontoken);
|
||||
}
|
||||
return { msg: "exist" };
|
||||
} else {
|
||||
@@ -139,7 +139,7 @@ const GoogleSignInBtn = ({
|
||||
setThirdpartyLoader(true);
|
||||
// e.preventDefault()
|
||||
// console.log("handelSubmit", userDetails);
|
||||
const params = { ...googleDetails, Phone: userDetails.Phone };
|
||||
const params = { ...googleDetails, Phone: userDetails?.Phone };
|
||||
const payload = await Parse.Cloud.run("googlesign", params);
|
||||
|
||||
// console.log("payload ", payload);
|
||||
@@ -149,7 +149,7 @@ const GoogleSignInBtn = ({
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
// "passsword":userDetails.Phone,
|
||||
phone: userDetails.Phone,
|
||||
phone: userDetails?.Phone || "",
|
||||
role: "contracts_User",
|
||||
company: userDetails.Company,
|
||||
jobTitle: userDetails.Destination
|
||||
@@ -161,7 +161,7 @@ const GoogleSignInBtn = ({
|
||||
const LocalUserDetails = {
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
phone: userDetails.Phone,
|
||||
phone: userDetails?.Phone || "",
|
||||
company: userDetails.Company
|
||||
// jobTitle: userDetails.JobTitle
|
||||
};
|
||||
|
||||
@@ -22,7 +22,8 @@ const RenderDebugPdf = (props) => {
|
||||
flex: 1,
|
||||
position: "relative",
|
||||
border: "1px solid grey",
|
||||
overflow: "auto"
|
||||
overflow: "auto",
|
||||
cursor: "crosshair"
|
||||
}}
|
||||
onMouseMove={props.handleMouseMoveDiv}
|
||||
>
|
||||
|
||||
@@ -149,7 +149,7 @@ const DashboardCard = (props) => {
|
||||
}
|
||||
})
|
||||
.then((res) => {
|
||||
const listData = res.data?.result.filter(
|
||||
const listData = res.data?.result?.filter(
|
||||
(x) => x.Signers.length > 0
|
||||
);
|
||||
let arr = [];
|
||||
|
||||
@@ -97,11 +97,13 @@ function DashboardReport(props) {
|
||||
setIsMoreDocs(false);
|
||||
}
|
||||
setIsNextRecord(false);
|
||||
setList((prevRecord) =>
|
||||
prevRecord.length > 0
|
||||
? [...prevRecord, ...res.data.result]
|
||||
: res.data.result
|
||||
);
|
||||
if (!res.data.result.error) {
|
||||
setList((prevRecord) =>
|
||||
prevRecord.length > 0
|
||||
? [...prevRecord, ...res.data.result]
|
||||
: res.data.result
|
||||
);
|
||||
}
|
||||
}
|
||||
setIsLoader(false);
|
||||
} catch (err) {
|
||||
@@ -110,6 +112,7 @@ function DashboardReport(props) {
|
||||
console.log("err ", err);
|
||||
setIsLoader(false);
|
||||
}
|
||||
setIsLoader(false);
|
||||
}
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
|
||||
@@ -70,6 +70,7 @@ function DriveBody(props) {
|
||||
return item;
|
||||
});
|
||||
props.setPdfData(updatedData);
|
||||
props.sortingData(null, null, updatedData);
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
|
||||
@@ -214,8 +215,8 @@ function DriveBody(props) {
|
||||
? true
|
||||
: false
|
||||
: selecFolderId
|
||||
? false
|
||||
: true;
|
||||
? false
|
||||
: true;
|
||||
if (!checkExist) {
|
||||
if (moveFolderId) {
|
||||
updateData = {
|
||||
@@ -384,6 +385,7 @@ function DriveBody(props) {
|
||||
flexDirection: "column",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
data-tut={props.dataTutSeventh}
|
||||
>
|
||||
<img
|
||||
alt="no img"
|
||||
@@ -451,6 +453,7 @@ function DriveBody(props) {
|
||||
className="pdf-icon"
|
||||
src={pdfLogo}
|
||||
onClick={() => checkPdfStatus(data)}
|
||||
data-tut={props.dataTutSixth}
|
||||
/>
|
||||
{rename === data.objectId ? (
|
||||
<input
|
||||
|
||||
@@ -30,7 +30,7 @@ const AddRoleModal = (props) => {
|
||||
margin: "10px 0 10px 5px"
|
||||
}}
|
||||
>
|
||||
e.g: Hr, Director, Manager, New joinee, Accountant, etc...
|
||||
e.g: Customer, Hr, Director, Manager, Student, etc...
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
|
||||
@@ -17,7 +17,7 @@ function DefaultSignature({ defaultSignImg, xyPostion, setDefaultSignAlert }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-tut="reactourThird">
|
||||
<div
|
||||
style={{
|
||||
background: themeColor,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { themeColor } from "../../constant/const";
|
||||
import { isEnableSubscription, themeColor } from "../../constant/const";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { radioButtonWidget } from "../../constant/Utils";
|
||||
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
function DropdownWidgetOption(props) {
|
||||
const [dropdownOptionList, setDropdownOptionList] = useState([
|
||||
"option-1",
|
||||
@@ -241,36 +242,59 @@ function DropdownWidgetOption(props) {
|
||||
}}
|
||||
className="fa-solid fa-square-plus"
|
||||
></i>
|
||||
{props.type === "checkbox" && !props.isSignYourself && (
|
||||
<>
|
||||
<label style={{ fontSize: "13px", fontWeight: "600" }}>
|
||||
Minimun check
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
defaultValue={0}
|
||||
value={minCount}
|
||||
onChange={(e) => {
|
||||
const count = handleSetMinMax(e);
|
||||
setMinCount(count);
|
||||
}}
|
||||
className="drodown-input"
|
||||
/>
|
||||
<label style={{ fontSize: "13px", fontWeight: "600" }}>
|
||||
Maximum check
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
defaultValue={0}
|
||||
value={maxCount}
|
||||
onChange={(e) => {
|
||||
const count = handleSetMinMax(e);
|
||||
setMaxCount(count);
|
||||
}}
|
||||
className="drodown-input"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div>
|
||||
{props.type === "checkbox" && !props.isSignYourself && (
|
||||
<>
|
||||
<label
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
fontWeight: "600",
|
||||
color: !props.isSubscribe && "gray"
|
||||
}}
|
||||
>
|
||||
Minimun check
|
||||
</label>
|
||||
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
<input
|
||||
required
|
||||
defaultValue={0}
|
||||
value={minCount}
|
||||
onChange={(e) => {
|
||||
const count = handleSetMinMax(e);
|
||||
setMinCount(count);
|
||||
}}
|
||||
className={
|
||||
props.isSubscribe || !isEnableSubscription
|
||||
? "drodown-input"
|
||||
: "disabled drodown-input"
|
||||
}
|
||||
/>
|
||||
<label
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
fontWeight: "600",
|
||||
color: !props.isSubscribe && "gray"
|
||||
}}
|
||||
>
|
||||
Maximum check
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
defaultValue={0}
|
||||
value={maxCount}
|
||||
onChange={(e) => {
|
||||
const count = handleSetMinMax(e);
|
||||
setMaxCount(count);
|
||||
}}
|
||||
className={
|
||||
props.isSubscribe || !isEnableSubscription
|
||||
? "drodown-input"
|
||||
: "disabled drodown-input"
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{["dropdown", radioButtonWidget].includes(props.type) && (
|
||||
<>
|
||||
@@ -378,13 +402,15 @@ function DropdownWidgetOption(props) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{props.type === "checkbox" && !props.isSignYourself && (
|
||||
<PremiumAlertHeader
|
||||
message={
|
||||
"Field validations are free in beta, this feature will incur a fee later."
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{props.type === "checkbox" &&
|
||||
!props.isSignYourself &&
|
||||
!isEnableSubscription && (
|
||||
<PremiumAlertHeader
|
||||
message={
|
||||
"Field validations are free in beta, this feature will incur a fee later."
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
props.type === "checkbox" && !props.isSignYourself
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import "../../styles/AddUser.css";
|
||||
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 }) => {
|
||||
@@ -8,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 });
|
||||
};
|
||||
@@ -24,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>
|
||||
@@ -45,7 +75,7 @@ const EditTemplate = ({ template, onSuccess }) => {
|
||||
fontWeight: "700"
|
||||
}}
|
||||
>
|
||||
{template.URL?.split("/")?.pop()?.split("_")[1]}
|
||||
{getFileName(template.URL)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-section">
|
||||
@@ -127,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">
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import React from "react";
|
||||
import { Quill } from "react-quill";
|
||||
|
||||
// Custom Undo button icon component for Quill editor. You can import it directly
|
||||
// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not
|
||||
// handle them correctly
|
||||
const CustomUndo = () => (
|
||||
<svg viewBox="0 0 18 18">
|
||||
<polygon className="ql-fill ql-stroke" points="6 10 4 12 2 10 6 10" />
|
||||
<path
|
||||
className="ql-stroke"
|
||||
d="M8.09,13.91A4.6,4.6,0,0,0,9,14,5,5,0,1,0,4,9"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Redo button icon component for Quill editor
|
||||
const CustomRedo = () => (
|
||||
<svg viewBox="0 0 18 18">
|
||||
<polygon className="ql-fill ql-stroke" points="12 10 14 12 16 10 12 10" />
|
||||
<path
|
||||
className="ql-stroke"
|
||||
d="M9.91,13.91A4.6,4.6,0,0,1,9,14a5,5,0,1,1,5-5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Undo and redo functions for Custom Toolbar
|
||||
function undoChange() {
|
||||
this.quill.history.undo();
|
||||
}
|
||||
function redoChange() {
|
||||
this.quill.history.redo();
|
||||
}
|
||||
|
||||
// Add sizes to whitelist and register them
|
||||
const Size = Quill.import("formats/size");
|
||||
Size.whitelist = ["extra-small", "small", "medium", "large"];
|
||||
Quill.register(Size, true);
|
||||
|
||||
// Add fonts to whitelist and register them
|
||||
const Font = Quill.import("formats/font");
|
||||
Font.whitelist = [
|
||||
"arial",
|
||||
"comic-sans",
|
||||
"courier-new",
|
||||
"georgia",
|
||||
"helvetica",
|
||||
"lucida"
|
||||
];
|
||||
Quill.register(Font, true);
|
||||
|
||||
// Modules object for setting up the Quill editor
|
||||
export const module1 = {
|
||||
toolbar: {
|
||||
container: "#toolbar1",
|
||||
handlers: {
|
||||
undo: undoChange,
|
||||
redo: redoChange
|
||||
}
|
||||
},
|
||||
history: {
|
||||
delay: 500,
|
||||
maxStack: 100,
|
||||
userOnly: true
|
||||
}
|
||||
};
|
||||
// Formats objects for setting up the Quill editor
|
||||
export const formats = [
|
||||
"header",
|
||||
"font",
|
||||
"size",
|
||||
"bold",
|
||||
"italic",
|
||||
"underline",
|
||||
"align",
|
||||
"strike",
|
||||
"script",
|
||||
"blockquote",
|
||||
"background",
|
||||
"list",
|
||||
"bullet",
|
||||
"indent",
|
||||
"link",
|
||||
"image",
|
||||
"color",
|
||||
"code-block"
|
||||
];
|
||||
// Quill Toolbar component
|
||||
export const QuillToolbar = ({ containerId }) => (
|
||||
<div id={containerId}>
|
||||
<span className="ql-formats">
|
||||
<select className="ql-font" defaultValue="arial">
|
||||
<option value="arial">Arial</option>
|
||||
<option value="comic-sans">Comic Sans</option>
|
||||
<option value="courier-new">Courier New</option>
|
||||
<option value="georgia">Georgia</option>
|
||||
<option value="helvetica">Helvetica</option>
|
||||
<option value="lucida">Lucida</option>
|
||||
</select>
|
||||
<select className="ql-size" defaultValue="medium">
|
||||
<option value="extra-small">Size 1</option>
|
||||
<option value="small">Size 2</option>
|
||||
<option value="medium">Size 3</option>
|
||||
<option value="large">Size 4</option>
|
||||
</select>
|
||||
<select className="ql-header" defaultValue="3">
|
||||
<option value="1">Heading</option>
|
||||
<option value="2">Subheading</option>
|
||||
<option value="3">Normal</option>
|
||||
</select>
|
||||
</span>
|
||||
<span className="ql-formats">
|
||||
<button className="ql-bold" />
|
||||
<button className="ql-italic" />
|
||||
<button className="ql-underline" />
|
||||
<button className="ql-strike" />
|
||||
</span>
|
||||
<span className="ql-formats">
|
||||
<button className="ql-list" value="ordered" />
|
||||
<button className="ql-list" value="bullet" />
|
||||
<button className="ql-indent" value="-1" />
|
||||
<button className="ql-indent" value="+1" />
|
||||
</span>
|
||||
<span className="ql-formats">
|
||||
<button className="ql-script" value="super" />
|
||||
<button className="ql-script" value="sub" />
|
||||
<button className="ql-blockquote" />
|
||||
<button className="ql-direction" />
|
||||
</span>
|
||||
<span className="ql-formats">
|
||||
<select className="ql-align" />
|
||||
<select className="ql-color" />
|
||||
<select className="ql-background" />
|
||||
</span>
|
||||
<span className="ql-formats">
|
||||
<button className="ql-link" />
|
||||
<button className="ql-image" />
|
||||
<button className="ql-video" />
|
||||
</span>
|
||||
<span className="ql-formats">
|
||||
<button className="ql-formula" />
|
||||
<button className="ql-code-block" />
|
||||
<button className="ql-clean" />
|
||||
</span>
|
||||
<span className="ql-formats">
|
||||
<button className="ql-undo">
|
||||
<CustomUndo />
|
||||
</button>
|
||||
<button className="ql-redo">
|
||||
<CustomRedo />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default QuillToolbar;
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
import Tooltip from "../../primitives/Tooltip";
|
||||
|
||||
import ReactQuill from "react-quill";
|
||||
import "react-quill/dist/quill.snow.css";
|
||||
import EditorToolbar, { module1, formats } from "./EditorToolbar";
|
||||
|
||||
export function EmailBody(props) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={`flex justify-center py-1 p-2`}>
|
||||
<div className=" flex flex-col">
|
||||
<form>
|
||||
<div className="text-lg font-normal text-black">
|
||||
<div className=" m-2 md:m-10 p-3 md:p-10 shadow-md hover:shadow-lg border-[1px] border-indigo-800 rounded-md">
|
||||
<label className="text-sm ml-2">
|
||||
Subject <Tooltip message={"email subject"} />
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
value={props.requestSubject}
|
||||
onChange={(e) => props.setRequestSubject(e.target.value)}
|
||||
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">
|
||||
Body <Tooltip message={"email body"} />
|
||||
</label>
|
||||
|
||||
<div className="px-3 py-2 w-full focus:outline-none text-xs">
|
||||
<EditorToolbar containerId="toolbar1" />
|
||||
<ReactQuill
|
||||
theme="snow"
|
||||
value={props.requestBody}
|
||||
// value={typedText}
|
||||
placeholder="add body of email "
|
||||
// readOnly={loading}
|
||||
// onChangeSelection={handleTextSelection} // Listen for text selection
|
||||
ref={props.editorRef}
|
||||
modules={module1}
|
||||
formats={formats}
|
||||
onChange={props.handleOnchangeRequest}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -10,19 +10,22 @@ import loader from "../../assets/images/loader2.gif";
|
||||
function EmailComponent({
|
||||
isEmail,
|
||||
pdfUrl,
|
||||
isCeleb,
|
||||
setIsEmail,
|
||||
setSuccessEmail,
|
||||
pdfName,
|
||||
sender,
|
||||
setIsAlert
|
||||
setIsAlert,
|
||||
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 {
|
||||
@@ -38,6 +41,8 @@ function EmailComponent({
|
||||
const openSignUrl = "https://www.opensignlabs.com/contact-us";
|
||||
const themeBGcolor = themeColor;
|
||||
let params = {
|
||||
mailProvider: activeMailAdapter,
|
||||
extUserId: extUserId,
|
||||
pdfName: pdfName,
|
||||
url: pdfUrl,
|
||||
recipient: emailList[i],
|
||||
@@ -57,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);
|
||||
@@ -240,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,26 +1,24 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import PrevNext from "./PrevNext";
|
||||
import { PDFDownloadLink } from "@react-pdf/renderer";
|
||||
import Certificate from "./Certificate";
|
||||
import printModule from "print-js";
|
||||
import { getBase64FromUrl } from "../../constant/Utils";
|
||||
import { saveAs } from "file-saver";
|
||||
import {
|
||||
handleDownloadCertificate,
|
||||
handleDownloadPdf,
|
||||
handleToPrint
|
||||
} from "../../constant/Utils";
|
||||
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 ModalUi from "../../primitives/ModalUi";
|
||||
|
||||
function Header({
|
||||
isPdfRequestFiles,
|
||||
isPlaceholder,
|
||||
recipient,
|
||||
setIsDecline,
|
||||
pageNumber,
|
||||
isAlreadySign,
|
||||
allPages,
|
||||
changePage,
|
||||
pdfUrl,
|
||||
documentStatus,
|
||||
embedWidgetsData,
|
||||
pdfDetails,
|
||||
signerPos,
|
||||
@@ -42,6 +40,7 @@ function Header({
|
||||
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const navigate = useNavigate();
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
//for go to previous page
|
||||
function previousPage() {
|
||||
@@ -60,143 +59,6 @@ function Header({
|
||||
setIsDecline(currentDecline);
|
||||
};
|
||||
|
||||
//function for print digital sign pdf
|
||||
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))
|
||||
);
|
||||
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 });
|
||||
}
|
||||
};
|
||||
|
||||
//handle download signed pdf
|
||||
const handleDownloadPdf = () => {
|
||||
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
|
||||
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
|
||||
};
|
||||
|
||||
const sanitizeFileName = (pdfName) => {
|
||||
// Replace spaces with underscore
|
||||
return pdfName.replace(/ /g, "_");
|
||||
};
|
||||
//certificate generate and download component in mobile view
|
||||
const CertificateDropDown = () => {
|
||||
//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 }) => (
|
||||
<>
|
||||
{loading ? (
|
||||
<div
|
||||
style={{
|
||||
border: "none",
|
||||
backgroundColor: "#fff"
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa-solid fa-award"
|
||||
style={{ marginRight: "2px" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Certificate
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{ border: "none", backgroundColor: "#fff" }}
|
||||
onClick={() =>
|
||||
handleDownload(
|
||||
blob,
|
||||
`completion certificate-${
|
||||
pdfDetails[0] && pdfDetails[0].Name
|
||||
}.pdf`
|
||||
)
|
||||
}
|
||||
>
|
||||
<i
|
||||
className="fa-solid fa-award"
|
||||
style={{ marginRight: "2px" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Certificate
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PDFDownloadLink>
|
||||
);
|
||||
};
|
||||
const CertificateComponent = () => {
|
||||
return (
|
||||
<PDFDownloadLink
|
||||
style={{ textDecoration: "none" }}
|
||||
document={<Certificate pdfData={pdfDetails} />}
|
||||
fileName={`completion certificate-${
|
||||
pdfDetails[0] && pdfDetails[0].Name
|
||||
}.pdf`}
|
||||
>
|
||||
{({ loading }) => (
|
||||
<button
|
||||
type="button"
|
||||
className="defaultBtn certificateBtn"
|
||||
disabled={loading}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa-solid fa-award"
|
||||
style={{
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
marginRight: "3px"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Certificate
|
||||
</button>
|
||||
)}
|
||||
</PDFDownloadLink>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div style={{ padding: "5px 0px 5px 0px" }} className="mobileHead">
|
||||
{isMobile && isShowHeader ? (
|
||||
@@ -210,11 +72,7 @@ function Header({
|
||||
}}
|
||||
>
|
||||
<div className="preBtn2">
|
||||
<div
|
||||
onClick={() => {
|
||||
navigate(-1);
|
||||
}}
|
||||
>
|
||||
<div onClick={() => navigate(-1)}>
|
||||
<i
|
||||
className="fa fa-arrow-left"
|
||||
aria-hidden="true"
|
||||
@@ -266,7 +124,6 @@ function Header({
|
||||
<i className="fa fa-ellipsis-v" aria-hidden="true"></i>
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="DropdownMenuContent"
|
||||
@@ -274,7 +131,9 @@ function Header({
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => handleDownloadPdf()}
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
@@ -290,46 +149,56 @@ function Header({
|
||||
Download
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
{recipient && pdfDetails[0] && pdfDetails.length > 0 ? (
|
||||
<DropdownMenu.Item className="DropdownMenuItem">
|
||||
<CertificateDropDown />
|
||||
{isCompleted && (
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(
|
||||
pdfDetails,
|
||||
setIsDownloading
|
||||
)
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
border: "none",
|
||||
backgroundColor: "#fff"
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa-solid fa-award"
|
||||
style={{ marginRight: "2px" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Certificate
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
) : isPdfRequestFiles &&
|
||||
alreadySign &&
|
||||
isCompleted.isCertificate ? (
|
||||
<DropdownMenu.Item className="DropdownMenuItem">
|
||||
<CertificateDropDown />
|
||||
)}
|
||||
{isSignYourself && (
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => setIsEmail(true)}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa fa-envelope"
|
||||
style={{ marginRight: "2px" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Mail
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
) : (
|
||||
isSignYourself && (
|
||||
<>
|
||||
<DropdownMenu.Item className="DropdownMenuItem">
|
||||
<CertificateDropDown />
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => setIsEmail(true)}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa fa-envelope"
|
||||
style={{ marginRight: "2px" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Mail
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={handleToPrint}
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
@@ -337,7 +206,6 @@ function Header({
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
<i
|
||||
className="fa fa-print"
|
||||
aria-hidden="true"
|
||||
@@ -350,24 +218,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>
|
||||
@@ -379,12 +239,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"}
|
||||
@@ -399,343 +255,279 @@ 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(
|
||||
pdfDetails,
|
||||
pdfUrl,
|
||||
setIsDownloading
|
||||
)
|
||||
}
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="signatureHeader headerBtn">
|
||||
<div className="flex flex-wrap justify-between items-center mt-[5px]">
|
||||
<PrevNext
|
||||
pageNumber={pageNumber}
|
||||
allPages={allPages}
|
||||
changePage={changePage}
|
||||
/>
|
||||
|
||||
{recipient ? (
|
||||
pdfUrl || isAlreadySign.mssg ? (
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
{pdfDetails && pdfDetails.length > 0 && (
|
||||
<CertificateComponent />
|
||||
)}
|
||||
<button
|
||||
onClick={handleToPrint}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
className="defaultBtn printBtn"
|
||||
>
|
||||
<i
|
||||
className="fa fa-print"
|
||||
style={{
|
||||
fontSize: "15px",
|
||||
marginRight: "3px",
|
||||
color: "white"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Print
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
className="defaultBtn downloadBtn"
|
||||
onClick={() => handleDownloadPdf()}
|
||||
>
|
||||
<i
|
||||
className="fa fa-download"
|
||||
style={{
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
marginRight: "3px"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
<button
|
||||
style={{
|
||||
background: "#de4337"
|
||||
}}
|
||||
className="finishBtn hoverCss"
|
||||
onClick={() => {
|
||||
handleDeclinePdfAlert();
|
||||
}}
|
||||
>
|
||||
Decline
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-tut="reactourThird"
|
||||
style={{
|
||||
background: !pdfUrl ? "#188ae2" : "#d3edeb"
|
||||
}}
|
||||
className="finishBtn sendHover"
|
||||
onClick={() => {
|
||||
if (!pdfUrl) {
|
||||
embedWidgetsData();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Finish
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
) : isPlaceholder ? (
|
||||
{isPlaceholder ? (
|
||||
<>
|
||||
{!isMailSend &&
|
||||
signersdata.length > 0 &&
|
||||
signersdata.length !== filterPrefill.length && (
|
||||
<div>
|
||||
{filterPrefill.length === 0 ? (
|
||||
<span style={{ fontSize: "13px", color: "#f5405e" }}>
|
||||
Add {signersdata.length - filterPrefill.length}{" "}
|
||||
recipients signature
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ fontSize: "13px", color: "#f5405e" }}>
|
||||
Add {signersdata.length - filterPrefill.length} more
|
||||
recipients signature
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<div className="flex mx-[100px] lg:mx-0 order-last lg:order-none">
|
||||
{!isMailSend &&
|
||||
signersdata.length > 0 &&
|
||||
signersdata.length !== filterPrefill.length && (
|
||||
<div>
|
||||
{filterPrefill.length === 0 ? (
|
||||
<span style={{ fontSize: "13px", color: "#f5405e" }}>
|
||||
Add {signersdata.length - filterPrefill.length}{" "}
|
||||
recipients signature
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ fontSize: "13px", color: "#f5405e" }}>
|
||||
Add {signersdata.length - filterPrefill.length} more
|
||||
recipients signature
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex">
|
||||
{setIsEditTemplate && (
|
||||
<button
|
||||
onClick={() => setIsEditTemplate(true)}
|
||||
style={{
|
||||
border: "none",
|
||||
outline: "none",
|
||||
textAlign: "center"
|
||||
}}
|
||||
className="outline-none border-none text-center mr-[5px]"
|
||||
>
|
||||
<i className="fa-solid fa-gear fa-lg"></i>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate(-1);
|
||||
}}
|
||||
onClick={() => navigate(-1)}
|
||||
type="button"
|
||||
className="defaultBtn backBtn"
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-black font-[500] text-sm mr-[5px] bg-white"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
|
||||
<button
|
||||
disabled={isMailSend && true}
|
||||
data-tut="reactourFour"
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] font-[500] text-sm mr-[5px]"
|
||||
style={{
|
||||
background: isMailSend ? "rgb(203, 203, 203)" : "#188ae2",
|
||||
color: isMailSend && "rgb(77, 75, 75)"
|
||||
color: isMailSend ? "rgb(77, 75, 75)" : "white"
|
||||
}}
|
||||
onClick={() => {
|
||||
alertSendEmail();
|
||||
}}
|
||||
className={isMailSend ? "sendMail" : "sendMail sendHover"}
|
||||
>
|
||||
{completeBtnTitle ? completeBtnTitle : "Send"}
|
||||
{completeBtnTitle
|
||||
? completeBtnTitle
|
||||
: isMailSend
|
||||
? "Sent"
|
||||
: "Send"}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : isPdfRequestFiles ? (
|
||||
alreadySign ? (
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
{isCompleted.isCertificate && <CertificateComponent />}
|
||||
<div className="flex flex-row">
|
||||
{isCompleted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(pdfDetails, setIsDownloading)
|
||||
}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
|
||||
>
|
||||
<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}
|
||||
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
|
||||
type="button"
|
||||
className="defaultBtn printBtn"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
|
||||
>
|
||||
<i
|
||||
className="fa fa-print"
|
||||
style={{
|
||||
fontSize: "15px",
|
||||
marginRight: "3px",
|
||||
color: "white"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Print
|
||||
<i className="fa fa-print py-[3px]" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">Print</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="defaultBtn downloadBtn"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
onClick={() => handleDownloadPdf()}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<i
|
||||
className="fa fa-download"
|
||||
style={{
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
marginRight: "3px"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Download
|
||||
<i className="fa fa-download py-[3px]" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">Download</span>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="flex" data-tut="reactourFifth">
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate(-1);
|
||||
}}
|
||||
onClick={() => navigate(-1)}
|
||||
type="button"
|
||||
className="defaultBtn backBtn"
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-black font-[500] text-sm mr-[5px] bg-white"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
{currentSigner && (
|
||||
<>
|
||||
<button
|
||||
style={{
|
||||
background: "#de4337"
|
||||
}}
|
||||
className="finishBtn hoverCss"
|
||||
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="finishBtn sendHover"
|
||||
onClick={() => {
|
||||
embedWidgetsData();
|
||||
}}
|
||||
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()}
|
||||
>
|
||||
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(
|
||||
pdfDetails,
|
||||
pdfUrl,
|
||||
setIsDownloading
|
||||
)
|
||||
}
|
||||
>
|
||||
<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>
|
||||
)
|
||||
) : pdfUrl || (documentStatus && documentStatus.isCompleted) ? (
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
<CertificateComponent />
|
||||
) : isCompleted ? (
|
||||
<div className="flex flex-row">
|
||||
{isCompleted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(pdfDetails, setIsDownloading)
|
||||
}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
|
||||
>
|
||||
<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}
|
||||
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
|
||||
type="button"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
className="defaultBtn printBtn"
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
|
||||
>
|
||||
<i
|
||||
className="fa fa-print"
|
||||
style={{
|
||||
fontSize: "15px",
|
||||
marginRight: "3px",
|
||||
color: "white"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Print
|
||||
<i className="fa fa-print py-[3px]" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">Print</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="defaultBtn downloadBtn"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginLeft: "10px"
|
||||
}}
|
||||
onClick={() => handleDownloadPdf()}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<i
|
||||
className="fa fa-download"
|
||||
style={{
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
marginRight: "3px"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Download
|
||||
<i className="fa fa-download py-[3px]" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">Download</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="defaultBtn mailBtn"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginLeft: "10px"
|
||||
}}
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#3ba7e5]"
|
||||
onClick={() => setIsEmail(true)}
|
||||
>
|
||||
<i
|
||||
className="fa fa-envelope"
|
||||
style={{
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
marginRight: "3px"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Mail
|
||||
<i className="fa fa-envelope py-[3px]" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">Mail</span>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="flex">
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate(-1);
|
||||
}}
|
||||
type="button"
|
||||
className="defaultBtn backBtn"
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-black font-[500] text-sm mr-[5px] bg-white"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
|
||||
<button
|
||||
style={{
|
||||
background: "#188ae2"
|
||||
}}
|
||||
type="button"
|
||||
className="finishBtn sendHover"
|
||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
|
||||
onClick={() => {
|
||||
if (!pdfUrl) {
|
||||
embedWidgetsData();
|
||||
@@ -748,6 +540,34 @@ function Header({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isDownloading === "pdf" && (
|
||||
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
|
||||
<div
|
||||
style={{ fontSize: "45px", color: "#3dd3e0" }}
|
||||
className="loader-37"
|
||||
></div>
|
||||
</div>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isDownloading === "certificate"}
|
||||
title={
|
||||
isDownloading === "certificate"
|
||||
? "Generating certificate"
|
||||
: "PDF Download"
|
||||
}
|
||||
handleClose={() => setIsDownloading("")}
|
||||
>
|
||||
<div className="p-3 md:p-5 text-[13px] md:text-base text-center">
|
||||
{isDownloading === "certificate"}{" "}
|
||||
<p>
|
||||
Your completion certificate is being generated. Please wait
|
||||
momentarily.
|
||||
</p>
|
||||
<p>
|
||||
If the download doesn't start shortly, click the button again.
|
||||
</p>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -416,7 +416,7 @@ function Placeholder(props) {
|
||||
|
||||
{props.pos.type !== textWidget && !props.isSignYourself && (
|
||||
<i
|
||||
data-tut="reactourLinkUser"
|
||||
data-tut="assignSigner"
|
||||
className="fa-regular fa-user signUserIcon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -542,7 +542,15 @@ function Placeholder(props) {
|
||||
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
|
||||
key={props.pos.key}
|
||||
lockAspectRatio={
|
||||
props.pos.type !== textWidget &&
|
||||
// props.pos.type !== textWidget
|
||||
![
|
||||
textWidget,
|
||||
"email",
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
textInputWidget
|
||||
].includes(props.pos.type) &&
|
||||
(props.pos.Width
|
||||
? props.pos.Width / props.pos.Height
|
||||
: defaultWidthHeight(props.pos.type).width /
|
||||
|
||||
@@ -38,15 +38,17 @@ function PlaceholderType(props) {
|
||||
"December"
|
||||
];
|
||||
const validateExpression = (regexValidation) => {
|
||||
let regexObject = regexValidation;
|
||||
if (props.pos?.options.validation.type === "regex") {
|
||||
regexObject = RegexParser(regexValidation);
|
||||
}
|
||||
// new RegExp(regexValidation);
|
||||
let isValidate = regexObject.test(textValue);
|
||||
if (!isValidate) {
|
||||
props?.setValidateAlert(true);
|
||||
inputRef.current.focus();
|
||||
if (textValue && regexValidation) {
|
||||
let regexObject = regexValidation;
|
||||
if (props.pos?.options?.validation?.type === "regex") {
|
||||
regexObject = RegexParser(regexValidation);
|
||||
}
|
||||
// new RegExp(regexValidation);
|
||||
let isValidate = regexObject.test(textValue);
|
||||
if (!isValidate) {
|
||||
props?.setValidateAlert(true);
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -54,22 +56,18 @@ 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,}$/;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
case "number":
|
||||
regexValidation = /^\d+$/;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
case "text":
|
||||
regexValidation = /^[a-zA-Z\s]+$/;
|
||||
regexValidation = /^[0-9\s]*$/;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
default:
|
||||
regexValidation = props.pos?.options.validation.pattern;
|
||||
regexValidation = props.pos?.options?.validation?.pattern || "";
|
||||
validateExpression(regexValidation);
|
||||
}
|
||||
}
|
||||
@@ -87,11 +85,11 @@ function PlaceholderType(props) {
|
||||
case "number":
|
||||
setValidatePlaceholder("12345");
|
||||
break;
|
||||
case textInputWidget:
|
||||
setValidatePlaceholder("enter text");
|
||||
case "text":
|
||||
setValidatePlaceholder("please enter text");
|
||||
break;
|
||||
default:
|
||||
setValidatePlaceholder("enter text");
|
||||
setValidatePlaceholder("please enter value");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +133,7 @@ function PlaceholderType(props) {
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.pos]);
|
||||
}, [props.pos?.options?.defaultValue]);
|
||||
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
@@ -288,7 +286,7 @@ function PlaceholderType(props) {
|
||||
};
|
||||
//handle height on enter press in text area
|
||||
const handleEnterPress = (e) => {
|
||||
const height = 15;
|
||||
const height = 18;
|
||||
if (e.key === "Enter") {
|
||||
//function to save height of text area
|
||||
onChangeHeightOfTextArea(
|
||||
@@ -403,19 +401,12 @@ function PlaceholderType(props) {
|
||||
case textInputWidget:
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<input
|
||||
className="inputPlaceholder"
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={validatePlaceholder}
|
||||
style={{ fontSize: calculateFontSize() }}
|
||||
placeholder={validatePlaceholder || "please enter value"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
type="text"
|
||||
tabIndex="0"
|
||||
disabled={
|
||||
props.isNeedSign && props.data?.signerObjId !== props.signerObjId
|
||||
? true
|
||||
: props.isPlaceholder
|
||||
}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
setTextValue(e.target.value);
|
||||
@@ -429,6 +420,13 @@ function PlaceholderType(props) {
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={
|
||||
isMobile
|
||||
? "labelTextArea labelWidthMobile"
|
||||
: "labelTextArea labelWidthDesktop"
|
||||
}
|
||||
style={{ whiteSpace: "pre-wrap" }}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@@ -506,15 +504,12 @@ function PlaceholderType(props) {
|
||||
case "name":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<input
|
||||
tabIndex="0"
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"name"}
|
||||
style={{ fontSize: calculateFontSize() }}
|
||||
className="inputPlaceholder"
|
||||
type="text"
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
const isDefault = false;
|
||||
handleTextValid(e);
|
||||
@@ -528,6 +523,13 @@ function PlaceholderType(props) {
|
||||
isDefault
|
||||
);
|
||||
}}
|
||||
className={
|
||||
isMobile
|
||||
? "labelTextArea labelWidthMobile"
|
||||
: "labelTextArea labelWidthDesktop"
|
||||
}
|
||||
style={{ whiteSpace: "pre-wrap" }}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@@ -542,14 +544,12 @@ function PlaceholderType(props) {
|
||||
case "company":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<input
|
||||
className="inputPlaceholder"
|
||||
type="text"
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"company"}
|
||||
style={{ fontSize: calculateFontSize() }}
|
||||
placeholder="Enter text"
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
@@ -562,6 +562,13 @@ function PlaceholderType(props) {
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={
|
||||
isMobile
|
||||
? "labelTextArea labelWidthMobile"
|
||||
: "labelTextArea labelWidthDesktop"
|
||||
}
|
||||
style={{ whiteSpace: "pre-wrap" }}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@@ -576,14 +583,12 @@ function PlaceholderType(props) {
|
||||
case "job title":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<input
|
||||
className="inputPlaceholder"
|
||||
type="text"
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"job title"}
|
||||
style={{ fontSize: calculateFontSize() }}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
@@ -596,6 +601,13 @@ function PlaceholderType(props) {
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={
|
||||
isMobile
|
||||
? "labelTextArea labelWidthMobile"
|
||||
: "labelTextArea labelWidthDesktop"
|
||||
}
|
||||
style={{ whiteSpace: "pre-wrap" }}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@@ -692,12 +704,11 @@ function PlaceholderType(props) {
|
||||
case "email":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<input
|
||||
className="inputPlaceholder"
|
||||
type="text"
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"email"}
|
||||
style={{ fontSize: calculateFontSize() }}
|
||||
placeholder={"enter email"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
@@ -712,6 +723,13 @@ function PlaceholderType(props) {
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={
|
||||
isMobile
|
||||
? "labelTextArea labelWidthMobile"
|
||||
: "labelTextArea labelWidthDesktop"
|
||||
}
|
||||
style={{ whiteSpace: "pre-wrap" }}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@@ -760,7 +778,7 @@ function PlaceholderType(props) {
|
||||
case textWidget:
|
||||
return (
|
||||
<textarea
|
||||
placeholder="Enter label"
|
||||
placeholder="Enter text"
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
@@ -782,7 +800,7 @@ function PlaceholderType(props) {
|
||||
? "labelTextArea labelWidthMobile"
|
||||
: "labelTextArea labelWidthDesktop"
|
||||
}
|
||||
style={{ whiteSpace: "pre-wrap" }}
|
||||
style={{ whiteSpace: "pre-wrap", overflow: "hidden" }}
|
||||
cols="50"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { themeColor } from "../../constant/const";
|
||||
|
||||
function PrevNext({ pageNumber, allPages, changePage }) {
|
||||
//for go to previous page
|
||||
@@ -14,41 +15,35 @@ function PrevNext({ pageNumber, allPages, changePage }) {
|
||||
<div>
|
||||
<div className="preBtn1">
|
||||
<button
|
||||
style={{
|
||||
padding: "3px 20px ",
|
||||
fontSize: "10px",
|
||||
background: "#d3edeb",
|
||||
fontWeight: "600",
|
||||
|
||||
border: "0px",
|
||||
marginRight: "5px"
|
||||
}}
|
||||
className="py-[3px] px-[10px] text-xs bg-[#d3edeb] font-[600] mr-[5px]"
|
||||
disabled={pageNumber <= 1}
|
||||
onClick={previousPage}
|
||||
>
|
||||
Prev
|
||||
<span className="block lg:hidden">
|
||||
<i
|
||||
className="fa fa-backward"
|
||||
aria-hidden="true"
|
||||
style={{ color: themeColor, cursor: "pointer" }}
|
||||
></i>
|
||||
</span>
|
||||
<span className="lg:block hidden">Prev</span>
|
||||
</button>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
fontWeight: "600"
|
||||
}}
|
||||
>
|
||||
<span className="text-xs font-[500]">
|
||||
{pageNumber || (allPages ? 1 : "--")} of {allPages || "--"}
|
||||
</span>
|
||||
<button
|
||||
style={{
|
||||
padding: "3px 20px ",
|
||||
fontSize: "10px",
|
||||
background: "#d3edeb",
|
||||
fontWeight: "600",
|
||||
marginLeft: "5px",
|
||||
border: "0px"
|
||||
}}
|
||||
className="py-[3px] px-[10px] text-xs bg-[#d3edeb] font-[600] ml-[5px]"
|
||||
disabled={pageNumber >= allPages}
|
||||
onClick={nextPage}
|
||||
>
|
||||
Next
|
||||
<span className="block lg:hidden">
|
||||
<i
|
||||
className="fa fa-forward"
|
||||
aria-hidden="true"
|
||||
style={{ color: themeColor, cursor: "pointer" }}
|
||||
></i>
|
||||
</span>
|
||||
<span className="lg:block hidden">Next</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -230,7 +230,11 @@ const RecipientList = (props) => {
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{obj.Name && <span className={"useEmail"}>{obj.Role}</span>}
|
||||
{obj.Name && (
|
||||
<span className={"useEmail"}>
|
||||
{obj?.Role || obj?.Email}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isMobile && props.sendInOrder && (
|
||||
|
||||
@@ -1,22 +1,62 @@
|
||||
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";
|
||||
|
||||
function RenderAllPdfPage({
|
||||
pdfUrl,
|
||||
signPdfUrl,
|
||||
allPages,
|
||||
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
|
||||
@@ -50,8 +90,7 @@ function RenderAllPdfPage({
|
||||
<Document
|
||||
loading={"Loading Document.."}
|
||||
onLoadSuccess={onDocumentLoad}
|
||||
file={pdfUrl ? pdfUrl : signPdfUrl}
|
||||
// file="https://api.printnode.com/static/test/pdf/multipage.pdf"
|
||||
file={signPdfUrl}
|
||||
>
|
||||
{Array.from(new Array(allPages), (el, index) => (
|
||||
<div
|
||||
@@ -68,7 +107,8 @@ function RenderAllPdfPage({
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
contain: "content"
|
||||
|
||||
position: "relative"
|
||||
}}
|
||||
onClick={() => {
|
||||
setPageNumber(index + 1);
|
||||
@@ -77,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>
|
||||
|
||||
@@ -204,34 +204,34 @@ function RenderPdf({
|
||||
};
|
||||
//function for render placeholder block over pdf document
|
||||
const checkSignedSignes = (data) => {
|
||||
const checkSign = signedSigners.filter(
|
||||
(sign) => sign.objectId === data.signerObjId
|
||||
let checkSign = [];
|
||||
//condition to handle quick send flow and using normal request sign flow
|
||||
checkSign = signedSigners.filter(
|
||||
(sign) => sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
|
||||
);
|
||||
if (data.signerObjId === signerObjectId) {
|
||||
setCurrentSigner(true);
|
||||
}
|
||||
const handleAllUserName = (Id, Role, type) => {
|
||||
return pdfDetails[0].Signers.map((signerData, key) => {
|
||||
return (
|
||||
signerData.objectId === data.signerObjId && (
|
||||
<React.Fragment key={key}>
|
||||
{signerData?.Name && (
|
||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
||||
{signerData.Name}
|
||||
</div>
|
||||
)}
|
||||
{type && (
|
||||
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
|
||||
)}
|
||||
{Role && (
|
||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
});
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
||||
{
|
||||
pdfDetails[0].Signers?.find(
|
||||
(signer) => signer.objectId === data.signerObjId
|
||||
)?.Name
|
||||
}
|
||||
</div>
|
||||
{type && (
|
||||
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
|
||||
)}
|
||||
{Role && (
|
||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -296,9 +296,11 @@ function RenderPdf({
|
||||
{type && (
|
||||
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
|
||||
)}
|
||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
{Role && (
|
||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
@@ -332,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"
|
||||
@@ -515,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
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { themeColor } from "../../constant/const";
|
||||
import RecipientList from "./RecipientList";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
function SignerListPlace(props) {
|
||||
return (
|
||||
@@ -13,12 +14,64 @@ function SignerListPlace(props) {
|
||||
>
|
||||
<span className="signedStyle">
|
||||
{props.title ? props.title : "Recipients"}
|
||||
<span className="absolute text-xs z-[30] mt-1 ml-0.5">
|
||||
{props?.title === "Roles" && (
|
||||
<>
|
||||
<a data-tooltip-id="my-tooltip">
|
||||
<sup>
|
||||
<i
|
||||
className="fa-solid fa-question rounded-full"
|
||||
style={{
|
||||
borderColor: "white",
|
||||
color: "white",
|
||||
fontSize: 11,
|
||||
borderWidth: 1.5,
|
||||
padding: "1px 3px"
|
||||
}}
|
||||
></i>
|
||||
</sup>
|
||||
</a>
|
||||
<Tooltip id="my-tooltip">
|
||||
<div className="max-w-[450px]">
|
||||
<p className="font-bold">What are template roles?</p>
|
||||
<p>
|
||||
Begin by specifying each role needed for the completion of
|
||||
the document. Think about the parties involved in the
|
||||
signing process and what their responsibilities are.
|
||||
Common roles include HR for internal documents, Customer
|
||||
for agreements or Vendor for business agreements.{" "}
|
||||
</p>
|
||||
<p className="font-bold">
|
||||
Why pre-attach users to some roles?
|
||||
</p>
|
||||
<p>
|
||||
For roles that consistently involve the same individual
|
||||
(e.g., the CEO's signature on employee offer
|
||||
letters), you can pre-attach a user to a role within the
|
||||
template. This step is optional but recommended for
|
||||
efficiency and consistency across documents.
|
||||
</p>
|
||||
<p className="font-bold">
|
||||
When do i specify the user attached to each role?
|
||||
</p>
|
||||
<p>
|
||||
When you create a document from your template, you'll
|
||||
be prompted to attach users to each defined role. If a
|
||||
role already has a user attached, this will be pre-filled,
|
||||
but you can modify it as needed before sending out the
|
||||
document.
|
||||
</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="signerList">
|
||||
<RecipientList {...props} />
|
||||
</div>
|
||||
{props.handleAddSigner && (
|
||||
{props.handleAddSigner ? (
|
||||
<div
|
||||
data-tut="reactourAddbtn"
|
||||
className="p-[10px] my-[2px] flex flex-row items-center justify-center border-[1px] border-[#47a3ad] hover:bg-[#47a3ad] text-[#47a3ad] hover:text-white cursor-pointer"
|
||||
@@ -31,6 +84,19 @@ function SignerListPlace(props) {
|
||||
<i className="fa-solid fa-plus"></i>
|
||||
<span style={{ marginLeft: 2 }}>Add role</span>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
data-tut="reactourAddbtn"
|
||||
className="p-[10px] my-[2px] flex flex-row items-center justify-center border-[1px] border-[#47a3ad] hover:bg-[#47a3ad] text-[#47a3ad] hover:text-white cursor-pointer"
|
||||
onClick={() => props.setIsAddSigner(true)}
|
||||
style={{
|
||||
opacity: props.isMailSend && "0.5",
|
||||
pointerEvents: props.isMailSend && "none"
|
||||
}}
|
||||
>
|
||||
<i className="fa-solid fa-plus"></i>
|
||||
<span style={{ marginLeft: 2 }}>Add recipients</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
@@ -38,7 +38,8 @@ function WidgetComponent({
|
||||
sendInOrder,
|
||||
isTemplateFlow,
|
||||
setBlockColor,
|
||||
blockColor
|
||||
blockColor,
|
||||
setIsAddSigner
|
||||
}) {
|
||||
const [isSignersModal, setIsSignersModal] = useState(false);
|
||||
|
||||
@@ -319,7 +320,7 @@ function WidgetComponent({
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{handleAddSigner && (
|
||||
{handleAddSigner ? (
|
||||
<div
|
||||
data-tut="reactourAddbtn"
|
||||
style={{
|
||||
@@ -333,6 +334,22 @@ function WidgetComponent({
|
||||
<i className="fa-solid fa-plus"></i>
|
||||
<span style={{ marginLeft: 2 }}>Add role</span>
|
||||
</div>
|
||||
) : (
|
||||
setIsAddSigner && (
|
||||
<div
|
||||
data-tut="reactourAddbtn"
|
||||
style={{
|
||||
margin: "5px 0 5px 0",
|
||||
backgroundColor: themeColor,
|
||||
color: "white"
|
||||
}}
|
||||
className="p-[10px] my-[2px] flex flex-row items-center justify-center border-[1px] border-[#47a3ad] hover:bg-[#47a3ad] text-[#47a3ad] hover:text-white cursor-pointer"
|
||||
onClick={() => setIsAddSigner(true)}
|
||||
>
|
||||
<i className="fa-solid fa-plus"></i>
|
||||
<span style={{ marginLeft: 2 }}>Add recipients</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
<div
|
||||
@@ -391,7 +408,7 @@ function WidgetComponent({
|
||||
)}
|
||||
{isSignersModal && (
|
||||
<ModalUi
|
||||
title={"Roles"}
|
||||
title={title ? title : "Recipients"}
|
||||
isOpen={isSignersModal}
|
||||
handleClose={handleModal}
|
||||
>
|
||||
@@ -424,7 +441,7 @@ function WidgetComponent({
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
Please add a role
|
||||
Please add a {title ? title : "recipients"}
|
||||
</div>
|
||||
)}
|
||||
</ModalUi>
|
||||
|
||||
@@ -4,6 +4,9 @@ import "../../styles/AddUser.css";
|
||||
import RegexParser from "regex-parser";
|
||||
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({
|
||||
@@ -11,7 +14,7 @@ const WidgetNameModal = (props) => {
|
||||
defaultValue: "",
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: "text"
|
||||
textvalidate: ""
|
||||
});
|
||||
const [isValid, setIsValid] = useState(true);
|
||||
const statusArr = ["Required", "Optional"];
|
||||
@@ -46,12 +49,16 @@ const WidgetNameModal = (props) => {
|
||||
defaultValue: "",
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: "text"
|
||||
textvalidate: ""
|
||||
});
|
||||
}
|
||||
};
|
||||
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) => {
|
||||
@@ -80,7 +87,7 @@ const WidgetNameModal = (props) => {
|
||||
|
||||
function handleBlurRegex() {
|
||||
if (!formdata.textvalidate) {
|
||||
setFormdata({ ...formdata, textvalidate: "text" });
|
||||
setFormdata({ ...formdata, textvalidate: "" });
|
||||
} else {
|
||||
if (formdata.defaultValue) {
|
||||
const regexObject = RegexParser(
|
||||
@@ -100,13 +107,14 @@ const WidgetNameModal = (props) => {
|
||||
title={"Widget info"}
|
||||
>
|
||||
{(props.defaultdata?.type === textInputWidget ||
|
||||
props.widgetName === textInputWidget) && (
|
||||
<PremiumAlertHeader
|
||||
message={
|
||||
"Field validations are free in beta, this feature will incur a fee later."
|
||||
}
|
||||
/>
|
||||
)}
|
||||
props.widgetName === textInputWidget) &&
|
||||
!isEnableSubscription && (
|
||||
<PremiumAlertHeader
|
||||
message={
|
||||
"Field validations are free in beta, this feature will incur a fee later."
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className={`${
|
||||
@@ -133,10 +141,25 @@ const WidgetNameModal = (props) => {
|
||||
props.widgetName === textInputWidget) && (
|
||||
<>
|
||||
<div className="form-section">
|
||||
<label htmlFor="textvalidate" style={{ fontSize: 13 }}>
|
||||
Validation
|
||||
</label>
|
||||
<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"
|
||||
}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
@@ -148,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()}
|
||||
@@ -160,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) => {
|
||||
@@ -178,6 +204,7 @@ const WidgetNameModal = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-section">
|
||||
<label htmlFor="name" style={{ fontSize: 13 }}>
|
||||
Default value
|
||||
@@ -196,7 +223,10 @@ const WidgetNameModal = (props) => {
|
||||
}}
|
||||
/>
|
||||
{isValid === false && (
|
||||
<div className="warning defaultvalueWarning" style={{ fontSize: 12 }}>
|
||||
<div
|
||||
className="warning defaultvalueWarning"
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
<i
|
||||
className="fas fa-exclamation-circle"
|
||||
style={{ color: "#fab005", fontSize: 15 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -53,9 +53,22 @@ const SelectSigners = (props) => {
|
||||
const contactRes = await contactbook.find();
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
// console.log("userList ", res);
|
||||
setUserList(res);
|
||||
return await res.map((item) => ({
|
||||
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
|
||||
//and filter signers from total signer's list which already present in document's signers list
|
||||
const compareArrays = (res, signerObj) => {
|
||||
return res.filter(
|
||||
(item1) =>
|
||||
!signerObj.find((item2) => item2.objectId === item1.objectId)
|
||||
);
|
||||
};
|
||||
|
||||
//get update signer's List if signersdata is present
|
||||
const updateSignersList =
|
||||
props?.signersData && compareArrays(res, props?.signersData);
|
||||
|
||||
const result = updateSignersList ? updateSignersList : res;
|
||||
setUserList(result);
|
||||
return await result.map((item) => ({
|
||||
label: item.Email,
|
||||
value: item.objectId
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { findContact } from "../../../constant/Utils";
|
||||
const SuggestionInput = (props) => {
|
||||
const [inputValue, setInputValue] = useState(props?.value || "");
|
||||
const [suggestions, setSuggestions] = useState([]);
|
||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("mousedown", Clickout);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", Clickout);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const Clickout = (event) => {
|
||||
if (ref.current && !ref.current.contains(event.target)) {
|
||||
setShowSuggestions(false);
|
||||
}
|
||||
};
|
||||
// create debounce to avoid unnecessay api calls
|
||||
useEffect(() => {
|
||||
let timer;
|
||||
if (inputValue) {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
(async () => {
|
||||
const res = await findContact(inputValue);
|
||||
if (res?.length > 0) {
|
||||
setSuggestions(res);
|
||||
setShowSuggestions(true);
|
||||
} else {
|
||||
setSuggestions(res);
|
||||
setShowSuggestions(false);
|
||||
}
|
||||
})();
|
||||
}, 1000);
|
||||
}
|
||||
return () => clearTimeout(timer);
|
||||
}, [inputValue]);
|
||||
|
||||
const handleInputChange = async (e) => {
|
||||
const value = e.target.value;
|
||||
setInputValue(value?.toLowerCase());
|
||||
if (props.onChange) {
|
||||
props.onChange(value);
|
||||
}
|
||||
|
||||
if (value.trim() === "") {
|
||||
setSuggestions([]);
|
||||
setShowSuggestions(false);
|
||||
return;
|
||||
}
|
||||
};
|
||||
const handleSuggestionClick = (suggestion) => {
|
||||
setInputValue(suggestion.Email);
|
||||
setSuggestions([]);
|
||||
setShowSuggestions(false);
|
||||
if (props.onChange) {
|
||||
props.onChange(suggestion);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex flex-col items-center relative">
|
||||
<input
|
||||
type={props?.type || "text"}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Email..."
|
||||
className="w-full border-[1px] border-gray-400 p-2 text-black rounded lowercase"
|
||||
required={props.required}
|
||||
/>
|
||||
{showSuggestions && (
|
||||
<ul
|
||||
ref={ref}
|
||||
className="absolute z-50 left-0 top-[2.55rem] w-full max-h-[100px] overflow-auto bg-white border border-gray-300 rounded shadow-md"
|
||||
>
|
||||
{suggestions.map((suggestion, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="py-2 px-2 w-full text-sm cursor-pointer hover:bg-gray-100"
|
||||
onClick={() => handleSuggestionClick(suggestion)}
|
||||
>
|
||||
{suggestion.Email}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default SuggestionInput;
|
||||
@@ -10,7 +10,11 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
|
||||
? `/${item.pageType}/${item.objectId}`
|
||||
: `/${item.objectId}`
|
||||
}
|
||||
className="mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4 cursor-pointer focus:text-[#0056b3] focus:bg-[#eef1f5]"
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? "text-[#0056b3] bg-[#eef1f5]" : ""
|
||||
} mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4 cursor-pointer`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
tabIndex={isOpen ? 0 : -1}
|
||||
role="menuitem"
|
||||
|
||||
@@ -41,7 +41,7 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
|
||||
<div className="flex px-2 py-3 gap-2 items-center shadow-md">
|
||||
<div className="w-[75px] h-[75px] rounded-full ring-[2px] ring-offset-2 ring-gray-400 overflow-hidden">
|
||||
<img
|
||||
className="w-full h-full object-cover"
|
||||
className="w-full h-full object-contain"
|
||||
src={image}
|
||||
alt="Profile"
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,86 @@
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import { themeColor } from "./const";
|
||||
import { isEnableSubscription, themeColor } from "./const";
|
||||
import React from "react";
|
||||
import { rgb } from "pdf-lib";
|
||||
import Parse from "parse";
|
||||
import { appInfo } from "./appinfo";
|
||||
import { saveAs } from "file-saver";
|
||||
import printModule from "print-js";
|
||||
|
||||
export const isMobile = window.innerWidth < 767;
|
||||
export const textInputWidget = "text input";
|
||||
export const textWidget = "text";
|
||||
export const radioButtonWidget = "radio button";
|
||||
export const openInNewTab = (url) => {
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
export const openInNewTab = (url, target) => {
|
||||
if (target) {
|
||||
window.open(url, target, "noopener,noreferrer");
|
||||
} else {
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
};
|
||||
|
||||
export async function fetchSubscription(
|
||||
extUserId,
|
||||
contactObjId,
|
||||
isGuestSign = false
|
||||
) {
|
||||
try {
|
||||
const Extand_Class = localStorage.getItem("Extand_Class");
|
||||
const extClass = Extand_Class && JSON.parse(Extand_Class);
|
||||
// console.log("extClass ", extClass);
|
||||
let extUser;
|
||||
if (extClass && extClass.length > 0) {
|
||||
extUser = extClass[0].objectId;
|
||||
} else {
|
||||
extUser = extUserId;
|
||||
}
|
||||
const baseURL = localStorage.getItem("baseUrl");
|
||||
const url = `${baseURL}functions/getsubscriptions`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const params = isGuestSign
|
||||
? { contactId: contactObjId }
|
||||
: { extUserId: extUser };
|
||||
const tenatRes = await axios.post(url, params, { headers: headers });
|
||||
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: "" };
|
||||
}
|
||||
}
|
||||
//function to get subcripition details from Extand user class
|
||||
export async function checkIsSubscribed() {
|
||||
try {
|
||||
const res = await fetchSubscription();
|
||||
if (res.plan === "freeplan") {
|
||||
return false;
|
||||
} else if (res.billingDate) {
|
||||
if (new Date(res.billingDate) > new Date()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in fetch subscription", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const color = [
|
||||
"#93a3db",
|
||||
"#e6c3db",
|
||||
@@ -110,11 +179,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);
|
||||
@@ -274,15 +342,15 @@ export const addWidgetOptions = (type) => {
|
||||
options: { isReadOnly: false, isHideLabel: false }
|
||||
};
|
||||
case textInputWidget:
|
||||
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
|
||||
return { ...defaultOpt };
|
||||
case "initials":
|
||||
return defaultOpt;
|
||||
case "name":
|
||||
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
|
||||
return { ...defaultOpt };
|
||||
case "company":
|
||||
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
|
||||
return { ...defaultOpt };
|
||||
case "job title":
|
||||
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
|
||||
return { ...defaultOpt };
|
||||
case "date":
|
||||
return {
|
||||
...defaultOpt,
|
||||
@@ -477,38 +545,96 @@ export const signPdfFun = async (
|
||||
base64Url,
|
||||
documentId,
|
||||
signerObjectId,
|
||||
setIsAlert
|
||||
setIsAlert,
|
||||
objectId,
|
||||
isSubscribed,
|
||||
activeMailAdapter,
|
||||
xyPosition
|
||||
) => {
|
||||
let singleSign;
|
||||
let singleSign,
|
||||
isCustomCompletionMail = false;
|
||||
|
||||
singleSign = {
|
||||
pdfFile: base64Url,
|
||||
docId: documentId,
|
||||
userId: signerObjectId
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
@@ -543,6 +669,7 @@ export const createDocument = async (template, placeholders, signerData) => {
|
||||
Name: Doc.Name,
|
||||
URL: Doc.URL,
|
||||
SignedUrl: Doc.SignedUrl,
|
||||
SentToOthers: Doc.SentToOthers,
|
||||
Description: Doc.Description,
|
||||
Note: Doc.Note,
|
||||
Placeholders: placeholdersArr,
|
||||
@@ -557,7 +684,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 {
|
||||
@@ -1045,7 +1174,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();
|
||||
@@ -1073,6 +1201,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,
|
||||
@@ -1111,10 +1283,10 @@ export const multiSignEmbed = async (
|
||||
const newUrl = await convertPNGtoJPEG(signUrl);
|
||||
signUrl = newUrl;
|
||||
}
|
||||
const checkUrl = urlValidator(signUrl);
|
||||
if (checkUrl) {
|
||||
signUrl = signUrl + "?get";
|
||||
}
|
||||
// const checkUrl = urlValidator(signUrl);
|
||||
// if (checkUrl) {
|
||||
// signUrl = signUrl + "?get";
|
||||
// }
|
||||
const res = await fetch(signUrl);
|
||||
return res.arrayBuffer();
|
||||
}
|
||||
@@ -1249,6 +1421,7 @@ export const multiSignEmbed = async (
|
||||
}
|
||||
};
|
||||
const widgetTypeExist = [
|
||||
textWidget,
|
||||
textInputWidget,
|
||||
"name",
|
||||
"company",
|
||||
@@ -1304,7 +1477,7 @@ export const multiSignEmbed = async (
|
||||
checkbox.enableReadOnly();
|
||||
});
|
||||
}
|
||||
} else if (position.type === textWidget) {
|
||||
} else if (widgetTypeExist) {
|
||||
const font = await pdfDoc.embedFont("Helvetica");
|
||||
const fontSize = 12;
|
||||
let textContent;
|
||||
@@ -1380,24 +1553,8 @@ export const multiSignEmbed = async (
|
||||
color: rgb(0, 0, 0),
|
||||
size: fontSize
|
||||
});
|
||||
y -= 15; // Adjust the line height as needed
|
||||
y -= 18; // Adjust the line height as needed
|
||||
}
|
||||
} else if (widgetTypeExist) {
|
||||
const font = await pdfDoc.embedFont("Helvetica");
|
||||
const fontSize = 12;
|
||||
let textContent;
|
||||
if (position?.options?.response) {
|
||||
textContent = position.options?.response;
|
||||
} else if (position?.options?.defaultValue) {
|
||||
textContent = position?.options?.defaultValue;
|
||||
}
|
||||
page.drawText(textContent, {
|
||||
x: xPos(position),
|
||||
y: yPos(position) + 10,
|
||||
size: fontSize,
|
||||
font,
|
||||
color: rgb(0, 0, 0)
|
||||
});
|
||||
} else if (position.type === "dropdown") {
|
||||
const dropdownRandomId = "dropdown" + randomId();
|
||||
const dropdown = form.createDropdown(dropdownRandomId);
|
||||
@@ -1768,3 +1925,263 @@ export const handleCopyNextToWidget = (
|
||||
setXyPostion(updatePlaceholder);
|
||||
}
|
||||
};
|
||||
|
||||
export const getFileName = (fileUrl) => {
|
||||
if (fileUrl) {
|
||||
const url = new URL(fileUrl);
|
||||
const filename = url.pathname.substring(url.pathname.indexOf("_") + 1);
|
||||
return filename || "";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
//fetch tenant app logo from `partners_Tenant` class by domain name
|
||||
export const getAppLogo = async () => {
|
||||
const domainName = window.location.host;
|
||||
try {
|
||||
const tenantCreditsQuery = new Parse.Query("partners_Tenant");
|
||||
tenantCreditsQuery.equalTo("Domain", domainName);
|
||||
const res = await tenantCreditsQuery.first();
|
||||
|
||||
if (res) {
|
||||
const updateRes = JSON.parse(JSON.stringify(res));
|
||||
return updateRes?.Logo;
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getTenantDetails = async (objectId) => {
|
||||
try {
|
||||
const tenantCreditsQuery = new Parse.Query("partners_Tenant");
|
||||
tenantCreditsQuery.equalTo("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: objectId
|
||||
});
|
||||
const res = await tenantCreditsQuery.first();
|
||||
if (res) {
|
||||
const updateRes = JSON.parse(JSON.stringify(res));
|
||||
return updateRes;
|
||||
}
|
||||
} catch (e) {
|
||||
return "user does not exist!";
|
||||
}
|
||||
};
|
||||
|
||||
//function to convert variable string name to variable value of email body and subject
|
||||
export 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;
|
||||
}
|
||||
|
||||
export const copytoData = (text) => {
|
||||
// navigator.clipboard.writeText(text);
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
// Fallback for browsers that don't support navigator.clipboard
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
};
|
||||
|
||||
export const convertPdfArrayBuffer = async (url) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
// Check if the response was successful (status 200)
|
||||
if (!response.ok) {
|
||||
return "Error";
|
||||
}
|
||||
// Convert the response to ArrayBuffer
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
return arrayBuffer;
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
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);
|
||||
}
|
||||
};
|
||||
//handle download signed pdf
|
||||
export const handleDownloadPdf = async (
|
||||
pdfDetails,
|
||||
pdfUrl,
|
||||
setIsDownloading
|
||||
) => {
|
||||
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
|
||||
setIsDownloading("pdf");
|
||||
try {
|
||||
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
|
||||
const axiosRes = await axios.post(
|
||||
`${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`);
|
||||
setIsDownloading("");
|
||||
} catch (err) {
|
||||
console.log("err in getsignedurl", err);
|
||||
setIsDownloading("");
|
||||
alert("something went wrong, please try again later.");
|
||||
}
|
||||
};
|
||||
|
||||
export const sanitizeFileName = (pdfName) => {
|
||||
// Replace spaces with underscore
|
||||
return pdfName.replace(/ /g, "_");
|
||||
};
|
||||
//function for print digital sign pdf
|
||||
export const handleToPrint = async (event, pdfUrl, setIsDownloading) => {
|
||||
event.preventDefault();
|
||||
setIsDownloading("pdf");
|
||||
try {
|
||||
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
|
||||
const axiosRes = await axios.post(
|
||||
`${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;
|
||||
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");
|
||||
setIsDownloading("");
|
||||
} else {
|
||||
printModule({ printable: pdf, type: "pdf", base64: true });
|
||||
setIsDownloading("");
|
||||
}
|
||||
} catch (err) {
|
||||
setIsDownloading("");
|
||||
console.log("err in getsignedurl", err);
|
||||
alert("something went wrong, please try again later.");
|
||||
}
|
||||
};
|
||||
|
||||
//handle download signed pdf
|
||||
export const handleDownloadCertificate = async (
|
||||
pdfDetails,
|
||||
setIsDownloading
|
||||
) => {
|
||||
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
|
||||
try {
|
||||
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
|
||||
const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl;
|
||||
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
|
||||
} catch (err) {
|
||||
console.log("err in download in certificate", err);
|
||||
}
|
||||
} else {
|
||||
setIsDownloading("certificate");
|
||||
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`);
|
||||
setIsDownloading("");
|
||||
} else {
|
||||
setIsDownloading("certificate");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in download in certificate", err);
|
||||
alert("something went wrong, please try again later.");
|
||||
}
|
||||
}
|
||||
};
|
||||
export async function findContact(value) {
|
||||
try {
|
||||
const currentUser = Parse.User.current();
|
||||
const contactbook = new Parse.Query("contracts_Contactbook");
|
||||
contactbook.equalTo(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
contactbook.notEqualTo("IsDeleted", true);
|
||||
contactbook.matches("Email", new RegExp(value, "i"));
|
||||
|
||||
const contactRes = await contactbook.find();
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
return res;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching suggestions:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,3 +9,4 @@ export const modalSubmitBtnColor = "#17a2b8";
|
||||
export const modalCancelBtnColor = "white";
|
||||
export const themeColor = "#47a3ad";
|
||||
export const iconColor = "#686968";
|
||||
export const isEnableSubscription = process.env.REACT_APP_ENABLE_SUBSCRIPTION;
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
import axios from "axios";
|
||||
const parseAppId = localStorage.getItem("parseAppId");
|
||||
const serverUrl = localStorage.getItem("baseUrl");
|
||||
const parseAppId = process.env.REACT_APP_APPID
|
||||
? process.env.REACT_APP_APPID
|
||||
: "opensign";
|
||||
const serverUrl = process.env.REACT_APP_SERVERURL
|
||||
? process.env.REACT_APP_SERVERURL
|
||||
: window.location.origin + "/api/app";
|
||||
|
||||
export const SaveFileSize = async (size, imageUrl) => {
|
||||
export const SaveFileSize = async (size, imageUrl, tenantId) => {
|
||||
//checking server url and save file's size
|
||||
|
||||
const tenantPtr = {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: tenantId
|
||||
};
|
||||
const _tenantPtr = JSON.stringify(tenantPtr);
|
||||
try {
|
||||
const response = await axios
|
||||
.get(`${serverUrl}classes/partners_TenantCredits`, {
|
||||
const res = await axios.get(
|
||||
`${serverUrl}/classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
})
|
||||
.then((result) => {
|
||||
const res = result.data;
|
||||
// console.log("res", res);
|
||||
|
||||
return res.results;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("axois err ", err);
|
||||
});
|
||||
}
|
||||
);
|
||||
const response = res.data.results;
|
||||
let data;
|
||||
// console.log("response", response);
|
||||
if (response && response.length > 0) {
|
||||
@@ -30,69 +33,48 @@ export const SaveFileSize = async (size, imageUrl) => {
|
||||
? response[0].usedStorage + size
|
||||
: size
|
||||
};
|
||||
await axios
|
||||
.put(
|
||||
`${serverUrl}classes/partners_TenantCredits/${response[0].objectId}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
// const res = result.data;
|
||||
// console.log("save res", res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("axois err ", err);
|
||||
});
|
||||
} else {
|
||||
data = {
|
||||
usedStorage: size
|
||||
};
|
||||
await axios
|
||||
.post(`${serverUrl}classes/partners_TenantCredits`, data, {
|
||||
await axios.put(
|
||||
`${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// const res = result.data;
|
||||
// console.log("res", res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("axois err ", err);
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
data = { usedStorage: size, PartnersTenant: tenantPtr };
|
||||
await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("org app error", e);
|
||||
} catch (err) {
|
||||
console.log("err in save usage", err);
|
||||
}
|
||||
saveDataFile(size, imageUrl);
|
||||
saveDataFile(size, imageUrl, tenantPtr);
|
||||
};
|
||||
|
||||
//function for save fileUrl and file size in particular client db class partners_DataFiles
|
||||
const saveDataFile = async (size, imageUrl) => {
|
||||
const saveDataFile = async (size, imageUrl, tenantPtr) => {
|
||||
const data = {
|
||||
FileUrl: imageUrl,
|
||||
FileSize: size
|
||||
FileSize: size,
|
||||
TenantPtr: tenantPtr
|
||||
};
|
||||
|
||||
// console.log("data save",file, data)
|
||||
await axios
|
||||
.post(`${serverUrl}classes/partners_DataFiles`, data, {
|
||||
try {
|
||||
await axios.post(`${serverUrl}/classes/partners_DataFiles`, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// const res = result.data;
|
||||
// console.log("res", res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("axois err ", err);
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("err in save usage ", err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import "./index.css";
|
||||
import App from "./App";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./redux/store";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { TouchBackend } from "react-dnd-touch-backend";
|
||||
import {
|
||||
@@ -62,10 +63,12 @@ if (process.env.REACT_APP_GTM) {
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<Provider store={store}>
|
||||
<DndProvider options={HTML5toTouch}>
|
||||
<Preview>{generatePreview}</Preview>
|
||||
<App />
|
||||
</DndProvider>
|
||||
</Provider>
|
||||
<CookiesProvider defaultSetOptions={{ path: "/" }}>
|
||||
<Provider store={store}>
|
||||
<DndProvider options={HTML5toTouch}>
|
||||
<Preview>{generatePreview}</Preview>
|
||||
<App />
|
||||
</DndProvider>
|
||||
</Provider>
|
||||
</CookiesProvider>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ export default function reportJson(id) {
|
||||
const head = ["Sr.No", "Title", "Note", "Folder", "File", "Owner", "Signers"];
|
||||
const contactbook = ["Sr.No", "Title", "Email", "Phone"];
|
||||
const dashboardReportHead = ["Title", "File", "Owner", "Signers"];
|
||||
|
||||
const templateReport = ["Sr.No", "Title", "File", "Owner", "Signers"];
|
||||
switch (id) {
|
||||
// draft documents report
|
||||
case "ByHuevtCFY":
|
||||
@@ -12,14 +12,26 @@ export default function reportJson(id) {
|
||||
heading: head,
|
||||
actions: [
|
||||
{
|
||||
btnLabel: "sign",
|
||||
btnId: "1231",
|
||||
hoverLabel: "Edit",
|
||||
btnColor: "#4bd396",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-plus",
|
||||
redirectUrl: "draftDocument"
|
||||
btnIcon: "fa-solid fa-pen",
|
||||
redirectUrl: "draftDocument",
|
||||
action: "redirect"
|
||||
},
|
||||
{
|
||||
btnId: "2142",
|
||||
hoverLabel: "Delete",
|
||||
btnColor: "#ff4848",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
}
|
||||
],
|
||||
helpMsg: "This are documents you have started but have not finalized for sending."
|
||||
helpMsg:
|
||||
"These are documents you have started but have not finalized for sending."
|
||||
};
|
||||
// Need your sign report
|
||||
case "4Hhwbp482K":
|
||||
@@ -28,11 +40,14 @@ export default function reportJson(id) {
|
||||
heading: head,
|
||||
actions: [
|
||||
{
|
||||
btnId: "4536",
|
||||
btnLabel: "sign",
|
||||
hoverLabel: "Sign",
|
||||
btnColor: "#3ac9d6",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-eye",
|
||||
redirectUrl: "pdfRequestFiles"
|
||||
btnIcon: "fa-solid fa-signature",
|
||||
redirectUrl: "pdfRequestFiles",
|
||||
action: "redirect"
|
||||
}
|
||||
],
|
||||
helpMsg:
|
||||
@@ -45,11 +60,56 @@ export default function reportJson(id) {
|
||||
heading: head,
|
||||
actions: [
|
||||
{
|
||||
btnLabel: "View",
|
||||
btnId: "8901",
|
||||
hoverLabel: "Share",
|
||||
btnColor: "#4bd396",
|
||||
textColor: "white",
|
||||
btnIcon: "fa-solid fa-share",
|
||||
redirectUrl: "",
|
||||
action: "share"
|
||||
},
|
||||
{
|
||||
btnId: "1588",
|
||||
hoverLabel: "View",
|
||||
btnColor: "#3ac9d6",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-eye",
|
||||
redirectUrl: "pdfRequestFiles"
|
||||
redirectUrl: "pdfRequestFiles",
|
||||
action: "redirect"
|
||||
},
|
||||
{
|
||||
btnId: "2234",
|
||||
hoverLabel: "option",
|
||||
btnColor: "transparent",
|
||||
textColor: "black",
|
||||
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",
|
||||
hoverLabel: "Revoke",
|
||||
btnIcon: "fa-solid fa-file-circle-xmark",
|
||||
redirectUrl: "",
|
||||
action: "revoke"
|
||||
},
|
||||
{
|
||||
btnId: "1488",
|
||||
btnLabel: "Delete",
|
||||
hoverLabel: "Delete",
|
||||
btnIcon: "fa fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
helpMsg:
|
||||
@@ -62,14 +122,26 @@ export default function reportJson(id) {
|
||||
heading: head,
|
||||
actions: [
|
||||
{
|
||||
btnLabel: "View",
|
||||
btnId: "1378",
|
||||
hoverLabel: "Edit",
|
||||
btnColor: "#4bd396",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-eye",
|
||||
redirectUrl: "draftDocument"
|
||||
redirectUrl: "draftDocument",
|
||||
action: "redirect"
|
||||
},
|
||||
{
|
||||
btnId: "1278",
|
||||
hoverLabel: "Delete",
|
||||
btnColor: "#ff4848",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
}
|
||||
],
|
||||
helpMsg: "This is a list of documents that have been signed by all parties."
|
||||
helpMsg:
|
||||
"This is a list of documents that have been signed by all parties."
|
||||
};
|
||||
// declined documents report
|
||||
case "UPr2Fm5WY3":
|
||||
@@ -78,14 +150,26 @@ export default function reportJson(id) {
|
||||
heading: head,
|
||||
actions: [
|
||||
{
|
||||
btnLabel: "View",
|
||||
btnId: "1458",
|
||||
hoverLabel: "View",
|
||||
btnColor: "#4bd396",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-eye",
|
||||
redirectUrl: "draftDocument"
|
||||
redirectUrl: "draftDocument",
|
||||
action: "redirect"
|
||||
},
|
||||
{
|
||||
btnId: "1358",
|
||||
hoverLabel: "Delete",
|
||||
btnColor: "#ff4848",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
}
|
||||
],
|
||||
helpMsg: "This is a list of documents that have been declined by one of the signers."
|
||||
helpMsg:
|
||||
"This is a list of documents that have been declined by one of the signers."
|
||||
};
|
||||
// Expired Documents report
|
||||
case "zNqBHXHsYH":
|
||||
@@ -94,14 +178,26 @@ export default function reportJson(id) {
|
||||
heading: head,
|
||||
actions: [
|
||||
{
|
||||
btnLabel: "View",
|
||||
btnId: "1898",
|
||||
hoverLabel: "View",
|
||||
btnColor: "#4bd396",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-eye",
|
||||
redirectUrl: "draftDocument"
|
||||
redirectUrl: "draftDocument",
|
||||
action: "redirect"
|
||||
},
|
||||
{
|
||||
btnId: "1998",
|
||||
hoverLabel: "Delete",
|
||||
btnColor: "#ff4848",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
}
|
||||
],
|
||||
helpMsg: "This is a list of documents that have reached their expiration date."
|
||||
helpMsg:
|
||||
"This is a list of documents that have reached their expiration date."
|
||||
};
|
||||
// Recently sent for signatures report show on dashboard
|
||||
case "d9k3UfYHBc":
|
||||
@@ -110,11 +206,56 @@ export default function reportJson(id) {
|
||||
heading: dashboardReportHead,
|
||||
actions: [
|
||||
{
|
||||
btnLabel: "View",
|
||||
btnId: "1999",
|
||||
hoverLabel: "Share",
|
||||
btnColor: "#4bd396",
|
||||
textColor: "white",
|
||||
btnIcon: "fa-solid fa-share",
|
||||
redirectUrl: "",
|
||||
action: "share"
|
||||
},
|
||||
{
|
||||
btnId: "1999",
|
||||
hoverLabel: "View",
|
||||
btnColor: "#3ac9d6",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-eye",
|
||||
redirectUrl: "pdfRequestFiles"
|
||||
redirectUrl: "pdfRequestFiles",
|
||||
action: "redirect"
|
||||
},
|
||||
{
|
||||
btnId: "2234",
|
||||
hoverLabel: "option",
|
||||
btnColor: "transparent",
|
||||
textColor: "black",
|
||||
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",
|
||||
hoverLabel: "Revoke",
|
||||
btnIcon: "fa-solid fa-file-circle-xmark",
|
||||
redirectUrl: "",
|
||||
action: "revoke"
|
||||
},
|
||||
{
|
||||
btnId: "2000",
|
||||
btnLabel: "Delete",
|
||||
hoverLabel: "Delete",
|
||||
btnIcon: "fa fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -125,11 +266,14 @@ export default function reportJson(id) {
|
||||
heading: dashboardReportHead,
|
||||
actions: [
|
||||
{
|
||||
btnLabel: "Sign",
|
||||
btnColor: "#4bd396",
|
||||
btnId: "2001",
|
||||
btnLabel: "sign",
|
||||
hoverLabel: "Sign",
|
||||
btnColor: "#3ac9d6",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-eye",
|
||||
redirectUrl: "pdfRequestFiles"
|
||||
btnIcon: "fa-solid fa-signature",
|
||||
redirectUrl: "pdfRequestFiles",
|
||||
action: "redirect"
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -140,11 +284,22 @@ export default function reportJson(id) {
|
||||
heading: ["Title", "Note", "Folder", "File", "Owner", "Signers"],
|
||||
actions: [
|
||||
{
|
||||
btnLabel: "sign",
|
||||
btnId: "2003",
|
||||
hoverLabel: "Edit",
|
||||
btnColor: "#4bd396",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-plus",
|
||||
redirectUrl: "draftDocument"
|
||||
btnIcon: "fa-solid fa-pen",
|
||||
redirectUrl: "draftDocument",
|
||||
action: "redirect"
|
||||
},
|
||||
{
|
||||
btnId: "2004",
|
||||
hoverLabel: "Delete",
|
||||
btnColor: "#ff4848",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -155,34 +310,76 @@ export default function reportJson(id) {
|
||||
heading: contactbook,
|
||||
actions: [
|
||||
{
|
||||
btnLabel: "",
|
||||
btnId: "2204",
|
||||
hoverLabel: "Delete",
|
||||
btnColor: "#f55a42",
|
||||
textColor: "white",
|
||||
btnIcon: "fa-solid fa-trash"
|
||||
btnIcon: "fa-solid fa-trash",
|
||||
action: "delete"
|
||||
}
|
||||
],
|
||||
form: "ContactBook",
|
||||
helpMsg: "This is a list of contacts/signers added by you. These will appear as suggestions when you try to add signers to a new document."
|
||||
helpMsg:
|
||||
"This is a list of contacts/signers added by you. These will appear as suggestions when you try to add signers to a new document."
|
||||
};
|
||||
// template report
|
||||
case "6TeaPr321t":
|
||||
return {
|
||||
reportName: "Templates",
|
||||
heading: head,
|
||||
heading: templateReport,
|
||||
actions: [
|
||||
{
|
||||
btnId: "2234",
|
||||
btnLabel: "Use",
|
||||
hoverLabel: "Use",
|
||||
btnColor: "#4bd396",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-plus",
|
||||
redirectUrl: "placeHolderSign"
|
||||
redirectUrl: "placeHolderSign",
|
||||
action: "redirect",
|
||||
selector: "reactourSecond",
|
||||
message:
|
||||
"Click the 'Use' button to create a new document from an existing template. "
|
||||
},
|
||||
{
|
||||
btnLabel: "Edit",
|
||||
btnColor: "#00c9d5",
|
||||
textColor: "white",
|
||||
btnIcon: "fa fa-plus",
|
||||
redirectUrl: "template"
|
||||
btnId: "1631",
|
||||
btnLabel: "Quick send",
|
||||
hoverLabel: "Quick send",
|
||||
btnIcon: "fa-solid fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "bulksend",
|
||||
selector: "tourbulksend",
|
||||
message:
|
||||
"To quickly create and send multiple documents using an existing template, click the 'Quick Send' button."
|
||||
},
|
||||
{
|
||||
btnId: "2234",
|
||||
hoverLabel: "option",
|
||||
btnColor: "transparent",
|
||||
textColor: "black",
|
||||
btnIcon: "fa-solid fa-ellipsis-vertical fa-lg",
|
||||
action: "option",
|
||||
selector: "reactourThird",
|
||||
message:
|
||||
"This menu reveals more options such as Edit & Delete. Use the 'Edit' button to add signer roles, modify fields, and update your template. Changes will apply to all future documents created from this template but won’t affect existing documents.Use the Delete button you can delete template. ",
|
||||
subaction: [
|
||||
{
|
||||
btnId: "2434",
|
||||
btnLabel: "Edit",
|
||||
hoverLabel: "Edit",
|
||||
btnIcon: "fa-solid fa-pen",
|
||||
redirectUrl: "template",
|
||||
action: "redirect"
|
||||
},
|
||||
{
|
||||
btnId: "1834",
|
||||
btnLabel: "Delete",
|
||||
hoverLabel: "Delete",
|
||||
btnIcon: "fa fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
helpMsg:
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
const plans = [
|
||||
{
|
||||
planName: "OPENSIGN™ FREE",
|
||||
currency: "",
|
||||
monthlyPrice: "Free",
|
||||
yearlyPrice: "Free",
|
||||
subtitle: "Free Unlimited E-signatures, Forever.",
|
||||
btnText: "Subscribe",
|
||||
url: "",
|
||||
target: "_blank",
|
||||
benefits: [
|
||||
"Unlimited envelopes",
|
||||
"Sign documents yourself",
|
||||
"Request signatures from others",
|
||||
"14 field types",
|
||||
"Automatic e-signatures",
|
||||
"Completion certificates",
|
||||
"Send in order",
|
||||
"Document templates",
|
||||
"Import from Dropbox",
|
||||
"Contact book",
|
||||
"Document expiry support",
|
||||
"Decline document support",
|
||||
"Email notifications",
|
||||
"Recipient authentication using OTP",
|
||||
"And much more"
|
||||
]
|
||||
},
|
||||
{
|
||||
planName: "OPENSIGN™ PROFESSIONAL",
|
||||
currency: "$",
|
||||
monthlyPrice: "29.99",
|
||||
yearlyPrice: "329.99",
|
||||
subtitle: "Exclusive Access to advanced features.",
|
||||
btnText: "Subscribe",
|
||||
url:
|
||||
window.location.origin === "https://staging-app.opensignlabs.com"
|
||||
? "https://billing.zoho.in/subscribe/9627f62a09df1c8ce500f2f4dc8328dd84ecda20eeae13878ce581d60240f206/pro-weekly"
|
||||
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d1a09dfa8085585cdd4ec4d7f32137f3/professional-monthly",
|
||||
yearlyUrl:
|
||||
window.location.origin === "https://staging-app.opensignlabs.com"
|
||||
? "https://billing.zoho.in/subscribe/9627f62a09df1c8ce500f2f4dc8328ddc184411129224f1f29ed84f6cef3b862/pro-yearly"
|
||||
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d8ad1135190dff951330360e47585a71/professional-yearly",
|
||||
target: "_self",
|
||||
benefits: [
|
||||
"Everything in OpenSign™ free",
|
||||
"Field validations",
|
||||
"Regular expression validations",
|
||||
"Organize docs in OpenSign™ Drive",
|
||||
"Webhooks",
|
||||
"Zapier integration",
|
||||
"API Access",
|
||||
"100 API signatures included",
|
||||
"DocumentId removal from signed docs",
|
||||
"Custom email templates",
|
||||
"Auto reminders"
|
||||
]
|
||||
},
|
||||
{
|
||||
planName: "OPENSIGN™ ENTERPRISE",
|
||||
currency: "",
|
||||
monthlyPrice: "Request Price",
|
||||
yearlyPrice: "Request Price",
|
||||
subtitle: "Scalable Features with priority support.",
|
||||
btnText: "Contact us",
|
||||
url: "https://www.opensignlabs.com/contact-us",
|
||||
target: "_blank",
|
||||
benefits: [
|
||||
"All features",
|
||||
"Custom domain",
|
||||
"Custom branding",
|
||||
"Uptime SLA",
|
||||
"SSO support",
|
||||
"Priority support"
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export default plans;
|
||||
@@ -1,65 +0,0 @@
|
||||
[
|
||||
{
|
||||
"planName": "SELF HOSTED",
|
||||
"currency": "",
|
||||
"price": "Free",
|
||||
"subtitle": "Free for life One click install.",
|
||||
"btnText": "Visit github",
|
||||
"url": "https://github.com/opensignlabs/opensign",
|
||||
"target": "_blank",
|
||||
"benefits": [
|
||||
"Sign Unlimited Documents",
|
||||
"S3 Doc Storage",
|
||||
"Unlimited Guest Signers",
|
||||
"Unlimited completion certificates",
|
||||
"Unique Code(OTP) verification for guest signers",
|
||||
"Expiring docs & reject signature support",
|
||||
"PDF Template Creation(coming soon)",
|
||||
"Audit Trails",
|
||||
"API support",
|
||||
"Add-ons support(coming soon)",
|
||||
"Self hosted cloud infrastructure",
|
||||
"Free for lifetime",
|
||||
"Community support"
|
||||
]
|
||||
},
|
||||
{
|
||||
"planName": "THANKS PLAN",
|
||||
"currency": "$",
|
||||
"price": "29.99",
|
||||
"subtitle": "Use code <span style='background-color: yellow;'>'FREEBETA'</span> to get this for free(First 1000 users)",
|
||||
"btnText": "Subscribe",
|
||||
"url": "https://subscriptions.zoho.in/subscribe/ef798486e6a0a11ea65f2bae8f2af901dbadf3175d495584fd25b9af5d2f2b9e/thanks",
|
||||
"target": "_self",
|
||||
"benefits": [
|
||||
"Sign Unlimited Documents",
|
||||
"Unlimited Secure Doc Storage with OpenSign™ Drive",
|
||||
"Unlimited Guest Signers",
|
||||
"Unlimited completion certificates",
|
||||
"Unique Code(OTP) verification for guest signers",
|
||||
"Expiring docs & reject signature support",
|
||||
"PDF Template Creation(coming soon)",
|
||||
"Audit Trails",
|
||||
"API support",
|
||||
"Add-ons support(coming soon)",
|
||||
"Self hosted cloud infrastructure"
|
||||
]
|
||||
},
|
||||
{
|
||||
"planName": "ENTERPRISE",
|
||||
"currency": "",
|
||||
"price": "Request Price",
|
||||
"subtitle": "Customization available Priority support.",
|
||||
"btnText": "Contact us",
|
||||
"url": "https://www.opensignlabs.com/contact-us",
|
||||
"target": "_blank",
|
||||
"benefits": [
|
||||
"All features",
|
||||
"Custom domain",
|
||||
"Whitelabeling",
|
||||
"Uptime SLA",
|
||||
"SSO support",
|
||||
"24/7 support"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -9,6 +9,9 @@ import { useSelector } from "react-redux";
|
||||
import Parse from "parse";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { useNavigate, useLocation, Outlet } from "react-router-dom";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import { useCookies } from "react-cookie";
|
||||
import { fetchSubscription } from "../constant/Utils";
|
||||
|
||||
const HomeLayout = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -18,33 +21,80 @@ const HomeLayout = () => {
|
||||
const arr = useSelector((state) => state.TourSteps);
|
||||
const [isUserValid, setIsUserValid] = useState(true);
|
||||
const [isLoader, setIsLoader] = useState(true);
|
||||
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) {
|
||||
setIsUserValid(true);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
setIsUserValid(false);
|
||||
}
|
||||
} catch (error) {
|
||||
// Session token is invalid or there was an error
|
||||
setIsUserValid(false);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
// reactour state
|
||||
const [isCloseBtn, setIsCloseBtn] = useState(true);
|
||||
const [isTour, setIsTour] = useState(false);
|
||||
const [tourStatusArr, setTourStatusArr] = useState([]);
|
||||
const [tourConfigs, setTourConfigs] = useState([]);
|
||||
const [, setCookie] = useCookies(["accesstoken", "main_Domain"]);
|
||||
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
useEffect(() => {
|
||||
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);
|
||||
}
|
||||
})();
|
||||
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;
|
||||
const domainName = window.location.hostname; //app.opensignlabs.com
|
||||
// Find the index of the first dot in the string
|
||||
const indexOfFirstDot = domainName.indexOf(".");
|
||||
// Remove the first dot and get the substring starting from the next character
|
||||
const updateDomain = domainName.substring(indexOfFirstDot); //.opensignlabs.com
|
||||
setCookie("accesstoken", localStorage.getItem("accesstoken"), {
|
||||
secure: true,
|
||||
domain: updateDomain
|
||||
});
|
||||
setCookie("main_Domain", main_Domain, {
|
||||
secure: true,
|
||||
domain: updateDomain
|
||||
});
|
||||
};
|
||||
|
||||
async function checkIsSubscribed() {
|
||||
if (isEnableSubscription) {
|
||||
const res = await fetchSubscription();
|
||||
if (res.plan === "freeplan") {
|
||||
setIsUserValid(true);
|
||||
setIsLoader(false);
|
||||
} else if (res.billingDate) {
|
||||
if (new Date(res.billingDate) > new Date()) {
|
||||
setIsUserValid(true);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
}
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
}
|
||||
} else {
|
||||
setIsUserValid(true);
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
const showSidebar = () => {
|
||||
setIsOpen((value) => !value);
|
||||
};
|
||||
@@ -198,7 +248,7 @@ const HomeLayout = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="sticky top-0 z-50">
|
||||
<Header showSidebar={showSidebar} />
|
||||
{!isLoader && <Header showSidebar={showSidebar} />}
|
||||
</div>
|
||||
{isUserValid ? (
|
||||
<>
|
||||
@@ -223,7 +273,6 @@ const HomeLayout = () => {
|
||||
<>
|
||||
<div className="flex md:flex-row flex-col z-50">
|
||||
<Sidebar isOpen={isOpen} closeSidebar={closeSidebar} />
|
||||
|
||||
<div
|
||||
id="renderList"
|
||||
className="relative h-screen flex flex-col justify-between w-full overflow-y-auto"
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
import Title from "../components/Title";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { saveTourSteps } from "../redux/reducers/TourStepsReducer";
|
||||
|
||||
const Dashboard = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
@@ -23,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>
|
||||
|
||||
+682
-204
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,16 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Title from "../components/Title";
|
||||
import axios from "axios";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Alert from "../primitives/Alert";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { rejectBtn, submitBtn } from "../constant/const";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
|
||||
import { checkIsSubscribed, copytoData, openInNewTab } from "../constant/Utils";
|
||||
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
|
||||
import Tooltip from "../primitives/Tooltip";
|
||||
|
||||
function GenerateToken() {
|
||||
const navigation = useNavigate();
|
||||
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||
const [apiToken, SetApiToken] = useState("");
|
||||
@@ -17,6 +19,7 @@ function GenerateToken() {
|
||||
const [isGenerate, setIsGenerate] = useState(false);
|
||||
const [isErr, setIsErr] = useState(false);
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchToken();
|
||||
@@ -25,6 +28,11 @@ function GenerateToken() {
|
||||
|
||||
const fetchToken = async () => {
|
||||
try {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
}
|
||||
|
||||
const url = parseBaseUrl + "functions/getapitoken";
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
@@ -35,6 +43,7 @@ function GenerateToken() {
|
||||
if (res) {
|
||||
SetApiToken(res.data.result.result);
|
||||
}
|
||||
|
||||
setIsLoader(false);
|
||||
} catch (err) {
|
||||
SetApiToken();
|
||||
@@ -83,13 +92,14 @@ function GenerateToken() {
|
||||
};
|
||||
|
||||
const copytoclipboard = (text) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
copytoData(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 1500); // Reset copied state after 1.5 seconds
|
||||
};
|
||||
const handleModal = () => setIsModal(!isModal);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title title={"API Token"} />
|
||||
@@ -117,35 +127,50 @@ function GenerateToken() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white flex flex-col justify-center shadow rounded">
|
||||
<PremiumAlertHeader />
|
||||
<h1 className="ml-4 mt-3 mb-2 font-semibold">
|
||||
API Token{" "}
|
||||
{!isEnableSubscription && <PremiumAlertHeader />}
|
||||
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
|
||||
OpenSign™ API{" "}
|
||||
<Tooltip
|
||||
url={"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"}
|
||||
url={
|
||||
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
|
||||
}
|
||||
isSubscribe={true}
|
||||
/>
|
||||
</h1>
|
||||
<ul className="w-full flex flex-col p-2 text-sm">
|
||||
<li
|
||||
className={`flex justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
|
||||
>
|
||||
<span className="w-[40%]">Api Token:</span>{" "}
|
||||
<span
|
||||
id="token"
|
||||
className="w-[60%] md:text-end cursor-pointer"
|
||||
onClick={() => copytoclipboard(apiToken)}
|
||||
<ul
|
||||
className={
|
||||
isSubscribe || !isEnableSubscription
|
||||
? "w-full flex flex-col p-2 text-sm "
|
||||
: "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-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 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
|
||||
onClick={apiToken ? handleModal : handleSubmit}
|
||||
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 && apiToken}
|
||||
</span>
|
||||
{apiToken ? "Regenerate Token" : "Generate Token"}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="flex flex-col md:flex-row items-center justify-center gap-2 pb-4">
|
||||
<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"
|
||||
>
|
||||
{apiToken ? "Regenerate Token" : "Generate Token"}
|
||||
</button>
|
||||
<div className="flex items-center justify-center ">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
@@ -153,12 +178,41 @@ function GenerateToken() {
|
||||
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
|
||||
)
|
||||
}
|
||||
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
|
||||
className="rounded hover:bg-[#15b4e9] border-[1px] my-2 border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
|
||||
>
|
||||
View Docs
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!isSubscribe && isEnableSubscription && (
|
||||
<>
|
||||
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
|
||||
Upgrade to PRO Plan
|
||||
</h1>
|
||||
<ul className={"w-full flex flex-col p-2 text-sm "}>
|
||||
<li
|
||||
className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
|
||||
>
|
||||
<div className="w-[70%] flex-col md:flex-row flex items-center gap-3 ">
|
||||
<span className="">$29.99/month:</span>{" "}
|
||||
<span id="token" className=" md:text-end cursor-pointer">
|
||||
Sign 100 docs using API. Only 0.15/docs after that.
|
||||
</span>
|
||||
</div>
|
||||
{!isSubscribe && isEnableSubscription && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigation("/subscription")}
|
||||
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
|
||||
>
|
||||
Upgrade Now
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
|
||||
<ModalUi
|
||||
isOpen={isModal}
|
||||
title={"Regenerate Token"}
|
||||
|
||||
@@ -1,80 +1,107 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import "../styles/loginPage.css";
|
||||
import loader from "../assets/images/loader2.gif";
|
||||
import axios from "axios";
|
||||
import { themeColor } from "../constant/const";
|
||||
import { contractUsers } from "../constant/Utils";
|
||||
import {
|
||||
isEnableSubscription,
|
||||
modalSubmitBtnColor,
|
||||
themeColor
|
||||
} from "../constant/const";
|
||||
import { contractUsers, getAppLogo } from "../constant/Utils";
|
||||
import logo from "../assets/images/logo.png";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import Parse from "parse";
|
||||
|
||||
function GuestLogin() {
|
||||
const { id, userMail, contactBookId, serverUrl } = useParams();
|
||||
let navigate = useNavigate();
|
||||
const { id, userMail, contactBookId, base64url } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [email, setEmail] = useState(userMail);
|
||||
const [OTP, setOTP] = useState("");
|
||||
const [EnterOTP, setEnterOtp] = useState(false);
|
||||
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();
|
||||
const [contact, setContact] = useState({ name: "", phone: "", email: "" });
|
||||
useEffect(() => {
|
||||
handleServerUrl();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//function generate serverUrl and parseAppId from url and save it in local storage
|
||||
const handleServerUrl = () => {
|
||||
//split url in array from '&'
|
||||
localStorage.clear();
|
||||
const checkSplit = serverUrl.split("&");
|
||||
const server = checkSplit[0];
|
||||
const parseId = checkSplit[1];
|
||||
const appName = checkSplit[2];
|
||||
const handleServerUrl = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const applogo = await getAppLogo();
|
||||
if (applogo) {
|
||||
setAppLogo(applogo);
|
||||
} else {
|
||||
setAppLogo(logo);
|
||||
}
|
||||
} else {
|
||||
setAppLogo(logo);
|
||||
}
|
||||
|
||||
const newServer = server.replaceAll("%2F", "/");
|
||||
localStorage.clear();
|
||||
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]);
|
||||
setContact((prev) => ({ ...prev, email: checkSplit[1] }));
|
||||
setEmail(checkSplit[1]);
|
||||
const contactId = checkSplit?.[2];
|
||||
setSendmail(checkSplit[3]);
|
||||
if (!contactId) {
|
||||
const params = { email: checkSplit[1], docId: checkSplit[0] };
|
||||
try {
|
||||
const linkContactRes = await Parse.Cloud.run(
|
||||
"linkcontacttodoc",
|
||||
params
|
||||
);
|
||||
// console.log("linkContactRes ", linkContactRes);
|
||||
setContactId(linkContactRes?.contactId);
|
||||
} catch (err) {
|
||||
console.log("Err in link ext contact", err);
|
||||
}
|
||||
} else {
|
||||
setContactId(checkSplit[2]);
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
const { value } = event.target;
|
||||
setOTP(value);
|
||||
};
|
||||
|
||||
//send email OTP function
|
||||
const SendOtp = async (e) => {
|
||||
const serverUrl =
|
||||
localStorage.getItem("baseUrl") && localStorage.getItem("baseUrl");
|
||||
const parseId =
|
||||
localStorage.getItem("parseAppId") && localStorage.getItem("parseAppId");
|
||||
if (serverUrl && localStorage) {
|
||||
setLoading(true);
|
||||
e.preventDefault();
|
||||
setEmail(email);
|
||||
|
||||
try {
|
||||
let url = `${serverUrl}functions/SendOTPMailV1/`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseId
|
||||
};
|
||||
let body = {
|
||||
email: email.toString()
|
||||
};
|
||||
let Otp = await axios.post(url, body, { headers: headers });
|
||||
|
||||
if (Otp) {
|
||||
setLoading(false);
|
||||
setEnterOtp(true);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("something went wrong!");
|
||||
const SendOtp = async () => {
|
||||
setLoading(true);
|
||||
setEmail(email);
|
||||
try {
|
||||
const params = { email: email.toString(), docId: documentId };
|
||||
const Otp = await Parse.Cloud.run("SendOTPMailV1", params);
|
||||
if (Otp) {
|
||||
setLoading(false);
|
||||
setEnterOtp(true);
|
||||
}
|
||||
} else {
|
||||
} catch (error) {
|
||||
alert("something went wrong!");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendOTPBtn = async (e) => {
|
||||
e.preventDefault();
|
||||
await SendOtp();
|
||||
};
|
||||
|
||||
//verify OTP send on via email
|
||||
const VerifyOTP = async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -110,7 +137,8 @@ function GuestLogin() {
|
||||
`Parse/${parseId}/currentUser`,
|
||||
JSON.stringify(_user)
|
||||
);
|
||||
if (contractUserDetails) {
|
||||
// console.log("contractUserDetails ", contractUserDetails);
|
||||
if (contractUserDetails && contractUserDetails.length > 0) {
|
||||
localStorage.setItem(
|
||||
"Extand_Class",
|
||||
JSON.stringify(contractUserDetails)
|
||||
@@ -122,7 +150,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);
|
||||
@@ -131,154 +165,171 @@ function GuestLogin() {
|
||||
alert("Please Enter OTP!");
|
||||
}
|
||||
};
|
||||
|
||||
const handleUserData = async (e) => {
|
||||
e.preventDefault();
|
||||
const params = { ...contact, docId: documentId };
|
||||
try {
|
||||
setLoading(true);
|
||||
const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params);
|
||||
// console.log("linkContactRes ", linkContactRes);
|
||||
setContactId(linkContactRes.contactId);
|
||||
setEnterOtp(true);
|
||||
await SendOtp();
|
||||
} catch (err) {
|
||||
setLoading(false);
|
||||
alert("something went wron, please try agian later.");
|
||||
console.log("Err in link ext contact", err);
|
||||
}
|
||||
};
|
||||
const handleInputChange = (e) => {
|
||||
setContact((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
return (
|
||||
<div style={{ padding: "2rem", background: "white" }}>
|
||||
{isLoading ? (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100vh",
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="no img"
|
||||
src={loader}
|
||||
style={{ width: "80px", height: "80px" }}
|
||||
/>
|
||||
<div className="flex flex-col justify-center items-center h-[100vh]">
|
||||
<img className="w-[80px] h-[80px]" alt="loader" src={loader} />
|
||||
<span style={{ fontSize: "13px", color: "gray" }}>
|
||||
{isLoading.message}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
margin: "10px",
|
||||
border: "0.5px solid #c5c7c9",
|
||||
padding: "30px",
|
||||
boxShadow: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"
|
||||
}}
|
||||
>
|
||||
<div className="main_head">
|
||||
<div className="main-logo">
|
||||
<img
|
||||
alt="sign img"
|
||||
src="https://qikinnovation.ams3.digitaloceanspaces.com/logo.png"
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
<div className="m-1 md:m-2 border-[0.5px] border-[#c5c7c9] p-[30px] shadow-md">
|
||||
<div className="md:w-[250px] md:h-[66px] inline-block overflow-hidden mt-2 mb-11">
|
||||
{appLogo && (
|
||||
<img src={appLogo} className="object-contain h-full" alt="logo" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!EnterOTP ? (
|
||||
<div className="row">
|
||||
<div className="col-sm-6 KLO">
|
||||
<span className="welcomeText">Welcome Back !</span>
|
||||
<br />
|
||||
<span className="KNLO">
|
||||
Verification code is sent to your email
|
||||
</span>
|
||||
<div className="card card-box" style={{ borderRadius: "0px" }}>
|
||||
<div className="card-body">
|
||||
{contactId ? (
|
||||
<>
|
||||
{!EnterOTP ? (
|
||||
<div className="w-full md:w-[50%]">
|
||||
<h1 className="text-2xl md:text-[30px]">Welcome Back!</h1>
|
||||
<legend className="text-[12px] text-[#878787] mt-2">
|
||||
Verification code is sent to your email
|
||||
</legend>
|
||||
<div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
|
||||
<input
|
||||
type="email"
|
||||
name="mobile"
|
||||
value={email}
|
||||
className="outline-none px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs disabled:bg-[#e0e5f5]"
|
||||
disabled
|
||||
className="loginInput"
|
||||
/>
|
||||
<br />
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<button
|
||||
className="w-[100px] text-white text-sm font-medium py-1 rounded-sm shadow-md hover:shadow-lg focus:outline-none"
|
||||
style={{ background: themeColor }}
|
||||
onClick={(e) => handleSendOTPBtn(e)}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Loading..." : "Send OTP"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="btnContainer">
|
||||
{loading ? (
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
background: themeColor,
|
||||
color: "white"
|
||||
}}
|
||||
className="verifyBtn"
|
||||
disabled
|
||||
>
|
||||
<span
|
||||
className="spinner-border spinner-border-sm "
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
Loading...
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="verifyBtn"
|
||||
style={{
|
||||
background: themeColor,
|
||||
color: "white",
|
||||
marginLeft: "0px !important"
|
||||
}}
|
||||
onClick={(e) => SendOtp(e)}
|
||||
>
|
||||
Send OTP
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="row">
|
||||
<div className="col-sm-6 KLO">
|
||||
<span className="welcomeText">Welcome Back !</span>
|
||||
<br />
|
||||
<span className="KNLO">You will get a OTP via Email</span>
|
||||
<div className="card card-box">
|
||||
<div className="card-body">
|
||||
<label>Enter Verification Code</label>
|
||||
) : (
|
||||
<form className="w-full md:w-[50%]" onSubmit={VerifyOTP}>
|
||||
<h1 className="text-2xl md:text-[30px]">Welcome Back!</h1>
|
||||
<legend className="text-[12px] text-[#878787] mt-2">
|
||||
You will get a OTP via Email
|
||||
</legend>
|
||||
<div className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
|
||||
<p>Enter Verification Code</p>
|
||||
<input
|
||||
type="number"
|
||||
className="loginInput"
|
||||
className="mt-1 outline-none px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs bg-[#e0e5f5]"
|
||||
name="OTP"
|
||||
value={OTP}
|
||||
onChange={handleChange}
|
||||
onChange={(e) => setOTP(e.target.value)}
|
||||
/>
|
||||
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{loading ? (
|
||||
<div className="mt-4">
|
||||
<button
|
||||
style={{
|
||||
background: themeColor,
|
||||
color: "white"
|
||||
}}
|
||||
className="verifyBtn"
|
||||
type="button"
|
||||
disabled
|
||||
style={{ background: themeColor }}
|
||||
className="w-[100px] text-white text-sm font-medium py-1 rounded-sm shadow-md hover:shadow-lg focus:outline-none"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
<span
|
||||
className="spinner-border spinner-border-sm "
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
Loading...
|
||||
{loading ? "Loading..." : "Verify"}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => VerifyOTP(e)}
|
||||
style={{
|
||||
background: themeColor,
|
||||
color: "white"
|
||||
}}
|
||||
className="verifyBtn"
|
||||
>
|
||||
Verify
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full md:w-[50%]">
|
||||
<h1 className="text-2xl md:text-[30px]">Welcome</h1>
|
||||
<legend className="text-[12px] text-[#878787] mt-2">
|
||||
Provide your details
|
||||
</legend>
|
||||
<form
|
||||
className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md"
|
||||
onSubmit={handleUserData}
|
||||
>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Name
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={contact.name}
|
||||
onChange={handleInputChange}
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded disabled:bg-[#e0e5f5] focus:outline-none text-xs"
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Email
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={contact.email}
|
||||
onChange={handleInputChange}
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 disabled:bg-[#e0e5f5] rounded focus:outline-none text-xs"
|
||||
required
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Phone
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="phone"
|
||||
value={contact.phone}
|
||||
onChange={handleInputChange}
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-start">
|
||||
<button
|
||||
type="submit"
|
||||
style={{ backgroundColor: modalSubmitBtnColor }}
|
||||
className="mr-2 px-[20px] py-1.5 text-white rounded shadow-md text-center focus:outline-none "
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Loading..." : "Next"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+312
-193
@@ -10,11 +10,16 @@ import { NavLink, useNavigate, useLocation } from "react-router-dom";
|
||||
import login_img from "../assets/images/login_img.svg";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { modalCancelBtnColor, modalSubmitBtnColor } from "../constant/const";
|
||||
import {
|
||||
isEnableSubscription,
|
||||
modalCancelBtnColor,
|
||||
modalSubmitBtnColor
|
||||
} from "../constant/const";
|
||||
import Alert from "../primitives/Alert";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import { fetchAppInfo } from "../redux/reducers/infoReducer";
|
||||
import { showTenant } from "../redux/reducers/ShowTenant";
|
||||
import { fetchSubscription, getAppLogo, openInNewTab } from "../constant/Utils";
|
||||
function Login() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -39,7 +44,8 @@ function Login() {
|
||||
Destination: ""
|
||||
});
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const image = appInfo?.applogo || undefined;
|
||||
const [image, setImage] = useState();
|
||||
const [isLoginSSO, setIsLoginSSO] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem("accesstoken")) {
|
||||
@@ -47,8 +53,23 @@ function Login() {
|
||||
GetLoginData();
|
||||
}
|
||||
dispatch(fetchAppInfo());
|
||||
saveLogo();
|
||||
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const saveLogo = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const logo = await getAppLogo();
|
||||
if (logo) {
|
||||
setImage(logo);
|
||||
} else {
|
||||
setImage(appInfo?.applogo || undefined);
|
||||
}
|
||||
} else {
|
||||
setImage(appInfo?.applogo || undefined);
|
||||
}
|
||||
};
|
||||
const handleChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
setState({ ...state, [name]: value });
|
||||
@@ -149,7 +170,7 @@ function Login() {
|
||||
await Parse.Cloud.run("getUserDetails", {
|
||||
email: currentUser.get("email")
|
||||
}).then(
|
||||
(result) => {
|
||||
async (result) => {
|
||||
let tenentInfo = [];
|
||||
const results = [result];
|
||||
if (results) {
|
||||
@@ -209,7 +230,7 @@ function Login() {
|
||||
x.TenantId.TenantName || ""
|
||||
};
|
||||
localStorage.setItem(
|
||||
"TenetId",
|
||||
"TenantId",
|
||||
x.TenantId.objectId
|
||||
);
|
||||
tenentInfo.push(obj);
|
||||
@@ -239,25 +260,26 @@ function Login() {
|
||||
element.pageType
|
||||
);
|
||||
setState({ ...state, loading: false });
|
||||
if (
|
||||
process.env
|
||||
.REACT_APP_ENABLE_SUBSCRIPTION
|
||||
) {
|
||||
if (isEnableSubscription) {
|
||||
const LocalUserDetails = {
|
||||
name: results[0].get("Name"),
|
||||
email: results[0].get("Email"),
|
||||
phone: results[0].get("Phone"),
|
||||
phone: results[0]?.get("Phone") || "",
|
||||
company: results[0].get("Company")
|
||||
};
|
||||
localStorage.setItem(
|
||||
"userDetails",
|
||||
JSON.stringify(LocalUserDetails)
|
||||
);
|
||||
const billingDate =
|
||||
results[0].get("Next_billing_date") &&
|
||||
results[0].get("Next_billing_date");
|
||||
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"
|
||||
);
|
||||
@@ -292,13 +314,11 @@ function Login() {
|
||||
element.pageType
|
||||
);
|
||||
setState({ ...state, loading: false });
|
||||
if (
|
||||
process.env.REACT_APP_ENABLE_SUBSCRIPTION
|
||||
) {
|
||||
if (isEnableSubscription) {
|
||||
const LocalUserDetails = {
|
||||
name: _user.name,
|
||||
email: email,
|
||||
phone: _user.phone
|
||||
phone: _user?.phone || ""
|
||||
// company: results.get("Company"),
|
||||
};
|
||||
localStorage.setItem(
|
||||
@@ -391,7 +411,19 @@ function Login() {
|
||||
const setThirdpartyLoader = (value) => {
|
||||
setState({ ...state, thirdpartyLoader: value });
|
||||
};
|
||||
const thirdpartyLoginfn = async (sessionToken, billingDate) => {
|
||||
const handleFreePlan = async (id) => {
|
||||
try {
|
||||
const params = { userId: id };
|
||||
const res = await Parse.Cloud.run("freesubscription", params);
|
||||
if (res.status === "error") {
|
||||
alert(res.result);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in free subscribe", err.message);
|
||||
alert("Somenthing went wrong, please try again later!");
|
||||
}
|
||||
};
|
||||
const thirdpartyLoginfn = async (sessionToken) => {
|
||||
const baseUrl = localStorage.getItem("baseUrl");
|
||||
const parseAppId = localStorage.getItem("parseAppId");
|
||||
const res = await axios.get(baseUrl + "users/me", {
|
||||
@@ -400,6 +432,11 @@ function Login() {
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
});
|
||||
const param = new URLSearchParams(location.search);
|
||||
const isFreeplan = param?.get("subscription") === "freeplan";
|
||||
if (isFreeplan) {
|
||||
await handleFreePlan(res.data.objectId);
|
||||
}
|
||||
await Parse.User.become(sessionToken).then(() => {
|
||||
window.localStorage.setItem("accesstoken", sessionToken);
|
||||
});
|
||||
@@ -485,7 +522,7 @@ function Login() {
|
||||
await Parse.Cloud.run("getUserDetails", {
|
||||
email: currentUser.get("email")
|
||||
}).then(
|
||||
(result) => {
|
||||
async (result) => {
|
||||
let tenentInfo = [];
|
||||
const results = [result];
|
||||
if (results) {
|
||||
@@ -535,14 +572,14 @@ function Login() {
|
||||
setState({ ...state, loading: false });
|
||||
navigate("/");
|
||||
} else {
|
||||
extendedInfo.forEach((x) => {
|
||||
extendedInfo.forEach(async (x) => {
|
||||
if (x.TenantId) {
|
||||
let obj = {
|
||||
tenentId: x.TenantId.objectId,
|
||||
tenentName: x.TenantId.TenantName || ""
|
||||
};
|
||||
localStorage.setItem(
|
||||
"TenetId",
|
||||
"TenantId",
|
||||
x.TenantId.objectId
|
||||
);
|
||||
tenentInfo.push(obj);
|
||||
@@ -571,18 +608,33 @@ function Login() {
|
||||
);
|
||||
setThirdpartyLoader(false);
|
||||
setState({ ...state, loading: false });
|
||||
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
|
||||
if (billingDate) {
|
||||
if (billingDate > new Date()) {
|
||||
if (isEnableSubscription) {
|
||||
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 {
|
||||
if (isFreeplan) {
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
navigate(`/subscription`, {
|
||||
replace: true
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isFreeplan) {
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
navigate(`/subscription`, {
|
||||
replace: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
}
|
||||
} else {
|
||||
navigate(redirectUrl);
|
||||
@@ -597,17 +649,34 @@ function Login() {
|
||||
localStorage.setItem("pageType", element.pageType);
|
||||
setState({ ...state, loading: false });
|
||||
setThirdpartyLoader(false);
|
||||
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
|
||||
if (billingDate) {
|
||||
if (billingDate > new Date()) {
|
||||
if (isEnableSubscription) {
|
||||
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);
|
||||
} else {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
if (isFreeplan) {
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
navigate(`/subscription`, {
|
||||
replace: true
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
if (isFreeplan) {
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
navigate(`/subscription`, {
|
||||
replace: true
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
navigate(redirectUrl);
|
||||
@@ -745,10 +814,17 @@ function Login() {
|
||||
// Get TenentID from Extendend Class
|
||||
localStorage.setItem("extended_class", item.extended_class);
|
||||
const currentUser = Parse.User.current();
|
||||
const userSettings = appInfo.settings;
|
||||
const setting = userSettings.find(
|
||||
(x) => x.role === _currentRole
|
||||
);
|
||||
const redirectUrl =
|
||||
location?.state?.from ||
|
||||
`/${setting.pageType}/${setting.pageId}`;
|
||||
await Parse.Cloud.run("getUserDetails", {
|
||||
email: currentUser.get("email")
|
||||
}).then(
|
||||
(result) => {
|
||||
async (result) => {
|
||||
let tenentInfo = [];
|
||||
const results = [result];
|
||||
if (results) {
|
||||
@@ -777,22 +853,74 @@ function Login() {
|
||||
tenentName: x.TenantId.TenantName || ""
|
||||
};
|
||||
localStorage.setItem(
|
||||
"TenetId",
|
||||
"TenantId",
|
||||
x.TenantId.objectId
|
||||
);
|
||||
tenentInfo.push(obj);
|
||||
}
|
||||
});
|
||||
localStorage.setItem("PageLanding", item.pageId);
|
||||
localStorage.setItem("defaultmenuid", item.menuId);
|
||||
localStorage.setItem("pageType", item.pageType);
|
||||
navigate(`/${item.pageType}/${item.pageId}`);
|
||||
localStorage.setItem("PageLanding", setting.pageId);
|
||||
localStorage.setItem(
|
||||
"defaultmenuid",
|
||||
setting.menuId
|
||||
);
|
||||
localStorage.setItem("pageType", setting.pageType);
|
||||
if (isEnableSubscription) {
|
||||
const LocalUserDetails = {
|
||||
name: results[0].get("Name"),
|
||||
email: results[0].get("Email"),
|
||||
phone: results[0]?.get("Phone") || "",
|
||||
company: results[0].get("Company")
|
||||
};
|
||||
localStorage.setItem(
|
||||
"userDetails",
|
||||
JSON.stringify(LocalUserDetails)
|
||||
);
|
||||
const res = await fetchSubscription();
|
||||
const billingDate = res.billingDate;
|
||||
const freeplan = res.plan;
|
||||
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);
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
}
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
}
|
||||
} else {
|
||||
// Redirect to the appropriate URL after successful login
|
||||
navigate(redirectUrl);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem("PageLanding", item.pageId);
|
||||
localStorage.setItem("defaultmenuid", item.menuId);
|
||||
localStorage.setItem("pageType", item.pageType);
|
||||
navigate(`/${item.pageType}/${item.pageId}`);
|
||||
localStorage.setItem("PageLanding", setting.pageId);
|
||||
localStorage.setItem("defaultmenuid", setting.menuId);
|
||||
localStorage.setItem("pageType", setting.pageType);
|
||||
setState({ ...state, loading: false });
|
||||
if (isEnableSubscription) {
|
||||
const LocalUserDetails = {
|
||||
name: results[0].get("Name"),
|
||||
email: results[0].get("Email"),
|
||||
phone: results[0]?.get("Phone") || ""
|
||||
// company: results.get("Company"),
|
||||
};
|
||||
localStorage.setItem(
|
||||
"userDetails",
|
||||
JSON.stringify(LocalUserDetails)
|
||||
);
|
||||
const billingDate = "";
|
||||
if (billingDate) {
|
||||
navigate(`/subscription`);
|
||||
}
|
||||
} else {
|
||||
// Redirect to the appropriate URL after successful login
|
||||
navigate(redirectUrl);
|
||||
}
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
@@ -855,7 +983,7 @@ function Login() {
|
||||
userDetails: {
|
||||
name: userInformation.name,
|
||||
email: userInformation.email,
|
||||
phone: userInformation.phone,
|
||||
phone: userInformation?.phone || "",
|
||||
role: "contracts_User",
|
||||
company: userDetails.Company,
|
||||
jobTitle: userDetails.Destination
|
||||
@@ -867,7 +995,7 @@ function Login() {
|
||||
const LocalUserDetails = {
|
||||
name: userInformation.name,
|
||||
email: userInformation.email,
|
||||
phone: userInformation.phone,
|
||||
phone: userInformation?.phone || "",
|
||||
company: userDetails.Company,
|
||||
jobTitle: userDetails.JobTitle
|
||||
};
|
||||
@@ -889,8 +1017,9 @@ function Login() {
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setIsModal(false);
|
||||
Parse.User.logOut();
|
||||
|
||||
if (Parse?.User?.current()) {
|
||||
Parse.User.logOut();
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let appName = localStorage.getItem("appName");
|
||||
@@ -913,6 +1042,26 @@ function Login() {
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
};
|
||||
|
||||
// `handleSignInWithSSO` is trigger when user click sign in with sso and open sso authorize endpoint
|
||||
const handleSignInWithSSO = () => {
|
||||
if (isLoginSSO === false) {
|
||||
if (state?.email) {
|
||||
setIsLoginSSO(true);
|
||||
const encodedEmail = encodeURIComponent(state.email);
|
||||
const clientUrl = window.location.origin;
|
||||
const domain = state.email.split("@")?.pop();
|
||||
const ssoApiUrl =
|
||||
process.env.SSO_API_URL || "https://sso.opensignlabs.com/api";
|
||||
openInNewTab(
|
||||
`${ssoApiUrl}/oauth/authorize?response_type=code&provider=saml&tenant=${domain}&product=OpenSign&redirect_uri=${clientUrl}/sso&state=${encodedEmail}`,
|
||||
"_self"
|
||||
);
|
||||
} else {
|
||||
alert("Please provide email.");
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="bg-white">
|
||||
<Title title={"Login Page"} />
|
||||
@@ -949,172 +1098,142 @@ function Login() {
|
||||
<>
|
||||
<div aria-labelledby="loginHeading" role="region">
|
||||
<div className="md:m-10 lg:m-16 md:p-4 lg:p-10 p-4 bg-[#ffffff] md:border-[1px] md:border-gray-400 ">
|
||||
<div className="w-[250px] h-[66px] inline-block">
|
||||
<img
|
||||
src={image}
|
||||
width="100%"
|
||||
alt="The image displays the OpenSign logo with a stylized blue square with an open corner, accompanied by the tagline Seal the Deal, Openly."
|
||||
/>
|
||||
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
|
||||
{image && (
|
||||
<img
|
||||
src={image}
|
||||
className="object-contain h-full"
|
||||
alt="The image displays the OpenSign logo with a stylized blue square with an open corner, accompanied by the tagline Seal the Deal, Openly."
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
|
||||
<div>
|
||||
<div>
|
||||
<form onSubmit={handleSubmit} aria-label="Login Form">
|
||||
<h1 className="text-[30px] mt-6">Welcome Back!</h1>
|
||||
<fieldset>
|
||||
<legend className="text-[12px] text-[#878787]">
|
||||
Login to your account
|
||||
</legend>
|
||||
<div className="px-6 py-4 outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
|
||||
<label className="block text-xs" htmlFor="email">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="text"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
|
||||
name="email"
|
||||
value={state.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<hr className="my-2 border-none" />
|
||||
<label className="block text-xs" htmlFor="password">
|
||||
Password
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="password"
|
||||
type={state.passwordVisible ? "text" : "password"}
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
|
||||
name="password"
|
||||
value={state.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<span
|
||||
className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer ${
|
||||
state.passwordVisible
|
||||
? "text-[#007bff]"
|
||||
: "text-black"
|
||||
}`}
|
||||
onClick={togglePasswordVisibility}
|
||||
>
|
||||
{state.passwordVisible ? (
|
||||
<i className="fa fa-eye-slash text-xs pb-1" /> // Close eye icon
|
||||
) : (
|
||||
<i className="fa fa-eye text-xs pb-1 " /> // Open eye icon
|
||||
)}
|
||||
</span>
|
||||
</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>
|
||||
<form onSubmit={handleSubmit} aria-label="Login Form">
|
||||
<h1 className="text-[30px] mt-6">Welcome Back!</h1>
|
||||
<fieldset>
|
||||
<legend className="text-[12px] text-[#878787]">
|
||||
Login to your account
|
||||
</legend>
|
||||
<div className="px-6 py-4 outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
|
||||
<label className="block text-xs" htmlFor="email">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="text"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
|
||||
name="email"
|
||||
value={state.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<hr className="my-2 border-none" />
|
||||
{!isLoginSSO && (
|
||||
<>
|
||||
<label className="block text-xs" htmlFor="password">
|
||||
Password
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="password"
|
||||
type={
|
||||
state.passwordVisible ? "text" : "password"
|
||||
}
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
|
||||
name="password"
|
||||
value={state.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
<span
|
||||
className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer ${
|
||||
state.passwordVisible
|
||||
? "text-[#007bff]"
|
||||
: "text-black"
|
||||
}`}
|
||||
onClick={togglePasswordVisibility}
|
||||
>
|
||||
{state.passwordVisible ? (
|
||||
<i className="fa fa-eye-slash text-xs pb-1" /> // Close eye icon
|
||||
) : (
|
||||
<i className="fa fa-eye text-xs pb-1 " /> // Open eye icon
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="relative mt-1">
|
||||
<NavLink
|
||||
to="/forgetpassword"
|
||||
className="text-[13px] underline focus:outline-none focus:ring-2 focus:ring-blue-600"
|
||||
className="text-[13px] text-[#002864] hover:underline underline-offset-1 focus:outline-none cursor-pointer ml-1"
|
||||
>
|
||||
Forgot Password ?
|
||||
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"
|
||||
className="rounded-sm bg-[#3ac9d6] text-white w-full py-3 shadow outline-none uppercase focus:ring-2 focus:ring-blue-600"
|
||||
disabled={state.loading}
|
||||
>
|
||||
{state.loading ? "Loading..." : "Login"}
|
||||
</button>
|
||||
<NavLink
|
||||
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
|
||||
to="/signup"
|
||||
style={width < 768 ? { textAlign: "center" } : {}}
|
||||
>
|
||||
Create Account
|
||||
</NavLink>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
{(appInfo.fbAppId || appInfo.googleClietId) && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
className="text-sm"
|
||||
</fieldset>
|
||||
<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"
|
||||
className="rounded-sm bg-[#3ac9d6] text-white w-full py-3 shadow outline-none uppercase focus:ring-2 focus:ring-blue-600"
|
||||
disabled={state.loading}
|
||||
>
|
||||
<hr
|
||||
className={"border-[1px] border-gray-300 w-full"}
|
||||
style={{ color: "grey" }}
|
||||
/>
|
||||
<span style={{ color: "grey" }} className="px-2 ">
|
||||
OR
|
||||
</span>
|
||||
<hr
|
||||
className={"border-[1px] border-gray-300 w-full"}
|
||||
style={{ color: "grey" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<br />
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
{/* {appInfo.fbAppId && appInfo.fbAppId !== "" ? (
|
||||
{state.loading ? "Loading..." : "Login"}
|
||||
</button>
|
||||
<NavLink
|
||||
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
|
||||
to={
|
||||
location.search
|
||||
? "/signup" + location.search
|
||||
: "/signup"
|
||||
}
|
||||
style={width < 768 ? { textAlign: "center" } : {}}
|
||||
>
|
||||
Create Account
|
||||
</NavLink>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
{appInfo.googleClietId && (
|
||||
<div className="text-sm flex justify-center items-center">
|
||||
<hr className="border-[1px] border-gray-300 w-full" />
|
||||
<span className="px-2 text-gray-500 cursor-default">
|
||||
OR
|
||||
</span>
|
||||
<hr className="border-[1px] border-gray-300 w-full" />
|
||||
</div>
|
||||
)}
|
||||
<br />
|
||||
<div className="flex flex-col justify-center items-center gap-y-3">
|
||||
{/* {appInfo?.fbAppId && (
|
||||
<LoginFacebook
|
||||
FBCred={appInfo.fbAppId}
|
||||
thirdpartyLoginfn={thirdpartyLoginfn}
|
||||
thirdpartyLoader={state.thirdpartyLoader}
|
||||
setThirdpartyLoader={setThirdpartyLoader}
|
||||
/>
|
||||
) : null} */}
|
||||
</div>
|
||||
<div style={{ margin: "10px 0" }}></div>
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
{appInfo.googleClietId && appInfo.googleClietId !== "" ? (
|
||||
<GoogleSignInBtn
|
||||
GoogleCred={appInfo.googleClietId}
|
||||
thirdpartyLoginfn={thirdpartyLoginfn}
|
||||
thirdpartyLoader={state.thirdpartyLoader}
|
||||
setThirdpartyLoader={setThirdpartyLoader}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)} */}
|
||||
{appInfo?.googleClietId && (
|
||||
<GoogleSignInBtn
|
||||
GoogleCred={appInfo.googleClietId}
|
||||
thirdpartyLoginfn={thirdpartyLoginfn}
|
||||
thirdpartyLoader={state.thirdpartyLoader}
|
||||
setThirdpartyLoader={setThirdpartyLoader}
|
||||
/>
|
||||
)}
|
||||
{isEnableSubscription && (
|
||||
<div
|
||||
className="cursor-pointer border-[1px] border-gray-300 rounded px-[40px] py-2 font-semibold text-sm hover:border-[#d2e3fc] hover:bg-[#ecf3feb7]"
|
||||
onClick={() => handleSignInWithSSO()}
|
||||
>
|
||||
Sign in with SSO
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{width >= 768 && (
|
||||
<div className="self-center">
|
||||
<div className="place-self-center">
|
||||
<div className="mx-auto md:w-[300px] lg:w-[400px] xl:w-[500px]">
|
||||
<img
|
||||
src={login_img}
|
||||
|
||||
@@ -5,6 +5,7 @@ import "../styles/signature.css";
|
||||
import { toDataUrl } from "../constant/Utils";
|
||||
import Parse from "parse";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import { SaveFileSize } from "../constant/saveFileSize";
|
||||
const ManageSign = () => {
|
||||
const appName = appInfo.appname;
|
||||
const [penColor, setPenColor] = useState("blue");
|
||||
@@ -158,7 +159,11 @@ const ManageSign = () => {
|
||||
try {
|
||||
const parseFile = new Parse.File(file.name, file);
|
||||
const response = await parseFile.save();
|
||||
return response?.url();
|
||||
if (response?.url()) {
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(file.size, response.url(), tenantId);
|
||||
return response?.url();
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("sign upload err", err);
|
||||
setIsLoader(false);
|
||||
|
||||
@@ -7,8 +7,12 @@ import { useNavigate } from "react-router-dom";
|
||||
import Title from "../components/Title";
|
||||
import Parse from "parse";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
const DriveBody = React.lazy(
|
||||
() => import("../components/opensigndrive/DriveBody")
|
||||
import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
import Tour from "reactour";
|
||||
import axios from "axios";
|
||||
|
||||
const DriveBody = React.lazy(() =>
|
||||
import("../components/opensigndrive/DriveBody")
|
||||
);
|
||||
const Loader = () => {
|
||||
return (
|
||||
@@ -42,6 +46,8 @@ function Opensigndrive() {
|
||||
const [error, setError] = useState();
|
||||
const [folderLoader, setIsFolderLoader] = useState(false);
|
||||
const [isShowSort, setIsShowSort] = useState(false);
|
||||
const [isTour, setIsTour] = useState(false);
|
||||
const [tourStatusArr, setTourStatusArr] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState({
|
||||
isLoad: true,
|
||||
message: "This might take some time"
|
||||
@@ -56,6 +62,9 @@ function Opensigndrive() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const sortOrder = ["Ascending", "Descending"];
|
||||
const sortingValue = ["Name", "Date"];
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
const [tourData, setTourData] = useState();
|
||||
const [showTourFirstTIme, setShowTourFirstTime] = useState(true);
|
||||
const orderName = {
|
||||
Ascending: "Ascending",
|
||||
Descending: "Descending",
|
||||
@@ -73,12 +82,62 @@ function Opensigndrive() {
|
||||
|
||||
useEffect(() => {
|
||||
getPdfDocumentList();
|
||||
|
||||
// eslint-disable-next-line
|
||||
}, [docId]);
|
||||
|
||||
const tourConfigs = [
|
||||
{
|
||||
selector: '[data-tut="reactourFirst"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={`Click on the breadcrumb links to easily navigate through the folder hierarchy and view the documents within each folder.`}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourSecond"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={`Click the add button to create a new folder or document.`}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourThird"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={`Sort your documents by Date or Name using this menu.`}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourForth"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={`Click on this menu to display the documents in list view.`}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
//function for get all pdf document list
|
||||
const getPdfDocumentList = async (disbaleLoading) => {
|
||||
setLoading(true);
|
||||
if (showTourFirstTIme) {
|
||||
checkTourStatus();
|
||||
}
|
||||
if (!disbaleLoading) {
|
||||
setIsLoading({ isLoad: true, message: "This might take some time" });
|
||||
}
|
||||
@@ -87,8 +146,61 @@ function Opensigndrive() {
|
||||
if (driveDetails && driveDetails === "Error: Something went wrong!") {
|
||||
setHandleError("Error: Something went wrong!");
|
||||
} else if (driveDetails && driveDetails.length > 0) {
|
||||
const addMoreTour = [
|
||||
{
|
||||
selector: '[data-tut="reactourFifth"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={`The document list is displayed according to the selected sorting option. Icons next to each document indicate its current status.`}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "bottom",
|
||||
style: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourSixth"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={`Right-click on a document to see options such as Download, Rename, Move, and Delete. Click on the document to open it.`}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "bottom",
|
||||
style: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourSeventh"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={`Right-click on any folder to see options. Choose ‘Rename’ to change the folder’s name or click on the folder to navigate through its contents.`}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "bottom",
|
||||
style: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
let newTour = [...tourConfigs, ...addMoreTour];
|
||||
const isFolderExist = driveDetails.some(
|
||||
(data) => data.Type === "Folder"
|
||||
);
|
||||
if (!isFolderExist) {
|
||||
newTour = newTour.filter(
|
||||
(data) => data.selector !== '[data-tut="reactourSeventh"]'
|
||||
);
|
||||
}
|
||||
const isDocumentExist = driveDetails.some((data) => data.URL);
|
||||
if (!isDocumentExist) {
|
||||
newTour = newTour.filter(
|
||||
(data) => data.selector !== '[data-tut="reactourSixth"]'
|
||||
);
|
||||
}
|
||||
setTourData(newTour);
|
||||
setSkip((prevSkip) => prevSkip + limit);
|
||||
sortingData(null, null, driveDetails, true);
|
||||
} else {
|
||||
setTourData(tourConfigs);
|
||||
}
|
||||
if (!docId) {
|
||||
setFolderName([{ name: "OpenSign™ Drive", objectId: "" }]);
|
||||
@@ -141,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
|
||||
@@ -182,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);
|
||||
}
|
||||
@@ -203,6 +322,7 @@ function Opensigndrive() {
|
||||
const result = JSON.parse(JSON.stringify(res));
|
||||
|
||||
setPdfData((prev) => [...prev, result]);
|
||||
sortingData(null, null, [result], true);
|
||||
setNewFolderName();
|
||||
setIsFolderLoader(false);
|
||||
setIsFolder(false);
|
||||
@@ -223,9 +343,13 @@ function Opensigndrive() {
|
||||
const sortedBy = (appInfo, type, order) => {
|
||||
if (type === orderName.Name) {
|
||||
if (order === orderName.Ascending) {
|
||||
return appInfo.sort((a, b) => (a.Name > b.Name ? 1 : -1));
|
||||
return appInfo.sort((a, b) =>
|
||||
a.Name.toLowerCase() < b.Name.toLowerCase() ? -1 : 1
|
||||
);
|
||||
} else if (order === orderName.Descending) {
|
||||
return appInfo.sort((a, b) => (a.Name > b.Name ? -1 : 1));
|
||||
return appInfo.sort((a, b) =>
|
||||
a.Name.toLowerCase() < b.Name.toLowerCase() ? 1 : -1
|
||||
);
|
||||
}
|
||||
} else if (type === orderName.Date) {
|
||||
if (order === orderName.Ascending) {
|
||||
@@ -325,7 +449,7 @@ function Opensigndrive() {
|
||||
return (
|
||||
<React.Fragment key={id}>
|
||||
<span
|
||||
onClick={() => handleRoute(id)}
|
||||
onClick={() => handleRoute(id, folderData)}
|
||||
style={{
|
||||
color: "#a64b4e",
|
||||
fontWeight: "400",
|
||||
@@ -353,9 +477,77 @@ function Opensigndrive() {
|
||||
setNewFolderName("");
|
||||
setError("");
|
||||
};
|
||||
|
||||
const handleDontShow = (isChecked) => {
|
||||
setIsDontShow(isChecked);
|
||||
};
|
||||
|
||||
const closeTour = async () => {
|
||||
setIsTour(false);
|
||||
setShowTourFirstTime(false);
|
||||
if (isDontShow) {
|
||||
const serverUrl = localStorage.getItem("baseUrl");
|
||||
const appId = localStorage.getItem("parseAppId");
|
||||
const extUserClass = localStorage.getItem("extended_class");
|
||||
const json = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
const extUserId = json && json.length > 0 && json[0].objectId;
|
||||
let updatedTourStatus = [];
|
||||
if (tourStatusArr.length > 0) {
|
||||
updatedTourStatus = [...tourStatusArr];
|
||||
const driveTourIndex = tourStatusArr.findIndex(
|
||||
(obj) => obj["driveTour"] === false || obj["driveTour"] === true
|
||||
);
|
||||
if (driveTourIndex !== -1) {
|
||||
updatedTourStatus[driveTourIndex] = { driveTour: true };
|
||||
} else {
|
||||
updatedTourStatus.push({ driveTour: true });
|
||||
}
|
||||
} else {
|
||||
updatedTourStatus = [{ driveTour: true }];
|
||||
}
|
||||
await axios.put(
|
||||
serverUrl + "classes/" + extUserClass + "/" + extUserId,
|
||||
{
|
||||
TourStatus: updatedTourStatus
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"X-Parse-Application-Id": appId
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
//function to use check tour status of open sign drive
|
||||
async function checkTourStatus() {
|
||||
const currentUser = Parse.User.current();
|
||||
const cloudRes = await Parse.Cloud.run("getUserDetails", {
|
||||
email: currentUser.get("email")
|
||||
});
|
||||
const res = { data: cloudRes.toJSON() };
|
||||
if (res.data && res.data.TourStatus && res.data.TourStatus.length > 0) {
|
||||
const tourStatus = res.data.TourStatus;
|
||||
setTourStatusArr(tourStatus);
|
||||
const filteredtourStatus = tourStatus.filter((obj) => obj["driveTour"]);
|
||||
if (filteredtourStatus.length > 0) {
|
||||
const driveTour = filteredtourStatus[0]["driveTour"];
|
||||
// console.log("loginTour", loginTour);
|
||||
if (driveTour) {
|
||||
setIsTour(false);
|
||||
} else {
|
||||
setIsTour(true);
|
||||
}
|
||||
} else {
|
||||
setIsTour(true);
|
||||
}
|
||||
} else {
|
||||
setIsTour(true);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div style={{ backgroundColor: "white" }} className="folderComponent">
|
||||
<Title title={"OpenSign™ Drive"} drive={true} />
|
||||
|
||||
<div>
|
||||
<ModalUi
|
||||
headerColor={"#dc3545"}
|
||||
@@ -518,7 +710,19 @@ function Opensigndrive() {
|
||||
) : (
|
||||
<>
|
||||
<div className="folderContainer">
|
||||
{tourData && (
|
||||
<Tour
|
||||
onRequestClose={closeTour}
|
||||
steps={tourData}
|
||||
isOpen={isTour}
|
||||
closeWithMask={false}
|
||||
scrollOffset={-100}
|
||||
rounded={5}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
data-tut="reactourFirst"
|
||||
onMouseEnter={(e) => handleMouseEnter(e)}
|
||||
ref={scrollRef}
|
||||
style={{
|
||||
@@ -536,7 +740,7 @@ function Opensigndrive() {
|
||||
}
|
||||
onClick={() => setIsNewFol(!isNewFol)}
|
||||
>
|
||||
<div className="sort">
|
||||
<div className="sort" data-tut="reactourSecond">
|
||||
<i
|
||||
className="fa fa-plus-square"
|
||||
aria-hidden="true"
|
||||
@@ -597,7 +801,11 @@ function Opensigndrive() {
|
||||
className={isShowSort ? "dropdown show" : "dropdown"}
|
||||
onClick={() => setIsShowSort(!isShowSort)}
|
||||
>
|
||||
<div className=" sort " data-toggle="dropdown">
|
||||
<div
|
||||
data-tut="reactourThird"
|
||||
className=" sort "
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<i
|
||||
className="fa fa-sort-amount-asc"
|
||||
aria-hidden="true"
|
||||
@@ -687,7 +895,11 @@ function Opensigndrive() {
|
||||
></i>
|
||||
</div>
|
||||
) : (
|
||||
<div className="sort" onClick={() => setIsList(!isList)}>
|
||||
<div
|
||||
data-tut="reactourForth"
|
||||
className="sort"
|
||||
onClick={() => setIsList(!isList)}
|
||||
>
|
||||
<i
|
||||
className="fa fa-list"
|
||||
aria-hidden="true"
|
||||
@@ -712,23 +924,28 @@ function Opensigndrive() {
|
||||
<span style={{ fontWeight: "bold" }}>No Data Found!</span>
|
||||
</div>
|
||||
) : (
|
||||
<React.Suspense fallback={<Loader />}>
|
||||
<DriveBody
|
||||
pdfData={pdfData}
|
||||
setFolderName={setFolderName}
|
||||
setIsLoading={setIsLoading}
|
||||
setDocId={setDocId}
|
||||
getPdfDocumentList={getPdfDocumentList}
|
||||
isDocId={docId}
|
||||
setPdfData={setPdfData}
|
||||
isList={isList}
|
||||
setIsAlert={setIsAlert}
|
||||
setSkip={setSkip}
|
||||
/>
|
||||
{loading && (
|
||||
<div style={{ textAlign: "center" }}>Loading...</div>
|
||||
)}
|
||||
</React.Suspense>
|
||||
<div data-tut="reactourFifth">
|
||||
<React.Suspense fallback={<Loader />}>
|
||||
<DriveBody
|
||||
dataTutSixth="reactourSixth"
|
||||
dataTutSeventh="reactourSeventh"
|
||||
pdfData={pdfData}
|
||||
setFolderName={setFolderName}
|
||||
setIsLoading={setIsLoading}
|
||||
setDocId={setDocId}
|
||||
getPdfDocumentList={getPdfDocumentList}
|
||||
isDocId={docId}
|
||||
setPdfData={setPdfData}
|
||||
isList={isList}
|
||||
setIsAlert={setIsAlert}
|
||||
setSkip={setSkip}
|
||||
sortingData={sortingData}
|
||||
/>
|
||||
{loading && (
|
||||
<div style={{ textAlign: "center" }}>Loading...</div>
|
||||
)}
|
||||
</React.Suspense>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,12 +41,8 @@ const PgSignUp = () => {
|
||||
const url = window.location.href;
|
||||
let paramString = url.split("?")[1];
|
||||
let queryString = new URLSearchParams(paramString);
|
||||
// console.log("url ", queryString);
|
||||
|
||||
let obj = { ...formData };
|
||||
for (let pair of queryString.entries()) {
|
||||
// console.log("Key is: " + pair[0]);
|
||||
// console.log("Value is: " + pair[1]);
|
||||
obj = { ...obj, [pair[0]]: pair[1] };
|
||||
}
|
||||
const zohoRes = await axios.post(
|
||||
@@ -61,34 +57,27 @@ const PgSignUp = () => {
|
||||
}
|
||||
}
|
||||
);
|
||||
// console.log("zohoRes ", zohoRes);
|
||||
const userSettings = JSON.parse(localStorage.getItem("userSettings"));
|
||||
const extClass = userSettings[0].extended_class;
|
||||
// console.log("extClass ", extClass);
|
||||
const params = { email: userDetails.email };
|
||||
const res = await Parse.Cloud.run("getUserDetails", params);
|
||||
// console.log("res", res);
|
||||
if (res) {
|
||||
const updateQuery = new Parse.Object(extClass);
|
||||
updateQuery.id = res.id;
|
||||
updateQuery.set(
|
||||
"Next_billing_date",
|
||||
new Date(zohoRes.data.result.nextBillingDate)
|
||||
);
|
||||
updateQuery.set("Plan", zohoRes.data.result.plan);
|
||||
updateQuery.set("Customer_id", zohoRes.data.result.customer_id);
|
||||
updateQuery.set(
|
||||
"Subscription_id",
|
||||
zohoRes.data.result.subscription_id
|
||||
);
|
||||
const updateRes = await updateQuery.save();
|
||||
if (updateRes) {
|
||||
const params = { subscription: zohoRes.data.result.subscription };
|
||||
const baseurl = `${parseBaseUrl}functions/savesubscription`;
|
||||
try {
|
||||
const res = await axios.post(baseurl, params, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
}
|
||||
});
|
||||
if (res) {
|
||||
localStorage.removeItem("userDetails");
|
||||
navigate(
|
||||
"/" + userSettings[0].pageType + "/" + userSettings[0].pageId
|
||||
);
|
||||
setIsLoader(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err.message);
|
||||
setIsLoader(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
@@ -108,12 +97,8 @@ const PgSignUp = () => {
|
||||
const url = window.location.href;
|
||||
let paramString = url.split("?")[1];
|
||||
let queryString = new URLSearchParams(paramString);
|
||||
// console.log("url ", queryString);
|
||||
|
||||
let obj = { ...formData };
|
||||
for (let pair of queryString.entries()) {
|
||||
// console.log("Key is: " + pair[0]);
|
||||
// console.log("Value is: " + pair[1]);
|
||||
obj = { ...obj, [pair[0]]: pair[1] };
|
||||
}
|
||||
saveUser(obj);
|
||||
@@ -142,7 +127,9 @@ const PgSignUp = () => {
|
||||
user.set("username", zohoRes.data.result.email);
|
||||
user.set("password", obj.password);
|
||||
user.set("email", zohoRes.data.result.email);
|
||||
user.set("phone", zohoRes.data.result.phone);
|
||||
if (zohoRes?.data?.result?.phone) {
|
||||
user.set("phone", zohoRes.data.result.phone);
|
||||
}
|
||||
user.set("name", zohoRes.data.result.name);
|
||||
|
||||
const res = await user.signUp();
|
||||
@@ -155,21 +142,10 @@ const PgSignUp = () => {
|
||||
company: zohoRes.data.result.company,
|
||||
name: zohoRes.data.result.name,
|
||||
email: zohoRes.data.result.email,
|
||||
phone: zohoRes.data.result.phone,
|
||||
phone: zohoRes?.data?.result?.phone || "",
|
||||
role: obj.role
|
||||
// "pincode": "",
|
||||
// "address": "",
|
||||
// "country": "",
|
||||
// "state": "",
|
||||
// "city": ""
|
||||
},
|
||||
|
||||
planDetails: {
|
||||
plan: zohoRes.data.result.plan || {},
|
||||
nextBillingDate: zohoRes.data.result.nextBillingDate || "",
|
||||
customer_id: zohoRes.data.result.customer_id || "",
|
||||
subscription_id: zohoRes.data.result.subscription_id || ""
|
||||
}
|
||||
subscription: zohoRes.data.result.subscription
|
||||
};
|
||||
const usersignup = await Parse.Cloud.run("usersignup", params);
|
||||
// console.log("usersignup ", usersignup);
|
||||
@@ -353,7 +329,7 @@ const PgSignUp = () => {
|
||||
tenentName: x.TenantId.TenantName || ""
|
||||
};
|
||||
localStorage.setItem(
|
||||
"TenetId",
|
||||
"TenantId",
|
||||
x.TenantId.objectId
|
||||
);
|
||||
tenentInfo.push(obj);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { NavLink, useNavigate } from "react-router-dom";
|
||||
import checkmark from "../assets/images/checkmark.png";
|
||||
import plansArr from "../json/plansArr.json";
|
||||
import plansArr from "../json/plansArr";
|
||||
import Title from "../components/Title";
|
||||
import Parse from "parse";
|
||||
const listItemStyle = {
|
||||
paddingLeft: "20px", // Add padding to create space for the image
|
||||
backgroundImage: `url(${checkmark})`, // Set your image as the list style image
|
||||
@@ -16,18 +16,34 @@ const PlanSubscriptions = () => {
|
||||
const navigate = useNavigate();
|
||||
const [yearlyVisible, setYearlyVisible] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(true);
|
||||
|
||||
const userDetails = JSON.parse(localStorage.getItem("userDetails"));
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
const user = {
|
||||
name: extUser?.Name,
|
||||
email: extUser?.Email,
|
||||
company: extUser?.Company,
|
||||
phone: extUser?.Phone
|
||||
};
|
||||
const userDetails = JSON.parse(localStorage.getItem("userDetails")) || user;
|
||||
// console.log("userDetails ", userDetails);
|
||||
const name =
|
||||
userDetails && userDetails.name ? "first_name=" + userDetails.name : "";
|
||||
userDetails && userDetails.name
|
||||
? "first_name=" + encodeURIComponent(userDetails.name)
|
||||
: "";
|
||||
const email =
|
||||
userDetails && userDetails.email ? "&email=" + userDetails.email : "";
|
||||
userDetails && userDetails.email
|
||||
? "&email=" + encodeURIComponent(userDetails.email)
|
||||
: "";
|
||||
const company =
|
||||
userDetails && userDetails.company
|
||||
? "&company_name=" + userDetails.company
|
||||
? "&company_name=" + encodeURIComponent(userDetails.company)
|
||||
: "";
|
||||
const phone =
|
||||
userDetails && userDetails.phone ? "&mobile=" + userDetails.phone : "";
|
||||
userDetails && userDetails.phone
|
||||
? "&mobile=" + encodeURIComponent(userDetails.phone)
|
||||
: "";
|
||||
|
||||
const details =
|
||||
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
|
||||
name +
|
||||
@@ -35,15 +51,35 @@ const PlanSubscriptions = () => {
|
||||
company +
|
||||
phone;
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem("accesstoken")) {
|
||||
setIsLoader(false);
|
||||
setYearlyVisible(false);
|
||||
} else {
|
||||
navigate("/", { replace: true });
|
||||
}
|
||||
// if (localStorage.getItem("accesstoken")) {
|
||||
setIsLoader(false);
|
||||
// } else {
|
||||
// navigate("/", { replace: true });
|
||||
// }
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const handleFreePlan = async () => {
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const params = { userId: Parse.User.current().id };
|
||||
const res = await Parse.Cloud.run("freesubscription", params);
|
||||
if (res.status === "success" && res.result === "already subscribed!") {
|
||||
setIsLoader(false);
|
||||
alert("You have already subscribed to plan!");
|
||||
} else if (res.status === "success") {
|
||||
setIsLoader(false);
|
||||
navigate("/");
|
||||
} else if (res.status === "error") {
|
||||
setIsLoader(false);
|
||||
alert(res.result);
|
||||
}
|
||||
} catch (err) {
|
||||
setIsLoader(false);
|
||||
console.log("err in free subscribe", err.message);
|
||||
alert("Somenthing went wrong, please try again later!");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Title title={"Subscriptions"} />
|
||||
@@ -69,23 +105,40 @@ const PlanSubscriptions = () => {
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
overflowY: "auto",
|
||||
maxHeight: "100%",
|
||||
"--theme-color": "#7952b3",
|
||||
"--plan-width": 30
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
id="monthlyPlans"
|
||||
className={`${yearlyVisible ? "none" : "block my-2"}`}
|
||||
>
|
||||
<div className="flex justify-center w-full my-2">
|
||||
<ul className=" flex flex-col md:flex-row h-full bg-white justify-center border-collapse border-[1px] border-gray-300">
|
||||
<div className="block my-2">
|
||||
<div className="flex flex-col justify-center items-center w-full">
|
||||
<div className="mb-6 mt-2 flex flex-row border-[1px] p-2 border-gray-300 rounded text-sm">
|
||||
<span
|
||||
onClick={() => setYearlyVisible(false)}
|
||||
className={`${
|
||||
!yearlyVisible
|
||||
? "bg-[#002862] text-white"
|
||||
: "bg-white text-black"
|
||||
} px-4 py-1 rounded cursor-pointer`}
|
||||
>
|
||||
Monthly
|
||||
</span>
|
||||
<span
|
||||
onClick={() => setYearlyVisible(true)}
|
||||
className={`${
|
||||
yearlyVisible
|
||||
? "bg-[#002862] text-white"
|
||||
: "bg-white text-black"
|
||||
} px-4 py-1 rounded cursor-pointer`}
|
||||
>
|
||||
Yearly (10% off)
|
||||
</span>
|
||||
</div>
|
||||
<ul className="flex flex-col md:flex-row h-full bg-white justify-center">
|
||||
{plansArr.map((item) => (
|
||||
<li
|
||||
className="flex flex-col md:my-0 text-center border-[1px] border-gray-300 w-[260px]"
|
||||
className="flex flex-col text-center border-collapse border-[1px] border-gray-300 max-w-[250px]"
|
||||
key={item.planName}
|
||||
>
|
||||
<div className="p-2 flex flex-col justify-center items-center">
|
||||
<div className="p-2 flex flex-col justify-center items-center max-h-[310px]">
|
||||
<h3 className="text-[#002862] uppercase">
|
||||
{item.planName}
|
||||
</h3>
|
||||
@@ -96,20 +149,20 @@ const PlanSubscriptions = () => {
|
||||
alt="freeimg"
|
||||
/>
|
||||
</div>
|
||||
<div className="">
|
||||
<div>
|
||||
<span className="text-3xl">
|
||||
{item.currency && <small>{item.currency}</small>}
|
||||
{item.price}
|
||||
{yearlyVisible
|
||||
? item?.yearlyPrice
|
||||
: item.monthlyPrice}
|
||||
</span>
|
||||
<div
|
||||
className={`${
|
||||
item.subtitle.length <= 32
|
||||
? "w-[150px] text-center"
|
||||
: ""
|
||||
} text-sm text-center my-2`}
|
||||
>
|
||||
<p
|
||||
<p className="font-semibold pt-2 text-sm">
|
||||
{yearlyVisible ? "Billed Yearly" : "Billed Monthly"}
|
||||
</p>
|
||||
<div className="max-w-[250px] h-[40px] text-center text-sm my-2">
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
backgroundColor: item.subtitlecolor
|
||||
? item.subtitlecolor
|
||||
: "white"
|
||||
@@ -124,23 +177,33 @@ const PlanSubscriptions = () => {
|
||||
) : (
|
||||
<span>{item.subtitle}</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NavLink
|
||||
to={
|
||||
item.btnText === "Subscribe"
|
||||
? item.url + details
|
||||
: item.url
|
||||
}
|
||||
className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:no-underline hover:text-white"
|
||||
target={item.target}
|
||||
>
|
||||
{item.btnText}
|
||||
</NavLink>
|
||||
{item.url ? (
|
||||
<NavLink
|
||||
to={
|
||||
item.btnText === "Subscribe"
|
||||
? yearlyVisible
|
||||
? item.yearlyUrl + details
|
||||
: item.url + details
|
||||
: item.url
|
||||
}
|
||||
className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
|
||||
target={item.target}
|
||||
>
|
||||
{item.btnText}
|
||||
</NavLink>
|
||||
) : (
|
||||
<button
|
||||
className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:text-white cursor-pointer"
|
||||
onClick={() => handleFreePlan()}
|
||||
>
|
||||
{item.btnText}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<hr className="w-full bg-gray-300 p-[.5px]" />
|
||||
<hr className="w-full bg-gray-300 h-[0.5px]" />
|
||||
<ul className="mx-1 p-3 text-left break-words text-sm list-none">
|
||||
{item.benefits.map((subitem, index) => (
|
||||
<li style={listItemStyle} key={index} className="m-1">
|
||||
@@ -154,6 +217,23 @@ const PlanSubscriptions = () => {
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="text-sm flex justify-center items-center">
|
||||
<hr className="border-[1px] border-gray-300 w-[20%]" />
|
||||
<span className="px-2 text-gray-500 cursor-default">or</span>
|
||||
<hr className="border-[1px] border-gray-300 w-[20%]" />
|
||||
</div>
|
||||
<div className="flex flex-col justify-center w-full items-center">
|
||||
<h3 className="text-[#002862] mt-1 mb-2">
|
||||
Host it yourself for free
|
||||
</h3>
|
||||
<NavLink
|
||||
to={"https://github.com/OpenSignLabs/OpenSign"}
|
||||
className="bg-[#002862] w-[200px] text-center text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
|
||||
target={"_blank"}
|
||||
>
|
||||
Visit Github
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@ import reportJson from "../json/ReportJson";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Title from "../components/Title";
|
||||
import PageNotFound from "./PageNotFound";
|
||||
import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
|
||||
const Report = () => {
|
||||
const { id } = useParams();
|
||||
@@ -18,6 +19,8 @@ const Report = () => {
|
||||
const [isNextRecord, setIsNextRecord] = useState(false);
|
||||
const [isMoreDocs, setIsMoreDocs] = useState(true);
|
||||
const [form, setForm] = useState("");
|
||||
const [tourData, setTourData] = useState([]);
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
const abortController = new AbortController();
|
||||
const docPerPage = 10;
|
||||
|
||||
@@ -46,6 +49,9 @@ const Report = () => {
|
||||
// eslint-disable-next-line
|
||||
}, [isNextRecord]);
|
||||
|
||||
const handleDontShow = (isChecked) => {
|
||||
setIsDontShow(isChecked);
|
||||
};
|
||||
const getReportData = async (skipUserRecord = 0, limit = 200) => {
|
||||
// setIsLoader(true);
|
||||
const json = reportJson(id);
|
||||
@@ -54,7 +60,7 @@ const Report = () => {
|
||||
setHeading(json.heading);
|
||||
setReportName(json.reportName);
|
||||
setForm(json.form);
|
||||
setReportHelp(json?.helpMsg)
|
||||
setReportHelp(json?.helpMsg);
|
||||
const currentUser = Parse.User.current().id;
|
||||
|
||||
const headers = {
|
||||
@@ -69,6 +75,42 @@ const Report = () => {
|
||||
headers: headers,
|
||||
signal: abortController.signal // is used to cancel fetch query
|
||||
});
|
||||
if (id === "6TeaPr321t") {
|
||||
const tourConfig = [
|
||||
{
|
||||
selector: "[data-tut=reactourFirst]",
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={
|
||||
"Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like ‘HR Manager’ and ‘New Employee’. Each time you use the template, you can assign the ‘New Employee’ role to different incoming staff members, while the ‘HR Manager’ role remains constant, facilitating a seamless onboarding process for each recruit. "
|
||||
}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
|
||||
if (res.data.result && res.data.result?.length > 0) {
|
||||
json.actions.map((data) => {
|
||||
const newConfig = {
|
||||
selector: `[data-tut="${data?.selector}"]`,
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={data?.message}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
};
|
||||
tourConfig.push(newConfig);
|
||||
});
|
||||
}
|
||||
|
||||
setTourData(tourConfig);
|
||||
}
|
||||
if (id === "4Hhwbp482K") {
|
||||
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
|
||||
let arr = [];
|
||||
@@ -106,12 +148,14 @@ const Report = () => {
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
}
|
||||
setIsNextRecord(false);
|
||||
setList((prevRecord) =>
|
||||
prevRecord.length > 0
|
||||
? [...prevRecord, ...res.data.result]
|
||||
: res.data.result
|
||||
);
|
||||
if (!res.data.result.error) {
|
||||
setIsNextRecord(false);
|
||||
setList((prevRecord) =>
|
||||
prevRecord.length > 0
|
||||
? [...prevRecord, ...res.data.result]
|
||||
: res.data.result
|
||||
);
|
||||
}
|
||||
}
|
||||
setIsLoader(false);
|
||||
} catch (err) {
|
||||
@@ -159,6 +203,8 @@ const Report = () => {
|
||||
docPerPage={docPerPage}
|
||||
form={form}
|
||||
report_help={reporthelp}
|
||||
tourData={tourData}
|
||||
isDontShow={isDontShow}
|
||||
/>
|
||||
) : (
|
||||
<PageNotFound prefix={"Report"} />
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import Parse from "parse";
|
||||
import axios from "axios";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import {
|
||||
isEnableSubscription,
|
||||
modalCancelBtnColor,
|
||||
modalSubmitBtnColor
|
||||
} from "../constant/const";
|
||||
import { fetchSubscription } from "../constant/Utils";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { showTenant } from "../redux/reducers/ShowTenant";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import loader from "../assets/images/loader2.gif";
|
||||
|
||||
const SSOVerify = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const [message, setMessage] = useState("Verifying SSO...");
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [userDetails, setUserDetails] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
Destination: "",
|
||||
Company: ""
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
linkUserWithSSO();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
// `linkUserWithSSO` is used to sign in or sign up a user using an SSO code and check if the user is present in the extended class
|
||||
const linkUserWithSSO = async () => {
|
||||
const param = new URLSearchParams(location.search);
|
||||
const code = param?.get("code");
|
||||
const state = param?.get("state");
|
||||
try {
|
||||
// The `ssosign` cloud function is used to sign in or sign up a user
|
||||
const ssosign = await Parse.Cloud.run("ssosign", {
|
||||
code: code,
|
||||
email: state
|
||||
});
|
||||
localStorage.setItem("accesstoken", ssosign.sessiontoken);
|
||||
// `checkExtUser` checks if the user is present in the extended class `contracts_Users` and if not, initiates the new user flow
|
||||
await checkExtUser(ssosign);
|
||||
} catch (err) {
|
||||
setMessage("Error: " + err.message);
|
||||
console.log("err", err.message);
|
||||
}
|
||||
};
|
||||
const checkExtUser = async (ssosign) => {
|
||||
const params = { email: ssosign?.email };
|
||||
try {
|
||||
// `isextenduser` checks if the current user is present in the extended class
|
||||
const extRes = await Parse.Cloud.run("isextenduser", params);
|
||||
if (extRes?.isUserExist) {
|
||||
// `getUserDetails` retrieves the current user's details from the extended class
|
||||
const extRes = await Parse.Cloud.run("getUserDetails", params);
|
||||
if (ssosign && ssosign.sessiontoken) {
|
||||
const LocalUserDetails = {
|
||||
name: extRes.Name,
|
||||
email: extRes.email,
|
||||
phone: extRes?.get("Phone") || "",
|
||||
company: extRes.get("Company")
|
||||
};
|
||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
||||
thirdpartyLoginfn(ssosign.sessiontoken);
|
||||
}
|
||||
} else {
|
||||
setIsModal(true);
|
||||
setUserDetails((prev) => ({
|
||||
...prev,
|
||||
name: ssosign.name,
|
||||
email: ssosign.email,
|
||||
phone: ssosign?.phone || ""
|
||||
}));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in isextenduser or getuserdetails cloud function", err);
|
||||
setMessage("Error: " + err.message);
|
||||
}
|
||||
};
|
||||
// `handleSubmitbtn` is used to create a user in the extended class
|
||||
const handleSubmitbtn = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoader(true);
|
||||
let phone;
|
||||
if (userDetails?.phone) {
|
||||
phone = validateInput(userDetails?.phone);
|
||||
} else {
|
||||
phone = true;
|
||||
}
|
||||
if (userDetails.Destination && userDetails.Company && phone) {
|
||||
const payload = { sessionToken: localStorage.getItem("accesstoken") };
|
||||
if (payload && payload.sessionToken) {
|
||||
const params = {
|
||||
userDetails: {
|
||||
name: userDetails.name,
|
||||
email: userDetails.email,
|
||||
phone: userDetails?.phone || "",
|
||||
role: "contracts_User",
|
||||
company: userDetails.Company,
|
||||
jobTitle: userDetails.Destination
|
||||
}
|
||||
};
|
||||
try {
|
||||
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
||||
if (userSignUp && userSignUp.sessionToken) {
|
||||
const LocalUserDetails = params.userDetails;
|
||||
localStorage.setItem(
|
||||
"userDetails",
|
||||
JSON.stringify(LocalUserDetails)
|
||||
);
|
||||
await thirdpartyLoginfn(userSignUp.sessionToken);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
alert(userSignUp.message);
|
||||
setIsLoader(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("error in usersignup", err);
|
||||
localStorage.removeItem("accesstoken");
|
||||
alert("Something went wrong.");
|
||||
setIsLoader(false);
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem("accesstoken");
|
||||
alert("Internal server error.");
|
||||
setIsLoader(false);
|
||||
}
|
||||
} else {
|
||||
alert("Please fill required details correctly.");
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
// `thirdpartyLoginfn` is used to save necessary parameters locally for the logged-in user
|
||||
const thirdpartyLoginfn = async (sessionToken) => {
|
||||
const baseUrl = localStorage.getItem("baseUrl");
|
||||
const parseAppId = localStorage.getItem("parseAppId");
|
||||
try {
|
||||
const validUser = await Parse.User.become(sessionToken);
|
||||
if (validUser) {
|
||||
localStorage.setItem("accesstoken", sessionToken);
|
||||
const _user = JSON.parse(JSON.stringify(validUser));
|
||||
localStorage.setItem("UserInformation", JSON.stringify(_user));
|
||||
if (_user.ProfilePic) {
|
||||
localStorage.setItem("profileImg", _user.ProfilePic);
|
||||
} else {
|
||||
localStorage.setItem("profileImg", "");
|
||||
}
|
||||
// Check extended class user role and tenentId
|
||||
try {
|
||||
let userRoles = [];
|
||||
const userSettings = appInfo.settings;
|
||||
//Get Current user roles
|
||||
const url = `${baseUrl}functions/UserGroups`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
sessionToken: _user.sessionToken
|
||||
};
|
||||
const body = { appname: appInfo.appname };
|
||||
const UserGroupsRes = await axios.post(url, JSON.stringify(body), {
|
||||
headers: headers
|
||||
});
|
||||
userRoles = (UserGroupsRes.data && UserGroupsRes.data.result) || [];
|
||||
if (userRoles) {
|
||||
let _currentRole = "";
|
||||
const excludeRoles = ["contracts_Guest", `contracts_appeditor`];
|
||||
const filteredRole = userRoles.filter(
|
||||
(x) => !excludeRoles.includes(x)
|
||||
);
|
||||
if (filteredRole?.length > 1) {
|
||||
console.log("user has two roles");
|
||||
// _currentRole = filteredRole.filter((x) => x === "contracts_User");
|
||||
} else {
|
||||
_currentRole = filteredRole?.[0] || "";
|
||||
}
|
||||
if (_currentRole) {
|
||||
const roleSetting = userSettings?.find(
|
||||
(setting) => setting.role === _currentRole
|
||||
);
|
||||
const redirectUrl =
|
||||
location?.state?.from ||
|
||||
`/${roleSetting.pageType}/${roleSetting.pageId}`;
|
||||
const _role = _currentRole.replace(`${appInfo.appname}_`, "");
|
||||
localStorage.setItem("_user_role", _role);
|
||||
// Get TenentID from Extendend Class
|
||||
localStorage.setItem(
|
||||
"extended_class",
|
||||
roleSetting.extended_class
|
||||
);
|
||||
try {
|
||||
const extUser = await Parse.Cloud.run("getUserDetails", {
|
||||
email: _user.email
|
||||
});
|
||||
if (extUser) {
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("userEmail", _extUser.Email);
|
||||
localStorage.setItem("username", _extUser.Name);
|
||||
localStorage.setItem("scriptId", true);
|
||||
let tenentInfo = [];
|
||||
const results = [extUser];
|
||||
if (results) {
|
||||
let extendedInfo_stringify = JSON.stringify(results);
|
||||
localStorage.setItem(
|
||||
"Extand_Class",
|
||||
extendedInfo_stringify
|
||||
);
|
||||
if (_extUser.TenantId) {
|
||||
const obj = {
|
||||
tenentId: _extUser.TenantId.objectId,
|
||||
tenentName: _extUser.TenantId.TenantName || ""
|
||||
};
|
||||
localStorage.setItem("TenantId", obj.tenentId);
|
||||
tenentInfo.push(obj);
|
||||
dispatch(showTenant(obj.tenentName || ""));
|
||||
localStorage.setItem("TenantName", obj.tenentName || "");
|
||||
}
|
||||
localStorage.setItem("PageLanding", roleSetting.pageId);
|
||||
localStorage.setItem("defaultmenuid", roleSetting.menuId);
|
||||
localStorage.setItem("pageType", roleSetting.pageType);
|
||||
if (isEnableSubscription) {
|
||||
const res = await fetchSubscription();
|
||||
const plan = res.plan;
|
||||
const billingDate = res.billingDate;
|
||||
if (plan === "freeplan") {
|
||||
navigate(redirectUrl);
|
||||
} else if (billingDate) {
|
||||
if (new Date(billingDate) > new Date()) {
|
||||
localStorage.removeItem("userDetails");
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
navigate(`/subscription`, {
|
||||
replace: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
navigate(`/subscription`, {
|
||||
replace: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
navigate(redirectUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
alert("user not exist.");
|
||||
setMessage("Error: User not exist.");
|
||||
console.log("err in get extUser", err);
|
||||
}
|
||||
} else {
|
||||
alert("Role does not exists.");
|
||||
setMessage("Error: Role does not exists.");
|
||||
}
|
||||
} else {
|
||||
alert("Role does not exists.");
|
||||
setMessage("Error: Role does not exists.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in usergroups", err);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in become method", err);
|
||||
}
|
||||
};
|
||||
// `handleCloseModal` is triggered when the user wants to close the new user flow modal
|
||||
const handleCloseModal = () => {
|
||||
setIsModal(false);
|
||||
if (Parse?.User?.current()) {
|
||||
Parse.User.logOut();
|
||||
}
|
||||
};
|
||||
|
||||
// `validateInput` is used to verify the phone pattern
|
||||
function validateInput(input) {
|
||||
if (input) {
|
||||
const pattern = /^(?!.*\+.*\+)[\d+-]*$/;
|
||||
return pattern.test(input);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className="w-full h-screen flex flex-col justify-center items-center text-sm md:text-xl ">
|
||||
{message === "Verifying SSO..." && (
|
||||
<img alt="loader" src={loader} className="w-[80px] h-[80px]" />
|
||||
)}
|
||||
<div className="text-[gray]">{message}</div>
|
||||
</div>
|
||||
<ModalUi isOpen={isModal} title="Additional Info" showClose={false}>
|
||||
<div className="relative">
|
||||
{isLoader && (
|
||||
<div className="absolute w-full h-full bg-black bg-opacity-25 flex justify-center items-center">
|
||||
<div
|
||||
style={{
|
||||
fontSize: "45px",
|
||||
color: "#3dd3e0"
|
||||
}}
|
||||
className="loader-37"
|
||||
></div>
|
||||
</div>
|
||||
)}
|
||||
<form className="px-4 py-3" onSubmit={handleSubmitbtn}>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="Company"
|
||||
style={{ display: "flex" }}
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Phone <span style={{ fontSize: 13, color: "red" }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
id="Phone"
|
||||
value={userDetails.phone}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
phone: e.target.value
|
||||
})
|
||||
}
|
||||
disabled={isLoader}
|
||||
required
|
||||
/>
|
||||
<p className="text-[10px] text-[red] ml-2">
|
||||
{!validateInput(userDetails.phone) && "Invalid phone value"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="Company"
|
||||
style={{ display: "flex" }}
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Company <span style={{ fontSize: 13, color: "red" }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
id="Company"
|
||||
value={userDetails.Company}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Company: e.target.value
|
||||
})
|
||||
}
|
||||
disabled={isLoader}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="JobTitle"
|
||||
style={{ display: "flex" }}
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Job Title
|
||||
<span style={{ fontSize: 13, color: "red" }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
id="JobTitle"
|
||||
value={userDetails.Destination}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Destination: e.target.value
|
||||
})
|
||||
}
|
||||
disabled={isLoader}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<button
|
||||
type="submit"
|
||||
className="px-3 py-1.5 text-white rounded shadow-md text-center focus:outline-none "
|
||||
style={{
|
||||
marginRight: 10,
|
||||
backgroundColor: modalSubmitBtnColor
|
||||
}}
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="py-1.5 text-black border-[1px] border-[#ccc] shadow-md rounded focus:outline-none"
|
||||
onClick={handleCloseModal}
|
||||
style={{ width: 75, backgroundColor: modalCancelBtnColor }}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SSOVerify;
|
||||
@@ -2,17 +2,20 @@ import React, { useState, useEffect } from "react";
|
||||
import Parse from "parse";
|
||||
import axios from "axios";
|
||||
import Title from "../components/Title";
|
||||
import { useNavigate, NavLink } from "react-router-dom";
|
||||
import { useNavigate, NavLink, useLocation } from "react-router-dom";
|
||||
import login_img from "../assets/images/login_img.svg";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
import Alert from "../primitives/Alert";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { fetchAppInfo } from "../redux/reducers/infoReducer";
|
||||
import { showTenant } from "../redux/reducers/ShowTenant";
|
||||
import { showTenant } from "../redux/reducers/ShowTenant";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import { getAppLogo, openInNewTab } from "../constant/Utils";
|
||||
const Signup = () => {
|
||||
const { width } = useWindowSize();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const dispatch = useDispatch();
|
||||
const [name, setName] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
@@ -20,6 +23,7 @@ const Signup = () => {
|
||||
const [email, setEmail] = useState("");
|
||||
const [company, setCompany] = useState("");
|
||||
const [jobTitle, setJobTitle] = useState("");
|
||||
const [image, setImage] = useState();
|
||||
const [state, setState] = useState({
|
||||
loading: false,
|
||||
alertType: "success",
|
||||
@@ -29,9 +33,8 @@ 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 image = appInfo.applogo;
|
||||
|
||||
const clearStorage = async () => {
|
||||
if (Parse.User.current()) {
|
||||
@@ -67,6 +70,19 @@ const Signup = () => {
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
};
|
||||
|
||||
const handleFreePlan = async (id) => {
|
||||
try {
|
||||
const params = { userId: id };
|
||||
const res = await Parse.Cloud.run("freesubscription", params);
|
||||
if (res.status === "error") {
|
||||
alert(res.result);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in free subscribe", err.message);
|
||||
alert("Somenthing went wrong, please try again later!");
|
||||
}
|
||||
};
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
if (lengthValid && caseDigitValid && specialCharValid) {
|
||||
@@ -110,7 +126,13 @@ const Signup = () => {
|
||||
params
|
||||
);
|
||||
if (usersignup) {
|
||||
handleNavigation(r.getSessionToken());
|
||||
const param = new URLSearchParams(location.search);
|
||||
const isFreeplan =
|
||||
param?.get("subscription") === "freeplan";
|
||||
if (isFreeplan) {
|
||||
await handleFreePlan(r.id);
|
||||
}
|
||||
handleNavigation(r.getSessionToken(), isFreeplan);
|
||||
}
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
@@ -155,7 +177,7 @@ const Signup = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleNavigation = async (sessionToken) => {
|
||||
const handleNavigation = async (sessionToken, isFreeplan = false) => {
|
||||
const baseUrl = localStorage.getItem("baseUrl");
|
||||
const parseAppId = localStorage.getItem("parseAppId");
|
||||
const res = await axios.get(baseUrl + "users/me", {
|
||||
@@ -259,9 +281,7 @@ const Signup = () => {
|
||||
});
|
||||
if (tenentInfo.length) {
|
||||
dispatch(
|
||||
showTenant(
|
||||
tenentInfo[0].tenentName || ""
|
||||
)
|
||||
showTenant(tenentInfo[0].tenentName || "")
|
||||
);
|
||||
localStorage.setItem(
|
||||
"TenantName",
|
||||
@@ -297,7 +317,7 @@ const Signup = () => {
|
||||
tenentName: x.TenantId.TenantName || ""
|
||||
};
|
||||
localStorage.setItem(
|
||||
"TenetId",
|
||||
"TenantId",
|
||||
x.TenantId.objectId
|
||||
);
|
||||
tenentInfo.push(obj);
|
||||
@@ -325,8 +345,14 @@ const Signup = () => {
|
||||
element.pageType
|
||||
);
|
||||
setState({ loading: false });
|
||||
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
if (isEnableSubscription) {
|
||||
if (isFreeplan) {
|
||||
navigate(
|
||||
`/${element.pageType}/${element.pageId}`
|
||||
);
|
||||
} else {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
}
|
||||
} else {
|
||||
alert("Registered user successfully");
|
||||
navigate(
|
||||
@@ -343,8 +369,14 @@ const Signup = () => {
|
||||
);
|
||||
localStorage.setItem("pageType", element.pageType);
|
||||
setState({ loading: false });
|
||||
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
if (isEnableSubscription) {
|
||||
if (isFreeplan) {
|
||||
navigate(
|
||||
`/${element.pageType}/${element.pageId}`
|
||||
);
|
||||
} else {
|
||||
navigate(`/subscription`, { replace: true });
|
||||
}
|
||||
} else {
|
||||
navigate(
|
||||
`/${element.pageType}/${element.pageId}`
|
||||
@@ -438,9 +470,23 @@ const Signup = () => {
|
||||
};
|
||||
useEffect(() => {
|
||||
dispatch(fetchAppInfo());
|
||||
saveLogo();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const saveLogo = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const logo = await getAppLogo();
|
||||
if (logo) {
|
||||
setImage(logo);
|
||||
} else {
|
||||
setImage(appInfo?.applogo || undefined);
|
||||
}
|
||||
} else {
|
||||
setImage(appInfo?.applogo || undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePasswordChange = (e) => {
|
||||
const newPassword = e.target.value;
|
||||
setPassword(newPassword);
|
||||
@@ -486,8 +532,10 @@ const Signup = () => {
|
||||
{appInfo && appInfo.applogo ? (
|
||||
<div>
|
||||
<div className="md:m-10 lg:m-16 md:p-4 lg:p-10 p-5 bg-[#ffffff] md:border-[1px] md:border-gray-400 ">
|
||||
<div className="w-[250px] h-[66px] inline-block">
|
||||
<img src={image} width="100%" alt="" />
|
||||
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
|
||||
{image && (
|
||||
<img src={image} className="object-contain h-full" alt="logo" />
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
|
||||
<div className="">
|
||||
@@ -514,9 +562,11 @@ const Signup = () => {
|
||||
<input
|
||||
id="email"
|
||||
type="text"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs lowercase"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onChange={(e) =>
|
||||
setEmail(e.target.value?.toLowerCase())
|
||||
}
|
||||
required
|
||||
/>
|
||||
<hr className="my-2 border-none" />
|
||||
@@ -610,10 +660,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">
|
||||
@@ -626,7 +700,7 @@ const Signup = () => {
|
||||
</button>
|
||||
<NavLink
|
||||
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
|
||||
to="/"
|
||||
to={location.search ? "/" + location.search : "/"}
|
||||
style={width < 768 ? { textAlign: "center" } : {}}
|
||||
>
|
||||
Login
|
||||
@@ -661,4 +735,4 @@ const Signup = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Signup
|
||||
export default Signup;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import "../styles/signature.css";
|
||||
import { themeColor } from "../constant/const";
|
||||
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";
|
||||
@@ -26,7 +28,13 @@ import {
|
||||
contactBook,
|
||||
randomId,
|
||||
getDate,
|
||||
textWidget
|
||||
textWidget,
|
||||
getTenantDetails,
|
||||
checkIsSubscribed,
|
||||
convertPdfArrayBuffer,
|
||||
fetchImageBase64,
|
||||
changeImageWH,
|
||||
handleSendOTP
|
||||
} from "../constant/Utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Tour from "reactour";
|
||||
@@ -39,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() {
|
||||
@@ -62,12 +71,10 @@ 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);
|
||||
const imageRef = useRef(null);
|
||||
const [documentStatus, setDocumentStatus] = useState({ isCompleted: false });
|
||||
const [myInitial, setMyInitial] = useState("");
|
||||
const [isInitial, setIsInitial] = useState(false);
|
||||
const [isUiLoading, setIsUiLoading] = useState(false);
|
||||
@@ -87,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
|
||||
});
|
||||
@@ -99,6 +107,14 @@ function SignYourSelf() {
|
||||
});
|
||||
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
|
||||
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({
|
||||
@@ -186,13 +202,23 @@ function SignYourSelf() {
|
||||
|
||||
if (documentData && documentData.length > 0) {
|
||||
setPdfDetails(documentData);
|
||||
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
|
||||
const url = documentData[0] && documentData[0]?.URL;
|
||||
if (url) {
|
||||
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
|
||||
const arrayBuffer = await convertPdfArrayBuffer(url);
|
||||
if (arrayBuffer === "Error") {
|
||||
setHandleError("Error: Something went wrong!");
|
||||
} else {
|
||||
setPdfArrayBuffer(arrayBuffer);
|
||||
}
|
||||
} else {
|
||||
setHandleError("Error: Something went wrong!");
|
||||
}
|
||||
|
||||
isCompleted = documentData[0].IsCompleted && documentData[0].IsCompleted;
|
||||
if (isCompleted) {
|
||||
const docStatus = {
|
||||
isCompleted: isCompleted
|
||||
};
|
||||
setDocumentStatus(docStatus);
|
||||
setIsCompleted(true);
|
||||
setPdfUrl(documentData[0].SignedUrl);
|
||||
const alreadySign = {
|
||||
status: true,
|
||||
@@ -204,8 +230,12 @@ function SignYourSelf() {
|
||||
setIsUiLoading(false);
|
||||
setIsSignPad(false);
|
||||
setIsEmail(true);
|
||||
setIsCelebration(true);
|
||||
setXyPostion([]);
|
||||
setSignBtnPosition([]);
|
||||
setTimeout(() => {
|
||||
setIsCelebration(false);
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
@@ -255,10 +285,15 @@ function SignYourSelf() {
|
||||
setHandleError("Error: Something went wrong!");
|
||||
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 =
|
||||
@@ -278,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) {
|
||||
@@ -517,89 +546,213 @@ 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."
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
//function for get digital signature
|
||||
const signPdfFun = async (base64Url, documentId) => {
|
||||
let isCustomCompletionMail = false;
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
const tenantDetails = await getTenantDetails(jsonSender.objectId);
|
||||
if (tenantDetails && tenantDetails === "user does not exist!") {
|
||||
alert("User does not exist");
|
||||
} else {
|
||||
if (
|
||||
tenantDetails?.CompletionBody &&
|
||||
tenantDetails?.CompletionSubject &&
|
||||
(!isEnableSubscription || getIsSubscribe)
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 url = pdfDetails[0] && pdfDetails[0].URL;
|
||||
|
||||
const existingPdfBytes = await fetch(url).then((res) =>
|
||||
res.arrayBuffer()
|
||||
);
|
||||
|
||||
// Load a PDFDocument from the existing PDF bytes
|
||||
const pdfDoc = await PDFDocument.load(existingPdfBytes, {
|
||||
ignoreEncryption: true
|
||||
});
|
||||
|
||||
const flag = 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);
|
||||
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);
|
||||
}
|
||||
//embed multi signature in pdf
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
xyPostion,
|
||||
pdfDoc,
|
||||
pdfOriginalWidth,
|
||||
flag,
|
||||
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) => {
|
||||
//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
|
||||
docId: documentId,
|
||||
isCustomCompletionMail: isCustomCompletionMail,
|
||||
mailProvider: activeMailAdapter,
|
||||
signature: suffixbase64
|
||||
};
|
||||
|
||||
await axios
|
||||
@@ -607,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) => {
|
||||
@@ -969,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
|
||||
@@ -985,8 +1156,9 @@ function SignYourSelf() {
|
||||
{/* this component used to render all pdf pages in left side */}
|
||||
|
||||
<RenderAllPdfPage
|
||||
pdfUrl={pdfUrl}
|
||||
signPdfUrl={pdfDetails[0] && pdfDetails[0].URL}
|
||||
signPdfUrl={
|
||||
pdfDetails[0] && (pdfDetails[0].SignedUrl || pdfDetails[0].URL)
|
||||
}
|
||||
allPages={allPages}
|
||||
setAllPages={setAllPages}
|
||||
setPageNumber={setPageNumber}
|
||||
@@ -1004,7 +1176,7 @@ function SignYourSelf() {
|
||||
<ModalUi
|
||||
headerColor={"#dc3545"}
|
||||
isOpen={isAlert.isShow}
|
||||
title={"Alert"}
|
||||
title={isAlert?.header || "Alert"}
|
||||
handleClose={() => {
|
||||
setIsAlert({
|
||||
isShow: false,
|
||||
@@ -1014,35 +1186,13 @@ 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>
|
||||
|
||||
{/* this modal is used show this document is already sign */}
|
||||
<ModalUi
|
||||
isOpen={showAlreadySignDoc.status}
|
||||
title={"Sign Documents"}
|
||||
title={"Document signed"}
|
||||
handleClose={() => {
|
||||
setShowAlreadySignDoc({ status: false });
|
||||
}}
|
||||
@@ -1114,12 +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
|
||||
@@ -1127,7 +1278,6 @@ function SignYourSelf() {
|
||||
allPages={allPages}
|
||||
changePage={changePage}
|
||||
pdfUrl={pdfUrl}
|
||||
documentStatus={documentStatus}
|
||||
embedWidgetsData={embedWidgetsData}
|
||||
pdfDetails={pdfDetails}
|
||||
isShowHeader={true}
|
||||
@@ -1135,6 +1285,7 @@ function SignYourSelf() {
|
||||
alreadySign={pdfUrl ? true : false}
|
||||
isSignYourself={true}
|
||||
setIsEmail={setIsEmail}
|
||||
isCompleted={isCompleted}
|
||||
/>
|
||||
|
||||
<div data-tut="reactourSecond">
|
||||
@@ -1187,7 +1338,7 @@ function SignYourSelf() {
|
||||
}}
|
||||
className="autoSignScroll"
|
||||
>
|
||||
{!documentStatus.isCompleted ? (
|
||||
{!isCompleted ? (
|
||||
<div>
|
||||
<WidgetComponent
|
||||
dataTut="reactourFirst"
|
||||
|
||||
@@ -3,7 +3,7 @@ import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import axios from "axios";
|
||||
import "../styles/signature.css";
|
||||
import { themeColor } from "../constant/const";
|
||||
import { isEnableSubscription, themeColor } from "../constant/const";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { useDrag, useDrop } from "react-dnd";
|
||||
@@ -23,7 +23,8 @@ import {
|
||||
defaultWidthHeight,
|
||||
addWidgetOptions,
|
||||
textInputWidget,
|
||||
radioButtonWidget
|
||||
radioButtonWidget,
|
||||
fetchSubscription
|
||||
} from "../constant/Utils";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import "../styles/AddUser.css";
|
||||
@@ -35,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();
|
||||
@@ -52,6 +54,7 @@ const TemplatePlaceholder = () => {
|
||||
const [isSelectListId, setIsSelectId] = useState();
|
||||
const [isSendAlert, setIsSendAlert] = useState(false);
|
||||
const [isCreateDocModal, setIsCreateDocModal] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState({
|
||||
isLoad: true,
|
||||
message: "This might take some time"
|
||||
@@ -143,6 +146,7 @@ const TemplatePlaceholder = () => {
|
||||
const [currWidgetsDetails, setCurrWidgetsDetails] = useState([]);
|
||||
const [isCheckbox, setIsCheckbox] = useState(false);
|
||||
const [widgetName, setWidgetName] = useState(false);
|
||||
const [isAddRole, setIsAddRole] = useState(false);
|
||||
const senderUser =
|
||||
localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
@@ -168,7 +172,23 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [divRef.current]);
|
||||
|
||||
async function checkIsSubscribed() {
|
||||
const res = await fetchSubscription();
|
||||
const freeplan = res.plan;
|
||||
const billingDate = res.billingDate;
|
||||
if (freeplan === "freeplan") {
|
||||
return true;
|
||||
} else if (billingDate) {
|
||||
if (new Date(billingDate) > new Date()) {
|
||||
setIsSubscribe(true);
|
||||
return true;
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
}
|
||||
} else {
|
||||
navigate(`/subscription`);
|
||||
}
|
||||
}
|
||||
// `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present
|
||||
const fetchTemplate = async () => {
|
||||
try {
|
||||
@@ -190,6 +210,9 @@ const TemplatePlaceholder = () => {
|
||||
: [];
|
||||
|
||||
if (documentData && documentData.length > 0) {
|
||||
if (isEnableSubscription) {
|
||||
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
|
||||
}
|
||||
setPdfDetails(documentData);
|
||||
setIsSigners(true);
|
||||
if (documentData[0].Signers && documentData[0].Signers.length > 0) {
|
||||
@@ -287,10 +310,10 @@ const TemplatePlaceholder = () => {
|
||||
if (tourstatus && tourstatus.length > 0) {
|
||||
setTourStatus(tourstatus);
|
||||
const checkTourRecipients = tourstatus.filter(
|
||||
(data) => data.templatetour
|
||||
(data) => data.templateTour
|
||||
);
|
||||
if (checkTourRecipients && checkTourRecipients.length > 0) {
|
||||
setCheckTourStatus(checkTourRecipients[0].templatetour);
|
||||
setCheckTourStatus(checkTourRecipients[0].templateTour);
|
||||
}
|
||||
}
|
||||
const loadObj = {
|
||||
@@ -460,9 +483,20 @@ const TemplatePlaceholder = () => {
|
||||
} else {
|
||||
setIsReceipent(false);
|
||||
}
|
||||
} else {
|
||||
setIsAddRole(true);
|
||||
}
|
||||
};
|
||||
|
||||
const tourAddRole = [
|
||||
{
|
||||
selector: '[data-tut="reactourAddbtn"]',
|
||||
content: "You need to add a role before you can add fields for it. ",
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
|
||||
//function for get pdf page details
|
||||
const pageDetails = async (pdf) => {
|
||||
const load = {
|
||||
@@ -671,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 {
|
||||
@@ -741,7 +769,7 @@ const TemplatePlaceholder = () => {
|
||||
selector: '[data-tut="reactourSecond"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={`Drag a widget placeholder onto the PDF to choose your desired signing location.`}
|
||||
message={`Drag or click on a field to add it to the document.`}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
@@ -763,7 +791,7 @@ const TemplatePlaceholder = () => {
|
||||
selector: '[data-tut="reactourFour"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={`Clicking "Save" button will save the template and will ask you if you want to create a new document using this template.`}
|
||||
message={`Clicking ‘Save’ will store the current template. After saving, you’ll be prompted to create a new document from this template if you wish.`}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
@@ -781,15 +809,15 @@ const TemplatePlaceholder = () => {
|
||||
if (tourStatus.length > 0) {
|
||||
updatedTourStatus = [...tourStatus];
|
||||
const templatetourIndex = tourStatus.findIndex(
|
||||
(obj) => obj["templatetour"] === false || obj["templatetour"] === true
|
||||
(obj) => obj["templateTour"] === false || obj["templateTour"] === true
|
||||
);
|
||||
if (templatetourIndex !== -1) {
|
||||
updatedTourStatus[templatetourIndex] = { templatetour: true };
|
||||
updatedTourStatus[templatetourIndex] = { templateTour: true };
|
||||
} else {
|
||||
updatedTourStatus.push({ templatetour: true });
|
||||
updatedTourStatus.push({ templateTour: true });
|
||||
}
|
||||
} else {
|
||||
updatedTourStatus = [{ templatetour: true }];
|
||||
updatedTourStatus = [{ templateTour: true }];
|
||||
}
|
||||
await axios
|
||||
.put(
|
||||
@@ -1099,8 +1127,6 @@ const TemplatePlaceholder = () => {
|
||||
inputype = options.includes(defaultdata.textvalidate)
|
||||
? defaultdata.textvalidate
|
||||
: "regex";
|
||||
} else {
|
||||
inputype = "text";
|
||||
}
|
||||
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||
@@ -1125,11 +1151,14 @@ const TemplatePlaceholder = () => {
|
||||
status: defaultdata?.status || "required",
|
||||
hint: defaultdata?.hint || "",
|
||||
defaultValue: defaultdata?.defaultValue || "",
|
||||
validation: {
|
||||
type: inputype,
|
||||
pattern:
|
||||
inputype === "regex" ? defaultdata.textvalidate : ""
|
||||
}
|
||||
validation:
|
||||
(isSubscribe || !isEnableSubscription) && inputype
|
||||
? {
|
||||
type: inputype,
|
||||
pattern:
|
||||
inputype === "regex" ? defaultdata.textvalidate : ""
|
||||
}
|
||||
: {}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
@@ -1197,6 +1226,15 @@ const TemplatePlaceholder = () => {
|
||||
closeWithMask={false}
|
||||
/>
|
||||
)}
|
||||
{isAddRole && (
|
||||
<Tour
|
||||
onRequestClose={() => setIsAddRole(false)}
|
||||
steps={tourAddRole}
|
||||
isOpen={isAddRole}
|
||||
rounded={5}
|
||||
closeWithMask={false}
|
||||
/>
|
||||
)}
|
||||
{/* this component used to render all pdf pages in left side */}
|
||||
<RenderAllPdfPage
|
||||
signPdfUrl={pdfDetails[0].URL}
|
||||
@@ -1222,7 +1260,7 @@ const TemplatePlaceholder = () => {
|
||||
handleClose={() => setIsSendAlert(false)}
|
||||
>
|
||||
<div style={{ height: "100%", padding: 20 }}>
|
||||
<p>Please add field for all recipients.</p>
|
||||
<p>Please add at least one signature field for all roles.</p>
|
||||
</div>
|
||||
</ModalUi>
|
||||
<ModalUi
|
||||
@@ -1323,6 +1361,7 @@ const TemplatePlaceholder = () => {
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleClose={handleNameModal}
|
||||
isSubscribe={isSubscribe}
|
||||
/>
|
||||
<DropdownWidgetOption
|
||||
type="checkbox"
|
||||
@@ -1333,6 +1372,7 @@ const TemplatePlaceholder = () => {
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleClose={handleNameModal}
|
||||
isSubscribe={isSubscribe}
|
||||
/>
|
||||
<DropdownWidgetOption
|
||||
type="dropdown"
|
||||
@@ -1343,6 +1383,7 @@ const TemplatePlaceholder = () => {
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleClose={handleNameModal}
|
||||
isSubscribe={isSubscribe}
|
||||
/>
|
||||
<PlaceholderCopy
|
||||
isPageCopy={isPageCopy}
|
||||
@@ -1524,6 +1565,7 @@ const TemplatePlaceholder = () => {
|
||||
isAddUser={isAddUser}
|
||||
uniqueId={uniqueId}
|
||||
closePopup={closePopup}
|
||||
signersData={signersdata}
|
||||
/>
|
||||
</div>
|
||||
<ModalUi
|
||||
@@ -1542,6 +1584,7 @@ const TemplatePlaceholder = () => {
|
||||
isOpen={isNameModal}
|
||||
handleClose={handleNameModal}
|
||||
handleData={handleWidgetdefaultdata}
|
||||
isSubscribe={isSubscribe}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect
|
||||
// useRef
|
||||
} from "react";
|
||||
import { Navigate, useNavigate } from "react-router-dom";
|
||||
import Parse from "parse";
|
||||
import { SaveFileSize } from "../constant/saveFileSize";
|
||||
@@ -8,10 +12,15 @@ import sanitizeFileName from "../primitives/sanitizeFileName";
|
||||
import axios from "axios";
|
||||
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
|
||||
import Tooltip from "../primitives/Tooltip";
|
||||
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();
|
||||
let UserProfile = JSON.parse(localStorage.getItem("UserInformation"));
|
||||
let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||
const [editmode, setEditMode] = useState(false);
|
||||
@@ -21,54 +30,125 @@ function UserProfile() {
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [percentage, setpercentage] = useState(0);
|
||||
const [isDisableDocId, setIsDisableDocId] = useState(false);
|
||||
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
// const [publicUserName, setPublicUserName] = useState(
|
||||
// extendUser && extendUser?.[0]?.UserName
|
||||
// );
|
||||
// const previousPublicUserName = useRef(publicUserName);
|
||||
const [company, setCompany] = useState(
|
||||
extendUser && extendUser?.[0]?.Company
|
||||
);
|
||||
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);
|
||||
// const [userNameError, setUserNameError] = useState("");
|
||||
useEffect(() => {
|
||||
getUserDetail();
|
||||
}, []);
|
||||
|
||||
const getUserDetail = async () => {
|
||||
setIsLoader(true);
|
||||
const extClass = localStorage.getItem("Extand_Class");
|
||||
const jsonSender = JSON.parse(extClass);
|
||||
const HeaderDocId = jsonSender[0]?.HeaderDocId;
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
}
|
||||
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!");
|
||||
}
|
||||
}
|
||||
};
|
||||
//function to check public username already exist
|
||||
// const handleCheckPublicUserName = async () => {
|
||||
// try {
|
||||
// const res = await Parse.Cloud.run("getpublicusername", {
|
||||
// username: publicUserName
|
||||
// });
|
||||
// if (res) {
|
||||
// setIsLoader(false);
|
||||
// setUserNameError("user name already exist");
|
||||
// setTimeout(() => {
|
||||
// setUserNameError("");
|
||||
// }, 3000);
|
||||
// return res;
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.log("error in getpublicusername cloud function");
|
||||
// }
|
||||
// };
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoader(true);
|
||||
let phn = Phone;
|
||||
|
||||
try {
|
||||
const tour = Parse.Object.extend("_User");
|
||||
const query = new Parse.Query(tour);
|
||||
|
||||
await query.get(UserProfile.objectId).then((object) => {
|
||||
object.set("name", name);
|
||||
object.set("ProfilePic", Image);
|
||||
object.set("phone", phn);
|
||||
|
||||
object.save().then(
|
||||
async (response) => {
|
||||
if (response) {
|
||||
let res = response.toJSON();
|
||||
let rr = JSON.stringify(res);
|
||||
localStorage.setItem("UserInformation", rr);
|
||||
SetName(res.name);
|
||||
SetPhone(res.phone);
|
||||
setImage(res.ProfilePic);
|
||||
localStorage.setItem("username", res.name);
|
||||
await updateExtUser({ Name: res.name, Phone: res.phone });
|
||||
alert("Profile updated successfully.");
|
||||
setEditMode(false);
|
||||
navigate("/dashboard/35KBoSgoAK");
|
||||
let phn = Phone,
|
||||
res = "";
|
||||
//condition to call cloud function when user change publicUserName
|
||||
// if (previousPublicUserName.current !== publicUserName) {
|
||||
// res = await handleCheckPublicUserName();
|
||||
// }
|
||||
if (!res) {
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const userQuery = Parse.Object.extend("_User");
|
||||
const query = new Parse.Query(userQuery);
|
||||
await query.get(UserProfile.objectId).then((object) => {
|
||||
object.set("name", name);
|
||||
object.set("ProfilePic", Image);
|
||||
object.set("phone", phn || "");
|
||||
object.save().then(
|
||||
async (response) => {
|
||||
if (response) {
|
||||
let res = response.toJSON();
|
||||
let rr = JSON.stringify(res);
|
||||
localStorage.setItem("UserInformation", rr);
|
||||
SetName(res.name);
|
||||
SetPhone(res?.phone || "");
|
||||
setImage(res.ProfilePic);
|
||||
localStorage.setItem("username", res.name);
|
||||
localStorage.setItem("profileImg", res.ProfilePic);
|
||||
await updateExtUser({
|
||||
Name: res.name,
|
||||
Phone: res?.phone || ""
|
||||
});
|
||||
alert("Profile updated successfully.");
|
||||
setEditMode(false);
|
||||
setIsLoader(false);
|
||||
//navigate("/dashboard/35KBoSgoAK");
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
alert("Something went wrong.");
|
||||
console.error("Error while updating tour", error);
|
||||
setIsLoader(false);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
alert("Something went wrong.");
|
||||
console.error("Error while updating tour", error);
|
||||
setIsLoader(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("err", error);
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("err", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,12 +157,15 @@ function UserProfile() {
|
||||
const extClass = localStorage.getItem("extended_class");
|
||||
const extData = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
const ExtUserId = extData[0].objectId;
|
||||
|
||||
const body = {
|
||||
Phone: obj.Phone,
|
||||
Phone: obj?.Phone || "",
|
||||
Name: obj.Name,
|
||||
HeaderDocId: isDisableDocId
|
||||
HeaderDocId: isDisableDocId,
|
||||
JobTitle: jobTitle,
|
||||
Company: company
|
||||
// UserName: publicUserName || ""
|
||||
};
|
||||
|
||||
await axios.put(
|
||||
parseBaseUrl + "classes/" + extClass + "/" + ExtUserId,
|
||||
body,
|
||||
@@ -100,7 +183,6 @@ function UserProfile() {
|
||||
|
||||
const json = JSON.parse(JSON.stringify([res]));
|
||||
const extRes = JSON.stringify(json);
|
||||
|
||||
localStorage.setItem("Extand_Class", extRes);
|
||||
// console.log("updateRes ", updateRes);
|
||||
};
|
||||
@@ -135,9 +217,9 @@ function UserProfile() {
|
||||
// console.log("File URL:", response.url());
|
||||
if (response.url()) {
|
||||
setImage(response.url());
|
||||
localStorage.setItem("profileImg", response.url());
|
||||
setpercentage(0);
|
||||
SaveFileSize(size, response.url());
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, response.url(), tenantId);
|
||||
return response.url();
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -155,7 +237,66 @@ 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");
|
||||
};
|
||||
//function to handle onchange username and restrict 6-characters username for free users
|
||||
// const handleOnchangeUserName = (e) => {
|
||||
// const value = e.target.value;
|
||||
// if (value.length > 6 && !isSubscribe) {
|
||||
// setUserNameError("Please upgrade to allow more than 6 characters.");
|
||||
// setTimeout(() => {
|
||||
// setUserNameError("");
|
||||
// }, 2000);
|
||||
// } else {
|
||||
// setPublicUserName(e.target.value);
|
||||
// }
|
||||
// };
|
||||
const handleCancel = () => {
|
||||
setEditMode(false);
|
||||
SetName(localStorage.getItem("username"));
|
||||
SetPhone(UserProfile && UserProfile.phone);
|
||||
setImage(localStorage.getItem("profileImg"));
|
||||
// setPublicUserName(extendUser && extendUser?.[0]?.UserName);
|
||||
setCompany(extendUser && extendUser?.[0]?.Company);
|
||||
setJobTitle(extendUser?.[0]?.JobTitle);
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title title={"Profile"} />
|
||||
@@ -177,7 +318,14 @@ function UserProfile() {
|
||||
></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center w-full">
|
||||
<div className="flex justify-center items-center w-full relative">
|
||||
{/* {userNameError && (
|
||||
<div
|
||||
className={`z-[1000] fixed top-[50%] transform border-[1px] text-sm border-[#f0a8a8] bg-[#f4bebe] text-[#c42121] rounded py-[.75rem] px-[1.25rem]`}
|
||||
>
|
||||
{userNameError}
|
||||
</div>
|
||||
)} */}
|
||||
<div className="bg-white flex flex-col justify-center shadow rounded w-[450px]">
|
||||
<div className="flex flex-col justify-center items-center my-4">
|
||||
<div className="w-[200px] h-[200px] overflow-hidden rounded-full">
|
||||
@@ -252,24 +400,115 @@ function UserProfile() {
|
||||
<span className="font-semibold">Email:</span>{" "}
|
||||
<span>{UserProfile && UserProfile.email}</span>
|
||||
</li>
|
||||
<li
|
||||
className={`flex justify-between items-center border-t-[1px] border-gray-300 break-all ${
|
||||
editmode ? "py-1" : "py-2"
|
||||
}`}
|
||||
>
|
||||
<span className="font-semibold">Company:</span>{" "}
|
||||
{editmode ? (
|
||||
<input
|
||||
type="text"
|
||||
value={company}
|
||||
className="py-1 px-2 text-sm border-[1px] border-[#15b4e9] text-black rounded"
|
||||
onChange={(e) => setCompany(e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
<span>{extendUser?.[0].Company}</span>
|
||||
)}
|
||||
</li>
|
||||
<li
|
||||
className={`flex justify-between items-center border-t-[1px] border-gray-300 break-all ${
|
||||
editmode ? "py-1" : "py-2"
|
||||
}`}
|
||||
>
|
||||
<span className="font-semibold">Job title:</span>{" "}
|
||||
{editmode ? (
|
||||
<input
|
||||
type="text"
|
||||
value={jobTitle}
|
||||
className="py-1 px-2 text-sm border-[1px] border-[#15b4e9] text-black rounded"
|
||||
onChange={(e) => setJobTitle(e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
<span>{extendUser?.[0]?.JobTitle}</span>
|
||||
)}
|
||||
</li>
|
||||
<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>
|
||||
{/* {isEnableSubscription && (
|
||||
<li className="flex justify-between items-center border-t-[1px] border-gray-300 py-2 break-all">
|
||||
<span className="font-semibold">
|
||||
Public profile :{" "}
|
||||
<Tooltip
|
||||
message={`this is your public URL. Copy or share it
|
||||
with the signer, and you will be able to see
|
||||
all your publicly set templates.`}
|
||||
/>
|
||||
</span>
|
||||
<div className="flex items-center">
|
||||
<span>opensign-me.vercel.app/</span>
|
||||
{editmode ? (
|
||||
<input
|
||||
onChange={handleOnchangeUserName}
|
||||
value={publicUserName}
|
||||
disabled={!editmode}
|
||||
placeholder="enter user name"
|
||||
className="border-[1px] border-gray-200 rounded-[3px]"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
value={extendUser?.[0]?.UserName}
|
||||
disabled
|
||||
placeholder="enter user name"
|
||||
className="border-[1px] border-gray-200 rounded-[3px]"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
)} */}
|
||||
<li className="border-y-[1px] border-gray-300 break-all">
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span className="font-semibold">
|
||||
<span
|
||||
className={
|
||||
isSubscribe || !isEnableSubscription
|
||||
? "font-semibold"
|
||||
: "font-semibold text-gray-300"
|
||||
}
|
||||
>
|
||||
Disable DocumentId :{" "}
|
||||
<Tooltip url={"https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid"} />{" "}
|
||||
<Tooltip
|
||||
url={
|
||||
"https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid"
|
||||
}
|
||||
/>{" "}
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</span>{" "}
|
||||
<label
|
||||
className={`${
|
||||
editmode ? "cursor-pointer" : ""
|
||||
} relative inline-flex items-center mb-0`}
|
||||
className={
|
||||
isSubscribe || !isEnableSubscription
|
||||
? `${
|
||||
editmode ? "cursor-pointer" : ""
|
||||
} relative inline-flex items-center mb-0`
|
||||
: "relative inline-flex items-center mb-0 pointer-events-none opacity-50"
|
||||
}
|
||||
>
|
||||
<input
|
||||
disabled={editmode ? false : true}
|
||||
@@ -282,11 +521,13 @@ function UserProfile() {
|
||||
<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"></div>
|
||||
</label>
|
||||
</div>
|
||||
<PremiumAlertHeader
|
||||
message={
|
||||
"Disable documentId is free in beta, this feature will incur a fee later."
|
||||
}
|
||||
/>
|
||||
{!isEnableSubscription && (
|
||||
<PremiumAlertHeader
|
||||
message={
|
||||
"Disable documentId is free in beta, this feature will incur a fee later."
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
<div className="flex justify-center pt-2 pb-3 md:pt-3 md:pb-4">
|
||||
@@ -313,7 +554,7 @@ function UserProfile() {
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (editmode) {
|
||||
setEditMode(false);
|
||||
handleCancel();
|
||||
} else {
|
||||
navigate("/changepassword");
|
||||
}
|
||||
@@ -326,6 +567,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>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Title from "../components/Title";
|
||||
import axios from "axios";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Alert from "../primitives/Alert";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { rejectBtn, submitBtn } from "../constant/const";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
|
||||
import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
|
||||
import Parse from "parse";
|
||||
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
|
||||
import Tooltip from "../primitives/Tooltip";
|
||||
|
||||
function Webhook() {
|
||||
const navigation = useNavigate();
|
||||
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||
const [webhook, setWebhook] = useState();
|
||||
@@ -17,6 +19,8 @@ function Webhook() {
|
||||
const [isGenerate, setIsGenerate] = useState(false);
|
||||
const [isErr, setIsErr] = useState(false);
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
fetchWebhook();
|
||||
@@ -25,6 +29,10 @@ function Webhook() {
|
||||
|
||||
const fetchWebhook = async () => {
|
||||
const email = Parse.User.current().getEmail();
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
}
|
||||
const params = { email: email };
|
||||
try {
|
||||
const extRes = await Parse.Cloud.run("getUserDetails", params);
|
||||
@@ -40,41 +48,48 @@ function Webhook() {
|
||||
};
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoader(true);
|
||||
setIsModal(false);
|
||||
try {
|
||||
const params = { url: webhook };
|
||||
const url = parseBaseUrl + "functions/savewebhook";
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
await axios.post(url, params, { headers: headers }).then((res) => {
|
||||
if (res.data && res.data.result && res.data.result.Webhook) {
|
||||
setWebhook(res.data.result.Webhook);
|
||||
setIsGenerate(true);
|
||||
setTimeout(() => {
|
||||
setIsGenerate(false);
|
||||
}, 1500);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
console.error("Error while generating webhook");
|
||||
setIsLoader(false);
|
||||
setIsErr(true);
|
||||
setTimeout(() => {
|
||||
setIsErr(false);
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
setIsLoader(false);
|
||||
setIsErr(true);
|
||||
setTimeout(() => {
|
||||
setIsErr(false);
|
||||
}, 1500);
|
||||
if (webhook && webhook.startsWith("https://")) {
|
||||
setIsLoader(true);
|
||||
setIsModal(false);
|
||||
try {
|
||||
const params = { url: webhook };
|
||||
const url = parseBaseUrl + "functions/savewebhook";
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
await axios.post(url, params, { headers: headers }).then((res) => {
|
||||
if (res.data && res.data.result && res.data.result.Webhook) {
|
||||
setWebhook(res.data.result.Webhook);
|
||||
setIsGenerate(true);
|
||||
setTimeout(() => {
|
||||
setIsGenerate(false);
|
||||
}, 1500);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
console.error("Error while generating webhook");
|
||||
setIsLoader(false);
|
||||
setIsErr(true);
|
||||
setTimeout(() => {
|
||||
setIsErr(false);
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
setIsLoader(false);
|
||||
setIsErr(true);
|
||||
setTimeout(() => {
|
||||
setIsErr(false);
|
||||
}, 1500);
|
||||
|
||||
console.log("err", error);
|
||||
console.log("err", error);
|
||||
}
|
||||
} else {
|
||||
setError("The webhook url should always start with https://");
|
||||
setTimeout(() => {
|
||||
setError("");
|
||||
}, 1500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,6 +99,7 @@ function Webhook() {
|
||||
<Title title={"Webhook"} />
|
||||
{isGenerate && <Alert type="success">Webhook added successfully!</Alert>}
|
||||
{isErr && <Alert type="danger">Something went wrong!</Alert>}
|
||||
|
||||
{isLoader ? (
|
||||
<div
|
||||
style={{
|
||||
@@ -103,31 +119,41 @@ function Webhook() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white flex flex-col justify-center shadow rounded">
|
||||
<PremiumAlertHeader />
|
||||
<h1 className="ml-4 mt-3 mb-2 font-semibold">
|
||||
Webhook{" "}
|
||||
{!isEnableSubscription && <PremiumAlertHeader />}
|
||||
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
|
||||
OpenSign™ Webhook{" "}
|
||||
<Tooltip
|
||||
url={"https://docs.opensignlabs.com/docs/API-docs/get-webhook"}
|
||||
isSubscribe={true}
|
||||
/>
|
||||
</h1>
|
||||
<ul className="w-full flex flex-col p-2 text-sm">
|
||||
<ul
|
||||
className={
|
||||
isSubscribe || !isEnableSubscription
|
||||
? "w-full flex flex-col p-2 text-sm "
|
||||
: "w-full flex flex-col p-2 text-sm opacity-20 pointer-events-none select-none"
|
||||
}
|
||||
>
|
||||
<li
|
||||
className={`flex justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
|
||||
className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
|
||||
>
|
||||
<span className="w-[40%]">Webhook:</span>{" "}
|
||||
<span id="token" className="w-[60%] md:text-end cursor-pointer">
|
||||
{webhook && webhook}
|
||||
</span>
|
||||
<div className="w-[70%] flex-col md:flex-row flex items-center gap-5 ">
|
||||
<span className="">Webhook:</span>{" "}
|
||||
<span id="token" className=" md:text-end cursor-pointer">
|
||||
{webhook ? webhook : "_____"}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleModal}
|
||||
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"
|
||||
>
|
||||
{webhook ? "Update Webhook" : "Add Webhook"}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="flex flex-col md:flex-row items-center justify-center gap-2 pb-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleModal}
|
||||
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"
|
||||
>
|
||||
{webhook ? "Update Webhook" : "Add Webhook"}
|
||||
</button>
|
||||
|
||||
<div className="flex items-center justify-center ">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
@@ -135,17 +161,47 @@ function Webhook() {
|
||||
"https://docs.opensignlabs.com/docs/API-docs/save-update-webhook"
|
||||
)
|
||||
}
|
||||
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
|
||||
className="rounded hover:bg-[#15b4e9] border-[1px] my-2 border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
|
||||
>
|
||||
View Docs
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!isSubscribe && isEnableSubscription && (
|
||||
<>
|
||||
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
|
||||
Upgrade to PRO Plan
|
||||
</h1>
|
||||
<ul className={"w-full flex flex-col p-2 text-sm "}>
|
||||
<li
|
||||
className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
|
||||
>
|
||||
<div className="w-[70%] flex-col md:flex-row flex items-center gap-3 ">
|
||||
<span className="">$29.99/month:</span>{" "}
|
||||
<span id="token" className=" md:text-end cursor-pointer">
|
||||
First 100 documents included, then just $0.15 per
|
||||
document.
|
||||
</span>
|
||||
</div>
|
||||
{!isSubscribe && isEnableSubscription && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigation("/subscription")}
|
||||
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
|
||||
>
|
||||
Upgrade Now
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isModal}
|
||||
title={"Regenerate Token"}
|
||||
handleClose={handleModal}
|
||||
>
|
||||
{error && <Alert type="danger">{error}</Alert>}
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
<label className="text-sm ml-2">Webhook</label>
|
||||
|
||||
+14
-12
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
|
||||
import Parse from "parse";
|
||||
import axios from "axios";
|
||||
import "../styles/AddUser.css";
|
||||
const AddUser = (props) => {
|
||||
const AddContact = (props) => {
|
||||
const [name, setName] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
@@ -21,7 +21,7 @@ const AddUser = (props) => {
|
||||
);
|
||||
if (savedUserDetails && addYourself) {
|
||||
setName(savedUserDetails.name);
|
||||
setPhone(savedUserDetails.phone);
|
||||
setPhone(savedUserDetails?.phone || "");
|
||||
setEmail(savedUserDetails.email);
|
||||
}
|
||||
}, [addYourself]);
|
||||
@@ -50,15 +50,17 @@ const AddUser = (props) => {
|
||||
try {
|
||||
const contactQuery = new Parse.Object("contracts_Contactbook");
|
||||
contactQuery.set("Name", name);
|
||||
contactQuery.set("Phone", phone);
|
||||
if (phone) {
|
||||
contactQuery.set("Phone", phone);
|
||||
}
|
||||
contactQuery.set("Email", email);
|
||||
contactQuery.set("UserRole", "contracts_Guest");
|
||||
|
||||
if (localStorage.getItem("TenetId")) {
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
contactQuery.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenetId")
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,8 +70,10 @@ const AddUser = (props) => {
|
||||
_user.set("name", name);
|
||||
_user.set("username", email);
|
||||
_user.set("email", email);
|
||||
_user.set("phone", phone);
|
||||
_user.set("password", phone);
|
||||
_user.set("password", email);
|
||||
if (phone) {
|
||||
_user.set("phone", phone);
|
||||
}
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
@@ -245,23 +249,21 @@ const AddUser = (props) => {
|
||||
type="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onChange={(e) => setEmail(e.target.value?.toLowerCase())}
|
||||
required
|
||||
disabled={addYourself}
|
||||
className="addUserInput"
|
||||
className="addUserInput lowercase"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-section">
|
||||
<label htmlFor="phone" style={{ fontSize: 13 }}>
|
||||
Phone
|
||||
<span style={{ color: "red", fontSize: 13 }}> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="phone"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
required
|
||||
disabled={addYourself}
|
||||
className="addUserInput"
|
||||
/>
|
||||
@@ -285,4 +287,4 @@ const AddUser = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AddUser;
|
||||
export default AddContact;
|
||||
@@ -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>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,22 @@
|
||||
import React from "react";
|
||||
import SelectSigners from "../components/shared/fields/SelectSigners";
|
||||
import AddUser from "./AddUser";
|
||||
import AddContact from "./AddContact";
|
||||
import ModalUi from "./ModalUi";
|
||||
|
||||
const LinkUserModal = (props) => {
|
||||
return (
|
||||
<ModalUi
|
||||
title={"Add/Choose Signer"}
|
||||
isOpen={props.isAddUser[props.uniqueId]}
|
||||
isOpen={
|
||||
props?.isAddSigner ||
|
||||
(props?.isAddUser && props?.isAddUser[props?.uniqueId])
|
||||
}
|
||||
handleClose={props.closePopup}
|
||||
>
|
||||
<SelectSigners
|
||||
details={props.handleAddUser}
|
||||
closePopup={props.closePopup}
|
||||
signersData={props?.signersData}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
@@ -39,7 +43,7 @@ const LinkUserModal = (props) => {
|
||||
}}
|
||||
></span>
|
||||
</div>
|
||||
<AddUser details={props.handleAddUser} closePopup={props.closePopup} />
|
||||
<AddContact details={props.handleAddUser} closePopup={props.closePopup} />
|
||||
</ModalUi>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React from "react";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import PremiumAlertHeader from "./PremiumAlertHeader";
|
||||
|
||||
const ModalUi = ({
|
||||
children,
|
||||
@@ -8,18 +10,17 @@ const ModalUi = ({
|
||||
handleClose,
|
||||
showHeader = true,
|
||||
showClose = true,
|
||||
styleClass
|
||||
reduceWidth,
|
||||
showHeaderMessage
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{isOpen && (
|
||||
<div className="fixed z-[999] top-0 left-0 w-[100%] h-[100%] bg-black bg-opacity-[75%]">
|
||||
<div
|
||||
className={
|
||||
styleClass
|
||||
? styleClass
|
||||
: "fixed z-[1000] top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-sm bg-white rounded shadow-md max-h-90 min-w-[90%] md:min-w-[500px] overflow-y-auto hide-scrollbar"
|
||||
}
|
||||
className={`${
|
||||
reduceWidth || "md:min-w-[500px]"
|
||||
} fixed z-[1000] top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-sm bg-white rounded shadow-md max-h-90 min-w-[90%] overflow-y-auto hide-scrollbar `}
|
||||
>
|
||||
{showHeader && (
|
||||
<div
|
||||
@@ -37,6 +38,13 @@ const ModalUi = ({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isEnableSubscription && showHeaderMessage && (
|
||||
<PremiumAlertHeader
|
||||
message={
|
||||
"Cutomize Email is free in beta, this feature will incur a fee later."
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,19 +8,11 @@ function CustomModal({
|
||||
bodyMssg,
|
||||
footerMessage,
|
||||
declineDoc,
|
||||
setIsDecline,
|
||||
containerWH
|
||||
setIsDecline
|
||||
}) {
|
||||
const isMobile = window.innerWidth < 767;
|
||||
return (
|
||||
show && (
|
||||
<div
|
||||
className="bg-black bg-opacity-[75%] absolute z-[999] flex flex-col items-center justify-center"
|
||||
style={{
|
||||
width: containerWH && containerWH.width,
|
||||
height: isMobile ? "100%" : containerWH && containerWH.height
|
||||
}}
|
||||
>
|
||||
<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 className="bg-[#de4337] text-white p-[10px] rounded-t">
|
||||
{headMsg && headMsg}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
const Tooltip = ({ id, message, url, iconColor }) =>
|
||||
url ? (
|
||||
<button onClick={() => openInNewTab(url)} className="text-center">
|
||||
<button onClick={() => openInNewTab(url)} className={"text-center"}>
|
||||
<sup>
|
||||
<i
|
||||
className="fa-solid fa-question rounded-full"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
|
||||
function Upgrade({ message }) {
|
||||
return (
|
||||
<sup>
|
||||
<span
|
||||
onClick={() => {
|
||||
const url = window.location.origin + "/subscription";
|
||||
openInNewTab(url);
|
||||
}}
|
||||
className="text-blue-800 text-sm cursor-pointer underline"
|
||||
>
|
||||
{message ? message : "Upgrade now"}
|
||||
</span>
|
||||
</sup>
|
||||
);
|
||||
}
|
||||
|
||||
export default Upgrade;
|
||||
@@ -9,9 +9,15 @@ const Validate = () => {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const userDetails = JSON.parse(
|
||||
localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
)
|
||||
);
|
||||
|
||||
// Use the session token to validate the user
|
||||
const userQuery = new Parse.Query(Parse.User);
|
||||
const user = await userQuery.get(Parse.User.current().id, {
|
||||
const user = await userQuery.get(userDetails?.objectId, {
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
});
|
||||
if (user) {
|
||||
@@ -24,7 +30,10 @@ const Validate = () => {
|
||||
setIsUserValid(false);
|
||||
}
|
||||
})();
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleLoginBtn = () => {
|
||||
try {
|
||||
Parse.User.logOut();
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
.loginInput {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
font-size: 0.75rem;
|
||||
border: 1px solid #e2dddd;
|
||||
background-color: #e0e5f5;
|
||||
color: black;
|
||||
padding-left: 1rem !important;
|
||||
padding-right: 1rem !important;
|
||||
padding-top: 0.5rem !important;
|
||||
font-family: inherit;
|
||||
font-feature-settings: inherit;
|
||||
font-variation-settings: inherit;
|
||||
font-weight: inherit;
|
||||
overflow: visible;
|
||||
}
|
||||
.loginInput:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
.verifyBtn {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
|
||||
padding: 3px 30px;
|
||||
color: white;
|
||||
font-weight: 500 !important;
|
||||
font-size: 14px !important;
|
||||
border: none;
|
||||
}
|
||||
.verifyBtn:focus {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.verifyBtn:hover {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18);
|
||||
color: white;
|
||||
}
|
||||
.fakeimg {
|
||||
height: 200px;
|
||||
background: #aaa;
|
||||
}
|
||||
|
||||
.GTRY {
|
||||
border: 1px solid #dee2e6;
|
||||
margin-top: 75px;
|
||||
padding-bottom: 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.main_head {
|
||||
width: 100%;
|
||||
height: 66px;
|
||||
background-color: #fff;
|
||||
margin-bottom: 52px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.main-logo {
|
||||
width: 250px;
|
||||
height: 66px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.card-box {
|
||||
background: #fff;
|
||||
min-height: 50px;
|
||||
box-shadow: 0 20px 20px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
transition: 0.5s;
|
||||
border: 1px solid #f2f2f2;
|
||||
border-radius: 7px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.LKJH {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.GTR {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.JUI {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
color: #fff;
|
||||
background-color: #15b4e9;
|
||||
border-color: #15b4e9;
|
||||
}
|
||||
|
||||
.btn-info:hover {
|
||||
color: #fff;
|
||||
background-color: #15b4e9;
|
||||
border-color: #15b4e9;
|
||||
}
|
||||
|
||||
.btn-reg {
|
||||
color: #15b4e9;
|
||||
background-color: #ffffff;
|
||||
border-color: #15b4e9;
|
||||
}
|
||||
|
||||
.btn-reg:hover {
|
||||
color: #15b4e9;
|
||||
background-color: #ffffff;
|
||||
border-color: #15b4e9;
|
||||
}
|
||||
|
||||
.SYTU {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.MLKI {
|
||||
float: right;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100% !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 667px) {
|
||||
.MLKI {
|
||||
float: inherit;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 210px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.MLKI a {
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.KNLO {
|
||||
color: #878787;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.form-check-label {
|
||||
margin-left: 17px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.welcomeText {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.KLO1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.MLKI a {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.form-check-label {
|
||||
font-size: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.MKUY {
|
||||
text-align: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.main_head {
|
||||
margin-bottom: 12px;
|
||||
padding-top: 10px;
|
||||
border-bottom: 1px dashed #eeeeee;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 366px) {
|
||||
.main-logo {
|
||||
width: 150px;
|
||||
height: 46px;
|
||||
display: inline-block;
|
||||
}
|
||||
.welcomeText {
|
||||
font-size: 20px;
|
||||
}
|
||||
.KNLO {
|
||||
color: #878787;
|
||||
font-size: 10px;
|
||||
}
|
||||
.btnContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
@@ -395,14 +395,6 @@
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.backBtn {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.backBtn:hover {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.defaultBtn {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
|
||||
padding: 3px 26px;
|
||||
@@ -420,42 +412,6 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.downloadBtn {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
|
||||
padding: 3px 15px !important;
|
||||
background-color: rgb(241 79 79);
|
||||
border: none !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.downloadBtn:hover {
|
||||
box-shadow: 0 2px 4px rgba(154, 36, 36, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.printBtn {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
|
||||
padding: 3px 15px !important;
|
||||
background: #188ae2;
|
||||
border: none !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.printBtn:hover {
|
||||
box-shadow: 0 2px 4px rgba(23, 48, 137, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.certificateBtn {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
|
||||
padding: 3px 15px !important;
|
||||
background: #08bc66;
|
||||
border: none !important;
|
||||
color: white !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.certificateBtn:hover {
|
||||
box-shadow: 0 2px 4px rgba(15, 150, 87, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.signerList {
|
||||
overflow-y: scroll;
|
||||
@@ -496,11 +452,15 @@
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5; /* Example: reduce opacity to visually indicate disabled state */
|
||||
pointer-events: none; /* Prevents mouse clicks and other events */
|
||||
opacity: 0.5;
|
||||
/* Example: reduce opacity to visually indicate disabled state */
|
||||
pointer-events: none;
|
||||
/* Prevents mouse clicks and other events */
|
||||
/* background-color: #888; */
|
||||
}
|
||||
|
||||
.useEmail {
|
||||
font-size: 10px;
|
||||
color: #424242;
|
||||
@@ -557,13 +517,6 @@
|
||||
position: "relative";
|
||||
}
|
||||
|
||||
.signatureHeader {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.signerComponent {
|
||||
/* box-shadow: rgba(17, 12, 46, 0.15) 0px 48px 100px 0px; */
|
||||
@@ -851,6 +804,7 @@ option {
|
||||
z-index: 999;
|
||||
border-radius: 2px;
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.labelTextArea:focus {
|
||||
@@ -1014,6 +968,9 @@ option {
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
@media screen and (max-width: 350px) and (min-width: 311px) {
|
||||
.signatureCanvas {
|
||||
width: 280px;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import axios from 'axios';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
const appId = process.env.APP_ID;
|
||||
const serverUrl = process.env.SERVER_URL;
|
||||
export function customAPIurl() {
|
||||
const url = new URL(process.env.SERVER_URL);
|
||||
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
|
||||
@@ -38,3 +44,206 @@ export function replaceMailVaribles(subject, body, variables) {
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
export const saveFileUsage = async (size, imageUrl, userId) => {
|
||||
//checking server url and save file's size
|
||||
|
||||
try {
|
||||
const tenantQuery = new Parse.Query('partners_Tenant');
|
||||
tenantQuery.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userId,
|
||||
});
|
||||
const tenant = await tenantQuery.first();
|
||||
if (tenant) {
|
||||
const tenantPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: tenant.id,
|
||||
};
|
||||
const _tenantPtr = JSON.stringify(tenantPtr);
|
||||
try {
|
||||
const res = await axios.get(
|
||||
`${serverUrl}/classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': appId,
|
||||
},
|
||||
}
|
||||
);
|
||||
const response = res.data.results;
|
||||
|
||||
let data;
|
||||
// console.log("response", response);
|
||||
if (response && response.length > 0) {
|
||||
data = {
|
||||
usedStorage: response[0].usedStorage ? response[0].usedStorage + size : size,
|
||||
};
|
||||
await axios.put(
|
||||
`${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': appId,
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
data = { usedStorage: size, PartnersTenant: tenantPtr };
|
||||
await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': appId,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in save usage', err);
|
||||
}
|
||||
saveDataFile(size, imageUrl, tenantPtr);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in fetch tenant Id', err);
|
||||
}
|
||||
};
|
||||
|
||||
//function for save fileUrl and file size in particular client db class partners_DataFiles
|
||||
const saveDataFile = async (size, imageUrl, tenantPtr) => {
|
||||
const data = {
|
||||
FileUrl: imageUrl,
|
||||
FileSize: size,
|
||||
TenantPtr: tenantPtr,
|
||||
};
|
||||
|
||||
// console.log("data save",file, data)
|
||||
try {
|
||||
await axios.post(`${serverUrl}/classes/partners_DataFiles`, data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': appId,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('error in save usage ', err);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateMailCount = async extUserId => {
|
||||
// Update count in contracts_Users class
|
||||
const query = new Parse.Query('contracts_Users');
|
||||
query.equalTo('objectId', extUserId);
|
||||
|
||||
try {
|
||||
const contractUser = await query.first({ useMasterKey: true });
|
||||
if (contractUser) {
|
||||
contractUser.increment('EmailCount', 1);
|
||||
await contractUser.save(null, { useMasterKey: true });
|
||||
} else {
|
||||
// Create new entry if not found
|
||||
const ContractsUsers = Parse.Object.extend('contracts_users');
|
||||
const newContractUser = new ContractsUsers();
|
||||
newContractUser.set('EmailCount', 1);
|
||||
await newContractUser.save(null, { useMasterKey: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error updating EmailCount in contracts_users: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export function formatWidgetOptions(type, options) {
|
||||
const status = options?.required === true ? 'required' : 'optional' || 'required';
|
||||
const defaultValue = options?.default || '';
|
||||
const values = options?.values || [];
|
||||
switch (type) {
|
||||
case 'signature':
|
||||
return { name: 'signature', status: 'required' };
|
||||
case 'stamp':
|
||||
return { status: status, name: 'stamp' };
|
||||
case 'initials':
|
||||
return { status: status, name: options.name || 'initials' };
|
||||
case 'image':
|
||||
return { status: status, name: options.name || 'image' };
|
||||
case 'email':
|
||||
return { status: status, name: options.name || 'email', validation: { type: 'email' } };
|
||||
case 'name':
|
||||
return { status: status, name: options.name || 'name' };
|
||||
case 'job title':
|
||||
return { status: status, name: options.name || 'job title' };
|
||||
case 'company':
|
||||
return { status: status, name: options.name || 'company' };
|
||||
case 'date': {
|
||||
let today = new Date();
|
||||
let dd = String(today.getDate()).padStart(2, '0');
|
||||
let mm = String(today.getMonth() + 1).padStart(2, '0'); // January is 0!
|
||||
let yyyy = today.getFullYear();
|
||||
today = dd + '-' + mm + '-' + yyyy;
|
||||
let dateFormat = options?.format;
|
||||
dateFormat = dateFormat.replace(/m/g, 'M');
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'date',
|
||||
response: defaultValue || today,
|
||||
validation: { format: dateFormat || 'dd-MM-yyyy', type: 'date-format' },
|
||||
};
|
||||
}
|
||||
case 'textbox':
|
||||
return {
|
||||
status: status,
|
||||
name: 'textbox',
|
||||
defaultValue: defaultValue,
|
||||
hint: options.hint,
|
||||
validation: { type: 'regex', pattern: options?.regularexpression || '/^[a-zA-Z0-9s]+$/' },
|
||||
};
|
||||
case 'checkbox': {
|
||||
const arr = options?.values;
|
||||
let selectedvalues = [];
|
||||
for (const obj of options.selectedvalues) {
|
||||
const index = arr.indexOf(obj);
|
||||
selectedvalues.push(index);
|
||||
}
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'checkbox',
|
||||
values: values,
|
||||
isReadOnly: options?.readonly || false,
|
||||
isHideLabel: options?.hidelabel || false,
|
||||
validation: {
|
||||
minRequiredCount: options?.validation?.minselections || 0,
|
||||
maxRequiredCount: options?.validation?.maxselections || 0,
|
||||
},
|
||||
defaultValue: selectedvalues || [],
|
||||
};
|
||||
}
|
||||
case 'radio button': {
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'radio',
|
||||
values: values,
|
||||
isReadOnly: options?.readonly || false,
|
||||
isHideLabel: options?.hidelabel || false,
|
||||
defaultValue: defaultValue,
|
||||
};
|
||||
}
|
||||
case 'dropdown':
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'dropdown',
|
||||
values: values,
|
||||
defaultValue: defaultValue,
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function sanitizeFileName(fileName) {
|
||||
// Remove spaces and invalid characters
|
||||
const file = fileName.replace(/[^a-zA-Z0-9._-]/g, '');
|
||||
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,28 @@
|
||||
import axios from 'axios';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
const ssoApiUrl = process.env.SSO_API_URL || 'https://sso.opensignlabs.com/api'; //'https://osl-jacksonv2.vercel.app/api';
|
||||
export const SSOAuth = {
|
||||
// Returns a promise that fulfills if this user mail is valid.
|
||||
validateAuthData: async authData => {
|
||||
try {
|
||||
const response = await axios.get(ssoApiUrl + '/oauth/userinfo', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authData.access_token}`,
|
||||
},
|
||||
});
|
||||
if (response.data && response.data.id && response.data.email === authData.id) {
|
||||
return;
|
||||
}
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'SSO auth is invalid for this user.');
|
||||
} catch (error) {
|
||||
console.log('error in sso adapter', error?.response);
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'SSO auth is invalid for this user.');
|
||||
}
|
||||
},
|
||||
|
||||
// Returns a promise that fulfills if this app id is valid.
|
||||
validateAppId: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,191 @@
|
||||
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) {
|
||||
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') {
|
||||
return { status: 'success' };
|
||||
}
|
||||
} 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;
|
||||
let docCount = 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();
|
||||
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) {
|
||||
docCount += 1;
|
||||
const mailRes = await sendMail(doc, signer);
|
||||
if (mailRes && mailRes.status === 'success') {
|
||||
mailCount += 1;
|
||||
}
|
||||
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) {
|
||||
docCount += 1;
|
||||
// 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);
|
||||
if (mailRes && mailRes.status === 'success') {
|
||||
mailCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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) {
|
||||
docCount += 1;
|
||||
for (const signer of signers) {
|
||||
const mailRes = await sendMail(doc, signer);
|
||||
if (mailRes && mailRes.status === 'success') {
|
||||
mailCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (docCount > 0) {
|
||||
response.json({ status: 'success', document_count: docCount, mail_count: mailCount });
|
||||
} else {
|
||||
response.json({ status: 'no record found' });
|
||||
}
|
||||
} 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 });
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,24 @@
|
||||
//--npm modules
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
export const app = express();
|
||||
import dotenv from 'dotenv';
|
||||
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();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
import uploadFile from './uploadFile.js';
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
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('/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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
export default async function saveInvoice(request, response) {
|
||||
const InvoiceId = request.body.data.invoice.invoice_id;
|
||||
const body = request.body;
|
||||
const Email = request.body.data.invoice.email;
|
||||
|
||||
try {
|
||||
const extUserCls = new Parse.Query('contracts_Users');
|
||||
extUserCls.equalTo('Email', Email);
|
||||
const extUser = await extUserCls.first({ useMasterKey: true });
|
||||
if (extUser) {
|
||||
const invoiceCls = new Parse.Query('contracts_Invoices');
|
||||
invoiceCls.equalTo('InvoiceId', InvoiceId);
|
||||
const invoice = await invoiceCls.first({ useMasterKey: true });
|
||||
if (invoice) {
|
||||
const updateInvoice = new Parse.Object('contracts_Invoices');
|
||||
updateInvoice.id = invoice.id;
|
||||
updateInvoice.set('InvoiceDetails', body);
|
||||
await updateInvoice.save(null, { useMasterKey: true });
|
||||
return response.status(200).json({ status: 'update invoice!' });
|
||||
} else {
|
||||
const createInvoice = new Parse.Object('contracts_Invoices');
|
||||
createInvoice.set('InvoiceId', InvoiceId);
|
||||
createInvoice.set('InvoiceDetails', body);
|
||||
createInvoice.set('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
});
|
||||
createInvoice.set('CreatedBy', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: extUser.get('UserId').id,
|
||||
});
|
||||
if (extUser?.get('TenantId')?.id) {
|
||||
createInvoice.set('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: extUser.get('TenantId').id,
|
||||
});
|
||||
}
|
||||
await createInvoice.save(null, { useMasterKey: true });
|
||||
return response.status(200).json({ status: 'create invoice!' });
|
||||
}
|
||||
} else {
|
||||
return response.status(404).json({ status: 'user not found!' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err in save invoice', err);
|
||||
return response.status(400).json({ status: 'error:' + err.message });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
export default async function savePayments(request, response) {
|
||||
const PaymentId = request.body.data.payment.payment_id;
|
||||
const body = request.body;
|
||||
const Email = request.body.data.payment.email;
|
||||
try {
|
||||
const extUserCls = new Parse.Query('contracts_Users');
|
||||
extUserCls.equalTo('Email', Email);
|
||||
const extUser = await extUserCls.first({ useMasterKey: true });
|
||||
if (extUser) {
|
||||
const paymentCls = new Parse.Query('contracts_Payments');
|
||||
paymentCls.equalTo('PaymentId', PaymentId);
|
||||
const payment = await paymentCls.first({ useMasterKey: true });
|
||||
if (payment) {
|
||||
const updatePayment = new Parse.Object('contracts_Payments');
|
||||
updatePayment.id = payment.id;
|
||||
updatePayment.set('PaymentDetails', body);
|
||||
await updatePayment.save(null, { useMasterKey: true });
|
||||
return response.status(200).json({ status: 'update Payment!' });
|
||||
} else {
|
||||
const createPayment = new Parse.Object('contracts_Payments');
|
||||
createPayment.set('PaymentId', PaymentId);
|
||||
createPayment.set('PaymentDetails', body);
|
||||
createPayment.set('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
});
|
||||
createPayment.set('CreatedBy', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: extUser.get('UserId').id,
|
||||
});
|
||||
if (extUser?.get('TenantId')?.id) {
|
||||
createPayment.set('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: extUser.get('TenantId').id,
|
||||
});
|
||||
}
|
||||
await createPayment.save(null, { useMasterKey: true });
|
||||
return response.status(200).json({ status: 'create payments!' });
|
||||
}
|
||||
} else {
|
||||
return response.status(404).json({ status: 'user not found!' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err in save payment', err);
|
||||
return response.status(400).json({ status: 'error:' + err.message });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
export default async function saveSubscription(request, response) {
|
||||
const SubscriptionId = request.body.data.subscription.subscription_id;
|
||||
const body = request.body;
|
||||
const Email = request.body.data.subscription.customer.email;
|
||||
const Next_billing_date = request.body.data.subscription.next_billing_at;
|
||||
const planCode = request.body.data.subscription.plan.plan_code
|
||||
|
||||
try {
|
||||
const extUserCls = new Parse.Query('contracts_Users');
|
||||
extUserCls.equalTo('Email', Email);
|
||||
const extUser = await extUserCls.first({ useMasterKey: true });
|
||||
if (extUser) {
|
||||
const subcriptionCls = new Parse.Query('contracts_Subscriptions');
|
||||
subcriptionCls.equalTo('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: extUser.get('TenantId').id,
|
||||
});
|
||||
const subscription = await subcriptionCls.first({ useMasterKey: true });
|
||||
if (subscription) {
|
||||
const updateSubscription = new Parse.Object('contracts_Subscriptions');
|
||||
updateSubscription.id = subscription.id;
|
||||
updateSubscription.set('SubscriptionId', SubscriptionId);
|
||||
updateSubscription.set('SubscriptionDetails', body);
|
||||
updateSubscription.set('Next_billing_date', new Date(Next_billing_date));
|
||||
updateSubscription.set('PlanCode', planCode);
|
||||
await updateSubscription.save(null, { useMasterKey: true });
|
||||
return response.status(200).json({ status: 'update subscription!' });
|
||||
} else {
|
||||
const createSubscription = new Parse.Object('contracts_Subscriptions');
|
||||
createSubscription.set('SubscriptionId', SubscriptionId);
|
||||
createSubscription.set('SubscriptionDetails', body);
|
||||
createSubscription.set('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
});
|
||||
createSubscription.set('CreatedBy', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: extUser.get('UserId').id,
|
||||
});
|
||||
createSubscription.set('TenantId', {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: extUser.get('TenantId').id,
|
||||
});
|
||||
createSubscription.set('Next_billing_date', new Date(Next_billing_date));
|
||||
createSubscription.set('PlanCode', planCode);
|
||||
await createSubscription.save(null, { useMasterKey: true });
|
||||
return response.status(200).json({ status: 'create subscription!' });
|
||||
}
|
||||
} else {
|
||||
return response.status(404).json({ status: 'user not found!' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err in save subscription', err);
|
||||
return response.status(400).json({ status: 'error:' + 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,11 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
object.set('Signers', templateSigner);
|
||||
}
|
||||
object.set('URL', template.URL);
|
||||
object.set('SignedUrl', template.URL);
|
||||
object.set('SentToOthers', true);
|
||||
if (TimeToCompleteDays) {
|
||||
object.set('TimeToCompleteDays', TimeToCompleteDays);
|
||||
}
|
||||
object.set('CreatedBy', template.CreatedBy);
|
||||
object.set('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
@@ -195,9 +209,6 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
});
|
||||
let sender = template.ExtUserPtr.Email;
|
||||
let sendMail;
|
||||
const serverUrl = process.env.SERVER_URL;
|
||||
const newServer = serverUrl.replaceAll('/', '%2F');
|
||||
const serverParams = `${newServer}%2F&${process.env.APP_ID}&contracts`;
|
||||
if (send_email === false) {
|
||||
console.log("don't send mail");
|
||||
} else {
|
||||
@@ -217,9 +228,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';
|
||||
@@ -251,10 +263,10 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
document_title: template.Name,
|
||||
sender_name: template.ExtUserPtr.Name,
|
||||
sender_mail: template.ExtUserPtr.Email,
|
||||
sender_phone: template.ExtUserPtr.Phone,
|
||||
sender_phone: template.ExtUserPtr?.Phone || '',
|
||||
receiver_name: contactMail[i].name,
|
||||
receiver_email: contactMail[i].email,
|
||||
receiver_phone: contactMail[i].phone,
|
||||
receiver_phone: contactMail[i]?.phone || '',
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: signPdf,
|
||||
@@ -266,13 +278,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,
|
||||
};
|
||||
}
|
||||
@@ -284,6 +296,7 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
subject: subject,
|
||||
from: sender,
|
||||
html: html,
|
||||
extUserId: template.ExtUserPtr.objectId,
|
||||
};
|
||||
sendMail = await axios.post(url, params, { headers: headers });
|
||||
} catch (error) {
|
||||
@@ -299,7 +312,11 @@ export default async function createDocumentWithTemplate(request, response) {
|
||||
name: template?.Name,
|
||||
note: template?.Note || '',
|
||||
description: template?.Description || '',
|
||||
signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x.phone })),
|
||||
signers: contact?.map(x => ({
|
||||
name: x.name,
|
||||
email: x.email,
|
||||
phone: x?.phone || '',
|
||||
})),
|
||||
createdAt: res.createdAt,
|
||||
};
|
||||
if (template.ExtUserPtr && template.ExtUserPtr?.Webhook) {
|
||||
@@ -319,7 +336,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!',
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ export default async function createContact(request, response) {
|
||||
const serverUrl = process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
const name = request.body.name;
|
||||
const phone = request.body.phone;
|
||||
const phone = request.body?.phone;
|
||||
const email = request.body.email;
|
||||
const reqToken = request.headers['x-api-token'];
|
||||
if (!reqToken) {
|
||||
@@ -49,7 +49,9 @@ export default async function createContact(request, response) {
|
||||
|
||||
const contactQuery = new Parse.Object('contracts_Contactbook');
|
||||
contactQuery.set('Name', name);
|
||||
contactQuery.set('Phone', phone);
|
||||
if (phone) {
|
||||
contactQuery.set('Phone', phone);
|
||||
}
|
||||
contactQuery.set('Email', email);
|
||||
contactQuery.set('UserRole', 'contracts_Guest');
|
||||
if (tenantRes && tenantRes.id) {
|
||||
@@ -65,8 +67,10 @@ export default async function createContact(request, response) {
|
||||
_user.set('name', name);
|
||||
_user.set('username', email);
|
||||
_user.set('email', email);
|
||||
_user.set('phone', phone);
|
||||
_user.set('password', phone);
|
||||
_user.set('password', email);
|
||||
if (phone) {
|
||||
_user.set('phone', phone);
|
||||
}
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
@@ -106,7 +110,7 @@ export default async function createContact(request, response) {
|
||||
objectId: parseRes.objectId,
|
||||
name: parseRes.Name,
|
||||
email: parseRes.Email,
|
||||
phone: parseRes.Phone,
|
||||
phone: parseRes?.Phone || '',
|
||||
createdAt: parseRes.createdAt,
|
||||
updatedAt: parseRes.updatedAt,
|
||||
});
|
||||
@@ -128,7 +132,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',
|
||||
@@ -155,7 +159,7 @@ export default async function createContact(request, response) {
|
||||
objectId: parseRes.objectId,
|
||||
name: parseRes.Name,
|
||||
email: parseRes.Email,
|
||||
phone: parseRes.Phone,
|
||||
phone: parseRes?.Phone || '',
|
||||
createdAt: parseRes.createdAt,
|
||||
updatedAt: parseRes.updatedAt,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import axios from 'axios';
|
||||
import { color, customAPIurl, replaceMailVaribles } from '../../../../Utils.js';
|
||||
import {
|
||||
color,
|
||||
customAPIurl,
|
||||
replaceMailVaribles,
|
||||
saveFileUsage,
|
||||
formatWidgetOptions,
|
||||
sanitizeFileName,
|
||||
} from '../../../../Utils.js';
|
||||
|
||||
// `sendDoctoWebhook` is used to send res data of document on webhook
|
||||
async function sendDoctoWebhook(doc, WebhookUrl, userId) {
|
||||
@@ -8,6 +15,7 @@ async function sendDoctoWebhook(doc, WebhookUrl, userId) {
|
||||
event: 'created',
|
||||
...doc,
|
||||
};
|
||||
|
||||
await axios
|
||||
.post(WebhookUrl, params, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -28,7 +36,7 @@ async function sendDoctoWebhook(doc, WebhookUrl, userId) {
|
||||
}
|
||||
})
|
||||
.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);
|
||||
@@ -39,7 +47,7 @@ async function sendDoctoWebhook(doc, WebhookUrl, userId) {
|
||||
});
|
||||
webhook.save(null, { useMasterKey: true });
|
||||
} catch (err) {
|
||||
console.log('err save in contracts_Webhook', err);
|
||||
console.log('err save in contracts_Webhook', err?.message);
|
||||
}
|
||||
});
|
||||
// console.log('res ', res.data);
|
||||
@@ -58,13 +66,15 @@ 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' });
|
||||
}
|
||||
|
||||
try {
|
||||
const tokenQuery = new Parse.Query('appToken');
|
||||
tokenQuery.equalTo('token', reqToken);
|
||||
@@ -79,19 +89,32 @@ 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');
|
||||
const file = new Parse.File(request.files?.[0]?.originalname, {
|
||||
base64: fileData?.toString('base64'),
|
||||
base64: base64,
|
||||
});
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
} else {
|
||||
const file = new Parse.File(`${name}.pdf`, {
|
||||
base64: base64File,
|
||||
});
|
||||
const filename = sanitizeFileName(`${name}.pdf`);
|
||||
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64File, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
}
|
||||
const contractsUser = new Parse.Query('contracts_Users');
|
||||
contractsUser.equalTo('UserId', userPtr);
|
||||
@@ -117,8 +140,13 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
object.set('SendinOrder', sendInOrder);
|
||||
}
|
||||
object.set('URL', fileUrl);
|
||||
object.set('SignedUrl', fileUrl);
|
||||
object.set('SentToOthers', true);
|
||||
object.set('CreatedBy', userPtr);
|
||||
object.set('ExtUserPtr', extUserPtr);
|
||||
if (TimeToCompleteDays) {
|
||||
object.set('TimeToCompleteDays', TimeToCompleteDays);
|
||||
}
|
||||
object.set('IsSendMail', send_email);
|
||||
let contact = [];
|
||||
if (signers && signers.length > 0) {
|
||||
@@ -170,17 +198,17 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
|
||||
for (const widget of signer.widgets) {
|
||||
const pageNumber = widget.page;
|
||||
const options = formatWidgetOptions(widget.type, widget.options);
|
||||
const page = placeHolder.find(page => page.pageNumber === pageNumber);
|
||||
const signOpt = { name: 'signature', status: 'required' };
|
||||
const widgetData = {
|
||||
isStamp: widget.type === 'stamp',
|
||||
isStamp: widget.type === 'stamp' || widget.type === 'image',
|
||||
key: randomId(),
|
||||
isDrag: false,
|
||||
scale: 1,
|
||||
isMobile: false,
|
||||
zIndex: 1,
|
||||
type: widget.type,
|
||||
options: widget.type === 'signature' ? signOpt : widget.options,
|
||||
type: widget.type === 'textbox' ? 'text input' : widget.type,
|
||||
options: options,
|
||||
Width: widget.w,
|
||||
Height: widget.h,
|
||||
xPosition: widget.x,
|
||||
@@ -228,7 +256,7 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
name: name,
|
||||
note: note || '',
|
||||
description: description || '',
|
||||
signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x.phone })),
|
||||
signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x?.phone || '' })),
|
||||
createdAt: res.createdAt,
|
||||
};
|
||||
if (parseExtUser && parseExtUser.Webhook) {
|
||||
@@ -242,10 +270,6 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
year: 'numeric',
|
||||
});
|
||||
let sender = parseExtUser.Email;
|
||||
let sendMail;
|
||||
const serverUrl = process.env.SERVER_URL;
|
||||
const newServer = serverUrl.replaceAll('/', '%2F');
|
||||
const serverParams = `${newServer}%2F&${process.env.APP_ID}&contracts`;
|
||||
if (send_email === false) {
|
||||
console.log("don't send mail");
|
||||
} else {
|
||||
@@ -266,7 +290,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';
|
||||
@@ -297,10 +323,10 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
document_title: name,
|
||||
sender_name: parseExtUser.Name,
|
||||
sender_mail: parseExtUser.Email,
|
||||
sender_phone: parseExtUser.Phone,
|
||||
sender_phone: parseExtUser?.Phone || '',
|
||||
receiver_name: contactMail[i].name,
|
||||
receiver_email: contactMail[i].email,
|
||||
receiver_phone: contactMail[i].phone,
|
||||
receiver_phone: contactMail[i]?.phone || '',
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: signPdf,
|
||||
@@ -312,13 +338,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,
|
||||
};
|
||||
}
|
||||
@@ -330,9 +356,10 @@ export default async function createDocumentwithCoordinate(request, response) {
|
||||
subject: subject,
|
||||
from: sender,
|
||||
html: html,
|
||||
extUserId: extUser.id,
|
||||
};
|
||||
|
||||
sendMail = await axios.post(url, params, { headers: headers });
|
||||
await axios.post(url, params, { headers: headers });
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
}
|
||||
@@ -350,7 +377,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!' });
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import { customAPIurl } from '../../../../Utils.js';
|
||||
|
||||
const randomId = () => Math.floor(1000 + Math.random() * 9000);
|
||||
import { saveFileUsage } from '../../../../Utils.js';
|
||||
export default async function createTemplate(request, response) {
|
||||
const name = request.body?.title;
|
||||
const note = request.body?.note;
|
||||
@@ -10,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'];
|
||||
@@ -36,12 +34,17 @@ export default async function createTemplate(request, response) {
|
||||
});
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64File, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
} else {
|
||||
const base64 = fileData?.toString('base64');
|
||||
const file = new Parse.File(request.files?.[0]?.originalname, {
|
||||
base64: fileData?.toString('base64'),
|
||||
base64: base64,
|
||||
});
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
}
|
||||
|
||||
const contractsUser = new Parse.Query('contracts_Users');
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import axios from 'axios';
|
||||
import { color, customAPIurl } from '../../../../Utils.js';
|
||||
import {
|
||||
color,
|
||||
customAPIurl,
|
||||
saveFileUsage,
|
||||
formatWidgetOptions,
|
||||
sanitizeFileName,
|
||||
} from '../../../../Utils.js';
|
||||
|
||||
const randomId = () => Math.floor(1000 + Math.random() * 9000);
|
||||
export default async function createTemplatewithCoordinate(request, response) {
|
||||
@@ -34,17 +40,21 @@ export default async function createTemplatewithCoordinate(request, response) {
|
||||
if (signers && signers.length > 0) {
|
||||
let fileUrl;
|
||||
if (request.files?.[0]) {
|
||||
const base64 = fileData?.toString('base64');
|
||||
const file = new Parse.File(request.files?.[0]?.originalname, {
|
||||
base64: fileData?.toString('base64'),
|
||||
base64: base64,
|
||||
});
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
} else {
|
||||
const file = new Parse.File(`${name}.pdf`, {
|
||||
base64: base64File,
|
||||
});
|
||||
const filename = sanitizeFileName(`${name}.pdf`);
|
||||
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64File, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
}
|
||||
const contractsUser = new Parse.Query('contracts_Users');
|
||||
contractsUser.equalTo('UserId', userPtr);
|
||||
@@ -81,7 +91,7 @@ export default async function createTemplatewithCoordinate(request, response) {
|
||||
let createContactUrl = protocol + '/v1/createcontact';
|
||||
|
||||
for (const [index, element] of parseSigners.entries()) {
|
||||
if (element?.name && element.phone && element?.email) {
|
||||
if (element?.name && element?.email) {
|
||||
const body = {
|
||||
name: element?.name || '',
|
||||
email: element?.email || '',
|
||||
@@ -129,16 +139,16 @@ export default async function createTemplatewithCoordinate(request, response) {
|
||||
for (const widget of signer.widgets) {
|
||||
const pageNumber = widget.page;
|
||||
const page = placeHolder.find(page => page.pageNumber === pageNumber);
|
||||
const signOpt = { name: 'signature', status: 'required' };
|
||||
const options = formatWidgetOptions(widget.type, widget.options);
|
||||
const widgetData = {
|
||||
isStamp: widget.type === 'stamp',
|
||||
isStamp: widget.type === 'stamp' || widget.type === 'image',
|
||||
key: randomId(),
|
||||
isDrag: false,
|
||||
scale: 1,
|
||||
isMobile: false,
|
||||
zIndex: 1,
|
||||
type: widget.type,
|
||||
options: widget.type === 'signature' ? signOpt : widget.options,
|
||||
type: widget.type === 'textbox' ? 'text input' : widget.type,
|
||||
options: options,
|
||||
Width: widget.w,
|
||||
Height: widget.h,
|
||||
xPosition: widget.x,
|
||||
|
||||
@@ -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'];
|
||||
@@ -36,17 +36,22 @@ export default async function draftDocument(request, response) {
|
||||
if (signers && signers.length > 0) {
|
||||
let fileUrl;
|
||||
if (request.files?.[0]) {
|
||||
const base64 = fileData?.toString('base64');
|
||||
const file = new Parse.File(request.files?.[0]?.originalname, {
|
||||
base64: fileData?.toString('base64'),
|
||||
base64: base64,
|
||||
});
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
} else {
|
||||
const file = new Parse.File(`${name}.pdf`, {
|
||||
base64: base64File,
|
||||
});
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64File, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
}
|
||||
const contractsUser = new Parse.Query('contracts_Users');
|
||||
contractsUser.equalTo('UserId', userPtr);
|
||||
|
||||
@@ -37,7 +37,7 @@ export default async function getContact(request, response) {
|
||||
objectId: parseRes.objectId,
|
||||
name: parseRes.Name,
|
||||
email: parseRes.Email,
|
||||
phone: parseRes.Phone,
|
||||
phone: parseRes?.Phone || '',
|
||||
createdAt: parseRes.createdAt,
|
||||
updatedAt: parseRes.updatedAt,
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user