Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b5c85fd5f | ||
|
|
123425d7d1 | ||
|
|
a5b9d8178c | ||
|
|
aefd2ff933 | ||
|
|
fe93be8be7 | ||
|
|
369e3660fc | ||
|
|
378c0f057b | ||
|
|
4f3c034cec | ||
|
|
e7d3453744 | ||
|
|
47cc0ef580 | ||
|
|
04f71be6a2 | ||
|
|
07603f4bc2 | ||
|
|
ea16180551 | ||
|
|
75ad566d54 | ||
|
|
fda9fb534b | ||
|
|
cb3e31ebbf | ||
|
|
400525debb | ||
|
|
1cc017bb05 | ||
|
|
531dd11185 | ||
|
|
1e80754750 | ||
|
|
0d286938c7 | ||
|
|
360c13df25 | ||
|
|
71b9bd6367 | ||
|
|
4a5b35e147 | ||
|
|
7930a8f439 | ||
|
|
7204183542 | ||
|
|
5561673fdc | ||
|
|
0df44062d6 | ||
|
|
8983440763 | ||
|
|
5a28c0ed06 | ||
|
|
cbca22c308 | ||
|
|
b0e0b38e8f | ||
|
|
49c23fb52b | ||
|
|
da4b604710 | ||
|
|
82d518b3c9 | ||
|
|
dcbde2b661 | ||
|
|
27b0b426ad | ||
|
|
8788afaa8e | ||
|
|
428aa65b38 | ||
|
|
295d942427 | ||
|
|
c4e28e6e6e | ||
|
|
b19fb29b82 | ||
|
|
3d223ba343 | ||
|
|
98754c776f | ||
|
|
1550e3a3d0 | ||
|
|
3500883205 | ||
|
|
d888897b62 | ||
|
|
8f2433fbea | ||
|
|
b671dbac19 | ||
|
|
79cdbc9c8c | ||
|
|
e585b7c943 | ||
|
|
5336e2cb7c | ||
|
|
72401291f8 | ||
|
|
7b34314b9e | ||
|
|
b6f1cceeca | ||
|
|
c422cdac06 | ||
|
|
ab08d2615d | ||
|
|
2b0c7488d9 | ||
|
|
6927540741 | ||
|
|
1ceeee36ac | ||
|
|
c4779c14c1 | ||
|
|
e48869cb2f | ||
|
|
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 | ||
|
|
af0d780ac0 | ||
|
|
45c2c863ef | ||
|
|
3372e16409 | ||
|
|
a04fad9c1f | ||
|
|
a8a89dc9bd | ||
|
|
a0305a745a | ||
|
|
f06b702360 | ||
|
|
6f104c4ee6 | ||
|
|
c721580516 | ||
|
|
1fe1381fe2 | ||
|
|
870a25dad1 | ||
|
|
e4e5d5c6d7 | ||
|
|
7e45f23b6c | ||
|
|
719f61f612 | ||
|
|
908edda14b |
@@ -232,6 +232,15 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "1024mb",
|
||||
"name": "1024mb",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9301204?v=4",
|
||||
"profile": "https://github.com/1024mb",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,18 +3,16 @@
|
||||
# Note that any data in staging will get cleared from time to time and you may have to sign up again and again
|
||||
|
||||
# Set it to the URL form where the app home page will be accessed
|
||||
PUBLIC_URL=http://localhost:3000
|
||||
PUBLIC_URL=https://localhost:3001
|
||||
# Set it to true if you want to generate the Sourcemap for debugging
|
||||
GENERATE_SOURCEMAP=false
|
||||
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
|
||||
# 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
|
||||
|
||||
|
||||
# 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.
|
||||
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.
|
||||
@@ -111,3 +109,6 @@ CRUxFgQUDYlgGVxSxuOknhQc256x3++7BDwwMTAhMAkGBSsOAwIaBQAEFFjASdYl
|
||||
|
||||
# Provide Pass pharse of above PFX or p12 document
|
||||
PASS_PHRASE=opensign
|
||||
|
||||
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
|
||||
APP_ID=opensign
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
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:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'staging'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
|
||||
@@ -77,6 +77,6 @@ Engage with the community on our [Gitter Channel](https://gitter.im/OpenSignLabs
|
||||
|
||||
## 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).
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{$HOST_URL} {
|
||||
reverse_proxy client:3000
|
||||
handle_path /app/* {
|
||||
handle_path /api/* {
|
||||
reverse_proxy server:8080
|
||||
rewrite * /app{uri}
|
||||
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
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
|
||||
@@ -73,11 +73,27 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
|
||||
|
||||
---
|
||||
|
||||
### Installation
|
||||
### Deploy
|
||||
|
||||
Note: The default MongoDB instance used in deployment is not persistant and will be cleared on every restart. To retain your data, configure and supply your own MongoDB connection URL.
|
||||
|
||||
#### DigitalOcean
|
||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0)
|
||||
|
||||
#### Docker
|
||||
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
|
||||
|
||||
**Command for linux/MacOS**
|
||||
```
|
||||
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
|
||||
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
|
||||
```
|
||||
**Command for Windows (Powershell)**
|
||||
```
|
||||
$env:HOST_URL="https://opensign.yourdomain.com"; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml -OutFile docker-compose.yml; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile -OutFile Caddyfile; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev -OutFile .env.local_dev; Rename-Item -Path .env.local_dev -NewName .env.prod; docker compose up --force-recreate
|
||||
```
|
||||
**Command for Windows (CMD/Terminal)**
|
||||
```
|
||||
set HOST_URL=https://opensign.yourdomain.com && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && rename .env.local_dev .env.prod && docker compose up --force-recreate
|
||||
```
|
||||
Make sure that you have `Docker` and `git` installed before you run this command -
|
||||
|
||||
@@ -147,7 +163,8 @@ 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/VikramNagwal"><img src="https://avatars.githubusercontent.com/u/123088024?v=4?s=100" width="100px;" alt="Vikram"/><br /><sub><b>Vikram</b></sub></a><br /><a href="#code-VikramNagwal" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ugoconsonni"><img src="https://avatars.githubusercontent.com/u/13661702?v=4?s=100" width="100px;" alt="ugoconsonni"/><br /><sub><b>ugoconsonni</b></sub></a><br /><a href="#code-ugoconsonni" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daniel-mutwiri"><img src="https://avatars.githubusercontent.com/u/8936960?v=4?s=100" width="100px;" alt="Daniel Mutwiri"/><br /><sub><b>Daniel Mutwiri</b></sub></a><br /><a href="#code-daniel-mutwiri" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zathiel"><img src="https://avatars.githubusercontent.com/u/26553418?v=4?s=100" width="100px;" alt="Zathiel"/><br /><sub><b>Zathiel</b></sub></a><br /><a href="#code-Zathiel" title="Code">💻</a><a href="#security-Zathiel" title="Security">🛡️</a></td>
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -157,3 +174,5 @@ We would like to thank all our contributors and users for their support and feed
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project is tested with BrowserStack.
|
||||
|
||||
|
||||
@@ -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
|
||||
FROM node:18
|
||||
FROM node:22.14.0
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
@@ -13,15 +13,24 @@ RUN npm install
|
||||
# Copy the current directory contents into the container
|
||||
COPY apps/OpenSign/ .
|
||||
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
|
||||
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
|
||||
EXPOSE 3000
|
||||
|
||||
# Define environment variables if needed
|
||||
# ENV NODE_ENV production
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
# Run the application
|
||||
CMD ["npm", "start"]
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
# Tailwind Dark Mode Usage Guide
|
||||
|
||||
## Overview
|
||||
This guide shows how to use the new Tailwind utilities for better dark mode visibility in OpenSign.
|
||||
|
||||
## Button Styling
|
||||
|
||||
### VS Code-style Disabled Buttons
|
||||
```jsx
|
||||
// Option 1: Direct VS Code styling
|
||||
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
|
||||
Disabled Button
|
||||
</button>
|
||||
|
||||
// Option 2: Themed disabled styling
|
||||
<button className="op-btn btn-themed-disabled">
|
||||
Themed Button
|
||||
</button>
|
||||
|
||||
// Option 3: Conditional styling
|
||||
<button
|
||||
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Dynamic Button
|
||||
</button>
|
||||
```
|
||||
|
||||
## Icon Styling
|
||||
|
||||
### Theme-aware Icons
|
||||
```jsx
|
||||
// Better visibility in dark mode
|
||||
<i className="fa-light fa-folder icon-improved"></i>
|
||||
|
||||
// Muted but still visible
|
||||
<i className="fa-light fa-plus icon-muted"></i>
|
||||
|
||||
// Disabled state
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
```
|
||||
|
||||
### CSS Variable Approach
|
||||
```jsx
|
||||
// Using CSS variables
|
||||
<i className="fa-light fa-search icon-themed"></i>
|
||||
<i className="fa-light fa-settings icon-themed-muted"></i>
|
||||
|
||||
// Inline styles with CSS variables
|
||||
<i
|
||||
className="fa-light fa-plus"
|
||||
style={{ color: 'var(--icon-color)' }}
|
||||
/>
|
||||
```
|
||||
|
||||
### Legacy JavaScript Function (Still Supported)
|
||||
```jsx
|
||||
// Existing approach - still works
|
||||
<i
|
||||
className="fa-light fa-plus"
|
||||
style={{ color: getThemeIconColor() }}
|
||||
/>
|
||||
```
|
||||
|
||||
## Text Styling
|
||||
|
||||
### Improved Gray Text
|
||||
```jsx
|
||||
// These automatically improve in dark mode
|
||||
<span className="text-gray-500">More visible in dark mode</span>
|
||||
<span className="text-gray-400">Muted but readable</span>
|
||||
<span className="text-gray-600">Clear text</span>
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Toolbar with Better Visibility
|
||||
```jsx
|
||||
const Toolbar = () => (
|
||||
<div className="flex space-x-2 p-2">
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-plus icon-improved"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded" disabled>
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-edit icon-improved"></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
### Form with Disabled States
|
||||
```jsx
|
||||
const Form = ({ isSubmitting }) => (
|
||||
<form>
|
||||
<input className="op-input" />
|
||||
<button
|
||||
className={`op-btn op-btn-primary ${isSubmitting ? 'op-btn-vscode-disabled' : ''}`}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
```
|
||||
|
||||
## React-Tour and Tooltip Dark Mode Support
|
||||
|
||||
### React-Tour Modals
|
||||
The react-tour modals now automatically support dark mode with VS Code-inspired styling:
|
||||
|
||||
```jsx
|
||||
// These components automatically get dark mode styling
|
||||
<Tour
|
||||
onRequestClose={closeTour}
|
||||
steps={tourConfig}
|
||||
isOpen={isOpen}
|
||||
rounded={5}
|
||||
/>
|
||||
```
|
||||
|
||||
### ReactTooltip Components
|
||||
All ReactTooltip instances now support dark mode:
|
||||
|
||||
```jsx
|
||||
// Automatically styled for dark mode
|
||||
<ReactTooltip id="my-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px]">
|
||||
<p>Tooltip content</p>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
```
|
||||
|
||||
### HoverCard Balloon UI
|
||||
The balloon tooltips in OpenSign Drive now properly support dark mode:
|
||||
|
||||
```jsx
|
||||
// These automatically get dark styling in dark mode
|
||||
<HoverCard>
|
||||
<HoverCardContent>
|
||||
Document information
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
```
|
||||
|
||||
## Dark Mode Features Added
|
||||
|
||||
### 1. **React-Tour Modal Styling**
|
||||
- Background: `#1F2937` (VS Code modal background)
|
||||
- Text: `#E5E7EB` (soft white for readability)
|
||||
- Borders: `#374151` (subtle borders)
|
||||
- Buttons: VS Code-style primary/secondary buttons
|
||||
|
||||
### 2. **ReactTooltip Styling**
|
||||
- Background: `#1F2937` with proper contrast
|
||||
- Border: `#374151` for definition
|
||||
- Box shadow: Enhanced for dark backgrounds
|
||||
- Text: `#E5E7EB` for optimal readability
|
||||
|
||||
### 3. **HoverCard Balloon UI**
|
||||
- Background: `#1F2937` (matches VS Code)
|
||||
- Text: `#E5E7EB` for readability
|
||||
- Arrow: Automatically matches background color
|
||||
- Enhanced shadows for dark backgrounds
|
||||
|
||||
### 4. **React-Datepicker Support**
|
||||
- Calendar background: `#1F2937`
|
||||
- Selected dates: VS Code blue (`#007ACC`)
|
||||
- Hover states: Proper contrast ratios
|
||||
- Navigation arrows: Themed appropriately
|
||||
|
||||
## CSS Classes Reference
|
||||
|
||||
| Class | Purpose | Dark Mode Color |
|
||||
|-------|---------|----------------|
|
||||
| `icon-improved` | Better icon visibility | `#CCCCCC` |
|
||||
| `icon-muted` | Muted but visible icons | `#999999` |
|
||||
| `icon-disabled` | Disabled icon state | `#858585` |
|
||||
| `op-btn-vscode-disabled` | VS Code disabled button | Background: `#3C3C3C` |
|
||||
| `btn-themed-disabled` | Themed disabled button | Uses CSS variables |
|
||||
| `icon-themed` | Variable-based icon color | `var(--icon-color)` |
|
||||
| `.reactour__helper` | `#1F2937` background |
|
||||
| `.react-tooltip` | `#1F2937` background |
|
||||
| `.HoverCardContent` | `#1F2937` background |
|
||||
| `.react-datepicker` | `#1F2937` background |
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From JavaScript Function to Tailwind
|
||||
```jsx
|
||||
// Before
|
||||
<i style={{ color: getThemeIconColor() }} className="fa-light fa-plus" />
|
||||
|
||||
// After
|
||||
<i className="fa-light fa-plus icon-improved" />
|
||||
```
|
||||
|
||||
### From Hardcoded Colors to Theme-aware
|
||||
```jsx
|
||||
// Before
|
||||
<i className="fa-light fa-plus text-gray-500" />
|
||||
|
||||
// After (automatic improvement)
|
||||
<i className="fa-light fa-plus text-gray-500" />
|
||||
// OR explicitly
|
||||
<i className="fa-light fa-plus icon-improved" />
|
||||
```
|
||||
|
||||
## Migration Notes
|
||||
|
||||
All existing tooltip and tour components will automatically inherit the new dark mode styling when the theme is set to `opensigndark`. No code changes required for existing implementations.
|
||||
@@ -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,92 @@
|
||||
/**
|
||||
* Tailwind Dark Mode Usage Examples for OpenSign
|
||||
*
|
||||
* This file demonstrates how to use the new Tailwind utilities
|
||||
* for better dark mode visibility of buttons and icons.
|
||||
*/
|
||||
|
||||
// Example 1: VS Code-style disabled buttons
|
||||
const DisabledButtonExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Option A: Using the VS Code disabled style */}
|
||||
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
|
||||
VS Code Style Disabled Button
|
||||
</button>
|
||||
|
||||
{/* Option B: Using themed disabled style */}
|
||||
<button className="op-btn btn-themed-disabled">
|
||||
Themed Disabled Button
|
||||
</button>
|
||||
|
||||
{/* Option C: Conditional styling */}
|
||||
<button
|
||||
className={`op-btn op-btn-primary ${
|
||||
isDisabled ? "op-btn-vscode-disabled" : ""
|
||||
}`}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Conditional Button
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 2: Icon visibility improvements
|
||||
const IconExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Theme-aware icons with better visibility */}
|
||||
<i className="fa-light fa-folder icon-improved"></i>
|
||||
<i className="fa-light fa-plus icon-muted"></i>
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
|
||||
{/* Using CSS variables */}
|
||||
<i className="fa-light fa-search icon-themed"></i>
|
||||
<i className="fa-light fa-settings icon-themed-muted"></i>
|
||||
|
||||
{/* Gray text that automatically improves in dark mode */}
|
||||
<span className="text-gray-500">
|
||||
This text is now more visible in dark mode
|
||||
</span>
|
||||
<span className="text-gray-400">Muted but still readable</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 3: Using CSS variables in inline styles
|
||||
const InlineStyleExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Using CSS variables directly */}
|
||||
<i className="fa-light fa-plus" style={{ color: "var(--icon-color)" }} />
|
||||
|
||||
{/* Using the existing JavaScript function */}
|
||||
<i className="fa-light fa-minus" style={{ color: getThemeIconColor() }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 4: Toolbar with improved icons
|
||||
const ToolbarExample = () => {
|
||||
return (
|
||||
<div className="flex space-x-2 p-2">
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-plus icon-improved"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded" disabled>
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-edit icon-improved"></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
DisabledButtonExamples,
|
||||
IconExamples,
|
||||
InlineStyleExamples,
|
||||
ToolbarExample
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Tailwind Dark Mode Usage Examples for OpenSign
|
||||
*
|
||||
* This file demonstrates how to use the new Tailwind utilities
|
||||
* for better dark mode visibility of buttons and icons.
|
||||
*/
|
||||
|
||||
// Example 1: VS Code-style disabled buttons
|
||||
const DisabledButtonExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Option A: Using the VS Code disabled style */}
|
||||
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
|
||||
VS Code Style Disabled Button
|
||||
</button>
|
||||
|
||||
{/* Option B: Using themed disabled style */}
|
||||
<button className="op-btn btn-themed-disabled">
|
||||
Themed Disabled Button
|
||||
</button>
|
||||
|
||||
{/* Option C: Conditional styling */}
|
||||
<button
|
||||
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Conditional Button
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 2: Icon visibility improvements
|
||||
const IconExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Theme-aware icons with better visibility */}
|
||||
<i className="fa-light fa-folder icon-improved"></i>
|
||||
<i className="fa-light fa-plus icon-muted"></i>
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
|
||||
{/* Using CSS variables */}
|
||||
<i className="fa-light fa-search icon-themed"></i>
|
||||
<i className="fa-light fa-settings icon-themed-muted"></i>
|
||||
|
||||
{/* Gray text that automatically improves in dark mode */}
|
||||
<span className="text-gray-500">This text is now more visible in dark mode</span>
|
||||
<span className="text-gray-400">Muted but still readable</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 3: Using CSS variables in inline styles
|
||||
const InlineStyleExamples = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* Using CSS variables directly */}
|
||||
<i
|
||||
className="fa-light fa-plus"
|
||||
style={{ color: 'var(--icon-color)' }}
|
||||
/>
|
||||
|
||||
{/* Using the existing JavaScript function */}
|
||||
<i
|
||||
className="fa-light fa-minus"
|
||||
style={{ color: getThemeIconColor() }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Example 4: Toolbar with improved icons
|
||||
const ToolbarExample = () => {
|
||||
return (
|
||||
<div className="flex space-x-2 p-2">
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-plus icon-improved"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded" disabled>
|
||||
<i className="fa-light fa-trash icon-disabled"></i>
|
||||
</button>
|
||||
<button className="p-2 hover:bg-gray-200 rounded">
|
||||
<i className="fa-light fa-edit icon-improved"></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
DisabledButtonExamples,
|
||||
IconExamples,
|
||||
InlineStyleExamples,
|
||||
ToolbarExample
|
||||
};
|
||||
@@ -1,20 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="opensigncss">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<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="%PUBLIC_URL%/logo192.png" /> -->
|
||||
<!-- <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>OpenSign™</title>
|
||||
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
|
||||
<title>...Loading</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="root" style="touch-action:pan-x pan-y;"></div>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,71 +1,72 @@
|
||||
{
|
||||
"name": "open_sign",
|
||||
"version": "0.1.0",
|
||||
"version": "2.21.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@lottiefiles/dotlottie-react": "^0.8.7",
|
||||
"@imgly/background-removal": "^1.6.0",
|
||||
"@lottiefiles/dotlottie-react": "^0.14.2",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.1.1",
|
||||
"@react-pdf/renderer": "^3.4.4",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"axios": "^1.7.3",
|
||||
"@radix-ui/themes": "^3.2.1",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"axios": "^1.10.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^23.12.2",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-http-backend": "^2.5.2",
|
||||
"i18next": "^25.3.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"nth-check": "^2.1.1",
|
||||
"parse": "^5.3.0",
|
||||
"parse": "^6.1.1",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pkijs": "^3.0.8",
|
||||
"print-js": "^1.6.0",
|
||||
"radix-ui": "^1.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.10.4",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-cookie": "^7.2.0",
|
||||
"react-datepicker": "^7.3.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"quill-html-edit-button": "^3.0.0",
|
||||
"radix-ui": "^1.4.2",
|
||||
"react": "^18.3.1",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-datepicker": "^8.4.0",
|
||||
"react-dnd": "^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-dom": "^18.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-i18next": "^15.5.3",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-pdf": "^9.1.0",
|
||||
"react-quill-new": "^3.2.2",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-rnd": "^10.4.11",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-pdf": "^9.2.1",
|
||||
"react-quill-new": "^3.4.6",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router": "^7.6.3",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.8.0",
|
||||
"react-signature-canvas": "^1.0.6",
|
||||
"react-tooltip": "^5.28.0",
|
||||
"react-web-share": "^2.0.2",
|
||||
"react-select": "^5.10.1",
|
||||
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||
"react-timezone-select": "^3.2.8",
|
||||
"react-tooltip": "^5.29.1",
|
||||
"reactour": "^1.19.4",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"regex-parser": "^2.3.0",
|
||||
"serve": "^14.2.3",
|
||||
"styled-components": "^5.3.0",
|
||||
"web-vitals": "^4.2.2",
|
||||
"ws": "^8.18.0"
|
||||
"regex-parser": "^2.3.1",
|
||||
"serve": "^14.2.4",
|
||||
"styled-components": "^5.3.11",
|
||||
"web-vitals": "^5.0.3",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||
},
|
||||
"scripts": {
|
||||
"build-webpack": "webpack --config webpack.config.js",
|
||||
"build": "npm run version && npm run build-webpack && react-scripts build",
|
||||
"build-watch": "webpack --config webpack.config.js --watch",
|
||||
"start-dev": "concurrently \"npm run build-watch\" \"react-scripts start\"",
|
||||
"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",
|
||||
"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\"",
|
||||
"build-win": "npm run version-win && npm run build-webpack && react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"build-win": "npm run version-win && vite build",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"release": "standard-version",
|
||||
"commit": "cz"
|
||||
},
|
||||
@@ -93,33 +94,40 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@babel/runtime-corejs2": "^7.25.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"babel-loader": "^9.1.3",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"commitizen": "^4.3.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"@babel/core": "^7.27.7",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/runtime-corejs2": "^7.27.6",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/react": "^18.3.23",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"@vitejs/plugin-react-swc": "^3.10.2",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-loader": "^10.0.0",
|
||||
"commitizen": "^4.3.1",
|
||||
"concurrently": "^9.2.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.10",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
"lint-staged": "^15.2.8",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"postcss": "^8.4.41",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-quick": "^4.0.0",
|
||||
"tailwindcss": "^3.4.9",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"webpack-cli": "^5.1.4"
|
||||
"daisyui": "^4.12.24",
|
||||
"dotenv": "^16.6.1",
|
||||
"eslint": "^9.30.0",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"jsdom": "^26.1.0",
|
||||
"lint-staged": "^16.1.2",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"pretty-quick": "^4.2.2",
|
||||
"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.2.4"
|
||||
},
|
||||
"overrides": {
|
||||
"nth-check": "$nth-check",
|
||||
"ws": "$ws"
|
||||
"engines": {
|
||||
"node": "18 || 20 || 22"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
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 |
@@ -0,0 +1,4 @@
|
||||
Name,Email,Phone
|
||||
John Doe,john@example.com,1234567890
|
||||
Jane Smith,jane@example.com,9876543210
|
||||
Foo Bar,foo@example.com,5555555555
|
||||
|
|
After Width: | Height: | Size: 95 KiB |
@@ -0,0 +1 @@
|
||||
import "@testing-library/jest-dom";
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect, lazy } from "react";
|
||||
import { Routes, Route, BrowserRouter } from "react-router-dom";
|
||||
import { useState, useEffect, lazy } from "react";
|
||||
import { Routes, Route, BrowserRouter } from "react-router";
|
||||
import { pdfjs } from "react-pdf";
|
||||
import Login from "./pages/Login";
|
||||
import Form from "./pages/Form";
|
||||
import Report from "./pages/Report";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
@@ -15,27 +14,23 @@ import DraftDocument from "./components/pdf/DraftDocument";
|
||||
import PlaceHolderSign from "./pages/PlaceHolderSign";
|
||||
import PdfRequestFiles from "./pages/PdfRequestFiles";
|
||||
import LazyPage from "./primitives/LazyPage";
|
||||
import { isEnableSubscription } from "./constant/const";
|
||||
import SSOVerify from "./pages/SSOVerify";
|
||||
import Loader from "./primitives/Loader";
|
||||
import TeamList from "./pages/TeamList";
|
||||
import UserList from "./pages/UserList";
|
||||
import { serverUrl_fn } from "./constant/appinfo";
|
||||
import DocSuccessPage from "./pages/DocSuccessPage";
|
||||
import ValidateSession from "./primitives/ValidateSession";
|
||||
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
|
||||
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
|
||||
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
|
||||
const Pgsignup = lazy(() => import("./pages/Pgsignup"));
|
||||
const Subscriptions = lazy(() => import("./pages/PlanSubscriptions"));
|
||||
const ChangePassword = lazy(() => import("./pages/ChangePassword"));
|
||||
const UserProfile = lazy(() => import("./pages/UserProfile"));
|
||||
const Signup = lazy(() => import("./pages/Signup"));
|
||||
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
|
||||
const ManageSign = lazy(() => import("./pages/Managesign"));
|
||||
const GenerateToken = lazy(() => import("./pages/GenerateToken"));
|
||||
const Webhook = lazy(() => import("./pages/Webhook"));
|
||||
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
|
||||
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
|
||||
|
||||
const Preferences = lazy(() => import("./pages/Preferences"));
|
||||
const Login = lazy(() => import("./pages/Login"));
|
||||
const VerifyDocument = lazy(() => import("./pages/VerifyDocument"));
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
|
||||
const AppLoader = () => {
|
||||
return (
|
||||
@@ -72,12 +67,7 @@ function App() {
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<ValidateRoute />}>
|
||||
<Route exact path="/" element={<Login />} />
|
||||
{isEnableSubscription && (
|
||||
<Route path="/signup" element={<LazyPage Page={Signup} />} />
|
||||
)}
|
||||
{!isEnableSubscription && (
|
||||
<>
|
||||
<Route exact path="/" element={<LazyPage Page={Login} />} />
|
||||
<Route
|
||||
path="/addadmin"
|
||||
element={<LazyPage Page={AddAdmin} />}
|
||||
@@ -86,8 +76,6 @@ function App() {
|
||||
path="/upgrade-2.1"
|
||||
element={<LazyPage Page={UpdateExistUserAdmin} />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Route>
|
||||
<Route element={<Validate />}>
|
||||
<Route
|
||||
@@ -118,27 +106,21 @@ function App() {
|
||||
element={<LazyPage Page={GuestLogin} />}
|
||||
/>
|
||||
<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
|
||||
path="/changepassword"
|
||||
element={<LazyPage Page={ChangePassword} />}
|
||||
path="/forgetpassword"
|
||||
element={<LazyPage Page={ForgetPassword} />}
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<ValidateSession>
|
||||
<HomeLayout />
|
||||
</ValidateSession>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path="/changepassword"
|
||||
element={<LazyPage Page={ChangePassword} />}
|
||||
/>
|
||||
<Route path="/form/:id" element={<Form />} />
|
||||
<Route path="/report/:id" element={<Report />} />
|
||||
<Route path="/dashboard/:id" element={<Dashboard />} />
|
||||
@@ -147,7 +129,7 @@ function App() {
|
||||
element={<LazyPage Page={UserProfile} />}
|
||||
/>
|
||||
<Route
|
||||
path="/opensigndrive"
|
||||
path="/drive"
|
||||
element={<LazyPage Page={Opensigndrive} />}
|
||||
/>
|
||||
<Route
|
||||
@@ -160,7 +142,7 @@ function App() {
|
||||
/>
|
||||
{/* signyouself route with no rowlevel data using docId from url */}
|
||||
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
|
||||
{/* draft document route to handle and navigate route page accordiing to document status */}
|
||||
{/* draft document route to handle and navigate route page according to document status */}
|
||||
<Route path="/draftDocument" element={<DraftDocument />} />
|
||||
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
|
||||
<Route
|
||||
@@ -174,7 +156,6 @@ function App() {
|
||||
path="/pdfRequestFiles/:docId"
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
|
||||
{/* recipient signature route with no rowlevel data using docId from url */}
|
||||
<Route
|
||||
path="/recipientSignPdf/:docId/:contactBookId"
|
||||
@@ -184,22 +165,17 @@ function App() {
|
||||
path="/recipientSignPdf/:docId"
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
{isEnableSubscription && (
|
||||
<>
|
||||
<Route path="/teams" element={<TeamList />} />
|
||||
<Route
|
||||
path="/generatetoken"
|
||||
element={<LazyPage Page={GenerateToken} />}
|
||||
/>
|
||||
<Route
|
||||
path="/webhook"
|
||||
element={<LazyPage Page={Webhook} />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Route path="/users" element={<UserList />} />
|
||||
<Route path="/users" element={<UserList />} />
|
||||
<Route
|
||||
path="/verify-document"
|
||||
element={<LazyPage Page={VerifyDocument} />}
|
||||
/>
|
||||
<Route
|
||||
path="/preferences"
|
||||
element={<LazyPage Page={Preferences} />}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/sso" element={<SSOVerify />} />
|
||||
<Route path="/success" element={<DocSuccessPage />} />
|
||||
<Route path="*" element={<PageNotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
Before Width: | Height: | Size: 805 B |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,184 +1,184 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="691.365" height="586.346" viewBox="0 0 691.365 586.346">
|
||||
<defs>
|
||||
<linearGradient id="linear-gradient" x1="0.5" y1="1" x2="0.5" y2="-0.001" gradientUnits="objectBoundingBox">
|
||||
<stop offset="0" stop-color="gray" stop-opacity="0.251"/>
|
||||
<stop offset="0.54" stop-color="gray" stop-opacity="0.122"/>
|
||||
<stop offset="1" stop-color="gray" stop-opacity="0.102"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="Group_644" data-name="Group 644" transform="translate(-588.635 -80)">
|
||||
<g id="Group_642" data-name="Group 642" transform="translate(22538.635 6999)">
|
||||
<path id="Path_472" data-name="Path 472" d="M482.764,637.675c51.612-29.22,67.336-29.693,124.954-17.169,81.646,17.758,88.43-49.93,108.306-186.933,33.946-233.98-108.306-295.737-108.306-295.737s-94.6-8.1-150.536-38.963C377.726,55.027,286.9,49.443,207.046,86.542c-51.069,23.7-93.125,56.714-100.31,102.034-20.09,126.746,12.9,196.58-48.819,237.954S48.944,581.5,96.263,577.564c34.968-2.906,80.842,52.415,130.687,60.111,17.382,2.684,88.7-13.043,99.578,0C341.327,655.4,464.763,647.87,482.764,637.675Z" transform="translate(-21979.895 -6980.982)" fill="#00ddf1" opacity="0.1"/>
|
||||
<g id="Group_641" data-name="Group 641" transform="translate(-21545.08 -6816.88)">
|
||||
<rect id="Rectangle_263" data-name="Rectangle 263" width="217.867" height="15.073" transform="translate(0 39.052)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_264" data-name="Rectangle 264" width="217.867" height="15.073" transform="translate(0 104.137)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_265" data-name="Rectangle 265" width="217.867" height="15.073" transform="translate(0 171.964)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_266" data-name="Rectangle 266" width="13.702" height="39.052" transform="translate(19.183)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_267" data-name="Rectangle 267" width="13.702" height="2.74" transform="translate(19.183 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_268" data-name="Rectangle 268" width="13.702" height="2.74" transform="translate(19.183 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_269" data-name="Rectangle 269" width="13.702" height="39.052" transform="translate(39.737)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_270" data-name="Rectangle 270" width="13.702" height="2.74" transform="translate(39.737 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_271" data-name="Rectangle 271" width="13.702" height="2.74" transform="translate(39.737 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_272" data-name="Rectangle 272" width="13.702" height="39.052" transform="translate(60.29)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_273" data-name="Rectangle 273" width="13.702" height="2.74" transform="translate(60.29 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_274" data-name="Rectangle 274" width="13.702" height="2.74" transform="translate(60.29 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_275" data-name="Rectangle 275" width="13.702" height="39.052" transform="translate(80.844)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_276" data-name="Rectangle 276" width="13.702" height="2.74" transform="translate(80.844 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_277" data-name="Rectangle 277" width="13.702" height="2.74" transform="translate(80.844 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_278" data-name="Rectangle 278" width="13.702" height="39.052" transform="translate(101.397)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_279" data-name="Rectangle 279" width="13.702" height="2.74" transform="translate(101.397 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_280" data-name="Rectangle 280" width="13.702" height="2.74" transform="translate(101.397 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_281" data-name="Rectangle 281" width="13.702" height="39.052" transform="translate(121.95)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_282" data-name="Rectangle 282" width="13.702" height="2.74" transform="translate(121.95 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_283" data-name="Rectangle 283" width="13.702" height="2.74" transform="translate(121.95 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_284" data-name="Rectangle 284" width="13.702" height="39.052" transform="translate(142.504)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_285" data-name="Rectangle 285" width="13.702" height="2.74" transform="translate(142.504 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_286" data-name="Rectangle 286" width="13.702" height="2.74" transform="translate(142.504 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_287" data-name="Rectangle 287" width="13.702" height="39.052" transform="matrix(0.894, -0.448, 0.448, 0.894, 135.847, 138.025)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_288" data-name="Rectangle 288" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 138.614, 143.541)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_289" data-name="Rectangle 289" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 148.128, 162.529)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_290" data-name="Rectangle 290" width="13.702" height="39.052" transform="translate(19.183 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_291" data-name="Rectangle 291" width="13.702" height="2.74" transform="translate(19.183 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_292" data-name="Rectangle 292" width="13.702" height="2.74" transform="translate(19.183 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_293" data-name="Rectangle 293" width="13.702" height="39.052" transform="translate(39.737 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_294" data-name="Rectangle 294" width="13.702" height="2.74" transform="translate(39.737 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_295" data-name="Rectangle 295" width="13.702" height="2.74" transform="translate(39.737 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_296" data-name="Rectangle 296" width="13.702" height="39.052" transform="translate(60.29 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_297" data-name="Rectangle 297" width="13.702" height="2.74" transform="translate(60.29 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_298" data-name="Rectangle 298" width="13.702" height="2.74" transform="translate(60.29 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_299" data-name="Rectangle 299" width="13.702" height="39.052" transform="translate(145.93 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_300" data-name="Rectangle 300" width="13.702" height="2.74" transform="translate(145.93 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_301" data-name="Rectangle 301" width="13.702" height="2.74" transform="translate(145.93 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_302" data-name="Rectangle 302" width="13.702" height="39.052" transform="translate(166.483 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_303" data-name="Rectangle 303" width="13.702" height="2.74" transform="translate(166.483 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_304" data-name="Rectangle 304" width="13.702" height="2.74" transform="translate(166.483 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_305" data-name="Rectangle 305" width="13.702" height="39.052" transform="translate(187.036 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_306" data-name="Rectangle 306" width="13.702" height="2.74" transform="translate(187.036 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_307" data-name="Rectangle 307" width="13.702" height="2.74" transform="translate(187.036 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_308" data-name="Rectangle 308" width="13.702" height="39.052" transform="translate(19.183 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_309" data-name="Rectangle 309" width="13.702" height="2.74" transform="translate(19.183 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_310" data-name="Rectangle 310" width="13.702" height="2.74" transform="translate(19.183 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_311" data-name="Rectangle 311" width="13.702" height="39.052" transform="translate(39.737 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_312" data-name="Rectangle 312" width="13.702" height="2.74" transform="translate(39.737 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_313" data-name="Rectangle 313" width="13.702" height="2.74" transform="translate(39.737 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_314" data-name="Rectangle 314" width="13.702" height="39.052" transform="translate(60.29 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_315" data-name="Rectangle 315" width="13.702" height="2.74" transform="translate(60.29 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_316" data-name="Rectangle 316" width="13.702" height="2.74" transform="translate(60.29 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_317" data-name="Rectangle 317" width="13.702" height="39.052" transform="translate(80.844 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_318" data-name="Rectangle 318" width="13.702" height="2.74" transform="translate(80.844 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_319" data-name="Rectangle 319" width="13.702" height="2.74" transform="translate(80.844 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_320" data-name="Rectangle 320" width="13.702" height="39.052" transform="translate(101.397 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_321" data-name="Rectangle 321" width="13.702" height="2.74" transform="translate(101.397 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_322" data-name="Rectangle 322" width="13.702" height="2.74" transform="translate(101.397 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_323" data-name="Rectangle 323" width="13.702" height="39.052" transform="translate(121.95 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_324" data-name="Rectangle 324" width="13.702" height="2.74" transform="translate(121.95 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_325" data-name="Rectangle 325" width="13.702" height="2.74" transform="translate(121.95 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_473" data-name="Path 473" d="M797.862,159.09v35.571h-13.7V156.48Q791.039,157.693,797.862,159.09Z" transform="translate(-621.103 -155.61)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_326" data-name="Rectangle 326" width="13.702" height="2.74" transform="translate(163.057 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_327" data-name="Rectangle 327" width="13.702" height="2.74" transform="translate(163.057 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_474" data-name="Path 474" d="M827.862,165.719v30.83h-13.7V162.43c1.781.391,3.556.8,5.33,1.22Q823.7,164.636,827.862,165.719Z" transform="translate(-630.549 -157.483)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_475" data-name="Path 475" d="M827.862,166.285v.685h-13.7v-2.74h5.33Q823.7,165.2,827.862,166.285Z" transform="translate(-630.549 -158.05)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_328" data-name="Rectangle 328" width="13.702" height="2.74" transform="translate(183.611 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_476" data-name="Path 476" d="M691.358,287.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.179c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-589.693 -196.649)" fill="#00ddf1"/>
|
||||
<path id="Path_477" data-name="Path 477" d="M686.2,296.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.277,10.277,0,0,1,686.2,296.239Z" transform="translate(-589.706 -196.595)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_478" data-name="Path 478" d="M691.42,290.06s1.542,1.213,5.282,0" transform="translate(-591.9 -197.672)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_479" data-name="Path 479" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1"/>
|
||||
<path id="Path_480" data-name="Path 480" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_481" data-name="Path 481" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1"/>
|
||||
<path id="Path_482" data-name="Path 482" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_483" data-name="Path 483" d="M694.75,283.958s1.089-5.207,8.16-5.878" transform="translate(-592.949 -193.9)" fill="#00ddf1"/>
|
||||
<path id="Path_484" data-name="Path 484" d="M819.358,386.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.18c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-629.999 -227.823)" fill="#00ddf1"/>
|
||||
<path id="Path_485" data-name="Path 485" d="M814.2,395.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.276,10.276,0,0,1,814.2,395.239Z" transform="translate(-630.011 -227.769)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_486" data-name="Path 486" d="M819.42,389.06s1.542,1.213,5.282,0" transform="translate(-632.206 -228.846)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_487" data-name="Path 487" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1"/>
|
||||
<path id="Path_488" data-name="Path 488" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_489" data-name="Path 489" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1"/>
|
||||
<path id="Path_490" data-name="Path 490" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_491" data-name="Path 491" d="M822.75,382.958s1.089-5.207,8.16-5.878" transform="translate(-633.254 -225.073)" fill="#00ddf1"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Group_643" data-name="Group 643" transform="translate(7 -63)">
|
||||
<g id="Group_639" data-name="Group 639" transform="translate(21233 8372)">
|
||||
<ellipse id="Ellipse_73" data-name="Ellipse 73" cx="49.851" cy="8.007" rx="49.851" ry="8.007" transform="translate(-20118.443 -7772.555)" fill="#00abf6" opacity="0.1"/>
|
||||
<rect id="Rectangle_182" data-name="Rectangle 182" width="3.208" height="72.224" transform="translate(-20070.199 -7917.934)" fill="#535461"/>
|
||||
<path id="Path_268" data-name="Path 268" d="M1025.906,587.251l-.2,3.254-.287,4.6-.118,1.913-.281,4.6-.123,1.913-.286,4.6-3.233,52.22a8.851,8.851,0,0,1-8.83,8.268H995.731a8.841,8.841,0,0,1-8.82-8.3l-3.244-52.225-.281-4.6-.117-1.913-.292-4.6-.117-1.913-.281-4.6-.2-3.254a4.6,4.6,0,0,1,4.574-4.865h34.391a4.6,4.6,0,0,1,4.569,4.9Z" transform="translate(-21072.734 -8433.181)" fill="#565661"/>
|
||||
<path id="Path_269" data-name="Path 269" d="M990.949,517.36l-.287,4.589H948.106l-.286-4.589Z" transform="translate(-21037.977 -8360.036)" fill="#9d9cb5"/>
|
||||
<path id="Path_270" data-name="Path 270" d="M990.93,530.08l-.281,4.594H948.9l-.286-4.594Z" transform="translate(-21038.363 -8366.248)" fill="#9d9cb5"/>
|
||||
<path id="Path_271" data-name="Path 271" d="M990.912,542.8l-.287,4.589H949.686L949.4,542.8Z" transform="translate(-21038.748 -8372.46)" fill="#9d9cb5"/>
|
||||
<path id="Path_272" data-name="Path 272" d="M933.875,490.464c18.684,13.517,26.466,34.656,26.466,34.656s-22.511-.773-41.2-14.289-26.46-34.651-26.46-34.651S915.2,476.953,933.875,490.464Z" transform="translate(-21028.934 -8381.328)" fill="#00abf6"/>
|
||||
<path id="Path_273" data-name="Path 273" d="M892.68,476.18s23.365,12.969,30.865,23.708,36.8,25.233,36.8,25.233" transform="translate(-21028.934 -8381.328)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path id="Path_274" data-name="Path 274" d="M975.393,443.457c10.171,7.352,14.407,18.863,14.407,18.863s-12.279-.425-22.424-7.776S953,435.68,953,435.68,965.227,436.1,975.393,443.457Z" transform="translate(-21058.393 -8361.549)" fill="#00abf6"/>
|
||||
<path id="Path_275" data-name="Path 275" d="M953,435.68s12.719,7.06,16.8,12.9,20.03,13.737,20.03,13.737" transform="translate(-21058.393 -8361.549)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path id="Path_276" data-name="Path 276" d="M1040.3,470.483c-11.68,12.836-14.151,29.612-14.151,29.612s16.469-4.042,28.139-16.883,14.151-29.612,14.151-29.612S1051.981,457.647,1040.3,470.483Z" transform="translate(-21094.119 -8370.301)" fill="#00abf6"/>
|
||||
<path id="Path_277" data-name="Path 277" d="M1068.45,453.61s-15.215,13.159-19.083,22.224S1026.15,500.1,1026.15,500.1" transform="translate(-21094.119 -8370.306)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
</g>
|
||||
<g id="Group_638" data-name="Group 638" transform="translate(-40 36)">
|
||||
<rect id="Rectangle_254" data-name="Rectangle 254" width="12.343" height="37.459" transform="translate(741.459 392.675)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_255" data-name="Rectangle 255" width="12.343" height="39.629" transform="translate(1073.647 390.726)" fill="#3f3d56"/>
|
||||
<path id="Path_418" data-name="Path 418" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" fill="#65617d"/>
|
||||
<path id="Path_419" data-name="Path 419" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" opacity="0.2"/>
|
||||
<path id="Path_420" data-name="Path 420" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" fill="#3f3d56"/>
|
||||
<path id="Path_421" data-name="Path 421" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" opacity="0.1"/>
|
||||
<path id="Path_422" data-name="Path 422" d="M360.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(436.74 -122.381)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_256" data-name="Rectangle 256" width="30.644" height="20.464" transform="translate(1046.362 534.189)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_257" data-name="Rectangle 257" width="30.644" height="20.464" transform="translate(1046.362 534.189)" opacity="0.1"/>
|
||||
<rect id="Rectangle_258" data-name="Rectangle 258" width="30.644" height="20.464" transform="translate(1045.062 534.189)" fill="#3f3d56"/>
|
||||
<path id="Path_423" data-name="Path 423" d="M223.943,265.82v3.723l-12.343-.318v-3.4Z" transform="translate(529.859 126.848)" opacity="0.1"/>
|
||||
<path id="Path_424" data-name="Path 424" d="M358.741,271.246,343.909,377.789a2.794,2.794,0,0,1-2.839,2.41L222.314,377.12l-2.254-.058a2.794,2.794,0,0,1-2.7-3.164l16.651-111.533a2.794,2.794,0,0,1,2.949-2.358l119.186,8.069a2.787,2.787,0,0,1,2.6,3.17Z" transform="translate(476.307 16.556)" fill="#65617d"/>
|
||||
<path id="Path_425" data-name="Path 425" d="M169.158,99.16,156.6,197.472l109.142,3.463,13.422-94.85Z" transform="translate(549.128 185.237)" fill="#00ddf1"/>
|
||||
<rect id="Rectangle_259" data-name="Rectangle 259" width="132.965" height="112.605" rx="5.33" transform="translate(853.849 282.877)" fill="#65617d"/>
|
||||
<rect id="Rectangle_260" data-name="Rectangle 260" width="120.836" height="93.765" transform="translate(860.125 290.458)" fill="#00ddf1"/>
|
||||
<path id="Path_426" data-name="Path 426" d="M735.273,262.82v2.462l-12.343.253V262.82Z" transform="translate(350.717 127.899)" opacity="0.1"/>
|
||||
<path id="Path_427" data-name="Path 427" d="M833.626,371a2,2,0,0,1-1.949,2.131l-121.615,2.514a2,2,0,0,1-2.033-1.813L697.575,265.156a2,2,0,0,1,1.9-2.189l120.693-6,1.3-.065a1.994,1.994,0,0,1,2.092,1.819Z" transform="translate(308.06 17.643)" fill="#65617d"/>
|
||||
<path id="Path_428" data-name="Path 428" d="M628.93,100.238,739.157,94.82l9.31,98.748-109.356,2.6Z" transform="translate(383.65 186.757)" fill="#00ddf1"/>
|
||||
<path id="Path_429" data-name="Path 429" d="M899.741,371a2,2,0,0,1-1.949,2.131L886.28,256.963l1.3-.065a2,2,0,0,1,2.092,1.819Z" transform="translate(241.945 17.643)" opacity="0.1"/>
|
||||
<rect id="Rectangle_261" data-name="Rectangle 261" width="12.343" height="11.096" transform="translate(741.459 419.038)" opacity="0.1"/>
|
||||
<path id="Path_431" data-name="Path 431" d="M281.574,483.65v3.229l-30.644.819V483.65Z" transform="translate(516.08 50.533)" opacity="0.1"/>
|
||||
<path id="Path_432" data-name="Path 432" d="M390.8,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h-.033a2.6,2.6,0,0,1-2.566-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H388.23A2.6,2.6,0,0,1,390.8,483.886Z" transform="translate(460.942 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_434" data-name="Path 434" d="M194.6,319.15H309.914v96.311L194.6,418.06Z" transform="translate(535.815 108.164)" fill="#d4dfec"/>
|
||||
<rect id="Rectangle_262" data-name="Rectangle 262" width="12.343" height="11.317" transform="translate(1073.647 419.038)" opacity="0.1"/>
|
||||
<path id="Path_435" data-name="Path 435" d="M709.574,483.65v4.164L678.93,487V483.65Z" transform="translate(366.132 50.533)" opacity="0.1"/>
|
||||
<path id="Path_436" data-name="Path 436" d="M797.782,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0l-124.532-3.313a2.6,2.6,0,0,1-2.5-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H795.19a2.6,2.6,0,0,1,2.592,2.566Z" transform="translate(318.365 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_437" data-name="Path 437" d="M866.289,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0V481.32h0a2.6,2.6,0,0,1,2.6,2.566Z" transform="translate(249.859 -60.983)" opacity="0.1"/>
|
||||
<path id="Path_438" data-name="Path 438" d="M713.414,319.15H598.1v96.311l115.314,2.6Z" transform="translate(394.451 108.164)" fill="#d4dfec"/>
|
||||
<path id="Path_439" data-name="Path 439" d="M593.3,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h0a2.6,2.6,0,0,1-2.6-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H590.73A2.6,2.6,0,0,1,593.3,483.886Z" transform="translate(389.997 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_440" data-name="Path 440" d="M397.1,319.15H512.414v96.311L397.1,418.06Z" transform="translate(464.87 108.164)" fill="#d4dfec"/>
|
||||
<path id="Path_441" data-name="Path 441" d="M597.889,533.65V542.1l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643-114.086,1.059h-.1l-29.007.273-.312.007L106.1,546.643V536.9l131.562-.864h.312l26.629-.182h.123l183.9-1.215h.188l.11-.006h.305l3.112-.019h.948l48.393-.325,2.858-.013.637-.007Z" transform="translate(566.82 33.015)" opacity="0.1"/>
|
||||
<circle id="Ellipse_99" data-name="Ellipse 99" cx="33.347" cy="33.347" r="33.347" transform="translate(887.196 363.44)" fill="#fbbebe"/>
|
||||
<path id="Path_442" data-name="Path 442" d="M383.74,592.973a.806.806,0,0,1,.065-.227c.65-2.332,1.806-6.276,2.67-9.427.11-.39.214-.773.312-1.137.955-3.554,2.813-11.876,3.846-17.066.364-1.826.65-3.248.708-3.963.13-1.1,2.085-5.795,4.3-11.194,1.553-3.794,3.248-7.939,4.489-11.434a39.876,39.876,0,0,0,1.9-6.4,3.313,3.313,0,0,0,0-1.384,9.543,9.543,0,0,1-.312-2.274h0c-.149-4.028,1.052-10.394,2.91-13.065,2.274-3.248,3.9-15.371,3.9-15.371s3.365.487,3.9-2.293a4,4,0,0,0,.065-.91,4.815,4.815,0,0,0-.058-.721c-.578-4.054,9.563-8.368,15.267-9.3a12.769,12.769,0,0,1,1.949-.182,17.694,17.694,0,0,0,4.06-.935q.909-.279,1.949-.65c2.391-.806,5.249-1.871,8.283-3.04,11.408-4.424,25.22-10.394,25.22-10.394.1-.461.208-.9.331-1.338a18,18,0,0,1,10.622-12.3h0v-1.3c0-2.241-.1-4.548-.195-6.847,0-.448-.039-.9-.065-1.345v-.734c0-.455-.046-.91-.071-1.358l-.117-.091a10.66,10.66,0,0,1-2.534-2.729,9.916,9.916,0,0,1-.923-1.949c-.812-2.2-1-4.658-1.546-6.984l-.117-.481a54.579,54.579,0,0,0-2.6-6.873c-1.436-3.521-2.54-7.146-3.638-10.817q-1-3.333-2.02-6.678l-1.026-3.4c-.195-.65-.39-1.3-.539-1.949h0a8.627,8.627,0,0,1-.169-.91h0a6.5,6.5,0,0,1,.156-2.995,20.854,20.854,0,0,1,.858-1.949,5.113,5.113,0,0,0,.325-.9h0a1.947,1.947,0,0,0,.052-.208,3.667,3.667,0,0,0,.071-.7,11.993,11.993,0,0,0-.338-2.527.045.045,0,0,1,0-.032,1.661,1.661,0,0,0-.032-.175,16.564,16.564,0,0,1-.234-1.949,18.483,18.483,0,0,1,2.254-10.115l.123-.234a16.383,16.383,0,0,1,1.2-1.806h0a9.122,9.122,0,0,1,.65-.793,2.594,2.594,0,0,1-3.606-.936,2.475,2.475,0,0,1-.149-.292c-.033-.078-.059-.162-.085-.247-.455-1.592.338-3.287,1.377-4.58a12.012,12.012,0,0,1,3.729-3.053h0a2.51,2.51,0,0,1,.26-.136,11.125,11.125,0,0,1,1.715-.689h0a9.781,9.781,0,0,1,1.059-.286l-2.41-.221-.974-.091A9.414,9.414,0,0,1,479,361.527h.7a7.625,7.625,0,0,1,1.02.084A2.885,2.885,0,0,1,479,358.8a9.362,9.362,0,0,0,5.97,1.988h.247a19.494,19.494,0,0,1-.221-2.124h0a21.545,21.545,0,0,1,0-2.9,5.483,5.483,0,0,0,4.4,1.468l-1.351-1.884a6.165,6.165,0,0,0,4.262,1.332l.312-.033a4.547,4.547,0,0,0,.773-.149h.058c-.123-.156-.234-.318-.344-.487l-.039-.078a6.282,6.282,0,0,1-.65-1.358,4.284,4.284,0,0,1-.156-.565h0a5.684,5.684,0,0,1,.292-3.541c2.1,1.754,4.4,4.151,7.146,2.339s2.839-5.139,7.146-4.8a6.626,6.626,0,0,1,5.464,3.976,4.385,4.385,0,0,1,4.983-1.137,4.45,4.45,0,0,1,2.521,4.476c1.786-.448,3.71-.89,5.425-.214s2.82,2.865,1.845,4.327a.931.931,0,0,1-.091.13c-.8,1.033-2.566,1.6-2.378,2.9l.273.195.845.611q4.97,3.586,9.68,7.536c1.611,1.351,3.326,2.975,3.437,5.087a5.617,5.617,0,0,1-.156,1.54h0c-.1.507-.247,1.013-.357,1.52a12.676,12.676,0,0,0-.221,3.93h0c.045.383.1.76.175,1.143.994,5.022,5.009,9.167,5.405,14.292v.364a11.546,11.546,0,0,1-.045,1.644c-.3,3.118-1.741,6.113-2.91,9.1-.072.182-.143.364-.208.546a90.5,90.5,0,0,0-4.548,18.294,65.14,65.14,0,0,1-1.3,6.555,17.365,17.365,0,0,1-3.307,6.776,10.536,10.536,0,0,1-2.183,1.949,12.132,12.132,0,0,1-1.722.916v.039c.071.4.13.793.2,1.189.039.208.071.416.11.65h0a.089.089,0,0,0,0,.039c.546,3.073,1.169,6.081,1.826,8.939l.331,1.423c8.186,2.956,9.154,9.011,9.212,11.33a6.259,6.259,0,0,1,0,.929s12.343.325,17.216,3.9a13.21,13.21,0,0,0,2.93,1.494,53.917,53.917,0,0,0,6.009,1.877c1.046.273,2.137.539,3.248.8,1.949.448,4.015.884,6.009,1.3,5.321,1.072,10.33,1.949,12.87,2.6,6.061,1.533,19.925,14.825,19.925,14.825s1.624,13.409,3.248,15.364,4.548,8.829,4.548,8.829l10.745,19.106,4.548,8.036h2.923a4.97,4.97,0,0,1,2.085,1.364c.773.689,1.631,1.546,2.384,2.326l1.247,1.332.78.864s4.723.117,6,3.852c3.846-.11,8.569.143,11.876,1.624a9.187,9.187,0,0,1,1.3.7,5.639,5.639,0,0,1,2.683,3.677,5.422,5.422,0,0,1,.117.935c0,.37,0,.741,0,1.1-.234,4.918-3.437,9.011-6.38,11.765l.208.37s-.1.234-.286.65c-.052.123-.117.26-.188.416h0c-.383.819-.994,2.046-1.793,3.482a41.136,41.136,0,0,1-6.217,8.556l-.188.195-37.511.344-.11-.13-7.276-8.679-3.619-4.314-.11-.136-.1,1.033-.351,3.443-.845,8.322-.045.416v.11l-.1.961.071-.961h-.188l-69.513.65-114.092,1.26v.546h-.091v-.546l-29.072.312Z" transform="translate(418.007 -14.269)" fill="url(#linear-gradient)"/>
|
||||
<path id="Path_443" data-name="Path 443" d="M586.185,511.522s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.691-.039-2.891-.143-5.886-.292-8.868-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522c-2.6,8.53-1.748,20.51.227,31.755.578,3.281,1.247,6.5,1.949,9.55a223.892,223.892,0,0,0,6.269,22.036Z" transform="translate(367.711 -46.521)" fill="#fbbebe"/>
|
||||
<path id="Path_444" data-name="Path 444" d="M586.185,537.354s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.692,9.855-4.21,22.394-1.072,22.394-1.072,13.2-2.631,21.666-2.118,27.078-.273A223.888,223.888,0,0,0,586.185,537.354Z" transform="translate(367.711 -72.353)" opacity="0.1"/>
|
||||
<path id="Path_445" data-name="Path 445" d="M413.346,651.973v-.546l114.092-1.241,69.513-.65h.195l-.071.961.1-.961h.123v-.11l.89-8.738.338-3.307.1-1.033.091-.864,4.015-13.389,4.015-13.37,3.378-11.258,5.158-17.2s-2.923-6.821-4.548-8.77-3.248-15.267-3.248-15.267-13.864-13.208-19.925-14.728c-2.54-.65-7.555-1.514-12.883-2.6-1.994-.4-4.041-.832-6.009-1.3-1.111-.253-2.2-.52-3.248-.786a57.086,57.086,0,0,1-6.009-1.858,13.448,13.448,0,0,1-2.924-1.488c-4.775-3.5-16.742-3.9-17.2-3.9h0a.269.269,0,0,1,0-.045v-.058a6.66,6.66,0,0,0,0-.819,5.208,5.208,0,0,0-.059-.91.82.82,0,0,0-.045-.325c-.032-.234-.071-.481-.13-.741a3.226,3.226,0,0,0-.078-.344,11.181,11.181,0,0,0-.812-2.267c-.045-.1-.1-.214-.156-.318a11.861,11.861,0,0,0-1.1-1.709c-.084-.11-.175-.227-.273-.338a12.721,12.721,0,0,0-1.767-1.715c-.175-.136-.357-.279-.546-.409a3.026,3.026,0,0,0-.331-.234l-.578-.377-.539-.312c-.474-.26-.974-.513-1.52-.754-.169-.078-.351-.149-.533-.221s-.468-.182-.708-.273h-.039l-.487-.195c-.37-.123-.747-.234-1.143-.344l-.6-.156-.65-.149-.65-.136c-.65-.13-1.345-.247-2.072-.338l-.741-.091-.76-.078-.786-.065-1.117-.065-1.3-.045a81.017,81.017,0,0,0-17.054,1.814,46.946,46.946,0,0,0-11.855-1.093h-.565l-.65.039h-.416l-.838.078a6.224,6.224,0,0,0-.845.1c-.279.039-.572.071-.851.117l-.429.071c-.422.071-.851.149-1.3.24q-.214.039-.429.1c-.565.123-1.13.273-1.689.442-.279.078-.559.169-.838.266-.143.039-.279.091-.416.143l-.416.149-.409.162-.487.195c-.247.1-.487.214-.728.331-.136.058-.273.123-.4.195s-.266.13-.39.2a18.03,18.03,0,0,0-4.034,2.846l-.331.318-.325.331c-.11.11-.214.227-.318.344a15.473,15.473,0,0,0-1.182,1.462c-.1.136-.2.273-.3.416a17.335,17.335,0,0,0-1.117,1.9c-.084.156-.162.318-.24.474a21.243,21.243,0,0,0-1.3,3.411q-.182.65-.331,1.3s-13.753,6.068-25.161,10.46c-3.034,1.169-5.892,2.228-8.283,3.027-.7.234-1.345.448-1.949.65a17.347,17.347,0,0,1-4.06.935,12.778,12.778,0,0,0-1.949.182c-5.243.851-14.227,4.548-15.208,8.257a2.4,2.4,0,0,0-.065.91v.071c.65,4.548-3.9,3.9-3.9,3.9s-1.624,12.019-3.9,15.267c-1.644,2.339-2.768,7.555-2.91,11.492h0a12.741,12.741,0,0,0,.286,3.658v.091c.26.871-.364,3.248-1.423,6.3-1.15,3.346-2.826,7.5-4.437,11.427-2.43,5.925-4.716,11.3-4.859,12.486-.058.526-.227,1.468-.461,2.683-.948,4.95-3.053,14.377-4.093,18.19-.773,2.845-1.9,6.743-2.67,9.42a.91.91,0,0,1-.065.227l29.124-.286v.546h.091Z" transform="translate(417.856 -73.042)" fill="#00ddf1"/>
|
||||
<path id="Path_446" data-name="Path 446" d="M445.44,607.57s37.03,19.165,50.348,16.891Z" transform="translate(396.391 -105.214)" opacity="0.1"/>
|
||||
<path id="Path_447" data-name="Path 447" d="M561.44,595.57s36.056,11.694,48.074,13.318S561.44,595.57,561.44,595.57Z" transform="translate(355.751 -101.01)" opacity="0.1"/>
|
||||
<path id="Path_448" data-name="Path 448" d="M585.443,530.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,512.821,585.339,526.866,585.443,530.686Z" transform="translate(371.74 -73.754)" opacity="0.1"/>
|
||||
<path id="Path_449" data-name="Path 449" d="M585.443,528.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,510.821,585.339,524.866,585.443,528.686Z" transform="translate(371.74 -73.053)" fill="#00ddf1"/>
|
||||
<path id="Path_450" data-name="Path 450" d="M776.94,672.008s22.192-3.411,22.628,6.334S788.25,694.9,788.25,694.9Z" transform="translate(280.251 -127.587)" fill="#fbbebe"/>
|
||||
<path id="Path_451" data-name="Path 451" d="M578.17,479.936a30.558,30.558,0,0,1-2.923,1.13c-7,2.417-14.065,4.853-21.439,5.736s-15.111.1-21.543-3.573a18.685,18.685,0,0,1-1.949-1.267c-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522C575.318,456.711,576.195,468.69,578.17,479.936Z" transform="translate(367.508 -46.521)" opacity="0.1"/>
|
||||
<path id="Path_452" data-name="Path 452" d="M536.9,360.29a6.153,6.153,0,0,1-5.405-1.137l1.351,1.878a5.5,5.5,0,0,1-4.4-1.462,22.146,22.146,0,0,0,.208,5.035,9.433,9.433,0,0,1-6.211-1.949,2.872,2.872,0,0,0,1.715,2.8,9.316,9.316,0,0,0-10.135,6.958l3.391.312a12,12,0,0,0-6.782,4.138c-1.039,1.3-1.832,2.962-1.377,4.548s2.651,2.6,3.846,1.468a18.151,18.151,0,0,0-3.963,14.806,9.4,9.4,0,0,1,.3,3.417,28.993,28.993,0,0,1-1.228,3.047c-.65,1.9-.032,3.956.546,5.847l3.047,10.018c1.1,3.619,2.2,7.244,3.645,10.745a53.328,53.328,0,0,1,2.6,6.828c.819,3.144.851,6.588,2.6,9.336a12.948,12.948,0,0,0,4.665,4.112c6.425,3.677,14.188,4.457,21.542,3.573s14.409-3.32,21.406-5.736a20.141,20.141,0,0,0,4.444-1.949c4.84-3.17,5.918-9.654,6.821-15.364a87.242,87.242,0,0,1,4.736-18.71c1.416-3.567,3.248-7.185,2.936-11.005-.4-5.074-4.411-9.186-5.405-14.175a12.344,12.344,0,0,1,.045-5.048,11.473,11.473,0,0,0,.513-3.053c-.11-2.092-1.826-3.7-3.437-5.048q-5.243-4.379-10.823-8.277c-.188-1.3,1.579-1.858,2.378-2.884,1.13-1.449-.039-3.748-1.754-4.424s-3.638-.234-5.425.214a4.281,4.281,0,0,0-7.5-3.32,6.627,6.627,0,0,0-5.47-3.95c-4.307-.338-4.392,2.956-7.146,4.768s-5.028-.578-7.146-2.319A6.029,6.029,0,0,0,536.9,360.29Z" transform="translate(374.5 -15.625)" fill="#3f3d56"/>
|
||||
<path id="Path_453" data-name="Path 453" d="M768.171,669.128s-.578,1.377-1.644,3.372a44.83,44.83,0,0,1-5.847,8.556l-.175.188h-1.052l-37.511.344h-.9l-.11-.13-7.276-8.634-2.741-3.255-.11-.136-.234-.273-.13-.156-.169-.2-5.334-6.328,1.949-8.121,3.32-13.682.084-.331.65-2.6.461-1.949,1.546-6.354,5.3-21.861,9.42,4.872,9.933,17.541,5.334,9.42h2.923c.552,0,1.819,1.065,3.092,2.293.455.435.91.9,1.3,1.3,1.156,1.2,2.072,2.222,2.072,2.222s4.736.1,6.009,3.833a4.537,4.537,0,0,1,.162.552,26.191,26.191,0,0,0,1.644,4.262c.838,1.826,1.891,3.9,2.956,5.97,1.981,3.8,4,7.419,4.736,8.731l.091.162Z" transform="translate(305.476 -105.217)" fill="#00ddf1"/>
|
||||
<path id="Path_454" data-name="Path 454" d="M615.681,653.093l-.065.929v.065l-.11,1.494-.864,12.025L613.6,682.093l-.3,4.08-.65,8.731v.11l-69.513.65L429.061,696.9v.546h-.091v-.728l.292-9.329.559-18.229L430.3,653.7l.045-1.54a6.841,6.841,0,0,1,7.419-6.607c3.066.26,7.049.578,11.811.916l1.949.143c27.5,1.91,78.322,4.379,129.606,1.3,1.078-.059,2.163-.123,3.248-.2,7.952-.5,15.9-1.143,23.764-1.949a6.841,6.841,0,0,1,7.536,7.3Z" transform="translate(402.161 -118.514)" opacity="0.1"/>
|
||||
<path id="Path_455" data-name="Path 455" d="M429.08,698.857v-.546l114.092-1.241,69.513-.65h.195v-.11l.65-8.731.279-3.9,1.065-14.689.838-11.629v-.286a6.893,6.893,0,0,0-.071-1.6.2.2,0,0,0,0-.065,6.751,6.751,0,0,0-1.3-3.014,6.847,6.847,0,0,0-6.12-2.6c-7.867.806-15.813,1.449-23.764,1.949-1.085.078-2.17.143-3.248.2-51.284,3.047-102.106.578-129.606-1.3l-1.949-.143c-4.762-.338-8.744-.65-11.811-.916a6.847,6.847,0,0,0-7.3,5.542,7.146,7.146,0,0,0-.117,1.065l-.442,14.4-.6,18.236-.286,9.329v.728Z" transform="translate(402.122 -119.926)" fill="#3f3d56"/>
|
||||
<path id="Path_456" data-name="Path 456" d="M615.925,661.692l-.8,9.959L613.937,686.5l-.3,3.716-.65,8.322-.039.416v.11h0l-.1.961.071-.961h-.188l-69.513.65L429.11,700.944v-.182l.318-9.329.65-18.229.416-12.116a4.132,4.132,0,0,1,1.988-3.411,4.061,4.061,0,0,1,2.456-.585c1.364.11,3.04.24,5,.383,3.177.234,7.094.507,11.655.793,8.445.533,19.041,1.1,31.183,1.572,33.782,1.3,79.388,1.735,122.707-2.059,1.949-.169,3.963-.357,5.931-.546a4.066,4.066,0,0,1,2.261.429,3.346,3.346,0,0,1,.559.351,3.989,3.989,0,0,1,1.3,1.6,2.315,2.315,0,0,1,.162.4A4.08,4.08,0,0,1,615.925,661.692Z" transform="translate(402.112 -122.559)" opacity="0.1"/>
|
||||
<path id="Path_457" data-name="Path 457" d="M360.914,677.87v2.072a3.982,3.982,0,0,1-2.274,3.6l-26.051.5a3.976,3.976,0,0,1-2.319-3.619v-1.949Z" transform="translate(436.74 -129.843)" opacity="0.1"/>
|
||||
<path id="Path_458" data-name="Path 458" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" fill="#3f3d56"/>
|
||||
<path id="Path_459" data-name="Path 459" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" opacity="0.1"/>
|
||||
<path id="Path_460" data-name="Path 460" d="M282.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(516.984 42.387)" fill="#3f3d56"/>
|
||||
<path id="Path_461" data-name="Path 461" d="M552.228,360.039A6.334,6.334,0,0,1,551,357.22a6.165,6.165,0,0,0,.273,2.988A5.847,5.847,0,0,0,552.228,360.039Z" transform="translate(359.434 -17.505)"/>
|
||||
<path id="Path_462" data-name="Path 462" d="M540.1,366.9a22.83,22.83,0,0,1-.24-2.4c0,.8,0,1.6,0,2.41Z" transform="translate(363.311 -20.056)"/>
|
||||
<path id="Path_463" data-name="Path 463" d="M621.706,398.627a11.912,11.912,0,0,1,.234-1.722,11.7,11.7,0,0,0,.52-3.047,4.008,4.008,0,0,0-.1-.689c-.11.6-.286,1.2-.416,1.787a12.433,12.433,0,0,0-.234,3.671Z" transform="translate(334.652 -30.1)"/>
|
||||
<path id="Path_464" data-name="Path 464" d="M600.922,368.894l.708.513a12.8,12.8,0,0,0,1.67-1.442,2.384,2.384,0,0,0,.247-2.365,1.95,1.95,0,0,1-.247.416C602.5,367.042,600.734,367.607,600.922,368.894Z" transform="translate(341.923 -20.441)"/>
|
||||
<path id="Path_465" data-name="Path 465" d="M518.455,380.29a8,8,0,0,0-.565,1.481,11.8,11.8,0,0,1,3.248-1.228Z" transform="translate(371.008 -25.587)"/>
|
||||
<path id="Path_466" data-name="Path 466" d="M531.888,368.979a9.637,9.637,0,0,1-1.221-.8,2.758,2.758,0,0,0,.058.767,7.7,7.7,0,0,1,1.163.033Z" transform="translate(366.534 -21.345)"/>
|
||||
<path id="Path_467" data-name="Path 467" d="M587.579,428.33c-.266,3.151-1.735,6.185-2.923,9.173a87.2,87.2,0,0,0-4.736,18.717c-.9,5.71-1.949,12.194-6.821,15.358a19.488,19.488,0,0,1-4.437,1.949c-7,2.423-14.065,4.853-21.439,5.736s-15.111.11-21.543-3.567a12.993,12.993,0,0,1-4.664-4.112c-1.735-2.748-1.767-6.191-2.6-9.336a53.424,53.424,0,0,0-2.6-6.834c-1.436-3.5-2.54-7.146-3.638-10.739q-1.527-5.009-3.047-10.024a18.716,18.716,0,0,1-.741-3.086,11.644,11.644,0,0,0,.741,5.035q1.52,5.022,3.047,10.024c1.1,3.619,2.2,7.244,3.638,10.739a53.417,53.417,0,0,1,2.6,6.834c.819,3.144.851,6.587,2.6,9.336a12.993,12.993,0,0,0,4.664,4.112c6.432,3.677,14.188,4.45,21.543,3.567s14.409-3.313,21.439-5.736a19.487,19.487,0,0,0,4.437-1.949c4.84-3.164,5.918-9.647,6.821-15.358a87.2,87.2,0,0,1,4.736-18.717c1.416-3.56,3.248-7.179,2.936-11.005C587.592,428.408,587.579,428.369,587.579,428.33Z" transform="translate(374.359 -42.418)"/>
|
||||
<path id="Path_468" data-name="Path 468" d="M510.318,421.419v-.071a9.1,9.1,0,0,0-.3-3.411,17.272,17.272,0,0,1-.253-2.157,17.769,17.769,0,0,0,.253,4.106C510.123,420.393,510.24,420.906,510.318,421.419Z" transform="translate(373.864 -38.021)"/>
|
||||
<path id="Path_469" data-name="Path 469" d="M512.452,397.929a18.02,18.02,0,0,1,1.7-2.384c-1.189,1.137-3.385.123-3.846-1.455a3.788,3.788,0,0,0,0,1.949,2.852,2.852,0,0,0,2.15,1.89Z" transform="translate(373.712 -30.422)"/>
|
||||
<path id="Path_470" data-name="Path 470" d="M762.946,656.57s2.059,7.471,0,13.968S762.946,656.57,762.946,656.57Z" transform="translate(285.475 -122.381)" opacity="0.1"/>
|
||||
<path id="Path_471" data-name="Path 471" d="M776,668.3s.864,9.485-1.085,11.168S776,668.3,776,668.3Z" transform="translate(281.189 -126.49)" opacity="0.1"/>
|
||||
<line id="Line_88" data-name="Line 88" y1="0.65" x2="66.011" transform="translate(733.267 442.068)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<line id="Line_89" data-name="Line 89" y1="0.643" x2="66.011" transform="translate(733.267 450.176)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<line id="Line_90" data-name="Line 90" y1="0.65" x2="66.011" transform="translate(733.267 458.277)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="691.365" height="586.346" viewBox="0 0 691.365 586.346">
|
||||
<defs>
|
||||
<linearGradient id="linear-gradient" x1="0.5" y1="1" x2="0.5" y2="-0.001" gradientUnits="objectBoundingBox">
|
||||
<stop offset="0" stop-color="gray" stop-opacity="0.251"/>
|
||||
<stop offset="0.54" stop-color="gray" stop-opacity="0.122"/>
|
||||
<stop offset="1" stop-color="gray" stop-opacity="0.102"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="Group_644" data-name="Group 644" transform="translate(-588.635 -80)">
|
||||
<g id="Group_642" data-name="Group 642" transform="translate(22538.635 6999)">
|
||||
<path id="Path_472" data-name="Path 472" d="M482.764,637.675c51.612-29.22,67.336-29.693,124.954-17.169,81.646,17.758,88.43-49.93,108.306-186.933,33.946-233.98-108.306-295.737-108.306-295.737s-94.6-8.1-150.536-38.963C377.726,55.027,286.9,49.443,207.046,86.542c-51.069,23.7-93.125,56.714-100.31,102.034-20.09,126.746,12.9,196.58-48.819,237.954S48.944,581.5,96.263,577.564c34.968-2.906,80.842,52.415,130.687,60.111,17.382,2.684,88.7-13.043,99.578,0C341.327,655.4,464.763,647.87,482.764,637.675Z" transform="translate(-21979.895 -6980.982)" fill="#00ddf1" opacity="0.1"/>
|
||||
<g id="Group_641" data-name="Group 641" transform="translate(-21545.08 -6816.88)">
|
||||
<rect id="Rectangle_263" data-name="Rectangle 263" width="217.867" height="15.073" transform="translate(0 39.052)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_264" data-name="Rectangle 264" width="217.867" height="15.073" transform="translate(0 104.137)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_265" data-name="Rectangle 265" width="217.867" height="15.073" transform="translate(0 171.964)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_266" data-name="Rectangle 266" width="13.702" height="39.052" transform="translate(19.183)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_267" data-name="Rectangle 267" width="13.702" height="2.74" transform="translate(19.183 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_268" data-name="Rectangle 268" width="13.702" height="2.74" transform="translate(19.183 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_269" data-name="Rectangle 269" width="13.702" height="39.052" transform="translate(39.737)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_270" data-name="Rectangle 270" width="13.702" height="2.74" transform="translate(39.737 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_271" data-name="Rectangle 271" width="13.702" height="2.74" transform="translate(39.737 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_272" data-name="Rectangle 272" width="13.702" height="39.052" transform="translate(60.29)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_273" data-name="Rectangle 273" width="13.702" height="2.74" transform="translate(60.29 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_274" data-name="Rectangle 274" width="13.702" height="2.74" transform="translate(60.29 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_275" data-name="Rectangle 275" width="13.702" height="39.052" transform="translate(80.844)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_276" data-name="Rectangle 276" width="13.702" height="2.74" transform="translate(80.844 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_277" data-name="Rectangle 277" width="13.702" height="2.74" transform="translate(80.844 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_278" data-name="Rectangle 278" width="13.702" height="39.052" transform="translate(101.397)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_279" data-name="Rectangle 279" width="13.702" height="2.74" transform="translate(101.397 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_280" data-name="Rectangle 280" width="13.702" height="2.74" transform="translate(101.397 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_281" data-name="Rectangle 281" width="13.702" height="39.052" transform="translate(121.95)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_282" data-name="Rectangle 282" width="13.702" height="2.74" transform="translate(121.95 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_283" data-name="Rectangle 283" width="13.702" height="2.74" transform="translate(121.95 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_284" data-name="Rectangle 284" width="13.702" height="39.052" transform="translate(142.504)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_285" data-name="Rectangle 285" width="13.702" height="2.74" transform="translate(142.504 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_286" data-name="Rectangle 286" width="13.702" height="2.74" transform="translate(142.504 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_287" data-name="Rectangle 287" width="13.702" height="39.052" transform="matrix(0.894, -0.448, 0.448, 0.894, 135.847, 138.025)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_288" data-name="Rectangle 288" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 138.614, 143.541)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_289" data-name="Rectangle 289" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 148.128, 162.529)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_290" data-name="Rectangle 290" width="13.702" height="39.052" transform="translate(19.183 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_291" data-name="Rectangle 291" width="13.702" height="2.74" transform="translate(19.183 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_292" data-name="Rectangle 292" width="13.702" height="2.74" transform="translate(19.183 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_293" data-name="Rectangle 293" width="13.702" height="39.052" transform="translate(39.737 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_294" data-name="Rectangle 294" width="13.702" height="2.74" transform="translate(39.737 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_295" data-name="Rectangle 295" width="13.702" height="2.74" transform="translate(39.737 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_296" data-name="Rectangle 296" width="13.702" height="39.052" transform="translate(60.29 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_297" data-name="Rectangle 297" width="13.702" height="2.74" transform="translate(60.29 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_298" data-name="Rectangle 298" width="13.702" height="2.74" transform="translate(60.29 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_299" data-name="Rectangle 299" width="13.702" height="39.052" transform="translate(145.93 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_300" data-name="Rectangle 300" width="13.702" height="2.74" transform="translate(145.93 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_301" data-name="Rectangle 301" width="13.702" height="2.74" transform="translate(145.93 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_302" data-name="Rectangle 302" width="13.702" height="39.052" transform="translate(166.483 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_303" data-name="Rectangle 303" width="13.702" height="2.74" transform="translate(166.483 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_304" data-name="Rectangle 304" width="13.702" height="2.74" transform="translate(166.483 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_305" data-name="Rectangle 305" width="13.702" height="39.052" transform="translate(187.036 65.771)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_306" data-name="Rectangle 306" width="13.702" height="2.74" transform="translate(187.036 71.937)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_307" data-name="Rectangle 307" width="13.702" height="2.74" transform="translate(187.036 93.176)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_308" data-name="Rectangle 308" width="13.702" height="39.052" transform="translate(19.183 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_309" data-name="Rectangle 309" width="13.702" height="2.74" transform="translate(19.183 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_310" data-name="Rectangle 310" width="13.702" height="2.74" transform="translate(19.183 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_311" data-name="Rectangle 311" width="13.702" height="39.052" transform="translate(39.737 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_312" data-name="Rectangle 312" width="13.702" height="2.74" transform="translate(39.737 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_313" data-name="Rectangle 313" width="13.702" height="2.74" transform="translate(39.737 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_314" data-name="Rectangle 314" width="13.702" height="39.052" transform="translate(60.29 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_315" data-name="Rectangle 315" width="13.702" height="2.74" transform="translate(60.29 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_316" data-name="Rectangle 316" width="13.702" height="2.74" transform="translate(60.29 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_317" data-name="Rectangle 317" width="13.702" height="39.052" transform="translate(80.844 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_318" data-name="Rectangle 318" width="13.702" height="2.74" transform="translate(80.844 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_319" data-name="Rectangle 319" width="13.702" height="2.74" transform="translate(80.844 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_320" data-name="Rectangle 320" width="13.702" height="39.052" transform="translate(101.397 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_321" data-name="Rectangle 321" width="13.702" height="2.74" transform="translate(101.397 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_322" data-name="Rectangle 322" width="13.702" height="2.74" transform="translate(101.397 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_323" data-name="Rectangle 323" width="13.702" height="39.052" transform="translate(121.95 132.912)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_324" data-name="Rectangle 324" width="13.702" height="2.74" transform="translate(121.95 139.078)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_325" data-name="Rectangle 325" width="13.702" height="2.74" transform="translate(121.95 160.317)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_473" data-name="Path 473" d="M797.862,159.09v35.571h-13.7V156.48Q791.039,157.693,797.862,159.09Z" transform="translate(-621.103 -155.61)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_326" data-name="Rectangle 326" width="13.702" height="2.74" transform="translate(163.057 6.166)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_327" data-name="Rectangle 327" width="13.702" height="2.74" transform="translate(163.057 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_474" data-name="Path 474" d="M827.862,165.719v30.83h-13.7V162.43c1.781.391,3.556.8,5.33,1.22Q823.7,164.636,827.862,165.719Z" transform="translate(-630.549 -157.483)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_475" data-name="Path 475" d="M827.862,166.285v.685h-13.7v-2.74h5.33Q823.7,165.2,827.862,166.285Z" transform="translate(-630.549 -158.05)" fill="#00ddf1" opacity="0.1"/>
|
||||
<rect id="Rectangle_328" data-name="Rectangle 328" width="13.702" height="2.74" transform="translate(183.611 27.405)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_476" data-name="Path 476" d="M691.358,287.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.179c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-589.693 -196.649)" fill="#00ddf1"/>
|
||||
<path id="Path_477" data-name="Path 477" d="M686.2,296.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.277,10.277,0,0,1,686.2,296.239Z" transform="translate(-589.706 -196.595)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_478" data-name="Path 478" d="M691.42,290.06s1.542,1.213,5.282,0" transform="translate(-591.9 -197.672)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_479" data-name="Path 479" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1"/>
|
||||
<path id="Path_480" data-name="Path 480" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_481" data-name="Path 481" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1"/>
|
||||
<path id="Path_482" data-name="Path 482" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_483" data-name="Path 483" d="M694.75,283.958s1.089-5.207,8.16-5.878" transform="translate(-592.949 -193.9)" fill="#00ddf1"/>
|
||||
<path id="Path_484" data-name="Path 484" d="M819.358,386.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.18c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-629.999 -227.823)" fill="#00ddf1"/>
|
||||
<path id="Path_485" data-name="Path 485" d="M814.2,395.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.276,10.276,0,0,1,814.2,395.239Z" transform="translate(-630.011 -227.769)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_486" data-name="Path 486" d="M819.42,389.06s1.542,1.213,5.282,0" transform="translate(-632.206 -228.846)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_487" data-name="Path 487" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1"/>
|
||||
<path id="Path_488" data-name="Path 488" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1" opacity="0.2"/>
|
||||
<path id="Path_489" data-name="Path 489" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1"/>
|
||||
<path id="Path_490" data-name="Path 490" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1" opacity="0.1"/>
|
||||
<path id="Path_491" data-name="Path 491" d="M822.75,382.958s1.089-5.207,8.16-5.878" transform="translate(-633.254 -225.073)" fill="#00ddf1"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Group_643" data-name="Group 643" transform="translate(7 -63)">
|
||||
<g id="Group_639" data-name="Group 639" transform="translate(21233 8372)">
|
||||
<ellipse id="Ellipse_73" data-name="Ellipse 73" cx="49.851" cy="8.007" rx="49.851" ry="8.007" transform="translate(-20118.443 -7772.555)" fill="#00abf6" opacity="0.1"/>
|
||||
<rect id="Rectangle_182" data-name="Rectangle 182" width="3.208" height="72.224" transform="translate(-20070.199 -7917.934)" fill="#535461"/>
|
||||
<path id="Path_268" data-name="Path 268" d="M1025.906,587.251l-.2,3.254-.287,4.6-.118,1.913-.281,4.6-.123,1.913-.286,4.6-3.233,52.22a8.851,8.851,0,0,1-8.83,8.268H995.731a8.841,8.841,0,0,1-8.82-8.3l-3.244-52.225-.281-4.6-.117-1.913-.292-4.6-.117-1.913-.281-4.6-.2-3.254a4.6,4.6,0,0,1,4.574-4.865h34.391a4.6,4.6,0,0,1,4.569,4.9Z" transform="translate(-21072.734 -8433.181)" fill="#565661"/>
|
||||
<path id="Path_269" data-name="Path 269" d="M990.949,517.36l-.287,4.589H948.106l-.286-4.589Z" transform="translate(-21037.977 -8360.036)" fill="#9d9cb5"/>
|
||||
<path id="Path_270" data-name="Path 270" d="M990.93,530.08l-.281,4.594H948.9l-.286-4.594Z" transform="translate(-21038.363 -8366.248)" fill="#9d9cb5"/>
|
||||
<path id="Path_271" data-name="Path 271" d="M990.912,542.8l-.287,4.589H949.686L949.4,542.8Z" transform="translate(-21038.748 -8372.46)" fill="#9d9cb5"/>
|
||||
<path id="Path_272" data-name="Path 272" d="M933.875,490.464c18.684,13.517,26.466,34.656,26.466,34.656s-22.511-.773-41.2-14.289-26.46-34.651-26.46-34.651S915.2,476.953,933.875,490.464Z" transform="translate(-21028.934 -8381.328)" fill="#00abf6"/>
|
||||
<path id="Path_273" data-name="Path 273" d="M892.68,476.18s23.365,12.969,30.865,23.708,36.8,25.233,36.8,25.233" transform="translate(-21028.934 -8381.328)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path id="Path_274" data-name="Path 274" d="M975.393,443.457c10.171,7.352,14.407,18.863,14.407,18.863s-12.279-.425-22.424-7.776S953,435.68,953,435.68,965.227,436.1,975.393,443.457Z" transform="translate(-21058.393 -8361.549)" fill="#00abf6"/>
|
||||
<path id="Path_275" data-name="Path 275" d="M953,435.68s12.719,7.06,16.8,12.9,20.03,13.737,20.03,13.737" transform="translate(-21058.393 -8361.549)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path id="Path_276" data-name="Path 276" d="M1040.3,470.483c-11.68,12.836-14.151,29.612-14.151,29.612s16.469-4.042,28.139-16.883,14.151-29.612,14.151-29.612S1051.981,457.647,1040.3,470.483Z" transform="translate(-21094.119 -8370.301)" fill="#00abf6"/>
|
||||
<path id="Path_277" data-name="Path 277" d="M1068.45,453.61s-15.215,13.159-19.083,22.224S1026.15,500.1,1026.15,500.1" transform="translate(-21094.119 -8370.306)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
|
||||
</g>
|
||||
<g id="Group_638" data-name="Group 638" transform="translate(-40 36)">
|
||||
<rect id="Rectangle_254" data-name="Rectangle 254" width="12.343" height="37.459" transform="translate(741.459 392.675)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_255" data-name="Rectangle 255" width="12.343" height="39.629" transform="translate(1073.647 390.726)" fill="#3f3d56"/>
|
||||
<path id="Path_418" data-name="Path 418" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" fill="#65617d"/>
|
||||
<path id="Path_419" data-name="Path 419" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" opacity="0.2"/>
|
||||
<path id="Path_420" data-name="Path 420" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" fill="#3f3d56"/>
|
||||
<path id="Path_421" data-name="Path 421" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" opacity="0.1"/>
|
||||
<path id="Path_422" data-name="Path 422" d="M360.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(436.74 -122.381)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_256" data-name="Rectangle 256" width="30.644" height="20.464" transform="translate(1046.362 534.189)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_257" data-name="Rectangle 257" width="30.644" height="20.464" transform="translate(1046.362 534.189)" opacity="0.1"/>
|
||||
<rect id="Rectangle_258" data-name="Rectangle 258" width="30.644" height="20.464" transform="translate(1045.062 534.189)" fill="#3f3d56"/>
|
||||
<path id="Path_423" data-name="Path 423" d="M223.943,265.82v3.723l-12.343-.318v-3.4Z" transform="translate(529.859 126.848)" opacity="0.1"/>
|
||||
<path id="Path_424" data-name="Path 424" d="M358.741,271.246,343.909,377.789a2.794,2.794,0,0,1-2.839,2.41L222.314,377.12l-2.254-.058a2.794,2.794,0,0,1-2.7-3.164l16.651-111.533a2.794,2.794,0,0,1,2.949-2.358l119.186,8.069a2.787,2.787,0,0,1,2.6,3.17Z" transform="translate(476.307 16.556)" fill="#65617d"/>
|
||||
<path id="Path_425" data-name="Path 425" d="M169.158,99.16,156.6,197.472l109.142,3.463,13.422-94.85Z" transform="translate(549.128 185.237)" fill="#00ddf1"/>
|
||||
<rect id="Rectangle_259" data-name="Rectangle 259" width="132.965" height="112.605" rx="5.33" transform="translate(853.849 282.877)" fill="#65617d"/>
|
||||
<rect id="Rectangle_260" data-name="Rectangle 260" width="120.836" height="93.765" transform="translate(860.125 290.458)" fill="#00ddf1"/>
|
||||
<path id="Path_426" data-name="Path 426" d="M735.273,262.82v2.462l-12.343.253V262.82Z" transform="translate(350.717 127.899)" opacity="0.1"/>
|
||||
<path id="Path_427" data-name="Path 427" d="M833.626,371a2,2,0,0,1-1.949,2.131l-121.615,2.514a2,2,0,0,1-2.033-1.813L697.575,265.156a2,2,0,0,1,1.9-2.189l120.693-6,1.3-.065a1.994,1.994,0,0,1,2.092,1.819Z" transform="translate(308.06 17.643)" fill="#65617d"/>
|
||||
<path id="Path_428" data-name="Path 428" d="M628.93,100.238,739.157,94.82l9.31,98.748-109.356,2.6Z" transform="translate(383.65 186.757)" fill="#00ddf1"/>
|
||||
<path id="Path_429" data-name="Path 429" d="M899.741,371a2,2,0,0,1-1.949,2.131L886.28,256.963l1.3-.065a2,2,0,0,1,2.092,1.819Z" transform="translate(241.945 17.643)" opacity="0.1"/>
|
||||
<rect id="Rectangle_261" data-name="Rectangle 261" width="12.343" height="11.096" transform="translate(741.459 419.038)" opacity="0.1"/>
|
||||
<path id="Path_431" data-name="Path 431" d="M281.574,483.65v3.229l-30.644.819V483.65Z" transform="translate(516.08 50.533)" opacity="0.1"/>
|
||||
<path id="Path_432" data-name="Path 432" d="M390.8,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h-.033a2.6,2.6,0,0,1-2.566-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H388.23A2.6,2.6,0,0,1,390.8,483.886Z" transform="translate(460.942 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_434" data-name="Path 434" d="M194.6,319.15H309.914v96.311L194.6,418.06Z" transform="translate(535.815 108.164)" fill="#d4dfec"/>
|
||||
<rect id="Rectangle_262" data-name="Rectangle 262" width="12.343" height="11.317" transform="translate(1073.647 419.038)" opacity="0.1"/>
|
||||
<path id="Path_435" data-name="Path 435" d="M709.574,483.65v4.164L678.93,487V483.65Z" transform="translate(366.132 50.533)" opacity="0.1"/>
|
||||
<path id="Path_436" data-name="Path 436" d="M797.782,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0l-124.532-3.313a2.6,2.6,0,0,1-2.5-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H795.19a2.6,2.6,0,0,1,2.592,2.566Z" transform="translate(318.365 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_437" data-name="Path 437" d="M866.289,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0V481.32h0a2.6,2.6,0,0,1,2.6,2.566Z" transform="translate(249.859 -60.983)" opacity="0.1"/>
|
||||
<path id="Path_438" data-name="Path 438" d="M713.414,319.15H598.1v96.311l115.314,2.6Z" transform="translate(394.451 108.164)" fill="#d4dfec"/>
|
||||
<path id="Path_439" data-name="Path 439" d="M593.3,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h0a2.6,2.6,0,0,1-2.6-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H590.73A2.6,2.6,0,0,1,593.3,483.886Z" transform="translate(389.997 -60.983)" fill="#65617d"/>
|
||||
<path id="Path_440" data-name="Path 440" d="M397.1,319.15H512.414v96.311L397.1,418.06Z" transform="translate(464.87 108.164)" fill="#d4dfec"/>
|
||||
<path id="Path_441" data-name="Path 441" d="M597.889,533.65V542.1l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643-114.086,1.059h-.1l-29.007.273-.312.007L106.1,546.643V536.9l131.562-.864h.312l26.629-.182h.123l183.9-1.215h.188l.11-.006h.305l3.112-.019h.948l48.393-.325,2.858-.013.637-.007Z" transform="translate(566.82 33.015)" opacity="0.1"/>
|
||||
<circle id="Ellipse_99" data-name="Ellipse 99" cx="33.347" cy="33.347" r="33.347" transform="translate(887.196 363.44)" fill="#fbbebe"/>
|
||||
<path id="Path_442" data-name="Path 442" d="M383.74,592.973a.806.806,0,0,1,.065-.227c.65-2.332,1.806-6.276,2.67-9.427.11-.39.214-.773.312-1.137.955-3.554,2.813-11.876,3.846-17.066.364-1.826.65-3.248.708-3.963.13-1.1,2.085-5.795,4.3-11.194,1.553-3.794,3.248-7.939,4.489-11.434a39.876,39.876,0,0,0,1.9-6.4,3.313,3.313,0,0,0,0-1.384,9.543,9.543,0,0,1-.312-2.274h0c-.149-4.028,1.052-10.394,2.91-13.065,2.274-3.248,3.9-15.371,3.9-15.371s3.365.487,3.9-2.293a4,4,0,0,0,.065-.91,4.815,4.815,0,0,0-.058-.721c-.578-4.054,9.563-8.368,15.267-9.3a12.769,12.769,0,0,1,1.949-.182,17.694,17.694,0,0,0,4.06-.935q.909-.279,1.949-.65c2.391-.806,5.249-1.871,8.283-3.04,11.408-4.424,25.22-10.394,25.22-10.394.1-.461.208-.9.331-1.338a18,18,0,0,1,10.622-12.3h0v-1.3c0-2.241-.1-4.548-.195-6.847,0-.448-.039-.9-.065-1.345v-.734c0-.455-.046-.91-.071-1.358l-.117-.091a10.66,10.66,0,0,1-2.534-2.729,9.916,9.916,0,0,1-.923-1.949c-.812-2.2-1-4.658-1.546-6.984l-.117-.481a54.579,54.579,0,0,0-2.6-6.873c-1.436-3.521-2.54-7.146-3.638-10.817q-1-3.333-2.02-6.678l-1.026-3.4c-.195-.65-.39-1.3-.539-1.949h0a8.627,8.627,0,0,1-.169-.91h0a6.5,6.5,0,0,1,.156-2.995,20.854,20.854,0,0,1,.858-1.949,5.113,5.113,0,0,0,.325-.9h0a1.947,1.947,0,0,0,.052-.208,3.667,3.667,0,0,0,.071-.7,11.993,11.993,0,0,0-.338-2.527.045.045,0,0,1,0-.032,1.661,1.661,0,0,0-.032-.175,16.564,16.564,0,0,1-.234-1.949,18.483,18.483,0,0,1,2.254-10.115l.123-.234a16.383,16.383,0,0,1,1.2-1.806h0a9.122,9.122,0,0,1,.65-.793,2.594,2.594,0,0,1-3.606-.936,2.475,2.475,0,0,1-.149-.292c-.033-.078-.059-.162-.085-.247-.455-1.592.338-3.287,1.377-4.58a12.012,12.012,0,0,1,3.729-3.053h0a2.51,2.51,0,0,1,.26-.136,11.125,11.125,0,0,1,1.715-.689h0a9.781,9.781,0,0,1,1.059-.286l-2.41-.221-.974-.091A9.414,9.414,0,0,1,479,361.527h.7a7.625,7.625,0,0,1,1.02.084A2.885,2.885,0,0,1,479,358.8a9.362,9.362,0,0,0,5.97,1.988h.247a19.494,19.494,0,0,1-.221-2.124h0a21.545,21.545,0,0,1,0-2.9,5.483,5.483,0,0,0,4.4,1.468l-1.351-1.884a6.165,6.165,0,0,0,4.262,1.332l.312-.033a4.547,4.547,0,0,0,.773-.149h.058c-.123-.156-.234-.318-.344-.487l-.039-.078a6.282,6.282,0,0,1-.65-1.358,4.284,4.284,0,0,1-.156-.565h0a5.684,5.684,0,0,1,.292-3.541c2.1,1.754,4.4,4.151,7.146,2.339s2.839-5.139,7.146-4.8a6.626,6.626,0,0,1,5.464,3.976,4.385,4.385,0,0,1,4.983-1.137,4.45,4.45,0,0,1,2.521,4.476c1.786-.448,3.71-.89,5.425-.214s2.82,2.865,1.845,4.327a.931.931,0,0,1-.091.13c-.8,1.033-2.566,1.6-2.378,2.9l.273.195.845.611q4.97,3.586,9.68,7.536c1.611,1.351,3.326,2.975,3.437,5.087a5.617,5.617,0,0,1-.156,1.54h0c-.1.507-.247,1.013-.357,1.52a12.676,12.676,0,0,0-.221,3.93h0c.045.383.1.76.175,1.143.994,5.022,5.009,9.167,5.405,14.292v.364a11.546,11.546,0,0,1-.045,1.644c-.3,3.118-1.741,6.113-2.91,9.1-.072.182-.143.364-.208.546a90.5,90.5,0,0,0-4.548,18.294,65.14,65.14,0,0,1-1.3,6.555,17.365,17.365,0,0,1-3.307,6.776,10.536,10.536,0,0,1-2.183,1.949,12.132,12.132,0,0,1-1.722.916v.039c.071.4.13.793.2,1.189.039.208.071.416.11.65h0a.089.089,0,0,0,0,.039c.546,3.073,1.169,6.081,1.826,8.939l.331,1.423c8.186,2.956,9.154,9.011,9.212,11.33a6.259,6.259,0,0,1,0,.929s12.343.325,17.216,3.9a13.21,13.21,0,0,0,2.93,1.494,53.917,53.917,0,0,0,6.009,1.877c1.046.273,2.137.539,3.248.8,1.949.448,4.015.884,6.009,1.3,5.321,1.072,10.33,1.949,12.87,2.6,6.061,1.533,19.925,14.825,19.925,14.825s1.624,13.409,3.248,15.364,4.548,8.829,4.548,8.829l10.745,19.106,4.548,8.036h2.923a4.97,4.97,0,0,1,2.085,1.364c.773.689,1.631,1.546,2.384,2.326l1.247,1.332.78.864s4.723.117,6,3.852c3.846-.11,8.569.143,11.876,1.624a9.187,9.187,0,0,1,1.3.7,5.639,5.639,0,0,1,2.683,3.677,5.422,5.422,0,0,1,.117.935c0,.37,0,.741,0,1.1-.234,4.918-3.437,9.011-6.38,11.765l.208.37s-.1.234-.286.65c-.052.123-.117.26-.188.416h0c-.383.819-.994,2.046-1.793,3.482a41.136,41.136,0,0,1-6.217,8.556l-.188.195-37.511.344-.11-.13-7.276-8.679-3.619-4.314-.11-.136-.1,1.033-.351,3.443-.845,8.322-.045.416v.11l-.1.961.071-.961h-.188l-69.513.65-114.092,1.26v.546h-.091v-.546l-29.072.312Z" transform="translate(418.007 -14.269)" fill="url(#linear-gradient)"/>
|
||||
<path id="Path_443" data-name="Path 443" d="M586.185,511.522s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.691-.039-2.891-.143-5.886-.292-8.868-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522c-2.6,8.53-1.748,20.51.227,31.755.578,3.281,1.247,6.5,1.949,9.55a223.892,223.892,0,0,0,6.269,22.036Z" transform="translate(367.711 -46.521)" fill="#fbbebe"/>
|
||||
<path id="Path_444" data-name="Path 444" d="M586.185,537.354s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.692,9.855-4.21,22.394-1.072,22.394-1.072,13.2-2.631,21.666-2.118,27.078-.273A223.888,223.888,0,0,0,586.185,537.354Z" transform="translate(367.711 -72.353)" opacity="0.1"/>
|
||||
<path id="Path_445" data-name="Path 445" d="M413.346,651.973v-.546l114.092-1.241,69.513-.65h.195l-.071.961.1-.961h.123v-.11l.89-8.738.338-3.307.1-1.033.091-.864,4.015-13.389,4.015-13.37,3.378-11.258,5.158-17.2s-2.923-6.821-4.548-8.77-3.248-15.267-3.248-15.267-13.864-13.208-19.925-14.728c-2.54-.65-7.555-1.514-12.883-2.6-1.994-.4-4.041-.832-6.009-1.3-1.111-.253-2.2-.52-3.248-.786a57.086,57.086,0,0,1-6.009-1.858,13.448,13.448,0,0,1-2.924-1.488c-4.775-3.5-16.742-3.9-17.2-3.9h0a.269.269,0,0,1,0-.045v-.058a6.66,6.66,0,0,0,0-.819,5.208,5.208,0,0,0-.059-.91.82.82,0,0,0-.045-.325c-.032-.234-.071-.481-.13-.741a3.226,3.226,0,0,0-.078-.344,11.181,11.181,0,0,0-.812-2.267c-.045-.1-.1-.214-.156-.318a11.861,11.861,0,0,0-1.1-1.709c-.084-.11-.175-.227-.273-.338a12.721,12.721,0,0,0-1.767-1.715c-.175-.136-.357-.279-.546-.409a3.026,3.026,0,0,0-.331-.234l-.578-.377-.539-.312c-.474-.26-.974-.513-1.52-.754-.169-.078-.351-.149-.533-.221s-.468-.182-.708-.273h-.039l-.487-.195c-.37-.123-.747-.234-1.143-.344l-.6-.156-.65-.149-.65-.136c-.65-.13-1.345-.247-2.072-.338l-.741-.091-.76-.078-.786-.065-1.117-.065-1.3-.045a81.017,81.017,0,0,0-17.054,1.814,46.946,46.946,0,0,0-11.855-1.093h-.565l-.65.039h-.416l-.838.078a6.224,6.224,0,0,0-.845.1c-.279.039-.572.071-.851.117l-.429.071c-.422.071-.851.149-1.3.24q-.214.039-.429.1c-.565.123-1.13.273-1.689.442-.279.078-.559.169-.838.266-.143.039-.279.091-.416.143l-.416.149-.409.162-.487.195c-.247.1-.487.214-.728.331-.136.058-.273.123-.4.195s-.266.13-.39.2a18.03,18.03,0,0,0-4.034,2.846l-.331.318-.325.331c-.11.11-.214.227-.318.344a15.473,15.473,0,0,0-1.182,1.462c-.1.136-.2.273-.3.416a17.335,17.335,0,0,0-1.117,1.9c-.084.156-.162.318-.24.474a21.243,21.243,0,0,0-1.3,3.411q-.182.65-.331,1.3s-13.753,6.068-25.161,10.46c-3.034,1.169-5.892,2.228-8.283,3.027-.7.234-1.345.448-1.949.65a17.347,17.347,0,0,1-4.06.935,12.778,12.778,0,0,0-1.949.182c-5.243.851-14.227,4.548-15.208,8.257a2.4,2.4,0,0,0-.065.91v.071c.65,4.548-3.9,3.9-3.9,3.9s-1.624,12.019-3.9,15.267c-1.644,2.339-2.768,7.555-2.91,11.492h0a12.741,12.741,0,0,0,.286,3.658v.091c.26.871-.364,3.248-1.423,6.3-1.15,3.346-2.826,7.5-4.437,11.427-2.43,5.925-4.716,11.3-4.859,12.486-.058.526-.227,1.468-.461,2.683-.948,4.95-3.053,14.377-4.093,18.19-.773,2.845-1.9,6.743-2.67,9.42a.91.91,0,0,1-.065.227l29.124-.286v.546h.091Z" transform="translate(417.856 -73.042)" fill="#00ddf1"/>
|
||||
<path id="Path_446" data-name="Path 446" d="M445.44,607.57s37.03,19.165,50.348,16.891Z" transform="translate(396.391 -105.214)" opacity="0.1"/>
|
||||
<path id="Path_447" data-name="Path 447" d="M561.44,595.57s36.056,11.694,48.074,13.318S561.44,595.57,561.44,595.57Z" transform="translate(355.751 -101.01)" opacity="0.1"/>
|
||||
<path id="Path_448" data-name="Path 448" d="M585.443,530.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,512.821,585.339,526.866,585.443,530.686Z" transform="translate(371.74 -73.754)" opacity="0.1"/>
|
||||
<path id="Path_449" data-name="Path 449" d="M585.443,528.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,510.821,585.339,524.866,585.443,528.686Z" transform="translate(371.74 -73.053)" fill="#00ddf1"/>
|
||||
<path id="Path_450" data-name="Path 450" d="M776.94,672.008s22.192-3.411,22.628,6.334S788.25,694.9,788.25,694.9Z" transform="translate(280.251 -127.587)" fill="#fbbebe"/>
|
||||
<path id="Path_451" data-name="Path 451" d="M578.17,479.936a30.558,30.558,0,0,1-2.923,1.13c-7,2.417-14.065,4.853-21.439,5.736s-15.111.1-21.543-3.573a18.685,18.685,0,0,1-1.949-1.267c-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522C575.318,456.711,576.195,468.69,578.17,479.936Z" transform="translate(367.508 -46.521)" opacity="0.1"/>
|
||||
<path id="Path_452" data-name="Path 452" d="M536.9,360.29a6.153,6.153,0,0,1-5.405-1.137l1.351,1.878a5.5,5.5,0,0,1-4.4-1.462,22.146,22.146,0,0,0,.208,5.035,9.433,9.433,0,0,1-6.211-1.949,2.872,2.872,0,0,0,1.715,2.8,9.316,9.316,0,0,0-10.135,6.958l3.391.312a12,12,0,0,0-6.782,4.138c-1.039,1.3-1.832,2.962-1.377,4.548s2.651,2.6,3.846,1.468a18.151,18.151,0,0,0-3.963,14.806,9.4,9.4,0,0,1,.3,3.417,28.993,28.993,0,0,1-1.228,3.047c-.65,1.9-.032,3.956.546,5.847l3.047,10.018c1.1,3.619,2.2,7.244,3.645,10.745a53.328,53.328,0,0,1,2.6,6.828c.819,3.144.851,6.588,2.6,9.336a12.948,12.948,0,0,0,4.665,4.112c6.425,3.677,14.188,4.457,21.542,3.573s14.409-3.32,21.406-5.736a20.141,20.141,0,0,0,4.444-1.949c4.84-3.17,5.918-9.654,6.821-15.364a87.242,87.242,0,0,1,4.736-18.71c1.416-3.567,3.248-7.185,2.936-11.005-.4-5.074-4.411-9.186-5.405-14.175a12.344,12.344,0,0,1,.045-5.048,11.473,11.473,0,0,0,.513-3.053c-.11-2.092-1.826-3.7-3.437-5.048q-5.243-4.379-10.823-8.277c-.188-1.3,1.579-1.858,2.378-2.884,1.13-1.449-.039-3.748-1.754-4.424s-3.638-.234-5.425.214a4.281,4.281,0,0,0-7.5-3.32,6.627,6.627,0,0,0-5.47-3.95c-4.307-.338-4.392,2.956-7.146,4.768s-5.028-.578-7.146-2.319A6.029,6.029,0,0,0,536.9,360.29Z" transform="translate(374.5 -15.625)" fill="#3f3d56"/>
|
||||
<path id="Path_453" data-name="Path 453" d="M768.171,669.128s-.578,1.377-1.644,3.372a44.83,44.83,0,0,1-5.847,8.556l-.175.188h-1.052l-37.511.344h-.9l-.11-.13-7.276-8.634-2.741-3.255-.11-.136-.234-.273-.13-.156-.169-.2-5.334-6.328,1.949-8.121,3.32-13.682.084-.331.65-2.6.461-1.949,1.546-6.354,5.3-21.861,9.42,4.872,9.933,17.541,5.334,9.42h2.923c.552,0,1.819,1.065,3.092,2.293.455.435.91.9,1.3,1.3,1.156,1.2,2.072,2.222,2.072,2.222s4.736.1,6.009,3.833a4.537,4.537,0,0,1,.162.552,26.191,26.191,0,0,0,1.644,4.262c.838,1.826,1.891,3.9,2.956,5.97,1.981,3.8,4,7.419,4.736,8.731l.091.162Z" transform="translate(305.476 -105.217)" fill="#00ddf1"/>
|
||||
<path id="Path_454" data-name="Path 454" d="M615.681,653.093l-.065.929v.065l-.11,1.494-.864,12.025L613.6,682.093l-.3,4.08-.65,8.731v.11l-69.513.65L429.061,696.9v.546h-.091v-.728l.292-9.329.559-18.229L430.3,653.7l.045-1.54a6.841,6.841,0,0,1,7.419-6.607c3.066.26,7.049.578,11.811.916l1.949.143c27.5,1.91,78.322,4.379,129.606,1.3,1.078-.059,2.163-.123,3.248-.2,7.952-.5,15.9-1.143,23.764-1.949a6.841,6.841,0,0,1,7.536,7.3Z" transform="translate(402.161 -118.514)" opacity="0.1"/>
|
||||
<path id="Path_455" data-name="Path 455" d="M429.08,698.857v-.546l114.092-1.241,69.513-.65h.195v-.11l.65-8.731.279-3.9,1.065-14.689.838-11.629v-.286a6.893,6.893,0,0,0-.071-1.6.2.2,0,0,0,0-.065,6.751,6.751,0,0,0-1.3-3.014,6.847,6.847,0,0,0-6.12-2.6c-7.867.806-15.813,1.449-23.764,1.949-1.085.078-2.17.143-3.248.2-51.284,3.047-102.106.578-129.606-1.3l-1.949-.143c-4.762-.338-8.744-.65-11.811-.916a6.847,6.847,0,0,0-7.3,5.542,7.146,7.146,0,0,0-.117,1.065l-.442,14.4-.6,18.236-.286,9.329v.728Z" transform="translate(402.122 -119.926)" fill="#3f3d56"/>
|
||||
<path id="Path_456" data-name="Path 456" d="M615.925,661.692l-.8,9.959L613.937,686.5l-.3,3.716-.65,8.322-.039.416v.11h0l-.1.961.071-.961h-.188l-69.513.65L429.11,700.944v-.182l.318-9.329.65-18.229.416-12.116a4.132,4.132,0,0,1,1.988-3.411,4.061,4.061,0,0,1,2.456-.585c1.364.11,3.04.24,5,.383,3.177.234,7.094.507,11.655.793,8.445.533,19.041,1.1,31.183,1.572,33.782,1.3,79.388,1.735,122.707-2.059,1.949-.169,3.963-.357,5.931-.546a4.066,4.066,0,0,1,2.261.429,3.346,3.346,0,0,1,.559.351,3.989,3.989,0,0,1,1.3,1.6,2.315,2.315,0,0,1,.162.4A4.08,4.08,0,0,1,615.925,661.692Z" transform="translate(402.112 -122.559)" opacity="0.1"/>
|
||||
<path id="Path_457" data-name="Path 457" d="M360.914,677.87v2.072a3.982,3.982,0,0,1-2.274,3.6l-26.051.5a3.976,3.976,0,0,1-2.319-3.619v-1.949Z" transform="translate(436.74 -129.843)" opacity="0.1"/>
|
||||
<path id="Path_458" data-name="Path 458" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" fill="#3f3d56"/>
|
||||
<path id="Path_459" data-name="Path 459" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" opacity="0.1"/>
|
||||
<path id="Path_460" data-name="Path 460" d="M282.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(516.984 42.387)" fill="#3f3d56"/>
|
||||
<path id="Path_461" data-name="Path 461" d="M552.228,360.039A6.334,6.334,0,0,1,551,357.22a6.165,6.165,0,0,0,.273,2.988A5.847,5.847,0,0,0,552.228,360.039Z" transform="translate(359.434 -17.505)"/>
|
||||
<path id="Path_462" data-name="Path 462" d="M540.1,366.9a22.83,22.83,0,0,1-.24-2.4c0,.8,0,1.6,0,2.41Z" transform="translate(363.311 -20.056)"/>
|
||||
<path id="Path_463" data-name="Path 463" d="M621.706,398.627a11.912,11.912,0,0,1,.234-1.722,11.7,11.7,0,0,0,.52-3.047,4.008,4.008,0,0,0-.1-.689c-.11.6-.286,1.2-.416,1.787a12.433,12.433,0,0,0-.234,3.671Z" transform="translate(334.652 -30.1)"/>
|
||||
<path id="Path_464" data-name="Path 464" d="M600.922,368.894l.708.513a12.8,12.8,0,0,0,1.67-1.442,2.384,2.384,0,0,0,.247-2.365,1.95,1.95,0,0,1-.247.416C602.5,367.042,600.734,367.607,600.922,368.894Z" transform="translate(341.923 -20.441)"/>
|
||||
<path id="Path_465" data-name="Path 465" d="M518.455,380.29a8,8,0,0,0-.565,1.481,11.8,11.8,0,0,1,3.248-1.228Z" transform="translate(371.008 -25.587)"/>
|
||||
<path id="Path_466" data-name="Path 466" d="M531.888,368.979a9.637,9.637,0,0,1-1.221-.8,2.758,2.758,0,0,0,.058.767,7.7,7.7,0,0,1,1.163.033Z" transform="translate(366.534 -21.345)"/>
|
||||
<path id="Path_467" data-name="Path 467" d="M587.579,428.33c-.266,3.151-1.735,6.185-2.923,9.173a87.2,87.2,0,0,0-4.736,18.717c-.9,5.71-1.949,12.194-6.821,15.358a19.488,19.488,0,0,1-4.437,1.949c-7,2.423-14.065,4.853-21.439,5.736s-15.111.11-21.543-3.567a12.993,12.993,0,0,1-4.664-4.112c-1.735-2.748-1.767-6.191-2.6-9.336a53.424,53.424,0,0,0-2.6-6.834c-1.436-3.5-2.54-7.146-3.638-10.739q-1.527-5.009-3.047-10.024a18.716,18.716,0,0,1-.741-3.086,11.644,11.644,0,0,0,.741,5.035q1.52,5.022,3.047,10.024c1.1,3.619,2.2,7.244,3.638,10.739a53.417,53.417,0,0,1,2.6,6.834c.819,3.144.851,6.587,2.6,9.336a12.993,12.993,0,0,0,4.664,4.112c6.432,3.677,14.188,4.45,21.543,3.567s14.409-3.313,21.439-5.736a19.487,19.487,0,0,0,4.437-1.949c4.84-3.164,5.918-9.647,6.821-15.358a87.2,87.2,0,0,1,4.736-18.717c1.416-3.56,3.248-7.179,2.936-11.005C587.592,428.408,587.579,428.369,587.579,428.33Z" transform="translate(374.359 -42.418)"/>
|
||||
<path id="Path_468" data-name="Path 468" d="M510.318,421.419v-.071a9.1,9.1,0,0,0-.3-3.411,17.272,17.272,0,0,1-.253-2.157,17.769,17.769,0,0,0,.253,4.106C510.123,420.393,510.24,420.906,510.318,421.419Z" transform="translate(373.864 -38.021)"/>
|
||||
<path id="Path_469" data-name="Path 469" d="M512.452,397.929a18.02,18.02,0,0,1,1.7-2.384c-1.189,1.137-3.385.123-3.846-1.455a3.788,3.788,0,0,0,0,1.949,2.852,2.852,0,0,0,2.15,1.89Z" transform="translate(373.712 -30.422)"/>
|
||||
<path id="Path_470" data-name="Path 470" d="M762.946,656.57s2.059,7.471,0,13.968S762.946,656.57,762.946,656.57Z" transform="translate(285.475 -122.381)" opacity="0.1"/>
|
||||
<path id="Path_471" data-name="Path 471" d="M776,668.3s.864,9.485-1.085,11.168S776,668.3,776,668.3Z" transform="translate(281.189 -126.49)" opacity="0.1"/>
|
||||
<line id="Line_88" data-name="Line 88" y1="0.65" x2="66.011" transform="translate(733.267 442.068)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<line id="Line_89" data-name="Line 89" y1="0.643" x2="66.011" transform="translate(733.267 450.176)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<line id="Line_90" data-name="Line 90" y1="0.65" x2="66.011" transform="translate(733.267 458.277)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 23 KiB |
@@ -2,6 +2,7 @@ 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();
|
||||
@@ -23,7 +24,7 @@ const AddSigner = (props) => {
|
||||
if (savedUserDetails && addYourself) {
|
||||
setName(savedUserDetails.name);
|
||||
setPhone(savedUserDetails?.phone || "");
|
||||
setEmail(savedUserDetails.email);
|
||||
setEmail(savedUserDetails.email?.toLowerCase()?.replace(/\s/g, ""));
|
||||
}
|
||||
}, [addYourself]);
|
||||
|
||||
@@ -47,139 +48,145 @@ const AddSigner = (props) => {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsLoader(true);
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
try {
|
||||
const user = Parse.User.current();
|
||||
const query = new Parse.Query("contracts_Contactbook");
|
||||
query.equalTo("CreatedBy", user);
|
||||
query.notEqualTo("IsDeleted", true);
|
||||
query.equalTo("Email", email);
|
||||
const res = await query.first();
|
||||
// console.log(res);
|
||||
if (!res) {
|
||||
const contactQuery = new Parse.Object("contracts_Contactbook");
|
||||
contactQuery.set("Name", name);
|
||||
if (phone) {
|
||||
contactQuery.set("Phone", phone);
|
||||
}
|
||||
contactQuery.set("Email", email);
|
||||
contactQuery.set("UserRole", "contracts_Guest");
|
||||
|
||||
contactQuery.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
const _user = new _users();
|
||||
_user.set("name", name);
|
||||
_user.set("username", email);
|
||||
_user.set("email", email);
|
||||
_user.set("password", email);
|
||||
if (!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) {
|
||||
_user.set("phone", phone);
|
||||
contactQuery.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("Email", email);
|
||||
contactQuery.set("UserRole", "contracts_Guest");
|
||||
|
||||
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.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
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)
|
||||
);
|
||||
|
||||
const res = await contactQuery.save();
|
||||
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);
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.details) {
|
||||
props.details({
|
||||
value: parseData[props.valueKey],
|
||||
label: parseData[props.displayKey]
|
||||
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
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.set("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: userRes.id
|
||||
});
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.details) {
|
||||
props.details({
|
||||
value: parseData[props.valueKey],
|
||||
label: parseData[props.displayKey]
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
alert(t("add-signer-alert"));
|
||||
} catch (err) {
|
||||
console.log("err in fetch contact", err);
|
||||
setIsLoader(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in fetch contact", err);
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -206,6 +213,7 @@ const AddSigner = (props) => {
|
||||
</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
|
||||
@@ -268,6 +276,7 @@ const AddSigner = (props) => {
|
||||
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">
|
||||
@@ -1,198 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Title from "./Title";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const AddTeam = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const [formdata, setFormdata] = useState({
|
||||
name: "",
|
||||
team: ""
|
||||
});
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [teamList, setTeamList] = useState([]);
|
||||
useEffect(() => {
|
||||
getTeamList();
|
||||
}, []);
|
||||
|
||||
const getTeamList = async () => {
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const teams = await Parse.Cloud.run("getteams", { active: true });
|
||||
const teamRes = JSON.parse(JSON.stringify(teams));
|
||||
if (teamRes.length > 0) {
|
||||
const _teamRes = JSON.parse(JSON.stringify(teamRes));
|
||||
const allUsersteam = _teamRes.find((x) => x.Name === "All Users");
|
||||
if (allUsersteam) {
|
||||
setFormdata({ team: allUsersteam.objectId });
|
||||
}
|
||||
setTeamList(_teamRes);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in fetch top level teamList", err);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDropdown = (e) => {
|
||||
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Extracting values except for the 'name' key
|
||||
let updatedAncestors = [];
|
||||
if (formdata.team) {
|
||||
const Ancestors = teamList.find(
|
||||
(x) => x.objectId === formdata.team
|
||||
)?.Ancestors;
|
||||
if (Ancestors && Ancestors.length > 0) {
|
||||
updatedAncestors = Ancestors.map((x) => ({
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: x.objectId
|
||||
}));
|
||||
} else {
|
||||
const AllUser = teamList.find((x) => x.objectId === "All Users");
|
||||
updatedAncestors = [
|
||||
AllUser,
|
||||
{
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: formdata.team
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
try {
|
||||
setIsLoader(true);
|
||||
let data = { Name: formdata.name };
|
||||
if (updatedAncestors.length > 0) {
|
||||
const ParentId = updatedAncestors[updatedAncestors.length - 1];
|
||||
data["ParentId"] = ParentId?.objectId;
|
||||
data["Ancestors"] = updatedAncestors;
|
||||
}
|
||||
const newTeamRes = await Parse.Cloud.run("addteam", data);
|
||||
// console.log("teamRes ", newTeamRes);
|
||||
if (updatedAncestors.length > 0) {
|
||||
const ParentId = teamList.find((x) => x.objectId === formdata.team);
|
||||
props.handleTeamInfo({
|
||||
objectId: newTeamRes.id,
|
||||
Name: formdata.name,
|
||||
ParentId: ParentId,
|
||||
Ancestors: updatedAncestors,
|
||||
IsActive: true
|
||||
});
|
||||
} else {
|
||||
props.handleTeamInfo({
|
||||
objectId: newTeamRes.id,
|
||||
Name: formdata.name,
|
||||
ParentId: "",
|
||||
Ancestors: "",
|
||||
IsActive: true
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
setFormdata({ name: "", team: { name: "", objectId: "" } });
|
||||
props.setIsAlert({ type: "success", msg: "Team created successfully." });
|
||||
} catch (err) {
|
||||
console.log("err in save team", err);
|
||||
if (err.code === 137) {
|
||||
props.setIsAlert({ type: "danger", msg: "Teams already exists." });
|
||||
} else if (err.code === 102) {
|
||||
props.setIsAlert({ type: "warning", msg: "Provide team name." });
|
||||
} else {
|
||||
props.setIsAlert({ type: "danger", msg: "Something went wrong." });
|
||||
}
|
||||
} finally {
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Define a function to handle the "add yourself" checkbox
|
||||
const handleReset = () => {
|
||||
setFormdata({
|
||||
name: "",
|
||||
team: { name: "", objectId: "" }
|
||||
});
|
||||
};
|
||||
const handleChange = (e) => {
|
||||
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
|
||||
<Title title="Add Team" />
|
||||
{isLoader && (
|
||||
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto">
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */}
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("name")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={formdata.name}
|
||||
onChange={(e) => handleChange(e)}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("report-heading.Parent Team")}
|
||||
</label>
|
||||
<select
|
||||
value={formdata.team}
|
||||
onChange={(e) => handleDropdown(e)}
|
||||
name="team"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
>
|
||||
{teamList.length > 0 &&
|
||||
teamList.map((x) => (
|
||||
<option key={x.objectId} value={x.objectId}>
|
||||
{x.Name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("submit")}
|
||||
</button>
|
||||
<div
|
||||
type="button"
|
||||
onClick={() => handleReset()}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
{t("reset")}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddTeam;
|
||||
@@ -1,420 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Title from "./Title";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { copytoData } from "../constant/Utils";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
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 [isLoader, setIsLoader] = useState(false);
|
||||
const [teamList, setTeamList] = useState([]);
|
||||
const role = ["OrgAdmin", "Editor", "User"];
|
||||
|
||||
useEffect(() => {
|
||||
getTeamList();
|
||||
}, []);
|
||||
|
||||
const getTeamList = async () => {
|
||||
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
|
||||
const teamRes = await Parse.Cloud.run("getteams", { active: true });
|
||||
if (teamRes.length > 0) {
|
||||
const _teamRes = JSON.parse(JSON.stringify(teamRes));
|
||||
setTeamList(_teamRes);
|
||||
if (!isEnableSubscription) {
|
||||
const allUserId =
|
||||
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
|
||||
setFormdata((prev) => ({ ...prev, team: allUserId }));
|
||||
}
|
||||
}
|
||||
};
|
||||
const checkUserExist = async () => {
|
||||
try {
|
||||
const res = await Parse.Cloud.run("getUserDetails", {
|
||||
email: formdata.email
|
||||
});
|
||||
if (res) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
}
|
||||
};
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
setIsLoader(true);
|
||||
const res = await checkUserExist();
|
||||
if (res) {
|
||||
props.setIsAlert({ type: "danger", msg: t("user-already-exist") });
|
||||
setIsLoader(false);
|
||||
setTimeout(() => {
|
||||
props.setIsAlert({ type: "success", msg: "" });
|
||||
}, 1000);
|
||||
} else {
|
||||
try {
|
||||
const extUser = new Parse.Object("contracts_Users");
|
||||
extUser.set("Name", formdata.name);
|
||||
if (formdata.phone) {
|
||||
extUser.set("Phone", formdata.phone);
|
||||
}
|
||||
extUser.set("Email", formdata.email);
|
||||
extUser.set("UserRole", `contracts_${formdata.role}`);
|
||||
if (formdata?.team) {
|
||||
extUser.set("TeamIds", [
|
||||
{
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: formdata.team
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (localUser && localUser.OrganizationId) {
|
||||
extUser.set("OrganizationId", {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Organizations",
|
||||
objectId: localUser.OrganizationId.objectId
|
||||
});
|
||||
}
|
||||
if (localUser && localUser.Company) {
|
||||
extUser.set("Company", localUser.Company);
|
||||
}
|
||||
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
extUser.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
const _user = new _users();
|
||||
_user.set("name", formdata.name);
|
||||
_user.set("username", formdata.email);
|
||||
_user.set("email", formdata.email);
|
||||
_user.set("password", formdata.password);
|
||||
if (formdata.phone) {
|
||||
_user.set("phone", formdata.phone);
|
||||
}
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
const currentUser = Parse.User.current();
|
||||
extUser.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
extUser.set("UserId", user);
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
extUser.setACL(acl);
|
||||
|
||||
const res = await extUser.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
if (formdata?.team) {
|
||||
const team = teamList.find((x) => x.objectId === formdata.team);
|
||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
||||
y.objectId === team.objectId ? team : y
|
||||
);
|
||||
}
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
|
||||
setIsLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: formdata.email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const currentUser = Parse.User.current();
|
||||
extUser.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
extUser.set("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: userRes.id
|
||||
});
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
extUser.setACL(acl);
|
||||
const res = await extUser.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
if (formdata?.team) {
|
||||
const team = teamList.find((x) => x.objectId === formdata.team);
|
||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
||||
y.objectId === team.objectId ? team : y
|
||||
);
|
||||
}
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
setIsLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
setIsLoader(false);
|
||||
props.setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("something-went-wrong-mssg")
|
||||
});
|
||||
} finally {
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Define a function to handle the "add yourself" checkbox
|
||||
const handleReset = () => {
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
};
|
||||
const handleChange = (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.setIsAlert({ type: "success", msg: t("copied") });
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
|
||||
};
|
||||
return (
|
||||
<div className="shadow-md rounded-box my-[1px] p-3 bg-[#ffffff]">
|
||||
<Title title={"Add User"} />
|
||||
{isLoader && (
|
||||
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50 rounded-box">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto">
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */}
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("name")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={formdata.name}
|
||||
onChange={(e) => handleChange(e)}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("email")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={formdata.email}
|
||||
onChange={(e) => handleChange(e)}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("password")}
|
||||
</label>
|
||||
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
|
||||
<div className="break-all">{formdata?.password}</div>
|
||||
<i
|
||||
onClick={() => copytoclipboard(formdata?.password)}
|
||||
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
|
||||
></i>
|
||||
</div>
|
||||
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
|
||||
{t("password-generateed")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
{t("phone")}
|
||||
{/* <span className="text-[red] text-[13px]"> *</span> */}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="phone"
|
||||
placeholder="optional"
|
||||
value={formdata.phone}
|
||||
onChange={(e) => handleChange(e)}
|
||||
// required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
{isEnableSubscription && (
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Team<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<select
|
||||
value={formdata.team}
|
||||
onChange={(e) => handleChange(e)}
|
||||
name="team"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
>
|
||||
<option defaultValue={""} value={""}>
|
||||
select
|
||||
</option>
|
||||
{teamList.length > 0 &&
|
||||
teamList.map((x) => (
|
||||
<option key={x.objectId} value={x.objectId}>
|
||||
{x.Name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-3">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="block text-xs text-gray-700 font-semibold"
|
||||
>
|
||||
Role<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<select
|
||||
value={formdata.role}
|
||||
onChange={(e) => handleChange(e)}
|
||||
name="role"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
>
|
||||
<option defaultValue={""} value={""}>
|
||||
select
|
||||
</option>
|
||||
{role.length > 0 &&
|
||||
role.map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
Submit
|
||||
</button>
|
||||
<div
|
||||
type="button"
|
||||
onClick={() => handleReset()}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -1,281 +0,0 @@
|
||||
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";
|
||||
|
||||
const BulkSendUi = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const [forms, setForms] = useState([]);
|
||||
const [formId, setFormId] = useState(2);
|
||||
const formRef = useRef(null);
|
||||
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
const [allowedForm, setAllowedForm] = useState(0);
|
||||
const [isSignatureExist, setIsSignatureExist] = useState();
|
||||
const allowedSigners = 50;
|
||||
useEffect(() => {
|
||||
signatureExist();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//function to check atleast one signature field exist
|
||||
const signatureExist = () => {
|
||||
const getPlaceholder = props.item?.Placeholders;
|
||||
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((holder) =>
|
||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
||||
)
|
||||
);
|
||||
setIsSignatureExist(checkIsSignatureExistt);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (scrollOnNextUpdate && formRef.current) {
|
||||
formRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "end",
|
||||
inline: "nearest"
|
||||
});
|
||||
setScrollOnNextUpdate(false);
|
||||
}
|
||||
}, [forms, scrollOnNextUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
(() => {
|
||||
if (props?.Placeholders?.length > 0) {
|
||||
let users = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
if (!element.signerObjId) {
|
||||
users = [
|
||||
...users,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
|
||||
const totalForms = Math.floor(allowedSigners / users?.length);
|
||||
setAllowedForm(totalForms);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
const handleInputChange = (index, signer, fieldIndex) => {
|
||||
const newForms = [...forms];
|
||||
newForms[index].fields[fieldIndex].email = signer?.Email
|
||||
? signer?.Email
|
||||
: signer || "";
|
||||
newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : "";
|
||||
setForms(newForms);
|
||||
};
|
||||
|
||||
const handleAddForm = (e) => {
|
||||
e.preventDefault();
|
||||
// Check if the quick send limit has been reached
|
||||
if (forms?.length < allowedForm) {
|
||||
if (props?.Placeholders.length > 0) {
|
||||
let newForm = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
if (!element.signerObjId) {
|
||||
newForm = [
|
||||
...newForm,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setForms([...forms, { Id: formId, fields: newForm }]);
|
||||
}
|
||||
setFormId(formId + 1);
|
||||
setScrollOnNextUpdate(true);
|
||||
} else {
|
||||
// If the limit has been reached, throw an error with the appropriate message
|
||||
alert(t("quick-send-alert-4"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveForm = (index) => {
|
||||
const updatedForms = forms.filter((_, i) => i !== index);
|
||||
setForms(updatedForms);
|
||||
};
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSubmit(true);
|
||||
|
||||
// Create a copy of Placeholders array from props.item
|
||||
let Placeholders = [...props.item.Placeholders];
|
||||
// Initialize an empty array to store updated documents
|
||||
let Documents = [];
|
||||
// Loop through each form
|
||||
forms.forEach((form) => {
|
||||
//checking if user enter email which already exist as a signer then add user in a signers array
|
||||
let existSigner = [];
|
||||
form.fields.map((data) => {
|
||||
if (data.signer) {
|
||||
existSigner.push(data.signer);
|
||||
}
|
||||
});
|
||||
// Map through the copied Placeholders array to update email values
|
||||
const updatedPlaceholders = Placeholders.map((placeholder) => {
|
||||
// Find the field in the current form that matches the placeholder Id
|
||||
const field = form.fields.find(
|
||||
(element) => parseInt(element.fieldId) === placeholder.Id
|
||||
);
|
||||
// If a matching field is found, update the email value in the placeholder
|
||||
const signer = field?.signer?.objectId ? field.signer : "";
|
||||
if (field) {
|
||||
if (signer) {
|
||||
return {
|
||||
...placeholder,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...placeholder,
|
||||
email: field.email,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
}
|
||||
}
|
||||
// If no matching field is found, keep the placeholder as is
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Push a new document object with updated Placeholders into the Documents array
|
||||
if (existSigner?.length > 0) {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
Signers: props.item.Signers
|
||||
? [...props.item.Signers, ...existSigner]
|
||||
: [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders
|
||||
});
|
||||
}
|
||||
});
|
||||
//console.log("Documents ", Documents);
|
||||
await batchQuery(Documents);
|
||||
};
|
||||
|
||||
const batchQuery = async (Documents) => {
|
||||
const serverUrl = localStorage.getItem("baseUrl");
|
||||
const functionsUrl = `${serverUrl}functions/batchdocuments`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const params = {
|
||||
Documents: JSON.stringify(Documents)
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(functionsUrl, params, { headers: headers });
|
||||
// console.log("res ", res);
|
||||
if (res.data && res.data.result) {
|
||||
props.handleClose("success", Documents?.length);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err ", err);
|
||||
props.handleClose("error", 0);
|
||||
} finally {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isSubmit && (
|
||||
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{props.Placeholders?.length > 0 ? (
|
||||
isSignatureExist ? (
|
||||
<>
|
||||
{props.Placeholders?.some((x) => !x.signerObjId) ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="min-h-max max-h-[250px] overflow-y-auto">
|
||||
{forms?.map((form, index) => (
|
||||
<div
|
||||
key={form.Id}
|
||||
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
|
||||
>
|
||||
{form?.fields?.map((field, fieldIndex) => (
|
||||
<div className="flex flex-col" key={field.fieldId}>
|
||||
<label>{field.label}</label>
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.value}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(index, signer, fieldIndex)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{forms?.length > 1 && (
|
||||
<button
|
||||
onClick={() => handleRemoveForm(index)}
|
||||
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
|
||||
>
|
||||
<i className="fa-light fa-trash"></i>
|
||||
</button>
|
||||
)}
|
||||
<div ref={formRef}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col mx-4 mb-4 gap-3">
|
||||
<button
|
||||
onClick={handleAddForm}
|
||||
className="op-btn op-btn-primary focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-plus"></i>{" "}
|
||||
<span>{t("add-new")}</span>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-secondary focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-paper-plane"></i>{" "}
|
||||
<span>{t("send")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-1")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-2")}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-3")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BulkSendUi;
|
||||
@@ -0,0 +1,306 @@
|
||||
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);
|
||||
const [signers, setSigners] = useState([]);
|
||||
const [emails, setEmails] = useState([]);
|
||||
useEffect(() => {
|
||||
signatureExist();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//function to check at least 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 = [];
|
||||
let emails = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
const signerEmail = element?.email || element?.signerPtr?.Email;
|
||||
|
||||
// only add when there's a non-empty signerEmail
|
||||
if (signerEmail) {
|
||||
emails = [...emails, signerEmail];
|
||||
}
|
||||
if (!element.signerObjId) {
|
||||
users = [
|
||||
...users,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setEmails(emails);
|
||||
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
|
||||
const signer = props.item?.Signers?.filter((x) => x?.objectId);
|
||||
setSigners(signer);
|
||||
}
|
||||
})();
|
||||
// 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) {
|
||||
let email = "";
|
||||
for (const field of item.fields) {
|
||||
if (!emailRegex.test(field.email)) {
|
||||
alert(t("invalid-email-found", { email: field.email }));
|
||||
return false;
|
||||
} else if (email === field.email || emails?.includes(field.email)) {
|
||||
alert(t("duplicate-email-found", { email: field.email }));
|
||||
return false;
|
||||
} else {
|
||||
email = field.email;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: signers ? [...signers, ...existSigner] : [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
SignatureType: props.signatureType,
|
||||
Signers: signers
|
||||
});
|
||||
}
|
||||
});
|
||||
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;
|
||||
@@ -4,6 +4,7 @@ import axios from "axios";
|
||||
import { openInNewTab } from "../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const Footer = () => {
|
||||
const appName = "OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const [showButton, setShowButton] = useState(false);
|
||||
const [version, setVersion] = useState("");
|
||||
@@ -39,7 +40,6 @@ const Footer = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const appName = "OpenSign™";
|
||||
const openUrl = () => {
|
||||
openInNewTab(
|
||||
"https://github.com/OpenSignLabs/OpenSign/releases/tag/" + version
|
||||
@@ -1,233 +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,
|
||||
checkIsSubscribedTeam,
|
||||
getAppLogo,
|
||||
openInNewTab,
|
||||
saveLanguageInLocal
|
||||
} from "../constant/Utils";
|
||||
import { isEnableSubscription, isStaging } from "../constant/const";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Header = ({ showSidebar }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
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 [isTeam, setIsTeam] = useState(false);
|
||||
const [applogo, setAppLogo] = useState(
|
||||
localStorage.getItem("appLogo") || " "
|
||||
);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
useEffect(() => {
|
||||
checkSubscription();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
async function checkSubscription() {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
const getIsTeam = await checkIsSubscribedTeam();
|
||||
if (getIsSubscribe) {
|
||||
const applogo = await getAppLogo();
|
||||
if (applogo?.logo) {
|
||||
setAppLogo(applogo?.logo);
|
||||
} else {
|
||||
setAppLogo(localStorage.getItem("appLogo") || "");
|
||||
}
|
||||
}
|
||||
setIsPro(getIsSubscribe);
|
||||
setIsTeam(getIsTeam);
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
}
|
||||
}
|
||||
|
||||
const closeDropdown = () => {
|
||||
setIsOpen(false);
|
||||
if (Parse?.User?.current()) {
|
||||
try {
|
||||
Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err", err);
|
||||
}
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
let 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]);
|
||||
const handleConsoleRedirect = () => {
|
||||
if (isStaging) {
|
||||
window.open(" https://staging-console.opensignlabs.com/");
|
||||
} else {
|
||||
window.open("https://console.opensignlabs.com/");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="op-navbar bg-base-100 shadow">
|
||||
<div className="flex-none">
|
||||
<button
|
||||
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
|
||||
onClick={showSidebar}
|
||||
>
|
||||
<i className="fa-light fa-bars text-xl text-base-content"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 ml-2">
|
||||
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
||||
<img
|
||||
className="object-contain h-full w-auto"
|
||||
src={applogo}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="profile-menu" className="flex-none gap-2">
|
||||
{!isSubscribe && (
|
||||
<div>
|
||||
<button
|
||||
className="text-xs md:text-sm shadow op-btn op-btn-outline op-btn-sm md:op-btn-md op-btn-accent"
|
||||
onClick={() => navigate("/subscription")}
|
||||
>
|
||||
{t("upgrade-now")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{!isTeam && isPro && (
|
||||
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
{t("pro")}
|
||||
</div>
|
||||
)}
|
||||
{isTeam && (
|
||||
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] text-[13px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
|
||||
{t("team")}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<FullScreenButton />
|
||||
</div>
|
||||
{width >= 768 && (
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
|
||||
>
|
||||
<img
|
||||
className="w-[35px] h-[35px] object-contain"
|
||||
src={image}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{width >= 768 && (
|
||||
<div
|
||||
onClick={toggleDropdown}
|
||||
className="cursor-pointer text-base-content text-sm"
|
||||
>
|
||||
{username && username}
|
||||
</div>
|
||||
)}
|
||||
<div className="op-dropdown op-dropdown-end" id="profile-menu">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
|
||||
>
|
||||
<i
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={toggleDropdown}
|
||||
className="fa-light fa-angle-down text-base-content"
|
||||
></i>
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className="mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52"
|
||||
>
|
||||
<li onClick={() => openInNewTab("https://docs.opensignlabs.com")}>
|
||||
<span>
|
||||
<i className="fa-light fa-book"></i> {t("docs")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/profile");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-user"></i> {t("profile")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/changepassword");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-lock"></i> {t("change-password")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
handleConsoleRedirect();
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-id-card"></i> Console
|
||||
</span>
|
||||
</li>
|
||||
<li onClick={closeDropdown}>
|
||||
<span>
|
||||
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
|
||||
{t("log-out")}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,254 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import dp from "../assets/images/dp.png";
|
||||
import FullScreenButton from "./FullScreenButton";
|
||||
import ThemeToggle from "./ThemeToggle";
|
||||
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 isAdmin =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0]?.UserRole ===
|
||||
"contracts_Admin";
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [applogo, setAppLogo] = useState("");
|
||||
const [isDarkTheme, setIsDarkTheme] = 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]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const updateThemeStatus = () => {
|
||||
const isDarkTheme =
|
||||
document.documentElement.getAttribute("data-theme") === "opensigndark";
|
||||
setIsDarkTheme(isDarkTheme);
|
||||
};
|
||||
updateThemeStatus();
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
updateThemeStatus();
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-theme"]
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="op-navbar bg-base-100 shadow touch-none">
|
||||
<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={
|
||||
isDarkTheme
|
||||
? "/static/js/assets/images/logo-dark.png"
|
||||
: 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-56 ${
|
||||
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={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/verify-document");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-check-square"></i>{" "}
|
||||
{t("verify-document")}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
<i className="fa-light fa-moon"></i>
|
||||
{t("dark-mode")}
|
||||
<span className="text-[10px] font-semibold bg-base-300 text-base-content px-1 rounded-md">
|
||||
BETA
|
||||
</span>
|
||||
<ThemeToggle />
|
||||
</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,220 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
// import FacebookLogin from "react-facebook-login";
|
||||
|
||||
const LoginFacebook = ({
|
||||
FBCred,
|
||||
thirdpartyLoginfn,
|
||||
thirdpartyLoader,
|
||||
setThirdpartyLoader
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
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(t("server-error"));
|
||||
}
|
||||
} else {
|
||||
alert(t("fill-required-details!"));
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{thirdpartyLoader && (
|
||||
<div className="fixed flex justify-center items-center inset-0 bg-black bg-opacity-25 z-20 ">
|
||||
<Loader />
|
||||
</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 block z-[1]"
|
||||
id="exampleModal"
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
>
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">{t("sign-up-form")}</h5>
|
||||
<span>
|
||||
<span></span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form>
|
||||
<div className="form-group">
|
||||
<label htmlFor="Phone" className="col-form-label">
|
||||
{t("phone")}{" "}
|
||||
<span className="text-[red] text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
className="form-control"
|
||||
id="Phone"
|
||||
value={userDetails.Phone}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Phone: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="Company" className="col-form-label">
|
||||
{t("company")}{" "}
|
||||
<span className="text-[red] text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="Company"
|
||||
value={userDetails.Company}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Company: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSubmitbtn()}
|
||||
className="btn btn-info mr-[10px]"
|
||||
>
|
||||
{t("sign-up")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary w-[90px]"
|
||||
onClick={() => setIsModal(false)}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginFacebook;
|
||||
@@ -1,275 +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 Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { saveLanguageInLocal } from "../constant/Utils";
|
||||
/*
|
||||
* `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 { t, i18n } = useTranslation();
|
||||
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 defaultmenuid = localStorage.getItem("defaultmenuid");
|
||||
let PageLanding = localStorage.getItem("PageLanding");
|
||||
let userSettings = localStorage.getItem("userSettings");
|
||||
|
||||
localStorage.clear();
|
||||
saveLanguageInLocal(i18n);
|
||||
|
||||
localStorage.setItem("baseUrl", baseUrl);
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
localStorage.setItem("appLogo", applogo);
|
||||
localStorage.setItem("defaultmenuid", defaultmenuid);
|
||||
localStorage.setItem("PageLanding", PageLanding);
|
||||
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 params = { email: details.Gmail };
|
||||
const extRes = await Parse.Cloud.run("getUserDetails", params);
|
||||
if (extRes) {
|
||||
// const params = { ...details, Phone: extRes?.get("Phone") || "" };
|
||||
const params = { ...details, extUserId: extRes.objectId };
|
||||
const payload = await Parse.Cloud.run("googlesign", params);
|
||||
if (payload && payload.sessiontoken) {
|
||||
const LocalUserDetails = {
|
||||
name: details.Name,
|
||||
email: details.Gmail,
|
||||
phone: payload?.phone || "",
|
||||
company: payload.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(t("server-error"));
|
||||
}
|
||||
} else {
|
||||
alert(t("fill-required-details!"));
|
||||
}
|
||||
};
|
||||
const handleCloseModal = () => {
|
||||
setIsModal(false);
|
||||
Parse.User.logOut();
|
||||
|
||||
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);
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{thirdpartyLoader && (
|
||||
<div className="fixed flex justify-center items-center inset-0 bg-black bg-opacity-25 z-20 ">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div ref={googleBtn} className="text-sm"></div>
|
||||
<ModalUi showClose={false} isOpen={isModal} title={t("sign-up-form")}>
|
||||
<form className="px-4 py-3 text-base-content">
|
||||
<div className="mb-3">
|
||||
<label htmlFor="Phone" className="block text-xs font-semibold">
|
||||
{t("phone")} <span className="text-[13px] text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
id="Phone"
|
||||
value={userDetails.Phone}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Phone: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="Company" className="block text-xs font-semibold">
|
||||
{t("company")} <span className="text-[13px] text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
id="Company"
|
||||
value={userDetails.Company}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Company: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="JobTitle" className="block text-xs font-semibold">
|
||||
{t("job-title")} <span className="text-[13px] text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
id="JobTitle"
|
||||
value={userDetails.Destination}
|
||||
onChange={(e) =>
|
||||
setUserDetails({
|
||||
...userDetails,
|
||||
Destination: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
onClick={() => handleSubmitbtn()}
|
||||
>
|
||||
{t("sign-up")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GoogleSignInBtn;
|
||||
@@ -1,7 +1,9 @@
|
||||
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]">
|
||||
@@ -12,10 +14,9 @@ const RenderDebugPdf = (props) => {
|
||||
onMouseMove={props.handleMouseMoveDiv}
|
||||
>
|
||||
<Document
|
||||
onLoadError={() => {
|
||||
props.setPdfLoadFail(false);
|
||||
}}
|
||||
loading={"Loading 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}
|
||||
@@ -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;
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { NavLink } from "react-router";
|
||||
|
||||
const SocialMedia = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const ThemeToggle = () => {
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const storedTheme = localStorage.getItem("theme");
|
||||
if (storedTheme === "dark") {
|
||||
setIsDark(true);
|
||||
document.documentElement.setAttribute("data-theme", "opensigndark");
|
||||
} else {
|
||||
document.documentElement.setAttribute("data-theme", "opensigncss");
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleChange = () => {
|
||||
const newTheme = !isDark;
|
||||
setIsDark(newTheme);
|
||||
if (newTheme) {
|
||||
document.documentElement.setAttribute("data-theme", "opensigndark");
|
||||
localStorage.setItem("theme", "dark");
|
||||
} else {
|
||||
document.documentElement.setAttribute("data-theme", "opensigncss");
|
||||
localStorage.setItem("theme", "light");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
id="dark-mode-toggle"
|
||||
type="checkbox"
|
||||
className="op-toggle checked:[--tglbg:#3368ff] transition-all checked:bg-white"
|
||||
checked={isDark}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeToggle;
|
||||
@@ -1,11 +1,12 @@
|
||||
import React from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
function Title({ title, drive }) {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
return (
|
||||
<Helmet>
|
||||
<title>{drive ? title : `${title} - OpenSign™`}</title>
|
||||
<meta name="description" content={`${title} - OpenSign™`} />
|
||||
<title>{drive ? title : `${title} - ${appName}`}</title>
|
||||
<meta name="description" content={`${title} - ${appName}`} />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import { openInNewTab } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -29,16 +29,28 @@ const DashboardButton = (props) => {
|
||||
: "cursor-default"
|
||||
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
||||
<i
|
||||
className={`${
|
||||
props.Icon ? props.Icon : "fa-light fa-info"
|
||||
} text-[25px] lg:text-[30px]`}
|
||||
></i>
|
||||
</span>
|
||||
<div className=" text-lg ml-3 text-base-content">
|
||||
<div className="flex flex-row items-center text-base-content">
|
||||
<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">
|
||||
{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>
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import Parse from "parse";
|
||||
import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import Tooltip from "../../primitives/Tooltip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -133,7 +133,7 @@ const DashboardCard = (props) => {
|
||||
skip: 0,
|
||||
limit: 200
|
||||
};
|
||||
const url = `${parseBaseUrl}/functions/getReport`;
|
||||
const url = `${parseBaseUrl}functions/getReport`;
|
||||
await axios
|
||||
.post(url, params, {
|
||||
headers: {
|
||||
@@ -148,7 +148,7 @@ const DashboardCard = (props) => {
|
||||
);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj.Signers.some(
|
||||
const isSigner = obj.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser.id
|
||||
);
|
||||
if (isSigner) {
|
||||
@@ -346,7 +346,6 @@ const DashboardCard = (props) => {
|
||||
id={props.Label}
|
||||
iconColor={"white"}
|
||||
message={t(`tour-mssg.${props.Label}`)}
|
||||
// {props?.Data?.tourMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import Parse from "parse";
|
||||
import ReportTable from "../../primitives/GetReportDisplay";
|
||||
import reportJson from "../../json/ReportJson";
|
||||
@@ -17,10 +17,16 @@ function DashboardReport(props) {
|
||||
const [isMoreDocs, setIsMoreDocs] = useState(true);
|
||||
const abortController = new AbortController();
|
||||
const docPerPage = 5;
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||
const [isSearchResult, setIsSearchResult] = useState(false);
|
||||
const debounceTimer = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setReportName("");
|
||||
getReportData(props.Record.reportId);
|
||||
setSearchTerm("");
|
||||
setMobileSearchOpen(false);
|
||||
getReportData(props.Record.reportId, 0, 20, "");
|
||||
|
||||
// Function returned from useEffect is called on unmount
|
||||
return () => {
|
||||
@@ -36,12 +42,69 @@ function DashboardReport(props) {
|
||||
// below useEffect call when isNextRecord state is true and fetch next record
|
||||
useEffect(() => {
|
||||
if (isNextRecord) {
|
||||
getReportData(props.Record.reportId, List.length, 200);
|
||||
getReportData(props.Record.reportId, List.length, 20, searchTerm);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [isNextRecord]);
|
||||
|
||||
const getReportData = async (id, skipUserRecord = 0, limit = 200) => {
|
||||
const handleSearchChange = async (e) => {
|
||||
const term = e.target.value.toLowerCase();
|
||||
setSearchTerm(term);
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
debounceTimer.current = setTimeout(async () => {
|
||||
try {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(
|
||||
url,
|
||||
{
|
||||
reportId: props.Record.reportId,
|
||||
searchTerm: term,
|
||||
skip: 0,
|
||||
limit: docPerPage
|
||||
},
|
||||
{ headers }
|
||||
);
|
||||
const data = res.data?.result || [];
|
||||
if (!data.error) {
|
||||
setList(data);
|
||||
setIsMoreDocs(data.length >= docPerPage);
|
||||
setIsNextRecord(false);
|
||||
setIsSearchResult(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Search error:", err);
|
||||
}
|
||||
}, 300);
|
||||
setIsSearchResult(false);
|
||||
};
|
||||
|
||||
const handleSearchPaste = (e) => {
|
||||
setTimeout(() => {
|
||||
handleSearchChange({ target: { value: e.target.value } });
|
||||
}, 0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getReportData = async (
|
||||
id,
|
||||
skipUserRecord = 0,
|
||||
limit = 20,
|
||||
term = searchTerm
|
||||
) => {
|
||||
setIsLoader(true);
|
||||
const json = reportJson(id);
|
||||
if (json) {
|
||||
@@ -56,8 +119,13 @@ function DashboardReport(props) {
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
try {
|
||||
const params = { reportId: id, skip: skipUserRecord, limit: limit };
|
||||
const url = `${localStorage.getItem("baseUrl")}/functions/getReport`;
|
||||
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
|
||||
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
|
||||
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||
if (term) {
|
||||
params.searchTerm = term;
|
||||
}
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(url, params, {
|
||||
headers: headers,
|
||||
signal: abortController.signal // is used to cancel fetch query
|
||||
@@ -66,7 +134,7 @@ function DashboardReport(props) {
|
||||
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj.Signers.some(
|
||||
const isSigner = obj.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser
|
||||
);
|
||||
if (isSigner) {
|
||||
@@ -94,7 +162,7 @@ function DashboardReport(props) {
|
||||
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
||||
);
|
||||
} else {
|
||||
if (res.data.result.length === docPerPage) {
|
||||
if (res.data.result.length >= docPerPage) {
|
||||
setIsMoreDocs(true);
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
@@ -139,11 +207,17 @@ function DashboardReport(props) {
|
||||
setIsNextRecord={setIsNextRecord}
|
||||
isMoreDocs={isMoreDocs}
|
||||
docPerPage={docPerPage}
|
||||
mobileSearchOpen={mobileSearchOpen}
|
||||
setMobileSearchOpen={setMobileSearchOpen}
|
||||
searchTerm={searchTerm}
|
||||
handleSearchChange={handleSearchChange}
|
||||
handleSearchPaste={handleSearchPaste}
|
||||
isSearchResult={isSearchResult}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded">
|
||||
<div className="text-center">
|
||||
<p className="text-xl text-black">{t("report-not-found")}</p>
|
||||
<p className="text-xl text-base-content">{t("report-not-found")}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1,14 +1,15 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import "../../styles/opensigndrive.css";
|
||||
import axios from "axios";
|
||||
import * as ContextMenu from "@radix-ui/react-context-menu";
|
||||
import { saveAs } from "file-saver";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ContextMenu } from "radix-ui";
|
||||
import { useNavigate } from "react-router";
|
||||
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 FolderModal from "../shared/fields/FolderModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { handleDownloadPdf, isMobile } from "../../constant/Utils";
|
||||
import Parse from "parse";
|
||||
|
||||
function DriveBody(props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -17,7 +18,7 @@ function DriveBody(props) {
|
||||
const inputRef = useRef(null);
|
||||
const [isOpenMoveModal, setIsOpenMoveModal] = useState(false);
|
||||
const [selectDoc, setSelectDoc] = useState();
|
||||
const [isDeleteDoc, setIsDeleteDoc] = useState(false);
|
||||
const [isDeleteDoc, setIsDeleteDoc] = useState({});
|
||||
const contextMenu = [
|
||||
{ type: "Download", icon: "fa-light fa-arrow-down" },
|
||||
{ type: "Rename", icon: "fa-light fa-font" },
|
||||
@@ -100,16 +101,17 @@ function DriveBody(props) {
|
||||
|
||||
//function for navigate user to microapp-signature component
|
||||
const checkPdfStatus = async (data) => {
|
||||
const signerExist = data.Signers && data.Signers;
|
||||
const isDecline = data.IsDeclined && data.IsDeclined;
|
||||
const isPlaceholder = data.Placeholders && data.Placeholders;
|
||||
const signedUrl = data.SignedUrl;
|
||||
const signerExist = data?.Signers;
|
||||
const isDecline = data?.IsDeclined;
|
||||
const isPlaceholder = data?.Placeholders;
|
||||
const signedUrl = data?.SignedUrl;
|
||||
const isSignYourself = data?.IsSignyourself;
|
||||
//checking if document has completed and request signature flow
|
||||
if (data?.IsCompleted && signerExist?.length > 0) {
|
||||
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||
}
|
||||
//checking if document has completed and signyour-self flow
|
||||
else if (!signerExist && !isPlaceholder) {
|
||||
else if ((!signerExist && !isPlaceholder) || isSignYourself) {
|
||||
navigate(`/signaturePdf/${data.objectId}`);
|
||||
}
|
||||
//checking if document has declined by someone
|
||||
@@ -136,12 +138,10 @@ function DriveBody(props) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuItemClick = (selectType, data) => {
|
||||
const handleMenuItemClick = async (selectType, data, deleteType) => {
|
||||
switch (selectType) {
|
||||
case "Download": {
|
||||
const pdfName = data && data.Name;
|
||||
const pdfUrl = data && data.SignedUrl ? data.SignedUrl : data.URL;
|
||||
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
|
||||
await handleDownloadPdf([data]);
|
||||
break;
|
||||
}
|
||||
case "Rename": {
|
||||
@@ -150,7 +150,7 @@ function DriveBody(props) {
|
||||
break;
|
||||
}
|
||||
case "Delete": {
|
||||
setIsDeleteDoc(true);
|
||||
setIsDeleteDoc({ status: true, deleteType });
|
||||
setSelectDoc(data);
|
||||
break;
|
||||
}
|
||||
@@ -164,7 +164,7 @@ function DriveBody(props) {
|
||||
};
|
||||
//function for delete document
|
||||
const handleDeleteDocument = async (docData) => {
|
||||
setIsDeleteDoc(false);
|
||||
setIsDeleteDoc({});
|
||||
const docId = docData.objectId;
|
||||
const data = {
|
||||
IsArchive: true
|
||||
@@ -264,18 +264,56 @@ function DriveBody(props) {
|
||||
setIsOpenMoveModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const sanitizeFileName = (pdfName) => {
|
||||
// Replace spaces with underscore
|
||||
return pdfName.replace(/ /g, "_");
|
||||
};
|
||||
|
||||
const handleEnterPress = (e, data) => {
|
||||
if (e.key === "Enter") {
|
||||
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
|
||||
const handleFolderData = (data, ind, listType) => {
|
||||
let createddate, status, isDecline, signerExist, isComplete;
|
||||
@@ -309,15 +347,18 @@ function DriveBody(props) {
|
||||
}
|
||||
|
||||
const signersName = () => {
|
||||
const getSignersName = signerExist.map((data) => data.Name);
|
||||
const signerName = getSignersName.join(", ");
|
||||
const getSignersName =
|
||||
signerExist?.length > 0 && signerExist?.map((data) => data?.Name || "");
|
||||
const signerName =
|
||||
getSignersName?.length > 0 ? getSignersName?.join(", ") : "";
|
||||
|
||||
return (
|
||||
<span className="text-[12px] font-medium w-[90%] break-words">
|
||||
{signerName}
|
||||
{signerName && signerName}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return listType === "table" ? (
|
||||
data.Type === "Folder" ? (
|
||||
<tr onClick={() => handleOnclikFolder(data)}>
|
||||
@@ -357,20 +398,24 @@ function DriveBody(props) {
|
||||
e.stopPropagation();
|
||||
handleMenuItemClick("Download", data);
|
||||
}}
|
||||
className="fa-light fa-download mr-[8px] op-text-primary"
|
||||
className="fa-light fa-download mr-[8px] op-text-primary cursor-pointer"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
) : listType === "list" && data.Type === "Folder" ? (
|
||||
<div key={ind} className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<div className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger className="flex flex-col justify-center items-center">
|
||||
<ContextMenu.Trigger className="flex flex-col justify-center items-center select-none-cls">
|
||||
{/* folder */}
|
||||
<div
|
||||
data-tut={props.dataTutSeventh}
|
||||
onClick={() => handleOnclikFolder(data)}
|
||||
onClick={() => {
|
||||
if (!rename) {
|
||||
handleOnclikFolder(data);
|
||||
}
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
@@ -383,7 +428,6 @@ function DriveBody(props) {
|
||||
{rename === data.objectId ? (
|
||||
<input
|
||||
onFocus={() => {
|
||||
inputRef.current.setSelectionRange(0, 0);
|
||||
const input = inputRef.current;
|
||||
if (input) {
|
||||
input.select();
|
||||
@@ -395,16 +439,14 @@ function DriveBody(props) {
|
||||
onKeyDown={(e) => handleEnterPress(e, data)}
|
||||
ref={inputRef}
|
||||
defaultValue={renameValue}
|
||||
// value={renameValue}
|
||||
onChange={(e) => setRenameValue(e.target.value)}
|
||||
className="w-[100px] border-[1.5px] border-black rounded-sm text-[10px]"
|
||||
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
|
||||
/>
|
||||
) : (
|
||||
<span className="fileName">{data.Name}</span>
|
||||
<span className="fileName select-none-cls">{data.Name}</span>
|
||||
)}
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content
|
||||
className="ContextMenuContent"
|
||||
@@ -418,21 +460,39 @@ function DriveBody(props) {
|
||||
<i className="fa-light fa-font mr-[8px]"></i>
|
||||
<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.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu.Root>
|
||||
</div>
|
||||
) : (
|
||||
<HoverCard.Root openDelay={0} closeDelay={100}>
|
||||
<HoverCard.Root
|
||||
open={rename || isMobile ? false : undefined}
|
||||
openDelay={0}
|
||||
closeDelay={100}
|
||||
>
|
||||
<HoverCard.Trigger asChild>
|
||||
<div>
|
||||
<ContextMenu.Root>
|
||||
<div className="relative w-[100px] h-[100px] mx-2 my-3">
|
||||
<ContextMenu.Trigger className="flex flex-col justify-center items-center">
|
||||
<ContextMenu.Trigger
|
||||
asChild
|
||||
className="flex flex-col justify-center items-center select-none-cls"
|
||||
>
|
||||
{/* pdf */}
|
||||
<div
|
||||
data-tut={props.dataTutSixth}
|
||||
onClick={() => checkPdfStatus(data)}
|
||||
onClick={() => {
|
||||
if (!rename) {
|
||||
checkPdfStatus(data);
|
||||
}
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
@@ -447,22 +507,22 @@ function DriveBody(props) {
|
||||
autoFocus={true}
|
||||
type="text"
|
||||
onFocus={() => {
|
||||
inputRef.current.setSelectionRange(0, 0);
|
||||
const input = inputRef.current;
|
||||
if (input) {
|
||||
input.select();
|
||||
}
|
||||
}}
|
||||
onBlur={() => handledRenameDoc(data)}
|
||||
onKeyDown={(e) => handleEnterPress(e, data)}
|
||||
onKeyDown={(e) => handleEnterPress(e, data, data.Type)}
|
||||
ref={inputRef}
|
||||
defaultValue={renameValue}
|
||||
// value={renameValue}
|
||||
onChange={(e) => setRenameValue(e.target.value)}
|
||||
className="w-[100px] border-[1.5px] border-black rounded-sm text-[10px]"
|
||||
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
|
||||
/>
|
||||
) : (
|
||||
<span className="fileName">{data.Name}</span>
|
||||
<span className="fileName select-none-cls">
|
||||
{data.Name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
@@ -567,7 +627,7 @@ function DriveBody(props) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{props.pdfData.map((data, ind) => {
|
||||
{props?.pdfData?.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{handleFolderData(data, ind, "table")}
|
||||
@@ -580,7 +640,7 @@ function DriveBody(props) {
|
||||
) : (
|
||||
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
|
||||
{props.pdfData.map((data, ind) => {
|
||||
return <div key={ind}>{handleFolderData(data, ind, "list")}</div>;
|
||||
return <React.Fragment key={ind}>{handleFolderData(data, ind, "list")}</React.Fragment>;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
@@ -595,22 +655,33 @@ function DriveBody(props) {
|
||||
/>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isDeleteDoc}
|
||||
isOpen={isDeleteDoc.status}
|
||||
title={t("delete-document")}
|
||||
handleClose={() => setIsDeleteDoc(false)}
|
||||
handleClose={() => setIsDeleteDoc({})}
|
||||
>
|
||||
<div className="h-full p-[20px] text-base-content">
|
||||
<p>{t("delete-document-alert")}</p>
|
||||
{isDeleteDoc.deleteType ? (
|
||||
<p>{t("delete-folder-alert")}</p>
|
||||
) : (
|
||||
<p>{t("delete-document-alert")}</p>
|
||||
)}
|
||||
|
||||
<div className="h-[1px] w-full bg-[#9f9f9f] my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => handleDeleteDocument(selectDoc)}
|
||||
onClick={() => {
|
||||
if (isDeleteDoc.deleteType) {
|
||||
handleDeleteFolder(selectDoc);
|
||||
} else {
|
||||
handleDeleteDocument(selectDoc);
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary mr-2"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsDeleteDoc(false)}
|
||||
onClick={() => setIsDeleteDoc({})}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral"
|
||||
>
|
||||
@@ -10,15 +10,15 @@ const AddRoleModal = (props) => {
|
||||
isOpen={props.isModalRole}
|
||||
handleClose={props.handleCloseRoleModal}
|
||||
>
|
||||
<div className="h-full py-[10px] px-[20px]">
|
||||
<div className="text-base-content h-full py-[10px] px-[20px]">
|
||||
<form className="flex flex-col" onSubmit={props.handleAddRole}>
|
||||
<input
|
||||
value={props.roleName}
|
||||
onChange={(e) => props.setRoleName(e.target.value)}
|
||||
placeholder={
|
||||
props.signersdata.length > 0
|
||||
? "User " + (props.signersdata.length + 1)
|
||||
: "User 1"
|
||||
? "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"
|
||||
/>
|
||||
@@ -0,0 +1,133 @@
|
||||
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.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,54 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AgreementContent from "./AgreementContent";
|
||||
|
||||
function AgreementSign(props) {
|
||||
const { t } = useTranslation();
|
||||
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">
|
||||
<div className="text-[11px] md:text-base text-base-content">
|
||||
<span>{t("agree-p1")}</span>
|
||||
<span
|
||||
className="font-bold text-blue-600 cursor-pointer"
|
||||
onClick={() => {
|
||||
setIsShowAgreeTerms(true);
|
||||
}}
|
||||
>
|
||||
{t("agree-p2")}
|
||||
</span>
|
||||
<span> {t("agree-p3")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex mt-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
props.setIsAgree(true);
|
||||
props.showFirstWidget()
|
||||
}}
|
||||
className="op-btn op-btn-primary op-btn-sm w-full md:w-auto"
|
||||
>
|
||||
{t("agrre-button")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-2 text-base-content">
|
||||
<span className="text-[11px]">{t("agreement-note")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isShowAgreeTerms && (
|
||||
<AgreementContent
|
||||
setIsAgree={props.setIsAgree}
|
||||
setIsShowAgreeTerms={setIsShowAgreeTerms}
|
||||
showFirstWidget={props.showFirstWidget}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AgreementSign;
|
||||
@@ -1,13 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
function BorderResize({ right, top }) {
|
||||
return (
|
||||
<div
|
||||
className={`${right ? `-right-[12px]` : "-right-[1px]"} ${
|
||||
top ? `-bottom-[11px]` : "-bottom-[1px]"
|
||||
} absolute inline-block w-[14px] h-[14px] hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BorderResize;
|
||||
@@ -0,0 +1,23 @@
|
||||
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;
|
||||
@@ -0,0 +1,104 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { fontsizeArr, fontColorArr } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function CellsSettingModal({
|
||||
isOpen,
|
||||
handleClose,
|
||||
defaultData,
|
||||
handleSave
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = useState("");
|
||||
const [cellCount, setCellCount] = useState(5);
|
||||
const [fontSize, setFontSize] = useState(12);
|
||||
const [fontColor, setFontColor] = useState("black");
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultData) {
|
||||
setName(defaultData.options?.name || "Cells");
|
||||
setCellCount(defaultData.options?.cellCount || 5);
|
||||
setFontSize(defaultData.options?.fontSize || 12);
|
||||
setFontColor(defaultData.options?.fontColor || "black");
|
||||
}
|
||||
}, [defaultData]);
|
||||
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
handleSave &&
|
||||
handleSave({
|
||||
name,
|
||||
cellCount: parseInt(cellCount, 10),
|
||||
fontSize,
|
||||
fontColor
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalUi isOpen={isOpen} handleClose={handleClose} title={t("widget-info")}>
|
||||
<form onSubmit={onSubmit} className="p-[20px] text-base-content flex flex-col gap-3">
|
||||
<div>
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("name")} <span className="text-[red]">*</span>
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
name="name"
|
||||
className="op-input op-input-bordered op-input-sm w-full text-xs focus:outline-none hover:border-base-content"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="cellCount" className="text-[13px]">
|
||||
{t("cell-count")}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
name="cellCount"
|
||||
className="op-input op-input-bordered op-input-sm w-full text-xs focus:outline-none hover:border-base-content"
|
||||
value={cellCount}
|
||||
onChange={(e) => setCellCount(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="whitespace-nowrap">{t("font-size")}: </span>
|
||||
<select
|
||||
className="ml-[7px] w-[60%] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={fontSize}
|
||||
onChange={(e) => setFontSize(parseInt(e.target.value))}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span>{t("color")}: </span>
|
||||
<select
|
||||
className="ml-[33px] md:ml-4 w-[65%] md:w-full op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={fontColor}
|
||||
onChange={(e) => setFontColor(e.target.value)}
|
||||
>
|
||||
{fontColorArr.map((color, ind) => (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="w-5 h-[19px] ml-1" style={{ background: fontColor }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[1px] w-full bg-[#b7b3b3] my-[16px]"></div>
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("save")}
|
||||
</button>
|
||||
</form>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
const Cell = ({
|
||||
isEnabled,
|
||||
count,
|
||||
h,
|
||||
value,
|
||||
editable,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
inputRef,
|
||||
index,
|
||||
fontSize,
|
||||
fontColor,
|
||||
hint
|
||||
}) => (
|
||||
<div
|
||||
className={`${isEnabled ? "bg-white border-gray-400" : "select-none-cls pointer-events-none border-gray-500"} flex items-center justify-center border-[1px]`}
|
||||
style={{ flex: `0 0 ${100 / count}%`, height: h }}
|
||||
>
|
||||
<input
|
||||
disabled={!isEnabled}
|
||||
maxLength={1}
|
||||
value={value}
|
||||
readOnly={!editable}
|
||||
ref={inputRef}
|
||||
onChange={editable ? (e) => onChange && onChange(e, index) : undefined}
|
||||
onKeyDown={editable ? (e) => onKeyDown && onKeyDown(e, index) : undefined}
|
||||
// trigger validation when leaving a cell
|
||||
onBlur={editable ? (e) => onBlur && onBlur(e, index) : undefined}
|
||||
className={`${isEnabled ? "placeholder-gray-300" : "placeholder-gray-500"} w-full text-center focus:outline-none bg-transparent`}
|
||||
placeholder={hint}
|
||||
style={{ fontFamily: "Arial, sans-serif", fontSize, color: fontColor }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function CellsWidget({
|
||||
isEnabled,
|
||||
count = 8,
|
||||
height = 40,
|
||||
value = "",
|
||||
editable = false,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
onCellCountChange,
|
||||
inputRefs,
|
||||
resizable = false,
|
||||
fontSize = "12px",
|
||||
fontColor = "black",
|
||||
hint = ""
|
||||
}) {
|
||||
const [cellCount, setCellCount] = useState(count);
|
||||
|
||||
// keep internal state in sync with prop updates
|
||||
useEffect(() => setCellCount(count), [count]);
|
||||
|
||||
const startX = useRef(0);
|
||||
const startCount = useRef(cellCount);
|
||||
|
||||
const capture = (downEv, onMove, onUp = () => {}) => {
|
||||
const id = downEv.pointerId;
|
||||
const move = (ev) => id === ev.pointerId && onMove(ev);
|
||||
const up = (ev) => {
|
||||
if (id !== ev.pointerId) return;
|
||||
onUp(ev);
|
||||
downEv.target.releasePointerCapture(id);
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
};
|
||||
window.addEventListener("pointermove", move);
|
||||
window.addEventListener("pointerup", up);
|
||||
downEv.target.setPointerCapture(id);
|
||||
};
|
||||
|
||||
const onTopHandlePointerDown = (ev) => {
|
||||
// Prevent triggering the widget drag logic
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
startX.current = ev.clientX;
|
||||
startCount.current = cellCount;
|
||||
capture(ev, (moveEv) => {
|
||||
const dx = moveEv.clientX - startX.current;
|
||||
const delta = Math.floor(-dx / 5);
|
||||
let newCount = Math.max(1, startCount.current + delta);
|
||||
if (newCount !== cellCount) {
|
||||
setCellCount(newCount);
|
||||
onCellCountChange?.(newCount);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cells = Array.from({ length: cellCount }).map((_, i) => value[i] || "");
|
||||
const hints = Array.from({ length: cellCount }).map((_, i) => hint[i] || "");
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex w-full h-full overflow-visible"
|
||||
style={{ height }}
|
||||
>
|
||||
{resizable && (
|
||||
<div
|
||||
className="cell-size-handle absolute left-1/2 -translate-x-1/2 -bottom-4 rotate-180 cursor-ew-resize touch-none"
|
||||
onPointerDown={onTopHandlePointerDown}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 text-blue-600"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="m9.69 18.933.003.001C9.89 19.02 10 19 10 19s.11.02.308-.066l.002-.001.006-.003.018-.008a5.741 5.741 0 0 0 .281-.14c.186-.096.446-.24.757-.433.62-.384 1.445-.966 2.274-1.765C15.302 14.988 17 12.493 17 9A7 7 0 1 0 3 9c0 3.492 1.698 5.988 3.355 7.584a13.731 13.731 0 0 0 2.273 1.765 11.842 11.842 0 0 0 .976.544l.062.029.018.008.006.003ZM10 11.25a2.25 2.25 0 1 0 0-4.5 2.25 2.25 0 0 0 0 4.5Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
{cells.map((val, i) => (
|
||||
<Cell
|
||||
key={i}
|
||||
isEnabled={isEnabled}
|
||||
count={cellCount}
|
||||
h={height}
|
||||
value={val}
|
||||
editable={editable}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
onBlur={onBlur}
|
||||
inputRef={inputRefs ? (el) => (inputRefs.current[i] = el) : undefined}
|
||||
index={i}
|
||||
fontSize={fontSize}
|
||||
fontColor={fontColor}
|
||||
hint={hints[i]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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,46 +0,0 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
function DefaultSignature({ defaultSignImg, xyPostion, setDefaultSignAlert }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const confirmToaddDefaultSign = () => {
|
||||
if (xyPostion.length > 0) {
|
||||
setDefaultSignAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("default-sign-alert")
|
||||
});
|
||||
} else {
|
||||
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">
|
||||
{t("signature")}
|
||||
</div>
|
||||
<div className="flex flex-col items-center mt-[10px] font-semibold">
|
||||
<p className="text-base-content">{t("Your-Signature")}</p>
|
||||
<div className="op-card shadow-md h-[111px] w-[90%] p-2">
|
||||
<img
|
||||
alt="signature"
|
||||
className="w-full h-full object-contain"
|
||||
src={defaultSignImg}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mt-[10px]"
|
||||
onClick={() => confirmToaddDefaultSign()}
|
||||
>
|
||||
{t("auto-sign-all")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DefaultSignature;
|
||||
@@ -0,0 +1,118 @@
|
||||
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?.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;
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import LoaderWithMsg from "../../primitives/LoaderWithMsg";
|
||||
import { contractDocument } from "../../constant/Utils";
|
||||
import HandleError from "../../primitives/HandleError";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
function useQuery() {
|
||||
return new URLSearchParams(useLocation().search);
|
||||
@@ -30,15 +30,19 @@ function DraftDocument() {
|
||||
documentData === "Error: Something went wrong!" ||
|
||||
(documentData.result && documentData.result.error)
|
||||
) {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: "Error: Something went wrong!"
|
||||
});
|
||||
if (documentData?.result?.error?.includes("deleted")) {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: t("document-deleted")
|
||||
});
|
||||
} else {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: t("something-went-wrong-mssg")
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: "No data found!"
|
||||
});
|
||||
setIsLoading({ isLoader: false, message: t("no-data") });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -54,7 +58,7 @@ function DraftDocument() {
|
||||
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||
}
|
||||
//checking if document has completed and signyour-self flow
|
||||
else if (!signerExist && !isPlaceholder) {
|
||||
else if ((!signerExist && !isPlaceholder) || data?.IsSignyourself) {
|
||||
navigate(`/signaturePdf/${data.objectId}`);
|
||||
}
|
||||
//checking if document has declined by someone
|
||||
@@ -1,40 +1,42 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { radioButtonWidget } from "../../constant/Utils";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
|
||||
function DropdownWidgetOption(props) {
|
||||
const { t } = useTranslation();
|
||||
const [dropdownOptionList, setDropdownOptionList] = useState([
|
||||
"option-1",
|
||||
"option-2"
|
||||
"Option-1",
|
||||
"Option-2"
|
||||
]);
|
||||
const [minCount, setMinCount] = useState(0);
|
||||
const [maxCount, setMaxCount] = useState(0);
|
||||
const [dropdownName, setDropdownName] = useState(props.type);
|
||||
const [dropdownName, setDropdownName] = useState();
|
||||
const [isReadOnly, setIsReadOnly] = useState(false);
|
||||
const [isHideLabel, setIsHideLabel] = useState(false);
|
||||
const [status, setStatus] = useState("required");
|
||||
const [defaultValue, setDefaultValue] = useState("");
|
||||
const statusArr = ["required", "optional"];
|
||||
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
|
||||
const [layout, setLayout] = useState("vertical");
|
||||
const statusArr = ["required", "optional"];
|
||||
const layoutArr = ["vertical", "horizontal"];
|
||||
|
||||
const resetState = () => {
|
||||
setDropdownOptionList(["option-1", "option-2"]);
|
||||
setDropdownName(props.type);
|
||||
setDropdownOptionList(["Option-1", "Option-2"]);
|
||||
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
|
||||
setIsReadOnly(false);
|
||||
setIsHideLabel(false);
|
||||
setMinCount(0);
|
||||
setMaxCount(0);
|
||||
setDefaultCheckbox([]);
|
||||
setDefaultValue("");
|
||||
setLayout("vertical");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.currWidgetsDetails?.options?.name &&
|
||||
props.currWidgetsDetails?.options?.values
|
||||
props.currWidgetsDetails?.options?.values?.length > 0
|
||||
) {
|
||||
setDropdownName(props.currWidgetsDetails?.options?.name);
|
||||
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
|
||||
@@ -49,6 +51,7 @@ function DropdownWidgetOption(props) {
|
||||
setStatus(props.currWidgetsDetails?.options?.status || "required");
|
||||
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
|
||||
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
|
||||
setLayout(props.currWidgetsDetails?.options?.layout || "vertical");
|
||||
} else {
|
||||
setStatus("required");
|
||||
resetState();
|
||||
@@ -104,6 +107,30 @@ function DropdownWidgetOption(props) {
|
||||
? defaultCheckbox
|
||||
: defaultValue;
|
||||
|
||||
const isDropdownOrRadio =
|
||||
props?.type === "dropdown" || props?.type === radioButtonWidget;
|
||||
const readOnlyWithoutValue =
|
||||
isReadOnly && !defaultValue && status !== "optional";
|
||||
const isCheckbox = props?.type === "checkbox";
|
||||
const WidgetLayout = ["checkbox", radioButtonWidget].includes(props.type)
|
||||
? layout
|
||||
: null;
|
||||
|
||||
// If it’s a dropdown and it’s read-only without a value (nor marked optional), stop here.
|
||||
if (isDropdownOrRadio && readOnlyWithoutValue) {
|
||||
alert(t("readonly-error", { widgetName: props?.type }));
|
||||
return;
|
||||
} else if (
|
||||
isCheckbox &&
|
||||
isReadOnly &&
|
||||
minCount > 0 &&
|
||||
defaultCheckbox?.length === 0
|
||||
) {
|
||||
alert(t("readonly-error", { widgetName: props?.type }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise (either not a dropdown, or a valid dropdown), do the save + reset exactly once.
|
||||
props.handleSaveWidgetsOptions(
|
||||
dropdownName,
|
||||
dropdownOptionList,
|
||||
@@ -114,28 +141,12 @@ function DropdownWidgetOption(props) {
|
||||
null,
|
||||
status,
|
||||
defaultData,
|
||||
isHideLabel
|
||||
isHideLabel,
|
||||
WidgetLayout
|
||||
);
|
||||
// props.setShowDropdown(false);
|
||||
setDropdownOptionList(["option-1", "option-2"]);
|
||||
setDropdownName(props.type);
|
||||
// props.setCurrWidgetsDetails({});
|
||||
setIsReadOnly(false);
|
||||
setIsHideLabel(false);
|
||||
setMinCount(0);
|
||||
setMaxCount(0);
|
||||
setDefaultCheckbox([]);
|
||||
setDefaultValue("");
|
||||
resetState();
|
||||
};
|
||||
|
||||
const handleSetMinMax = (e) => {
|
||||
const minValue = e.target.value;
|
||||
if (minValue > dropdownOptionList.length) {
|
||||
return "";
|
||||
} else {
|
||||
return minValue;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDefaultCheck = (index) => {
|
||||
const getDefaultCheck = defaultCheckbox.includes(index);
|
||||
@@ -145,7 +156,6 @@ function DropdownWidgetOption(props) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
|
||||
<div className="h-full p-[15px] text-base-content">
|
||||
@@ -156,30 +166,30 @@ function DropdownWidgetOption(props) {
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<label className="text-[13px] font-semibold">
|
||||
<label htmlFor="title" className="text-[13px] font-semibold">
|
||||
{t("name")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
id="title"
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
defaultValue={dropdownName}
|
||||
value={dropdownName}
|
||||
onChange={(e) => setDropdownName(e.target.value)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
required
|
||||
/>
|
||||
|
||||
<label className="text-[13px] font-semibold mt-[5px]">
|
||||
{t("options")}
|
||||
</label>
|
||||
<div className="flex flex-col">
|
||||
{dropdownOptionList.map((option, index) => (
|
||||
{dropdownOptionList?.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-row mb-[5px] items-center"
|
||||
>
|
||||
{props.type === "checkbox" && !props.isSignYourself && (
|
||||
{props.type === "checkbox" && props.isShowAdvanceFeature && (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={handleDefaultCheck(index)}
|
||||
@@ -222,65 +232,6 @@ function DropdownWidgetOption(props) {
|
||||
onClick={handleAddInput}
|
||||
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
|
||||
></i>
|
||||
{isEnableSubscription && (
|
||||
<div>
|
||||
{props.type === "checkbox" && !props.isSignYourself && (
|
||||
<>
|
||||
<label
|
||||
className={`${
|
||||
!props.isSubscribe ? "text-[gray]" : ""
|
||||
} text-[13px] font-semibold`}
|
||||
>
|
||||
{t("minimun-check")}
|
||||
</label>
|
||||
{!props.isSubscribe && isEnableSubscription && (
|
||||
<Upgrade />
|
||||
)}
|
||||
<input
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
defaultValue={0}
|
||||
value={minCount}
|
||||
disabled={props.isSubscribe ? false : true}
|
||||
onChange={(e) => {
|
||||
const count = handleSetMinMax(e);
|
||||
setMinCount(count);
|
||||
}}
|
||||
className={`${
|
||||
props.isSubscribe ? "" : "pointer-events-none"
|
||||
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
|
||||
/>
|
||||
<label
|
||||
className={`${
|
||||
!props.isSubscribe ? "text-[gray]" : ""
|
||||
} text-[13px] font-semibold`}
|
||||
>
|
||||
{t("maximum-check")}
|
||||
</label>
|
||||
<input
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
defaultValue={0}
|
||||
value={maxCount}
|
||||
disabled={props.isSubscribe ? false : true}
|
||||
onChange={(e) => {
|
||||
const count = handleSetMinMax(e);
|
||||
setMaxCount(count);
|
||||
}}
|
||||
className={`${
|
||||
props.isSubscribe ? "" : "pointer-events-none"
|
||||
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{["dropdown", radioButtonWidget].includes(props.type) && (
|
||||
<>
|
||||
@@ -307,32 +258,81 @@ function DropdownWidgetOption(props) {
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
{props.type !== "checkbox" && props.type !== radioButtonWidget && (
|
||||
<>
|
||||
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
|
||||
{statusArr.map((data, ind) => {
|
||||
{props.type !== "checkbox" && (
|
||||
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
|
||||
{statusArr.map((data, ind) => (
|
||||
<div
|
||||
key={ind}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="op-radio op-radio-xs my-1"
|
||||
type="radio"
|
||||
name="status"
|
||||
onChange={() => setStatus(data.toLowerCase())}
|
||||
checked={status.toLowerCase() === data.toLowerCase()}
|
||||
/>
|
||||
<div className="text-[13px] font-500 capitalize">
|
||||
{data}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center mt-3 mb-3">
|
||||
<span>{t("font-size")} :</span>
|
||||
<select
|
||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={
|
||||
props.fontSize ||
|
||||
props.currWidgetsDetails?.options?.fontSize ||
|
||||
12
|
||||
}
|
||||
onChange={(e) => props.setFontSize(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 className="capitalize">{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 (
|
||||
<div
|
||||
key={ind}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="op-radio op-radio-xs my-1"
|
||||
type="radio"
|
||||
name="status"
|
||||
onChange={() => setStatus(data.toLowerCase())}
|
||||
checked={status.toLowerCase() === data.toLowerCase()}
|
||||
/>
|
||||
<div className="text-[13px] font-500">{data}</div>
|
||||
</div>
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{["checkbox", radioButtonWidget].includes(props.type) && (
|
||||
</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.isSignYourself && (
|
||||
{props.isShowAdvanceFeature && (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="isreadonly"
|
||||
@@ -341,30 +341,66 @@ function DropdownWidgetOption(props) {
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
onChange={(e) => setIsReadOnly(e.target.checked)}
|
||||
/>
|
||||
<label className="ml-1 mb-0" htmlFor="isreadonly">
|
||||
<label
|
||||
className="ml-2 mb-0 capitalize"
|
||||
htmlFor="isreadonly"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="ishidelabel"
|
||||
type="checkbox"
|
||||
checked={isHideLabel}
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
onChange={(e) => setIsHideLabel(e.target.checked)}
|
||||
/>
|
||||
{props.type !== "dropdown" && (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="ishidelabel"
|
||||
type="checkbox"
|
||||
checked={isHideLabel}
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
onChange={(e) => setIsHideLabel(e.target.checked)}
|
||||
/>
|
||||
|
||||
<label className="ml-1 mb-0" htmlFor="ishidelabel">
|
||||
{t("hide-labels")}
|
||||
</label>
|
||||
</div>
|
||||
<label
|
||||
className="ml-2 mb-0 capitalize"
|
||||
htmlFor="ishidelabel"
|
||||
>
|
||||
{t("hide-labels")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{["checkbox", radioButtonWidget].includes(props.type) && (
|
||||
<>
|
||||
<div className="text-[13px] font-semibold mt-[5px] capitalize">
|
||||
{t("layout")}
|
||||
</div>
|
||||
<div
|
||||
className={`${props.type === "checkbox" ? "mb-[10px]" : ""} flex flex-row gap-[10px] mt-[0.5rem]`}
|
||||
>
|
||||
{layoutArr.map((data, ind) => (
|
||||
<div
|
||||
key={ind}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="op-radio op-radio-xs my-1"
|
||||
type="radio"
|
||||
name="layout"
|
||||
checked={layout.toLowerCase() === data.toLowerCase()}
|
||||
onChange={() => setLayout(data.toLowerCase())}
|
||||
/>
|
||||
<label className="text-[13px] font-500 mb-0">
|
||||
{t(data)}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
props.type === "checkbox" && !props.isSignYourself
|
||||
props.type === "checkbox" && props.isShowAdvanceFeature
|
||||
? "mb-[15px]"
|
||||
: "my-[15px]"
|
||||
} w-full h-[1px] bg-[#9f9f9f]`}
|
||||
@@ -1,198 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { checkIsSubscribed, getFileName } from "../../constant/Utils";
|
||||
import Upgrade from "../../primitives/Upgrade";
|
||||
import { isEnableSubscription } from "../../constant/const";
|
||||
import { useTranslation } from "react-i18next";
|
||||
// import SelectFolder from "../../premitives/SelectFolder";
|
||||
|
||||
const EditTemplate = ({ template, onSuccess }) => {
|
||||
const { t } = useTranslation();
|
||||
// const [folder, setFolder] = useState({ ObjectId: "", Name: "" });
|
||||
const [formData, setFormData] = useState({
|
||||
Name: template?.Name || "",
|
||||
Note: template?.Note || "",
|
||||
Description: template?.Description || "",
|
||||
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
|
||||
AutomaticReminders: template?.AutomaticReminders || false,
|
||||
RemindOnceInEvery: template?.RemindOnceInEvery || 5
|
||||
});
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
useEffect(() => {
|
||||
fetchSubscription();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const fetchSubscription = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const getIsSubscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(getIsSubscribe);
|
||||
}
|
||||
};
|
||||
const handleStrInput = (e) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
};
|
||||
// const handleFolder = (data) => {
|
||||
// console.log("handleFolder ", data)
|
||||
// setFolder(data);
|
||||
// };
|
||||
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const isChecked = formData.SendinOrder === "true" ? true : false;
|
||||
const AutoReminder = formData?.AutomaticReminders || false;
|
||||
|
||||
let reminderDate = {};
|
||||
if (AutoReminder) {
|
||||
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
|
||||
const ReminderDate = new Date(template?.createdAt);
|
||||
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
|
||||
reminderDate = { NextReminderDate: ReminderDate };
|
||||
}
|
||||
const data = { ...formData, SendinOrder: isChecked, ...reminderDate };
|
||||
onSuccess(data);
|
||||
};
|
||||
const handleAutoReminder = () => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
AutomaticReminders: !formData.AutomaticReminders
|
||||
}));
|
||||
};
|
||||
return (
|
||||
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
||||
<div className="text-base-content">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("report-heading.File")}
|
||||
</label>
|
||||
<div className="op-input op-input-bordered op-input-sm focus:outline-none py-2 font-semibold w-full text-xs">
|
||||
{getFileName(template.URL)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("name")}
|
||||
<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 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>
|
||||
{isEnableSubscription && (
|
||||
<div className="text-xs mt-2">
|
||||
<span
|
||||
className={
|
||||
isSubscribe ? "font-semibold" : "font-semibold text-gray-300"
|
||||
}
|
||||
>
|
||||
{t("auto-reminder")}
|
||||
{" "}
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</span>
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe
|
||||
? "cursor-pointer "
|
||||
: "pointer-events-none opacity-50"
|
||||
} relative block items-center mb-0`}
|
||||
>
|
||||
<input
|
||||
checked={formData.AutomaticReminders}
|
||||
onChange={handleAutoReminder}
|
||||
type="checkbox"
|
||||
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white mt-2"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{isSubscribe && formData?.AutomaticReminders === true && (
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
{t("remind-once")}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.RemindOnceInEvery}
|
||||
name="RemindOnceInEvery"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onChange={handleStrInput}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-[1rem] flex justify-start">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("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"
|
||||
>
|
||||
{t("browse-or-drag-to-replace-existing-file")}
|
||||
</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,10 @@
|
||||
import React from "react";
|
||||
import { Quill } from "react-quill-new";
|
||||
// Use a third-party plugin so editors can toggle raw HTML view.
|
||||
import htmlEditButton from "quill-html-edit-button";
|
||||
|
||||
// Register the module which adds a "<>" button for HTML editing.
|
||||
Quill.register("modules/htmlEditButton", htmlEditButton);
|
||||
|
||||
// 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
|
||||
@@ -54,17 +59,24 @@ Quill.register(Font, true);
|
||||
export const module1 = {
|
||||
toolbar: {
|
||||
container: "#toolbar1",
|
||||
handlers: {
|
||||
undo: undoChange,
|
||||
redo: redoChange
|
||||
}
|
||||
handlers: { undo: undoChange, redo: redoChange }
|
||||
},
|
||||
history: {
|
||||
delay: 500,
|
||||
maxStack: 100,
|
||||
userOnly: true
|
||||
}
|
||||
history: { delay: 500, maxStack: 100, userOnly: true },
|
||||
// Enable the "<>" button registered above
|
||||
htmlEditButton: {}
|
||||
};
|
||||
|
||||
// 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 },
|
||||
// Enable the "<>" button registered above
|
||||
htmlEditButton: {}
|
||||
};
|
||||
|
||||
// Formats objects for setting up the Quill editor
|
||||
export const formats = [
|
||||
"header",
|
||||
@@ -1,21 +1,22 @@
|
||||
import React, { useState } from "react";
|
||||
import axios from "axios";
|
||||
import { handleDownloadPdf, handleToPrint } from "../../constant/Utils";
|
||||
import { themeColor, emailRegex } from "../../constant/const";
|
||||
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,
|
||||
pdfUrl,
|
||||
setIsEmail,
|
||||
setSuccessEmail,
|
||||
pdfDetails,
|
||||
sender,
|
||||
setIsAlert,
|
||||
extUserId,
|
||||
activeMailAdapter
|
||||
setIsDownloadModal
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [emailList, setEmailList] = useState([]);
|
||||
@@ -24,77 +25,24 @@ function EmailComponent({
|
||||
const [emailErr, setEmailErr] = useState(false);
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
const isAndroid = /Android/i.test(navigator.userAgent);
|
||||
|
||||
//function for send email
|
||||
const sendEmail = async () => {
|
||||
const pdfName = pdfDetails[0]?.Name;
|
||||
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: t("something-went-wrong-mssg")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (sendMail && sendMail.data.result.status === "success") {
|
||||
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);
|
||||
setIsEmail(false);
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
}, 1500);
|
||||
|
||||
setIsLoading(false);
|
||||
} else if (sendMail && sendMail.data.result.status === "error") {
|
||||
setIsLoading(false);
|
||||
setIsEmail(false);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
setIsLoading(false);
|
||||
setIsEmail(false);
|
||||
setIsAlert({
|
||||
@@ -140,6 +88,11 @@ function EmailComponent({
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleClose = () => {
|
||||
setIsEmail(false);
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{/* isEmail */}
|
||||
@@ -159,24 +112,27 @@ function EmailComponent({
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center py-[10px] px-[20px] border-b-[1px] border-base-content">
|
||||
<span className="text-base-content font-semibold">
|
||||
<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, pdfUrl, setIsDownloading)}
|
||||
className="op-btn op-btn-neutral op-btn-sm text-[15px]"
|
||||
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-[15px] ml-2"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
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")}
|
||||
@@ -188,12 +144,12 @@ function EmailComponent({
|
||||
{t("email-mssg")}
|
||||
</p>
|
||||
{emailList.length > 0 ? (
|
||||
<div className="p-0 border-[1.5px] op-border-primary rounded w-full text-[15px]">
|
||||
<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 m-[4px] rounded-md py-[5px] px-[10px]"
|
||||
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]">
|
||||
@@ -213,7 +169,7 @@ function EmailComponent({
|
||||
<input
|
||||
type="email"
|
||||
value={emailValue}
|
||||
className="p-[10px] pb-[20px] rounded w-full text-[15px] bg-transparent outline-none"
|
||||
className="p-[10px] rounded-md w-full text-[15px] bg-transparent outline-none"
|
||||
onChange={handleEmailValue}
|
||||
onKeyDown={handleEnterPress}
|
||||
onBlur={() => emailValue && handleEnterPress("add")}
|
||||
@@ -230,7 +186,7 @@ function EmailComponent({
|
||||
<input
|
||||
type="email"
|
||||
value={emailValue}
|
||||
className="p-[10px] pb-[20px] rounded w-full text-[15px] outline-none bg-transparent border-[1.5px] op-border-primary"
|
||||
className="p-[10px] pb-[20px] text-base-content rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
|
||||
onChange={handleEmailValue}
|
||||
onKeyDown={handleEnterPress}
|
||||
placeholder={t("enter-email-plaholder")}
|
||||
@@ -248,38 +204,22 @@ function EmailComponent({
|
||||
{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 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 text-base-content ml-2"
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
<hr className="w-full my-[15px] bg-base-content" />
|
||||
<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={() => {
|
||||
setIsEmail(false);
|
||||
setEmailValue("");
|
||||
setEmailList([]);
|
||||
}}
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
@@ -0,0 +1,94 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function PageReorderModal({
|
||||
isOpen,
|
||||
handleClose,
|
||||
totalPages = 0,
|
||||
onSave
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [order, setOrder] = useState([]);
|
||||
// Keeps track of the page order relative to the original PDF
|
||||
const orderRef = useRef([]);
|
||||
// Captures the order when the modal opens
|
||||
const initialOrderRef = useRef([]);
|
||||
|
||||
// Initialize orderRef when total pages change (e.g. after upload)
|
||||
useEffect(() => {
|
||||
if (orderRef.current.length !== totalPages) {
|
||||
orderRef.current = Array.from({ length: totalPages }, (_, i) => i + 1);
|
||||
}
|
||||
}, [totalPages]);
|
||||
|
||||
// When modal opens, display the last saved order and store it as initial
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setOrder(orderRef.current);
|
||||
initialOrderRef.current = [...orderRef.current];
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const move = (index, dir) => {
|
||||
const swapIndex = index + dir;
|
||||
if (swapIndex < 0 || swapIndex >= order.length) return;
|
||||
const newOrder = [...order];
|
||||
[newOrder[index], newOrder[swapIndex]] = [newOrder[swapIndex], newOrder[index]];
|
||||
setOrder(newOrder);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const saveOrder = order.map((num) =>
|
||||
initialOrderRef.current.indexOf(num) + 1
|
||||
);
|
||||
// Persist the new display order for next time
|
||||
orderRef.current = [...order];
|
||||
onSave && onSave(saveOrder);
|
||||
};
|
||||
|
||||
const isUnchanged =
|
||||
order.length === initialOrderRef.current.length &&
|
||||
order.every((n, i) => n === initialOrderRef.current[i]);
|
||||
|
||||
return (
|
||||
<ModalUi isOpen={isOpen} handleClose={handleClose} title={t("reorder-pages")}>
|
||||
<div className="p-[20px] flex flex-col gap-2 text-base-content">
|
||||
{order.map((num, i) => (
|
||||
<div key={num} className="flex items-center justify-between">
|
||||
<span>
|
||||
{t("page")} {num}
|
||||
</span>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
className="op-btn op-btn-xs op-btn-ghost"
|
||||
disabled={i === 0}
|
||||
onClick={() => move(i, -1)}
|
||||
>
|
||||
<i className="fa-light fa-arrow-up"></i>
|
||||
</button>
|
||||
<button
|
||||
className="op-btn op-btn-xs op-btn-ghost"
|
||||
disabled={i === order.length - 1}
|
||||
onClick={() => move(i, 1)}
|
||||
>
|
||||
<i className="fa-light fa-arrow-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button onClick={handleSave} type="button" className="op-btn op-btn-primary" disabled={isUnchanged}>
|
||||
{t("save")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-1"
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
}
|
||||
@@ -1,480 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import PrevNext from "./PrevNext";
|
||||
import {
|
||||
handleDownloadCertificate,
|
||||
handleDownloadPdf,
|
||||
handleToPrint
|
||||
} from "../../constant/Utils";
|
||||
import "../../styles/signature.css";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function Header({
|
||||
isPdfRequestFiles,
|
||||
isPlaceholder,
|
||||
setIsDecline,
|
||||
pageNumber,
|
||||
allPages,
|
||||
changePage,
|
||||
pdfUrl,
|
||||
embedWidgetsData,
|
||||
pdfDetails,
|
||||
signerPos,
|
||||
signersdata,
|
||||
isMailSend,
|
||||
alertSendEmail,
|
||||
isCompleted,
|
||||
isShowHeader,
|
||||
decline,
|
||||
currentSigner,
|
||||
alreadySign,
|
||||
isSignYourself,
|
||||
setIsEmail,
|
||||
completeBtnTitle,
|
||||
setIsEditTemplate,
|
||||
isPublicTemplate
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const filterPrefill =
|
||||
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
|
||||
//function for show decline alert
|
||||
const handleDeclinePdfAlert = async () => {
|
||||
const currentDecline = { currnt: "Sure", isDeclined: true };
|
||||
setIsDecline(currentDecline);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex py-[5px]">
|
||||
{isMobile && isShowHeader ? (
|
||||
<div
|
||||
id="navbar"
|
||||
className={isGuestSigner ? "stickySignerHead" : "stickyHead"}
|
||||
style={{
|
||||
width: isGuestSigner
|
||||
? window.innerWidth
|
||||
: window.innerWidth - 30 + "px"
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between items-center py-[5px] px-[10px] ">
|
||||
<div onClick={() => window.history.go(-2)}>
|
||||
<i
|
||||
className="fa-light fa-arrow-left text-base-content"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
<PrevNext
|
||||
pageNumber={pageNumber}
|
||||
allPages={allPages}
|
||||
changePage={changePage}
|
||||
/>
|
||||
{pdfUrl && alreadySign ? (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div className="op-link op-link-primary no-underline text-[16px] font-semibold pr-[3px] pl-[5px]">
|
||||
<i
|
||||
className="fa-light fa-ellipsis-v"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="DropdownMenuContent"
|
||||
sideOffset={5}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i
|
||||
className="fa-light fa-arrow-down mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
{t("download")}
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
{isCompleted && (
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(
|
||||
pdfDetails,
|
||||
setIsDownloading
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="border-none bg-[#fff]">
|
||||
<i
|
||||
className="fa-light fa-award mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
{t("certificate")}
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
{isSignYourself && (
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => setIsEmail(true)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i
|
||||
className="fa-light fa-envelope mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
{t("mail")}
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i
|
||||
className="fa-light fa-print mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
{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
|
||||
*/}
|
||||
{currentSigner && (
|
||||
<div className="flex items-center" data-tut="reactourFifth">
|
||||
{decline && (
|
||||
<div
|
||||
onClick={() => handleDeclinePdfAlert()}
|
||||
className="text-[red] border-none font-[650] text-[14px] mr-2"
|
||||
>
|
||||
{t("decline")}
|
||||
</div>
|
||||
)}
|
||||
{isPlaceholder ? (
|
||||
<div
|
||||
onClick={() => {
|
||||
if (!isMailSend) {
|
||||
alertSendEmail();
|
||||
}
|
||||
}}
|
||||
className={`${
|
||||
isMailSend ? "" : "op-link-primary"
|
||||
} op-link no-underline font-[650] text-[14px]`}
|
||||
data-tut="headerArea"
|
||||
>
|
||||
{completeBtnTitle ? completeBtnTitle : t("send")}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
data-tut="reactourThird"
|
||||
onClick={() => {
|
||||
if (!pdfUrl) {
|
||||
embedWidgetsData();
|
||||
} else if (isPdfRequestFiles) {
|
||||
embedWidgetsData();
|
||||
}
|
||||
}}
|
||||
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
|
||||
>
|
||||
{t("finish")}
|
||||
</div>
|
||||
)}
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<div className="font-[650] text-[18px] px-2 ml-[4px] text-base-content no-underline">
|
||||
<i
|
||||
className="fa-light fa-ellipsis-v"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</div>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="bg-white shadow-md rounded-full px-3 py-2"
|
||||
sideOffset={5}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(
|
||||
pdfDetails,
|
||||
pdfUrl,
|
||||
setIsDownloading
|
||||
)
|
||||
}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-arrow-down mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="font-[500]">{t("download")}</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-wrap justify-between items-center w-full gap-y-1 ml-1">
|
||||
<PrevNext
|
||||
pageNumber={pageNumber}
|
||||
allPages={allPages}
|
||||
changePage={changePage}
|
||||
/>
|
||||
{isPlaceholder ? (
|
||||
<>
|
||||
<div className="flex mx-[100px] lg:mx-0 order-last lg:order-none">
|
||||
{!isMailSend &&
|
||||
signersdata.length > 0 &&
|
||||
signersdata.length !== filterPrefill.length && (
|
||||
<div>
|
||||
{filterPrefill.length === 0 ? (
|
||||
<span className="text-[13px] text-[#f5405e]">
|
||||
{t("add")} {signersdata.length - filterPrefill.length}{" "}
|
||||
{t("recipients")} {t("widgets-name.signature")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[13px] text-[#f5405e]">
|
||||
{t("add")} {signersdata.length - filterPrefill.length}{" "}
|
||||
{t("more")}
|
||||
{t("recipients")} {t("widgets-name.signature")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex">
|
||||
{setIsEditTemplate && (
|
||||
<button
|
||||
onClick={() => setIsEditTemplate(true)}
|
||||
className="outline-none border-none text-center mr-[3px]"
|
||||
>
|
||||
<i className="fa-light fa-gear fa-lg"></i>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => window.history.go(-2)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
>
|
||||
{t("back")}
|
||||
</button>
|
||||
<button
|
||||
disabled={isMailSend && true}
|
||||
data-tut="headerArea"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
|
||||
onClick={() => alertSendEmail()}
|
||||
>
|
||||
{completeBtnTitle
|
||||
? completeBtnTitle
|
||||
: isMailSend
|
||||
? t("sent")
|
||||
: t("send")}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : isPdfRequestFiles ? (
|
||||
alreadySign ? (
|
||||
<div className="flex flex-row">
|
||||
<button
|
||||
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-print py-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="hidden lg:block">{t("print")}</span>
|
||||
</button>
|
||||
{isCompleted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(pdfDetails, setIsDownloading)
|
||||
}
|
||||
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-award py-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="hidden lg:block">{t("certificate")}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-download py-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="hidden lg:block">{t("download")}</span>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex" data-tut="reactourFifth">
|
||||
<button
|
||||
onClick={() => window.history.go(-2)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
||||
>
|
||||
{t("back")}
|
||||
</button>
|
||||
{currentSigner && (
|
||||
<>
|
||||
<button
|
||||
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() => handleDeclinePdfAlert()}
|
||||
>
|
||||
{t("decline")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
|
||||
onClick={() => embedWidgetsData()}
|
||||
>
|
||||
{t("finish")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
|
||||
<span className="hidden lg:block">{t("download")}</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
) : isCompleted ? (
|
||||
<div className="flex flex-row">
|
||||
{isCompleted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(pdfDetails, setIsDownloading)
|
||||
}
|
||||
className="op-btn op-btn-secondary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
>
|
||||
<i className="fa-light fa-award" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">
|
||||
{t("certificate")}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
|
||||
type="button"
|
||||
className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
|
||||
>
|
||||
<i className="fa-light fa-print" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">{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={() =>
|
||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
||||
}
|
||||
>
|
||||
<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={() => setIsEmail(true)}
|
||||
>
|
||||
<i className="fa-light fa-envelope" aria-hidden="true"></i>
|
||||
<span className="hidden lg:block ml-1">{t("mail")}</span>
|
||||
</button>
|
||||
</div>
|
||||
) : isPublicTemplate ? (
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary op-btn-sm shadow"
|
||||
onClick={() => {
|
||||
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={() => {
|
||||
if (!pdfUrl) {
|
||||
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"}
|
||||
title={
|
||||
isDownloading === "certificate"
|
||||
? t("generating-certificate")
|
||||
: t("pdf-download")
|
||||
}
|
||||
handleClose={() => setIsDownloading("")}
|
||||
>
|
||||
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
|
||||
{isDownloading === "certificate"}{" "}
|
||||
<p>{t("generate-certificate-alert")}</p>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,776 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import PrevNext from "./PrevNext";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
decryptPdf,
|
||||
deletePdfPage,
|
||||
flattenPdf,
|
||||
getFileAsArrayBuffer,
|
||||
handleDownloadCertificate,
|
||||
handleDownloadPdf,
|
||||
handleRemoveWidgets,
|
||||
handleToPrint,
|
||||
reorderPdfPages
|
||||
} from "../../constant/Utils";
|
||||
import "../../styles/signature.css";
|
||||
import { DropdownMenu } from "radix-ui";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import PageReorderModal from "./PageReorderModal";
|
||||
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 [isReorderModal, setIsReorderModal] = 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 {
|
||||
let uploadedPdfBytes = await file.arrayBuffer();
|
||||
try {
|
||||
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 401) {
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
// Upload the file to Parse Server
|
||||
} catch (err) {
|
||||
console.error("Incorrect password or decryption failed", err);
|
||||
alert("Incorrect password or decryption failed.");
|
||||
}
|
||||
} else {
|
||||
alert("Please provided Password.");
|
||||
}
|
||||
} else {
|
||||
console.log("Err ", err);
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReorderSave = async (order) => {
|
||||
try {
|
||||
const pdfupdatedData = await reorderPdfPages(props.pdfArrayBuffer, order);
|
||||
if (pdfupdatedData) {
|
||||
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
props.setPdfBase64Url(pdfupdatedData.base64);
|
||||
props.setAllPages(pdfupdatedData.totalPages);
|
||||
props.setPageNumber(1);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in reorder pdf pages", e);
|
||||
}
|
||||
setIsReorderModal(false);
|
||||
};
|
||||
return (
|
||||
<div className="flex py-[5px]">
|
||||
{isMobile && props?.isShowHeader ? (
|
||||
<div
|
||||
id="navbar"
|
||||
className="stickyHead touch-none"
|
||||
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={() => setIsReorderModal(true)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-list-ol text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">
|
||||
{t("reorder-pages")}
|
||||
</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>
|
||||
<PageReorderModal
|
||||
isOpen={isReorderModal}
|
||||
handleClose={() => setIsReorderModal(false)}
|
||||
totalPages={props.allPages}
|
||||
onSave={handleReorderSave}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -1,55 +0,0 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function PdfZoom(props) {
|
||||
const { t } = useTranslation();
|
||||
const onClickZoomIn = () => {
|
||||
props.setScale(props.scale + 0.1 * props.scale);
|
||||
props.setZoomPercent(props.zoomPercent + 10);
|
||||
};
|
||||
const onClickZoomOut = () => {
|
||||
if (props.zoomPercent > 0) {
|
||||
if (props.zoomPercent === 10) {
|
||||
props.setScale(1);
|
||||
} else {
|
||||
props.setScale(props.scale - 0.1 * props.scale);
|
||||
}
|
||||
props.setZoomPercent(props.zoomPercent - 10);
|
||||
}
|
||||
};
|
||||
const handleReset = () => {
|
||||
props.setScale(1);
|
||||
props.setZoomPercent(0);
|
||||
};
|
||||
return (
|
||||
// md:mt-[41px] xl:mt-[63px]mt=[]
|
||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[50px] 2xl:mt-[63px]">
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={onClickZoomIn}
|
||||
title={t("zoom-in")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={handleReset}
|
||||
title={t("reset")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-arrows-rotate text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
|
||||
onClick={onClickZoomOut}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default PdfZoom;
|
||||
@@ -0,0 +1,261 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
decryptPdf,
|
||||
deletePdfPage,
|
||||
flattenPdf,
|
||||
getFileAsArrayBuffer,
|
||||
handleRemoveWidgets,
|
||||
reorderPdfPages
|
||||
} from "../../constant/Utils";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
import PageReorderModal from "./PageReorderModal";
|
||||
|
||||
function PdfZoom(props) {
|
||||
const { t } = useTranslation();
|
||||
const mergePdfInputRef = useRef(null);
|
||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||
const [isReorderModal, setIsReorderModal] = 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 {
|
||||
let uploadedPdfBytes = await file.arrayBuffer();
|
||||
try {
|
||||
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 401) {
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
// Upload the file to Parse Server
|
||||
} catch (err) {
|
||||
console.error("Incorrect password or decryption failed", err);
|
||||
alert("Incorrect password or decryption failed.");
|
||||
}
|
||||
} else {
|
||||
alert("Please provided Password.");
|
||||
}
|
||||
} else {
|
||||
console.log("Err ", err);
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReorderSave = async (order) => {
|
||||
try {
|
||||
const pdfupdatedData = await reorderPdfPages(props.pdfArrayBuffer, order);
|
||||
if (pdfupdatedData) {
|
||||
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
props.setPdfBase64Url(pdfupdatedData.base64);
|
||||
props.setAllPages(pdfupdatedData.totalPages);
|
||||
props.setPageNumber(1);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in reorder pdf pages", e);
|
||||
}
|
||||
setIsReorderModal(false);
|
||||
};
|
||||
|
||||
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={() => setIsReorderModal(true)}
|
||||
title={t("reorder-pages")}
|
||||
>
|
||||
<i className="fa-light fa-list-ol 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>
|
||||
<PageReorderModal
|
||||
isOpen={isReorderModal}
|
||||
handleClose={() => setIsReorderModal(false)}
|
||||
totalPages={props.allPages}
|
||||
onSave={handleReorderSave}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PdfZoom;
|
||||
@@ -1,933 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import BorderResize from "./BorderResize";
|
||||
import PlaceholderBorder from "./PlaceholderBorder";
|
||||
import { Rnd } from "react-rnd";
|
||||
import {
|
||||
defaultWidthHeight,
|
||||
getContainerScale,
|
||||
handleCopyNextToWidget,
|
||||
isTabAndMobile,
|
||||
onChangeInput,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
import PlaceholderType from "./PlaceholderType";
|
||||
import moment from "moment";
|
||||
import "../../styles/opensigndrive.css";
|
||||
|
||||
const selectFormat = (data) => {
|
||||
switch (data) {
|
||||
case "L":
|
||||
return "MM/dd/yyyy";
|
||||
case "DD-MM-YYYY":
|
||||
return "dd-MM-yyyy";
|
||||
case "DD/MM/YYYY":
|
||||
return "dd/MM/yyyy";
|
||||
case "LL":
|
||||
return "MMMM dd, yyyy";
|
||||
case "DD MMM, YYYY":
|
||||
return "dd MMM, yyyy";
|
||||
case "YYYY-MM-DD":
|
||||
return "yyyy-MM-dd";
|
||||
case "MM-DD-YYYY":
|
||||
return "MM-dd-yyyy";
|
||||
case "MM.DD.YYYY":
|
||||
return "MM.dd.yyyy";
|
||||
case "MMM DD, YYYY":
|
||||
return "MMM dd, yyyy";
|
||||
case "DD MMMM, YYYY":
|
||||
return "dd MMMM, yyyy";
|
||||
default:
|
||||
return "MM/dd/yyyy";
|
||||
}
|
||||
};
|
||||
|
||||
const changeDateToMomentFormat = (format) => {
|
||||
switch (format) {
|
||||
case "MM/dd/yyyy":
|
||||
return "L";
|
||||
case "dd-MM-yyyy":
|
||||
return "DD-MM-YYYY";
|
||||
case "dd/MM/yyyy":
|
||||
return "DD/MM/YYYY";
|
||||
case "MMMM dd, yyyy":
|
||||
return "LL";
|
||||
case "dd MMM, yyyy":
|
||||
return "DD MMM, YYYY";
|
||||
case "yyyy-MM-dd":
|
||||
return "YYYY-MM-DD";
|
||||
case "MM-dd-yyyy":
|
||||
return "MM-DD-YYYY";
|
||||
case "MM.dd.yyyy":
|
||||
return "MM.DD.YYYY";
|
||||
case "MMM dd, yyyy":
|
||||
return "MMM DD, YYYY";
|
||||
case "dd MMMM, yyyy":
|
||||
return "DD MMMM, YYYY";
|
||||
default:
|
||||
return "L";
|
||||
}
|
||||
};
|
||||
|
||||
//function to get default date
|
||||
const getDefaultdate = (selectedDate, format = "dd-MM-yyyy") => {
|
||||
let date;
|
||||
if (format && format === "dd-MM-yyyy") {
|
||||
const newdate = selectedDate
|
||||
? selectedDate
|
||||
: moment(new Date()).format(changeDateToMomentFormat(format));
|
||||
const [day, month, year] = newdate.split("-");
|
||||
date = new Date(`${year}-${month}-${day}`);
|
||||
} else {
|
||||
date = new Date(selectedDate);
|
||||
}
|
||||
const value = date;
|
||||
return value;
|
||||
};
|
||||
//function to get default format
|
||||
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
||||
|
||||
function Placeholder(props) {
|
||||
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
|
||||
const [isDraggingEnabled, setDraggingEnabled] = useState(true);
|
||||
const [isShowDateFormat, setIsShowDateFormat] = useState(false);
|
||||
const [containerScale, setContainerScale] = useState();
|
||||
const [selectDate, setSelectDate] = useState({
|
||||
date:
|
||||
props.pos.type === "date"
|
||||
? moment(
|
||||
getDefaultdate(
|
||||
props?.pos?.options?.response,
|
||||
props.pos?.options?.validation?.format
|
||||
).getTime()
|
||||
).format(
|
||||
changeDateToMomentFormat(props.pos?.options?.validation?.format)
|
||||
)
|
||||
: "",
|
||||
format:
|
||||
props.pos.type === "date"
|
||||
? getDefaultFormat(props.pos?.options?.validation?.format)
|
||||
: ""
|
||||
});
|
||||
const [dateFormat, setDateFormat] = useState([]);
|
||||
const [startDate, setStartDate] = useState(
|
||||
props.pos.type === "date" &&
|
||||
getDefaultdate(
|
||||
props?.pos?.options?.response,
|
||||
props.pos?.options?.validation?.format
|
||||
)
|
||||
);
|
||||
const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({
|
||||
width: null,
|
||||
height: null
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const getPdfPageWidth = props.pdfOriginalWH.find(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
);
|
||||
setContainerScale(props.containerWH.width / getPdfPageWidth.width);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.containerWH.width, props.pdfOriginalWH]);
|
||||
const dateFormatArr = [
|
||||
"L",
|
||||
"DD-MM-YYYY",
|
||||
"YYYY-MM-DD",
|
||||
"MM.DD.YYYY",
|
||||
"MM-DD-YYYY",
|
||||
"MMM DD, YYYY",
|
||||
"LL",
|
||||
"DD MMM, YYYY",
|
||||
"DD MMMM, YYYY"
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const updateWidth = () => {
|
||||
const rndElement = document.getElementById(props.pos.key);
|
||||
if (rndElement) {
|
||||
const { width, height } = rndElement.getBoundingClientRect();
|
||||
setGetCheckboxRenderWidth({ width: width, height: height });
|
||||
}
|
||||
};
|
||||
|
||||
// Delay to ensure rendering is complete
|
||||
const timer = setTimeout(updateWidth, 0);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [props.pos]);
|
||||
|
||||
useEffect(() => {
|
||||
const closeMenuOnOutsideClick = (e) => {
|
||||
if (!isDraggingEnabled && !e.target.closest("#changeIsDragging")) {
|
||||
setDraggingEnabled(true);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", closeMenuOnOutsideClick);
|
||||
|
||||
return () => {
|
||||
// Cleanup the event listener when the component unmounts
|
||||
document.removeEventListener("click", closeMenuOnOutsideClick);
|
||||
};
|
||||
}, [isDraggingEnabled]);
|
||||
//function change format array list with selected date and format
|
||||
const changeDateFormat = () => {
|
||||
const updateDate = [];
|
||||
dateFormatArr.map((data) => {
|
||||
let date;
|
||||
if (selectDate && selectDate.format === "dd-MM-yyyy") {
|
||||
const [day, month, year] = selectDate.date.split("-");
|
||||
date = new Date(`${year}-${month}-${day}`);
|
||||
} else {
|
||||
date = new Date(selectDate?.date);
|
||||
}
|
||||
const milliseconds = date.getTime();
|
||||
const newDate = moment(milliseconds).format(data);
|
||||
const dateObj = {
|
||||
date: newDate,
|
||||
format: selectFormat(data)
|
||||
};
|
||||
updateDate.push(dateObj);
|
||||
});
|
||||
setDateFormat(updateDate);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isPlaceholder || props.isSignYourself) {
|
||||
selectDate && changeDateFormat();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectDate]);
|
||||
|
||||
//it detect outside click of date dropdown menu
|
||||
useEffect(() => {
|
||||
const closeMenuOnOutsideClick = (e) => {
|
||||
if (isShowDateFormat && !e.target.closest("#menu-container")) {
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
}
|
||||
};
|
||||
document.addEventListener("click", closeMenuOnOutsideClick);
|
||||
return () => {
|
||||
// Cleanup the event listener when the component unmounts
|
||||
document.removeEventListener("click", closeMenuOnOutsideClick);
|
||||
};
|
||||
}, [isShowDateFormat]);
|
||||
|
||||
//`handleWidgetIdandPopup` is used to set current widget id and open relative popup
|
||||
const handleWidgetIdandPopup = () => {
|
||||
if (props.setSelectWidgetId) {
|
||||
props.setSelectWidgetId(props.pos.key);
|
||||
}
|
||||
|
||||
const widgetTypeExist = [
|
||||
textInputWidget,
|
||||
"checkbox",
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"date",
|
||||
"email",
|
||||
textWidget
|
||||
].includes(props.pos.type);
|
||||
|
||||
if (widgetTypeExist) {
|
||||
setDraggingEnabled(false);
|
||||
}
|
||||
if ((props.isNeedSign || props.isSignYourself) && !props.isDragging) {
|
||||
if (props?.ispublicTemplate) {
|
||||
props.handleUserDetails();
|
||||
} else {
|
||||
if (props.pos.type) {
|
||||
if (
|
||||
props.pos.type === "signature" ||
|
||||
props.pos.type === "stamp" ||
|
||||
props.pos.type === "image"
|
||||
) {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
} else if (props.pos.type === "initials") {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
props.setIsInitial(true);
|
||||
}
|
||||
} else {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos?.isStamp ? props.pos.isStamp : false);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
props.isPlaceholder &&
|
||||
!props.isDragging &&
|
||||
props.pos.type !== textWidget
|
||||
) {
|
||||
if (props.pos.key === props.selectWidgetId) {
|
||||
props.handleLinkUser(props.data.Id);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
} else if (!props.pos.type) {
|
||||
if (
|
||||
!props.pos.type &&
|
||||
props.isNeedSign &&
|
||||
props.data.signerObjId === props.signerObjId
|
||||
) {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
} else if (
|
||||
(props.isNeedSign && props.pos.type === "signature") ||
|
||||
props.pos.type === "stamp"
|
||||
) {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
} else if (props.isNeedSign && props.pos.type === "dropdown") {
|
||||
props.setSignKey(props.pos.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleOnClickPlaceholder = () => {
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
|
||||
if (!props.isNeedSign) {
|
||||
props.setWidgetType(props.pos.type);
|
||||
}
|
||||
if (props.isNeedSign && props.data?.signerObjId === props.signerObjId) {
|
||||
handleWidgetIdandPopup();
|
||||
} else if (
|
||||
props?.isNeedSign &&
|
||||
props?.uniqueId &&
|
||||
props.data?.Id === props?.uniqueId
|
||||
) {
|
||||
handleWidgetIdandPopup();
|
||||
} else if (props.isPlaceholder || props.isSignYourself) {
|
||||
handleWidgetIdandPopup();
|
||||
}
|
||||
};
|
||||
//`handleOnClickSettingIcon` is used set current widget details and open setting of it
|
||||
const handleOnClickSettingIcon = () => {
|
||||
if (props.pos.type === radioButtonWidget) {
|
||||
props.setIsRadio(true);
|
||||
} else if (props.pos.type === "dropdown") {
|
||||
props?.setShowDropdown(true);
|
||||
} else if (props.pos.type === "checkbox") {
|
||||
props?.setIsCheckbox(true);
|
||||
}
|
||||
//condition to handle setting icon for signyour-self flow for all type text widgets
|
||||
else if (
|
||||
[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
].includes(props.pos.type) &&
|
||||
props.isSignYourself
|
||||
) {
|
||||
props.handleTextSettingModal(true);
|
||||
} else {
|
||||
props?.handleNameModal && props?.handleNameModal(true);
|
||||
}
|
||||
|
||||
//condition for only placeholder and template flow
|
||||
if (props.data && props?.pos?.type !== textWidget) {
|
||||
props.setSignerObjId(props?.data?.signerObjId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
//condition to handle in placeholder and template flow for text widget signerId for text widgets i have to set uniqueId in tempSignerId because
|
||||
//it does not have any signer user and any signerobjId
|
||||
else if (props.data && props.pos.type === textWidget) {
|
||||
props.setTempSignerId(props.uniqueId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setWidgetType(props.pos.type);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
};
|
||||
//function to set required state value onclick on widget's copy icon
|
||||
const handleCopyPlaceholder = (e) => {
|
||||
e.stopPropagation();
|
||||
//condition to handle text widget signer obj id and unique id
|
||||
//when user click on copy icon of text widget in that condition text widget does not have any signerObjId
|
||||
//in that case i have to save in tempSignerId as a unique id of previous select signer's unique id
|
||||
//and on save or cancel button of copy all page popup i have set this temp signer Id in unique id
|
||||
|
||||
if (props.data && props?.pos?.type !== textWidget) {
|
||||
props.setSignerObjId(props?.data?.signerObjId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
} else if (props.data && props.pos.type === textWidget) {
|
||||
props.setTempSignerId(props.uniqueId);
|
||||
props.setSignerObjId(props?.data?.signerObjId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
|
||||
//checking widget's type and open widget copy modal for required widgets
|
||||
if (
|
||||
["signature", textWidget, "stamp", "initial"].includes(props.pos.type)
|
||||
) {
|
||||
props.setIsPageCopy(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
} else {
|
||||
//function to create new widget next to just widget
|
||||
handleCopyNextToWidget(
|
||||
props.pos,
|
||||
props.pos.type,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
//function to save date and format on local array onchange date and onclick format
|
||||
const handleSaveDate = (data, isDateChange) => {
|
||||
let updateDate = data.date;
|
||||
//check if date change by user
|
||||
if (isDateChange) {
|
||||
//`changeDateToMomentFormat` is used to convert date as per required to moment package
|
||||
updateDate = moment(data.date).format(
|
||||
changeDateToMomentFormat(data.format)
|
||||
);
|
||||
}
|
||||
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
|
||||
//`getDefaultdate` is used to convert update date in new Date() format
|
||||
const date = moment(
|
||||
getDefaultdate(updateDate, data?.format).getTime()
|
||||
).format(changeDateToMomentFormat(data?.format));
|
||||
|
||||
//`onChangeInput` is used to save data related to date in a placeholder field
|
||||
onChangeInput(
|
||||
date,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
data?.format
|
||||
);
|
||||
setSelectDate({ date: date, format: data?.format });
|
||||
};
|
||||
const PlaceholderIcon = () => {
|
||||
return (
|
||||
props.isShowBorder && (
|
||||
<>
|
||||
{(props.isPlaceholder || props.isSignYourself) && (
|
||||
<>
|
||||
{/* condition to add setting icon for signyour-self flow for particular text widgets
|
||||
and also it have diffrent position
|
||||
*/}
|
||||
{[
|
||||
"checkbox",
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
].includes(props.pos.type) && props.isSignYourself ? (
|
||||
<i
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
className="fa-light fa-gear icon"
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
right: "29px",
|
||||
top: "-19px"
|
||||
}}
|
||||
></i>
|
||||
) : (
|
||||
/* condition to add setting icon for placeholder & template flow for all widgets except signature and date */
|
||||
((!props?.pos?.type && props.pos.isStamp) ||
|
||||
(props?.pos?.type &&
|
||||
!["date", "signature"].includes(props.pos.type) &&
|
||||
!props.isSignYourself)) && (
|
||||
<i
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOnClickSettingIcon();
|
||||
}}
|
||||
className="fa-light fa-gear icon"
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
right: props?.pos?.type === textWidget ? "32px" : "47px",
|
||||
top: "-19px"
|
||||
}}
|
||||
></i>
|
||||
)
|
||||
)}
|
||||
{/* condition for usericon for all widgets except text widgets and signyour-self flow */}
|
||||
{props.pos.type !== textWidget && !props.isSignYourself && (
|
||||
<i
|
||||
data-tut="assignSigner"
|
||||
className="fa-light fa-user icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
props.handleLinkUser(props.data.Id);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
props.handleLinkUser(props.data.Id);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}}
|
||||
style={{ color: "#188ae2", right: "32px", top: "-18px" }}
|
||||
></i>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* setting icon only for date widgets */}
|
||||
{props.pos.type === "date" && selectDate && (
|
||||
<div
|
||||
id="menu-container"
|
||||
style={{
|
||||
zIndex: "99",
|
||||
top: "-19px",
|
||||
right: props.isPlaceholder ? "60px" : "44px"
|
||||
}}
|
||||
className={`${isShowDateFormat ? "show" : ""} dropdown icon`}
|
||||
onClick={(e) => {
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
e.stopPropagation();
|
||||
if (props.data) {
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
if (props.data) {
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPostion.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-gear icon"
|
||||
style={{
|
||||
color: "#188ae2",
|
||||
fontSize: "14px"
|
||||
}}
|
||||
></i>
|
||||
<div
|
||||
className={
|
||||
isShowDateFormat ? "dropdown-menu show" : "dropdown-menu"
|
||||
}
|
||||
aria-labelledby="dropdownMenuButton"
|
||||
aria-expanded={isShowDateFormat ? "true" : "false"}
|
||||
>
|
||||
{dateFormat.map((data, ind) => {
|
||||
return (
|
||||
<span
|
||||
key={ind}
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
setSelectDate(data);
|
||||
handleSaveDate(data);
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsShowDateFormat(!isShowDateFormat);
|
||||
setSelectDate(data);
|
||||
handleSaveDate(data);
|
||||
}}
|
||||
className="dropdown-item text-[13px]"
|
||||
>
|
||||
{data?.date ? data?.date : "nodata"}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* copy icon for all widgets */}
|
||||
<i
|
||||
className="fa-light fa-copy icon"
|
||||
onClick={(e) => handleCopyPlaceholder(e)}
|
||||
onTouchEnd={(e) => handleCopyPlaceholder(e)}
|
||||
style={{ color: "#188ae2", right: "12px", top: "-18px" }}
|
||||
></i>
|
||||
{/* delete icon for all widgets */}
|
||||
<i
|
||||
className="fa-light fa-circle-xmark icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
//condition for template and placeholder flow
|
||||
if (props.data) {
|
||||
props.handleDeleteSign(props.pos.key, props.data.Id);
|
||||
}
|
||||
//condition for signyour-self flow
|
||||
else {
|
||||
props.handleDeleteSign(props.pos.key);
|
||||
props.setIsStamp(false);
|
||||
}
|
||||
}}
|
||||
//for mobile and tablet touch event
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
//condition for template and placeholder flow
|
||||
if (props.data) {
|
||||
props.handleDeleteSign(props.pos.key, props.data?.Id);
|
||||
}
|
||||
//condition for signyour-self flow
|
||||
else {
|
||||
props.handleDeleteSign(props.pos.key);
|
||||
props.setIsStamp(false);
|
||||
}
|
||||
}}
|
||||
style={{ color: "#188ae2", right: "-8px", top: "-18px" }}
|
||||
></i>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
const xPos = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
props.pdfOriginalWH,
|
||||
props.pageNumber,
|
||||
props.containerWH
|
||||
);
|
||||
const resizePos = pos.xPosition;
|
||||
if (signYourself) {
|
||||
return resizePos * containerScale * props.scale;
|
||||
} else {
|
||||
//checking both condition mobile and desktop view
|
||||
if (pos.isMobile && pos.scale) {
|
||||
if (props.scale > 1) {
|
||||
return resizePos * pos.scale * containerScale * props.scale;
|
||||
} else {
|
||||
return resizePos * pos.scale * containerScale;
|
||||
}
|
||||
} else {
|
||||
// if (pos.scale === containerScale) {
|
||||
// return resizePos * pos.scale * props.scale;
|
||||
// } else {
|
||||
return resizePos * containerScale * props.scale;
|
||||
// }
|
||||
}
|
||||
}
|
||||
};
|
||||
const yPos = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
props.pdfOriginalWH,
|
||||
props.pageNumber,
|
||||
props.containerWH
|
||||
);
|
||||
const resizePos = pos.yPosition;
|
||||
|
||||
if (signYourself) {
|
||||
return resizePos * containerScale * props.scale;
|
||||
} else {
|
||||
//checking both condition mobile and desktop view
|
||||
if (pos.isMobile && pos.scale) {
|
||||
if (props.scale > 1) {
|
||||
return resizePos * pos.scale * containerScale * props.scale;
|
||||
} else {
|
||||
return resizePos * pos.scale * containerScale;
|
||||
}
|
||||
}
|
||||
// else if (pos.scale === containerScale) {
|
||||
// if (props.scale > 1) {
|
||||
// return resizePos * pos.scale * props.scale;
|
||||
// } else {
|
||||
// return resizePos * pos.scale;
|
||||
// }
|
||||
// }
|
||||
else {
|
||||
return resizePos * containerScale * props.scale;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getCursor = () => {
|
||||
if (props.data && props.isNeedSign) {
|
||||
if (props?.signerObjId && props.data?.signerObjId === props.signerObjId) {
|
||||
return "pointer";
|
||||
} else if (props?.uniqueId && props.data?.Id === props.uniqueId) {
|
||||
return "pointer";
|
||||
} else {
|
||||
return "not-allowed";
|
||||
}
|
||||
} else {
|
||||
return "all-scroll";
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Rnd
|
||||
id={props.pos.key}
|
||||
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
|
||||
key={props.pos.key}
|
||||
lockAspectRatio={
|
||||
![
|
||||
textWidget,
|
||||
"email",
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
textInputWidget
|
||||
].includes(props.pos.type) &&
|
||||
(props.pos.Width
|
||||
? props.pos.Width / props.pos.Height
|
||||
: defaultWidthHeight(props.pos.type).width /
|
||||
defaultWidthHeight(props.pos.type).height)
|
||||
}
|
||||
enableResizing={{
|
||||
top: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight:
|
||||
props.data && props.isNeedSign
|
||||
? props.data?.signerObjId === props.signerObjId &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.type !== radioButtonWidget
|
||||
? true
|
||||
: false
|
||||
: props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId &&
|
||||
true,
|
||||
bottomLeft: false,
|
||||
topLeft: false
|
||||
}}
|
||||
bounds="parent"
|
||||
className="signYourselfBlock"
|
||||
style={{
|
||||
border: "1px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
textAlign:
|
||||
props.pos.type !== "name" &&
|
||||
props.pos.type !== "company" &&
|
||||
props.pos.type !== "job title" &&
|
||||
"center",
|
||||
cursor: getCursor(),
|
||||
zIndex:
|
||||
props.pos.type === "date"
|
||||
? props.pos.key === props.selectWidgetId
|
||||
? 99 + 1
|
||||
: 99
|
||||
: props?.pos?.zIndex
|
||||
? props.pos.zIndex
|
||||
: "5",
|
||||
background: props.data ? props.data.blockColor : "rgb(203 233 237)"
|
||||
}}
|
||||
onDrag={() => {
|
||||
setDraggingEnabled(true);
|
||||
props.handleTabDrag && props.handleTabDrag(props.pos.key);
|
||||
}}
|
||||
size={{
|
||||
width:
|
||||
props.pos.type === radioButtonWidget || props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posWidth(props.pos, props.isSignYourself),
|
||||
height:
|
||||
props.pos.type === radioButtonWidget || props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself)
|
||||
}}
|
||||
onResizeStart={() => {
|
||||
setDraggingEnabled(true);
|
||||
props.setIsResize && props.setIsResize(true);
|
||||
}}
|
||||
onResizeStop={(e, direction, ref) => {
|
||||
props.setIsResize && props.setIsResize(false);
|
||||
props.handleSignYourselfImageResize &&
|
||||
props.handleSignYourselfImageResize(
|
||||
ref,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.setXyPostion,
|
||||
props.index,
|
||||
containerScale,
|
||||
props.scale,
|
||||
props.data && props.data.Id,
|
||||
props.isResize
|
||||
);
|
||||
}}
|
||||
disableDragging={
|
||||
props.isNeedSign
|
||||
? true
|
||||
: props.isPlaceholder && ![textWidget].includes(props.pos.type)
|
||||
? false
|
||||
: !isDraggingEnabled
|
||||
}
|
||||
onDragStop={(event, dragElement) =>
|
||||
props.handleStop &&
|
||||
props.handleStop(event, dragElement, props.data?.Id, props.pos?.key)
|
||||
}
|
||||
position={{
|
||||
x: xPos(props.pos, props.isSignYourself),
|
||||
y: yPos(props.pos, props.isSignYourself)
|
||||
}}
|
||||
onResize={(e, direction, ref) => {
|
||||
setPlaceholderBorder({
|
||||
w: ref.offsetWidth / (props.scale * containerScale),
|
||||
h: ref.offsetHeight / (props.scale * containerScale)
|
||||
});
|
||||
}}
|
||||
onClick={() => handleOnClickPlaceholder()}
|
||||
>
|
||||
{props.isShowBorder &&
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId ? (
|
||||
<BorderResize right={-12} top={-11} />
|
||||
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
|
||||
props.data?.signerObjId === props.signerObjId &&
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" ? (
|
||||
<BorderResize />
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
) : (
|
||||
props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId && <BorderResize />
|
||||
)}
|
||||
|
||||
{props.isShowBorder && props.pos.key === props.selectWidgetId && (
|
||||
<PlaceholderBorder
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pos={props.pos}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
getCheckboxRenderWidth={getCheckboxRenderWidth}
|
||||
scale={props.scale}
|
||||
containerScale={containerScale}
|
||||
placeholderBorder={placeholderBorder}
|
||||
/>
|
||||
)}
|
||||
{isTabAndMobile ? (
|
||||
<div
|
||||
className="flex items-stretch"
|
||||
style={{
|
||||
left: xPos(props.pos, props.isSignYourself),
|
||||
top: yPos(props.pos, props.isSignYourself),
|
||||
width:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posWidth(props.pos, props.isSignYourself),
|
||||
height:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself),
|
||||
zIndex: "10"
|
||||
}}
|
||||
onTouchEnd={() => handleOnClickPlaceholder()}
|
||||
onClick={() => handleOnClickPlaceholder()}
|
||||
>
|
||||
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
|
||||
|
||||
<PlaceholderType
|
||||
pos={props.pos}
|
||||
xyPostion={props.xyPostion}
|
||||
index={props.index}
|
||||
setXyPostion={props.setXyPostion}
|
||||
data={props.data}
|
||||
setSignKey={props.setSignKey}
|
||||
isShowDropdown={props?.isShowDropdown}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
isSignYourself={props.isSignYourself}
|
||||
signerObjId={props.signerObjId}
|
||||
handleUserName={props.handleUserName}
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
|
||||
isNeedSign={props.isNeedSign}
|
||||
setSelectDate={setSelectDate}
|
||||
selectDate={selectDate}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setStartDate={setStartDate}
|
||||
startDate={startDate}
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
|
||||
<PlaceholderType
|
||||
pos={props.pos}
|
||||
xyPostion={props.xyPostion}
|
||||
index={props.index}
|
||||
setXyPostion={props.setXyPostion}
|
||||
data={props.data}
|
||||
setSignKey={props.setSignKey}
|
||||
isShowDropdown={props?.isShowDropdown}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
isSignYourself={props.isSignYourself}
|
||||
signerObjId={props.signerObjId}
|
||||
handleUserName={props.handleUserName}
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
|
||||
isNeedSign={props.isNeedSign}
|
||||
setSelectDate={setSelectDate}
|
||||
selectDate={selectDate}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setStartDate={setStartDate}
|
||||
startDate={startDate}
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Rnd>
|
||||
);
|
||||
}
|
||||
|
||||
export default Placeholder;
|
||||
@@ -1,60 +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 width = () => {
|
||||
const getWidth =
|
||||
props.placeholderBorder.w || props.pos.Width || defaultWidth;
|
||||
return (
|
||||
getWidth * props.scale * props.containerScale + getResizeBorderExtraWidth
|
||||
);
|
||||
};
|
||||
const height = () => {
|
||||
const getHeight =
|
||||
props.placeholderBorder.h || props.pos.Height || defaultHeight;
|
||||
|
||||
return (
|
||||
getHeight * props.scale * props.containerScale + getResizeBorderExtraWidth
|
||||
);
|
||||
};
|
||||
|
||||
const handleMinWidth = () => {
|
||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
||||
return props.getCheckboxRenderWidth.width + getResizeBorderExtraWidth;
|
||||
} else {
|
||||
return width();
|
||||
}
|
||||
};
|
||||
const handleMinHeight = () => {
|
||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
||||
return props.getCheckboxRenderWidth.height + getResizeBorderExtraWidth;
|
||||
} else {
|
||||
return height();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => !isMobile && props?.setDraggingEnabled(true)}
|
||||
onTouchEnd={() =>
|
||||
props.pos.type === textWidget && props?.setDraggingEnabled(true)
|
||||
}
|
||||
className="absolute inline-block w-[14px] h-[14px] border-[0.2px] overflow-hidden border-dashed"
|
||||
style={{
|
||||
borderColor: themeColor,
|
||||
minWidth: handleMinWidth() || 0,
|
||||
minHeight: handleMinHeight() || 0
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlaceholderBorder;
|
||||
@@ -1,65 +1,107 @@
|
||||
import React, { useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { randomId, textWidget } from "../../constant/Utils";
|
||||
import {
|
||||
handleCopyNextToWidget,
|
||||
randomId,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function PlaceholderCopy(props) {
|
||||
const { t } = useTranslation();
|
||||
const copyType = ["All pages", "All pages but last", "All pages but first"];
|
||||
const [selectCopyType, setSelectCopyType] = useState("");
|
||||
const copyType = [
|
||||
{ id: 1, type: "All pages" },
|
||||
{ id: 2, type: "All pages but last" },
|
||||
{ id: 3, type: "All pages but first" },
|
||||
{ id: 4, type: "Next to current widget" }
|
||||
];
|
||||
const [selectCopyType, setSelectCopyType] = useState(1);
|
||||
//function for get copy placeholder position
|
||||
const getCopyPlaceholderPosition = (
|
||||
type,
|
||||
currentPlaceholder,
|
||||
newPlaceholder,
|
||||
newPageNumber,
|
||||
existPlaceholderPosition
|
||||
) => {
|
||||
//remove existing page position when used option copy but first or copy but last
|
||||
const filterPosition =
|
||||
existPlaceholderPosition &&
|
||||
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
|
||||
if (newPageNumber === 1 && type === "first") {
|
||||
if (filterPosition && filterPosition.length > 0) {
|
||||
if (newPageNumber === 1 && type === 3) {
|
||||
//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 {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...filterPosition]
|
||||
};
|
||||
}
|
||||
}
|
||||
//second option - copy all page but not last
|
||||
//copy all page placeholder at requested position except last page
|
||||
else if (newPageNumber === props.allPages && type === "last") {
|
||||
else if (newPageNumber === props.allPages && type === 2) {
|
||||
if (existPlaceholderPosition) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...filterPosition]
|
||||
};
|
||||
//when user choose this option and placed widget on last page then do not remove last page widget
|
||||
//it should be copy all page included last page on same postion
|
||||
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
|
||||
else if (existPlaceholderPosition) {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...filterPosition, currentPlaceholder]
|
||||
};
|
||||
if (newPageNumber === props.pageNumber) {
|
||||
return {
|
||||
pageNumber: props.pageNumber,
|
||||
pos: [...existPlaceholderPosition]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [...existPlaceholderPosition, newPlaceholder]
|
||||
};
|
||||
}
|
||||
}
|
||||
//copy all page placeholder at requested position
|
||||
else {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [currentPlaceholder]
|
||||
};
|
||||
if (newPageNumber === props.pageNumber) {
|
||||
return {
|
||||
pageNumber: props.pageNumber,
|
||||
pos: [...existPlaceholderPosition]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
pageNumber: newPageNumber,
|
||||
pos: [newPlaceholder]
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//function for copy placeholder as per select copy type
|
||||
const copyPlaceholder = (type) => {
|
||||
let newPlaceholderPosition = [];
|
||||
let newPageNumber = 1;
|
||||
const signerPosition = props.xyPostion;
|
||||
const signerPosition = props.xyPosition;
|
||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||
|
||||
//handle placeholder array and copy for multiple signers placeholder at requested location
|
||||
if (signerId) {
|
||||
//get current signers data
|
||||
@@ -81,10 +123,9 @@ function PlaceholderCopy(props) {
|
||||
const currentPlaceholder = placeholderPosition[0].pos.find(
|
||||
(position) => position.key === props.signKey
|
||||
);
|
||||
|
||||
for (let i = 0; i < props.allPages; i++) {
|
||||
const newId = randomId();
|
||||
currentPlaceholder.key = newId;
|
||||
const newPlaceholder = { ...currentPlaceholder, key: newId };
|
||||
//get exist placeholder position for particular page
|
||||
const existPlaceholder = filterSignerPosition[0].placeHolder.filter(
|
||||
(data) => data.pageNumber === newPageNumber
|
||||
@@ -95,11 +136,11 @@ function PlaceholderCopy(props) {
|
||||
//function for get copy to requested location of placeholder position
|
||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||
type,
|
||||
currentPlaceholder,
|
||||
newPlaceholder,
|
||||
newPageNumber,
|
||||
existPlaceholderPosition
|
||||
);
|
||||
if (getPlaceholderObj) {
|
||||
if (getPlaceholderObj?.pos) {
|
||||
newPlaceholderPosition.push(getPlaceholderObj);
|
||||
}
|
||||
newPageNumber++;
|
||||
@@ -129,13 +170,13 @@ function PlaceholderCopy(props) {
|
||||
|
||||
const signersData = signerPosition;
|
||||
signersData.splice(0, signerPosition.length, ...updatedSignerPlaceholder);
|
||||
props.setXyPostion(signersData);
|
||||
props.setXyPosition(signersData);
|
||||
}
|
||||
//handle signyourself array and copy for single signers placeholder at requested location
|
||||
else {
|
||||
const xyPostion = props.xyPostion;
|
||||
const xyPosition = props.xyPosition;
|
||||
|
||||
const placeholderPosition = xyPostion.filter(
|
||||
const placeholderPosition = xyPosition.filter(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
);
|
||||
//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++) {
|
||||
//get exist placeholder position for particular page
|
||||
const existPlaceholder = xyPostion.filter(
|
||||
const existPlaceholder = xyPosition.filter(
|
||||
(data) => data.pageNumber === newPageNumber
|
||||
);
|
||||
const existPlaceholderPosition =
|
||||
existPlaceholder[0] && existPlaceholder[0].pos;
|
||||
|
||||
const newId = randomId();
|
||||
currentPlaceholder.key = newId;
|
||||
const newPlaceholder = { ...currentPlaceholder, key: newId };
|
||||
//function for get copy to requested location of placeholder position
|
||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||
type,
|
||||
currentPlaceholder,
|
||||
newPlaceholder,
|
||||
newPageNumber,
|
||||
existPlaceholderPosition
|
||||
);
|
||||
@@ -166,31 +207,71 @@ function PlaceholderCopy(props) {
|
||||
|
||||
newPageNumber++;
|
||||
}
|
||||
props.setXyPostion(newPlaceholderPosition);
|
||||
props.setXyPosition(newPlaceholderPosition);
|
||||
}
|
||||
};
|
||||
|
||||
//function for getting selected type placeholder copy
|
||||
const handleApplyCopy = () => {
|
||||
if (selectCopyType === "All pages") {
|
||||
copyPlaceholder("all");
|
||||
} else if (selectCopyType === "All pages but last") {
|
||||
copyPlaceholder("last");
|
||||
} else if (selectCopyType === "All pages but first") {
|
||||
copyPlaceholder("first");
|
||||
if (selectCopyType === 4) {
|
||||
const signerPosition = props.xyPosition;
|
||||
let currentXYposition;
|
||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||
if (signerId) {
|
||||
const filterSignerPosition = signerPosition.filter(
|
||||
(item) => item?.Id === signerId
|
||||
);
|
||||
currentXYposition = filterSignerPosition[0].placeHolder.filter(
|
||||
(data) => data.pageNumber === props?.pageNumber
|
||||
);
|
||||
//get current placeholder position data which user want to copy
|
||||
currentXYposition = currentXYposition[0].pos.find(
|
||||
(position) => position.key === props.signKey
|
||||
);
|
||||
//function to create new widget next to just widget
|
||||
handleCopyNextToWidget(
|
||||
currentXYposition,
|
||||
props.widgetType,
|
||||
props.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 = () => {
|
||||
if (props.widgetType === textWidget) {
|
||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||
if (signerId && props.widgetType === textWidget && props.setTempSignerId) {
|
||||
props.setUniqueId(props?.tempSignerId);
|
||||
props.setTempSignerId("");
|
||||
}
|
||||
props.setIsPageCopy(false);
|
||||
setSelectCopyType(1);
|
||||
};
|
||||
return (
|
||||
<ModalUi
|
||||
isOpen={props.isPageCopy}
|
||||
title={t("copy-to-all-pages")}
|
||||
title={t("copy-title")}
|
||||
handleClose={() => handleUniqueId()}
|
||||
>
|
||||
<div className="h-full p-[20px] text-base-content">
|
||||
@@ -201,17 +282,17 @@ function PlaceholderCopy(props) {
|
||||
<input
|
||||
className="mr-[8px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
value={data}
|
||||
onChange={() => setSelectCopyType(data)}
|
||||
checked={selectCopyType === data}
|
||||
value={data.id}
|
||||
onChange={() => setSelectCopyType(data.id)}
|
||||
checked={selectCopyType === data.id}
|
||||
/>
|
||||
{t(`copy-type.${data}`)}
|
||||
{t(`copy-type.${data.type}`)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="flex bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<div className="flex flex-row bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => {
|
||||
handleApplyCopy();
|
||||
@@ -225,7 +306,7 @@ function PlaceholderCopy(props) {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
className="op-btn op-btn-ghost ml-2"
|
||||
onClick={() => handleUniqueId()}
|
||||
>
|
||||
{t("cancel")}
|
||||
@@ -1,839 +0,0 @@
|
||||
import React, { useEffect, useState, forwardRef, useRef } from "react";
|
||||
import {
|
||||
getMonth,
|
||||
getYear,
|
||||
onChangeHeightOfTextArea,
|
||||
onChangeInput,
|
||||
radioButtonWidget,
|
||||
range,
|
||||
textInputWidget,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import "../../styles/signature.css";
|
||||
import RegexParser from "regex-parser";
|
||||
import { emailRegex } from "../../constant/const";
|
||||
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";
|
||||
function PlaceholderType(props) {
|
||||
const { t } = useTranslation();
|
||||
const type = props?.pos?.type;
|
||||
const widgetTypeTraslation = t(`widgets-name.${props?.pos?.type}`);
|
||||
const [selectOption, setSelectOption] = useState("");
|
||||
const [validatePlaceholder, setValidatePlaceholder] = useState("");
|
||||
const inputRef = useRef(null);
|
||||
const [textValue, setTextValue] = useState();
|
||||
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
|
||||
const years = range(1990, getYear(new Date()) + 16, 1);
|
||||
const months = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December"
|
||||
];
|
||||
const validateExpression = (regexValidation) => {
|
||||
if (textValue && regexValidation) {
|
||||
let regexObject = regexValidation;
|
||||
if (props.pos?.options?.validation?.type === "regex") {
|
||||
regexObject = RegexParser(regexValidation);
|
||||
}
|
||||
// new RegExp(regexValidation);
|
||||
let isValidate = regexObject.test(textValue);
|
||||
if (!isValidate) {
|
||||
props?.setValidateAlert(true);
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
props.setDraggingEnabled(true);
|
||||
const validateType = props.pos?.options?.validation?.type;
|
||||
let regexValidation;
|
||||
if (validateType && validateType !== "text") {
|
||||
switch (validateType) {
|
||||
case "email":
|
||||
regexValidation = emailRegex;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
case "number":
|
||||
regexValidation = /^[0-9\s]*$/;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
default:
|
||||
regexValidation = props.pos?.options?.validation?.pattern || "";
|
||||
validateExpression(regexValidation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleTextValid = (e) => {
|
||||
const textInput = e.target.value;
|
||||
setTextValue(textInput);
|
||||
};
|
||||
function checkRegularExpress(validateType) {
|
||||
switch (validateType) {
|
||||
case "email":
|
||||
setValidatePlaceholder("demo@gmail.com");
|
||||
break;
|
||||
case "number":
|
||||
setValidatePlaceholder("12345");
|
||||
break;
|
||||
case "text":
|
||||
setValidatePlaceholder("please enter text");
|
||||
break;
|
||||
default:
|
||||
setValidatePlaceholder("please enter value");
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (type && type === "checkbox" && props.isNeedSign) {
|
||||
const isDefaultValue = props.pos.options?.defaultValue;
|
||||
if (isDefaultValue) {
|
||||
setSelectedCheckbox(isDefaultValue);
|
||||
}
|
||||
} else if (props.pos?.options?.hint) {
|
||||
setValidatePlaceholder(props.pos?.options.hint);
|
||||
} else if (props.pos?.options?.validation?.type) {
|
||||
checkRegularExpress(props.pos?.options?.validation?.type);
|
||||
}
|
||||
setTextValue(
|
||||
props.pos?.options?.response
|
||||
? props.pos?.options?.response
|
||||
: props.pos?.options?.defaultValue
|
||||
? props.pos?.options?.defaultValue
|
||||
: ""
|
||||
);
|
||||
setSelectOption(
|
||||
props.pos?.options?.response
|
||||
? props.pos?.options?.response
|
||||
: props.pos?.options?.defaultValue
|
||||
? props.pos?.options?.defaultValue
|
||||
: ""
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
["name", "email", "job title", "company"].includes(props.pos?.type) &&
|
||||
props.isNeedSign &&
|
||||
props.data?.signerObjId === props?.signerObjId
|
||||
) {
|
||||
const defaultData = props.pos?.options?.defaultValue;
|
||||
if (defaultData) {
|
||||
setTextValue(defaultData);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.pos?.options?.defaultValue]);
|
||||
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
className={`${selectWidgetCls} text-[12px] overflow-hidden`}
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
{value}
|
||||
<i className="fa-light fa-calendar ml-[5px]"></i>
|
||||
</div>
|
||||
));
|
||||
ExampleCustomInput.displayName = "ExampleCustomInput";
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
["name", "email", "job title", "company"].includes(type) &&
|
||||
props.isNeedSign &&
|
||||
props.data?.signerObjId === props.signerObjId
|
||||
) {
|
||||
const isDefault = true;
|
||||
const senderUser = localStorage.getItem(`Extand_Class`);
|
||||
const jsonSender = JSON.parse(senderUser);
|
||||
onChangeInput(
|
||||
jsonSender && jsonSender[0],
|
||||
null,
|
||||
props.xyPostion,
|
||||
null,
|
||||
props.setXyPostion,
|
||||
props.data.Id,
|
||||
isDefault
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [type]);
|
||||
//function for show checked checkbox
|
||||
const selectCheckbox = (ind) => {
|
||||
const res = props.pos.options?.response;
|
||||
const defaultCheck = props.pos.options?.defaultValue;
|
||||
if (res && res?.length > 0) {
|
||||
const isSelectIndex = res.indexOf(ind);
|
||||
if (isSelectIndex > -1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// }
|
||||
} else if (defaultCheck) {
|
||||
const isSelectIndex = defaultCheck.indexOf(ind);
|
||||
if (isSelectIndex > -1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRadioCheck = (data) => {
|
||||
const defaultData = props.pos.options?.defaultValue;
|
||||
if (textValue === data) {
|
||||
return true;
|
||||
} else if (defaultData === data) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//function for set checked and unchecked value of checkbox
|
||||
const handleCheckboxValue = (isChecked, ind) => {
|
||||
let updateSelectedCheckbox = [],
|
||||
checkedList;
|
||||
let isDefaultValue, isDefaultEmpty;
|
||||
if (type === "checkbox") {
|
||||
updateSelectedCheckbox = selectedCheckbox ? selectedCheckbox : [];
|
||||
|
||||
if (isChecked) {
|
||||
updateSelectedCheckbox.push(ind);
|
||||
setSelectedCheckbox(updateSelectedCheckbox);
|
||||
} else {
|
||||
checkedList = selectedCheckbox.filter((data) => data !== ind);
|
||||
setSelectedCheckbox(checkedList);
|
||||
}
|
||||
if (props.isNeedSign) {
|
||||
isDefaultValue = props.pos.options?.defaultValue;
|
||||
}
|
||||
if (isDefaultValue && isDefaultValue.length > 0) {
|
||||
isDefaultEmpty = true;
|
||||
}
|
||||
onChangeInput(
|
||||
checkedList ? checkedList : updateSelectedCheckbox,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
null,
|
||||
isDefaultEmpty
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
//function to handle select radio widget and set value seletced by user
|
||||
const handleCheckRadio = (isChecked, data) => {
|
||||
let isDefaultValue,
|
||||
isDefaultEmpty,
|
||||
isRadio = true;
|
||||
if (props.isNeedSign) {
|
||||
isDefaultValue = props.pos.options?.defaultValue;
|
||||
}
|
||||
if (isDefaultValue) {
|
||||
isDefaultEmpty = true;
|
||||
}
|
||||
if (isChecked) {
|
||||
setTextValue(data);
|
||||
} else {
|
||||
setTextValue("");
|
||||
}
|
||||
onChangeInput(
|
||||
data,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
null,
|
||||
isDefaultEmpty,
|
||||
isRadio
|
||||
);
|
||||
};
|
||||
//function to set onchange date
|
||||
const handleOnDateChange = (date) => {
|
||||
props.setStartDate(date);
|
||||
const isDateChange = true;
|
||||
const dateObj = {
|
||||
date: date,
|
||||
format: props.selectDate.format
|
||||
};
|
||||
props.handleSaveDate(dateObj, isDateChange);
|
||||
};
|
||||
//handle height on enter press in text area
|
||||
const handleEnterPress = (e) => {
|
||||
const height = 18;
|
||||
if (e.key === "Enter") {
|
||||
//function to save height of text area
|
||||
onChangeHeightOfTextArea(
|
||||
height,
|
||||
props.pos.type,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id
|
||||
);
|
||||
}
|
||||
};
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
alt="signature"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full "
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "stamp":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
alt="stamp"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "checkbox":
|
||||
return (
|
||||
<div style={{ zIndex: props.isSignYourself && "99" }}>
|
||||
{props.pos.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="flex items-center text-center gap-0.5">
|
||||
<input
|
||||
className={`${
|
||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||
} flex justify-center op-checkbox op-checkbox-xs rounded-[0.350rem]`}
|
||||
onBlur={handleInputBlur}
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
type="checkbox"
|
||||
checked={selectCheckbox(ind)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
if (!props.isPlaceholder) {
|
||||
const maxRequired =
|
||||
props.pos.options?.validation?.maxRequiredCount;
|
||||
const maxCountInt =
|
||||
maxRequired && parseInt(maxRequired);
|
||||
|
||||
if (maxCountInt > 0) {
|
||||
if (
|
||||
selectedCheckbox &&
|
||||
selectedCheckbox?.length <= maxCountInt - 1
|
||||
) {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
} else {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<label className="text-xs mb-0 text-center">{data}</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
case textInputWidget:
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={validatePlaceholder || "please enter value"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
setTextValue(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "dropdown":
|
||||
return props.data?.signerObjId === props.signerObjId ? (
|
||||
<select
|
||||
className={`${selectWidgetCls} text-[12px] bg-inherit`}
|
||||
id="myDropdown"
|
||||
value={selectOption}
|
||||
onChange={(e) => {
|
||||
setSelectOption(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
>
|
||||
{/* Default/Title option */}
|
||||
<option value="" disabled hidden>
|
||||
{props?.pos?.options?.name}
|
||||
</option>
|
||||
|
||||
{props.pos?.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<option key={ind} value={data}>
|
||||
{data}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
) : (
|
||||
<div className="text-[12px] overflow-hidden">
|
||||
{props.pos?.options?.name
|
||||
? props.pos.options.name
|
||||
: widgetTypeTraslation}
|
||||
</div>
|
||||
);
|
||||
case "initials":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
alt="initials"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "name":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"name"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
const isDefault = false;
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
isDefault
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "company":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder="Enter text"
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
overflow: "hidden",
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "job title":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"job title"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "date":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<div>
|
||||
<DatePicker
|
||||
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
||||
<div className="flex justify-start ml-2 ">
|
||||
<select
|
||||
className="bg-transparent outline-none"
|
||||
value={months[getMonth(date)]}
|
||||
onChange={({ target: { value } }) =>
|
||||
changeMonth(months.indexOf(value))
|
||||
}
|
||||
>
|
||||
{months.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
className="bg-transparent outline-none"
|
||||
value={getYear(date)}
|
||||
onChange={({ target: { value } }) => changeYear(value)}
|
||||
>
|
||||
{years.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
disabled={
|
||||
props.isPlaceholder ||
|
||||
(props.isNeedSign &&
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
onBlur={handleInputBlur}
|
||||
closeOnScroll={true}
|
||||
className={`${selectWidgetCls} outline-[#007bff]`}
|
||||
selected={
|
||||
props?.startDate
|
||||
? props?.startDate
|
||||
: props.pos.options?.response &&
|
||||
new Date(props.pos.options.response)
|
||||
}
|
||||
onChange={(date) => handleOnDateChange(date)}
|
||||
popperPlacement="top-end"
|
||||
customInput={<ExampleCustomInput />}
|
||||
dateFormat={
|
||||
props.selectDate
|
||||
? props.selectDate?.format
|
||||
: props.pos?.options?.validation?.format
|
||||
? props.pos?.options?.validation?.format
|
||||
: "MM/dd/yyyy"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[12px] text-black items-center overflow-hidden">
|
||||
<span>
|
||||
{props.selectDate
|
||||
? props.selectDate?.format
|
||||
: props.pos?.options?.validation?.format
|
||||
? props.pos?.options?.validation?.format
|
||||
: "MM/dd/yyyy"}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
case "image":
|
||||
return props.pos.SignUrl ? (
|
||||
<img
|
||||
alt="image"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "email":
|
||||
return props.isSignYourself ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={"enter email"}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black",
|
||||
fontFamily: "Arial, sans-serif",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
</div>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
return (
|
||||
<div>
|
||||
{props.pos.options?.values.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind} className="flex items-center text-center gap-0.5">
|
||||
<input
|
||||
className={`${
|
||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||
} ${"w-[15px] h-[15px]"} flex justify-center op-radio`}
|
||||
type="radio"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
checked={handleRadioCheck(data)}
|
||||
onChange={(e) => {
|
||||
if (!props.isPlaceholder) {
|
||||
handleCheckRadio(e.target.checked, data);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<label className="text-xs mb-0">{data}</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
case textWidget:
|
||||
return (
|
||||
<textarea
|
||||
placeholder="Enter text"
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
setTextValue(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPostion,
|
||||
props.index,
|
||||
props.setXyPostion,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontFamily: "Arial, sans-serif",
|
||||
fontSize: props.pos.options?.fontSize
|
||||
? props.pos.options?.fontSize + "px"
|
||||
: "12px",
|
||||
color: props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return props.pos.SignUrl ? (
|
||||
<div className="pointer-events-none">
|
||||
<img
|
||||
alt="image"
|
||||
draggable="false"
|
||||
src={props.pos.SignUrl}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
|
||||
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(props?.data?.Id, props?.data?.Role)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PlaceholderType;
|
||||
@@ -0,0 +1,528 @@
|
||||
import React, { useEffect, useState, forwardRef } from "react";
|
||||
import {
|
||||
getMonth,
|
||||
getYear,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
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";
|
||||
import CellsWidget from "./CellsWidget";
|
||||
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";
|
||||
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 isReadOnly =
|
||||
props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId;
|
||||
// prefer the latest response value over any default value
|
||||
const widgetData =
|
||||
props.pos?.options?.response ?? props.pos?.options?.defaultValue ?? "";
|
||||
const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`);
|
||||
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 {
|
||||
// keep displayed value in sync with the stored response
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 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={`${isReadOnly ? `select-none` : ``} ${selectWidgetCls} overflow-hidden`}
|
||||
disabled={isReadOnly}
|
||||
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={`${props.pos.signatureType !== "type" ? "object-contain" : ""} 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 object-contain"
|
||||
/>
|
||||
) : (
|
||||
<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":
|
||||
const checkBoxLayout = props.pos.options?.layout || "vertical";
|
||||
const isMultipleCheckbox =
|
||||
props.pos.options?.values?.length > 0 ? true : false;
|
||||
const checkBoxWrapperClass = `flex items-start ${
|
||||
checkBoxLayout === "horizontal"
|
||||
? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}`
|
||||
: `flex-col ${isMultipleCheckbox ? "gap-y-[3px]" : ""}`
|
||||
}`; // Using gap-y-1 for consistency, adjust if needed
|
||||
|
||||
return (
|
||||
<div
|
||||
className={checkBoxWrapperClass}
|
||||
style={{ zIndex: props.isSignYourself && "99" }}
|
||||
>
|
||||
{props.pos.options?.values?.map((data, ind) => (
|
||||
<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`}
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ width: fontSize, height: fontSize }}
|
||||
className="op-checkbox rounded-[1px]"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
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
|
||||
placeholder={hint || t("widgets-name.text")}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
readOnly
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case cellsWidget: {
|
||||
const count = props.pos.options?.cellCount || 5;
|
||||
const cells = (widgetValue || "").split("");
|
||||
const height = "100%";
|
||||
const fontSize = props.calculateFont(props.pos.options?.fontSize);
|
||||
const fontColor = props.pos.options?.fontColor || "black";
|
||||
const handleCellResize = (newCount) => {
|
||||
if (props.setCellCount) props.setCellCount(props.pos.key, newCount);
|
||||
};
|
||||
const isEditable =
|
||||
props.isPlaceholder || props.isSignYourself || props.isSelfSign;
|
||||
return (
|
||||
<CellsWidget
|
||||
isEnabled={iswidgetEnable}
|
||||
count={count}
|
||||
height={height}
|
||||
value={cells.join("")}
|
||||
editable={isEditable}
|
||||
resizable={isEditable}
|
||||
fontSize={fontSize}
|
||||
fontColor={fontColor}
|
||||
hint={hint}
|
||||
onCellCountChange={handleCellResize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case "dropdown":
|
||||
return (
|
||||
<div
|
||||
style={textWidgetStyle}
|
||||
className="select-none-cls flex justify-between items-center"
|
||||
>
|
||||
{widgetData || t("choose-one")}
|
||||
<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={`${props.pos.signatureType !== "type" ? "object-contain" : ""} 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
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
cols="50"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
|
||||
<span> {props.pos?.options?.hint || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "company":
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
cols="50"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "job title":
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
cols="50"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
/>
|
||||
) : (
|
||||
<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 object-contain"
|
||||
/>
|
||||
) : (
|
||||
<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
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
cols="1"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
const radioLayout = props.pos.options?.layout || "vertical";
|
||||
const isOnlyOneBtn = props.pos.options?.values?.length > 0 ? true : false;
|
||||
const radioWrapperClass = `flex items-start ${
|
||||
radioLayout === "horizontal"
|
||||
? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-[10px]" : ""}`
|
||||
: `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}`
|
||||
}`; // Using gap-y-1 for consistency, adjust if needed
|
||||
return (
|
||||
<div className={radioWrapperClass}>
|
||||
{props.pos.options?.values.map((data, ind) => (
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`radio-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
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-black appearance-none bg-white inline-block align-middle relative ${
|
||||
handleRadioCheck(data) ? "checked-radio" : ""
|
||||
}`}
|
||||
type="radio"
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
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,
|
||||
background: "white"
|
||||
}}
|
||||
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;
|
||||
@@ -23,7 +23,7 @@ function PrevNext({ pageNumber, allPages, changePage }) {
|
||||
<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-[30px]">
|
||||
<span className="text-xs text-base-content font-medium mx-2 2xl:text-[20px]">
|
||||
{pageNumber || (allPages ? 1 : "--")} {t("of")} {allPages || "--"}
|
||||
</span>
|
||||
<button
|
||||
@@ -16,7 +16,7 @@ const RecipientList = (props) => {
|
||||
//function for onhover signer name change background color
|
||||
const inputRef = useRef(null);
|
||||
const isWidgetExist = (Id) => {
|
||||
return props.signerPos.some((x) => x.Id === Id);
|
||||
return props.signerPos.some((x) => x.Id === Id && x.placeHolder);
|
||||
};
|
||||
|
||||
//handle drag start
|
||||
@@ -31,8 +31,8 @@ const RecipientList = (props) => {
|
||||
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) => {
|
||||
//handle draggable element drop and also used in mobile view on up and down key to change sequence of recipient's list
|
||||
const handleChangeSequence = (e, ind, isUp, isDown, obj) => {
|
||||
e.preventDefault();
|
||||
let draggedItemId;
|
||||
let index = ind;
|
||||
@@ -63,13 +63,10 @@ const RecipientList = (props) => {
|
||||
remainingItems.splice(index, 0, ...draggedItem);
|
||||
//set current draggable recipient details,objectId,index,contract_className ... after replace recipient list
|
||||
props?.setSignersData(remainingItems);
|
||||
props?.setSignerObjId(remainingItems[index]?.objectId || "");
|
||||
props?.setIsSelectId(index);
|
||||
props?.setContractName(remainingItems[index]?.className || "");
|
||||
props?.setUniqueId(remainingItems[index]?.Id);
|
||||
props?.setRoleName(remainingItems[index]?.Role);
|
||||
props?.setBlockColor(remainingItems[index]?.blockColor);
|
||||
|
||||
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) => {
|
||||
@@ -85,7 +82,7 @@ const RecipientList = (props) => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{props.signersdata.length > 0 &&
|
||||
{props.signersdata?.length > 0 &&
|
||||
props.signersdata.map((obj, ind) => {
|
||||
return (
|
||||
<div
|
||||
@@ -99,7 +96,9 @@ const RecipientList = (props) => {
|
||||
props.sendInOrder && !isMobile && handleDragOver(e)
|
||||
}
|
||||
onDrop={(e) =>
|
||||
props.sendInOrder && !isMobile && handleChangeSequence(e, ind)
|
||||
props.sendInOrder &&
|
||||
!isMobile &&
|
||||
handleChangeSequence(e, ind, null, null, obj)
|
||||
}
|
||||
data-tut="recipientArea"
|
||||
onMouseEnter={() => setIsHover(ind)}
|
||||
@@ -121,9 +120,7 @@ const RecipientList = (props) => {
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
props.setSignerObjId(obj?.objectId || "");
|
||||
props.setIsSelectId(ind);
|
||||
props.setContractName(obj?.className || "");
|
||||
props.setUniqueId(obj.Id);
|
||||
props.setRoleName(obj.Role);
|
||||
props.setBlockColor(obj?.blockColor);
|
||||
@@ -132,9 +129,9 @@ const RecipientList = (props) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row items-center w-full">
|
||||
<div className="flex flex-row items-center w-full overflow-hidden pr-2">
|
||||
<div
|
||||
className="flex w-[30px] h-[30px] rounded-full items-center justify-center mr-2"
|
||||
className="flex flex-shrink-0 w-[30px] h-[30px] rounded-full items-center justify-center mr-2"
|
||||
style={{
|
||||
background: obj?.blockColor
|
||||
? darkenColor(obj?.blockColor, 0.4)
|
||||
@@ -156,7 +153,7 @@ const RecipientList = (props) => {
|
||||
<div
|
||||
className={`${
|
||||
obj.Name ? "flex-col" : "flex-row"
|
||||
} flex items-center`}
|
||||
} flex overflow-hidden flex-grow-0`}
|
||||
>
|
||||
{obj.Name ? (
|
||||
<span
|
||||
@@ -165,7 +162,7 @@ const RecipientList = (props) => {
|
||||
props.isSelectListId === ind
|
||||
? "text-[#424242]"
|
||||
: "text-base-content"
|
||||
} text-[12px] font-bold w-[100px] whitespace-nowrap overflow-hidden text-ellipsis`}
|
||||
} text-[12px] font-bold truncate whitespace-nowrap`}
|
||||
>
|
||||
{obj.Name}
|
||||
</span>
|
||||
@@ -176,7 +173,7 @@ const RecipientList = (props) => {
|
||||
props.isSelectListId === ind
|
||||
? "text-[#424242]"
|
||||
: "text-base-content"
|
||||
} text-[12px] font-bold w-[100px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer`}
|
||||
} text-[12px] font-bold truncate whitespace-nowrap cursor-pointer`}
|
||||
onClick={() => {
|
||||
setIsEdit({ [obj.Id]: true });
|
||||
props.setRoleName(obj.Role);
|
||||
@@ -185,7 +182,7 @@ const RecipientList = (props) => {
|
||||
{isEdit?.[obj.Id] && props.handleRoleChange ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="bg-transparent p-[3px]"
|
||||
className="bg-transparent p-[3px] w-full"
|
||||
value={obj.Role}
|
||||
onChange={(e) => props.handleRoleChange(e, obj.Id)}
|
||||
onBlur={() => {
|
||||
@@ -200,7 +197,7 @@ const RecipientList = (props) => {
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-[13px] 2xl:text-[25px]">
|
||||
<span className="text-[13px] 2xl:text-[17px]">
|
||||
{obj.Role}
|
||||
</span>
|
||||
)}
|
||||
@@ -213,7 +210,7 @@ const RecipientList = (props) => {
|
||||
props.isSelectListId === ind
|
||||
? "text-[#424242]"
|
||||
: "text-base-content"
|
||||
} text-[10px] font-medium w-[100px] whitespace-nowrap overflow-hidden text-ellipsis`}
|
||||
} text-[10px] font-medium truncate whitespace-nowrap`}
|
||||
>
|
||||
{obj?.Role || obj?.Email}
|
||||
</span>
|
||||
@@ -226,7 +223,7 @@ const RecipientList = (props) => {
|
||||
onClick={(e) => {
|
||||
if (ind !== 0) {
|
||||
e.stopPropagation();
|
||||
handleChangeSequence(e, ind, "up");
|
||||
handleChangeSequence(e, ind, "up", null, obj);
|
||||
}
|
||||
}}
|
||||
className={ind === 0 ? "text-[gray]" : "text-black"}
|
||||
@@ -237,7 +234,7 @@ const RecipientList = (props) => {
|
||||
onClick={(e) => {
|
||||
if (ind !== props.signersdata.length - 1) {
|
||||
e.stopPropagation();
|
||||
handleChangeSequence(e, ind, null, "down");
|
||||
handleChangeSequence(e, ind, null, "down", obj);
|
||||
}
|
||||
}}
|
||||
className={
|
||||
@@ -263,7 +260,7 @@ const RecipientList = (props) => {
|
||||
: "text-base-content"
|
||||
} cursor-pointer`}
|
||||
>
|
||||
<i className="fa-light fa-trash-can 2xl:text-[30px]"></i>
|
||||
<i className="fa-light fa-trash-can 2xl:text-[22px]"></i>
|
||||
</div>
|
||||
)}
|
||||
<hr />
|
||||
@@ -1,116 +0,0 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
function RenderAllPdfPage({
|
||||
signPdfUrl,
|
||||
allPages,
|
||||
setAllPages,
|
||||
setPageNumber,
|
||||
setSignBtnPosition,
|
||||
pageNumber,
|
||||
signerPos,
|
||||
signerObjectId,
|
||||
containerWH
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [signPageNumber, setSignPageNumber] = useState([]);
|
||||
const [bookmarkColor, setBookmarkColor] = useState("");
|
||||
//set all number of pages after load pdf
|
||||
function onDocumentLoad({ numPages }) {
|
||||
setAllPages(numPages);
|
||||
//check if signerPos array exist then save page number exist in signerPos array to show bookmark icon
|
||||
if (signerPos) {
|
||||
const checkUser = signerPos.filter(
|
||||
(data) => data.signerObjId === signerObjectId
|
||||
);
|
||||
setBookmarkColor(checkUser[0]?.blockColor);
|
||||
let pageNumberArr = [];
|
||||
if (checkUser?.length > 0) {
|
||||
checkUser[0]?.placeHolder?.map((data) => {
|
||||
pageNumberArr.push(data?.pageNumber);
|
||||
});
|
||||
|
||||
setSignPageNumber(pageNumberArr);
|
||||
}
|
||||
}
|
||||
}
|
||||
const pageContainer = useRef();
|
||||
const isHeader = useSelector((state) => state.showHeader);
|
||||
const [pageWidth, setPageWidth] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const updateSize = () => {
|
||||
if (pageContainer.current) {
|
||||
setPageWidth(pageContainer.current.offsetWidth);
|
||||
}
|
||||
};
|
||||
|
||||
// Use setTimeout to wait for the transition to complete
|
||||
const timer = setTimeout(updateSize, 100); // match the transition duration
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isHeader, pageContainer, containerWH]);
|
||||
//'function `addSignatureBookmark` is used to display the page where the user's signature is located.
|
||||
const addSignatureBookmark = (index) => {
|
||||
const ispageNumber = signPageNumber.includes(index + 1);
|
||||
return (
|
||||
ispageNumber && (
|
||||
<div className="absolute z-20 top-[1px] -right-[13px] -translate-x-1/2 -translate-y-1/2">
|
||||
<i
|
||||
style={{ color: bookmarkColor || "red" }}
|
||||
className="fa-solid fa-bookmark"
|
||||
></i>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div ref={pageContainer} className="hidden w-[20%] bg-base-100 md:block">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
{t("pages")}
|
||||
</div>
|
||||
<div
|
||||
className={`flex h-[90%] flex-col items-center m-2
|
||||
autoSignScroll hide-scrollbar max-h-[100vh] `}
|
||||
>
|
||||
<Document
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={onDocumentLoad}
|
||||
file={signPdfUrl}
|
||||
>
|
||||
{Array.from(new Array(allPages), (el, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`${
|
||||
pageNumber - 1 === index ? "border-[red]" : "border-[#878787]"
|
||||
} border-2 m-[10px] flex justify-center items-center relative`}
|
||||
onClick={() => {
|
||||
setPageNumber(index + 1);
|
||||
if (setSignBtnPosition) {
|
||||
setSignBtnPosition([]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{signerPos && addSignatureBookmark(index)}
|
||||
|
||||
<div className="relative z-[1] overflow-hidden">
|
||||
<Page
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={pageWidth - 60}
|
||||
scale={1}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Document>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenderAllPdfPage;
|
||||
@@ -0,0 +1,222 @@
|
||||
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,
|
||||
decryptPdf,
|
||||
flattenPdf,
|
||||
getFileAsArrayBuffer
|
||||
} 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 {
|
||||
let uploadedPdfBytes = await file.arrayBuffer();
|
||||
try {
|
||||
uploadedPdfBytes = await flattenPdf(uploadedPdfBytes);
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, "");
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 401) {
|
||||
const password = prompt(
|
||||
`PDF "${file.name}" is password-protected. Enter password:`
|
||||
);
|
||||
if (password) {
|
||||
try {
|
||||
const pdfFile = await decryptPdf(file, password);
|
||||
const pdfArrayBuffer = await getFileAsArrayBuffer(pdfFile);
|
||||
uploadedPdfBytes = await flattenPdf(pdfArrayBuffer);
|
||||
// Upload the file to Parse Server
|
||||
} catch (err) {
|
||||
console.error("Incorrect password or decryption failed", err);
|
||||
alert("Incorrect password or decryption failed.");
|
||||
}
|
||||
} else {
|
||||
alert("Please provided Password.");
|
||||
}
|
||||
} else {
|
||||
console.log("Err ", err);
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert("error while uploading pdf.");
|
||||
}
|
||||
}
|
||||
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">
|
||||
{t("add-pages")}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenderAllPdfPage;
|
||||
@@ -1,611 +0,0 @@
|
||||
import React from "react";
|
||||
import RSC from "react-scrollbars-custom";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import {
|
||||
defaultWidthHeight,
|
||||
getContainerScale,
|
||||
handleImageResize,
|
||||
handleSignYourselfImageResize
|
||||
} from "../../constant/Utils";
|
||||
import Placeholder from "./Placeholder";
|
||||
import Alert from "../../primitives/Alert";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function RenderPdf({
|
||||
pageNumber,
|
||||
drop,
|
||||
signerPos,
|
||||
successEmail,
|
||||
handleTabDrag,
|
||||
handleStop,
|
||||
isDragging,
|
||||
setIsSignPad,
|
||||
setIsStamp,
|
||||
handleDeleteSign,
|
||||
setSignKey,
|
||||
pdfDetails,
|
||||
xyPostion,
|
||||
pageDetails,
|
||||
pdfRequest,
|
||||
signerObjectId,
|
||||
signedSigners,
|
||||
pdfLoad,
|
||||
setPdfLoad,
|
||||
placeholder,
|
||||
setSignerPos,
|
||||
setXyPostion,
|
||||
index,
|
||||
containerWH,
|
||||
setIsResize,
|
||||
handleLinkUser,
|
||||
setUniqueId,
|
||||
signersdata,
|
||||
setIsPageCopy,
|
||||
setSignerObjId,
|
||||
setShowDropdown,
|
||||
setIsInitial,
|
||||
setIsValidate,
|
||||
setWidgetType,
|
||||
setValidateAlert,
|
||||
setIsRadio,
|
||||
setCurrWidgetsDetails,
|
||||
setSelectWidgetId,
|
||||
selectWidgetId,
|
||||
unSignedWidgetId,
|
||||
setIsCheckbox,
|
||||
handleNameModal,
|
||||
handleTextSettingModal,
|
||||
setTempSignerId,
|
||||
uniqueId,
|
||||
pdfOriginalWH,
|
||||
scale,
|
||||
setIsSelectId,
|
||||
ispublicTemplate,
|
||||
handleUserDetails
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const isMobile = window.innerWidth < 767;
|
||||
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
|
||||
// handle signature block width and height according to screen
|
||||
const posWidth = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
pdfOriginalWH,
|
||||
pageNumber,
|
||||
containerWH
|
||||
);
|
||||
const defaultWidth = defaultWidthHeight(pos.type).width;
|
||||
const posWidth = pos.Width ? pos.Width : defaultWidth;
|
||||
if (signYourself) {
|
||||
return posWidth * scale * containerScale;
|
||||
} else {
|
||||
if (pos.isMobile && pos.scale) {
|
||||
if (pos.IsResize) {
|
||||
if (scale > 1) {
|
||||
return posWidth * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posWidth * containerScale;
|
||||
}
|
||||
} else {
|
||||
if (scale > 1) {
|
||||
return posWidth * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posWidth * pos.scale * containerScale;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return posWidth * scale * containerScale;
|
||||
}
|
||||
}
|
||||
};
|
||||
const posHeight = (pos, signYourself) => {
|
||||
const containerScale = getContainerScale(
|
||||
pdfOriginalWH,
|
||||
pageNumber,
|
||||
containerWH
|
||||
);
|
||||
const posHeight = pos.Height || defaultWidthHeight(pos.type).height;
|
||||
if (signYourself) {
|
||||
return posHeight * scale * containerScale;
|
||||
} else {
|
||||
if (pos.isMobile && pos.scale) {
|
||||
if (pos.IsResize) {
|
||||
if (scale > 1) {
|
||||
return posHeight * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posHeight * containerScale;
|
||||
}
|
||||
} else {
|
||||
if (scale > 1) {
|
||||
return posHeight * pos.scale * containerScale * scale;
|
||||
} else {
|
||||
return posHeight * pos.scale * containerScale;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return posHeight * scale * containerScale;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//function for render placeholder block over pdf document
|
||||
const checkSignedSigners = (data) => {
|
||||
let checkSign = [];
|
||||
//condition to handle quick send flow and using normal request sign flow
|
||||
checkSign = signedSigners
|
||||
? signedSigners?.filter(
|
||||
(sign) =>
|
||||
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
|
||||
)
|
||||
: [];
|
||||
const handleAllUserName = (Id, Role, type) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="text-black text-[8px] font-bold">
|
||||
{
|
||||
pdfDetails[0].Signers?.find(
|
||||
(signer) => signer.objectId === data.signerObjId
|
||||
)?.Name
|
||||
}
|
||||
</div>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
{Role && (
|
||||
<div className="text-black text-[8px] font-medium">
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
return (
|
||||
checkSign.length === 0 &&
|
||||
data.placeHolder.map((placeData, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
pos && (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setSignKey={setSignKey}
|
||||
setIsSignPad={setIsSignPad}
|
||||
setIsStamp={setIsStamp}
|
||||
handleSignYourselfImageResize={handleImageResize}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
isShowBorder={false}
|
||||
signerObjId={signerObjectId}
|
||||
isShowDropdown={true}
|
||||
isNeedSign={true}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
handleUserName={handleAllUserName}
|
||||
isDragging={false}
|
||||
pdfDetails={pdfDetails}
|
||||
setIsInitial={setIsInitial}
|
||||
setValidateAlert={setValidateAlert}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
uniqueId={uniqueId}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
ispublicTemplate={ispublicTemplate}
|
||||
handleUserDetails={handleUserDetails}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
//function for render placeholder block over pdf document
|
||||
|
||||
const handleUserName = (Id, Role, type) => {
|
||||
if (Id) {
|
||||
const checkSign = signersdata.find((sign) => sign.Id === Id);
|
||||
if (checkSign?.Name) {
|
||||
return (
|
||||
<>
|
||||
<div className="text-black text-[8px] font-medium">
|
||||
{checkSign?.Name}
|
||||
</div>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
{Role && (
|
||||
<div className="text-black text-[8px] font-medium">
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
<div className="text-black text-[8px] font-medium">{Role}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
<div className="text-black text-[8px] font-medium">{Role}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{successEmail && (
|
||||
<Alert type={"success"}>{t("success-email-alert")}</Alert>
|
||||
)}
|
||||
{isMobile ? (
|
||||
<div
|
||||
data-tut="reactourForth"
|
||||
className={`${
|
||||
isGuestSigner ? "30px" : ""
|
||||
} border-[0.1px] border-[#ebe8e8]`}
|
||||
ref={drop}
|
||||
id="container"
|
||||
>
|
||||
{containerWH?.width &&
|
||||
pdfOriginalWH.length > 0 &&
|
||||
(pdfRequest
|
||||
? signerPos.map((data, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: placeholder // placeholder mobile
|
||||
? signerPos.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
setSignerObjId={setSignerObjId}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
setShowDropdown={setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={setUniqueId}
|
||||
handleLinkUser={handleLinkUser}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={isDragging}
|
||||
setIsValidate={setIsValidate}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={handleNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
setIsSelectId={setIsSelectId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: xyPostion.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === pageNumber &&
|
||||
data.pos.map((pos) => {
|
||||
return (
|
||||
pos && (
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleSignYourselfImageResize
|
||||
}
|
||||
index={index}
|
||||
xyPostion={xyPostion}
|
||||
setXyPostion={setXyPostion}
|
||||
containerWH={containerWH}
|
||||
setIsSignPad={setIsSignPad}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={pdfDetails[0]}
|
||||
isDragging={isDragging}
|
||||
setIsInitial={setIsInitial}
|
||||
setWidgetType={setWidgetType}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleUserName={handleUserName}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setValidateAlert={setValidateAlert}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
|
||||
{/* this component for render pdf document is in middle of the component */}
|
||||
<div className="flex items-center justify-center">
|
||||
<Document
|
||||
onLoadError={() => {
|
||||
setPdfLoad(false);
|
||||
}}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={pageDetails}
|
||||
// ref={pdfRef}'
|
||||
onClick={() => {
|
||||
if (setSelectWidgetId) {
|
||||
setSelectWidgetId("");
|
||||
}
|
||||
}}
|
||||
file={pdfDetails[0]?.SignedUrl || pdfDetails[0]?.URL}
|
||||
>
|
||||
<Page
|
||||
key={index}
|
||||
pageNumber={pageNumber}
|
||||
width={containerWH.width}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
/>
|
||||
</Document>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<RSC
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||
height: window.innerHeight + "px",
|
||||
zIndex: 0
|
||||
}}
|
||||
noScrollY={false}
|
||||
noScrollX={scale === 1 ? true : false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: containerWH?.width && containerWH?.width * scale
|
||||
}}
|
||||
ref={drop}
|
||||
id="container"
|
||||
>
|
||||
{pdfLoad &&
|
||||
containerWH?.width &&
|
||||
pdfOriginalWH.length > 0 &&
|
||||
(pdfRequest //pdf request sign flow
|
||||
? signerPos?.map((data, key) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: placeholder //placeholder and template flow
|
||||
? signerPos.map((data, ind) => {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.placeHolder.map((placeData, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
handleImageResize
|
||||
}
|
||||
index={pageNumber}
|
||||
xyPostion={signerPos}
|
||||
setXyPostion={setSignerPos}
|
||||
setSignerObjId={setSignerObjId}
|
||||
data={data}
|
||||
setIsResize={setIsResize}
|
||||
setShowDropdown={setShowDropdown}
|
||||
isShowBorder={true}
|
||||
isPlaceholder={true}
|
||||
setUniqueId={setUniqueId}
|
||||
handleLinkUser={handleLinkUser}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isDragging={isDragging}
|
||||
setIsValidate={setIsValidate}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={handleNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
setIsSelectId={setIsSelectId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
: xyPostion.map((data, ind) => {
|
||||
// signyourself flow
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === pageNumber &&
|
||||
data.pos.map((pos) => {
|
||||
return (
|
||||
<React.Fragment key={pos.key}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setIsStamp={setIsStamp}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={(event, dragElement) =>
|
||||
handleStop(event, dragElement, pos.type)
|
||||
}
|
||||
handleSignYourselfImageResize={
|
||||
handleSignYourselfImageResize
|
||||
}
|
||||
index={index}
|
||||
xyPostion={xyPostion}
|
||||
setXyPostion={setXyPostion}
|
||||
setIsSignPad={setIsSignPad}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={pdfDetails[0]}
|
||||
isDragging={isDragging}
|
||||
setIsInitial={setIsInitial}
|
||||
setWidgetType={setWidgetType}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleUserName={handleUserName}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setValidateAlert={setValidateAlert}
|
||||
setCurrWidgetsDetails={
|
||||
setCurrWidgetsDetails
|
||||
}
|
||||
handleTextSettingModal={
|
||||
handleTextSettingModal
|
||||
}
|
||||
scale={scale}
|
||||
containerWH={containerWH}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pageNumber={pageNumber}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
|
||||
{/* this component for render pdf document is in middle of the component */}
|
||||
<Document
|
||||
onLoadError={() => {
|
||||
setPdfLoad(false);
|
||||
}}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={pageDetails}
|
||||
onClick={() => {
|
||||
if (setSelectWidgetId) {
|
||||
setSelectWidgetId("");
|
||||
}
|
||||
}}
|
||||
// ref={pdfRef}
|
||||
file={pdfDetails[0]?.SignedUrl || pdfDetails[0].URL}
|
||||
>
|
||||
<Page
|
||||
key={index}
|
||||
width={containerWH.width}
|
||||
scale={scale || 1}
|
||||
className={"-z-[1]"} // when user zoom-in in tablet widgets move backward that's why pass -z-[1]
|
||||
pageNumber={pageNumber}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
/>
|
||||
</Document>
|
||||
</div>
|
||||
</RSC>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenderPdf;
|
||||
@@ -0,0 +1,458 @@
|
||||
import React, { useState, useRef } 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";
|
||||
import usePdfPinchZoom from "../../hook/usePdfPinchZoom";
|
||||
|
||||
function RenderPdf(props) {
|
||||
const { t } = useTranslation();
|
||||
const [scaledHeight, setScaledHeight] = useState();
|
||||
const [guideline, setGuideline] = useState({
|
||||
show: false,
|
||||
x1: 0,
|
||||
x2: 0,
|
||||
y1: 0,
|
||||
y2: 0
|
||||
});
|
||||
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
|
||||
const pdfContainerRef = useRef(null);
|
||||
|
||||
// enable pinch to zoom only on actual pdf wrapper
|
||||
usePdfPinchZoom(
|
||||
pdfContainerRef,
|
||||
props.scale,
|
||||
props.setScale,
|
||||
props.setZoomPercent
|
||||
);
|
||||
|
||||
const handleGuideline = (isShow, x = 0, y = 0, width = 0, height = 0) => {
|
||||
if (isShow) {
|
||||
setGuideline({
|
||||
show: true,
|
||||
x1: x,
|
||||
x2: x + width,
|
||||
y1: y,
|
||||
y2: y + height
|
||||
});
|
||||
} else {
|
||||
setGuideline({ show: false, x1: 0, x2: 0, y1: 0, y2: 0 });
|
||||
}
|
||||
};
|
||||
|
||||
// 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 (all signing flow)
|
||||
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) => (
|
||||
<React.Fragment key={key}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map(
|
||||
(pos, ind) =>
|
||||
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}
|
||||
showGuidelines={handleGuideline}
|
||||
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}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
setUniqueId={props.setUniqueId}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
handleDeleteSign={props.handleDeleteSign}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
handleTextSettingModal={props.handleTextSettingModal}
|
||||
handleCellSettingModal={props.handleCellSettingModal}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
isFreeResize={props.isSelfSign ? true : false}
|
||||
isOpenSignPad={true}
|
||||
assignedWidgetId={props.assignedWidgetId}
|
||||
isApplyAll={true}
|
||||
setCellCount={props.setCellCount}
|
||||
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) => {
|
||||
if (isMobile) {
|
||||
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>
|
||||
)}
|
||||
<RSC
|
||||
style={{
|
||||
position: "relative",
|
||||
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
|
||||
height: isMobile
|
||||
? isGuestSigner
|
||||
? window.innerHeight - 49 // 49 is height of header
|
||||
: scaledHeight
|
||||
: `${window.innerHeight}px`,
|
||||
zIndex: 0
|
||||
}}
|
||||
noScrollY={isMobile ? props.scale === 1 : false}
|
||||
noScrollX={props.scale === 1}
|
||||
>
|
||||
<div
|
||||
data-tut={isMobile ? "reactourForth" : undefined}
|
||||
className={
|
||||
isMobile
|
||||
? `${isGuestSigner ? "30px" : ""} border-[0.1px] border-[#ebe8e8] overflow-x-auto relative`
|
||||
: "relative"
|
||||
}
|
||||
style={{
|
||||
width:
|
||||
props.containerWH?.width && props.containerWH?.width * props.scale
|
||||
}}
|
||||
ref={(node) => {
|
||||
pdfContainerRef.current = node;
|
||||
props.drop && props.drop(node);
|
||||
}}
|
||||
id="container"
|
||||
>
|
||||
{props.pdfLoad !== false &&
|
||||
props.containerWH?.width &&
|
||||
props.pdfOriginalWH.length > 0 && (
|
||||
<>
|
||||
{props.pdfRequest || props.isSelfSign
|
||||
? // request sign, guest sign,
|
||||
props.signerPos?.map((data, key) => (
|
||||
<React.Fragment key={key}>
|
||||
{checkSignedSigners(data)}
|
||||
</React.Fragment>
|
||||
))
|
||||
: props.placeholder // placeholdersign document, draft document, create template, draft template
|
||||
? props.signerPos?.map((data, ind) => (
|
||||
<React.Fragment key={ind}>
|
||||
{data?.placeHolder &&
|
||||
data?.placeHolder.map((placeData, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map((pos) => (
|
||||
<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}
|
||||
showGuidelines={handleGuideline}
|
||||
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
|
||||
}
|
||||
handleCellSettingModal={
|
||||
props.handleCellSettingModal
|
||||
}
|
||||
scale={props.scale}
|
||||
containerWH={props.containerWH}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
fontSize={props.fontSize}
|
||||
setFontSize={props.setFontSize}
|
||||
setCellCount={props.setCellCount}
|
||||
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 && // signyourself flow
|
||||
props.xyPosition?.map((data, ind) => (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === props.pageNumber &&
|
||||
data.pos.map(
|
||||
(pos, id) =>
|
||||
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}
|
||||
showGuidelines={handleGuideline}
|
||||
pdfDetails={props.pdfDetails[0]}
|
||||
isDragging={props.isDragging}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
handleTextSettingModal={
|
||||
props.handleTextSettingModal
|
||||
}
|
||||
handleCellSettingModal={
|
||||
props.handleCellSettingModal
|
||||
}
|
||||
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={true}
|
||||
isOpenSignPad={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<Document
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadError={(e) => {
|
||||
console.log("PDF load error", e);
|
||||
props.setPdfLoad(false);
|
||||
}}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={(pdf) => {
|
||||
props.setPdfLoad(true);
|
||||
props.pageDetails(pdf);
|
||||
}}
|
||||
onClick={() =>
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
<Page
|
||||
key={props.index}
|
||||
onLoadSuccess={handlePageLoadSuccess}
|
||||
width={props.containerWH.width}
|
||||
scale={props.scale || 1}
|
||||
className={isMobile ? "select-none touch-callout-none" : "-z-[1]"}
|
||||
pageNumber={props.pageNumber}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
onGetAnnotationsError={(error) => {
|
||||
console.log("annotation error", error);
|
||||
}}
|
||||
/>
|
||||
</Document>
|
||||
{guideline.show && (
|
||||
<>
|
||||
{/* top guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ top: guideline.y1 }}
|
||||
/>
|
||||
{/* bottom guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ top: guideline.y2 }}
|
||||
/>
|
||||
{/* left guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ left: guideline.x1 }}
|
||||
/>
|
||||
{/* right guide */}
|
||||
<div
|
||||
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
|
||||
style={{ left: guideline.x2 }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</RSC>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenderPdf;
|
||||
@@ -5,35 +5,35 @@ import { useTranslation } from "react-i18next";
|
||||
function SelectLanguage(props) {
|
||||
const { i18n } = useTranslation();
|
||||
const languages = [
|
||||
{ value: "en", text: "English" },
|
||||
{ value: "fr", text: "Français" }
|
||||
{ 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 [lang, setLang] = useState(i18next.language || "en");
|
||||
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
|
||||
});
|
||||
props?.updateExtUser && props.updateExtUser({ language: e.target.value });
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
!props.isProfile && " mt-3 pb-2 md:pb-0 "
|
||||
} flex justify-center items-center `}
|
||||
!props.isProfile && " mt-[9px] pb-2 md:pb-0 "
|
||||
} flex justify-center items-center text-base-content`}
|
||||
>
|
||||
<select
|
||||
value={lang}
|
||||
onChange={handleChangeLang}
|
||||
className={`${
|
||||
!props.isProfile && " md:w-[15%] w-[50%]"
|
||||
} op-select op-select-bordered bg-white op-select-sm`}
|
||||
!props.isProfile ? " md:w-[15%] w-[50%]" : "w-[180px]"
|
||||
} op-select op-select-bordered op-select-sm `}
|
||||
>
|
||||
<option disabled selected>
|
||||
select
|
||||
</option>
|
||||
<option disabled>select</option>
|
||||
{languages.map((item) => {
|
||||
return (
|
||||
<option key={item.value} value={item.value}>
|
||||
@@ -1,618 +0,0 @@
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SignatureCanvas from "react-signature-canvas";
|
||||
function SignPad({
|
||||
isSignPad,
|
||||
isStamp,
|
||||
setIsImageSelect,
|
||||
onSaveSign,
|
||||
setIsSignPad,
|
||||
setImage,
|
||||
isImageSelect,
|
||||
imageRef,
|
||||
onImageChange,
|
||||
onSaveImage,
|
||||
image,
|
||||
defaultSign,
|
||||
setSignature,
|
||||
myInitial,
|
||||
isInitial,
|
||||
setIsInitial,
|
||||
setIsStamp,
|
||||
widgetType,
|
||||
currWidgetsDetails,
|
||||
setCurrWidgetsDetails
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [penColor, setPenColor] = useState("blue");
|
||||
const allColor = ["blue", "red", "black"];
|
||||
const canvasRef = useRef(null);
|
||||
const [isDefaultSign, setIsDefaultSign] = useState(false);
|
||||
const [isTab, setIsTab] = useState("draw");
|
||||
const [isSignImg, setIsSignImg] = useState("");
|
||||
const [signValue, setSignValue] = useState("");
|
||||
const [textWidth, setTextWidth] = useState(null);
|
||||
const [textHeight, setTextHeight] = useState(null);
|
||||
const [signatureType, setSignatureType] = useState("draw");
|
||||
const fontOptions = [
|
||||
{ value: "Fasthand" },
|
||||
{ value: "Dancing Script" },
|
||||
{ value: "Cedarville Cursive" },
|
||||
{ value: "Delicious Handrawn" }
|
||||
// Add more font options as needed
|
||||
];
|
||||
const [fontSelect, setFontSelect] = useState(fontOptions[0].value);
|
||||
|
||||
const senderUser =
|
||||
localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
) &&
|
||||
localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
);
|
||||
const jsonSender = JSON.parse(senderUser);
|
||||
const currentUserName = jsonSender && jsonSender.name;
|
||||
|
||||
//function for clear signature image
|
||||
const handleClear = () => {
|
||||
setCurrWidgetsDetails({});
|
||||
if (isTab === "draw") {
|
||||
if (canvasRef.current) {
|
||||
canvasRef.current.clear();
|
||||
} else if (isStamp) {
|
||||
setImage("");
|
||||
}
|
||||
setIsSignImg("");
|
||||
} else if (isTab === "uploadImage") {
|
||||
setImage("");
|
||||
}
|
||||
// setIsInitial(false);
|
||||
};
|
||||
//function for set signature url
|
||||
const handleSignatureChange = (data) => {
|
||||
setSignature(data);
|
||||
setIsSignImg(data);
|
||||
};
|
||||
//save button component
|
||||
const SaveBtn = () => {
|
||||
return (
|
||||
<div>
|
||||
{(isTab === "draw" || isTab === "uploadImage") && (
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost mr-1 mt-[2px]"
|
||||
onClick={() => handleClear()}
|
||||
>
|
||||
{t("clear")}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
setCurrWidgetsDetails({});
|
||||
if (!image) {
|
||||
if (isTab === "mysignature") {
|
||||
setIsSignImg("");
|
||||
if (isInitial) {
|
||||
onSaveSign(signatureType, "initials");
|
||||
} else {
|
||||
onSaveSign(null, "default");
|
||||
}
|
||||
} else {
|
||||
if (isTab === "type") {
|
||||
setIsSignImg("");
|
||||
onSaveSign(
|
||||
null,
|
||||
false,
|
||||
!isInitial && textWidth > 150 ? 150 : textWidth,
|
||||
!isInitial && textHeight > 35 ? 35 : textHeight
|
||||
);
|
||||
} else {
|
||||
setIsSignImg("");
|
||||
canvasRef.current.clear();
|
||||
onSaveSign(signatureType);
|
||||
}
|
||||
}
|
||||
|
||||
setPenColor("blue");
|
||||
} else {
|
||||
setIsSignImg("");
|
||||
onSaveImage(signatureType);
|
||||
}
|
||||
setIsSignPad(false);
|
||||
setIsInitial(false);
|
||||
setIsImageSelect(false);
|
||||
setIsDefaultSign(false);
|
||||
setImage();
|
||||
setIsTab("draw");
|
||||
setSignValue("");
|
||||
setIsStamp(false);
|
||||
}}
|
||||
type="button"
|
||||
className={`${
|
||||
isSignImg || image || isDefaultSign || textWidth
|
||||
? ""
|
||||
: "pointer-events-none"
|
||||
} op-btn op-btn-primary shadow-lg`}
|
||||
disabled={
|
||||
(isTab === "draw" && isSignImg) ||
|
||||
(isTab === "image" && image) ||
|
||||
(isTab === "mysignature" && isDefaultSign) ||
|
||||
(isTab === "type" && textWidth)
|
||||
? false
|
||||
: image
|
||||
? false
|
||||
: true
|
||||
}
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
//useEffect for set already draw or save signature url/text url of signature text type and draw type for initial type and signature type widgets
|
||||
useEffect(() => {
|
||||
if (currWidgetsDetails && canvasRef.current) {
|
||||
const isWidgetType = currWidgetsDetails?.type;
|
||||
const signatureType = currWidgetsDetails?.signatureType;
|
||||
const url = currWidgetsDetails?.SignUrl;
|
||||
//checking widget type and draw type signature url
|
||||
if (isInitial) {
|
||||
if (isWidgetType === "initials" && signatureType === "draw" && url) {
|
||||
canvasRef.current.fromDataURL(url);
|
||||
}
|
||||
} else if (
|
||||
isWidgetType === "signature" &&
|
||||
signatureType === "draw" &&
|
||||
url
|
||||
) {
|
||||
canvasRef.current.fromDataURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
const trimmedName = currentUserName && currentUserName.trim();
|
||||
const firstCharacter = trimmedName?.charAt(0);
|
||||
const userName = isInitial ? firstCharacter : currentUserName;
|
||||
setSignValue(userName);
|
||||
setFontSelect("Fasthand");
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isSignPad]);
|
||||
useEffect(() => {
|
||||
const loadFont = async () => {
|
||||
try {
|
||||
await document.fonts.load(`20px ${fontSelect}`);
|
||||
const selectFontSTyle = fontOptions.find(
|
||||
(font) => font.value === fontSelect
|
||||
);
|
||||
setFontSelect(selectFontSTyle?.value || fontOptions[0].value);
|
||||
} catch (error) {
|
||||
console.error("Error loading font:", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadFont();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fontSelect]);
|
||||
|
||||
useEffect(() => {
|
||||
// Load the default signature after the component mounts
|
||||
if (canvasRef.current) {
|
||||
canvasRef.current.fromDataURL(isSignImg);
|
||||
}
|
||||
if (isTab === "type") {
|
||||
const trimmedName = signValue ? signValue.trim() : currentUserName.trim();
|
||||
const firstCharacter = trimmedName.charAt(0);
|
||||
const userName = isInitial ? firstCharacter : signValue;
|
||||
setSignValue(userName);
|
||||
convertToImg(fontSelect, userName);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isTab]);
|
||||
//function for convert input text value in image
|
||||
const convertToImg = async (fontStyle, text, color) => {
|
||||
//get text content to convert in image
|
||||
const textContent = text;
|
||||
const fontfamily = fontStyle
|
||||
? fontStyle
|
||||
: fontSelect
|
||||
? fontSelect
|
||||
: "Fasthand";
|
||||
const fontSizeValue = "40px";
|
||||
//creating span for getting text content width
|
||||
const span = document.createElement("span");
|
||||
span.textContent = textContent;
|
||||
span.style.font = `${fontSizeValue} ${fontfamily}`; // here put your text size and font family
|
||||
span.style.color = color ? color : penColor;
|
||||
span.style.display = "hidden";
|
||||
document.body.appendChild(span); // Replace 'container' with the ID of the container element
|
||||
|
||||
//create canvas to render text in canvas and convert in image
|
||||
const canvasElement = document.createElement("canvas");
|
||||
// Draw the text content on the canvas
|
||||
const ctx = canvasElement.getContext("2d");
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
const addExtraWidth = isInitial ? 10 : 50;
|
||||
const width = span.offsetWidth + addExtraWidth;
|
||||
const height = span.offsetHeight;
|
||||
setTextWidth(width);
|
||||
setTextHeight(height);
|
||||
const font = span.style["font"];
|
||||
// Set the canvas dimensions to match the span
|
||||
canvasElement.width = width * pixelRatio;
|
||||
canvasElement.height = height * pixelRatio;
|
||||
|
||||
// You can customize text styles if needed
|
||||
ctx.font = font;
|
||||
ctx.fillStyle = color ? color : penColor; // Set the text color
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.scale(pixelRatio, pixelRatio);
|
||||
// Draw the content of the span onto the canvas
|
||||
ctx.fillText(span.textContent, width / 2, height / 2); // Adjust the x,y-coordinate as needed
|
||||
//remove span tag
|
||||
document.body.removeChild(span);
|
||||
// Convert the canvas to image data
|
||||
const dataUrl = canvasElement.toDataURL("image/png");
|
||||
setSignature(dataUrl);
|
||||
};
|
||||
|
||||
const PenColorComponent = (props) => {
|
||||
return (
|
||||
<div className="flex flex-row items-start">
|
||||
{allColor.map((data, key) => {
|
||||
return (
|
||||
<i
|
||||
style={{
|
||||
margin: "5px",
|
||||
color: data,
|
||||
borderBottom:
|
||||
key === 0 && penColor === "blue"
|
||||
? "2px solid blue"
|
||||
: key === 1 && penColor === "red"
|
||||
? "2px solid red"
|
||||
: key === 2 && penColor === "black"
|
||||
? "2px solid black"
|
||||
: "2px solid white"
|
||||
}}
|
||||
onClick={() => {
|
||||
props?.convertToImg &&
|
||||
props?.convertToImg(fontSelect, signValue, data);
|
||||
if (key === 0) {
|
||||
setPenColor("blue");
|
||||
} else if (key === 1) {
|
||||
setPenColor("red");
|
||||
} else if (key === 2) {
|
||||
setPenColor("black");
|
||||
}
|
||||
}}
|
||||
key={key}
|
||||
className="fa-light fa-pen-nib"
|
||||
width={20}
|
||||
height={20}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{isSignPad && (
|
||||
<div className="op-modal op-modal-open">
|
||||
<div className="op-modal-box px-[13px] pt-2 pb-0">
|
||||
<div className="flex justify-between text-base-content items-center">
|
||||
<div className="text-[1.2rem]">
|
||||
<div className="flex flex-row justify-between mt-[3px]">
|
||||
<div className="flex flex-row justify-between gap-[5px] md:gap-[8px] text-[11px] md:text-base">
|
||||
{isStamp ? (
|
||||
<span className="text-base-content font-bold text-lg">
|
||||
{widgetType === "image" ||
|
||||
currWidgetsDetails?.type === "image"
|
||||
? t("upload-image")
|
||||
: t("upload-stamp-image")}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(false);
|
||||
setIsImageSelect(false);
|
||||
setIsTab("draw");
|
||||
setImage();
|
||||
if (isSignImg) {
|
||||
setSignature(isSignImg);
|
||||
}
|
||||
}}
|
||||
className={`${
|
||||
isTab === "draw"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
{t("draw")}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(false);
|
||||
setIsImageSelect(true);
|
||||
setIsTab("uploadImage");
|
||||
setSignatureType("");
|
||||
}}
|
||||
className={`${
|
||||
isTab === "uploadImage"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
{t("upload-image")}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(false);
|
||||
setIsImageSelect(false);
|
||||
setIsTab("type");
|
||||
setSignatureType("");
|
||||
setImage();
|
||||
}}
|
||||
className={`${
|
||||
isTab === "type"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
{t("type")}
|
||||
</span>
|
||||
</div>
|
||||
{!isInitial && defaultSign ? (
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(true);
|
||||
setIsImageSelect(true);
|
||||
setIsTab("mysignature");
|
||||
setSignatureType("");
|
||||
setImage();
|
||||
}}
|
||||
className={`${
|
||||
isTab === "mysignature"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
{t("my-signature")}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
isInitial &&
|
||||
myInitial && (
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(true);
|
||||
setIsImageSelect(true);
|
||||
setIsTab("mysignature");
|
||||
setSignatureType("");
|
||||
setImage();
|
||||
}}
|
||||
className={`${
|
||||
isTab === "mysignature"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
{t("my-initials")}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="text-[1.5rem] cursor-pointer"
|
||||
onClick={() => {
|
||||
setPenColor("blue");
|
||||
setIsSignPad(false);
|
||||
setIsInitial(false);
|
||||
setIsImageSelect(false);
|
||||
setIsDefaultSign(false);
|
||||
setImage();
|
||||
setIsTab("draw");
|
||||
setSignatureType("draw");
|
||||
setSignValue("");
|
||||
setIsStamp(false);
|
||||
}}
|
||||
>
|
||||
×
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-[20px] h-full">
|
||||
{isDefaultSign ? (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
style={{
|
||||
border: "1.3px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginBottom: 6,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
className={
|
||||
isInitial ? "intialSignatureCanvas" : "signatureCanvas"
|
||||
}
|
||||
>
|
||||
<img
|
||||
alt="stamp img"
|
||||
className="w-full h-full object-contain bg-white"
|
||||
draggable="false"
|
||||
src={isInitial ? myInitial : defaultSign}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
) : isImageSelect || isStamp ? (
|
||||
!image ? (
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
style={{
|
||||
border: "1.3px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginBottom: 6,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
className={
|
||||
isInitial ? "intialSignatureCanvas" : "signatureCanvas"
|
||||
}
|
||||
onClick={() => imageRef.current.click()}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
onChange={onImageChange}
|
||||
className="filetype"
|
||||
accept="image/png,image/jpeg"
|
||||
ref={imageRef}
|
||||
hidden
|
||||
/>
|
||||
<i className="fa-light fa-cloud-upload-alt uploadImgLogo"></i>
|
||||
<div className="text-[10px]">{t("upload")}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
style={{
|
||||
border: "1.3px solid #007bff",
|
||||
borderRadius: "2px",
|
||||
marginBottom: 6,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
className={
|
||||
isInitial
|
||||
? "intialSignatureCanvas"
|
||||
: "signatureCanvas"
|
||||
}
|
||||
>
|
||||
<img
|
||||
alt="print img"
|
||||
ref={imageRef}
|
||||
src={image.src}
|
||||
draggable="false"
|
||||
className=" object-contain h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
) : isTab === "type" ? (
|
||||
<div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="mr-[5px] text-[12px]">
|
||||
{isInitial ? t("initial-teb") : t("signature-tab")}:
|
||||
</span>
|
||||
<input
|
||||
maxLength={isInitial ? 3 : 30}
|
||||
style={{ fontFamily: fontSelect, color: penColor }}
|
||||
type="text"
|
||||
className="ml-1 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-[20px]"
|
||||
placeholder="Your signature"
|
||||
value={signValue}
|
||||
onChange={(e) => {
|
||||
setSignValue(e.target.value);
|
||||
convertToImg(fontSelect, e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-[1px] border-[#d6d3d3] mt-[10px] ml-[5px]">
|
||||
{fontOptions.map((font, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
fontFamily: font.value,
|
||||
backgroundColor:
|
||||
fontSelect === font.value && "rgb(206 225 247)"
|
||||
}}
|
||||
onClick={() => {
|
||||
setFontSelect(font.value);
|
||||
convertToImg(font.value, signValue);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
padding: "5px 10px 5px 10px",
|
||||
fontSize: "20px",
|
||||
color: penColor
|
||||
}}
|
||||
>
|
||||
{signValue ? signValue : "Your signature"}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent convertToImg={convertToImg} />
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<SignatureCanvas
|
||||
ref={canvasRef}
|
||||
penColor={penColor}
|
||||
canvasProps={{
|
||||
className: isInitial
|
||||
? "intialSignatureCanvas"
|
||||
: "signatureCanvas",
|
||||
style: {
|
||||
border: "1.6px solid #007bff",
|
||||
borderRadius: "2px"
|
||||
}
|
||||
}}
|
||||
backgroundColor="rgb(255, 255, 255)"
|
||||
onEnd={() =>
|
||||
handleSignatureChange(canvasRef.current?.toDataURL())
|
||||
}
|
||||
dotSize={1}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent />
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignPad;
|
||||
@@ -1,31 +1,41 @@
|
||||
import React from "react";
|
||||
import "../../styles/signature.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
function Signedby({ pdfDetails }) {
|
||||
function Signedby(props) {
|
||||
const { t } = useTranslation();
|
||||
const getFirstLetter = (name) => {
|
||||
const getFirstLetter = (pdfData) => {
|
||||
const name = props.isSelfSign
|
||||
? (pdfData?.Signers && pdfData?.Signers[0]?.Name) || "User"
|
||||
: pdfData.ExtUserPtr?.Name;
|
||||
const firstLetter = name.charAt(0);
|
||||
return firstLetter;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="hidden md:block w-full h-full bg-base-100">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
{t("signed-by")}
|
||||
{props.isSelfSign ? t("user") : t("signed-by")}
|
||||
</div>
|
||||
<div className="mt-[2px] bg-base-100">
|
||||
<div className="bg-[#93a3db] rounded-xl mx-1 flex flex-row items-center py-[10px]">
|
||||
<div className="bg-[#576081] flex w-[30px] h-[30px] rounded-full justify-center items-center mx-1">
|
||||
<span className="text-[12px] text-center font-bold text-white uppercase">
|
||||
{getFirstLetter(pdfDetails.ExtUserPtr.Name)}
|
||||
{getFirstLetter(props.pdfDetails)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[12px] font-bold text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{pdfDetails.ExtUserPtr.Name}
|
||||
{props.isSelfSign
|
||||
? (props.pdfDetails?.Signers &&
|
||||
props.pdfDetails?.Signers[0]?.Name) ||
|
||||
"User"
|
||||
: props.pdfDetails.ExtUserPtr.Name || "User"}
|
||||
</span>
|
||||
<span className="text-[10px] font-medium text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{pdfDetails.ExtUserPtr.Email}
|
||||
{props.isSelfSign
|
||||
? (props.pdfDetails?.Signers &&
|
||||
props.pdfDetails?.Signers[0]?.Email) ||
|
||||
""
|
||||
: props.pdfDetails.ExtUserPtr.Email || ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||