Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
375dc86f0a | ||
|
|
d2f187f6c0 | ||
|
|
5393ee9ba9 | ||
|
|
d4d2948455 | ||
|
|
37617c04ad | ||
|
|
10e0153018 | ||
|
|
42cbe94af8 | ||
|
|
8f95e53d9d | ||
|
|
0dae21dcf1 | ||
|
|
912efeb5fd | ||
|
|
3d9307f96d | ||
|
|
873f4b080a | ||
|
|
e337a79e2e | ||
|
|
980ce62bf6 | ||
|
|
dccf757f56 | ||
|
|
4a6b88328e | ||
|
|
779a84cf0b | ||
|
|
aff409ba63 | ||
|
|
275167c0c4 | ||
|
|
19b2af7f57 | ||
|
|
5050b9ee97 | ||
|
|
23da4be6b1 | ||
|
|
299648b8e0 | ||
|
|
86fa0c87ae | ||
|
|
9619eec40f | ||
|
|
886161d65f | ||
|
|
d5d646e6e0 | ||
|
|
494103163c | ||
|
|
72e9e4848b | ||
|
|
1181ba6f90 | ||
|
|
b8d6ec5fe0 | ||
|
|
7335d7df0a | ||
|
|
44754095fb | ||
|
|
0f515bae01 | ||
|
|
76a11f6ee6 | ||
|
|
5fb28feb65 | ||
|
|
e5f1524b10 | ||
|
|
8a875691fb | ||
|
|
0b2710b97d | ||
|
|
57552a32b0 | ||
|
|
572ec3658e | ||
|
|
6d5c791f61 | ||
|
|
168e479c0c | ||
|
|
927e1b7ef9 | ||
|
|
76dccf804e | ||
|
|
4262e0e6eb | ||
|
|
7d487420aa | ||
|
|
c20611b50b | ||
|
|
15d73ee62d | ||
|
|
a334cda9aa | ||
|
|
69d4254b44 | ||
|
|
c422b11e3a | ||
|
|
95d2794754 | ||
|
|
65ce0d430c | ||
|
|
e7a98d55f0 | ||
|
|
d247c6d0e7 | ||
|
|
20e0277113 | ||
|
|
2ed3609c46 | ||
|
|
2e41dc20c1 | ||
|
|
50a8f93014 | ||
|
|
3803b29835 | ||
|
|
e18586887e | ||
|
|
d137c081ee | ||
|
|
62dfe32d32 | ||
|
|
f801192a8c | ||
|
|
13d161e3c8 | ||
|
|
a249baea66 | ||
|
|
f6f5b08d43 | ||
|
|
a1f4e9ea74 | ||
|
|
5eed145f4b | ||
|
|
546a0f8e87 | ||
|
|
975c4b079a | ||
|
|
09e0ac2415 | ||
|
|
6140aea31a | ||
|
|
c59c921a8b | ||
|
|
4b1e13d39a | ||
|
|
e33e0be735 | ||
|
|
2ff4e372d6 | ||
|
|
9cfffe3fbd | ||
|
|
82ef278a0e | ||
|
|
574d2d05a4 | ||
|
|
38f72c7415 | ||
|
|
e00704a43c | ||
|
|
778e28fae0 | ||
|
|
1574f86654 | ||
|
|
df17bdcd68 | ||
|
|
f290fd875f | ||
|
|
caeb055fc2 | ||
|
|
1eafc113e6 | ||
|
|
4e46795e0f | ||
|
|
f7087dfb29 | ||
|
|
e6a7cda3b6 | ||
|
|
db120ecda7 | ||
|
|
9bfe8b0e26 | ||
|
|
c1438be9e0 | ||
|
|
f4d7e33f97 | ||
|
|
4ec7fdb078 | ||
|
|
3413d0914d | ||
|
|
793b6a5550 | ||
|
|
77894f6754 | ||
|
|
812928a6a3 | ||
|
|
bb398d13a2 | ||
|
|
2d7a893424 | ||
|
|
62e943161a | ||
|
|
2604156c7f | ||
|
|
1e04bb4dbe | ||
|
|
0ac3a70a83 | ||
|
|
2f1ad45f92 | ||
|
|
8263e3247e | ||
|
|
9666e41880 | ||
|
|
e50f062a91 | ||
|
|
1c8f401060 | ||
|
|
a8e764a62c | ||
|
|
43bc1f3e17 | ||
|
|
eebed949cd | ||
|
|
c7cd9e56f8 | ||
|
|
1c9fb5ebcb | ||
|
|
1339259c3b | ||
|
|
27a069e1a2 | ||
|
|
7a69537255 | ||
|
|
9569b78e66 | ||
|
|
266b0f27f5 | ||
|
|
00e5c48828 | ||
|
|
c9e010e993 | ||
|
|
5b52825ea6 | ||
|
|
100b97cf1a | ||
|
|
7a3216a81c | ||
|
|
c6412be70d | ||
|
|
954dbb3375 | ||
|
|
c996825665 | ||
|
|
5fc1a58bdb | ||
|
|
5c947181a2 | ||
|
|
2e8b2fa525 | ||
|
|
1e6e5f9dba | ||
|
|
fa2e169df4 | ||
|
|
174023c62f | ||
|
|
1198460c97 | ||
|
|
07954c8bb6 | ||
|
|
d4d9521c31 | ||
|
|
d77477f3d1 | ||
|
|
ff36faa96c | ||
|
|
450de195a7 | ||
|
|
57b23d7f97 | ||
|
|
a246f74e11 | ||
|
|
89c687ed06 | ||
|
|
f7e2fa8aec | ||
|
|
8a2960511c | ||
|
|
832e5c2181 | ||
|
|
00d900c2b2 | ||
|
|
47ef685bcc | ||
|
|
9f4aff65f2 | ||
|
|
2eddb49b8e | ||
|
|
63f1bb9fc7 | ||
|
|
e73b055cc2 | ||
|
|
abd7cf9b71 | ||
|
|
2dbd41ce65 | ||
|
|
25ef7c9f39 | ||
|
|
81a4daa5e8 | ||
|
|
3f9e6d1f81 | ||
|
|
1507f113f4 | ||
|
|
d46ba8f307 | ||
|
|
2426e7484c | ||
|
|
17b9fc772e | ||
|
|
7b2b71fea5 | ||
|
|
70a3adebd7 | ||
|
|
9e012ef6c7 | ||
|
|
948e0d3270 | ||
|
|
6e4f9ba0e9 | ||
|
|
a816ceb04b | ||
|
|
f864f74f14 | ||
|
|
2c715a77a2 | ||
|
|
9b4cdde80a | ||
|
|
de2154e6fe | ||
|
|
76f4ccfcf3 | ||
|
|
af52bb30f5 | ||
|
|
cc398d68e4 | ||
|
|
cd4844863e | ||
|
|
6d4ebf7be5 | ||
|
|
18992e0334 | ||
|
|
0580a18b0d | ||
|
|
5a4114b786 | ||
|
|
4fdf2299f8 | ||
|
|
f15aa39588 | ||
|
|
694312812a | ||
|
|
2606b58434 | ||
|
|
943f9a0673 | ||
|
|
77622ebea9 | ||
|
|
6fb714d565 | ||
|
|
f6170e01f6 | ||
|
|
1e857b615f | ||
|
|
b982ca5218 | ||
|
|
c83817800e | ||
|
|
d1992839a8 | ||
|
|
bfb7136c89 | ||
|
|
52b2cc6af4 | ||
|
|
94d386541d | ||
|
|
d85d76c8aa | ||
|
|
ade97b972f | ||
|
|
532780629b | ||
|
|
1f2298f192 | ||
|
|
ae5d527e4f | ||
|
|
e59a7e0028 | ||
|
|
1d6655eafe | ||
|
|
09b39a467b | ||
|
|
e0e7dd5298 | ||
|
|
56d5091b96 | ||
|
|
0b28e64111 | ||
|
|
482b75c6a7 | ||
|
|
cdff7e252d | ||
|
|
a18aee0be1 | ||
|
|
89f67bfeeb | ||
|
|
e4f0e9cef3 | ||
|
|
12599c1e5a | ||
|
|
6bb3203417 | ||
|
|
0eb2c5ecc4 | ||
|
|
01ab19b235 | ||
|
|
ba6b0ce7e5 | ||
|
|
b99d3f643b | ||
|
|
fea174c48d | ||
|
|
1aa1a602b3 | ||
|
|
37b7e50b4c | ||
|
|
e9fab26ef3 | ||
|
|
3216a1d4ae | ||
|
|
eb0941f087 | ||
|
|
0a315ef9c7 | ||
|
|
134babd070 | ||
|
|
954ba13f22 | ||
|
|
66718fd416 | ||
|
|
f4358c5004 | ||
|
|
63f888b3ec | ||
|
|
e066d1d273 | ||
|
|
daddb10030 | ||
|
|
6d9090d414 | ||
|
|
795e8a8ec9 | ||
|
|
e8b0b333a1 | ||
|
|
93d9a761ca | ||
|
|
3642d4f3a3 | ||
|
|
08387fc406 | ||
|
|
d1d0f40b00 | ||
|
|
ad4a9e7141 | ||
|
|
d46cb7dc10 | ||
|
|
50b9717cb9 | ||
|
|
4e83734c5c | ||
|
|
6eeaea137a | ||
|
|
2c91499232 | ||
|
|
e6085eb2c0 | ||
|
|
6fa4c4759b | ||
|
|
f8d6547c24 | ||
|
|
ffdd952440 | ||
|
|
9594e9438e | ||
|
|
08d1e9a360 | ||
|
|
74a9d08550 | ||
|
|
6fc86e026b | ||
|
|
72da56a916 | ||
|
|
4ba7ffbb1a | ||
|
|
0d9a0b8bb1 | ||
|
|
084aa69702 | ||
|
|
597635800f | ||
|
|
8da2c7fad4 | ||
|
|
2b0276ead9 | ||
|
|
45a96ece54 | ||
|
|
729d19dfd3 | ||
|
|
29aa123669 | ||
|
|
f69caf016a | ||
|
|
48e0e0ee15 | ||
|
|
a71278f95c | ||
|
|
f7bb11ede1 | ||
|
|
6a365f7734 | ||
|
|
e4d946e519 | ||
|
|
149a16f150 | ||
|
|
d8ff3d9faa | ||
|
|
fe34001e42 | ||
|
|
fcdd7e2758 | ||
|
|
025c30b685 | ||
|
|
e742e85f0e | ||
|
|
476a99feae | ||
|
|
29464585b8 | ||
|
|
cdd078699b | ||
|
|
9ef1e22fda | ||
|
|
655401e04b | ||
|
|
b550c07ca8 | ||
|
|
fd72094b8a | ||
|
|
4d7348abc2 | ||
|
|
ec68479e98 | ||
|
|
ac05942e58 | ||
|
|
b387b6b402 | ||
|
|
e13baeb2fc | ||
|
|
52eb8e4a68 | ||
|
|
71189783a8 | ||
|
|
aec9396524 | ||
|
|
d74390b974 | ||
|
|
998fe013c8 | ||
|
|
06d4c9aeaf | ||
|
|
bc77a9b0a1 | ||
|
|
07c0390401 | ||
|
|
c50d753d74 | ||
|
|
65eef5739d | ||
|
|
01eed3664b | ||
|
|
685e09d406 | ||
|
|
7bacca1f78 | ||
|
|
3764ab5c30 | ||
|
|
2f7a1d67b6 | ||
|
|
8726efb0aa | ||
|
|
e073e83ad6 | ||
|
|
69140d2139 | ||
|
|
aeed48c0ef | ||
|
|
1f9504b9cc | ||
|
|
5518d6932e | ||
|
|
f14e301b6c | ||
|
|
901ddf35bb | ||
|
|
9aa3166528 | ||
|
|
a90e679228 | ||
|
|
90b5e3c860 | ||
|
|
74f6612271 | ||
|
|
2862a97c4e | ||
|
|
5c3f967128 | ||
|
|
6fde71e947 | ||
|
|
2900464272 | ||
|
|
10bac3c11f | ||
|
|
302f38d472 | ||
|
|
8ecdbc43a3 | ||
|
|
6721b334cf | ||
|
|
454f557d14 | ||
|
|
635693b394 | ||
|
|
0be1816046 | ||
|
|
feb162e03e | ||
|
|
c8801846fa | ||
|
|
a793fc3722 | ||
|
|
ffdabe4a00 | ||
|
|
5536c99052 | ||
|
|
ba1c3145f3 | ||
|
|
0fbe6aeb6c | ||
|
|
4898c4a851 | ||
|
|
85aa6b2947 | ||
|
|
3b02d2bcdb | ||
|
|
ed01f4a7a0 | ||
|
|
1f395cf25f | ||
|
|
cf98e73d13 | ||
|
|
10112ac20d | ||
|
|
9a0183274f | ||
|
|
6b2e16c21d | ||
|
|
3c51baf1c9 | ||
|
|
3f4be1c4f3 | ||
|
|
62b36f58f7 | ||
|
|
ba3591c7bc | ||
|
|
1ec11b8733 | ||
|
|
ab89d3b71f | ||
|
|
1811173648 | ||
|
|
b218e96107 | ||
|
|
64a0be8776 | ||
|
|
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 | ||
|
|
3ede441f54 | ||
|
|
2433ad5957 | ||
|
|
fffd907d63 | ||
|
|
51edc339cd | ||
|
|
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 | ||
|
|
741efff1fa | ||
|
|
93b0d00ab0 | ||
|
|
d731951159 | ||
|
|
24bf08c14f | ||
|
|
c03819f5a9 | ||
|
|
4521d16891 | ||
|
|
f77525cd8b | ||
|
|
38b7311e66 | ||
|
|
c934f8ccd6 | ||
|
|
4e128e5a5a | ||
|
|
e0ccf049a6 | ||
|
|
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 | ||
|
|
498bd18630 | ||
|
|
56dd060531 | ||
|
|
960fbdb0eb | ||
|
|
b530ef882a | ||
|
|
f8145feebc | ||
|
|
df38d15611 | ||
|
|
bb69518674 | ||
|
|
fee53fb020 | ||
|
|
d086e60ae5 | ||
|
|
51150d4e47 | ||
|
|
1d3cd02f9d | ||
|
|
850d14064a | ||
|
|
d03515efc4 | ||
|
|
30e6e4b3fd | ||
|
|
cbdd729e95 | ||
|
|
ac82209d52 | ||
|
|
d5cc6f6758 | ||
|
|
3aa211dc1a | ||
|
|
33d5eecbbf | ||
|
|
1d6c108ba4 | ||
|
|
79da0f285d | ||
|
|
e8eef32958 | ||
|
|
dc1fab5250 | ||
|
|
3e185ef796 | ||
|
|
8cf5adb273 | ||
|
|
803faed97b | ||
|
|
660eaeb714 | ||
|
|
273ada837b | ||
|
|
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 | ||
|
|
0bf07c2703 | ||
|
|
de26c2d719 | ||
|
|
881ac12b03 | ||
|
|
1932dda6d6 | ||
|
|
46159b7214 | ||
|
|
c57c15a4cb | ||
|
|
724fa7c27e | ||
|
|
2762c3e170 | ||
|
|
10a0c60056 | ||
|
|
036c9b8d4a | ||
|
|
1f50368259 | ||
|
|
6d5e07dac7 | ||
|
|
3656175f11 | ||
|
|
febb50c3cd | ||
|
|
f8ba0d357b | ||
|
|
74bfd39abc | ||
|
|
5a25b2d874 | ||
|
|
3115ff3e2a | ||
|
|
d1e7b9a2ab | ||
|
|
adbf1fe1e0 | ||
|
|
60140885e0 | ||
|
|
fe2f3c5f7c | ||
|
|
a06723ff3f | ||
|
|
387154c07b | ||
|
|
12a6a84450 | ||
|
|
1f350fc459 | ||
|
|
d9fd2a5531 | ||
|
|
cd4fd6e3de | ||
|
|
49cd30de96 | ||
|
|
a24ebe25a8 | ||
|
|
3e664bf677 | ||
|
|
6161e975fb | ||
|
|
c2a45c230b | ||
|
|
a629aa0bff | ||
|
|
899678d98a | ||
|
|
076a0f54c5 | ||
|
|
a94e67cd7c | ||
|
|
e78a3991f0 | ||
|
|
593a8499f4 | ||
|
|
70d643a5e2 | ||
|
|
f2b1a54695 | ||
|
|
4353a4f732 | ||
|
|
34c99edf1a | ||
|
|
91f3bdb299 | ||
|
|
276fc974fa | ||
|
|
081e11f9af | ||
|
|
4f056cc9d3 | ||
|
|
d9da25e908 | ||
|
|
99b3436c8c | ||
|
|
452d4d7a1d | ||
|
|
ed613c3424 | ||
|
|
bf70fa0b11 | ||
|
|
8684197d49 | ||
|
|
648d1224b3 | ||
|
|
126b6ad355 | ||
|
|
0512f27e2e | ||
|
|
2c61630017 | ||
|
|
72c2b2dde6 | ||
|
|
29eb78c960 | ||
|
|
aa9ba05dde | ||
|
|
365b074f62 | ||
|
|
af6cb4c17d | ||
|
|
6bcad91b0f | ||
|
|
1aa2b003b2 | ||
|
|
1a27e9addf | ||
|
|
c2891a0858 | ||
|
|
c1b226db96 | ||
|
|
8a01efee4c | ||
|
|
eafb26d462 | ||
|
|
62e1a1bd9b | ||
|
|
58eb71c52d | ||
|
|
c1e6f2b5ab | ||
|
|
ba0c18b591 | ||
|
|
c3a902dcc7 | ||
|
|
5ece3c0694 | ||
|
|
1053540410 | ||
|
|
24773759c2 | ||
|
|
7a6de70169 | ||
|
|
04b2c7c4da | ||
|
|
d11171e91c | ||
|
|
a4a78cb96c | ||
|
|
dfdcde8fcc | ||
|
|
5e1b84d2e6 | ||
|
|
f7606bce35 | ||
|
|
e643d96797 | ||
|
|
1ff4160ca4 | ||
|
|
62f66bf524 | ||
|
|
de3bc37fa5 | ||
|
|
ea660384c3 | ||
|
|
210cb5e754 | ||
|
|
ed45bfef84 | ||
|
|
a8e77e93f7 | ||
|
|
969071bb28 | ||
|
|
76491a77f1 | ||
|
|
9a51eed2bc | ||
|
|
a675a3c8fd | ||
|
|
fea30930eb | ||
|
|
7373b086e4 | ||
|
|
2fc1eda761 | ||
|
|
2c100ad399 | ||
|
|
1cefe01aee | ||
|
|
f1cf395761 | ||
|
|
4af3d8f78b | ||
|
|
cf21ac7405 | ||
|
|
5f11e92c0c | ||
|
|
c3ffda4dd2 | ||
|
|
c929b6b7a9 | ||
|
|
eb2abd3e6d | ||
|
|
1c4c2e87b4 | ||
|
|
4fda5b4531 | ||
|
|
df61e143af | ||
|
|
3a55c35445 | ||
|
|
f08758fd71 | ||
|
|
08476ba3d6 | ||
|
|
db9ae71d67 | ||
|
|
39806e3ad6 | ||
|
|
7882c61b81 | ||
|
|
b8f886902f | ||
|
|
17660dae21 | ||
|
|
7584667643 | ||
|
|
b47d1cff07 | ||
|
|
b99d8e6d6e | ||
|
|
8b15bceac8 | ||
|
|
86b0e90948 | ||
|
|
3bc57a999c | ||
|
|
27730c89e8 | ||
|
|
82eeffa6e4 | ||
|
|
53147caebc | ||
|
|
6004522d5c | ||
|
|
266c2463c9 | ||
|
|
1b215f53e7 | ||
|
|
73b2ba6abd | ||
|
|
c04fd80a38 | ||
|
|
61b123cb61 | ||
|
|
08e805569f | ||
|
|
375aa0b7e7 | ||
|
|
b94e0fa052 | ||
|
|
b6752549ed | ||
|
|
51505af6d8 | ||
|
|
0e5cffa1fa | ||
|
|
d3b1a37a7c | ||
|
|
885b5287b4 | ||
|
|
5348c6620a | ||
|
|
73c219fa4d | ||
|
|
f4090c0c61 | ||
|
|
cd2be94d29 | ||
|
|
878dca3cd6 | ||
|
|
90cba26904 | ||
|
|
c5c001f030 | ||
|
|
a3ba4e26c3 | ||
|
|
dd95862d5a | ||
|
|
5d3de41e63 | ||
|
|
5cad0a96cd | ||
|
|
e74fd79efc | ||
|
|
6f49fc9126 | ||
|
|
bbf3cc2f44 | ||
|
|
7bf196ef75 | ||
|
|
4b5b3aff49 | ||
|
|
15b0cb132c | ||
|
|
e67f91ea90 | ||
|
|
59350f02fb | ||
|
|
32cde66d64 | ||
|
|
7ba111a275 | ||
|
|
8d3fa6cef8 | ||
|
|
ef7aede146 | ||
|
|
11ae58aad3 | ||
|
|
05e30a5d19 | ||
|
|
64773cafec | ||
|
|
c6be39ffc3 | ||
|
|
376d4293c2 | ||
|
|
67b3752297 | ||
|
|
f463ffc8ed | ||
|
|
6e2aac51b4 | ||
|
|
a9a3c25dfd | ||
|
|
645898ab14 | ||
|
|
3ad1c46455 | ||
|
|
d3d33c85d9 | ||
|
|
c2796f66fb | ||
|
|
f57fcd25c9 | ||
|
|
9bda7dee7f | ||
|
|
7e1f21b02b | ||
|
|
a83acabf1a | ||
|
|
fb99c76b0c | ||
|
|
cc0ff1f5e7 | ||
|
|
afbfd9ed43 | ||
|
|
fcad04461f | ||
|
|
ca98b5a210 | ||
|
|
bd72ef84ed | ||
|
|
cb3c62fb6c | ||
|
|
2976220e4d | ||
|
|
192371c07b | ||
|
|
708a3f4ad9 | ||
|
|
4718c653f7 | ||
|
|
fbcdfa97cf | ||
|
|
4e39762b46 | ||
|
|
4239e8d5fb | ||
|
|
c8ee455865 | ||
|
|
d5be7b8c57 | ||
|
|
bc2f4f1ef3 | ||
|
|
e72baf3c1f | ||
|
|
b2b52b6af7 | ||
|
|
4f417ccf0f | ||
|
|
780c3f5f6d | ||
|
|
f9eff12f94 | ||
|
|
e912a8d4e0 | ||
|
|
224af292d6 | ||
|
|
e07a4457ec | ||
|
|
58cd0495e6 | ||
|
|
6b9b2afbef | ||
|
|
c55a8d09a2 | ||
|
|
71b06f92ec | ||
|
|
692ec8ecbf | ||
|
|
523ea7f314 | ||
|
|
f48444f21d | ||
|
|
b4c5b3cbe6 | ||
|
|
ed88ae013d | ||
|
|
0fe7937808 | ||
|
|
564f57b879 | ||
|
|
a47dc8e767 | ||
|
|
41e192acb0 | ||
|
|
ff4fb191b8 | ||
|
|
2feee80168 | ||
|
|
59fed8a9cc | ||
|
|
7cfca0a650 | ||
|
|
6f1a5fd3a9 | ||
|
|
cd6869778a | ||
|
|
2e8d84430e | ||
|
|
b8f32a6d8d | ||
|
|
11ab37baf1 | ||
|
|
9dbde43a44 | ||
|
|
2a24d71ee3 | ||
|
|
134444c20a | ||
|
|
44b87f178e | ||
|
|
666636d554 | ||
|
|
5ec9d59382 | ||
|
|
addc7622ab | ||
|
|
1c7c3a4777 | ||
|
|
2a46b02487 | ||
|
|
5974515362 | ||
|
|
3fab1d9c6f | ||
|
|
f9b8437981 | ||
|
|
b0c73bc63e | ||
|
|
a283f2ee5d | ||
|
|
934823fad4 | ||
|
|
351ed795ca | ||
|
|
a335d02156 | ||
|
|
3e0747164a | ||
|
|
8ad396d3ba | ||
|
|
26b54f0c00 | ||
|
|
de5b58fff6 | ||
|
|
b9b329dde4 | ||
|
|
2581500307 | ||
|
|
ef5c20a9f2 | ||
|
|
01aee7c028 | ||
|
|
569c7f4b48 | ||
|
|
bb849f175c | ||
|
|
ede0eea944 | ||
|
|
47150cca41 | ||
|
|
86df98a6a4 | ||
|
|
8dd5e90f8b | ||
|
|
f31023e2ed | ||
|
|
740d2e5a6c | ||
|
|
20d37e1ac9 | ||
|
|
f3f79ed499 | ||
|
|
c581222022 | ||
|
|
a3fd738b46 | ||
|
|
3f16e973eb | ||
|
|
cb1013762f | ||
|
|
74e275c5d0 | ||
|
|
dbeb513f65 | ||
|
|
269246659b | ||
|
|
6f4561a38f | ||
|
|
12507190d8 | ||
|
|
85dac65030 | ||
|
|
4f1708619e | ||
|
|
84b545c37d | ||
|
|
b24086e679 | ||
|
|
8308376f53 | ||
|
|
137f371528 | ||
|
|
5707039002 | ||
|
|
d4fabf0fe9 | ||
|
|
a4a0630102 | ||
|
|
439ac7eac6 | ||
|
|
ef1b82f426 | ||
|
|
51fe5d133e | ||
|
|
e7b9f3f121 | ||
|
|
14b4d10be3 | ||
|
|
36a1123535 | ||
|
|
03750e3145 | ||
|
|
ff4879a984 | ||
|
|
7a6a543df5 | ||
|
|
edda20940b | ||
|
|
987a4bd04c | ||
|
|
35d20bd42c | ||
|
|
97983c423f | ||
|
|
68050e3a35 | ||
|
|
4f2801d901 | ||
|
|
aaa3dfc304 | ||
|
|
7a9bebe035 | ||
|
|
ce86510d8e | ||
|
|
9d15b035c5 | ||
|
|
156a05f6ed | ||
|
|
22de053cd3 | ||
|
|
13845adbd5 | ||
|
|
ef68f2da16 | ||
|
|
9114908521 | ||
|
|
f565edc655 | ||
|
|
b0e26b8ae2 | ||
|
|
b35d8406a3 | ||
|
|
3910486b17 | ||
|
|
60ba77a06f | ||
|
|
f86ea04a02 | ||
|
|
9e185fa33c | ||
|
|
5142a8c62d | ||
|
|
db4b5f0452 | ||
|
|
042ad8465e | ||
|
|
ebfd9d3af7 | ||
|
|
c0ae0c7ffa | ||
|
|
861a065849 | ||
|
|
3884909965 | ||
|
|
6a0788c2a1 | ||
|
|
bd60f91734 | ||
|
|
b58c3fd467 | ||
|
|
9d431af0b9 | ||
|
|
cb1ebc6a72 | ||
|
|
a198bce3e2 | ||
|
|
e5c50a0dbb | ||
|
|
caa1dfaf34 | ||
|
|
eb01a0b17d | ||
|
|
1add575d26 | ||
|
|
4a52d327ce | ||
|
|
ef68fe1c85 | ||
|
|
83fa9c9ebd | ||
|
|
1a774bfcfa | ||
|
|
db9ec6bfd5 | ||
|
|
5736306f8c | ||
|
|
67cb508944 | ||
|
|
cbcfb8c063 | ||
|
|
da846dcbbb | ||
|
|
e7280bf017 | ||
|
|
9692f57663 | ||
|
|
f0655893eb | ||
|
|
f9e59905b7 | ||
|
|
c115c7c757 | ||
|
|
4edcf807ad | ||
|
|
416d8c823c | ||
|
|
bea158b8f2 | ||
|
|
39c6560a2d | ||
|
|
509b004014 | ||
|
|
f1b7e1164b | ||
|
|
3a2f626cb9 | ||
|
|
7cde6fd73c | ||
|
|
06d6eecaf0 | ||
|
|
c9ed29261d | ||
|
|
87fe5cdd6f | ||
|
|
f74819f50a | ||
|
|
98c8af5b9c | ||
|
|
ca17f1cfc9 | ||
|
|
74e2582ff9 | ||
|
|
8bbbf9b3d8 | ||
|
|
048629dd32 | ||
|
|
fe03da1d97 | ||
|
|
0a6622159f | ||
|
|
7a5cdefcfa | ||
|
|
0a3d89d0e2 | ||
|
|
882b97da5e | ||
|
|
fa405752da | ||
|
|
2d7571af5a | ||
|
|
07a57969ac | ||
|
|
01b7b1cab8 | ||
|
|
fee3d8f672 | ||
|
|
f1628e46fa | ||
|
|
17cb4479cb | ||
|
|
b52511113b | ||
|
|
95cc15ec4c | ||
|
|
1a4b4655c8 | ||
|
|
9fc69ba7e6 | ||
|
|
8ce31d3d80 | ||
|
|
d097f9a741 | ||
|
|
8686a35405 | ||
|
|
a92d90db4a | ||
|
|
bfa0e0e144 | ||
|
|
d3b2e1f24b | ||
|
|
bc90da4cf2 | ||
|
|
61afc35ac8 | ||
|
|
00e9396590 | ||
|
|
bbedb13f3e | ||
|
|
55cfbae8a7 | ||
|
|
bfa19eec0c | ||
|
|
07851ccde2 | ||
|
|
ad5776cfba | ||
|
|
a13478fa64 | ||
|
|
85a1c10d26 | ||
|
|
6918282e31 | ||
|
|
fd778c25e6 | ||
|
|
af344c8ae2 | ||
|
|
c505f8cf86 | ||
|
|
52320e0cd9 | ||
|
|
214db474c2 | ||
|
|
1bffabc94d | ||
|
|
7c8a5e02f2 | ||
|
|
a49c76c188 | ||
|
|
1f16123865 | ||
|
|
e85bbb7bd9 | ||
|
|
34fc3afb1e | ||
|
|
67479a9c1e | ||
|
|
2a8cedca58 | ||
|
|
57fc4aa06b | ||
|
|
39f3f73896 | ||
|
|
f4e907de36 | ||
|
|
72f7e1fb27 | ||
|
|
3dc109c3dd | ||
|
|
83df89c115 | ||
|
|
4070409a59 | ||
|
|
bb732d7606 | ||
|
|
70963c06b8 | ||
|
|
a1a84b6928 | ||
|
|
eefaf5fe50 | ||
|
|
9500b2d678 | ||
|
|
221a49bb4a | ||
|
|
3c1ceb4995 | ||
|
|
babe17838b | ||
|
|
f5d11f9ba6 | ||
|
|
e52c7c97cf | ||
|
|
ebdc5ef566 | ||
|
|
939d917b7b | ||
|
|
2afb868edc | ||
|
|
a5fe4a345b | ||
|
|
629bf7f51f | ||
|
|
350854ab6b | ||
|
|
cc980758fd | ||
|
|
0f1ce43cc5 | ||
|
|
5e1a9cdc20 | ||
|
|
550ce10c92 | ||
|
|
6080c59bd7 | ||
|
|
273a35d42f | ||
|
|
2f195fe02e | ||
|
|
0715ca9d85 | ||
|
|
a8d564d74d | ||
|
|
89ba3e0fa4 | ||
|
|
eebcfe94d9 | ||
|
|
ea9b0370c4 | ||
|
|
e264511a6e | ||
|
|
1ec10fbcfc | ||
|
|
53d154fa59 | ||
|
|
274620d17b | ||
|
|
06db7d2360 | ||
|
|
2b3549e4df | ||
|
|
d16478792c | ||
|
|
7ba2ef6420 | ||
|
|
b5d98796b7 | ||
|
|
97c17cfe84 | ||
|
|
4b4e200090 | ||
|
|
232d38440d | ||
|
|
341bd9bef0 | ||
|
|
2f4d04c722 | ||
|
|
148ae40e1a | ||
|
|
9cb0ced4f6 | ||
|
|
9a3b70de43 | ||
|
|
742b8f7eae | ||
|
|
abc58941e3 | ||
|
|
aac200bfd3 | ||
|
|
27d3297d61 | ||
|
|
72431b2c82 | ||
|
|
3ca48e5848 | ||
|
|
14784ec80f | ||
|
|
127741eb27 | ||
|
|
2ed987fe10 | ||
|
|
7aaa1876c8 | ||
|
|
390ffb83a2 | ||
|
|
d63e51b8cb | ||
|
|
7bd8d64649 | ||
|
|
f9e6f49ede | ||
|
|
b701d2863f | ||
|
|
cf47093227 | ||
|
|
81d0aea66c | ||
|
|
506ca35be8 | ||
|
|
1cd6718489 | ||
|
|
153c5db4b5 | ||
|
|
ac9367a715 | ||
|
|
081dae0b90 | ||
|
|
e286390aec | ||
|
|
cf88eb2124 | ||
|
|
3226e4fd85 | ||
|
|
cab94eb63d | ||
|
|
bbd5d6805e | ||
|
|
2904ba3085 | ||
|
|
ee609061ed | ||
|
|
6c98a4e899 | ||
|
|
eef9ee7f3c | ||
|
|
2cb4575db1 | ||
|
|
e7bcad80cc | ||
|
|
353839a1dc | ||
|
|
e6d30744ba | ||
|
|
b67932f448 | ||
|
|
c185c42b80 | ||
|
|
4648ace84e | ||
|
|
ccdf50dbe0 | ||
|
|
852a3dc8bb | ||
|
|
d49b7bd201 | ||
|
|
07f2ceed6e | ||
|
|
751c7b5787 | ||
|
|
e82384dff8 | ||
|
|
92d3a85147 | ||
|
|
c5762a0baf | ||
|
|
dc40b04a18 | ||
|
|
1cca5a3085 | ||
|
|
4fe4ca638c | ||
|
|
b8c7110c63 | ||
|
|
288365b74b | ||
|
|
b57e17c01c | ||
|
|
711ab6a556 | ||
|
|
7a12f54587 | ||
|
|
056f4b3e80 | ||
|
|
730a7a4e15 | ||
|
|
a4b4dc885c | ||
|
|
8562a4bb46 | ||
|
|
d3b56432e0 | ||
|
|
527c5be42c | ||
|
|
6c1d0c712a | ||
|
|
fd0ec5fd31 | ||
|
|
f5f19c9b52 | ||
|
|
642ef4f5b7 | ||
|
|
66b65e600b | ||
|
|
01a130ef8c | ||
|
|
c3bccb5412 | ||
|
|
b32e6e71f8 | ||
|
|
3b5875e283 | ||
|
|
da42fe97ae | ||
|
|
56bb7d36eb | ||
|
|
d86b7345d7 | ||
|
|
c1c17160ff | ||
|
|
d400c843e4 | ||
|
|
4e67ab50fe | ||
|
|
74c9e21d55 | ||
|
|
fefcdf6ffc | ||
|
|
0c14d71fa8 | ||
|
|
5170e4eee2 | ||
|
|
8ebfd781c3 | ||
|
|
4e8988bf57 | ||
|
|
c26734751f | ||
|
|
ca6b4e7d82 | ||
|
|
cb4a94d036 | ||
|
|
300ddb810f | ||
|
|
0b687310fd | ||
|
|
3e741ac2f9 | ||
|
|
f65b23ca26 | ||
|
|
92527e5847 | ||
|
|
db9b8d2ba1 | ||
|
|
4866447f33 | ||
|
|
2b345ff840 | ||
|
|
b47bf437a9 | ||
|
|
c44149050c | ||
|
|
0576123a77 |
@@ -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,6 +1,5 @@
|
||||
# Frontend config ************************************************************************************************************************************************************************************************
|
||||
# Set CI to false while running the app locally
|
||||
CI=false
|
||||
|
||||
# Set it to the URL form where the app home page will be accessed
|
||||
PUBLIC_URL=https://app.opensignlabs.com/
|
||||
# Set it to true if you want to generate the Sourcemap for debugging
|
||||
@@ -8,13 +7,13 @@ GENERATE_SOURCEMAP=false
|
||||
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
|
||||
REACT_APP_SERVERURL=https://app.opensignlabs.com/api/app
|
||||
# A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API.
|
||||
REACT_APP_APPID=aDsADsqweqwe
|
||||
REACT_APP_APPID=opensign
|
||||
# Google tag manager container id for configuring GTM on all pages of the app
|
||||
REACT_APP_GTM=GTM-123N7LD
|
||||
|
||||
# Backend ExpressJS config ****************************************************************************************************************************************************************************************
|
||||
# A 12 character long random app identifier. The value of this should be same as REACT_APP_APPID which is a variable used by Frontend React App.
|
||||
APP_ID=aDsADsqweqwe
|
||||
APP_ID=opensign
|
||||
# Name of the app. It will be visible in the verification emails sent out.
|
||||
appName=open_sign_server
|
||||
# A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database.
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# Clone the repo and rename this file to .env if you want to run the frontend react app locally for some quick contributions to the project.
|
||||
# When used as it is, this env config will connect to our hosted UAT backend
|
||||
# Note that any data in staging will get cleared from time to time and you may have to sign up again and again
|
||||
|
||||
# Set CI to false while running the app locally
|
||||
CI=false
|
||||
# Set it to the URL form where the app home page will be accessed
|
||||
PUBLIC_URL=http://localhost:3000
|
||||
# Set it to true if you want to generate the Sourcemap for debugging
|
||||
GENERATE_SOURCEMAP=false
|
||||
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
|
||||
REACT_APP_SERVERURL=https://staging-app.opensignlabs.com/api/app
|
||||
# A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API.
|
||||
REACT_APP_APPID=opensignstgn
|
||||
@@ -2,8 +2,6 @@
|
||||
# When used as it is, this env config will connect to our hosted UAT backend
|
||||
# Note that any data in staging will get cleared from time to time and you may have to sign up again and again
|
||||
|
||||
# Set CI to false while running the app locally
|
||||
CI=false
|
||||
# Set it to the URL form where the app home page will be accessed
|
||||
PUBLIC_URL=http://localhost:3000
|
||||
# Set it to true if you want to generate the Sourcemap for debugging
|
||||
@@ -11,18 +9,18 @@ GENERATE_SOURCEMAP=false
|
||||
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
|
||||
REACT_APP_SERVERURL=http://localhost:8080/app
|
||||
# A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API.
|
||||
REACT_APP_APPID=opensignstgn
|
||||
REACT_APP_APPID=opensign
|
||||
|
||||
|
||||
# Backend ExpressJS config ****************************************************************************************************************************************************************************************
|
||||
# A 12 character long random app identifier. The value of this should be same as REACT_APP_APPID which is a variable used by Frontend React App.
|
||||
APP_ID=opensignstgn
|
||||
APP_ID=opensign
|
||||
# Name of the app. It will be visible in the verification emails sent out.
|
||||
appName=open_sign_server
|
||||
# A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database.
|
||||
MASTER_KEY=XnAadwKxxByMr
|
||||
# Mongodb URI to connect to
|
||||
MONGODB_URI=mongodb://host.docker.internal:27017/OpenSignDB
|
||||
MONGODB_URI=mongodb://mongo-container:27017/OpenSignDB
|
||||
# Path on which APIs should be mounted. Do not change this. This variable shall be removed & value hardcoded in the source code in coming versions.
|
||||
PARSE_MOUNT=/app
|
||||
# Set it to the URL from where APIs will be accessible to the NodeJS functions, for local development it should be localhost:3000/api/app (use your local port number instead)
|
||||
@@ -42,17 +40,17 @@ 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=XXXXX
|
||||
MAILGUN_API_KEY=
|
||||
MAILGUN_DOMAIN=mail.yourdomain.com
|
||||
MAILGUN_SENDER=postmaster@mail.yourdomain.com
|
||||
SMTP_ENABLE=
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=
|
||||
SMTP_USER_EMAIL=
|
||||
SMTP_PASS=
|
||||
SMTP_HOST=smtp.yourhost.com
|
||||
SMTP_PORT=443
|
||||
SMTP_USER_EMAIL=mailer@yourdomain.com
|
||||
SMTP_PASS=password # if your password includes spaces then write password in single quotes ('asdf pasd asdf bgds').
|
||||
|
||||
|
||||
# Base64 encoded PFX or p12 document signing certificate file *********************************************************************************************************************
|
||||
|
||||
@@ -2,7 +2,7 @@ name: ci
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'staging'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
@@ -10,10 +10,10 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- image: amolshejole/OpenSign
|
||||
dockerfile: apps/OpenSign/Dockerfile
|
||||
- image: amolshejole/OpenSignServer
|
||||
dockerfile: apps/OpenSignServer/Dockerfile
|
||||
- image: opensign/opensign
|
||||
dockerfile: apps/OpenSign/Dockerhubfile
|
||||
- image: opensign/opensignserver
|
||||
dockerfile: apps/OpenSignServer/Dockerhubfile
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
@@ -31,8 +31,11 @@ jobs:
|
||||
with:
|
||||
images: ${{ matrix.image }}
|
||||
-
|
||||
name: Debug - List files
|
||||
name: Debug - List files opensign
|
||||
run: ls -R apps/OpenSign/ # Adjust the path as needed
|
||||
-
|
||||
name: Debug - List files opensignserver
|
||||
run: ls -R apps/OpenSignServer/ # Adjust the path as needed
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
name: Deploy to GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- feat-docs
|
||||
# Review gh actions docs if you want to further define triggers, paths, etc
|
||||
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy to GitHub Pages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: npm
|
||||
- name: Install dependencies
|
||||
working-directory: ./docs
|
||||
run: npm ci
|
||||
- name: Clean api-docs
|
||||
working-directory: ./docs
|
||||
run: npm run docusaurus clean-api-docs all
|
||||
- name: Build api-docs
|
||||
working-directory: ./docs
|
||||
run: npm run docusaurus gen-api-docs all
|
||||
- name: Build website
|
||||
working-directory: ./docs
|
||||
run: npm run build
|
||||
|
||||
# Popular action to deploy to GitHub Pages:
|
||||
# Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Build output to publish to the `gh-pages` branch:
|
||||
publish_dir: ./docs/build
|
||||
# The following lines assign commit authorship to the official
|
||||
# GH-Actions bot for deploys to `gh-pages` branch:
|
||||
# https://github.com/actions/checkout/issues/13#issuecomment-724415212
|
||||
# The GH actions bot is used by default if you didn't specify the two fields.
|
||||
# You can swap them out with your own user credentials.
|
||||
user_name: github-actions[bot]
|
||||
user_email: 41898282+github-actions[bot]@users.noreply.github.com
|
||||
@@ -1,149 +1,29 @@
|
||||
|
||||
# INSTALLATON INSTRUCTIONS
|
||||
# INSTALLATON INSTRUCTIONS HAS A NEW HOME
|
||||
|
||||
You can use our app as a cloud version from [OpenSignLabs](https://www.opensignlabs.com)
|
||||
We've updated our installation instructions and moved them to our dedicated documentation portal to provide you with a better experience and updated guidance. Whether you're looking to self-host OpenSign™ or contribute to its development, you can find detailed instructions tailored to your needs at [docs.opensignlabs.com](https://docs.opensignlabs.com).
|
||||
|
||||
or follow below instructions to install it on your own infrastructure.
|
||||
## Self Hosting
|
||||
|
||||
- Localhost(Frontend only)
|
||||
- Digital Ocean
|
||||
- Localhost(Docker)
|
||||
If you're interested in self-hosting OpenSign™, our new documentation portal provides comprehensive, step-by-step instructions to help you set up OpenSign™ in your own environment. This section is designed for system administrators and those looking to deploy OpenSign™ within their organization or for personal use.
|
||||
|
||||
## Localhost(Frontend only)
|
||||
- **Access Self Hosting Instructions:** [Self Hosting Guide](https://docs.opensignlabs.com/docs/category/docker)
|
||||
|
||||
This is the easiest way to run the frontend application for development or testing purpose without the hassle of installing backend & DB. All the features including document upload, signing, email notifications will work seamlessly from staging backend.
|
||||
## Contributors
|
||||
|
||||
**Warning:** All data, including user accounts and documents, is stored in the staging backend. This data will be erased every time there is a merge to the main branch. As a result, you may need to recreate user accounts repeatedly. If you find that login attempts are failing, it is likely because the data has been cleared due to a recent merge.
|
||||
For developers and contributors who are looking to build upon or contribute to OpenSign™, we've prepared a separate section that covers the setup, development environment configuration, and guidelines for contributing to the OpenSign™ project.
|
||||
|
||||
Below are the steps to follow -
|
||||
- [Clone the repository](https://help.github.com/articles/cloning-a-repository/) to your local machine using below command -
|
||||
```
|
||||
git clone https://github.com/OpenSignLabs/OpenSign.git
|
||||
```
|
||||
- Copy the .env.frontend_dev file to apps/OpenSign/.env using below command(on mac & linux). For windows use COPY command instead.
|
||||
```
|
||||
cp .env.frontend_dev apps/OpenSign/.env
|
||||
```
|
||||
- CD to /apps/OpenSign directory
|
||||
- Install NPM packages using
|
||||
```
|
||||
npm install
|
||||
```
|
||||
- Run the project locally using
|
||||
```
|
||||
npm run start
|
||||
```
|
||||
You should be able to access the application from http://localhost:3000 after this.
|
||||
Create an account by signing-up and start contributing.
|
||||
- **Access Contributor Instructions:** [Contributors Guide](https://docs.opensignlabs.com/docs/contribute/INSTALLATION)
|
||||
|
||||
## Additional Resources
|
||||
|
||||
## Digital Ocean
|
||||
Should you need further assistance or have any questions, please feel free to reach out to us through our support channels:
|
||||
|
||||
OpenSign application consistes of 3 components -
|
||||
- ReactJS frontend
|
||||
- NodeJS API
|
||||
- MongoDB database
|
||||
- **Twitter:** [OpenSignHQ on Twitter](https://twitter.com/OpenSignHQ)
|
||||
- **Facebook:** [OpenSign on Facebook](https://www.facebook.com/profile.php?id=61551030403669)
|
||||
- **LinkedIn:** [OpenSign™ on LinkedIn](https://www.linkedin.com/company/opensign%E2%84%A2/)
|
||||
- **Discord:** [Join our Discord Community](https://discord.com/invite/opensign)
|
||||
- **YouTube:** [OpenSignHQ on YouTube](https://www.youtube.com/@opensignhq)
|
||||
|
||||
You can install all 3 components on digital ocean using the button below -
|
||||
We are committed to providing you with the support you need to successfully install and use OpenSign™. Visit our website at [www.opensignlabs.com](https://www.opensignlabs.com) for more information about our project and its features.
|
||||
|
||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0)
|
||||
|
||||
## You will need to create an AWS S3 bucket or digital ocean space in order to store your uploaded documents
|
||||
|
||||
### AWS S3 -
|
||||
- Step 1 : Create a S3 bucket
|
||||
- Login to AWS console
|
||||
- Navigate to S3 under services
|
||||
- Hit "Create Bucket" button on upper right corner
|
||||
- Remove the check from "block all public access" checkbox(we need this in order to provide access to not-logged in users after OTP verification)
|
||||
- Set bucket versioning and tags as per your requirements
|
||||
- Hit "Create bucket" button
|
||||
- Step 2 : Create IAM user and provide access to AWS bucket
|
||||
- Search for "IAM" on the search bar in AWS console
|
||||
- On IAM dashboard, click the number of users(count) under IAM resources table
|
||||
- Hit "create user" button on the upper right corner of the page
|
||||
- Enter the user name & click next
|
||||
- Click create policy, search for S3 and provide the Read, Write & list permissions
|
||||
- Click next and click "Create user"
|
||||
- Step 3 : Generate Credentials
|
||||
- Go to IAM/Users in AWS console
|
||||
- Hit the hyperlink for the user created in the previous step
|
||||
- Click the "Security credentials" tab
|
||||
- Scroll down to "Access keys" and hit "Create access key"
|
||||
- In the next step select "Application running outside AWS"
|
||||
- Add a description tag if needed & hit "Create access key"
|
||||
- In the next step you will see "Access key" and "Secret Access key". Copy both the values.
|
||||
- Set the value of "Access key" to "DO_ACCESS_KEY_ID" environment variable
|
||||
- Set the value of "Secret Access key" to "DO_SECRET_ACCESS_KEY" environment variable
|
||||
- Step 3 : Copy bucket credentials
|
||||
- Visit "Amazon S3 -> Buckets" in aws console
|
||||
- Click the bucket created in previous steps & visit the properties tab
|
||||
- Under "Bucket overview" you will find the value of AWS region(for ex. ap-south-1). Set that value to env variable "DO_REGION"
|
||||
- You can create the value for "DO_ENDPOINT" env variable by appending the region value to amazonaws.com (for ex. s3.ap-south-1.amazonaws.com)
|
||||
- You can create the value for "DO_BASEURL" by adding the bucketname in front of the endpoint value(for ex. https://bucketname.s3.ap-south-1.amazonaws.com)
|
||||
|
||||
Visit below link if you face any issues while following the above instructions -
|
||||
- https://repost.aws/knowledge-center/create-access-key
|
||||
|
||||
|
||||
## Localhost(Docker)
|
||||
|
||||
For local Setup we need to need following prerequisite:
|
||||
|
||||
Environment Varaibles:
|
||||
|
||||
| Environment Varibale | Value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| CI | false | Set CI to false while running the app locally |
|
||||
| PUBLIC_URL | http://localhost:3000 | Set it to the URL form where the app home page will be accessed |
|
||||
| GENERATE_SOURCEMAP | false | Set it to true if you want to generate the Sourcemap for debugging |
|
||||
| REACT_APP_SERVERURL | http://localhost:8080/app | Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead) |
|
||||
| REACT_APP_APPID | opensignstgn | A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API. |
|
||||
| APP_ID | opensignstgn | A 12 character long random app identifier. The value of this should be same as REACT_APP_APPID which is a variable used by Frontend React App. |
|
||||
| appName | open_sign_server | Name of the app. It will be visible in the verification emails sent out. |
|
||||
| MASTER_KEY | XnAadwKxxByMr | A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database. |
|
||||
| MONGODB_URI | mongodb://host.docker.internal:27017/OpenSignDB | Mongodb URI to connect to |
|
||||
| PARSE_MOUNT |/app | Path on which APIs should be mounted. Do not change this. This variable shall be removed & value hardcoded in the source code in coming versions. |
|
||||
| SERVER_URL | http://127.0.0.1:8080/app | Set it to the URL from where APIs will be accessible to the NodeJS functions, for local development it should be localhost:3000/api/app (use your local port number instead) |
|
||||
| DO_SPACE | DOSPACENAME | Digital ocean space name or AWS S3 bucket name for uploading documents |
|
||||
| DO_ENDPOINT | ams3.digitaloceanspaces.com | Digital ocean spaces endpoint or AWS S3 endpoint for uploading documents |
|
||||
| DO_BASEURL |https://DOSPACENAME.ams3.digitaloceanspaces.com | Digital ocean baseurl or AWS S3 base URL |
|
||||
| DO_ACCESS_KEY_ID | YOUR_S3_ACCESS_ID | Digital ocean spaces access key ID or AWS s3 Access key ID for uploading the docs |
|
||||
| DO_SECRET_ACCESS_KEY | YOUR_S3_ACCESS_KEY | Digital ocean spaces secret access key or AWS s3 secret access key for uploading the docs |
|
||||
| DO_REGION | YOUR_S3_REGION | Digital ocean spaces region or AWS s3 region |
|
||||
| USE_LOCAL | FALSE | To use local file storage to save file |
|
||||
| MAILGUN_API_KEY | YOUR_MAILGUNAPI_KEY | Mailgun API Key |
|
||||
| MAILGUN_DOMAIN | YOUR_MAILGUNAPI_DOMAIN | Mailgun API Domain |
|
||||
| MAILGUN_SENDER | - | Mailgun Sender Mail ID |
|
||||
| PFX_BASE64 | - | Base64 encoded PFX or p12 document signing certificate file. You can generate base64 encoded self sign certificate using the passphrase `emudhra` |
|
||||
|
||||
# Steps to Generate Self Sign Certificate
|
||||
```
|
||||
# execute below command and use passphrase emudhra
|
||||
openssl genrsa -des3 -out ./cert/local_dev.key 2048
|
||||
openssl req -key ./cert/local_dev.key -new -x509 -days 365 -out ./cert/local_dev.crt
|
||||
openssl pkcs12 -inkey ./cert/local_dev.key -in ./cert/local_dev.crt -export -out ./cert/local_dev.pfx
|
||||
openssl base64 -in ./cert/local_dev.pfx -out ./cert/base64_pfx
|
||||
```
|
||||
|
||||
# CORS Configuration
|
||||
|
||||
As document storage is delegated to S3-compatible services that reside in a different host than the OpenSign one, document operations (loading, storing, deleting) are subject to [Cross-Origin Resource Sharing](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) restriction policies; as a consequence, OpenSign app may fail with (browser console) errors like the following:
|
||||
```
|
||||
Access to fetch at 'https://foo.nyc3.digitaloceanspaces.com/exported_file_4627_0000-00-00T00%3A45%3A43.344Z.pdf'
|
||||
from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header
|
||||
is present on the requested resource. If an opaque response serves your needs, set the request's mode to
|
||||
'no-cors' to fetch the resource with CORS disabled.
|
||||
```
|
||||
|
||||
In order to address this, your document storage system must be instructed to accept requests from other hosts; below the relevant documentation links:
|
||||
- [How to Configure CORS on DigitalOcean Spaces](https://docs.digitalocean.com/products/spaces/how-to/configure-cors/)
|
||||
- [Configuring cross-origin resource sharing on AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html)
|
||||
|
||||
# Build Local Environment
|
||||
|
||||
Command to build project -
|
||||
- Execute `make build`
|
||||
|
||||
Command to run project -
|
||||
- Execute `make run`
|
||||
@@ -1,7 +1,6 @@
|
||||
build:
|
||||
cp .env.local_dev .env
|
||||
rm -rf apps/OpenSign/public/mfbuild
|
||||
cd microfrontends/SignDocuments && npm install && npm run build
|
||||
cd apps/OpenSign && cp ../../.env.local_dev .env && npm install && npm run build
|
||||
docker compose up --build --force-recreate
|
||||
|
||||
run:
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
|
||||
<a href="https://www.opensignlabs.com/">Website</a>
|
||||
<span> • </span>
|
||||
<a href="https://www.opensignlabs.com">Docs</a>
|
||||
<a href="https://docs.opensignlabs.com">Help Docs</a>
|
||||
<span> • </span>
|
||||
<a href="https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1">API Docs</a>
|
||||
<span> • </span>
|
||||
<a href="https://www.opensignlabs.com/blog">Blog</a>
|
||||
<span> • </span>
|
||||
@@ -22,7 +24,7 @@
|
||||
<a href="https://www.linkedin.com/company/opensign%E2%84%A2/about/">LinkedIn</a>
|
||||
|
||||
|
||||
## The open-source document e-signing solution
|
||||
## The premier open source document signing solution(DocuSign alternative)
|
||||
|
||||
---
|
||||
</div>
|
||||
@@ -38,32 +40,36 @@
|
||||
7. [Acknowledgments](#acknowledgments)
|
||||
|
||||
---
|
||||
Please star ⭐ the repo to support us! 😀
|
||||
|
||||
### Introduction
|
||||
|
||||
Welcome to OpenSign, an open-source document e-signing solution designed to provide a secure, reliable, and free alternative to commercial platforms like DocuSign, PandaDoc, SignNow, Adobe Sign, Smartwaiver, SignRequest, HelloSign & Zoho sign. Developed under the OpenSignLabs organization, our mission is to democratize the e-signing process, making it accessible and straightforward for everyone.
|
||||
Welcome to OpenSign, the premier open source docusign alternative - document e-signing solution designed to provide a secure, reliable and free alternative to commercial esign platforms like DocuSign, PandaDoc, SignNow, Adobe Sign, Smartwaiver, SignRequest, HelloSign & Zoho sign. Our mission is to democratize the document signing process, making it accessible and straightforward for everyone.
|
||||
|
||||
---
|
||||
|
||||
### Features
|
||||
|
||||
- **Secure PDF E-Signing:** With the help of Robust encryption algorithms, OpenSign™ ensures maximum security, privacy & compatibility.
|
||||
- **Annotate Documents:** OpenSign™ allows you to annotate PDF documents with an advanced signing pad that comes with hand drawn signatures support as well as uploaded images & saved signatures for the simplest signing experience.
|
||||
- **User-Friendly Interface:** OpenSign™ was built while keeping Intuitive design in mind for ease of use. Features like "Sign yourself", "One click signatures" and "OpenSign Drive" makes it stand out of the crowd and even makes it better than a lot of so-called industry leaders.
|
||||
- **Multi-signer Support:** OpenSign's ability to invite multiple signers for signing along with the ability to invite witnesses & being able to enforce signing in a sequence makes it the only open source solution that is fully loaded and allows it to compete head-to-head with established players.
|
||||
- **Secure PDF E-Signing:** With the help of robust encryption algorithms, OpenSign™ ensures maximum security, privacy & compatibility. Now sign unlimited documents even on the [cloud hosted free version of OpenSign](https://www.opensignlabs.com/).
|
||||
- **Annotate Documents:** OpenSign™ allows you to annotate PDF documents with an advanced signing pad that allows hand drawn signatures, uploaded images, typed signatures & saved signatures for the simplest open source document signing experience ever.
|
||||
- **User-Friendly Interface:** OpenSign™ was built while keeping Intuitive design in mind for ease of use. Features like "Sign yourself", "Templates", "One click signatures" and "OpenSign™ Drive" makes it stand out of the crowd and even makes it better than a lot of so-called industry leaders. OpenSign intends to provide the best document signing experience in the open source ecosystem.
|
||||
- **Multi-signer Support:** OpenSign's ability to invite multiple signers for signing along with the ability to invite by sharing signing links & being able to enforce signing in a sequence makes it the only open source solution that is fully loaded and allows it to compete head-to-head with established players in e-signature space.
|
||||
- **Email Unique Code(OTP) verification support for guest signers:** With OpenSign™, your documents are fully secure even when being signed by guest users. Guest signers can only sign the document after entering a unique code sent to their email address.
|
||||
- **"Expiring Docs" & "Rejection":** You can set documents to expire after certain number of days after which nobody will be able to sign it. Not just this, OpenSign also allows signers to reject signing a document.
|
||||
- **Beautiful email templates:** All document signing invitations, completion notifications & reminders are formatted using great looking email templates.
|
||||
- **PDF Template Creation(coming soon):** OpenSign™ allows you to create and store PDF document templates for repeated use thereby saving you a lot of time.
|
||||
- **OpenSign™ Drive:** It is a centralised secure vault for your signed documents that makes storing, signing, organizing, sharing & achieving your docs a breeze.
|
||||
- **"Expiring Docs" & "Rejection":** You can set documents to expire after certain number of days after which nobody will be able to sign. Not just this, OpenSign™ also allows signers to reject signing a document with a reason that will be promptly shared with the sender.
|
||||
- **Beautiful email templates:** All document signing invitations, completion notifications & reminders are formatted using great looking email templates. Not just this, you are even allowed to customise the email templates making your free document signing invitations look the way you always wanted them to be.
|
||||
- **PDF Template Creation:** OpenSign™ allows you to create and store PDF document templates for repeated use thereby saving you a lot of time & collect e-signatures seamlessly.
|
||||
- **OpenSign™ Drive:** It is a centralised secure vault for your digital documents that makes storing, signing, organizing, sharing & archieving your docs a breeze.
|
||||
- **Audit Trails & completion certificate:** Being a security focused solution, OpenSign™ makes it a top priority to save detailed logs for tracking document activities along with time-stamps, IP addresses, email IDs & phone numbers. A completion certificate is generated as soon as document is completed which contains all the document related logs for added safety.
|
||||
- **API Support(coming soon):** OpenSign™ API allows seamless integration into existing systems and software. APIs will soon be available as a cloud hosted solution.
|
||||
- **Integrations:** Seamless integrations with various Cloud storage systems, CRMs & enterprise platforms is coming soon.
|
||||
|
||||
<img src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/b37ff443-7099-4273-9aeb-21c46d7154cf height='200'>
|
||||
<img src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/86db91b7-6c2f-4885-a33c-58f4fd35ec89 height='200'>
|
||||
<img src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/56835efd-c40e-42de-a206-20202c293876 height='200'>
|
||||
|
||||
- **API Support:** OpenSign™ API allows seamless integration into existing systems and software. You can generate an API key from the app and refer the [official API docs](https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1) to start integrating it in your existing applications.
|
||||
- **Integrations:** The open source document signing experience becomes even more seamless because of integrations with various Cloud storage systems, CRMs & enterprise platforms. We also have a Zapier integration that allows you to integrate it with virtually any application.
|
||||
<img alt="Login page" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/8016edb6-7d6e-4185-ab72-82d2c4a16032" height='200' >
|
||||
<img alt="Dash_board" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/6e7b96f7-3154-4c25-bc1e-caf33925cc47" height='200'>
|
||||
<img alt="Widgets" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/7edb65bd-c265-4b3d-9758-38e79766c5c2" height='200'>
|
||||
<img alt="Request Signature" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/1697ae46-dcac-41c9-a081-aded25693846" height='200'>
|
||||
<img alt="Create template" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/c2060fda-50f1-4116-a532-51b94b48463a" height='200'>
|
||||
<img alt="Manage_Template" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/90310c74-8271-49bd-8c5f-89daa5a623c4" height='200'>
|
||||
<img alt="Folders" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/a52207e7-3b0e-497c-b5f6-747bc4e67918" height='200'>
|
||||
<img alt="OpenSign_Confetti" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/8f4c6a6e-4a73-439c-9f34-f426044d0a7f" height='200'>
|
||||
|
||||
---
|
||||
|
||||
@@ -75,7 +81,7 @@ Please refer to the [Installation Guide](INSTALLATION.md) for detailed instructi
|
||||
|
||||
### Usage
|
||||
|
||||
For comprehensive guidelines on how to use OpenSign, please consult our [User Manual](USAGE.md).
|
||||
For comprehensive guidelines on how to use OpenSign™, please consult our [User Manual](USAGE.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -87,13 +93,13 @@ We welcome contributions from the open-source community. For more information on
|
||||
|
||||
### License
|
||||
|
||||
OpenSign is licensed under the AGPL License. For more details, see the [LICENSE](LICENSE) file.
|
||||
OpenSign is licensed under the AGPL-3 License. For more details, see the [LICENSE](LICENSE) file.
|
||||
|
||||
---
|
||||
|
||||
### Acknowledgments
|
||||
|
||||
We would like to thank all our contributors and users for their support and feedback. Special thanks to [OpenSignLabs](https://github.com/OpenSignLabs) for spearheading this initiative.
|
||||
We would like to thank all our contributors and users for their support and feedback. Special thanks to [OpenSignLabs](https://www.opensignlabs.com) for spearheading this initiative.
|
||||
|
||||
---
|
||||
|
||||
@@ -133,6 +139,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>
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
The community is working hard to put together a great usage guide.
|
||||
You will soon see one of the best usage guides here.
|
||||
|
||||
Till then you can view the quick promo video by clicking the link below -
|
||||
|
||||
[<img src="https://i.ytimg.com/vi/szYaBZhXZNo/maxresdefault.jpg" width="50%">](https://www.youtube.com/watch?v=szYaBZhXZNo "OpenSign promo")
|
||||
Please visit the official documentation [here](https://docs.opensignlabs.com).
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env", "@babel/preset-react"]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:16
|
||||
FROM node:18
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
@@ -21,5 +21,5 @@ EXPOSE 3000
|
||||
# ENV NODE_ENV production
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT npm run start-dev
|
||||
ENTRYPOINT npm run start
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:18
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy package.json and package-lock.json first to leverage Docker cache
|
||||
COPY apps/OpenSign/package*.json ./
|
||||
|
||||
# Install application dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy the current directory contents into the container
|
||||
COPY apps/OpenSign/ .
|
||||
COPY apps/OpenSign/.husky .
|
||||
|
||||
# build
|
||||
RUN npm run build
|
||||
|
||||
# Make port 3000 available to the world outside this container
|
||||
EXPOSE 3000
|
||||
|
||||
# Define environment variables if needed
|
||||
# ENV NODE_ENV production
|
||||
|
||||
# Run the application
|
||||
CMD ["npm", "start"]
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ["@babel/preset-env", "@babel/preset-react"]
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
const { dependencies } = require("./package.json");
|
||||
|
||||
module.exports = {
|
||||
name: "shell_app", // change me
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {},
|
||||
remotes: {},
|
||||
shared: {
|
||||
...dependencies,
|
||||
react: {
|
||||
singleton: true,
|
||||
import: "react", // fallback is also react
|
||||
shareScope: "default",
|
||||
requiredVersion: dependencies.react
|
||||
},
|
||||
"react-dom": {
|
||||
singleton: true,
|
||||
requiredVersion: dependencies["react-dom"]
|
||||
},
|
||||
"react-router-dom": {
|
||||
singleton: true,
|
||||
requiredVersion: dependencies["react-router-dom"]
|
||||
},
|
||||
"rjsf-conditionals": {
|
||||
singleton: true,
|
||||
requiredVersion: dependencies["rjsf-conditionals"]
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -3,48 +3,62 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.9.7",
|
||||
"@rjsf/core": "^5.13.0",
|
||||
"@rjsf/utils": "^5.13.0",
|
||||
"@rjsf/validator-ajv8": "^5.13.0",
|
||||
"axios": "^1.5.1",
|
||||
"daisyui": "^3.9.2",
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@lottiefiles/dotlottie-react": "^0.8.5",
|
||||
"@radix-ui/themes": "^3.1.1",
|
||||
"@react-pdf/renderer": "^3.4.4",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"axios": "^1.7.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"html-react-parser": "^4.2.2",
|
||||
"json-rules-engine-simplified": "^0.1.17",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"mf-cra": "^0.0.5",
|
||||
"moment": "^2.29.4",
|
||||
"parse": "^4.2.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"nth-check": "^2.1.1",
|
||||
"parse": "^5.3.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"print-js": "^1.6.0",
|
||||
"radix-ui": "^1.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.9.0",
|
||||
"react-bootstrap": "^2.10.4",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-cookie": "^7.2.0",
|
||||
"react-datepicker": "^7.3.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dnd-multi-backend": "^8.0.3",
|
||||
"react-dnd-touch-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-redux": "^8.1.3",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-pdf": "^9.1.0",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-rnd": "^10.4.11",
|
||||
"react-router-dom": "^6.25.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-select": "^5.7.7",
|
||||
"reactour": "^1.19.2",
|
||||
"redux": "^4.2.1",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"rjsf-conditionals": "github:prafullnavkar-nxg/rjsf-conditionals",
|
||||
"serve": "^14.2.1",
|
||||
"styled-components": "^4.4.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.8.0",
|
||||
"react-signature-canvas": "^1.0.6",
|
||||
"react-tooltip": "^5.27.1",
|
||||
"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.3",
|
||||
"styled-components": "^5.3.0",
|
||||
"web-vitals": "^4.2.2",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build-webpack": "webpack --config webpack.config.js",
|
||||
"build": "npm run version && npm run build-webpack && react-scripts build",
|
||||
"build-watch": "webpack --config webpack.config.js --watch",
|
||||
"start-dev": "concurrently \"npm run build-watch\" \"react-scripts start\"",
|
||||
"start": "serve -s build",
|
||||
"start-dev": "mf-cra start",
|
||||
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
|
||||
"build": "npm run version && rm -rf public/mfbuild && npm run micro && CI=false && mf-cra build",
|
||||
"micro": "cd ../../microfrontends/SignDocuments && npm install && npm run build",
|
||||
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
|
||||
"build-win": "npm run version-win && npm run build-webpack && react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"release": "standard-version",
|
||||
@@ -74,16 +88,33 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/runtime-corejs2": "^7.23.2",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@babel/runtime-corejs2": "^7.25.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"babel-loader": "^9.1.3",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"commitizen": "^4.3.0",
|
||||
"eslint": "^8.51.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"lint-staged": "^15.0.1",
|
||||
"postcss": "^8.4.31",
|
||||
"prettier": "^2.8.0",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"tailwindcss": "^3.3.3"
|
||||
"concurrently": "^8.2.2",
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.10",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
"lint-staged": "^15.2.7",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"postcss": "^8.4.40",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-quick": "^4.0.0",
|
||||
"tailwindcss": "^3.4.7",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"overrides": {
|
||||
"nth-check": "$nth-check",
|
||||
"ws": "$ws"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<html lang="en" data-theme="opensigncss">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<!-- <link rel="icon" type="image/png"
|
||||
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAALlJREFUaEPtmN0NwjAMBpNxYDKYiM1Yp90g93CKStH1NbIdfz921Dker2Pc+Js1cDF7MXAxASMGYkAigBI6vh9ZwoXP53uZoAYcvhwdA3mAVbI2aSb+9zFKU4IURB6j/HoPUIEa2G3iGIAhQQDlAUIoE2diabIklISS0NoFPebo77RF6OenEF3QntOm128he0GKrwHyACFoz2MgBqSGkpAEcHs47oHtN5AFakACqMNjQEMoE8SABFCHn4HE2zGHSLeEAAAAAElFTkSuQmCC"
|
||||
sizes="40x40" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="The fastest way to sign PDFs & request signatures from others" />
|
||||
<!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> -->
|
||||
<!--
|
||||
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" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"
|
||||
integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
|
||||
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
|
||||
<title>OpenSign™</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
@@ -1 +1 @@
|
||||
v1.0.5-beta
|
||||
v2.1.0
|
||||
|
||||
@@ -1,21 +1,48 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, lazy } from "react";
|
||||
import { Routes, Route, BrowserRouter } from "react-router-dom";
|
||||
import Pgsignup from "./routes/Pgsignup";
|
||||
import Login from "./routes/Login";
|
||||
import Microapp from "./routes/RemoteApp";
|
||||
import Signup from "./routes/Signup";
|
||||
import Form from "./routes/Form";
|
||||
import Report from "./routes/Report";
|
||||
import Dashboard from "./routes/Dashboard";
|
||||
import PlanSubscriptions from "./routes/PlanSubscriptions";
|
||||
import { pdfjs } from "react-pdf";
|
||||
import Login from "./pages/Login";
|
||||
import Form from "./pages/Form";
|
||||
import Report from "./pages/Report";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import HomeLayout from "./layout/HomeLayout";
|
||||
import UserProfile from "./routes/UserProfile";
|
||||
import PageNotFound from "./routes/PageNotFound";
|
||||
import ForgetPassword from "./routes/ForgetPassword";
|
||||
import ChangePassword from "./routes/ChangePassword";
|
||||
import ReportMicroapp from "./components/ReportMicroapp";
|
||||
import LoadMf from "./routes/LoadMf";
|
||||
import PageNotFound from "./pages/PageNotFound";
|
||||
import ValidateRoute from "./primitives/ValidateRoute";
|
||||
import Validate from "./primitives/Validate";
|
||||
import TemplatePlaceholder from "./pages/TemplatePlaceholder";
|
||||
import SignYourSelf from "./pages/SignyourselfPdf";
|
||||
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";
|
||||
import Loader from "./primitives/Loader";
|
||||
import TeamList from "./pages/TeamList";
|
||||
import UserList from "./pages/UserList";
|
||||
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
|
||||
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
|
||||
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
|
||||
const Pgsignup = lazy(() => import("./pages/Pgsignup"));
|
||||
const Subscriptions = lazy(() => import("./pages/PlanSubscriptions"));
|
||||
const ChangePassword = lazy(() => import("./pages/ChangePassword"));
|
||||
const UserProfile = lazy(() => import("./pages/UserProfile"));
|
||||
const Signup = lazy(() => import("./pages/Signup"));
|
||||
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
|
||||
const ManageSign = lazy(() => import("./pages/Managesign"));
|
||||
const GenerateToken = lazy(() => import("./pages/GenerateToken"));
|
||||
const Webhook = lazy(() => import("./pages/Webhook"));
|
||||
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
|
||||
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
|
||||
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
|
||||
const AppLoader = () => {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-[100vh]">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
function App() {
|
||||
const [isloading, setIsLoading] = useState(true);
|
||||
useEffect(() => {
|
||||
@@ -23,13 +50,15 @@ function App() {
|
||||
}, []);
|
||||
|
||||
const handleCredentials = () => {
|
||||
const appId = process.env.REACT_APP_APPID;
|
||||
const baseurl = process.env.REACT_APP_SERVERURL;
|
||||
const appName = "contracts";
|
||||
const appId = process.env.REACT_APP_APPID
|
||||
? process.env.REACT_APP_APPID
|
||||
: "opensign";
|
||||
const baseurl = process.env.REACT_APP_SERVERURL
|
||||
? process.env.REACT_APP_SERVERURL
|
||||
: window.location.origin + "/api/app";
|
||||
try {
|
||||
localStorage.setItem("BaseUrl12", `${baseurl}/`);
|
||||
localStorage.setItem("AppID12", appId);
|
||||
localStorage.setItem("domain", appName);
|
||||
localStorage.setItem("baseUrl", `${baseurl}/`);
|
||||
localStorage.setItem("parseAppId", appId);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.log("err ", error);
|
||||
@@ -37,109 +66,142 @@ function App() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-[#eef1f5]">
|
||||
<div className="bg-base-200">
|
||||
{isloading ? (
|
||||
<div
|
||||
style={{
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "45px",
|
||||
color: "#3dd3e0"
|
||||
}}
|
||||
className="loader-37"
|
||||
></div>
|
||||
</div>
|
||||
<AppLoader />
|
||||
) : (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route exact path="/" element={<Login />} />
|
||||
<Route exact path="/signup" element={<Signup />} />
|
||||
<Route exact path="/loadmf/:remoteApp/*" element={<LoadMf />} />
|
||||
<Route exact path="/forgetpassword" element={<ForgetPassword />} />
|
||||
{process.env.REACT_APP_ENABLE_SUBSCRIPTION && (
|
||||
<Route element={<ValidateRoute />}>
|
||||
<Route exact path="/" element={<Login />} />
|
||||
{isEnableSubscription && (
|
||||
<Route path="/signup" element={<LazyPage Page={Signup} />} />
|
||||
)}
|
||||
{!isEnableSubscription && (
|
||||
<>
|
||||
<Route
|
||||
path="/addadmin"
|
||||
element={<LazyPage Page={AddAdmin} />}
|
||||
/>
|
||||
<Route
|
||||
path="/upgrade-2.1"
|
||||
element={<LazyPage Page={UpdateExistUserAdmin} />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Route>
|
||||
<Route element={<Validate />}>
|
||||
<Route
|
||||
path="/load/template/:templateId"
|
||||
element={<TemplatePlaceholder />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/load/placeholdersign/:docId"
|
||||
element={<PlaceHolderSign />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/load/recipientSignPdf/:docId/:contactBookId"
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
path="/loadmf/signmicroapp/login/:id/:userMail/:contactBookId/:serverUrl"
|
||||
element={<LazyPage Page={GuestLogin} />}
|
||||
/>
|
||||
<Route
|
||||
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} />}
|
||||
/>
|
||||
{isEnableSubscription && (
|
||||
<>
|
||||
<Route exact path="/pgsignup" element={<Pgsignup />} />
|
||||
<Route
|
||||
exact
|
||||
path="/pgsignup"
|
||||
element={<LazyPage Page={Pgsignup} />}
|
||||
/>
|
||||
<Route
|
||||
path="/subscription"
|
||||
element={<PlanSubscriptions />}
|
||||
element={<LazyPage Page={Subscriptions} />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Route
|
||||
exact
|
||||
path="/changepassword"
|
||||
element={
|
||||
<HomeLayout>
|
||||
<ChangePassword />
|
||||
</HomeLayout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/mf/:remoteApp/*"
|
||||
element={
|
||||
<HomeLayout>
|
||||
<Microapp />
|
||||
</HomeLayout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/asmf/:remoteApp/*"
|
||||
element={
|
||||
<HomeLayout>
|
||||
<Microapp />
|
||||
</HomeLayout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/rpmf/:remoteApp/*"
|
||||
element={
|
||||
<HomeLayout>
|
||||
<ReportMicroapp />
|
||||
</HomeLayout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/form/:id"
|
||||
element={
|
||||
<HomeLayout>
|
||||
<Form />
|
||||
</HomeLayout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/report/:id"
|
||||
element={
|
||||
<HomeLayout>
|
||||
<Report />
|
||||
</HomeLayout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/dashboard/:id"
|
||||
element={
|
||||
<HomeLayout>
|
||||
<Dashboard />
|
||||
</HomeLayout>
|
||||
}
|
||||
/>
|
||||
<Route element={<HomeLayout />}>
|
||||
<Route
|
||||
path="/changepassword"
|
||||
element={<LazyPage Page={ChangePassword} />}
|
||||
/>
|
||||
<Route path="/form/:id" element={<Form />} />
|
||||
<Route path="/report/:id" element={<Report />} />
|
||||
<Route path="/dashboard/:id" element={<Dashboard />} />
|
||||
<Route
|
||||
path="/profile"
|
||||
element={<LazyPage Page={UserProfile} />}
|
||||
/>
|
||||
<Route
|
||||
path="/opensigndrive"
|
||||
element={<LazyPage Page={Opensigndrive} />}
|
||||
/>
|
||||
<Route
|
||||
path="/managesign"
|
||||
element={<LazyPage Page={ManageSign} />}
|
||||
/>
|
||||
<Route
|
||||
path="/template/:templateId"
|
||||
element={<TemplatePlaceholder />}
|
||||
/>
|
||||
{/* signyouself route with no rowlevel data using docId from url */}
|
||||
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
|
||||
{/* draft document route to handle and navigate route page accordiing to document status */}
|
||||
<Route path="/draftDocument" element={<DraftDocument />} />
|
||||
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
|
||||
<Route
|
||||
path="/placeHolderSign/:docId"
|
||||
element={<PlaceHolderSign />}
|
||||
/>
|
||||
{/* for user signature (need your sign route) with row level data */}
|
||||
<Route path="/pdfRequestFiles" element={<PdfRequestFiles />} />
|
||||
{/* for user signature (need your sign route) with no row level data */}
|
||||
<Route
|
||||
path="/pdfRequestFiles/:docId"
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<HomeLayout>
|
||||
<UserProfile />
|
||||
</HomeLayout>
|
||||
}
|
||||
/>
|
||||
{/* recipient signature route with no rowlevel data using docId from url */}
|
||||
<Route
|
||||
path="/recipientSignPdf/:docId/:contactBookId"
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
<Route
|
||||
path="/recipientSignPdf/:docId"
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
{isEnableSubscription && (
|
||||
<>
|
||||
<Route path="/teams" element={<TeamList />} />
|
||||
<Route
|
||||
path="/generatetoken"
|
||||
element={<LazyPage Page={GenerateToken} />}
|
||||
/>
|
||||
<Route
|
||||
path="/webhook"
|
||||
element={<LazyPage Page={Webhook} />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Route path="/users" element={<UserList />} />
|
||||
</Route>
|
||||
<Route path="/sso" element={<SSOVerify />} />
|
||||
<Route path="*" element={<PageNotFound />} />
|
||||
{/* <Route exact path="/ForgotPassword" element={<ForgotPassword />} /> */}
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
)}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./redux/store";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { TouchBackend } from "react-dnd-touch-backend";
|
||||
import {
|
||||
DndProvider,
|
||||
TouchTransition,
|
||||
MouseTransition,
|
||||
Preview
|
||||
} from "react-dnd-multi-backend";
|
||||
import DragElement from "./microappComponent/DragElement";
|
||||
import TagManager from "react-gtm-module";
|
||||
|
||||
const HTML5toTouch = {
|
||||
backends: [
|
||||
{
|
||||
id: "html5",
|
||||
backend: HTML5Backend,
|
||||
transition: MouseTransition
|
||||
},
|
||||
{
|
||||
id: "touch",
|
||||
backend: TouchBackend,
|
||||
options: { enableMouseEvents: true },
|
||||
preview: true,
|
||||
transition: TouchTransition
|
||||
}
|
||||
]
|
||||
};
|
||||
const generatePreview = (props) => {
|
||||
const { item, style } = props;
|
||||
const newStyle = {
|
||||
...style
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={newStyle}>
|
||||
<DragElement {...item} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (process.env.REACT_APP_GTM) {
|
||||
const tagManagerArgs = {
|
||||
gtmId: process.env.REACT_APP_GTM
|
||||
};
|
||||
TagManager.initialize(tagManagerArgs);
|
||||
}
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<Provider store={store}>
|
||||
<DndProvider options={HTML5toTouch}>
|
||||
<Preview>{generatePreview}</Preview>
|
||||
<App />
|
||||
</DndProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,283 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Parse from "parse";
|
||||
import Loader from "../primitives/Loader";
|
||||
|
||||
const AddSigner = (props) => {
|
||||
const [name, setName] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [addYourself, setAddYourself] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [isUserExist, setIsUserExist] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
checkUserExist();
|
||||
}, []);
|
||||
// Load user details from localStorage when the component mounts
|
||||
useEffect(() => {
|
||||
const savedUserDetails = JSON.parse(
|
||||
localStorage.getItem("UserInformation")
|
||||
);
|
||||
if (savedUserDetails && addYourself) {
|
||||
setName(savedUserDetails.name);
|
||||
setPhone(savedUserDetails?.phone || "");
|
||||
setEmail(savedUserDetails.email);
|
||||
}
|
||||
}, [addYourself]);
|
||||
|
||||
const checkUserExist = async () => {
|
||||
const user = Parse.User.current();
|
||||
try {
|
||||
const query = new Parse.Query("contracts_Contactbook");
|
||||
query.equalTo("CreatedBy", user);
|
||||
query.notEqualTo("IsDeleted", true);
|
||||
query.equalTo("Email", user.getEmail());
|
||||
const res = await query.first();
|
||||
// console.log(res);
|
||||
if (!res) {
|
||||
setIsUserExist(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
}
|
||||
};
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsLoader(true);
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
try {
|
||||
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 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 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);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in fetch contact", err);
|
||||
setIsLoader(false);
|
||||
alert("something went wrong, please try again later");
|
||||
}
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
alert("something went wrong, please try again later");
|
||||
}
|
||||
};
|
||||
|
||||
// Define a function to handle the "add yourself" checkbox
|
||||
const handleAddYourselfChange = () => {
|
||||
if (addYourself) {
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
} else {
|
||||
setAddYourself(true);
|
||||
}
|
||||
};
|
||||
const handleReset = () => {
|
||||
setAddYourself(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full p-[20px]">
|
||||
{isLoader && (
|
||||
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto p-2 text-base-content">
|
||||
{isUserExist && (
|
||||
<div className="mb-3 flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="addYourself"
|
||||
checked={addYourself}
|
||||
onChange={handleAddYourselfChange}
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
/>
|
||||
<label htmlFor="addYourself" className="ml-2 mb-0">
|
||||
Add Yourself
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="name" className="block text-xs font-semibold">
|
||||
Name
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
disabled={addYourself}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="block text-xs font-semibold">
|
||||
Email
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value?.toLowerCase())}
|
||||
required
|
||||
disabled={addYourself}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="phone" className="block text-xs font-semibold">
|
||||
Phone
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="phone"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
disabled={addYourself}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex gap-x-2 justify-start">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleReset()}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddSigner;
|
||||
@@ -0,0 +1,194 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Title from "./Title";
|
||||
import Loader from "../primitives/Loader";
|
||||
|
||||
const AddTeam = (props) => {
|
||||
const [formdata, setFormdata] = useState({
|
||||
name: "",
|
||||
team: ""
|
||||
});
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [teamList, setTeamList] = useState([]);
|
||||
useEffect(() => {
|
||||
getTeamList();
|
||||
}, []);
|
||||
|
||||
const getTeamList = async () => {
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const teams = await Parse.Cloud.run("getteams", { active: true });
|
||||
const teamRes = JSON.parse(JSON.stringify(teams));
|
||||
if (teamRes.length > 0) {
|
||||
const _teamRes = JSON.parse(JSON.stringify(teamRes));
|
||||
const allUsersteam = _teamRes.find((x) => x.Name === "All Users");
|
||||
if (allUsersteam) {
|
||||
setFormdata({ team: allUsersteam.objectId });
|
||||
}
|
||||
setTeamList(_teamRes);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in fetch top level teamList", err);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDropdown = (e) => {
|
||||
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Extracting values except for the 'name' key
|
||||
let updatedAncestors = [];
|
||||
if (formdata.team) {
|
||||
const Ancestors = teamList.find(
|
||||
(x) => x.objectId === formdata.team
|
||||
)?.Ancestors;
|
||||
if (Ancestors && Ancestors.length > 0) {
|
||||
updatedAncestors = Ancestors.map((x) => ({
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: x.objectId
|
||||
}));
|
||||
} else {
|
||||
const AllUser = teamList.find((x) => x.objectId === "All Users");
|
||||
updatedAncestors = [
|
||||
AllUser,
|
||||
{
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: formdata.team
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
try {
|
||||
setIsLoader(true);
|
||||
let data = { Name: formdata.name };
|
||||
if (updatedAncestors.length > 0) {
|
||||
const ParentId = updatedAncestors[updatedAncestors.length - 1];
|
||||
data["ParentId"] = ParentId?.objectId;
|
||||
data["Ancestors"] = updatedAncestors;
|
||||
}
|
||||
const newTeamRes = await Parse.Cloud.run("addteam", data);
|
||||
// console.log("teamRes ", newTeamRes);
|
||||
if (updatedAncestors.length > 0) {
|
||||
const ParentId = teamList.find((x) => x.objectId === formdata.team);
|
||||
props.handleTeamInfo({
|
||||
objectId: newTeamRes.id,
|
||||
Name: formdata.name,
|
||||
ParentId: ParentId,
|
||||
Ancestors: updatedAncestors,
|
||||
IsActive: true
|
||||
});
|
||||
} else {
|
||||
props.handleTeamInfo({
|
||||
objectId: newTeamRes.id,
|
||||
Name: formdata.name,
|
||||
ParentId: "",
|
||||
Ancestors: "",
|
||||
IsActive: true
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
setFormdata({ name: "", team: { name: "", objectId: "" } });
|
||||
props.setIsAlert({ type: "success", msg: "Team created successfully." });
|
||||
} catch (err) {
|
||||
console.log("err in save team", err);
|
||||
if (err.code === 137) {
|
||||
props.setIsAlert({ type: "danger", msg: "Teams already exists." });
|
||||
} else if (err.code === 102) {
|
||||
props.setIsAlert({ type: "warning", msg: "Provide team name." });
|
||||
} else {
|
||||
props.setIsAlert({ type: "danger", msg: "Something went wrong." });
|
||||
}
|
||||
} finally {
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Define a function to handle the "add yourself" checkbox
|
||||
const handleReset = () => {
|
||||
setFormdata({
|
||||
name: "",
|
||||
team: { name: "", objectId: "" }
|
||||
});
|
||||
};
|
||||
const handleChange = (e) => {
|
||||
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="shadow-md rounded-box my-[1px] p-3 bg-[#ffffff]">
|
||||
<Title title="Add Team" />
|
||||
{isLoader && (
|
||||
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50 rounded-box">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto">
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */}
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Name
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={formdata.name}
|
||||
onChange={(e) => handleChange(e)}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Parent Team
|
||||
</label>
|
||||
<select
|
||||
value={formdata.team}
|
||||
onChange={(e) => handleDropdown(e)}
|
||||
name="team"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
>
|
||||
{teamList.length > 0 &&
|
||||
teamList.map((x) => (
|
||||
<option key={x.objectId} value={x.objectId}>
|
||||
{x.Name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
<div
|
||||
type="button"
|
||||
onClick={() => handleReset()}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
Reset
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddTeam;
|
||||
@@ -0,0 +1,398 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Title from "./Title";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { copytoData } from "../constant/Utils";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
function generatePassword(length) {
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
const charactersLength = characters.length;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const AddUser = (props) => {
|
||||
const [formdata, setFormdata] = useState({
|
||||
name: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
team: "",
|
||||
password: "",
|
||||
role: ""
|
||||
});
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [teamList, setTeamList] = useState([]);
|
||||
const role = ["OrgAdmin", "Editor", "User"];
|
||||
|
||||
useEffect(() => {
|
||||
getTeamList();
|
||||
}, []);
|
||||
|
||||
const getTeamList = async () => {
|
||||
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
|
||||
const teamRes = await Parse.Cloud.run("getteams", { active: true });
|
||||
if (teamRes.length > 0) {
|
||||
const _teamRes = JSON.parse(JSON.stringify(teamRes));
|
||||
setTeamList(_teamRes);
|
||||
if (!isEnableSubscription) {
|
||||
const allUserId =
|
||||
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
|
||||
setFormdata((prev) => ({ ...prev, team: allUserId }));
|
||||
}
|
||||
}
|
||||
};
|
||||
const checkUserExist = async () => {
|
||||
try {
|
||||
const res = await Parse.Cloud.run("getUserDetails", {
|
||||
email: formdata.email
|
||||
});
|
||||
if (res) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
}
|
||||
};
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
setIsLoader(true);
|
||||
const res = await checkUserExist();
|
||||
if (res) {
|
||||
props.setIsAlert({ type: "danger", msg: "User already exist." });
|
||||
setIsLoader(false);
|
||||
setTimeout(() => {
|
||||
props.setIsAlert({ type: "success", msg: "" });
|
||||
}, 1000);
|
||||
} else {
|
||||
try {
|
||||
const extUser = new Parse.Object("contracts_Users");
|
||||
extUser.set("Name", formdata.name);
|
||||
if (formdata.phone) {
|
||||
extUser.set("Phone", formdata.phone);
|
||||
}
|
||||
extUser.set("Email", formdata.email);
|
||||
extUser.set("UserRole", `contracts_${formdata.role}`);
|
||||
if (formdata?.team) {
|
||||
extUser.set("TeamIds", [
|
||||
{
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: formdata.team
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (localUser && localUser.OrganizationId) {
|
||||
extUser.set("OrganizationId", {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Organizations",
|
||||
objectId: localUser.OrganizationId.objectId
|
||||
});
|
||||
}
|
||||
if (localUser && localUser.Company) {
|
||||
extUser.set("Company", localUser.Company);
|
||||
}
|
||||
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
extUser.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", formdata.name);
|
||||
_user.set("username", formdata.email);
|
||||
_user.set("email", formdata.email);
|
||||
_user.set("password", formdata.password);
|
||||
if (formdata.phone) {
|
||||
_user.set("phone", formdata.phone);
|
||||
}
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
const currentUser = Parse.User.current();
|
||||
extUser.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
extUser.set("UserId", user);
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
extUser.setACL(acl);
|
||||
|
||||
const res = await extUser.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
if (formdata?.team) {
|
||||
const team = teamList.find((x) => x.objectId === formdata.team);
|
||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
||||
y.objectId === team.objectId ? team : y
|
||||
);
|
||||
}
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
|
||||
setIsLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: formdata.email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const currentUser = Parse.User.current();
|
||||
extUser.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
extUser.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);
|
||||
|
||||
extUser.setACL(acl);
|
||||
const res = await extUser.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
if (formdata?.team) {
|
||||
const team = teamList.find((x) => x.objectId === formdata.team);
|
||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
||||
y.objectId === team.objectId ? team : y
|
||||
);
|
||||
}
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
setIsLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
setIsLoader(false);
|
||||
props.setIsAlert({ type: "danger", msg: "something went wrong." });
|
||||
} finally {
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Define a function to handle the "add yourself" checkbox
|
||||
const handleReset = () => {
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
};
|
||||
const handleChange = (e) => {
|
||||
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
|
||||
const copytoclipboard = (text) => {
|
||||
copytoData(text);
|
||||
props.setIsAlert({ type: "success", msg: "Copied" });
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
|
||||
};
|
||||
return (
|
||||
<div className="shadow-md rounded-box my-[1px] p-3 bg-[#ffffff]">
|
||||
<Title title={"Add User"} />
|
||||
{isLoader && (
|
||||
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50 rounded-box">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto">
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */}
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Name
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={formdata.name}
|
||||
onChange={(e) => handleChange(e)}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<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={formdata.email}
|
||||
onChange={(e) => handleChange(e)}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
|
||||
<div className="break-all">{formdata?.password}</div>
|
||||
<i
|
||||
onClick={() => copytoclipboard(formdata?.password)}
|
||||
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
|
||||
></i>
|
||||
</div>
|
||||
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
|
||||
Password will only be generated once; make sure to copy it.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Phone
|
||||
{/* <span className="text-[red] text-[13px]"> *</span> */}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="phone"
|
||||
placeholder="optional"
|
||||
value={formdata.phone}
|
||||
onChange={(e) => handleChange(e)}
|
||||
// required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
{isEnableSubscription && (
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Team<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<select
|
||||
value={formdata.team}
|
||||
onChange={(e) => handleChange(e)}
|
||||
name="team"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
required
|
||||
>
|
||||
<option defaultValue={""} value={""}>
|
||||
select
|
||||
</option>
|
||||
{teamList.length > 0 &&
|
||||
teamList.map((x) => (
|
||||
<option key={x.objectId} value={x.objectId}>
|
||||
{x.Name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Role<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<select
|
||||
value={formdata.role}
|
||||
onChange={(e) => handleChange(e)}
|
||||
name="role"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
required
|
||||
>
|
||||
<option defaultValue={""} value={""}>
|
||||
select
|
||||
</option>
|
||||
{role.length > 0 &&
|
||||
role.map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
<div
|
||||
type="button"
|
||||
onClick={() => handleReset()}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddUser;
|
||||
@@ -0,0 +1,283 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import axios from "axios";
|
||||
import SuggestionInput from "./shared/fields/SuggestionInput";
|
||||
import Loader from "../primitives/Loader";
|
||||
|
||||
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) => {
|
||||
//checking if user enter email which already exist as a signer then add user in a signers array
|
||||
let existSigner = [];
|
||||
form.fields.map((data) => {
|
||||
if (data.signer) {
|
||||
existSigner.push(data.signer);
|
||||
}
|
||||
});
|
||||
// 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) {
|
||||
if (signer) {
|
||||
return {
|
||||
...placeholder,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
} else {
|
||||
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
|
||||
if (existSigner?.length > 0) {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
Signers: props.item.Signers
|
||||
? [...props.item.Signers, ...existSigner]
|
||||
: [...existSigner]
|
||||
});
|
||||
} else {
|
||||
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-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{props.Placeholders?.length > 0 ? (
|
||||
isSignatureExist ? (
|
||||
<>
|
||||
{props.Placeholders?.some((x) => !x.signerObjId) ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="min-h-max max-h-[250px] overflow-y-auto">
|
||||
{forms?.map((form, index) => (
|
||||
<div
|
||||
key={form.Id}
|
||||
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
|
||||
>
|
||||
{form?.fields?.map((field, fieldIndex) => (
|
||||
<div className="flex flex-col" key={field.fieldId}>
|
||||
<label>{field.label}</label>
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.value}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(index, signer, fieldIndex)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{forms?.length > 1 && (
|
||||
<button
|
||||
onClick={() => handleRemoveForm(index)}
|
||||
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
|
||||
>
|
||||
<i className="fa-light fa-trash"></i>
|
||||
</button>
|
||||
)}
|
||||
<div ref={formRef}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col mx-4 mb-4 gap-3">
|
||||
<button
|
||||
onClick={handleAddForm}
|
||||
className="op-btn op-btn-primary focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-plus"></i> <span>Add new</span>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-secondary focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-paper-plane"></i>{" "}
|
||||
<span>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;
|
||||
@@ -1,49 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
export class ErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError() {
|
||||
return {
|
||||
hasError: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
console.log(error);
|
||||
console.log(info);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className="p-2">
|
||||
<div
|
||||
style={{
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
|
||||
flexFlow: "column"
|
||||
}}
|
||||
>
|
||||
<h1
|
||||
style={{
|
||||
fontSize: "2em",
|
||||
fontFamily: "sans-serif",
|
||||
textShadow: "0 0 20px black"
|
||||
}}
|
||||
>
|
||||
Something went wrong.
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Package from "../../package.json";
|
||||
import axios from "axios";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
const Footer = () => {
|
||||
const [showButton, setShowButton] = useState(false);
|
||||
const [version, setVersion] = useState("");
|
||||
@@ -37,20 +38,30 @@ const Footer = () => {
|
||||
}, []);
|
||||
|
||||
const appName = "OpenSign™";
|
||||
|
||||
const openUrl = () => {
|
||||
openInNewTab(
|
||||
"https://github.com/OpenSignLabs/OpenSign/releases/tag/" + version
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className="bg-[#222c3c] text-[#98a6ba] text-center text-[13px] py-3">
|
||||
All Rights Reserved © {new Date().getFullYear()}
|
||||
{appName} ( version: {version ? version : `${Package.version} `})
|
||||
</div>
|
||||
<footer className="op-footer op-footer-center py-3 bg-base-300 text-base-content text-center text-[13px]">
|
||||
<aside>
|
||||
<p>
|
||||
All Rights Reserved © {new Date().getFullYear()}
|
||||
<span onClick={openUrl} className="hover:underline cursor-pointer">
|
||||
{appName} ( version: {version ? version : `${Package.version} `})
|
||||
</span>
|
||||
</p>
|
||||
</aside>
|
||||
</footer>
|
||||
<button
|
||||
className={`${
|
||||
showButton ? "block" : "hidden"
|
||||
} fixed bottom-4 right-4 px-3 p-2 text-xl bg-blue-500 text-white rounded focus:outline-none`}
|
||||
} fixed bottom-4 right-4 px-3 p-2 text-xl op-bg-secondary text-white rounded focus:outline-none`}
|
||||
onClick={scrollToTop}
|
||||
>
|
||||
<i className="fa-solid fa-angle-up"></i>
|
||||
<i className="fa-light fa-angle-up"></i>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -67,12 +67,12 @@ const FullScreenButton = () => {
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={toggleFullScreen}
|
||||
className="text-black p-2 text-sm focus:outline-none"
|
||||
className="text-base-content p-2 text-sm focus:outline-none"
|
||||
>
|
||||
{isFullScreen ? (
|
||||
<i className="fa-solid fa-compress"></i>
|
||||
<i className="fa-light fa-compress"></i>
|
||||
) : (
|
||||
<i className="fa fa-maximize"></i>
|
||||
<i className="fa-light fa-maximize"></i>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -4,49 +4,78 @@ import FullScreenButton from "./FullScreenButton";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Parse from "parse";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
import {
|
||||
checkIsSubscribed,
|
||||
checkIsSubscribedTeam,
|
||||
getAppLogo,
|
||||
openInNewTab
|
||||
} from "../constant/Utils";
|
||||
import { isEnableSubscription, isStaging } 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 [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isSubscribe, setIsSubscribe] = useState(true);
|
||||
const [isPro, setIsPro] = useState(false);
|
||||
const [isTeam, setIsTeam] = 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();
|
||||
const getIsTeam = await checkIsSubscribedTeam();
|
||||
if (getIsSubscribe) {
|
||||
const applogo = await getAppLogo();
|
||||
if (applogo?.logo) {
|
||||
setAppLogo(applogo?.logo);
|
||||
} else {
|
||||
setAppLogo(localStorage.getItem("appLogo") || "");
|
||||
}
|
||||
}
|
||||
setIsPro(getIsSubscribe);
|
||||
setIsTeam(getIsTeam);
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
}
|
||||
}
|
||||
|
||||
const closeDropdown = () => {
|
||||
setIsOpen(false);
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
Parse.User.logOut();
|
||||
if (Parse?.User?.current()) {
|
||||
try {
|
||||
Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err", err);
|
||||
}
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let appName = localStorage.getItem("appName");
|
||||
let defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||
let PageLanding = localStorage.getItem("PageLanding");
|
||||
let domain = localStorage.getItem("domain");
|
||||
let _appName = localStorage.getItem("_appName");
|
||||
let baseUrl = localStorage.getItem("BaseUrl12");
|
||||
let appid = localStorage.getItem("AppID12");
|
||||
let baseUrl = localStorage.getItem("baseUrl");
|
||||
let appid = localStorage.getItem("parseAppId");
|
||||
|
||||
localStorage.clear();
|
||||
|
||||
localStorage.setItem("appLogo", applogo);
|
||||
localStorage.setItem("appName", appName);
|
||||
localStorage.setItem("_appName", _appName);
|
||||
localStorage.setItem("defaultmenuid", defaultmenuid);
|
||||
localStorage.setItem("PageLanding", PageLanding);
|
||||
localStorage.setItem("domain", domain);
|
||||
localStorage.setItem("userSettings", appdata);
|
||||
localStorage.setItem("BaseUrl12", baseUrl);
|
||||
localStorage.setItem("AppID12", appid);
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
|
||||
navigation("/");
|
||||
navigate("/");
|
||||
};
|
||||
|
||||
//handle to close profile drop down menu onclick screen
|
||||
@@ -64,16 +93,24 @@ const Header = ({ showSidebar }) => {
|
||||
document.removeEventListener("click", closeMenuOnOutsideClick);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const handleConsoleRedirect = () => {
|
||||
if (isStaging) {
|
||||
window.open(" https://staging-console.opensignlabs.com/");
|
||||
} else {
|
||||
window.open("https://console.opensignlabs.com/");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex flex-row justify-between items-center gap-x-3 md:gap-x-4 bg-white md:px-4 px-2 shadow h-[50px] md:w-full ">
|
||||
<button
|
||||
className="cursor-pointer focus:outline-none"
|
||||
onClick={showSidebar}
|
||||
>
|
||||
<i className={`fa-solid fa-bars text-xl `}></i>
|
||||
</button>
|
||||
<div className="flex-1">
|
||||
<div className="op-navbar bg-base-100 shadow">
|
||||
<div className="flex-none">
|
||||
<button
|
||||
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
|
||||
onClick={showSidebar}
|
||||
>
|
||||
<i className="fa-light fa-bars text-xl text-base-content"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 ml-2">
|
||||
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
||||
<img
|
||||
className="object-contain h-full w-auto"
|
||||
@@ -82,10 +119,27 @@ const Header = ({ showSidebar }) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="profile-menu"
|
||||
className="flex justify-between items-center gap-x-3"
|
||||
>
|
||||
<div id="profile-menu" className="flex-none gap-2">
|
||||
{!isSubscribe && (
|
||||
<div>
|
||||
<button
|
||||
className="text-xs md:text-sm shadow op-btn op-btn-outline op-btn-sm md:op-btn-md op-btn-accent"
|
||||
onClick={() => navigate("/subscription")}
|
||||
>
|
||||
Upgrade Now
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{!isTeam && isPro && (
|
||||
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
PRO
|
||||
</div>
|
||||
)}
|
||||
{isTeam && (
|
||||
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] text-[13px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
TEAM
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
@@ -102,46 +156,70 @@ const Header = ({ showSidebar }) => {
|
||||
</div>
|
||||
)}
|
||||
{width >= 768 && (
|
||||
<div onClick={toggleDropdown} className="cursor-pointer text-sm">
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
className="cursor-pointer text-base-content text-sm"
|
||||
>
|
||||
{username && username}
|
||||
</div>
|
||||
)}
|
||||
<div className="relative">
|
||||
<div className="cursor-pointer">
|
||||
<i onClick={toggleDropdown} className="fa-solid fa-angle-down"></i>
|
||||
</div>
|
||||
<div className="op-dropdown op-dropdown-end" id="profile-menu">
|
||||
<div
|
||||
className={`dropdown absolute text-sm text-gray-700 font-light right-0 mt-4 p-1 w-48 bg-white border rounded-lg shadow-lg z-10 ${
|
||||
isOpen ? "block" : "hidden"
|
||||
}`}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
|
||||
>
|
||||
<ul>
|
||||
<li
|
||||
className="hover:bg-gray-100 rounded-t-lg py-1 px-2 cursor-pointer font-normal"
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigation("/profile");
|
||||
}}
|
||||
>
|
||||
<i className="fa-regular fa-user"></i> Profile
|
||||
</li>
|
||||
<li
|
||||
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigation("/changepassword");
|
||||
}}
|
||||
>
|
||||
<i className="fa-solid fa-lock"></i> Change Password
|
||||
</li>
|
||||
<li
|
||||
className="hover:bg-gray-100 rounded-b-lg py-1 px-2 cursor-pointer font-normal"
|
||||
onClick={closeDropdown}
|
||||
>
|
||||
<i className="fa-solid fa-arrow-right-from-bracket"></i> Log Out
|
||||
</li>
|
||||
</ul>
|
||||
<i
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={toggleDropdown}
|
||||
className="fa-light fa-angle-down text-base-content"
|
||||
></i>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52"
|
||||
>
|
||||
<li onClick={() => openInNewTab("https://docs.opensignlabs.com")}>
|
||||
<span>
|
||||
<i className="fa-light fa-book"></i> Docs
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/profile");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-user"></i> Profile
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/changepassword");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-lock"></i> Change Password
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
handleConsoleRedirect();
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-id-card"></i> Console
|
||||
</span>
|
||||
</li>
|
||||
<li onClick={closeDropdown}>
|
||||
<span>
|
||||
<i className="fa-light fa-arrow-right-from-bracket"></i> Log Out
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Loader from "../primitives/Loader";
|
||||
// import FacebookLogin from "react-facebook-login";
|
||||
|
||||
const LoginFacebook = ({
|
||||
@@ -49,7 +50,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 +60,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 +76,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,8 +86,8 @@ const LoginFacebook = ({
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
// "passsword":userDetails.Phone,
|
||||
phone: userDetails.Phone,
|
||||
role: "contracts_Admin",
|
||||
phone: userDetails?.Phone || "",
|
||||
role: "contracts_User",
|
||||
company: userDetails.Company
|
||||
}
|
||||
};
|
||||
@@ -96,7 +97,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));
|
||||
@@ -112,29 +113,10 @@ const LoginFacebook = ({
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<div className="relative">
|
||||
{thirdpartyLoader && (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
backgroundColor: "rgba(0,0,0,0.2)",
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 2
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
fontSize: "50px",
|
||||
color: "#3ac9d6",
|
||||
top: "50%",
|
||||
left: "45%"
|
||||
}}
|
||||
className="loader-37"
|
||||
></div>
|
||||
<div className="fixed flex justify-center items-center inset-0 bg-black bg-opacity-25 z-20 ">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{/* <FacebookLogin
|
||||
@@ -146,11 +128,10 @@ const LoginFacebook = ({
|
||||
/> */}
|
||||
{isModal && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
className="modal fade show block z-[1]"
|
||||
id="exampleModal"
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: "block", zIndex: 1 }}
|
||||
>
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
@@ -163,13 +144,8 @@ const LoginFacebook = ({
|
||||
<div className="modal-body">
|
||||
<form>
|
||||
<div className="form-group">
|
||||
<label
|
||||
htmlFor="Phone"
|
||||
style={{ display: "flex" }}
|
||||
className="col-form-label"
|
||||
>
|
||||
Phone{" "}
|
||||
<span style={{ fontSize: 13, color: "red" }}>*</span>
|
||||
<label htmlFor="Phone" className="col-form-label">
|
||||
Phone <span className="text-[red] text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
@@ -186,13 +162,8 @@ const LoginFacebook = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label
|
||||
htmlFor="Company"
|
||||
style={{ display: "flex" }}
|
||||
className="col-form-label"
|
||||
>
|
||||
Company{" "}
|
||||
<span style={{ fontSize: 13, color: "red" }}>*</span>
|
||||
<label htmlFor="Company" className="col-form-label">
|
||||
Company <span className="text-[red] text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -212,16 +183,14 @@ const LoginFacebook = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSubmitbtn()}
|
||||
className="btn btn-info"
|
||||
style={{ marginRight: 10 }}
|
||||
className="btn btn-info mr-[10px]"
|
||||
>
|
||||
Sign up
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
className="btn btn-secondary w-[90px]"
|
||||
onClick={() => setIsModal(false)}
|
||||
style={{ width: 90 }}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
import Parse from "parse";
|
||||
import jwtDecode from "jwt-decode";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
import { useScript } from "../hook/useScript";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import Loader from "../primitives/Loader";
|
||||
|
||||
/*
|
||||
* `GoogleSignInBtn`as it's name indicates it render google sign in button
|
||||
@@ -46,32 +48,20 @@ const GoogleSignInBtn = ({
|
||||
if (Parse.User.current()) {
|
||||
await Parse.User.logOut();
|
||||
}
|
||||
let baseUrl = localStorage.getItem("BaseUrl12");
|
||||
let appid = localStorage.getItem("AppID12");
|
||||
let baseUrl = localStorage.getItem("baseUrl");
|
||||
let appid = localStorage.getItem("parseAppId");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let domain = localStorage.getItem("domain");
|
||||
let appversion = localStorage.getItem("appVersion");
|
||||
let appTitle = localStorage.getItem("appTitle");
|
||||
let defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||
let PageLanding = localStorage.getItem("PageLanding");
|
||||
let _appName = localStorage.getItem("_appName");
|
||||
let _app_objectId = localStorage.getItem("_app_objectId");
|
||||
let appName = localStorage.getItem("appName");
|
||||
let userSettings = localStorage.getItem("userSettings");
|
||||
|
||||
localStorage.clear();
|
||||
|
||||
localStorage.setItem("BaseUrl12", baseUrl);
|
||||
localStorage.setItem("AppID12", appid);
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
localStorage.setItem("appLogo", applogo);
|
||||
localStorage.setItem("domain", domain);
|
||||
localStorage.setItem("appversion", appversion);
|
||||
localStorage.setItem("appTitle", appTitle);
|
||||
localStorage.setItem("defaultmenuid", defaultmenuid);
|
||||
localStorage.setItem("PageLanding", PageLanding);
|
||||
localStorage.setItem("_appName", _appName);
|
||||
localStorage.setItem("_app_objectId", _app_objectId);
|
||||
localStorage.setItem("appName", appName);
|
||||
localStorage.setItem("userSettings", userSettings);
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
@@ -108,22 +98,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 {
|
||||
@@ -137,7 +127,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);
|
||||
@@ -147,8 +137,8 @@ const GoogleSignInBtn = ({
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
// "passsword":userDetails.Phone,
|
||||
phone: userDetails.Phone,
|
||||
role: "contracts_Admin",
|
||||
phone: userDetails?.Phone || "",
|
||||
role: "contracts_User",
|
||||
company: userDetails.Company,
|
||||
jobTitle: userDetails.Destination
|
||||
}
|
||||
@@ -159,7 +149,7 @@ const GoogleSignInBtn = ({
|
||||
const LocalUserDetails = {
|
||||
name: userDetails.Name,
|
||||
email: userDetails.Email,
|
||||
phone: userDetails.Phone,
|
||||
phone: userDetails?.Phone || "",
|
||||
company: userDetails.Company
|
||||
// jobTitle: userDetails.JobTitle
|
||||
};
|
||||
@@ -184,165 +174,102 @@ const GoogleSignInBtn = ({
|
||||
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let appName = localStorage.getItem("appName");
|
||||
let defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||
let PageLanding = localStorage.getItem("PageLanding");
|
||||
let domain = localStorage.getItem("domain");
|
||||
let _appName = localStorage.getItem("_appName");
|
||||
let baseUrl = localStorage.getItem("BaseUrl12");
|
||||
let appid = localStorage.getItem("AppID12");
|
||||
let baseUrl = localStorage.getItem("baseUrl");
|
||||
let appid = localStorage.getItem("parseAppId");
|
||||
|
||||
localStorage.clear();
|
||||
|
||||
localStorage.setItem("appLogo", applogo);
|
||||
localStorage.setItem("appName", appName);
|
||||
localStorage.setItem("_appName", _appName);
|
||||
localStorage.setItem("defaultmenuid", defaultmenuid);
|
||||
localStorage.setItem("PageLanding", PageLanding);
|
||||
localStorage.setItem("domain", domain);
|
||||
localStorage.setItem("userSettings", appdata);
|
||||
localStorage.setItem("BaseUrl12", baseUrl);
|
||||
localStorage.setItem("AppID12", appid);
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
};
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<div className="relative">
|
||||
{thirdpartyLoader && (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
backgroundColor: "rgba(0,0,0,0.2)",
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 2
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
fontSize: "50px",
|
||||
color: "#3ac9d6",
|
||||
top: "50%",
|
||||
left: "45%"
|
||||
}}
|
||||
className="loader-37"
|
||||
></div>
|
||||
<div className="fixed flex justify-center items-center inset-0 bg-black bg-opacity-25 z-20 ">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div ref={googleBtn} className="text-sm"></div>
|
||||
|
||||
{isModal && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
id="exampleModal"
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: "block", zIndex: 1 }}
|
||||
>
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Sign up form</h5>
|
||||
<span>
|
||||
<span></span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form>
|
||||
<div className="form-group">
|
||||
<label
|
||||
htmlFor="Phone"
|
||||
style={{ display: "flex" }}
|
||||
className="col-form-label"
|
||||
>
|
||||
Phone{" "}
|
||||
<span style={{ fontSize: 13, color: "red" }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
className="form-control"
|
||||
id="Phone"
|
||||
value={userDetails.Phone}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Phone: e.target.value
|
||||
})
|
||||
}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label
|
||||
htmlFor="Company"
|
||||
style={{ display: "flex" }}
|
||||
className="col-form-label"
|
||||
>
|
||||
Company{" "}
|
||||
<span style={{ fontSize: 13, color: "red" }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="Company"
|
||||
value={userDetails.Company}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Company: e.target.value
|
||||
})
|
||||
}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label
|
||||
htmlFor="JobTitle"
|
||||
style={{ display: "flex" }}
|
||||
className="col-form-label"
|
||||
>
|
||||
Job Title{" "}
|
||||
<span style={{ fontSize: 13, color: "red" }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="JobTitle"
|
||||
value={userDetails.Destination}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Destination: e.target.value
|
||||
})
|
||||
}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-[#17a2b8] p-2 text-white rounded"
|
||||
onClick={() => handleSubmitbtn()}
|
||||
style={{ marginRight: 10 }}
|
||||
>
|
||||
Sign up
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-[#6c757d] p-2 text-white rounded"
|
||||
onClick={handleCloseModal}
|
||||
style={{ width: 90 }}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<ModalUi showClose={false} isOpen={isModal} title="Sign up form">
|
||||
<form className="px-4 py-3 text-base-content">
|
||||
<div className="mb-3">
|
||||
<label htmlFor="Phone" className="block text-xs font-semibold">
|
||||
Phone <span className="text-[13px] text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
id="Phone"
|
||||
value={userDetails.Phone}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Phone: e.target.value
|
||||
})
|
||||
}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3">
|
||||
<label htmlFor="Company" className="block text-xs font-semibold">
|
||||
Company <span className="text-[13px] text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
id="Company"
|
||||
value={userDetails.Company}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Company: e.target.value
|
||||
})
|
||||
}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="JobTitle" className="block text-xs font-semibold">
|
||||
Job Title <span className="text-[13px] text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
id="JobTitle"
|
||||
value={userDetails.Destination}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Destination: e.target.value
|
||||
})
|
||||
}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
onClick={() => handleSubmitbtn()}
|
||||
>
|
||||
Sign up
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from "react";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import { Stage, Layer, Rect, Text } from "react-konva";
|
||||
const RenderDebugPdf = (props) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="sticky top-0 p-[10px] z-10 bg-white border-[1px] border-[gray] my-[5px]">
|
||||
{`Co-ordinates: X - ${props.hoverCoordinates.x}, Y - ${props.hoverCoordinates.y}`}
|
||||
</div>
|
||||
<div
|
||||
className="relative flex-1 cursor-crosshair border-[1px] border-[gray] overflow-auto"
|
||||
onMouseMove={props.handleMouseMoveDiv}
|
||||
>
|
||||
<Document
|
||||
onLoadError={() => {
|
||||
props.setPdfLoadFail(false);
|
||||
}}
|
||||
loading={"Loading Document.."}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
ref={props.pdfRef}
|
||||
file={props.pdfUrl}
|
||||
>
|
||||
<Page
|
||||
onLoadSuccess={props.handlePageLoadSuccess}
|
||||
pageNumber={props.pageNumber}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
/>
|
||||
</Document>
|
||||
|
||||
<Stage
|
||||
onMouseMove={props.handleMouseMove}
|
||||
onMouseDown={props.handleMouseDown}
|
||||
onMouseUp={props.handleMouseUp}
|
||||
width={props.pdfDimension.width}
|
||||
height={props.pdfDimension.height}
|
||||
className="absolute top-0 right-0"
|
||||
>
|
||||
<Layer>
|
||||
{props.annotations
|
||||
.filter((value) => value.page === props.pageNumber)
|
||||
.map((value) => {
|
||||
const textX = value.x + value.width / 3;
|
||||
const textY = value.y + value.height / 2.5;
|
||||
return (
|
||||
<React.Fragment key={value.key}>
|
||||
<Rect
|
||||
x={value.x}
|
||||
y={value.y}
|
||||
width={value.width}
|
||||
height={value.height}
|
||||
fill="#cbe9ed"
|
||||
stroke="black"
|
||||
/>
|
||||
<Text
|
||||
x={textX}
|
||||
y={textY}
|
||||
text={
|
||||
value?.key > 0 ? "box " + value.key.toString() : "box"
|
||||
}
|
||||
fontSize={16}
|
||||
fill="black"
|
||||
align="center"
|
||||
verticalAlign="middle"
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</Layer>
|
||||
</Stage>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenderDebugPdf;
|
||||
@@ -1,66 +0,0 @@
|
||||
import useFederatedComponent from "mf-cra";
|
||||
import React, { Suspense, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
const MicroappModal = () => {
|
||||
const { remoteApp } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
return () => localStorage.removeItem("rowlevel");
|
||||
}, []);
|
||||
let paramString = remoteApp;
|
||||
let queryString = new URLSearchParams(paramString);
|
||||
let obj = {};
|
||||
for (const [key, value] of queryString.entries()) {
|
||||
// console.log("value ", key + ":" + value);
|
||||
|
||||
if (key === "remoteUrl") {
|
||||
const url = window.location.origin + "/mfbuild/remoteEntry.js";
|
||||
// console.log("atob(value) ", atob(value))
|
||||
obj = { ...obj, [key]: url };
|
||||
} else {
|
||||
if (key === "moduleToLoad") {
|
||||
obj = { ...obj, [key]: "./" + value };
|
||||
} else {
|
||||
obj = { ...obj, [key]: value };
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log("app ", remoteApp);
|
||||
const { Component: RemoteComponent } = useFederatedComponent(obj);
|
||||
// console.log("RemoteComponent ", RemoteComponent);
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center ">
|
||||
<div className="w-full">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div style={{ height: "300px" }}>
|
||||
<div
|
||||
style={{
|
||||
marginLeft: "45%",
|
||||
marginTop: "150px",
|
||||
fontSize: "45px",
|
||||
color: "#3dd3e0"
|
||||
}}
|
||||
className="loader-37"
|
||||
></div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#ffffff",
|
||||
width: "100%",
|
||||
overflow: "hidden",
|
||||
minHeight: "50vh"
|
||||
}}
|
||||
>
|
||||
{RemoteComponent && <RemoteComponent />}
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MicroappModal;
|
||||
@@ -1,21 +1,11 @@
|
||||
import React from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
function Title(props) {
|
||||
function Title({ title, drive }) {
|
||||
return (
|
||||
<Helmet>
|
||||
<title>{`${props.title} - OpenSign™`}</title>
|
||||
{/* <title>
|
||||
{`${localStorage.getItem("appTitle") ? localStorage.getItem("appTitle") : ""} - ${props.title}`}
|
||||
</title> */}
|
||||
<meta
|
||||
name="description"
|
||||
content={`${props.title} - OpenSign™`}
|
||||
/>
|
||||
{/* <meta
|
||||
name="description"
|
||||
content={`${localStorage.getItem("appTitle")} - ${props.title}`}
|
||||
/> */}
|
||||
<title>{drive ? title : `${title} - OpenSign™`}</title>
|
||||
<meta name="description" content={`${title} - OpenSign™`} />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Form from "@rjsf/core";
|
||||
import validator from "@rjsf/validator-ajv8";
|
||||
import Parse from "parse";
|
||||
import "../styles/loader.css";
|
||||
import LayoutField from "./fields/Rjsf-layout";
|
||||
import TimeWidget from "./fields/TimeWidget";
|
||||
import Level1Dropdown from "./fields/Level1Dropdown";
|
||||
import HiddenField from "./fields/HiddenField";
|
||||
import MultiSelectField from "./fields/MultiSelectField";
|
||||
import TreeWidget from "../components/TreeWidget";
|
||||
import { formJson } from "../json/FormJson";
|
||||
const TreeEditForm = (props) => {
|
||||
const widget = {
|
||||
TimeWidget: TimeWidget
|
||||
};
|
||||
const fields = {
|
||||
layout: LayoutField,
|
||||
Level1Dropdown: Level1Dropdown,
|
||||
HiddenField: HiddenField,
|
||||
MultiSelectField: MultiSelectField,
|
||||
FolderComponent: TreeWidget
|
||||
};
|
||||
|
||||
const [schema, setschema] = useState({});
|
||||
const [ui_schema, setui_schema] = useState({});
|
||||
const [title, settitle] = useState("");
|
||||
const [schemaState, setschemaState] = useState({});
|
||||
const [formData, setformData] = useState({});
|
||||
const [active, setactive] = useState(true);
|
||||
const [loading, setloading] = useState(false);
|
||||
const [_validate, set_validate] = useState(null);
|
||||
const [noValidate, setnoValidate] = useState(false);
|
||||
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||
|
||||
const getForm = async (id) => {
|
||||
setloading(true);
|
||||
try {
|
||||
//get json form data (schema ,uischema)
|
||||
const results = formJson(id);
|
||||
try {
|
||||
let _record = Parse.Object.extend(results.class);
|
||||
var query1 = new Parse.Query(_record);
|
||||
await query1.get(props.objectId).then(
|
||||
(x) => {
|
||||
try {
|
||||
if (x) {
|
||||
let result = x.toJSON();
|
||||
let new_result = result;
|
||||
for (let [key, value] of Object.entries(result)) {
|
||||
if (value["__type"] === "Date") {
|
||||
let todayTime1 = new Date(value.iso);
|
||||
var month = String(todayTime1.getMonth() + 1);
|
||||
var day = String(todayTime1.getDate());
|
||||
var year = String(todayTime1.getFullYear());
|
||||
if (month.length < 2) month = "0" + month;
|
||||
if (day.length < 2) day = "0" + day;
|
||||
let date1 = year + "-" + month + "-" + day;
|
||||
let bindVar = date1;
|
||||
if (!results.jsonSchema.properties[key].format) {
|
||||
bindVar = todayTime1.toISOString();
|
||||
}
|
||||
new_result[key] = bindVar;
|
||||
}
|
||||
}
|
||||
setschemaState(results.jsonSchema);
|
||||
setformData(new_result);
|
||||
}
|
||||
} catch (error) {
|
||||
//alert(error.message);
|
||||
|
||||
setloading(false);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.log("err", error);
|
||||
setloading(false);
|
||||
}
|
||||
);
|
||||
setschema(results.jsonSchema);
|
||||
setui_schema(results.uiSchema);
|
||||
set_validate(results.validFunction);
|
||||
setnoValidate(results.noValidate);
|
||||
settitle(results.class);
|
||||
setloading(false);
|
||||
setactive(true);
|
||||
} catch (error) {
|
||||
setloading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
setloading(false);
|
||||
console.error("Problem", e);
|
||||
}
|
||||
};
|
||||
|
||||
const wrap = (s) => "{ return " + s + " };";
|
||||
|
||||
const dynamicValidate = (formData, errors) => {
|
||||
try {
|
||||
let body = atob(_validate);
|
||||
let res = new Function(wrap(body))
|
||||
.call(null)
|
||||
.call(null, formData, errors);
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async ({ formData }) => {
|
||||
setactive(false);
|
||||
setloading(true);
|
||||
let RowData = formData;
|
||||
RowData &&
|
||||
Object.entries(RowData).forEach(([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
RowData[key] = value.trim();
|
||||
}
|
||||
});
|
||||
let _scanData = schemaState;
|
||||
if (_scanData.dependencies) {
|
||||
Object.keys(_scanData.dependencies).forEach((key) => {
|
||||
if (_scanData.dependencies[key].oneOf) {
|
||||
_scanData.dependencies[key].oneOf.forEach((val) => {
|
||||
Object.keys(val.properties).forEach((k) => {
|
||||
if (typeof val.properties[k] === "object") {
|
||||
if (val.properties[k].format === "date") {
|
||||
if (RowData[k]) {
|
||||
let newdate = new Date(RowData[k]);
|
||||
RowData[k] = newdate;
|
||||
}
|
||||
}
|
||||
if (val.properties[k].component === "HtmlEditor") {
|
||||
if (RowData[k]) {
|
||||
let newHtml = RowData[k]
|
||||
.replace(/<p[^>]*>/g, "")
|
||||
.replace(/<\/p>/g, " ");
|
||||
RowData[k] = newHtml;
|
||||
}
|
||||
}
|
||||
if (val.properties[k].component === "DateTime") {
|
||||
if (RowData[k]) {
|
||||
let newDate11 = new Date(RowData[k]);
|
||||
RowData[k] = newDate11;
|
||||
}
|
||||
}
|
||||
if (val.properties[k].component === "CurrencyInput") {
|
||||
if (val.properties[k].currencyColumn) {
|
||||
RowData[`${val.properties[k].currencyColumn}`] =
|
||||
val.properties[k].defaultcurrency;
|
||||
}
|
||||
}
|
||||
if (val.properties[k].type === "string") {
|
||||
if (typeof RowData[k] === "string")
|
||||
RowData[k] = RowData[k].trim();
|
||||
}
|
||||
if (val.properties[k].data !== undefined) {
|
||||
if (val.properties[k].data.isPointer) {
|
||||
let pointer = undefined;
|
||||
if (val.properties[k].data.class) {
|
||||
if (RowData[k]) {
|
||||
pointer = {
|
||||
__type: "Pointer",
|
||||
className: val.properties[k].data.class,
|
||||
objectId: RowData[k]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (RowData[k]) {
|
||||
pointer = {
|
||||
__type: "Pointer",
|
||||
className: localStorage.getItem("extended_class"),
|
||||
objectId: RowData[k]
|
||||
};
|
||||
}
|
||||
}
|
||||
RowData[k] = pointer;
|
||||
}
|
||||
if (val.properties[k].data.FolderTypeValue) {
|
||||
if (RowData[k]) {
|
||||
let obj = {
|
||||
__type: "Pointer",
|
||||
className: val.properties[k].data.ClassName,
|
||||
objectId: RowData[k]
|
||||
};
|
||||
RowData[k] = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
let temp = [];
|
||||
|
||||
let _dd = _scanData.properties;
|
||||
let allowed = [];
|
||||
if (_dd) {
|
||||
Object.keys(_dd).forEach(function (k) {
|
||||
allowed.push(k);
|
||||
if (RowData[k]) {
|
||||
let pointer = {
|
||||
__type: "Pointer",
|
||||
className: _dd[k].class,
|
||||
objectId: RowData[k]
|
||||
};
|
||||
RowData[k] = pointer;
|
||||
}
|
||||
if (_dd[k].format === "date") {
|
||||
if (RowData[k]) {
|
||||
let newdate = new Date(RowData[k]);
|
||||
if (!isNaN(newdate.getTime())) {
|
||||
RowData[k] = newdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_dd[k].component === "HtmlEditor") {
|
||||
if (RowData[k]) {
|
||||
let newHtml = RowData[k]
|
||||
.replace(/<p[^>]*>/g, "")
|
||||
.replace(/<\/p>/g, " ");
|
||||
RowData[k] = newHtml;
|
||||
}
|
||||
}
|
||||
if (_dd[k].component === "DateTime") {
|
||||
if (RowData[k]) {
|
||||
let newdate = new Date(RowData[k]);
|
||||
if (!isNaN(newdate.getTime())) {
|
||||
RowData[k] = newdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_dd[k].format === "date-time") {
|
||||
let newdate = new Date(RowData[k]);
|
||||
RowData[k] = newdate;
|
||||
}
|
||||
if (_dd[k].component === "CurrencyInput") {
|
||||
if (_dd[k].currencyColumn) {
|
||||
RowData[`${_dd[k].currencyColumn}`] = _dd[k].defaultcurrency;
|
||||
}
|
||||
}
|
||||
if (_dd[k].data !== undefined) {
|
||||
if (_dd[k].data.isPointer) {
|
||||
let pointer = undefined;
|
||||
if (typeof RowData[k] === "object") {
|
||||
if (RowData[k]) {
|
||||
if (RowData[k].objectId !== "Select") {
|
||||
let obj = {
|
||||
__type: "Pointer",
|
||||
className: RowData[k].className,
|
||||
objectId: RowData[k].objectId
|
||||
};
|
||||
pointer = obj;
|
||||
}
|
||||
}
|
||||
} else if (_dd[k].data.class) {
|
||||
if (RowData[k]) {
|
||||
pointer = {
|
||||
__type: "Pointer",
|
||||
className: _dd[k].data.class,
|
||||
objectId: RowData[k]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (RowData[k]) {
|
||||
pointer = {
|
||||
__type: "Pointer",
|
||||
className: localStorage.getItem("extended_class"),
|
||||
objectId: RowData[k]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
RowData[k] = pointer;
|
||||
}
|
||||
if (_dd[k].data.FolderTypeValue) {
|
||||
if (RowData[k]) {
|
||||
let obj = {
|
||||
__type: "Pointer",
|
||||
className: _dd[k].data.ClassName,
|
||||
objectId: RowData[k]
|
||||
};
|
||||
RowData[k] = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_dd[k].type === "string") {
|
||||
if (typeof RowData[k] === "string") RowData[k] = RowData[k].trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
const filtered = Object.keys(RowData)
|
||||
.filter((key) => allowed.includes(key))
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = RowData[key];
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
try {
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
const currentUser = Parse.User.current();
|
||||
if (temp.length !== 0) {
|
||||
let userPointer = {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: currentUser.id
|
||||
};
|
||||
filtered[[temp[0]]] = userPointer;
|
||||
}
|
||||
var data = Parse.Object.extend(title);
|
||||
var query = new Parse.Query(data);
|
||||
query.get(props.objectId).then(async (object) => {
|
||||
object.save(filtered).then(
|
||||
() => {
|
||||
try {
|
||||
alert("Record updated successfully");
|
||||
props.HideView(false);
|
||||
setloading(false);
|
||||
setactive(true);
|
||||
} catch (error) {
|
||||
console.log("err", error);
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.log("err", error);
|
||||
setloading(false);
|
||||
setactive(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("err", error);
|
||||
|
||||
alert(error.message);
|
||||
setloading(false);
|
||||
setactive(true);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.FormId) {
|
||||
getForm(props.FormId);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [props.FormId]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div
|
||||
className="loader-01"
|
||||
style={{
|
||||
marginTop: "50px",
|
||||
marginLeft: "50%",
|
||||
color: "rgb(0,28,28)",
|
||||
fontSize: "35px"
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Form
|
||||
schema={schema}
|
||||
uiSchema={ui_schema}
|
||||
formData={formData}
|
||||
showErrorList={false}
|
||||
widgets={widget}
|
||||
fields={fields}
|
||||
validate={noValidate && dynamicValidate}
|
||||
onSubmit={handleSubmit}
|
||||
validator={validator}
|
||||
>
|
||||
<div>
|
||||
{active ? (
|
||||
<button className="bg-[#1ab6ce] px-3 py-2 rounded text-white text-sm" type="submit">
|
||||
Update
|
||||
</button>
|
||||
) : (
|
||||
<button className="bg-[#1ab6ce] px-3 py-2 rounded text-white text-sm" type="submit" disabled>
|
||||
Update
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default TreeEditForm;
|
||||
@@ -1,237 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Form from "@rjsf/core";
|
||||
import validator from "@rjsf/validator-ajv8";
|
||||
import LayoutField from "./fields/Rjsf-layout";
|
||||
import Level1Dropdown from "./fields/Level1Dropdown";
|
||||
import HiddenField from "./fields/HiddenField";
|
||||
import MultiSelectField from "./fields/MultiSelectField";
|
||||
import "../styles/loader.css";
|
||||
import "../styles/form.css";
|
||||
import FileUpload from "./fields/FileUpload";
|
||||
import parse from "html-react-parser";
|
||||
import TimeWidget from "./fields/TimeWidget";
|
||||
import { formJson } from "../json/FormJson";
|
||||
function TreeFormComponent(props) {
|
||||
const widget = {
|
||||
TimeWidget: TimeWidget
|
||||
};
|
||||
|
||||
const fields = {
|
||||
layout: LayoutField,
|
||||
FileUpload: FileUpload,
|
||||
Level1Dropdown: Level1Dropdown,
|
||||
HiddenField: HiddenField,
|
||||
MultiSelectField: MultiSelectField
|
||||
};
|
||||
|
||||
const [link, setLink] = useState("");
|
||||
const [help, setHelp] = useState("");
|
||||
const toastDescription = "";
|
||||
const toastColor = "#5cb85c";
|
||||
const [loading, setLoding] = useState(true);
|
||||
const [schemaState, setSchemaState] = useState({});
|
||||
const [formData, setFormData] = useState({});
|
||||
const [ui_schema, setUi_schema] = useState({});
|
||||
const active = true;
|
||||
|
||||
console.log("new form", props);
|
||||
|
||||
const getForm = async (id) => {
|
||||
setLoding(true);
|
||||
try {
|
||||
// get json from data(jsonschema, uischema)
|
||||
const results = formJson(id);
|
||||
// console.log("result", results);
|
||||
if (results) {
|
||||
let parentObject = {},
|
||||
childObject = {};
|
||||
const resultjson = results;
|
||||
for (let [key, value] of Object.entries(
|
||||
resultjson.jsonSchema.properties
|
||||
)) {
|
||||
if (key === props.ParentField) {
|
||||
if (props.ParentValue[key]) {
|
||||
parentObject = { [key]: props.ParentValue[key] };
|
||||
}
|
||||
} else {
|
||||
if (props.ParentValue && typeof props.ParentValue === "object") {
|
||||
Object.entries(props.ParentValue).forEach(([k, value]) => {
|
||||
if (k === key) {
|
||||
let newO = { [k]: value };
|
||||
parentObject = { ...parentObject, ...newO };
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (props.ChildField) {
|
||||
Object.entries(props.ChildField).forEach(([k, value]) => {
|
||||
if (k === key) {
|
||||
let newO = { [k]: value };
|
||||
childObject = { ...childObject, ...newO };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof value === "object") {
|
||||
for (let [k, v] of Object.entries(value)) {
|
||||
if (k === "format" && v === "date") {
|
||||
let today = new Date();
|
||||
let date =
|
||||
today.getFullYear() +
|
||||
"-" +
|
||||
("0" + (today.getMonth() + 1)).slice(-2) +
|
||||
"-" +
|
||||
("0" + today.getDate()).slice(-2);
|
||||
value.default = date;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (props.ParentField) {
|
||||
setFormData({ ...parentObject, ...childObject });
|
||||
}
|
||||
|
||||
let txt, link;
|
||||
if (resultjson.help) {
|
||||
if (resultjson.help.htmlbody) {
|
||||
txt = resultjson.help.htmlbody;
|
||||
}
|
||||
if (resultjson.help.link) {
|
||||
link = resultjson.help.link;
|
||||
}
|
||||
}
|
||||
setHelp(txt);
|
||||
setLink(link);
|
||||
|
||||
props.ClassName(resultjson.class);
|
||||
props.SchemaState(resultjson.jsonSchema);
|
||||
setSchemaState(resultjson.jsonSchema);
|
||||
setUi_schema(resultjson.uiSchema);
|
||||
setLoding(false);
|
||||
} else {
|
||||
alert("form not found");
|
||||
setLoding(false);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Problem", e);
|
||||
setLoding(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.IsEdit) {
|
||||
getForm(props.Id);
|
||||
}
|
||||
// console.log("props tree", props);
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
let formView = (
|
||||
<React.Fragment>
|
||||
<Form
|
||||
schema={schemaState}
|
||||
uiSchema={ui_schema}
|
||||
showErrorList={false}
|
||||
widgets={widget}
|
||||
fields={fields}
|
||||
formData={formData}
|
||||
onSubmit={props.handleSubmit}
|
||||
validator={validator}
|
||||
>
|
||||
{active ? (
|
||||
<button className="btn btn-info submiBtn" type="submit">
|
||||
{props.IsEdit ? "update folder" : "create folder"}
|
||||
</button>
|
||||
) : (
|
||||
<button className="btn btn-info submiBtn " type="submit" disabled>
|
||||
submitting...
|
||||
</button>
|
||||
)}
|
||||
</Form>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
formView = (
|
||||
<div style={{ height: "300px" }}>
|
||||
<div
|
||||
style={{
|
||||
marginLeft: "45%",
|
||||
marginTop: "150px",
|
||||
fontSize: "45px",
|
||||
color: "#3dd3e0"
|
||||
}}
|
||||
className="loader-37"
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<div className="card">
|
||||
<div className="card-body no-padding height-9">
|
||||
{help ? (
|
||||
<div className="dropdown" style={{ marginTop: "-30px" }}>
|
||||
<i
|
||||
className="far fa-question-circle dropdown-toggle hovereffect"
|
||||
aria-hidden="true"
|
||||
id="dropdownMenuButton"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
color: "purple",
|
||||
cursor: "pointer !important",
|
||||
position: "relative",
|
||||
top: "40px",
|
||||
left: "98%"
|
||||
}}
|
||||
></i>
|
||||
<div
|
||||
className="dropdown-menu"
|
||||
aria-labelledby="dropdownMenuButton"
|
||||
style={{
|
||||
marginleft: "-121px",
|
||||
margintop: "-14px",
|
||||
position: "absolute",
|
||||
padding: "10px",
|
||||
width: "300px",
|
||||
top: "102px!important"
|
||||
}}
|
||||
>
|
||||
{parse(`
|
||||
${help}
|
||||
`)}
|
||||
<br />
|
||||
{link ? (
|
||||
<a
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.location.href = link;
|
||||
}}
|
||||
target="_blank"
|
||||
className="btn btn-xs btn-primary"
|
||||
>
|
||||
Read more..
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div style={{ fontSize: "13px" }}>{formView}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="snackbar" style={{ backgroundColor: toastColor }}>
|
||||
{toastDescription}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default TreeFormComponent;
|
||||
@@ -1,895 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import LabelField from "./fields/LabelField";
|
||||
import Parse from "parse";
|
||||
import axios from "axios";
|
||||
import "../styles/spinner.css";
|
||||
import TreeFormComponent from "./TreeFormComponent";
|
||||
import TreeEditForm from "./TreeEditForm";
|
||||
import "../styles/modal.css";
|
||||
import Modal from "react-modal";
|
||||
|
||||
const TreeWidget = (props) => {
|
||||
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||
const [isAddField, setIsAddField] = useState(false);
|
||||
const [folderList, setFolderList] = useState([]);
|
||||
const [tabList, setTabList] = useState([]);
|
||||
const [appId, setAppId] = useState(undefined);
|
||||
const [selectedFolder, setSelectedFolder] = useState(undefined);
|
||||
const [loader, setLoader] = useState(false);
|
||||
const [className, setClassName] = useState("");
|
||||
const [schemaState, setSchemaState] = useState({});
|
||||
const [TabURL, setTabURL] = useState("");
|
||||
const [editable, setEditable] = useState(false);
|
||||
const [editId, setEditId] = useState("");
|
||||
const [defaultState, setDefaultState] = useState(false);
|
||||
const [isShowModal, setIsShowModal] = useState(false);
|
||||
const selectFolderHandle = async () => {
|
||||
setIsShowModal(true);
|
||||
setIsAddField(false);
|
||||
setDefaultState(false);
|
||||
setFolderList([]);
|
||||
setLoader(true);
|
||||
|
||||
if (tabList.length > 0) {
|
||||
let len = tabList.length - 1;
|
||||
selectedItemList(tabList[len]);
|
||||
} else {
|
||||
try {
|
||||
var test;
|
||||
if (props.schema.parent) {
|
||||
try {
|
||||
if (props.parent[props.schema.parent]) {
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
const currentUser = Parse.User.current();
|
||||
let res;
|
||||
if (localStorage.getItem("Extand_Class")) {
|
||||
let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
res = data[0];
|
||||
} else {
|
||||
res = await Parse.Cloud.run("getUserDetails", {
|
||||
email: currentUser.get("email")
|
||||
});
|
||||
if (res) res = res.toJSON();
|
||||
}
|
||||
if (res) {
|
||||
let json = res;
|
||||
setAppId(json.AppId);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
let reg = /(\#.*?\#)/gi;
|
||||
let str = props.schema.data.Query;
|
||||
if (str.includes("#")) {
|
||||
test = str.replace(reg, props.parent[props.schema.parent]);
|
||||
} else {
|
||||
test = str;
|
||||
}
|
||||
} else {
|
||||
if (props.child[props.schema.parent]) {
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
const currentUser = Parse.User.current();
|
||||
let res;
|
||||
if (localStorage.getItem("Extand_Class")) {
|
||||
let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
res = data[0];
|
||||
} else {
|
||||
res = await Parse.Cloud.run("getUserDetails", {
|
||||
email: currentUser.get("email")
|
||||
});
|
||||
if (res) res = res.toJSON();
|
||||
}
|
||||
|
||||
if (res) {
|
||||
let json = res;
|
||||
setAppId(json.AppId);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
let reg = /(\#.*?\#)/gi;
|
||||
let str = props.schema.data.Query;
|
||||
if (str.includes("#")) {
|
||||
test = str.replace(reg, props.child[props.schema.parent]);
|
||||
} else {
|
||||
test = str;
|
||||
}
|
||||
} else {
|
||||
alert(`Please select ${props.schema.parent}`);
|
||||
setLoader(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("error ", error);
|
||||
}
|
||||
} else {
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
const currentUser = Parse.User.current();
|
||||
// eslint-disable-next-line
|
||||
let reg = /(\#.*?\#)/gi;
|
||||
let str = props.schema.data.Query;
|
||||
let res;
|
||||
if (localStorage.getItem("Extand_Class")) {
|
||||
let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
res = data[0];
|
||||
} else {
|
||||
// emp = Parse.Object.extend(localStorage.getItem("extended_class"));
|
||||
// q = new Parse.Query(emp);
|
||||
// q.equalTo("UserId", currentUser);
|
||||
// res = await q.first();
|
||||
// if (res) res = res.toJSON();
|
||||
const currentUser = Parse.User.current();
|
||||
res = await Parse.Cloud.run("getUserDetails", {
|
||||
email: currentUser.get("email")
|
||||
});
|
||||
if (res) res = res.toJSON();
|
||||
}
|
||||
|
||||
if (res) {
|
||||
let json = res;
|
||||
setAppId(json.AppId);
|
||||
}
|
||||
if (str.includes("#")) {
|
||||
if (res) {
|
||||
let json = res.toJSON();
|
||||
let output = str.match(reg);
|
||||
if (output.length === 1) {
|
||||
output = output.join();
|
||||
output = output.substring(1, output.length - 1);
|
||||
output = output.split(".");
|
||||
if (output.length === 1) {
|
||||
let out = output[0];
|
||||
if (json[out]) {
|
||||
if (typeof json[out] === "object") {
|
||||
test = str.replace(reg, JSON.stringify(json[out]));
|
||||
} else {
|
||||
test = str.replace(reg, json[out]);
|
||||
}
|
||||
} else {
|
||||
test = str.replace(reg, currentUser.id);
|
||||
}
|
||||
} else if (output.length === 2) {
|
||||
let out1 = json[output[0]][output[1]];
|
||||
if (out1) {
|
||||
test = str.replace(reg, out1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
test = str;
|
||||
}
|
||||
}
|
||||
|
||||
let url = `${parseBaseUrl}classes/${props.schema.data.ClassName}?${test}`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
await axios
|
||||
.get(url, { headers: headers })
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
setFolderList(res.data.results);
|
||||
setLoader(false);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoader(false);
|
||||
console.log(err);
|
||||
});
|
||||
} catch (error) {
|
||||
setLoader(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const selectedItemList = async (folder) => {
|
||||
setLoader(true);
|
||||
setFolderList([]);
|
||||
try {
|
||||
let url = `${parseBaseUrl}classes/${props.schema.data.ClassName}?where={"${props.schema.data.ParentFolderField}":{"__type":"Pointer","className":"${props.schema.data.ClassName}","objectId":"${folder.objectId}"}}`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
await axios.get(url, { headers: headers }).then((result) => {
|
||||
if (result) {
|
||||
setFolderList(result.data.results);
|
||||
setLoader(false);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
setLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
const removeTabListItem = async (e, i) => {
|
||||
e.preventDefault();
|
||||
setEditable(false);
|
||||
setLoader(true);
|
||||
setFolderList([]);
|
||||
let list = tabList.filter((itm, j) => {
|
||||
if (j <= i) {
|
||||
return itm;
|
||||
}
|
||||
});
|
||||
setTabList(list);
|
||||
setIsAddField(false);
|
||||
try {
|
||||
let _len = list.length - 1;
|
||||
let url = `${parseBaseUrl}classes/${props.schema.data.ClassName}?where={"${props.schema.data.ParentFolderField}":{"__type":"Pointer","className":"${props.schema.data.ClassName}","objectId":"${list[_len].objectId}"}}`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
await axios.get(url, { headers: headers }).then((result) => {
|
||||
if (result) {
|
||||
setFolderList(result.data.results);
|
||||
setLoader(false);
|
||||
}
|
||||
setLoader(false);
|
||||
});
|
||||
} catch (error) {
|
||||
setLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onDeleteFolder = async (e) => {
|
||||
e.preventDefault();
|
||||
if (folderList.length > 0) {
|
||||
alert(
|
||||
"Folder should not delete. To delete first delete sub items in it."
|
||||
);
|
||||
} else if (window.confirm(`Are you sure you want to delete this record`)) {
|
||||
try {
|
||||
setLoader(true);
|
||||
let len = tabList.length - 1;
|
||||
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
var deleted = Parse.Object.extend(props.schema.data.ClassName);
|
||||
var query = new Parse.Query(deleted);
|
||||
await query.get(tabList[len].objectId).then(
|
||||
(del) => {
|
||||
del.destroy().then(
|
||||
(y) => {
|
||||
if (y) {
|
||||
setLoader(false);
|
||||
let list = tabList.filter((itm, j) => {
|
||||
if (j !== len) return itm;
|
||||
});
|
||||
setTabList(list);
|
||||
if (list.length > 0) {
|
||||
let len = list.length - 1;
|
||||
selectedItemList(list[len]);
|
||||
} else {
|
||||
selectFolderHandle();
|
||||
}
|
||||
alert("Folder deleted successfully.");
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
alert(error.message);
|
||||
setLoader(false);
|
||||
}
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
console.log("error ", error);
|
||||
// The object was not retrieved successfully.
|
||||
setLoader(false);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
setLoader(false);
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmitResult = async (e) => {
|
||||
setIsShowModal(false);
|
||||
e.preventDefault();
|
||||
//console.log("tabList ", tabList);
|
||||
if (tabList.length > 0) {
|
||||
try {
|
||||
let len = tabList.length - 1;
|
||||
setSelectedFolder({
|
||||
Topic: tabList[len][`${props.schema.data.FolderNameField}`],
|
||||
description: tabList[len][`${props.schema.data.FolderDescription}`]
|
||||
});
|
||||
props.onChange(tabList[len]["objectId"]);
|
||||
let url = "Folders";
|
||||
tabList.forEach((t) => {
|
||||
url = url + " / " + t[`${props.schema.data.FolderNameField}`];
|
||||
});
|
||||
setTabURL(url);
|
||||
setIsAddField(false);
|
||||
if (!props.formData) {
|
||||
setTabList([]);
|
||||
}
|
||||
} catch (error) {
|
||||
setIsAddField(false);
|
||||
alert(error.message);
|
||||
}
|
||||
} else {
|
||||
setIsAddField(false);
|
||||
}
|
||||
};
|
||||
|
||||
const editFolderDisplay = async (formData) => {
|
||||
try {
|
||||
let objectId = formData.objectId;
|
||||
let className = formData.className;
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
const fldr = Parse.Object.extend(className);
|
||||
let query = new Parse.Query(fldr);
|
||||
query.equalTo("objectId", objectId);
|
||||
const result = await query.first();
|
||||
let resultJson = result.toJSON();
|
||||
let CustomTabList = [];
|
||||
if (resultJson) {
|
||||
CustomTabList.push(resultJson);
|
||||
if (resultJson[`${props.schema.data.ParentFolderField}`]) {
|
||||
let tabsRes = resultJson[`${props.schema.data.ParentFolderField}`];
|
||||
let looping = false;
|
||||
do {
|
||||
let fldttr = Parse.Object.extend(tabsRes.className);
|
||||
let query2 = new Parse.Query(fldttr);
|
||||
query2.equalTo("objectId", tabsRes.objectId);
|
||||
let custome_res = await query2.first();
|
||||
if (custome_res) {
|
||||
let tabsData = custome_res.toJSON();
|
||||
CustomTabList.push(tabsData);
|
||||
if (tabsData[`${props.schema.data.ParentFolderField}`]) {
|
||||
tabsRes = tabsData[`${props.schema.data.ParentFolderField}`];
|
||||
looping = true;
|
||||
} else {
|
||||
looping = false;
|
||||
}
|
||||
}
|
||||
} while (looping);
|
||||
}
|
||||
let url = "Folders";
|
||||
CustomTabList.length > 0 &&
|
||||
CustomTabList.reverse().forEach(
|
||||
(t) => (url = url + " / " + t[props.schema.data.FolderNameField])
|
||||
);
|
||||
setTabURL(url);
|
||||
setSelectedFolder({
|
||||
Topic: resultJson[`${props.schema.data.FolderNameField}`],
|
||||
description: resultJson[`${props.schema.data.FolderDescription}`]
|
||||
});
|
||||
setTabList(CustomTabList);
|
||||
props.onChange(resultJson.objectId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async ({ formData }) => {
|
||||
setLoader(true);
|
||||
|
||||
try {
|
||||
let RowData = formData;
|
||||
let _scanData = schemaState;
|
||||
Object.keys(_scanData).forEach(function (key) {
|
||||
let _dd = _scanData[key];
|
||||
Object.keys(_dd).forEach(function (k) {
|
||||
if (_dd[k].component === "AutoSuggest" && _dd[k].isPointer) {
|
||||
let pointer = {
|
||||
__type: "Pointer",
|
||||
className: _dd[k].class,
|
||||
objectId: RowData[k]
|
||||
};
|
||||
RowData[k] = pointer;
|
||||
}
|
||||
if (_dd[k].format === "date") {
|
||||
let newdate = new Date(RowData[k]);
|
||||
RowData[k] = newdate;
|
||||
}
|
||||
if (_dd[k].component === "DateTime") {
|
||||
let newDate11;
|
||||
if (!RowData[k]) {
|
||||
newDate11 = new Date();
|
||||
} else {
|
||||
newDate11 = new Date(RowData[k]);
|
||||
}
|
||||
RowData[k] = newDate11;
|
||||
}
|
||||
if (_dd[k].component === "CurrencyInput") {
|
||||
if (_dd[k].currencyColumn) {
|
||||
RowData[`${_dd[k].currencyColumn}`] = _dd[k].defaultcurrency;
|
||||
}
|
||||
}
|
||||
if (_dd[k].data !== undefined) {
|
||||
if (_dd[k].data.isPointer) {
|
||||
let pointer = {};
|
||||
if (RowData[k] && RowData[k] !== "Select") {
|
||||
if (_dd[k].data.class) {
|
||||
if (RowData[k]) {
|
||||
pointer = {
|
||||
__type: "Pointer",
|
||||
className: _dd[k].data.class,
|
||||
objectId: RowData[k]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
pointer = {
|
||||
__type: "Pointer",
|
||||
className: localStorage.getItem("extended_class"),
|
||||
objectId: RowData[k]
|
||||
};
|
||||
}
|
||||
|
||||
RowData[k] = pointer;
|
||||
}
|
||||
}
|
||||
if (_dd[k].data.FolderTypeValue) {
|
||||
let obj = {
|
||||
__type: "Pointer",
|
||||
className: _dd[k].data.ClassName,
|
||||
objectId: RowData[k]
|
||||
};
|
||||
RowData[k] = obj;
|
||||
}
|
||||
}
|
||||
if (_dd[k].type === "string") {
|
||||
let d = RowData[k];
|
||||
if (typeof d === "string") {
|
||||
RowData[k] = d.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
|
||||
var forms = Parse.Object.extend(className);
|
||||
var folder = new forms();
|
||||
folder.set(
|
||||
props.schema.data.FolderNameField,
|
||||
formData[props.schema.data.FolderNameField]
|
||||
);
|
||||
if (props.schema.data.FolderDescription) {
|
||||
folder.set(
|
||||
props.schema.data.FolderDescription,
|
||||
formData[props.schema.data.FolderDescription]
|
||||
);
|
||||
}
|
||||
|
||||
folder.set("AppId", appId);
|
||||
folder.set(
|
||||
props.schema.data.FolderTypeField,
|
||||
props.schema.data.FolderTypeValue
|
||||
);
|
||||
const currentUser = Parse.User.current();
|
||||
folder.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
|
||||
if (tabList.length > 0) {
|
||||
let len = tabList.length - 1;
|
||||
folder.set(props.schema.data.ParentFolderField, {
|
||||
__type: "Pointer",
|
||||
className: props.schema.data.ClassName,
|
||||
objectId: tabList[len]["objectId"]
|
||||
});
|
||||
}
|
||||
folder.save(RowData).then(
|
||||
(result) => {
|
||||
RowData = {};
|
||||
if (result) {
|
||||
setIsAddField(false);
|
||||
setLoader(false);
|
||||
if (tabList.length > 0) {
|
||||
let len = tabList.length - 1;
|
||||
selectedItemList(tabList[len]);
|
||||
} else {
|
||||
selectFolderHandle();
|
||||
}
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.log("error ", error);
|
||||
setLoader(false);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
setLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.formData) {
|
||||
editFolderDisplay(props.formData);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
}, [props.formData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultState) {
|
||||
selectFolderHandle();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
}, [defaultState]);
|
||||
const customStyles = {
|
||||
content: {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
right: "auto",
|
||||
bottom: "auto",
|
||||
marginRight: "-50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
backgroundColor: "white",
|
||||
padding: 0
|
||||
},
|
||||
overlay: {
|
||||
width: "100%",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.75)",
|
||||
zIndex: 50
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<LabelField
|
||||
Title={props.schema.title}
|
||||
Name={props.name}
|
||||
Required={props.required}
|
||||
HelpBody={props.schema.helpbody}
|
||||
HelpLink={props.schema.helplink}
|
||||
/>
|
||||
<br />
|
||||
<div className="block max-w-sm bg-white border border-gray-200 shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
color: "black",
|
||||
fontWeight: "bold",
|
||||
fontSize: "13px",
|
||||
padding: "2%",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<div style={{ width: "20%" }}>
|
||||
<div
|
||||
className="pull-left"
|
||||
title="Select Folder"
|
||||
style={{
|
||||
color: "#33bbff",
|
||||
margin: "auto",
|
||||
padding: "10px 10px",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="far fa-folder-open"
|
||||
style={{ fontSize: "40px" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ margin: "0px 10px 0 20px", width: "80%" }}>
|
||||
<div>
|
||||
{selectedFolder && selectedFolder.Topic
|
||||
? selectedFolder.Topic
|
||||
: "Root"}
|
||||
<i
|
||||
className="fa fa-pencil"
|
||||
title="Select Folder"
|
||||
style={{ fontSize: 17, cursor: "pointer", marginLeft: 12 }}
|
||||
onClick={selectFolderHandle}
|
||||
></i>
|
||||
{selectedFolder && (
|
||||
<a
|
||||
className="pull-right"
|
||||
onClick={() => {
|
||||
setSelectedFolder(undefined);
|
||||
setTabURL(undefined);
|
||||
props.onChange(undefined);
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa fa-times-circle-o"
|
||||
aria-hidden="true"
|
||||
style={{ fontSize: 17 }}
|
||||
></i>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
{TabURL && (
|
||||
<div
|
||||
style={{ fontSize: "10px", color: "gray", fontWeight: "bold" }}
|
||||
>
|
||||
({TabURL})
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* save document in folder and add new folder modal */}
|
||||
<Modal
|
||||
isOpen={isShowModal}
|
||||
onRequestClose={() => setIsShowModal(false)}
|
||||
shouldCloseOnOverlayClick={false}
|
||||
contentLabel="Modal"
|
||||
style={customStyles}
|
||||
>
|
||||
<div className="w-full min-w-[300px] md:min-w-[500px]">
|
||||
{/* header */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
backgroundColor: "rgba(203, 200, 200, 0.09)"
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: "10px" }}>
|
||||
<h4 className=" text-black">Select Folder</h4>
|
||||
</div>
|
||||
<div style={{ padding: "10px" }}>
|
||||
<button
|
||||
className="btn btn-sm pull-right"
|
||||
data-dismiss="modal"
|
||||
title="Select Folder"
|
||||
style={{
|
||||
width: "auto",
|
||||
backgroundColor: "white",
|
||||
border: "1px solid gray",
|
||||
boxshadow: "rgba(100, 100, 111, 0.2) 0px 7px 29px 0px"
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setIsAddField(false);
|
||||
setFolderList([]);
|
||||
setEditable(false);
|
||||
if (!props.formData) {
|
||||
setTabList([]);
|
||||
}
|
||||
setIsShowModal(false);
|
||||
}}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
{/* body */}
|
||||
<div className="p-3 space-y-6">
|
||||
<div
|
||||
style={{
|
||||
color: "#ac4848",
|
||||
cursor: "pointer",
|
||||
fontSize: "14px",
|
||||
fontWeight: "500"
|
||||
}}
|
||||
>
|
||||
<a
|
||||
onClick={() => {
|
||||
setTabList([]);
|
||||
setIsAddField(false);
|
||||
setDefaultState(true);
|
||||
setEditable(false);
|
||||
}}
|
||||
>
|
||||
Folders
|
||||
</a>
|
||||
{" / "}
|
||||
|
||||
{tabList &&
|
||||
tabList.map((tab, i) => (
|
||||
<React.Fragment key={`${tab.objectId}-${i}`}>
|
||||
<a
|
||||
title={tab[`${props.schema.data.FolderNameField}`]}
|
||||
onClick={(e) => removeTabListItem(e, i)}
|
||||
>
|
||||
{tab[`${props.schema.data.FolderNameField}`]}
|
||||
</a>
|
||||
{" / "}
|
||||
</React.Fragment>
|
||||
))}
|
||||
<hr />
|
||||
</div>
|
||||
{/* {editable && (
|
||||
<TreeEditForm
|
||||
FormId={props.schema.data.FormId}
|
||||
objectId={editId}
|
||||
HideView={(cl) => {
|
||||
setEditable(cl);
|
||||
selectFolderHandle();
|
||||
}}
|
||||
/>
|
||||
)} */}
|
||||
{isAddField && !loader && !editable && (
|
||||
<TreeFormComponent
|
||||
Id={props.schema.data.FormId}
|
||||
handleSubmit={handleSubmit}
|
||||
ParentField={props.schema.parent && props.schema.parent}
|
||||
ParentValue={props.schema.parent && props.parent}
|
||||
ChildField={props.child && props.child}
|
||||
ClassName={(cls) => setClassName(cls)}
|
||||
SchemaState={(cls) => setSchemaState(cls)}
|
||||
/>
|
||||
)}
|
||||
{loader && (
|
||||
<div
|
||||
className="loader-01"
|
||||
style={{
|
||||
marginTop: "50px",
|
||||
marginLeft: "50%",
|
||||
color: "rgb(0,28,28)",
|
||||
fontSize: "35px"
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
|
||||
{!isAddField && !editable && (
|
||||
<div
|
||||
style={{
|
||||
maxHeight: "280px",
|
||||
overflowY: "auto",
|
||||
marginTop: "5px"
|
||||
}}
|
||||
>
|
||||
<ul>
|
||||
{folderList &&
|
||||
folderList.map(
|
||||
(fldr) =>
|
||||
fldr[props.schema.data.FolderTypeField] ===
|
||||
props.schema.data.FolderTypeValue && (
|
||||
<li
|
||||
key={`${fldr.objectId}`}
|
||||
style={{
|
||||
listStyle: "none",
|
||||
padding: "10px 20px",
|
||||
textTransform: "capitalize",
|
||||
fontWeight: 300,
|
||||
border: "1px solid gray",
|
||||
borderRadius: "1px",
|
||||
marginTop: "8px",
|
||||
transition: "all 0.75s ease",
|
||||
WebkitTransition: "all 0.5s ease",
|
||||
MozTransition: "all 0.5s ease",
|
||||
MsTransition: "all 0.5s ease",
|
||||
OTransition: "all 0.5 ease",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={"fa fa-folder"}
|
||||
style={{
|
||||
fontSize: "1.4rem",
|
||||
color: "skyblue"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<a
|
||||
style={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "12px",
|
||||
marginLeft: "4px",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
onClick={() => {
|
||||
setTabList((tabs) => tabs.concat(fldr));
|
||||
selectedItemList(fldr);
|
||||
}}
|
||||
>
|
||||
{fldr[`${props.schema.data.FolderNameField}`]}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* {fldr[props.schema.data.FolderTypeField] ===
|
||||
props.schema.data.FolderTypeValue && (
|
||||
<a
|
||||
className="float-right"
|
||||
onClick={() => {
|
||||
setEditable(true);
|
||||
setEditId(fldr.objectId);
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={"fa fa-pencil"}
|
||||
title={"Edit Folder"}
|
||||
style={{
|
||||
fontSize: "0.9rem",
|
||||
color: "purple",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</a>
|
||||
)} */}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* footer */}
|
||||
|
||||
<div className="flex items-center p-3 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600 justify-between">
|
||||
<button
|
||||
className="btn btn-sm float-left createFolder"
|
||||
title="Create New Folder"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setIsAddField(true);
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
<i
|
||||
className="fa fa-plus"
|
||||
style={{ fontSize: "1.5rem" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
{!isAddField && !editable && (
|
||||
<div className="flex">
|
||||
{tabList.length > 0 && (
|
||||
<button
|
||||
className="btn btn-sm float-right deleteFolder"
|
||||
title="Delete Folder"
|
||||
onClick={onDeleteFolder}
|
||||
>
|
||||
{" "}
|
||||
<i
|
||||
className="fa fa-trash"
|
||||
style={{ fontSize: "1rem" }}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="btn btn-sm float-right saveFolder"
|
||||
title="Save Here"
|
||||
onClick={onSubmitResult}
|
||||
>
|
||||
{" "}
|
||||
{/* <i
|
||||
className="fa fa-floppy-o"
|
||||
style={{ fontSize: "2rem" }}
|
||||
aria-hidden="true"
|
||||
></i> */}
|
||||
<i
|
||||
className="fas fa-save"
|
||||
aria-hidden="true"
|
||||
style={{ fontSize: "20px" }}
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
parent: state.Level1_Dropdown,
|
||||
child: state.Level2_Dropdown
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(TreeWidget);
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { openInNewTab } from "../../constant/Utils";
|
||||
|
||||
const DashboardButton = (props) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
function openReport() {
|
||||
if (props.Data && props.Data.Redirect_type) {
|
||||
const Redirect_type = props.Data.Redirect_type;
|
||||
const id = props.Data.Redirect_id;
|
||||
if (Redirect_type === "Form") {
|
||||
navigate(`/form/${id}`);
|
||||
} else if (Redirect_type === "Report") {
|
||||
navigate(`/report/${id}`);
|
||||
} else if (Redirect_type === "Url") {
|
||||
openInNewTab(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
onClick={() => openReport()}
|
||||
className={`${
|
||||
props.Data && props.Data.Redirect_type
|
||||
? "cursor-pointer"
|
||||
: "cursor-default"
|
||||
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
||||
<i
|
||||
className={`${
|
||||
props.Icon ? props.Icon : "fa-light fa-info"
|
||||
} text-[25px] lg:text-[30px]`}
|
||||
></i>
|
||||
</span>
|
||||
<div className=" text-lg ml-3 text-base-content">{props.Label}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardButton;
|
||||
@@ -2,8 +2,8 @@ import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import Parse from "parse";
|
||||
import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
|
||||
import "../../styles/loader.css";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Tooltip from "../../primitives/Tooltip";
|
||||
|
||||
const DashboardCard = (props) => {
|
||||
const navigate = useNavigate();
|
||||
@@ -24,9 +24,6 @@ const DashboardCard = (props) => {
|
||||
};
|
||||
let body = {};
|
||||
let currentUser;
|
||||
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
let currentUser1 = Parse.User.current();
|
||||
currentUser = currentUser1.id;
|
||||
let res;
|
||||
@@ -82,8 +79,6 @@ const DashboardCard = (props) => {
|
||||
} else {
|
||||
setLoading(true);
|
||||
try {
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
const currentUser = Parse.User.current();
|
||||
let reg1 = /(\#.*?\#)/gi; // eslint-disable-line
|
||||
let _query = props.Data.query;
|
||||
@@ -153,7 +148,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 = [];
|
||||
@@ -223,8 +218,6 @@ const DashboardCard = (props) => {
|
||||
|
||||
if (restr.includes("#")) {
|
||||
try {
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
const currentUser = Parse.User.current();
|
||||
let res;
|
||||
if (localStorage.getItem("Extand_Class")) {
|
||||
@@ -268,21 +261,14 @@ const DashboardCard = (props) => {
|
||||
}
|
||||
}
|
||||
body = str;
|
||||
await axios
|
||||
.post(url, body, { headers: headers })
|
||||
.then((response) => {
|
||||
try {
|
||||
if (response.data.result.length > 0) {
|
||||
setresponse(response.data.result[0][props.FilterData.key]);
|
||||
setLoading(false);
|
||||
} else {
|
||||
setresponse("0");
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
const response = await axios.post(url, body, { headers: headers });
|
||||
if (response.data.result.length > 0) {
|
||||
setresponse(response.data.result[0][props.FilterData.key]);
|
||||
setLoading(false);
|
||||
} else {
|
||||
setresponse("0");
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -342,23 +328,30 @@ const DashboardCard = (props) => {
|
||||
props.Data && props.Data.Redirect_type
|
||||
? "cursor-pointer"
|
||||
: "cursor-default"
|
||||
} w-full h-[140px] px-3 pt-4 pb-10 text-white rounded-md shadow overflow-hidden`}
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-start gap-5">
|
||||
<span className="rounded-full bg-black bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
||||
<div className="flex items-center justify-start gap-5 text-white">
|
||||
<span className="rounded-full bg-base-300 bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
||||
<i
|
||||
className={`${
|
||||
props.Icon ? props.Icon : "fa fa-solid fa-info"
|
||||
props.Icon ? props.Icon : "fa-light fa-info"
|
||||
} text-[25px] lg:text-[30px]`}
|
||||
></i>
|
||||
</span>
|
||||
<div className="">
|
||||
<div className="font-medium">
|
||||
<div className="text-base lg:text-lg"> {props.Label}</div>
|
||||
<div className="text-2xl font-light">
|
||||
{loading ? <div className="loader-01"></div> : setFormat(response)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs absolute top-3 right-2">
|
||||
<Tooltip
|
||||
id={props.Label}
|
||||
iconColor={"white"}
|
||||
message={props?.Data?.tourMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import Parse from "parse";
|
||||
import ReportTable from "../../primitives/GetReportDisplay";
|
||||
import reportJson from "../../json/ReportJson";
|
||||
import axios from "axios";
|
||||
import Loader from "../../primitives/Loader";
|
||||
function DashboardReport(props) {
|
||||
// console.log("props ", props)
|
||||
const [List, setList] = useState([]);
|
||||
@@ -45,18 +46,16 @@ function DashboardReport(props) {
|
||||
setActions(json.actions);
|
||||
setReportName(json.reportName);
|
||||
setHeading(json.heading);
|
||||
Parse.serverURL = localStorage.getItem("BaseUrl12");
|
||||
Parse.initialize(localStorage.getItem("AppID12"));
|
||||
const currentUser = Parse.User.current().id;
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("AppID12"),
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
try {
|
||||
const params = { reportId: id, skip: skipUserRecord, limit: limit };
|
||||
const url = `${localStorage.getItem("BaseUrl12")}/functions/getReport`;
|
||||
const url = `${localStorage.getItem("baseUrl")}/functions/getReport`;
|
||||
const res = await axios.post(url, params, {
|
||||
headers: headers,
|
||||
signal: abortController.signal // is used to cancel fetch query
|
||||
@@ -99,11 +98,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) {
|
||||
@@ -112,6 +113,7 @@ function DashboardReport(props) {
|
||||
console.log("err ", err);
|
||||
setIsLoader(false);
|
||||
}
|
||||
setIsLoader(false);
|
||||
}
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
@@ -120,21 +122,8 @@ function DashboardReport(props) {
|
||||
return (
|
||||
<>
|
||||
{isLoader ? (
|
||||
<div
|
||||
style={{
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "45px",
|
||||
color: "#3dd3e0"
|
||||
}}
|
||||
className="loader-37"
|
||||
></div>
|
||||
<div className="h-[250px] flex justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -1,28 +1,52 @@
|
||||
import React, { Suspense, lazy } from "react";
|
||||
import ErrorBoundary from "../ErrorBoundary";
|
||||
|
||||
const DashboardButton = lazy(() => import("./DashboardButton"));
|
||||
const DashboardCard = lazy(() => import("./DashboardCard"));
|
||||
|
||||
const DashboardReport = lazy(() => import("./DashboardReport"));
|
||||
|
||||
const buttonList = [
|
||||
{
|
||||
label: "Sign yourself",
|
||||
redirectId: "sHAnZphf69",
|
||||
redirectType: "Form",
|
||||
icon: "fa-light fa-pen-nib"
|
||||
},
|
||||
{
|
||||
label: "Request signature",
|
||||
redirectId: "8mZzFxbG1z",
|
||||
redirectType: "Form",
|
||||
icon: "fa-light fa-paper-plane"
|
||||
}
|
||||
];
|
||||
const GetDashboard = (props) => {
|
||||
const Button = ({ label, redirectId, redirectType, icon }) => (
|
||||
<DashboardButton
|
||||
Icon={icon}
|
||||
Label={label}
|
||||
Data={{ Redirect_type: redirectType, Redirect_id: redirectId }}
|
||||
/>
|
||||
);
|
||||
const renderSwitchWithTour = (col) => {
|
||||
switch (col.widget.type) {
|
||||
case "Card":
|
||||
return (
|
||||
<div
|
||||
className={"bg-[#2ed8b6] rounded-md shadow mb-4 md:mb-0"}
|
||||
className={`${
|
||||
col?.widget?.bgColor ? col.widget.bgColor : "bg-[#2ed8b6]"
|
||||
} op-card w-full h-[140px] px-3 pt-4 mb-3 shadow-md`}
|
||||
data-tut={col.widget.data.tourSection}
|
||||
style={{ background: col.widget.bgColor }}
|
||||
>
|
||||
<Suspense fallback={<div>please wait</div>}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="h-[150px] w-full flex justify-center items-center">
|
||||
loading...
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<DashboardCard
|
||||
Icon={col.widget.icon}
|
||||
Label={col.widget.label}
|
||||
Format={col.widget.format && col.widget.format}
|
||||
Data={col.widget.data}
|
||||
FilterData={col.widget.filter}
|
||||
DefaultQuery={props.DefaultQuery}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
@@ -31,7 +55,7 @@ const GetDashboard = (props) => {
|
||||
return (
|
||||
<div data-tut={col.widget.data.tourSection}>
|
||||
<Suspense fallback={<div>please wait</div>}>
|
||||
<div className="mb-4 md:mb-0">
|
||||
<div className="mb-3 md:mb-0">
|
||||
<DashboardReport Record={col.widget} />
|
||||
</div>
|
||||
</Suspense>
|
||||
@@ -39,7 +63,7 @@ const GetDashboard = (props) => {
|
||||
);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
const renderSwitch = (col) => {
|
||||
@@ -47,8 +71,9 @@ const GetDashboard = (props) => {
|
||||
case "Card":
|
||||
return (
|
||||
<div
|
||||
className={"bg-[#2ed8b6] rounded-md shadow mb-4 md:mb-0"}
|
||||
style={{ background: col.widget.bgColor }}
|
||||
className={`${
|
||||
col?.widget?.bgColor ? col.widget.bgColor : "bg-[#2ed8b6]"
|
||||
} op-card w-full h-[140px] px-3 pt-4 mb-3 shadow-md"`}
|
||||
>
|
||||
<Suspense fallback={<div>please wait</div>}>
|
||||
<DashboardCard
|
||||
@@ -57,7 +82,6 @@ const GetDashboard = (props) => {
|
||||
Format={col.widget.format && col.widget.format}
|
||||
Data={col.widget.data}
|
||||
FilterData={col.widget.filter}
|
||||
DefaultQuery={props.DefaultQuery}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
@@ -65,36 +89,48 @@ const GetDashboard = (props) => {
|
||||
case "report": {
|
||||
return (
|
||||
<Suspense fallback={<div>please wait</div>}>
|
||||
<div className="mb-4 md:mb-0">
|
||||
<div className="mb-3 md:mb-0">
|
||||
<DashboardReport Record={col.widget} />
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<div>
|
||||
{props.dashboard.map((val, key) => (
|
||||
<div key={"a" + key} className="row">
|
||||
{val.columns.map((col, i) =>
|
||||
col.widget.data && col.widget.data.tourSection ? (
|
||||
<div key={i} className={props.classnameArray[key][i]}>
|
||||
{renderSwitchWithTour(col)}
|
||||
</div>
|
||||
) : (
|
||||
<div key={i} className={props.classnameArray[key][i]}>
|
||||
{renderSwitch(col)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<div className="mb-3">
|
||||
<div
|
||||
data-tut={"tourbutton"}
|
||||
className="flex flex-col md:flex-row gap-4"
|
||||
>
|
||||
{buttonList.map((btn) => (
|
||||
<Button
|
||||
key={btn.label}
|
||||
label={btn.label}
|
||||
redirectType={btn.redirectType}
|
||||
redirectId={btn.redirectId}
|
||||
icon={btn.icon}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
<div className="grid grid-cols-12 w-full gap-x-4">
|
||||
{props?.dashboard?.columns?.map((col, i) =>
|
||||
col.widget.data && col.widget.data.tourSection ? (
|
||||
<div key={i} className={col?.colsize}>
|
||||
{renderSwitchWithTour(col)}
|
||||
</div>
|
||||
) : (
|
||||
<div key={i} className={col?.colsize}>
|
||||
{renderSwitch(col)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,308 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { SaveFileSize } from "../../constant/saveFileSize";
|
||||
import Parse from "parse";
|
||||
import sanitizeFileName from "../../primitives/sanitizeFileName";
|
||||
import DropboxChooser from "./DropboxChoose";
|
||||
const FileUpload = (props) => {
|
||||
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||
const [_fileupload, setFileUpload] = useState("");
|
||||
const [fileload, setfileload] = useState(false);
|
||||
const [Message] = useState(false);
|
||||
const [percentage, setpercentage] = useState(0);
|
||||
|
||||
const REQUIRED_FIELD_SYMBOL = "*";
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.formData) {
|
||||
setFileUpload([]);
|
||||
props.onChange(undefined);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [props.formData]);
|
||||
const onChange = (e) => {
|
||||
try {
|
||||
let files = e.target.files;
|
||||
if (typeof files[0] !== "undefined") {
|
||||
if (props.schema.filetypes && props.schema.filetypes.length > 0) {
|
||||
var fileName = files[0].name;
|
||||
var fileNameExt = fileName
|
||||
.substr(fileName.lastIndexOf(".") + 1)
|
||||
.toLowerCase();
|
||||
let Extensions = props.schema.filetypes.map((x) => x.toLowerCase());
|
||||
let arr = Extensions.filter((x) => x === fileNameExt);
|
||||
if (arr.length > 0) {
|
||||
console.log("multiple type");
|
||||
} else {
|
||||
alert(
|
||||
"Only these file types are accepted : " + Extensions.join(", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
if (props.schema.maxfilesizeKB && props.schema.maxfilesizeKB !== "") {
|
||||
// console.log(Math.round(files[0].size / 1024));
|
||||
if (
|
||||
Math.round(Number(files[0].size) / 1024) >=
|
||||
props.schema.maxfilesizeKB
|
||||
) {
|
||||
alert(
|
||||
`The selected file size is too large. Please select a file less than ${Math.round(
|
||||
props.schema.maxfilesizeKB / 1024
|
||||
)} MB`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
handleFileUpload(files[0]);
|
||||
} else {
|
||||
alert("Please select file.");
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileUpload = async (file) => {
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
setfileload(true);
|
||||
const size = file.size;
|
||||
const fileName = file.name;
|
||||
const name = sanitizeFileName(fileName);
|
||||
const pdfFile = file;
|
||||
const parseFile = new Parse.File(name, pdfFile);
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
// console.log("percentCompleted ", percentCompleted);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setFileUpload(response.url());
|
||||
props.onChange(response.url());
|
||||
setfileload(false);
|
||||
// The response object will contain information about the uploaded file
|
||||
// console.log("File uploaded:", response);
|
||||
|
||||
// You can access the URL of the uploaded file using response.url()
|
||||
// console.log("File URL:", response.url());
|
||||
if (response.url()) {
|
||||
SaveFileSize(size, response.url());
|
||||
return response.url();
|
||||
}
|
||||
} catch (error) {
|
||||
setfileload(false);
|
||||
setpercentage(0);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const dropboxSuccess = async (files) => {
|
||||
// console.log("file ", files);
|
||||
setfileload(true);
|
||||
const file = files[0];
|
||||
const url = file.link;
|
||||
const size = file.bytes;
|
||||
const mb = Math.round(file.bytes / Math.pow(1024, 2));
|
||||
|
||||
if (mb > 10) {
|
||||
setTimeout(() => {
|
||||
alert(
|
||||
`The selected file size is too large. Please select a file less than 10 MB`
|
||||
);
|
||||
}, 500);
|
||||
return;
|
||||
} else {
|
||||
const name = sanitizeFileName(file.name);
|
||||
|
||||
const parseFile = new Parse.File(name, { uri: url });
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
// console.log("percentCompleted ", percentCompleted);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// console.log("response.url() ", response.url());
|
||||
setFileUpload(response.url());
|
||||
props.onChange(response.url());
|
||||
setfileload(false);
|
||||
|
||||
if (response.url()) {
|
||||
SaveFileSize(size, response.url());
|
||||
return response.url();
|
||||
}
|
||||
} catch (error) {
|
||||
setfileload(false);
|
||||
setpercentage(0);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
const dropboxCancel = async () => {
|
||||
console.log("cancel clicked ");
|
||||
};
|
||||
let fileView =
|
||||
props.formData &&
|
||||
props.schema.uploadtype === "s3viajw" ? null : props.formData &&
|
||||
props.formData ? (
|
||||
<React.Fragment>
|
||||
<a
|
||||
href={props.formData}
|
||||
title={props.formData}
|
||||
style={{ paddingBottom: "10px", color: "blue" }}
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
</React.Fragment>
|
||||
) : fileload === false ? null : (
|
||||
<React.Fragment>
|
||||
<a
|
||||
href={_fileupload}
|
||||
title={_fileupload}
|
||||
style={{ paddingBottom: "10px" }}
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
<br />
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div style={{ display: "inline-block" }}>
|
||||
<label htmlFor={props.name}>
|
||||
{props.schema.title}
|
||||
{props.required && (
|
||||
<span className="required">{REQUIRED_FIELD_SYMBOL}</span>
|
||||
)}
|
||||
{fileload ? (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="h-2 rounded-full w-[200px] md:w-[400px] bg-gray-200">
|
||||
<div
|
||||
className="h-2 rounded-full bg-blue-500"
|
||||
style={{ width: `${percentage}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="text-black text-sm">{percentage}%</span>
|
||||
</div>
|
||||
) : (
|
||||
Message && (
|
||||
<span style={{ fontSize: "9px", marginLeft: "4px" }}>
|
||||
Processing will take 5-10 mins....
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
{props.schema.helpbody ? (
|
||||
<div className="dropdown pull-right">
|
||||
<i
|
||||
className="far fa-question-circle dropdown-toggle hovereffect"
|
||||
aria-hidden="true"
|
||||
id="dropdownMenuButton"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "purple",
|
||||
cursor: "pointer !important",
|
||||
position: "relative",
|
||||
bottom: "0px",
|
||||
left: "0px",
|
||||
paddingBottom: "4px",
|
||||
paddingLeft: "4px"
|
||||
}}
|
||||
></i>
|
||||
<div
|
||||
className="dropdown-menu"
|
||||
aria-labelledby="dropdownMenuButton"
|
||||
style={{
|
||||
marginleft: "-121px",
|
||||
margintop: "-14px",
|
||||
position: "absolute",
|
||||
padding: "10px",
|
||||
top: "102px!important"
|
||||
}}
|
||||
>
|
||||
<br />
|
||||
{props.schema.helplink ? (
|
||||
<a
|
||||
href={props.schema.helplink}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="btn btn-xs btn-primary"
|
||||
>
|
||||
Read more..
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<span style={{ marginLeft: "10px" }}>{fileView}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<>
|
||||
{props.formData ? (
|
||||
<div className="flex gap-2 justify-center items-center">
|
||||
<div className="flex justify-between items-center px-2 py-[3px] w-full font-bold rounded border-[1px] border-[#ccc] text-gray-500 bg-white text-[13px]">
|
||||
<div className="break-all">
|
||||
file selected : {props.formData.split("/")[3]}
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
console.log("clicked");
|
||||
setFileUpload([]);
|
||||
props.onChange(undefined);
|
||||
}}
|
||||
className="cursor-pointer px-[10px] text-[20px] font-bold bg-white text-red-500"
|
||||
>
|
||||
<i className="fa-solid fa-xmark"></i>
|
||||
</div>
|
||||
</div>
|
||||
<DropboxChooser
|
||||
onSuccess={dropboxSuccess}
|
||||
onCancel={dropboxCancel}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-2 justify-center items-center">
|
||||
<input
|
||||
type="file"
|
||||
id="hashfile"
|
||||
style={{
|
||||
border: "1px solid #ccc",
|
||||
color: "gray",
|
||||
backgroundColor: "white",
|
||||
padding: "5px 10px",
|
||||
borderRadius: "4px",
|
||||
fontSize: "13px",
|
||||
width: "100%",
|
||||
fontWeight: "bold"
|
||||
}}
|
||||
accept="application/pdf,application/vnd.ms-excel"
|
||||
onChange={onChange}
|
||||
/>
|
||||
<DropboxChooser
|
||||
onSuccess={dropboxSuccess}
|
||||
onCancel={dropboxCancel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileUpload;
|
||||
@@ -1,98 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import Parse from "parse";
|
||||
// import axios from "axios";
|
||||
|
||||
export class HiddenField extends Component {
|
||||
state = {
|
||||
baseUrl: localStorage.getItem("baseUrl"),
|
||||
parseAppId: localStorage.getItem("parseAppId"),
|
||||
_fields: ""
|
||||
};
|
||||
loadData = async () => {
|
||||
if (this.props.schema.default) {
|
||||
this.setState({ _fields: this.props.schema.default });
|
||||
} else {
|
||||
try {
|
||||
Parse.serverURL = this.state.baseUrl;
|
||||
Parse.initialize(this.state.parseAppId);
|
||||
let currentUser = await Parse.User.current();
|
||||
if (localStorage.getItem("Extand_Class")) {
|
||||
let _rdata = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
let custome_Filed = "";
|
||||
let splitArray = this.props.schema.data.valueKey.split(".");
|
||||
if (splitArray.length > 1) {
|
||||
custome_Filed = _rdata[0][splitArray[0]][splitArray[1]];
|
||||
} else {
|
||||
let _Filed = _rdata[0][splitArray[0]];
|
||||
if (typeof _Filed === "object") {
|
||||
custome_Filed = {
|
||||
__type: "Pointer",
|
||||
className: _Filed["className"],
|
||||
objectId: _Filed["objectId"]
|
||||
};
|
||||
} else {
|
||||
custome_Filed = _rdata[0][splitArray[0]];
|
||||
}
|
||||
}
|
||||
this.setState({ _fields: custome_Filed });
|
||||
} else {
|
||||
if (currentUser) {
|
||||
// let url = `${this.state.baseUrl}classes/${localStorage.getItem(
|
||||
// "extended_class"
|
||||
// )}?where={"UserId":{"__type":"Pointer","className":"_User","objectId":"${
|
||||
// currentUser.id
|
||||
// }"}}&keys=${this.props.schema.data.valueKey}`;
|
||||
// const headers = {
|
||||
// "Content-Type": "application/json",
|
||||
// "X-Parse-Application-Id": this.state.parseAppId
|
||||
// };
|
||||
// await axios.get(url, { headers: headers }).then((res) => {
|
||||
// let custome_Filed = "";
|
||||
// let _rdata = res.data.results;
|
||||
// let splitArray = this.props.schema.data.valueKey.split(".");
|
||||
// if (splitArray.length > 1) {
|
||||
// custome_Filed = _rdata[0][splitArray[0]][splitArray[1]];
|
||||
// } else {
|
||||
// console.log(_rdata[0][splitArray[0]]);
|
||||
// custome_Filed = _rdata[0][splitArray[0]];
|
||||
// }
|
||||
// this.setState({ _fields: custome_Filed });
|
||||
// });
|
||||
|
||||
const res = await Parse.Cloud.run("getUserDetails", {
|
||||
email: currentUser.get("email")
|
||||
});
|
||||
let custome_Filed = "";
|
||||
const result = res.toJSON();
|
||||
let _rdata = [result];
|
||||
let splitArray = this.props.schema.data.valueKey.split(".");
|
||||
if (splitArray.length > 1) {
|
||||
custome_Filed = _rdata[0][splitArray[0]][splitArray[1]];
|
||||
} else {
|
||||
console.log(_rdata[0][splitArray[0]]);
|
||||
custome_Filed = _rdata[0][splitArray[0]];
|
||||
}
|
||||
this.setState({ _fields: custome_Filed });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("err", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
componentDidMount() {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<input
|
||||
type="hidden"
|
||||
value={this.state._fields}
|
||||
onChange={this.props.onChange(this.state._fields)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HiddenField;
|
||||
@@ -1,70 +0,0 @@
|
||||
import React from "react";
|
||||
import parse from "html-react-parser";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const LabelField = (props) => {
|
||||
const { Title, Name, Required, HelpBody, HelpLink } = props;
|
||||
const REQUIRED_FIELD_SYMBOL = "*";
|
||||
return (
|
||||
<div style={{ display: "inline-block" }}>
|
||||
<label htmlFor={Name}>
|
||||
{Title}
|
||||
{Required && <span className="required">{REQUIRED_FIELD_SYMBOL}</span>}
|
||||
{HelpBody ? (
|
||||
<div className="dropdown pull-right">
|
||||
<i
|
||||
className="far fa-question-circle dropdown-toggle hovereffect pull-right"
|
||||
aria-hidden="true"
|
||||
id="dropdownMenuButton"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "purple",
|
||||
cursor: "pointer !important",
|
||||
position: "relative",
|
||||
bottom: "0px",
|
||||
left: "0px",
|
||||
paddingBottom: "4px",
|
||||
paddingLeft: "4px"
|
||||
}}
|
||||
></i>
|
||||
<div
|
||||
className="dropdown-menu"
|
||||
aria-labelledby="dropdownMenuButton"
|
||||
style={{
|
||||
marginleft: "-121px",
|
||||
margintop: "-14px",
|
||||
position: "absolute",
|
||||
padding: "10px",
|
||||
top: "102px!important"
|
||||
}}
|
||||
>
|
||||
{parse(`
|
||||
${HelpBody}
|
||||
`)}
|
||||
<br />
|
||||
{HelpLink ? (
|
||||
<a
|
||||
href={HelpLink}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="btn btn-xs btn-primary"
|
||||
>
|
||||
Read more..
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
LabelField.propTypes = {
|
||||
Name: PropTypes.string.isRequired,
|
||||
Title: PropTypes.string.isRequired
|
||||
};
|
||||
export default LabelField;
|
||||
@@ -1,482 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { onChangeLevel1Dropdown } from "../../redux/actions/index";
|
||||
import axios from "axios";
|
||||
import Parse from "parse";
|
||||
import parse from "html-react-parser";
|
||||
|
||||
const Level1Dropdown = (props) => {
|
||||
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||
const [Level1_DD, setLevel1_DD] = useState([]);
|
||||
const [toastColor] = useState("#d9534f");
|
||||
const [toastDescription, setToastDescription] = useState("");
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
const getParams = (offset) => {
|
||||
const params = {
|
||||
skip: offset
|
||||
};
|
||||
return params;
|
||||
};
|
||||
|
||||
const Level1DropdownData = async () => {
|
||||
try {
|
||||
if (
|
||||
props.formData &&
|
||||
props.uiSchema["ui:disabled"] &&
|
||||
props.formData !== "Select"
|
||||
) {
|
||||
let url = `${parseBaseUrl}classes/${props.schema.data.class}/${props.formData}`;
|
||||
if (typeof props.formData === "object") {
|
||||
url = `${parseBaseUrl}classes/${props.schema.data.class}/${props.formData.objectId}`;
|
||||
}
|
||||
let headers1 = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
let data = [];
|
||||
let response = [];
|
||||
await axios.get(url, { headers: headers1 }).then((res) => {
|
||||
if (res) {
|
||||
data.push(res.data);
|
||||
}
|
||||
});
|
||||
|
||||
if (data.length > 0) {
|
||||
let temp = [];
|
||||
data.forEach((x) => {
|
||||
let opt = "";
|
||||
if (Array.isArray(x[props.schema.data.valueKey])) {
|
||||
x[props.schema.data.valueKey].forEach((tt) => {
|
||||
if (typeof tt === "object") {
|
||||
opt = {
|
||||
objectId: tt[props.schema.data.displayKey],
|
||||
option: tt[props.schema.data.displayKey]
|
||||
};
|
||||
} else {
|
||||
opt = {
|
||||
objectId: tt,
|
||||
option: tt
|
||||
};
|
||||
}
|
||||
temp.push(opt);
|
||||
});
|
||||
} else if (props.schema.data.isPointer) {
|
||||
if (props.schema.data.valueKey.includes(".")) {
|
||||
let newArr = props.schema.data.valueKey.split(".");
|
||||
if (newArr.length === 2) {
|
||||
opt = {
|
||||
objectId: x[newArr[0]][newArr[1]],
|
||||
option: x[props.schema.data.displayKey]
|
||||
};
|
||||
} else {
|
||||
opt = {
|
||||
objectId: x[newArr[0]],
|
||||
option: x[props.schema.data.displayKey]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
opt = {
|
||||
objectId: x[props.schema.data.valueKey],
|
||||
option: x[props.schema.data.displayKey]
|
||||
};
|
||||
}
|
||||
temp.push(opt);
|
||||
} else {
|
||||
opt = {
|
||||
objectId: x[props.schema.data.valueKey],
|
||||
option: x[props.schema.data.displayKey]
|
||||
};
|
||||
temp.push(opt);
|
||||
}
|
||||
});
|
||||
response = temp;
|
||||
setLevel1_DD(response);
|
||||
setActive(false);
|
||||
if (response.length === 1) {
|
||||
props.onChangeLevel1Dropdown(response[0].objectId, props.name);
|
||||
props.onChange(response[0].objectId);
|
||||
} else if (response.length === 0) {
|
||||
props.onChangeLevel1Dropdown("", props.name);
|
||||
props.onChange("");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let response = [];
|
||||
setActive(true);
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
const currentUser = Parse.User.current();
|
||||
|
||||
// eslint-disable-next-line
|
||||
var reg = /(\#.*?\#)/gi;
|
||||
let str = props.schema.data.query;
|
||||
var test;
|
||||
if (str.includes("#")) {
|
||||
let res;
|
||||
if (localStorage.getItem("Extand_Class")) {
|
||||
let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
res = data[0];
|
||||
} else {
|
||||
const currentUser = Parse.User.current();
|
||||
res = await Parse.Cloud.run("getUserDetails", {
|
||||
email: currentUser.get("email")
|
||||
});
|
||||
if (res) res = res.toJSON();
|
||||
}
|
||||
|
||||
if (res) {
|
||||
let json = res;
|
||||
let output = str.match(reg);
|
||||
if (output.length === 1) {
|
||||
output = output.join();
|
||||
output = output.substring(1, output.length - 1);
|
||||
output = output.split(".");
|
||||
if (output.length === 1) {
|
||||
let out = output[0];
|
||||
if (json[out]) {
|
||||
if (typeof json[out] === "object") {
|
||||
test = str.replace(reg, JSON.stringify(json[out]));
|
||||
} else {
|
||||
test = str.replace(reg, json[out]);
|
||||
}
|
||||
} else {
|
||||
test = str.replace(reg, currentUser.id);
|
||||
}
|
||||
} else if (output.length === 2) {
|
||||
let out1 = json[output[0]][output[1]];
|
||||
if (out1) {
|
||||
test = str.replace(reg, out1);
|
||||
}
|
||||
}
|
||||
} else if (output.length === 2) {
|
||||
let output1 = output[0];
|
||||
output1 = output1.substring(1, output1.length - 1);
|
||||
output1 = output1.split(".");
|
||||
if (output1.length === 1) {
|
||||
let out = output1[0];
|
||||
if (json[out]) {
|
||||
if (typeof json[out] === "object") {
|
||||
str = str.replace(output[0], JSON.stringify(json[out]));
|
||||
} else {
|
||||
str = str.replace(output[0], json[out]);
|
||||
}
|
||||
} else {
|
||||
str = str.replace(output[0], currentUser.id);
|
||||
}
|
||||
} else if (output1.length === 2) {
|
||||
let out1 = json[output1[0]][output1[1]];
|
||||
if (out1) {
|
||||
str = str.replace(output[0], out1);
|
||||
}
|
||||
}
|
||||
let output2 = output[1];
|
||||
output2 = output2.substring(1, output2.length - 1);
|
||||
output2 = output2.split(".");
|
||||
if (output2.length === 1) {
|
||||
let out = output2[0];
|
||||
if (json[out]) {
|
||||
if (typeof json[out] === "object") {
|
||||
str = str.replace(output[1], JSON.stringify(json[out]));
|
||||
} else {
|
||||
str = str.replace(output[1], json[out]);
|
||||
}
|
||||
} else {
|
||||
str = str.replace(output[1], currentUser.id);
|
||||
}
|
||||
} else if (output2.length === 2) {
|
||||
let out1 = json[output2[0]][output2[1]];
|
||||
if (out1) {
|
||||
str = str.replace(output[1], out1);
|
||||
}
|
||||
}
|
||||
test = str;
|
||||
}
|
||||
} else {
|
||||
setToastDescription(
|
||||
`User not found in ${localStorage.getItem("extended_class")}`
|
||||
);
|
||||
setActive(false);
|
||||
var x = document.getElementById("Level_1");
|
||||
x.className = "show";
|
||||
setTimeout(function () {
|
||||
x.className = x.className.replace("show", "");
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
test = str;
|
||||
}
|
||||
let url = `${parseBaseUrl}classes/${props.schema.data.class}?${test}`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
let fetchCount = 0;
|
||||
let offset = 0;
|
||||
let data = [];
|
||||
/* eslint-disable no-constant-condition */
|
||||
while (true) {
|
||||
const params = getParams(offset);
|
||||
await axios
|
||||
.get(url, { params: params, headers: headers })
|
||||
.then((response) => {
|
||||
fetchCount = response.data.results.length;
|
||||
offset += response.data.results.length;
|
||||
|
||||
if (fetchCount > 0) {
|
||||
response.data.results.forEach((x) => {
|
||||
data.push(x);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Problem", err.response);
|
||||
setToastDescription(err.response.data.message);
|
||||
setActive(false);
|
||||
var x = document.getElementById("Level_1");
|
||||
x.className = "show";
|
||||
setTimeout(function () {
|
||||
x.className = x.className.replace("show", "");
|
||||
}, 5000);
|
||||
});
|
||||
if (fetchCount === 0 || fetchCount < 100) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (data.length > 0) {
|
||||
let temp = [];
|
||||
data.forEach((x) => {
|
||||
let opt = "";
|
||||
if (Array.isArray(x[props.schema.data.valueKey])) {
|
||||
x[props.schema.data.valueKey].forEach((tt) => {
|
||||
if (typeof tt === "object") {
|
||||
if (props.schema.data.isPointer) {
|
||||
opt = {
|
||||
objectId: tt["objectId"],
|
||||
option: tt[props.schema.data.displayKey]
|
||||
};
|
||||
} else {
|
||||
opt = {
|
||||
objectId: tt[props.schema.data.displayKey],
|
||||
option: tt[props.schema.data.displayKey]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
opt = {
|
||||
objectId: tt,
|
||||
option: tt
|
||||
};
|
||||
}
|
||||
temp.push(opt);
|
||||
});
|
||||
} else if (props.schema.data.isPointer) {
|
||||
if (props.schema.data.valueKey.includes(".")) {
|
||||
let newArr = props.schema.data.valueKey.split(".");
|
||||
if (newArr.length === 2) {
|
||||
let _dis = props.schema.data.displayKey.split(".");
|
||||
if (_dis.length === 2) {
|
||||
opt = {
|
||||
objectId: x[newArr[0]][newArr[1]],
|
||||
option: x[_dis[0]][_dis[1]]
|
||||
};
|
||||
} else {
|
||||
opt = {
|
||||
objectId: x[newArr[0]][newArr[1]],
|
||||
option: x[props.schema.data.displayKey]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
opt = {
|
||||
objectId: x[newArr[0]],
|
||||
option: x[props.schema.data.displayKey]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
opt = {
|
||||
objectId: x[props.schema.data.valueKey],
|
||||
option: x[props.schema.data.displayKey]
|
||||
};
|
||||
}
|
||||
temp.push(opt);
|
||||
} else {
|
||||
opt = {
|
||||
objectId: x[props.schema.data.valueKey],
|
||||
option: x[props.schema.data.displayKey]
|
||||
};
|
||||
temp.push(opt);
|
||||
}
|
||||
});
|
||||
response = temp;
|
||||
setLevel1_DD(response);
|
||||
setActive(false);
|
||||
if (!props.formData) {
|
||||
props.onChangeLevel1Dropdown("", props.name);
|
||||
}
|
||||
if (response.length === 1) {
|
||||
props.onChangeLevel1Dropdown(response[0].objectId, props.name);
|
||||
props.onChange(response[0].objectId);
|
||||
} else if (response.length === 0) {
|
||||
props.onChangeLevel1Dropdown("", props.name);
|
||||
props.onChange("");
|
||||
}
|
||||
} else {
|
||||
setActive(false);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
try {
|
||||
setToastDescription(e.message);
|
||||
setActive(false);
|
||||
x = document.getElementById("Level_1");
|
||||
x.className = "show";
|
||||
setTimeout(function () {
|
||||
x.className = x.className.replace("show", "");
|
||||
}, 5000);
|
||||
} catch (error) {
|
||||
console.log("Err", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Level1DropdownData();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.formData && props.formData[`objectId`]) {
|
||||
props.onChangeLevel1Dropdown(props.formData.objectId, props.name);
|
||||
} else if (props.formData) {
|
||||
props.onChangeLevel1Dropdown(props.formData, props.name);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
}, [props.formData]);
|
||||
|
||||
const selectDefault = () => {
|
||||
props.onChange(Level1_DD[0].objectId);
|
||||
props.onChangeLevel1Dropdown(Level1_DD[0].objectId, props.name);
|
||||
};
|
||||
|
||||
const REQUIRED_FIELD_SYMBOL = "*";
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div style={{ display: "inline-block" }}>
|
||||
<label htmlFor={props.name}>
|
||||
{props.schema.title}
|
||||
{props.required && (
|
||||
<span className="required">{REQUIRED_FIELD_SYMBOL}</span>
|
||||
)}
|
||||
{props.schema.data.helpbody ? (
|
||||
<div className="dropdown pull-right">
|
||||
<i
|
||||
className="far fa-question-circle dropdown-toggle hovereffect pull-right"
|
||||
aria-hidden="true"
|
||||
id="dropdownMenuButton"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "purple",
|
||||
cursor: "pointer !important",
|
||||
position: "relative",
|
||||
bottom: "0px",
|
||||
left: "0px",
|
||||
paddingBottom: "4px",
|
||||
paddingLeft: "4px"
|
||||
}}
|
||||
></i>
|
||||
<div
|
||||
className="dropdown-menu"
|
||||
aria-labelledby="dropdownMenuButton"
|
||||
style={{
|
||||
marginleft: "-121px",
|
||||
margintop: "-14px",
|
||||
position: "absolute",
|
||||
padding: "10px",
|
||||
top: "102px!important"
|
||||
}}
|
||||
>
|
||||
{parse(`
|
||||
${props.schema.data.helpbody}
|
||||
`)}
|
||||
<br />
|
||||
{props.schema.data.helplink ? (
|
||||
<a
|
||||
href={props.schema.data.helplink}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="btn btn-xs btn-primary"
|
||||
>
|
||||
Read more..
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{Level1_DD && (
|
||||
<select
|
||||
className="form-control"
|
||||
name={props.name}
|
||||
value={props.value}
|
||||
required={props.required}
|
||||
disabled={props.uiSchema["ui:disabled"]}
|
||||
onChange={(e) => {
|
||||
if (!e.target.value) {
|
||||
if (props.schema.data.isPointer) {
|
||||
localStorage.setItem("Level1", undefined);
|
||||
props.onChangeLevel1Dropdown(undefined, props.name);
|
||||
return props.onChange(undefined);
|
||||
} else {
|
||||
localStorage.setItem("Level1", e.target.value);
|
||||
props.onChangeLevel1Dropdown(e.target.value, props.name);
|
||||
return props.onChange(e.target.value);
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem("Level1", e.target.value);
|
||||
props.onChangeLevel1Dropdown(e.target.value, props.name);
|
||||
return props.onChange(e.target.value);
|
||||
}
|
||||
}}
|
||||
readOnly={props.uiSchema["ui:disabled"] ? true : false}
|
||||
defaultValue={Level1_DD.length === 1 && selectDefault}
|
||||
>
|
||||
<option value="">{!active ? "Select" : "loading..."}</option>
|
||||
{Level1_DD &&
|
||||
Level1_DD.map((x, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{props.formData && props.formData.objectId ? (
|
||||
props.formData.objectId === x.objectId ? (
|
||||
<option value={x.objectId} selected>
|
||||
{x.option}
|
||||
</option>
|
||||
) : (
|
||||
<option value={x.objectId}>{x.option}</option>
|
||||
)
|
||||
) : props.formData && props.formData === x.objectId ? (
|
||||
<option value={x.objectId} selected>
|
||||
{x.option}
|
||||
</option>
|
||||
) : (
|
||||
<option value={x.objectId}>{x.option}</option>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
<div id="Level_1" style={{ backgroundColor: toastColor }}>
|
||||
{toastDescription}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(null, { onChangeLevel1Dropdown })(Level1Dropdown);
|
||||
@@ -1,709 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Select from "react-select";
|
||||
import axios from "axios";
|
||||
import LabelField from "./LabelField";
|
||||
import ReactDragListView from "react-drag-listview";
|
||||
import AppendFormInForm from "../AppendFormInForm";
|
||||
import Modal from "react-modal";
|
||||
function arrayMove(array, from, to) {
|
||||
array = array.slice();
|
||||
array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* react-sortable-hoc is depcreated not usable from react 18.x.x
|
||||
* need to replace it with @dnd-kit
|
||||
* code changes required
|
||||
*/
|
||||
|
||||
const MultiSelectField = (props) => {
|
||||
Modal.setAppElement("body");
|
||||
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||
const [state, setState] = useState(undefined);
|
||||
// const [editFormData, setEditFormData] = useState([]);
|
||||
const [selected, setSelected] = React.useState([]);
|
||||
const [isModal, setIsModel] = useState(false);
|
||||
const onChange = (selectedOptions) => setSelected(selectedOptions);
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
|
||||
const onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
const newValue = arrayMove(selected, oldIndex, newIndex);
|
||||
setSelected(newValue);
|
||||
};
|
||||
// console.log("props",props)
|
||||
const GetSelectListData = async () => {
|
||||
try {
|
||||
let response = [];
|
||||
let str = props.schema.data.query;
|
||||
let _query;
|
||||
/* eslint-disable no-useless-escape */
|
||||
let reg = /(\#.*?\#)/gi;
|
||||
let extclass =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
let extRow = extclass.length && extclass[0];
|
||||
if (str.includes("#")) {
|
||||
let output = str.match(reg);
|
||||
output = output.join();
|
||||
output = output.substring(1, output.length - 1);
|
||||
output = output.split(".");
|
||||
|
||||
if (output.length === 2) {
|
||||
let out1 = extRow[output[0]][output[1]];
|
||||
_query = str.replace(reg, out1 && out1);
|
||||
} else {
|
||||
let out1 = extRow[output[0]];
|
||||
_query = str.replace(reg, out1 && out1);
|
||||
}
|
||||
} else {
|
||||
_query = str;
|
||||
}
|
||||
let url = `${parseBaseUrl}classes/${props.schema.data.class}?${_query}`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId,
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
await axios.get(url, { headers: headers }).then((res) => {
|
||||
let temp = [];
|
||||
let formArray = [];
|
||||
let _selected = [];
|
||||
if (props.schema.uiLayout === "MultiDropdownList") {
|
||||
if (props.formData) {
|
||||
props.formData.forEach((x) => {
|
||||
if (typeof x === "object") {
|
||||
formArray.push(x.objectId);
|
||||
} else {
|
||||
formArray.push(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
res.data.results.forEach((x) => {
|
||||
let obj = {};
|
||||
if (formArray.includes(x[props.schema.data.valueKey])) {
|
||||
obj = {
|
||||
name: x[props.schema.data.displayKey],
|
||||
value: x[props.schema.data.valueKey],
|
||||
isChecked: true
|
||||
};
|
||||
} else {
|
||||
if (props.schema.selectAll) {
|
||||
obj = {
|
||||
name: x[props.schema.data.displayKey],
|
||||
value: x[props.schema.data.valueKey],
|
||||
isChecked: true
|
||||
};
|
||||
} else {
|
||||
obj = {
|
||||
name: x[props.schema.data.displayKey],
|
||||
value: x[props.schema.data.valueKey],
|
||||
isChecked: false
|
||||
};
|
||||
}
|
||||
}
|
||||
temp.push(obj);
|
||||
});
|
||||
response = temp;
|
||||
if (props.formData) {
|
||||
let checkData = [];
|
||||
response.forEach((x) => {
|
||||
if (x.isChecked) {
|
||||
checkData.push(x.value);
|
||||
}
|
||||
});
|
||||
setSelected(checkData);
|
||||
}
|
||||
setState({ [`${props.name}_DD`]: response });
|
||||
} else {
|
||||
res.data.results.forEach((x) => {
|
||||
let obj = {};
|
||||
if (props.formData) {
|
||||
props.formData.forEach((x) => {
|
||||
if (typeof x === "object") {
|
||||
formArray.push(x.objectId);
|
||||
} else {
|
||||
formArray.push(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
obj = {
|
||||
label: x[props.schema.data.displayKey],
|
||||
value: x[props.schema.data.valueKey]
|
||||
};
|
||||
if (formArray.includes(x[props.schema.data.valueKey])) {
|
||||
_selected.push(obj);
|
||||
}
|
||||
if (props.schema.selectAll) {
|
||||
_selected.push(obj);
|
||||
}
|
||||
temp.push(obj);
|
||||
});
|
||||
response = temp;
|
||||
setState({ [`${props.name}_DD`]: response });
|
||||
setSelected(_selected);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("err", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckChieldElement = (event) => {
|
||||
let SelectLists = state[`${props.name}_DD`];
|
||||
let List = [];
|
||||
SelectLists.forEach((select) => {
|
||||
if (select.value === event.target.value) {
|
||||
select.isChecked = event.target.checked;
|
||||
}
|
||||
if (select.isChecked) {
|
||||
List.push(select.value);
|
||||
}
|
||||
});
|
||||
setSelected(List);
|
||||
setState({ [`${props.name}_DD`]: SelectLists });
|
||||
};
|
||||
|
||||
// const Level1CheckList = async (id) => {
|
||||
// try {
|
||||
// let response = [];
|
||||
// // eslint-disable-next-line
|
||||
// let reg = /(\#.*?\#)/gi;
|
||||
// let _query = props.schema.data.query;
|
||||
// let output = _query.match(reg);
|
||||
// if (output.length === 2) {
|
||||
// let res;
|
||||
// if (localStorage.getItem("Extand_Class")) {
|
||||
// let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
// res = data[0];
|
||||
// }
|
||||
// output = output.filter((x) => x !== "#queryString#");
|
||||
// if (output.length === 1) {
|
||||
// _query = _query.replace("#queryString#", id);
|
||||
// output = output.join();
|
||||
// output = output.substring(1, output.length - 1);
|
||||
// output = output.split(".");
|
||||
// if (output.length > 0) {
|
||||
// _query = _query.replace(reg, res[output[0]][output[1]]);
|
||||
// } else {
|
||||
// _query = _query.replace(reg, res[output[0]]);
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// _query = props.schema.data.query.replace(reg, id);
|
||||
// }
|
||||
|
||||
// let url = `${parseBaseUrl}classes/${props.schema.data.class}?${_query}`;
|
||||
// const headers = {
|
||||
// "Content-Type": "application/json",
|
||||
// "X-Parse-Application-Id": parseAppId,
|
||||
// "X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
// };
|
||||
// await axios.get(url, { headers: headers }).then(async (res) => {
|
||||
// let temp = [];
|
||||
// let formArray = [];
|
||||
// let _selected = [];
|
||||
// if (editFormData.length > 0) {
|
||||
// editFormData.forEach((x) => {
|
||||
// if (typeof x === "object") {
|
||||
// formArray.push(x.objectId);
|
||||
// } else {
|
||||
// formArray.push(x);
|
||||
// }
|
||||
// });
|
||||
// } else if (
|
||||
// props.schema.selectedData &&
|
||||
// Object.keys(props.schema.selectedData).length !== 0 &&
|
||||
// props.schema.selectedData.constructor === Object
|
||||
// ) {
|
||||
// try {
|
||||
// let selectedDataQuery = props.schema.selectedData.query.replace(
|
||||
// reg,
|
||||
// id
|
||||
// );
|
||||
// let selectedDataUrl = `${parseBaseUrl}classes/${props.schema.selectedData.class}?${selectedDataQuery}`;
|
||||
|
||||
// await axios.get(selectedDataUrl, { headers }).then((sltres) => {
|
||||
// let sltData = sltres.data.results;
|
||||
// sltData.forEach((x) => {
|
||||
// if (props.schema.selectedData.valueKey.includes(".")) {
|
||||
// let sltArr = props.schema.selectedData.valueKey.split(".");
|
||||
|
||||
// if (Array.isArray(x[sltArr[0]])) {
|
||||
// x[sltArr[0]].forEach((l) => {
|
||||
// formArray.push(l[sltArr[1]]);
|
||||
// });
|
||||
// } else {
|
||||
// formArray.push(x[sltArr[0][sltArr[1]]]);
|
||||
// }
|
||||
// } else {
|
||||
// formArray.push(x[props.schema.selectedData.valueKey]);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// } catch (error) {}
|
||||
// }
|
||||
// res.data.results.forEach((x) => {
|
||||
// let obj = {};
|
||||
// if (props.schema.data.valueKey.includes(".")) {
|
||||
// let newArr = props.schema.data.valueKey.split(".");
|
||||
// if (Array.isArray(x[newArr[0]])) {
|
||||
// if (props.schema.data.displayKey.includes(".")) {
|
||||
// let _dis = props.schema.data.displayKey.split(".");
|
||||
// x[newArr[0]].forEach((l) => {
|
||||
// if (formArray.includes(l[newArr[1]])) {
|
||||
// obj = {
|
||||
// label: l[_dis[1]],
|
||||
// value: l[newArr[1]],
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// if (props.schema.selectAll) {
|
||||
// obj = {
|
||||
// label: l[_dis[1]],
|
||||
// value: l[newArr[1]],
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// obj = {
|
||||
// label: l[_dis[1]],
|
||||
// value: l[newArr[1]],
|
||||
// isChecked: false
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// temp.push(obj);
|
||||
// });
|
||||
// } else {
|
||||
// x[newArr[0]].forEach((l) => {
|
||||
// if (formArray.includes(l[newArr[1]])) {
|
||||
// obj = {
|
||||
// label: x[props.schema.data.displayKey],
|
||||
// value: l[newArr[1]],
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// if (props.schema.selectAll) {
|
||||
// obj = {
|
||||
// label: x[props.schema.data.displayKey],
|
||||
// value: l[newArr[1]],
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// obj = {
|
||||
// label: x[props.schema.data.displayKey],
|
||||
// value: l[newArr[1]],
|
||||
// isChecked: false
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// temp.push(obj);
|
||||
// });
|
||||
// }
|
||||
// } else {
|
||||
// if (props.schema.data.displayKey.includes(".")) {
|
||||
// let disArr = props.schema.data.displayKey.split(".");
|
||||
// if (formArray.includes(x[newArr[0]][newArr[1]])) {
|
||||
// obj = {
|
||||
// label: x[disArr[0]][disArr[1]],
|
||||
// value: x[newArr[0]][newArr[1]],
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// if (props.schema.selectAll) {
|
||||
// obj = {
|
||||
// label: x[disArr[0]][disArr[1]],
|
||||
// value: x[newArr[0]][newArr[1]],
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// obj = {
|
||||
// label: x[disArr[0]][disArr[1]],
|
||||
// value: x[newArr[0]][newArr[1]],
|
||||
// isChecked: false
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// if (formArray.includes(x[newArr[0]][newArr[1]])) {
|
||||
// obj = {
|
||||
// label: x[props.schema.data.displayKey],
|
||||
// value: x[newArr[0]][newArr[1]],
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// if (props.schema.selectAll) {
|
||||
// obj = {
|
||||
// label: x[props.schema.data.displayKey],
|
||||
// value: x[newArr[0]][newArr[1]],
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// obj = {
|
||||
// label: x[props.schema.data.displayKey],
|
||||
// value: x[newArr[0]][newArr[1]],
|
||||
// isChecked: false
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// if (Array.isArray(x[props.schema.data.valueKey])) {
|
||||
// x[props.schema.data.valueKey].forEach((t) => {
|
||||
// if (formArray.includes(t)) {
|
||||
// obj = {
|
||||
// label: t,
|
||||
// value: t,
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// if (props.schema.selectAll) {
|
||||
// obj = {
|
||||
// label: t,
|
||||
// value: t,
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// obj = {
|
||||
// label: t,
|
||||
// value: t,
|
||||
// isChecked: false
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// temp.push(obj);
|
||||
// });
|
||||
// } else if (formArray.includes(x[props.schema.data.valueKey])) {
|
||||
// obj = {
|
||||
// label: x[props.schema.data.displayKey],
|
||||
// value: x[props.schema.data.valueKey],
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// if (props.schema.selectAll) {
|
||||
// obj = {
|
||||
// label: x[props.schema.data.displayKey],
|
||||
// value: x[props.schema.data.valueKey],
|
||||
// isChecked: true
|
||||
// };
|
||||
// _selected.push(obj);
|
||||
// } else {
|
||||
// obj = {
|
||||
// label: x[props.schema.data.displayKey],
|
||||
// value: x[props.schema.data.valueKey],
|
||||
// isChecked: false
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// temp.push(obj);
|
||||
// }
|
||||
// });
|
||||
// response = temp;
|
||||
// if (props.schema.uiLayout === "MultiDropdownList") {
|
||||
// if (editFormData) {
|
||||
// let checkData = [];
|
||||
// response.forEach((x) => {
|
||||
// if (x.isChecked) {
|
||||
// checkData.push(x.value);
|
||||
// }
|
||||
// });
|
||||
// setSelected(checkData);
|
||||
// }
|
||||
// setState({ [`${props.name}_DD`]: response });
|
||||
// } else {
|
||||
// setState({ [`${props.name}_DD`]: response });
|
||||
// setSelected(_selected);
|
||||
// }
|
||||
// });
|
||||
// } catch (error) {}
|
||||
// };
|
||||
|
||||
const dragProps = {
|
||||
onDragEnd(fromIndex, toIndex) {
|
||||
const data = [...state[`${props.name}_DD`]];
|
||||
const item = data.splice(fromIndex, 1)[0];
|
||||
data.splice(toIndex, 0, item);
|
||||
let NewList = [];
|
||||
data.forEach((x) => {
|
||||
if (x.isChecked) {
|
||||
NewList.push(x.value);
|
||||
}
|
||||
});
|
||||
|
||||
setState({ [`${props.name}_DD`]: data });
|
||||
setSelected(NewList);
|
||||
},
|
||||
nodeSelector: "li",
|
||||
handleSelector: "a"
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.schema.parent) GetSelectListData();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.schema.uiLayout === "MultiDropdownList") {
|
||||
props.onChange(selected);
|
||||
} else {
|
||||
if (selected && selected.length) {
|
||||
let newData = [];
|
||||
selected.forEach((x) => {
|
||||
newData.push(x.value);
|
||||
});
|
||||
props.onChange(newData);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [selected]);
|
||||
|
||||
// useState(() => {
|
||||
// if (props.formData) {
|
||||
// if (props.formData === "Select") {
|
||||
// } else {
|
||||
// setEditFormData(props.formData);
|
||||
// }
|
||||
// }
|
||||
// // eslint-disable-next-line
|
||||
// }, [props.formData]);
|
||||
|
||||
const handleModalCloseClick = () => {
|
||||
setIsModel(false);
|
||||
setModalIsOpen(false);
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
setModalIsOpen(true);
|
||||
};
|
||||
|
||||
// `handleNewDetails` is used to set just save from quick form to selected option in dropdown
|
||||
const handleNewDetails = (data) => {
|
||||
setState({ [`${props.name}_DD`]: [...state[`${props.name}_DD`], data] });
|
||||
if (selected.length > 0) {
|
||||
setSelected([...selected, data]);
|
||||
} else {
|
||||
setSelected([data]);
|
||||
}
|
||||
};
|
||||
if (props.schema.uiLayout === "MultiDropdownList") {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<LabelField
|
||||
Title={props.schema.title}
|
||||
Name={props.name}
|
||||
Required={props.required}
|
||||
HelpBody={props.schema.helpbody}
|
||||
HelpLink={props.schema.helplink}
|
||||
/>
|
||||
<ReactDragListView {...dragProps}>
|
||||
<ul
|
||||
style={{
|
||||
listStyleType: "square",
|
||||
listStylePosition: "outside",
|
||||
maxHeight: "250px",
|
||||
overflow: "auto",
|
||||
paddingTop: "10px",
|
||||
paddingBottom: "10px",
|
||||
border: "0px solid #b3b3b3",
|
||||
borderRadius: "5px"
|
||||
}}
|
||||
>
|
||||
{state && state[`${props.name}_DD`].length > 0 ? (
|
||||
state[`${props.name}_DD`].map((x, i) => {
|
||||
return (
|
||||
<li key={i} className="multiSelectList">
|
||||
<input
|
||||
key={x.value}
|
||||
onClick={handleCheckChieldElement}
|
||||
type="checkbox"
|
||||
checked={x.isChecked}
|
||||
style={{ marginTop: "7px" }}
|
||||
value={x.value}
|
||||
disabled={props.disabled}
|
||||
/>{" "}
|
||||
<span style={{ marginTop: "7px" }}>{x.name}</span>
|
||||
{props.schema.isSortable && !props.disabled && (
|
||||
<a
|
||||
onClick={(e) => e.preventDefault()}
|
||||
className="pull-right"
|
||||
title="Sort List"
|
||||
style={{ textDecoration: "none" }}
|
||||
>
|
||||
<i
|
||||
className="fa fa-bars"
|
||||
style={{
|
||||
fontSize: "30px",
|
||||
color: "skyblue",
|
||||
paddingTop: "11px"
|
||||
}}
|
||||
></i>
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<li className="multiSelectList">No Record Found</li>
|
||||
)}
|
||||
</ul>
|
||||
</ReactDragListView>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<LabelField
|
||||
Title={props.schema.title}
|
||||
Name={props.name}
|
||||
Required={props.required}
|
||||
HelpBody={props.schema.helpbody}
|
||||
HelpLink={props.schema.helplink}
|
||||
/>
|
||||
<div style={{ display: "flex", gap: 5 }}>
|
||||
<div style={{ flexWrap: "wrap", width: "100%" }}>
|
||||
<Select
|
||||
onSortEnd={onSortEnd}
|
||||
distance={4}
|
||||
isMulti
|
||||
options={(state && state[`${props.name}_DD`]) || []}
|
||||
value={selected}
|
||||
onChange={onChange}
|
||||
closeMenuOnSelect={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{props.schema.data.quickAddFormId && (
|
||||
<div
|
||||
onClick={() => {
|
||||
setIsModel(true);
|
||||
openModal();
|
||||
}}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
borderRadius: 4,
|
||||
border: "1px solid #ccc",
|
||||
minHeight: 38,
|
||||
minWidth: 48,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<i className="fas fa-plus"></i>
|
||||
</div>
|
||||
)}
|
||||
{/* {props.schema.data.quickAddFormId && (
|
||||
<div
|
||||
className="modal fade"
|
||||
id={"multiSelectModal" + props.schema.data.quickAddFormId}
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="exampleModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div className="modal-dialog modal-lg" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button
|
||||
type="button"
|
||||
className="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
onClick={handleModalCloseClick}
|
||||
>
|
||||
<i className="fa fa-times-circle" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{isModal && (
|
||||
<AppendFormInForm
|
||||
id={props.schema.data.quickAddFormId}
|
||||
valueKey={props.schema.data.valueKey}
|
||||
displayKey={props.schema.data.displayKey}
|
||||
details={handleNewDetails}
|
||||
closePopup={handleModalCloseClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
<Modal
|
||||
isOpen={modalIsOpen}
|
||||
onRequestClose={handleModalCloseClick}
|
||||
shouldCloseOnOverlayClick={false}
|
||||
contentLabel="Modal"
|
||||
style={{
|
||||
content: {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
right: "auto",
|
||||
bottom: "auto",
|
||||
transform: "translate(-50%, -50%)",
|
||||
padding: 0
|
||||
},
|
||||
overlay: {
|
||||
width: "100%",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.75)",
|
||||
zIndex: 50
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="min-w-full md:min-w-[500px]">
|
||||
<div
|
||||
type="button"
|
||||
className="flex justify-between items-center p-3 border-b-[1px] border-gray-300"
|
||||
>
|
||||
<div className=" text-black text-xl font-semibold pl-3">
|
||||
Add Signer
|
||||
</div>
|
||||
<button onClick={handleModalCloseClick}>
|
||||
<i
|
||||
style={{ fontSize: 25 }}
|
||||
className="fa fa-times-circle"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isModal && (
|
||||
<AppendFormInForm
|
||||
id={props.schema.data.quickAddFormId}
|
||||
valueKey={props.schema.data.valueKey}
|
||||
displayKey={props.schema.data.displayKey}
|
||||
details={handleNewDetails}
|
||||
closePopup={handleModalCloseClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
Level1_Dropdown: state.Level1_Dropdown,
|
||||
Level2_Dropdown: state.Level2_Dropdown,
|
||||
Level3_Dropdown: state.Level3_Dropdown
|
||||
};
|
||||
};
|
||||
export default connect(mapStateToProps, null)(MultiSelectField);
|
||||
@@ -1,92 +0,0 @@
|
||||
import React from "react";
|
||||
import ObjectField from "@rjsf/core/lib/components/fields/ObjectField";
|
||||
import validator from "@rjsf/validator-ajv8";
|
||||
import { Col } from "react-bootstrap";
|
||||
import { retrieveSchema } from "@rjsf/utils";
|
||||
|
||||
class GridField extends ObjectField {
|
||||
state = {};
|
||||
render() {
|
||||
const {
|
||||
uiSchema,
|
||||
errorSchema,
|
||||
idSchema,
|
||||
|
||||
disabled,
|
||||
readonly,
|
||||
onBlur,
|
||||
formData
|
||||
} = this.props;
|
||||
const { fields, rootSchema } = this.props.registry;
|
||||
|
||||
const { SchemaField } = fields;
|
||||
const schema = retrieveSchema(validator, this.props.schema, rootSchema);
|
||||
const title = schema.title === undefined ? "" : schema.title;
|
||||
const description =
|
||||
schema && schema.description === undefined ? "" : schema.description;
|
||||
// console.log('this.props.schema ', this.props.schema)
|
||||
// console.log("schema ", schema);
|
||||
const layout = uiSchema["ui:layout"];
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
{title ? <h4>{title}</h4> : null}
|
||||
{description ? <h6>{description}</h6> : null}
|
||||
{layout.map((row, index) => {
|
||||
return (
|
||||
<div className="row" key={index}>
|
||||
{Object.keys(row).map((name, index) => {
|
||||
const { doShow, ...rowProps } = row[name];
|
||||
let style = {};
|
||||
if (doShow && !doShow({ formData })) {
|
||||
style = { display: "none" };
|
||||
}
|
||||
if (schema.properties[name]) {
|
||||
return (
|
||||
<Col {...rowProps} key={index} style={style}>
|
||||
<SchemaField
|
||||
name={name}
|
||||
required={this.isRequired(name)}
|
||||
schema={schema.properties[name]}
|
||||
uiSchema={uiSchema[name]}
|
||||
errorSchema={errorSchema[name]}
|
||||
idSchema={idSchema[name]}
|
||||
formData={formData[name]}
|
||||
onChange={this.onPropertyChange(name)}
|
||||
onBlur={onBlur}
|
||||
registry={this.props.registry}
|
||||
disabled={disabled}
|
||||
readonly={readonly}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
} else {
|
||||
const { render, ...rowProps } = row[name];
|
||||
let UIComponent = () => null;
|
||||
|
||||
if (render) {
|
||||
UIComponent = render;
|
||||
}
|
||||
|
||||
return (
|
||||
<Col {...rowProps} key={index} style={style}>
|
||||
<UIComponent
|
||||
name={name}
|
||||
formData={formData}
|
||||
errorSchema={errorSchema}
|
||||
uiSchema={uiSchema}
|
||||
schema={schema}
|
||||
registry={this.props.registry}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default GridField;
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
function TimeWidget(props) {
|
||||
const {
|
||||
onChange,
|
||||
registry: {
|
||||
widgets: { BaseInput }
|
||||
}
|
||||
} = props;
|
||||
return (
|
||||
<BaseInput
|
||||
type="time"
|
||||
{...props}
|
||||
onChange={(value) => onChange(value || undefined)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
TimeWidget.propTypes = {
|
||||
value: PropTypes.string
|
||||
};
|
||||
}
|
||||
|
||||
export default TimeWidget;
|
||||
@@ -0,0 +1,611 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import "../../styles/opensigndrive.css";
|
||||
import axios from "axios";
|
||||
import * as ContextMenu from "@radix-ui/react-context-menu";
|
||||
import { saveAs } from "file-saver";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Table from "react-bootstrap/Table";
|
||||
import * as HoverCard from "@radix-ui/react-hover-card";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import FolderModal from "../shared/fields/FolderModal";
|
||||
|
||||
function DriveBody(props) {
|
||||
const [rename, setRename] = useState("");
|
||||
const [renameValue, setRenameValue] = useState("");
|
||||
const inputRef = useRef(null);
|
||||
const [isOpenMoveModal, setIsOpenMoveModal] = useState(false);
|
||||
const [selectDoc, setSelectDoc] = useState();
|
||||
const [isDeleteDoc, setIsDeleteDoc] = useState(false);
|
||||
const contextMenu = [
|
||||
{ type: "Download", icon: "fa-light fa-arrow-down" },
|
||||
{ type: "Rename", icon: "fa-light fa-font" },
|
||||
{ type: "Move", icon: "fa-light fa-file-export" },
|
||||
{ type: "Delete", icon: "fa-light fa-trash" }
|
||||
];
|
||||
const navigate = useNavigate();
|
||||
|
||||
//to focus input box on press rename to change doc name
|
||||
useEffect(() => {
|
||||
if (rename && inputRef.current) {
|
||||
setTimeout(() => {
|
||||
inputRef.current.focus();
|
||||
}, 10);
|
||||
}
|
||||
}, [rename]);
|
||||
|
||||
//function to handle folder component
|
||||
const handleOnclikFolder = (data) => {
|
||||
const folderData = {
|
||||
name: data.Name,
|
||||
objectId: data.objectId
|
||||
};
|
||||
props.setFolderName((prev) => [...prev, folderData]);
|
||||
props.setIsLoading({
|
||||
isLoad: true,
|
||||
message: "This might take some time"
|
||||
});
|
||||
props.setDocId(data.objectId);
|
||||
props.setPdfData([]);
|
||||
props.setSkip(0);
|
||||
};
|
||||
//function for change doc name and update doc name in _document class
|
||||
const handledRenameDoc = async (data) => {
|
||||
setRename("");
|
||||
const trimmedValue = renameValue.trim();
|
||||
if (trimmedValue.length > 0) {
|
||||
const updateName = {
|
||||
Name: renameValue
|
||||
};
|
||||
const docId = data.objectId;
|
||||
const docData = props.pdfData;
|
||||
const updatedData = docData.map((item) => {
|
||||
if (item.objectId === docId) {
|
||||
// If the item's ID matches the target ID, update the name
|
||||
return { ...item, Name: renameValue };
|
||||
}
|
||||
// If the item's ID doesn't match, keep it unchanged
|
||||
return item;
|
||||
});
|
||||
props.setPdfData(updatedData);
|
||||
props.sortingData(null, null, updatedData);
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem(
|
||||
"baseUrl"
|
||||
)}classes/contracts_Document/${docId}`,
|
||||
updateName,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
// const res = result.data;
|
||||
// console.log("res", res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Err ", err);
|
||||
props.setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "something went wrong"
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//function for navigate user to microapp-signature component
|
||||
const checkPdfStatus = async (data) => {
|
||||
const signerExist = data.Signers && data.Signers;
|
||||
const isDecline = data.IsDeclined && data.IsDeclined;
|
||||
const isPlaceholder = data.Placeholders && data.Placeholders;
|
||||
const signedUrl = data.SignedUrl;
|
||||
//checking if document has completed and request signature flow
|
||||
if (data?.IsCompleted && signerExist?.length > 0) {
|
||||
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||
}
|
||||
//checking if document has completed and signyour-self flow
|
||||
else if (!signerExist && !isPlaceholder) {
|
||||
navigate(`/signaturePdf/${data.objectId}`);
|
||||
}
|
||||
//checking if document has declined by someone
|
||||
else if (isDecline) {
|
||||
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||
//checking draft type document
|
||||
} else if (
|
||||
signerExist?.length > 0 &&
|
||||
isPlaceholder?.length > 0 &&
|
||||
!signedUrl
|
||||
) {
|
||||
navigate(`/placeHolderSign/${data.objectId}`);
|
||||
}
|
||||
//Inprogress document
|
||||
else if (isPlaceholder?.length > 0 && signedUrl) {
|
||||
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||
} //placeholder draft document
|
||||
else if (
|
||||
(signerExist?.length > 0 &&
|
||||
(!isPlaceholder || isPlaceholder?.length === 0)) ||
|
||||
((!signerExist || signerExist?.length === 0) && isPlaceholder?.length > 0)
|
||||
) {
|
||||
navigate(`/placeHolderSign/${data.objectId}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuItemClick = (selectType, data) => {
|
||||
switch (selectType) {
|
||||
case "Download": {
|
||||
const pdfName = data && data.Name;
|
||||
const pdfUrl = data && data.SignedUrl ? data.SignedUrl : data.URL;
|
||||
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
|
||||
break;
|
||||
}
|
||||
case "Rename": {
|
||||
setRenameValue(data.Name);
|
||||
setRename(data.objectId);
|
||||
break;
|
||||
}
|
||||
case "Delete": {
|
||||
setIsDeleteDoc(true);
|
||||
setSelectDoc(data);
|
||||
break;
|
||||
}
|
||||
case "Move": {
|
||||
handleMoveDocument(data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
null;
|
||||
}
|
||||
};
|
||||
//function for delete document
|
||||
const handleDeleteDocument = async (docData) => {
|
||||
setIsDeleteDoc(false);
|
||||
const docId = docData.objectId;
|
||||
const data = {
|
||||
IsArchive: true
|
||||
};
|
||||
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${docId}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
const res = result.data;
|
||||
if (res) {
|
||||
const updatedData = props.pdfData.filter((x) => x.objectId !== docId);
|
||||
props.setPdfData(updatedData);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Err ", err);
|
||||
props.setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "something went wrong"
|
||||
});
|
||||
});
|
||||
};
|
||||
const handleMoveDocument = async (docData) => {
|
||||
setIsOpenMoveModal(true);
|
||||
setSelectDoc(docData);
|
||||
};
|
||||
//function for move document from one folder to another folder
|
||||
const handleMoveFolder = async (selectFolderData) => {
|
||||
const selecFolderId = selectDoc?.Folder?.objectId;
|
||||
const moveFolderId = selectFolderData?.ObjectId;
|
||||
let updateDocId = selectDoc?.objectId;
|
||||
let updateData;
|
||||
const checkExist = moveFolderId
|
||||
? selecFolderId === moveFolderId
|
||||
? true
|
||||
: false
|
||||
: selecFolderId
|
||||
? false
|
||||
: true;
|
||||
if (!checkExist) {
|
||||
if (moveFolderId) {
|
||||
updateData = {
|
||||
Folder: {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Document",
|
||||
objectId: moveFolderId
|
||||
}
|
||||
};
|
||||
} else {
|
||||
updateData = {
|
||||
Folder: { __op: "Delete" }
|
||||
};
|
||||
}
|
||||
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem(
|
||||
"baseUrl"
|
||||
)}classes/contracts_Document/${updateDocId}`,
|
||||
updateData,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
.then((Listdata) => {
|
||||
// console.log("Listdata ", Listdata);
|
||||
const res = Listdata.data;
|
||||
if (res) {
|
||||
const updatedData = props.pdfData.filter(
|
||||
(x) => x.objectId !== updateDocId
|
||||
);
|
||||
props.setPdfData(updatedData);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("err", err);
|
||||
});
|
||||
|
||||
setIsOpenMoveModal(false);
|
||||
} else {
|
||||
alert("folder already exist!");
|
||||
setIsOpenMoveModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const sanitizeFileName = (pdfName) => {
|
||||
// Replace spaces with underscore
|
||||
return pdfName.replace(/ /g, "_");
|
||||
};
|
||||
|
||||
const handleEnterPress = (e, data) => {
|
||||
if (e.key === "Enter") {
|
||||
handledRenameDoc(data);
|
||||
}
|
||||
};
|
||||
|
||||
//component to handle type of document and render according to type
|
||||
const handleFolderData = (data, ind, listType) => {
|
||||
let createddate, status, isDecline, signerExist, isComplete;
|
||||
if (data.Type !== "Folder") {
|
||||
const expireDate = data.ExpiryDate && data.ExpiryDate.iso;
|
||||
const createdDate = data.createdAt && data.createdAt;
|
||||
createddate = new Date(createdDate).toLocaleDateString();
|
||||
isComplete = data.IsCompleted && data.IsCompleted ? true : false;
|
||||
isDecline = data.IsDeclined && data.IsDeclined;
|
||||
signerExist = data.Signers && data.Signers;
|
||||
const signedUrl = data.SignedUrl;
|
||||
|
||||
const expireUpdateDate = new Date(expireDate).getTime();
|
||||
const currDate = new Date().getTime();
|
||||
let isExpire = false;
|
||||
if (currDate > expireUpdateDate) {
|
||||
isExpire = true;
|
||||
}
|
||||
|
||||
if (isComplete) {
|
||||
status = "Completed";
|
||||
} else if (isDecline) {
|
||||
status = "Declined";
|
||||
} else if (!signedUrl) {
|
||||
status = "Draft";
|
||||
} else if (isExpire) {
|
||||
status = "Expired";
|
||||
} else {
|
||||
status = "In Progress";
|
||||
}
|
||||
}
|
||||
|
||||
const signersName = () => {
|
||||
const getSignersName = signerExist.map((data) => data.Name);
|
||||
const signerName = getSignersName.join(", ");
|
||||
|
||||
return (
|
||||
<span className="text-[12px] font-medium w-[90%] break-words">
|
||||
{signerName}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
return listType === "table" ? (
|
||||
data.Type === "Folder" ? (
|
||||
<tr onClick={() => handleOnclikFolder(data)}>
|
||||
<td className="cursor-pointer flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
className="w-[26px] h-[26px] fill-current op-text-secondary"
|
||||
>
|
||||
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
|
||||
</svg>
|
||||
<span className="text-[12px] font-medium">{data.Name}</span>
|
||||
</td>
|
||||
<td>_</td>
|
||||
<td>Folder</td>
|
||||
<td>_</td>
|
||||
<td>_</td>
|
||||
</tr>
|
||||
) : (
|
||||
<tr onClick={() => checkPdfStatus(data)}>
|
||||
<td className="cursor-pointer flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 384 512"
|
||||
className="w-[26px] h-[26px] fill-current op-text-primary"
|
||||
>
|
||||
<path d="M374.629 150.627L233.371 9.373C227.371 3.371 219.23 0 210.746 0H64C28.652 0 0 28.652 0 64V448C0 483.345 28.652 512 64 512H320C355.348 512 384 483.345 384 448V173.254C384 164.767 380.629 156.629 374.629 150.627ZM224 22.629L361.375 160H248C234.781 160 224 149.234 224 136V22.629ZM368 448C368 474.467 346.469 496 320 496H64C37.531 496 16 474.467 16 448V64C16 37.533 37.531 16 64 16H208V136C208 158.062 225.938 176 248 176H368V448ZM96 264C96 268.406 99.594 272 104 272H280C284.406 272 288 268.406 288 264S284.406 256 280 256H104C99.594 256 96 259.594 96 264ZM280 320H104C99.594 320 96 323.594 96 328S99.594 336 104 336H280C284.406 336 288 332.406 288 328S284.406 320 280 320ZM280 384H104C99.594 384 96 387.594 96 392S99.594 400 104 400H280C284.406 400 288 396.406 288 392S284.406 384 280 384Z" />
|
||||
</svg>
|
||||
<span className="text-[12px] font-medium">{data.Name}</span>
|
||||
</td>
|
||||
<td>{createddate}</td>
|
||||
<td>Pdf</td>
|
||||
<td>{status}</td>
|
||||
<td>
|
||||
<i
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleMenuItemClick("Download", data);
|
||||
}}
|
||||
className="fa-light fa-download mr-[8px] op-text-primary"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
) : listType === "list" && data.Type === "Folder" ? (
|
||||
<div key={ind} className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger className="flex flex-col justify-center items-center">
|
||||
{/* folder */}
|
||||
<div
|
||||
data-tut={props.dataTutSeventh}
|
||||
onClick={() => handleOnclikFolder(data)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
className="w-[100px] h-[100px] fill-current op-text-secondary"
|
||||
>
|
||||
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
|
||||
</svg>
|
||||
{rename === data.objectId ? (
|
||||
<input
|
||||
onFocus={() => {
|
||||
inputRef.current.setSelectionRange(0, 0);
|
||||
const input = inputRef.current;
|
||||
if (input) {
|
||||
input.select();
|
||||
}
|
||||
}}
|
||||
autoFocus={true}
|
||||
type="text"
|
||||
onBlur={() => handledRenameDoc(data)}
|
||||
onKeyDown={(e) => handleEnterPress(e, data)}
|
||||
ref={inputRef}
|
||||
defaultValue={renameValue}
|
||||
// value={renameValue}
|
||||
onChange={(e) => setRenameValue(e.target.value)}
|
||||
className="w-[100px] border-[1.5px] border-black rounded-sm text-[10px]"
|
||||
/>
|
||||
) : (
|
||||
<span className="fileName">{data.Name}</span>
|
||||
)}
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content
|
||||
className="ContextMenuContent"
|
||||
sideOffset={5}
|
||||
align="end"
|
||||
>
|
||||
<ContextMenu.Item
|
||||
onClick={() => handleMenuItemClick("Rename", data)}
|
||||
className="ContextMenuItem"
|
||||
>
|
||||
<i className="fa-light fa-font mr-[8px]"></i>
|
||||
<span>Rename</span>
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu.Root>
|
||||
</div>
|
||||
) : (
|
||||
<HoverCard.Root openDelay={0} closeDelay={100}>
|
||||
<HoverCard.Trigger asChild>
|
||||
<div>
|
||||
<ContextMenu.Root>
|
||||
<div className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<ContextMenu.Trigger className="flex flex-col justify-center items-center">
|
||||
{/* pdf */}
|
||||
<div
|
||||
data-tut={props.dataTutSixth}
|
||||
onClick={() => checkPdfStatus(data)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 384 512"
|
||||
className="w-[100px] h-[100px] fill-current op-text-primary"
|
||||
>
|
||||
<path d="M374.629 150.627L233.371 9.373C227.371 3.371 219.23 0 210.746 0H64C28.652 0 0 28.652 0 64V448C0 483.345 28.652 512 64 512H320C355.348 512 384 483.345 384 448V173.254C384 164.767 380.629 156.629 374.629 150.627ZM224 22.629L361.375 160H248C234.781 160 224 149.234 224 136V22.629ZM368 448C368 474.467 346.469 496 320 496H64C37.531 496 16 474.467 16 448V64C16 37.533 37.531 16 64 16H208V136C208 158.062 225.938 176 248 176H368V448ZM96 264C96 268.406 99.594 272 104 272H280C284.406 272 288 268.406 288 264S284.406 256 280 256H104C99.594 256 96 259.594 96 264ZM280 320H104C99.594 320 96 323.594 96 328S99.594 336 104 336H280C284.406 336 288 332.406 288 328S284.406 320 280 320ZM280 384H104C99.594 384 96 387.594 96 392S99.594 400 104 400H280C284.406 400 288 396.406 288 392S284.406 384 280 384Z" />
|
||||
</svg>
|
||||
{rename === data.objectId ? (
|
||||
<input
|
||||
autoFocus={true}
|
||||
type="text"
|
||||
onFocus={() => {
|
||||
inputRef.current.setSelectionRange(0, 0);
|
||||
const input = inputRef.current;
|
||||
if (input) {
|
||||
input.select();
|
||||
}
|
||||
}}
|
||||
onBlur={() => handledRenameDoc(data)}
|
||||
onKeyDown={(e) => handleEnterPress(e, data)}
|
||||
ref={inputRef}
|
||||
defaultValue={renameValue}
|
||||
// value={renameValue}
|
||||
onChange={(e) => setRenameValue(e.target.value)}
|
||||
className="w-[100px] border-[1.5px] border-black rounded-sm text-[10px]"
|
||||
/>
|
||||
) : (
|
||||
<span className="fileName">{data.Name}</span>
|
||||
)}
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
{status === "Completed" ? (
|
||||
<div className="status-badge completed">
|
||||
<i className="fa-light fa-check-circle"></i>
|
||||
</div>
|
||||
) : status === "Declined" ? (
|
||||
<div className="status-badge declined">
|
||||
<i className="fa-light fa-thumbs-down"></i>
|
||||
</div>
|
||||
) : status === "Expired" ? (
|
||||
<div className="status-badge expired">
|
||||
<i className="fa-light fa-hourglass-end"></i>
|
||||
</div>
|
||||
) : status === "Draft" ? (
|
||||
<div className="status-badge draft">
|
||||
<i className="fa-light fa-file"></i>
|
||||
</div>
|
||||
) : (
|
||||
status === "In Progress" && (
|
||||
<div className="status-badge in-progress">
|
||||
<i className="fa-light fa-paper-plane"></i>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content
|
||||
className="ContextMenuContent"
|
||||
sideOffset={5}
|
||||
align="end"
|
||||
>
|
||||
{contextMenu.map((menu, ind) => {
|
||||
return (
|
||||
<ContextMenu.Item
|
||||
key={ind}
|
||||
onClick={() => handleMenuItemClick(menu.type, data)}
|
||||
className="ContextMenuItem"
|
||||
>
|
||||
<i className={menu.icon}></i>
|
||||
<span className="ml-[8px]">{menu.type}</span>
|
||||
</ContextMenu.Item>
|
||||
);
|
||||
})}
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu.Root>
|
||||
</div>
|
||||
</HoverCard.Trigger>
|
||||
<HoverCard.Portal>
|
||||
<HoverCard.Content className="HoverCardContent" sideOffset={5}>
|
||||
<strong className="text-[13px]">Title: </strong>
|
||||
<span className="text-[12px] font-medium mb-0"> {data.Name}</span>
|
||||
<br />
|
||||
<strong className="text-[13px]">Status: </strong>
|
||||
<span className="text-[12px] font-medium"> {status}</span>
|
||||
<br />
|
||||
<strong className="text-[13px]">Created Date: </strong>
|
||||
<span className="text-[12px] font-medium">{createddate}</span>
|
||||
<br />
|
||||
{signerExist && (
|
||||
<>
|
||||
<strong className="text-[13px]">Signers: </strong>
|
||||
{signersName()}
|
||||
</>
|
||||
)}
|
||||
|
||||
<HoverCard.Arrow className="HoverCardArrow" />
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Portal>
|
||||
</HoverCard.Root>
|
||||
);
|
||||
};
|
||||
|
||||
//component to handle type of document and render according to type
|
||||
return (
|
||||
<>
|
||||
{props.isList ? (
|
||||
<div className="container" style={{ overflowX: "auto" }}>
|
||||
<Table striped bordered hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Created Date</th>
|
||||
<th>Type</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{props.pdfData.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{handleFolderData(data, ind, "table")}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
|
||||
{props.pdfData.map((data, ind) => {
|
||||
return <div key={ind}>{handleFolderData(data, ind, "list")}</div>;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isOpenMoveModal && (
|
||||
<FolderModal
|
||||
onSuccess={handleMoveFolder}
|
||||
isOpenModal={isOpenMoveModal}
|
||||
folderCls={"contracts_Document"}
|
||||
setIsOpenMoveModal={setIsOpenMoveModal}
|
||||
setPdfData={props.setPdfData}
|
||||
/>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isDeleteDoc}
|
||||
title={"Delete Document"}
|
||||
handleClose={() => setIsDeleteDoc(false)}
|
||||
>
|
||||
<div className="h-full p-[20px] text-base-content">
|
||||
<p>Are you sure you want to delete this document?</p>
|
||||
<div className="h-[1px] w-full bg-[#9f9f9f] my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => handleDeleteDocument(selectDoc)}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary mr-2"
|
||||
>
|
||||
Yes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsDeleteDoc(false)}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral"
|
||||
>
|
||||
No
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DriveBody;
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
|
||||
const AddRoleModal = (props) => {
|
||||
return (
|
||||
<ModalUi
|
||||
title={"Add Role"}
|
||||
isOpen={props.isModalRole}
|
||||
handleClose={props.handleCloseRoleModal}
|
||||
>
|
||||
<div className="h-full py-[10px] px-[20px]">
|
||||
<form className="flex flex-col" onSubmit={props.handleAddRole}>
|
||||
<input
|
||||
value={props.roleName}
|
||||
onChange={(e) => props.setRoleName(e.target.value)}
|
||||
placeholder={
|
||||
props.signersdata.length > 0
|
||||
? "User " + (props.signersdata.length + 1)
|
||||
: "User 1"
|
||||
}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs mt-1"
|
||||
/>
|
||||
<p className="text-[gray] text-[11px] mt-[5px] mb-[10px] ml-[10px]">
|
||||
e.g: Customer, Hr, Director, Manager, Student, etc...
|
||||
</p>
|
||||
<div>
|
||||
<div className="h-[1px] w-full bg-[#9f9f9f] mb-[10px]"></div>
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
onClick={props.handleCloseRoleModal}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-2"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddRoleModal;
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
|
||||
function BorderResize({ right, top }) {
|
||||
return (
|
||||
<div
|
||||
className={`${right ? `-right-[12px]` : "-right-[1px]"} ${
|
||||
top ? `-bottom-[11px]` : "-bottom-[1px]"
|
||||
} absolute inline-block w-[14px] h-[14px] hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BorderResize;
|
||||
@@ -1,7 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import "./certificate.css";
|
||||
import opensignLogo from "../../assests/open-sign-logo.png";
|
||||
|
||||
import opensignLogo from "../../assets/images/logo.png";
|
||||
import {
|
||||
Page,
|
||||
Text,
|
||||
@@ -18,6 +16,7 @@ function Certificate({ pdfData }) {
|
||||
|
||||
useEffect(() => {
|
||||
handleSignerData();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const handleSignerData = () => {
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
function DefaultSignature({ defaultSignImg, xyPostion, setDefaultSignAlert }) {
|
||||
const confirmToaddDefaultSign = () => {
|
||||
if (xyPostion.length > 0) {
|
||||
setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage:
|
||||
"Are you sure you want to auto sign at requested all locations?"
|
||||
});
|
||||
} else {
|
||||
setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage: "please select position!"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-tut="reactourThird">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
Signature
|
||||
</div>
|
||||
<div className="flex flex-col items-center mt-[10px] font-semibold">
|
||||
<p className="text-base-content">Your Signature</p>
|
||||
<div className="op-card shadow-md h-[111px] w-[90%] p-2">
|
||||
<img
|
||||
alt="signature"
|
||||
className="w-full h-full object-contain"
|
||||
src={defaultSignImg}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mt-[10px]"
|
||||
onClick={() => confirmToaddDefaultSign()}
|
||||
>
|
||||
Auto Sign All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DefaultSignature;
|
||||
@@ -0,0 +1,93 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import LoaderWithMsg from "../../primitives/LoaderWithMsg";
|
||||
import { contractDocument } from "../../constant/Utils";
|
||||
import HandleError from "../../primitives/HandleError";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
function useQuery() {
|
||||
return new URLSearchParams(useLocation().search);
|
||||
}
|
||||
function DraftDocument() {
|
||||
const navigate = useNavigate();
|
||||
const query = useQuery();
|
||||
const docId = query.get("docId");
|
||||
const [isLoading, setIsLoading] = useState({
|
||||
isLoader: true,
|
||||
message: "This might take some time"
|
||||
});
|
||||
useEffect(() => {
|
||||
getDocumentDetails();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
//get document details
|
||||
const getDocumentDetails = async () => {
|
||||
//getting document details
|
||||
const documentData = await contractDocument(docId);
|
||||
if (documentData && documentData.length > 0) {
|
||||
handleDraftDoc(documentData);
|
||||
} else if (
|
||||
documentData === "Error: Something went wrong!" ||
|
||||
(documentData.result && documentData.result.error)
|
||||
) {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: "Error: Something went wrong!"
|
||||
});
|
||||
} else {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: "No data found!"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//check document type and render on signyour self and placeholder route
|
||||
const handleDraftDoc = (documentData) => {
|
||||
const data = documentData[0];
|
||||
const signerExist = data.Signers && data.Signers;
|
||||
const isDecline = data.IsDeclined && data.IsDeclined;
|
||||
const isPlaceholder = data.Placeholders && data.Placeholders;
|
||||
const signedUrl = data.SignedUrl;
|
||||
//checking if document has completed and request signature flow
|
||||
if (data?.IsCompleted && signerExist?.length > 0) {
|
||||
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||
}
|
||||
//checking if document has completed and signyour-self flow
|
||||
else if (!signerExist && !isPlaceholder) {
|
||||
navigate(`/signaturePdf/${data.objectId}`);
|
||||
}
|
||||
//checking if document has declined by someone
|
||||
else if (isDecline) {
|
||||
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||
//checking draft type document
|
||||
} else if (
|
||||
signerExist?.length > 0 &&
|
||||
isPlaceholder?.length > 0 &&
|
||||
!signedUrl
|
||||
) {
|
||||
navigate(`/placeHolderSign/${data.objectId}`);
|
||||
}
|
||||
//Inprogress document
|
||||
else if (isPlaceholder?.length > 0 && signedUrl) {
|
||||
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||
} //placeholder draft document
|
||||
else if (
|
||||
(signerExist?.length > 0 &&
|
||||
(!isPlaceholder || isPlaceholder?.length === 0)) ||
|
||||
((!signerExist || signerExist?.length === 0) && isPlaceholder?.length > 0)
|
||||
) {
|
||||
navigate(`/placeHolderSign/${data.objectId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading.isLoader ? (
|
||||
<LoaderWithMsg isLoading={isLoading} />
|
||||
) : (
|
||||
<HandleError handleError={isLoading.message} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DraftDocument;
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { getWidgetType, widgets } from "../../constant/Utils";
|
||||
|
||||
function DragElement(item) {
|
||||
const getWidgets = widgets;
|
||||
const filterWidgetPreview = getWidgets.filter(
|
||||
(data) => data.type === item?.text
|
||||
);
|
||||
|
||||
return <div>{getWidgetType(filterWidgetPreview[0])}</div>;
|
||||
}
|
||||
|
||||
export default DragElement;
|
||||
@@ -0,0 +1,381 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { radioButtonWidget } from "../../constant/Utils";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
function DropdownWidgetOption(props) {
|
||||
const [dropdownOptionList, setDropdownOptionList] = useState([
|
||||
"option-1",
|
||||
"option-2"
|
||||
]);
|
||||
const [minCount, setMinCount] = useState(0);
|
||||
const [maxCount, setMaxCount] = useState(0);
|
||||
const [dropdownName, setDropdownName] = useState(props.type);
|
||||
const [isReadOnly, setIsReadOnly] = useState(false);
|
||||
const [isHideLabel, setIsHideLabel] = useState(false);
|
||||
const [status, setStatus] = useState("required");
|
||||
const [defaultValue, setDefaultValue] = useState("");
|
||||
const statusArr = ["required", "optional"];
|
||||
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
|
||||
|
||||
const resetState = () => {
|
||||
setDropdownOptionList(["option-1", "option-2"]);
|
||||
setDropdownName(props.type);
|
||||
setIsReadOnly(false);
|
||||
setIsHideLabel(false);
|
||||
setMinCount(0);
|
||||
setMaxCount(0);
|
||||
setDefaultCheckbox([]);
|
||||
setDefaultValue("");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.currWidgetsDetails?.options?.name &&
|
||||
props.currWidgetsDetails?.options?.values
|
||||
) {
|
||||
setDropdownName(props.currWidgetsDetails?.options?.name);
|
||||
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
|
||||
setMinCount(
|
||||
props.currWidgetsDetails?.options?.validation?.minRequiredCount
|
||||
);
|
||||
setMaxCount(
|
||||
props.currWidgetsDetails?.options?.validation?.maxRequiredCount
|
||||
);
|
||||
setIsReadOnly(props.currWidgetsDetails?.options?.isReadOnly);
|
||||
setIsHideLabel(props.currWidgetsDetails?.options?.isHideLabel);
|
||||
setStatus(props.currWidgetsDetails?.options?.status || "required");
|
||||
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
|
||||
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
|
||||
} else {
|
||||
setStatus("required");
|
||||
resetState();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.currWidgetsDetails]);
|
||||
const handleInputChange = (index, value) => {
|
||||
setDropdownOptionList((prevInputs) => {
|
||||
const newInputs = [...prevInputs];
|
||||
newInputs[index] = value;
|
||||
return newInputs;
|
||||
});
|
||||
};
|
||||
|
||||
//function add add checkbox option and add width of checkbox
|
||||
const handleAddInput = () => {
|
||||
const deleteOption = false;
|
||||
const addOption = true;
|
||||
setDropdownOptionList((prevInputs) => [...prevInputs, ""]);
|
||||
props.handleSaveWidgetsOptions(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
addOption,
|
||||
deleteOption
|
||||
);
|
||||
};
|
||||
|
||||
//function add add checkbox option and delete width of checkbox
|
||||
const handleDeleteInput = (ind) => {
|
||||
const deleteOption = true;
|
||||
const addOption = false;
|
||||
const getUpdatedOptions = dropdownOptionList.filter(
|
||||
(data, index) => index !== ind
|
||||
);
|
||||
setDropdownOptionList(getUpdatedOptions);
|
||||
props.handleSaveWidgetsOptions(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
addOption,
|
||||
deleteOption
|
||||
);
|
||||
};
|
||||
|
||||
const handleSaveOption = () => {
|
||||
const defaultData =
|
||||
defaultCheckbox && defaultCheckbox.length > 0
|
||||
? defaultCheckbox
|
||||
: defaultValue;
|
||||
|
||||
props.handleSaveWidgetsOptions(
|
||||
dropdownName,
|
||||
dropdownOptionList,
|
||||
minCount,
|
||||
maxCount,
|
||||
isReadOnly,
|
||||
null,
|
||||
null,
|
||||
status,
|
||||
defaultData,
|
||||
isHideLabel
|
||||
);
|
||||
// props.setShowDropdown(false);
|
||||
setDropdownOptionList(["option-1", "option-2"]);
|
||||
setDropdownName(props.type);
|
||||
// props.setCurrWidgetsDetails({});
|
||||
setIsReadOnly(false);
|
||||
setIsHideLabel(false);
|
||||
setMinCount(0);
|
||||
setMaxCount(0);
|
||||
setDefaultCheckbox([]);
|
||||
setDefaultValue("");
|
||||
};
|
||||
|
||||
const handleSetMinMax = (e) => {
|
||||
const minValue = e.target.value;
|
||||
if (minValue > dropdownOptionList.length) {
|
||||
return "";
|
||||
} else {
|
||||
return minValue;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDefaultCheck = (index) => {
|
||||
const getDefaultCheck = defaultCheckbox.includes(index);
|
||||
if (getDefaultCheck) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
|
||||
<div className="h-full p-[15px] text-base-content">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleSaveOption();
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<label className="text-[13px] font-semibold">
|
||||
Name<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
defaultValue={dropdownName}
|
||||
value={dropdownName}
|
||||
onChange={(e) => setDropdownName(e.target.value)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
|
||||
<label className="text-[13px] font-semibold mt-[5px]">
|
||||
Options
|
||||
</label>
|
||||
<div className="flex flex-col">
|
||||
{dropdownOptionList.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-row mb-[5px] items-center"
|
||||
>
|
||||
{props.type === "checkbox" && !props.isSignYourself && (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={handleDefaultCheck(index)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
const getDefaultCheck =
|
||||
defaultCheckbox?.includes(index);
|
||||
if (!getDefaultCheck) {
|
||||
setDefaultCheckbox((prev) => [...prev, index]);
|
||||
}
|
||||
} else {
|
||||
const removeOption = defaultCheckbox.filter(
|
||||
(data) => data !== index
|
||||
);
|
||||
setDefaultCheckbox(removeOption);
|
||||
}
|
||||
}}
|
||||
className="op-checkbox focus:outline-none hover:border-base-content mr-[5px]"
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
type="text"
|
||||
value={option}
|
||||
onChange={(e) => handleInputChange(index, e.target.value)}
|
||||
/>
|
||||
|
||||
<i
|
||||
className="fa-light fa-rectangle-xmark text-[25px] ml-[10px] text-accent cursor-pointer"
|
||||
onClick={() => handleDeleteInput(index)}
|
||||
></i>
|
||||
</div>
|
||||
))}
|
||||
<i
|
||||
onClick={handleAddInput}
|
||||
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
|
||||
></i>
|
||||
{isEnableSubscription && (
|
||||
<div>
|
||||
{props.type === "checkbox" && !props.isSignYourself && (
|
||||
<>
|
||||
<label
|
||||
className={`${
|
||||
!props.isSubscribe ? "text-[gray]" : ""
|
||||
} text-[13px] font-semibold`}
|
||||
>
|
||||
Minimun check
|
||||
</label>
|
||||
{!props.isSubscribe && isEnableSubscription && (
|
||||
<Upgrade />
|
||||
)}
|
||||
<input
|
||||
required
|
||||
defaultValue={0}
|
||||
value={minCount}
|
||||
disabled={props.isSubscribe ? false : true}
|
||||
onChange={(e) => {
|
||||
const count = handleSetMinMax(e);
|
||||
setMinCount(count);
|
||||
}}
|
||||
className={`${
|
||||
props.isSubscribe ? "" : "pointer-events-none"
|
||||
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
|
||||
/>
|
||||
<label
|
||||
className={`${
|
||||
!props.isSubscribe ? "text-[gray]" : ""
|
||||
} text-[13px] font-semibold`}
|
||||
>
|
||||
Maximum check
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
defaultValue={0}
|
||||
value={maxCount}
|
||||
disabled={props.isSubscribe ? false : true}
|
||||
onChange={(e) => {
|
||||
const count = handleSetMinMax(e);
|
||||
setMaxCount(count);
|
||||
}}
|
||||
className={`${
|
||||
props.isSubscribe ? "" : "pointer-events-none"
|
||||
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{["dropdown", radioButtonWidget].includes(props.type) && (
|
||||
<>
|
||||
<label className="text-[13px] font-semibold mt-[5px]">
|
||||
Default value
|
||||
</label>
|
||||
<select
|
||||
onChange={(e) => setDefaultValue(e.target.value)}
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
name="defaultvalue"
|
||||
value={defaultValue}
|
||||
placeholder="select default value"
|
||||
>
|
||||
<option value="" disabled hidden className="text-[13px]">
|
||||
Select...
|
||||
</option>
|
||||
{dropdownOptionList.map((data, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" key={ind} value={data}>
|
||||
{data}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
{props.type !== "checkbox" && props.type !== radioButtonWidget && (
|
||||
<>
|
||||
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
|
||||
{statusArr.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="op-radio op-radio-xs my-1"
|
||||
type="radio"
|
||||
name="status"
|
||||
onChange={() => setStatus(data.toLowerCase())}
|
||||
checked={status.toLowerCase() === data.toLowerCase()}
|
||||
/>
|
||||
<div className="text-[13px] font-500">{data}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{["checkbox", radioButtonWidget].includes(props.type) && (
|
||||
<div className="flex flex-row gap-5 my-2 items-center text-center">
|
||||
{!props.isSignYourself && (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="isreadonly"
|
||||
type="checkbox"
|
||||
checked={isReadOnly}
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
onChange={(e) => setIsReadOnly(e.target.checked)}
|
||||
/>
|
||||
<label className="ml-1 mb-0" htmlFor="isreadonly">
|
||||
Is read only
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="ishidelabel"
|
||||
type="checkbox"
|
||||
checked={isHideLabel}
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
onChange={(e) => setIsHideLabel(e.target.checked)}
|
||||
/>
|
||||
|
||||
<label className="ml-1 mb-0" htmlFor="ishidelabel">
|
||||
Hide labels
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
props.type === "checkbox" && !props.isSignYourself
|
||||
? "mb-[15px]"
|
||||
: "my-[15px]"
|
||||
} w-full h-[1px] bg-[#9f9f9f]`}
|
||||
></div>
|
||||
|
||||
<button
|
||||
disabled={dropdownOptionList.length === 0 && true}
|
||||
type="submit"
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
{props.currWidgetsDetails?.options?.values?.length > 0 && (
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-ghost ml-1"
|
||||
onClick={() => {
|
||||
props.handleClose && props.handleClose();
|
||||
resetState();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
|
||||
export default DropdownWidgetOption;
|
||||
@@ -0,0 +1,189 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { checkIsSubscribed, getFileName } from "../../constant/Utils";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
// import SelectFolder from "../../premitives/SelectFolder";
|
||||
|
||||
const EditTemplate = ({ template, onSuccess }) => {
|
||||
// const [folder, setFolder] = useState({ ObjectId: "", Name: "" });
|
||||
const [formData, setFormData] = useState({
|
||||
Name: template?.Name || "",
|
||||
Note: template?.Note || "",
|
||||
Description: template?.Description || "",
|
||||
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 });
|
||||
};
|
||||
// const handleFolder = (data) => {
|
||||
// console.log("handleFolder ", data)
|
||||
// setFolder(data);
|
||||
// };
|
||||
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const isChecked = formData.SendinOrder === "true" ? true : false;
|
||||
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="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
||||
<div className="text-base-content">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
File
|
||||
</label>
|
||||
<div className="op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs">
|
||||
{getFileName(template.URL)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
Name
|
||||
<span className="text-[13px] text-[red]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Name"
|
||||
value={formData.Name}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="Note" className="text-[13px]">
|
||||
Note
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Note"
|
||||
id="Note"
|
||||
value={formData.Note}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="Description" className="text-[13px]">
|
||||
Description
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="Description"
|
||||
id="Description"
|
||||
value={formData.Description}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label className="text-[13px]">Send In Order</label>
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">Yes</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="SendinOrder"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.SendinOrder === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">No</div>
|
||||
</div>
|
||||
</div>
|
||||
{isEnableSubscription && (
|
||||
<div className="text-xs mt-2">
|
||||
<span
|
||||
className={
|
||||
isSubscribe ? "font-semibold" : "font-semibold text-gray-300"
|
||||
}
|
||||
>
|
||||
Auto reminder{" "}
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</span>
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe
|
||||
? "cursor-pointer "
|
||||
: "pointer-events-none opacity-50"
|
||||
} relative block items-center mb-0`}
|
||||
>
|
||||
<input
|
||||
checked={formData.AutomaticReminders}
|
||||
onChange={handleAutoReminder}
|
||||
type="checkbox"
|
||||
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white mt-2"
|
||||
/>
|
||||
</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="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onChange={handleStrInput}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-[1rem] flex justify-start">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditTemplate;
|
||||
@@ -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,42 @@
|
||||
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 (
|
||||
<form className="flex flex-col text-base-content text-lg font-normal">
|
||||
<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="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full 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>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
import React, { useState } from "react";
|
||||
import { saveAs } from "file-saver";
|
||||
import axios from "axios";
|
||||
import { getBase64FromUrl } from "../../constant/Utils";
|
||||
import { themeColor, emailRegex } from "../../constant/const";
|
||||
import printModule from "print-js";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
|
||||
function EmailComponent({
|
||||
isEmail,
|
||||
pdfUrl,
|
||||
setIsEmail,
|
||||
setSuccessEmail,
|
||||
pdfName,
|
||||
sender,
|
||||
setIsAlert,
|
||||
extUserId,
|
||||
activeMailAdapter
|
||||
}) {
|
||||
const [emailList, setEmailList] = useState([]);
|
||||
const [emailValue, setEmailValue] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [emailErr, setEmailErr] = useState(false);
|
||||
//function for send email
|
||||
const sendEmail = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
let sendMail;
|
||||
for (let i = 0; i < emailList.length; i++) {
|
||||
try {
|
||||
const imgPng =
|
||||
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
|
||||
|
||||
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3/`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const openSignUrl = "https://www.opensignlabs.com/contact-us";
|
||||
const themeBGcolor = themeColor;
|
||||
let params = {
|
||||
mailProvider: activeMailAdapter,
|
||||
extUserId: extUserId,
|
||||
pdfName: pdfName,
|
||||
url: pdfUrl,
|
||||
recipient: emailList[i],
|
||||
subject: `${sender.name} has signed the doc - ${pdfName}`,
|
||||
from: sender.email,
|
||||
html:
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=" +
|
||||
imgPng +
|
||||
" height='50' style='padding:20px,width:170px,height:40px'/> </div><div style='padding:2px;font-family:system-ui; background-color:" +
|
||||
themeBGcolor +
|
||||
";'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document Copy</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>" +
|
||||
pdfName +
|
||||
" </strong>is attached to this email. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
|
||||
sender.email +
|
||||
" directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
|
||||
openSignUrl +
|
||||
" target=_blank>here</a> </p></div></div></body></html>"
|
||||
};
|
||||
sendMail = await axios.post(url, params, { headers: headers });
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
setIsLoading(false);
|
||||
setIsEmail(false);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "something went wrong"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (sendMail && sendMail.data.result.status === "success") {
|
||||
setSuccessEmail(true);
|
||||
setTimeout(() => {
|
||||
setSuccessEmail(false);
|
||||
setIsEmail(false);
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
}, 1500);
|
||||
|
||||
setIsLoading(false);
|
||||
} else if (sendMail && sendMail.data.result.status === "error") {
|
||||
setIsLoading(false);
|
||||
setIsEmail(false);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "something went wrong"
|
||||
});
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setIsEmail(false);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: "something went wrong"
|
||||
});
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
}
|
||||
};
|
||||
|
||||
//function for remove email
|
||||
const removeChip = (index) => {
|
||||
const updateEmailCount = emailList.filter((data, key) => key !== index);
|
||||
setEmailList(updateEmailCount);
|
||||
};
|
||||
//function for get email value
|
||||
const handleEmailValue = (e) => {
|
||||
const value = e.target.value;
|
||||
setEmailErr(false);
|
||||
setEmailValue(value);
|
||||
};
|
||||
|
||||
//function for save email in array after press enter
|
||||
const handleEnterPress = (e) => {
|
||||
const pattern = emailRegex;
|
||||
const validate = emailValue?.match(pattern);
|
||||
if (e.key === "Enter" && emailValue) {
|
||||
if (validate) {
|
||||
const emailLowerCase = emailValue?.toLowerCase();
|
||||
setEmailList((prev) => [...prev, emailLowerCase]);
|
||||
setEmailValue("");
|
||||
} else {
|
||||
setEmailErr(true);
|
||||
}
|
||||
} else if (e === "add" && emailValue) {
|
||||
if (validate) {
|
||||
const emailLowerCase = emailValue?.toLowerCase();
|
||||
setEmailList((prev) => [...prev, emailLowerCase]);
|
||||
setEmailValue("");
|
||||
} else {
|
||||
setEmailErr(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// function for print signed 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 = () => {
|
||||
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
|
||||
};
|
||||
|
||||
const sanitizeFileName = (pdfName) => {
|
||||
// Replace spaces with underscore
|
||||
return pdfName.replace(/ /g, "_");
|
||||
};
|
||||
|
||||
const isAndroid = /Android/i.test(navigator.userAgent);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* isEmail */}
|
||||
{isEmail && (
|
||||
<ModalUi isOpen showHeader={false}>
|
||||
{isLoading && (
|
||||
<div className="absolute w-full h-full flex flex-col justify-center items-center z-[20] bg-[#e6f2f2]/70">
|
||||
<Loader />
|
||||
<span className="text-[12px] text-base-content">
|
||||
This might take some time
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center py-[10px] px-[20px] border-b-[1px] border-base-content">
|
||||
<span className="text-base-content font-semibold">
|
||||
Successfully signed!
|
||||
</span>
|
||||
<div className="flex flex-row">
|
||||
{!isAndroid && (
|
||||
<button
|
||||
onClick={handleToPrint}
|
||||
className="op-btn op-btn-neutral op-btn-sm text-[15px]"
|
||||
>
|
||||
<i className="fa-light fa-print" aria-hidden="true"></i>
|
||||
Print
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
|
||||
onClick={() => handleDownloadPdf()}
|
||||
>
|
||||
<i className="fa-light fa-download" aria-hidden="true"></i>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full p-[20px]">
|
||||
<p className="font-medium text-[15px] mb-[5px] text-base-content align-baseline">
|
||||
Recipients added here will get a copy of the signed document.
|
||||
</p>
|
||||
{emailList.length > 0 ? (
|
||||
<div className="p-0 border-[1.5px] op-border-primary rounded w-full text-[15px]">
|
||||
<div className="flex flex-row flex-wrap">
|
||||
{emailList.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row items-center op-bg-primary m-[4px] rounded-md py-[5px] px-[10px]"
|
||||
key={ind}
|
||||
>
|
||||
<span className="text-base-100 text-[13px]">
|
||||
{data}
|
||||
</span>
|
||||
<span
|
||||
className="text-base-100 text-[13px] font-semibold ml-[7px] cursor-pointer"
|
||||
onClick={() => removeChip(ind)}
|
||||
>
|
||||
<i className="fa-light fa-xmark"></i>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{emailList.length <= 9 && (
|
||||
<input
|
||||
type="email"
|
||||
value={emailValue}
|
||||
className="p-[10px] pb-[20px] rounded w-full text-[15px] bg-transparent outline-none"
|
||||
onChange={handleEmailValue}
|
||||
onKeyDown={handleEnterPress}
|
||||
onBlur={() => emailValue && handleEnterPress("add")}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<input
|
||||
type="email"
|
||||
value={emailValue}
|
||||
className="p-[10px] pb-[20px] rounded w-full text-[15px] outline-none bg-transparent border-[1.5px] op-border-primary"
|
||||
onChange={handleEmailValue}
|
||||
onKeyDown={handleEnterPress}
|
||||
placeholder="Add an email address and hit enter"
|
||||
onBlur={() => emailValue && handleEnterPress("add")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{emailErr && (
|
||||
<p className="text-xs text-[red] ml-1.5 mt-0.5">
|
||||
please provide correct email address
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
className={`${
|
||||
emailValue ? "cursor-pointer" : "cursor-default"
|
||||
} op-btn op-btn-primary op-btn-sm m-2 shadow-md`}
|
||||
onClick={() => emailValue && handleEnterPress("add")}
|
||||
>
|
||||
<i className="fa-light fa-plus" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<div className="bg-[#e3e2e1] mt-[10px] p-[5px] rounded">
|
||||
<span className="font-bold">Note: </span>
|
||||
<span className="text-[15px]">
|
||||
You can only send to ten recipients at a time.
|
||||
</span>
|
||||
</div>
|
||||
<hr className="w-full my-[15px] bg-base-content" />
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => emailList.length > 0 && sendEmail()}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-2"
|
||||
onClick={() => {
|
||||
setIsEmail(false);
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmailComponent;
|
||||
@@ -0,0 +1,481 @@
|
||||
import React, { useState } from "react";
|
||||
import PrevNext from "./PrevNext";
|
||||
import {
|
||||
handleDownloadCertificate,
|
||||
handleDownloadPdf,
|
||||
handleToPrint
|
||||
} from "../../constant/Utils";
|
||||
import "../../styles/signature.css";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import Loader from "../../primitives/Loader";
|
||||
|
||||
function Header({
|
||||
isPdfRequestFiles,
|
||||
isPlaceholder,
|
||||
setIsDecline,
|
||||
pageNumber,
|
||||
allPages,
|
||||
changePage,
|
||||
pdfUrl,
|
||||
embedWidgetsData,
|
||||
pdfDetails,
|
||||
signerPos,
|
||||
signersdata,
|
||||
isMailSend,
|
||||
alertSendEmail,
|
||||
isCompleted,
|
||||
isShowHeader,
|
||||
decline,
|
||||
currentSigner,
|
||||
alreadySign,
|
||||
isSignYourself,
|
||||
setIsEmail,
|
||||
completeBtnTitle,
|
||||
setIsEditTemplate,
|
||||
isPublicTemplate
|
||||
}) {
|
||||
const filterPrefill =
|
||||
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
|
||||
//function for show decline alert
|
||||
const handleDeclinePdfAlert = async () => {
|
||||
const currentDecline = { currnt: "Sure", isDeclined: true };
|
||||
setIsDecline(currentDecline);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex py-[5px]">
|
||||
{isMobile && isShowHeader ? (
|
||||
<div
|
||||
id="navbar"
|
||||
className={isGuestSigner ? "stickySignerHead" : "stickyHead"}
|
||||
style={{
|
||||
width: isGuestSigner
|
||||
? window.innerWidth
|
||||
: window.innerWidth - 30 + "px"
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between items-center py-[5px] px-[10px] ">
|
||||
<div onClick={() => window.history.go(-2)}>
|
||||
<i
|
||||
className="fa-light fa-arrow-left text-base-content"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
<PrevNext
|
||||
pageNumber={pageNumber}
|
||||
allPages={allPages}
|
||||
changePage={changePage}
|
||||
/>
|
||||
{pdfUrl && alreadySign ? (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div className="op-link op-link-primary no-underline text-[16px] font-semibold pr-[3px] pl-[5px]">
|
||||
<i
|
||||
className="fa-light fa-ellipsis-v"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="DropdownMenuContent"
|
||||
sideOffset={5}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i
|
||||
className="fa-light fa-arrow-down mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Download
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
{isCompleted && (
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(
|
||||
pdfDetails,
|
||||
setIsDownloading
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="border-none bg-[#fff]">
|
||||
<i
|
||||
className="fa-light fa-award mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Certificate
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
{isSignYourself && (
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => setIsEmail(true)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i
|
||||
className="fa-light fa-envelope mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Mail
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i
|
||||
className="fa-light fa-print mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
Print
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
) : (
|
||||
<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()}
|
||||
className="text-[red] border-none font-[650] text-[14px] mr-2"
|
||||
>
|
||||
Decline
|
||||
</div>
|
||||
)}
|
||||
{isPlaceholder ? (
|
||||
<div
|
||||
onClick={() => {
|
||||
if (!isMailSend) {
|
||||
alertSendEmail();
|
||||
}
|
||||
}}
|
||||
className={`${
|
||||
isMailSend ? "" : "op-link-primary"
|
||||
} op-link no-underline font-[650] text-[14px]`}
|
||||
data-tut="headerArea"
|
||||
>
|
||||
{completeBtnTitle ? completeBtnTitle : "Send"}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
data-tut="reactourThird"
|
||||
onClick={() => {
|
||||
if (!pdfUrl) {
|
||||
embedWidgetsData();
|
||||
} else if (isPdfRequestFiles) {
|
||||
embedWidgetsData();
|
||||
}
|
||||
}}
|
||||
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
|
||||
>
|
||||
Finish
|
||||
</div>
|
||||
)}
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div className="font-[650] text-[18px] px-2 ml-[4px] text-base-content no-underline">
|
||||
<i
|
||||
className="fa-light fa-ellipsis-v"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="bg-white shadow-md rounded-full px-3 py-2"
|
||||
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-light fa-arrow-down mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="font-[500]">Download</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-wrap justify-between items-center w-full gap-y-1 ml-1">
|
||||
<PrevNext
|
||||
pageNumber={pageNumber}
|
||||
allPages={allPages}
|
||||
changePage={changePage}
|
||||
/>
|
||||
{isPlaceholder ? (
|
||||
<>
|
||||
<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 className="text-[13px] text-[#f5405e]">
|
||||
Add {signersdata.length - filterPrefill.length}{" "}
|
||||
recipients signature
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[13px] text-[#f5405e]">
|
||||
Add {signersdata.length - filterPrefill.length} more
|
||||
recipients signature
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex">
|
||||
{setIsEditTemplate && (
|
||||
<button
|
||||
onClick={() => setIsEditTemplate(true)}
|
||||
className="outline-none border-none text-center mr-[3px]"
|
||||
>
|
||||
<i className="fa-light fa-gear fa-lg"></i>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => window.history.go(-2)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
disabled={isMailSend && true}
|
||||
data-tut="headerArea"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
|
||||
onClick={() => alertSendEmail()}
|
||||
>
|
||||
{completeBtnTitle
|
||||
? completeBtnTitle
|
||||
: isMailSend
|
||||
? "Sent"
|
||||
: "Send"}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : isPdfRequestFiles ? (
|
||||
alreadySign ? (
|
||||
<div className="flex flex-row">
|
||||
<button
|
||||
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-print py-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="hidden lg:block">Print</span>
|
||||
</button>
|
||||
{isCompleted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(pdfDetails, setIsDownloading)
|
||||
}
|
||||
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-award py-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="hidden lg:block">Certificate</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-download py-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="hidden lg:block">Download</span>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex" data-tut="reactourFifth">
|
||||
<button
|
||||
onClick={() => window.history.go(-2)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
{currentSigner && (
|
||||
<>
|
||||
<button
|
||||
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() => handleDeclinePdfAlert()}
|
||||
>
|
||||
Decline
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() => embedWidgetsData()}
|
||||
>
|
||||
Finish
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
|
||||
<span className="hidden lg:block">Download</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
) : isCompleted ? (
|
||||
<div className="flex flex-row">
|
||||
{isCompleted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(pdfDetails, setIsDownloading)
|
||||
}
|
||||
className="op-btn op-btn-secondary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
>
|
||||
<i className="fa-light fa-award" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">Certificate</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
>
|
||||
<i className="fa-light fa-print" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">Print</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<i className="fa-light fa-download" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">Download</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-info op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
onClick={() => setIsEmail(true)}
|
||||
>
|
||||
<i className="fa-light fa-envelope" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">Mail</span>
|
||||
</button>
|
||||
</div>
|
||||
) : isPublicTemplate ? (
|
||||
<>
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm shadow"
|
||||
onClick={() => {
|
||||
embedWidgetsData();
|
||||
}}
|
||||
>
|
||||
Sign Now
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex">
|
||||
<button
|
||||
onClick={() => window.history.go(-2)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
|
||||
onClick={() => {
|
||||
if (!pdfUrl) {
|
||||
embedWidgetsData();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Finish
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isDownloading === "pdf" && (
|
||||
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isDownloading === "certificate"}
|
||||
title={
|
||||
isDownloading === "certificate"
|
||||
? "Generating certificate"
|
||||
: "PDF Download"
|
||||
}
|
||||
handleClose={() => setIsDownloading("")}
|
||||
>
|
||||
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
|
||||
{isDownloading === "certificate"}{" "}
|
||||
<p>
|
||||
Your completion certificate is being generated. Please wait
|
||||
momentarily. If the download doesn't start shortly, click the
|
||||
button again.
|
||||
</p>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
|
||||
function PdfZoom(props) {
|
||||
const onClickZoomIn = () => {
|
||||
props.setScale(props.scale + 0.1 * props.scale);
|
||||
props.setZoomPercent(props.zoomPercent + 10);
|
||||
};
|
||||
const onClickZoomOut = () => {
|
||||
if (props.zoomPercent > 0) {
|
||||
if (props.zoomPercent === 10) {
|
||||
props.setScale(1);
|
||||
} else {
|
||||
props.setScale(props.scale - 0.1 * props.scale);
|
||||
}
|
||||
props.setZoomPercent(props.zoomPercent - 10);
|
||||
}
|
||||
};
|
||||
const handleReset = () => {
|
||||
props.setScale(1);
|
||||
props.setZoomPercent(0);
|
||||
};
|
||||
return (
|
||||
// md:mt-[41px] xl:mt-[63px]mt=[]
|
||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[50px] 2xl:mt-[63px]">
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={onClickZoomIn}
|
||||
title="Zoom in"
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={handleReset}
|
||||
title="Reset"
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-arrows-rotate text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
|
||||
onClick={onClickZoomOut}
|
||||
style={{
|
||||
cursor: props.zoomPercent > 0 ? "pointer" : "default"
|
||||
}}
|
||||
title="Zoom out"
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default PdfZoom;
|
||||
@@ -0,0 +1,933 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import BorderResize from "./BorderResize";
|
||||
import PlaceholderBorder from "./PlaceholderBorder";
|
||||
import { Rnd } from "react-rnd";
|
||||
import {
|
||||
defaultWidthHeight,
|
||||
getContainerScale,
|
||||
handleCopyNextToWidget,
|
||||
isTabAndMobile,
|
||||
onChangeInput,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
import PlaceholderType from "./PlaceholderType";
|
||||
import moment from "moment";
|
||||
import "../../styles/opensigndrive.css";
|
||||
|
||||
const selectFormat = (data) => {
|
||||
switch (data) {
|
||||
case "L":
|
||||
return "MM/dd/yyyy";
|
||||
case "DD-MM-YYYY":
|
||||
return "dd-MM-yyyy";
|
||||
case "DD/MM/YYYY":
|
||||
return "dd/MM/yyyy";
|
||||
case "LL":
|
||||
return "MMMM dd, yyyy";
|
||||
case "DD MMM, YYYY":
|
||||
return "dd MMM, yyyy";
|
||||
case "YYYY-MM-DD":
|
||||
return "yyyy-MM-dd";
|
||||
case "MM-DD-YYYY":
|
||||
return "MM-dd-yyyy";
|
||||
case "MM.DD.YYYY":
|
||||
return "MM.dd.yyyy";
|
||||
case "MMM DD, YYYY":
|
||||
return "MMM dd, yyyy";
|
||||
case "DD MMMM, YYYY":
|
||||
return "dd MMMM, yyyy";
|
||||
default:
|
||||
return "MM/dd/yyyy";
|
||||
}
|
||||
};
|
||||
|
||||
const changeDateToMomentFormat = (format) => {
|
||||
switch (format) {
|
||||
case "MM/dd/yyyy":
|
||||
return "L";
|
||||
case "dd-MM-yyyy":
|
||||
return "DD-MM-YYYY";
|
||||
case "dd/MM/yyyy":
|
||||
return "DD/MM/YYYY";
|
||||
case "MMMM dd, yyyy":
|
||||
return "LL";
|
||||
case "dd MMM, yyyy":
|
||||
return "DD MMM, YYYY";
|
||||
case "yyyy-MM-dd":
|
||||
return "YYYY-MM-DD";
|
||||
case "MM-dd-yyyy":
|
||||
return "MM-DD-YYYY";
|
||||
case "MM.dd.yyyy":
|
||||
return "MM.DD.YYYY";
|
||||
case "MMM dd, yyyy":
|
||||
return "MMM DD, YYYY";
|
||||
case "dd MMMM, yyyy":
|
||||
return "DD MMMM, YYYY";
|
||||
default:
|
||||
return "L";
|
||||
}
|
||||
};
|
||||
|
||||
//function to get default date
|
||||
const getDefaultdate = (selectedDate, format = "dd-MM-yyyy") => {
|
||||
let date;
|
||||
if (format && format === "dd-MM-yyyy") {
|
||||
const newdate = selectedDate
|
||||
? selectedDate
|
||||
: moment(new Date()).format(changeDateToMomentFormat(format));
|
||||
const [day, month, year] = newdate.split("-");
|
||||
date = new Date(`${year}-${month}-${day}`);
|
||||
} else {
|
||||
date = new Date(selectedDate);
|
||||
}
|
||||
const value = date;
|
||||
return value;
|
||||
};
|
||||
//function to get default format
|
||||
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
||||
|
||||
function Placeholder(props) {
|
||||
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
|
||||
const [isDraggingEnabled, setDraggingEnabled] = useState(true);
|
||||
const [isShowDateFormat, setIsShowDateFormat] = useState(false);
|
||||
const [containerScale, setContainerScale] = useState();
|
||||
const [selectDate, setSelectDate] = useState({
|
||||
date:
|
||||
props.pos.type === "date"
|
||||
? moment(
|
||||
getDefaultdate(
|
||||
props?.pos?.options?.response,
|
||||
props.pos?.options?.validation?.format
|
||||
).getTime()
|
||||
).format(
|
||||
changeDateToMomentFormat(props.pos?.options?.validation?.format)
|
||||
)
|
||||
: "",
|
||||
format:
|
||||
props.pos.type === "date"
|
||||
? getDefaultFormat(props.pos?.options?.validation?.format)
|
||||
: ""
|
||||
});
|
||||
const [dateFormat, setDateFormat] = useState([]);
|
||||
const [startDate, setStartDate] = useState(
|
||||
props.pos.type === "date" &&
|
||||
getDefaultdate(
|
||||
props?.pos?.options?.response,
|
||||
props.pos?.options?.validation?.format
|
||||
)
|
||||
);
|
||||
const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({
|
||||
width: null,
|
||||
height: null
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const getPdfPageWidth = props.pdfOriginalWH.find(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
);
|
||||
setContainerScale(props.containerWH.width / getPdfPageWidth.width);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.containerWH.width, props.pdfOriginalWH]);
|
||||
const dateFormatArr = [
|
||||
"L",
|
||||
"DD-MM-YYYY",
|
||||
"YYYY-MM-DD",
|
||||
"MM.DD.YYYY",
|
||||
"MM-DD-YYYY",
|
||||
"MMM DD, YYYY",
|
||||
"LL",
|
||||
"DD MMM, YYYY",
|
||||
"DD MMMM, YYYY"
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const updateWidth = () => {
|
||||
const rndElement = document.getElementById(props.pos.key);
|
||||
if (rndElement) {
|
||||
const { width, height } = rndElement.getBoundingClientRect();
|
||||
setGetCheckboxRenderWidth({ width: width, height: height });
|
||||
}
|
||||
};
|
||||
|
||||
// Delay to ensure rendering is complete
|
||||
const timer = setTimeout(updateWidth, 0);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [props.pos]);
|
||||
|
||||
useEffect(() => {
|
||||
const closeMenuOnOutsideClick = (e) => {
|
||||
if (!isDraggingEnabled && !e.target.closest("#changeIsDragging")) {
|
||||
setDraggingEnabled(true);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", closeMenuOnOutsideClick);
|
||||
|
||||
return () => {
|
||||
// Cleanup the event listener when the component unmounts
|
||||
document.removeEventListener("click", closeMenuOnOutsideClick);
|
||||
};
|
||||
}, [isDraggingEnabled]);
|
||||
//function change format array list with selected date and format
|
||||
const changeDateFormat = () => {
|
||||
const updateDate = [];
|
||||
dateFormatArr.map((data) => {
|
||||
let date;
|
||||
if (selectDate && selectDate.format === "dd-MM-yyyy") {
|
||||
const [day, month, year] = selectDate.date.split("-");
|
||||
date = new Date(`${year}-${month}-${day}`);
|
||||
} else {
|
||||
date = new Date(selectDate?.date);
|
||||
}
|
||||
const milliseconds = date.getTime();
|
||||
const newDate = moment(milliseconds).format(data);
|
||||
const dateObj = {
|
||||
date: newDate,
|
||||
format: selectFormat(data)
|
||||
};
|
||||
updateDate.push(dateObj);
|
||||
});
|
||||
setDateFormat(updateDate);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isPlaceholder || props.isSignYourself) {
|
||||
selectDate && changeDateFormat();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectDate]);
|
||||
|
||||
//it detect outside click of date dropdown menu
|
||||
useEffect(() => {
|
||||
const closeMenuOnOutsideClick = (e) => {
|
||||
if (isShowDateFormat && !e.target.closest("#menu-container")) {
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
}
|
||||
};
|
||||
document.addEventListener("click", closeMenuOnOutsideClick);
|
||||
return () => {
|
||||
// Cleanup the event listener when the component unmounts
|
||||
document.removeEventListener("click", closeMenuOnOutsideClick);
|
||||
};
|
||||
}, [isShowDateFormat]);
|
||||
|
||||
//`handleWidgetIdandPopup` is used to set current widget id and open relative popup
|
||||
const handleWidgetIdandPopup = () => {
|
||||
if (props.setSelectWidgetId) {
|
||||
props.setSelectWidgetId(props.pos.key);
|
||||
}
|
||||
|
||||
const widgetTypeExist = [
|
||||
textInputWidget,
|
||||
"checkbox",
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"date",
|
||||
"email",
|
||||
textWidget
|
||||
].includes(props.pos.type);
|
||||
|
||||
if (widgetTypeExist) {
|
||||
setDraggingEnabled(false);
|
||||
}
|
||||
if ((props.isNeedSign || props.isSignYourself) && !props.isDragging) {
|
||||
if (props?.ispublicTemplate) {
|
||||
props.handleUserDetails();
|
||||
} else {
|
||||
if (props.pos.type) {
|
||||
if (
|
||||
props.pos.type === "signature" ||
|
||||
props.pos.type === "stamp" ||
|
||||
props.pos.type === "image"
|
||||
) {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
} else if (props.pos.type === "initials") {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
props.setIsInitial(true);
|
||||
}
|
||||
} else {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos?.isStamp ? props.pos.isStamp : false);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
props.isPlaceholder &&
|
||||
!props.isDragging &&
|
||||
props.pos.type !== textWidget
|
||||
) {
|
||||
if (props.pos.key === props.selectWidgetId) {
|
||||
props.handleLinkUser(props.data.Id);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
} else if (!props.pos.type) {
|
||||
if (
|
||||
!props.pos.type &&
|
||||
props.isNeedSign &&
|
||||
props.data.signerObjId === props.signerObjId
|
||||
) {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
} else if (
|
||||
(props.isNeedSign && props.pos.type === "signature") ||
|
||||
props.pos.type === "stamp"
|
||||
) {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
} else if (props.isNeedSign && props.pos.type === "dropdown") {
|
||||
props.setSignKey(props.pos.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleOnClickPlaceholder = () => {
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
|
||||
if (!props.isNeedSign) {
|
||||
props.setWidgetType(props.pos.type);
|
||||
}
|
||||
if (props.isNeedSign && props.data?.signerObjId === props.signerObjId) {
|
||||
handleWidgetIdandPopup();
|
||||
} else if (
|
||||
props?.isNeedSign &&
|
||||
props?.uniqueId &&
|
||||
props.data?.Id === props?.uniqueId
|
||||
) {
|
||||
handleWidgetIdandPopup();
|
||||
} else if (props.isPlaceholder || props.isSignYourself) {
|
||||
handleWidgetIdandPopup();
|
||||
}
|
||||
};
|
||||
//`handleOnClickSettingIcon` is used set current widget details and open setting of it
|
||||
const handleOnClickSettingIcon = () => {
|
||||
if (props.pos.type === radioButtonWidget) {
|
||||
props.setIsRadio(true);
|
||||
} else if (props.pos.type === "dropdown") {
|
||||
props?.setShowDropdown(true);
|
||||
} else if (props.pos.type === "checkbox") {
|
||||
props?.setIsCheckbox(true);
|
||||
}
|
||||
//condition to handle setting icon for signyour-self flow for all type text widgets
|
||||
else if (
|
||||
[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
].includes(props.pos.type) &&
|
||||
props.isSignYourself
|
||||
) {
|
||||
props.handleTextSettingModal(true);
|
||||
} else {
|
||||
props?.handleNameModal && props?.handleNameModal(true);
|
||||
}
|
||||
|
||||
//condition for only placeholder and template flow
|
||||
if (props.data && props?.pos?.type !== textWidget) {
|
||||
props.setSignerObjId(props?.data?.signerObjId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
//condition to handle in placeholder and template flow for text widget signerId for text widgets i have to set uniqueId in tempSignerId because
|
||||
//it does not have any signer user and any signerobjId
|
||||
else if (props.data && props.pos.type === textWidget) {
|
||||
props.setTempSignerId(props.uniqueId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setWidgetType(props.pos.type);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
};
|
||||
//function to set required state value onclick on widget's copy icon
|
||||
const handleCopyPlaceholder = (e) => {
|
||||
e.stopPropagation();
|
||||
//condition to handle text widget signer obj id and unique id
|
||||
//when user click on copy icon of text widget in that condition text widget does not have any signerObjId
|
||||
//in that case i have to save in tempSignerId as a unique id of previous select signer's unique id
|
||||
//and on save or cancel button of copy all page popup i have set this temp signer Id in unique id
|
||||
|
||||
if (props.data && props?.pos?.type !== textWidget) {
|
||||
props.setSignerObjId(props?.data?.signerObjId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
} else if (props.data && props.pos.type === textWidget) {
|
||||
props.setTempSignerId(props.uniqueId);
|
||||
props.setSignerObjId(props?.data?.signerObjId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
|
||||
//checking widget's type and open widget copy modal for required widgets
|
||||
if (
|
||||
["signature", textWidget, "stamp", "initial"].includes(props.pos.type)
|
||||
) {
|
||||
props.setIsPageCopy(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
} else {
|
||||
//function to create new widget next to just widget
|
||||
handleCopyNextToWidget(
|
||||
props.pos,
|
||||
props.pos.type,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
//function to save date and format on local array onchange date and onclick format
|
||||
const handleSaveDate = (data, isDateChange) => {
|
||||
let updateDate = data.date;
|
||||
//check if date change by user
|
||||
if (isDateChange) {
|
||||
//`changeDateToMomentFormat` is used to convert date as per required to moment package
|
||||
updateDate = moment(data.date).format(
|
||||
changeDateToMomentFormat(data.format)
|
||||
);
|
||||
}
|
||||
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
|
||||
//`getDefaultdate` is used to convert update date in new Date() format
|
||||
const date = moment(
|
||||
getDefaultdate(updateDate, data?.format).getTime()
|
||||
).format(changeDateToMomentFormat(data?.format));
|
||||
|
||||
//`onChangeInput` is used to save data related to date in a placeholder field
|
||||
onChangeInput(
|
||||
date,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
data?.format
|
||||
);
|
||||
setSelectDate({ date: date, format: data?.format });
|
||||
};
|
||||
const PlaceholderIcon = () => {
|
||||
return (
|
||||
props.isShowBorder && (
|
||||
<>
|
||||
{(props.isPlaceholder || props.isSignYourself) && (
|
||||
<>
|
||||
{/* condition to add setting icon for signyour-self flow for particular text widgets
|
||||
and also it have diffrent position
|
||||
*/}
|
||||
{[
|
||||
"checkbox",
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
].includes(props.pos.type) && props.isSignYourself ? (
|
||||
<i
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
className="fa-light fa-gear icon"
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
right: "29px",
|
||||
top: "-19px"
|
||||
}}
|
||||
></i>
|
||||
) : (
|
||||
/* condition to add setting icon for placeholder & template flow for all widgets except signature and date */
|
||||
((!props?.pos?.type && props.pos.isStamp) ||
|
||||
(props?.pos?.type &&
|
||||
!["date", "signature"].includes(props.pos.type) &&
|
||||
!props.isSignYourself)) && (
|
||||
<i
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
className="fa-light fa-gear icon"
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
right: props?.pos?.type === textWidget ? "32px" : "47px",
|
||||
top: "-19px"
|
||||
}}
|
||||
></i>
|
||||
)
|
||||
)}
|
||||
{/* condition for usericon for all widgets except text widgets and signyour-self flow */}
|
||||
{props.pos.type !== textWidget && !props.isSignYourself && (
|
||||
<i
|
||||
data-tut="assignSigner"
|
||||
className="fa-light fa-user icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
props.handleLinkUser(props.data.Id);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
props.handleLinkUser(props.data.Id);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}}
|
||||
style={{ color: "#188ae2", right: "32px", top: "-18px" }}
|
||||
></i>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* setting icon only for date widgets */}
|
||||
{props.pos.type === "date" && selectDate && (
|
||||
<div
|
||||
id="menu-container"
|
||||
style={{
|
||||
zIndex: "99",
|
||||
top: "-19px",
|
||||
right: props.isPlaceholder ? "60px" : "44px"
|
||||
}}
|
||||
className={`${isShowDateFormat ? "show" : ""} dropdown icon`}
|
||||
onClick={(e) => {
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
e.stopPropagation();
|
||||
if (props.data) {
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
if (props.data) {
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-gear icon"
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
fontSize: "14px"
|
||||
}}
|
||||
></i>
|
||||
<div
|
||||
className={
|
||||
isShowDateFormat ? "dropdown-menu show" : "dropdown-menu"
|
||||
}
|
||||
aria-labelledby="dropdownMenuButton"
|
||||
aria-expanded={isShowDateFormat ? "true" : "false"}
|
||||
>
|
||||
{dateFormat.map((data, ind) => {
|
||||
return (
|
||||
<span
|
||||
key={ind}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
setSelectDate(data);
|
||||
handleSaveDate(data);
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
setSelectDate(data);
|
||||
handleSaveDate(data);
|
||||
}}
|
||||
className="dropdown-item text-[13px]"
|
||||
>
|
||||
{data?.date ? data?.date : "nodata"}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* copy icon for all widgets */}
|
||||
<i
|
||||
className="fa-light fa-copy icon"
|
||||
onClick={(e) => handleCopyPlaceholder(e)}
|
||||
onTouchEnd={(e) => handleCopyPlaceholder(e)}
|
||||
style={{ color: "#188ae2", right: "12px", top: "-18px" }}
|
||||
></i>
|
||||
{/* delete icon for all widgets */}
|
||||
<i
|
||||
className="fa-light fa-circle-xmark icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
//condition for template and placeholder flow
|
||||
if (props.data) {
|
||||
props.handleDeleteSign(props.pos.key, props.data.Id);
|
||||
}
|
||||
//condition for signyour-self flow
|
||||
else {
|
||||
props.handleDeleteSign(props.pos.key);
|
||||
props.setIsStamp(false);
|
||||
}
|
||||
}}
|
||||
//for mobile and tablet touch event
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
//condition for template and placeholder flow
|
||||
if (props.data) {
|
||||
props.handleDeleteSign(props.pos.key, props.data?.Id);
|
||||
}
|
||||
//condition for signyour-self flow
|
||||
else {
|
||||
props.handleDeleteSign(props.pos.key);
|
||||
props.setIsStamp(false);
|
||||
}
|
||||
}}
|
||||
style={{ color: "#188ae2", right: "-8px", top: "-18px" }}
|
||||
></i>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
const xPos = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
props.pdfOriginalWH,
|
||||
props.pageNumber,
|
||||
props.containerWH
|
||||
);
|
||||
const resizePos = pos.xPosition;
|
||||
if (signYourself) {
|
||||
return resizePos * containerScale * props.scale;
|
||||
} else {
|
||||
//checking both condition mobile and desktop view
|
||||
if (pos.isMobile && pos.scale) {
|
||||
if (props.scale > 1) {
|
||||
return resizePos * pos.scale * containerScale * props.scale;
|
||||
} else {
|
||||
return resizePos * pos.scale * containerScale;
|
||||
}
|
||||
} else {
|
||||
// if (pos.scale === containerScale) {
|
||||
// return resizePos * pos.scale * props.scale;
|
||||
// } else {
|
||||
return resizePos * containerScale * props.scale;
|
||||
// }
|
||||
}
|
||||
}
|
||||
};
|
||||
const yPos = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
props.pdfOriginalWH,
|
||||
props.pageNumber,
|
||||
props.containerWH
|
||||
);
|
||||
const resizePos = pos.yPosition;
|
||||
|
||||
if (signYourself) {
|
||||
return resizePos * containerScale * props.scale;
|
||||
} else {
|
||||
//checking both condition mobile and desktop view
|
||||
if (pos.isMobile && pos.scale) {
|
||||
if (props.scale > 1) {
|
||||
return resizePos * pos.scale * containerScale * props.scale;
|
||||
} else {
|
||||
return resizePos * pos.scale * containerScale;
|
||||
}
|
||||
}
|
||||
// else if (pos.scale === containerScale) {
|
||||
// if (props.scale > 1) {
|
||||
// return resizePos * pos.scale * props.scale;
|
||||
// } else {
|
||||
// return resizePos * pos.scale;
|
||||
// }
|
||||
// }
|
||||
else {
|
||||
return resizePos * containerScale * props.scale;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getCursor = () => {
|
||||
if (props.data && props.isNeedSign) {
|
||||
if (props?.signerObjId && props.data?.signerObjId === props.signerObjId) {
|
||||
return "pointer";
|
||||
} else if (props?.uniqueId && props.data?.Id === props.uniqueId) {
|
||||
return "pointer";
|
||||
} else {
|
||||
return "not-allowed";
|
||||
}
|
||||
} else {
|
||||
return "all-scroll";
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Rnd
|
||||
id={props.pos.key}
|
||||
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
|
||||
key={props.pos.key}
|
||||
lockAspectRatio={
|
||||
![
|
||||
textWidget,
|
||||
"email",
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
textInputWidget
|
||||
].includes(props.pos.type) &&
|
||||
(props.pos.Width
|
||||
? props.pos.Width / props.pos.Height
|
||||
: defaultWidthHeight(props.pos.type).width /
|
||||
defaultWidthHeight(props.pos.type).height)
|
||||
}
|
||||
enableResizing={{
|
||||
top: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight:
|
||||
props.data && props.isNeedSign
|
||||
? props.data?.signerObjId === props.signerObjId &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.type !== radioButtonWidget
|
||||
? true
|
||||
: false
|
||||
: props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId &&
|
||||
true,
|
||||
bottomLeft: false,
|
||||
topLeft: false
|
||||
}}
|
||||
bounds="parent"
|
||||
className="signYourselfBlock"
|
||||
style={{
|
||||
border: "1px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
textAlign:
|
||||
props.pos.type !== "name" &&
|
||||
props.pos.type !== "company" &&
|
||||
props.pos.type !== "job title" &&
|
||||
"center",
|
||||
cursor: getCursor(),
|
||||
zIndex:
|
||||
props.pos.type === "date"
|
||||
? props.pos.key === props.selectWidgetId
|
||||
? 99 + 1
|
||||
: 99
|
||||
: props?.pos?.zIndex
|
||||
? props.pos.zIndex
|
||||
: "5",
|
||||
background: props.data ? props.data.blockColor : "rgb(203 233 237)"
|
||||
}}
|
||||
onDrag={() => {
|
||||
setDraggingEnabled(true);
|
||||
props.handleTabDrag && props.handleTabDrag(props.pos.key);
|
||||
}}
|
||||
size={{
|
||||
width:
|
||||
props.pos.type === radioButtonWidget || props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posWidth(props.pos, props.isSignYourself),
|
||||
height:
|
||||
props.pos.type === radioButtonWidget || props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself)
|
||||
}}
|
||||
onResizeStart={() => {
|
||||
setDraggingEnabled(true);
|
||||
props.setIsResize && props.setIsResize(true);
|
||||
}}
|
||||
onResizeStop={(e, direction, ref) => {
|
||||
props.setIsResize && props.setIsResize(false);
|
||||
props.handleSignYourselfImageResize &&
|
||||
props.handleSignYourselfImageResize(
|
||||
ref,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.setXyPostion,
|
||||
props.index,
|
||||
containerScale,
|
||||
props.scale,
|
||||
props.data && props.data.Id,
|
||||
props.isResize
|
||||
);
|
||||
}}
|
||||
disableDragging={
|
||||
props.isNeedSign
|
||||
? true
|
||||
: props.isPlaceholder && ![textWidget].includes(props.pos.type)
|
||||
? false
|
||||
: !isDraggingEnabled
|
||||
}
|
||||
onDragStop={(event, dragElement) =>
|
||||
props.handleStop &&
|
||||
props.handleStop(event, dragElement, props.data?.Id, props.pos?.key)
|
||||
}
|
||||
position={{
|
||||
x: xPos(props.pos, props.isSignYourself),
|
||||
y: yPos(props.pos, props.isSignYourself)
|
||||
}}
|
||||
onResize={(e, direction, ref) => {
|
||||
setPlaceholderBorder({
|
||||
w: ref.offsetWidth / (props.scale * containerScale),
|
||||
h: ref.offsetHeight / (props.scale * containerScale)
|
||||
});
|
||||
}}
|
||||
onClick={() => handleOnClickPlaceholder()}
|
||||
>
|
||||
{props.isShowBorder &&
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId ? (
|
||||
<BorderResize right={-12} top={-11} />
|
||||
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
|
||||
props.data?.signerObjId === props.signerObjId &&
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" ? (
|
||||
<BorderResize />
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
) : (
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId && <BorderResize />
|
||||
)}
|
||||
|
||||
{props.isShowBorder && props.pos.key === props.selectWidgetId && (
|
||||
<PlaceholderBorder
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pos={props.pos}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
getCheckboxRenderWidth={getCheckboxRenderWidth}
|
||||
scale={props.scale}
|
||||
containerScale={containerScale}
|
||||
placeholderBorder={placeholderBorder}
|
||||
/>
|
||||
)}
|
||||
{isTabAndMobile ? (
|
||||
<div
|
||||
className="flex items-stretch"
|
||||
style={{
|
||||
left: xPos(props.pos, props.isSignYourself),
|
||||
top: yPos(props.pos, props.isSignYourself),
|
||||
width:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posWidth(props.pos, props.isSignYourself),
|
||||
height:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself),
|
||||
zIndex: "10"
|
||||
}}
|
||||
onTouchEnd={() => handleOnClickPlaceholder()}
|
||||
onClick={() => handleOnClickPlaceholder()}
|
||||
>
|
||||
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
|
||||
|
||||
<PlaceholderType
|
||||
pos={props.pos}
|
||||
xyPostion={props.xyPostion}
|
||||
index={props.index}
|
||||
setXyPostion={props.setXyPostion}
|
||||
data={props.data}
|
||||
setSignKey={props.setSignKey}
|
||||
isShowDropdown={props?.isShowDropdown}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
isSignYourself={props.isSignYourself}
|
||||
signerObjId={props.signerObjId}
|
||||
handleUserName={props.handleUserName}
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
|
||||
isNeedSign={props.isNeedSign}
|
||||
setSelectDate={setSelectDate}
|
||||
selectDate={selectDate}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setStartDate={setStartDate}
|
||||
startDate={startDate}
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
|
||||
<PlaceholderType
|
||||
pos={props.pos}
|
||||
xyPostion={props.xyPostion}
|
||||
index={props.index}
|
||||
setXyPostion={props.setXyPostion}
|
||||
data={props.data}
|
||||
setSignKey={props.setSignKey}
|
||||
isShowDropdown={props?.isShowDropdown}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
isSignYourself={props.isSignYourself}
|
||||
signerObjId={props.signerObjId}
|
||||
handleUserName={props.handleUserName}
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
|
||||
isNeedSign={props.isNeedSign}
|
||||
setSelectDate={setSelectDate}
|
||||
selectDate={selectDate}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setStartDate={setStartDate}
|
||||
startDate={startDate}
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Rnd>
|
||||
);
|
||||
}
|
||||
|
||||
export default Placeholder;
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from "react";
|
||||
import { themeColor } from "../../constant/const";
|
||||
import {
|
||||
defaultWidthHeight,
|
||||
isMobile,
|
||||
radioButtonWidget,
|
||||
resizeBorderExtraWidth,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
function PlaceholderBorder(props) {
|
||||
const getResizeBorderExtraWidth = resizeBorderExtraWidth();
|
||||
const defaultWidth = defaultWidthHeight(props.pos.type).width;
|
||||
const defaultHeight = defaultWidthHeight(props.pos.type).height;
|
||||
const width = () => {
|
||||
const getWidth =
|
||||
props.placeholderBorder.w || props.pos.Width || defaultWidth;
|
||||
return (
|
||||
getWidth * props.scale * props.containerScale + getResizeBorderExtraWidth
|
||||
);
|
||||
};
|
||||
const height = () => {
|
||||
const getHeight =
|
||||
props.placeholderBorder.h || props.pos.Height || defaultHeight;
|
||||
|
||||
return (
|
||||
getHeight * props.scale * props.containerScale + getResizeBorderExtraWidth
|
||||
);
|
||||
};
|
||||
|
||||
const handleMinWidth = () => {
|
||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
||||
return props.getCheckboxRenderWidth.width + getResizeBorderExtraWidth;
|
||||
} else {
|
||||
return width();
|
||||
}
|
||||
};
|
||||
const handleMinHeight = () => {
|
||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
||||
return props.getCheckboxRenderWidth.height + getResizeBorderExtraWidth;
|
||||
} else {
|
||||
return height();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => !isMobile && props?.setDraggingEnabled(true)}
|
||||
onTouchEnd={() =>
|
||||
props.pos.type === textWidget && props?.setDraggingEnabled(true)
|
||||
}
|
||||
className="absolute inline-block w-[14px] h-[14px] border-[0.2px] overflow-hidden border-dashed"
|
||||
style={{
|
||||
borderColor: themeColor,
|
||||
minWidth: handleMinWidth() || 0,
|
||||
minHeight: handleMinHeight() || 0
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlaceholderBorder;
|
||||
@@ -0,0 +1,237 @@
|
||||
import React, { useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { randomId, textWidget } from "../../constant/Utils";
|
||||
|
||||
function PlaceholderCopy(props) {
|
||||
const copyType = ["All pages", "All pages but last", "All pages but first"];
|
||||
const [selectCopyType, setSelectCopyType] = useState("");
|
||||
//function for get copy placeholder position
|
||||
const getCopyPlaceholderPosition = (
|
||||
type,
|
||||
currentPlaceholder,
|
||||
newPageNumber,
|
||||
existPlaceholderPosition
|
||||
) => {
|
||||
const filterPosition =
|
||||
existPlaceholderPosition &&
|
||||
existPlaceholderPosition.filter(
|
||||
(data) => data.xPosition !== currentPlaceholder.xPosition
|
||||
);
|
||||
//copy all page placeholder at requested position except first page
|
||||
if (newPageNumber === 1 && type === "first") {
|
||||
if (filterPosition && filterPosition.length > 0) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...filterPosition]
|
||||
};
|
||||
}
|
||||
}
|
||||
//copy all page placeholder at requested position except last page
|
||||
else if (newPageNumber === props.allPages && type === "last") {
|
||||
if (existPlaceholderPosition) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...filterPosition]
|
||||
};
|
||||
}
|
||||
}
|
||||
//copy all page placeholder at requested position with existing placeholder position
|
||||
else if (existPlaceholderPosition) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...filterPosition, currentPlaceholder]
|
||||
};
|
||||
}
|
||||
//copy all page placeholder at requested position
|
||||
else {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [currentPlaceholder]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
//function for copy placeholder as per select copy type
|
||||
const copyPlaceholder = (type) => {
|
||||
let newPlaceholderPosition = [];
|
||||
let newPageNumber = 1;
|
||||
const signerPosition = props.xyPostion;
|
||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||
|
||||
//handle placeholder array and copy for multiple signers placeholder at requested location
|
||||
if (signerId) {
|
||||
//get current signers data
|
||||
let filterSignerPosition;
|
||||
if (props?.signerObjId) {
|
||||
filterSignerPosition = signerPosition.filter(
|
||||
(data) => data.signerObjId === signerId
|
||||
);
|
||||
} else {
|
||||
filterSignerPosition = signerPosition.filter(
|
||||
(item) => item.Id === signerId
|
||||
);
|
||||
}
|
||||
//get current pagenumber's all placeholder position data
|
||||
const placeholderPosition = filterSignerPosition[0].placeHolder.filter(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
);
|
||||
//get current placeholder position data which user want to copy
|
||||
const currentPlaceholder = placeholderPosition[0].pos.find(
|
||||
(position) => position.key === props.signKey
|
||||
);
|
||||
|
||||
for (let i = 0; i < props.allPages; i++) {
|
||||
const newId = randomId();
|
||||
currentPlaceholder.key = newId;
|
||||
//get exist placeholder position for particular page
|
||||
const existPlaceholder = filterSignerPosition[0].placeHolder.filter(
|
||||
(data) => data.pageNumber === newPageNumber
|
||||
);
|
||||
const existPlaceholderPosition =
|
||||
existPlaceholder[0] && existPlaceholder[0].pos;
|
||||
|
||||
//function for get copy to requested location of placeholder position
|
||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||
type,
|
||||
currentPlaceholder,
|
||||
newPageNumber,
|
||||
existPlaceholderPosition
|
||||
);
|
||||
if (getPlaceholderObj) {
|
||||
newPlaceholderPosition.push(getPlaceholderObj);
|
||||
}
|
||||
newPageNumber++;
|
||||
}
|
||||
let updatedSignerPlaceholder;
|
||||
if (props?.signerObjId) {
|
||||
updatedSignerPlaceholder = signerPosition.map((signersData) => {
|
||||
if (signersData.signerObjId === props.signerObjId) {
|
||||
return {
|
||||
...signersData,
|
||||
placeHolder: newPlaceholderPosition
|
||||
};
|
||||
}
|
||||
return signersData;
|
||||
});
|
||||
} else {
|
||||
updatedSignerPlaceholder = signerPosition.map((signersData) => {
|
||||
if (signersData.Id === props.Id) {
|
||||
return {
|
||||
...signersData,
|
||||
placeHolder: newPlaceholderPosition
|
||||
};
|
||||
}
|
||||
return signersData;
|
||||
});
|
||||
}
|
||||
|
||||
const signersData = signerPosition;
|
||||
signersData.splice(0, signerPosition.length, ...updatedSignerPlaceholder);
|
||||
props.setXyPostion(signersData);
|
||||
}
|
||||
//handle signyourself array and copy for single signers placeholder at requested location
|
||||
else {
|
||||
const xyPostion = props.xyPostion;
|
||||
|
||||
const placeholderPosition = xyPostion.filter(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
);
|
||||
//get current placeholder position data which user want to copy
|
||||
const currentPlaceholder = placeholderPosition[0].pos.find(
|
||||
(pos) => pos.key === props.signKey
|
||||
);
|
||||
|
||||
for (let i = 0; i < props.allPages; i++) {
|
||||
//get exist placeholder position for particular page
|
||||
const existPlaceholder = xyPostion.filter(
|
||||
(data) => data.pageNumber === newPageNumber
|
||||
);
|
||||
const existPlaceholderPosition =
|
||||
existPlaceholder[0] && existPlaceholder[0].pos;
|
||||
|
||||
const newId = randomId();
|
||||
currentPlaceholder.key = newId;
|
||||
//function for get copy to requested location of placeholder position
|
||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||
type,
|
||||
currentPlaceholder,
|
||||
newPageNumber,
|
||||
existPlaceholderPosition
|
||||
);
|
||||
if (getPlaceholderObj) {
|
||||
newPlaceholderPosition.push(getPlaceholderObj);
|
||||
}
|
||||
|
||||
newPageNumber++;
|
||||
}
|
||||
props.setXyPostion(newPlaceholderPosition);
|
||||
}
|
||||
};
|
||||
|
||||
//function for getting selected type placeholder copy
|
||||
const handleApplyCopy = () => {
|
||||
if (selectCopyType === "All pages") {
|
||||
copyPlaceholder("all");
|
||||
} else if (selectCopyType === "All pages but last") {
|
||||
copyPlaceholder("last");
|
||||
} else if (selectCopyType === "All pages but first") {
|
||||
copyPlaceholder("first");
|
||||
}
|
||||
};
|
||||
const handleUniqueId = () => {
|
||||
if (props.widgetType === textWidget) {
|
||||
props.setUniqueId(props?.tempSignerId);
|
||||
props.setTempSignerId("");
|
||||
}
|
||||
props.setIsPageCopy(false);
|
||||
};
|
||||
return (
|
||||
<ModalUi
|
||||
isOpen={props.isPageCopy}
|
||||
title={"Copy to all pages"}
|
||||
handleClose={() => handleUniqueId()}
|
||||
>
|
||||
<div className="h-full p-[20px] text-base-content">
|
||||
{copyType.map((data, key) => {
|
||||
return (
|
||||
<div key={key} className="flex flex-col">
|
||||
<label className="text-[16px] font-medium">
|
||||
<input
|
||||
className="mr-[8px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
value={data}
|
||||
onChange={() => setSelectCopyType(data)}
|
||||
checked={selectCopyType === data}
|
||||
/>
|
||||
|
||||
{data}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="flex bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => {
|
||||
handleApplyCopy();
|
||||
handleUniqueId();
|
||||
}}
|
||||
type="button"
|
||||
disabled={!selectCopyType}
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
onClick={() => handleUniqueId()}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlaceholderCopy;
|
||||
@@ -0,0 +1,834 @@
|
||||
import React, { useEffect, useState, forwardRef, useRef } from "react";
|
||||
import {
|
||||
getMonth,
|
||||
getYear,
|
||||
onChangeHeightOfTextArea,
|
||||
onChangeInput,
|
||||
radioButtonWidget,
|
||||
range,
|
||||
textInputWidget,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import "../../styles/signature.css";
|
||||
import RegexParser from "regex-parser";
|
||||
import { emailRegex } from "../../constant/const";
|
||||
const textWidgetCls =
|
||||
"w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] rounded-[2px] border-[1px] border-[#007bff] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap bg-white";
|
||||
const selectWidgetCls =
|
||||
"w-full h-full absolute left-0 top-0 border-[1px] border-[#007bff] rounded-[2px] focus:outline-none text-base-content";
|
||||
function PlaceholderType(props) {
|
||||
const type = props?.pos?.type;
|
||||
const [selectOption, setSelectOption] = useState("");
|
||||
const [validatePlaceholder, setValidatePlaceholder] = useState("");
|
||||
const inputRef = useRef(null);
|
||||
const [textValue, setTextValue] = useState();
|
||||
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
|
||||
const years = range(1990, getYear(new Date()) + 16, 1);
|
||||
const months = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December"
|
||||
];
|
||||
const validateExpression = (regexValidation) => {
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
props.setDraggingEnabled(true);
|
||||
const validateType = props.pos?.options?.validation?.type;
|
||||
let regexValidation;
|
||||
if (validateType && validateType !== "text") {
|
||||
switch (validateType) {
|
||||
case "email":
|
||||
regexValidation = emailRegex;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
case "number":
|
||||
regexValidation = /^[0-9\s]*$/;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
default:
|
||||
regexValidation = props.pos?.options?.validation?.pattern || "";
|
||||
validateExpression(regexValidation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleTextValid = (e) => {
|
||||
const textInput = e.target.value;
|
||||
setTextValue(textInput);
|
||||
};
|
||||
function checkRegularExpress(validateType) {
|
||||
switch (validateType) {
|
||||
case "email":
|
||||
setValidatePlaceholder("demo@gmail.com");
|
||||
break;
|
||||
case "number":
|
||||
setValidatePlaceholder("12345");
|
||||
break;
|
||||
case "text":
|
||||
setValidatePlaceholder("please enter text");
|
||||
break;
|
||||
default:
|
||||
setValidatePlaceholder("please enter value");
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (type && type === "checkbox" && props.isNeedSign) {
|
||||
const isDefaultValue = props.pos.options?.defaultValue;
|
||||
if (isDefaultValue) {
|
||||
setSelectedCheckbox(isDefaultValue);
|
||||
}
|
||||
} else if (props.pos?.options?.hint) {
|
||||
setValidatePlaceholder(props.pos?.options.hint);
|
||||
} else if (props.pos?.options?.validation?.type) {
|
||||
checkRegularExpress(props.pos?.options?.validation?.type);
|
||||
}
|
||||
setTextValue(
|
||||
props.pos?.options?.response
|
||||
? props.pos?.options?.response
|
||||
: props.pos?.options?.defaultValue
|
||||
? props.pos?.options?.defaultValue
|
||||
: ""
|
||||
);
|
||||
setSelectOption(
|
||||
props.pos?.options?.response
|
||||
? props.pos?.options?.response
|
||||
: props.pos?.options?.defaultValue
|
||||
? props.pos?.options?.defaultValue
|
||||
: ""
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
["name", "email", "job title", "company"].includes(props.pos?.type) &&
|
||||
props.isNeedSign &&
|
||||
props.data?.signerObjId === props?.signerObjId
|
||||
) {
|
||||
const defaultData = props.pos?.options?.defaultValue;
|
||||
if (defaultData) {
|
||||
setTextValue(defaultData);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.pos?.options?.defaultValue]);
|
||||
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
className={`${selectWidgetCls} text-[12px] overflow-hidden`}
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
{value}
|
||||
<i className="fa-light fa-calendar ml-[5px]"></i>
|
||||
</div>
|
||||
));
|
||||
ExampleCustomInput.displayName = "ExampleCustomInput";
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
["name", "email", "job title", "company"].includes(type) &&
|
||||
props.isNeedSign &&
|
||||
props.data?.signerObjId === props.signerObjId
|
||||
) {
|
||||
const isDefault = true;
|
||||
const senderUser = localStorage.getItem(`Extand_Class`);
|
||||
const jsonSender = JSON.parse(senderUser);
|
||||
onChangeInput(
|
||||
jsonSender && jsonSender[0],
|
||||
null,
|
||||
props.xyPostion,
|
||||
null,
|
||||
props.setXyPostion,
|
||||
props.data.Id,
|
||||
isDefault
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [type]);
|
||||
//function for show checked checkbox
|
||||
const selectCheckbox = (ind) => {
|
||||
const res = props.pos.options?.response;
|
||||
const defaultCheck = props.pos.options?.defaultValue;
|
||||
if (res && res?.length > 0) {
|
||||
const isSelectIndex = res.indexOf(ind);
|
||||
if (isSelectIndex > -1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// }
|
||||
} else if (defaultCheck) {
|
||||
const isSelectIndex = defaultCheck.indexOf(ind);
|
||||
if (isSelectIndex > -1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRadioCheck = (data) => {
|
||||
const defaultData = props.pos.options?.defaultValue;
|
||||
if (textValue === data) {
|
||||
return true;
|
||||
} else if (defaultData === data) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//function for set checked and unchecked value of checkbox
|
||||
const handleCheckboxValue = (isChecked, ind) => {
|
||||
let updateSelectedCheckbox = [],
|
||||
checkedList;
|
||||
let isDefaultValue, isDefaultEmpty;
|
||||
if (type === "checkbox") {
|
||||
updateSelectedCheckbox = selectedCheckbox ? selectedCheckbox : [];
|
||||
|
||||
if (isChecked) {
|
||||
updateSelectedCheckbox.push(ind);
|
||||
setSelectedCheckbox(updateSelectedCheckbox);
|
||||
} else {
|
||||
checkedList = selectedCheckbox.filter((data) => data !== ind);
|
||||
setSelectedCheckbox(checkedList);
|
||||
}
|
||||
if (props.isNeedSign) {
|
||||
isDefaultValue = props.pos.options?.defaultValue;
|
||||
}
|
||||
if (isDefaultValue && isDefaultValue.length > 0) {
|
||||
isDefaultEmpty = true;
|
||||
}
|
||||
onChangeInput(
|
||||
checkedList ? checkedList : updateSelectedCheckbox,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
null,
|
||||
isDefaultEmpty
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
//function to handle select radio widget and set value seletced by user
|
||||
const handleCheckRadio = (isChecked, data) => {
|
||||
let isDefaultValue,
|
||||
isDefaultEmpty,
|
||||
isRadio = true;
|
||||
if (props.isNeedSign) {
|
||||
isDefaultValue = props.pos.options?.defaultValue;
|
||||
}
|
||||
if (isDefaultValue) {
|
||||
isDefaultEmpty = true;
|
||||
}
|
||||
if (isChecked) {
|
||||
setTextValue(data);
|
||||
} else {
|
||||
setTextValue("");
|
||||
}
|
||||
onChangeInput(
|
||||
data,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
null,
|
||||
isDefaultEmpty,
|
||||
isRadio
|
||||
);
|
||||
};
|
||||
//function to set onchange date
|
||||
const handleOnDateChange = (date) => {
|
||||
props.setStartDate(date);
|
||||
const isDateChange = true;
|
||||
const dateObj = {
|
||||
date: date,
|
||||
format: props.selectDate.format
|
||||
};
|
||||
props.handleSaveDate(dateObj, isDateChange);
|
||||
};
|
||||
//handle height on enter press in text area
|
||||
const handleEnterPress = (e) => {
|
||||
const height = 18;
|
||||
if (e.key === "Enter") {
|
||||
//function to save height of text area
|
||||
onChangeHeightOfTextArea(
|
||||
height,
|
||||
props.pos.type,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id
|
||||
);
|
||||
}
|
||||
};
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
alt="signature"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full "
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
props?.pos?.type
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "stamp":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
alt="stamp"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
props?.pos?.type
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "checkbox":
|
||||
return (
|
||||
<div style={{ zIndex: props.isSignYourself && "99" }}>
|
||||
{props.pos.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="flex items-center text-center gap-0.5">
|
||||
<input
|
||||
className={`${
|
||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||
} flex justify-center op-checkbox op-checkbox-xs rounded-[0.350rem]`}
|
||||
onBlur={handleInputBlur}
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
type="checkbox"
|
||||
checked={selectCheckbox(ind)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
if (!props.isPlaceholder) {
|
||||
const maxRequired =
|
||||
props.pos.options?.validation?.maxRequiredCount;
|
||||
const maxCountInt =
|
||||
maxRequired && parseInt(maxRequired);
|
||||
|
||||
if (maxCountInt > 0) {
|
||||
if (
|
||||
selectedCheckbox &&
|
||||
selectedCheckbox?.length <= maxCountInt - 1
|
||||
) {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
} else {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<label className="text-xs mb-0 text-center">{data}</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
case textInputWidget:
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={validatePlaceholder || "please enter value"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
setTextValue(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{type}</span>
|
||||
</div>
|
||||
);
|
||||
case "dropdown":
|
||||
return props.data?.signerObjId === props.signerObjId ? (
|
||||
<select
|
||||
className={`${selectWidgetCls} text-[12px] bg-inherit`}
|
||||
id="myDropdown"
|
||||
value={selectOption}
|
||||
onChange={(e) => {
|
||||
setSelectOption(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
>
|
||||
{/* Default/Title option */}
|
||||
<option value="" disabled hidden>
|
||||
{props?.pos?.options?.name}
|
||||
</option>
|
||||
|
||||
{props.pos?.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<option key={ind} value={data}>
|
||||
{data}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
) : (
|
||||
<div className="text-[12px] overflow-hidden">
|
||||
{props.pos?.options?.name ? props.pos.options.name : type}
|
||||
</div>
|
||||
);
|
||||
case "initials":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
alt="initials"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
props?.pos?.type
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "name":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"name"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
const isDefault = false;
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
isDefault
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{type}</span>
|
||||
</div>
|
||||
);
|
||||
case "company":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder="Enter text"
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
overflow: "hidden",
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
>
|
||||
<span>{type}</span>
|
||||
</div>
|
||||
);
|
||||
case "job title":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"job title"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{type}</span>
|
||||
</div>
|
||||
);
|
||||
case "date":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<div>
|
||||
<DatePicker
|
||||
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
||||
<div className="flex justify-start ml-2 ">
|
||||
<select
|
||||
className="bg-transparent outline-none"
|
||||
value={months[getMonth(date)]}
|
||||
onChange={({ target: { value } }) =>
|
||||
changeMonth(months.indexOf(value))
|
||||
}
|
||||
>
|
||||
{months.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
className="bg-transparent outline-none"
|
||||
value={getYear(date)}
|
||||
onChange={({ target: { value } }) => changeYear(value)}
|
||||
>
|
||||
{years.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
disabled={
|
||||
props.isPlaceholder ||
|
||||
(props.isNeedSign &&
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
onBlur={handleInputBlur}
|
||||
closeOnScroll={true}
|
||||
className={`${selectWidgetCls} outline-[#007bff]`}
|
||||
selected={
|
||||
props?.startDate
|
||||
? props?.startDate
|
||||
: props.pos.options?.response &&
|
||||
new Date(props.pos.options.response)
|
||||
}
|
||||
onChange={(date) => handleOnDateChange(date)}
|
||||
popperPlacement="top-end"
|
||||
customInput={<ExampleCustomInput />}
|
||||
dateFormat={
|
||||
props.selectDate
|
||||
? props.selectDate?.format
|
||||
: props.pos?.options?.validation?.format
|
||||
? props.pos?.options?.validation?.format
|
||||
: "MM/dd/yyyy"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[12px] text-black items-center overflow-hidden">
|
||||
<span>
|
||||
{props.selectDate
|
||||
? props.selectDate?.format
|
||||
: props.pos?.options?.validation?.format
|
||||
? props.pos?.options?.validation?.format
|
||||
: "MM/dd/yyyy"}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case "image":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
alt="image"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
props?.pos?.type
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "email":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"enter email"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{type}</span>
|
||||
</div>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
return (
|
||||
<div>
|
||||
{props.pos.options?.values.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="flex items-center text-center gap-0.5">
|
||||
<input
|
||||
className={`${
|
||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||
} ${"w-[15px] h-[15px]"} flex justify-center op-radio`}
|
||||
type="radio"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
checked={handleRadioCheck(data)}
|
||||
onChange={(e) => {
|
||||
if (!props.isPlaceholder) {
|
||||
handleCheckRadio(e.target.checked, data);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<label className="text-xs mb-0">{data}</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
case textWidget:
|
||||
return (
|
||||
<textarea
|
||||
placeholder="Enter text"
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
setTextValue(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontFamily: "Arial, sans-serif",
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return props.pos.SignUrl ? (
|
||||
<div className="pointer-events-none">
|
||||
<img
|
||||
alt="image"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(props?.data?.Id, props?.data?.Role)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PlaceholderType;
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
|
||||
function PrevNext({ pageNumber, allPages, changePage }) {
|
||||
//for go to previous page
|
||||
function previousPage() {
|
||||
changePage(-1);
|
||||
}
|
||||
//for go to next page
|
||||
function nextPage() {
|
||||
changePage(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className="op-btn op-btn-neutral op-btn-xs md:op-btn-sm font-semibold text-xs"
|
||||
disabled={pageNumber <= 1}
|
||||
onClick={previousPage}
|
||||
>
|
||||
<span className="block lg:hidden">
|
||||
<i className="fa-light fa-backward" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span className="lg:block hidden 2xl:text-[30px]">Prev</span>
|
||||
</button>
|
||||
<span className="text-xs text-base-content font-medium mx-2 2xl:text-[30px]">
|
||||
{pageNumber || (allPages ? 1 : "--")} of {allPages || "--"}
|
||||
</span>
|
||||
<button
|
||||
className="op-btn op-btn-neutral op-btn-xs md:op-btn-sm font-semibold text-xs"
|
||||
disabled={pageNumber >= allPages}
|
||||
onClick={nextPage}
|
||||
>
|
||||
<span className="block lg:hidden">
|
||||
<i className="fa-light fa-forward" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span className="lg:block hidden 2xl:text-[30px]">Next</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PrevNext;
|
||||
@@ -0,0 +1,277 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import {
|
||||
color,
|
||||
darkenColor,
|
||||
getFirstLetter,
|
||||
isMobile,
|
||||
nameColor
|
||||
} from "../../constant/Utils";
|
||||
const cursor =
|
||||
"cursor-[url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAASElEQVR4nGNgwAMkJSUbpKSkOvCpIaT5PxSTbogUQjMYMwxeIIXmVFIxA8UGDDyQGg0DnIDi6JKUlCxHMqCeZAOghjSAMD5FAKfeaURdUFxCAAAAAElFTkSuQmCC'),_pointer]";
|
||||
const RecipientList = (props) => {
|
||||
const [animationParent] = useAutoAnimate();
|
||||
const [isHover, setIsHover] = useState();
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
//function for onhover signer name change background color
|
||||
const inputRef = useRef(null);
|
||||
const isWidgetExist = (Id) => {
|
||||
return props.signerPos.some((x) => x.Id === Id);
|
||||
};
|
||||
|
||||
//handle drag start
|
||||
const handleDragStart = (e, id) => {
|
||||
// `e.dataTransfer.setData('text/plain')`is used to set the data to be transferred during a drag operation.
|
||||
// The first argument specifies the type of data being set, and the second argument is the actual data you want to transfer.
|
||||
e.dataTransfer.setData("text/plain", id);
|
||||
};
|
||||
|
||||
//handleDragOver prevents the default behavior of the dragover event, which is necessary for the drop event to be triggered.
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
//handle draggable element drop and also used in mobile view on up and down key to chnage sequence of recipient's list
|
||||
const handleChangeSequence = (e, ind, isUp, isDown) => {
|
||||
e.preventDefault();
|
||||
let draggedItemId;
|
||||
let index = ind;
|
||||
|
||||
//if isUp true then set item's id and index in mobile view
|
||||
if (isUp) {
|
||||
draggedItemId = index;
|
||||
index = index - 1;
|
||||
}
|
||||
//if isDown true then set item's id and index in mobile view
|
||||
else if (isDown) {
|
||||
draggedItemId = index;
|
||||
index = index + 1;
|
||||
} //else set id on drag element in desktop viiew
|
||||
else {
|
||||
//`e.dataTransfer.getData('text/plain')` is used to get data that you have saved.
|
||||
draggedItemId = e.dataTransfer.getData("text/plain");
|
||||
}
|
||||
|
||||
//convert string to number
|
||||
const intDragId = parseInt(draggedItemId);
|
||||
//get that item to change position
|
||||
const draggedItem = props.signersdata.filter((_, ind) => ind === intDragId);
|
||||
const remainingItems = props.signersdata.filter(
|
||||
(_, ind) => ind !== intDragId
|
||||
);
|
||||
//splice method is used to replace or add new value in array at specific index
|
||||
remainingItems.splice(index, 0, ...draggedItem);
|
||||
//set current draggable recipient details,objectId,index,contract_className ... after replace recipient list
|
||||
props?.setSignersData(remainingItems);
|
||||
props?.setSignerObjId(remainingItems[index]?.objectId || "");
|
||||
props?.setIsSelectId(index);
|
||||
props?.setContractName(remainingItems[index]?.className || "");
|
||||
props?.setUniqueId(remainingItems[index]?.Id);
|
||||
props?.setRoleName(remainingItems[index]?.Role);
|
||||
props?.setBlockColor(remainingItems[index]?.blockColor);
|
||||
|
||||
//change order of placeholder's list using sorting method
|
||||
//`remainingItems` is correct order of signers after change order
|
||||
const changeOrderSignerList = props?.signerPos.sort((a, b) => {
|
||||
//`indexA` and `indexB` is to get element position using index in correct order array
|
||||
const indexA = remainingItems.findIndex((item) => item.Id === a.Id);
|
||||
const indexB = remainingItems.findIndex((item) => item.Id === b.Id);
|
||||
//and then compare `indexA - indexB` value
|
||||
//if positive it means indexB element comes before indexA then need to sorting
|
||||
//if negative it means indexA element is on correct position do not need to sorting
|
||||
return indexA - indexB;
|
||||
});
|
||||
props?.setSignerPos(changeOrderSignerList);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{props.signersdata.length > 0 &&
|
||||
props.signersdata.map((obj, ind) => {
|
||||
return (
|
||||
<div
|
||||
ref={animationParent}
|
||||
key={ind}
|
||||
draggable={props.sendInOrder && !isMobile ? true : false}
|
||||
onDragStart={(e) =>
|
||||
props.sendInOrder && !isMobile && handleDragStart(e, ind)
|
||||
}
|
||||
onDragOver={(e) =>
|
||||
props.sendInOrder && !isMobile && handleDragOver(e)
|
||||
}
|
||||
onDrop={(e) =>
|
||||
props.sendInOrder && !isMobile && handleChangeSequence(e, ind)
|
||||
}
|
||||
data-tut="recipientArea"
|
||||
onMouseEnter={() => setIsHover(ind)}
|
||||
onMouseLeave={() => setIsHover(null)}
|
||||
className={`${
|
||||
props.sendInOrder
|
||||
? props.isMailSend
|
||||
? "pointer-events-none bg-opacity-80"
|
||||
: `text-[12px] font-bold ${cursor}`
|
||||
: props.isMailSend && "pointer-events-none bg-opacity-80"
|
||||
} flex flex-row rounded-xl px-2 py-[10px] mt-1 mx-1 items-center last:mb-0.5`}
|
||||
style={{
|
||||
background:
|
||||
(!isMobile && isHover === ind) || props.uniqueId === obj.Id
|
||||
? obj?.blockColor
|
||||
? obj?.blockColor
|
||||
: color[ind % color.length]
|
||||
: "transparent"
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
props.setSignerObjId(obj?.objectId || "");
|
||||
props.setIsSelectId(ind);
|
||||
props.setContractName(obj?.className || "");
|
||||
props.setUniqueId(obj.Id);
|
||||
props.setRoleName(obj.Role);
|
||||
props.setBlockColor(obj?.blockColor);
|
||||
if (props.handleModal) {
|
||||
props.handleModal();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row items-center w-full">
|
||||
<div
|
||||
className="flex w-[30px] h-[30px] rounded-full items-center justify-center mr-2"
|
||||
style={{
|
||||
background: obj?.blockColor
|
||||
? darkenColor(obj?.blockColor, 0.4)
|
||||
: nameColor[ind % nameColor.length]
|
||||
}}
|
||||
>
|
||||
<span className="text-white uppercase font-bold text-center text-[12px]">
|
||||
{isWidgetExist(obj.Id) ? (
|
||||
<i className="fa-light fa-check"></i>
|
||||
) : (
|
||||
<>
|
||||
{obj.Name
|
||||
? getFirstLetter(obj.Name)
|
||||
: getFirstLetter(obj.Role)}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
obj.Name ? "flex-col" : "flex-row"
|
||||
} flex items-center`}
|
||||
>
|
||||
{obj.Name ? (
|
||||
<span
|
||||
className={`${
|
||||
(!isMobile && isHover === ind) ||
|
||||
props.isSelectListId === ind
|
||||
? "text-[#424242]"
|
||||
: "text-base-content"
|
||||
} text-[12px] font-bold w-[100px] whitespace-nowrap overflow-hidden text-ellipsis`}
|
||||
>
|
||||
{obj.Name}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className={`${
|
||||
(!isMobile && isHover === ind) ||
|
||||
props.isSelectListId === ind
|
||||
? "text-[#424242]"
|
||||
: "text-base-content"
|
||||
} text-[12px] font-bold w-[100px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer`}
|
||||
onClick={() => {
|
||||
setIsEdit({ [obj.Id]: true });
|
||||
props.setRoleName(obj.Role);
|
||||
}}
|
||||
>
|
||||
{isEdit?.[obj.Id] && props.handleRoleChange ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="bg-transparent p-[3px]"
|
||||
value={obj.Role}
|
||||
onChange={(e) => props.handleRoleChange(e, obj.Id)}
|
||||
onBlur={() => {
|
||||
setIsEdit({});
|
||||
props.handleOnBlur(obj.Role, obj.Id);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
inputRef.current.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-[13px] 2xl:text-[25px]">
|
||||
{obj.Role}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{obj.Name && (
|
||||
<span
|
||||
className={` ${
|
||||
(!isMobile && isHover === ind) ||
|
||||
props.isSelectListId === ind
|
||||
? "text-[#424242]"
|
||||
: "text-base-content"
|
||||
} text-[10px] font-medium w-[100px] whitespace-nowrap overflow-hidden text-ellipsis`}
|
||||
>
|
||||
{obj?.Role || obj?.Email}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isMobile && props.sendInOrder && (
|
||||
<div className="flex flex-row items-center gap-[5px] mr-2">
|
||||
<div
|
||||
onClick={(e) => {
|
||||
if (ind !== 0) {
|
||||
e.stopPropagation();
|
||||
handleChangeSequence(e, ind, "up");
|
||||
}
|
||||
}}
|
||||
className={ind === 0 ? "text-[gray]" : "text-black"}
|
||||
>
|
||||
▲
|
||||
</div>
|
||||
<div
|
||||
onClick={(e) => {
|
||||
if (ind !== props.signersdata.length - 1) {
|
||||
e.stopPropagation();
|
||||
handleChangeSequence(e, ind, null, "down");
|
||||
}
|
||||
}}
|
||||
className={
|
||||
ind === props.signersdata.length - 1
|
||||
? "text-[gray]"
|
||||
: "text-black"
|
||||
}
|
||||
>
|
||||
▼
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{props.handleDeleteUser && (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
props.handleDeleteUser(obj.Id);
|
||||
}}
|
||||
className={`${
|
||||
(!isMobile && isHover === ind) ||
|
||||
props.isSelectListId === ind
|
||||
? "text-[#424242]"
|
||||
: "text-base-content"
|
||||
} cursor-pointer`}
|
||||
>
|
||||
<i className="fa-light fa-trash-can 2xl:text-[30px]"></i>
|
||||
</div>
|
||||
)}
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecipientList;
|
||||
@@ -0,0 +1,114 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
function RenderAllPdfPage({
|
||||
signPdfUrl,
|
||||
allPages,
|
||||
setAllPages,
|
||||
setPageNumber,
|
||||
setSignBtnPosition,
|
||||
pageNumber,
|
||||
signerPos,
|
||||
signerObjectId,
|
||||
containerWH
|
||||
}) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
const pageContainer = useRef();
|
||||
const isHeader = useSelector((state) => state.showHeader);
|
||||
const [pageWidth, setPageWidth] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const updateSize = () => {
|
||||
if (pageContainer.current) {
|
||||
setPageWidth(pageContainer.current.offsetWidth);
|
||||
}
|
||||
};
|
||||
|
||||
// Use setTimeout to wait for the transition to complete
|
||||
const timer = setTimeout(updateSize, 100); // match the transition duration
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isHeader, pageContainer, containerWH]);
|
||||
//'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 className="absolute z-20 top-[1px] -right-[13px] -translate-x-1/2 -translate-y-1/2">
|
||||
<i
|
||||
style={{ color: bookmarkColor || "red" }}
|
||||
className="fa-solid fa-bookmark"
|
||||
></i>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div ref={pageContainer} className="hidden w-[20%] bg-base-100 md:block">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
Pages
|
||||
</div>
|
||||
<div
|
||||
className={`flex h-[90%] flex-col items-center m-2
|
||||
autoSignScroll hide-scrollbar max-h-[100vh] `}
|
||||
>
|
||||
<Document
|
||||
loading={"Loading Document.."}
|
||||
onLoadSuccess={onDocumentLoad}
|
||||
file={signPdfUrl}
|
||||
>
|
||||
{Array.from(new Array(allPages), (el, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`${
|
||||
pageNumber - 1 === index ? "border-[red]" : "border-[#878787]"
|
||||
} border-2 m-[10px] flex justify-center items-center relative`}
|
||||
onClick={() => {
|
||||
setPageNumber(index + 1);
|
||||
if (setSignBtnPosition) {
|
||||
setSignBtnPosition([]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{signerPos && addSignatureBookmark(index)}
|
||||
|
||||
<div className="relative z-[1] overflow-hidden">
|
||||
<Page
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={pageWidth - 60}
|
||||
scale={1}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Document>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenderAllPdfPage;
|
||||
@@ -0,0 +1,620 @@
|
||||
import React from "react";
|
||||
import RSC from "react-scrollbars-custom";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import {
|
||||
defaultWidthHeight,
|
||||
getContainerScale,
|
||||
handleImageResize,
|
||||
handleSignYourselfImageResize
|
||||
} from "../../constant/Utils";
|
||||
import Placeholder from "./Placeholder";
|
||||
import Alert from "../../primitives/Alert";
|
||||
|
||||
function RenderPdf({
|
||||
pageNumber,
|
||||
drop,
|
||||
signerPos,
|
||||
successEmail,
|
||||
handleTabDrag,
|
||||
handleStop,
|
||||
isDragging,
|
||||
setIsSignPad,
|
||||
setIsStamp,
|
||||
handleDeleteSign,
|
||||
setSignKey,
|
||||
pdfDetails,
|
||||
xyPostion,
|
||||
pdfUrl,
|
||||
pageDetails,
|
||||
pdfRequest,
|
||||
signerObjectId,
|
||||
signedSigners,
|
||||
pdfLoad,
|
||||
setPdfLoad,
|
||||
placeholder,
|
||||
setSignerPos,
|
||||
setXyPostion,
|
||||
index,
|
||||
containerWH,
|
||||
setIsResize,
|
||||
handleLinkUser,
|
||||
setUniqueId,
|
||||
signersdata,
|
||||
setIsPageCopy,
|
||||
setSignerObjId,
|
||||
setShowDropdown,
|
||||
setIsInitial,
|
||||
setIsValidate,
|
||||
setWidgetType,
|
||||
setValidateAlert,
|
||||
setIsRadio,
|
||||
setCurrWidgetsDetails,
|
||||
setSelectWidgetId,
|
||||
selectWidgetId,
|
||||
unSignedWidgetId,
|
||||
setIsCheckbox,
|
||||
handleNameModal,
|
||||
handleTextSettingModal,
|
||||
setTempSignerId,
|
||||
uniqueId,
|
||||
pdfOriginalWH,
|
||||
scale,
|
||||
setIsSelectId,
|
||||
ispublicTemplate,
|
||||
handleUserDetails
|
||||
}) {
|
||||
const isMobile = window.innerWidth < 767;
|
||||
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
|
||||
// handle signature block width and height according to screen
|
||||
const posWidth = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
pdfOriginalWH,
|
||||
pageNumber,
|
||||
containerWH
|
||||
);
|
||||
const defaultWidth = defaultWidthHeight(pos.type).width;
|
||||
const posWidth = pos.Width ? pos.Width : defaultWidth;
|
||||
if (signYourself) {
|
||||
return posWidth * scale * containerScale;
|
||||
} else {
|
||||
if (pos.isMobile && pos.scale) {
|
||||
if (pos.IsResize) {
|
||||
if (scale > 1) {
|
||||
return posWidth * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posWidth * containerScale;
|
||||
}
|
||||
} else {
|
||||
if (scale > 1) {
|
||||
return posWidth * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posWidth * pos.scale * containerScale;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return posWidth * scale * containerScale;
|
||||
}
|
||||
}
|
||||
};
|
||||
const posHeight = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
pdfOriginalWH,
|
||||
pageNumber,
|
||||
containerWH
|
||||
);
|
||||
const posHeight = pos.Height || defaultWidthHeight(pos.type).height;
|
||||
if (signYourself) {
|
||||
return posHeight * scale * containerScale;
|
||||
} else {
|
||||
if (pos.isMobile && pos.scale) {
|
||||
if (pos.IsResize) {
|
||||
if (scale > 1) {
|
||||
return posHeight * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posHeight * containerScale;
|
||||
}
|
||||
} else {
|
||||
if (scale > 1) {
|
||||
return posHeight * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posHeight * pos.scale * containerScale;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return posHeight * scale * containerScale;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//function for render placeholder block over pdf document
|
||||
const checkSignedSigners = (data) => {
|
||||
let checkSign = [];
|
||||
//condition to handle quick send flow and using normal request sign flow
|
||||
checkSign = signedSigners
|
||||
? signedSigners?.filter(
|
||||
(sign) =>
|
||||
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
|
||||
)
|
||||
: [];
|
||||
const handleAllUserName = (Id, Role, type) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="text-black text-[8px] font-bold">
|
||||
{
|
||||
pdfDetails[0].Signers?.find(
|
||||
(signer) => signer.objectId === data.signerObjId
|
||||
)?.Name
|
||||
}
|
||||
</div>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
{Role && (
|
||||
<div className="text-black text-[8px] font-medium">
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
return (
|
||||
checkSign.length === 0 &&
|
||||
data.placeHolder.map((placeData, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
pos && (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setSignKey={setSignKey}
|
||||
setIsSignPad={setIsSignPad}
|
||||
setIsStamp={setIsStamp}
|
||||
handleSignYourselfImageResize={handleImageResize}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
isShowBorder={false}
|
||||
signerObjId={signerObjectId}
|
||||
isShowDropdown={true}
|
||||
isNeedSign={true}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
handleUserName={handleAllUserName}
|
||||
isDragging={false}
|
||||
pdfDetails={pdfDetails}
|
||||
setIsInitial={setIsInitial}
|
||||
setValidateAlert={setValidateAlert}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
uniqueId={uniqueId}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
ispublicTemplate={ispublicTemplate}
|
||||
handleUserDetails={handleUserDetails}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
//function for render placeholder block over pdf document
|
||||
|
||||
const handleUserName = (Id, Role, type) => {
|
||||
if (Id) {
|
||||
const checkSign = signersdata.find((sign) => sign.Id === Id);
|
||||
if (checkSign?.Name) {
|
||||
return (
|
||||
<>
|
||||
<div className="text-black text-[8px] font-medium">
|
||||
{checkSign?.Name}
|
||||
</div>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
{Role && (
|
||||
<div className="text-black text-[8px] font-medium">
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
<div className="text-black text-[8px] font-medium">{Role}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
<div className="text-black text-[8px] font-medium">{Role}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{successEmail && <Alert type={"success"}>Email sent successfully!</Alert>}
|
||||
{isMobile ? (
|
||||
<div
|
||||
data-tut="reactourForth"
|
||||
className={`${
|
||||
isGuestSigner ? "30px" : ""
|
||||
} border-[0.1px] border-[#ebe8e8]`}
|
||||
ref={drop}
|
||||
id="container"
|
||||
>
|
||||
{containerWH?.width &&
|
||||
pdfOriginalWH.length > 0 &&
|
||||
(pdfRequest
|
||||
? signerPos.map((data, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: placeholder // placeholder mobile
|
||||
? signerPos.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
setSignerObjId={setSignerObjId}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
setShowDropdown={setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={setUniqueId}
|
||||
handleLinkUser={handleLinkUser}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={isDragging}
|
||||
setIsValidate={setIsValidate}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={handleNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
setIsSelectId={setIsSelectId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: xyPostion.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === pageNumber &&
|
||||
data.pos.map((pos) => {
|
||||
return (
|
||||
pos && (
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleSignYourselfImageResize
|
||||
}
|
||||
index={index}
|
||||
xyPostion={xyPostion}
|
||||
setXyPostion={setXyPostion}
|
||||
containerWH={containerWH}
|
||||
setIsSignPad={setIsSignPad}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={pdfDetails[0]}
|
||||
isDragging={isDragging}
|
||||
setIsInitial={setIsInitial}
|
||||
setWidgetType={setWidgetType}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleUserName={handleUserName}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setValidateAlert={setValidateAlert}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
|
||||
{/* this component for render pdf document is in middle of the component */}
|
||||
<div className="flex items-center justify-center">
|
||||
<Document
|
||||
onLoadError={() => {
|
||||
setPdfLoad(false);
|
||||
}}
|
||||
loading={"Loading Document.."}
|
||||
onLoadSuccess={pageDetails}
|
||||
// ref={pdfRef}'
|
||||
onClick={() => {
|
||||
if (setSelectWidgetId) {
|
||||
setSelectWidgetId("");
|
||||
}
|
||||
}}
|
||||
file={
|
||||
pdfUrl
|
||||
? pdfUrl
|
||||
: pdfDetails[0] && pdfDetails[0].SignedUrl
|
||||
? pdfDetails[0].SignedUrl
|
||||
: pdfDetails[0].URL
|
||||
}
|
||||
>
|
||||
<Page
|
||||
key={index}
|
||||
pageNumber={pageNumber}
|
||||
width={containerWH.width}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
/>
|
||||
</Document>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<RSC
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||
height: window.innerHeight + "px",
|
||||
zIndex: 0
|
||||
}}
|
||||
noScrollY={false}
|
||||
noScrollX={scale === 1 ? true : false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: containerWH?.width && containerWH?.width * scale
|
||||
}}
|
||||
ref={drop}
|
||||
id="container"
|
||||
>
|
||||
{pdfLoad &&
|
||||
containerWH?.width &&
|
||||
pdfOriginalWH.length > 0 &&
|
||||
(pdfRequest //pdf request sign flow
|
||||
? signerPos?.map((data, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: placeholder //placeholder and template flow
|
||||
? signerPos.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
setSignerObjId={setSignerObjId}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
setShowDropdown={setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={setUniqueId}
|
||||
handleLinkUser={handleLinkUser}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={isDragging}
|
||||
setIsValidate={setIsValidate}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={handleNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
setIsSelectId={setIsSelectId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: xyPostion.map((data, ind) => {
|
||||
// signyourself flow
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === pageNumber &&
|
||||
data.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={(event, dragElement) =>
|
||||
handleStop(event, dragElement, pos.type)
|
||||
}
|
||||
handleSignYourselfImageResize={
|
||||
handleSignYourselfImageResize
|
||||
}
|
||||
index={index}
|
||||
xyPostion={xyPostion}
|
||||
setXyPostion={setXyPostion}
|
||||
setIsSignPad={setIsSignPad}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={pdfDetails[0]}
|
||||
isDragging={isDragging}
|
||||
setIsInitial={setIsInitial}
|
||||
setWidgetType={setWidgetType}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleUserName={handleUserName}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setValidateAlert={setValidateAlert}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
|
||||
{/* this component for render pdf document is in middle of the component */}
|
||||
<Document
|
||||
onLoadError={() => {
|
||||
setPdfLoad(false);
|
||||
}}
|
||||
loading={"Loading Document.."}
|
||||
onLoadSuccess={pageDetails}
|
||||
onClick={() => {
|
||||
if (setSelectWidgetId) {
|
||||
setSelectWidgetId("");
|
||||
}
|
||||
}}
|
||||
// ref={pdfRef}
|
||||
file={
|
||||
pdfUrl
|
||||
? pdfUrl
|
||||
: pdfDetails[0] && pdfDetails[0].SignedUrl
|
||||
? pdfDetails[0].SignedUrl
|
||||
: pdfDetails[0].URL
|
||||
}
|
||||
>
|
||||
<Page
|
||||
key={index}
|
||||
width={containerWH.width}
|
||||
scale={scale || 1}
|
||||
className={"-z-[1]"} // when user zoom-in in tablet widgets move backward that's why pass -z-[1]
|
||||
pageNumber={pageNumber}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
/>
|
||||
</Document>
|
||||
</div>
|
||||
</RSC>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenderPdf;
|
||||
@@ -0,0 +1,616 @@
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import SignatureCanvas from "react-signature-canvas";
|
||||
function SignPad({
|
||||
isSignPad,
|
||||
isStamp,
|
||||
setIsImageSelect,
|
||||
onSaveSign,
|
||||
setIsSignPad,
|
||||
setImage,
|
||||
isImageSelect,
|
||||
imageRef,
|
||||
onImageChange,
|
||||
onSaveImage,
|
||||
image,
|
||||
defaultSign,
|
||||
setSignature,
|
||||
myInitial,
|
||||
isInitial,
|
||||
setIsInitial,
|
||||
setIsStamp,
|
||||
widgetType,
|
||||
currWidgetsDetails,
|
||||
setCurrWidgetsDetails
|
||||
}) {
|
||||
const [penColor, setPenColor] = useState("blue");
|
||||
const allColor = ["blue", "red", "black"];
|
||||
const canvasRef = useRef(null);
|
||||
const [isDefaultSign, setIsDefaultSign] = useState(false);
|
||||
const [isTab, setIsTab] = useState("draw");
|
||||
const [isSignImg, setIsSignImg] = useState("");
|
||||
const [signValue, setSignValue] = useState("");
|
||||
const [textWidth, setTextWidth] = useState(null);
|
||||
const [textHeight, setTextHeight] = useState(null);
|
||||
const [signatureType, setSignatureType] = useState("draw");
|
||||
const fontOptions = [
|
||||
{ value: "Fasthand" },
|
||||
{ value: "Dancing Script" },
|
||||
{ value: "Cedarville Cursive" },
|
||||
{ value: "Delicious Handrawn" }
|
||||
// Add more font options as needed
|
||||
];
|
||||
const [fontSelect, setFontSelect] = useState(fontOptions[0].value);
|
||||
|
||||
const senderUser =
|
||||
localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
) &&
|
||||
localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
);
|
||||
const jsonSender = JSON.parse(senderUser);
|
||||
const currentUserName = jsonSender && jsonSender.name;
|
||||
|
||||
//function for clear signature image
|
||||
const handleClear = () => {
|
||||
setCurrWidgetsDetails({});
|
||||
if (isTab === "draw") {
|
||||
if (canvasRef.current) {
|
||||
canvasRef.current.clear();
|
||||
} else if (isStamp) {
|
||||
setImage("");
|
||||
}
|
||||
setIsSignImg("");
|
||||
} else if (isTab === "uploadImage") {
|
||||
setImage("");
|
||||
}
|
||||
// setIsInitial(false);
|
||||
};
|
||||
//function for set signature url
|
||||
const handleSignatureChange = (data) => {
|
||||
setSignature(data);
|
||||
setIsSignImg(data);
|
||||
};
|
||||
//save button component
|
||||
const SaveBtn = () => {
|
||||
return (
|
||||
<div>
|
||||
{(isTab === "draw" || isTab === "uploadImage") && (
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost mr-1 mt-[2px]"
|
||||
onClick={() => handleClear()}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
setCurrWidgetsDetails({});
|
||||
if (!image) {
|
||||
if (isTab === "mysignature") {
|
||||
setIsSignImg("");
|
||||
if (isInitial) {
|
||||
onSaveSign(signatureType, "initials");
|
||||
} else {
|
||||
onSaveSign(null, "default");
|
||||
}
|
||||
} else {
|
||||
if (isTab === "type") {
|
||||
setIsSignImg("");
|
||||
onSaveSign(
|
||||
null,
|
||||
false,
|
||||
!isInitial && textWidth > 150 ? 150 : textWidth,
|
||||
!isInitial && textHeight > 35 ? 35 : textHeight
|
||||
);
|
||||
} else {
|
||||
setIsSignImg("");
|
||||
canvasRef.current.clear();
|
||||
onSaveSign(signatureType);
|
||||
}
|
||||
}
|
||||
|
||||
setPenColor("blue");
|
||||
} else {
|
||||
setIsSignImg("");
|
||||
onSaveImage(signatureType);
|
||||
}
|
||||
setIsSignPad(false);
|
||||
setIsInitial(false);
|
||||
setIsImageSelect(false);
|
||||
setIsDefaultSign(false);
|
||||
setImage();
|
||||
setIsTab("draw");
|
||||
setSignValue("");
|
||||
setIsStamp(false);
|
||||
}}
|
||||
type="button"
|
||||
className={`${
|
||||
isSignImg || image || isDefaultSign || textWidth
|
||||
? ""
|
||||
: "pointer-events-none"
|
||||
} op-btn op-btn-primary shadow-lg`}
|
||||
disabled={
|
||||
(isTab === "draw" && isSignImg) ||
|
||||
(isTab === "image" && image) ||
|
||||
(isTab === "mysignature" && isDefaultSign) ||
|
||||
(isTab === "type" && textWidth)
|
||||
? false
|
||||
: image
|
||||
? false
|
||||
: true
|
||||
}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
//useEffect for set already draw or save signature url/text url of signature text type and draw type for initial type and signature type widgets
|
||||
useEffect(() => {
|
||||
if (currWidgetsDetails && canvasRef.current) {
|
||||
const isWidgetType = currWidgetsDetails?.type;
|
||||
const signatureType = currWidgetsDetails?.signatureType;
|
||||
const url = currWidgetsDetails?.SignUrl;
|
||||
//checking widget type and draw type signature url
|
||||
if (isInitial) {
|
||||
if (isWidgetType === "initials" && signatureType === "draw" && url) {
|
||||
canvasRef.current.fromDataURL(url);
|
||||
}
|
||||
} else if (
|
||||
isWidgetType === "signature" &&
|
||||
signatureType === "draw" &&
|
||||
url
|
||||
) {
|
||||
canvasRef.current.fromDataURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
const trimmedName = currentUserName && currentUserName.trim();
|
||||
const firstCharacter = trimmedName?.charAt(0);
|
||||
const userName = isInitial ? firstCharacter : currentUserName;
|
||||
setSignValue(userName);
|
||||
setFontSelect("Fasthand");
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isSignPad]);
|
||||
useEffect(() => {
|
||||
const loadFont = async () => {
|
||||
try {
|
||||
await document.fonts.load(`20px ${fontSelect}`);
|
||||
const selectFontSTyle = fontOptions.find(
|
||||
(font) => font.value === fontSelect
|
||||
);
|
||||
setFontSelect(selectFontSTyle?.value || fontOptions[0].value);
|
||||
} catch (error) {
|
||||
console.error("Error loading font:", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadFont();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fontSelect]);
|
||||
|
||||
useEffect(() => {
|
||||
// Load the default signature after the component mounts
|
||||
if (canvasRef.current) {
|
||||
canvasRef.current.fromDataURL(isSignImg);
|
||||
}
|
||||
if (isTab === "type") {
|
||||
const trimmedName = signValue ? signValue.trim() : currentUserName.trim();
|
||||
const firstCharacter = trimmedName.charAt(0);
|
||||
const userName = isInitial ? firstCharacter : signValue;
|
||||
setSignValue(userName);
|
||||
convertToImg(fontSelect, userName);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isTab]);
|
||||
//function for convert input text value in image
|
||||
const convertToImg = async (fontStyle, text, color) => {
|
||||
//get text content to convert in image
|
||||
const textContent = text;
|
||||
const fontfamily = fontStyle
|
||||
? fontStyle
|
||||
: fontSelect
|
||||
? fontSelect
|
||||
: "Fasthand";
|
||||
const fontSizeValue = "40px";
|
||||
//creating span for getting text content width
|
||||
const span = document.createElement("span");
|
||||
span.textContent = textContent;
|
||||
span.style.font = `${fontSizeValue} ${fontfamily}`; // here put your text size and font family
|
||||
span.style.color = color ? color : penColor;
|
||||
span.style.display = "hidden";
|
||||
document.body.appendChild(span); // Replace 'container' with the ID of the container element
|
||||
|
||||
//create canvas to render text in canvas and convert in image
|
||||
const canvasElement = document.createElement("canvas");
|
||||
// Draw the text content on the canvas
|
||||
const ctx = canvasElement.getContext("2d");
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
const addExtraWidth = isInitial ? 10 : 50;
|
||||
const width = span.offsetWidth + addExtraWidth;
|
||||
const height = span.offsetHeight;
|
||||
setTextWidth(width);
|
||||
setTextHeight(height);
|
||||
const font = span.style["font"];
|
||||
// Set the canvas dimensions to match the span
|
||||
canvasElement.width = width * pixelRatio;
|
||||
canvasElement.height = height * pixelRatio;
|
||||
|
||||
// You can customize text styles if needed
|
||||
ctx.font = font;
|
||||
ctx.fillStyle = color ? color : penColor; // Set the text color
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.scale(pixelRatio, pixelRatio);
|
||||
// Draw the content of the span onto the canvas
|
||||
ctx.fillText(span.textContent, width / 2, height / 2); // Adjust the x,y-coordinate as needed
|
||||
//remove span tag
|
||||
document.body.removeChild(span);
|
||||
// Convert the canvas to image data
|
||||
const dataUrl = canvasElement.toDataURL("image/png");
|
||||
setSignature(dataUrl);
|
||||
};
|
||||
|
||||
const PenColorComponent = (props) => {
|
||||
return (
|
||||
<div className="flex flex-row items-start">
|
||||
{allColor.map((data, key) => {
|
||||
return (
|
||||
<i
|
||||
style={{
|
||||
margin: "5px",
|
||||
color: data,
|
||||
borderBottom:
|
||||
key === 0 && penColor === "blue"
|
||||
? "2px solid blue"
|
||||
: key === 1 && penColor === "red"
|
||||
? "2px solid red"
|
||||
: key === 2 && penColor === "black"
|
||||
? "2px solid black"
|
||||
: "2px solid white"
|
||||
}}
|
||||
onClick={() => {
|
||||
props?.convertToImg &&
|
||||
props?.convertToImg(fontSelect, signValue, data);
|
||||
if (key === 0) {
|
||||
setPenColor("blue");
|
||||
} else if (key === 1) {
|
||||
setPenColor("red");
|
||||
} else if (key === 2) {
|
||||
setPenColor("black");
|
||||
}
|
||||
}}
|
||||
key={key}
|
||||
className="fa-light fa-pen-nib"
|
||||
width={20}
|
||||
height={20}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{isSignPad && (
|
||||
<div className="op-modal op-modal-open">
|
||||
<div className="op-modal-box px-[13px] pt-2 pb-0">
|
||||
<div className="flex justify-between text-base-content items-center">
|
||||
<div className="text-[1.2rem]">
|
||||
<div className="flex flex-row justify-between mt-[3px]">
|
||||
<div className="flex flex-row justify-between gap-[5px] md:gap-[8px] text-[11px] md:text-base">
|
||||
{isStamp ? (
|
||||
<span className="text-base-content font-bold text-lg">
|
||||
{widgetType === "image" ||
|
||||
currWidgetsDetails?.type === "image"
|
||||
? "Upload image"
|
||||
: "Upload stamp image"}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(false);
|
||||
setIsImageSelect(false);
|
||||
setIsTab("draw");
|
||||
setImage();
|
||||
if (isSignImg) {
|
||||
setSignature(isSignImg);
|
||||
}
|
||||
}}
|
||||
className={`${
|
||||
isTab === "draw"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
Draw
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(false);
|
||||
setIsImageSelect(true);
|
||||
setIsTab("uploadImage");
|
||||
setSignatureType("");
|
||||
}}
|
||||
className={`${
|
||||
isTab === "uploadImage"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
Upload Image
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(false);
|
||||
setIsImageSelect(false);
|
||||
setIsTab("type");
|
||||
setSignatureType("");
|
||||
setImage();
|
||||
}}
|
||||
className={`${
|
||||
isTab === "type"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
Type
|
||||
</span>
|
||||
</div>
|
||||
{!isInitial && defaultSign ? (
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(true);
|
||||
setIsImageSelect(true);
|
||||
setIsTab("mysignature");
|
||||
setSignatureType("");
|
||||
setImage();
|
||||
}}
|
||||
className={`${
|
||||
isTab === "mysignature"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
My Signature
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
isInitial &&
|
||||
myInitial && (
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(true);
|
||||
setIsImageSelect(true);
|
||||
setIsTab("mysignature");
|
||||
setSignatureType("");
|
||||
setImage();
|
||||
}}
|
||||
className={`${
|
||||
isTab === "mysignature"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
My Initials
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="text-[1.5rem] cursor-pointer"
|
||||
onClick={() => {
|
||||
setPenColor("blue");
|
||||
setIsSignPad(false);
|
||||
setIsInitial(false);
|
||||
setIsImageSelect(false);
|
||||
setIsDefaultSign(false);
|
||||
setImage();
|
||||
setIsTab("draw");
|
||||
setSignatureType("draw");
|
||||
setSignValue("");
|
||||
setIsStamp(false);
|
||||
}}
|
||||
>
|
||||
×
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-[20px] h-full">
|
||||
{isDefaultSign ? (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
style={{
|
||||
border: "1.3px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginBottom: 6,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
className={
|
||||
isInitial ? "intialSignatureCanvas" : "signatureCanvas"
|
||||
}
|
||||
>
|
||||
<img
|
||||
alt="stamp img"
|
||||
className="w-full h-full object-contain bg-white"
|
||||
draggable="false"
|
||||
src={isInitial ? myInitial : defaultSign}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
) : isImageSelect || isStamp ? (
|
||||
!image ? (
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
style={{
|
||||
border: "1.3px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginBottom: 6,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
className={
|
||||
isInitial ? "intialSignatureCanvas" : "signatureCanvas"
|
||||
}
|
||||
onClick={() => imageRef.current.click()}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
onChange={onImageChange}
|
||||
className="filetype"
|
||||
accept="image/png,image/jpeg"
|
||||
ref={imageRef}
|
||||
hidden
|
||||
/>
|
||||
<i className="fa-light fa-cloud-upload-alt uploadImgLogo"></i>
|
||||
<div className="text-[10px]">Upload</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
style={{
|
||||
border: "1.3px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
marginBottom: 6,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
className={
|
||||
isInitial
|
||||
? "intialSignatureCanvas"
|
||||
: "signatureCanvas"
|
||||
}
|
||||
>
|
||||
<img
|
||||
alt="print img"
|
||||
ref={imageRef}
|
||||
src={image.src}
|
||||
draggable="false"
|
||||
className=" object-contain h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
) : isTab === "type" ? (
|
||||
<div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="mr-[5px] text-[12px]">
|
||||
{isInitial ? "Initials" : "Signature"}:
|
||||
</span>
|
||||
<input
|
||||
maxLength={isInitial ? 3 : 30}
|
||||
style={{ fontFamily: fontSelect, color: penColor }}
|
||||
type="text"
|
||||
className="ml-1 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-[20px]"
|
||||
placeholder="Your signature"
|
||||
value={signValue}
|
||||
onChange={(e) => {
|
||||
setSignValue(e.target.value);
|
||||
convertToImg(fontSelect, e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-[1px] border-[#d6d3d3] mt-[10px] ml-[5px]">
|
||||
{fontOptions.map((font, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
fontFamily: font.value,
|
||||
backgroundColor:
|
||||
fontSelect === font.value && "rgb(206 225 247)"
|
||||
}}
|
||||
onClick={() => {
|
||||
setFontSelect(font.value);
|
||||
convertToImg(font.value, signValue);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
padding: "5px 10px 5px 10px",
|
||||
fontSize: "20px",
|
||||
color: penColor
|
||||
}}
|
||||
>
|
||||
{signValue ? signValue : "Your signature"}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent convertToImg={convertToImg} />
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<SignatureCanvas
|
||||
ref={canvasRef}
|
||||
penColor={penColor}
|
||||
canvasProps={{
|
||||
className: isInitial
|
||||
? "intialSignatureCanvas"
|
||||
: "signatureCanvas",
|
||||
style: {
|
||||
border: "1.6px solid #007bff",
|
||||
borderRadius: "2px"
|
||||
}
|
||||
}}
|
||||
backgroundColor="rgb(255, 255, 255)"
|
||||
onEnd={() =>
|
||||
handleSignatureChange(canvasRef.current?.toDataURL())
|
||||
}
|
||||
dotSize={1}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent />
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignPad;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import "../../styles/signature.css";
|
||||
function Signedby({ pdfDetails }) {
|
||||
const getFirstLetter = (name) => {
|
||||
const firstLetter = name.charAt(0);
|
||||
return firstLetter;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="hidden md:block w-full h-full bg-base-100">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
Signed By
|
||||
</div>
|
||||
<div className="mt-[2px] bg-base-100">
|
||||
<div className="bg-[#93a3db] rounded-xl mx-1 flex flex-row items-center py-[10px]">
|
||||
<div className="bg-[#576081] flex w-[30px] h-[30px] rounded-full justify-center items-center mx-1">
|
||||
<span className="text-[12px] text-center font-bold text-white uppercase">
|
||||
{getFirstLetter(pdfDetails.ExtUserPtr.Name)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[12px] font-bold text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{pdfDetails.ExtUserPtr.Name}
|
||||
</span>
|
||||
<span className="text-[10px] font-medium text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{pdfDetails.ExtUserPtr.Email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Signedby;
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
import { darkenColor, getFirstLetter } from "../../constant/Utils";
|
||||
|
||||
function SignerListComponent(props) {
|
||||
const checkSignerBackColor = (obj) => {
|
||||
if (obj) {
|
||||
let data = "";
|
||||
if (obj?.Id) {
|
||||
data = props.signerPos.filter((data) => data.Id === obj.Id);
|
||||
} else {
|
||||
data = props.signerPos.filter(
|
||||
(data) => data.signerObjId === obj.objectId
|
||||
);
|
||||
}
|
||||
return data && data.length > 0 && data[0].blockColor;
|
||||
}
|
||||
};
|
||||
const checkUserNameColor = (obj) => {
|
||||
const getBackColor = checkSignerBackColor(obj);
|
||||
if (getBackColor) {
|
||||
const color = darkenColor(getBackColor, 0.4);
|
||||
return color;
|
||||
} else {
|
||||
return "#abd1d0";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rounded-xl mx-1 flex flex-row items-center py-[10px] mt-1"
|
||||
style={{ background: checkSignerBackColor(props.obj) }}
|
||||
>
|
||||
<div
|
||||
style={{ background: checkUserNameColor(props.obj) }}
|
||||
className="flex w-[30px] h-[30px] rounded-full justify-center items-center mx-1"
|
||||
>
|
||||
<span className="text-[12px] text-center font-bold text-black uppercase">
|
||||
{getFirstLetter(
|
||||
props.obj?.Name || props.obj?.email || props.obj?.Role
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[12px] font-bold text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{props.obj?.Name || props?.obj?.Role}
|
||||
</span>
|
||||
<span className="text-[10px] font-medium text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{props.obj?.Email || props.obj?.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignerListComponent;
|
||||
@@ -0,0 +1,88 @@
|
||||
import React from "react";
|
||||
import RecipientList from "./RecipientList";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
function SignerListPlace(props) {
|
||||
return (
|
||||
<div>
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
<span className="relative">
|
||||
{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-light fa-question rounded-full border-[1px] border-base-content text-[11px] py-[1px] px-[3px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
<Tooltip id="my-tooltip" className="z-[100]">
|
||||
<div className="max-w-[450px] 2xl:max-w-[500px] 2xl:text-[20px] p-[1px]">
|
||||
<p className="font-bold pb-[1px]">
|
||||
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="overflow-auto hide-scrollbar max-h-[180px]">
|
||||
<RecipientList {...props} />
|
||||
</div>
|
||||
<div className="mx-1">
|
||||
{props.handleAddSigner ? (
|
||||
<div
|
||||
role="button"
|
||||
data-tut="reactourAddbtn"
|
||||
disabled={props?.isMailSend ? true : false}
|
||||
className="op-btn op-btn-accent op-btn-outline w-full mt-[14px]"
|
||||
onClick={() => props.handleAddSigner()}
|
||||
>
|
||||
<i className="fa-light fa-plus"></i> Add role
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
role="button"
|
||||
data-tut="addRecipient"
|
||||
className="op-btn op-btn-accent op-btn-outline w-full mt-[14px]"
|
||||
disabled={props?.isMailSend ? true : false}
|
||||
onClick={() => props.setIsAddSigner(true)}
|
||||
>
|
||||
<i className="fa-light fa-plus"></i> Add recipients
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignerListPlace;
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
|
||||
function TextFontSetting(props) {
|
||||
return (
|
||||
<ModalUi
|
||||
headerColor={"#dc3545"}
|
||||
isOpen={props.isTextSetting}
|
||||
reduceWidth={"max-w-[350px]"}
|
||||
title={"Text field"}
|
||||
handleClose={() => props.setIsTextSetting(false)}
|
||||
>
|
||||
<div className="h-full p-[20px]">
|
||||
<div className="flex items-center">
|
||||
<span>Font size:</span>
|
||||
<select
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={
|
||||
props.fontSize ||
|
||||
props.currWidgetsDetails?.options?.fontSize ||
|
||||
"12"
|
||||
}
|
||||
onChange={(e) => props.setFontSize(e.target.value)}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<div className="flex flex-row gap-1 items-center ml-4">
|
||||
<span>color: </span>
|
||||
<select
|
||||
value={
|
||||
props.fontColor ||
|
||||
props.currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
onChange={(e) => props.setFontColor(e.target.value)}
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
>
|
||||
{fontColorArr.map((color, ind) => {
|
||||
return (
|
||||
<option value={color} key={ind}>
|
||||
{color}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<span
|
||||
style={{
|
||||
background:
|
||||
props.fontColor ||
|
||||
props.currWidgetsDetails?.options?.fontColor
|
||||
}}
|
||||
className="w-5 h-[19px] ml-1"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full mt-[15px] mb-2"></div>
|
||||
<button
|
||||
onClick={() => props.handleSaveFontSize()}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary mt-2"
|
||||
>
|
||||
save
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
|
||||
export default TextFontSetting;
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
import Loader from "../../primitives/Loader";
|
||||
|
||||
function VerifyEmail(props) {
|
||||
return (
|
||||
<dialog className="op-modal op-modal-open absolute z-[1999]">
|
||||
<div className="md:w-[40%] w-[80%] op-modal-box p-0 overflow-y-auto hide-scrollbar text-sm">
|
||||
<h3 className="font-bold text-lg pt-[15px] px-[20px] text-base-content">
|
||||
OTP verification
|
||||
</h3>
|
||||
{props.isVerifyModal ? (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
props.setIsVerifyModal(false);
|
||||
props.handleVerifyEmail(e);
|
||||
}}
|
||||
>
|
||||
<div className="px-6 py-3 text-base-content">
|
||||
<label className="mb-2">Enter OTP</label>
|
||||
<input
|
||||
required
|
||||
type="tel"
|
||||
pattern="[0-9]{4}"
|
||||
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
|
||||
placeholder="Enter OTP received over email"
|
||||
value={props.otp}
|
||||
onChange={(e) => props.setOtp(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="px-6 my-3">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
Verify
|
||||
</button>
|
||||
<button
|
||||
className="op-btn op-btn-secondary ml-2"
|
||||
onClick={(e) => props.handleResend(e)}
|
||||
>
|
||||
Resend
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : props.otpLoader ? (
|
||||
<div className="h-[150px] flex items-center justify-center">
|
||||
<Loader />
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-6 py-3 text-base-content">
|
||||
<p className="mb-2 text-sm">Please verify your email !</p>
|
||||
<div className="px-0 mt-3">
|
||||
<button
|
||||
className="op-btn op-btn-primary"
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
props.handleVerifyBtn();
|
||||
}}
|
||||
>
|
||||
Send OTP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default VerifyEmail;
|
||||
@@ -0,0 +1,387 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import RecipientList from "./RecipientList";
|
||||
import { useDrag } from "react-dnd";
|
||||
import WidgetList from "./WidgetList";
|
||||
import {
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
widgets
|
||||
} from "../../constant/Utils";
|
||||
function WidgetComponent({
|
||||
dragSignature,
|
||||
signRef,
|
||||
handleDivClick,
|
||||
handleMouseLeave,
|
||||
dragStamp,
|
||||
isSignYourself,
|
||||
addPositionOfSignature,
|
||||
signersdata,
|
||||
isSelectListId,
|
||||
setSignerObjId,
|
||||
setIsSelectId,
|
||||
setContractName,
|
||||
isSigners,
|
||||
dataTut,
|
||||
isMailSend,
|
||||
handleAddSigner,
|
||||
setUniqueId,
|
||||
setRoleName,
|
||||
handleDeleteUser,
|
||||
signerPos,
|
||||
handleRoleChange,
|
||||
handleOnBlur,
|
||||
title,
|
||||
setSignersData,
|
||||
sendInOrder,
|
||||
isTemplateFlow,
|
||||
setBlockColor,
|
||||
setIsAddSigner,
|
||||
uniqueId
|
||||
}) {
|
||||
const [isSignersModal, setIsSignersModal] = useState(false);
|
||||
const [, dropdown] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 5,
|
||||
text: "dropdown"
|
||||
},
|
||||
|
||||
collect: (monitor) => ({
|
||||
isDragDropdown: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, checkbox] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 6,
|
||||
text: "checkbox"
|
||||
},
|
||||
|
||||
collect: (monitor) => ({
|
||||
isDragCheck: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, textInput] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 7,
|
||||
text: textInputWidget
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isDragTextInput: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, initials] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 8,
|
||||
text: "initials"
|
||||
},
|
||||
|
||||
collect: (monitor) => ({
|
||||
isDragInitial: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, name] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 9,
|
||||
text: "name"
|
||||
},
|
||||
|
||||
collect: (monitor) => ({
|
||||
isDragName: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, company] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 10,
|
||||
text: "company"
|
||||
},
|
||||
|
||||
collect: (monitor) => ({
|
||||
isDragCompany: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, jobTitle] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 11,
|
||||
text: "job title"
|
||||
},
|
||||
|
||||
collect: (monitor) => ({
|
||||
isDragJobtitle: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, date] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 12,
|
||||
text: "date"
|
||||
},
|
||||
|
||||
collect: (monitor) => ({
|
||||
isDragDate: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, image] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 13,
|
||||
text: "image"
|
||||
},
|
||||
|
||||
collect: (monitor) => ({
|
||||
isDragImage: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, email] = useDrag({
|
||||
type: "BOX",
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 14,
|
||||
text: "email"
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isDragEmail: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, radioButton] = useDrag({
|
||||
type: "BOX",
|
||||
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 15,
|
||||
text: radioButtonWidget
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isDragRadiotton: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const [, text] = useDrag({
|
||||
type: "BOX",
|
||||
|
||||
item: {
|
||||
type: "BOX",
|
||||
id: 16,
|
||||
text: textWidget
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isDragText: !!monitor.isDragging()
|
||||
})
|
||||
});
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const scrollContainerRef = useRef(null);
|
||||
const [widget, setWidget] = useState([]);
|
||||
const color = [
|
||||
"#93a3db",
|
||||
"#e6c3db",
|
||||
"#c0e3bc",
|
||||
"#bce3db",
|
||||
"#b8ccdb",
|
||||
"#ceb8db",
|
||||
"#ffccff",
|
||||
"#99ffcc",
|
||||
"#cc99ff",
|
||||
"#ffcc99",
|
||||
"#66ccff",
|
||||
"#ffffcc"
|
||||
];
|
||||
const handleModal = () => {
|
||||
setIsSignersModal(!isSignersModal);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const widgetRef = [
|
||||
dragSignature,
|
||||
dragStamp,
|
||||
initials,
|
||||
name,
|
||||
jobTitle,
|
||||
company,
|
||||
date,
|
||||
text,
|
||||
textInput,
|
||||
checkbox,
|
||||
dropdown,
|
||||
radioButton,
|
||||
image,
|
||||
email
|
||||
];
|
||||
const getWidgetArray = widgets;
|
||||
const newUpdateSigner = getWidgetArray.map((obj, ind) => {
|
||||
return { ...obj, ref: widgetRef[ind] };
|
||||
});
|
||||
|
||||
setWidget(newUpdateSigner);
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const filterWidgets = widget.filter(
|
||||
(data) =>
|
||||
data.type !== "dropdown" &&
|
||||
data.type !== radioButtonWidget &&
|
||||
data.type !== textInputWidget
|
||||
);
|
||||
const textWidgetData = widget.filter((data) => data.type !== textWidget);
|
||||
const updateWidgets = isSignYourself
|
||||
? filterWidgets
|
||||
: isTemplateFlow
|
||||
? textWidgetData
|
||||
: widget;
|
||||
|
||||
const handleSelectRecipient = () => {
|
||||
if (
|
||||
signersdata[isSelectListId]?.Email ||
|
||||
signersdata[isSelectListId]?.Role
|
||||
) {
|
||||
const userData =
|
||||
signersdata[isSelectListId]?.Name || signersdata[isSelectListId]?.Role;
|
||||
const name =
|
||||
userData?.length > 20 ? `${userData.slice(0, 20)}...` : userData;
|
||||
return name;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
!isMailSend && (
|
||||
<div id="navbar" className="fixed z-[99] bottom-0 right-0 w-full">
|
||||
{isSigners && (
|
||||
<div className="w-full mb-[5px] flex justify-center items-center gap-1">
|
||||
<div className="w-full ml-[5px]" onClick={() => handleModal()}>
|
||||
<select
|
||||
data-tut="recipientArea"
|
||||
className="w-full op-select op-select-bordered pointer-events-none"
|
||||
value={handleSelectRecipient()}
|
||||
style={{
|
||||
backgroundColor: isSelectListId
|
||||
? color[isSelectListId % color.length]
|
||||
: color[0]
|
||||
}}
|
||||
>
|
||||
<option value={handleSelectRecipient()}>
|
||||
{handleSelectRecipient()}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="w-[18%]">
|
||||
{handleAddSigner ? (
|
||||
<button
|
||||
data-tut="reactourAddbtn"
|
||||
onClick={() => handleAddSigner()}
|
||||
className="op-btn op-btn-accent"
|
||||
>
|
||||
<i className="fa-light fa-plus "></i>
|
||||
</button>
|
||||
) : (
|
||||
setIsAddSigner && (
|
||||
<button
|
||||
data-tut="addRecipient"
|
||||
onClick={() => setIsAddSigner(true)}
|
||||
className="op-btn op-btn-accent"
|
||||
>
|
||||
<i className="fa-light fa-plus"></i>
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
data-tut="addWidgets"
|
||||
ref={scrollContainerRef}
|
||||
className="bg-base-100 border-[2px] border-t-primary"
|
||||
>
|
||||
<div className="flex whitespace-nowrap overflow-x-scroll pt-[10px] pb-[5px] pr-[5px]">
|
||||
<WidgetList
|
||||
updateWidgets={updateWidgets}
|
||||
handleDivClick={handleDivClick}
|
||||
handleMouseLeave={handleMouseLeave}
|
||||
signRef={signRef}
|
||||
marginLeft={5}
|
||||
addPositionOfSignature={addPositionOfSignature}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
data-tut={dataTut}
|
||||
className={`${
|
||||
isMailSend ? "bg-opacity-50 pointer-events-none" : ""
|
||||
} hidden md:block h-full bg-base-100`}
|
||||
>
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
<span>Fields</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="p-[15px] flex flex-col pt-4 2xl:m-5"
|
||||
data-tut="addWidgets"
|
||||
>
|
||||
<WidgetList
|
||||
updateWidgets={updateWidgets}
|
||||
handleDivClick={handleDivClick}
|
||||
handleMouseLeave={handleMouseLeave}
|
||||
signRef={signRef}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isSignersModal && (
|
||||
<ModalUi
|
||||
title={title ? title : "Recipients"}
|
||||
isOpen={isSignersModal}
|
||||
handleClose={handleModal}
|
||||
>
|
||||
{signersdata.length > 0 ? (
|
||||
<div className="max-h-[600px] overflow-auto pb-1">
|
||||
<RecipientList
|
||||
signerPos={signerPos}
|
||||
signersdata={signersdata}
|
||||
isSelectListId={isSelectListId}
|
||||
setSignerObjId={setSignerObjId}
|
||||
setIsSelectId={setIsSelectId}
|
||||
setContractName={setContractName}
|
||||
setUniqueId={setUniqueId}
|
||||
setRoleName={setRoleName}
|
||||
handleDeleteUser={handleDeleteUser}
|
||||
handleRoleChange={handleRoleChange}
|
||||
handleOnBlur={handleOnBlur}
|
||||
handleModal={handleModal}
|
||||
sendInOrder={sendInOrder}
|
||||
setSignersData={setSignersData}
|
||||
setBlockColor={setBlockColor}
|
||||
uniqueId={uniqueId}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className=" p-[20px] text-[15px] font-medium text-center">
|
||||
Please add a {title ? title : "recipients"}
|
||||
</div>
|
||||
)}
|
||||
</ModalUi>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default WidgetComponent;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { getWidgetType } from "../../constant/Utils";
|
||||
|
||||
function WidgetList(props) {
|
||||
return props.updateWidgets.map((item, ind) => {
|
||||
return (
|
||||
<div className="2xl:p-3 mb-[5px]" key={ind}>
|
||||
<div
|
||||
className="select-none mx-[2px] md:mx-0 cursor-all-scroll"
|
||||
onClick={() => {
|
||||
props.addPositionOfSignature &&
|
||||
props.addPositionOfSignature("onclick", item);
|
||||
}}
|
||||
ref={(element) => {
|
||||
if (!props.isMobile) {
|
||||
item.ref(element);
|
||||
if (element) {
|
||||
if (props?.signRef) {
|
||||
props.signRef.current = element;
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
onMouseMove={props?.handleDivClick}
|
||||
onMouseDown={() => props?.handleMouseLeave()}
|
||||
onTouchStart={props?.handleDivClick}
|
||||
>
|
||||
{item.ref && getWidgetType(item)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WidgetList;
|
||||
@@ -0,0 +1,326 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import "../../styles/AddUser.css";
|
||||
import RegexParser from "regex-parser";
|
||||
import { textInputWidget, textWidget } from "../../constant/Utils";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
import Tooltip from "../../primitives/Tooltip";
|
||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
|
||||
const WidgetNameModal = (props) => {
|
||||
const [formdata, setFormdata] = useState({
|
||||
name: "",
|
||||
defaultValue: "",
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: ""
|
||||
});
|
||||
const [isValid, setIsValid] = useState(true);
|
||||
const statusArr = ["Required", "Optional"];
|
||||
const inputOpt = ["text", "email", "number"];
|
||||
|
||||
useEffect(() => {
|
||||
if (props.defaultdata) {
|
||||
setFormdata({
|
||||
name: props.defaultdata?.options?.name || "",
|
||||
defaultValue: props.defaultdata?.options?.defaultValue || "",
|
||||
status: props.defaultdata?.options?.status || "required",
|
||||
hint: props.defaultdata?.options?.hint || "",
|
||||
textvalidate:
|
||||
props.defaultdata?.options?.validation?.type === "regex"
|
||||
? props.defaultdata?.options?.validation?.pattern
|
||||
: props.defaultdata?.options?.validation?.type || ""
|
||||
});
|
||||
} else {
|
||||
setFormdata({
|
||||
...formdata,
|
||||
name: props.defaultdata?.options?.name || ""
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [props.defaultdata]);
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (props.handleData) {
|
||||
props.handleData(formdata);
|
||||
setFormdata({
|
||||
name: "",
|
||||
defaultValue: "",
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: ""
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleChange = (e) => {
|
||||
if (e) {
|
||||
setFormdata({ ...formdata, [e.target.name]: e.target.value });
|
||||
} else {
|
||||
setFormdata({ ...formdata, textvalidate: "" });
|
||||
}
|
||||
};
|
||||
|
||||
const handledefaultChange = (e) => {
|
||||
if (formdata.textvalidate) {
|
||||
const regexObject = RegexParser(handleValidation(formdata.textvalidate));
|
||||
const isValidate = regexObject?.test(e.target.value);
|
||||
setIsValid(isValidate);
|
||||
} else {
|
||||
setIsValid(true);
|
||||
}
|
||||
setFormdata({ ...formdata, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
function handleValidation(type) {
|
||||
switch (type) {
|
||||
case "email":
|
||||
return "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/";
|
||||
case "number":
|
||||
return "/^\\d+$/";
|
||||
case "text":
|
||||
return "/^[a-zA-Zs]+$/";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
function handleBlurRegex() {
|
||||
if (!formdata.textvalidate) {
|
||||
setFormdata({ ...formdata, textvalidate: "" });
|
||||
} else {
|
||||
if (formdata.defaultValue) {
|
||||
const regexObject = RegexParser(
|
||||
handleValidation(formdata.textvalidate)
|
||||
);
|
||||
const isValidate = regexObject?.test(formdata.defaultValue);
|
||||
if (isValidate === false) {
|
||||
setFormdata({ ...formdata, defaultValue: "" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ModalUi
|
||||
isOpen={props.isOpen}
|
||||
handleClose={props.handleClose && props.handleClose}
|
||||
title={"Widget info"}
|
||||
>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className={`${
|
||||
props.defaultdata?.type === textInputWidget ? "pt-0" : ""
|
||||
} p-[20px] text-base-content`}
|
||||
>
|
||||
<div className="mb-[0.75rem] text-[13px]">
|
||||
<label htmlFor="name">
|
||||
Name
|
||||
<span className="text-[red]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
name="name"
|
||||
value={formdata.name}
|
||||
onChange={(e) => handleChange(e)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{props.defaultdata?.type === textInputWidget && (
|
||||
<>
|
||||
{isEnableSubscription && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<div className="flex items-center gap-1">
|
||||
<label
|
||||
htmlFor="textvalidate"
|
||||
className={`${
|
||||
!props.isSubscribe && isEnableSubscription
|
||||
? "bg-opacity-50 pointer-events-none"
|
||||
: ""
|
||||
} text-[13px]`}
|
||||
>
|
||||
Validation
|
||||
</label>
|
||||
<Tooltip
|
||||
url={"https://www.w3schools.com/jsref/jsref_obj_regexp.asp"}
|
||||
/>
|
||||
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
!props.isSubscribe && isEnableSubscription
|
||||
? "bg-opacity-50 pointer-events-none"
|
||||
: ""
|
||||
} flex flex-row gap-[10px] mb-[0.5rem]`}
|
||||
>
|
||||
<div className="w-full relative group">
|
||||
<input
|
||||
className="z-20 relative op-input op-input-bordered rounded-r-none op-input-sm focus:outline-none group-hover:border-base-content w-[87%] md:w-[92%] text-xs"
|
||||
name="textvalidate"
|
||||
placeholder="Enter custom regular expression"
|
||||
value={formdata.textvalidate}
|
||||
onChange={(e) => handleChange(e)}
|
||||
// onBlur={() => handleBlurRegex()}
|
||||
/>
|
||||
<select
|
||||
className="validationlist op-input op-input-bordered op-input-sm focus:outline-none group-hover:border-base-content w-full text-xs"
|
||||
name="textvalidate"
|
||||
value={formdata.textvalidate}
|
||||
onChange={(e) => handleChange(e)}
|
||||
onBlur={() => handleBlurRegex()}
|
||||
>
|
||||
<option
|
||||
disabled={formdata?.textvalidate}
|
||||
className="text-[13px]"
|
||||
>
|
||||
Select...
|
||||
</option>
|
||||
{inputOpt.map((data, ind) => {
|
||||
return (
|
||||
<option
|
||||
className="text-[13px]"
|
||||
key={ind}
|
||||
value={data}
|
||||
>
|
||||
{data}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
Default value
|
||||
</label>
|
||||
<input
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
name="defaultValue"
|
||||
value={formdata.defaultValue}
|
||||
onChange={(e) => handledefaultChange(e)}
|
||||
autoComplete="off"
|
||||
onBlur={() => {
|
||||
if (isValid === false) {
|
||||
setFormdata({ ...formdata, defaultValue: "" });
|
||||
setIsValid(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{isValid === false && (
|
||||
<div
|
||||
className="warning defaultvalueWarning"
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-exclamation-circle text-[15px]"
|
||||
style={{ color: "#fab005" }}
|
||||
></i>
|
||||
invalid default value
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="mb-[0.75rem]">
|
||||
<div className="flex flex-row gap-[10px] mb-[0.5rem]">
|
||||
{statusArr.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="flex flex-row gap-[5px] items-center">
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
name="status"
|
||||
onChange={() =>
|
||||
setFormdata({ ...formdata, status: data.toLowerCase() })
|
||||
}
|
||||
checked={
|
||||
formdata.status.toLowerCase() === data.toLowerCase()
|
||||
}
|
||||
/>
|
||||
<div className="text-[13px] font-medium">{data}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{props.defaultdata?.type === textInputWidget && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="hint" className="text-[13px]">
|
||||
Hint
|
||||
</label>
|
||||
<input
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
name="hint"
|
||||
value={formdata.hint}
|
||||
onChange={(e) => handleChange(e)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
].includes(props.defaultdata?.type) && (
|
||||
<div className="flex items-center mb-[0.75rem]">
|
||||
<span>Font size:</span>
|
||||
<select
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={
|
||||
props.fontSize || props.defaultdata?.options?.fontSize || "12"
|
||||
}
|
||||
onChange={(e) => props.setFontSize(e.target.value)}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<div className="flex flex-row gap-1 items-center ml-4">
|
||||
<span>color: </span>
|
||||
<select
|
||||
value={
|
||||
props.fontColor ||
|
||||
props.defaultdata?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
onChange={(e) => props.setFontColor(e.target.value)}
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
>
|
||||
{fontColorArr.map((color, ind) => {
|
||||
return (
|
||||
<option value={color} key={ind}>
|
||||
{color}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<span
|
||||
style={{
|
||||
background:
|
||||
props.fontColor ||
|
||||
props.defaultdata?.options?.fontColor ||
|
||||
"black"
|
||||
}}
|
||||
className="w-5 h-[19px] ml-1"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="h-[1px] w-full mb-[16px] bg-[#b7b3b3]"></div>
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
</ModalUi>
|
||||
);
|
||||
};
|
||||
|
||||
export default WidgetNameModal;
|
||||
@@ -0,0 +1,166 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Alert from "../../../primitives/Alert";
|
||||
import Loader from "../../../primitives/Loader";
|
||||
|
||||
const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
|
||||
const folderPtr = {
|
||||
__type: "Pointer",
|
||||
className: folderCls,
|
||||
objectId: parentFolderId
|
||||
};
|
||||
const [name, setName] = useState("");
|
||||
const [folderList, setFolderList] = useState([]);
|
||||
const [isAlert, setIsAlert] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [selectedParent, setSelectedParent] = useState();
|
||||
const [alert, setAlert] = useState({ type: "info", message: "" });
|
||||
useEffect(() => {
|
||||
fetchFolder();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const fetchFolder = async () => {
|
||||
try {
|
||||
const FolderQuery = new Parse.Query(folderCls);
|
||||
if (parentFolderId) {
|
||||
FolderQuery.equalTo("Folder", folderPtr);
|
||||
FolderQuery.equalTo("Type", "Folder");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
FolderQuery.equalTo("CreatedBy", Parse.User.current());
|
||||
} else {
|
||||
FolderQuery.doesNotExist("Folder");
|
||||
FolderQuery.equalTo("Type", "Folder");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
FolderQuery.equalTo("CreatedBy", Parse.User.current());
|
||||
}
|
||||
|
||||
const res = await FolderQuery.find();
|
||||
if (res) {
|
||||
const result = JSON.parse(JSON.stringify(res));
|
||||
if (result) {
|
||||
setFolderList(result);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Err ", error);
|
||||
}
|
||||
};
|
||||
const handleCreateFolder = async (event) => {
|
||||
event.preventDefault();
|
||||
handleLoader(true);
|
||||
if (name) {
|
||||
const currentUser = Parse.User.current();
|
||||
const exsitQuery = new Parse.Query(folderCls);
|
||||
exsitQuery.equalTo("Name", name);
|
||||
exsitQuery.equalTo("Type", "Folder");
|
||||
exsitQuery.notEqualTo("IsArchive", true);
|
||||
if (parentFolderId) {
|
||||
exsitQuery.equalTo("Folder", folderPtr);
|
||||
}
|
||||
const templExist = await exsitQuery.first();
|
||||
if (templExist) {
|
||||
setAlert({ type: "danger", message: "Folder already exist!" });
|
||||
setIsAlert(true);
|
||||
setTimeout(() => {
|
||||
setIsAlert(false);
|
||||
}, 1000);
|
||||
} else {
|
||||
const template = new Parse.Object(folderCls);
|
||||
template.set("Name", name);
|
||||
template.set("Type", "Folder");
|
||||
|
||||
if (selectedParent) {
|
||||
template.set("Folder", {
|
||||
__type: "Pointer",
|
||||
className: folderCls,
|
||||
objectId: selectedParent
|
||||
});
|
||||
} else if (parentFolderId) {
|
||||
template.set("Folder", folderPtr);
|
||||
}
|
||||
template.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
|
||||
const res = await template.save();
|
||||
if (res) {
|
||||
handleLoader(false);
|
||||
setAlert({
|
||||
type: "success",
|
||||
message: "Folder created successfully!"
|
||||
});
|
||||
setIsAlert(true);
|
||||
setTimeout(() => {
|
||||
setIsAlert(false);
|
||||
}, 1000);
|
||||
if (onSuccess) {
|
||||
onSuccess(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleLoader(false);
|
||||
setAlert({ type: "info", message: "Please fill folder name" });
|
||||
setIsAlert(true);
|
||||
setTimeout(() => {
|
||||
setIsAlert(false);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
const handleOptions = (e) => {
|
||||
setSelectedParent(e.target.value);
|
||||
};
|
||||
const handleLoader = (status) => {
|
||||
setIsLoader(status);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{isAlert && <Alert type={alert.type}>{alert.message}</Alert>}
|
||||
<div id="createFolder" className="relative">
|
||||
{isLoader && (
|
||||
<div className="absolute h-full w-full flex justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<h1 className="text-base font-semibold mt-[0.4rem]">Create Folder</h1>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
Name<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">Parent Folder</label>
|
||||
<select
|
||||
value={selectedParent}
|
||||
onChange={handleOptions}
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
>
|
||||
<option>select</option>
|
||||
{folderList.length > 0 &&
|
||||
folderList.map((x) => (
|
||||
<option key={x.objectId} value={x.objectId}>
|
||||
{x.Name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
onClick={handleCreateFolder}
|
||||
disabled={isLoader}
|
||||
className="op-btn op-btn-primary op-btn-sm mt-3"
|
||||
>
|
||||
<i className="fa-light fa-plus"></i>
|
||||
<span>Create</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateFolder;
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import { useDropScript } from "../../hook/useScript";
|
||||
import { useDropScript } from "../../../hook/useScript";
|
||||
|
||||
const DROPBOX_APP_KEY = process.env.REACT_APP_DROPBOX_API_KEY; // App key
|
||||
const DROPBOX_SDK_URL = "https://www.dropbox.com/static/api/2/dropins.js";
|
||||
@@ -41,16 +41,20 @@ export default function DropboxChooser({ children, onSuccess, onCancel }) {
|
||||
[onSuccess, onCancel]
|
||||
);
|
||||
|
||||
const handleChoose = useCallback(() => {
|
||||
if (window.Dropbox) {
|
||||
window.Dropbox.choose(options);
|
||||
}
|
||||
}, [options]);
|
||||
const handleChoose = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
if (window.Dropbox) {
|
||||
window.Dropbox.choose(options);
|
||||
}
|
||||
},
|
||||
[options]
|
||||
);
|
||||
|
||||
return (
|
||||
<div onClick={handleChoose}>
|
||||
{children || (
|
||||
<button className="px-2 py-[2px] text-2xl rounded border-[1px] text-blue-400 border-gray-300 w-full">
|
||||
<button className="cursor-pointer op-input op-input-bordered focus:outline-none hover:border-base-content max-h-[35px] max-w-[45px] flex justify-center items-center">
|
||||
<i className="fa-brands fa-dropbox"></i>
|
||||
</button>
|
||||
)}
|
||||
@@ -0,0 +1,267 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import CreateFolder from "./CreateFolder";
|
||||
import ModalUi from "../../../primitives/ModalUi";
|
||||
|
||||
const FolderModal = (props) => {
|
||||
const [clickFolder, setClickFolder] = useState("");
|
||||
const [folderList, setFolderList] = useState([]);
|
||||
const [tabList, setTabList] = useState([]);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [isAdd, setIsAdd] = useState(false);
|
||||
// below useEffect is called when user open popup
|
||||
useEffect(() => {
|
||||
if (props.isOpenModal) {
|
||||
fetchFolder();
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [props.isOpenModal]);
|
||||
|
||||
// `fetchFolder` is used to fetch of folder list created by user on basis of folderPtr or without folderPtr
|
||||
const fetchFolder = async (folderPtr) => {
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const FolderQuery = new Parse.Query(props.folderCls);
|
||||
if (folderPtr) {
|
||||
FolderQuery.equalTo("Folder", folderPtr);
|
||||
FolderQuery.descending("Type");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
FolderQuery.equalTo("CreatedBy", Parse.User.current());
|
||||
} else {
|
||||
FolderQuery.doesNotExist("Folder");
|
||||
FolderQuery.descending("Type");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
FolderQuery.equalTo("CreatedBy", Parse.User.current());
|
||||
}
|
||||
|
||||
const res = await FolderQuery.find();
|
||||
if (res) {
|
||||
const result = JSON.parse(JSON.stringify(res));
|
||||
if (result) {
|
||||
setFolderList(result);
|
||||
setIsLoader(false);
|
||||
}
|
||||
setIsLoader(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
// `handleSelect` is used to save pointer of folder selected by user and it's path in state
|
||||
const handleSelect = (item) => {
|
||||
setFolderList([]);
|
||||
setClickFolder({ ObjectId: item.objectId, Name: item.Name });
|
||||
if (tabList.length > 0) {
|
||||
const tab = tabList.some((x) => x.objectId === item.objectId);
|
||||
if (!tab) {
|
||||
setTabList((tabs) => [...tabs, item]);
|
||||
const folderPtr = {
|
||||
__type: "Pointer",
|
||||
className: props.folderCls,
|
||||
objectId: item.objectId
|
||||
};
|
||||
fetchFolder(folderPtr);
|
||||
}
|
||||
} else {
|
||||
setTabList((tabs) => [...tabs, item]);
|
||||
const folderPtr = {
|
||||
__type: "Pointer",
|
||||
className: props.folderCls,
|
||||
objectId: item.objectId
|
||||
};
|
||||
|
||||
fetchFolder(folderPtr);
|
||||
}
|
||||
};
|
||||
|
||||
// `handleSubmit` is used to pass folderPtr to parent component
|
||||
const handleSubmit = () => {
|
||||
let url = "OpenSign™ Drive";
|
||||
tabList.forEach((t) => {
|
||||
url = url + " / " + t.Name;
|
||||
});
|
||||
if (props.onSuccess) {
|
||||
props.onSuccess(clickFolder);
|
||||
}
|
||||
// SetIsOpen(false);
|
||||
props.setIsOpenMoveModal(false);
|
||||
};
|
||||
|
||||
// `handleCancel` is used to clear list of folder, close popup and folderUrl
|
||||
const handleCancel = () => {
|
||||
// SetIsOpen(false);
|
||||
props.setIsOpenMoveModal(false);
|
||||
setClickFolder({});
|
||||
setFolderList([]);
|
||||
setTabList([]);
|
||||
};
|
||||
|
||||
// `handleCancel` is call when user click on folder name from path/tab in popup
|
||||
const removeTabListItem = async (e, i) => {
|
||||
e.preventDefault();
|
||||
// setEditable(false);
|
||||
if (!isAdd) {
|
||||
setIsLoader(true);
|
||||
let folderPtr;
|
||||
if (i) {
|
||||
setFolderList([]);
|
||||
let list = tabList.filter((itm, j) => {
|
||||
if (j <= i) {
|
||||
return itm;
|
||||
}
|
||||
});
|
||||
let _len = list.length - 1;
|
||||
folderPtr = {
|
||||
__type: "Pointer",
|
||||
className: props.folderCls,
|
||||
objectId: list[_len].objectId
|
||||
};
|
||||
setTabList(list);
|
||||
} else {
|
||||
setClickFolder({});
|
||||
setFolderList([]);
|
||||
setTabList([]);
|
||||
}
|
||||
fetchFolder(folderPtr);
|
||||
}
|
||||
};
|
||||
// `handleCreate` is used to open folder creation form in popup
|
||||
const handleCreate = () => {
|
||||
setIsAdd(!isAdd);
|
||||
};
|
||||
// `handleAddFolder` is call when user folder created successfully and it fetch folder list on the basis of folderPtr or without folderPtr
|
||||
const handleAddFolder = (newFolder) => {
|
||||
props.setPdfData((prev) => [...prev, newFolder?.toJSON()]);
|
||||
if (clickFolder && clickFolder.ObjectId) {
|
||||
fetchFolder({
|
||||
__type: "Pointer",
|
||||
className: props.folderCls,
|
||||
objectId: clickFolder.ObjectId
|
||||
});
|
||||
} else {
|
||||
fetchFolder();
|
||||
}
|
||||
handleCreate();
|
||||
};
|
||||
return (
|
||||
<div className="text-xs mt-2">
|
||||
<ModalUi
|
||||
title={"Select Folder"}
|
||||
isOpen={props.isOpenModal}
|
||||
handleClose={handleCancel}
|
||||
>
|
||||
<div className="w-full min-w-[300px] md:min-w-[500px] max-w-[500px] px-3">
|
||||
<div className="pt-1 text-[#ac4848] text-[14px] font-[500]">
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
title="OpenSign™ Drive"
|
||||
onClick={(e) => removeTabListItem(e)}
|
||||
>
|
||||
OpenSign™ Drive /{" "}
|
||||
</span>
|
||||
{tabList &&
|
||||
tabList.map((tab, i) => (
|
||||
<React.Fragment key={`${tab.objectId}-${i}`}>
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
title={tab.Name}
|
||||
onClick={(e) => removeTabListItem(e, i)}
|
||||
>
|
||||
{tab.Name}
|
||||
</span>
|
||||
{" / "}
|
||||
</React.Fragment>
|
||||
))}
|
||||
<hr className="bg-[#8a8a8a] mt-[0.750rem]" />
|
||||
</div>
|
||||
<div className="mt-2 mb-3">
|
||||
<div className="max-h-[210px] overflow-auto">
|
||||
{!isAdd && folderList.length > 0
|
||||
? folderList.map((folder) => (
|
||||
<div
|
||||
key={folder.objectId}
|
||||
className={`${
|
||||
folder.Type === "Folder"
|
||||
? "cursor-pointer"
|
||||
: "cursor-default"
|
||||
} border-b-[1px] border-[#8a8a8a] py-2 mb-0.5"`}
|
||||
onClick={() =>
|
||||
folder.Type === "Folder" && handleSelect(folder)
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{folder.Type === "Folder" ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
className="w-[1.4rem] h-[1.4rem] fill-current op-text-secondary"
|
||||
>
|
||||
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 384 512"
|
||||
className="w-[1.4rem] h-[1.4rem] fill-current op-text-primary"
|
||||
>
|
||||
<path d="M374.629 150.627L233.371 9.373C227.371 3.371 219.23 0 210.746 0H64C28.652 0 0 28.652 0 64V448C0 483.345 28.652 512 64 512H320C355.348 512 384 483.345 384 448V173.254C384 164.767 380.629 156.629 374.629 150.627ZM224 22.629L361.375 160H248C234.781 160 224 149.234 224 136V22.629ZM368 448C368 474.467 346.469 496 320 496H64C37.531 496 16 474.467 16 448V64C16 37.533 37.531 16 64 16H208V136C208 158.062 225.938 176 248 176H368V448ZM96 264C96 268.406 99.594 272 104 272H280C284.406 272 288 268.406 288 264S284.406 256 280 256H104C99.594 256 96 259.594 96 264ZM280 320H104C99.594 320 96 323.594 96 328S99.594 336 104 336H280C284.406 336 288 332.406 288 328S284.406 320 280 320ZM280 384H104C99.594 384 96 387.594 96 392S99.594 400 104 400H280C284.406 400 288 396.406 288 392S284.406 384 280 384Z" />
|
||||
</svg>
|
||||
)}
|
||||
<span className="font-semibold">{folder.Name}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
: !isLoader && (
|
||||
<div className="text-base-content text-center my-2">
|
||||
No data found
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isAdd && (
|
||||
<CreateFolder
|
||||
parentFolderId={clickFolder && clickFolder.ObjectId}
|
||||
folderCls={props.folderCls}
|
||||
onSuccess={handleAddFolder}
|
||||
/>
|
||||
)}
|
||||
{isLoader && (
|
||||
<div className="flex justify-center my-4">
|
||||
<i className="fa-light fa-spinner fa-spin-pulse text-[30px]"></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="flex justify-between items-center py-[.75rem] px-[1.25rem]">
|
||||
<div
|
||||
className="op-btn op-btn-seconday op-btn-sm"
|
||||
title="Save Here"
|
||||
onClick={handleCreate}
|
||||
>
|
||||
{isAdd ? (
|
||||
<>
|
||||
<i className="fa-light fa-arrow-left" aria-hidden="true"></i>
|
||||
<span className="text-xs">Back</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<i className="fa-light fa-square-plus" aria-hidden="true"></i>
|
||||
<span className="">Add folder</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="op-btn op-btn-primary op-btn-sm"
|
||||
title="Save Here"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<i className="fa-light fa-save" aria-hidden="true"></i>Save here
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FolderModal;
|
||||
@@ -0,0 +1,336 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import CreateFolder from "./CreateFolder";
|
||||
import ModalUi from "../../../primitives/ModalUi";
|
||||
import Tooltip from "../../../primitives/Tooltip";
|
||||
|
||||
const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
|
||||
const [isOpen, SetIsOpen] = useState(false);
|
||||
const [clickFolder, setClickFolder] = useState("");
|
||||
const [selectFolder, setSelectedFolder] = useState({});
|
||||
const [folderList, setFolderList] = useState([]);
|
||||
const [tabList, setTabList] = useState([]);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [folderPath, setFolderPath] = useState("");
|
||||
const [isAdd, setIsAdd] = useState(false);
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setIsAdd(false);
|
||||
setClickFolder({});
|
||||
setFolderList([]);
|
||||
setTabList([]);
|
||||
fetchFolder();
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [isOpen]);
|
||||
// below useEffect is used to reset folder selection if user pass isReset = true from parent component
|
||||
useEffect(() => {
|
||||
if (isReset == true) {
|
||||
handleReset();
|
||||
}
|
||||
}, [isReset]);
|
||||
const handleReset = () => {
|
||||
setFolderPath({});
|
||||
setSelectedFolder("");
|
||||
setClickFolder("");
|
||||
setIsAdd(false);
|
||||
setFolderList([]);
|
||||
setTabList([]);
|
||||
};
|
||||
// `fetchFolder` is used to fetch of folder list created by user on basis of folderPtr or without folderPtr
|
||||
const fetchFolder = async (folderPtr) => {
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const FolderQuery = new Parse.Query(folderCls);
|
||||
if (folderPtr) {
|
||||
FolderQuery.equalTo("Folder", folderPtr);
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
FolderQuery.descending("Type");
|
||||
FolderQuery.equalTo("CreatedBy", Parse.User.current());
|
||||
} else {
|
||||
FolderQuery.doesNotExist("Folder");
|
||||
FolderQuery.notEqualTo("IsArchive", true);
|
||||
FolderQuery.descending("Type");
|
||||
FolderQuery.equalTo("CreatedBy", Parse.User.current());
|
||||
}
|
||||
|
||||
const res = await FolderQuery.find();
|
||||
if (res) {
|
||||
const result = JSON.parse(JSON.stringify(res));
|
||||
if (result) {
|
||||
setFolderList(result);
|
||||
setIsLoader(false);
|
||||
}
|
||||
setIsLoader(false);
|
||||
}
|
||||
} catch (error) {
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
// `handleSelect` is used to save pointer of folder selected by user and it's path in state
|
||||
const handleSelect = (item) => {
|
||||
setFolderList([]);
|
||||
setClickFolder({ ObjectId: item.objectId, Name: item.Name });
|
||||
if (tabList.length > 0) {
|
||||
const tab = tabList.some((x) => x.objectId === item.objectId);
|
||||
if (!tab) {
|
||||
setTabList((tabs) => [...tabs, item]);
|
||||
const folderPtr = {
|
||||
__type: "Pointer",
|
||||
className: folderCls,
|
||||
objectId: item.objectId
|
||||
};
|
||||
fetchFolder(folderPtr);
|
||||
}
|
||||
} else {
|
||||
setTabList((tabs) => [...tabs, item]);
|
||||
const folderPtr = {
|
||||
__type: "Pointer",
|
||||
className: folderCls,
|
||||
objectId: item.objectId
|
||||
};
|
||||
|
||||
fetchFolder(folderPtr);
|
||||
}
|
||||
};
|
||||
|
||||
// `handleSubmit` is used to pass folderPtr to parent component
|
||||
const handleSubmit = () => {
|
||||
let url = "OpenSign™ Drive";
|
||||
tabList.forEach((t) => {
|
||||
url = url + " / " + t.Name;
|
||||
});
|
||||
setFolderPath(url);
|
||||
setSelectedFolder(clickFolder);
|
||||
if (onSuccess) {
|
||||
onSuccess(clickFolder);
|
||||
}
|
||||
SetIsOpen(false);
|
||||
};
|
||||
|
||||
// `handleCancel` is used to clear list of folder, close popup and folderUrl
|
||||
const handleCancel = () => {
|
||||
SetIsOpen(false);
|
||||
setClickFolder({});
|
||||
setFolderList([]);
|
||||
setTabList([]);
|
||||
};
|
||||
|
||||
// `handleCancel` is call when user click on folder name from path/tab in popup
|
||||
const removeTabListItem = async (e, i) => {
|
||||
e.preventDefault();
|
||||
// setEditable(false);
|
||||
if (!isAdd) {
|
||||
setIsLoader(true);
|
||||
let folderPtr;
|
||||
if (i) {
|
||||
setFolderList([]);
|
||||
let list = tabList.filter((itm, j) => {
|
||||
if (j <= i) {
|
||||
return itm;
|
||||
}
|
||||
});
|
||||
let _len = list.length - 1;
|
||||
folderPtr = {
|
||||
__type: "Pointer",
|
||||
className: folderCls,
|
||||
objectId: list[_len].objectId
|
||||
};
|
||||
setTabList(list);
|
||||
} else {
|
||||
setClickFolder({});
|
||||
setSelectedFolder({});
|
||||
setFolderList([]);
|
||||
setTabList([]);
|
||||
}
|
||||
fetchFolder(folderPtr);
|
||||
}
|
||||
};
|
||||
// `handleCreate` is used to open folder creation form in popup
|
||||
const handleCreate = () => {
|
||||
setIsAdd(!isAdd);
|
||||
};
|
||||
// `handleAddFolder` is call when user folder created successfully and it fetch folder list on the basis of folderPtr or without folderPtr
|
||||
const handleAddFolder = () => {
|
||||
setFolderList([]);
|
||||
if (clickFolder && clickFolder.ObjectId) {
|
||||
fetchFolder({
|
||||
__type: "Pointer",
|
||||
className: folderCls,
|
||||
objectId: clickFolder.ObjectId
|
||||
});
|
||||
} else {
|
||||
fetchFolder();
|
||||
}
|
||||
handleCreate();
|
||||
};
|
||||
return (
|
||||
<div className="text-xs mt-2 ">
|
||||
<div>
|
||||
<label className="block">
|
||||
Select Folder
|
||||
{required && <span className="text-red-500 text-[13px]">*</span>}
|
||||
</label>
|
||||
</div>
|
||||
<div className="relative max-w-sm">
|
||||
<div
|
||||
onClick={() => SetIsOpen(true)}
|
||||
className="cursor-pointer rounded px-[20px] py-[20px] bg-base-100 border-[1px] border-base-200 shadow flex max-w-sm gap-8 items-center"
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
className="w-[40px] h-[40px] fill-current op-text-secondary"
|
||||
>
|
||||
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
|
||||
</svg>
|
||||
</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">
|
||||
<i
|
||||
className="fa-light 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>
|
||||
</div>
|
||||
<div className="absolute top-2 right-1 cursor-pointer">
|
||||
<Tooltip
|
||||
message={
|
||||
"If you do not select a folder, your signed document will be saved in the Main OpenSign drive folder."
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ModalUi
|
||||
title={"Select Folder"}
|
||||
isOpen={isOpen}
|
||||
handleClose={handleCancel}
|
||||
>
|
||||
<div className="w-full min-w-[300px] md:min-w-[500px] max-w-[500px] px-3">
|
||||
<div className="pt-1 text-[#ac4848] text-[14px] font-[500]">
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
title="OpenSign™ Drive"
|
||||
onClick={(e) => removeTabListItem(e)}
|
||||
>
|
||||
OpenSign™ Drive /{" "}
|
||||
</span>
|
||||
{tabList &&
|
||||
tabList.map((tab, i) => (
|
||||
<React.Fragment key={`${tab.objectId}-${i}`}>
|
||||
<span
|
||||
className="cursor-pointer"
|
||||
title={tab.Name}
|
||||
onClick={(e) => removeTabListItem(e, i)}
|
||||
>
|
||||
{tab.Name}
|
||||
</span>
|
||||
{" / "}
|
||||
</React.Fragment>
|
||||
))}
|
||||
<hr className="bg-[#8a8a8a] mt-[0.750rem]" />
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<div className="max-h-[210px] overflow-auto">
|
||||
{!isAdd && folderList.length > 0
|
||||
? folderList.map((folder) => (
|
||||
<div
|
||||
key={folder.objectId}
|
||||
className={`${
|
||||
folder.Type === "Folder"
|
||||
? "cursor-pointer"
|
||||
: "cursor-default"
|
||||
} border-b-[1px] border-[#8a8a8a] py-2 mb-0.5"`}
|
||||
onClick={() =>
|
||||
folder.Type === "Folder" && handleSelect(folder)
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{folder.Type === "Folder" ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
className="w-[1.4rem] h-[1.4rem] fill-current op-text-secondary"
|
||||
>
|
||||
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 384 512"
|
||||
className="w-[1.4rem] h-[1.4rem] fill-current op-text-primary"
|
||||
>
|
||||
<path d="M374.629 150.627L233.371 9.373C227.371 3.371 219.23 0 210.746 0H64C28.652 0 0 28.652 0 64V448C0 483.345 28.652 512 64 512H320C355.348 512 384 483.345 384 448V173.254C384 164.767 380.629 156.629 374.629 150.627ZM224 22.629L361.375 160H248C234.781 160 224 149.234 224 136V22.629ZM368 448C368 474.467 346.469 496 320 496H64C37.531 496 16 474.467 16 448V64C16 37.533 37.531 16 64 16H208V136C208 158.062 225.938 176 248 176H368V448ZM96 264C96 268.406 99.594 272 104 272H280C284.406 272 288 268.406 288 264S284.406 256 280 256H104C99.594 256 96 259.594 96 264ZM280 320H104C99.594 320 96 323.594 96 328S99.594 336 104 336H280C284.406 336 288 332.406 288 328S284.406 320 280 320ZM280 384H104C99.594 384 96 387.594 96 392S99.594 400 104 400H280C284.406 400 288 396.406 288 392S284.406 384 280 384Z" />
|
||||
</svg>
|
||||
)}
|
||||
<span className="font-semibold">{folder.Name}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
: !isLoader && (
|
||||
<div className="text-base-content text-center my-2">
|
||||
No data found
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isAdd && (
|
||||
<CreateFolder
|
||||
parentFolderId={clickFolder && clickFolder.ObjectId}
|
||||
folderCls={folderCls}
|
||||
onSuccess={handleAddFolder}
|
||||
/>
|
||||
)}
|
||||
{isLoader && (
|
||||
<div className="flex justify-center my-4">
|
||||
<i className="fa-light fa-spinner fa-spin-pulse text-[30px]"></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="flex justify-between items-center py-[.75rem] px-[1.25rem]">
|
||||
<div
|
||||
className="op-btn op-btn-seconday op-btn-sm"
|
||||
title="Save Here"
|
||||
onClick={handleCreate}
|
||||
>
|
||||
{isAdd ? (
|
||||
<>
|
||||
<i className="fa-light fa-arrow-left" aria-hidden="true"></i>
|
||||
<span className="text-xs">Back</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<i className="fa-light fa-square-plus" aria-hidden="true"></i>
|
||||
<span className="">Add folder</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="op-btn op-btn-primary op-btn-sm"
|
||||
title="Save Here"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<i className="fa-light fa-save" aria-hidden="true"></i>Save here
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectFolder;
|
||||
@@ -0,0 +1,117 @@
|
||||
import React, { useState } from "react";
|
||||
import Parse from "parse";
|
||||
import AsyncSelect from "react-select/async";
|
||||
|
||||
const SelectSigners = (props) => {
|
||||
const [userList, setUserList] = useState([]);
|
||||
const [selected, setSelected] = useState();
|
||||
const [userData, setUserData] = useState({});
|
||||
const [isError, setIsError] = useState(false);
|
||||
// `handleOptions` is used to set just save from quick form to selected option in dropdown
|
||||
const handleOptions = (item) => {
|
||||
setSelected(item);
|
||||
const userData = userList.find((x) => x.objectId === item.value);
|
||||
if (userData) {
|
||||
setUserData(userData);
|
||||
}
|
||||
};
|
||||
const handleAdd = () => {
|
||||
if (userData && userData.objectId) {
|
||||
props.details(userData);
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
} else {
|
||||
setIsError(true);
|
||||
setTimeout(() => setIsError(false), 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const loadOptions = async (inputValue) => {
|
||||
try {
|
||||
const currentUser = Parse.User.current();
|
||||
const contactbook = new Parse.Query("contracts_Contactbook");
|
||||
contactbook.equalTo(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
if (inputValue.length > 1) {
|
||||
contactbook.matches("Name", new RegExp(inputValue, "i"));
|
||||
}
|
||||
contactbook.notEqualTo("IsDeleted", true);
|
||||
const contactRes = await contactbook.find();
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
|
||||
//and filter signers from total signer's list which already present in document's signers list
|
||||
const compareArrays = (res, signerObj) => {
|
||||
return res.filter(
|
||||
(item1) =>
|
||||
!signerObj.find((item2) => item2.objectId === item1.objectId)
|
||||
);
|
||||
};
|
||||
|
||||
//get update signer's List if signersdata is present
|
||||
const updateSignersList =
|
||||
props?.signersData && compareArrays(res, props?.signersData);
|
||||
|
||||
const result = updateSignersList ? updateSignersList : res;
|
||||
setUserList(result);
|
||||
return await result.map((item) => ({
|
||||
label: item.Email,
|
||||
value: item.objectId
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("err", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full px-[20px] py-[10px] text-base-content">
|
||||
<div className="w-full mx-auto p-[8px]">
|
||||
<div className="mb-0">
|
||||
<label className="text-[14px] font-bold">Choose from contacts</label>
|
||||
<AsyncSelect
|
||||
cacheOptions
|
||||
defaultOptions
|
||||
value={selected}
|
||||
loadingMessage={() => "Loading..."}
|
||||
noOptionsMessage={() => "Contact not found"}
|
||||
loadOptions={loadOptions}
|
||||
onChange={handleOptions}
|
||||
unstyled
|
||||
classNames={{
|
||||
control: () =>
|
||||
"op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full h-full text-[11px]",
|
||||
valueContainer: () =>
|
||||
"flex flex-row gap-x-[2px] gap-y-[2px] md:gap-y-0 w-full my-[2px]",
|
||||
multiValue: () => "op-badge op-badge-primary h-full text-[11px]",
|
||||
multiValueLabel: () => "mb-[2px]",
|
||||
menu: () =>
|
||||
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content",
|
||||
menuList: () => "shadow-md rounded-lg overflow-hidden",
|
||||
option: () =>
|
||||
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2",
|
||||
noOptionsMessage: () => "p-2 bg-base-200 rounded-lg m-1 p-2"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
className={`${
|
||||
isError ? "text-[red]" : "text-transparent"
|
||||
} text-[11px] ml-[6px] my-[2px]`}
|
||||
>
|
||||
Please select signer
|
||||
</p>
|
||||
<div>
|
||||
<button className="op-btn op-btn-primary" onClick={() => handleAdd()}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectSigners;
|
||||
@@ -0,0 +1,196 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Select from "react-select";
|
||||
import AddSigner from "../../AddSigner";
|
||||
import Parse from "parse";
|
||||
import Tooltip from "../../../primitives/Tooltip";
|
||||
import { createPortal } from "react-dom";
|
||||
function arrayMove(array, from, to) {
|
||||
array = array.slice();
|
||||
array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
|
||||
return array;
|
||||
}
|
||||
|
||||
const AddSignerModal = ({ isOpen, children }) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div className="op-modal op-modal-open">
|
||||
<div className="op-modal-box p-0 min-w-[90%] md:min-w-[500px] max-h-90 overflow-y-auto hide-scrollbar text-sm">
|
||||
{children}
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* react-sortable-hoc is depcreated not usable from react 18.x.x
|
||||
* need to replace it with @dnd-kit
|
||||
* code changes required
|
||||
*/
|
||||
|
||||
const SignersInput = (props) => {
|
||||
const [state, setState] = useState(undefined);
|
||||
// const [editFormData, setEditFormData] = useState([]);
|
||||
const [selected, setSelected] = useState([]);
|
||||
const [isModal, setIsModel] = useState(false);
|
||||
const onChange = (selectedOptions) => setSelected(selectedOptions);
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
|
||||
const onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
const newValue = arrayMove(selected, oldIndex, newIndex);
|
||||
setSelected(newValue);
|
||||
};
|
||||
|
||||
const GetSelectListData = async () => {
|
||||
try {
|
||||
const currentUser = Parse.User.current();
|
||||
const contactbook = new Parse.Query("contracts_Contactbook");
|
||||
contactbook.equalTo(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
contactbook.notEqualTo("IsDeleted", true);
|
||||
const contactRes = await contactbook.find();
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
let list = [];
|
||||
|
||||
// let _selected = [];
|
||||
res.forEach((x) => {
|
||||
let obj = {
|
||||
label: x.Name,
|
||||
value: x.objectId,
|
||||
isChecked: true
|
||||
};
|
||||
|
||||
list.push(obj);
|
||||
});
|
||||
setState(list);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("err", error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
GetSelectListData();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (props.isReset && props.isReset === true) {
|
||||
setSelected([]);
|
||||
}
|
||||
}, [props.isReset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected && selected.length) {
|
||||
let newData = [];
|
||||
selected.forEach((x) => {
|
||||
newData.push(x.value);
|
||||
});
|
||||
if (props.onChange) {
|
||||
props.onChange(newData);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
}, [selected]);
|
||||
|
||||
const handleModalCloseClick = () => {
|
||||
setIsModel(false);
|
||||
setModalIsOpen(false);
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
setModalIsOpen(true);
|
||||
};
|
||||
|
||||
// `handleNewDetails` is used to set just save from quick form to selected option in dropdown
|
||||
const handleNewDetails = (data) => {
|
||||
setState([...state, data]);
|
||||
if (selected.length > 0) {
|
||||
setSelected([...selected, data]);
|
||||
} else {
|
||||
setSelected([data]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="text-xs mt-2 ">
|
||||
<label className="block relative">
|
||||
Signers
|
||||
{props.required && <span className="text-red-500 text-[13px]">*</span>}
|
||||
<span className="absolute ml-1 text-xs z-30">
|
||||
<Tooltip
|
||||
id={"signer-tooltip"}
|
||||
message={
|
||||
"Begin typing a contact's name to see suggested signers from your saved contacts or add new ones. Arrange the signing order by adding signers in the desired sequence. Use the '+' button to include signers and the 'x' to remove them. Each signer will receive an email prompt to sign the document in the order listed."
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
<div className="flex gap-x-[5px]">
|
||||
<div className="w-full">
|
||||
<Select
|
||||
onSortEnd={onSortEnd}
|
||||
distance={4}
|
||||
isMulti
|
||||
options={state || []}
|
||||
value={selected}
|
||||
onChange={onChange}
|
||||
closeMenuOnSelect={false}
|
||||
required={props.required}
|
||||
noOptionsMessage={() => "Contact not found"}
|
||||
unstyled
|
||||
classNames={{
|
||||
control: () =>
|
||||
"op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full h-full text-[11px]",
|
||||
valueContainer: () =>
|
||||
"flex flex-row gap-x-[2px] gap-y-[2px] md:gap-y-0 w-full my-[2px]",
|
||||
multiValue: () => "op-badge op-badge-primary h-full text-[11px]",
|
||||
multiValueLabel: () => "mb-[2px]",
|
||||
menu: () =>
|
||||
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content",
|
||||
menuList: () => "shadow-md rounded-lg overflow-hidden",
|
||||
option: () =>
|
||||
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2",
|
||||
noOptionsMessage: () => "p-2 bg-base-200 rounded-lg m-1 p-2"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
setIsModel(true);
|
||||
openModal();
|
||||
}}
|
||||
className="cursor-pointer op-input op-input-bordered focus:outline-none hover:border-base-content max-h-[38px] min-w-[48px] flex justify-center items-center"
|
||||
>
|
||||
<i className="fa-light fa-plus"></i>
|
||||
</div>
|
||||
<AddSignerModal isOpen={modalIsOpen}>
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||
Add Signer
|
||||
</h3>
|
||||
<button
|
||||
onClick={handleModalCloseClick}
|
||||
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
{isModal && (
|
||||
<AddSigner
|
||||
valueKey={"objectId"}
|
||||
displayKey={"Name"}
|
||||
details={handleNewDetails}
|
||||
closePopup={handleModalCloseClick}
|
||||
/>
|
||||
)}
|
||||
</AddSignerModal>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignersInput;
|
||||
@@ -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 text-base-content">
|
||||
<input
|
||||
type={props?.type || "text"}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Email..."
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs 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-base-200 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-base-300"
|
||||
onClick={() => handleSuggestionClick(suggestion)}
|
||||
>
|
||||
{suggestion.Email}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default SuggestionInput;
|
||||
@@ -2,20 +2,28 @@ import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
|
||||
const Menu = ({ item, isOpen, closeSidebar }) => {
|
||||
return (
|
||||
<li key={item.title} role="none">
|
||||
<NavLink
|
||||
to={`/${item.pageType}/${item.objectId}`}
|
||||
className="mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4"
|
||||
onClick={closeSidebar}
|
||||
tabIndex={isOpen ? 0 : -1}
|
||||
role="menuitem"
|
||||
>
|
||||
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i>
|
||||
<span className="ml-3 lg:ml-4">{item.title}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<li key={item.title} role="none" className="my-0.5">
|
||||
<NavLink
|
||||
to={
|
||||
item.pageType
|
||||
? `/${item.pageType}/${item.objectId}`
|
||||
: `/${item.objectId}`
|
||||
}
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? " bg-base-300 text-base-content" : ""
|
||||
} flex items-center text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
tabIndex={isOpen ? 0 : -1}
|
||||
role="menuitem"
|
||||
>
|
||||
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i>
|
||||
<span className="ml-3 lg:ml-4">{item.title}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export default Menu;
|
||||
export default Menu;
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import Menu from './Menu';
|
||||
import Menu from "./Menu";
|
||||
import Submenu from "./SubMenu";
|
||||
import SocialMedia from './SocialMedia';
|
||||
|
||||
import Parse from "parse";
|
||||
import SocialMedia from "./SocialMedia";
|
||||
import dp from "../../assets/images/dp.png";
|
||||
import sidebarList from "../../json/menuJson";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
|
||||
const Sidebar = ({ isOpen, closeSidebar }) => {
|
||||
const navigate = useNavigate();
|
||||
const [menuList, setmenuList] = useState([]);
|
||||
let username = localStorage.getItem("username");
|
||||
const [submenuOpen, setSubmenuOpen] = useState(false);
|
||||
const username = localStorage.getItem("username");
|
||||
const image = localStorage.getItem("profileImg") || dp;
|
||||
const tenantname = localStorage.getItem("TenantName");
|
||||
const tenantname = localStorage.getItem("Extand_Class")
|
||||
? JSON.parse(localStorage.getItem("Extand_Class"))?.[0]?.Company
|
||||
: "";
|
||||
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem("accesstoken")) {
|
||||
@@ -20,54 +24,159 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
|
||||
}, []);
|
||||
|
||||
const menuItem = async () => {
|
||||
const parseBaseUrl = localStorage.getItem("baseUrl");
|
||||
const parseAppId = localStorage.getItem("parseAppId");
|
||||
|
||||
try {
|
||||
Parse.serverURL = parseBaseUrl;
|
||||
Parse.initialize(parseAppId);
|
||||
var sideMenu = Parse.Object.extend("w_menu");
|
||||
var query = new Parse.Query(sideMenu);
|
||||
query.equalTo("objectId", localStorage.getItem("defaultmenuid"));
|
||||
|
||||
const results = await query.first();
|
||||
const resultjson = results.toJSON();
|
||||
let result = resultjson;
|
||||
setmenuList(result.menuItems);
|
||||
if (localStorage.getItem("defaultmenuid")) {
|
||||
const Extand_Class = localStorage.getItem("Extand_Class");
|
||||
const extClass = Extand_Class && JSON.parse(Extand_Class);
|
||||
// console.log("extClass ", extClass);
|
||||
let userRole = "contracts_User";
|
||||
if (extClass && extClass.length > 0) {
|
||||
userRole = extClass[0].UserRole;
|
||||
}
|
||||
if (
|
||||
userRole === "contracts_Admin" ||
|
||||
userRole === "contracts_OrgAdmin"
|
||||
) {
|
||||
const newSidebarList = sidebarList.map((item) => {
|
||||
if (item.title === "Settings") {
|
||||
// Make a shallow copy of the item
|
||||
const newItem = { ...item };
|
||||
if (isEnableSubscription) {
|
||||
newItem.children = [
|
||||
...newItem.children,
|
||||
{
|
||||
icon: "fa-light fa-building-memo",
|
||||
title: "Teams",
|
||||
target: "_self",
|
||||
pageType: "",
|
||||
description: "",
|
||||
objectId: "teams"
|
||||
},
|
||||
{
|
||||
icon: "fa-light fa-users fa-fw",
|
||||
title: "Users",
|
||||
target: "_self",
|
||||
pageType: "",
|
||||
description: "",
|
||||
objectId: "users"
|
||||
}
|
||||
];
|
||||
} else {
|
||||
const arr = newItem.children.slice(0, 1);
|
||||
newItem.children = [
|
||||
...arr,
|
||||
{
|
||||
icon: "fa-light fa-users fa-fw",
|
||||
title: "Users",
|
||||
target: "_self",
|
||||
pageType: "",
|
||||
description: "",
|
||||
objectId: "users"
|
||||
}
|
||||
];
|
||||
}
|
||||
return newItem;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setmenuList(newSidebarList);
|
||||
} else {
|
||||
if (!isEnableSubscription) {
|
||||
const newSidebarList = sidebarList.map((item) => {
|
||||
if (item.title === "Settings") {
|
||||
// Make a shallow copy of the item
|
||||
const newItem = { ...item };
|
||||
const arr = newItem.children.slice(0, 1);
|
||||
newItem.children = arr;
|
||||
return newItem;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setmenuList(newSidebarList);
|
||||
} else {
|
||||
setmenuList(sidebarList);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Problem", e);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const toggleSubmenu = (title) => {
|
||||
setSubmenuOpen({ [title]: !submenuOpen[title] });
|
||||
};
|
||||
|
||||
const handleMenuItem = () => {
|
||||
closeSidebar();
|
||||
setSubmenuOpen({});
|
||||
};
|
||||
const handleProfile = () => {
|
||||
navigate("/profile");
|
||||
};
|
||||
return (
|
||||
<aside
|
||||
className={`bg-white text-[#444] h-screen w-full md:w-[300px] overflow-y-auto transform transition-transform z-40 md:translate-x-0
|
||||
${isOpen ? "translate-x-0 block" : "-translate-x-full hidden"}`}
|
||||
className={`absolute lg:relative bg-base-100 h-screen overflow-y-auto transition-all z-[500] shadow-lg hide-scrollbar
|
||||
${isOpen ? "w-full md:w-[300px]" : "w-0"}`}
|
||||
>
|
||||
<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" src={image} alt="Profile" />
|
||||
<div
|
||||
onClick={() => handleProfile()}
|
||||
className="w-[75px] h-[75px] rounded-full ring-[2px] ring-offset-2 ring-gray-400 overflow-hidden cursor-pointer"
|
||||
>
|
||||
<img
|
||||
className="w-full h-full object-contain"
|
||||
src={image}
|
||||
alt="Profile"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[14px] font-bold">{username}</p>
|
||||
<p className={`text-[12px] ${tenantname ? "mt-2" : ""}`}>
|
||||
<p
|
||||
onClick={handleProfile}
|
||||
className="text-[14px] font-bold text-base-content cursor-pointer"
|
||||
>
|
||||
{username}
|
||||
</p>
|
||||
<p
|
||||
onClick={handleProfile}
|
||||
className={`cursor-pointer text-[12px] text-base-content ${
|
||||
tenantname ? "mt-2" : ""
|
||||
}`}
|
||||
>
|
||||
{tenantname}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav aria-label="OpenSign Sidebar Navigation">
|
||||
<ul className="text-sm" role="menubar" aria-label="OpenSign Sidebar Navigation">
|
||||
{menuList.map((item) => (
|
||||
<nav
|
||||
className="op-menu op-menu-sm"
|
||||
aria-label="OpenSign Sidebar Navigation"
|
||||
>
|
||||
<ul
|
||||
className="text-sm"
|
||||
role="menubar"
|
||||
aria-label="OpenSign Sidebar Navigation"
|
||||
>
|
||||
{menuList.map((item) =>
|
||||
!item.children ? (
|
||||
<Menu key={item.title} item={item} isOpen={isOpen} closeSidebar={closeSidebar} />
|
||||
<Menu
|
||||
key={item.title}
|
||||
item={item}
|
||||
isOpen={isOpen}
|
||||
closeSidebar={handleMenuItem}
|
||||
/>
|
||||
) : (
|
||||
<Submenu key={item.title} item={item} closeSidebar={closeSidebar} />
|
||||
<Submenu
|
||||
key={item.title}
|
||||
item={item}
|
||||
closeSidebar={closeSidebar}
|
||||
toggleSubmenu={toggleSubmenu}
|
||||
submenuOpen={submenuOpen}
|
||||
/>
|
||||
)
|
||||
))}
|
||||
)}
|
||||
</ul>
|
||||
</nav>
|
||||
<footer className="mt-4 flex justify-center items-center text-[25px] text-black gap-3">
|
||||
<footer className="mt-4 flex justify-center items-center text-[25px] text-base-content gap-3">
|
||||
<SocialMedia />
|
||||
</footer>
|
||||
</aside>
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
|
||||
const Submenu = ({ item, closeSidebar }) => {
|
||||
const [submenuOpen, setSubmenuOpen] = useState(false);
|
||||
const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
const { title, icon, children } = item;
|
||||
|
||||
const toggleSubmenu = () => {
|
||||
setSubmenuOpen(!submenuOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<li role="none">
|
||||
<li role="none" className="my-0.5">
|
||||
<button
|
||||
onClick={toggleSubmenu}
|
||||
className="w-full flex items-center text-left text-[#444] p-3 lg:p-4 hover:bg-[#eef1f5] focus:ring-2 focus:ring-blue-600"
|
||||
onClick={() => toggleSubmenu(item.title)}
|
||||
className="flex items-center text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none "
|
||||
aria-expanded={submenuOpen}
|
||||
aria-haspopup="true"
|
||||
aria-controls={`submenu-${title}`}
|
||||
@@ -22,24 +16,38 @@ const Submenu = ({ item, closeSidebar }) => {
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<span className="ml-3 lg:ml-4">{title}</span>
|
||||
<i
|
||||
className={`${submenuOpen ? "fa-solid fa-angle-down" : "fa-solid fa-angle-right"
|
||||
}`}
|
||||
className={`${
|
||||
submenuOpen[item.title]
|
||||
? "fa-light fa-angle-down"
|
||||
: "fa-light fa-angle-right"
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
</button>
|
||||
{submenuOpen && (
|
||||
{submenuOpen[item.title] && (
|
||||
<ul id={`submenu-${title}`} role="menu" aria-label={`${title} submenu`}>
|
||||
{children.map((childItem) => (
|
||||
<li key={childItem.title} role="none">
|
||||
<li key={childItem.title} role="none" className="my-0.5">
|
||||
<NavLink
|
||||
to={`/${childItem.pageType}/${childItem.objectId}`}
|
||||
className="block pl-6 md:pl-8 py-2 text-sm text-gray-700 hover:bg-blue-500 hover:text-white"
|
||||
to={
|
||||
childItem.pageType
|
||||
? `/${childItem.pageType}/${childItem.objectId}`
|
||||
: `/${childItem.objectId}`
|
||||
}
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? "bg-base-300 text-base-content" : ""
|
||||
} flex items-center text-left pl-6 md:pl-8 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
role="menuitem"
|
||||
tabIndex={submenuOpen ? 0 : -1}
|
||||
>
|
||||
<i className={`${childItem.icon} text-[18px]`} aria-hidden="true"></i>
|
||||
<i
|
||||
className={`${childItem.icon} text-[18px]`}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="ml-3 lg:ml-4">{childItem.title}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import logo from "../assets/images/logo.png";
|
||||
export const appInfo = {
|
||||
appTitle: "contracts",
|
||||
applogo: logo,
|
||||
appname: "contracts",
|
||||
baseurl: process.env.REACT_APP_SERVERURL,
|
||||
appId: process.env.REACT_APP_APPID ? process.env.REACT_APP_APPID : "opensign",
|
||||
baseUrl: process.env.REACT_APP_SERVERURL
|
||||
? process.env.REACT_APP_SERVERURL
|
||||
: window.location.origin + "/api/app",
|
||||
defaultRole: "contracts_User",
|
||||
fbAppId: process.env.REACT_APP_FBAPPID
|
||||
? `${process.env.REACT_APP_FBAPPID}`
|
||||
@@ -15,22 +16,34 @@ export const appInfo = {
|
||||
: "",
|
||||
metaDescription:
|
||||
"The fastest way to sign PDFs & request signatures from others.",
|
||||
objectId: "aIPmIvMzGM",
|
||||
settings: [
|
||||
{
|
||||
role: "contracts_User",
|
||||
menuId: "H9vRfEYKhT",
|
||||
pageType: "dashboard",
|
||||
pageId: "35KBoSgoAK",
|
||||
extended_class: "contracts_Users"
|
||||
},
|
||||
{
|
||||
role: "contracts_Admin",
|
||||
menuId: "VPh91h0ZHk",
|
||||
pageType: "dashboard",
|
||||
pageId: "35KBoSgoAK",
|
||||
extended_class: "contracts_Users"
|
||||
},
|
||||
{
|
||||
role: "contracts_OrgAdmin",
|
||||
menuId: "VPh91h0ZHk",
|
||||
pageType: "dashboard",
|
||||
pageId: "35KBoSgoAK",
|
||||
extended_class: "contracts_Users"
|
||||
},
|
||||
{
|
||||
role: "contracts_Editor",
|
||||
menuId: "H9vRfEYKhT",
|
||||
pageType: "dashboard",
|
||||
pageId: "35KBoSgoAK",
|
||||
extended_class: "contracts_Users"
|
||||
},
|
||||
{
|
||||
role: "contracts_User",
|
||||
menuId: "H9vRfEYKhT",
|
||||
pageType: "dashboard",
|
||||
pageId: "35KBoSgoAK",
|
||||
extended_class: "contracts_Users"
|
||||
}
|
||||
],
|
||||
version: "0.1"
|
||||
]
|
||||
};
|
||||
|
||||