Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4e28e6e6e | ||
|
|
b19fb29b82 | ||
|
|
3d223ba343 | ||
|
|
98754c776f | ||
|
|
1550e3a3d0 | ||
|
|
3500883205 | ||
|
|
8f2433fbea | ||
|
|
b671dbac19 | ||
|
|
79cdbc9c8c | ||
|
|
e585b7c943 | ||
|
|
5336e2cb7c | ||
|
|
72401291f8 | ||
|
|
7b34314b9e | ||
|
|
b6f1cceeca | ||
|
|
c422cdac06 | ||
|
|
ab08d2615d | ||
|
|
2b0c7488d9 | ||
|
|
6927540741 | ||
|
|
1ceeee36ac | ||
|
|
81d3969d1f | ||
|
|
5819103f57 | ||
|
|
6653b7ae5a | ||
|
|
b50c1e70ee | ||
|
|
365c2a217e | ||
|
|
b1b9b20e97 | ||
|
|
75c0cdb5f9 | ||
|
|
740ccca1f6 | ||
|
|
f3b79cb71f | ||
|
|
f95ea0b9e9 | ||
|
|
647a9ce5b4 | ||
|
|
3f3ca668f5 | ||
|
|
2fd6d1a37a | ||
|
|
307137f926 | ||
|
|
2394880dfc | ||
|
|
414cdcd4b5 | ||
|
|
1fa6b25890 | ||
|
|
ff2fd44b19 | ||
|
|
ad466fb066 | ||
|
|
0f179fed67 | ||
|
|
3ded6c35a1 | ||
|
|
64293db3a1 | ||
|
|
18a4c8523a | ||
|
|
f7b39409a8 | ||
|
|
6283524d2f | ||
|
|
216288fd09 | ||
|
|
3b1d8a48b7 | ||
|
|
ec68770ff0 | ||
|
|
f5d2027e3a | ||
|
|
8af0010f55 | ||
|
|
d7b7ad6285 | ||
|
|
fca45ced38 | ||
|
|
ac8ab70246 | ||
|
|
8373679f27 | ||
|
|
0a23aabbe4 | ||
|
|
4a85599703 | ||
|
|
3e9b84de72 | ||
|
|
4313d8d0e4 | ||
|
|
3088fdd163 | ||
|
|
8c86fe27ec | ||
|
|
01e09998e5 | ||
|
|
55fdb52bf3 | ||
|
|
08f46a393b | ||
|
|
9345da543a | ||
|
|
208891e10d | ||
|
|
db0fa61b2b | ||
|
|
221f098de6 | ||
|
|
9658761ede | ||
|
|
6aad832e37 | ||
|
|
e4b6be51d9 | ||
|
|
a3b2c797fb | ||
|
|
22d27abb76 | ||
|
|
45e0679ca7 | ||
|
|
2b2aa20deb | ||
|
|
8d6ed3d2b5 | ||
|
|
6064f66cdf | ||
|
|
0ba29eb799 | ||
|
|
d0dd571478 | ||
|
|
ddb63d81a7 | ||
|
|
f78451d3f9 | ||
|
|
28630e2d23 | ||
|
|
c5022eaee6 | ||
|
|
1b7f07a2c4 | ||
|
|
2484c67406 | ||
|
|
abfc3f32f2 | ||
|
|
5c06ff4242 | ||
|
|
1899acfffc | ||
|
|
796ff058c8 | ||
|
|
c88cb5357c | ||
|
|
a6a0325d4b | ||
|
|
b18d17c44d | ||
|
|
a73779e74b | ||
|
|
5b943c1965 | ||
|
|
f73844d730 | ||
|
|
aaadab540a | ||
|
|
83bc466df4 | ||
|
|
d85ed954f1 | ||
|
|
369deb6e26 | ||
|
|
295d1af910 | ||
|
|
080d7c08dc | ||
|
|
c877f9fc5d | ||
|
|
99c7a58d62 | ||
|
|
97642bf857 | ||
|
|
2f67ac7b0a | ||
|
|
23aecb45d9 | ||
|
|
92b507e621 | ||
|
|
1f6c2c7d38 | ||
|
|
c1be197346 | ||
|
|
38f8561d18 | ||
|
|
10e6ab58a5 | ||
|
|
24a95e8852 | ||
|
|
39fc2daa19 | ||
|
|
c38f59642f | ||
|
|
e9d0af7abb | ||
|
|
b39d845cd6 | ||
|
|
031673f925 | ||
|
|
e299891174 | ||
|
|
b55aff20d3 | ||
|
|
a9ccb1a3e5 | ||
|
|
f3d34b1d2c | ||
|
|
3f56aa0860 | ||
|
|
4eeacbaaf1 | ||
|
|
096a9e5736 | ||
|
|
4650660a26 | ||
|
|
9e6309644a | ||
|
|
14477a17ee | ||
|
|
7d3d618ff3 | ||
|
|
284cd277ea | ||
|
|
df61d752c8 | ||
|
|
9def956929 | ||
|
|
53aa5a26aa | ||
|
|
86cb5ccf78 | ||
|
|
c8e91e5205 | ||
|
|
87cfb643f9 | ||
|
|
2f7bd75608 | ||
|
|
930bac71a0 | ||
|
|
fcad7dc020 | ||
|
|
bada731d16 | ||
|
|
fd5e86edf2 | ||
|
|
1a886261ef | ||
|
|
41c3b4746d | ||
|
|
f52d013b06 | ||
|
|
0045603cb0 | ||
|
|
4235bc077d | ||
|
|
58b3cc6ba2 | ||
|
|
97f5d09306 | ||
|
|
fa98972b27 | ||
|
|
76848b7e09 | ||
|
|
c3faaa65b9 | ||
|
|
e819513650 | ||
|
|
dd0e2f0e58 | ||
|
|
fa08efeac6 | ||
|
|
f3ba02496c | ||
|
|
5cbbb00f66 | ||
|
|
7050df8b4c | ||
|
|
4fb27f4896 | ||
|
|
145aa5da97 | ||
|
|
1a455415f1 | ||
|
|
44ef1fe825 | ||
|
|
cb07e376d5 | ||
|
|
9b472a6b8b | ||
|
|
9840fe9412 | ||
|
|
d1a99d5211 | ||
|
|
5f7736882b | ||
|
|
d8095e077a | ||
|
|
c8fae00ce5 | ||
|
|
7c6a03a2d2 | ||
|
|
657ab13a04 | ||
|
|
40c21321dd | ||
|
|
476dbb555d | ||
|
|
19b4bfb9ef | ||
|
|
e9a85162e7 | ||
|
|
960c297995 | ||
|
|
50e525f448 | ||
|
|
c86f048498 | ||
|
|
39fd2f697f | ||
|
|
3f2cf3ddf4 | ||
|
|
5a53a70dcf | ||
|
|
66323f9347 | ||
|
|
c65f17a216 | ||
|
|
9bf93fb231 | ||
|
|
f418e02270 | ||
|
|
403254a5c6 | ||
|
|
c893d96ad3 | ||
|
|
3971118af8 | ||
|
|
4db0d93f41 | ||
|
|
affbfc5061 | ||
|
|
886ede2007 | ||
|
|
a3936c66aa | ||
|
|
d98453ffc2 | ||
|
|
7424912455 | ||
|
|
e095495081 | ||
|
|
eb4ce5544f | ||
|
|
8eef17834d | ||
|
|
40ed5e2353 | ||
|
|
2aa4c8c361 | ||
|
|
e5b120fa36 | ||
|
|
1e026f8ebd | ||
|
|
e5c7da0847 | ||
|
|
2e67002768 | ||
|
|
9c03649d48 | ||
|
|
df436b3b20 | ||
|
|
34acf4170e | ||
|
|
f9a13c880a | ||
|
|
df7cd45be6 | ||
|
|
b22d70c833 | ||
|
|
1ef8e8aa6a | ||
|
|
2aa58b7eb3 | ||
|
|
9fb61509c4 | ||
|
|
b99dc875ed | ||
|
|
7da31f00c3 | ||
|
|
894ccd1244 | ||
|
|
8d90e6e808 | ||
|
|
79771f725e | ||
|
|
786f56941a | ||
|
|
8fae910d46 | ||
|
|
8b5920bba3 | ||
|
|
03f1451e64 | ||
|
|
1d2a975b5f | ||
|
|
3db32979dc | ||
|
|
110431dae2 | ||
|
|
ade228f756 | ||
|
|
9b18a66f22 | ||
|
|
c88de90a24 | ||
|
|
737d66d97d | ||
|
|
b77b57d3fd | ||
|
|
240c7766c1 | ||
|
|
508b2e43d3 | ||
|
|
d6129e21e9 | ||
|
|
a11a2444f1 | ||
|
|
db1fb1c0e1 | ||
|
|
7f326b19aa | ||
|
|
2a1d7d2502 | ||
|
|
01edfce6e7 | ||
|
|
9176b8f99c | ||
|
|
0ff2e57424 | ||
|
|
567966866c | ||
|
|
e03f07a8ef | ||
|
|
9915a700bb | ||
|
|
f0a406a397 | ||
|
|
851ea24be4 | ||
|
|
50c4eff790 | ||
|
|
e69e6998c9 | ||
|
|
c87873cfb6 | ||
|
|
b09becf3eb | ||
|
|
0673242f41 | ||
|
|
1776931df5 | ||
|
|
124fbfd798 | ||
|
|
893e433805 | ||
|
|
a2c0921345 | ||
|
|
ad0a974c14 | ||
|
|
2d4a7d336c | ||
|
|
8d2df87de3 | ||
|
|
873e7e2068 | ||
|
|
3fcb1d0138 | ||
|
|
ce41f9a0ce | ||
|
|
34d8ed19bf | ||
|
|
ea9f34f0b3 | ||
|
|
4e9cbd23f2 | ||
|
|
bbd65c01e9 | ||
|
|
5dc30b7702 | ||
|
|
eb3b31bd59 | ||
|
|
57597dfef0 | ||
|
|
8cb7a610c7 | ||
|
|
2c464978b4 | ||
|
|
8d0bf6abe8 | ||
|
|
82c724a768 | ||
|
|
6146fe5f86 | ||
|
|
24ce6eae3b | ||
|
|
8db934a2c2 | ||
|
|
dc90d412ca | ||
|
|
b8415c8995 | ||
|
|
0d86f39ba3 | ||
|
|
0bab033673 | ||
|
|
72a0ce3f51 | ||
|
|
c3a14b82b3 | ||
|
|
b3252e276b | ||
|
|
10c63a66f6 | ||
|
|
f44205b90d | ||
|
|
cd087d410f | ||
|
|
c5812ba938 | ||
|
|
2e4c7f7690 | ||
|
|
a7389efb00 | ||
|
|
532062687c | ||
|
|
d3a603d115 | ||
|
|
26fece78c5 | ||
|
|
b8fb626591 | ||
|
|
732786067f | ||
|
|
45c0cd48c7 | ||
|
|
50d7982dd5 | ||
|
|
ff085b1284 | ||
|
|
c3564b1f8f | ||
|
|
32eba21e7a | ||
|
|
9952afcc38 | ||
|
|
b3feccab0d | ||
|
|
86ebd4903b | ||
|
|
0533bb9c30 | ||
|
|
185e90494c | ||
|
|
3f77b497ce | ||
|
|
87a8333cdf | ||
|
|
3d12261aa7 | ||
|
|
19c6cbbcd3 | ||
|
|
39c191db7c | ||
|
|
58c36266e0 | ||
|
|
0ebdfffdeb | ||
|
|
ed840f3048 | ||
|
|
1e13b509df | ||
|
|
d76a9a4628 | ||
|
|
f70e20498e | ||
|
|
1715b39629 | ||
|
|
3c63db8c9c | ||
|
|
7af78c95dd | ||
|
|
1c4a377641 | ||
|
|
bf56937585 | ||
|
|
e97442b0c8 | ||
|
|
d029ce5380 | ||
|
|
20049921ad | ||
|
|
05a0db75db | ||
|
|
a7b8100a18 | ||
|
|
112369b283 | ||
|
|
f9b915b728 | ||
|
|
8acdbc928e | ||
|
|
9665e14e6d | ||
|
|
b45e05cc9e | ||
|
|
116f4d94ea | ||
|
|
f18b5b49c5 | ||
|
|
09a4db7c03 | ||
|
|
2616a5776b | ||
|
|
36cfc4a867 | ||
|
|
631f118afb | ||
|
|
b43e3a61e6 | ||
|
|
04100adece | ||
|
|
a103f93925 | ||
|
|
8467340fb5 | ||
|
|
3a8e41de27 | ||
|
|
97c2f183ca | ||
|
|
f1ab35e71e | ||
|
|
240d892b94 | ||
|
|
364d96e7f6 | ||
|
|
03dfd58ac5 | ||
|
|
716023b534 | ||
|
|
67b3417ac9 | ||
|
|
f3667f6aaf | ||
|
|
7b35e71f3d | ||
|
|
51031878eb | ||
|
|
19d96230cd | ||
|
|
dbee7e2894 | ||
|
|
656241da54 | ||
|
|
2a3947ea1d | ||
|
|
9c1b4261dc | ||
|
|
7c37d8128e | ||
|
|
404785bf29 | ||
|
|
dc7fd34b07 | ||
|
|
86a2975b3c | ||
|
|
f824fc7c81 | ||
|
|
244cc30eb6 | ||
|
|
a833ed8060 | ||
|
|
1e869d2d2b | ||
|
|
64df5cfff3 | ||
|
|
d8b2a80e90 | ||
|
|
10061c061e | ||
|
|
6a4124fdf0 | ||
|
|
faaa915163 | ||
|
|
3fb911298d | ||
|
|
761f81d27c | ||
|
|
d4b7c8ae97 | ||
|
|
23679e58e1 | ||
|
|
f3e39f18ef | ||
|
|
55a4cda351 | ||
|
|
f0f270b44d | ||
|
|
529de2bd90 | ||
|
|
51dd83df09 | ||
|
|
9abe4ffd8e | ||
|
|
cdd92f6990 | ||
|
|
b08fa9bf60 | ||
|
|
f0bcc3c3e7 | ||
|
|
b9c45f24f5 | ||
|
|
d5f3967558 | ||
|
|
d3fccc9b87 | ||
|
|
7e6c03faf5 | ||
|
|
02b2a52280 | ||
|
|
6eb708b155 | ||
|
|
83e6444ddc | ||
|
|
168fe4d680 | ||
|
|
0a10c9a7c9 | ||
|
|
204c530712 | ||
|
|
5e55f0debf | ||
|
|
f8bd5a6c86 | ||
|
|
101e7a9a49 | ||
|
|
271e79d6a2 | ||
|
|
2ca8f8aff8 | ||
|
|
bc6891e746 | ||
|
|
26aaeccf11 | ||
|
|
91b512b693 | ||
|
|
88d7917a84 | ||
|
|
7ae0964dd1 | ||
|
|
edb0c305dd | ||
|
|
0321f35869 | ||
|
|
1870f8e90e | ||
|
|
6e112b904f | ||
|
|
dca8d2533e | ||
|
|
c57957b56d | ||
|
|
1f5ab87653 | ||
|
|
d5513e90bd | ||
|
|
00fcd17285 | ||
|
|
356ab4e7cb | ||
|
|
572cb55bf8 | ||
|
|
08b83469e3 | ||
|
|
6939150d2b | ||
|
|
cf9a4d8a26 | ||
|
|
a5f8e4fc58 | ||
|
|
775e325503 | ||
|
|
0c472ba22c | ||
|
|
4c0d15d6ef | ||
|
|
28cea81f61 | ||
|
|
d975dae88f | ||
|
|
30297584d5 | ||
|
|
1105f8e608 | ||
|
|
eeae097a54 | ||
|
|
8199e5a253 | ||
|
|
08c7f4b491 | ||
|
|
634744c22d | ||
|
|
45f4bdd1c5 | ||
|
|
c8f5c80d5b | ||
|
|
5556fafa94 | ||
|
|
20c21cb964 | ||
|
|
d2de889995 | ||
|
|
05cf3ef7fe | ||
|
|
6bd8a8fea8 | ||
|
|
4cba1a2fd2 | ||
|
|
86f0622c4a | ||
|
|
85ad7ca187 | ||
|
|
879279a382 | ||
|
|
74df84e449 | ||
|
|
2862ae3302 | ||
|
|
7d0dfe480c | ||
|
|
c47748ed4d | ||
|
|
396504138c | ||
|
|
6e067a160c | ||
|
|
66a56757d1 | ||
|
|
088ec5580a | ||
|
|
63b2693fe0 | ||
|
|
76a69d8f3b | ||
|
|
5cd0f55cb2 | ||
|
|
db640b04a6 | ||
|
|
0665bcf30a | ||
|
|
7e8ed42f46 | ||
|
|
8d7b1a5b9b | ||
|
|
c6829a7d5e | ||
|
|
bef8fbf183 | ||
|
|
fca95f09cc | ||
|
|
4d6cdf8823 | ||
|
|
e34f52dcc5 | ||
|
|
91cc4e6f3d | ||
|
|
429b7dcb0e | ||
|
|
5ffa681a80 | ||
|
|
91364db608 | ||
|
|
2433c68f27 | ||
|
|
6ea254d9ac | ||
|
|
a4d1df8324 | ||
|
|
ba15611903 | ||
|
|
eda6113792 | ||
|
|
4f1509c836 | ||
|
|
dfcf32ef3e | ||
|
|
4a95257ee2 | ||
|
|
ee1e13ef61 | ||
|
|
c9c099ffc5 | ||
|
|
a80f4fb3c9 | ||
|
|
941f3ac47f | ||
|
|
80d3200d57 | ||
|
|
55926e25d2 | ||
|
|
a31f3ebad7 | ||
|
|
dc9c32c6ee | ||
|
|
dac3aac2a1 | ||
|
|
40196e823c | ||
|
|
7708b7e938 | ||
|
|
8bbc5a9bc6 | ||
|
|
4dbdfb1b77 | ||
|
|
05c51edce3 | ||
|
|
379e8cd95e | ||
|
|
9fd00d4427 | ||
|
|
ca114cf124 | ||
|
|
75128784db | ||
|
|
722f0e277e | ||
|
|
7089998966 | ||
|
|
52211ddfa4 | ||
|
|
2b8b762d35 | ||
|
|
04c228cd36 | ||
|
|
915f8e7700 | ||
|
|
218cea00b5 | ||
|
|
f38f5ea670 | ||
|
|
a9c08c8a9f | ||
|
|
9406c4f718 | ||
|
|
791d8beb06 | ||
|
|
57a9f3aca0 | ||
|
|
ae8bf187c8 | ||
|
|
88f2ab1f8e | ||
|
|
76f5136073 | ||
|
|
0f84cd8949 | ||
|
|
adfdf7606f | ||
|
|
ce671d4294 | ||
|
|
2e7cf7a9df | ||
|
|
f699d93e44 | ||
|
|
531ef4d0b2 | ||
|
|
4c6dbb6690 | ||
|
|
0beb60d1b5 | ||
|
|
893c9d7c42 | ||
|
|
b7a22d94db | ||
|
|
3d854756d1 | ||
|
|
251e7058f1 | ||
|
|
368eb24217 | ||
|
|
f89ef54b82 | ||
|
|
b9d9f8772b | ||
|
|
f2ee03e850 | ||
|
|
92e59a0072 | ||
|
|
5394c0b520 | ||
|
|
e4a9c6f8c2 | ||
|
|
e559d037c3 | ||
|
|
c9909a0869 | ||
|
|
988fc1bbfd | ||
|
|
1831bbcd70 | ||
|
|
ced9bbdbd5 | ||
|
|
15d57d937b | ||
|
|
555979869d | ||
|
|
4e6477297e | ||
|
|
e9e06fc53b | ||
|
|
ecb2818589 | ||
|
|
c442689573 | ||
|
|
88919f2634 | ||
|
|
1a1bc41283 | ||
|
|
4188caed33 | ||
|
|
50557d8bc5 | ||
|
|
0f1ef0406e | ||
|
|
ffc742b460 | ||
|
|
0c04cc6ce6 | ||
|
|
4e6dfed838 | ||
|
|
fdc0524ad2 | ||
|
|
096d5962a3 | ||
|
|
9ea36f94a7 | ||
|
|
2b66df992f | ||
|
|
ff3c31975e | ||
|
|
beaa5eca69 | ||
|
|
b98040faac | ||
|
|
fbbff0df61 | ||
|
|
07d5f393c0 | ||
|
|
8220b15a29 | ||
|
|
7cf638b2be | ||
|
|
9852c87be7 | ||
|
|
30fb8f31e3 | ||
|
|
ec4d875547 | ||
|
|
1268f65516 | ||
|
|
6551b2537a | ||
|
|
43eee63df2 | ||
|
|
4caf9f598a | ||
|
|
107eaca8f0 | ||
|
|
cc11848346 | ||
|
|
b138ccf625 | ||
|
|
27ff7baa6d | ||
|
|
c8ecd34bd3 | ||
|
|
d4a4eb3248 | ||
|
|
f782733397 | ||
|
|
7720eddc47 | ||
|
|
71170a373a | ||
|
|
8f080080a7 | ||
|
|
71b2958c14 | ||
|
|
f69aafcbfa | ||
|
|
5f6a423697 | ||
|
|
5d245927c4 | ||
|
|
17c96fb849 | ||
|
|
b7c28cdf36 | ||
|
|
d05aa0d350 | ||
|
|
875dc086bf | ||
|
|
4719ebd52a | ||
|
|
c8a436a7df | ||
|
|
8d6f4dd64e | ||
|
|
63486927d6 | ||
|
|
c36a70f8d3 | ||
|
|
39472d6874 | ||
|
|
6207e99cc0 | ||
|
|
bdc163199d | ||
|
|
e7a04ede65 | ||
|
|
f6aee4148d | ||
|
|
6cb30a6597 | ||
|
|
34fcfc8f30 | ||
|
|
bc89b33468 | ||
|
|
af9af347e6 | ||
|
|
6f46279b52 | ||
|
|
8f93ed8b8e | ||
|
|
df91363ff4 | ||
|
|
e40e59714d | ||
|
|
a43d95afc3 | ||
|
|
8fa4b5ab22 | ||
|
|
68a030145f | ||
|
|
4f48657623 | ||
|
|
3fc41f4255 | ||
|
|
ff2f7f6586 | ||
|
|
93f33d73d9 | ||
|
|
402c176277 | ||
|
|
79b05b4a66 | ||
|
|
fb9e37e71c | ||
|
|
cd7a741d53 | ||
|
|
5fdc90b4f9 | ||
|
|
e775840abb | ||
|
|
d7df54dfa1 | ||
|
|
646d165d37 | ||
|
|
ba0a80cca4 | ||
|
|
9d59858542 | ||
|
|
aaff0a5697 | ||
|
|
1bb7cee238 | ||
|
|
d330e662d3 | ||
|
|
b530ff76c4 | ||
|
|
b20aa33a04 | ||
|
|
e59a166445 | ||
|
|
980e713735 | ||
|
|
7a5228bbab | ||
|
|
5a26e5c2ee | ||
|
|
a64da4cd85 | ||
|
|
c2dccf17ce | ||
|
|
181d6bf1e4 | ||
|
|
e80bcfc586 | ||
|
|
a07be3b5dc | ||
|
|
af2666c83d | ||
|
|
cad9929b56 | ||
|
|
3b79da527d | ||
|
|
3eaba55823 | ||
|
|
e2eddb6362 | ||
|
|
de5b8160a2 | ||
|
|
83df969751 | ||
|
|
d885570dd8 | ||
|
|
f3de883e39 | ||
|
|
6da22d23a8 | ||
|
|
49461a43bb | ||
|
|
d4414f99af | ||
|
|
52f37b0a37 | ||
|
|
ce949e4029 | ||
|
|
19d3465a13 | ||
|
|
a0e1425c74 | ||
|
|
a5d8379b18 | ||
|
|
9ac487ea5b | ||
|
|
9cc9a93c3d | ||
|
|
4f2b0313de | ||
|
|
806894b24b | ||
|
|
cbb737f96b | ||
|
|
5407f8156a | ||
|
|
a0c2fd09ae | ||
|
|
48b744c8ba | ||
|
|
812b946d85 | ||
|
|
27eca2b470 | ||
|
|
ab6c88bebd | ||
|
|
d547e0552a | ||
|
|
03fc0b84cb | ||
|
|
b0825634ee | ||
|
|
9675e051bd | ||
|
|
981654a2aa | ||
|
|
8c97ea0da0 | ||
|
|
6c80911c90 | ||
|
|
740b5ea719 | ||
|
|
76c958fb7a | ||
|
|
28c53fd115 | ||
|
|
6d9fc8b3df | ||
|
|
a044f4eceb | ||
|
|
1f35efcd51 | ||
|
|
c587abddae | ||
|
|
d7f4c9a329 | ||
|
|
8a3e6f5781 | ||
|
|
cfbebc4989 | ||
|
|
99ec772d64 | ||
|
|
1f60b0bbcb | ||
|
|
c8fb9a5b09 | ||
|
|
d6f117c7bf | ||
|
|
a765668354 | ||
|
|
da053658e8 | ||
|
|
cb5bcd72e4 | ||
|
|
d4d53623ef | ||
|
|
8a31b008d2 | ||
|
|
fa4541b59f | ||
|
|
aae6b32828 | ||
|
|
7158d6735b | ||
|
|
f04fd16c9e | ||
|
|
f4b896d5e6 | ||
|
|
d2f814e6f8 | ||
|
|
87cec67e60 | ||
|
|
041c46f38e | ||
|
|
54a7628f75 | ||
|
|
7cbbc16390 | ||
|
|
14532bf6db | ||
|
|
7bfe18e718 | ||
|
|
07903a07a8 | ||
|
|
14fe3b2b98 | ||
|
|
fa2756ff3f | ||
|
|
9d4841b4dc | ||
|
|
4b74b08825 | ||
|
|
6457cfc958 | ||
|
|
30081c893b | ||
|
|
e69890a47e | ||
|
|
9d31162eec | ||
|
|
0031cc7418 | ||
|
|
57132892b5 | ||
|
|
be084d1570 | ||
|
|
03d29a9ebd | ||
|
|
e9a837b534 | ||
|
|
36c601f9fa | ||
|
|
af41234484 | ||
|
|
bcaf03a770 | ||
|
|
9429b009ce | ||
|
|
3395785cbc | ||
|
|
97c048c7af | ||
|
|
726eb2ffe6 | ||
|
|
a9f22d3a10 | ||
|
|
5a6437e371 | ||
|
|
4ffc98b371 | ||
|
|
16c73d4f91 | ||
|
|
96aff5230f | ||
|
|
b298571be8 | ||
|
|
7b7b10c9a5 | ||
|
|
56beed5ad5 | ||
|
|
8c8ef0ea53 | ||
|
|
90377d98c0 | ||
|
|
d167e474d8 | ||
|
|
0cfacfe057 | ||
|
|
fbda5231d0 | ||
|
|
bd4d333d59 | ||
|
|
07878207de | ||
|
|
b2352877b9 | ||
|
|
669ba9e8cb | ||
|
|
5fa8a76219 | ||
|
|
476dcf051c | ||
|
|
db96dbba6a | ||
|
|
e9c6509970 | ||
|
|
b26c7dfa8f | ||
|
|
3f251761d6 | ||
|
|
bc9d489ee8 | ||
|
|
271563ba9f | ||
|
|
82aba20e83 | ||
|
|
12f60c9be5 | ||
|
|
1c98a354c7 | ||
|
|
9ca7aee859 | ||
|
|
937b306128 | ||
|
|
69a2ab0ce2 | ||
|
|
b720711217 | ||
|
|
bf56348c62 | ||
|
|
f993817cb6 | ||
|
|
1c2b4794f3 | ||
|
|
523d210503 | ||
|
|
39da8bd636 | ||
|
|
fbea98bbe6 | ||
|
|
5d89f724f4 | ||
|
|
0313b6f3ab | ||
|
|
fe92111dc9 | ||
|
|
9a03bfb9e7 | ||
|
|
882db6acc9 | ||
|
|
5596468b03 | ||
|
|
eb66de1220 | ||
|
|
f97896f6a6 | ||
|
|
cfb47f880e | ||
|
|
eba87776a0 | ||
|
|
dda0e6d676 | ||
|
|
8d514644c1 | ||
|
|
0870a34391 | ||
|
|
38118986a3 | ||
|
|
1b4e5247ad | ||
|
|
3ef2eeef93 | ||
|
|
4d5682e79b | ||
|
|
24100d203f | ||
|
|
86946a2c21 | ||
|
|
54f3e4cc6f | ||
|
|
d9360b2d8c | ||
|
|
abb47118c0 | ||
|
|
9e4e55bb8d | ||
|
|
498f720507 | ||
|
|
b9bc9cb75e | ||
|
|
3b65917422 | ||
|
|
862dd78cd5 | ||
|
|
d0bc48d119 | ||
|
|
37462ba282 | ||
|
|
99e7f5862a | ||
|
|
048e529bb5 | ||
|
|
5b8b06f3a1 | ||
|
|
3c7d025802 | ||
|
|
4f8b46da14 | ||
|
|
0b386a882e | ||
|
|
6329d58a67 | ||
|
|
0444e17848 | ||
|
|
c3d039fe45 | ||
|
|
1d5d713854 | ||
|
|
4336c15ca4 | ||
|
|
aac644d15c | ||
|
|
ab3dde17c7 | ||
|
|
924ec33d73 | ||
|
|
d798e02126 | ||
|
|
af0d780ac0 | ||
|
|
45c2c863ef | ||
|
|
bc08aeb5b7 | ||
|
|
c32067890c | ||
|
|
83a180e3b8 | ||
|
|
3372e16409 | ||
|
|
a04fad9c1f | ||
|
|
7500797dd0 | ||
|
|
9427f4141f | ||
|
|
1440244c4d | ||
|
|
47c8b82cfa | ||
|
|
540f23fb41 | ||
|
|
323c9f2d4f | ||
|
|
1d648b525f | ||
|
|
11c2771451 | ||
|
|
1733756b4a | ||
|
|
edf1933800 | ||
|
|
38d3165d14 | ||
|
|
5766b307c1 | ||
|
|
6497703d3b | ||
|
|
60e48580ae | ||
|
|
08b52f3618 | ||
|
|
55001cc434 | ||
|
|
b84b864396 | ||
|
|
34835b8cdf | ||
|
|
df8bdbf5b3 | ||
|
|
e775a832cb | ||
|
|
b9c4bada23 | ||
|
|
97f35f7ee6 | ||
|
|
0f90a06028 | ||
|
|
db4f2e7696 | ||
|
|
69837b0515 | ||
|
|
9811c49288 | ||
|
|
d709c99641 | ||
|
|
a92666082f | ||
|
|
cc05b28a42 | ||
|
|
a2c9953096 | ||
|
|
75ef6934b4 | ||
|
|
aa4d3c039b | ||
|
|
a8a89dc9bd | ||
|
|
a0305a745a | ||
|
|
290ff54638 | ||
|
|
dc4decffe4 | ||
|
|
f06b702360 | ||
|
|
e2eca66db0 | ||
|
|
d20913023e | ||
|
|
f4466dc325 | ||
|
|
00e6ea5b3b | ||
|
|
50b0a3ea17 | ||
|
|
0072e3e5ba | ||
|
|
6f104c4ee6 | ||
|
|
c721580516 | ||
|
|
2238f1fafc | ||
|
|
00bc5a00c1 | ||
|
|
1fe1381fe2 | ||
|
|
6bc0470c69 | ||
|
|
a9ab25e9c2 | ||
|
|
fd5bc22a70 | ||
|
|
28fe1fafd6 | ||
|
|
6695138244 | ||
|
|
3fb19cbe96 | ||
|
|
3f8a17c3ea | ||
|
|
9fa97f27f0 | ||
|
|
0e98312d55 | ||
|
|
06057a1d35 | ||
|
|
4373fddaa8 | ||
|
|
c45511d9a5 | ||
|
|
7d2b2494e5 | ||
|
|
870a25dad1 | ||
|
|
d74dc2f4ca | ||
|
|
f8c9c535a0 | ||
|
|
d22322be86 | ||
|
|
e4e5d5c6d7 | ||
|
|
7e45f23b6c | ||
|
|
719f61f612 | ||
|
|
908edda14b | ||
|
|
74792a9c21 | ||
|
|
eb345b5ca2 | ||
|
|
23c2b4b204 | ||
|
|
095e616e4c | ||
|
|
89f4626fce | ||
|
|
c759d673f0 | ||
|
|
8d1be554d4 | ||
|
|
e931d06ed4 | ||
|
|
76b90f9bc0 | ||
|
|
b4e5fedb51 | ||
|
|
6a229aabaf | ||
|
|
841c7df2f3 | ||
|
|
dd85ce7cfe | ||
|
|
4c7d771bb3 | ||
|
|
c55bdba948 | ||
|
|
4f2dce0a03 | ||
|
|
450a992b26 | ||
|
|
4284b2402b | ||
|
|
50c88f82f4 | ||
|
|
7dff3dc658 | ||
|
|
adb3864ea9 | ||
|
|
d6330060dc | ||
|
|
2dcb35a34c | ||
|
|
624ba95792 | ||
|
|
ae24a80b95 | ||
|
|
e7f1b1cc32 | ||
|
|
e71940ecdc | ||
|
|
65a800701c | ||
|
|
36dc1fb3e9 | ||
|
|
99305b4185 | ||
|
|
bd2e8be186 | ||
|
|
dc4c6ceff5 | ||
|
|
10083ac807 | ||
|
|
11023c6400 | ||
|
|
6d31314df6 | ||
|
|
d5367194ab | ||
|
|
cf179b2f9c | ||
|
|
de82188663 | ||
|
|
4aefe775f9 | ||
|
|
8c06c98917 | ||
|
|
ee9f778725 | ||
|
|
ff0883c8e6 | ||
|
|
d4e0f05cef | ||
|
|
dfcb4fe286 | ||
|
|
d546d5d0a7 | ||
|
|
375dc86f0a | ||
|
|
d2f187f6c0 | ||
|
|
5393ee9ba9 | ||
|
|
d4d2948455 | ||
|
|
37617c04ad | ||
|
|
10e0153018 | ||
|
|
42cbe94af8 | ||
|
|
8f95e53d9d | ||
|
|
0dae21dcf1 | ||
|
|
912efeb5fd | ||
|
|
3d9307f96d | ||
|
|
873f4b080a | ||
|
|
e337a79e2e | ||
|
|
980ce62bf6 | ||
|
|
dccf757f56 | ||
|
|
c6e93cd3c8 | ||
|
|
4a6b88328e | ||
|
|
779a84cf0b | ||
|
|
aff409ba63 | ||
|
|
275167c0c4 | ||
|
|
19b2af7f57 | ||
|
|
779d5df264 | ||
|
|
5050b9ee97 | ||
|
|
23da4be6b1 | ||
|
|
299648b8e0 | ||
|
|
86fa0c87ae | ||
|
|
77cd52ca04 | ||
|
|
bed2a77232 | ||
|
|
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 | ||
|
|
1e58b35db5 | ||
|
|
964ec0d422 | ||
|
|
498bd18630 | ||
|
|
b530ef882a | ||
|
|
850d14064a | ||
|
|
273ada837b | ||
|
|
280b812e5b |
@@ -232,6 +232,15 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "1024mb",
|
||||||
|
"name": "1024mb",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/9301204?v=4",
|
||||||
|
"profile": "https://github.com/1024mb",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +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 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=opensign
|
|
||||||
@@ -3,24 +3,22 @@
|
|||||||
# Note that any data in staging will get cleared from time to time and you may have to sign up again and again
|
# Note that any data in staging will get cleared from time to time and you may have to sign up again and again
|
||||||
|
|
||||||
# Set it to the URL form where the app home page will be accessed
|
# Set it to the URL form where the app home page will be accessed
|
||||||
PUBLIC_URL=http://localhost:3000
|
PUBLIC_URL=https://localhost:3001
|
||||||
# Set it to true if you want to generate the Sourcemap for debugging
|
# Set it to true if you want to generate the Sourcemap for debugging
|
||||||
GENERATE_SOURCEMAP=false
|
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)
|
# 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
|
# 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.
|
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
|
||||||
REACT_APP_APPID=opensign
|
REACT_APP_APPID=opensign
|
||||||
|
|
||||||
|
|
||||||
# Backend ExpressJS config ****************************************************************************************************************************************************************************************
|
# 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=opensign
|
|
||||||
# Name of the app. It will be visible in the verification emails sent out.
|
# Name of the app. It will be visible in the verification emails sent out.
|
||||||
appName=open_sign_server
|
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.
|
# 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
|
MASTER_KEY=XnAadwKxxByMr
|
||||||
# Mongodb URI to connect to
|
# Mongodb URI to connect to
|
||||||
MONGODB_URI=mongodb://mongo: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.
|
# 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
|
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)
|
# 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)
|
||||||
@@ -40,13 +38,13 @@ DO_SECRET_ACCESS_KEY=
|
|||||||
# Digital ocean spaces region or AWS s3 region
|
# Digital ocean spaces region or AWS s3 region
|
||||||
DO_REGION=us-west
|
DO_REGION=us-west
|
||||||
# local storage
|
# 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) *********************************************************************************************************************
|
# 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=
|
MAILGUN_API_KEY=
|
||||||
MAILGUN_DOMAIN=mail.yourdomain.com
|
MAILGUN_DOMAIN=mail.yourdomain.com
|
||||||
MAILGUN_SENDER=postmaster@mail.yourdomain.com
|
MAILGUN_SENDER=postmaster@mail.yourdomain.com
|
||||||
SMTP_ENABLE=
|
SMTP_ENABLE=true
|
||||||
SMTP_HOST=smtp.yourhost.com
|
SMTP_HOST=smtp.yourhost.com
|
||||||
SMTP_PORT=443
|
SMTP_PORT=443
|
||||||
SMTP_USER_EMAIL=mailer@yourdomain.com
|
SMTP_USER_EMAIL=mailer@yourdomain.com
|
||||||
@@ -111,3 +109,6 @@ CRUxFgQUDYlgGVxSxuOknhQc256x3++7BDwwMTAhMAkGBSsOAwIaBQAEFFjASdYl
|
|||||||
|
|
||||||
# Provide Pass pharse of above PFX or p12 document
|
# Provide Pass pharse of above PFX or p12 document
|
||||||
PASS_PHRASE=opensign
|
PASS_PHRASE=opensign
|
||||||
|
|
||||||
|
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
|
||||||
|
APP_ID=opensign
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
name: ci
|
name: ci
|
||||||
|
|
||||||
|
# 👇 add this block
|
||||||
|
permissions:
|
||||||
|
contents: read # allow checkout & metadata-action to read repo
|
||||||
|
id-token: write # needed by docker/metadata-action v4
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
- 'main'
|
||||||
- 'staging'
|
- 'staging'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -10,9 +17,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- image: amolshejole/OpenSign
|
- image: opensign/opensign
|
||||||
dockerfile: apps/OpenSign/Dockerhubfile
|
dockerfile: apps/OpenSign/Dockerhubfile
|
||||||
- image: amolshejole/OpenSignServer
|
- image: opensign/opensignserver
|
||||||
dockerfile: apps/OpenSignServer/Dockerhubfile
|
dockerfile: apps/OpenSignServer/Dockerhubfile
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
|
|||||||
@@ -77,6 +77,6 @@ Engage with the community on our [Gitter Channel](https://gitter.im/OpenSignLabs
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
By contributing, you agree that your contributions will be licensed under its [MIT License](LICENSE).
|
By contributing, you agree that your contributions will be licensed under its [AGPL-V3 License](LICENSE).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{$HOST_URL} {
|
||||||
|
reverse_proxy client:3000
|
||||||
|
handle_path /api/* {
|
||||||
|
reverse_proxy server:8080
|
||||||
|
rewrite * {uri}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
* All content that resides under the "apps/OpenSignServer/cloud/customRoute" directory of this repository, if that directory exists, is licensed under the license defined in "apps/OpenSignServer/cloud/customRoute".
|
||||||
|
* All third party components incorporated into the OpenSign Software are licensed under the original license provided by the owner of the applicable component.
|
||||||
|
* Content outside of the above mentioned directories or restrictions above is available under the "GNU AFFERO GENERAL PUBLIC LICENSE" as defined below -
|
||||||
|
|
||||||
|
|
||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 19 November 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
build:
|
build:
|
||||||
|
@echo "Building with HOST_URL=${HOST_URL}"
|
||||||
cp .env.local_dev .env
|
cp .env.local_dev .env
|
||||||
cd apps/OpenSign && cp ../../.env.local_dev .env && npm install && npm run build
|
cd apps/OpenSign && cp ../../.env.local_dev .env && npm install && npm run build
|
||||||
docker compose up --build --force-recreate
|
HOST_URL=${HOST_URL} docker compose up --build --force-recreate
|
||||||
|
|
||||||
run:
|
run:
|
||||||
|
@echo "Building with HOST_URL=${HOST_URL}"
|
||||||
cp .env.local_dev .env
|
cp .env.local_dev .env
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
@@ -11,7 +11,9 @@
|
|||||||
|
|
||||||
<a href="https://www.opensignlabs.com/">Website</a>
|
<a href="https://www.opensignlabs.com/">Website</a>
|
||||||
<span> • </span>
|
<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>
|
<span> • </span>
|
||||||
<a href="https://www.opensignlabs.com/blog">Blog</a>
|
<a href="https://www.opensignlabs.com/blog">Blog</a>
|
||||||
<span> • </span>
|
<span> • </span>
|
||||||
@@ -22,7 +24,7 @@
|
|||||||
<a href="https://www.linkedin.com/company/opensign%E2%84%A2/about/">LinkedIn</a>
|
<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>
|
</div>
|
||||||
@@ -42,35 +44,44 @@ Please star ⭐ the repo to support us! 😀
|
|||||||
|
|
||||||
### Introduction
|
### Introduction
|
||||||
|
|
||||||
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 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
|
### Features
|
||||||
|
|
||||||
- **Secure PDF E-Signing:** With the help of robust encryption algorithms, OpenSign™ ensures maximum security, privacy & compatibility.
|
- **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 signing experience ever.
|
- **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.
|
- **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 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 in e-signature space.
|
- **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.
|
- **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. Not just this, OpenSign™ also allows signers to reject signing a document.
|
- **"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.
|
- **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.
|
- **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 & achieving your docs a breeze.
|
- **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.
|
- **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:** OpenSign™ API allows seamless integration into existing systems and software. You can generate an API key from the app and refer the [docs](https://docs.opensignlabs.com) to start integrating it in your existing applications.
|
- **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:** Seamless integrations with various Cloud storage systems, CRMs & enterprise platforms is available. We also have a Zapier integration that allows you to integrate it with virtually any application.
|
- **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 src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/b37ff443-7099-4273-9aeb-21c46d7154cf height='200'>
|
<img alt="Dash_board" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/6e7b96f7-3154-4c25-bc1e-caf33925cc47" height='200'>
|
||||||
<img src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/86db91b7-6c2f-4885-a33c-58f4fd35ec89 height='200'>
|
<img alt="Widgets" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/7edb65bd-c265-4b3d-9758-38e79766c5c2" height='200'>
|
||||||
<img src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/56835efd-c40e-42de-a206-20202c293876 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'>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
Please refer to the [Installation Guide](INSTALLATION.md) for detailed instructions on how to install OpenSign on your system.
|
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
|
||||||
|
```
|
||||||
|
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
|
||||||
|
```
|
||||||
|
Make sure that you have `Docker` and `git` installed before you run this command -
|
||||||
|
|
||||||
|
Please refer to the [Installation Guide](https://docs.opensignlabs.com/docs/self-host/docker/run-locally/) for detailed instructions on how to install OpenSign on your system.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -88,13 +99,13 @@ We welcome contributions from the open-source community. For more information on
|
|||||||
|
|
||||||
### License
|
### 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
|
### 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -137,6 +148,7 @@ We would like to thank all our contributors and users for their support and feed
|
|||||||
<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/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/daniel-mutwiri"><img src="https://avatars.githubusercontent.com/u/8936960?v=4?s=100" width="100px;" alt="Daniel Mutwiri"/><br /><sub><b>Daniel Mutwiri</b></sub></a><br /><a href="#code-daniel-mutwiri" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zathiel"><img src="https://avatars.githubusercontent.com/u/26553418?v=4?s=100" width="100px;" alt="Zathiel"/><br /><sub><b>Zathiel</b></sub></a><br /><a href="#code-Zathiel" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zathiel"><img src="https://avatars.githubusercontent.com/u/26553418?v=4?s=100" width="100px;" alt="Zathiel"/><br /><sub><b>Zathiel</b></sub></a><br /><a href="#code-Zathiel" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/1024mb"><img src="https://avatars.githubusercontent.com/u/9301204?v=4?s=100" width="100px;" alt="1024mb"/><br /><sub><b>1024mb</b></sub></a><br /><a href="#translation-1024mb" title="Translation">🌍</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -146,3 +158,5 @@ We would like to thank all our contributors and users for their support and feed
|
|||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
|
This project is tested with BrowserStack.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1 @@
|
|||||||
The community is working hard to put together a great usage guide.
|
Please visit the official documentation [here](https://docs.opensignlabs.com).
|
||||||
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")
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"presets": ["@babel/preset-env", "@babel/preset-react"]
|
||||||
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# 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 ./package*.json ./
|
|
||||||
|
|
||||||
# Install application dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# Copy the current directory contents into the container
|
|
||||||
COPY ./ .
|
|
||||||
COPY ./.husky .
|
|
||||||
|
|
||||||
# Make port 3000 available to the world outside this container
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
# Define environment variables if needed
|
|
||||||
# ENV NODE_ENV production
|
|
||||||
|
|
||||||
# Run the application
|
|
||||||
ENTRYPOINT npm run start
|
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Use an official Node runtime as the base image
|
# Use an official Node runtime as the base image
|
||||||
FROM node:18
|
FROM node:22.14.0
|
||||||
|
|
||||||
# Set the working directory inside the container
|
# Set the working directory inside the container
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
@@ -13,15 +13,24 @@ RUN npm install
|
|||||||
# Copy the current directory contents into the container
|
# Copy the current directory contents into the container
|
||||||
COPY apps/OpenSign/ .
|
COPY apps/OpenSign/ .
|
||||||
COPY apps/OpenSign/.husky .
|
COPY apps/OpenSign/.husky .
|
||||||
|
COPY apps/OpenSign/entrypoint.sh .
|
||||||
|
|
||||||
|
# make the entrypoint.sh file executable
|
||||||
|
RUN chmod +x entrypoint.sh
|
||||||
|
|
||||||
|
# Define environment variables if needed
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV GENERATE_SOURCEMAP=false
|
||||||
# build
|
# build
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
# Inject env.js loader into index.html
|
||||||
|
RUN sed -i '/<head>/a\<script src="/env.js"></script>' build/index.html
|
||||||
|
|
||||||
# Make port 3000 available to the world outside this container
|
# Make port 3000 available to the world outside this container
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
# Define environment variables if needed
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
# ENV NODE_ENV production
|
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
CMD ["npm", "start"]
|
CMD ["npm", "start"]
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: ["@babel/preset-env", "@babel/preset-react"]
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
ENV_FILE=./build/env.js
|
||||||
|
DOTENV_FILE=./.env.prod # ✅ use .env.prod
|
||||||
|
|
||||||
|
echo "Generating runtime env file at $ENV_FILE..."
|
||||||
|
|
||||||
|
echo "window.RUNTIME_ENV = {" > $ENV_FILE
|
||||||
|
|
||||||
|
# List of keys to include
|
||||||
|
RUNTIME_KEYS="REACT_APP_SERVERURL"
|
||||||
|
|
||||||
|
for key in $RUNTIME_KEYS; do
|
||||||
|
# First check docker env (-e), fallback to .env file
|
||||||
|
value=$(printenv "$key")
|
||||||
|
|
||||||
|
if [ -z "$value" ] && [ -f "$DOTENV_FILE" ]; then
|
||||||
|
# fallback: read from .env
|
||||||
|
value=$(grep "^$key=" "$DOTENV_FILE" | cut -d '=' -f2- | tr -d '\r\n' | sed 's/"/\\"/g')
|
||||||
|
else
|
||||||
|
value=$(echo "$value" | sed 's/"/\\"/g')
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " $key: \"$value\"," >> $ENV_FILE
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "};" >> $ENV_FILE
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="opensigncss">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||||
|
<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="/logo192.png" /> -->
|
||||||
|
|
||||||
|
<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>...Loading</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root" style="touch-action:pan-x pan-y;"></div>
|
||||||
|
<script type="module" src="/src/index.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,61 +1,71 @@
|
|||||||
{
|
{
|
||||||
"name": "open_sign",
|
"name": "open_sign",
|
||||||
"version": "0.1.0",
|
"version": "2.21.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formkit/auto-animate": "^0.8.2",
|
"@formkit/auto-animate": "^0.8.2",
|
||||||
"@radix-ui/themes": "^2.0.3",
|
"@lottiefiles/dotlottie-react": "^0.13.5",
|
||||||
"@react-pdf/renderer": "^3.4.4",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@reduxjs/toolkit": "^2.2.1",
|
"@radix-ui/themes": "^3.2.1",
|
||||||
"axios": "^1.6.8",
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
|
"@imgly/background-removal": "^1.6.0",
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"date-fns-tz": "^3.2.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
"i18next": "^23.16.8",
|
||||||
|
"i18next-browser-languagedetector": "^8.1.0",
|
||||||
|
"i18next-http-backend": "^3.0.2",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"parse": "^4.3.1",
|
"parse": "^6.1.1",
|
||||||
|
"pkijs": "^3.0.8",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"print-js": "^1.6.0",
|
"print-js": "^1.6.0",
|
||||||
"radix-ui": "^1.0.1",
|
"prismjs": "^1.30.0",
|
||||||
"react": "^18.2.0",
|
"radix-ui": "^1.4.2",
|
||||||
"react-bootstrap": "^2.10.2",
|
"react": "^18.3.1",
|
||||||
"react-cookie": "^7.1.4",
|
"react-bootstrap": "^2.10.10",
|
||||||
"react-datepicker": "^6.4.0",
|
"react-confetti": "^6.4.0",
|
||||||
|
"react-datepicker": "^8.3.0",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dnd-multi-backend": "^8.0.3",
|
"react-dnd-multi-backend": "^9.0.0",
|
||||||
"react-dnd-touch-backend": "^16.0.1",
|
"react-dnd-touch-backend": "^16.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.3.1",
|
||||||
"react-gtm-module": "^2.0.11",
|
"react-gtm-module": "^2.0.11",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
|
"react-i18next": "^15.5.1",
|
||||||
"react-konva": "^18.2.10",
|
"react-konva": "^18.2.10",
|
||||||
"react-modal": "^3.16.1",
|
"react-pdf": "^9.2.1",
|
||||||
"react-pdf": "^7.7.3",
|
"react-quill-new": "^3.4.6",
|
||||||
"react-quill": "^2.0.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-redux": "^9.1.0",
|
"react-rnd": "^10.5.2",
|
||||||
"react-rnd": "^10.4.10",
|
"react-router": "^7.6.0",
|
||||||
"react-router-dom": "^6.22.3",
|
|
||||||
"react-scripts": "5.0.1",
|
|
||||||
"react-scrollbars-custom": "^4.1.1",
|
"react-scrollbars-custom": "^4.1.1",
|
||||||
"react-select": "^5.8.0",
|
"react-select": "^5.10.1",
|
||||||
"react-signature-canvas": "^1.0.6",
|
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||||
"react-tooltip": "^5.26.3",
|
"react-timezone-select": "^3.2.8",
|
||||||
"react-web-share": "^2.0.2",
|
"react-tooltip": "^5.28.1",
|
||||||
"reactour": "^1.19.3",
|
"reactour": "^1.19.4",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"redux-thunk": "^3.1.0",
|
"regex-parser": "^2.3.1",
|
||||||
"regex-parser": "^2.3.0",
|
"serve": "^14.2.4",
|
||||||
"serve": "^14.2.1",
|
"styled-components": "^5.3.11",
|
||||||
"styled-components": "^4.4.1",
|
"web-vitals": "^5.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"build": "npm run version && NODE_OPTIONS=\"--max-old-space-size=8192\" vite build",
|
||||||
|
"start-dev": "vite",
|
||||||
|
"dev": "vite",
|
||||||
|
"preview": "vite preview",
|
||||||
"start": "serve -s build",
|
"start": "serve -s build",
|
||||||
"start-dev": "react-scripts start",
|
|
||||||
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
|
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
|
||||||
"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\"",
|
"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": "npm run version && react-scripts build",
|
"build-win": "npm run version-win && vite build",
|
||||||
"build-win": "npm run version-win && react-scripts build",
|
"test": "vitest run",
|
||||||
"test": "react-scripts test",
|
"test:watch": "vitest",
|
||||||
"eject": "react-scripts eject",
|
|
||||||
"release": "standard-version",
|
"release": "standard-version",
|
||||||
"commit": "cz"
|
"commit": "cz"
|
||||||
},
|
},
|
||||||
@@ -83,16 +93,38 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/runtime-corejs2": "^7.24.5",
|
"@babel/core": "^7.27.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"@babel/preset-env": "^7.27.2",
|
||||||
"commitizen": "^4.3.0",
|
"@babel/preset-react": "^7.27.1",
|
||||||
"eslint": "^8.57.0",
|
"@babel/runtime-corejs2": "^7.27.1",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"eslint-plugin-react": "^7.34.1",
|
"@testing-library/react": "^16.3.0",
|
||||||
"lint-staged": "^15.2.2",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"postcss": "^8.4.38",
|
"@types/react": "^18.3.22",
|
||||||
"prettier": "^2.8.8",
|
"@vitejs/plugin-react": "^4.4.1",
|
||||||
"pretty-quick": "^3.3.1",
|
"@vitejs/plugin-react-swc": "^3.9.0",
|
||||||
"tailwindcss": "^3.4.1"
|
"autoprefixer": "^10.4.21",
|
||||||
|
"babel-loader": "^10.0.0",
|
||||||
|
"commitizen": "^4.3.1",
|
||||||
|
"concurrently": "^9.1.2",
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
|
"daisyui": "^4.12.24",
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
|
"eslint": "^9.27.0",
|
||||||
|
"eslint-plugin-prettier": "^5.4.0",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
"lint-staged": "^16.0.0",
|
||||||
|
"postcss": "^8.5.3",
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
"pretty-quick": "^4.1.1",
|
||||||
|
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"vite": "^6.3.5",
|
||||||
|
"vite-plugin-svgr": "^4.3.0",
|
||||||
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
|
"vitest": "^3.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || 22"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,53 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="/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" />
|
|
||||||
<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="/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="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
|
|
||||||
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 86 KiB |
@@ -0,0 +1 @@
|
|||||||
|
import "@testing-library/jest-dom";
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useState, useEffect, lazy } from "react";
|
import { useState, useEffect, lazy } from "react";
|
||||||
import { Routes, Route, BrowserRouter } from "react-router-dom";
|
import { Routes, Route, BrowserRouter } from "react-router";
|
||||||
import { pdfjs } from "react-pdf";
|
import { pdfjs } from "react-pdf";
|
||||||
import Login from "./pages/Login";
|
|
||||||
import Form from "./pages/Form";
|
import Form from "./pages/Form";
|
||||||
import Report from "./pages/Report";
|
import Report from "./pages/Report";
|
||||||
import Dashboard from "./pages/Dashboard";
|
import Dashboard from "./pages/Dashboard";
|
||||||
@@ -15,38 +14,28 @@ import DraftDocument from "./components/pdf/DraftDocument";
|
|||||||
import PlaceHolderSign from "./pages/PlaceHolderSign";
|
import PlaceHolderSign from "./pages/PlaceHolderSign";
|
||||||
import PdfRequestFiles from "./pages/PdfRequestFiles";
|
import PdfRequestFiles from "./pages/PdfRequestFiles";
|
||||||
import LazyPage from "./primitives/LazyPage";
|
import LazyPage from "./primitives/LazyPage";
|
||||||
import { isEnableSubscription } from "./constant/const";
|
import Loader from "./primitives/Loader";
|
||||||
|
import UserList from "./pages/UserList";
|
||||||
|
import { serverUrl_fn } from "./constant/appinfo";
|
||||||
|
import DocSuccessPage from "./pages/DocSuccessPage";
|
||||||
|
import ValidateSession from "./primitives/ValidateSession";
|
||||||
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
|
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
|
||||||
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
|
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
|
||||||
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
|
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 ChangePassword = lazy(() => import("./pages/ChangePassword"));
|
||||||
const UserProfile = lazy(() => import("./pages/UserProfile"));
|
const UserProfile = lazy(() => import("./pages/UserProfile"));
|
||||||
const Signup = lazy(() => import("./pages/Signup"));
|
|
||||||
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
|
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
|
||||||
const ManageSign = lazy(() => import("./pages/Managesign"));
|
const ManageSign = lazy(() => import("./pages/Managesign"));
|
||||||
const GenerateToken = lazy(() => import("./pages/GenerateToken"));
|
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
|
||||||
const Webhook = lazy(() => import("./pages/Webhook"));
|
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
|
||||||
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
|
const Preferences = lazy(() => import("./pages/Preferences"));
|
||||||
|
const Login = lazy(() => import("./pages/Login"));
|
||||||
|
|
||||||
const Loader = () => {
|
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
|
||||||
|
const AppLoader = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex justify-center items-center h-[100vh]">
|
||||||
style={{
|
<Loader />
|
||||||
height: "100vh",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: "45px",
|
|
||||||
color: "#3dd3e0"
|
|
||||||
}}
|
|
||||||
className="loader-37"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -60,14 +49,10 @@ function App() {
|
|||||||
const appId = process.env.REACT_APP_APPID
|
const appId = process.env.REACT_APP_APPID
|
||||||
? process.env.REACT_APP_APPID
|
? process.env.REACT_APP_APPID
|
||||||
: "opensign";
|
: "opensign";
|
||||||
const baseurl = process.env.REACT_APP_SERVERURL
|
const baseurl = serverUrl_fn();
|
||||||
? process.env.REACT_APP_SERVERURL
|
|
||||||
: window.location.origin + "/api/app";
|
|
||||||
const appName = "contracts";
|
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("baseUrl", `${baseurl}/`);
|
localStorage.setItem("baseUrl", `${baseurl}/`);
|
||||||
localStorage.setItem("parseAppId", appId);
|
localStorage.setItem("parseAppId", appId);
|
||||||
localStorage.setItem("domain", appName);
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("err ", error);
|
console.log("err ", error);
|
||||||
@@ -75,15 +60,22 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-[#eef1f5]">
|
<div className="bg-base-200">
|
||||||
{isloading ? (
|
{isloading ? (
|
||||||
<Loader />
|
<AppLoader />
|
||||||
) : (
|
) : (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<ValidateRoute />}>
|
<Route element={<ValidateRoute />}>
|
||||||
<Route exact path="/" element={<Login />} />
|
<Route exact path="/" element={<LazyPage Page={Login} />} />
|
||||||
<Route path="/signup" element={<LazyPage Page={Signup} />} />
|
<Route
|
||||||
|
path="/addadmin"
|
||||||
|
element={<LazyPage Page={AddAdmin} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/upgrade-2.1"
|
||||||
|
element={<LazyPage Page={UpdateExistUserAdmin} />}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route element={<Validate />}>
|
<Route element={<Validate />}>
|
||||||
<Route
|
<Route
|
||||||
@@ -114,27 +106,21 @@ function App() {
|
|||||||
element={<LazyPage Page={GuestLogin} />}
|
element={<LazyPage Page={GuestLogin} />}
|
||||||
/>
|
/>
|
||||||
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
|
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
|
||||||
<Route
|
|
||||||
path="/forgetpassword"
|
|
||||||
element={<LazyPage Page={ForgetPassword} />}
|
|
||||||
/>
|
|
||||||
{isEnableSubscription && (
|
|
||||||
<>
|
|
||||||
<Route
|
|
||||||
path="/pgsignup"
|
|
||||||
element={<LazyPage Page={Pgsignup} />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/subscription"
|
|
||||||
element={<LazyPage Page={Subscriptions} />}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Route element={<HomeLayout />}>
|
|
||||||
<Route
|
<Route
|
||||||
path="/changepassword"
|
path="/forgetpassword"
|
||||||
element={<LazyPage Page={ChangePassword} />}
|
element={<LazyPage Page={ForgetPassword} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
<ValidateSession>
|
||||||
|
<HomeLayout />
|
||||||
|
</ValidateSession>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route
|
||||||
|
path="/changepassword"
|
||||||
|
element={<LazyPage Page={ChangePassword} />}
|
||||||
|
/>
|
||||||
<Route path="/form/:id" element={<Form />} />
|
<Route path="/form/:id" element={<Form />} />
|
||||||
<Route path="/report/:id" element={<Report />} />
|
<Route path="/report/:id" element={<Report />} />
|
||||||
<Route path="/dashboard/:id" element={<Dashboard />} />
|
<Route path="/dashboard/:id" element={<Dashboard />} />
|
||||||
@@ -143,18 +129,13 @@ function App() {
|
|||||||
element={<LazyPage Page={UserProfile} />}
|
element={<LazyPage Page={UserProfile} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/opensigndrive"
|
path="/drive"
|
||||||
element={<LazyPage Page={Opensigndrive} />}
|
element={<LazyPage Page={Opensigndrive} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/managesign"
|
path="/managesign"
|
||||||
element={<LazyPage Page={ManageSign} />}
|
element={<LazyPage Page={ManageSign} />}
|
||||||
/>
|
/>
|
||||||
<Route
|
|
||||||
path="/generatetoken"
|
|
||||||
element={<LazyPage Page={GenerateToken} />}
|
|
||||||
/>
|
|
||||||
<Route path="/webhook" element={<LazyPage Page={Webhook} />} />
|
|
||||||
<Route
|
<Route
|
||||||
path="/template/:templateId"
|
path="/template/:templateId"
|
||||||
element={<TemplatePlaceholder />}
|
element={<TemplatePlaceholder />}
|
||||||
@@ -180,7 +161,17 @@ function App() {
|
|||||||
path="/recipientSignPdf/:docId/:contactBookId"
|
path="/recipientSignPdf/:docId/:contactBookId"
|
||||||
element={<PdfRequestFiles />}
|
element={<PdfRequestFiles />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/recipientSignPdf/:docId"
|
||||||
|
element={<PdfRequestFiles />}
|
||||||
|
/>
|
||||||
|
<Route path="/users" element={<UserList />} />
|
||||||
|
<Route
|
||||||
|
path="/preferences"
|
||||||
|
element={<LazyPage Page={Preferences} />}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/success" element={<DocSuccessPage />} />
|
||||||
<Route path="*" element={<PageNotFound />} />
|
<Route path="*" element={<PageNotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
Before Width: | Height: | Size: 805 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 234 KiB |
|
After Width: | Height: | Size: 41 KiB |
@@ -1,317 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import Parse from "parse";
|
|
||||||
import axios from "axios";
|
|
||||||
import { modalCancelBtnColor, modalSubmitBtnColor } from "../constant/const";
|
|
||||||
|
|
||||||
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);
|
|
||||||
const parseBaseUrl = localStorage.getItem("baseUrl");
|
|
||||||
const parseAppId = localStorage.getItem("parseAppId");
|
|
||||||
|
|
||||||
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 contactQuery = new Parse.Object("contracts_Contactbook");
|
|
||||||
contactQuery.set("Name", name);
|
|
||||||
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("phone", phone);
|
|
||||||
_user.set("password", phone);
|
|
||||||
|
|
||||||
const user = await _user.save();
|
|
||||||
if (user) {
|
|
||||||
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
|
|
||||||
const headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Parse-Application-Id": parseAppId,
|
|
||||||
sessionToken: localStorage.getItem("accesstoken")
|
|
||||||
};
|
|
||||||
const body = {
|
|
||||||
appName: localStorage.getItem("_appName"),
|
|
||||||
roleName: "contracts_Guest",
|
|
||||||
userId: user.id
|
|
||||||
};
|
|
||||||
await axios.post(roleurl, body, { headers: headers });
|
|
||||||
const currentUser = Parse.User.current();
|
|
||||||
contactQuery.set(
|
|
||||||
"CreatedBy",
|
|
||||||
Parse.User.createWithoutData(currentUser.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
contactQuery.set("UserId", user);
|
|
||||||
const acl = new Parse.ACL();
|
|
||||||
acl.setPublicReadAccess(true);
|
|
||||||
acl.setPublicWriteAccess(true);
|
|
||||||
acl.setReadAccess(currentUser.id, true);
|
|
||||||
acl.setWriteAccess(currentUser.id, true);
|
|
||||||
|
|
||||||
contactQuery.setACL(acl);
|
|
||||||
|
|
||||||
const res = await contactQuery.save();
|
|
||||||
|
|
||||||
const parseData = JSON.parse(JSON.stringify(res));
|
|
||||||
if (props.details) {
|
|
||||||
props.details({
|
|
||||||
value: parseData[props.valueKey],
|
|
||||||
label: parseData[props.displayKey]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (props.closePopup) {
|
|
||||||
props.closePopup();
|
|
||||||
}
|
|
||||||
if (props.handleUserData) {
|
|
||||||
props.handleUserData(parseData);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoader(false);
|
|
||||||
// Reset the form fields
|
|
||||||
setAddYourself(false);
|
|
||||||
setName("");
|
|
||||||
setPhone("");
|
|
||||||
setEmail("");
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("err ", err);
|
|
||||||
if (err.code === 202) {
|
|
||||||
const params = { email: email };
|
|
||||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
|
||||||
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
|
|
||||||
const headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Parse-Application-Id": parseAppId,
|
|
||||||
sessionToken: localStorage.getItem("accesstoken")
|
|
||||||
};
|
|
||||||
const body = {
|
|
||||||
appName: localStorage.getItem("_appName"),
|
|
||||||
roleName: "contracts_Guest",
|
|
||||||
userId: userRes.id
|
|
||||||
};
|
|
||||||
await axios.post(roleurl, body, { headers: headers });
|
|
||||||
const currentUser = Parse.User.current();
|
|
||||||
contactQuery.set(
|
|
||||||
"CreatedBy",
|
|
||||||
Parse.User.createWithoutData(currentUser.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
contactQuery.set("UserId", {
|
|
||||||
__type: "Pointer",
|
|
||||||
className: "_User",
|
|
||||||
objectId: userRes.id
|
|
||||||
});
|
|
||||||
const acl = new Parse.ACL();
|
|
||||||
acl.setPublicReadAccess(true);
|
|
||||||
acl.setPublicWriteAccess(true);
|
|
||||||
acl.setReadAccess(currentUser.id, true);
|
|
||||||
acl.setWriteAccess(currentUser.id, true);
|
|
||||||
|
|
||||||
contactQuery.setACL(acl);
|
|
||||||
const res = await contactQuery.save();
|
|
||||||
|
|
||||||
const parseData = JSON.parse(JSON.stringify(res));
|
|
||||||
if (props.details) {
|
|
||||||
props.details({
|
|
||||||
value: parseData[props.valueKey],
|
|
||||||
label: parseData[props.displayKey]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (props.closePopup) {
|
|
||||||
props.closePopup();
|
|
||||||
}
|
|
||||||
if (props.handleUserData) {
|
|
||||||
props.handleUserData(parseData);
|
|
||||||
}
|
|
||||||
setIsLoader(false);
|
|
||||||
// Reset the form fields
|
|
||||||
setAddYourself(false);
|
|
||||||
setName("");
|
|
||||||
setPhone("");
|
|
||||||
setEmail("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// console.log("err", err);
|
|
||||||
setIsLoader(false);
|
|
||||||
alert("something went wrong!");
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
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">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: "45px",
|
|
||||||
color: "#3dd3e0"
|
|
||||||
}}
|
|
||||||
className="loader-37"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="w-full mx-auto p-2">
|
|
||||||
{isUserExist && (
|
|
||||||
<div className="mb-3">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="addYourself"
|
|
||||||
checked={addYourself}
|
|
||||||
onChange={handleAddYourselfChange}
|
|
||||||
className="form-checkbox"
|
|
||||||
/>
|
|
||||||
<label htmlFor="addYourself" className="ml-2 text-gray-700">
|
|
||||||
Add Yourself
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label
|
|
||||||
htmlFor="name"
|
|
||||||
className="block text-xs text-gray-700 font-semibold"
|
|
||||||
>
|
|
||||||
Name
|
|
||||||
<span style={{ color: "red", fontSize: 13 }}> *</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="name"
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
required
|
|
||||||
disabled={addYourself}
|
|
||||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label
|
|
||||||
htmlFor="email"
|
|
||||||
className="block text-xs text-gray-700 font-semibold"
|
|
||||||
>
|
|
||||||
Email
|
|
||||||
<span style={{ color: "red", fontSize: 13 }}> *</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
value={email}
|
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
|
||||||
required
|
|
||||||
disabled={addYourself}
|
|
||||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label
|
|
||||||
htmlFor="phone"
|
|
||||||
className="block text-xs text-gray-700 font-semibold"
|
|
||||||
>
|
|
||||||
Phone
|
|
||||||
<span style={{ color: "red", fontSize: 13 }}> *</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="phone"
|
|
||||||
value={phone}
|
|
||||||
onChange={(e) => setPhone(e.target.value)}
|
|
||||||
required
|
|
||||||
disabled={addYourself}
|
|
||||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-4 flex justify-start">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
style={{ backgroundColor: modalSubmitBtnColor }}
|
|
||||||
className="mr-2 px-[20px] py-1.5 text-white rounded shadow-md text-center focus:outline-none "
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleReset()}
|
|
||||||
style={{ backgroundColor: modalCancelBtnColor }}
|
|
||||||
className="px-4 py-1.5 text-black border-[1px] border-[#ccc] shadow-md rounded focus:outline-none"
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddSigner;
|
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import Parse from "parse";
|
||||||
|
import Loader from "../primitives/Loader";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { emailRegex } from "../constant/const";
|
||||||
|
|
||||||
|
const AddSigner = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
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?.toLowerCase()?.replace(/\s/g, ""));
|
||||||
|
}
|
||||||
|
}, [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();
|
||||||
|
if (!emailRegex.test(email)) {
|
||||||
|
alert("Please enter a valid email address.");
|
||||||
|
} else {
|
||||||
|
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],
|
||||||
|
email: parseData?.Email
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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],
|
||||||
|
email: parseData?.Email
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (props.closePopup) {
|
||||||
|
props.closePopup();
|
||||||
|
}
|
||||||
|
if (props.handleUserData) {
|
||||||
|
props.handleUserData(parseData);
|
||||||
|
}
|
||||||
|
setIsLoader(false);
|
||||||
|
// Reset the form fields
|
||||||
|
setAddYourself(false);
|
||||||
|
setName("");
|
||||||
|
setPhone("");
|
||||||
|
setEmail("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(t("add-signer-alert"));
|
||||||
|
setIsLoader(false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("err in fetch contact", err);
|
||||||
|
setIsLoader(false);
|
||||||
|
alert(t("something-went-wrong-mssg"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsLoader(false);
|
||||||
|
alert(t("something-went-wrong-mssg"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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">
|
||||||
|
<p className="mb-3 text-xs text-gray-500">{t("add-signer-note")}</p>
|
||||||
|
{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">
|
||||||
|
{t("add-yourself")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="name" className="block text-xs font-semibold">
|
||||||
|
{t("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}
|
||||||
|
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="email" className="block text-xs font-semibold">
|
||||||
|
{t("email")}
|
||||||
|
<span className="text-[red] text-[13px]"> *</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEmail(e.target.value?.toLowerCase()?.replace(/\s/g, ""))
|
||||||
|
}
|
||||||
|
required
|
||||||
|
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
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">
|
||||||
|
{t("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"
|
||||||
|
placeholder={t("phone-optional")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex gap-x-2 justify-start">
|
||||||
|
<button type="submit" className="op-btn op-btn-primary">
|
||||||
|
{t("submit")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleReset()}
|
||||||
|
className="op-btn op-btn-secondary"
|
||||||
|
>
|
||||||
|
{t("reset")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddSigner;
|
||||||
@@ -1,281 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import Parse from "parse";
|
|
||||||
import axios from "axios";
|
|
||||||
import Title from "./Title";
|
|
||||||
import Alert from "../primitives/Alert";
|
|
||||||
|
|
||||||
const AddUser = (props) => {
|
|
||||||
const [name, setName] = useState("");
|
|
||||||
const [phone, setPhone] = useState("");
|
|
||||||
const [email, setEmail] = useState("");
|
|
||||||
const [isLoader, setIsLoader] = useState(false);
|
|
||||||
const [isUserExist, setIsUserExist] = useState(false);
|
|
||||||
const parseBaseUrl = localStorage.getItem("baseUrl");
|
|
||||||
const parseAppId = localStorage.getItem("parseAppId");
|
|
||||||
|
|
||||||
const checkUserExist = async () => {
|
|
||||||
try {
|
|
||||||
const res = await Parse.Cloud.run("getUserDetails", {
|
|
||||||
email: email,
|
|
||||||
userId: Parse.User.current().id
|
|
||||||
});
|
|
||||||
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();
|
|
||||||
setIsLoader(true);
|
|
||||||
const res = await checkUserExist();
|
|
||||||
if (res) {
|
|
||||||
setIsUserExist(true);
|
|
||||||
setIsLoader(false);
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsUserExist(false);
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const contactQuery = new Parse.Object("contracts_Users");
|
|
||||||
contactQuery.set("Name", name);
|
|
||||||
contactQuery.set("Phone", phone);
|
|
||||||
contactQuery.set("Email", email);
|
|
||||||
contactQuery.set("UserRole", "contracts_User");
|
|
||||||
|
|
||||||
if (localStorage.getItem("TenantId")) {
|
|
||||||
contactQuery.set("TenantId", {
|
|
||||||
__type: "Pointer",
|
|
||||||
className: "partners_Tenant",
|
|
||||||
objectId: localStorage.getItem("TenantId")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const _users = Parse.Object.extend("User");
|
|
||||||
const _user = new _users();
|
|
||||||
_user.set("name", name);
|
|
||||||
_user.set("username", email);
|
|
||||||
_user.set("email", email);
|
|
||||||
_user.set("phone", phone);
|
|
||||||
_user.set("password", phone);
|
|
||||||
|
|
||||||
const user = await _user.save();
|
|
||||||
if (user) {
|
|
||||||
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
|
|
||||||
const headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Parse-Application-Id": parseAppId,
|
|
||||||
sessionToken: localStorage.getItem("accesstoken")
|
|
||||||
};
|
|
||||||
const body = {
|
|
||||||
appName: localStorage.getItem("_appName"),
|
|
||||||
roleName: "contracts_User",
|
|
||||||
userId: user.id
|
|
||||||
};
|
|
||||||
await axios.post(roleurl, body, { headers: headers });
|
|
||||||
const currentUser = Parse.User.current();
|
|
||||||
contactQuery.set(
|
|
||||||
"CreatedBy",
|
|
||||||
Parse.User.createWithoutData(currentUser.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
contactQuery.set("UserId", user);
|
|
||||||
const acl = new Parse.ACL();
|
|
||||||
acl.setPublicReadAccess(true);
|
|
||||||
acl.setPublicWriteAccess(true);
|
|
||||||
acl.setReadAccess(currentUser.id, true);
|
|
||||||
acl.setWriteAccess(currentUser.id, true);
|
|
||||||
|
|
||||||
contactQuery.setACL(acl);
|
|
||||||
|
|
||||||
const res = await contactQuery.save();
|
|
||||||
|
|
||||||
const parseData = JSON.parse(JSON.stringify(res));
|
|
||||||
if (props.details) {
|
|
||||||
props.details({
|
|
||||||
value: parseData[props.valueKey],
|
|
||||||
label: parseData[props.displayKey]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (props.closePopup) {
|
|
||||||
props.closePopup();
|
|
||||||
}
|
|
||||||
if (props.handleUserData) {
|
|
||||||
props.handleUserData(parseData);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoader(false);
|
|
||||||
|
|
||||||
setName("");
|
|
||||||
setPhone("");
|
|
||||||
setEmail("");
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("err ", err);
|
|
||||||
if (err.code === 202) {
|
|
||||||
const params = { email: email };
|
|
||||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
|
||||||
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
|
|
||||||
const headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Parse-Application-Id": parseAppId,
|
|
||||||
sessionToken: localStorage.getItem("accesstoken")
|
|
||||||
};
|
|
||||||
const body = {
|
|
||||||
appName: localStorage.getItem("_appName"),
|
|
||||||
roleName: "contracts_User",
|
|
||||||
userId: userRes.id
|
|
||||||
};
|
|
||||||
await axios.post(roleurl, body, { headers: headers });
|
|
||||||
const currentUser = Parse.User.current();
|
|
||||||
contactQuery.set(
|
|
||||||
"CreatedBy",
|
|
||||||
Parse.User.createWithoutData(currentUser.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
contactQuery.set("UserId", {
|
|
||||||
__type: "Pointer",
|
|
||||||
className: "_User",
|
|
||||||
objectId: userRes.id
|
|
||||||
});
|
|
||||||
const acl = new Parse.ACL();
|
|
||||||
acl.setPublicReadAccess(true);
|
|
||||||
acl.setPublicWriteAccess(true);
|
|
||||||
acl.setReadAccess(currentUser.id, true);
|
|
||||||
acl.setWriteAccess(currentUser.id, true);
|
|
||||||
|
|
||||||
contactQuery.setACL(acl);
|
|
||||||
const res = await contactQuery.save();
|
|
||||||
|
|
||||||
const parseData = JSON.parse(JSON.stringify(res));
|
|
||||||
if (props.details) {
|
|
||||||
props.details({
|
|
||||||
value: parseData[props.valueKey],
|
|
||||||
label: parseData[props.displayKey]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (props.closePopup) {
|
|
||||||
props.closePopup();
|
|
||||||
}
|
|
||||||
if (props.handleUserData) {
|
|
||||||
props.handleUserData(parseData);
|
|
||||||
}
|
|
||||||
setIsLoader(false);
|
|
||||||
setName("");
|
|
||||||
setPhone("");
|
|
||||||
setEmail("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// console.log("err", err);
|
|
||||||
setIsLoader(false);
|
|
||||||
alert("something went wrong!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Define a function to handle the "add yourself" checkbox
|
|
||||||
const handleReset = () => {
|
|
||||||
setName("");
|
|
||||||
setPhone("");
|
|
||||||
setEmail("");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="shadow-md rounded my-2 p-3 bg-[#ffffff] md:border-[1px] md:border-gray-600/50">
|
|
||||||
<Title title={"Add User"} />
|
|
||||||
{isUserExist && <Alert type="danger">User already exists!</Alert>}
|
|
||||||
{isLoader && (
|
|
||||||
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: "45px",
|
|
||||||
color: "#3dd3e0"
|
|
||||||
}}
|
|
||||||
className="loader-37"
|
|
||||||
></div>
|
|
||||||
</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 style={{ color: "red", fontSize: 13 }}> *</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="name"
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
required
|
|
||||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label
|
|
||||||
htmlFor="email"
|
|
||||||
className="block text-xs text-gray-700 font-semibold"
|
|
||||||
>
|
|
||||||
Email
|
|
||||||
<span style={{ color: "red", fontSize: 13 }}> *</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
value={email}
|
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
|
||||||
required
|
|
||||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label
|
|
||||||
htmlFor="phone"
|
|
||||||
className="block text-xs text-gray-700 font-semibold"
|
|
||||||
>
|
|
||||||
Phone
|
|
||||||
<span style={{ color: "red", fontSize: 13 }}> *</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="phone"
|
|
||||||
value={phone}
|
|
||||||
onChange={(e) => setPhone(e.target.value)}
|
|
||||||
required
|
|
||||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center mt-3 gap-2 text-white">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="bg-[#1ab6ce] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 focus:outline-none"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleReset()}
|
|
||||||
className="bg-[#188ae2] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 text-center ml-[2px] focus:outline-none"
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddUser;
|
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import Parse from "parse";
|
||||||
|
import Title from "./Title";
|
||||||
|
import Loader from "../primitives/Loader";
|
||||||
|
import {
|
||||||
|
copytoData,
|
||||||
|
usertimezone
|
||||||
|
} from "../constant/Utils";
|
||||||
|
import {
|
||||||
|
emailRegex,
|
||||||
|
} from "../constant/const";
|
||||||
|
import {
|
||||||
|
useTranslation
|
||||||
|
} from "react-i18next";
|
||||||
|
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 { t } = useTranslation();
|
||||||
|
const [formdata, setFormdata] = useState({
|
||||||
|
name: "",
|
||||||
|
phone: "",
|
||||||
|
email: "",
|
||||||
|
team: "",
|
||||||
|
password: "",
|
||||||
|
role: ""
|
||||||
|
});
|
||||||
|
const [isFormLoader, setIsFormLoader] = useState(false);
|
||||||
|
const [teamList, setTeamList] = useState([]);
|
||||||
|
const role = ["OrgAdmin", "Editor", "User"];
|
||||||
|
useEffect(() => {
|
||||||
|
getTeamList();
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
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);
|
||||||
|
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();
|
||||||
|
if (!emailRegex.test(formdata.email)) {
|
||||||
|
alert("Please enter a valid email address.");
|
||||||
|
} else {
|
||||||
|
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||||
|
setIsFormLoader(true);
|
||||||
|
const res = await checkUserExist();
|
||||||
|
if (res) {
|
||||||
|
props.showAlert("danger", t("user-already-exist"));
|
||||||
|
setIsFormLoader(false);
|
||||||
|
} else {
|
||||||
|
if (localStorage.getItem("TenantId")) {
|
||||||
|
const timezone = usertimezone;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
name: formdata.name,
|
||||||
|
email: formdata.email,
|
||||||
|
phone: formdata.phone,
|
||||||
|
password: formdata.password,
|
||||||
|
role: formdata.role,
|
||||||
|
team: formdata.team,
|
||||||
|
timezone: timezone,
|
||||||
|
tenantId: localStorage.getItem("TenantId"),
|
||||||
|
organization: {
|
||||||
|
objectId: localUser?.OrganizationId?.objectId,
|
||||||
|
company: localUser?.Company
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const res = await Parse.Cloud.run("adduser", params);
|
||||||
|
const parseData = JSON.parse(JSON.stringify(res));
|
||||||
|
console.log("parseData ", parseData);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
setIsFormLoader(false);
|
||||||
|
setFormdata({
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
phone: "",
|
||||||
|
team: "",
|
||||||
|
role: ""
|
||||||
|
});
|
||||||
|
props.showAlert("success", t("user-created-successfully"));
|
||||||
|
} catch (err) {
|
||||||
|
console.log("err", err);
|
||||||
|
setIsFormLoader(false);
|
||||||
|
props.showAlert("danger", t("something-went-wrong-mssg"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
props.showAlert("danger", t("something-went-wrong-mssg"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define a function to handle the "add yourself" checkbox
|
||||||
|
const handleReset = () => {
|
||||||
|
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
|
||||||
|
if (props.closePopup) {
|
||||||
|
props.closePopup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleChange = (event) => {
|
||||||
|
let { name, value } = event.target;
|
||||||
|
if (name === "email") {
|
||||||
|
value = value?.toLowerCase()?.replace(/\s/g, "");
|
||||||
|
}
|
||||||
|
setFormdata((prev) => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const copytoclipboard = (text) => {
|
||||||
|
copytoData(text);
|
||||||
|
props.showAlert("success", t("copied"));
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
|
||||||
|
<Title title={t("add-user")} />
|
||||||
|
{isFormLoader && (
|
||||||
|
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="w-full mx-auto">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label
|
||||||
|
htmlFor="name"
|
||||||
|
className="block text-xs text-gray-700 font-semibold"
|
||||||
|
>
|
||||||
|
{t("name")}
|
||||||
|
<span className="text-[red] text-[13px]"> *</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
value={formdata.name}
|
||||||
|
onChange={(e) => handleChange(e)}
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
required
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label
|
||||||
|
htmlFor="email"
|
||||||
|
className="block text-xs text-gray-700 font-semibold"
|
||||||
|
>
|
||||||
|
{t("email")}
|
||||||
|
<span className="text-[red] text-[13px]"> *</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
value={formdata.email}
|
||||||
|
onChange={(e) => handleChange(e)}
|
||||||
|
required
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="block text-xs text-gray-700 font-semibold">
|
||||||
|
{t("password")}
|
||||||
|
</label>
|
||||||
|
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
|
||||||
|
<div className="break-all">{formdata?.password}</div>
|
||||||
|
<i
|
||||||
|
onClick={() => copytoclipboard(formdata?.password)}
|
||||||
|
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
|
||||||
|
{t("password-generateed")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label
|
||||||
|
htmlFor="phone"
|
||||||
|
className="block text-xs text-gray-700 font-semibold"
|
||||||
|
>
|
||||||
|
{t("phone")}
|
||||||
|
{/* <span className="text-[red] text-[13px]"> *</span> */}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="phone"
|
||||||
|
placeholder={t("phone-optional")}
|
||||||
|
value={formdata.phone}
|
||||||
|
onChange={(e) => handleChange(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-3">
|
||||||
|
<label
|
||||||
|
htmlFor="phone"
|
||||||
|
className="block text-xs text-gray-700 font-semibold"
|
||||||
|
>
|
||||||
|
{t("Role")}
|
||||||
|
<span className="text-[red] text-[13px]"> *</span>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={formdata.role}
|
||||||
|
onChange={(e) => handleChange(e)}
|
||||||
|
name="role"
|
||||||
|
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option defaultValue={""} value={""}>
|
||||||
|
{t("Select")}
|
||||||
|
</option>
|
||||||
|
{role.length > 0 &&
|
||||||
|
role.map((x) => (
|
||||||
|
<option key={x} value={x}>
|
||||||
|
{x}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center mt-3 gap-2 text-white">
|
||||||
|
<button type="submit" className="op-btn op-btn-primary">
|
||||||
|
{t("submit")}
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleReset()}
|
||||||
|
className="op-btn op-btn-secondary"
|
||||||
|
>
|
||||||
|
{t("cancel")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddUser;
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import SuggestionInput from "./shared/fields/SuggestionInput";
|
||||||
|
import Loader from "../primitives/Loader";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
emailRegex,
|
||||||
|
} from "../constant/const";
|
||||||
|
const BulkSendUi = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [forms, setForms] = useState([]);
|
||||||
|
const formRef = useRef(null);
|
||||||
|
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
|
||||||
|
const [isSubmit, setIsSubmit] = useState(false);
|
||||||
|
const [isSignatureExist, setIsSignatureExist] = useState();
|
||||||
|
const [isDisableBulkSend, setIsDisableBulkSend] = useState(false);
|
||||||
|
const [isLoader, setIsLoader] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
signatureExist();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//function to check atleast one signature field exist
|
||||||
|
const signatureExist = async () => {
|
||||||
|
setIsDisableBulkSend(false);
|
||||||
|
const getPlaceholder = props?.Placeholders;
|
||||||
|
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
||||||
|
placeholderObj?.placeHolder?.some((holder) =>
|
||||||
|
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setIsSignatureExist(checkIsSignatureExistt);
|
||||||
|
setIsLoader(false);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (scrollOnNextUpdate && formRef.current) {
|
||||||
|
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 }]);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// 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 handleRemoveForm = (index) => {
|
||||||
|
const updatedForms = forms.filter((_, i) => i !== index);
|
||||||
|
setForms(updatedForms);
|
||||||
|
};
|
||||||
|
|
||||||
|
function validateEmails(data) {
|
||||||
|
for (const item of data) {
|
||||||
|
for (const field of item.fields) {
|
||||||
|
if (!emailRegex.test(field.email)) {
|
||||||
|
alert(`Invalid email found: ${field.email}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsSubmit(true);
|
||||||
|
if (validateEmails(forms)) {
|
||||||
|
// Create a copy of Placeholders array from props.item
|
||||||
|
let Placeholders = [...props.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,
|
||||||
|
SignatureType: props.signatureType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await batchQuery(Documents);
|
||||||
|
} else {
|
||||||
|
setIsSubmit(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const batchQuery = async (Documents) => {
|
||||||
|
const token =
|
||||||
|
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
|
||||||
|
const functionsUrl = `${localStorage.getItem(
|
||||||
|
"baseUrl"
|
||||||
|
)}functions/batchdocuments`;
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||||
|
...token,
|
||||||
|
};
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
{isLoader ? (
|
||||||
|
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{isSubmit && (
|
||||||
|
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isDisableBulkSend ? (
|
||||||
|
<>
|
||||||
|
{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 className="block text-xs font-semibold">
|
||||||
|
{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
|
||||||
|
type="submit"
|
||||||
|
className="op-btn op-btn-accent focus:outline-none"
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-paper-plane"></i>{" "}
|
||||||
|
<span>{t("send")}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||||
|
{t("quick-send-alert-1")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||||
|
{t("quick-send-alert-2")}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||||
|
{t("quick-send-alert-3")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BulkSendUi;
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import Loader from "../primitives/Loader";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Parse from "parse";
|
||||||
|
const EditContactForm = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [isLoader, setIsLoader] = useState(false);
|
||||||
|
const [formData, setFormData] = useState({ Name: "", Email: "", Phone: "" });
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.contact?.Email) {
|
||||||
|
setFormData((prev) => ({ ...prev, ...props.contact }));
|
||||||
|
}
|
||||||
|
}, [props.contact]);
|
||||||
|
const handleChange = (e) => {
|
||||||
|
if (e.target.name === "Email") {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[e.target.name]: e.target.value?.toLowerCase()?.replace(/\s/g, "")
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (props.handleEditContact) {
|
||||||
|
try {
|
||||||
|
setIsLoader(true);
|
||||||
|
const params = {
|
||||||
|
contactId: props.contact.objectId,
|
||||||
|
name: formData.Name,
|
||||||
|
email: formData.Email,
|
||||||
|
phone: formData.Phone,
|
||||||
|
tenantId: localStorage.getItem("TenantId")
|
||||||
|
};
|
||||||
|
const res = await Parse.Cloud.run("editcontact", params);
|
||||||
|
const updateContact = {
|
||||||
|
...res,
|
||||||
|
Name: formData.Name,
|
||||||
|
Email: formData.Email,
|
||||||
|
Phone: formData.Phone
|
||||||
|
};
|
||||||
|
props.handleEditContact(updateContact);
|
||||||
|
} catch (err) {
|
||||||
|
console.log("err in edit contact ", err);
|
||||||
|
if (err.code === 137) {
|
||||||
|
alert(t("contact-already-exists"));
|
||||||
|
} else {
|
||||||
|
alert(t("something-went-wrong-mssg"));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setIsLoader(false);
|
||||||
|
props.handleClose && props.handleClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="name" className="block text-xs font-semibold">
|
||||||
|
{t("name")}
|
||||||
|
<span className="text-[red] text-[13px]"> *</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
name="Name"
|
||||||
|
value={formData.Name}
|
||||||
|
onChange={(e) => handleChange(e)}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="email" className="block text-xs font-semibold">
|
||||||
|
{t("email")}
|
||||||
|
<span className="text-[red] text-[13px]"> *</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
name="Email"
|
||||||
|
value={formData.Email}
|
||||||
|
onChange={(e) => handleChange(e)}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="phone" className="block text-xs font-semibold">
|
||||||
|
{t("phone")}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="phone"
|
||||||
|
name="Phone"
|
||||||
|
value={formData.Phone}
|
||||||
|
onChange={(e) => handleChange(e)}
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
placeholder={t("phone-optional")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex gap-x-2 justify-start">
|
||||||
|
<button type="submit" className="op-btn op-btn-primary">
|
||||||
|
{t("submit")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditContactForm;
|
||||||
@@ -2,7 +2,10 @@ import React, { useEffect, useState } from "react";
|
|||||||
import Package from "../../package.json";
|
import Package from "../../package.json";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { openInNewTab } from "../constant/Utils";
|
import { openInNewTab } from "../constant/Utils";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
|
const appName = "OpenSign™";
|
||||||
|
const { t } = useTranslation();
|
||||||
const [showButton, setShowButton] = useState(false);
|
const [showButton, setShowButton] = useState(false);
|
||||||
const [version, setVersion] = useState("");
|
const [version, setVersion] = useState("");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -37,7 +40,6 @@ const Footer = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const appName = "OpenSign™";
|
|
||||||
const openUrl = () => {
|
const openUrl = () => {
|
||||||
openInNewTab(
|
openInNewTab(
|
||||||
"https://github.com/OpenSignLabs/OpenSign/releases/tag/" + version
|
"https://github.com/OpenSignLabs/OpenSign/releases/tag/" + version
|
||||||
@@ -45,19 +47,24 @@ const Footer = () => {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bg-[#222c3c] text-[#98a6ba] text-center text-[13px] py-3">
|
<footer className="op-footer op-footer-center py-3 bg-base-300 text-base-content text-center text-[13px]">
|
||||||
All Rights Reserved © {new Date().getFullYear()}
|
<aside>
|
||||||
<span onClick={openUrl} className="hover:underline cursor-pointer">
|
<p>
|
||||||
{appName} ( version: {version ? version : `${Package.version} `})
|
{t("all-right")} © {new Date().getFullYear()}
|
||||||
</span>
|
<span onClick={openUrl} className="hover:underline cursor-pointer">
|
||||||
</div>
|
{appName} ( {t("version")}:{" "}
|
||||||
|
{version ? version : `${Package.version} `})
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
<button
|
<button
|
||||||
className={`${
|
className={`${
|
||||||
showButton ? "block" : "hidden"
|
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}
|
onClick={scrollToTop}
|
||||||
>
|
>
|
||||||
<i className="fa-solid fa-angle-up"></i>
|
<i className="fa-light fa-angle-up"></i>
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -67,12 +67,12 @@ const FullScreenButton = () => {
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<button
|
<button
|
||||||
onClick={toggleFullScreen}
|
onClick={toggleFullScreen}
|
||||||
className="text-black p-2 text-sm focus:outline-none"
|
className="text-base-content p-2 text-sm focus:outline-none"
|
||||||
>
|
>
|
||||||
{isFullScreen ? (
|
{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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import dp from "../assets/images/dp.png";
|
|
||||||
import FullScreenButton from "./FullScreenButton";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import Parse from "parse";
|
|
||||||
import { useWindowSize } from "../hook/useWindowSize";
|
|
||||||
import { checkIsSubscribed, getAppLogo, openInNewTab } from "../constant/Utils";
|
|
||||||
import { isEnableSubscription } from "../constant/const";
|
|
||||||
|
|
||||||
const Header = ({ showSidebar }) => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { width } = useWindowSize();
|
|
||||||
let username = localStorage.getItem("username");
|
|
||||||
const image = localStorage.getItem("profileImg") || dp;
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [isSubscribe, setIsSubscribe] = useState(true);
|
|
||||||
const [isPro, setIsPro] = useState(false);
|
|
||||||
const [applogo, setAppLogo] = useState(
|
|
||||||
localStorage.getItem("appLogo") || " "
|
|
||||||
);
|
|
||||||
|
|
||||||
const toggleDropdown = () => {
|
|
||||||
setIsOpen(!isOpen);
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
|
||||||
checkSubscription();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
async function checkSubscription() {
|
|
||||||
if (isEnableSubscription) {
|
|
||||||
const getIsSubscribe = await checkIsSubscribed();
|
|
||||||
if (getIsSubscribe) {
|
|
||||||
const applogo = await getAppLogo();
|
|
||||||
if (applogo) {
|
|
||||||
setAppLogo(applogo);
|
|
||||||
} else {
|
|
||||||
setAppLogo(localStorage.getItem("appLogo") || "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setIsPro(getIsSubscribe);
|
|
||||||
setIsSubscribe(getIsSubscribe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeDropdown = () => {
|
|
||||||
setIsOpen(false);
|
|
||||||
Parse.User.logOut();
|
|
||||||
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("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("baseUrl", baseUrl);
|
|
||||||
localStorage.setItem("parseAppId", appid);
|
|
||||||
|
|
||||||
navigate("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
//handle to close profile drop down menu onclick screen
|
|
||||||
useEffect(() => {
|
|
||||||
const closeMenuOnOutsideClick = (e) => {
|
|
||||||
if (isOpen && !e.target.closest("#profile-menu")) {
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("click", closeMenuOnOutsideClick);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// Cleanup the event listener when the component unmounts
|
|
||||||
document.removeEventListener("click", closeMenuOnOutsideClick);
|
|
||||||
};
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
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="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
|
||||||
<img
|
|
||||||
className="object-contain h-full w-auto"
|
|
||||||
src={applogo}
|
|
||||||
alt="img"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
id="profile-menu"
|
|
||||||
className="flex justify-between items-center gap-x-3"
|
|
||||||
>
|
|
||||||
{!isSubscribe && (
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
className="text-xs bg-[#002864] p-2 text-white rounded shadow"
|
|
||||||
onClick={() => navigate("/subscription")}
|
|
||||||
>
|
|
||||||
Upgrade Now
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isPro && (
|
|
||||||
<div className="w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
|
||||||
PRO
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<FullScreenButton />
|
|
||||||
</div>
|
|
||||||
{width >= 768 && (
|
|
||||||
<div
|
|
||||||
onClick={toggleDropdown}
|
|
||||||
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className="w-[35px] h-[35px] object-contain"
|
|
||||||
src={image}
|
|
||||||
alt="img"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{width >= 768 && (
|
|
||||||
<div onClick={toggleDropdown} className="cursor-pointer text-sm">
|
|
||||||
{username && username}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="relative">
|
|
||||||
<div className="cursor-pointer">
|
|
||||||
<i onClick={toggleDropdown} className="fa-solid fa-angle-down"></i>
|
|
||||||
</div>
|
|
||||||
<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"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<ul>
|
|
||||||
<li
|
|
||||||
className="hover:bg-gray-100 rounded-t-lg py-1 px-2 cursor-pointer font-normal"
|
|
||||||
onClick={() => openInNewTab("https://docs.opensignlabs.com")}
|
|
||||||
>
|
|
||||||
<i className="fa-solid fa-book"></i> Docs
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
|
|
||||||
onClick={() => {
|
|
||||||
setIsOpen(false);
|
|
||||||
navigate("/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);
|
|
||||||
navigate("/changepassword");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i className="fa-solid fa-lock"></i> Change Password
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
|
|
||||||
onClick={() => {
|
|
||||||
window.open("https://console.opensignlabs.com/");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i className="fa-regular fa-id-card"></i> Console
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
className="hover:bg-gray-100 rounded-b-lg py-1 px-2 cursor-pointer font-normal"
|
|
||||||
onClick={closeDropdown}
|
|
||||||
>
|
|
||||||
<i className="fa-solid fa-arrow-right-from-bracket"></i> Log Out
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Header;
|
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import dp from "../assets/images/dp.png";
|
||||||
|
import FullScreenButton from "./FullScreenButton";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
import Parse from "parse";
|
||||||
|
import { useWindowSize } from "../hook/useWindowSize";
|
||||||
|
import {
|
||||||
|
getAppLogo,
|
||||||
|
openInNewTab,
|
||||||
|
saveLanguageInLocal
|
||||||
|
} from "../constant/Utils";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { appInfo } from "../constant/appinfo";
|
||||||
|
|
||||||
|
const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { width } = useWindowSize();
|
||||||
|
const username = localStorage.getItem("username") || "";
|
||||||
|
const image = localStorage.getItem("profileImg") || dp;
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [applogo, setAppLogo] = useState("");
|
||||||
|
|
||||||
|
const toggleDropdown = () => {
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
if (width <= 768) {
|
||||||
|
setIsMenu(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
initializeHead();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
async function initializeHead() {
|
||||||
|
const applogo = await getAppLogo();
|
||||||
|
if (applogo?.logo) {
|
||||||
|
setAppLogo(applogo?.logo);
|
||||||
|
} else {
|
||||||
|
const logo = localStorage.getItem("appLogo") || appInfo.applogo;
|
||||||
|
setAppLogo(logo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeDropdown = async () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
try {
|
||||||
|
await Parse.User.logOut();
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Err while logging out", err);
|
||||||
|
}
|
||||||
|
let appdata = localStorage.getItem("userSettings");
|
||||||
|
let applogo = localStorage.getItem("appLogo");
|
||||||
|
let defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||||
|
let PageLanding = localStorage.getItem("PageLanding");
|
||||||
|
let baseUrl = localStorage.getItem("baseUrl");
|
||||||
|
let appid = localStorage.getItem("parseAppId");
|
||||||
|
|
||||||
|
localStorage.clear();
|
||||||
|
saveLanguageInLocal(i18n);
|
||||||
|
localStorage.setItem("appLogo", applogo);
|
||||||
|
localStorage.setItem("defaultmenuid", defaultmenuid);
|
||||||
|
localStorage.setItem("PageLanding", PageLanding);
|
||||||
|
localStorage.setItem("userSettings", appdata);
|
||||||
|
localStorage.setItem("baseUrl", baseUrl);
|
||||||
|
localStorage.setItem("parseAppId", appid);
|
||||||
|
|
||||||
|
navigate("/");
|
||||||
|
};
|
||||||
|
|
||||||
|
//handle to close profile drop down menu onclick screen
|
||||||
|
useEffect(() => {
|
||||||
|
const closeMenuOnOutsideClick = (e) => {
|
||||||
|
if (isOpen && !e.target.closest("#profile-menu")) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("click", closeMenuOnOutsideClick);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Cleanup the event listener when the component unmounts
|
||||||
|
document.removeEventListener("click", closeMenuOnOutsideClick);
|
||||||
|
};
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="op-navbar bg-base-100 shadow">
|
||||||
|
<div className="flex-none">
|
||||||
|
<button
|
||||||
|
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
|
||||||
|
onClick={showSidebar}
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-bars text-xl text-base-content"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 ml-2">
|
||||||
|
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
||||||
|
{applogo && (
|
||||||
|
<img
|
||||||
|
className="object-contain h-full w-auto"
|
||||||
|
src={applogo}
|
||||||
|
alt="logo"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="profile-menu" className="flex-none gap-2">
|
||||||
|
<div>
|
||||||
|
<FullScreenButton />
|
||||||
|
</div>
|
||||||
|
{width >= 768 && (
|
||||||
|
<div
|
||||||
|
onClick={toggleDropdown}
|
||||||
|
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="w-[35px] h-[35px] object-contain"
|
||||||
|
src={image}
|
||||||
|
alt="img"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{width >= 768 && (
|
||||||
|
<div
|
||||||
|
onClick={toggleDropdown}
|
||||||
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
|
className="cursor-pointer text-base-content text-sm"
|
||||||
|
>
|
||||||
|
{username && username}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className="op-dropdown op-dropdown-open op-dropdown-end"
|
||||||
|
id="profile-menu"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
onClick={toggleDropdown}
|
||||||
|
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-angle-down text-base-content"></i>
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
tabIndex={0}
|
||||||
|
className={`mt-3 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52 ${
|
||||||
|
isOpen ? "" : "hidden"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{!isConsole && (
|
||||||
|
<>
|
||||||
|
<li
|
||||||
|
onClick={() =>
|
||||||
|
openInNewTab("https://docs.opensignlabs.com")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<i className="fa-light fa-book"></i> {t("docs")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
navigate("/profile");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<i className="fa-light fa-user"></i> {t("profile")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
navigate("/changepassword");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<i className="fa-light fa-lock"></i>{" "}
|
||||||
|
{t("change-password")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<li onClick={closeDropdown}>
|
||||||
|
<span>
|
||||||
|
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
|
||||||
|
{t("log-out")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import Parse from "parse";
|
|
||||||
// import FacebookLogin from "react-facebook-login";
|
|
||||||
|
|
||||||
const LoginFacebook = ({
|
|
||||||
FBCred,
|
|
||||||
thirdpartyLoginfn,
|
|
||||||
thirdpartyLoader,
|
|
||||||
setThirdpartyLoader
|
|
||||||
}) => {
|
|
||||||
const [isModal, setIsModal] = useState(false);
|
|
||||||
const [userDetails, setUserDetails] = useState({
|
|
||||||
Phone: "",
|
|
||||||
Company: "",
|
|
||||||
Name: "",
|
|
||||||
Email: ""
|
|
||||||
});
|
|
||||||
const [fBDetails, setFBDetails] = useState({
|
|
||||||
Id: "",
|
|
||||||
AccessToken: "",
|
|
||||||
Name: "",
|
|
||||||
Email: ""
|
|
||||||
});
|
|
||||||
const responseFacebook = async (response) => {
|
|
||||||
// console.log("response ", response);
|
|
||||||
if (response.userID) {
|
|
||||||
setThirdpartyLoader(true);
|
|
||||||
const details = {
|
|
||||||
Email: response.email,
|
|
||||||
Name: response.name
|
|
||||||
};
|
|
||||||
setUserDetails({ ...userDetails, ...details });
|
|
||||||
const fDetails = {
|
|
||||||
Id: response.userID,
|
|
||||||
AccessToken: response.accessToken,
|
|
||||||
Name: response.name,
|
|
||||||
Email: response.email
|
|
||||||
};
|
|
||||||
setFBDetails({ ...fBDetails, ...fDetails });
|
|
||||||
const res = await checkExtUser(fDetails);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkExtUser = async (details) => {
|
|
||||||
// const extUser = new Parse.Query("contracts_Users");
|
|
||||||
// extUser.equalTo("Email", details.Email);
|
|
||||||
// const extRes = await extUser.first();
|
|
||||||
const params = { email: details.Email };
|
|
||||||
const extRes = await Parse.Cloud.run("getUserDetails", params);
|
|
||||||
// console.log("extRes ", extRes);
|
|
||||||
if (extRes) {
|
|
||||||
const params = { ...details, Phone: extRes.get("Phone") };
|
|
||||||
const payload = await Parse.Cloud.run("facebooksign", params);
|
|
||||||
// console.log("payload ", payload);
|
|
||||||
if (payload && payload.sessiontoken) {
|
|
||||||
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.Email,
|
|
||||||
phone: extRes.get("Phone"),
|
|
||||||
company: extRes.get("Company")
|
|
||||||
};
|
|
||||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
|
||||||
thirdpartyLoginfn(payload.sessiontoken, billingDate);
|
|
||||||
}
|
|
||||||
return { msg: "exist" };
|
|
||||||
} else {
|
|
||||||
setIsModal(true);
|
|
||||||
setThirdpartyLoader(false);
|
|
||||||
return { msg: "notexist" };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleSubmitbtn = async () => {
|
|
||||||
if (userDetails.Phone && userDetails.Company) {
|
|
||||||
setThirdpartyLoader(true);
|
|
||||||
const params = { ...fBDetails, Phone: userDetails.Phone };
|
|
||||||
const payload = await Parse.Cloud.run("facebooksign", params);
|
|
||||||
// console.log("payload ", payload);
|
|
||||||
|
|
||||||
if (payload && payload.sessiontoken) {
|
|
||||||
const params = {
|
|
||||||
userDetails: {
|
|
||||||
name: userDetails.Name,
|
|
||||||
email: userDetails.Email,
|
|
||||||
// "passsword":userDetails.Phone,
|
|
||||||
phone: userDetails.Phone,
|
|
||||||
role: "contracts_User",
|
|
||||||
company: userDetails.Company
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
|
||||||
// console.log("userSignUp ", userSignUp);
|
|
||||||
if (userSignUp && userSignUp.sessionToken) {
|
|
||||||
const LocalUserDetails = {
|
|
||||||
name: userDetails.Name,
|
|
||||||
email: userDetails.Email,
|
|
||||||
phone: userDetails.Phone,
|
|
||||||
company: userDetails.Company
|
|
||||||
};
|
|
||||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
|
||||||
thirdpartyLoginfn(userSignUp.sessionToken);
|
|
||||||
} else {
|
|
||||||
alert(userSignUp.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
alert("Internal server error !");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
alert("Please fill required details!");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div style={{ position: "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>
|
|
||||||
)}
|
|
||||||
{/* <FacebookLogin
|
|
||||||
appId={FBCred}
|
|
||||||
fields="name,email,picture"
|
|
||||||
callback={responseFacebook}
|
|
||||||
cssClass="btn btn-info btn-md btn-fb"
|
|
||||||
icon="fa fa-facebook-f"
|
|
||||||
/> */}
|
|
||||||
{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>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleSubmitbtn()}
|
|
||||||
className="btn btn-info"
|
|
||||||
style={{ marginRight: 10 }}
|
|
||||||
>
|
|
||||||
Sign up
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-secondary"
|
|
||||||
onClick={() => setIsModal(false)}
|
|
||||||
style={{ width: 90 }}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LoginFacebook;
|
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
import React, { useState, useRef } from "react";
|
|
||||||
import Parse from "parse";
|
|
||||||
import { jwtDecode } from "jwt-decode";
|
|
||||||
import { useScript } from "../hook/useScript";
|
|
||||||
import ModalUi from "../primitives/ModalUi";
|
|
||||||
import { modalCancelBtnColor, modalSubmitBtnColor } from "../constant/const";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* `GoogleSignInBtn`as it's name indicates it render google sign in button
|
|
||||||
* and in this `useScript` in which we have created for generate google sign button
|
|
||||||
* and when user click on sign in with google it will be verify on server side
|
|
||||||
* and then generate token
|
|
||||||
*/
|
|
||||||
const GoogleSignInBtn = ({
|
|
||||||
GoogleCred,
|
|
||||||
thirdpartyLoginfn,
|
|
||||||
thirdpartyLoader,
|
|
||||||
setThirdpartyLoader
|
|
||||||
}) => {
|
|
||||||
const [isModal, setIsModal] = useState(false);
|
|
||||||
const googleBtn = useRef();
|
|
||||||
const [userDetails, setUserDetails] = useState({
|
|
||||||
Phone: "",
|
|
||||||
Company: "",
|
|
||||||
Name: "",
|
|
||||||
Email: "",
|
|
||||||
Destination: ""
|
|
||||||
});
|
|
||||||
const [googleDetails, setGoogleDetails] = useState({
|
|
||||||
Id: "",
|
|
||||||
TokenId: "",
|
|
||||||
Gmail: "",
|
|
||||||
Name: ""
|
|
||||||
});
|
|
||||||
useScript("https://accounts.google.com/gsi/client", () => {
|
|
||||||
window.google.accounts.id.initialize({
|
|
||||||
client_id: GoogleCred,
|
|
||||||
callback: responseGoogle,
|
|
||||||
auto_select: false
|
|
||||||
});
|
|
||||||
window.google.accounts.id.renderButton(googleBtn.current, {
|
|
||||||
theme: "outline",
|
|
||||||
size: "large",
|
|
||||||
width: "187px"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const clearStorage = async () => {
|
|
||||||
if (Parse.User.current()) {
|
|
||||||
await Parse.User.logOut();
|
|
||||||
}
|
|
||||||
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("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);
|
|
||||||
};
|
|
||||||
const responseGoogle = async (response) => {
|
|
||||||
clearStorage();
|
|
||||||
setThirdpartyLoader(true);
|
|
||||||
// console.log("response ", response);
|
|
||||||
if (response.credential) {
|
|
||||||
const data = jwtDecode(response.credential);
|
|
||||||
// console.log("data ", data);
|
|
||||||
if (data.sub && data.email) {
|
|
||||||
const details = {
|
|
||||||
Email: data.email,
|
|
||||||
Name: data.name
|
|
||||||
};
|
|
||||||
setUserDetails({ ...userDetails, ...details });
|
|
||||||
const Gdetails = {
|
|
||||||
Id: data.sub,
|
|
||||||
TokenId: response.credential,
|
|
||||||
Gmail: data.email,
|
|
||||||
Name: data.name
|
|
||||||
};
|
|
||||||
setGoogleDetails({ ...googleDetails, ...Gdetails });
|
|
||||||
await checkExtUser(Gdetails);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const checkExtUser = async (details) => {
|
|
||||||
// const extUser = new Parse.Query("contracts_Users");
|
|
||||||
// extUser.equalTo("Email", details.Gmail);
|
|
||||||
// const extRes = await extUser.first();
|
|
||||||
const params = { email: details.Gmail };
|
|
||||||
const extRes = await Parse.Cloud.run("getUserDetails", params);
|
|
||||||
// console.log("extRes ", extRes);
|
|
||||||
if (extRes) {
|
|
||||||
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");
|
|
||||||
// console.log("billingDate expired", billingDate > new Date());
|
|
||||||
const LocalUserDetails = {
|
|
||||||
name: details.Name,
|
|
||||||
email: details.Gmail,
|
|
||||||
phone: extRes.get("Phone"),
|
|
||||||
company: extRes.get("Company")
|
|
||||||
};
|
|
||||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
|
||||||
thirdpartyLoginfn(payload.sessiontoken);
|
|
||||||
}
|
|
||||||
return { msg: "exist" };
|
|
||||||
} else {
|
|
||||||
setIsModal(true);
|
|
||||||
setThirdpartyLoader(false);
|
|
||||||
return { msg: "notexist" };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleSubmitbtn = async () => {
|
|
||||||
if (userDetails.Phone && userDetails.Company) {
|
|
||||||
setThirdpartyLoader(true);
|
|
||||||
// e.preventDefault()
|
|
||||||
// console.log("handelSubmit", userDetails);
|
|
||||||
const params = { ...googleDetails, Phone: userDetails.Phone };
|
|
||||||
const payload = await Parse.Cloud.run("googlesign", params);
|
|
||||||
|
|
||||||
// console.log("payload ", payload);
|
|
||||||
if (payload && payload.sessiontoken) {
|
|
||||||
const params = {
|
|
||||||
userDetails: {
|
|
||||||
name: userDetails.Name,
|
|
||||||
email: userDetails.Email,
|
|
||||||
// "passsword":userDetails.Phone,
|
|
||||||
phone: userDetails.Phone,
|
|
||||||
role: "contracts_User",
|
|
||||||
company: userDetails.Company,
|
|
||||||
jobTitle: userDetails.Destination
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
|
||||||
// console.log("userSignUp ", userSignUp);
|
|
||||||
if (userSignUp && userSignUp.sessionToken) {
|
|
||||||
const LocalUserDetails = {
|
|
||||||
name: userDetails.Name,
|
|
||||||
email: userDetails.Email,
|
|
||||||
phone: userDetails.Phone,
|
|
||||||
company: userDetails.Company
|
|
||||||
// jobTitle: userDetails.JobTitle
|
|
||||||
};
|
|
||||||
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
|
|
||||||
thirdpartyLoginfn(userSignUp.sessionToken);
|
|
||||||
} else {
|
|
||||||
alert(userSignUp.message);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
payload &&
|
|
||||||
payload.message.replace(/ /g, "_") === "Internal_server_err"
|
|
||||||
) {
|
|
||||||
alert("Internal server error !");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
alert("Please fill required details!");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleCloseModal = () => {
|
|
||||||
setIsModal(false);
|
|
||||||
Parse.User.logOut();
|
|
||||||
|
|
||||||
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("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("baseUrl", baseUrl);
|
|
||||||
localStorage.setItem("parseAppId", appid);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div style={{ position: "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>
|
|
||||||
)}
|
|
||||||
<div ref={googleBtn} className="text-sm"></div>
|
|
||||||
<ModalUi showClose={false} isOpen={isModal} title="Sign up form">
|
|
||||||
<form className="px-4 py-3">
|
|
||||||
<div className="mb-3">
|
|
||||||
<label
|
|
||||||
htmlFor="Phone"
|
|
||||||
style={{ display: "flex" }}
|
|
||||||
className="block text-xs text-gray-700 font-semibold"
|
|
||||||
>
|
|
||||||
Phone <span style={{ fontSize: 13, color: "red" }}>*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="tel"
|
|
||||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
|
||||||
id="Phone"
|
|
||||||
value={userDetails.Phone}
|
|
||||||
onChange={(e) =>
|
|
||||||
setUserDetails({
|
|
||||||
...userDetails,
|
|
||||||
Phone: e.target.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label
|
|
||||||
htmlFor="Company"
|
|
||||||
style={{ display: "flex" }}
|
|
||||||
className="block text-xs text-gray-700 font-semibold"
|
|
||||||
>
|
|
||||||
Company <span style={{ fontSize: 13, color: "red" }}>*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
|
||||||
id="Company"
|
|
||||||
value={userDetails.Company}
|
|
||||||
onChange={(e) =>
|
|
||||||
setUserDetails({
|
|
||||||
...userDetails,
|
|
||||||
Company: e.target.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3">
|
|
||||||
<label
|
|
||||||
htmlFor="JobTitle"
|
|
||||||
style={{ display: "flex" }}
|
|
||||||
className="block text-xs text-gray-700 font-semibold"
|
|
||||||
>
|
|
||||||
Job Title <span style={{ fontSize: 13, color: "red" }}>*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
|
||||||
id="JobTitle"
|
|
||||||
value={userDetails.Destination}
|
|
||||||
onChange={(e) =>
|
|
||||||
setUserDetails({
|
|
||||||
...userDetails,
|
|
||||||
Destination: e.target.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="px-3 py-1.5 text-white rounded shadow-md text-center focus:outline-none "
|
|
||||||
onClick={() => handleSubmitbtn()}
|
|
||||||
style={{ marginRight: 10, backgroundColor: modalSubmitBtnColor }}
|
|
||||||
>
|
|
||||||
Sign up
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="p-1.5 text-black border-[1px] border-[#ccc] shadow-md rounded focus:outline-none"
|
|
||||||
onClick={handleCloseModal}
|
|
||||||
style={{ width: 90, backgroundColor: modalCancelBtnColor }}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</ModalUi>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GoogleSignInBtn;
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Document, Page } from "react-pdf";
|
|
||||||
import { Stage, Layer, Rect } from "react-konva";
|
|
||||||
const RenderDebugPdf = (props) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "sticky",
|
|
||||||
top: 0,
|
|
||||||
padding: 10,
|
|
||||||
zIndex: 1,
|
|
||||||
backgroundColor: "white",
|
|
||||||
border: "1px solid grey",
|
|
||||||
margin: "5px 0"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{`Co-ordinates: X - ${props.hoverCoordinates.x}, Y - ${props.hoverCoordinates.y}`}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
position: "relative",
|
|
||||||
border: "1px solid grey",
|
|
||||||
overflow: "auto",
|
|
||||||
cursor: "crosshair"
|
|
||||||
}}
|
|
||||||
onMouseMove={props.handleMouseMoveDiv}
|
|
||||||
>
|
|
||||||
<Document
|
|
||||||
onLoadError={() => {
|
|
||||||
const load = {
|
|
||||||
status: false,
|
|
||||||
type: "failed"
|
|
||||||
};
|
|
||||||
props.setPdfLoadFail(load);
|
|
||||||
}}
|
|
||||||
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}
|
|
||||||
style={{ position: "absolute", top: 0, left: 0 }}
|
|
||||||
>
|
|
||||||
<Layer>
|
|
||||||
{props.annotations
|
|
||||||
.filter((value) => value.page === props.pageNumber)
|
|
||||||
.map((value) => {
|
|
||||||
return (
|
|
||||||
<Rect
|
|
||||||
key={value.key}
|
|
||||||
x={value.x}
|
|
||||||
y={value.y}
|
|
||||||
width={value.width}
|
|
||||||
height={value.height}
|
|
||||||
fill="transparent"
|
|
||||||
stroke="black"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Layer>
|
|
||||||
</Stage>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RenderDebugPdf;
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Document, Page } from "react-pdf";
|
||||||
|
import { Stage, Layer, Rect, Text } from "react-konva";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
const RenderDebugPdf = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
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={t("loading-doc")}
|
||||||
|
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||||
|
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;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ModalUi from "../primitives/ModalUi";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
function RotateAlert(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<ModalUi
|
||||||
|
isOpen={props.showRotateAlert}
|
||||||
|
title={t("Rotation-alert")}
|
||||||
|
handleClose={() => props.setShowRotateAlert({ status: false, degree: 0 })}
|
||||||
|
>
|
||||||
|
<div className="p-[20px] h-full">
|
||||||
|
<p>{t("rotate-alert-mssg")}</p>
|
||||||
|
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<button
|
||||||
|
onClick={() => props.handleRemoveWidgets()}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-primary"
|
||||||
|
>
|
||||||
|
{t("yes")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
props.setShowRotateAlert({ status: false, degree: 0 })
|
||||||
|
}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost shadow-md"
|
||||||
|
>
|
||||||
|
{t("no")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RotateAlert;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { NavLink } from "react-router";
|
||||||
|
|
||||||
|
const SocialMedia = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<NavLink
|
||||||
|
to="https://github.com/opensignlabs/opensign"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<i aria-hidden="true" className="fa-brands fa-github"></i>
|
||||||
|
<span className="fa-sr-only">
|
||||||
|
OpenSign's {t("social-media.github")}
|
||||||
|
</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
to="https://www.linkedin.com/company/opensign%E2%84%A2/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<i aria-hidden="true" className="fa-brands fa-linkedin"></i>
|
||||||
|
<span className="fa-sr-only">
|
||||||
|
OpenSign's {t("social-media.linked-in")}
|
||||||
|
</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
to="https://www.twitter.com/opensignlabs"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<i aria-hidden="true" className="fa-brands fa-square-x-twitter"></i>
|
||||||
|
<span className="fa-sr-only">
|
||||||
|
OpenSign's {t("social-media.twitter")}
|
||||||
|
</span>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
to="https://discord.com/invite/xe9TDuyAyj"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<i aria-hidden="true" className="fa-brands fa-discord"></i>
|
||||||
|
<span className="fa-sr-only">
|
||||||
|
OpenSign's {t("social-media.discord")}
|
||||||
|
</span>
|
||||||
|
</NavLink>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SocialMedia;
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from "react";
|
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
|
|
||||||
function Title({ title, drive }) {
|
function Title({ title, drive }) {
|
||||||
|
const appName =
|
||||||
|
"OpenSign™";
|
||||||
return (
|
return (
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{drive ? title : `${title} - OpenSign™`}</title>
|
<title>{drive ? title : `${title} - ${appName}`}</title>
|
||||||
<meta name="description" content={`${title} - OpenSign™`} />
|
<meta name="description" content={`${title} - ${appName}`} />
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
rel="icon"
|
||||||
type="image/png"
|
type="image/png"
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import "../../styles/loader.css";
|
|
||||||
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 px-3 py-2 flex 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">
|
|
||||||
<i
|
|
||||||
className={`${
|
|
||||||
props.Icon ? props.Icon : "fa fa-solid fa-info"
|
|
||||||
} text-[25px] lg:text-[30px]`}
|
|
||||||
></i>
|
|
||||||
</span>
|
|
||||||
<div className="">
|
|
||||||
<div className="text-base lg:text-lg text-black"> {props.Label}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DashboardButton;
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
import { openInNewTab } from "../../constant/Utils";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const DashboardButton = (props) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
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">
|
||||||
|
<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>
|
||||||
|
<div className="text-lg ml-3 text-base-content">
|
||||||
|
{t(`sidebar.${props.Label}`)}
|
||||||
|
{props.Label === "Sign yourself" && (
|
||||||
|
<div className="text-gray-500 text-xs mt-1">
|
||||||
|
{t("signyour-self-button")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{props.Label === "Request signatures" && (
|
||||||
|
<div className="text-gray-500 text-xs mt-1">
|
||||||
|
{t("requestsign-button")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardButton;
|
||||||
@@ -2,12 +2,13 @@ import React, { useState, useEffect } from "react";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
|
import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
|
||||||
import "../../styles/loader.css";
|
import { useNavigate } from "react-router";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import Tooltip from "../../primitives/Tooltip";
|
import Tooltip from "../../primitives/Tooltip";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const DashboardCard = (props) => {
|
const DashboardCard = (props) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
|
||||||
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
|
||||||
const [response, setresponse] = useState("");
|
const [response, setresponse] = useState("");
|
||||||
@@ -24,17 +25,12 @@ const DashboardCard = (props) => {
|
|||||||
sessionToken: localStorage.getItem("accesstoken")
|
sessionToken: localStorage.getItem("accesstoken")
|
||||||
};
|
};
|
||||||
let body = {};
|
let body = {};
|
||||||
let currentUser;
|
|
||||||
let currentUser1 = Parse.User.current();
|
|
||||||
currentUser = currentUser1.id;
|
|
||||||
let res;
|
let res;
|
||||||
if (localStorage.getItem("Extand_Class")) {
|
if (localStorage.getItem("Extand_Class")) {
|
||||||
let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||||
res = data[0];
|
res = data[0];
|
||||||
} else {
|
} else {
|
||||||
res = await Parse.Cloud.run("getUserDetails", {
|
res = await Parse.Cloud.run("getUserDetails");
|
||||||
email: currentUser.get("email")
|
|
||||||
});
|
|
||||||
if (res) res = res.toJSON();
|
if (res) res = res.toJSON();
|
||||||
}
|
}
|
||||||
if (res) {
|
if (res) {
|
||||||
@@ -95,9 +91,7 @@ const DashboardCard = (props) => {
|
|||||||
let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||||
resr = data[0];
|
resr = data[0];
|
||||||
} else {
|
} else {
|
||||||
resr = await Parse.Cloud.run("getUserDetails", {
|
resr = await Parse.Cloud.run("getUserDetails");
|
||||||
email: currentUser.get("email")
|
|
||||||
});
|
|
||||||
if (resr) resr = resr.toJSON();
|
if (resr) resr = resr.toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +133,7 @@ const DashboardCard = (props) => {
|
|||||||
skip: 0,
|
skip: 0,
|
||||||
limit: 200
|
limit: 200
|
||||||
};
|
};
|
||||||
const url = `${parseBaseUrl}/functions/getReport`;
|
const url = `${parseBaseUrl}functions/getReport`;
|
||||||
await axios
|
await axios
|
||||||
.post(url, params, {
|
.post(url, params, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -219,15 +213,12 @@ const DashboardCard = (props) => {
|
|||||||
|
|
||||||
if (restr.includes("#")) {
|
if (restr.includes("#")) {
|
||||||
try {
|
try {
|
||||||
const currentUser = Parse.User.current();
|
|
||||||
let res;
|
let res;
|
||||||
if (localStorage.getItem("Extand_Class")) {
|
if (localStorage.getItem("Extand_Class")) {
|
||||||
let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
let data = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||||
res = data[0];
|
res = data[0];
|
||||||
} else {
|
} else {
|
||||||
let resr = await Parse.Cloud.run("getUserDetails", {
|
let resr = await Parse.Cloud.run("getUserDetails");
|
||||||
email: currentUser.get("email")
|
|
||||||
});
|
|
||||||
if (res) res = resr.toJSON();
|
if (res) res = resr.toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,21 +253,14 @@ const DashboardCard = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
body = str;
|
body = str;
|
||||||
await axios
|
const response = await axios.post(url, body, { headers: headers });
|
||||||
.post(url, body, { headers: headers })
|
if (response.data.result.length > 0) {
|
||||||
.then((response) => {
|
setresponse(response.data.result[0][props.FilterData.key]);
|
||||||
try {
|
setLoading(false);
|
||||||
if (response.data.result.length > 0) {
|
} else {
|
||||||
setresponse(response.data.result[0][props.FilterData.key]);
|
setresponse("0");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} else {
|
}
|
||||||
setresponse("0");
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -329,6 +313,7 @@ const DashboardCard = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={() => openReport()}
|
onClick={() => openReport()}
|
||||||
@@ -336,25 +321,32 @@ const DashboardCard = (props) => {
|
|||||||
props.Data && props.Data.Redirect_type
|
props.Data && props.Data.Redirect_type
|
||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "cursor-default"
|
: "cursor-default"
|
||||||
} w-full h-[140px] px-3 pt-4 pb-10 text-white rounded-md shadow overflow-hidden relative`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-start gap-5">
|
<div className="flex items-center justify-start gap-5 text-white">
|
||||||
<span className="rounded-full bg-black bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
<span className="rounded-full bg-base-300 bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
||||||
<i
|
<i
|
||||||
className={`${
|
className={`${
|
||||||
props.Icon ? props.Icon : "fa fa-solid fa-info"
|
props.Icon ? props.Icon : "fa-light fa-info"
|
||||||
} text-[25px] lg:text-[30px]`}
|
} text-[25px] lg:text-[30px]`}
|
||||||
></i>
|
></i>
|
||||||
</span>
|
</span>
|
||||||
<div className="">
|
|
||||||
<div className="text-base lg:text-lg"> {props.Label}</div>
|
<div className="font-medium">
|
||||||
|
<div className="text-base lg:text-lg">
|
||||||
|
{t(`dashboard-card.${props.Label}`)}
|
||||||
|
</div>
|
||||||
<div className="text-2xl font-light">
|
<div className="text-2xl font-light">
|
||||||
{loading ? <div className="loader-01"></div> : setFormat(response)}
|
{loading ? <div className="loader-01"></div> : setFormat(response)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs absolute top-2 right-1">
|
<div className="text-xs absolute top-3 right-2">
|
||||||
<Tooltip id={props.Label} iconColor={"white"} message={props?.Data?.tourMessage} />
|
<Tooltip
|
||||||
|
id={props.Label}
|
||||||
|
iconColor={"white"}
|
||||||
|
message={t(`tour-mssg.${props.Label}`)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -3,7 +3,10 @@ import Parse from "parse";
|
|||||||
import ReportTable from "../../primitives/GetReportDisplay";
|
import ReportTable from "../../primitives/GetReportDisplay";
|
||||||
import reportJson from "../../json/ReportJson";
|
import reportJson from "../../json/ReportJson";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import Loader from "../../primitives/Loader";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
function DashboardReport(props) {
|
function DashboardReport(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
// console.log("props ", props)
|
// console.log("props ", props)
|
||||||
const [List, setList] = useState([]);
|
const [List, setList] = useState([]);
|
||||||
const [isLoader, setIsLoader] = useState(true);
|
const [isLoader, setIsLoader] = useState(true);
|
||||||
@@ -33,12 +36,12 @@ function DashboardReport(props) {
|
|||||||
// below useEffect call when isNextRecord state is true and fetch next record
|
// below useEffect call when isNextRecord state is true and fetch next record
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isNextRecord) {
|
if (isNextRecord) {
|
||||||
getReportData(props.Record.reportId, List.length, 200);
|
getReportData(props.Record.reportId, List.length, 20);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [isNextRecord]);
|
}, [isNextRecord]);
|
||||||
|
|
||||||
const getReportData = async (id, skipUserRecord = 0, limit = 200) => {
|
const getReportData = async (id, skipUserRecord = 0, limit = 20) => {
|
||||||
setIsLoader(true);
|
setIsLoader(true);
|
||||||
const json = reportJson(id);
|
const json = reportJson(id);
|
||||||
if (json) {
|
if (json) {
|
||||||
@@ -53,8 +56,10 @@ function DashboardReport(props) {
|
|||||||
sessiontoken: localStorage.getItem("accesstoken")
|
sessiontoken: localStorage.getItem("accesstoken")
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const params = { reportId: id, skip: skipUserRecord, limit: limit };
|
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
|
||||||
const url = `${localStorage.getItem("baseUrl")}/functions/getReport`;
|
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
|
||||||
|
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||||
|
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||||
const res = await axios.post(url, params, {
|
const res = await axios.post(url, params, {
|
||||||
headers: headers,
|
headers: headers,
|
||||||
signal: abortController.signal // is used to cancel fetch query
|
signal: abortController.signal // is used to cancel fetch query
|
||||||
@@ -91,7 +96,7 @@ function DashboardReport(props) {
|
|||||||
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (res.data.result.length === docPerPage) {
|
if (res.data.result.length >= docPerPage) {
|
||||||
setIsMoreDocs(true);
|
setIsMoreDocs(true);
|
||||||
} else {
|
} else {
|
||||||
setIsMoreDocs(false);
|
setIsMoreDocs(false);
|
||||||
@@ -121,21 +126,8 @@ function DashboardReport(props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoader ? (
|
{isLoader ? (
|
||||||
<div
|
<div className="h-[250px] flex justify-center items-center">
|
||||||
style={{
|
<Loader />
|
||||||
height: "100vh",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: "45px",
|
|
||||||
color: "#3dd3e0"
|
|
||||||
}}
|
|
||||||
className="loader-37"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -153,7 +145,7 @@ function DashboardReport(props) {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded">
|
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-xl text-black">Report Not Found</p>
|
<p className="text-xl text-black">{t("report-not-found")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { Suspense, lazy } from "react";
|
import React, { Suspense, lazy } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
const DashboardButton = lazy(() => import("./DashboardButton"));
|
const DashboardButton = lazy(() => import("./DashboardButton"));
|
||||||
const DashboardCard = lazy(() => import("./DashboardCard"));
|
const DashboardCard = lazy(() => import("./DashboardCard"));
|
||||||
const DashboardReport = lazy(() => import("./DashboardReport"));
|
const DashboardReport = lazy(() => import("./DashboardReport"));
|
||||||
@@ -7,62 +8,38 @@ const buttonList = [
|
|||||||
label: "Sign yourself",
|
label: "Sign yourself",
|
||||||
redirectId: "sHAnZphf69",
|
redirectId: "sHAnZphf69",
|
||||||
redirectType: "Form",
|
redirectType: "Form",
|
||||||
icon: "fas fa-pen-nib"
|
icon: "fa-light fa-pen-nib"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Request signature",
|
label: "Request signatures",
|
||||||
redirectId: "8mZzFxbG1z",
|
redirectId: "8mZzFxbG1z",
|
||||||
redirectType: "Form",
|
redirectType: "Form",
|
||||||
icon: "fa-solid fa-paper-plane"
|
icon: "fa-light fa-paper-plane"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const GetDashboard = (props) => {
|
const GetDashboard = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const Button = ({ label, redirectId, redirectType, icon }) => (
|
const Button = ({ label, redirectId, redirectType, icon }) => (
|
||||||
<div className={"bg-white rounded-md shadow w-full"}>
|
<DashboardButton
|
||||||
<Suspense
|
Icon={icon}
|
||||||
fallback={
|
Label={label}
|
||||||
<div style={{ height: "300px" }}>
|
Data={{ Redirect_type: redirectType, Redirect_id: redirectId }}
|
||||||
<div
|
/>
|
||||||
style={{
|
|
||||||
marginLeft: "45%",
|
|
||||||
marginTop: "150px",
|
|
||||||
fontSize: "45px",
|
|
||||||
color: "#3dd3e0"
|
|
||||||
}}
|
|
||||||
className="loader-37"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DashboardButton
|
|
||||||
Icon={icon}
|
|
||||||
Label={label}
|
|
||||||
Data={{ Redirect_type: redirectType, Redirect_id: redirectId }}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
const renderSwitchWithTour = (col) => {
|
const renderSwitchWithTour = (col) => {
|
||||||
switch (col.widget.type) {
|
switch (col.widget.type) {
|
||||||
case "Card":
|
case "Card":
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={"bg-[#2ed8b6] rounded-md shadow mb-3 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}
|
data-tut={col.widget.data.tourSection}
|
||||||
style={{ background: col.widget.bgColor }}
|
|
||||||
>
|
>
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<div style={{ height: "300px" }}>
|
<div className="h-[150px] w-full flex justify-center items-center">
|
||||||
<div
|
{t("loading")}
|
||||||
style={{
|
|
||||||
marginLeft: "45%",
|
|
||||||
marginTop: "150px",
|
|
||||||
fontSize: "45px",
|
|
||||||
color: "#3dd3e0"
|
|
||||||
}}
|
|
||||||
className="loader-37"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -72,7 +49,6 @@ const GetDashboard = (props) => {
|
|||||||
Format={col.widget.format && col.widget.format}
|
Format={col.widget.format && col.widget.format}
|
||||||
Data={col.widget.data}
|
Data={col.widget.data}
|
||||||
FilterData={col.widget.filter}
|
FilterData={col.widget.filter}
|
||||||
DefaultQuery={props.DefaultQuery}
|
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,7 +65,7 @@ const GetDashboard = (props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return null;
|
return <></>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const renderSwitch = (col) => {
|
const renderSwitch = (col) => {
|
||||||
@@ -97,8 +73,9 @@ const GetDashboard = (props) => {
|
|||||||
case "Card":
|
case "Card":
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={"bg-[#2ed8b6] rounded-md shadow mb-3 md:mb-0"}
|
className={`${
|
||||||
style={{ background: col.widget.bgColor }}
|
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>}>
|
<Suspense fallback={<div>please wait</div>}>
|
||||||
<DashboardCard
|
<DashboardCard
|
||||||
@@ -107,7 +84,6 @@ const GetDashboard = (props) => {
|
|||||||
Format={col.widget.format && col.widget.format}
|
Format={col.widget.format && col.widget.format}
|
||||||
Data={col.widget.data}
|
Data={col.widget.data}
|
||||||
FilterData={col.widget.filter}
|
FilterData={col.widget.filter}
|
||||||
DefaultQuery={props.DefaultQuery}
|
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +98,7 @@ const GetDashboard = (props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return null;
|
return <></>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
@@ -130,7 +106,7 @@ const GetDashboard = (props) => {
|
|||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<div
|
<div
|
||||||
data-tut={"tourbutton"}
|
data-tut={"tourbutton"}
|
||||||
className="flex flex-col md:flex-row gap-6 md:gap-8"
|
className="flex flex-col md:flex-row gap-4"
|
||||||
>
|
>
|
||||||
{buttonList.map((btn) => (
|
{buttonList.map((btn) => (
|
||||||
<Button
|
<Button
|
||||||
@@ -143,21 +119,19 @@ const GetDashboard = (props) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{props.dashboard.map((val, key) => (
|
<div className="grid grid-cols-12 w-full gap-x-4">
|
||||||
<div key={"a" + key} className="row">
|
{props?.dashboard?.columns?.map((col, i) =>
|
||||||
{val.columns.map((col, i) =>
|
col.widget.data && col.widget.data.tourSection ? (
|
||||||
col.widget.data && col.widget.data.tourSection ? (
|
<div key={i} className={col?.colsize}>
|
||||||
<div key={i} className={props.classnameArray[key][i]}>
|
{renderSwitchWithTour(col)}
|
||||||
{renderSwitchWithTour(col)}
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<div key={i} className={col?.colsize}>
|
||||||
<div key={i} className={props.classnameArray[key][i]}>
|
{renderSwitch(col)}
|
||||||
{renderSwitch(col)}
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,29 +1,29 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import folder from "../../assets/images/folder.png";
|
|
||||||
import "../../styles/opensigndrive.css";
|
import "../../styles/opensigndrive.css";
|
||||||
import pdfLogo from "../../assets/images/pdf3.png";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import * as ContextMenu from "@radix-ui/react-context-menu";
|
import { ContextMenu } from "radix-ui";
|
||||||
import { saveAs } from "file-saver";
|
import { useNavigate } from "react-router";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import Table from "react-bootstrap/Table";
|
import Table from "react-bootstrap/Table";
|
||||||
import * as HoverCard from "@radix-ui/react-hover-card";
|
import { HoverCard } from "radix-ui";
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
import { themeColor } from "../../constant/const";
|
|
||||||
import FolderModal from "../shared/fields/FolderModal";
|
import FolderModal from "../shared/fields/FolderModal";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { handleDownloadPdf, isMobile } from "../../constant/Utils";
|
||||||
|
import Parse from "parse";
|
||||||
|
|
||||||
function DriveBody(props) {
|
function DriveBody(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [rename, setRename] = useState("");
|
const [rename, setRename] = useState("");
|
||||||
const [renameValue, setRenameValue] = useState("");
|
const [renameValue, setRenameValue] = useState("");
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
const [isOpenMoveModal, setIsOpenMoveModal] = useState(false);
|
const [isOpenMoveModal, setIsOpenMoveModal] = useState(false);
|
||||||
const [selectDoc, setSelectDoc] = useState();
|
const [selectDoc, setSelectDoc] = useState();
|
||||||
const [isDeleteDoc, setIsDeleteDoc] = useState(false);
|
const [isDeleteDoc, setIsDeleteDoc] = useState({});
|
||||||
const contextMenu = [
|
const contextMenu = [
|
||||||
{ type: "Download", icon: "fa-solid fa-arrow-down" },
|
{ type: "Download", icon: "fa-light fa-arrow-down" },
|
||||||
{ type: "Rename", icon: "fa-solid fa-font" },
|
{ type: "Rename", icon: "fa-light fa-font" },
|
||||||
{ type: "Move", icon: "fa-solid fa-file-export" },
|
{ type: "Move", icon: "fa-light fa-file-export" },
|
||||||
{ type: "Delete", icon: "fa-solid fa-trash" }
|
{ type: "Delete", icon: "fa-light fa-trash" }
|
||||||
];
|
];
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ function DriveBody(props) {
|
|||||||
props.setFolderName((prev) => [...prev, folderData]);
|
props.setFolderName((prev) => [...prev, folderData]);
|
||||||
props.setIsLoading({
|
props.setIsLoading({
|
||||||
isLoad: true,
|
isLoad: true,
|
||||||
message: "This might take some time"
|
message: t("loading-mssg")
|
||||||
});
|
});
|
||||||
props.setDocId(data.objectId);
|
props.setDocId(data.objectId);
|
||||||
props.setPdfData([]);
|
props.setPdfData([]);
|
||||||
@@ -73,9 +73,9 @@ function DriveBody(props) {
|
|||||||
props.sortingData(null, null, updatedData);
|
props.sortingData(null, null, updatedData);
|
||||||
await axios
|
await axios
|
||||||
.put(
|
.put(
|
||||||
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
|
`${localStorage.getItem(
|
||||||
"_appName"
|
"baseUrl"
|
||||||
)}_Document/${docId}`,
|
)}classes/contracts_Document/${docId}`,
|
||||||
updateName,
|
updateName,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -93,7 +93,7 @@ function DriveBody(props) {
|
|||||||
console.log("Err ", err);
|
console.log("Err ", err);
|
||||||
props.setIsAlert({
|
props.setIsAlert({
|
||||||
isShow: true,
|
isShow: true,
|
||||||
alertMessage: "something went wrong"
|
alertMessage: t("something-went-wrong-mssg")
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -101,21 +101,22 @@ function DriveBody(props) {
|
|||||||
|
|
||||||
//function for navigate user to microapp-signature component
|
//function for navigate user to microapp-signature component
|
||||||
const checkPdfStatus = async (data) => {
|
const checkPdfStatus = async (data) => {
|
||||||
const signerExist = data.Signers && data.Signers;
|
const signerExist = data?.Signers;
|
||||||
const isDecline = data.IsDeclined && data.IsDeclined;
|
const isDecline = data?.IsDeclined;
|
||||||
const isPlaceholder = data.Placeholders && data.Placeholders;
|
const isPlaceholder = data?.Placeholders;
|
||||||
const signedUrl = data.SignedUrl;
|
const signedUrl = data?.SignedUrl;
|
||||||
|
const isSignYourself = data?.IsSignyourself;
|
||||||
//checking if document has completed and request signature flow
|
//checking if document has completed and request signature flow
|
||||||
if (data?.IsCompleted && signerExist?.length > 0) {
|
if (data?.IsCompleted && signerExist?.length > 0) {
|
||||||
navigate(`/pdfRequestFiles/${data.objectId}`);
|
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||||
}
|
}
|
||||||
//checking if document has completed and signyour-self flow
|
//checking if document has completed and signyour-self flow
|
||||||
else if (!signerExist && !isPlaceholder) {
|
else if ((!signerExist && !isPlaceholder) || isSignYourself) {
|
||||||
navigate(`/signaturePdf/${data.objectId}`);
|
navigate(`/signaturePdf/${data.objectId}`);
|
||||||
}
|
}
|
||||||
//checking if document has declined by someone
|
//checking if document has declined by someone
|
||||||
else if (isDecline) {
|
else if (isDecline) {
|
||||||
navigate(`/pdfRequestFiles/${data.objectId}`);
|
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||||
//checking draft type document
|
//checking draft type document
|
||||||
} else if (
|
} else if (
|
||||||
signerExist?.length > 0 &&
|
signerExist?.length > 0 &&
|
||||||
@@ -125,8 +126,8 @@ function DriveBody(props) {
|
|||||||
navigate(`/placeHolderSign/${data.objectId}`);
|
navigate(`/placeHolderSign/${data.objectId}`);
|
||||||
}
|
}
|
||||||
//Inprogress document
|
//Inprogress document
|
||||||
else if (isPlaceholder?.length > 0 && signerExist?.length > 0) {
|
else if (isPlaceholder?.length > 0 && signedUrl) {
|
||||||
navigate(`/pdfRequestFiles/${data.objectId}`);
|
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||||
} //placeholder draft document
|
} //placeholder draft document
|
||||||
else if (
|
else if (
|
||||||
(signerExist?.length > 0 &&
|
(signerExist?.length > 0 &&
|
||||||
@@ -137,12 +138,10 @@ function DriveBody(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMenuItemClick = (selectType, data) => {
|
const handleMenuItemClick = async (selectType, data, deleteType) => {
|
||||||
switch (selectType) {
|
switch (selectType) {
|
||||||
case "Download": {
|
case "Download": {
|
||||||
const pdfName = data && data.Name;
|
await handleDownloadPdf([data]);
|
||||||
const pdfUrl = data && data.SignedUrl ? data.SignedUrl : data.URL;
|
|
||||||
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Rename": {
|
case "Rename": {
|
||||||
@@ -151,7 +150,7 @@ function DriveBody(props) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Delete": {
|
case "Delete": {
|
||||||
setIsDeleteDoc(true);
|
setIsDeleteDoc({ status: true, deleteType });
|
||||||
setSelectDoc(data);
|
setSelectDoc(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -165,7 +164,7 @@ function DriveBody(props) {
|
|||||||
};
|
};
|
||||||
//function for delete document
|
//function for delete document
|
||||||
const handleDeleteDocument = async (docData) => {
|
const handleDeleteDocument = async (docData) => {
|
||||||
setIsDeleteDoc(false);
|
setIsDeleteDoc({});
|
||||||
const docId = docData.objectId;
|
const docId = docData.objectId;
|
||||||
const data = {
|
const data = {
|
||||||
IsArchive: true
|
IsArchive: true
|
||||||
@@ -173,9 +172,7 @@ function DriveBody(props) {
|
|||||||
|
|
||||||
await axios
|
await axios
|
||||||
.put(
|
.put(
|
||||||
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
|
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${docId}`,
|
||||||
"_appName"
|
|
||||||
)}_Document/${docId}`,
|
|
||||||
data,
|
data,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -196,7 +193,7 @@ function DriveBody(props) {
|
|||||||
console.log("Err ", err);
|
console.log("Err ", err);
|
||||||
props.setIsAlert({
|
props.setIsAlert({
|
||||||
isShow: true,
|
isShow: true,
|
||||||
alertMessage: "something went wrong"
|
alertMessage: t("something-went-wrong-mssg")
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -222,7 +219,7 @@ function DriveBody(props) {
|
|||||||
updateData = {
|
updateData = {
|
||||||
Folder: {
|
Folder: {
|
||||||
__type: "Pointer",
|
__type: "Pointer",
|
||||||
className: `${localStorage.getItem("_appName")}_Document`,
|
className: "contracts_Document",
|
||||||
objectId: moveFolderId
|
objectId: moveFolderId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -234,9 +231,9 @@ function DriveBody(props) {
|
|||||||
|
|
||||||
await axios
|
await axios
|
||||||
.put(
|
.put(
|
||||||
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
|
`${localStorage.getItem(
|
||||||
"_appName"
|
"baseUrl"
|
||||||
)}_Document/${updateDocId}`,
|
)}classes/contracts_Document/${updateDocId}`,
|
||||||
updateData,
|
updateData,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -263,22 +260,60 @@ function DriveBody(props) {
|
|||||||
|
|
||||||
setIsOpenMoveModal(false);
|
setIsOpenMoveModal(false);
|
||||||
} else {
|
} else {
|
||||||
alert("folder already exist!");
|
alert(t("folder-already-exist!"));
|
||||||
setIsOpenMoveModal(false);
|
setIsOpenMoveModal(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sanitizeFileName = (pdfName) => {
|
|
||||||
// Replace spaces with underscore
|
|
||||||
return pdfName.replace(/ /g, "_");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEnterPress = (e, data) => {
|
const handleEnterPress = (e, data) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
handledRenameDoc(data);
|
handledRenameDoc(data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkFolderEmpty = async (docData) => {
|
||||||
|
let isEmptyFolder = true;
|
||||||
|
const query = new Parse.Query("contracts_Document");
|
||||||
|
query.equalTo("Folder", {
|
||||||
|
__type: "Pointer",
|
||||||
|
className: "contracts_Document",
|
||||||
|
objectId: docData.objectId
|
||||||
|
});
|
||||||
|
query.notEqualTo("IsArchive", true);
|
||||||
|
const res = await query.find();
|
||||||
|
const jsonRes = JSON.parse(JSON.stringify(res));
|
||||||
|
if (jsonRes && jsonRes.length > 0) {
|
||||||
|
isEmptyFolder = false;
|
||||||
|
return isEmptyFolder;
|
||||||
|
} else {
|
||||||
|
return isEmptyFolder;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDeleteFolder = async (docData) => {
|
||||||
|
setIsDeleteDoc({});
|
||||||
|
const isEmptyFolder = await checkFolderEmpty(docData);
|
||||||
|
if (isEmptyFolder) {
|
||||||
|
const docId = docData?.objectId;
|
||||||
|
try {
|
||||||
|
const updateQuery = new Parse.Query("contracts_Document");
|
||||||
|
const updateObj = await updateQuery.get(docId);
|
||||||
|
updateObj.set("IsArchive", true);
|
||||||
|
const res = await updateObj.save();
|
||||||
|
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: t("something-went-wrong-mssg")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(t("delete-folder-alert-1"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//component to handle type of document and render according to type
|
//component to handle type of document and render according to type
|
||||||
const handleFolderData = (data, ind, listType) => {
|
const handleFolderData = (data, ind, listType) => {
|
||||||
let createddate, status, isDecline, signerExist, isComplete;
|
let createddate, status, isDecline, signerExist, isComplete;
|
||||||
@@ -312,33 +347,30 @@ function DriveBody(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const signersName = () => {
|
const signersName = () => {
|
||||||
const getSignersName = signerExist.map((data) => data.Name);
|
const getSignersName =
|
||||||
const signerName = getSignersName.join(",");
|
signerExist?.length > 0 && signerExist?.map((data) => data?.Name || "");
|
||||||
|
const signerName =
|
||||||
|
getSignersName?.length > 0 ? getSignersName?.join(", ") : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span className="text-[12px] font-medium w-[90%] break-words">
|
||||||
className="statusSpan"
|
{signerName && signerName}
|
||||||
style={{ width: "90%", wordWrap: "break-word" }}
|
|
||||||
>
|
|
||||||
{signerName}{" "}
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return listType === "table" ? (
|
return listType === "table" ? (
|
||||||
data.Type === "Folder" ? (
|
data.Type === "Folder" ? (
|
||||||
<tr onClick={() => handleOnclikFolder(data)}>
|
<tr onClick={() => handleOnclikFolder(data)}>
|
||||||
<td
|
<td className="cursor-pointer flex items-center gap-2">
|
||||||
style={{ cursor: "pointer", display: "flex", alignItems: "center" }}
|
<svg
|
||||||
>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<i
|
viewBox="0 0 512 512"
|
||||||
className="fa fa-folder"
|
className="w-[26px] h-[26px] fill-current op-text-secondary"
|
||||||
aria-hidden="true"
|
>
|
||||||
style={{ color: "#f0ad26", marginRight: "8px", fontSize: "26px" }}
|
<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" />
|
||||||
></i>
|
</svg>
|
||||||
|
<span className="text-[12px] font-medium">{data.Name}</span>
|
||||||
<span style={{ fontSize: "12px", fontWeight: "500" }}>
|
|
||||||
{data.Name}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td>_</td>
|
<td>_</td>
|
||||||
<td>Folder</td>
|
<td>Folder</td>
|
||||||
@@ -347,57 +379,55 @@ function DriveBody(props) {
|
|||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
<tr onClick={() => checkPdfStatus(data)}>
|
<tr onClick={() => checkPdfStatus(data)}>
|
||||||
<td
|
<td className="cursor-pointer flex items-center gap-2">
|
||||||
style={{ cursor: "pointer", display: "flex", alignItems: "center" }}
|
<svg
|
||||||
>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<i
|
viewBox="0 0 384 512"
|
||||||
className="fa fa-file-pdf"
|
className="w-[26px] h-[26px] fill-current op-text-primary"
|
||||||
style={{ color: "#ed4d0e", marginRight: "8px", fontSize: "26px" }}
|
>
|
||||||
></i>
|
<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 style={{ fontSize: "12px", fontWeight: "500" }}>
|
<span className="text-[12px] font-medium">{data.Name}</span>
|
||||||
{data.Name}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td>{createddate}</td>
|
<td>{createddate}</td>
|
||||||
<td>Pdf</td>
|
<td>Pdf</td>
|
||||||
<td>{status}</td>
|
<td>{t(`drive-document-status.${status}`)}</td>
|
||||||
<td>
|
<td>
|
||||||
<i
|
<i
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleMenuItemClick("Download", data);
|
handleMenuItemClick("Download", data);
|
||||||
}}
|
}}
|
||||||
className="fa fa-download"
|
className="fa-light fa-download mr-[8px] op-text-primary cursor-pointer"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style={{ color: "#ed280e", marginRight: "8px" }}
|
|
||||||
></i>
|
></i>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
) : listType === "list" && data.Type === "Folder" ? (
|
) : listType === "list" && data.Type === "Folder" ? (
|
||||||
<div key={ind} className="folderBox">
|
<div key={ind} className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||||
<ContextMenu.Root>
|
<ContextMenu.Root>
|
||||||
<ContextMenu.Trigger className="ContextMenuTrigger">
|
<ContextMenu.Trigger className="flex flex-col justify-center items-center select-none-cls">
|
||||||
|
{/* folder */}
|
||||||
<div
|
<div
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
cursor: "pointer"
|
|
||||||
}}
|
|
||||||
data-tut={props.dataTutSeventh}
|
data-tut={props.dataTutSeventh}
|
||||||
|
onClick={() => {
|
||||||
|
if (!rename) {
|
||||||
|
handleOnclikFolder(data);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
<img
|
<svg
|
||||||
alt="no img"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
onClick={() => handleOnclikFolder(data)}
|
viewBox="0 0 512 512"
|
||||||
src={folder}
|
className="w-[100px] h-[100px] fill-current op-text-secondary"
|
||||||
width={100}
|
>
|
||||||
height={103}
|
<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 ? (
|
{rename === data.objectId ? (
|
||||||
<input
|
<input
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
inputRef.current.setSelectionRange(0, 0);
|
|
||||||
const input = inputRef.current;
|
const input = inputRef.current;
|
||||||
if (input) {
|
if (input) {
|
||||||
input.select();
|
input.select();
|
||||||
@@ -409,17 +439,11 @@ function DriveBody(props) {
|
|||||||
onKeyDown={(e) => handleEnterPress(e, data)}
|
onKeyDown={(e) => handleEnterPress(e, data)}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
defaultValue={renameValue}
|
defaultValue={renameValue}
|
||||||
// value={renameValue}
|
|
||||||
onChange={(e) => setRenameValue(e.target.value)}
|
onChange={(e) => setRenameValue(e.target.value)}
|
||||||
style={{
|
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
|
||||||
width: "100px",
|
|
||||||
border: "1.5px solid black",
|
|
||||||
borderRadius: "2px",
|
|
||||||
fontSize: "10px"
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className="foldName">{data.Name}</span>
|
<span className="fileName select-none-cls">{data.Name}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ContextMenu.Trigger>
|
</ContextMenu.Trigger>
|
||||||
@@ -434,75 +458,95 @@ function DriveBody(props) {
|
|||||||
onClick={() => handleMenuItemClick("Rename", data)}
|
onClick={() => handleMenuItemClick("Rename", data)}
|
||||||
className="ContextMenuItem"
|
className="ContextMenuItem"
|
||||||
>
|
>
|
||||||
<i className="fa-solid fa-font"></i>
|
<i className="fa-light fa-font mr-[8px]"></i>
|
||||||
<span style={{ marginLeft: "8px" }}>Rename</span>
|
<span>Rename</span>
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item
|
||||||
|
onClick={() => handleMenuItemClick("Delete", data, data.Type)}
|
||||||
|
className="ContextMenuItem"
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-trash mr-[8px]"></i>
|
||||||
|
<span>Delete</span>
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
</ContextMenu.Portal>
|
</ContextMenu.Portal>
|
||||||
</ContextMenu.Root>
|
</ContextMenu.Root>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<HoverCard.Root openDelay={0} closeDelay={100}>
|
<HoverCard.Root
|
||||||
|
open={rename || isMobile ? false : undefined}
|
||||||
|
openDelay={0}
|
||||||
|
closeDelay={100}
|
||||||
|
>
|
||||||
<HoverCard.Trigger asChild>
|
<HoverCard.Trigger asChild>
|
||||||
<div>
|
<div>
|
||||||
<ContextMenu.Root>
|
<ContextMenu.Root>
|
||||||
<div className="icon-container">
|
<div className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||||
<ContextMenu.Trigger className="ContextMenuTrigger">
|
<ContextMenu.Trigger
|
||||||
<img
|
asChild
|
||||||
alt="PDF"
|
className="flex flex-col justify-center items-center select-none-cls"
|
||||||
className="pdf-icon"
|
>
|
||||||
src={pdfLogo}
|
{/* pdf */}
|
||||||
onClick={() => checkPdfStatus(data)}
|
<div
|
||||||
data-tut={props.dataTutSixth}
|
data-tut={props.dataTutSixth}
|
||||||
/>
|
onClick={() => {
|
||||||
{rename === data.objectId ? (
|
if (!rename) {
|
||||||
<input
|
checkPdfStatus(data);
|
||||||
autoFocus={true}
|
}
|
||||||
type="text"
|
}}
|
||||||
onFocus={() => {
|
className="cursor-pointer"
|
||||||
inputRef.current.setSelectionRange(0, 0);
|
>
|
||||||
const input = inputRef.current;
|
<svg
|
||||||
if (input) {
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
input.select();
|
viewBox="0 0 384 512"
|
||||||
}
|
className="w-[100px] h-[100px] fill-current op-text-primary"
|
||||||
}}
|
>
|
||||||
onBlur={() => handledRenameDoc(data)}
|
<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" />
|
||||||
onKeyDown={(e) => handleEnterPress(e, data)}
|
</svg>
|
||||||
ref={inputRef}
|
{rename === data.objectId ? (
|
||||||
defaultValue={renameValue}
|
<input
|
||||||
// value={renameValue}
|
autoFocus={true}
|
||||||
onChange={(e) => setRenameValue(e.target.value)}
|
type="text"
|
||||||
style={{
|
onFocus={() => {
|
||||||
width: "100px",
|
const input = inputRef.current;
|
||||||
border: "1.5px solid black",
|
if (input) {
|
||||||
borderRadius: "2px",
|
input.select();
|
||||||
fontSize: "10px"
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onBlur={() => handledRenameDoc(data)}
|
||||||
) : (
|
onKeyDown={(e) => handleEnterPress(e, data, data.Type)}
|
||||||
<span className="fileName">{data.Name}</span>
|
ref={inputRef}
|
||||||
)}
|
defaultValue={renameValue}
|
||||||
|
onChange={(e) => setRenameValue(e.target.value)}
|
||||||
|
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span className="fileName select-none-cls">
|
||||||
|
{data.Name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</ContextMenu.Trigger>
|
</ContextMenu.Trigger>
|
||||||
{status === "Completed" ? (
|
{status === "Completed" ? (
|
||||||
<div className="status-badge completed">
|
<div className="status-badge completed">
|
||||||
<i className="fas fa-check-circle"></i>
|
<i className="fa-light fa-check-circle"></i>
|
||||||
</div>
|
</div>
|
||||||
) : status === "Declined" ? (
|
) : status === "Declined" ? (
|
||||||
<div className="status-badge declined">
|
<div className="status-badge declined">
|
||||||
<i className="fa fa-thumbs-down"></i>
|
<i className="fa-light fa-thumbs-down"></i>
|
||||||
</div>
|
</div>
|
||||||
) : status === "Expired" ? (
|
) : status === "Expired" ? (
|
||||||
<div className="status-badge expired">
|
<div className="status-badge expired">
|
||||||
<i className="fa fa-hourglass-end"></i>
|
<i className="fa-light fa-hourglass-end"></i>
|
||||||
</div>
|
</div>
|
||||||
) : status === "Draft" ? (
|
) : status === "Draft" ? (
|
||||||
<div className="status-badge draft">
|
<div className="status-badge draft">
|
||||||
<i className="fa fa-file"></i>
|
<i className="fa-light fa-file"></i>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
status === "In Progress" && (
|
status === "In Progress" && (
|
||||||
<div className="status-badge in-progress">
|
<div className="status-badge in-progress">
|
||||||
<i className="fa fa-paper-plane"></i>
|
<i className="fa-light fa-paper-plane"></i>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
@@ -522,7 +566,9 @@ function DriveBody(props) {
|
|||||||
className="ContextMenuItem"
|
className="ContextMenuItem"
|
||||||
>
|
>
|
||||||
<i className={menu.icon}></i>
|
<i className={menu.icon}></i>
|
||||||
<span style={{ marginLeft: "8px" }}>{menu.type}</span>
|
<span className="ml-[8px]">
|
||||||
|
{t(`context-menu.${menu.type}`)}
|
||||||
|
</span>
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -533,22 +579,28 @@ function DriveBody(props) {
|
|||||||
</HoverCard.Trigger>
|
</HoverCard.Trigger>
|
||||||
<HoverCard.Portal>
|
<HoverCard.Portal>
|
||||||
<HoverCard.Content className="HoverCardContent" sideOffset={5}>
|
<HoverCard.Content className="HoverCardContent" sideOffset={5}>
|
||||||
<strong style={{ fontSize: "13px" }}>Title: </strong>
|
<strong className="text-[13px]">
|
||||||
<span className="statusSpan" style={{ marginBottom: "0px" }}>
|
{t("report-heading.Title")}:{" "}
|
||||||
{" "}
|
</strong>
|
||||||
{data.Name}
|
<span className="text-[12px] font-medium mb-0"> {data.Name}</span>
|
||||||
|
<br />
|
||||||
|
<strong className="text-[13px]">
|
||||||
|
{t("report-heading.Status")}:{" "}
|
||||||
|
</strong>
|
||||||
|
<span className="text-[12px] font-medium">
|
||||||
|
{t(`drive-document-status.${status}`)}
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<strong style={{ fontSize: "13px" }}>Status: </strong>
|
<strong className="text-[13px]">
|
||||||
<span className="statusSpan"> {status}</span>
|
{t("report-heading.created-date")}:{" "}
|
||||||
<br />
|
</strong>
|
||||||
<strong style={{ fontSize: "13px" }}>Created Date: </strong>
|
<span className="text-[12px] font-medium">{createddate}</span>
|
||||||
<span className="statusSpan">{createddate}</span>
|
|
||||||
<br />
|
<br />
|
||||||
{signerExist && (
|
{signerExist && (
|
||||||
<>
|
<>
|
||||||
<strong style={{ fontSize: "13px" }}>Signers: </strong>
|
<strong className="text-[13px]">
|
||||||
{/* <span className="statusSpan">kjefjjnejkfnkbjs bbfjkdsbjbfjkbjk kscbjkbjkb</span> */}
|
{t("report-heading.Signers")}:{" "}
|
||||||
|
</strong>
|
||||||
{signersName()}
|
{signersName()}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -568,15 +620,15 @@ function DriveBody(props) {
|
|||||||
<Table striped bordered hover>
|
<Table striped bordered hover>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>{t("report-heading.Name")}</th>
|
||||||
<th>Created Date</th>
|
<th>{t("report-heading.created-date")}</th>
|
||||||
<th>Type</th>
|
<th>{t("report-heading.Type")}</th>
|
||||||
<th>Status</th>
|
<th>{t("report-heading.Status")}</th>
|
||||||
<th>Action</th>
|
<th>{t("action")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{props.pdfData.map((data, ind) => {
|
{props?.pdfData?.map((data, ind) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={ind}>
|
<React.Fragment key={ind}>
|
||||||
{handleFolderData(data, ind, "table")}
|
{handleFolderData(data, ind, "table")}
|
||||||
@@ -587,13 +639,9 @@ function DriveBody(props) {
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="pdfContainer">
|
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
|
||||||
{props.pdfData.map((data, ind) => {
|
{props.pdfData.map((data, ind) => {
|
||||||
return (
|
return <div key={ind}>{handleFolderData(data, ind, "list")}</div>;
|
||||||
<div className="box" key={ind}>
|
|
||||||
{handleFolderData(data, ind, "list")}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -608,47 +656,37 @@ function DriveBody(props) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ModalUi
|
<ModalUi
|
||||||
isOpen={isDeleteDoc}
|
isOpen={isDeleteDoc.status}
|
||||||
headerColor={themeColor}
|
title={t("delete-document")}
|
||||||
title={"Delete Document"}
|
handleClose={() => setIsDeleteDoc({})}
|
||||||
handleClose={() => {
|
|
||||||
setIsDeleteDoc(false);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div style={{ height: "100%", padding: 20 }}>
|
<div className="h-full p-[20px] text-base-content">
|
||||||
<p>Are you sure you want to delete this document?</p>
|
{isDeleteDoc.deleteType ? (
|
||||||
|
<p>{t("delete-folder-alert")}</p>
|
||||||
<div
|
) : (
|
||||||
style={{
|
<p>{t("delete-document-alert")}</p>
|
||||||
height: "1px",
|
)}
|
||||||
backgroundColor: "#9f9f9f",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "15px",
|
|
||||||
marginBottom: "15px"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
|
|
||||||
|
<div className="h-[1px] w-full bg-[#9f9f9f] my-[15px]"></div>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteDocument(selectDoc);
|
if (isDeleteDoc.deleteType) {
|
||||||
}}
|
handleDeleteFolder(selectDoc);
|
||||||
style={{
|
} else {
|
||||||
background: themeColor,
|
handleDeleteDocument(selectDoc);
|
||||||
color: "white"
|
}
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
className="finishBtn"
|
className="op-btn op-btn-primary mr-2"
|
||||||
>
|
>
|
||||||
Yes
|
{t("yes")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => setIsDeleteDoc({})}
|
||||||
setIsDeleteDoc(false);
|
|
||||||
}}
|
|
||||||
type="button"
|
type="button"
|
||||||
className="finishBtn cancelBtn"
|
className="op-btn op-btn-neutral"
|
||||||
>
|
>
|
||||||
No
|
{t("no")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ModalUi>
|
</ModalUi>
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
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="addusercontainer">
|
|
||||||
<form
|
|
||||||
style={{ display: "flex", flexDirection: "column" }}
|
|
||||||
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="addUserInput"
|
|
||||||
/>
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "grey",
|
|
||||||
fontSize: 11,
|
|
||||||
margin: "10px 0 10px 5px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
e.g: Customer, Hr, Director, Manager, Student, etc...
|
|
||||||
</p>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
height: "1px",
|
|
||||||
backgroundColor: "#9f9f9f",
|
|
||||||
width: "100%",
|
|
||||||
marginBottom: "15px"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
style={{
|
|
||||||
background: "#00a2b7"
|
|
||||||
}}
|
|
||||||
className="finishBtn"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={props.handleCloseRoleModal}
|
|
||||||
type="button"
|
|
||||||
className="finishBtn cancelBtn"
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</ModalUi>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddRoleModal;
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const AddRoleModal = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<ModalUi
|
||||||
|
title={t("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
|
||||||
|
? "Role " + (props.signersdata.length + 1)
|
||||||
|
: "Role 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]">
|
||||||
|
{t("role-ex")}..
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<div className="h-[1px] w-full bg-[#9f9f9f] mb-[10px]"></div>
|
||||||
|
<button type="submit" className="op-btn op-btn-primary">
|
||||||
|
{t("add")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={props.handleCloseRoleModal}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost ml-2"
|
||||||
|
>
|
||||||
|
{t("close")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddRoleModal;
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
|
|
||||||
|
function AgreementContent(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const appName =
|
||||||
|
"OpenSign™";
|
||||||
|
const h2Style = "text-base-content font-medium text-lg";
|
||||||
|
const ulStyle = "list-disc px-4 py-3";
|
||||||
|
const handleOnclick = () => {
|
||||||
|
props.setIsAgreeTour(false);
|
||||||
|
props.setIsAgree(true);
|
||||||
|
props.setIsShowAgreeTerms(false);
|
||||||
|
props.showFirstWidget();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ModalUi
|
||||||
|
isOpen={true}
|
||||||
|
title={t("term-cond-title")}
|
||||||
|
handleClose={() => props.setIsShowAgreeTerms(false)}
|
||||||
|
>
|
||||||
|
<div className="h-[100%] p-[20px]">
|
||||||
|
<h2 className={h2Style}>{t("term-cond-h")}</h2>
|
||||||
|
<span className="mt-2">
|
||||||
|
{t("term-cond-p1", { appName: appName })}
|
||||||
|
</span>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<h2 className={h2Style}>{t("term-cond-h1")}</h2>
|
||||||
|
<span className="mt-2">
|
||||||
|
{t("term-cond-p2", { appName: appName })}
|
||||||
|
</span>
|
||||||
|
<ul className={ulStyle}>
|
||||||
|
<li>{t("term-cond-p3", { appName: appName })}</li>
|
||||||
|
<li>{t("term-cond-p4")}</li>
|
||||||
|
</ul>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<h2 className={h2Style}>{t("term-cond-h2")}</h2>
|
||||||
|
<span className="mt-2">{t("term-cond-p5")}</span>
|
||||||
|
<ul className={ulStyle}>
|
||||||
|
<li>{t("term-cond-p6", { appName: appName })}</li>
|
||||||
|
<li>{t("term-cond-p7", { appName: appName })}</li>
|
||||||
|
<li>{t("term-cond-p8")}</li>
|
||||||
|
</ul>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<h2 className={h2Style}>{t("term-cond-h3")}</h2>
|
||||||
|
<span className="mt-2">{t("term-cond-p9")}</span>
|
||||||
|
<ul className={ulStyle}>
|
||||||
|
<li>{t("term-cond-p10")}</li>
|
||||||
|
<li>{t("term-cond-p11")}</li>
|
||||||
|
</ul>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<h2 className={h2Style}>{t("term-cond-h4")}</h2>
|
||||||
|
<span className="mt-2">
|
||||||
|
{t("term-cond-p12", { appName: appName })}
|
||||||
|
</span>
|
||||||
|
<ul className={ulStyle}>
|
||||||
|
<li>{t("term-cond-p13")}</li>
|
||||||
|
<li>{t("term-cond-p14")}</li>
|
||||||
|
<li>{t("term-cond-p15")}</li>
|
||||||
|
</ul>
|
||||||
|
<span>{t("term-cond-p16", { appName: appName })}</span>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<h2 className={h2Style}>{t("term-cond-h5")}</h2>
|
||||||
|
<p>{t("term-cond-p17", { appName: appName })}</p>
|
||||||
|
<p className="mt-2">{t("term-cond-p18")}</p>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<h2 className={h2Style}>{t("term-cond-h6")}</h2>
|
||||||
|
<span className="mt-2">{t("term-cond-p19")}</span>
|
||||||
|
<ul className={ulStyle}>
|
||||||
|
<li>{t("term-cond-p20")}</li>
|
||||||
|
<li>{t("term-cond-p21")}</li>
|
||||||
|
<li>{t("term-cond-p22", { appName: appName })}</li>
|
||||||
|
</ul>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<h2 className={h2Style}>{t("term-cond-h7")}</h2>
|
||||||
|
<span className="mt-2">
|
||||||
|
{t("term-cond-p23", { appName: appName })}
|
||||||
|
</span>
|
||||||
|
<ul className={ulStyle}>
|
||||||
|
<li>{t("term-cond-p24")}</li>
|
||||||
|
<li>{t("term-cond-p25")}</li>
|
||||||
|
</ul>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<h2 className={h2Style}>{t("term-cond-h8")}</h2>
|
||||||
|
<span className="mt-2">
|
||||||
|
{t("term-cond-p26", { appName: appName })}
|
||||||
|
</span>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<h2 className={h2Style}>{t("term-cond-h9")}</h2>
|
||||||
|
<span className="mt-2">
|
||||||
|
{t("term-cond-p27", { appName: appName })}
|
||||||
|
</span>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<span className="mt-2 font-medium">
|
||||||
|
{t("term-cond-p28", { appName: appName })}
|
||||||
|
</span>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<span className="mt-2">
|
||||||
|
{t("term-cond-p29", { appName: appName })}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="www.opensignlabs.com"
|
||||||
|
target="_blank"
|
||||||
|
className="text-blue-700 cursor-pointer"
|
||||||
|
>
|
||||||
|
www.opensignlabs.com
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<span>{t("term-cond-p30")}</span>
|
||||||
|
<span className="font-medium"> support@opensignlabs.com </span>
|
||||||
|
<hr className="bg-[#9f9f9f] w-full my-[15px]" />
|
||||||
|
<div className="mt-6 flex justify-start gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => handleOnclick()}
|
||||||
|
className="op-btn op-btn-primary"
|
||||||
|
>
|
||||||
|
{t("agrre-button")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="op-btn op-btn-ghost"
|
||||||
|
onClick={() => props.setIsShowAgreeTerms(false)}
|
||||||
|
>
|
||||||
|
{t("close")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AgreementContent;
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import AgreementContent from "./AgreementContent";
|
||||||
|
|
||||||
|
function AgreementSign(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [isChecked, setIsChecked] = useState(false);
|
||||||
|
const [isShowAgreeTerms, setIsShowAgreeTerms] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="op-modal op-modal-open absolute z-[448]">
|
||||||
|
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box overflow-y-auto hide-scrollbar text-sm p-4">
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<input
|
||||||
|
data-tut="IsAgree"
|
||||||
|
className="mr-3 op-checkbox op-checkbox-m"
|
||||||
|
type="checkbox"
|
||||||
|
value={isChecked}
|
||||||
|
onChange={(e) => {
|
||||||
|
setIsChecked(e.target.checked);
|
||||||
|
if (e.target.checked) {
|
||||||
|
props.setIsAgreeTour(false);
|
||||||
|
}
|
||||||
|
props.showFirstWidget();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-[11px] md:text-base">
|
||||||
|
<span>{t("agree-p1")}</span>
|
||||||
|
<span
|
||||||
|
className="font-bold text-blue-600 cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
setIsShowAgreeTerms(true);
|
||||||
|
props.setIsAgreeTour(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("agree-p2")}
|
||||||
|
</span>
|
||||||
|
<span> {t("agree-p3")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex ml-[35px] mt-3">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (isChecked) {
|
||||||
|
props.setIsAgreeTour(false);
|
||||||
|
props.setIsAgree(true);
|
||||||
|
} else {
|
||||||
|
props.setIsAgreeTour(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="op-btn op-btn-primary op-btn-sm w-full md:w-auto"
|
||||||
|
>
|
||||||
|
{t("agrre-button")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<span className="text-[11px]">{t("agreement-note")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isShowAgreeTerms && (
|
||||||
|
<AgreementContent
|
||||||
|
setIsAgreeTour={props.setIsAgreeTour}
|
||||||
|
setIsAgree={props.setIsAgree}
|
||||||
|
setIsShowAgreeTerms={setIsShowAgreeTerms}
|
||||||
|
showFirstWidget={props.showFirstWidget}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AgreementSign;
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
function BorderResize({ right, top }) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="borderResize"
|
|
||||||
style={{
|
|
||||||
right: right ? right + "px" : "-1px",
|
|
||||||
bottom: top ? top + "px" : "-1px",
|
|
||||||
borderRight: "3px solid #188ae2",
|
|
||||||
borderBottom: "3px solid #188ae2"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BorderResize;
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function BorderResize(props) {
|
||||||
|
const getHeight = () => {
|
||||||
|
const height = props.posHeight(props.pos, props.isSignYourself);
|
||||||
|
if (height > 14) {
|
||||||
|
return "14px";
|
||||||
|
} else {
|
||||||
|
return `${height}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: getHeight() || "14px",
|
||||||
|
height: getHeight() || "14px"
|
||||||
|
}}
|
||||||
|
className={`${props.right ? `-right-[12px]` : "-right-[2px]"} ${
|
||||||
|
props.top ? `-bottom-[12px]` : "-bottom-[2px] "
|
||||||
|
} absolute inline-block hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BorderResize;
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import opensignLogo from "../../assets/images/logo.png";
|
|
||||||
import {
|
|
||||||
Page,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
Document,
|
|
||||||
StyleSheet,
|
|
||||||
Image
|
|
||||||
} from "@react-pdf/renderer";
|
|
||||||
|
|
||||||
function Certificate({ pdfData }) {
|
|
||||||
const [isMultiSigners, setIsMultiSigners] = useState();
|
|
||||||
const [multiSigner, setMultiSigners] = useState([]);
|
|
||||||
const [isLoad, setIsLoad] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
handleSignerData();
|
|
||||||
// eslint-disable-next-line
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSignerData = () => {
|
|
||||||
const checkSigners = pdfData.filter((data) => data.Signers);
|
|
||||||
if (checkSigners && checkSigners.length > 0) {
|
|
||||||
setIsMultiSigners(true);
|
|
||||||
|
|
||||||
const checkSignSigners =
|
|
||||||
pdfData[0].AuditTrail &&
|
|
||||||
pdfData[0].AuditTrail.length > 0 &&
|
|
||||||
pdfData[0].AuditTrail.filter((data) => data.Activity === "Signed");
|
|
||||||
|
|
||||||
setMultiSigners(checkSignSigners);
|
|
||||||
} else {
|
|
||||||
setIsMultiSigners(false);
|
|
||||||
}
|
|
||||||
setIsLoad(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
page: {
|
|
||||||
borderRadius: "5px",
|
|
||||||
padding: "10px",
|
|
||||||
backgroundColor: "white"
|
|
||||||
},
|
|
||||||
section1: {
|
|
||||||
border: "1px solid rgb(177, 174, 174)",
|
|
||||||
padding: "20px"
|
|
||||||
},
|
|
||||||
textStyle: {
|
|
||||||
fontWeight: "bold",
|
|
||||||
fontSize: "11px",
|
|
||||||
marginBottom: "10px"
|
|
||||||
},
|
|
||||||
textStyle2: {
|
|
||||||
fontWeight: "600",
|
|
||||||
fontSize: "11px",
|
|
||||||
marginBottom: "10px",
|
|
||||||
color: "gray"
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
width: "71px",
|
|
||||||
height: "17px"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const generatedDate = () => {
|
|
||||||
const newDate = new Date();
|
|
||||||
const utcTime = newDate.toUTCString();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: "gray",
|
|
||||||
fontSize: "10px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Generated On {utcTime}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const changeCompletedDate = () => {
|
|
||||||
const completedOn = pdfData[0].updatedAt;
|
|
||||||
const newDate = new Date(completedOn);
|
|
||||||
const utcTime = newDate.toUTCString();
|
|
||||||
|
|
||||||
return <Text style={styles.textStyle2}>{utcTime}</Text>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const signerName = (data) => {
|
|
||||||
const getSignerName = pdfData[0].Signers.filter(
|
|
||||||
(sign) => sign.objectId === data.UserPtr.objectId
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
getSignerName[0] &&
|
|
||||||
getSignerName.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Name :
|
|
||||||
<Text style={styles.textStyle2}>{getSignerName[0].Name}</Text>
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Email :
|
|
||||||
<Text style={styles.textStyle2}>{getSignerName[0].Email}</Text>
|
|
||||||
</Text>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
isLoad && (
|
|
||||||
<Document>
|
|
||||||
{/** Page defines a single page of content. */}
|
|
||||||
<Page size="A4" style={styles.page}>
|
|
||||||
<View style={styles.section1}>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
marginBottom: "30px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image src={opensignLogo} style={styles.image} />
|
|
||||||
{generatedDate()}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={{ justifyContent: "center" }}>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
textAlign: "center",
|
|
||||||
fontSize: "20px",
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: "#31bceb",
|
|
||||||
marginBottom: "10px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
Certificate of Completion
|
|
||||||
</Text>
|
|
||||||
<View style={{ border: "1px solid #bdbbbb" }}></View>
|
|
||||||
<View>
|
|
||||||
<View>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontSize: "16px",
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: "#31bceb",
|
|
||||||
margin: "10px 0px 10px 0px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Summary
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View style={{ display: "flex", flexDirection: "column" }}>
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Document ID :
|
|
||||||
<Text style={styles.textStyle2}>{pdfData[0].objectId}</Text>
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Document Name :
|
|
||||||
<Text style={styles.textStyle2}>{pdfData[0].Name}</Text>
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Organization :
|
|
||||||
<Text style={styles.textStyle2}>
|
|
||||||
{pdfData[0].ExtUserPtr.Company}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Completed on : {changeCompletedDate()}
|
|
||||||
</Text>
|
|
||||||
{multiSigner && multiSigner.length > 0 && (
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Signers :
|
|
||||||
<Text style={styles.textStyle2}>
|
|
||||||
{multiSigner.length}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
{isMultiSigners ? (
|
|
||||||
<View style={{ display: "flex", flexDirection: "column" }}>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontSize: "16px",
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: "#31bceb",
|
|
||||||
margin: "10px 0px 10px 0px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Recipients
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<View>
|
|
||||||
{multiSigner &&
|
|
||||||
multiSigner.map((data, ind) => {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
key={ind}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
border: "0.4px solid #bdbbbb",
|
|
||||||
marginBottom: "10px"
|
|
||||||
}}
|
|
||||||
></View>
|
|
||||||
{signerName(data)}
|
|
||||||
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Accessed from :
|
|
||||||
<Text style={styles.textStyle2}>
|
|
||||||
{data.ipAddress}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View style={{ display: "flex", flexDirection: "column" }}>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontSize: "16px",
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: "#31bceb",
|
|
||||||
margin: "10px 0px 10px 0px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Recipients
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Signers : <Text style={styles.textStyle2}>1</Text>
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Name :
|
|
||||||
<Text style={styles.textStyle2}>
|
|
||||||
{pdfData[0].ExtUserPtr.Name}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Email :
|
|
||||||
<Text style={styles.textStyle2}>
|
|
||||||
{pdfData[0].ExtUserPtr.Email}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Accessed from :
|
|
||||||
<Text style={styles.textStyle2}>
|
|
||||||
{pdfData[0].AuditTrail &&
|
|
||||||
pdfData[0].AuditTrail[0].ipAddress}
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text style={styles.textStyle}>
|
|
||||||
Signed on : {changeCompletedDate()}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</Page>
|
|
||||||
</Document>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Certificate;
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { themeColor } from "../../constant/const";
|
|
||||||
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>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
background: themeColor,
|
|
||||||
color: "white",
|
|
||||||
padding: "5px",
|
|
||||||
fontFamily: "sans-serif",
|
|
||||||
marginTop: "5px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Signature
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
marginTop: "10px",
|
|
||||||
fontWeight: "600"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
<p>Your Signature</p>
|
|
||||||
<div className="defaultSignBox">
|
|
||||||
<img
|
|
||||||
alt="default img"
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "contain"
|
|
||||||
}}
|
|
||||||
src={defaultSignImg}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
style={{
|
|
||||||
background: themeColor,
|
|
||||||
color: "white",
|
|
||||||
marginTop: "20px",
|
|
||||||
cursor: "pointer",
|
|
||||||
marginBottom: "10px"
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
className="finishBtn finishnHover"
|
|
||||||
onClick={() => confirmToaddDefaultSign()}
|
|
||||||
>
|
|
||||||
Auto Sign All
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DefaultSignature;
|
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
function DefaultSignature(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
|
||||||
|
const myInitial = useSelector((state) => state.widget.myInitial)
|
||||||
|
const tabName = ["my-signature", "my-initials"];
|
||||||
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
|
const confirmToaddDefaultSign = (type) => {
|
||||||
|
if (!props.isAgree) {
|
||||||
|
props.setIsAgreeTour(true);
|
||||||
|
} else {
|
||||||
|
if (props?.xyPosition.length > 0) {
|
||||||
|
//check signature or initial widgets exist or not for auto signing
|
||||||
|
const getCurrentSignerXY = props?.xyPosition.filter(
|
||||||
|
(data) => data.Id === props.uniqueId
|
||||||
|
);
|
||||||
|
const checkIsSignInitialExist = getCurrentSignerXY?.every(
|
||||||
|
(placeholderObj) =>
|
||||||
|
placeholderObj?.placeHolder?.some((placeholder) =>
|
||||||
|
placeholder?.pos?.some((posItem) => posItem?.type === type)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (checkIsSignInitialExist) {
|
||||||
|
props?.setDefaultSignAlert({
|
||||||
|
isShow: true,
|
||||||
|
alertMessage: t("default-sign-alert", { widgetsType: type }),
|
||||||
|
type: type
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
props?.setDefaultSignAlert({
|
||||||
|
isShow: true,
|
||||||
|
alertMessage: t("defaultSign-alert", { widgetsType: type })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
props?.setDefaultSignAlert({
|
||||||
|
isShow: true,
|
||||||
|
alertMessage: t("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">
|
||||||
|
<p className="text-base-content">{t("signature")}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center items-center mt-2">
|
||||||
|
<div role="tablist" className="op-tabs op-tabs-bordered">
|
||||||
|
{tabName.map((tabData, ind) => (
|
||||||
|
<div
|
||||||
|
onClick={() => setActiveTab(ind)}
|
||||||
|
key={ind}
|
||||||
|
role="tab"
|
||||||
|
className={`${
|
||||||
|
activeTab === ind ? "op-tab-active" : ""
|
||||||
|
} op-tab flex items-center pb-10 md:pb-0`}
|
||||||
|
>
|
||||||
|
<span className="ml-1 text-[7px] font-medium md:font-normal md:text-[12px]">
|
||||||
|
{t(`${tabData}`)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center mt-[10px] font-semibold relative">
|
||||||
|
<div className="op-card shadow-md h-[111px] w-[90%] p-2">
|
||||||
|
{activeTab === 0 ? (
|
||||||
|
<img
|
||||||
|
alt="signature"
|
||||||
|
className="w-full h-full object-contain"
|
||||||
|
src={defaultSignImg}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
activeTab === 1 &&
|
||||||
|
(myInitial ? (
|
||||||
|
<img
|
||||||
|
alt="signature"
|
||||||
|
className="w-full h-full object-contain"
|
||||||
|
src={myInitial}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex justify-center items-center h-full">
|
||||||
|
<span>{t("initial-alert")}</span>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-primary op-btn-sm mt-[10px]"
|
||||||
|
onClick={() =>
|
||||||
|
confirmToaddDefaultSign(
|
||||||
|
activeTab === 0 ? "signature" : activeTab === 1 && "initials"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
disabled={
|
||||||
|
activeTab === 0 && !props?.isDefault
|
||||||
|
? true
|
||||||
|
: activeTab === 1 && !myInitial
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("auto-sign-all")}
|
||||||
|
</button>
|
||||||
|
{!props.isDefault && (
|
||||||
|
<div className="absolute bg-black/70 text-white w-full h-full flex items-center justify-center text-[11px] cursor-default">
|
||||||
|
<span className="-rotate-45">
|
||||||
|
Document creator has disabled this option
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DefaultSignature;
|
||||||
@@ -1,23 +1,21 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import LoaderWithMsg from "../../primitives/LoaderWithMsg";
|
||||||
import Loader from "../../primitives/LoaderWithMsg";
|
|
||||||
import { contractDocument } from "../../constant/Utils";
|
import { contractDocument } from "../../constant/Utils";
|
||||||
import HandleError from "../../primitives/HandleError";
|
import HandleError from "../../primitives/HandleError";
|
||||||
|
import { useLocation, useNavigate } from "react-router";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
function useQuery() {
|
||||||
|
return new URLSearchParams(useLocation().search);
|
||||||
|
}
|
||||||
function DraftDocument() {
|
function DraftDocument() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const query = useQuery();
|
||||||
|
const docId = query.get("docId");
|
||||||
const [isLoading, setIsLoading] = useState({
|
const [isLoading, setIsLoading] = useState({
|
||||||
isLoader: true,
|
isLoader: true,
|
||||||
message: "This might take some time"
|
message: t("loading-mssg")
|
||||||
});
|
});
|
||||||
|
|
||||||
const rowLevel =
|
|
||||||
localStorage.getItem("rowlevel") &&
|
|
||||||
JSON.parse(localStorage.getItem("rowlevel"));
|
|
||||||
const docId =
|
|
||||||
rowLevel && rowLevel?.id
|
|
||||||
? rowLevel.id
|
|
||||||
: rowLevel?.objectId && rowLevel.objectId;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDocumentDetails();
|
getDocumentDetails();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -32,15 +30,19 @@ function DraftDocument() {
|
|||||||
documentData === "Error: Something went wrong!" ||
|
documentData === "Error: Something went wrong!" ||
|
||||||
(documentData.result && documentData.result.error)
|
(documentData.result && documentData.result.error)
|
||||||
) {
|
) {
|
||||||
setIsLoading({
|
if (documentData?.result?.error?.includes("deleted")) {
|
||||||
isLoader: false,
|
setIsLoading({
|
||||||
message: "Error: Something went wrong!"
|
isLoader: false,
|
||||||
});
|
message: t("document-deleted")
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setIsLoading({
|
||||||
|
isLoader: false,
|
||||||
|
message: t("something-went-wrong-mssg")
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setIsLoading({
|
setIsLoading({ isLoader: false, message: t("no-data") });
|
||||||
isLoader: false,
|
|
||||||
message: "No data found!"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,15 +55,15 @@ function DraftDocument() {
|
|||||||
const signedUrl = data.SignedUrl;
|
const signedUrl = data.SignedUrl;
|
||||||
//checking if document has completed and request signature flow
|
//checking if document has completed and request signature flow
|
||||||
if (data?.IsCompleted && signerExist?.length > 0) {
|
if (data?.IsCompleted && signerExist?.length > 0) {
|
||||||
navigate(`/pdfRequestFiles/${data.objectId}`);
|
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||||
}
|
}
|
||||||
//checking if document has completed and signyour-self flow
|
//checking if document has completed and signyour-self flow
|
||||||
else if (!signerExist && !isPlaceholder) {
|
else if ((!signerExist && !isPlaceholder) || data?.IsSignyourself) {
|
||||||
navigate(`/signaturePdf/${data.objectId}`);
|
navigate(`/signaturePdf/${data.objectId}`);
|
||||||
}
|
}
|
||||||
//checking if document has declined by someone
|
//checking if document has declined by someone
|
||||||
else if (isDecline) {
|
else if (isDecline) {
|
||||||
navigate(`/pdfRequestFiles/${data.objectId}`);
|
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||||
//checking draft type document
|
//checking draft type document
|
||||||
} else if (
|
} else if (
|
||||||
signerExist?.length > 0 &&
|
signerExist?.length > 0 &&
|
||||||
@@ -71,8 +73,8 @@ function DraftDocument() {
|
|||||||
navigate(`/placeHolderSign/${data.objectId}`);
|
navigate(`/placeHolderSign/${data.objectId}`);
|
||||||
}
|
}
|
||||||
//Inprogress document
|
//Inprogress document
|
||||||
else if (isPlaceholder?.length > 0 && signerExist?.length > 0) {
|
else if (isPlaceholder?.length > 0 && signedUrl) {
|
||||||
navigate(`/pdfRequestFiles/${data.objectId}`);
|
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||||
} //placeholder draft document
|
} //placeholder draft document
|
||||||
else if (
|
else if (
|
||||||
(signerExist?.length > 0 &&
|
(signerExist?.length > 0 &&
|
||||||
@@ -86,7 +88,7 @@ function DraftDocument() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{isLoading.isLoader ? (
|
{isLoading.isLoader ? (
|
||||||
<Loader isLoading={isLoading} />
|
<LoaderWithMsg isLoading={isLoading} />
|
||||||
) : (
|
) : (
|
||||||
<HandleError handleError={isLoading.message} />
|
<HandleError handleError={isLoading.message} />
|
||||||
)}
|
)}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { isEnableSubscription, themeColor } from "../../constant/const";
|
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
import { radioButtonWidget } from "../../constant/Utils";
|
import { radioButtonWidget } from "../../constant/Utils";
|
||||||
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
|
import { useTranslation } from "react-i18next";
|
||||||
import Upgrade from "../../primitives/Upgrade";
|
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||||
|
|
||||||
function DropdownWidgetOption(props) {
|
function DropdownWidgetOption(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [dropdownOptionList, setDropdownOptionList] = useState([
|
const [dropdownOptionList, setDropdownOptionList] = useState([
|
||||||
"option-1",
|
"option-1",
|
||||||
"option-2"
|
"option-2"
|
||||||
]);
|
]);
|
||||||
const [minCount, setMinCount] = useState(0);
|
const [minCount, setMinCount] = useState(0);
|
||||||
const [maxCount, setMaxCount] = useState(0);
|
const [maxCount, setMaxCount] = useState(0);
|
||||||
const [dropdownName, setDropdownName] = useState(props.type);
|
const [dropdownName, setDropdownName] = useState();
|
||||||
const [isReadOnly, setIsReadOnly] = useState(false);
|
const [isReadOnly, setIsReadOnly] = useState(false);
|
||||||
const [isHideLabel, setIsHideLabel] = useState(false);
|
const [isHideLabel, setIsHideLabel] = useState(false);
|
||||||
const [status, setStatus] = useState("required");
|
const [status, setStatus] = useState("required");
|
||||||
@@ -21,7 +22,7 @@ function DropdownWidgetOption(props) {
|
|||||||
|
|
||||||
const resetState = () => {
|
const resetState = () => {
|
||||||
setDropdownOptionList(["option-1", "option-2"]);
|
setDropdownOptionList(["option-1", "option-2"]);
|
||||||
setDropdownName(props.type);
|
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
|
||||||
setIsReadOnly(false);
|
setIsReadOnly(false);
|
||||||
setIsHideLabel(false);
|
setIsHideLabel(false);
|
||||||
setMinCount(0);
|
setMinCount(0);
|
||||||
@@ -29,11 +30,10 @@ function DropdownWidgetOption(props) {
|
|||||||
setDefaultCheckbox([]);
|
setDefaultCheckbox([]);
|
||||||
setDefaultValue("");
|
setDefaultValue("");
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
props.currWidgetsDetails?.options?.name &&
|
props.currWidgetsDetails?.options?.name &&
|
||||||
props.currWidgetsDetails?.options?.values
|
props.currWidgetsDetails?.options?.values?.length > 0
|
||||||
) {
|
) {
|
||||||
setDropdownName(props.currWidgetsDetails?.options?.name);
|
setDropdownName(props.currWidgetsDetails?.options?.name);
|
||||||
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
|
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
|
||||||
@@ -115,26 +115,9 @@ function DropdownWidgetOption(props) {
|
|||||||
defaultData,
|
defaultData,
|
||||||
isHideLabel
|
isHideLabel
|
||||||
);
|
);
|
||||||
// props.setShowDropdown(false);
|
resetState();
|
||||||
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 handleDefaultCheck = (index) => {
|
||||||
const getDefaultCheck = defaultCheckbox.includes(index);
|
const getDefaultCheck = defaultCheckbox.includes(index);
|
||||||
@@ -144,10 +127,9 @@ function DropdownWidgetOption(props) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
|
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
|
||||||
<div className="h-full p-[15px]">
|
<div className="h-full p-[15px] text-base-content">
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -155,40 +137,29 @@ function DropdownWidgetOption(props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ fontSize: "13px", fontWeight: "600" }}>
|
<label className="text-[13px] font-semibold">
|
||||||
Name<span style={{ color: "red", fontSize: 13 }}> *</span>
|
{t("name")}
|
||||||
|
<span className="text-[red] text-[13px]"> *</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
required
|
required
|
||||||
defaultValue={dropdownName}
|
|
||||||
value={dropdownName}
|
value={dropdownName}
|
||||||
onChange={(e) => setDropdownName(e.target.value)}
|
onChange={(e) => setDropdownName(e.target.value)}
|
||||||
className="drodown-input"
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label
|
<label className="text-[13px] font-semibold mt-[5px]">
|
||||||
style={{ fontSize: "13px", fontWeight: "600", marginTop: "5px" }}
|
{t("options")}
|
||||||
>
|
|
||||||
Options
|
|
||||||
</label>
|
</label>
|
||||||
|
<div className="flex flex-col">
|
||||||
<div
|
{dropdownOptionList?.map((option, index) => (
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{dropdownOptionList.map((option, index) => (
|
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
style={{
|
className="flex flex-row mb-[5px] items-center"
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
marginBottom: "5px",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{props.type === "checkbox" && !props.isSignYourself && (
|
{props.type === "checkbox" && props.isShowAdvanceFeature && (
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={handleDefaultCheck(index)}
|
checked={handleDefaultCheck(index)}
|
||||||
@@ -206,124 +177,50 @@ function DropdownWidgetOption(props) {
|
|||||||
setDefaultCheckbox(removeOption);
|
setDefaultCheckbox(removeOption);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
className="op-checkbox focus:outline-none hover:border-base-content mr-[5px]"
|
||||||
width: "23px",
|
|
||||||
height: "19px",
|
|
||||||
marginRight: "5px"
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<input
|
<input
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
required
|
required
|
||||||
className="drodown-input"
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
type="text"
|
type="text"
|
||||||
value={option}
|
value={option}
|
||||||
onChange={(e) => handleInputChange(index, e.target.value)}
|
onChange={(e) => handleInputChange(index, e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<i
|
<i
|
||||||
className="fa-solid fa-rectangle-xmark"
|
className="fa-light fa-rectangle-xmark text-[25px] ml-[10px] text-accent cursor-pointer"
|
||||||
onClick={() => handleDeleteInput(index)}
|
onClick={() => handleDeleteInput(index)}
|
||||||
style={{
|
|
||||||
color: "red",
|
|
||||||
fontSize: "25px",
|
|
||||||
marginLeft: "10px"
|
|
||||||
}}
|
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<i
|
<i
|
||||||
onClick={handleAddInput}
|
onClick={handleAddInput}
|
||||||
style={{
|
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
|
||||||
cursor: "pointer",
|
|
||||||
color: themeColor,
|
|
||||||
fontSize: "25px",
|
|
||||||
marginLeft: "10px"
|
|
||||||
}}
|
|
||||||
className="fa-solid fa-square-plus"
|
|
||||||
></i>
|
></i>
|
||||||
<div>
|
|
||||||
{props.type === "checkbox" && !props.isSignYourself && (
|
|
||||||
<>
|
|
||||||
<label
|
|
||||||
style={{
|
|
||||||
fontSize: "13px",
|
|
||||||
fontWeight: "600",
|
|
||||||
color: !props.isSubscribe && "gray"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Minimun check
|
|
||||||
</label>
|
|
||||||
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
defaultValue={0}
|
|
||||||
value={minCount}
|
|
||||||
onChange={(e) => {
|
|
||||||
const count = handleSetMinMax(e);
|
|
||||||
setMinCount(count);
|
|
||||||
}}
|
|
||||||
className={
|
|
||||||
props.isSubscribe || !isEnableSubscription
|
|
||||||
? "drodown-input"
|
|
||||||
: "disabled drodown-input"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
style={{
|
|
||||||
fontSize: "13px",
|
|
||||||
fontWeight: "600",
|
|
||||||
color: !props.isSubscribe && "gray"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Maximum check
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
defaultValue={0}
|
|
||||||
value={maxCount}
|
|
||||||
onChange={(e) => {
|
|
||||||
const count = handleSetMinMax(e);
|
|
||||||
setMaxCount(count);
|
|
||||||
}}
|
|
||||||
className={
|
|
||||||
props.isSubscribe || !isEnableSubscription
|
|
||||||
? "drodown-input"
|
|
||||||
: "disabled drodown-input"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{["dropdown", radioButtonWidget].includes(props.type) && (
|
{["dropdown", radioButtonWidget].includes(props.type) && (
|
||||||
<>
|
<>
|
||||||
<label
|
<label className="text-[13px] font-semibold mt-[5px]">
|
||||||
style={{
|
{t("default-value")}
|
||||||
fontSize: "13px",
|
|
||||||
fontWeight: "600",
|
|
||||||
marginTop: "5px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Default value
|
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
onChange={(e) => setDefaultValue(e.target.value)}
|
onChange={(e) => setDefaultValue(e.target.value)}
|
||||||
className="drodown-input"
|
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
name="defaultvalue"
|
name="defaultvalue"
|
||||||
value={defaultValue}
|
value={defaultValue}
|
||||||
placeholder="select default value"
|
placeholder="select default value"
|
||||||
>
|
>
|
||||||
<option value="" disabled hidden style={{ fontSize: "13px" }}>
|
<option value="" disabled hidden className="text-[13px]">
|
||||||
Select...
|
{t("select")}...
|
||||||
</option>
|
</option>
|
||||||
{dropdownOptionList.map((data, ind) => {
|
{dropdownOptionList.map((data, ind) => {
|
||||||
return (
|
return (
|
||||||
<option
|
<option className="text-[13px]" key={ind} value={data}>
|
||||||
style={{ fontSize: "13px" }}
|
|
||||||
key={ind}
|
|
||||||
value={data}
|
|
||||||
>
|
|
||||||
{data}
|
{data}
|
||||||
</option>
|
</option>
|
||||||
);
|
);
|
||||||
@@ -333,87 +230,116 @@ function DropdownWidgetOption(props) {
|
|||||||
)}
|
)}
|
||||||
{props.type !== "checkbox" && props.type !== radioButtonWidget && (
|
{props.type !== "checkbox" && props.type !== radioButtonWidget && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
gap: 10,
|
|
||||||
marginTop: "0.5rem"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{statusArr.map((data, ind) => {
|
{statusArr.map((data, ind) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={ind}
|
key={ind}
|
||||||
style={{
|
className="flex flex-row gap-[5px] items-center"
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
gap: 5,
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
style={{ accentColor: "red" }}
|
className="op-radio op-radio-xs my-1"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="status"
|
name="status"
|
||||||
onChange={() => setStatus(data.toLowerCase())}
|
onChange={() => setStatus(data.toLowerCase())}
|
||||||
checked={status.toLowerCase() === data.toLowerCase()}
|
checked={status.toLowerCase() === data.toLowerCase()}
|
||||||
/>
|
/>
|
||||||
<div style={{ fontSize: "13px", fontWeight: "500" }}>
|
<div className="text-[13px] font-500">{data}</div>
|
||||||
{data}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{["checkbox", radioButtonWidget].includes(props.type) && (
|
<div className="flex items-center mt-3 mb-3">
|
||||||
<div className="flex flex-row gap-5 mt-2 items-center text-center">
|
<span>{t("font-size")} :</span>
|
||||||
{!props.isSignYourself && (
|
<select
|
||||||
<div>
|
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(parseInt(e.target.value))}
|
||||||
|
>
|
||||||
|
{fontsizeArr.map((size, ind) => {
|
||||||
|
return (
|
||||||
|
<option className="text-[13px]" value={size} key={ind}>
|
||||||
|
{size}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
<div className="flex flex-row gap-1 items-center ml-4 ">
|
||||||
|
<span>{t("color")} : </span>
|
||||||
|
<select
|
||||||
|
value={
|
||||||
|
props.fontColor ||
|
||||||
|
props.currWidgetsDetails?.options?.fontColor ||
|
||||||
|
"black"
|
||||||
|
}
|
||||||
|
onChange={(e) => props.setFontColor(e.target.value)}
|
||||||
|
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||||
|
>
|
||||||
|
{fontColorArr.map((color, ind) => {
|
||||||
|
return (
|
||||||
|
<option value={color} key={ind}>
|
||||||
|
{t(`color-type.${color}`)}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
props.fontColor ||
|
||||||
|
props.currWidgetsDetails?.options?.fontColor ||
|
||||||
|
"black"
|
||||||
|
}}
|
||||||
|
className="w-5 h-[19px] ml-1"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{["checkbox", radioButtonWidget, "dropdown"].includes(
|
||||||
|
props.type
|
||||||
|
) && (
|
||||||
|
<div className="flex flex-row gap-5 my-2 items-center text-center">
|
||||||
|
{props.isShowAdvanceFeature && (
|
||||||
|
<div className="flex items-center">
|
||||||
<input
|
<input
|
||||||
id="isreadonly"
|
id="isreadonly"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={isReadOnly}
|
checked={isReadOnly}
|
||||||
onChange={(e) => {
|
className="op-checkbox op-checkbox-sm"
|
||||||
setIsReadOnly(e.target.checked);
|
onChange={(e) => setIsReadOnly(e.target.checked)}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<label className="ml-1" htmlFor="isreadonly">
|
<label className="ml-1 mb-0" htmlFor="isreadonly">
|
||||||
Is read only
|
{t("read-only")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
{props.type !== "dropdown" && (
|
||||||
<input
|
<div className="flex items-center">
|
||||||
id="ishidelabel"
|
<input
|
||||||
type="checkbox"
|
id="ishidelabel"
|
||||||
checked={isHideLabel}
|
type="checkbox"
|
||||||
onChange={(e) => {
|
checked={isHideLabel}
|
||||||
setIsHideLabel(e.target.checked);
|
className="op-checkbox op-checkbox-sm"
|
||||||
}}
|
onChange={(e) => setIsHideLabel(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label className="ml-1" htmlFor="ishidelabel">
|
<label className="ml-1 mb-0" htmlFor="ishidelabel">
|
||||||
Hide labels
|
{t("hide-labels")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{props.type === "checkbox" &&
|
|
||||||
!props.isSignYourself &&
|
|
||||||
!isEnableSubscription && (
|
|
||||||
<PremiumAlertHeader
|
|
||||||
message={
|
|
||||||
"Field validations are free in beta, this feature will incur a fee later."
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
props.type === "checkbox" && !props.isSignYourself
|
props.type === "checkbox" && props.isShowAdvanceFeature
|
||||||
? "mb-[15px]"
|
? "mb-[15px]"
|
||||||
: "my-[15px]"
|
: "my-[15px]"
|
||||||
} w-full h-[1px] bg-[#9f9f9f]`}
|
} w-full h-[1px] bg-[#9f9f9f]`}
|
||||||
@@ -421,25 +347,21 @@ function DropdownWidgetOption(props) {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
disabled={dropdownOptionList.length === 0 && true}
|
disabled={dropdownOptionList.length === 0 && true}
|
||||||
style={{
|
|
||||||
background: themeColor,
|
|
||||||
color: "white"
|
|
||||||
}}
|
|
||||||
type="submit"
|
type="submit"
|
||||||
className={"finishBtn"}
|
className="op-btn op-btn-primary"
|
||||||
>
|
>
|
||||||
Save
|
{t("save")}
|
||||||
</button>
|
</button>
|
||||||
{props.currWidgetsDetails?.options?.values?.length > 0 && (
|
{props.currWidgetsDetails?.options?.values?.length > 0 && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="finishBtn cancelBtn"
|
className="op-btn op-btn-ghost ml-1"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.handleClose && props.handleClose();
|
props.handleClose && props.handleClose();
|
||||||
resetState();
|
resetState();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
{t("cancel")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import "../../styles/AddUser.css";
|
|
||||||
import { getFileName } from "../../constant/Utils";
|
|
||||||
// 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"
|
|
||||||
});
|
|
||||||
|
|
||||||
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 data = { ...formData, SendinOrder: isChecked };
|
|
||||||
onSuccess(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="addusercontainer">
|
|
||||||
<div className="form-wrapper">
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div>
|
|
||||||
<label htmlFor="name" style={{ fontSize: 13 }}>
|
|
||||||
File
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: "0.5rem 0.75rem",
|
|
||||||
border: "1px solid #d1d5db",
|
|
||||||
borderRadius: "0.375rem",
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
fontWeight: "700"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getFileName(template.URL)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="form-section">
|
|
||||||
<label htmlFor="name" style={{ fontSize: 13 }}>
|
|
||||||
Name
|
|
||||||
<span style={{ color: "red", fontSize: 13 }}> *</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="Name"
|
|
||||||
value={formData.Name}
|
|
||||||
onChange={(e) => handleStrInput(e)}
|
|
||||||
required
|
|
||||||
className="addUserInput"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-section">
|
|
||||||
<label htmlFor="Note" style={{ fontSize: 13 }}>
|
|
||||||
Note
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="Note"
|
|
||||||
id="Note"
|
|
||||||
value={formData.Note}
|
|
||||||
onChange={(e) => handleStrInput(e)}
|
|
||||||
className="addUserInput"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-section">
|
|
||||||
<label htmlFor="Description" style={{ fontSize: 13 }}>
|
|
||||||
Description
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="Description"
|
|
||||||
id="Description"
|
|
||||||
value={formData.Description}
|
|
||||||
onChange={(e) => handleStrInput(e)}
|
|
||||||
className="addUserInput"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-section">
|
|
||||||
<label style={{ fontSize: 13 }}>Send In Order</label>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 8,
|
|
||||||
marginLeft: 8,
|
|
||||||
marginBottom: 5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={"true"}
|
|
||||||
name="SendinOrder"
|
|
||||||
checked={formData.SendinOrder === "true"}
|
|
||||||
onChange={handleStrInput}
|
|
||||||
/>
|
|
||||||
<div style={{ fontSize: 12 }}>Yes</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 8,
|
|
||||||
marginLeft: 8,
|
|
||||||
marginBottom: 5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={"false"}
|
|
||||||
name="SendinOrder"
|
|
||||||
checked={formData.SendinOrder === "false"}
|
|
||||||
onChange={handleStrInput}
|
|
||||||
/>
|
|
||||||
<div style={{ fontSize: 12 }}>No</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* <SelectFolder onSuccess={handleFolder} folderCls={"contracts_Template"} /> */}
|
|
||||||
<div className="buttoncontainer">
|
|
||||||
<button type="submit" className="submitbutton">
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditTemplate;
|
|
||||||
@@ -0,0 +1,538 @@
|
|||||||
|
import {
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
} from "react";
|
||||||
|
import {
|
||||||
|
base64ToArrayBuffer,
|
||||||
|
convertBase64ToFile,
|
||||||
|
generatePdfName,
|
||||||
|
getFileName
|
||||||
|
} from "../../constant/Utils";
|
||||||
|
import {
|
||||||
|
maxDescriptionLength,
|
||||||
|
maxNoteLength,
|
||||||
|
maxTitleLength
|
||||||
|
} from "../../constant/const";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Tooltip } from "react-tooltip";
|
||||||
|
import SignersInput from "../shared/fields/SignersInput";
|
||||||
|
import { PDFDocument } from "pdf-lib";
|
||||||
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
|
import { SaveFileSize } from "../../constant/saveFileSize";
|
||||||
|
|
||||||
|
const EditTemplate = ({
|
||||||
|
title,
|
||||||
|
handleClose,
|
||||||
|
pdfbase64,
|
||||||
|
template,
|
||||||
|
onSuccess,
|
||||||
|
setPdfArrayBuffer,
|
||||||
|
setPdfBase64Url,
|
||||||
|
}) => {
|
||||||
|
const appName =
|
||||||
|
"OpenSign™";
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const inputFileRef = useRef(null);
|
||||||
|
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,
|
||||||
|
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false",
|
||||||
|
IsTourEnabled: template?.IsTourEnabled
|
||||||
|
? `${template?.IsTourEnabled}`
|
||||||
|
: "false",
|
||||||
|
NotifyOnSignatures:
|
||||||
|
template?.NotifyOnSignatures !== undefined
|
||||||
|
? template?.NotifyOnSignatures
|
||||||
|
: false,
|
||||||
|
Bcc: template?.Bcc,
|
||||||
|
RedirectUrl: template?.RedirectUrl || "",
|
||||||
|
AllowModifications: template?.AllowModifications || false,
|
||||||
|
TimeToCompleteDays: template?.TimeToCompleteDays || 15
|
||||||
|
});
|
||||||
|
const [isUpdate, setIsUpdate] = useState(false);
|
||||||
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
|
const [uploadPdf, setUploadPdf] = useState({
|
||||||
|
name: "",
|
||||||
|
base64: "",
|
||||||
|
url: ""
|
||||||
|
});
|
||||||
|
const handleDrop = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const file = e.dataTransfer.files[0];
|
||||||
|
handleFile(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFile = (file) => {
|
||||||
|
if (file && file.type === "application/pdf") {
|
||||||
|
handleReplaceFileValdition(file);
|
||||||
|
// You can handle the file here
|
||||||
|
} else {
|
||||||
|
alert("Only pdf files are allowed.");
|
||||||
|
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
// `isValidURL` is used to check valid webhook url
|
||||||
|
function isValidURL(value) {
|
||||||
|
try {
|
||||||
|
const url = new URL(value);
|
||||||
|
return url.protocol === "https:" || url.protocol === "http:";
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStrInput = (e) => {
|
||||||
|
setIsUpdate(true);
|
||||||
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
|
};
|
||||||
|
const getPdfMetadataHash = async (pdfBytes) => {
|
||||||
|
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||||
|
const pages = pdfDoc.getPages();
|
||||||
|
const metaString = pages
|
||||||
|
.map((page, index) => {
|
||||||
|
const { width, height } = page.getSize();
|
||||||
|
return `${index + 1}:${Math.round(width)}x${Math.round(height)}`;
|
||||||
|
})
|
||||||
|
.join("|");
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(metaString);
|
||||||
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
||||||
|
return Array.from(new Uint8Array(hashBuffer))
|
||||||
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileInput = async (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
handleReplaceFileValdition(file);
|
||||||
|
};
|
||||||
|
const handleReplaceFileValdition = async (file) => {
|
||||||
|
try {
|
||||||
|
const basePdfBytes = base64ToArrayBuffer(pdfbase64);
|
||||||
|
const expectedHash = await getPdfMetadataHash(basePdfBytes);
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onload = async (event) => {
|
||||||
|
const uploadedPdfBytes = event.target.result;
|
||||||
|
const uploadedHash = await getPdfMetadataHash(uploadedPdfBytes);
|
||||||
|
|
||||||
|
if (expectedHash === uploadedHash) {
|
||||||
|
const arrayBuffer = uploadedPdfBytes;
|
||||||
|
const uint8Array = new Uint8Array(arrayBuffer);
|
||||||
|
const binaryString = Array.from(uint8Array)
|
||||||
|
.map((b) => String.fromCharCode(b))
|
||||||
|
.join("");
|
||||||
|
const base64 = btoa(binaryString);
|
||||||
|
const pdfName = generatePdfName(16);
|
||||||
|
setIsUpdate(true);
|
||||||
|
setUploadPdf((prev) => ({ ...prev, name: pdfName, base64: base64 }));
|
||||||
|
// alert("✅ PDFs match (based on page number, width, height)");
|
||||||
|
} else {
|
||||||
|
alert("❌ PDF do NOT match based on page number, width, height");
|
||||||
|
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fileReader.readAsArrayBuffer(file);
|
||||||
|
} catch (err) {
|
||||||
|
alert("Error: " + err.message);
|
||||||
|
if (inputFileRef.current) inputFileRef.current.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Define a function to handle form submission
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (formData.RedirectUrl && !isValidURL(formData?.RedirectUrl)) {
|
||||||
|
alert(t("invalid-redirect-url"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (formData?.Name?.length > maxTitleLength) {
|
||||||
|
alert(t("title-length-alert"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (formData?.Note?.length > maxNoteLength) {
|
||||||
|
alert(t("note-length-alert"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (formData?.Description?.length > maxDescriptionLength) {
|
||||||
|
alert(t("description-length-alert"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let pdfUrl;
|
||||||
|
if (uploadPdf?.base64) {
|
||||||
|
pdfUrl = await convertBase64ToFile(
|
||||||
|
uploadPdf.name,
|
||||||
|
uploadPdf.base64,
|
||||||
|
);
|
||||||
|
setUploadPdf((prev) => ({ ...prev, url: pdfUrl }));
|
||||||
|
const pdfBuffer = base64ToArrayBuffer(uploadPdf.base64);
|
||||||
|
setPdfArrayBuffer && setPdfArrayBuffer(pdfBuffer);
|
||||||
|
setPdfBase64Url && setPdfBase64Url(uploadPdf.base64);
|
||||||
|
const tenantId =
|
||||||
|
localStorage.getItem("TenantId") ||
|
||||||
|
template?.ExtUserPtr?.TenantId?.objectId;
|
||||||
|
const buffer = atob(uploadPdf.base64);
|
||||||
|
SaveFileSize(buffer.length, pdfUrl, tenantId);
|
||||||
|
}
|
||||||
|
const isChecked = formData.SendinOrder === "true" ? true : false;
|
||||||
|
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
|
||||||
|
const AutoReminder = formData?.AutomaticReminders || false;
|
||||||
|
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
|
||||||
|
const allowModify = formData?.AllowModifications || false;
|
||||||
|
let reminderDate = {};
|
||||||
|
const remindOnceInEvery = formData?.RemindOnceInEvery;
|
||||||
|
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
|
||||||
|
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
|
||||||
|
if (AutoReminder && reminderCount > 15) {
|
||||||
|
alert(t("only-15-reminder-allowed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (AutoReminder) {
|
||||||
|
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
|
||||||
|
const ReminderDate = new Date(template?.createdAt);
|
||||||
|
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
|
||||||
|
reminderDate = { NextReminderDate: ReminderDate };
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
...formData,
|
||||||
|
...(pdfUrl ? { URL: pdfUrl } : {}),
|
||||||
|
SendinOrder: isChecked,
|
||||||
|
IsEnableOTP: IsEnableOTP,
|
||||||
|
IsTourEnabled: isTourEnabled,
|
||||||
|
AllowModifications: allowModify,
|
||||||
|
...reminderDate
|
||||||
|
};
|
||||||
|
onSuccess(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||||
|
const handleNotifySignChange = (value) => {
|
||||||
|
setIsUpdate(true);
|
||||||
|
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
||||||
|
};
|
||||||
|
const handleBcc = (data) => {
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
const trimEmail = data.map((item) => ({
|
||||||
|
objectId: item?.value,
|
||||||
|
Name: item?.label,
|
||||||
|
Email: item?.email
|
||||||
|
}));
|
||||||
|
setIsUpdate(true);
|
||||||
|
setFormData((prev) => ({ ...prev, Bcc: trimEmail }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditTemplateClose = () => {
|
||||||
|
if (isUpdate) {
|
||||||
|
setShowConfirm(true);
|
||||||
|
} else {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const discardChanges = () => {
|
||||||
|
setShowConfirm(false);
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ModalUi
|
||||||
|
isOpen
|
||||||
|
title={isUpdate ? `${title} (unsaved)` : title}
|
||||||
|
handleClose={handleEditTemplateClose}
|
||||||
|
>
|
||||||
|
<ModalUi isOpen={showConfirm} showClose={false}>
|
||||||
|
<div className="p-[20px]">
|
||||||
|
<p className="text-base font-normal text-base-content py-[5px] md:py-[6px] px-[5px]">
|
||||||
|
{t("unsaved-changes-discard-them?")}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center mt-2.5 gap-2 md:gap-3 text-white">
|
||||||
|
<button
|
||||||
|
className="op-btn op-btn-primary px-6"
|
||||||
|
onClick={discardChanges}
|
||||||
|
>
|
||||||
|
{t("yes-discard")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="op-btn op-btn-secondary px-4 md:px-6"
|
||||||
|
onClick={() => setShowConfirm(false)}
|
||||||
|
>
|
||||||
|
{t("cancel")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
<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]">
|
||||||
|
{t("report-heading.File")}
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className="border-[1.5px] border-dashed border-gray-300 rounded-lg px-4 py-6 text-center text-gray-500 bg-white cursor-pointer hover:border-base-content transition"
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onClick={() => inputFileRef?.current?.click()}
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
htmlFor="fileUpload"
|
||||||
|
className="cursor-pointer text-center mb-0"
|
||||||
|
>
|
||||||
|
Browse or drag & drop a new file to replace the existng one
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref={inputFileRef}
|
||||||
|
type="file"
|
||||||
|
className="hidden"
|
||||||
|
accept="application/pdf"
|
||||||
|
onChange={(e) => handleFileInput(e)}
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
/>
|
||||||
|
{uploadPdf?.name && (
|
||||||
|
<div
|
||||||
|
onClick={() => inputFileRef?.current?.click()}
|
||||||
|
className="mt-2 cursor-pointer op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs"
|
||||||
|
>
|
||||||
|
selected:{" "}
|
||||||
|
{uploadPdf?.url
|
||||||
|
? `${uploadPdf?.name}.pdf`
|
||||||
|
: getFileName(template.URL)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="mb-[0.35rem]">
|
||||||
|
<label htmlFor="name" className="text-[13px]">
|
||||||
|
{t("Title")}
|
||||||
|
<span className="text-[13px] text-[red]"> *</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="Name"
|
||||||
|
value={formData.Name}
|
||||||
|
onChange={(e) => handleStrInput(e)}
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
required
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-[0.35rem]">
|
||||||
|
<label htmlFor="Note" className="text-[13px]">
|
||||||
|
{t("report-heading.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]">
|
||||||
|
{t("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]">{t("send-in-order")}</label>
|
||||||
|
<div className="flex flex-col md:flex-row md:gap-4">
|
||||||
|
<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]">{t("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]">{t("no")}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs mt-3">
|
||||||
|
<label className="block">
|
||||||
|
<span>
|
||||||
|
{t("enable-tour")}
|
||||||
|
<a data-tooltip-id="istourenabled-tooltip" className="ml-1">
|
||||||
|
<sup>
|
||||||
|
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||||
|
</sup>
|
||||||
|
</a>{" "}
|
||||||
|
</span>
|
||||||
|
<Tooltip id="istourenabled-tooltip" className="z-50">
|
||||||
|
<div className="max-w-[200px] md:max-w-[450px]">
|
||||||
|
<p className="font-bold">{t("enable-tour")}</p>
|
||||||
|
<div className="p-[5px]">
|
||||||
|
<ol className="list-disc">
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">{t("yes")}: </span>
|
||||||
|
<span>{t("istourenabled-help.p1")}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">{t("no")}: </span>
|
||||||
|
<span>{t("istourenabled-help.p2")}</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<p>{t("istourenabled-help.p3", { appName: appName })}</p>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-col md:flex-row md:gap-4">
|
||||||
|
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value={"true"}
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
name="IsTourEnabled"
|
||||||
|
checked={formData.IsTourEnabled === "true"}
|
||||||
|
onChange={handleStrInput}
|
||||||
|
/>
|
||||||
|
<div className="text-center">{t("yes")}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
value={"false"}
|
||||||
|
name="IsTourEnabled"
|
||||||
|
className="op-radio op-radio-xs"
|
||||||
|
checked={formData.IsTourEnabled === "false"}
|
||||||
|
onChange={handleStrInput}
|
||||||
|
/>
|
||||||
|
<div className="text-center">{t("no")}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs mt-3">
|
||||||
|
<label>
|
||||||
|
{t("notify-on-signatures")}
|
||||||
|
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||||
|
<sup>
|
||||||
|
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||||
|
</sup>
|
||||||
|
</a>{" "}
|
||||||
|
<Tooltip id="nos-tooltip" className="z-[999]">
|
||||||
|
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
||||||
|
<p className="font-bold">{t("notify-on-signatures")}</p>
|
||||||
|
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||||
|
<p>{t("notify-on-signatures-help.note")}</p>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-col md:flex-row md:gap-4">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
`flex items-center gap-2 ml-2 mb-1`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="mr-[2px] op-radio op-radio-xs"
|
||||||
|
type="radio"
|
||||||
|
onChange={() => handleNotifySignChange(true)}
|
||||||
|
checked={formData.NotifyOnSignatures === true}
|
||||||
|
/>
|
||||||
|
<div className="text-center">{t("yes")}</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
`flex items-center gap-2 ml-2 mb-1`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="mr-[2px] op-radio op-radio-xs"
|
||||||
|
type="radio"
|
||||||
|
onChange={() => handleNotifySignChange(false)}
|
||||||
|
checked={formData.NotifyOnSignatures === false}
|
||||||
|
/>
|
||||||
|
<div className="text-center">{t("no")}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs mt-3">
|
||||||
|
<SignersInput
|
||||||
|
label={t("Bcc")}
|
||||||
|
initialData={template?.Bcc}
|
||||||
|
onChange={handleBcc}
|
||||||
|
helptextZindex={50}
|
||||||
|
helpText={t("bcc-help")}
|
||||||
|
isCaptureAllData
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs mt-2">
|
||||||
|
<label className="block">Redirect Url</label>
|
||||||
|
<input
|
||||||
|
name="RedirectUrl"
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
value={formData.RedirectUrl}
|
||||||
|
onChange={handleStrInput}
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs mt-2">
|
||||||
|
<label className="block">
|
||||||
|
{t("time-to-complete")}
|
||||||
|
<span className="text-red-500 text-[13px]">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="TimeToCompleteDays"
|
||||||
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
value={formData.TimeToCompleteDays}
|
||||||
|
onChange={(e) => handleStrInput(e)}
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-[1rem] flex justify-start">
|
||||||
|
<button type="submit" className="op-btn op-btn-primary">
|
||||||
|
{t("submit")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditTemplate;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Quill } from "react-quill";
|
import { Quill } from "react-quill-new";
|
||||||
|
|
||||||
// Custom Undo button icon component for Quill editor. You can import it directly
|
// 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
|
// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not
|
||||||
@@ -54,17 +54,20 @@ Quill.register(Font, true);
|
|||||||
export const module1 = {
|
export const module1 = {
|
||||||
toolbar: {
|
toolbar: {
|
||||||
container: "#toolbar1",
|
container: "#toolbar1",
|
||||||
handlers: {
|
handlers: { undo: undoChange, redo: redoChange }
|
||||||
undo: undoChange,
|
|
||||||
redo: redoChange
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
history: {
|
history: { delay: 500, maxStack: 100, userOnly: true }
|
||||||
delay: 500,
|
|
||||||
maxStack: 100,
|
|
||||||
userOnly: true
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Modules object for setting up the Quill editor
|
||||||
|
export const module2 = {
|
||||||
|
toolbar: {
|
||||||
|
container: "#toolbar2",
|
||||||
|
handlers: { undo: undoChange, redo: redoChange }
|
||||||
|
},
|
||||||
|
history: { delay: 500, maxStack: 100, userOnly: true }
|
||||||
|
};
|
||||||
|
|
||||||
// Formats objects for setting up the Quill editor
|
// Formats objects for setting up the Quill editor
|
||||||
export const formats = [
|
export const formats = [
|
||||||
"header",
|
"header",
|
||||||
@@ -79,7 +82,6 @@ export const formats = [
|
|||||||
"blockquote",
|
"blockquote",
|
||||||
"background",
|
"background",
|
||||||
"list",
|
"list",
|
||||||
"bullet",
|
|
||||||
"indent",
|
"indent",
|
||||||
"link",
|
"link",
|
||||||
"image",
|
"image",
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import Tooltip from "../../primitives/Tooltip";
|
|
||||||
|
|
||||||
import ReactQuill from "react-quill";
|
|
||||||
import "react-quill/dist/quill.snow.css";
|
|
||||||
import EditorToolbar, { module1, formats } from "./EditorToolbar";
|
|
||||||
|
|
||||||
export function EmailBody(props) {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<div className={`flex justify-center py-1 p-2`}>
|
|
||||||
<div className=" flex flex-col">
|
|
||||||
<form>
|
|
||||||
<div className="text-lg font-normal text-black">
|
|
||||||
<div className=" m-2 md:m-10 p-3 md:p-10 shadow-md hover:shadow-lg border-[1px] border-indigo-800 rounded-md">
|
|
||||||
<label className="text-sm ml-2">
|
|
||||||
Subject <Tooltip message={"email subject"} />
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
value={props.requestSubject}
|
|
||||||
onChange={(e) => props.setRequestSubject(e.target.value)}
|
|
||||||
placeholder='${senderName} has requested you to sign "${documentName}"'
|
|
||||||
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
|
|
||||||
/>
|
|
||||||
<label className="text-sm ml-2 mt-3">
|
|
||||||
Body <Tooltip message={"email body"} />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div className="px-3 py-2 w-full focus:outline-none text-xs">
|
|
||||||
<EditorToolbar containerId="toolbar1" />
|
|
||||||
<ReactQuill
|
|
||||||
theme="snow"
|
|
||||||
value={props.requestBody}
|
|
||||||
// value={typedText}
|
|
||||||
placeholder="add body of email "
|
|
||||||
// readOnly={loading}
|
|
||||||
// onChangeSelection={handleTextSelection} // Listen for text selection
|
|
||||||
ref={props.editorRef}
|
|
||||||
modules={module1}
|
|
||||||
formats={formats}
|
|
||||||
onChange={props.handleOnchangeRequest}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Tooltip from "../../primitives/Tooltip";
|
||||||
|
import ReactQuill from "react-quill-new";
|
||||||
|
import "../../styles/quill.css";
|
||||||
|
import EditorToolbar, { module1, formats } from "./EditorToolbar";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export function EmailBody(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
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">
|
||||||
|
{t("subject")} <Tooltip message={t("email-subject")} />
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
required
|
||||||
|
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
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">
|
||||||
|
{t("body")} <Tooltip message={t("email-body")} />
|
||||||
|
</label>
|
||||||
|
<div className="px-1 py-2 w-full focus:outline-none text-xs">
|
||||||
|
<EditorToolbar containerId="toolbar1" />
|
||||||
|
<ReactQuill
|
||||||
|
theme="snow"
|
||||||
|
value={props.requestBody}
|
||||||
|
placeholder={t("email-placeholder")}
|
||||||
|
ref={props.editorRef}
|
||||||
|
modules={module1}
|
||||||
|
formats={formats}
|
||||||
|
onChange={props.handleOnchangeRequest}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,434 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import { saveAs } from "file-saver";
|
|
||||||
import celebration from "../../assets/images/newCeleb.gif";
|
|
||||||
import axios from "axios";
|
|
||||||
import { getBase64FromUrl } from "../../constant/Utils";
|
|
||||||
import { themeColor } from "../../constant/const";
|
|
||||||
import printModule from "print-js";
|
|
||||||
import loader from "../../assets/images/loader2.gif";
|
|
||||||
|
|
||||||
function EmailComponent({
|
|
||||||
isEmail,
|
|
||||||
pdfUrl,
|
|
||||||
isCeleb,
|
|
||||||
setIsEmail,
|
|
||||||
setSuccessEmail,
|
|
||||||
pdfName,
|
|
||||||
sender,
|
|
||||||
setIsAlert,
|
|
||||||
extUserId,
|
|
||||||
activeMailAdapter
|
|
||||||
}) {
|
|
||||||
const [emailList, setEmailList] = useState([]);
|
|
||||||
const [emailValue, setEmailValue] = useState();
|
|
||||||
const [isLoading, setIsLoading] = 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;
|
|
||||||
setEmailValue(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//function for save email in array after press enter
|
|
||||||
const handleEnterPress = (e) => {
|
|
||||||
if (e.key === "Enter" && emailValue) {
|
|
||||||
setEmailList((prev) => [...prev, emailValue]);
|
|
||||||
setEmailValue("");
|
|
||||||
} else if (e === "add" && emailValue) {
|
|
||||||
setEmailList((prev) => [...prev, emailValue]);
|
|
||||||
setEmailValue("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 && (
|
|
||||||
<div className="modaloverlay">
|
|
||||||
<div className="modalcontainer">
|
|
||||||
{isLoading && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
height: "100%",
|
|
||||||
width: "100%",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
zIndex: "20",
|
|
||||||
backgroundColor: "#e6f2f2",
|
|
||||||
opacity: 0.8
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="no img"
|
|
||||||
src={loader}
|
|
||||||
style={{ width: "70px", height: "70px" }}
|
|
||||||
/>
|
|
||||||
<span style={{ fontSize: "12px", fontWeight: "bold" }}>
|
|
||||||
This might take some time
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div style={{ background: "#32a3ac" }} className="modalheader">
|
|
||||||
<span style={{ color: "white" }}>Successfully signed!</span>
|
|
||||||
|
|
||||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
|
||||||
<div></div>
|
|
||||||
{!isAndroid && (
|
|
||||||
<button
|
|
||||||
onClick={handleToPrint}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
className="btn btn-primary btn-sm"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-print"
|
|
||||||
style={{
|
|
||||||
fontSize: "15px",
|
|
||||||
marginRight: "3px",
|
|
||||||
color: "white"
|
|
||||||
}}
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
Print
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="btn btn-danger btn-sm"
|
|
||||||
onClick={() => handleDownloadPdf()}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
marginLeft: "10px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-download"
|
|
||||||
style={{
|
|
||||||
color: "white",
|
|
||||||
fontSize: "15px",
|
|
||||||
marginRight: "3px"
|
|
||||||
}}
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
Download
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ height: "100%", padding: 20 }}>
|
|
||||||
{isCeleb && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
marginLeft: "50px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="print img"
|
|
||||||
width={300}
|
|
||||||
height={250}
|
|
||||||
// style={styles.gifCeleb}
|
|
||||||
src={celebration}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
fontFamily: "system-ui",
|
|
||||||
verticalAlign: "baseline",
|
|
||||||
fontWeight: "500",
|
|
||||||
color: "#403f3e",
|
|
||||||
fontSize: "15px",
|
|
||||||
marginBottom: "5px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Recipients added here will get a copy of the signed document.
|
|
||||||
</p>
|
|
||||||
{emailList.length > 0 ? (
|
|
||||||
<>
|
|
||||||
<div className="addEmail">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
|
|
||||||
flexWrap: "wrap"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{emailList.map((data, ind) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="emailChip"
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
key={ind}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: "white",
|
|
||||||
fontSize: "13px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{data}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: "white",
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: 600,
|
|
||||||
marginLeft: 7,
|
|
||||||
cursor: "pointer"
|
|
||||||
}}
|
|
||||||
onClick={() => removeChip(ind)}
|
|
||||||
>
|
|
||||||
<i className="fa-solid fa-xmark"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
{emailList.length <= 9 && (
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={emailValue}
|
|
||||||
className="addEmailInput"
|
|
||||||
onChange={handleEmailValue}
|
|
||||||
onKeyDown={handleEnterPress}
|
|
||||||
onBlur={() => {
|
|
||||||
if (emailValue) {
|
|
||||||
handleEnterPress("add");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={emailValue}
|
|
||||||
className="emailInput"
|
|
||||||
onChange={handleEmailValue}
|
|
||||||
onKeyDown={handleEnterPress}
|
|
||||||
placeholder="Add the email addresses"
|
|
||||||
onBlur={() => {
|
|
||||||
if (emailValue) {
|
|
||||||
handleEnterPress("add");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
cursor: emailValue && "pointer"
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
if (emailValue) {
|
|
||||||
handleEnterPress("add");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
style={{
|
|
||||||
backgroundColor: themeColor,
|
|
||||||
padding: "5px 7px",
|
|
||||||
marginTop: "10px",
|
|
||||||
color: "white",
|
|
||||||
borderRadius: "2px"
|
|
||||||
}}
|
|
||||||
className="fa fa-plus"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
background: "#e3e2e1",
|
|
||||||
marginTop: "10px",
|
|
||||||
padding: "5px",
|
|
||||||
borderRadius: "3px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ fontWeight: "700" }}>Note:</span>
|
|
||||||
<span style={{ fontSize: "15px" }}>
|
|
||||||
{" "}
|
|
||||||
You can only send to ten recipients at a time.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
height: "1px",
|
|
||||||
backgroundColor: "#9f9f9f",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "15px",
|
|
||||||
marginBottom: "15px"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<button
|
|
||||||
disabled={emailList.length === 0 && true}
|
|
||||||
style={{
|
|
||||||
background: themeColor,
|
|
||||||
color: "white"
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
className={emailList.length === 0 ? "defaultBtn" : "finishBtn"}
|
|
||||||
onClick={() => sendEmail()}
|
|
||||||
>
|
|
||||||
Send
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="finishBtn cancelBtn"
|
|
||||||
onClick={() => {
|
|
||||||
setIsEmail(false);
|
|
||||||
setEmailValue("");
|
|
||||||
setEmailList([]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EmailComponent;
|
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
handleToPrint,
|
||||||
|
} from "../../constant/Utils";
|
||||||
|
import {
|
||||||
|
emailRegex,
|
||||||
|
} from "../../constant/const";
|
||||||
|
import Loader from "../../primitives/Loader";
|
||||||
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Parse from "parse";
|
||||||
|
|
||||||
|
function EmailComponent({
|
||||||
|
isEmail,
|
||||||
|
setIsEmail,
|
||||||
|
setSuccessEmail,
|
||||||
|
pdfDetails,
|
||||||
|
setIsAlert,
|
||||||
|
setIsDownloadModal
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [emailList, setEmailList] = useState([]);
|
||||||
|
const [emailValue, setEmailValue] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [emailErr, setEmailErr] = useState(false);
|
||||||
|
const [isDownloading, setIsDownloading] = useState("");
|
||||||
|
const isAndroid = /Android/i.test(navigator.userAgent);
|
||||||
|
|
||||||
|
//function for send email
|
||||||
|
const sendEmail = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
|
||||||
|
const sendmail = await Parse.Cloud.run("forwarddoc", params);
|
||||||
|
console.log("sendmail ", sendmail);
|
||||||
|
if (sendmail?.status === "success") {
|
||||||
|
setSuccessEmail(true);
|
||||||
|
setIsEmail(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
setSuccessEmail(false);
|
||||||
|
setEmailValue("");
|
||||||
|
setEmailList([]);
|
||||||
|
}, 1500);
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setIsLoading(false);
|
||||||
|
setIsEmail(false);
|
||||||
|
setIsAlert({
|
||||||
|
isShow: true,
|
||||||
|
alertMessage: t("something-went-wrong-mssg")
|
||||||
|
});
|
||||||
|
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?.toLowerCase()?.replace(/\s/g, "");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleClose = () => {
|
||||||
|
setIsEmail(false);
|
||||||
|
setEmailValue("");
|
||||||
|
setEmailList([]);
|
||||||
|
};
|
||||||
|
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">
|
||||||
|
{t("loader")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isDownloading === "pdf" && (
|
||||||
|
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-between items-center py-[10px] px-[20px] border-b-[1px] border-base-content">
|
||||||
|
<span className="text-base-content font-bold text-sm md:text-lg">
|
||||||
|
{t("successfully-signed")}
|
||||||
|
</span>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
{!isAndroid && (
|
||||||
|
<button
|
||||||
|
onClick={(e) =>
|
||||||
|
handleToPrint(e, setIsDownloading, pdfDetails)
|
||||||
|
}
|
||||||
|
className="op-btn op-btn-neutral op-btn-sm text-xs md:text-[15px]"
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-print" aria-hidden="true"></i>
|
||||||
|
{t("print")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="op-btn op-btn-primary op-btn-sm text-xs md:text-[15px] ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
handleClose();
|
||||||
|
setIsDownloadModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-download" aria-hidden="true"></i>
|
||||||
|
{t("download")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="h-full p-[20px]">
|
||||||
|
<p className="font-medium text-[15px] mb-[5px] text-base-content align-baseline">
|
||||||
|
{t("email-mssg")}
|
||||||
|
</p>
|
||||||
|
{emailList.length > 0 ? (
|
||||||
|
<div className="p-0 border-[1px] op-border-primary w-full rounded-md text-[15px] overflow-hidden">
|
||||||
|
<div className="flex flex-row flex-wrap">
|
||||||
|
{emailList.map((data, ind) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex flex-row items-center op-bg-primary mx-[2px] mt-[2px] 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] rounded-md w-full text-[15px] bg-transparent outline-none"
|
||||||
|
onChange={handleEmailValue}
|
||||||
|
onKeyDown={handleEnterPress}
|
||||||
|
onBlur={() => emailValue && handleEnterPress("add")}
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={emailValue}
|
||||||
|
className="p-[10px] pb-[20px] rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
|
||||||
|
onChange={handleEmailValue}
|
||||||
|
onKeyDown={handleEnterPress}
|
||||||
|
placeholder={t("enter-email-plaholder")}
|
||||||
|
onBlur={() => emailValue && handleEnterPress("add")}
|
||||||
|
onInvalid={(e) =>
|
||||||
|
e.target.setCustomValidity(t("input-required"))
|
||||||
|
}
|
||||||
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{emailErr && (
|
||||||
|
<p className="text-xs text-[red] ml-1.5 mt-0.5">
|
||||||
|
{t("email-error-1")}
|
||||||
|
</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">{t("report-heading.Note")}: </span>
|
||||||
|
<span className="text-[15px]">{t("email-error-2")}</span>
|
||||||
|
</div>
|
||||||
|
<hr className="w-full my-[15px] bg-base-content" /> */}
|
||||||
|
<div className="mt-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-secondary"
|
||||||
|
onClick={() => emailList.length > 0 && sendEmail()}
|
||||||
|
>
|
||||||
|
{t("send")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost ml-2"
|
||||||
|
onClick={() => handleClose()}
|
||||||
|
>
|
||||||
|
{t("close")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmailComponent;
|
||||||
@@ -1,617 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import PrevNext from "./PrevNext";
|
|
||||||
import printModule from "print-js";
|
|
||||||
import { getBase64FromUrl } from "../../constant/Utils";
|
|
||||||
import { saveAs } from "file-saver";
|
|
||||||
import "../../styles/signature.css";
|
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { themeColor } from "../../constant/const";
|
|
||||||
import axios from "axios";
|
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
|
||||||
import { appInfo } from "../../constant/appinfo";
|
|
||||||
|
|
||||||
function Header({
|
|
||||||
isPdfRequestFiles,
|
|
||||||
isPlaceholder,
|
|
||||||
setIsDecline,
|
|
||||||
pageNumber,
|
|
||||||
allPages,
|
|
||||||
changePage,
|
|
||||||
pdfUrl,
|
|
||||||
embedWidgetsData,
|
|
||||||
pdfDetails,
|
|
||||||
signerPos,
|
|
||||||
signersdata,
|
|
||||||
isMailSend,
|
|
||||||
alertSendEmail,
|
|
||||||
isCompleted,
|
|
||||||
isShowHeader,
|
|
||||||
decline,
|
|
||||||
currentSigner,
|
|
||||||
dataTut4,
|
|
||||||
alreadySign,
|
|
||||||
isSignYourself,
|
|
||||||
setIsEmail,
|
|
||||||
completeBtnTitle,
|
|
||||||
setIsEditTemplate
|
|
||||||
}) {
|
|
||||||
const filterPrefill =
|
|
||||||
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
|
|
||||||
const isMobile = window.innerWidth < 767;
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [isCertificate, setIsCertificate] = useState(false);
|
|
||||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
|
||||||
//for go to previous page
|
|
||||||
function previousPage() {
|
|
||||||
changePage(-1);
|
|
||||||
}
|
|
||||||
//for go to next page
|
|
||||||
function nextPage() {
|
|
||||||
changePage(1);
|
|
||||||
}
|
|
||||||
//function for show decline alert
|
|
||||||
const handleDeclinePdfAlert = async () => {
|
|
||||||
const currentDecline = {
|
|
||||||
currnt: "Sure",
|
|
||||||
isDeclined: true
|
|
||||||
};
|
|
||||||
setIsDecline(currentDecline);
|
|
||||||
};
|
|
||||||
|
|
||||||
//function for print digital sign pdf
|
|
||||||
const handleToPrint = async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
|
|
||||||
const axiosRes = await axios.post(
|
|
||||||
`${appInfo.baseUrl}/functions/getsignedurl`,
|
|
||||||
{ url: pdfUrl },
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"content-type": "Application/json",
|
|
||||||
"X-Parse-Application-Id": appInfo.appId,
|
|
||||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const url = axiosRes.data.result;
|
|
||||||
const pdf = await getBase64FromUrl(url);
|
|
||||||
const isAndroidDevice = navigator.userAgent.match(/Android/i);
|
|
||||||
const isAppleDevice =
|
|
||||||
(/iPad|iPhone|iPod/.test(navigator.platform) ||
|
|
||||||
(navigator.platform === "MacIntel" &&
|
|
||||||
navigator.maxTouchPoints > 1)) &&
|
|
||||||
!window.MSStream;
|
|
||||||
if (isAndroidDevice || isAppleDevice) {
|
|
||||||
const byteArray = Uint8Array.from(
|
|
||||||
atob(pdf)
|
|
||||||
.split("")
|
|
||||||
.map((char) => char.charCodeAt(0))
|
|
||||||
);
|
|
||||||
const blob = new Blob([byteArray], { type: "application/pdf" });
|
|
||||||
const blobUrl = URL.createObjectURL(blob);
|
|
||||||
window.open(blobUrl, "_blank");
|
|
||||||
} else {
|
|
||||||
printModule({ printable: pdf, type: "pdf", base64: true });
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("err in getsignedurl", err);
|
|
||||||
alert("something went wrong, please try again later.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//handle download signed pdf
|
|
||||||
const handleDownloadPdf = async () => {
|
|
||||||
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
|
|
||||||
try {
|
|
||||||
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
|
|
||||||
const axiosRes = await axios.post(
|
|
||||||
`${appInfo.baseUrl}/functions/getsignedurl`,
|
|
||||||
{ url: pdfUrl },
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"content-type": "Application/json",
|
|
||||||
"X-Parse-Application-Id": appInfo.appId,
|
|
||||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const url = axiosRes.data.result;
|
|
||||||
saveAs(url, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
|
|
||||||
} catch (err) {
|
|
||||||
console.log("err in getsignedurl", err);
|
|
||||||
alert("something went wrong, please try again later.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const sanitizeFileName = (pdfName) => {
|
|
||||||
// Replace spaces with underscore
|
|
||||||
return pdfName.replace(/ /g, "_");
|
|
||||||
};
|
|
||||||
|
|
||||||
//handle download signed pdf
|
|
||||||
const handleDownloadCertificate = async () => {
|
|
||||||
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
|
|
||||||
try {
|
|
||||||
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
|
|
||||||
const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl;
|
|
||||||
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
|
|
||||||
} catch (err) {
|
|
||||||
console.log("err in download in certificate", err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setIsCertificate(true);
|
|
||||||
try {
|
|
||||||
const data = {
|
|
||||||
docId: pdfDetails[0]?.objectId
|
|
||||||
};
|
|
||||||
const docDetails = await axios.post(
|
|
||||||
`${localStorage.getItem("baseUrl")}functions/getDocument`,
|
|
||||||
data,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
|
||||||
sessionToken: localStorage.getItem("accesstoken")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (docDetails.data && docDetails.data.result) {
|
|
||||||
const doc = docDetails.data.result;
|
|
||||||
if (doc?.CertificateUrl) {
|
|
||||||
await fetch(doc?.CertificateUrl);
|
|
||||||
const certificateUrl = doc?.CertificateUrl;
|
|
||||||
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
|
|
||||||
setIsCertificate(false);
|
|
||||||
} else {
|
|
||||||
setIsCertificate(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("err in download in certificate", err);
|
|
||||||
alert("something went wrong, please try again later.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ padding: "5px 0px 5px 0px" }} className="mobileHead">
|
|
||||||
{isMobile && isShowHeader ? (
|
|
||||||
<div
|
|
||||||
id="navbar"
|
|
||||||
className={isGuestSigner ? "stickySignerHead" : "stickyHead"}
|
|
||||||
style={{
|
|
||||||
width: isGuestSigner
|
|
||||||
? window.innerWidth
|
|
||||||
: window.innerWidth - 30 + "px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="preBtn2">
|
|
||||||
<div onClick={() => navigate(-1)}>
|
|
||||||
<i
|
|
||||||
className="fa fa-arrow-left"
|
|
||||||
aria-hidden="true"
|
|
||||||
style={{ color: themeColor, cursor: "pointer" }}
|
|
||||||
></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<i
|
|
||||||
onClick={() => {
|
|
||||||
if (pageNumber > 1) {
|
|
||||||
previousPage();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="fa fa-backward"
|
|
||||||
style={{ color: "gray", cursor: "pointer" }}
|
|
||||||
></i>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: "13px",
|
|
||||||
fontWeight: "600",
|
|
||||||
marginLeft: "10px",
|
|
||||||
marginRight: "10px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pageNumber || (allPages ? 1 : "--")} of {allPages || "--"}
|
|
||||||
</span>
|
|
||||||
<i
|
|
||||||
onClick={() => {
|
|
||||||
if (pageNumber < allPages) {
|
|
||||||
nextPage();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="fa fa-forward"
|
|
||||||
style={{ color: "gray", cursor: "pointer" }}
|
|
||||||
></i>
|
|
||||||
</div>
|
|
||||||
{pdfUrl && alreadySign ? (
|
|
||||||
<DropdownMenu.Root>
|
|
||||||
<DropdownMenu.Trigger asChild>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
color: themeColor,
|
|
||||||
border: "none",
|
|
||||||
fontWeight: "650",
|
|
||||||
fontSize: "16px",
|
|
||||||
padding: "0px 3px 0px 5px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i className="fa fa-ellipsis-v" aria-hidden="true"></i>
|
|
||||||
</div>
|
|
||||||
</DropdownMenu.Trigger>
|
|
||||||
<DropdownMenu.Portal>
|
|
||||||
<DropdownMenu.Content
|
|
||||||
className="DropdownMenuContent"
|
|
||||||
sideOffset={5}
|
|
||||||
>
|
|
||||||
<DropdownMenu.Item
|
|
||||||
className="DropdownMenuItem"
|
|
||||||
onClick={() => handleDownloadPdf()}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-arrow-down"
|
|
||||||
aria-hidden="true"
|
|
||||||
style={{ marginRight: "2px" }}
|
|
||||||
></i>
|
|
||||||
Download
|
|
||||||
</div>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
{isCompleted && (
|
|
||||||
<DropdownMenu.Item
|
|
||||||
className="DropdownMenuItem"
|
|
||||||
onClick={() => handleDownloadCertificate()}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
border: "none",
|
|
||||||
backgroundColor: "#fff"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa-solid fa-award"
|
|
||||||
style={{ marginRight: "2px" }}
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
Certificate
|
|
||||||
</div>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
)}
|
|
||||||
{isSignYourself && (
|
|
||||||
<DropdownMenu.Item
|
|
||||||
className="DropdownMenuItem"
|
|
||||||
onClick={() => setIsEmail(true)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-envelope"
|
|
||||||
style={{ marginRight: "2px" }}
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
Mail
|
|
||||||
</div>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
)}
|
|
||||||
<DropdownMenu.Item
|
|
||||||
className="DropdownMenuItem"
|
|
||||||
onClick={handleToPrint}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa fa-print"
|
|
||||||
aria-hidden="true"
|
|
||||||
style={{ marginRight: "2px" }}
|
|
||||||
></i>
|
|
||||||
Print
|
|
||||||
</div>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
</DropdownMenu.Content>
|
|
||||||
</DropdownMenu.Portal>
|
|
||||||
</DropdownMenu.Root>
|
|
||||||
) : (
|
|
||||||
<div style={{ display: "flex", justifyContent: "space-around" }}>
|
|
||||||
{/* 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 && (
|
|
||||||
<>
|
|
||||||
{decline && (
|
|
||||||
<div
|
|
||||||
onClick={() => {
|
|
||||||
handleDeclinePdfAlert();
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
color: "red",
|
|
||||||
border: "none",
|
|
||||||
fontWeight: "650",
|
|
||||||
fontSize: "16px",
|
|
||||||
marginRight: "10px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Decline
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isPlaceholder ? (
|
|
||||||
<div
|
|
||||||
onClick={() => {
|
|
||||||
if (!isMailSend) {
|
|
||||||
alertSendEmail();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
color: isMailSend ? "gray" : themeColor,
|
|
||||||
border: "none",
|
|
||||||
fontWeight: "650",
|
|
||||||
fontSize: "16px"
|
|
||||||
}}
|
|
||||||
data-tut={dataTut4}
|
|
||||||
>
|
|
||||||
{completeBtnTitle ? completeBtnTitle : "Send"}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
data-tut="reactourThird"
|
|
||||||
onClick={() => {
|
|
||||||
if (!pdfUrl) {
|
|
||||||
embedWidgetsData();
|
|
||||||
} else if (isPdfRequestFiles) {
|
|
||||||
embedWidgetsData();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
color: themeColor,
|
|
||||||
border: "none",
|
|
||||||
fontWeight: "650",
|
|
||||||
fontSize: "16px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Finish
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-wrap justify-between items-center mt-[5px]">
|
|
||||||
<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 style={{ fontSize: "13px", color: "#f5405e" }}>
|
|
||||||
Add {signersdata.length - filterPrefill.length}{" "}
|
|
||||||
recipients signature
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span style={{ fontSize: "13px", color: "#f5405e" }}>
|
|
||||||
Add {signersdata.length - filterPrefill.length} more
|
|
||||||
recipients signature
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
{setIsEditTemplate && (
|
|
||||||
<button
|
|
||||||
onClick={() => setIsEditTemplate(true)}
|
|
||||||
className="outline-none border-none text-center mr-[5px]"
|
|
||||||
>
|
|
||||||
<i className="fa-solid fa-gear fa-lg"></i>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={() => navigate(-1)}
|
|
||||||
type="button"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-black font-[500] text-sm mr-[5px] bg-white"
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
disabled={isMailSend && true}
|
|
||||||
data-tut="reactourFour"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] font-[500] text-sm mr-[5px]"
|
|
||||||
style={{
|
|
||||||
background: isMailSend ? "rgb(203, 203, 203)" : "#188ae2",
|
|
||||||
color: isMailSend ? "rgb(77, 75, 75)" : "white"
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
alertSendEmail();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{completeBtnTitle
|
|
||||||
? completeBtnTitle
|
|
||||||
: isMailSend
|
|
||||||
? "Sent"
|
|
||||||
: "Send"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : isPdfRequestFiles ? (
|
|
||||||
alreadySign ? (
|
|
||||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
|
||||||
{isCompleted && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleDownloadCertificate()}
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa-solid fa-award py-[3px]"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span className="hidden lg:block ml-1">Certificate</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleToPrint}
|
|
||||||
type="button"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
|
|
||||||
>
|
|
||||||
<i className="fa fa-print py-[3px]" aria-hidden="true"></i>
|
|
||||||
<span className="hidden lg:block ml-1">Print</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
|
|
||||||
onClick={() => handleDownloadPdf()}
|
|
||||||
>
|
|
||||||
<i className="fa fa-download py-[3px]" aria-hidden="true"></i>
|
|
||||||
<span className="hidden lg:block ml-1">Download</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex">
|
|
||||||
<button
|
|
||||||
onClick={() => navigate(-1)}
|
|
||||||
type="button"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-black font-[500] text-sm mr-[5px] bg-white"
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
{currentSigner && (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
style={{ background: "#de4337" }}
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
|
|
||||||
onClick={() => {
|
|
||||||
handleDeclinePdfAlert();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Decline
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
style={{
|
|
||||||
background: "#188ae2"
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
|
|
||||||
onClick={() => {
|
|
||||||
embedWidgetsData();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Finish
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
) : isCompleted ? (
|
|
||||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
|
||||||
{isCompleted && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleDownloadCertificate()}
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa-solid fa-award py-[3px]"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span className="hidden lg:block ml-1">Certificate</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={handleToPrint}
|
|
||||||
type="button"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
|
|
||||||
>
|
|
||||||
<i className="fa fa-print py-[3px]" aria-hidden="true"></i>
|
|
||||||
<span className="hidden lg:block ml-1">Print</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
|
|
||||||
onClick={() => handleDownloadPdf()}
|
|
||||||
>
|
|
||||||
<i className="fa fa-download py-[3px]" aria-hidden="true"></i>
|
|
||||||
<span className="hidden lg:block ml-1">Download</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#3ba7e5]"
|
|
||||||
onClick={() => setIsEmail(true)}
|
|
||||||
>
|
|
||||||
<i className="fa fa-envelope py-[3px]" aria-hidden="true"></i>
|
|
||||||
<span className="hidden lg:block ml-1">Mail</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex">
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
navigate(-1);
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-black font-[500] text-sm mr-[5px] bg-white"
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
style={{
|
|
||||||
background: "#188ae2"
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
|
|
||||||
onClick={() => {
|
|
||||||
if (!pdfUrl) {
|
|
||||||
embedWidgetsData();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Finish
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ModalUi
|
|
||||||
isOpen={isCertificate}
|
|
||||||
title={"Generating certificate"}
|
|
||||||
handleClose={() => setIsCertificate(false)}
|
|
||||||
>
|
|
||||||
<div className="p-3 md:p-5 text-[13px] md:text-base text-center">
|
|
||||||
<p>
|
|
||||||
Your completion certificate is being generated. Please wait
|
|
||||||
momentarily.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If the download doesn't start shortly, click the button again.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</ModalUi>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Header;
|
|
||||||
@@ -0,0 +1,703 @@
|
|||||||
|
import React, { useRef, useState } from "react";
|
||||||
|
import PrevNext from "./PrevNext";
|
||||||
|
import {
|
||||||
|
base64ToArrayBuffer,
|
||||||
|
deletePdfPage,
|
||||||
|
handleDownloadCertificate,
|
||||||
|
handleDownloadPdf,
|
||||||
|
handleRemoveWidgets,
|
||||||
|
handleToPrint
|
||||||
|
} from "../../constant/Utils";
|
||||||
|
import "../../styles/signature.css";
|
||||||
|
import { DropdownMenu } from "radix-ui";
|
||||||
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
|
import Loader from "../../primitives/Loader";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { PDFDocument } from "pdf-lib";
|
||||||
|
import { maxFileSize } from "../../constant/const";
|
||||||
|
|
||||||
|
function Header(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const filterPrefill =
|
||||||
|
props?.signerPos &&
|
||||||
|
props?.signerPos?.filter((data) => data.Role !== "prefill");
|
||||||
|
const isMobile = window.innerWidth < 767;
|
||||||
|
const [isDownloading, setIsDownloading] = useState("");
|
||||||
|
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||||
|
const mergePdfInputRef = useRef(null);
|
||||||
|
const enabledBackBtn = props?.disabledBackBtn === true ? false : true;
|
||||||
|
//function for show decline alert
|
||||||
|
const handleDeclinePdfAlert = async () => {
|
||||||
|
if (props?.handleDecline) {
|
||||||
|
props.handleDecline();
|
||||||
|
} else {
|
||||||
|
const currentDecline = { currnt: "Sure", isDeclined: true };
|
||||||
|
props?.setIsDecline(currentDecline);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDetelePage = async () => {
|
||||||
|
props?.setIsUploadPdf && props?.setIsUploadPdf(true);
|
||||||
|
const pdfupdatedData = await deletePdfPage(
|
||||||
|
props?.pdfArrayBuffer,
|
||||||
|
props?.pageNumber
|
||||||
|
);
|
||||||
|
if (pdfupdatedData?.totalPages === 1) {
|
||||||
|
alert(t("delete-alert"));
|
||||||
|
} else {
|
||||||
|
props?.setPdfBase64Url(pdfupdatedData.base64);
|
||||||
|
props?.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||||
|
setIsDeletePage(false);
|
||||||
|
handleRemoveWidgets(
|
||||||
|
props?.setSignerPos,
|
||||||
|
props?.signerPos,
|
||||||
|
props?.pageNumber
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// `removeFile` is used to remove file if exists
|
||||||
|
const removeFile = (e) => {
|
||||||
|
if (e) {
|
||||||
|
e.target.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileUpload = async (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) {
|
||||||
|
alert("Please upload a valid PDF file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!file.type.includes("pdf")) {
|
||||||
|
alert("Only PDF files are allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||||
|
if (mb > maxFileSize) {
|
||||||
|
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||||
|
removeFile(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const uploadedPdfBytes = await file.arrayBuffer();
|
||||||
|
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||||
|
ignoreEncryption: true
|
||||||
|
});
|
||||||
|
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
|
||||||
|
|
||||||
|
// Copy pages from the uploaded PDF to the base PDF
|
||||||
|
const uploadedPdfPages = await basePdfDoc.copyPages(
|
||||||
|
uploadedPdfDoc,
|
||||||
|
uploadedPdfDoc.getPageIndices()
|
||||||
|
);
|
||||||
|
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
|
||||||
|
// Save the updated PDF
|
||||||
|
const pdfBase64 = await basePdfDoc.saveAsBase64({
|
||||||
|
useObjectStreams: false
|
||||||
|
});
|
||||||
|
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
|
||||||
|
props.setPdfArrayBuffer(pdfBuffer);
|
||||||
|
props.setPdfBase64Url(pdfBase64);
|
||||||
|
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||||
|
mergePdfInputRef.current.value = "";
|
||||||
|
} catch (error) {
|
||||||
|
mergePdfInputRef.current.value = "";
|
||||||
|
console.error("Error merging PDF:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="flex py-[5px]">
|
||||||
|
{isMobile && props?.isShowHeader ? (
|
||||||
|
<div
|
||||||
|
id="navbar"
|
||||||
|
className="stickyHead"
|
||||||
|
style={{
|
||||||
|
width: window.innerWidth + "px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-center py-[5px] pl-[10px] ">
|
||||||
|
<div onClick={() => window.history.go(-2)}>
|
||||||
|
<i
|
||||||
|
className="fa-light fa-arrow-left text-base-content"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
<PrevNext
|
||||||
|
pageNumber={props?.pageNumber}
|
||||||
|
allPages={props?.allPages}
|
||||||
|
changePage={props?.changePage}
|
||||||
|
/>
|
||||||
|
{props?.isCompleted || props?.alreadySign ? (
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger asChild>
|
||||||
|
<div className="op-link op-link-primary no-underline text-[16px] font-semibold px-3">
|
||||||
|
<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={() => {
|
||||||
|
if (props?.isCompleted) {
|
||||||
|
props?.setIsDownloadModal(true);
|
||||||
|
} else {
|
||||||
|
handleDownloadPdf(
|
||||||
|
props?.pdfDetails,
|
||||||
|
setIsDownloading,
|
||||||
|
props.pdfBase64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i
|
||||||
|
className="fa-light fa-arrow-down mr-[3px]"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
{t("download")}
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{props?.isCompleted && (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={() =>
|
||||||
|
handleDownloadCertificate(
|
||||||
|
props?.pdfDetails,
|
||||||
|
setIsDownloading
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="border-none bg-[#fff]">
|
||||||
|
<i
|
||||||
|
className="fa-light fa-award mr-[3px]"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
{t("certificate")}
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
)}
|
||||||
|
{props?.isSignYourself && (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={() => props?.setIsEmail(true)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i
|
||||||
|
className="fa-light fa-envelope mr-[3px]"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
{t("mail")}
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
)}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={(e) =>
|
||||||
|
handleToPrint(e, setIsDownloading, props?.pdfDetails)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i
|
||||||
|
className="fa-light fa-print mr-[3px]"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
{t("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
|
||||||
|
*/}
|
||||||
|
{props?.currentSigner && (
|
||||||
|
<div className="flex items-center" data-tut="reactourFifth">
|
||||||
|
{props?.decline && (
|
||||||
|
<div
|
||||||
|
onClick={() => handleDeclinePdfAlert()}
|
||||||
|
className="text-[red] border-none font-[650] text-[14px] mr-2"
|
||||||
|
>
|
||||||
|
{t("decline")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{props?.isPlaceholder ? (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (!props?.isMailSend) {
|
||||||
|
props?.alertSendEmail();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`${
|
||||||
|
props?.isMailSend ? "" : "op-link-primary"
|
||||||
|
} op-link no-underline font-[650] text-[14px]`}
|
||||||
|
data-tut="headerArea"
|
||||||
|
>
|
||||||
|
{props?.completeBtnTitle
|
||||||
|
? props?.completeBtnTitle
|
||||||
|
: t("send")}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
data-tut="reactourThird"
|
||||||
|
onClick={() => props?.embedWidgetsData()}
|
||||||
|
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
|
||||||
|
>
|
||||||
|
{t("finish")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
className="hidden"
|
||||||
|
accept="application/pdf"
|
||||||
|
ref={mergePdfInputRef}
|
||||||
|
onChange={handleFileUpload}
|
||||||
|
/>
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger asChild>
|
||||||
|
<div className="font-[650] text-[18px] px-3 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-md px-3 py-2"
|
||||||
|
sideOffset={5}
|
||||||
|
>
|
||||||
|
{props?.setIsEditTemplate && (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={() => props?.setIsEditTemplate(true)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i
|
||||||
|
className="fa-light fa-gear mr-[3px]"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span className="font-[500]">{t("Edit")}</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
)}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={() =>
|
||||||
|
handleDownloadPdf(
|
||||||
|
props?.pdfDetails,
|
||||||
|
setIsDownloading,
|
||||||
|
props.pdfBase64
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i
|
||||||
|
className="fa-light fa-arrow-down mr-[3px]"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span className="font-[500]">
|
||||||
|
{t("download")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{!props?.isDisablePdfEditTools && (
|
||||||
|
<>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={() => mergePdfInputRef.current.click()}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i className="fa-light fa-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||||
|
<span className="font-[500]">
|
||||||
|
{t("add-pages")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={() => setIsDeletePage(true)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i className="fa-light fa-trash text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||||
|
<span className="font-[500]">
|
||||||
|
{t("delete-page")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={() => props?.handleRotationFun(90)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||||
|
<span className="font-[500]">
|
||||||
|
{t("rotate-right")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={() => props?.handleRotationFun(-90)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||||
|
<span className="font-[500]">
|
||||||
|
{t("rotate-left")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={() => props?.clickOnZoomIn()}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||||
|
<span className="font-[500]">{t("zoom-in")}</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
className="DropdownMenuItem"
|
||||||
|
onClick={() => props?.clickOnZoomOut()}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||||
|
<span className="font-[500]">
|
||||||
|
{t("zoom-out")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Portal>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{props?.isPublicTemplate && (
|
||||||
|
<div
|
||||||
|
data-tut="reactourThird"
|
||||||
|
onClick={() => props?.embedWidgetsData()}
|
||||||
|
className="border-none font-[650] text-[14px] pr-2 op-link op-link-primary no-underline"
|
||||||
|
>
|
||||||
|
{t("sign-now")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-wrap justify-between items-center w-full gap-y-1 ml-1">
|
||||||
|
<PrevNext
|
||||||
|
pageNumber={props?.pageNumber}
|
||||||
|
allPages={props?.allPages}
|
||||||
|
changePage={props?.changePage}
|
||||||
|
/>
|
||||||
|
{props?.isPlaceholder ? (
|
||||||
|
<>
|
||||||
|
<div className="flex mx-[100px] lg:mx-0 order-last lg:order-none">
|
||||||
|
{!props?.isMailSend &&
|
||||||
|
props?.signersdata.length > 0 &&
|
||||||
|
props?.signersdata.length !== filterPrefill.length && (
|
||||||
|
<div>
|
||||||
|
{filterPrefill.length === 0 ? (
|
||||||
|
<span className="text-[13px] text-[#f5405e]">
|
||||||
|
{t("add")}{" "}
|
||||||
|
{props?.signersdata.length - filterPrefill.length}{" "}
|
||||||
|
{t("recipients")} {t("widgets-name.signature")}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-[13px] text-[#f5405e]">
|
||||||
|
{t("add")}{" "}
|
||||||
|
{props?.signersdata.length - filterPrefill.length}{" "}
|
||||||
|
{t("more")}
|
||||||
|
{t("recipients")} {t("widgets-name.signature")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
{props?.setIsEditTemplate && (
|
||||||
|
<button
|
||||||
|
onClick={() => props?.setIsEditTemplate(true)}
|
||||||
|
className="outline-none border-none text-center mr-[3px]"
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-gear fa-lg"></i>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{enabledBackBtn && (
|
||||||
|
<button
|
||||||
|
onClick={() => window.history.go(-2)}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||||
|
>
|
||||||
|
{t("back")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
disabled={props?.isMailSend && true}
|
||||||
|
data-tut="headerArea"
|
||||||
|
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
|
||||||
|
onClick={() => props?.alertSendEmail()}
|
||||||
|
>
|
||||||
|
{props?.completeBtnTitle
|
||||||
|
? props?.completeBtnTitle
|
||||||
|
: props?.isMailSend
|
||||||
|
? t("sent")
|
||||||
|
: t("send")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : props?.isPdfRequestFiles || props?.isSelfSign ? (
|
||||||
|
props?.alreadySign || (props?.isSelfSign && props?.isCompleted) ? (
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<button
|
||||||
|
onClick={(e) =>
|
||||||
|
handleToPrint(e, setIsDownloading, props?.pdfDetails)
|
||||||
|
}
|
||||||
|
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">{t("print")}</span>
|
||||||
|
</button>
|
||||||
|
{props?.isCompleted && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
handleDownloadCertificate(
|
||||||
|
props?.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">{t("certificate")}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
|
||||||
|
onClick={() => {
|
||||||
|
if (props?.isCompleted) {
|
||||||
|
props?.setIsDownloadModal(true);
|
||||||
|
} else {
|
||||||
|
handleDownloadPdf(
|
||||||
|
props?.pdfDetails,
|
||||||
|
setIsDownloading,
|
||||||
|
props.pdfBase64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className="fa-light fa-download py-[3px]"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span className="hidden lg:block">{t("download")}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex" data-tut="reactourFifth">
|
||||||
|
{props?.currentSigner && (
|
||||||
|
<>
|
||||||
|
{props?.templateId && (
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
handleDownloadPdf(
|
||||||
|
props?.pdfDetails,
|
||||||
|
setIsDownloading,
|
||||||
|
props.pdfBase64
|
||||||
|
)
|
||||||
|
}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||||
|
>
|
||||||
|
<span className="hidden lg:block">{t("download")}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{!props?.isSelfSign && (
|
||||||
|
<button
|
||||||
|
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
|
||||||
|
onClick={() => handleDeclinePdfAlert()}
|
||||||
|
>
|
||||||
|
{t("decline")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{!props?.templateId && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||||
|
onClick={() =>
|
||||||
|
handleDownloadPdf(
|
||||||
|
props?.pdfDetails,
|
||||||
|
setIsDownloading,
|
||||||
|
props.pdfBase64
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
|
||||||
|
<span className="hidden lg:block">{t("download")}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
|
||||||
|
onClick={() => props?.embedWidgetsData()}
|
||||||
|
>
|
||||||
|
{t("finish")}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
) : props?.isCompleted ? (
|
||||||
|
<div className="flex flex-row">
|
||||||
|
{props?.isCompleted && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
handleDownloadCertificate(
|
||||||
|
props?.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">
|
||||||
|
{t("certificate")}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={(e) =>
|
||||||
|
handleToPrint(e, setIsDownloading, props?.pdfDetails)
|
||||||
|
}
|
||||||
|
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">{t("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={() => props?.setIsDownloadModal(true)}
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-download" aria-hidden="true"></i>
|
||||||
|
<span className="hidden lg:block ml-1">{t("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={() => props?.setIsEmail(true)}
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-envelope" aria-hidden="true"></i>
|
||||||
|
<span className="hidden lg:block ml-1">{t("mail")}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : props?.isPublicTemplate ? (
|
||||||
|
<div className="flex">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-primary op-btn-sm shadow"
|
||||||
|
onClick={() => props?.embedWidgetsData()}
|
||||||
|
>
|
||||||
|
{t("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]"
|
||||||
|
>
|
||||||
|
{t("back")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
|
||||||
|
onClick={() => props?.embedWidgetsData()}
|
||||||
|
>
|
||||||
|
{t("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" || isDownloading === "certificate_err"
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
isDownloading === "certificate" || isDownloading === "certificate_err"
|
||||||
|
? t("generating-certificate")
|
||||||
|
: t("pdf-download")
|
||||||
|
}
|
||||||
|
handleClose={() => setIsDownloading("")}
|
||||||
|
>
|
||||||
|
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
|
||||||
|
{isDownloading === "certificate" ? (
|
||||||
|
<p>{t("generate-certificate-alert")}</p>
|
||||||
|
) : (
|
||||||
|
<p>{t("generate-certificate-err")}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
<ModalUi
|
||||||
|
isOpen={isDeletePage}
|
||||||
|
title={t("delete-page")}
|
||||||
|
handleClose={() => setIsDeletePage(false)}
|
||||||
|
>
|
||||||
|
<div className="h-[100%] p-[20px]">
|
||||||
|
<p className="font-medium">{t("delete-alert-2")}</p>
|
||||||
|
<p className="pt-3">{t("delete-note")}</p>
|
||||||
|
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDetelePage()}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-primary"
|
||||||
|
>
|
||||||
|
{t("yes")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsDeletePage(false)}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost"
|
||||||
|
>
|
||||||
|
{t("no")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header;
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import React, { useRef, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
base64ToArrayBuffer,
|
||||||
|
deletePdfPage,
|
||||||
|
handleRemoveWidgets
|
||||||
|
} from "../../constant/Utils";
|
||||||
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
|
import { PDFDocument } from "pdf-lib";
|
||||||
|
import { maxFileSize } from "../../constant/const";
|
||||||
|
|
||||||
|
function PdfZoom(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const mergePdfInputRef = useRef(null);
|
||||||
|
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||||
|
const handleDetelePage = async () => {
|
||||||
|
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||||
|
try {
|
||||||
|
const pdfupdatedData = await deletePdfPage(
|
||||||
|
props.pdfArrayBuffer,
|
||||||
|
props.pageNumber
|
||||||
|
);
|
||||||
|
if (pdfupdatedData?.totalPages === 1) {
|
||||||
|
alert(t("delete-alert"));
|
||||||
|
} else {
|
||||||
|
props.setPdfBase64Url(pdfupdatedData.base64);
|
||||||
|
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||||
|
setIsDeletePage(false);
|
||||||
|
handleRemoveWidgets(
|
||||||
|
props.setSignerPos,
|
||||||
|
props.signerPos,
|
||||||
|
props.pageNumber
|
||||||
|
);
|
||||||
|
props.setAllPages(pdfupdatedData.remainingPages || 1);
|
||||||
|
if (props.allPages === props.pageNumber) {
|
||||||
|
props.setPageNumber(props.pageNumber - 1);
|
||||||
|
} else if (props.allPages > 2) {
|
||||||
|
props.setPageNumber(props.pageNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("error in delete pdf page", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// `removeFile` is used to remove file if exists
|
||||||
|
const removeFile = (e) => {
|
||||||
|
if (e) {
|
||||||
|
e.target.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileUpload = async (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) {
|
||||||
|
alert("Please upload a valid PDF file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!file.type.includes("pdf")) {
|
||||||
|
alert("Only PDF files are allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||||
|
if (mb > maxFileSize) {
|
||||||
|
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||||
|
removeFile(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const uploadedPdfBytes = await file.arrayBuffer();
|
||||||
|
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||||
|
ignoreEncryption: true
|
||||||
|
});
|
||||||
|
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
|
||||||
|
|
||||||
|
// Copy pages from the uploaded PDF to the base PDF
|
||||||
|
const uploadedPdfPages = await basePdfDoc.copyPages(
|
||||||
|
uploadedPdfDoc,
|
||||||
|
uploadedPdfDoc.getPageIndices()
|
||||||
|
);
|
||||||
|
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
|
||||||
|
// Save the updated PDF
|
||||||
|
const pdfBase64 = await basePdfDoc.saveAsBase64({
|
||||||
|
useObjectStreams: false
|
||||||
|
});
|
||||||
|
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
|
||||||
|
props.setPdfArrayBuffer(pdfBuffer);
|
||||||
|
props.setPdfBase64Url(pdfBase64);
|
||||||
|
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||||
|
mergePdfInputRef.current.value = "";
|
||||||
|
} catch (error) {
|
||||||
|
mergePdfInputRef.current.value = "";
|
||||||
|
console.error("Error merging PDF:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[42px]">
|
||||||
|
{!props.isDisableEditTools && (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||||
|
onClick={() => mergePdfInputRef.current.click()}
|
||||||
|
title={t("add-pages")}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
className="hidden"
|
||||||
|
accept="application/pdf"
|
||||||
|
ref={mergePdfInputRef}
|
||||||
|
onChange={handleFileUpload}
|
||||||
|
/>
|
||||||
|
<i className="fa-light fa-plus text-gray-500 2xl:text-[25px]"></i>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||||
|
onClick={() => setIsDeletePage(true)}
|
||||||
|
title={t("delete-page")}
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-trash text-gray-500 2xl:text-[25px]"></i>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||||
|
onClick={() => props.clickOnZoomIn()}
|
||||||
|
title={t("zoom-in")}
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[25px]"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{!props.isDisableEditTools && (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||||
|
onClick={() => props.handleRotationFun(90)}
|
||||||
|
title={t("rotate-right")}
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[25px]"></i>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
|
||||||
|
title={t("rotate-left")}
|
||||||
|
onClick={() => props.handleRotationFun(-90)}
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[25px]"></i>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className="bg-gray-50 px-[4px] 2xl:py-[10px]"
|
||||||
|
onClick={() => props.clickOnZoomOut()}
|
||||||
|
style={{
|
||||||
|
cursor: props.zoomPercent > 0 ? "pointer" : "default"
|
||||||
|
}}
|
||||||
|
title={t("zoom-out")}
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ModalUi
|
||||||
|
isOpen={isDeletePage}
|
||||||
|
title={t("delete-page")}
|
||||||
|
handleClose={() => setIsDeletePage(false)}
|
||||||
|
>
|
||||||
|
<div className="h-[100%] p-[20px]">
|
||||||
|
<p className="font-medium">{t("delete-alert-2")}</p>
|
||||||
|
<p className="pt-3">{t("delete-note")}</p>
|
||||||
|
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDetelePage()}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-primary"
|
||||||
|
>
|
||||||
|
{t("yes")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsDeletePage(false)}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost ml-1"
|
||||||
|
>
|
||||||
|
{t("no")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PdfZoom;
|
||||||
@@ -1,759 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import BorderResize from "./BorderResize";
|
|
||||||
import PlaceholderBorder from "./PlaceholderBorder";
|
|
||||||
import { Rnd } from "react-rnd";
|
|
||||||
import {
|
|
||||||
defaultWidthHeight,
|
|
||||||
handleCopyNextToWidget,
|
|
||||||
isMobile,
|
|
||||||
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 [isDraggingEnabled, setDraggingEnabled] = useState(true);
|
|
||||||
const [isShowDateFormat, setIsShowDateFormat] = useState(false);
|
|
||||||
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
|
|
||||||
});
|
|
||||||
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.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);
|
|
||||||
}
|
|
||||||
} 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.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);
|
|
||||||
} else {
|
|
||||||
props?.handleNameModal(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.isPlaceholder && props.type !== textWidget) {
|
|
||||||
props.setUniqueId(props.data.Id);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
} else if (props.data && props.pos.type === textWidget) {
|
|
||||||
props.setTempSignerId(props.uniqueId);
|
|
||||||
props.setSignerObjId(props?.data?.signerObjId);
|
|
||||||
props.setUniqueId(props?.data?.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
//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) && (
|
|
||||||
<>
|
|
||||||
{props.pos.type === "checkbox" && props.isSignYourself ? (
|
|
||||||
<i
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleOnClickSettingIcon();
|
|
||||||
}}
|
|
||||||
onTouchEnd={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleOnClickSettingIcon();
|
|
||||||
}}
|
|
||||||
className="fa-solid fa-gear settingIcon"
|
|
||||||
style={{
|
|
||||||
color: "#188ae2",
|
|
||||||
right: "29px",
|
|
||||||
top: "-19px"
|
|
||||||
}}
|
|
||||||
></i>
|
|
||||||
) : (
|
|
||||||
((!props?.pos?.type && props.pos.isStamp) ||
|
|
||||||
(props?.pos?.type &&
|
|
||||||
!["date", textWidget, "signature"].includes(
|
|
||||||
props.pos.type
|
|
||||||
) &&
|
|
||||||
!props.isSignYourself)) && (
|
|
||||||
<i
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleOnClickSettingIcon();
|
|
||||||
}}
|
|
||||||
onTouchEnd={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleOnClickSettingIcon();
|
|
||||||
}}
|
|
||||||
className="fa-solid fa-gear settingIcon"
|
|
||||||
style={{ color: "#188ae2", right: "47px", top: "-19px" }}
|
|
||||||
></i>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{props.pos.type !== textWidget && !props.isSignYourself && (
|
|
||||||
<i
|
|
||||||
data-tut="assignSigner"
|
|
||||||
className="fa-regular fa-user signUserIcon"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
props.handleLinkUser(props.data.Id);
|
|
||||||
props.setUniqueId(props.data.Id);
|
|
||||||
}}
|
|
||||||
onTouchEnd={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
props.handleLinkUser(props.data.Id);
|
|
||||||
props.setUniqueId(props.data.Id);
|
|
||||||
}}
|
|
||||||
style={{ color: "#188ae2", right: "32px", top: "-18px" }}
|
|
||||||
></i>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{props.pos.type === "date" && selectDate && (
|
|
||||||
<div
|
|
||||||
id="menu-container"
|
|
||||||
style={{
|
|
||||||
zIndex: "99",
|
|
||||||
top: "-19px",
|
|
||||||
right: props.isPlaceholder ? "60px" : "44px"
|
|
||||||
}}
|
|
||||||
className={
|
|
||||||
isShowDateFormat
|
|
||||||
? "dropdown signUserIcon show"
|
|
||||||
: "dropdown signUserIcon"
|
|
||||||
}
|
|
||||||
onClick={(e) => {
|
|
||||||
setIsShowDateFormat(!isShowDateFormat);
|
|
||||||
e.stopPropagation();
|
|
||||||
if (props.data) {
|
|
||||||
props.setSignKey(props.pos.key);
|
|
||||||
props.setUniqueId(props.data.Id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onTouchEnd={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsShowDateFormat(!isShowDateFormat);
|
|
||||||
if (props.data) {
|
|
||||||
props.setSignKey(props.pos.key);
|
|
||||||
props.setUniqueId(props.data.Id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa-solid fa-gear settingIcon"
|
|
||||||
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 itemColor"
|
|
||||||
style={{ fontSize: "12px" }}
|
|
||||||
>
|
|
||||||
{data?.date ? data?.date : "nodata"}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<i
|
|
||||||
className="fa-regular fa-copy signCopy"
|
|
||||||
onClick={(e) => handleCopyPlaceholder(e)}
|
|
||||||
onTouchEnd={(e) => handleCopyPlaceholder(e)}
|
|
||||||
style={{ color: "#188ae2", right: "12px", top: "-18px" }}
|
|
||||||
></i>
|
|
||||||
<i
|
|
||||||
className="fa-regular fa-circle-xmark signCloseBtn"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (props.data) {
|
|
||||||
props.handleDeleteSign(props.pos.key, props.data.Id);
|
|
||||||
} else {
|
|
||||||
props.handleDeleteSign(props.pos.key);
|
|
||||||
props.setIsStamp(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onTouchEnd={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (props.data) {
|
|
||||||
props.handleDeleteSign(props.pos.key, props.data?.Id);
|
|
||||||
} else {
|
|
||||||
props.handleDeleteSign(props.pos.key);
|
|
||||||
props.setIsStamp(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{ color: "#188ae2", right: "-8px", top: "-18px" }}
|
|
||||||
></i>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Rnd
|
|
||||||
id={props.pos.key}
|
|
||||||
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
|
|
||||||
key={props.pos.key}
|
|
||||||
lockAspectRatio={
|
|
||||||
// props.pos.type !== textWidget
|
|
||||||
![
|
|
||||||
textWidget,
|
|
||||||
"email",
|
|
||||||
"name",
|
|
||||||
"company",
|
|
||||||
"job title",
|
|
||||||
textInputWidget
|
|
||||||
].includes(props.pos.type) &&
|
|
||||||
(props.pos.Width
|
|
||||||
? props.pos.Width / props.pos.Height
|
|
||||||
: defaultWidthHeight(props.pos.type).width /
|
|
||||||
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:
|
|
||||||
props.data && props.isNeedSign
|
|
||||||
? props.data?.signerObjId === props.signerObjId
|
|
||||||
? "pointer"
|
|
||||||
: "not-allowed"
|
|
||||||
: "all-scroll",
|
|
||||||
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={() => {
|
|
||||||
props.setIsResize && props.setIsResize(false);
|
|
||||||
}}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
default={{
|
|
||||||
x: props.xPos(props.pos, props.isSignYourself),
|
|
||||||
y: props.yPos(props.pos, props.isSignYourself)
|
|
||||||
}}
|
|
||||||
onResize={(e, direction, ref) => {
|
|
||||||
props.handleSignYourselfImageResize &&
|
|
||||||
props.handleSignYourselfImageResize(
|
|
||||||
ref,
|
|
||||||
props.pos.key,
|
|
||||||
props.xyPostion,
|
|
||||||
props.setXyPostion,
|
|
||||||
props.index,
|
|
||||||
props.data && props.data.Id,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isMobile ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
left: props.xPos(props.pos, props.isSignYourself),
|
|
||||||
top: props.yPos(props.pos, props.isSignYourself),
|
|
||||||
width:
|
|
||||||
props.pos.type === radioButtonWidget ||
|
|
||||||
props.pos.type === "checkbox"
|
|
||||||
? "auto"
|
|
||||||
: props.posWidth(props.pos, props.isSignYourself),
|
|
||||||
// "auto", //props.posWidth(props.pos, props.isSignYourself),
|
|
||||||
// height: props.posHeight(props.pos, props.isSignYourself),
|
|
||||||
height:
|
|
||||||
props.pos.type === radioButtonWidget ||
|
|
||||||
props.pos.type === "checkbox"
|
|
||||||
? "auto"
|
|
||||||
: props.posHeight(props.pos, props.isSignYourself),
|
|
||||||
zIndex: "10"
|
|
||||||
}}
|
|
||||||
onTouchEnd={() => 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}
|
|
||||||
/>
|
|
||||||
</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}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Rnd>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Placeholder;
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
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 handleMinWidth = () => {
|
|
||||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
|
||||||
return props.getCheckboxRenderWidth.width + getResizeBorderExtraWidth;
|
|
||||||
} else {
|
|
||||||
return props.pos.Width
|
|
||||||
? props.pos.Width + getResizeBorderExtraWidth
|
|
||||||
: defaultWidth + getResizeBorderExtraWidth;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleMinHeight = () => {
|
|
||||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
|
||||||
return props.getCheckboxRenderWidth.height + getResizeBorderExtraWidth;
|
|
||||||
} else {
|
|
||||||
return props.pos.Height
|
|
||||||
? props.pos.Height + getResizeBorderExtraWidth
|
|
||||||
: defaultHeight + getResizeBorderExtraWidth;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
onMouseEnter={() => !isMobile && props?.setDraggingEnabled(true)}
|
|
||||||
onTouchEnd={() =>
|
|
||||||
props.pos.type === textWidget && props?.setDraggingEnabled(true)
|
|
||||||
}
|
|
||||||
className="borderResize"
|
|
||||||
style={{
|
|
||||||
borderColor: themeColor,
|
|
||||||
borderStyle: "dashed",
|
|
||||||
minWidth: handleMinWidth(),
|
|
||||||
minHeight: handleMinHeight(),
|
|
||||||
borderWidth: "0.2px",
|
|
||||||
overflow: "hidden"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PlaceholderBorder;
|
|
||||||
@@ -1,65 +1,107 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { themeColor } from "../../constant/const";
|
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
import { randomId, textWidget } from "../../constant/Utils";
|
import {
|
||||||
|
handleCopyNextToWidget,
|
||||||
|
randomId,
|
||||||
|
textWidget
|
||||||
|
} from "../../constant/Utils";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function PlaceholderCopy(props) {
|
function PlaceholderCopy(props) {
|
||||||
const copyType = ["All pages", "All pages but last", "All pages but first"];
|
const { t } = useTranslation();
|
||||||
const [selectCopyType, setSelectCopyType] = useState("");
|
const copyType = [
|
||||||
|
{ id: 1, type: "All pages" },
|
||||||
|
{ id: 2, type: "All pages but last" },
|
||||||
|
{ id: 3, type: "All pages but first" },
|
||||||
|
{ id: 4, type: "Next to current widget" }
|
||||||
|
];
|
||||||
|
const [selectCopyType, setSelectCopyType] = useState(1);
|
||||||
//function for get copy placeholder position
|
//function for get copy placeholder position
|
||||||
const getCopyPlaceholderPosition = (
|
const getCopyPlaceholderPosition = (
|
||||||
type,
|
type,
|
||||||
currentPlaceholder,
|
newPlaceholder,
|
||||||
newPageNumber,
|
newPageNumber,
|
||||||
existPlaceholderPosition
|
existPlaceholderPosition
|
||||||
) => {
|
) => {
|
||||||
|
//remove existing page position when used option copy but first or copy but last
|
||||||
const filterPosition =
|
const filterPosition =
|
||||||
existPlaceholderPosition &&
|
existPlaceholderPosition &&
|
||||||
existPlaceholderPosition.filter(
|
existPlaceholderPosition.filter(
|
||||||
(data) => data.xPosition !== currentPlaceholder.xPosition
|
(data) => data.xPosition !== newPlaceholder.xPosition
|
||||||
);
|
);
|
||||||
|
//first option - copy all page but not first
|
||||||
//copy all page placeholder at requested position except first page
|
//copy all page placeholder at requested position except first page
|
||||||
if (newPageNumber === 1 && type === "first") {
|
if (newPageNumber === 1 && type === 3) {
|
||||||
if (filterPosition && filterPosition.length > 0) {
|
//when user choose this option and placed widget on first page then do not remove first page widget
|
||||||
|
//it should be copy all page included first page on same postion
|
||||||
|
if (props.pageNumber === 1) {
|
||||||
|
return {
|
||||||
|
pageNumber: newPageNumber,
|
||||||
|
pos: [...existPlaceholderPosition]
|
||||||
|
};
|
||||||
|
} //else when user copy widget and choose this option and not at first page then copy all page but not at first
|
||||||
|
else if (filterPosition && filterPosition.length > 0) {
|
||||||
return {
|
return {
|
||||||
pageNumber: newPageNumber,
|
pageNumber: newPageNumber,
|
||||||
pos: [...filterPosition]
|
pos: [...filterPosition]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//second option - copy all page but not last
|
||||||
//copy all page placeholder at requested position except last page
|
//copy all page placeholder at requested position except last page
|
||||||
else if (newPageNumber === props.allPages && type === "last") {
|
else if (newPageNumber === props.allPages && type === 2) {
|
||||||
if (existPlaceholderPosition) {
|
if (existPlaceholderPosition) {
|
||||||
return {
|
//when user choose this option and placed widget on last page then do not remove last page widget
|
||||||
pageNumber: newPageNumber,
|
//it should be copy all page included last page on same postion
|
||||||
pos: [...filterPosition]
|
if (props.pageNumber === props.allPages) {
|
||||||
};
|
return {
|
||||||
|
pageNumber: newPageNumber,
|
||||||
|
pos: [...existPlaceholderPosition]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
pageNumber: newPageNumber,
|
||||||
|
pos: [...filterPosition]
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//this optin - copy all page with exiting widgets on same page
|
||||||
//copy all page placeholder at requested position with existing placeholder position
|
//copy all page placeholder at requested position with existing placeholder position
|
||||||
else if (existPlaceholderPosition) {
|
else if (existPlaceholderPosition) {
|
||||||
return {
|
if (newPageNumber === props.pageNumber) {
|
||||||
pageNumber: newPageNumber,
|
return {
|
||||||
pos: [...filterPosition, currentPlaceholder]
|
pageNumber: props.pageNumber,
|
||||||
};
|
pos: [...existPlaceholderPosition]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
pageNumber: newPageNumber,
|
||||||
|
pos: [...existPlaceholderPosition, newPlaceholder]
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//copy all page placeholder at requested position
|
//copy all page placeholder at requested position
|
||||||
else {
|
else {
|
||||||
return {
|
if (newPageNumber === props.pageNumber) {
|
||||||
pageNumber: newPageNumber,
|
return {
|
||||||
pos: [currentPlaceholder]
|
pageNumber: props.pageNumber,
|
||||||
};
|
pos: [...existPlaceholderPosition]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
pageNumber: newPageNumber,
|
||||||
|
pos: [newPlaceholder]
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//function for copy placeholder as per select copy type
|
//function for copy placeholder as per select copy type
|
||||||
const copyPlaceholder = (type) => {
|
const copyPlaceholder = (type) => {
|
||||||
let newPlaceholderPosition = [];
|
let newPlaceholderPosition = [];
|
||||||
let newPageNumber = 1;
|
let newPageNumber = 1;
|
||||||
const signerPosition = props.xyPostion;
|
const signerPosition = props.xyPosition;
|
||||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||||
|
|
||||||
//handle placeholder array and copy for multiple signers placeholder at requested location
|
//handle placeholder array and copy for multiple signers placeholder at requested location
|
||||||
if (signerId) {
|
if (signerId) {
|
||||||
//get current signers data
|
//get current signers data
|
||||||
@@ -81,10 +123,9 @@ function PlaceholderCopy(props) {
|
|||||||
const currentPlaceholder = placeholderPosition[0].pos.find(
|
const currentPlaceholder = placeholderPosition[0].pos.find(
|
||||||
(position) => position.key === props.signKey
|
(position) => position.key === props.signKey
|
||||||
);
|
);
|
||||||
|
|
||||||
for (let i = 0; i < props.allPages; i++) {
|
for (let i = 0; i < props.allPages; i++) {
|
||||||
const newId = randomId();
|
const newId = randomId();
|
||||||
currentPlaceholder.key = newId;
|
const newPlaceholder = { ...currentPlaceholder, key: newId };
|
||||||
//get exist placeholder position for particular page
|
//get exist placeholder position for particular page
|
||||||
const existPlaceholder = filterSignerPosition[0].placeHolder.filter(
|
const existPlaceholder = filterSignerPosition[0].placeHolder.filter(
|
||||||
(data) => data.pageNumber === newPageNumber
|
(data) => data.pageNumber === newPageNumber
|
||||||
@@ -95,11 +136,11 @@ function PlaceholderCopy(props) {
|
|||||||
//function for get copy to requested location of placeholder position
|
//function for get copy to requested location of placeholder position
|
||||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||||
type,
|
type,
|
||||||
currentPlaceholder,
|
newPlaceholder,
|
||||||
newPageNumber,
|
newPageNumber,
|
||||||
existPlaceholderPosition
|
existPlaceholderPosition
|
||||||
);
|
);
|
||||||
if (getPlaceholderObj) {
|
if (getPlaceholderObj?.pos) {
|
||||||
newPlaceholderPosition.push(getPlaceholderObj);
|
newPlaceholderPosition.push(getPlaceholderObj);
|
||||||
}
|
}
|
||||||
newPageNumber++;
|
newPageNumber++;
|
||||||
@@ -129,13 +170,13 @@ function PlaceholderCopy(props) {
|
|||||||
|
|
||||||
const signersData = signerPosition;
|
const signersData = signerPosition;
|
||||||
signersData.splice(0, signerPosition.length, ...updatedSignerPlaceholder);
|
signersData.splice(0, signerPosition.length, ...updatedSignerPlaceholder);
|
||||||
props.setXyPostion(signersData);
|
props.setXyPosition(signersData);
|
||||||
}
|
}
|
||||||
//handle signyourself array and copy for single signers placeholder at requested location
|
//handle signyourself array and copy for single signers placeholder at requested location
|
||||||
else {
|
else {
|
||||||
const xyPostion = props.xyPostion;
|
const xyPosition = props.xyPosition;
|
||||||
|
|
||||||
const placeholderPosition = xyPostion.filter(
|
const placeholderPosition = xyPosition.filter(
|
||||||
(data) => data.pageNumber === props.pageNumber
|
(data) => data.pageNumber === props.pageNumber
|
||||||
);
|
);
|
||||||
//get current placeholder position data which user want to copy
|
//get current placeholder position data which user want to copy
|
||||||
@@ -145,18 +186,18 @@ function PlaceholderCopy(props) {
|
|||||||
|
|
||||||
for (let i = 0; i < props.allPages; i++) {
|
for (let i = 0; i < props.allPages; i++) {
|
||||||
//get exist placeholder position for particular page
|
//get exist placeholder position for particular page
|
||||||
const existPlaceholder = xyPostion.filter(
|
const existPlaceholder = xyPosition.filter(
|
||||||
(data) => data.pageNumber === newPageNumber
|
(data) => data.pageNumber === newPageNumber
|
||||||
);
|
);
|
||||||
const existPlaceholderPosition =
|
const existPlaceholderPosition =
|
||||||
existPlaceholder[0] && existPlaceholder[0].pos;
|
existPlaceholder[0] && existPlaceholder[0].pos;
|
||||||
|
|
||||||
const newId = randomId();
|
const newId = randomId();
|
||||||
currentPlaceholder.key = newId;
|
const newPlaceholder = { ...currentPlaceholder, key: newId };
|
||||||
//function for get copy to requested location of placeholder position
|
//function for get copy to requested location of placeholder position
|
||||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||||
type,
|
type,
|
||||||
currentPlaceholder,
|
newPlaceholder,
|
||||||
newPageNumber,
|
newPageNumber,
|
||||||
existPlaceholderPosition
|
existPlaceholderPosition
|
||||||
);
|
);
|
||||||
@@ -166,83 +207,109 @@ function PlaceholderCopy(props) {
|
|||||||
|
|
||||||
newPageNumber++;
|
newPageNumber++;
|
||||||
}
|
}
|
||||||
props.setXyPostion(newPlaceholderPosition);
|
props.setXyPosition(newPlaceholderPosition);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//function for getting selected type placeholder copy
|
//function for getting selected type placeholder copy
|
||||||
const handleApplyCopy = () => {
|
const handleApplyCopy = () => {
|
||||||
if (selectCopyType === "All pages") {
|
if (selectCopyType === 4) {
|
||||||
copyPlaceholder("all");
|
const signerPosition = props.xyPosition;
|
||||||
} else if (selectCopyType === "All pages but last") {
|
let currentXYposition;
|
||||||
copyPlaceholder("last");
|
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||||
} else if (selectCopyType === "All pages but first") {
|
if (signerId) {
|
||||||
copyPlaceholder("first");
|
const filterSignerPosition = signerPosition.filter(
|
||||||
|
(item) => item?.Id === signerId
|
||||||
|
);
|
||||||
|
currentXYposition = filterSignerPosition[0].placeHolder.filter(
|
||||||
|
(data) => data.pageNumber === props?.pageNumber
|
||||||
|
);
|
||||||
|
//get current placeholder position data which user want to copy
|
||||||
|
currentXYposition = currentXYposition[0].pos.find(
|
||||||
|
(position) => position.key === props.signKey
|
||||||
|
);
|
||||||
|
//function to create new widget next to just widget
|
||||||
|
handleCopyNextToWidget(
|
||||||
|
currentXYposition,
|
||||||
|
props.widgetType,
|
||||||
|
props.xyPosition,
|
||||||
|
props.pageNumber,
|
||||||
|
props.setXyPosition,
|
||||||
|
props?.Id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const getIndex = props?.xyPosition.findIndex(
|
||||||
|
(data) => data.pageNumber === props.pageNumber
|
||||||
|
);
|
||||||
|
const placeholderPosition = props?.xyPosition[getIndex];
|
||||||
|
//get current placeholder position data which user want to copy
|
||||||
|
currentXYposition = placeholderPosition.pos.find(
|
||||||
|
(pos) => pos.key === props.signKey
|
||||||
|
);
|
||||||
|
//function to create new widget next to just widget
|
||||||
|
handleCopyNextToWidget(
|
||||||
|
currentXYposition,
|
||||||
|
props.widgetType,
|
||||||
|
props.xyPosition,
|
||||||
|
getIndex,
|
||||||
|
props.setXyPosition
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
copyPlaceholder(selectCopyType);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleUniqueId = () => {
|
const handleUniqueId = () => {
|
||||||
if (props.widgetType === textWidget) {
|
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||||
|
if (signerId && props.widgetType === textWidget && props.setTempSignerId) {
|
||||||
props.setUniqueId(props?.tempSignerId);
|
props.setUniqueId(props?.tempSignerId);
|
||||||
props.setTempSignerId("");
|
props.setTempSignerId("");
|
||||||
}
|
}
|
||||||
props.setIsPageCopy(false);
|
props.setIsPageCopy(false);
|
||||||
|
setSelectCopyType(1);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ModalUi
|
<ModalUi
|
||||||
isOpen={props.isPageCopy}
|
isOpen={props.isPageCopy}
|
||||||
title={"Copy to all pages"}
|
title={t("copy-title")}
|
||||||
handleClose={() => {
|
handleClose={() => handleUniqueId()}
|
||||||
props.setIsPageCopy(false);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div style={{ height: "100%", padding: 20 }}>
|
<div className="h-full p-[20px] text-base-content">
|
||||||
{copyType.map((data, key) => {
|
{copyType.map((data, key) => {
|
||||||
return (
|
return (
|
||||||
<div key={key} style={{ display: "flex", flexDirection: "column" }}>
|
<div key={key} className="flex flex-col">
|
||||||
<label key={key} style={{ fontSize: "16px", fontWeight: "500" }}>
|
<label className="text-[16px] font-medium">
|
||||||
<input
|
<input
|
||||||
style={{ accentColor: "red", marginRight: "10px" }}
|
className="mr-[8px] op-radio op-radio-xs"
|
||||||
type="radio"
|
type="radio"
|
||||||
value={data}
|
value={data.id}
|
||||||
onChange={() => setSelectCopyType(data)}
|
onChange={() => setSelectCopyType(data.id)}
|
||||||
checked={selectCopyType === data}
|
checked={selectCopyType === data.id}
|
||||||
/>
|
/>
|
||||||
|
{t(`copy-type.${data.type}`)}
|
||||||
{data}
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div
|
<div className="flex flex-row bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||||
style={{
|
|
||||||
height: "1px",
|
|
||||||
backgroundColor: "#9f9f9f",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "15px",
|
|
||||||
marginBottom: "15px"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleApplyCopy();
|
handleApplyCopy();
|
||||||
handleUniqueId();
|
handleUniqueId();
|
||||||
}}
|
}}
|
||||||
style={{ background: themeColor }}
|
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!selectCopyType}
|
disabled={!selectCopyType}
|
||||||
className="finishBtn"
|
className="op-btn op-btn-primary"
|
||||||
>
|
>
|
||||||
Apply
|
{t("apply")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="finishBtn cancelBtn"
|
className="op-btn op-btn-ghost ml-2"
|
||||||
onClick={() => {
|
onClick={() => handleUniqueId()}
|
||||||
handleUniqueId();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Cancel
|
{t("cancel")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ModalUi>
|
</ModalUi>
|
||||||
@@ -1,837 +0,0 @@
|
|||||||
import React, { useEffect, useState, forwardRef, useRef } from "react";
|
|
||||||
import {
|
|
||||||
getMonth,
|
|
||||||
getYear,
|
|
||||||
isMobile,
|
|
||||||
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";
|
|
||||||
|
|
||||||
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 = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
||||||
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="inputPlaceholder"
|
|
||||||
style={{ overflow: "hidden", fontSize: calculateFontSize() }}
|
|
||||||
onClick={onClick}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
<i className="fa-regular fa-calendar" style={{ marginLeft: "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]);
|
|
||||||
|
|
||||||
const calculateFontSize = () => {
|
|
||||||
const fontSize = 10 + Math.min(props.pos.Width, props.pos.Height) * 0.1;
|
|
||||||
const size = fontSize ? fontSize : 12;
|
|
||||||
return size + "px";
|
|
||||||
};
|
|
||||||
//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="signimg"
|
|
||||||
draggable="false"
|
|
||||||
src={props.pos.SignUrl}
|
|
||||||
style={{
|
|
||||||
width: "99%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "contain"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div style={{ fontSize: 11, color: "black", justifyContent: "center" }}>
|
|
||||||
{props?.handleUserName &&
|
|
||||||
props?.handleUserName(
|
|
||||||
props?.data?.Id,
|
|
||||||
props?.data?.Role,
|
|
||||||
props?.pos?.type
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "stamp":
|
|
||||||
return props.pos.SignUrl ? (
|
|
||||||
<img
|
|
||||||
alt="signimg"
|
|
||||||
draggable="false"
|
|
||||||
src={props.pos.SignUrl}
|
|
||||||
style={{
|
|
||||||
width: "99%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "contain"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div style={{ fontSize: 11, color: "black", justifyContent: "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
|
|
||||||
style={{
|
|
||||||
// width: props.pos.Width,
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
marginTop: ind === 0 ? 0 : "5px"
|
|
||||||
}}
|
|
||||||
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={
|
|
||||||
isMobile
|
|
||||||
? "labelTextArea labelWidthMobile"
|
|
||||||
: "labelTextArea labelWidthDesktop"
|
|
||||||
}
|
|
||||||
style={{ whiteSpace: "pre-wrap" }}
|
|
||||||
cols="50"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
color: "black",
|
|
||||||
fontSize: calculateFontSize()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>{type}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "dropdown":
|
|
||||||
return props.data?.signerObjId === props.signerObjId ? (
|
|
||||||
<select
|
|
||||||
className="inputPlaceholder"
|
|
||||||
id="myDropdown"
|
|
||||||
style={{ fontSize: "12px" }}
|
|
||||||
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="inputPlaceholder"
|
|
||||||
style={{ fontSize: calculateFontSize() }}
|
|
||||||
>
|
|
||||||
{props.pos?.options?.name ? props.pos.options.name : type}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "initials":
|
|
||||||
return props.pos.SignUrl ? (
|
|
||||||
<img
|
|
||||||
alt="signimg"
|
|
||||||
draggable="false"
|
|
||||||
src={props.pos.SignUrl}
|
|
||||||
style={{
|
|
||||||
width: "99%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "contain"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div style={{ fontSize: 11, color: "black", justifyContent: "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={
|
|
||||||
isMobile
|
|
||||||
? "labelTextArea labelWidthMobile"
|
|
||||||
: "labelTextArea labelWidthDesktop"
|
|
||||||
}
|
|
||||||
style={{ whiteSpace: "pre-wrap" }}
|
|
||||||
cols="50"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
color: "black",
|
|
||||||
fontSize: calculateFontSize()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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={
|
|
||||||
isMobile
|
|
||||||
? "labelTextArea labelWidthMobile"
|
|
||||||
: "labelTextArea labelWidthDesktop"
|
|
||||||
}
|
|
||||||
style={{ whiteSpace: "pre-wrap" }}
|
|
||||||
cols="50"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
color: "black",
|
|
||||||
fontSize: calculateFontSize()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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={
|
|
||||||
isMobile
|
|
||||||
? "labelTextArea labelWidthMobile"
|
|
||||||
: "labelTextArea labelWidthDesktop"
|
|
||||||
}
|
|
||||||
style={{ whiteSpace: "pre-wrap" }}
|
|
||||||
cols="50"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
color: "black",
|
|
||||||
fontSize: calculateFontSize()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>{type}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "date":
|
|
||||||
return (
|
|
||||||
<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="inputPlaceholder"
|
|
||||||
style={{ outlineColor: "#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>
|
|
||||||
);
|
|
||||||
case "image":
|
|
||||||
return props.pos.SignUrl ? (
|
|
||||||
<img
|
|
||||||
alt="signimg"
|
|
||||||
draggable="false"
|
|
||||||
src={props.pos.SignUrl}
|
|
||||||
style={{
|
|
||||||
width: "99%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "contain"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div style={{ fontSize: 11, color: "black", justifyContent: "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={
|
|
||||||
isMobile
|
|
||||||
? "labelTextArea labelWidthMobile"
|
|
||||||
: "labelTextArea labelWidthDesktop"
|
|
||||||
}
|
|
||||||
style={{ whiteSpace: "pre-wrap" }}
|
|
||||||
cols="50"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
color: "black",
|
|
||||||
fontSize: calculateFontSize()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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
|
|
||||||
style={{
|
|
||||||
width: props.pos.Width,
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
marginTop: ind === 0 ? 0 : "5px"
|
|
||||||
}}
|
|
||||||
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={
|
|
||||||
isMobile
|
|
||||||
? "labelTextArea labelWidthMobile"
|
|
||||||
: "labelTextArea labelWidthDesktop"
|
|
||||||
}
|
|
||||||
style={{ whiteSpace: "pre-wrap", overflow: "hidden" }}
|
|
||||||
cols="50"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return props.pos.SignUrl ? (
|
|
||||||
<div style={{ pointerEvents: "none" }}>
|
|
||||||
<img
|
|
||||||
alt="signimg"
|
|
||||||
draggable="false"
|
|
||||||
src={props.pos.SignUrl}
|
|
||||||
style={{
|
|
||||||
width: "99%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "contain"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: "10px",
|
|
||||||
color: "black",
|
|
||||||
justifyContent: "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,512 @@
|
|||||||
|
import React, { useEffect, useState, forwardRef, useRef } from "react";
|
||||||
|
import {
|
||||||
|
getMonth,
|
||||||
|
getYear,
|
||||||
|
radioButtonWidget,
|
||||||
|
textInputWidget,
|
||||||
|
textWidget,
|
||||||
|
months,
|
||||||
|
years,
|
||||||
|
selectCheckbox,
|
||||||
|
checkRegularExpress
|
||||||
|
} from "../../constant/Utils";
|
||||||
|
import DatePicker from "react-datepicker";
|
||||||
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
|
import "../../styles/signature.css";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
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";
|
||||||
|
const widgetCls =
|
||||||
|
"select-none-cls overflow-hidden w-full h-full text-black flex flex-col justify-center items-center";
|
||||||
|
function PlaceholderType(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const type = props?.pos?.type;
|
||||||
|
const iswidgetEnable =
|
||||||
|
props.isSignYourself ||
|
||||||
|
((props.isSelfSign || props.isNeedSign) &&
|
||||||
|
props.data?.signerObjId === props.signerObjId);
|
||||||
|
const widgetData =
|
||||||
|
props.pos?.options?.defaultValue || props.pos?.options?.response;
|
||||||
|
const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`);
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
const [widgetValue, setwidgetValue] = useState();
|
||||||
|
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
|
||||||
|
const [hint, setHint] = useState("");
|
||||||
|
const fontSize = props.calculateFont(props.pos.options?.fontSize);
|
||||||
|
const fontColor = props.pos.options?.fontColor || "black";
|
||||||
|
const textWidgetStyle = {
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor,
|
||||||
|
fontFamily: "Arial, sans-serif",
|
||||||
|
overflow: "hidden",
|
||||||
|
textAlign: "start",
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
height: "100%"
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (type !== "date") {
|
||||||
|
if (type && type === "checkbox") {
|
||||||
|
setSelectedCheckbox(
|
||||||
|
props?.pos?.options?.response ||
|
||||||
|
props?.pos?.options?.defaultValue ||
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (widgetData) {
|
||||||
|
setwidgetValue(widgetData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (props.pos?.options?.hint) {
|
||||||
|
setHint(props.pos?.options.hint);
|
||||||
|
} else if (props.pos?.options?.validation?.type) {
|
||||||
|
checkRegularExpress(props.pos?.options?.validation?.type, setHint);
|
||||||
|
} else {
|
||||||
|
setHint(props.pos?.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [props.pos]);
|
||||||
|
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor,
|
||||||
|
fontFamily: "Arial, sans-serif"
|
||||||
|
}}
|
||||||
|
className={`${selectWidgetCls} overflow-hidden`}
|
||||||
|
onClick={onClick}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
<i className="fa-light fa-calendar ml-[5px]"></i>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
ExampleCustomInput.displayName = "ExampleCustomInput";
|
||||||
|
|
||||||
|
const handleRadioCheck = (data) => {
|
||||||
|
const defaultData = props.pos.options?.defaultValue;
|
||||||
|
if (widgetValue === data) {
|
||||||
|
return true;
|
||||||
|
} else if (defaultData === data) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "signature":
|
||||||
|
return props.pos.SignUrl ? (
|
||||||
|
<img
|
||||||
|
alt="signature"
|
||||||
|
draggable="false"
|
||||||
|
src={props.pos.SignUrl}
|
||||||
|
className="w-full h-full select-none-cls "
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={widgetCls}>
|
||||||
|
{props.pos.type && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: props.pos
|
||||||
|
? props.calculateFontsize(props.pos)
|
||||||
|
: "11px"
|
||||||
|
}}
|
||||||
|
className="font-medium"
|
||||||
|
>
|
||||||
|
{hint || widgetTypeTranslation}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "stamp":
|
||||||
|
return props.pos.SignUrl ? (
|
||||||
|
<img
|
||||||
|
alt="stamp"
|
||||||
|
draggable="false"
|
||||||
|
src={props.pos.SignUrl}
|
||||||
|
className="w-full h-full select-none-cls"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={widgetCls}>
|
||||||
|
{props.pos.type && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: props.pos
|
||||||
|
? props.calculateFontsize(props.pos)
|
||||||
|
: "11px"
|
||||||
|
}}
|
||||||
|
className="font-medium"
|
||||||
|
>
|
||||||
|
{hint || widgetTypeTranslation}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "checkbox":
|
||||||
|
return (
|
||||||
|
<div style={{ zIndex: props.isSignYourself && "99" }}>
|
||||||
|
{props.pos.options?.values?.map((data, ind) => {
|
||||||
|
return (
|
||||||
|
<div key={ind} className="select-none-cls pointer-events-none">
|
||||||
|
<label
|
||||||
|
htmlFor={`checkbox-${props.pos.key + ind}`}
|
||||||
|
style={{ fontSize: fontSize, color: fontColor }}
|
||||||
|
className={`mb-0 flex items-center gap-1 ${
|
||||||
|
ind > 0 ? "mt-[3px]" : "mt-[0px]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id={`checkbox-${props.pos.key + ind}`}
|
||||||
|
style={{
|
||||||
|
width: fontSize,
|
||||||
|
height: fontSize
|
||||||
|
}}
|
||||||
|
className="op-checkbox rounded-[1px]"
|
||||||
|
disabled={
|
||||||
|
props.isNeedSign &&
|
||||||
|
(props.pos.options?.isReadOnly ||
|
||||||
|
props.data?.signerObjId !== props.signerObjId)
|
||||||
|
}
|
||||||
|
type="checkbox"
|
||||||
|
readOnly
|
||||||
|
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||||
|
/>
|
||||||
|
{!props.pos.options?.isHideLabel && (
|
||||||
|
<span className="leading-none">{data}</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case textInputWidget:
|
||||||
|
return props.isSignYourself || iswidgetEnable ? (
|
||||||
|
<textarea
|
||||||
|
ref={inputRef}
|
||||||
|
placeholder={hint || t("widgets-name.text")}
|
||||||
|
rows={1}
|
||||||
|
value={widgetValue}
|
||||||
|
className={`${
|
||||||
|
props.pos.options?.isReadOnly ||
|
||||||
|
props.data?.signerObjId !== props.signerObjId
|
||||||
|
? "select-none"
|
||||||
|
: textWidgetCls
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor,
|
||||||
|
background: props.data?.blockColor,
|
||||||
|
pointerEvents: "none"
|
||||||
|
}}
|
||||||
|
readOnly
|
||||||
|
disabled={
|
||||||
|
props.isNeedSign &&
|
||||||
|
(props.pos.options?.isReadOnly ||
|
||||||
|
props.data?.signerObjId !== props.signerObjId)
|
||||||
|
}
|
||||||
|
cols="50"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={textWidgetStyle} className="select-none-cls">
|
||||||
|
<span>{hint || widgetTypeTranslation}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "dropdown":
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={textWidgetStyle}
|
||||||
|
className="select-none-cls flex justify-between items-center"
|
||||||
|
>
|
||||||
|
{widgetData || hint || widgetTypeTranslation}
|
||||||
|
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "initials":
|
||||||
|
return props.pos.SignUrl ? (
|
||||||
|
<img
|
||||||
|
alt="initials"
|
||||||
|
draggable="false"
|
||||||
|
src={props.pos.SignUrl}
|
||||||
|
className="w-full h-full select-none-cls"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={widgetCls}>
|
||||||
|
{props.pos.type && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: props.pos
|
||||||
|
? props.calculateFontsize(props.pos)
|
||||||
|
: "11px"
|
||||||
|
}}
|
||||||
|
className="font-medium text-center"
|
||||||
|
>
|
||||||
|
{hint || widgetTypeTranslation}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "name":
|
||||||
|
return iswidgetEnable ? (
|
||||||
|
<textarea
|
||||||
|
readOnly
|
||||||
|
ref={inputRef}
|
||||||
|
placeholder={hint || widgetTypeTranslation}
|
||||||
|
rows={1}
|
||||||
|
value={widgetValue}
|
||||||
|
className={textWidgetCls}
|
||||||
|
style={{
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor,
|
||||||
|
pointerEvents: "none"
|
||||||
|
}}
|
||||||
|
cols="50"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
|
||||||
|
<span> {props.pos?.options?.hint || widgetTypeTranslation}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "company":
|
||||||
|
return iswidgetEnable ? (
|
||||||
|
<textarea
|
||||||
|
readOnly
|
||||||
|
ref={inputRef}
|
||||||
|
placeholder={hint || widgetTypeTranslation}
|
||||||
|
rows={1}
|
||||||
|
value={widgetValue}
|
||||||
|
className={textWidgetCls}
|
||||||
|
style={{
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor,
|
||||||
|
pointerEvents: "none"
|
||||||
|
}}
|
||||||
|
cols="50"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={textWidgetStyle} className="select-none-cls">
|
||||||
|
<span>{hint || widgetTypeTranslation}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "job title":
|
||||||
|
return iswidgetEnable ? (
|
||||||
|
<textarea
|
||||||
|
readOnly
|
||||||
|
ref={inputRef}
|
||||||
|
placeholder={hint || widgetTypeTranslation}
|
||||||
|
rows={1}
|
||||||
|
value={widgetValue}
|
||||||
|
className={textWidgetCls}
|
||||||
|
style={{
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor,
|
||||||
|
pointerEvents: "none"
|
||||||
|
}}
|
||||||
|
cols="50"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={textWidgetStyle} className="select-none-cls">
|
||||||
|
<span>{hint || widgetTypeTranslation}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "date":
|
||||||
|
return iswidgetEnable ? (
|
||||||
|
<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={true}
|
||||||
|
closeOnScroll={true}
|
||||||
|
className={`${selectWidgetCls} outline-[#007bff]`}
|
||||||
|
selected={props?.startDate}
|
||||||
|
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
|
||||||
|
style={textWidgetStyle}
|
||||||
|
className="select-none-cls 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 select-none-cls"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={widgetCls}>
|
||||||
|
{props.pos.type && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: props.pos
|
||||||
|
? props.calculateFontsize(props.pos)
|
||||||
|
: "11px"
|
||||||
|
}}
|
||||||
|
className="font-medium text-center"
|
||||||
|
>
|
||||||
|
{hint || widgetTypeTranslation}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "email":
|
||||||
|
return iswidgetEnable ? (
|
||||||
|
<textarea
|
||||||
|
readOnly
|
||||||
|
ref={inputRef}
|
||||||
|
placeholder={hint || widgetTypeTranslation}
|
||||||
|
rows={1}
|
||||||
|
value={widgetValue}
|
||||||
|
className={textWidgetCls}
|
||||||
|
style={{
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor,
|
||||||
|
fontFamily: "Arial, sans-serif",
|
||||||
|
pointerEvents: "none"
|
||||||
|
}}
|
||||||
|
disabled
|
||||||
|
cols="1"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={textWidgetStyle} className="select-none-cls">
|
||||||
|
<span>{hint || widgetTypeTranslation}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case radioButtonWidget:
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{props.pos.options?.values.map((data, ind) => {
|
||||||
|
return (
|
||||||
|
<div key={ind} className="select-none-cls pointer-events-none">
|
||||||
|
<label
|
||||||
|
htmlFor={`radio-${props.pos.key + ind}`}
|
||||||
|
style={{
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor,
|
||||||
|
marginTop: ind > 0 ? "5px" : "0px"
|
||||||
|
}}
|
||||||
|
className="text-xs mb-0 flex items-center gap-1 "
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
readOnly
|
||||||
|
id={`radio-${props.pos.key + ind}`}
|
||||||
|
style={{
|
||||||
|
width: fontSize,
|
||||||
|
height: fontSize,
|
||||||
|
lineHeight: 2
|
||||||
|
}}
|
||||||
|
className={`op-radio rounded-full border- border-black appearance-none bg-white inline-block align-middle relative ${
|
||||||
|
handleRadioCheck(data) ? "checked-radio" : ""
|
||||||
|
}`}
|
||||||
|
type="radio"
|
||||||
|
disabled={
|
||||||
|
props.isNeedSign &&
|
||||||
|
(props.pos.options?.isReadOnly ||
|
||||||
|
props.data?.signerObjId !== props.signerObjId)
|
||||||
|
}
|
||||||
|
checked={handleRadioCheck(data)}
|
||||||
|
/>
|
||||||
|
{!props.pos.options?.isHideLabel && (
|
||||||
|
<span className="leading-none">{data}</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case textWidget:
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
readOnly
|
||||||
|
placeholder={t("widgets-name.text")}
|
||||||
|
rows={1}
|
||||||
|
value={widgetValue}
|
||||||
|
className={textWidgetCls}
|
||||||
|
style={{
|
||||||
|
fontFamily: "Arial, sans-serif",
|
||||||
|
fontSize: fontSize,
|
||||||
|
color: fontColor
|
||||||
|
}}
|
||||||
|
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={widgetCls}>
|
||||||
|
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
|
||||||
|
{props.pos.type && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: props.pos
|
||||||
|
? props.calculateFontsize(props.pos)
|
||||||
|
: "11px"
|
||||||
|
}}
|
||||||
|
className="font-medium"
|
||||||
|
>
|
||||||
|
{hint || widgetTypeTranslation}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlaceholderType;
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { themeColor } from "../../constant/const";
|
|
||||||
|
|
||||||
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>
|
|
||||||
<div className="preBtn1">
|
|
||||||
<button
|
|
||||||
className="py-[3px] px-[10px] text-xs bg-[#d3edeb] font-[600] mr-[5px]"
|
|
||||||
disabled={pageNumber <= 1}
|
|
||||||
onClick={previousPage}
|
|
||||||
>
|
|
||||||
<span className="block lg:hidden">
|
|
||||||
<i
|
|
||||||
className="fa fa-backward"
|
|
||||||
aria-hidden="true"
|
|
||||||
style={{ color: themeColor, cursor: "pointer" }}
|
|
||||||
></i>
|
|
||||||
</span>
|
|
||||||
<span className="lg:block hidden">Prev</span>
|
|
||||||
</button>
|
|
||||||
<span className="text-xs font-[500]">
|
|
||||||
{pageNumber || (allPages ? 1 : "--")} of {allPages || "--"}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
className="py-[3px] px-[10px] text-xs bg-[#d3edeb] font-[600] ml-[5px]"
|
|
||||||
disabled={pageNumber >= allPages}
|
|
||||||
onClick={nextPage}
|
|
||||||
>
|
|
||||||
<span className="block lg:hidden">
|
|
||||||
<i
|
|
||||||
className="fa fa-forward"
|
|
||||||
aria-hidden="true"
|
|
||||||
style={{ color: themeColor, cursor: "pointer" }}
|
|
||||||
></i>
|
|
||||||
</span>
|
|
||||||
<span className="lg:block hidden">Next</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PrevNext;
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
function PrevNext({ pageNumber, allPages, changePage }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
//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">
|
||||||
|
<i className="fa-light fa-backward" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<span className="text-xs text-base-content font-medium mx-2 2xl:text-[20px]">
|
||||||
|
{pageNumber || (allPages ? 1 : "--")} {t("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">
|
||||||
|
<i className="fa-light fa-forward" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PrevNext;
|
||||||
@@ -1,295 +0,0 @@
|
|||||||
import React, { useRef, useState } from "react";
|
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
|
||||||
import {
|
|
||||||
color,
|
|
||||||
darkenColor,
|
|
||||||
getFirstLetter,
|
|
||||||
isMobile,
|
|
||||||
nameColor
|
|
||||||
} from "../../constant/Utils";
|
|
||||||
|
|
||||||
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 onHoverStyle = (ind, blockColor) => {
|
|
||||||
const style = {
|
|
||||||
background: blockColor ? blockColor : color[ind % color.length],
|
|
||||||
padding: "10px",
|
|
||||||
marginTop: "2px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
borderBottom: "1px solid #e3e1e1",
|
|
||||||
alignItems: "center"
|
|
||||||
};
|
|
||||||
return style;
|
|
||||||
};
|
|
||||||
//function for onhover signer name remove background color
|
|
||||||
const nonHoverStyle = () => {
|
|
||||||
const style = {
|
|
||||||
padding: "10px",
|
|
||||||
marginTop: "2px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
borderBottom: "1px solid #e3e1e1",
|
|
||||||
alignItems: "center"
|
|
||||||
};
|
|
||||||
return style;
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
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="reactourFirst"
|
|
||||||
onMouseEnter={() => setIsHover(ind)}
|
|
||||||
onMouseLeave={() => setIsHover(null)}
|
|
||||||
className={
|
|
||||||
props.sendInOrder
|
|
||||||
? props.isMailSend
|
|
||||||
? "disabled"
|
|
||||||
: "dragCursor"
|
|
||||||
: props.isMailSend && "disabled"
|
|
||||||
}
|
|
||||||
style={
|
|
||||||
(!isMobile && isHover === ind) || props.isSelectListId === ind
|
|
||||||
? onHoverStyle(ind, obj?.blockColor)
|
|
||||||
: nonHoverStyle(ind)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
width: "100%"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
background: obj?.blockColor
|
|
||||||
? darkenColor(obj?.blockColor, 0.4)
|
|
||||||
: nameColor[ind % nameColor.length],
|
|
||||||
width: 30,
|
|
||||||
height: 30,
|
|
||||||
display: "flex",
|
|
||||||
borderRadius: 30 / 2,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
marginRight: "12px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={props.sendInOrder && "dragCursor"}
|
|
||||||
style={{
|
|
||||||
fontSize: "12px",
|
|
||||||
textAlign: "center",
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: "white",
|
|
||||||
textTransform: "uppercase"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isWidgetExist(obj.Id) ? (
|
|
||||||
<i className="fa-solid fa-check"></i>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{obj.Name
|
|
||||||
? getFirstLetter(obj.Name)
|
|
||||||
: getFirstLetter(obj.Role)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: obj.Name ? "column" : "row",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{obj.Name ? (
|
|
||||||
<span className={"userName"}>{obj.Name}</span>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
className="userName"
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
onClick={() => {
|
|
||||||
setIsEdit({ [obj.Id]: true });
|
|
||||||
props.setRoleName(obj.Role);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isEdit?.[obj.Id] && props.handleRoleChange ? (
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
style={{
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
width: "inherit",
|
|
||||||
padding: "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 style={{ padding: "3px" }}>{obj.Role}</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{obj.Name && (
|
|
||||||
<span className={"useEmail"}>
|
|
||||||
{obj?.Role || obj?.Email}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isMobile && props.sendInOrder && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 5
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onClick={(e) => {
|
|
||||||
if (ind !== 0) {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleChangeSequence(e, ind, "up");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{ color: ind === 0 ? "gray" : "black" }}
|
|
||||||
>
|
|
||||||
▲
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
onClick={(e) => {
|
|
||||||
if (ind !== props.signersdata.length - 1) {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleChangeSequence(e, ind, null, "down");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
color:
|
|
||||||
ind === props.signersdata.length - 1 ? "gray" : "black"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
▼
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{props.handleDeleteUser && (
|
|
||||||
<div
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
props.handleDeleteUser(obj.Id);
|
|
||||||
}}
|
|
||||||
style={{ cursor: "pointer", marginLeft: "5px" }}
|
|
||||||
>
|
|
||||||
<i className="fa-regular fa-trash-can"></i>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RecipientList;
|
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
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 && x.placeHolder);
|
||||||
|
};
|
||||||
|
|
||||||
|
//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, obj) => {
|
||||||
|
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?.setIsSelectId(index);
|
||||||
|
props?.setUniqueId(remainingItems[index]?.Id);
|
||||||
|
props?.setRoleName(remainingItems[index]?.Role);
|
||||||
|
props?.setBlockColor(obj.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, null, null, obj)
|
||||||
|
}
|
||||||
|
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.setIsSelectId(ind);
|
||||||
|
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-[17px]">
|
||||||
|
{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", null, obj);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={ind === 0 ? "text-[gray]" : "text-black"}
|
||||||
|
>
|
||||||
|
▲
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={(e) => {
|
||||||
|
if (ind !== props.signersdata.length - 1) {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleChangeSequence(e, ind, null, "down", obj);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
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-[22px]"></i>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RecipientList;
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import RSC from "react-scrollbars-custom";
|
|
||||||
import { Document, Page } from "react-pdf";
|
|
||||||
import { themeColor } from "../../constant/const";
|
|
||||||
|
|
||||||
function RenderAllPdfPage({
|
|
||||||
signPdfUrl,
|
|
||||||
allPages,
|
|
||||||
setAllPages,
|
|
||||||
setPageNumber,
|
|
||||||
setSignBtnPosition,
|
|
||||||
pageNumber,
|
|
||||||
signerPos,
|
|
||||||
signerObjectId
|
|
||||||
}) {
|
|
||||||
const [signPageNumber, setSignPageNumber] = useState([]);
|
|
||||||
const [bookmarkColor, setBookmarkColor] = useState("");
|
|
||||||
//set all number of pages after load pdf
|
|
||||||
function onDocumentLoad({ numPages }) {
|
|
||||||
setAllPages(numPages);
|
|
||||||
//check if signerPos array exist then save page number exist in signerPos array to show bookmark icon
|
|
||||||
if (signerPos) {
|
|
||||||
const checkUser = signerPos.filter(
|
|
||||||
(data) => data.signerObjId === signerObjectId
|
|
||||||
);
|
|
||||||
setBookmarkColor(checkUser[0]?.blockColor);
|
|
||||||
let pageNumberArr = [];
|
|
||||||
if (checkUser?.length > 0) {
|
|
||||||
checkUser[0]?.placeHolder?.map((data) => {
|
|
||||||
pageNumberArr.push(data?.pageNumber);
|
|
||||||
});
|
|
||||||
|
|
||||||
setSignPageNumber(pageNumberArr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//'function `addSignatureBookmark` is used to display the page where the user's signature is located.
|
|
||||||
const addSignatureBookmark = (index) => {
|
|
||||||
const ispageNumber = signPageNumber.includes(index + 1);
|
|
||||||
return (
|
|
||||||
ispageNumber && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
zIndex: 2,
|
|
||||||
top: -12,
|
|
||||||
right: -7,
|
|
||||||
transform: "translate(50% -50%)"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
style={{ color: bookmarkColor || "red" }}
|
|
||||||
className="fa-solid fa-bookmark"
|
|
||||||
></i>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div className="showPages">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
|
||||||
backgroundColor: "white",
|
|
||||||
height: "100%"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ width: "140px" }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
background: themeColor
|
|
||||||
// color:"white"
|
|
||||||
}}
|
|
||||||
className="signedStyle"
|
|
||||||
>
|
|
||||||
Pages
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<RSC
|
|
||||||
id="RSC-Example"
|
|
||||||
style={{
|
|
||||||
width: "135px",
|
|
||||||
height: window.innerHeight - 130 + "px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Document
|
|
||||||
loading={"Loading Document.."}
|
|
||||||
onLoadSuccess={onDocumentLoad}
|
|
||||||
file={signPdfUrl}
|
|
||||||
>
|
|
||||||
{Array.from(new Array(allPages), (el, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
style={{
|
|
||||||
width: "100px",
|
|
||||||
border:
|
|
||||||
pageNumber - 1 === index
|
|
||||||
? "2px solid red"
|
|
||||||
: "2px solid #878787",
|
|
||||||
// padding: "5px",
|
|
||||||
margin: "10px",
|
|
||||||
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
|
|
||||||
position: "relative"
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
setPageNumber(index + 1);
|
|
||||||
if (setSignBtnPosition) {
|
|
||||||
setSignBtnPosition([]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{signerPos && addSignatureBookmark(index)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "relative",
|
|
||||||
zIndex: 1,
|
|
||||||
overflow: "hidden"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Page
|
|
||||||
key={`page_${index + 1}`}
|
|
||||||
pageNumber={index + 1}
|
|
||||||
width={100}
|
|
||||||
height={100}
|
|
||||||
scale={1}
|
|
||||||
renderAnnotationLayer={false}
|
|
||||||
renderTextLayer={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Document>
|
|
||||||
</RSC>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RenderAllPdfPage;
|
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Document, Page } from "react-pdf";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { PDFDocument } from "pdf-lib";
|
||||||
|
import { base64ToArrayBuffer } from "../../constant/Utils";
|
||||||
|
import { maxFileSize } from "../../constant/const";
|
||||||
|
|
||||||
|
function RenderAllPdfPage(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const pageContainer = useRef();
|
||||||
|
const mergePdfInputRef = useRef(null);
|
||||||
|
const [signPageNumber, setSignPageNumber] = useState([]);
|
||||||
|
const [bookmarkColor, setBookmarkColor] = useState("");
|
||||||
|
const isHeader = useSelector((state) => state.showHeader);
|
||||||
|
const [pageWidth, setPageWidth] = useState("");
|
||||||
|
|
||||||
|
//set all number of pages after load pdf
|
||||||
|
function onDocumentLoad({ numPages }) {
|
||||||
|
props?.setAllPages(numPages);
|
||||||
|
//check if signerPos array exist then save page number exist in signerPos array to show bookmark icon
|
||||||
|
if (props?.signerPos) {
|
||||||
|
const checkUser = props?.signerPos.filter(
|
||||||
|
(data) => data.Id === props?.id
|
||||||
|
);
|
||||||
|
setBookmarkColor(checkUser[0]?.blockColor);
|
||||||
|
let pageNumberArr = [];
|
||||||
|
if (checkUser?.length > 0) {
|
||||||
|
checkUser[0]?.placeHolder?.map((data) => {
|
||||||
|
pageNumberArr.push(data?.pageNumber);
|
||||||
|
});
|
||||||
|
|
||||||
|
setSignPageNumber(pageNumberArr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, props?.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>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const pdfDataBase64 = `data:application/pdf;base64,${props?.pdfBase64Url}`;
|
||||||
|
|
||||||
|
// `removeFile` is used to remove file if exists
|
||||||
|
const removeFile = (e) => {
|
||||||
|
if (e) {
|
||||||
|
e.target.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// `handleFileUpload` is trigger when user click on add pages btn and is used to merge multiple pdf
|
||||||
|
const handleFileUpload = async (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) {
|
||||||
|
alert("Please upload a valid PDF file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!file.type.includes("pdf")) {
|
||||||
|
alert("Only PDF files are allowed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||||
|
if (mb > maxFileSize) {
|
||||||
|
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||||
|
removeFile(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const uploadedPdfBytes = await file.arrayBuffer();
|
||||||
|
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||||
|
ignoreEncryption: true
|
||||||
|
});
|
||||||
|
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
|
||||||
|
|
||||||
|
// Copy pages from the uploaded PDF to the base PDF
|
||||||
|
const uploadedPdfPages = await basePdfDoc.copyPages(
|
||||||
|
uploadedPdfDoc,
|
||||||
|
uploadedPdfDoc.getPageIndices()
|
||||||
|
);
|
||||||
|
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
|
||||||
|
// Save the updated PDF
|
||||||
|
const pdfBase64 = await basePdfDoc.saveAsBase64({
|
||||||
|
useObjectStreams: false
|
||||||
|
});
|
||||||
|
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
|
||||||
|
props.setPdfArrayBuffer(pdfBuffer);
|
||||||
|
props.setPdfBase64Url(pdfBase64);
|
||||||
|
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||||
|
mergePdfInputRef.current.value = "";
|
||||||
|
} catch (error) {
|
||||||
|
mergePdfInputRef.current.value = "";
|
||||||
|
console.error("Error merging PDF:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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">
|
||||||
|
{t("pages")}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`flex h-[90%] flex-col items-center m-2
|
||||||
|
autoSignScroll hide-scrollbar max-h-[100vh] `}
|
||||||
|
>
|
||||||
|
<Document
|
||||||
|
error=""
|
||||||
|
loading={t("loading-doc")}
|
||||||
|
onLoadSuccess={onDocumentLoad}
|
||||||
|
file={pdfDataBase64}
|
||||||
|
>
|
||||||
|
{Array.from(new Array(props?.allPages), (el, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`${
|
||||||
|
props?.pageNumber - 1 === index
|
||||||
|
? "border-[red]"
|
||||||
|
: "border-[#878787]"
|
||||||
|
} border-2 m-[10px] flex justify-center items-center relative`}
|
||||||
|
onClick={() => {
|
||||||
|
props?.setPageNumber(index + 1);
|
||||||
|
if (props?.setSignBtnPosition) {
|
||||||
|
props?.setSignBtnPosition([]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props?.signerPos && addSignatureBookmark(index)}
|
||||||
|
<Page
|
||||||
|
key={`page_${index + 1}`}
|
||||||
|
pageNumber={index + 1}
|
||||||
|
width={pageWidth - 60}
|
||||||
|
scale={1}
|
||||||
|
renderAnnotationLayer={false}
|
||||||
|
renderTextLayer={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Document>
|
||||||
|
{props?.isMergePdfBtn && (
|
||||||
|
<button
|
||||||
|
className="mb-2 bg-base-100 px-2 py-2 ring-[0.5px] ring-base-content rounded-box flex gap-1 justify-center items-center"
|
||||||
|
onClick={() => mergePdfInputRef.current.click()}
|
||||||
|
title={t("add-pages")}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
className="hidden"
|
||||||
|
accept="application/pdf"
|
||||||
|
ref={mergePdfInputRef}
|
||||||
|
onChange={handleFileUpload}
|
||||||
|
/>
|
||||||
|
<i className="fa-light fa-plus text-gray-500"></i>
|
||||||
|
<span className="text-xs lg:text-sm text-base-content ">
|
||||||
|
Add pages
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RenderAllPdfPage;
|
||||||
@@ -1,696 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import RSC from "react-scrollbars-custom";
|
|
||||||
import { Document, Page } from "react-pdf";
|
|
||||||
import {
|
|
||||||
defaultWidthHeight,
|
|
||||||
handleImageResize,
|
|
||||||
handleSignYourselfImageResize
|
|
||||||
} from "../../constant/Utils";
|
|
||||||
import Placeholder from "./Placeholder";
|
|
||||||
import Alert from "../../primitives/Alert";
|
|
||||||
|
|
||||||
function RenderPdf({
|
|
||||||
pageNumber,
|
|
||||||
pdfOriginalWidth,
|
|
||||||
pdfNewWidth,
|
|
||||||
drop,
|
|
||||||
signerPos,
|
|
||||||
successEmail,
|
|
||||||
handleTabDrag,
|
|
||||||
handleStop,
|
|
||||||
isDragging,
|
|
||||||
setIsSignPad,
|
|
||||||
setIsStamp,
|
|
||||||
handleDeleteSign,
|
|
||||||
setSignKey,
|
|
||||||
pdfDetails,
|
|
||||||
xyPostion,
|
|
||||||
pdfUrl,
|
|
||||||
numPages,
|
|
||||||
pageDetails,
|
|
||||||
pdfRequest,
|
|
||||||
setCurrentSigner,
|
|
||||||
signerObjectId,
|
|
||||||
signedSigners,
|
|
||||||
setPdfLoadFail,
|
|
||||||
placeholder,
|
|
||||||
pdfLoadFail,
|
|
||||||
setSignerPos,
|
|
||||||
setXyPostion,
|
|
||||||
index,
|
|
||||||
containerWH,
|
|
||||||
setIsResize,
|
|
||||||
handleLinkUser,
|
|
||||||
setUniqueId,
|
|
||||||
signersdata,
|
|
||||||
setIsPageCopy,
|
|
||||||
setSignerObjId,
|
|
||||||
setShowDropdown,
|
|
||||||
setIsInitial,
|
|
||||||
setIsValidate,
|
|
||||||
setWidgetType,
|
|
||||||
setValidateAlert,
|
|
||||||
setIsRadio,
|
|
||||||
setCurrWidgetsDetails,
|
|
||||||
setSelectWidgetId,
|
|
||||||
selectWidgetId,
|
|
||||||
unSignedWidgetId,
|
|
||||||
setIsCheckbox,
|
|
||||||
handleNameModal,
|
|
||||||
setTempSignerId,
|
|
||||||
uniqueId
|
|
||||||
}) {
|
|
||||||
const isMobile = window.innerWidth < 767;
|
|
||||||
const newWidth = containerWH.width;
|
|
||||||
const scale = isMobile ? pdfOriginalWidth / newWidth : 1;
|
|
||||||
//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 defaultWidth = defaultWidthHeight(pos.type).width;
|
|
||||||
const posWidth = pos.Width ? pos.Width : defaultWidth;
|
|
||||||
let width;
|
|
||||||
if (signYourself) {
|
|
||||||
width = posWidth;
|
|
||||||
return width;
|
|
||||||
} else {
|
|
||||||
if (isMobile) {
|
|
||||||
if (!pos.isMobile) {
|
|
||||||
if (pos.IsResize) {
|
|
||||||
width = posWidth ? posWidth : defaultWidth;
|
|
||||||
return width;
|
|
||||||
} else {
|
|
||||||
width = (posWidth || defaultWidth) / scale;
|
|
||||||
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
width = posWidth;
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (pos.isMobile) {
|
|
||||||
if (pos.IsResize) {
|
|
||||||
width = posWidth ? posWidth : defaultWidth;
|
|
||||||
return width;
|
|
||||||
} else {
|
|
||||||
width = (posWidth || defaultWidth) * pos.scale;
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
width = posWidth ? posWidth : defaultWidth;
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const posHeight = (pos, signYourself) => {
|
|
||||||
let height;
|
|
||||||
const posHeight = pos.Height;
|
|
||||||
const defaultHeight = defaultWidthHeight(pos.type).height;
|
|
||||||
|
|
||||||
if (signYourself) {
|
|
||||||
height = posHeight ? posHeight : defaultHeight;
|
|
||||||
|
|
||||||
return height;
|
|
||||||
} else {
|
|
||||||
if (isMobile) {
|
|
||||||
if (!pos.isMobile) {
|
|
||||||
if (pos.IsResize) {
|
|
||||||
height = posHeight ? posHeight : defaultHeight;
|
|
||||||
return height;
|
|
||||||
} else {
|
|
||||||
height = (posHeight || defaultHeight) / scale;
|
|
||||||
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
height = posHeight ? posHeight : defaultHeight;
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (pos.isMobile) {
|
|
||||||
if (pos.IsResize) {
|
|
||||||
height = posHeight ? posHeight : defaultHeight;
|
|
||||||
return height;
|
|
||||||
} else {
|
|
||||||
height = (posHeight || defaultHeight) * pos.scale;
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
height = posHeight ? posHeight : defaultHeight;
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const xPos = (pos, signYourself) => {
|
|
||||||
const resizePos = pos.xPosition;
|
|
||||||
if (signYourself) {
|
|
||||||
return resizePos;
|
|
||||||
} else {
|
|
||||||
//checking both condition mobile and desktop view
|
|
||||||
if (isMobile) {
|
|
||||||
//if pos.isMobile false -- placeholder saved from desktop view then handle position in mobile view divided by scale
|
|
||||||
if (!pos.isMobile) {
|
|
||||||
return resizePos / scale;
|
|
||||||
}
|
|
||||||
//pos.isMobile true -- placeholder save from mobile view(small device) handle position in mobile view(small screen) view divided by scale
|
|
||||||
else {
|
|
||||||
return resizePos * (pos.scale / scale);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//else if pos.isMobile true -- placeholder saved from mobile or tablet view then handle position in desktop view divide by scale
|
|
||||||
|
|
||||||
if (pos.isMobile) {
|
|
||||||
return pos.scale && resizePos * pos.scale;
|
|
||||||
}
|
|
||||||
//else placeholder save from desktop(bigscreen) and show in desktop(bigscreen)
|
|
||||||
else {
|
|
||||||
return resizePos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const yPos = (pos, signYourself) => {
|
|
||||||
const resizePos = pos.yPosition;
|
|
||||||
if (signYourself) {
|
|
||||||
return resizePos;
|
|
||||||
} else {
|
|
||||||
//checking both condition mobile and desktop view
|
|
||||||
if (isMobile) {
|
|
||||||
//if pos.isMobile false -- placeholder saved from desktop view then handle position in mobile view divided by scale
|
|
||||||
if (!pos.isMobile) {
|
|
||||||
return resizePos / scale;
|
|
||||||
}
|
|
||||||
//pos.isMobile true -- placeholder save from mobile view(small device) handle position in mobile view(small screen) view divided by scale
|
|
||||||
else {
|
|
||||||
return resizePos * (pos.scale / scale);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//else if pos.isMobile true -- placeholder saved from mobile or tablet view then handle position in desktop view divide by scale
|
|
||||||
|
|
||||||
if (pos.isMobile) {
|
|
||||||
return pos.scale && resizePos * pos.scale;
|
|
||||||
}
|
|
||||||
//else placeholder save from desktop(bigscreen) and show in desktop(bigscreen)
|
|
||||||
else {
|
|
||||||
return resizePos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//function for render placeholder block over pdf document
|
|
||||||
const checkSignedSignes = (data) => {
|
|
||||||
const checkSign = signedSigners.filter(
|
|
||||||
(sign) => sign.objectId === data.signerObjId
|
|
||||||
);
|
|
||||||
if (data.signerObjId === signerObjectId) {
|
|
||||||
setCurrentSigner(true);
|
|
||||||
}
|
|
||||||
const handleAllUserName = (Id, Role, type) => {
|
|
||||||
return pdfDetails[0].Signers.map((signerData, key) => {
|
|
||||||
return (
|
|
||||||
signerData.objectId === data.signerObjId && (
|
|
||||||
<React.Fragment key={key}>
|
|
||||||
{signerData?.Name && (
|
|
||||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
|
||||||
{signerData.Name}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{type && (
|
|
||||||
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
|
|
||||||
)}
|
|
||||||
{Role && (
|
|
||||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
|
||||||
{`(${Role})`}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
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}
|
|
||||||
xPos={xPos}
|
|
||||||
yPos={yPos}
|
|
||||||
posWidth={posWidth}
|
|
||||||
posHeight={posHeight}
|
|
||||||
handleUserName={handleAllUserName}
|
|
||||||
isDragging={false}
|
|
||||||
pdfDetails={pdfDetails}
|
|
||||||
setIsInitial={setIsInitial}
|
|
||||||
setValidateAlert={setValidateAlert}
|
|
||||||
unSignedWidgetId={unSignedWidgetId}
|
|
||||||
setSelectWidgetId={setSelectWidgetId}
|
|
||||||
selectWidgetId={selectWidgetId}
|
|
||||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUserName = (Id, Role, type) => {
|
|
||||||
if (Id) {
|
|
||||||
const checkSign = signersdata.find((sign) => sign.Id === Id);
|
|
||||||
if (checkSign?.Name) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
|
||||||
{checkSign?.Name}
|
|
||||||
</div>
|
|
||||||
{type && (
|
|
||||||
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
|
|
||||||
)}
|
|
||||||
{Role && (
|
|
||||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
|
||||||
{`(${Role})`}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{type && (
|
|
||||||
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
|
|
||||||
)}
|
|
||||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
|
||||||
{Role}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{type && (
|
|
||||||
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
|
|
||||||
)}
|
|
||||||
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
|
|
||||||
{Role}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{successEmail && <Alert type={"success"}>Email sent successfully!</Alert>}
|
|
||||||
{isMobile && scale ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
border: "0.1px solid #ebe8e8",
|
|
||||||
marginTop: isGuestSigner && "30px"
|
|
||||||
}}
|
|
||||||
ref={drop}
|
|
||||||
id="container"
|
|
||||||
>
|
|
||||||
{pdfLoadFail.status &&
|
|
||||||
(pdfRequest
|
|
||||||
? signerPos.map((data, key) => {
|
|
||||||
return (
|
|
||||||
<React.Fragment key={key}>
|
|
||||||
{checkSignedSignes(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}
|
|
||||||
xPos={xPos}
|
|
||||||
yPos={yPos}
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
</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}
|
|
||||||
pdfOriginalWidth={pdfOriginalWidth}
|
|
||||||
containerWH={containerWH}
|
|
||||||
setIsSignPad={setIsSignPad}
|
|
||||||
isShowBorder={true}
|
|
||||||
isSignYourself={true}
|
|
||||||
xPos={xPos}
|
|
||||||
yPos={yPos}
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}))}
|
|
||||||
|
|
||||||
{/* this component for render pdf document is in middle of the component */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Document
|
|
||||||
onLoadError={() => {
|
|
||||||
setPdfLoadFail(true);
|
|
||||||
}}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{Array.from(new Array(numPages), (el, index) => (
|
|
||||||
<Page
|
|
||||||
key={index}
|
|
||||||
pageNumber={pageNumber}
|
|
||||||
width={containerWH.width}
|
|
||||||
height={containerWH.height}
|
|
||||||
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",
|
|
||||||
width:
|
|
||||||
pdfOriginalWidth > pdfNewWidth ? pdfNewWidth : pdfOriginalWidth,
|
|
||||||
height: window.innerHeight - 110 + "px"
|
|
||||||
}}
|
|
||||||
noScrollY={false}
|
|
||||||
noScrollX={pdfNewWidth < pdfOriginalWidth ? false : true}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
border: "0.1px solid #ebe8e8",
|
|
||||||
width: pdfOriginalWidth
|
|
||||||
}}
|
|
||||||
ref={drop}
|
|
||||||
id="container"
|
|
||||||
>
|
|
||||||
{pdfLoadFail.status &&
|
|
||||||
(pdfRequest
|
|
||||||
? signerPos.map((data, key) => {
|
|
||||||
return (
|
|
||||||
<React.Fragment key={key}>
|
|
||||||
{checkSignedSignes(data)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: placeholder
|
|
||||||
? 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}
|
|
||||||
xPos={xPos}
|
|
||||||
yPos={yPos}
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: xyPostion.map((data, ind) => {
|
|
||||||
return (
|
|
||||||
<React.Fragment key={ind}>
|
|
||||||
{data.pageNumber === pageNumber &&
|
|
||||||
data.pos.map((pos) => {
|
|
||||||
return (
|
|
||||||
pos && (
|
|
||||||
<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}
|
|
||||||
pdfOriginalWidth={pdfOriginalWidth}
|
|
||||||
containerWH={containerWH}
|
|
||||||
setIsSignPad={setIsSignPad}
|
|
||||||
isShowBorder={true}
|
|
||||||
isSignYourself={true}
|
|
||||||
xPos={xPos}
|
|
||||||
yPos={yPos}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}))}
|
|
||||||
|
|
||||||
{/* this component for render pdf document is in middle of the component */}
|
|
||||||
<Document
|
|
||||||
onLoadError={() => {
|
|
||||||
const load = {
|
|
||||||
status: false,
|
|
||||||
type: "failed"
|
|
||||||
};
|
|
||||||
setPdfLoadFail(load);
|
|
||||||
}}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{Array.from(new Array(numPages), (el, index) => (
|
|
||||||
<Page
|
|
||||||
key={index}
|
|
||||||
pageNumber={pageNumber}
|
|
||||||
renderAnnotationLayer={false}
|
|
||||||
renderTextLayer={false}
|
|
||||||
onGetAnnotationsError={(error) => {
|
|
||||||
console.log("annotation error", error);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Document>
|
|
||||||
</div>
|
|
||||||
</RSC>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RenderPdf;
|
|
||||||
@@ -0,0 +1,607 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import RSC from "react-scrollbars-custom";
|
||||||
|
import { Document, Page } from "react-pdf";
|
||||||
|
import {
|
||||||
|
defaultWidthHeight,
|
||||||
|
getContainerScale,
|
||||||
|
handleImageResize,
|
||||||
|
handleSignYourselfImageResize,
|
||||||
|
isMobile
|
||||||
|
} from "../../constant/Utils";
|
||||||
|
import Placeholder from "./Placeholder";
|
||||||
|
import Alert from "../../primitives/Alert";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
function RenderPdf(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [scaledHeight, setScaledHeight] = useState();
|
||||||
|
//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(
|
||||||
|
props.pdfOriginalWH,
|
||||||
|
props.pageNumber,
|
||||||
|
props.containerWH
|
||||||
|
);
|
||||||
|
const defaultWidth = defaultWidthHeight(pos.type).width;
|
||||||
|
const posWidth = pos.Width ? pos.Width : defaultWidth;
|
||||||
|
if (signYourself) {
|
||||||
|
return posWidth * props.scale * containerScale;
|
||||||
|
} else {
|
||||||
|
if (pos.isMobile && pos.scale) {
|
||||||
|
if (pos.IsResize) {
|
||||||
|
if (props.scale > 1) {
|
||||||
|
return posWidth * pos.scale * containerScale * props.scale;
|
||||||
|
} else {
|
||||||
|
return posWidth * containerScale;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.scale > 1) {
|
||||||
|
return posWidth * pos.scale * containerScale * props.scale;
|
||||||
|
} else {
|
||||||
|
return posWidth * pos.scale * containerScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return posWidth * props.scale * containerScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const posHeight = (pos, signYourself) => {
|
||||||
|
const containerScale = getContainerScale(
|
||||||
|
props.pdfOriginalWH,
|
||||||
|
props.pageNumber,
|
||||||
|
props.containerWH
|
||||||
|
);
|
||||||
|
const posHeight = pos.Height || defaultWidthHeight(pos.type).height;
|
||||||
|
if (signYourself) {
|
||||||
|
return posHeight * props.scale * containerScale;
|
||||||
|
} else {
|
||||||
|
if (pos.isMobile && pos.scale) {
|
||||||
|
if (pos.IsResize) {
|
||||||
|
if (props.scale > 1) {
|
||||||
|
return posHeight * pos.scale * containerScale * props.scale;
|
||||||
|
} else {
|
||||||
|
return posHeight * containerScale;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.scale > 1) {
|
||||||
|
return posHeight * pos.scale * containerScale * props.scale;
|
||||||
|
} else {
|
||||||
|
return posHeight * pos.scale * containerScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return posHeight * props.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 = props.signedSigners
|
||||||
|
? props.signedSigners?.filter(
|
||||||
|
(sign) =>
|
||||||
|
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
return (
|
||||||
|
checkSign.length === 0 &&
|
||||||
|
data?.placeHolder?.map((placeData, key) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={key}>
|
||||||
|
{placeData.pageNumber === props.pageNumber &&
|
||||||
|
placeData.pos.map((pos, ind) => {
|
||||||
|
return (
|
||||||
|
pos && (
|
||||||
|
<React.Fragment key={ind}>
|
||||||
|
<Placeholder
|
||||||
|
pos={pos}
|
||||||
|
handleSignYourselfImageResize={handleImageResize}
|
||||||
|
index={props.pageNumber}
|
||||||
|
xyPosition={props.signerPos}
|
||||||
|
setXyPosition={props.setSignerPos}
|
||||||
|
data={data}
|
||||||
|
setIsResize={props.setIsResize}
|
||||||
|
isShowBorder={props.isSelfSign}
|
||||||
|
isAlllowModify={props.isAlllowModify}
|
||||||
|
signerObjId={props.signerObjectId}
|
||||||
|
isShowDropdown={true}
|
||||||
|
isNeedSign={props.pdfRequest}
|
||||||
|
isSelfSign={true}
|
||||||
|
isSignYourself={false}
|
||||||
|
posWidth={posWidth}
|
||||||
|
posHeight={posHeight}
|
||||||
|
isDragging={props.isDragging}
|
||||||
|
pdfDetails={props.pdfDetails}
|
||||||
|
unSignedWidgetId={props.unSignedWidgetId}
|
||||||
|
setCurrWidgetsDetails={props.setCurrWidgetsDetails}
|
||||||
|
uniqueId={props.uniqueId}
|
||||||
|
scale={props.scale}
|
||||||
|
containerWH={props.containerWH}
|
||||||
|
pdfOriginalWH={props.pdfOriginalWH}
|
||||||
|
pageNumber={props.pageNumber}
|
||||||
|
ispublicTemplate={props.ispublicTemplate}
|
||||||
|
handleUserDetails={props.handleUserDetails}
|
||||||
|
isResize={props.isResize}
|
||||||
|
setIsAgreeTour={props.setIsAgreeTour}
|
||||||
|
isAgree={props.isAgree}
|
||||||
|
handleTabDrag={props.handleTabDrag}
|
||||||
|
handleStop={props.handleStop}
|
||||||
|
setUniqueId={props.setUniqueId}
|
||||||
|
setIsSelectId={props.setIsSelectId}
|
||||||
|
handleDeleteSign={props.handleDeleteSign}
|
||||||
|
setIsPageCopy={props.setIsPageCopy}
|
||||||
|
handleTextSettingModal={props.handleTextSettingModal}
|
||||||
|
setIsCheckbox={props.setIsCheckbox}
|
||||||
|
isFreeResize={false}
|
||||||
|
isOpenSignPad={true}
|
||||||
|
assignedWidgetId={props.assignedWidgetId}
|
||||||
|
isApplyAll={true}
|
||||||
|
setFontSize={props.setFontSize}
|
||||||
|
fontSize={props.fontSize}
|
||||||
|
fontColor={props.fontColor}
|
||||||
|
setFontColor={props.setFontColor}
|
||||||
|
setRequestSignTour={props.setRequestSignTour}
|
||||||
|
calculateFontsize={calculateFontsize}
|
||||||
|
currWidgetsDetails={props?.currWidgetsDetails}
|
||||||
|
setTempSignerId={props.setTempSignerId}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateFontsize = (pos) => {
|
||||||
|
const width = posWidth(pos);
|
||||||
|
const height = posHeight(pos);
|
||||||
|
|
||||||
|
if (height === width || height < width) {
|
||||||
|
return `${height / 5}px`;
|
||||||
|
} else if (width < height) {
|
||||||
|
return `${width / 10}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
|
||||||
|
//calculate render height of pdf in mobile view
|
||||||
|
const handlePageLoadSuccess = (page) => {
|
||||||
|
const containerWidth = props.divRef.current.offsetWidth; // Get container width
|
||||||
|
const viewport = page.getViewport({ scale: 1 });
|
||||||
|
const scale = containerWidth / viewport.width; // Scale to fit container width
|
||||||
|
const scaleHeight = viewport.height * scale;
|
||||||
|
setScaledHeight(scaleHeight);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.successEmail && (
|
||||||
|
<Alert type={"success"}>{t("success-email-alert")}</Alert>
|
||||||
|
)}
|
||||||
|
{isMobile ? (
|
||||||
|
<RSC
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||||
|
//49 is height of header
|
||||||
|
height: isGuestSigner ? window.innerHeight - 49 : scaledHeight,
|
||||||
|
zIndex: 0
|
||||||
|
}}
|
||||||
|
noScrollY={props.scale === 1 ? true : false}
|
||||||
|
noScrollX={props.scale === 1 ? true : false}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-tut="reactourForth"
|
||||||
|
className={`${
|
||||||
|
isGuestSigner ? "30px" : ""
|
||||||
|
} border-[0.1px] border-[#ebe8e8] overflow-x-auto`}
|
||||||
|
style={{
|
||||||
|
width:
|
||||||
|
props.containerWH?.width &&
|
||||||
|
props.containerWH?.width * props.scale
|
||||||
|
}}
|
||||||
|
ref={props.drop}
|
||||||
|
id="container"
|
||||||
|
>
|
||||||
|
{props.containerWH?.width &&
|
||||||
|
props.pdfOriginalWH.length > 0 &&
|
||||||
|
(props.pdfRequest || props.isSelfSign
|
||||||
|
? props.signerPos?.map((data, key) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={key}>
|
||||||
|
{checkSignedSigners(data)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: props.placeholder // placeholder mobile
|
||||||
|
? props.signerPos?.map((data, ind) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={ind}>
|
||||||
|
{data?.placeHolder &&
|
||||||
|
data?.placeHolder.map((placeData, index) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
{placeData.pageNumber === props.pageNumber &&
|
||||||
|
placeData.pos.map((pos) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={pos.key}>
|
||||||
|
<Placeholder
|
||||||
|
pos={pos}
|
||||||
|
setIsPageCopy={props.setIsPageCopy}
|
||||||
|
handleDeleteSign={
|
||||||
|
props.handleDeleteSign
|
||||||
|
}
|
||||||
|
handleTabDrag={props.handleTabDrag}
|
||||||
|
handleStop={props.handleStop}
|
||||||
|
handleSignYourselfImageResize={
|
||||||
|
handleImageResize
|
||||||
|
}
|
||||||
|
index={props.pageNumber}
|
||||||
|
xyPosition={props.signerPos}
|
||||||
|
setXyPosition={props.setSignerPos}
|
||||||
|
data={data}
|
||||||
|
setIsResize={props.setIsResize}
|
||||||
|
setShowDropdown={
|
||||||
|
props.setShowDropdown
|
||||||
|
}
|
||||||
|
isShowBorder={true}
|
||||||
|
isPlaceholder={true}
|
||||||
|
setUniqueId={props.setUniqueId}
|
||||||
|
handleLinkUser={
|
||||||
|
props.handleLinkUser
|
||||||
|
}
|
||||||
|
isSignYourself={false}
|
||||||
|
posWidth={posWidth}
|
||||||
|
posHeight={posHeight}
|
||||||
|
isDragging={props.isDragging}
|
||||||
|
setIsValidate={props.setIsValidate}
|
||||||
|
setIsRadio={props.setIsRadio}
|
||||||
|
setIsCheckbox={props.setIsCheckbox}
|
||||||
|
setCurrWidgetsDetails={
|
||||||
|
props.setCurrWidgetsDetails
|
||||||
|
}
|
||||||
|
handleNameModal={
|
||||||
|
props.handleNameModal
|
||||||
|
}
|
||||||
|
setTempSignerId={
|
||||||
|
props.setTempSignerId
|
||||||
|
}
|
||||||
|
uniqueId={props.uniqueId}
|
||||||
|
handleTextSettingModal={
|
||||||
|
props.handleTextSettingModal
|
||||||
|
}
|
||||||
|
scale={props.scale}
|
||||||
|
containerWH={props.containerWH}
|
||||||
|
pdfOriginalWH={props.pdfOriginalWH}
|
||||||
|
pageNumber={props.pageNumber}
|
||||||
|
setIsSelectId={props.setIsSelectId}
|
||||||
|
fontSize={props.fontSize}
|
||||||
|
setFontSize={props.setFontSize}
|
||||||
|
fontColor={props.fontColor}
|
||||||
|
setFontColor={props.setFontColor}
|
||||||
|
isResize={props.isResize}
|
||||||
|
unSignedWidgetId={
|
||||||
|
props.unSignedWidgetId
|
||||||
|
}
|
||||||
|
isFreeResize={true}
|
||||||
|
calculateFontsize={
|
||||||
|
calculateFontsize
|
||||||
|
}
|
||||||
|
currWidgetsDetails={
|
||||||
|
props?.currWidgetsDetails
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: !props.pdfDetails?.[0]?.IsCompleted &&
|
||||||
|
props.xyPosition?.map((data, ind) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={ind}>
|
||||||
|
{data.pageNumber === props.pageNumber &&
|
||||||
|
data.pos.map((pos, id) => {
|
||||||
|
return (
|
||||||
|
pos && (
|
||||||
|
<Placeholder
|
||||||
|
key={id}
|
||||||
|
pos={pos}
|
||||||
|
setIsPageCopy={props.setIsPageCopy}
|
||||||
|
handleDeleteSign={props.handleDeleteSign}
|
||||||
|
handleTabDrag={props.handleTabDrag}
|
||||||
|
handleStop={props.handleStop}
|
||||||
|
handleSignYourselfImageResize={
|
||||||
|
handleSignYourselfImageResize
|
||||||
|
}
|
||||||
|
index={props.index}
|
||||||
|
xyPosition={props.xyPosition}
|
||||||
|
setXyPosition={props.setXyPosition}
|
||||||
|
containerWH={props.containerWH}
|
||||||
|
isShowBorder={true}
|
||||||
|
isSignYourself={true}
|
||||||
|
posWidth={posWidth}
|
||||||
|
posHeight={posHeight}
|
||||||
|
pdfDetails={props.pdfDetails[0]}
|
||||||
|
isDragging={props.isDragging}
|
||||||
|
setIsCheckbox={props.setIsCheckbox}
|
||||||
|
setCurrWidgetsDetails={
|
||||||
|
props.setCurrWidgetsDetails
|
||||||
|
}
|
||||||
|
handleTextSettingModal={
|
||||||
|
props.handleTextSettingModal
|
||||||
|
}
|
||||||
|
scale={props.scale}
|
||||||
|
pdfOriginalWH={props.pdfOriginalWH}
|
||||||
|
pageNumber={props.pageNumber}
|
||||||
|
fontSize={props.fontSize}
|
||||||
|
setFontSize={props.setFontSize}
|
||||||
|
fontColor={props.fontColor}
|
||||||
|
setFontColor={props.setFontColor}
|
||||||
|
isResize={props.isResize}
|
||||||
|
setIsResize={props.setIsResize}
|
||||||
|
isFreeResize={false}
|
||||||
|
isOpenSignPad={true}
|
||||||
|
calculateFontsize={calculateFontsize}
|
||||||
|
currWidgetsDetails={
|
||||||
|
props?.currWidgetsDetails
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}))}
|
||||||
|
{/* Mobile */}
|
||||||
|
<Document
|
||||||
|
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||||
|
onLoadError={() => props.setPdfLoad(false)}
|
||||||
|
loading={t("loading-doc")}
|
||||||
|
onLoadSuccess={props.pageDetails}
|
||||||
|
onClick={() =>
|
||||||
|
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||||
|
}
|
||||||
|
file={pdfDataBase64}
|
||||||
|
>
|
||||||
|
<Page
|
||||||
|
onLoadSuccess={handlePageLoadSuccess}
|
||||||
|
scale={props.scale || 1}
|
||||||
|
key={props.index}
|
||||||
|
pageNumber={props.pageNumber}
|
||||||
|
width={props.containerWH.width}
|
||||||
|
renderAnnotationLayer={false}
|
||||||
|
renderTextLayer={false}
|
||||||
|
onGetAnnotationsError={(error) => {
|
||||||
|
console.log("annotation error", error);
|
||||||
|
}}
|
||||||
|
className="select-none touch-callout-none"
|
||||||
|
/>
|
||||||
|
</Document>
|
||||||
|
</div>
|
||||||
|
</RSC>
|
||||||
|
) : (
|
||||||
|
<RSC
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||||
|
height: window.innerHeight + "px",
|
||||||
|
zIndex: 0
|
||||||
|
}}
|
||||||
|
noScrollY={false}
|
||||||
|
noScrollX={props.scale === 1 ? true : false}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width:
|
||||||
|
props.containerWH?.width &&
|
||||||
|
props.containerWH?.width * props.scale
|
||||||
|
}}
|
||||||
|
ref={props.drop}
|
||||||
|
id="container"
|
||||||
|
>
|
||||||
|
{props.pdfLoad &&
|
||||||
|
props.containerWH?.width &&
|
||||||
|
props.pdfOriginalWH.length > 0 &&
|
||||||
|
(props.pdfRequest || props.isSelfSign //pdf request sign flow
|
||||||
|
? props.signerPos?.map((data, key) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={key}>
|
||||||
|
{checkSignedSigners(data)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: props.placeholder //placeholder and template flow
|
||||||
|
? props.signerPos.map((data, ind) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={ind}>
|
||||||
|
{data?.placeHolder &&
|
||||||
|
data?.placeHolder.map((placeData, index) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
{placeData.pageNumber === props.pageNumber &&
|
||||||
|
placeData.pos.map((pos) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={pos.key}>
|
||||||
|
<Placeholder
|
||||||
|
pos={pos}
|
||||||
|
setIsPageCopy={props.setIsPageCopy}
|
||||||
|
handleDeleteSign={
|
||||||
|
props.handleDeleteSign
|
||||||
|
}
|
||||||
|
handleTabDrag={props.handleTabDrag}
|
||||||
|
handleStop={props.handleStop}
|
||||||
|
handleSignYourselfImageResize={
|
||||||
|
handleImageResize
|
||||||
|
}
|
||||||
|
index={props.pageNumber}
|
||||||
|
xyPosition={props.signerPos}
|
||||||
|
setXyPosition={props.setSignerPos}
|
||||||
|
data={data}
|
||||||
|
setIsResize={props.setIsResize}
|
||||||
|
setShowDropdown={
|
||||||
|
props.setShowDropdown
|
||||||
|
}
|
||||||
|
isShowBorder={true}
|
||||||
|
isPlaceholder={true}
|
||||||
|
setUniqueId={props.setUniqueId}
|
||||||
|
handleLinkUser={
|
||||||
|
props.handleLinkUser
|
||||||
|
}
|
||||||
|
isSignYourself={false}
|
||||||
|
posWidth={posWidth}
|
||||||
|
posHeight={posHeight}
|
||||||
|
isDragging={props.isDragging}
|
||||||
|
setIsValidate={props.setIsValidate}
|
||||||
|
setIsRadio={props.setIsRadio}
|
||||||
|
setIsCheckbox={props.setIsCheckbox}
|
||||||
|
setCurrWidgetsDetails={
|
||||||
|
props.setCurrWidgetsDetails
|
||||||
|
}
|
||||||
|
handleNameModal={
|
||||||
|
props.handleNameModal
|
||||||
|
}
|
||||||
|
setTempSignerId={
|
||||||
|
props.setTempSignerId
|
||||||
|
}
|
||||||
|
uniqueId={props.uniqueId}
|
||||||
|
handleTextSettingModal={
|
||||||
|
props.handleTextSettingModal
|
||||||
|
}
|
||||||
|
scale={props.scale}
|
||||||
|
containerWH={props.containerWH}
|
||||||
|
pdfOriginalWH={props.pdfOriginalWH}
|
||||||
|
pageNumber={props.pageNumber}
|
||||||
|
setIsSelectId={props.setIsSelectId}
|
||||||
|
fontSize={props.fontSize}
|
||||||
|
setFontSize={props.setFontSize}
|
||||||
|
fontColor={props.fontColor}
|
||||||
|
setFontColor={props.setFontColor}
|
||||||
|
isResize={props.isResize}
|
||||||
|
unSignedWidgetId={
|
||||||
|
props.unSignedWidgetId
|
||||||
|
}
|
||||||
|
isFreeResize={true}
|
||||||
|
calculateFontsize={
|
||||||
|
calculateFontsize
|
||||||
|
}
|
||||||
|
currWidgetsDetails={
|
||||||
|
props?.currWidgetsDetails
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: !props.pdfDetails?.[0]?.IsCompleted &&
|
||||||
|
props.xyPosition?.map((data, ind) => {
|
||||||
|
// signyourself flow
|
||||||
|
return (
|
||||||
|
<React.Fragment key={ind}>
|
||||||
|
{data.pageNumber === props.pageNumber &&
|
||||||
|
data.pos.map((pos) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={pos.key}>
|
||||||
|
<Placeholder
|
||||||
|
pos={pos}
|
||||||
|
setIsPageCopy={props.setIsPageCopy}
|
||||||
|
handleDeleteSign={props.handleDeleteSign}
|
||||||
|
handleTabDrag={props.handleTabDrag}
|
||||||
|
handleStop={(event, dragElement) =>
|
||||||
|
props.handleStop(
|
||||||
|
event,
|
||||||
|
dragElement,
|
||||||
|
pos.type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
handleSignYourselfImageResize={
|
||||||
|
handleSignYourselfImageResize
|
||||||
|
}
|
||||||
|
index={props.index}
|
||||||
|
xyPosition={props.xyPosition}
|
||||||
|
setXyPosition={props.setXyPosition}
|
||||||
|
isShowBorder={true}
|
||||||
|
isSignYourself={true}
|
||||||
|
posWidth={posWidth}
|
||||||
|
posHeight={posHeight}
|
||||||
|
pdfDetails={props.pdfDetails[0]}
|
||||||
|
isDragging={props.isDragging}
|
||||||
|
setIsCheckbox={props.setIsCheckbox}
|
||||||
|
setCurrWidgetsDetails={
|
||||||
|
props.setCurrWidgetsDetails
|
||||||
|
}
|
||||||
|
handleTextSettingModal={
|
||||||
|
props.handleTextSettingModal
|
||||||
|
}
|
||||||
|
scale={props.scale}
|
||||||
|
containerWH={props.containerWH}
|
||||||
|
pdfOriginalWH={props.pdfOriginalWH}
|
||||||
|
pageNumber={props.pageNumber}
|
||||||
|
fontSize={props.fontSize}
|
||||||
|
setFontSize={props.setFontSize}
|
||||||
|
fontColor={props.fontColor}
|
||||||
|
setFontColor={props.setFontColor}
|
||||||
|
isResize={props.isResize}
|
||||||
|
setIsResize={props.setIsResize}
|
||||||
|
isFreeResize={false}
|
||||||
|
isOpenSignPad={true}
|
||||||
|
calculateFontsize={calculateFontsize}
|
||||||
|
currWidgetsDetails={
|
||||||
|
props?.currWidgetsDetails
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}))}
|
||||||
|
{/* large device */}
|
||||||
|
{/* this component for render pdf document is in middle of the component */}
|
||||||
|
<Document
|
||||||
|
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||||
|
onLoadError={() => props.setPdfLoad(false)}
|
||||||
|
loading={t("loading-doc")}
|
||||||
|
onLoadSuccess={props.pageDetails}
|
||||||
|
onClick={() =>
|
||||||
|
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||||
|
}
|
||||||
|
file={pdfDataBase64}
|
||||||
|
>
|
||||||
|
<Page
|
||||||
|
key={props.index}
|
||||||
|
width={props.containerWH.width}
|
||||||
|
scale={props.scale || 1}
|
||||||
|
className={"-z-[1]"} // when user zoom-in in tablet widgets move backward that's why pass -z-[1]
|
||||||
|
pageNumber={props.pageNumber}
|
||||||
|
renderAnnotationLayer={false}
|
||||||
|
renderTextLayer={false}
|
||||||
|
onGetAnnotationsError={(error) => {
|
||||||
|
console.log("annotation error", error);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Document>
|
||||||
|
</div>
|
||||||
|
</RSC>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RenderPdf;
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import i18next from "i18next";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
function SelectLanguage(props) {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
const languages = [
|
||||||
|
{ value: "en", text: "English" }, //english
|
||||||
|
{ value: "es", text: "Española" }, //spanish
|
||||||
|
{ value: "fr", text: "Français" }, //french
|
||||||
|
{ value: "it", text: "Italiano" }, //italian
|
||||||
|
{ value: "de", text: "Deutsch" }, //german
|
||||||
|
{ value: "hi", text: "हिन्दी" } //hindi
|
||||||
|
];
|
||||||
|
const defaultLanguage = i18next.language || "en";
|
||||||
|
const [lang, setLang] = useState(defaultLanguage);
|
||||||
|
// This function put query that helps to change the language
|
||||||
|
const handleChangeLang = (e) => {
|
||||||
|
setLang(e.target.value);
|
||||||
|
i18n.changeLanguage(e.target.value);
|
||||||
|
props?.updateExtUser && props.updateExtUser({ language: e.target.value });
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
!props.isProfile && " mt-[9px] pb-2 md:pb-0 "
|
||||||
|
} flex justify-center items-center `}
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
value={lang}
|
||||||
|
onChange={handleChangeLang}
|
||||||
|
className={`${
|
||||||
|
!props.isProfile ? " md:w-[15%] w-[50%]" : "w-[180px]"
|
||||||
|
} op-select op-select-bordered bg-white op-select-sm `}
|
||||||
|
>
|
||||||
|
<option disabled>select</option>
|
||||||
|
{languages.map((item) => {
|
||||||
|
return (
|
||||||
|
<option key={item.value} value={item.value}>
|
||||||
|
{item.text}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectLanguage;
|
||||||
@@ -1,711 +0,0 @@
|
|||||||
import React, { useRef, useState, useEffect } from "react";
|
|
||||||
import SignatureCanvas from "react-signature-canvas";
|
|
||||||
import { themeColor } from "../../constant/const";
|
|
||||||
|
|
||||||
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 = () => {
|
|
||||||
setSignature(canvasRef.current.toDataURL());
|
|
||||||
setIsSignImg(canvasRef.current.toDataURL());
|
|
||||||
};
|
|
||||||
|
|
||||||
//save button component
|
|
||||||
const SaveBtn = () => {
|
|
||||||
return (
|
|
||||||
<div style={{ marginTop: "2px" }}>
|
|
||||||
{(isTab === "draw" || isTab === "uploadImage") && (
|
|
||||||
<button
|
|
||||||
style={{
|
|
||||||
color: "black",
|
|
||||||
border: "1px solid #ccc"
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
className="finishBtn saveBtn"
|
|
||||||
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, textWidth, textHeight);
|
|
||||||
} else {
|
|
||||||
onSaveSign(signatureType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPenColor("blue");
|
|
||||||
} else {
|
|
||||||
setIsSignImg("");
|
|
||||||
onSaveImage(signatureType);
|
|
||||||
}
|
|
||||||
setIsSignPad(false);
|
|
||||||
setIsInitial(false);
|
|
||||||
setIsImageSelect(false);
|
|
||||||
setIsDefaultSign(false);
|
|
||||||
setImage();
|
|
||||||
setIsTab("draw");
|
|
||||||
setSignValue("");
|
|
||||||
setIsStamp(false);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
background: themeColor,
|
|
||||||
color: "white"
|
|
||||||
}}
|
|
||||||
disabled={
|
|
||||||
isSignImg || image || isDefaultSign || textWidth ? false : true
|
|
||||||
}
|
|
||||||
type="button"
|
|
||||||
className={
|
|
||||||
isSignImg || image || isDefaultSign || textWidth
|
|
||||||
? "finishBtn saveBtn"
|
|
||||||
: "disabledFinish saveBtn"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
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.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";
|
|
||||||
|
|
||||||
//creating span for getting text content width
|
|
||||||
const span = document.createElement("span");
|
|
||||||
span.textContent = textContent;
|
|
||||||
span.style.font = `20px ${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 width = span.offsetWidth;
|
|
||||||
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 style={{ display: "flex", flexDirection: "row" }}>
|
|
||||||
{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 solid fa-pen-nib"
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
></i>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{isSignPad && (
|
|
||||||
<div className="modaloverlay">
|
|
||||||
<div className="modalcontainer">
|
|
||||||
<div
|
|
||||||
className="modalheader"
|
|
||||||
style={{ padding: "0 13px", marginTop: "5px" }}
|
|
||||||
>
|
|
||||||
<div className="modaltitle">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
background: "white",
|
|
||||||
marginTop: "3px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-around",
|
|
||||||
alignItems: "end",
|
|
||||||
gap: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isStamp ? (
|
|
||||||
<span style={{ color: themeColor }} className="signTab">
|
|
||||||
{widgetType === "image"
|
|
||||||
? "Upload image"
|
|
||||||
: "Upload stamp image"}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
onClick={() => {
|
|
||||||
setIsDefaultSign(false);
|
|
||||||
setIsImageSelect(false);
|
|
||||||
setIsTab("draw");
|
|
||||||
setImage();
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
color: isTab === "draw" ? themeColor : "#515252",
|
|
||||||
marginLeft: "2px"
|
|
||||||
}}
|
|
||||||
className="signTab"
|
|
||||||
>
|
|
||||||
Draw
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
border:
|
|
||||||
isTab === "draw"
|
|
||||||
? "1.5px solid #108783"
|
|
||||||
: "1.5px solid #ffffff"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
onClick={() => {
|
|
||||||
setIsDefaultSign(false);
|
|
||||||
setIsImageSelect(true);
|
|
||||||
setIsTab("uploadImage");
|
|
||||||
setSignatureType("");
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
color:
|
|
||||||
isTab === "uploadImage" ? themeColor : "#515252"
|
|
||||||
}}
|
|
||||||
className="signTab"
|
|
||||||
>
|
|
||||||
Upload Image
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
border:
|
|
||||||
isTab === "uploadImage"
|
|
||||||
? "1.5px solid #108783"
|
|
||||||
: "1.5px solid #ffffff"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
onClick={() => {
|
|
||||||
setIsDefaultSign(false);
|
|
||||||
setIsImageSelect(false);
|
|
||||||
setIsTab("type");
|
|
||||||
setSignatureType("");
|
|
||||||
setImage();
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
color: isTab === "type" ? themeColor : "#515252",
|
|
||||||
marginLeft: "2px"
|
|
||||||
}}
|
|
||||||
className="signTab"
|
|
||||||
>
|
|
||||||
Type
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
border:
|
|
||||||
isTab === "type"
|
|
||||||
? "1.5px solid #108783"
|
|
||||||
: "1.5px solid #ffffff"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
{!isInitial && defaultSign ? (
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
onClick={() => {
|
|
||||||
setIsDefaultSign(true);
|
|
||||||
setIsImageSelect(true);
|
|
||||||
setIsTab("mysignature");
|
|
||||||
setSignatureType("");
|
|
||||||
setImage();
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
color:
|
|
||||||
isTab === "mysignature"
|
|
||||||
? themeColor
|
|
||||||
: "#515252"
|
|
||||||
}}
|
|
||||||
className="signTab"
|
|
||||||
>
|
|
||||||
My Signature
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
border:
|
|
||||||
isTab === "mysignature"
|
|
||||||
? "1.5px solid #108783"
|
|
||||||
: "1.5px solid #ffffff"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
isInitial &&
|
|
||||||
myInitial && (
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
onClick={() => {
|
|
||||||
setIsDefaultSign(true);
|
|
||||||
setIsImageSelect(true);
|
|
||||||
setIsTab("mysignature");
|
|
||||||
setSignatureType("");
|
|
||||||
setImage();
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
color:
|
|
||||||
isTab === "mysignature"
|
|
||||||
? themeColor
|
|
||||||
: "#515252"
|
|
||||||
}}
|
|
||||||
className="signTab"
|
|
||||||
>
|
|
||||||
My Initials
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
border:
|
|
||||||
isTab === "mysignature"
|
|
||||||
? "1.5px solid #108783"
|
|
||||||
: "1.5px solid #ffffff"
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="closebtn"
|
|
||||||
style={{ color: "black" }}
|
|
||||||
onClick={() => {
|
|
||||||
setPenColor("blue");
|
|
||||||
setIsSignPad(false);
|
|
||||||
setIsInitial(false);
|
|
||||||
setIsImageSelect(false);
|
|
||||||
setIsDefaultSign(false);
|
|
||||||
setImage();
|
|
||||||
setIsTab("draw");
|
|
||||||
setSignatureType("draw");
|
|
||||||
setSignValue("");
|
|
||||||
setIsStamp(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ height: "100%", padding: 20 }}>
|
|
||||||
{isDefaultSign ? (
|
|
||||||
<>
|
|
||||||
<div style={{ display: "flex", justifyContent: "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"
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
background: "rgb(255, 255, 255)",
|
|
||||||
objectFit: "contain"
|
|
||||||
}}
|
|
||||||
draggable="false"
|
|
||||||
src={isInitial ? myInitial : defaultSign}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
|
||||||
<SaveBtn />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : isImageSelect || isStamp ? (
|
|
||||||
!image ? (
|
|
||||||
<div style={{ display: "flex", justifyContent: "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="fas fa-cloud-upload-alt uploadImgLogo"></i>
|
|
||||||
<div className="uploadImg">Upload</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div style={{ display: "flex", justifyContent: "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"
|
|
||||||
style={{
|
|
||||||
objectFit: "contain",
|
|
||||||
height: "100%",
|
|
||||||
width: "100%"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{ display: "flex", justifyContent: "flex-end" }}
|
|
||||||
>
|
|
||||||
<SaveBtn />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
) : isTab === "type" ? (
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="signatureText">
|
|
||||||
{isInitial ? "Initials" : "Signature"}:
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
maxLength={isInitial ? 3 : 30}
|
|
||||||
style={{ fontFamily: fontSelect, color: penColor }}
|
|
||||||
type="text"
|
|
||||||
className="signatureInput"
|
|
||||||
placeholder="Your signature"
|
|
||||||
value={signValue}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSignValue(e.target.value);
|
|
||||||
convertToImg(fontSelect, e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="fontOptionContainer">
|
|
||||||
{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
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
marginTop: "10px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PenColorComponent convertToImg={convertToImg} />
|
|
||||||
<SaveBtn />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div style={{ display: "flex", justifyContent: "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
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
marginTop: "10px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PenColorComponent />
|
|
||||||
<SaveBtn />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SignPad;
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { themeColor } from "../../constant/const";
|
|
||||||
import "../../styles/signature.css";
|
|
||||||
function Signedby({ pdfDetails }) {
|
|
||||||
const getFirstLetter = (name) => {
|
|
||||||
const firstLetter = name.charAt(0);
|
|
||||||
return firstLetter;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="signerComponent">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
background: themeColor,
|
|
||||||
color: "white"
|
|
||||||
}}
|
|
||||||
className="signedStyle"
|
|
||||||
>
|
|
||||||
Signed By
|
|
||||||
</div>
|
|
||||||
<div style={{ marginTop: "2px", background: "white" }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
padding: "10px",
|
|
||||||
background: "#93a3db"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="signerStyle"
|
|
||||||
style={{
|
|
||||||
background: "#abd1d0",
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
display: "flex",
|
|
||||||
borderRadius: 30 / 2,
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
marginRight: "20px",
|
|
||||||
marginTop: "5px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: "8px",
|
|
||||||
textAlign: "center",
|
|
||||||
fontWeight: "bold"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getFirstLetter(pdfDetails.ExtUserPtr.Name)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
||||||
<span className="userName">{pdfDetails.ExtUserPtr.Name}</span>
|
|
||||||
<span className="useEmail">{pdfDetails.ExtUserPtr.Email}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Signedby;
|
|
||||||