Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8833018027 | ||
|
|
d1c405912b | ||
|
|
745aca1754 | ||
|
|
672eeea851 | ||
|
|
98efff65c9 | ||
|
|
fe21978538 | ||
|
|
ca4ccfd889 | ||
|
|
6cb2d69566 | ||
|
|
98b9ebb228 | ||
|
|
1c2f453496 | ||
|
|
b01d8125d9 | ||
|
|
85bc2fbbb0 | ||
|
|
e857724502 | ||
|
|
086d8ee5be | ||
|
|
04543e826a | ||
|
|
04f6e38778 | ||
|
|
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 | ||
|
|
9852c87be7 | ||
|
|
1268f65516 | ||
|
|
6551b2537a | ||
|
|
b138ccf625 | ||
|
|
27ff7baa6d | ||
|
|
3fc41f4255 | ||
|
|
b20aa33a04 | ||
|
|
a64da4cd85 | ||
|
|
e80bcfc586 | ||
|
|
de5b8160a2 | ||
|
|
83df969751 | ||
|
|
f04fd16c9e |
@@ -232,6 +232,15 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "1024mb",
|
||||||
|
"name": "1024mb",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/9301204?v=4",
|
||||||
|
"profile": "https://github.com/1024mb",
|
||||||
|
"contributions": [
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,11 @@ PUBLIC_URL=https://localhost:3001
|
|||||||
GENERATE_SOURCEMAP=false
|
GENERATE_SOURCEMAP=false
|
||||||
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
|
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
|
||||||
# REACT_APP_SERVERURL=http://localhost:8080/app
|
# REACT_APP_SERVERURL=http://localhost:8080/app
|
||||||
# A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API.
|
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
|
||||||
REACT_APP_APPID=opensign
|
REACT_APP_APPID=opensign
|
||||||
|
|
||||||
|
|
||||||
# Backend ExpressJS config ****************************************************************************************************************************************************************************************
|
# Backend ExpressJS config ****************************************************************************************************************************************************************************************
|
||||||
# A 12 character long random app identifier. The value of this should be same as REACT_APP_APPID which is a variable used by Frontend React App.
|
|
||||||
APP_ID=opensign
|
|
||||||
# Name of the app. It will be visible in the verification emails sent out.
|
# Name of the app. It will be visible in the verification emails sent out.
|
||||||
appName=open_sign_server
|
appName=open_sign_server
|
||||||
# A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database.
|
# A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database.
|
||||||
@@ -111,3 +109,6 @@ CRUxFgQUDYlgGVxSxuOknhQc256x3++7BDwwMTAhMAkGBSsOAwIaBQAEFFjASdYl
|
|||||||
|
|
||||||
# Provide Pass pharse of above PFX or p12 document
|
# Provide Pass pharse of above PFX or p12 document
|
||||||
PASS_PHRASE=opensign
|
PASS_PHRASE=opensign
|
||||||
|
|
||||||
|
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
|
||||||
|
APP_ID=opensign
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
name: ci
|
name: ci
|
||||||
|
|
||||||
|
# 👇 add this block
|
||||||
|
permissions:
|
||||||
|
contents: read # allow checkout & metadata-action to read repo
|
||||||
|
id-token: write # needed by docker/metadata-action v4
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
|
- 'staging'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
@@ -77,6 +77,6 @@ Engage with the community on our [Gitter Channel](https://gitter.im/OpenSignLabs
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
By contributing, you agree that your contributions will be licensed under its [MIT License](LICENSE).
|
By contributing, you agree that your contributions will be licensed under its [AGPL-V3 License](LICENSE).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{$HOST_URL} {
|
{$HOST_URL} {
|
||||||
reverse_proxy client:3000
|
reverse_proxy client:3000
|
||||||
handle_path /app/* {
|
handle_path /api/* {
|
||||||
reverse_proxy server:8080
|
reverse_proxy server:8080
|
||||||
rewrite * /app{uri}
|
rewrite * {uri}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
* 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
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 19 November 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
|||||||
@@ -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 -
|
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 -
|
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/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/ugoconsonni"><img src="https://avatars.githubusercontent.com/u/13661702?v=4?s=100" width="100px;" alt="ugoconsonni"/><br /><sub><b>ugoconsonni</b></sub></a><br /><a href="#code-ugoconsonni" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daniel-mutwiri"><img src="https://avatars.githubusercontent.com/u/8936960?v=4?s=100" width="100px;" alt="Daniel Mutwiri"/><br /><sub><b>Daniel Mutwiri</b></sub></a><br /><a href="#code-daniel-mutwiri" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daniel-mutwiri"><img src="https://avatars.githubusercontent.com/u/8936960?v=4?s=100" width="100px;" alt="Daniel Mutwiri"/><br /><sub><b>Daniel Mutwiri</b></sub></a><br /><a href="#code-daniel-mutwiri" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zathiel"><img src="https://avatars.githubusercontent.com/u/26553418?v=4?s=100" width="100px;" alt="Zathiel"/><br /><sub><b>Zathiel</b></sub></a><br /><a href="#code-Zathiel" title="Code">💻</a><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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -157,3 +174,5 @@ We would like to thank all our contributors and users for their support and feed
|
|||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
|
This project is tested with BrowserStack.
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
# Use an official Node runtime as the base image
|
|
||||||
FROM node:18
|
|
||||||
|
|
||||||
# Set the working directory inside the container
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
# Copy package.json and package-lock.json first to leverage Docker cache
|
|
||||||
COPY ./package*.json ./
|
|
||||||
|
|
||||||
# Install application dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# Copy the current directory contents into the container
|
|
||||||
COPY ./ .
|
|
||||||
COPY ./.husky .
|
|
||||||
|
|
||||||
# Make port 3000 available to the world outside this container
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
# Define environment variables if needed
|
|
||||||
# ENV NODE_ENV production
|
|
||||||
|
|
||||||
# Run the application
|
|
||||||
ENTRYPOINT npm run start
|
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Use an official Node runtime as the base image
|
# Use an official Node runtime as the base image
|
||||||
FROM node:18
|
FROM node:22.14.0
|
||||||
|
|
||||||
# Set the working directory inside the container
|
# Set the working directory inside the container
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
@@ -13,15 +13,24 @@ RUN npm install
|
|||||||
# Copy the current directory contents into the container
|
# Copy the current directory contents into the container
|
||||||
COPY apps/OpenSign/ .
|
COPY apps/OpenSign/ .
|
||||||
COPY apps/OpenSign/.husky .
|
COPY apps/OpenSign/.husky .
|
||||||
|
COPY apps/OpenSign/entrypoint.sh .
|
||||||
|
|
||||||
|
# make the entrypoint.sh file executable
|
||||||
|
RUN chmod +x entrypoint.sh
|
||||||
|
|
||||||
|
# Define environment variables if needed
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV GENERATE_SOURCEMAP=false
|
||||||
# build
|
# build
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
# Inject env.js loader into index.html
|
||||||
|
RUN sed -i '/<head>/a\<script src="/env.js"></script>' build/index.html
|
||||||
|
|
||||||
# Make port 3000 available to the world outside this container
|
# Make port 3000 available to the world outside this container
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
# Define environment variables if needed
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
# ENV NODE_ENV production
|
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
CMD ["npm", "start"]
|
CMD ["npm", "start"]
|
||||||
|
|||||||
@@ -0,0 +1,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>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="opensigncss">
|
<html lang="en" data-theme="opensigncss">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description" content="The fastest way to sign PDFs & request signatures from others" />
|
<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"
|
<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" />
|
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
|
||||||
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
|
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
|
||||||
<title>OpenSign™</title>
|
<title>...Loading</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,71 +1,72 @@
|
|||||||
{
|
{
|
||||||
"name": "open_sign",
|
"name": "open_sign",
|
||||||
"version": "0.1.0",
|
"version": "2.26.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formkit/auto-animate": "^0.8.2",
|
"@formkit/auto-animate": "^0.9.0",
|
||||||
"@lottiefiles/dotlottie-react": "^0.8.9",
|
"@imgly/background-removal": "^1.7.0",
|
||||||
|
"@lottiefiles/dotlottie-react": "^0.16.2",
|
||||||
"@pdf-lib/fontkit": "^1.1.1",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@radix-ui/themes": "^3.1.3",
|
"@radix-ui/themes": "^3.2.1",
|
||||||
"@react-pdf/renderer": "^3.4.4",
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"@reduxjs/toolkit": "^2.2.7",
|
"axios": "^1.12.2",
|
||||||
"axios": "^1.7.5",
|
"date-fns-tz": "^3.2.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"i18next": "^23.14.0",
|
"i18next": "^25.4.0",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"i18next-http-backend": "^2.6.1",
|
"i18next-http-backend": "^3.0.2",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"nth-check": "^2.1.1",
|
"parse": "^6.1.1",
|
||||||
"parse": "^5.3.0",
|
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
|
"pkijs": "^3.0.8",
|
||||||
"print-js": "^1.6.0",
|
"print-js": "^1.6.0",
|
||||||
"radix-ui": "^1.0.1",
|
"prismjs": "^1.30.0",
|
||||||
"react": "^18.2.0",
|
"quill-html-edit-button": "^3.0.0",
|
||||||
"react-bootstrap": "^2.10.4",
|
"radix-ui": "^1.4.3",
|
||||||
"react-confetti": "^6.1.0",
|
"react": "^18.3.1",
|
||||||
"react-cookie": "^7.2.0",
|
"react-bootstrap": "^2.10.10",
|
||||||
"react-datepicker": "^7.3.0",
|
"react-confetti": "^6.4.0",
|
||||||
|
"react-datepicker": "^8.7.0",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dnd-multi-backend": "^8.0.3",
|
"react-dnd-multi-backend": "^9.0.0",
|
||||||
"react-dnd-touch-backend": "^16.0.1",
|
"react-dnd-touch-backend": "^16.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.3.1",
|
||||||
"react-gtm-module": "^2.0.11",
|
"react-gtm-module": "^2.0.11",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.7.3",
|
||||||
"react-konva": "^18.2.10",
|
"react-konva": "^18.2.12",
|
||||||
"react-pdf": "^9.1.0",
|
"react-pdf": "^9.2.1",
|
||||||
"react-quill-new": "^3.3.0",
|
"react-quill-new": "^3.6.0",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.2.0",
|
||||||
"react-rnd": "^10.4.11",
|
"react-rnd": "^10.5.2",
|
||||||
"react-router-dom": "^6.26.1",
|
"react-router": "^7.9.1",
|
||||||
"react-scripts": "5.0.1",
|
|
||||||
"react-scrollbars-custom": "^4.1.1",
|
"react-scrollbars-custom": "^4.1.1",
|
||||||
"react-select": "^5.8.0",
|
"react-select": "^5.10.2",
|
||||||
"react-signature-canvas": "^1.0.6",
|
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||||
"react-tooltip": "^5.28.0",
|
"react-timezone-select": "^3.2.8",
|
||||||
"react-web-share": "^2.0.2",
|
"react-tooltip": "^5.29.1",
|
||||||
"reactour": "^1.19.4",
|
"reactour": "^1.19.4",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"redux-thunk": "^3.1.0",
|
"regex-parser": "^2.3.1",
|
||||||
"regex-parser": "^2.3.0",
|
"serve": "^14.2.5",
|
||||||
"serve": "^14.2.3",
|
"styled-components": "^5.3.11",
|
||||||
"styled-components": "^5.3.0",
|
"web-vitals": "^5.1.0",
|
||||||
"web-vitals": "^4.2.3",
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||||
"ws": "^8.18.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-webpack": "webpack --config webpack.config.js",
|
"build": "npm run version && NODE_OPTIONS=\"--max-old-space-size=8192\" vite build",
|
||||||
"build": "npm run version && npm run build-webpack && react-scripts build",
|
"start-dev": "vite",
|
||||||
"build-watch": "webpack --config webpack.config.js --watch",
|
"dev": "vite",
|
||||||
"start-dev": "concurrently \"npm run build-watch\" \"react-scripts start\"",
|
"preview": "vite preview",
|
||||||
"start": "serve -s build",
|
"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": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
|
||||||
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
|
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
|
||||||
"build-win": "npm run version-win && npm run build-webpack && react-scripts build",
|
"build-win": "npm run version-win && vite build",
|
||||||
"test": "react-scripts test",
|
"test": "vitest run",
|
||||||
"eject": "react-scripts eject",
|
"test:watch": "vitest",
|
||||||
"release": "standard-version",
|
"release": "standard-version",
|
||||||
"commit": "cz"
|
"commit": "cz"
|
||||||
},
|
},
|
||||||
@@ -93,33 +94,38 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.28.3",
|
||||||
"@babel/preset-env": "^7.25.4",
|
"@babel/preset-env": "^7.28.3",
|
||||||
"@babel/preset-react": "^7.24.7",
|
"@babel/preset-react": "^7.27.1",
|
||||||
"@babel/runtime-corejs2": "^7.25.4",
|
"@babel/runtime-corejs2": "^7.28.3",
|
||||||
"autoprefixer": "^10.4.20",
|
"@testing-library/jest-dom": "^6.8.0",
|
||||||
"babel-loader": "^9.1.3",
|
"@testing-library/react": "^16.3.0",
|
||||||
"clean-webpack-plugin": "^4.0.0",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"commitizen": "^4.3.0",
|
"@types/react": "^19.1.13",
|
||||||
"concurrently": "^8.2.2",
|
"@vitejs/plugin-react": "^4.7.0",
|
||||||
|
"@vitest/ui": "^3.2.4",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
|
"babel-loader": "^10.0.0",
|
||||||
|
"commitizen": "^4.3.1",
|
||||||
|
"concurrently": "^9.2.1",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
"daisyui": "^4.12.10",
|
"daisyui": "^4.12.24",
|
||||||
"dotenv": "^16.4.5",
|
"eslint": "^9.34.0",
|
||||||
"dotenv-webpack": "^8.1.0",
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
"eslint": "^8.57.0",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"jsdom": "^27.0.0",
|
||||||
"eslint-plugin-react": "^7.34.3",
|
"lint-staged": "^16.1.6",
|
||||||
"lint-staged": "^15.2.10",
|
"postcss": "^8.5.6",
|
||||||
"mini-css-extract-plugin": "^2.9.1",
|
"prettier": "^3.6.2",
|
||||||
"postcss": "^8.4.45",
|
"pretty-quick": "^4.2.2",
|
||||||
"prettier": "^3.3.3",
|
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||||
"pretty-quick": "^4.0.0",
|
"tailwindcss": "^3.4.17",
|
||||||
"tailwindcss": "^3.4.10",
|
"vite": "^7.1.4",
|
||||||
"terser-webpack-plugin": "^5.3.10",
|
"vite-plugin-svgr": "^4.5.0",
|
||||||
"webpack-cli": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"engines": {
|
||||||
"nth-check": "$nth-check",
|
"node": "18 || 20 || 22"
|
||||||
"ws": "$ws"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,12 +1,14 @@
|
|||||||
{
|
{
|
||||||
"header-news": "Nouvelle fonctionnalité : les profils publics sont désormais en ligne ! Créez et partagez facilement des modèles publics",
|
"header-news": "Nouvelle fonctionnalité : les utilisateurs du forfait Teams peuvent désormais intégrer leurs propres compartiments AWS S3 pour le stockage de fichiers",
|
||||||
"header-news-btn": "Configurer maintenant",
|
"header-news-btn": "Configurer maintenant",
|
||||||
|
"sandbox-news": "Ceci est un environnement sandbox. Veuillez ne pas l'utiliser à des fins de production.",
|
||||||
"create-account": "Créer un compte",
|
"create-account": "Créer un compte",
|
||||||
"login": "Se Connecter",
|
"login": "Se Connecter",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
|
"dark-mode": "Mode sombre",
|
||||||
"name": "Nom et Prénom",
|
"name": "Nom et Prénom",
|
||||||
"phone": "Téléphone",
|
"phone": "Téléphone",
|
||||||
"phone-optional": "(facultatif)",
|
"phone-optional": "Optionnel",
|
||||||
"email": "E-mail",
|
"email": "E-mail",
|
||||||
"company": "Organisation",
|
"company": "Organisation",
|
||||||
"job-title": "Votre Fonction",
|
"job-title": "Votre Fonction",
|
||||||
@@ -24,6 +26,8 @@
|
|||||||
"Name": "Nom et Prénom",
|
"Name": "Nom et Prénom",
|
||||||
"Date": "Date"
|
"Date": "Date"
|
||||||
},
|
},
|
||||||
|
"folder": "Dossier",
|
||||||
|
"pdf": "Pdf",
|
||||||
"context-menu": {
|
"context-menu": {
|
||||||
"Download": "Télécharger",
|
"Download": "Télécharger",
|
||||||
"Rename": "Renommer",
|
"Rename": "Renommer",
|
||||||
@@ -39,23 +43,28 @@
|
|||||||
"save": "Sauvegarder",
|
"save": "Sauvegarder",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"upgrade-now": "Mettre à jour maintenant",
|
"upgrade-now": "Mettre à jour maintenant",
|
||||||
|
"contact-now": "Contacter maintenant",
|
||||||
"upgrade-to": "Mettre à niveau vers",
|
"upgrade-to": "Mettre à niveau vers",
|
||||||
"pro": "PRO",
|
|
||||||
"plan": "Offre",
|
"plan": "Offre",
|
||||||
|
"connect": "Connecter",
|
||||||
|
"connect-to-g-drive": "Se connecter à Google Drive",
|
||||||
|
"reconnect-to-g-drive": "Se reconnecter à Google Drive",
|
||||||
|
"gdrive-info-connect": "Lorsque Google Drive est connecté, le document complété sera enregistré dans le dossier {{appName}} sur Google Drive.",
|
||||||
|
"subscription-renew-warning": "Votre abonnement expirera dans {{remainingDays}} jours. Veuillez renouveler votre abonnement.",
|
||||||
"subscribe-card-teamplan": "Libérez toute la puissance de la collaboration ! Créez un nombre illimité d'organisations, d'équipes et de hiérarchies. Partagez des modèles de manière transparente entre les équipes et attribuez des rôles d'utilisateur personnalisés. Améliorez votre flux de travail dès aujourd'hui !",
|
"subscribe-card-teamplan": "Libérez toute la puissance de la collaboration ! Créez un nombre illimité d'organisations, d'équipes et de hiérarchies. Partagez des modèles de manière transparente entre les équipes et attribuez des rôles d'utilisateur personnalisés. Améliorez votre flux de travail dès aujourd'hui !",
|
||||||
"subscribe-card-plan": "Débloquez des fonctionnalités premium à partir de seulement {{premiumPrice}}/mois. Bénéficiez de performances améliorées et de seulement {{addonPrice}} par crédit supplémentaire après vos crédits premium inclus.",
|
"subscribe-card-plan": "Débloquez des fonctionnalités premium à partir de seulement {{premiumPrice}}/mois. Bénéficiez de performances améliorées et de seulement {{addonPrice}} par crédit supplémentaire après vos crédits premium inclus.",
|
||||||
"user-name-limit-char": "Pour avoir un nom d'utilisateur de moins de 8 caractères s'il vous plaît s'abonner",
|
"user-name-limit-char": "Pour avoir un nom d'utilisateur de moins de 8 caractères s'il vous plaît s'abonner",
|
||||||
"tour-content": "Ne plus afficher",
|
"tour-content": "Ne plus afficher",
|
||||||
|
"pro": "PRO",
|
||||||
"team": "Équipe",
|
"team": "Équipe",
|
||||||
"docs": "Documents",
|
"docs": "Documents",
|
||||||
"session-expired": "Votre session a expiré.",
|
"session-expired": "Votre session a expiré.",
|
||||||
"user": "Utilisateur",
|
"user": "Utilisateur",
|
||||||
"public-profile-help": "Il s'agit de votre URL publique. Copiez-le ou partagez-le avec le signataire et vous pourrez voir tous vos modèles définis publiquement.",
|
"public-profile-help": "Il s'agit de votre URL publique. Copiez-le ou partagez-le avec le signataire et vous pourrez voir tous vos modèles définis publiquement.",
|
||||||
"tagline-help": "Ce surnom apparaîtra sur votre profil public OpenSign par exemple. https://opensign.me/alex",
|
|
||||||
"welcome": "Content de te revoir!",
|
"welcome": "Content de te revoir!",
|
||||||
"Login-to-your-account": "Se connecter à son compte",
|
"Login-to-your-account": "Se connecter à son compte",
|
||||||
"password": "Mot de passe",
|
"password": "Mot de passe",
|
||||||
"forgot-password": "Mot de passe oublié?",
|
"forgot-password": "Mot de passe oublié",
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
"of": "de",
|
"of": "de",
|
||||||
"sign-SSO": "Connectez-vous avec SSO",
|
"sign-SSO": "Connectez-vous avec SSO",
|
||||||
@@ -74,26 +83,40 @@
|
|||||||
"Sign yourself": "signez vous-même",
|
"Sign yourself": "signez vous-même",
|
||||||
"Request signatures": "Demander des signatures",
|
"Request signatures": "Demander des signatures",
|
||||||
"Templates": "Modèles",
|
"Templates": "Modèles",
|
||||||
|
"Analytics": "Analytique",
|
||||||
|
"Branding": "Image de marque",
|
||||||
|
"Mail": "Courrier",
|
||||||
|
"Storage": "Stockage",
|
||||||
|
"Signing certificate": "Certificat de signature",
|
||||||
|
"Teams": "Équipes",
|
||||||
|
"General": "Général",
|
||||||
|
"Teams-Children": {
|
||||||
|
"Organizations": "Organisations",
|
||||||
|
"OrgAdmins": "OrgAdmins"
|
||||||
|
},
|
||||||
"Templates-Children": {
|
"Templates-Children": {
|
||||||
"Create template": "Créer un modèle",
|
"Create template": "Créer un modèle",
|
||||||
"Manage templates": "Gérer les modèles"
|
"Manage templates": "Gérer les modèles",
|
||||||
|
"Embedding": "Intégration"
|
||||||
},
|
},
|
||||||
"OpenSign™ Drive": "Lecteur OpenSign™",
|
"OpenSign™ Drive": "Lecteur {{appName}}",
|
||||||
"Reports": "Rapports",
|
"Reports": "Rapports",
|
||||||
"Reports-Children": {
|
"Documents": "Documents",
|
||||||
|
"Documents-Children": {
|
||||||
"Need your sign": "Besoin de votre signature",
|
"Need your sign": "Besoin de votre signature",
|
||||||
"In Progress": "En cours",
|
"In Progress": "En cours",
|
||||||
"Completed": "Complété",
|
"Completed": "Complété",
|
||||||
"Drafts": "Brouillons",
|
"Drafts": "Brouillons",
|
||||||
"Declined": "Signature refusé",
|
"Declined": "Signature refusé",
|
||||||
"Expired": "Expiré",
|
"Expired": "Expiré"
|
||||||
"Contactbook": "Carnet de contacts"
|
|
||||||
},
|
},
|
||||||
|
"Contactbook": "Carnet de contacts",
|
||||||
"Settings": "Paramètres",
|
"Settings": "Paramètres",
|
||||||
"Settings-Children": {
|
"Settings-Children": {
|
||||||
"My Signature": "Ma signature",
|
"My Signature": "Ma signature",
|
||||||
"API Token": "Jeton API",
|
"API Token": "Jeton API",
|
||||||
"Webhook": "Webhook",
|
"Webhook": "Webhook",
|
||||||
|
"Preferences": "Préférences",
|
||||||
"Teams": "Équipe",
|
"Teams": "Équipe",
|
||||||
"Team-update-successfully": "Équipe mise à jour avec succès.",
|
"Team-update-successfully": "Équipe mise à jour avec succès.",
|
||||||
"Users": "Utilisateurs"
|
"Users": "Utilisateurs"
|
||||||
@@ -136,21 +159,48 @@
|
|||||||
"Name": "Nom",
|
"Name": "Nom",
|
||||||
"Status": "Statut",
|
"Status": "Statut",
|
||||||
"created-date": "Date de création",
|
"created-date": "Date de création",
|
||||||
"Type": "Saisir"
|
"Type": "Saisir",
|
||||||
|
"Logs": "Journaux",
|
||||||
|
"Expiry-date": "Fecha de caducidad",
|
||||||
|
"Company": "Organisation",
|
||||||
|
"JobTitle": "Votre Fonction",
|
||||||
|
"Time to complete (Days)": "Temps pour terminer (jours)",
|
||||||
|
"Auto reminder": "Rappel automatique",
|
||||||
|
"Remind once in every (Days)": "Rappeler une fois tous les (jours)",
|
||||||
|
"Enable OTP verification": "Activer la vérification OTP",
|
||||||
|
"Enable Tour": "Activer la visite",
|
||||||
|
"Notify on signatures": "Notifier lors des signatures",
|
||||||
|
"Allow modifications": "Autoriser les modifications",
|
||||||
|
"Redirect url": "URL de redirection",
|
||||||
|
"Created Date": "Date de création",
|
||||||
|
"Updated Date": "Date de mise à jour",
|
||||||
|
"Expiry Date": "Date d'expiration"
|
||||||
},
|
},
|
||||||
"btnLabel": {
|
"btnLabel": {
|
||||||
"sign": "Signer",
|
"sign": "Signer",
|
||||||
|
"Sign": "SIGNER",
|
||||||
"Resend": "Relancer",
|
"Resend": "Relancer",
|
||||||
|
"Rename": "Renommer",
|
||||||
"Revoke": "Révoquer",
|
"Revoke": "Révoquer",
|
||||||
"Delete": "Supprimer",
|
"Delete": "Supprimer",
|
||||||
"Use": "Utiliser",
|
"Use": "Utiliser",
|
||||||
"Quick send": "Envoi rapide",
|
"Quick send": "Envoi rapide",
|
||||||
"Edit": "Modifier",
|
"Edit": "Modifier",
|
||||||
"Share with team": "Partager avec l'équipe",
|
"Share with team": "Partager avec l'équipe",
|
||||||
|
"Share with user": "Partager avec un collègue",
|
||||||
"Share": "Partager",
|
"Share": "Partager",
|
||||||
"View": "Voir",
|
"View": "Voir",
|
||||||
"option": "Option",
|
"option": "Option",
|
||||||
"Embed": "Intégrer"
|
"Embed": "Intégrer",
|
||||||
|
"Copy TemplateId": "Copier l'ID du modèle",
|
||||||
|
"Copy Public URL": "Copier l'URL publique",
|
||||||
|
"extend-expiry-date": "Prolonger la date d'expiration",
|
||||||
|
"Duplicate Template": "dupliquer le modèle",
|
||||||
|
"Duplicate": "Double",
|
||||||
|
"daily-mail-quota": "Quota d'e-mails quotidien",
|
||||||
|
"Save as template": "Enregistrer comme modèle",
|
||||||
|
"Fix & resend": "Corriger et renvoyer",
|
||||||
|
"Kiosk Mode": "Mode Kiosque"
|
||||||
},
|
},
|
||||||
"report-help": {
|
"report-help": {
|
||||||
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||||
@@ -162,23 +212,22 @@
|
|||||||
"Contactbook": "Il s'agit d'une liste de contacts/signataires que vous avez ajoutés. Ceux-ci apparaîtront sous forme de suggestions lorsque vous tenterez d'ajouter des signataires à un nouveau document.",
|
"Contactbook": "Il s'agit d'une liste de contacts/signataires que vous avez ajoutés. Ceux-ci apparaîtront sous forme de suggestions lorsque vous tenterez d'ajouter des signataires à un nouveau document.",
|
||||||
"Templates": "Il s'agit d'une liste de modèles à votre disposition pour créer des documents. Vous pouvez cliquer sur le bouton 'Utiliser' pour créer un nouveau document à l'aide d'un modèle, modifier le document et ajouter des signataires à l'étape suivante."
|
"Templates": "Il s'agit d'une liste de modèles à votre disposition pour créer des documents. Vous pouvez cliquer sur le bouton 'Utiliser' pour créer un nouveau document à l'aide d'un modèle, modifier le document et ajouter des signataires à l'étape suivante."
|
||||||
},
|
},
|
||||||
"form-name": {
|
|
||||||
"Sign Yourself": "signez vous-même",
|
"Sign Yourself": "signez vous-même",
|
||||||
"Request Signatures": "Demander des signatures",
|
"Request Signatures": "Demander des signatures",
|
||||||
"New Template": "Nouveau modèle"
|
"New Template": "Nouveau modèle",
|
||||||
},
|
|
||||||
"file-type": "pdf, png, jpg, jpeg",
|
"file-type": "pdf, png, jpg, jpeg",
|
||||||
"docx": "docx",
|
"docx": "docx",
|
||||||
"file-selected": "fichier sélectionné",
|
"file-selected": "fichier sélectionné",
|
||||||
"template-title": "Titre du modèle",
|
"template-title": "Titre du modèle",
|
||||||
"document-title": "Titre du document",
|
"document-title": "Titre du document",
|
||||||
|
"title": "Titre",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"time-to-complete": "Temps de réalisation (jours)",
|
"time-to-complete": "Temps de réalisation (jours)",
|
||||||
"send-in-order": "Envoyer dans l'ordre ?",
|
"send-in-order": "Envoyer dans l'ordre ?",
|
||||||
"send-in-order-help": {
|
"send-in-order-help": {
|
||||||
"p1": "Choisissez la manière dont vous souhaitez que les demandes de signature soient envoyées aux signataires du document :",
|
"p1": "Choisissez la manière dont vous souhaitez que les demandes de signature soient envoyées aux signataires du document :",
|
||||||
"p2": "La sélection de cette option enverra initialement la demande de signature au premier signataire. Une fois que le premier signataire a terminé sa partie, le prochain signataire de la séquence recevra la demande. Ce processus se poursuit jusqu'à ce que tous les signataires aient signé le document. Cette méthode garantit que le document est signé dans un ordre spécifique.",
|
"p2": "La sélection de cette option enverra initialement la demande de signature au premier signataire. Une fois que le premier signataire a terminé sa partie, le prochain signataire de la séquence recevra la demande. Ce processus se poursuit jusqu'à ce que tous les signataires aient signé le document. Cette méthode garantit que le document est signé dans un ordre spécifique.",
|
||||||
"p3": "La sélection de cette option enverra les liens de signature à tous les signataires simultanément. Chaque signataire peut signer le document à sa convenance, que d'autres signataires aient ou non complété leur signature. Cette méthode est plus rapide mais n’impose aucun ordre de signature entre les participants.",
|
"p3": "La sélection de cette option enverra les liens de signature à tous les signataires simultanément. Chaque signataire peut signer le document à sa convenance, que d'autres signataires aient ou non complété leur signature. Cette méthode est plus rapide mais n'impose aucun ordre de signature entre les participants.",
|
||||||
"p4": "Sélectionnez l'option qui correspond le mieux aux besoins de votre traitement de documents."
|
"p4": "Sélectionnez l'option qui correspond le mieux aux besoins de votre traitement de documents."
|
||||||
},
|
},
|
||||||
"no": "Non",
|
"no": "Non",
|
||||||
@@ -186,7 +235,7 @@
|
|||||||
"remind-once": "Rappeler une fois tous les (jours)",
|
"remind-once": "Rappeler une fois tous les (jours)",
|
||||||
"next": "Suivant",
|
"next": "Suivant",
|
||||||
"select-folder": "Sélectionner le dossier",
|
"select-folder": "Sélectionner le dossier",
|
||||||
"OpenSign-drive": "Lecteur OpenSign™",
|
"OpenSign-drive": "Lecteur {{appName}}",
|
||||||
"drive-document-status": {
|
"drive-document-status": {
|
||||||
"Completed": "Complété",
|
"Completed": "Complété",
|
||||||
"Declined": "Refuser",
|
"Declined": "Refuser",
|
||||||
@@ -194,7 +243,7 @@
|
|||||||
"Expired": "Expiré",
|
"Expired": "Expiré",
|
||||||
"In Progress": "En cours"
|
"In Progress": "En cours"
|
||||||
},
|
},
|
||||||
"select-folder-help": "Si vous ne sélectionnez pas de dossier, votre document signé sera enregistré dans le dossier du lecteur principal OpenSign.",
|
"select-folder-help": "Si vous ne sélectionnez pas de dossier, votre document signé sera enregistré dans le dossier du lecteur principal {{appName}}.",
|
||||||
"no-data": "Aucune données disponible",
|
"no-data": "Aucune données disponible",
|
||||||
"save-here": "Enregistrer ici",
|
"save-here": "Enregistrer ici",
|
||||||
"back": "Retour",
|
"back": "Retour",
|
||||||
@@ -204,6 +253,7 @@
|
|||||||
"create": "Créer",
|
"create": "Créer",
|
||||||
"signers": "Signataires",
|
"signers": "Signataires",
|
||||||
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
|
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
|
||||||
|
"bcc-help": "Commencez à taper le nom d'un contact pour voir les suggestions parmi vos contacts enregistrés ou en ajouter de nouveaux. Utilisez le bouton '+' pour ajouter un utilisateur et le bouton 'x' pour le supprimer. L'adresse e-mail de l'utilisateur sélectionné sera ajoutée en Bcc (copie carbone invisible). Chaque utilisateur recevra une notification par e-mail une fois le document terminé.",
|
||||||
"add-signer": "Ajouter un signataire",
|
"add-signer": "Ajouter un signataire",
|
||||||
"contact-not-found": "Contact introuvable",
|
"contact-not-found": "Contact introuvable",
|
||||||
"add-yourself": "Ajoutez-vous",
|
"add-yourself": "Ajoutez-vous",
|
||||||
@@ -218,6 +268,7 @@
|
|||||||
"API": "API",
|
"API": "API",
|
||||||
"api-token": "Jeton API",
|
"api-token": "Jeton API",
|
||||||
"regenerate-token": "Régénérer en direct jeton",
|
"regenerate-token": "Régénérer en direct jeton",
|
||||||
|
"remove-background": "Supprimer l'arrière-plan",
|
||||||
"generate-token": "Générer en direct jeton",
|
"generate-token": "Générer en direct jeton",
|
||||||
"view-docs": "Afficher les documents",
|
"view-docs": "Afficher les documents",
|
||||||
"generate-token-alert": "Êtes-vous sûr de vouloir régénérer le jeton, votre ancien jeton sera supprimer?",
|
"generate-token-alert": "Êtes-vous sûr de vouloir régénérer le jeton, votre ancien jeton sera supprimer?",
|
||||||
@@ -247,23 +298,26 @@
|
|||||||
"deactivate": "désactiver",
|
"deactivate": "désactiver",
|
||||||
"this-user": "cet utilisateur ",
|
"this-user": "cet utilisateur ",
|
||||||
"delete-user": "Supprimer l'utilisateur",
|
"delete-user": "Supprimer l'utilisateur",
|
||||||
"delete": "supprimer",
|
"delete": "Supprimer",
|
||||||
"add-user": "Ajouter un utilisateur",
|
"add-user": "Ajouter un utilisateur",
|
||||||
"password-generateed": "Le mot de passe ne sera généré qu'une seule fois ; assurez-vous de le copier.",
|
"password-generateed": "Le mot de passe ne sera généré qu'une seule fois ; assurez-vous de le copier.",
|
||||||
"Team status": "Statut de l'équipe",
|
"Team status": "Statut de l'équipe",
|
||||||
"user-deactivated": "Utilisateur désactivé.",
|
"user-deactivated": "Utilisateur désactivé.",
|
||||||
"user-activated": "Activé par l'utilisateur.",
|
"user-activated": "Activé par l'utilisateur.",
|
||||||
"public": "Public",
|
"public": "Public",
|
||||||
"download": "Téléchargement",
|
"download": "Télécharger",
|
||||||
"view": "voir",
|
"view": "voir",
|
||||||
"make-template-public": "Rendre le modèle public",
|
"make-template-public": "Rendre le modèle public",
|
||||||
"make-template-private": "Rendre le modèle privé",
|
"make-template-private": "Rendre le modèle privé",
|
||||||
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
|
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
|
||||||
|
"make-template-private-alert-non": "Êtes-vous sûr de vouloir rendre ce modèle privé ?",
|
||||||
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
|
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
|
||||||
"public-role": "Rôle public",
|
"public-role": "Rôle public",
|
||||||
"public-url": "URL publique",
|
"public-url": "Profil public",
|
||||||
"public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
|
"embed-template": "Intégrer le modèle",
|
||||||
"add-public-url-alert": "Veuillez ajouter votre URL publique et vous pourra rendre public modèle.",
|
"public-url-copy": "Voici votre URL publique : ",
|
||||||
|
"public-url-copy-mssg": "copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
|
||||||
|
"add-public-url-alert": "Vous pouvez générer votre profil public {{appName}}",
|
||||||
"share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.",
|
"share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.",
|
||||||
"share-with": "Partager avec",
|
"share-with": "Partager avec",
|
||||||
"team-not-found": "Équipe introuvable",
|
"team-not-found": "Équipe introuvable",
|
||||||
@@ -275,18 +329,19 @@
|
|||||||
"send": "envoyer",
|
"send": "envoyer",
|
||||||
"quick-send-alert-1": "Tous les rôles dans ce document sont actuellement liés à des contacts. Pour envoyer rapidement des copies de ce modèle à plusieurs signataires, veuillez vous assurer qu'au moins un rôle n'est lié à aucun contact.",
|
"quick-send-alert-1": "Tous les rôles dans ce document sont actuellement liés à des contacts. Pour envoyer rapidement des copies de ce modèle à plusieurs signataires, veuillez vous assurer qu'au moins un rôle n'est lié à aucun contact.",
|
||||||
"quick-send-alert-2": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
|
"quick-send-alert-2": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
|
||||||
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce modèle afin d'en « envoyer rapidement » des copies à plusieurs signataires.",
|
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce template.",
|
||||||
"quick-send-alert-4": "L'envoi rapide a atteint la limite.",
|
"quick-send-alert-4": "L'envoi rapide a atteint la limite.",
|
||||||
"copy-link": "Copier le lien",
|
"copy-link": "Copier le lien",
|
||||||
"copy": "Copier",
|
"copy": "Copier",
|
||||||
"revoke-document": "Révoquer le document",
|
"revoke-document": "Révoquer le document",
|
||||||
"revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?",
|
"revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?",
|
||||||
"resend-mail": "Renvoyer le courrier",
|
"resend-mail": "Renvoyer le courrier",
|
||||||
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
|
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
|
||||||
"subject": "Sujet",
|
"subject": "Sujet",
|
||||||
"body": "Corps",
|
"body": "Corps",
|
||||||
"add-contact": "Ajouter le contact",
|
"add-contact": "Ajouter le contact",
|
||||||
"add-signer-alert": "Le contact existe déjà !",
|
"edit-contact": "Modifier le contact",
|
||||||
|
"add-signer-alert": "Le contact existe déjà ! Veuillez le sélectionner dans la liste déroulante 'Signataires'",
|
||||||
"record-delete-alert": "Enregistrement supprimé avec succès !",
|
"record-delete-alert": "Enregistrement supprimé avec succès !",
|
||||||
"record-revoke-alert": " Enregistrement révoqué avec succès ! ",
|
"record-revoke-alert": " Enregistrement révoqué avec succès ! ",
|
||||||
"mail-sent-alert": "Courriel envoyé avec succès.",
|
"mail-sent-alert": "Courriel envoyé avec succès.",
|
||||||
@@ -311,7 +366,7 @@
|
|||||||
"verify-email-1": "Vérifier l'e-mail",
|
"verify-email-1": "Vérifier l'e-mail",
|
||||||
"resend": "Renvoyer",
|
"resend": "Renvoyer",
|
||||||
"contact-details": "Coordonnées",
|
"contact-details": "Coordonnées",
|
||||||
"verify-email": "Veuillez vérifier votre email!",
|
"verify-email": "Veuillez vérifier votre e-mail pour continuer. C'est une étape unique pour confirmer l'adresse e-mail associée à votre compte OpenSign.",
|
||||||
"send-otp": "envoyer un code à usage unique",
|
"send-otp": "envoyer un code à usage unique",
|
||||||
"otp-placeholder": "Entrez le code de vérification reçu par e-mail",
|
"otp-placeholder": "Entrez le code de vérification reçu par e-mail",
|
||||||
"loading-doc": "Chargement du document..",
|
"loading-doc": "Chargement du document..",
|
||||||
@@ -325,6 +380,7 @@
|
|||||||
"date": "date",
|
"date": "date",
|
||||||
"text": "texte",
|
"text": "texte",
|
||||||
"text input": "saisie de texte",
|
"text input": "saisie de texte",
|
||||||
|
"cells": "cellules",
|
||||||
"checkbox": "case à cocher",
|
"checkbox": "case à cocher",
|
||||||
"dropdown": "dérouler",
|
"dropdown": "dérouler",
|
||||||
"radio button": "bouton radio",
|
"radio button": "bouton radio",
|
||||||
@@ -341,6 +397,7 @@
|
|||||||
"certificate": "Certificat",
|
"certificate": "Certificat",
|
||||||
"decline": "refusé",
|
"decline": "refusé",
|
||||||
"finish": "terminé",
|
"finish": "terminé",
|
||||||
|
"done": "Terminé",
|
||||||
"mail": "Mail",
|
"mail": "Mail",
|
||||||
"sign-now": "Signez maintenant",
|
"sign-now": "Signez maintenant",
|
||||||
"successfully-signed": "Signé avec succès !",
|
"successfully-signed": "Signé avec succès !",
|
||||||
@@ -350,6 +407,10 @@
|
|||||||
"Email-verified-alert-1": "L'e-mail est vérifié.",
|
"Email-verified-alert-1": "L'e-mail est vérifié.",
|
||||||
"Email-verified-alert-2": "L'e-mail est déjà vérifié.",
|
"Email-verified-alert-2": "L'e-mail est déjà vérifié.",
|
||||||
"upload-stamp-image": "Télécharger l'image du tampon",
|
"upload-stamp-image": "Télécharger l'image du tampon",
|
||||||
|
"draw-signature": "Dessiner la signature",
|
||||||
|
"draw-initials": "Dessiner les initiales",
|
||||||
|
"enter-text": "Saisir du texte",
|
||||||
|
"enter-widgettype": "Saisir {{widgetType}}",
|
||||||
"draw": "Dessiner",
|
"draw": "Dessiner",
|
||||||
"type": "Taper",
|
"type": "Taper",
|
||||||
"color-type": {
|
"color-type": {
|
||||||
@@ -362,7 +423,7 @@
|
|||||||
"upload": "Télécharger",
|
"upload": "Télécharger",
|
||||||
"initial-teb": "Initiales",
|
"initial-teb": "Initiales",
|
||||||
"signature-tab": "Signature",
|
"signature-tab": "Signature",
|
||||||
"your-signature": "Votre signature",
|
"initial-alert": "Mon initiale est introuvable",
|
||||||
"copy-title": "Copier le widget vers",
|
"copy-title": "Copier le widget vers",
|
||||||
"contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?",
|
"contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?",
|
||||||
"reset-password": "Le lien de réinitialisation du mot de passe a été envoyé à votre identifiant de messagerie",
|
"reset-password": "Le lien de réinitialisation du mot de passe a été envoyé à votre identifiant de messagerie",
|
||||||
@@ -379,10 +440,12 @@
|
|||||||
"options": "Possibilités",
|
"options": "Possibilités",
|
||||||
"minimun-check": "Vérification minimale",
|
"minimun-check": "Vérification minimale",
|
||||||
"maximum-check": "Contrôle maximum",
|
"maximum-check": "Contrôle maximum",
|
||||||
|
"cell-count": "numération cellulaire",
|
||||||
"default-value": "Valeur par défaut",
|
"default-value": "Valeur par défaut",
|
||||||
"select": "Sélectionner",
|
"select": "Sélectionner",
|
||||||
"read-only": "Est en lecture seule",
|
"read-only": "Est en lecture seule",
|
||||||
"hide-labels": "Masquer les étiquettes",
|
"hide-labels": "Masquer les étiquettes",
|
||||||
|
"layout": "Disposition",
|
||||||
"checkbox": "Case à cocher",
|
"checkbox": "Case à cocher",
|
||||||
"alert": "Alerte",
|
"alert": "Alerte",
|
||||||
"zoom-in": "Agrandir",
|
"zoom-in": "Agrandir",
|
||||||
@@ -395,10 +458,9 @@
|
|||||||
"encrypted-pdf-alert": "Les fichiers PDF actuellement cryptés ne sont pas pris en charge.",
|
"encrypted-pdf-alert": "Les fichiers PDF actuellement cryptés ne sont pas pris en charge.",
|
||||||
"encrypted-pdf-alert-1": "Le mot de passe du fichier PFX n'est pas valide.",
|
"encrypted-pdf-alert-1": "Le mot de passe du fichier PFX n'est pas valide.",
|
||||||
"user-not-exist": "L'utilisateur n'existe pas",
|
"user-not-exist": "L'utilisateur n'existe pas",
|
||||||
"template-signature-field": "Veuillez ajouter au moins un champ de signature pour tous les rôles.",
|
|
||||||
"template-role-alert": "Veuillez d'abord ajouter des rôles",
|
"template-role-alert": "Veuillez d'abord ajouter des rôles",
|
||||||
"create-document": "Créer un document",
|
"create-document": "Créer un document",
|
||||||
"template-created-alert": "Voulez-vous créer un document en utilisant le modèle que vous vient de créer ?",
|
"template-created-alert": "Que voulez-vous faire avec le modèle que vous venez de créer ?",
|
||||||
"signers-alert": "alerte des signataires",
|
"signers-alert": "alerte des signataires",
|
||||||
"template-creation-alert-1": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
"template-creation-alert-1": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
||||||
"ok": "D'accord",
|
"ok": "D'accord",
|
||||||
@@ -426,10 +488,13 @@
|
|||||||
"add-recipients": "Ajouter des destinataires",
|
"add-recipients": "Ajouter des destinataires",
|
||||||
"loading-mssg": "Cela pourrait prendre du temps",
|
"loading-mssg": "Cela pourrait prendre du temps",
|
||||||
"send-mail": "Envoyer un mail",
|
"send-mail": "Envoyer un mail",
|
||||||
|
"signature-field-widget": "Chaque utilisateur doit avoir au moins un widget de signature. Vous n’avez pas ajouté de widget de signature pour {{signersName}}.",
|
||||||
"placeholder-alert-1": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
|
"placeholder-alert-1": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
|
||||||
"placeholder-alert-2": "Veuillez confirmer que vous avez rempli le champ de texte.",
|
"placeholder-alert-2": "Veuillez confirmer que vous avez rempli le champ de texte.",
|
||||||
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
|
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
|
||||||
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
|
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
|
||||||
|
"placeholder-mail-alert": "Vous avez envoyé un e-mail avec succès à {{name}}. Les signataires suivants recevront un e-mail une fois que {{name}} aura signé le document.",
|
||||||
|
"placeholder-mail-alert-you": "Les signataires suivants recevront un e-mail une fois que vous aurez signé le document.",
|
||||||
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
|
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
|
||||||
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
|
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
|
||||||
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
||||||
@@ -440,9 +505,11 @@
|
|||||||
"cutomize-email": "Personnaliser l'e-mail",
|
"cutomize-email": "Personnaliser l'e-mail",
|
||||||
"upgrade-to-customize-email": "Mise à niveau pour personnaliser l'e-mail",
|
"upgrade-to-customize-email": "Mise à niveau pour personnaliser l'e-mail",
|
||||||
"sign-url": "Signer l'URL",
|
"sign-url": "Signer l'URL",
|
||||||
"Mails Sent": "Mails envoyés avec succès !",
|
"mails-sent": "Mails envoyés avec succès !",
|
||||||
|
"mail-not-delivered": "Courrier non distribué",
|
||||||
"document-alert": "Alerte document",
|
"document-alert": "Alerte document",
|
||||||
"owner-subscription-expired": "L'abonnement du propriétaire a expiré.",
|
"owner-subscription-expired": "L'abonnement du propriétaire a expiré.",
|
||||||
|
"owner-doesnt-have-paid-plan": "Le propriétaire n'a pas de plan payant.",
|
||||||
"subscription-expired": "Abonnement expiré",
|
"subscription-expired": "Abonnement expiré",
|
||||||
"alert-message": "Message d'alerte",
|
"alert-message": "Message d'alerte",
|
||||||
"document-decline": "Document-refusé",
|
"document-decline": "Document-refusé",
|
||||||
@@ -459,12 +526,13 @@
|
|||||||
"sign-up": "S'inscrire",
|
"sign-up": "S'inscrire",
|
||||||
"folder-already-exist!": "le dossier existe déjà !",
|
"folder-already-exist!": "le dossier existe déjà !",
|
||||||
"please-select-position!": "Veuillez sélectionner un poste !",
|
"please-select-position!": "Veuillez sélectionner un poste !",
|
||||||
"default-sign-alert": "Êtes-vous sûr de vouloir vous connecter automatiquement à tous les emplacements demandés ? ",
|
"default-sign-alert": "Êtes-vous sûr de vouloir vous connecter automatiquement à tous les widgets {{widgetsType}} ?",
|
||||||
"Your-Signature": "Votre signature",
|
"Your-Signature": "Votre signature",
|
||||||
"auto-sign-all": "Tout signer automatiquement",
|
"auto-sign-all": "Tout signer automatiquement",
|
||||||
"generating-certificate": "Génération du certificat",
|
"generating-certificate": "Génération du certificat",
|
||||||
"pdf-download": "Téléchargement PDF",
|
"pdf-download": "Télécharger PDF",
|
||||||
"generate-certificate-alert": " Votre certificat d'achèvement est en cours de génération. Veuillez patienter quelques instants. Si le téléchargement ne démarre pas sous peu, cliquez à nouveau sur le bouton. ",
|
"generate-certificate-alert": " Votre certificat d'achèvement est en cours de génération. Veuillez patienter quelques instants. Si le téléchargement ne démarre pas sous peu, cliquez à nouveau sur le bouton. ",
|
||||||
|
"generate-certificate-err": "Nous rencontrons actuellement un problème lors de la génération du certificat. Veuillez réessayer plus tard.",
|
||||||
"folder-already-exist": "Le dossier existe déjà !",
|
"folder-already-exist": "Le dossier existe déjà !",
|
||||||
"folder-created-successfully": "Dossier créé avec succès !",
|
"folder-created-successfully": "Dossier créé avec succès !",
|
||||||
"fill-folder-name": "Veuillez indiquer le nom du dossier",
|
"fill-folder-name": "Veuillez indiquer le nom du dossier",
|
||||||
@@ -474,7 +542,7 @@
|
|||||||
"role-not-found": "Rôle introuvable.",
|
"role-not-found": "Rôle introuvable.",
|
||||||
"do-not-access": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
|
"do-not-access": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
|
||||||
"add-admin": "Ajouter un administrateur",
|
"add-admin": "Ajouter un administrateur",
|
||||||
"opensign-setup": "Configuration d'OpenSign",
|
"opensign-setup": "Configuration {{appName}}",
|
||||||
"join-discord": "Rejoignez notre serveur Discord",
|
"join-discord": "Rejoignez notre serveur Discord",
|
||||||
"admin-already-exist": "L'administrateur existe déjà.",
|
"admin-already-exist": "L'administrateur existe déjà.",
|
||||||
"password-update-alert-1": "Mot de passe mis à jour avec succès.",
|
"password-update-alert-1": "Mot de passe mis à jour avec succès.",
|
||||||
@@ -491,7 +559,7 @@
|
|||||||
"correct-password": "Veuillez fournir un mot de passe correct",
|
"correct-password": "Veuillez fournir un mot de passe correct",
|
||||||
"decrypting-pdf": "Décryptage du pdf, veuillez patienter...",
|
"decrypting-pdf": "Décryptage du pdf, veuillez patienter...",
|
||||||
"invalid-otp": "OTP invalide",
|
"invalid-otp": "OTP invalide",
|
||||||
"user-not-found": "Utilisateur non trouvé!",
|
"user-not-found": "Utilisateur non trouvé",
|
||||||
"enter-otp-alert": "Veuillez saisir OTP !",
|
"enter-otp-alert": "Veuillez saisir OTP !",
|
||||||
"get-verification-code": "Obtenir le code de vérification",
|
"get-verification-code": "Obtenir le code de vérification",
|
||||||
"get-verification-code-2": "Vous recevrez un code de vérification par e-mail",
|
"get-verification-code-2": "Vous recevrez un code de vérification par e-mail",
|
||||||
@@ -500,12 +568,13 @@
|
|||||||
"provide-email": "Veuillez fournir un e-mail.",
|
"provide-email": "Veuillez fournir un e-mail.",
|
||||||
"additional-info": "Information additionnelle",
|
"additional-info": "Information additionnelle",
|
||||||
"signature-saved-alert": "Signature enregistrée avec succès.",
|
"signature-saved-alert": "Signature enregistrée avec succès.",
|
||||||
|
"saved-successfully": "Enregistré avec succès.",
|
||||||
"fill-field": "Veuillez remplir ce champ",
|
"fill-field": "Veuillez remplir ce champ",
|
||||||
"error-template": "Erreur : modèle introuvable ! ",
|
"error-template": "Erreur : modèle introuvable ! ",
|
||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
"invalid-document": "Erreur : document invalide !",
|
"invalid-document": "Erreur : document invalide !",
|
||||||
"document-not-found": "Document introuvable !",
|
"document-not-found": "Document introuvable !",
|
||||||
"document-signed-alert-1": "Vous avez signé le document avec succès. Vous pouvez télécharger ou imprimer une copie du document partiellement signé. Une copie du document signé numériquement sera envoyée au propriétaire par e-mail une fois qu'il sera signé par tous les signataires.",
|
"document-signed-alert-1": "Vous avez signé le document avec succès. Vous pouvez télécharger ou imprimer une copie du document partiellement signé. Une copie du document signé numériquement et du certificat d'achèvement sera envoyée au propriétaire et à tous les signataires par e-mail une fois qu'il aura été signé par tous les signataires.",
|
||||||
"encrypted-pdf-not-support": "Les fichiers PDF actuellement cryptés ne sont pas pris en charge. ",
|
"encrypted-pdf-not-support": "Les fichiers PDF actuellement cryptés ne sont pas pris en charge. ",
|
||||||
"document-signed-alert-4": "Toutes nos félicitations! 🎉 Ce document a été signé avec succès par tous les participants !",
|
"document-signed-alert-4": "Toutes nos félicitations! 🎉 Ce document a été signé avec succès par tous les participants !",
|
||||||
"document-signed-alert-5": "Ce document a été signé par tous les signataires.",
|
"document-signed-alert-5": "Ce document a été signé par tous les signataires.",
|
||||||
@@ -530,7 +599,6 @@
|
|||||||
"filed-required-correctly": "Veuillez remplir correctement les informations requises.",
|
"filed-required-correctly": "Veuillez remplir correctement les informations requises.",
|
||||||
"admin-created": "Administrateur créé",
|
"admin-created": "Administrateur créé",
|
||||||
"invalid-masterkey": "Clé principale invalide",
|
"invalid-masterkey": "Clé principale invalide",
|
||||||
"opensign-Setup": "Configuration d'OpenSign",
|
|
||||||
"master-key": "La clef maitresse",
|
"master-key": "La clef maitresse",
|
||||||
"profile-update-alert": "Mise à jour du profil réussie.",
|
"profile-update-alert": "Mise à jour du profil réussie.",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
@@ -555,7 +623,8 @@
|
|||||||
"tour-mssg": {
|
"tour-mssg": {
|
||||||
"home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.",
|
"home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.",
|
||||||
"home-layout-2": "Pour télécharger des documents à auto-signature ou pour demander la signature d'autres personnes, sélectionnez simplement les boutons correspondants.",
|
"home-layout-2": "Pour télécharger des documents à auto-signature ou pour demander la signature d'autres personnes, sélectionnez simplement les boutons correspondants.",
|
||||||
"home-layout-3": "Vous êtes prêt à commencer à utiliser OpenSign ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.",
|
"home-layout-3": "Vous êtes prêt à commencer à utiliser {{appName}} !",
|
||||||
|
"home-layout-4": "Ajoutez-nous une étoile sur",
|
||||||
"generate-token": "Effectuez la mise à niveau maintenant pour générer un jeton API de production.",
|
"generate-token": "Effectuez la mise à niveau maintenant pour générer un jeton API de production.",
|
||||||
"opensign-drive-1": "Cliquez sur les liens du fil d'Ariane pour naviguer facilement dans la hiérarchie des dossiers et afficher les documents dans chaque dossier.",
|
"opensign-drive-1": "Cliquez sur les liens du fil d'Ariane pour naviguer facilement dans la hiérarchie des dossiers et afficher les documents dans chaque dossier.",
|
||||||
"opensign-drive-2": "Cliquez sur le bouton Ajouter pour créer un nouveau dossier ou document.",
|
"opensign-drive-2": "Cliquez sur le bouton Ajouter pour créer un nouveau dossier ou document.",
|
||||||
@@ -565,33 +634,34 @@
|
|||||||
"opensign-drive-6": "Faites un clic droit sur un document pour voir les options telles que Télécharger, Renommer, Déplacer et Supprimer. Cliquez sur le document pour l'ouvrir.",
|
"opensign-drive-6": "Faites un clic droit sur un document pour voir les options telles que Télécharger, Renommer, Déplacer et Supprimer. Cliquez sur le document pour l'ouvrir.",
|
||||||
"opensign-drive-7": "Faites un clic droit sur n'importe quel dossier pour voir les options. Choisissez 'Renommer' pour changer le nom du dossier ou cliquez sur le dossier pour naviguer dans son contenu.",
|
"opensign-drive-7": "Faites un clic droit sur n'importe quel dossier pour voir les options. Choisissez 'Renommer' pour changer le nom du dossier ou cliquez sur le dossier pour naviguer dans son contenu.",
|
||||||
"pdf-request-file-1": "Liste des signataires qui doivent encore signer le document.",
|
"pdf-request-file-1": "Liste des signataires qui doivent encore signer le document.",
|
||||||
"pdf-request-file-2": "Cliquez sur l'un des espaces réservés apparaissant sur le document pour signer. Vous verrez alors des options pour dessiner votre signature, la saisir ou télécharger une image.",
|
"pdf-request-file-2": "Cliquez sur l'un des champs figurant dans le document pour commencer. Vous pourrez ensuite remplir les informations requises.",
|
||||||
"pdf-request-file-3": "Cliquez sur les boutons Précédent, Refuser ou Terminer pour parcourir votre document. Utilisez le menu points de suspension pour des options supplémentaires, notamment le bouton Télécharger.",
|
"pdf-request-file-3": "Une fois que vous avez rempli tous les champs obligatoires, cliquez sur « Terminer ». Vous pourrez ensuite télécharger le document signé. Selon les paramètres de l'expéditeur, vous pourriez recevoir une copie du document complété avec le certificat de finalisation une fois que tous les destinataires auront signé.",
|
||||||
"pdf-request-file-4": "Liste des signataires ayant déjà signé le document.",
|
"pdf-request-file-4": "Liste des signataires ayant déjà signé le document.",
|
||||||
"pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.",
|
"pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.",
|
||||||
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.",
|
"pdf-request-file-6": "Veuillez remplir les champs à la page {{pagenumbers}}, tous mis en évidence avec la même couleur pour une identification facile.",
|
||||||
|
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un widget. Le widget apparaîtra dans la même couleur que le nom du destinataire une fois déposé sur le document.",
|
||||||
"placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.",
|
"placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.",
|
||||||
"placeholder-sign-3": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
|
"placeholder-sign-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
|
||||||
"placeholder-sign-4": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
|
"placeholder-sign-4": "Faites glisser ou cliquez sur un widget pour l'ajouter au document.",
|
||||||
"placeholder-sign-5": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
|
"placeholder-sign-5": "La zone de contenu du PDF affiche déjà les widgets existants du modèle. Pour plus de commodité, ces widgets correspondront à la couleur du nom du destinataire, ce qui les rend facilement identifiables.",
|
||||||
|
"placeholder-sign-6": "En cliquant sur 'Suivant', le document sera enregistré. À l'étape suivante, vous pouvez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
|
||||||
"report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue",
|
"report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue",
|
||||||
"redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.",
|
"redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.",
|
||||||
"bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.",
|
"bulksend": "Pour envoyer rapidement plusieurs documents en utilisant un modèle existant, saisissez simplement les adresses e-mail des destinataires et cliquez sur le bouton 'Envoi groupé'. Vous pouvez envoyer jusqu'à 50 destinataires.",
|
||||||
"option": "Ce menu révèle plus d'options telles que Modifier et Supprimer. Utilisez le bouton « Modifier » pour ajouter des rôles de signataire, modifier les champs et mettre à jour votre modèle. Les modifications s'appliqueront à tous les futurs documents créés à partir de ce modèle mais n'affecteront pas les documents existants. Utilisez le bouton Supprimer, vous pouvez supprimer le modèle ",
|
"option": "Ce menu affiche plus d'options comme Modifier, Supprimer, Renommer, Dupliquer, Partager, etc. <1>Cliquez ici</1> pour en savoir plus sur toutes les options disponibles. <3>Remarque : Les modifications apportées à un modèle existant s'appliqueront à tous les futurs documents créés à partir de ce modèle, mais n'affecteront pas les documents déjà envoyés.</3>",
|
||||||
"signyour-self-1": "Sélectionnez et faites glisser vos widgets préférés sur le PDF pour personnaliser votre document avant de le signer. Choisissez les endroits parfaits pour chaque modification afin d'adapter le document à vos besoins.",
|
"signyour-self-1": "Sélectionnez ou faites glisser vos widgets préférés sur le PDF pour personnaliser votre document avant de le signer. Choisissez les emplacements parfaits pour chaque widget afin d'adapter le document à vos besoins.",
|
||||||
"signyour-self-2": "Faites glisser et déposer n'importe où dans cette zone. Vous pourrez la redimensionner et la déplacer plus tard.",
|
"signyour-self-2": "Faites glisser et déposer n'importe où dans cette zone. Vous pourrez la redimensionner et la déplacer plus tard.",
|
||||||
"template-placeholder-1": "Cliquer sur le bouton 'Ajouter un rôle' vous permettra d'ajouter différents rôles de signataire. Vous pourrez attacher des utilisateurs à chaque rôle dans les étapes suivantes.",
|
"template-placeholder-1": "Cliquer sur le bouton 'Ajouter un rôle' vous permettra d'ajouter différents rôles de signataire. Vous pourrez attacher des utilisateurs à chaque rôle dans les étapes suivantes.",
|
||||||
"template-placeholder-2": "Une fois les rôles ajoutés, sélectionnez un rôle dans la liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra dans la même couleur que le nom du rôle une fois que vous l'aurez déposé sur le document.",
|
"template-placeholder-2": "Après avoir ajouté des rôles, choisissez-en un dans la liste pour placer un widget pour ce destinataire. Vous pouvez cliquer sur le widget ou le faire glisser sur le document. Une fois placé, le widget s'affichera dans la même couleur que le rôle sélectionné.",
|
||||||
"template-placeholder-3": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
|
"template-placeholder-3": "Cliquer sur 'Suivant' stockera le modèle actuel. Après l'enregistrement, vous serez invité à créer un nouveau document à partir de ce modèle si vous le souhaitez.",
|
||||||
"template-placeholder-4": "Faites glisser l'espace réservé pour un rôle n'importe où sur le document. N'oubliez pas qu'il apparaîtra dans la même couleur que le nom du destinataire pour une référence facile.",
|
|
||||||
"template-placeholder-5": "Cliquer sur 'Suivant' stockera le modèle actuel. Après l'enregistrement, vous serez invité à créer un nouveau document à partir de ce modèle si vous le souhaitez.",
|
|
||||||
"webhook-1": "Mettez à niveau maintenant pour définir le webhook",
|
"webhook-1": "Mettez à niveau maintenant pour définir le webhook",
|
||||||
"Need your Signature": "En cliquant sur cette carte, vous accéderez à la liste des documents en attente de révision.",
|
"Need your Signature": "En cliquant sur cette carte, vous accéderez à la liste des documents en attente de révision.",
|
||||||
"Out for signatures": "En cliquant sur cette carte, vous accéderez à une liste de documents en attente de signature.",
|
"Out for signatures": "En cliquant sur cette carte, vous accéderez à une liste de documents en attente de signature.",
|
||||||
"Recent signature requests": "Voici une liste de documents qui attendent votre signature.",
|
"Recent signature requests": "Voici une liste de documents qui attendent votre signature.",
|
||||||
"Recently sent for signatures": "Il s'agit d'une liste de documents que vous avez envoyés à d'autres parties pour signature.",
|
"Recently sent for signatures": "Il s'agit d'une liste de documents que vous avez envoyés à d'autres parties pour signature.",
|
||||||
"Drafts": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
"Drafts": "Ce sont des documents que vous avez commencés mais que vous n'avez pas finalisés pour l'envoi.",
|
||||||
"public-template": "Cette vidéo montre comment configurer votre profil public personnalisé, tel que 'https://opensign.me/your-username'. Vous apprendrez également comment personnaliser votre slogan et rendre vos modèles disponibles pour la signature publique."
|
"public-template": "Cette vidéo montre comment configurer votre profil public personnalisé, tel que 'https://opensign.me/your-username'. Vous apprendrez également comment personnaliser votre slogan et rendre vos modèles disponibles pour la signature publique.",
|
||||||
|
"allowModify-widgets": "Vous pouvez faire glisser et déposer n'importe lequel de ces champs sur le document, en plus des champs déjà désignés pour vous par le créateur du document."
|
||||||
},
|
},
|
||||||
"enter-email-plaholder": "Ajoutez une adresse email et appuyez sur Entrée",
|
"enter-email-plaholder": "Ajoutez une adresse email et appuyez sur Entrée",
|
||||||
"success-email-alert": "E-mail envoyé avec succès!",
|
"success-email-alert": "E-mail envoyé avec succès!",
|
||||||
@@ -624,15 +694,31 @@
|
|||||||
"bulk-send-subcription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
|
"bulk-send-subcription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
|
||||||
"generate-test-token": "Générer jeton de test",
|
"generate-test-token": "Générer jeton de test",
|
||||||
"regenerate-test-token": "Régénérer le jeton de test",
|
"regenerate-test-token": "Régénérer le jeton de test",
|
||||||
"help-test-token":"Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à l’un de nos forfaits payants pour générer un jeton de production.",
|
"help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à l'un de nos forfaits payants pour générer un jeton de production.",
|
||||||
"help-api-token": "Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
|
"help-api-token": "Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
|
||||||
"reason": "Raison",
|
"reason": "Raison",
|
||||||
"decline-by": "Refusé/révoqué par",
|
"decline-by": "Refusé/révoqué par",
|
||||||
"document-declined": "document refusé",
|
"document-declined": "document refusé",
|
||||||
"quota-mail-info-head":"Limite mensuelle d'e-mails de signatures de demandes",
|
"public-template-mssg-1": "Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
|
||||||
"quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.",
|
"public-template-mssg-2": "Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
|
||||||
|
"public-template-mssg-3": "Besoin de plus de détails ou d'exemples ?",
|
||||||
|
"public-template-mssg-4": "Visitez le ",
|
||||||
|
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
|
||||||
|
"public-template-mssg-6": "Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
|
||||||
|
"public-template-mssg-7": "Avant de pouvoir générer un lien public, vous devez rendre ce modèle public.",
|
||||||
|
"here": "ici",
|
||||||
|
"copy-code": "COPIE",
|
||||||
|
"copied-code": "COPIÉ",
|
||||||
|
"Installation": "Installation",
|
||||||
|
"Usage": "Usage",
|
||||||
|
"insufficient-credits": "Crédits de signature insuffisants",
|
||||||
|
"insufficient-credits-mssg": "Le propriétaire de ce document ne dispose actuellement pas des crédits OpenSign nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
|
||||||
|
"angular-npm-mssg-1": "Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
|
||||||
|
"quota-mail-info-head": "Limite mensuelle d'e-mails",
|
||||||
|
"quota-mail-info": "Pour maintenir la qualité du service et prévenir le spam, OpenSign permet jusqu'à 15 e-mails par mois avec le plan gratuit. Passez à l'offre supérieure pour un envoi illimité d'e-mails.",
|
||||||
|
"quota-mail-reset": "Les crédits de votre email de demande de signature seront réinitialisés le",
|
||||||
"quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.",
|
"quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.",
|
||||||
"quota-mail-tip-tip":"Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.",
|
"quota-mail-tip": "Astuce: Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement le lien de demande de signature.",
|
||||||
"quota-mail-head": "Quota atteint",
|
"quota-mail-head": "Quota atteint",
|
||||||
"unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
|
"unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
|
||||||
"sent-this-month": "envoyé ce mois-ci",
|
"sent-this-month": "envoyé ce mois-ci",
|
||||||
@@ -646,6 +732,490 @@
|
|||||||
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
|
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
|
||||||
},
|
},
|
||||||
"advanced-options": "Options avancées",
|
"advanced-options": "Options avancées",
|
||||||
"hide-advanced-options": "Masquer les options avancées"
|
"hide-advanced-options": "Masquer les options avancées",
|
||||||
|
"download-files": "Télécharger des fichiers",
|
||||||
|
"download-pdf": "Télécharger le PDF",
|
||||||
|
"pdf-certificate": "Télécharger Pdf + Certificat",
|
||||||
|
"document-logs": "Journaux de documents",
|
||||||
|
"server-down": "Impossible de se connecter au serveur {{appName}}. Si vous hébergez vous-même {{appName}}, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez {{appName}} localement, vous y accédez peut-être via un numéro de port incorrect.",
|
||||||
|
"admin-exists": "L'administrateur existe déjà. Veuillez vous connecter à l'application en utilisant les informations d'identification de l'administrateur afin de gérer les utilisateurs.",
|
||||||
|
"enable-tour": "Activer la visite",
|
||||||
|
"istourenabled-otp-help": {
|
||||||
|
"p1": "Cela permettra une visite guidée pour les signataires, fournissant des instructions pendant le processus de signature.",
|
||||||
|
"p2": "Cela désactivera la visite guidée, garantissant ainsi une expérience de signature plus rapide et ininterrompue.",
|
||||||
|
"p3": "Conseil: si vos signataires connaissent {{appName}} et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
|
||||||
|
},
|
||||||
|
"form-title-1": "Paramètre du flux de documents",
|
||||||
|
"form-title-2": "Paramètre de sécurité",
|
||||||
|
"public-tour-message": "Le modèle doit être public avant que vous puissiez générer un lien partageable.",
|
||||||
|
"add-user-template": "Vous devez ajouter un rôle avant de pouvoir lui ajouter des champs. ",
|
||||||
|
"pdf-uncompatible": "PDF n'est pas compatible, veuillez contacter {{appName}}",
|
||||||
|
"text-field-tour": "Les champs 'Pré-remplissage' doivent être remplis avant l'envoi du document. Si vous avez besoin de l'intervention des signataires, utilisez plutôt les champs réservés aux signataires.",
|
||||||
|
"attach-signer-tour": "Vous devez associer un signataire à chaque rôle. Vous pouvez le faire en cliquant sur cette icône. Une fois que vous avez sélectionné un signataire, il sera attaché à tous les champs associés à ce rôle qui apparaissent dans la même couleur.",
|
||||||
|
"allowed-signature-types": "Types de signature autorisés",
|
||||||
|
"at-least-one-signature-type": "Au moins un type de signature doit être activé.",
|
||||||
|
"expect-default-one-signature-type": "Veuillez activer un type de signature supplémentaire en plus de celui par défaut.",
|
||||||
|
"add-signer-note": "Remarque - Ce contact sera enregistré dans vos contacts",
|
||||||
|
"allowed-signature-types-help": {
|
||||||
|
"p1": "Cette préférence de signature contrôle les options de signature disponibles pour vos signataires. Seuls les types de signature que vous sélectionnez apparaîtront dans le widget de signature lorsqu'un document est signé. Par exemple, si vous choisissez uniquement l'option 'Draw' dans les préférences, votre signataire ne verra que l'option 'Draw' dans le widget de signature, tandis que les trois autres options ne seront pas disponibles.",
|
||||||
|
"l1": "Créez une signature manuscrite directement sur l'écran à l'aide de votre doigt ou d'un stylet.",
|
||||||
|
"l2": "Entrez votre nom et choisissez parmi différents styles de police pour créer une signature.",
|
||||||
|
"l3": "Téléchargez une version numérique existante de votre signature.",
|
||||||
|
"l4": "Utilisez un style de signature prédéfini enregistré sous 'Ma signature' dans vos paramètres."
|
||||||
|
},
|
||||||
|
"notify-on-signatures": "Notifier les signatures",
|
||||||
|
"notify-on-signatures-help": {
|
||||||
|
"p1": "Le créateur du document recevra une notification par e-mail chaque fois qu'un signataire signera le document.",
|
||||||
|
"note": "Remarque: Quel que soit ce paramètre, un e-mail d'achèvement contenant le document signé et le certificat d'achèvement en pièce jointe est toujours envoyé à tous les signataires et au propriétaire du document."
|
||||||
|
},
|
||||||
|
"expiry-date": "Fecha de caducidad",
|
||||||
|
"expiry-date-updated": "La date d'expiration a été prolongée avec succès jusqu'au {{newexpirydate}}",
|
||||||
|
"expiry-date-error": "Veuillez fournir une nouvelle date d'expiration postérieure à la date actuelle",
|
||||||
|
"duplicate-template-alert": "Êtes-vous sûr de vouloir créer une copie de ce modèle ?",
|
||||||
|
"duplicate-template-created": "Modèle en double créé avec succès.",
|
||||||
|
"delete-page": "supprimer la page",
|
||||||
|
"merge-pdf": "Fusionner le pdf",
|
||||||
|
"add-pages": "Ajouter des pages",
|
||||||
|
"reorder-pages": "Réorganiser les pages",
|
||||||
|
"delete-alert": "Impossible de supprimer une seule page",
|
||||||
|
"delete-alert-2": "Etes-vous sûr de vouloir supprimer cette page ?",
|
||||||
|
"delete-note": "Remarque : une fois cette page supprimée, vous ne pouvez plus l'annuler",
|
||||||
|
"Rotation-alert": "Faire pivoter la page",
|
||||||
|
"bulk-import": "Importation en masse",
|
||||||
|
"contacts-file": "Fichier de contacts (xlsx, csv)",
|
||||||
|
"import-guideline": "Téléchargez un fichier CSV ou Excel avec les colonnes Name, Email et éventuellement Phone. Seuls les 100 premiers contacts seront importés.",
|
||||||
|
"download-sample": "Télécharger un fichier d'exemple",
|
||||||
|
"100-records-only": "Actuellement, vous ne pouvez importer que 100 enregistrements.",
|
||||||
|
"csv-excel-support-only": "Veuillez télécharger un fichier dans l'un des formats suivants : CSV, XLSX ou XLS.",
|
||||||
|
"contact-imported": "{{imported}} contacts ont été importés. {{failed}} contacts n'ont pas pu être importés.",
|
||||||
|
"invalid-data": "Données invalides",
|
||||||
|
"delete-folder-alert": "Êtes-vous sûr de vouloir supprimer ce Dossier ?",
|
||||||
|
"delete-folder-alert-1": "Impossible de supprimer : le dossier doit être vide.",
|
||||||
|
"defaultSign-alert": "Les widgets {{widgetsType}} ne sont pas disponibles pour la signature automatique.",
|
||||||
|
"embed-heading": "Intégrer des modèles {{appName}}",
|
||||||
|
"public-template-edit-title": "Modifier le modèle public",
|
||||||
|
"public-template-edit-mssg": "Vous ne pouvez pas modifier directement un modèle public. Pour apporter des modifications, le modèle sera défini comme privé et vous devrez ensuite le rendre à nouveau public. Voulez-vous continuer ?",
|
||||||
|
"agree-p1": "Je confirme avoir lu et compris les ",
|
||||||
|
"agree-p2": "Divulgation des enregistrements électroniques et des signatures",
|
||||||
|
"agree-p3": "et consentez à l'utilisation d'enregistrements et de signatures électroniques.",
|
||||||
|
"agrre-button": "Je confirme et j'accepte de continuer",
|
||||||
|
"term-cond-title": "Termes et conditions",
|
||||||
|
"term-cond-h": "DIVULGATION D'ENREGISTREMENT ÉLECTRONIQUE ET DE SIGNATURE",
|
||||||
|
"term-cond-p1": "Cette divulgation d'enregistrement électronique et de signature (' Divulgation ') est un accord entre le créateur du document (' Expéditeur ') et le signataire (' Vous '), facilité via la plateforme {{appName}} ( Plateforme ). En signant des documents via {{appName}}, vous acceptez les conditions décrites dans cette divulgation. Veuillez la lire attentivement avant de continuer.",
|
||||||
|
"term-cond-h1": "1. Objet et portée",
|
||||||
|
"term-cond-p2": "Cette divulgation régit l'utilisation d'enregistrements et de signatures électroniques pour les documents envoyés par l'expéditeur et signés par vous via la plateforme {{appName}}. En acceptant, vous reconnaissez que :",
|
||||||
|
"term-cond-p3": "Vous recevrez et signerez des documents électroniquement via {{appName}}.",
|
||||||
|
"term-cond-p4": "Votre signature électronique est juridiquement contraignante et équivaut à une signature manuscrite.",
|
||||||
|
"js-snippet-msg-2": "Gérer les modèles",
|
||||||
|
"js-snippet-msg-3": "page.",
|
||||||
|
"term-cond-h2": "2. Consentement à l'utilisation d'enregistrements et de signatures électroniques",
|
||||||
|
"term-cond-p5": "En acceptant cette divulgation:",
|
||||||
|
"term-cond-p6": "Vous consentez à effectuer des transactions électroniques avec l'expéditeur en utilisant {{appName}} et comprenez que ce consentement est valable jusqu'à son retrait.",
|
||||||
|
"term-cond-p7": "Vous acceptez de consulter, de signer et de renvoyer des documents par voie électronique à l'aide d'{{appName}}.",
|
||||||
|
"term-cond-p8": "Ce consentement s'applique uniquement au(x) document(s) spécifique(s) fourni(s) par l'Expéditeur.",
|
||||||
|
"term-cond-h3": "3. Droit de retirer le consentement",
|
||||||
|
"term-cond-p9": "Vous avez le droit de retirer votre consentement à l'utilisation d'enregistrements et de signatures électroniques en :",
|
||||||
|
"term-cond-p10": "Contacter directement l'expéditeur en utilisant les coordonnées fournies dans sa communication.",
|
||||||
|
"term-cond-p11": "Notifier l'expéditeur avant de terminer le processus de signature.",
|
||||||
|
"term-cond-h4": "4. Configuration matérielle et logicielle requise",
|
||||||
|
"term-cond-p12": "Pour signer des documents avec {{appName}}, vous devez avoir:",
|
||||||
|
"term-cond-p13": "Un appareil avec accès à Internet.",
|
||||||
|
"term-cond-p14": "Un navigateur Web compatible avec JavaScript activé.",
|
||||||
|
"term-cond-p15": "Un compte email pour recevoir des notifications et des documents signés.",
|
||||||
|
"term-cond-p16": "Si ces exigences changent, l'expéditeur ou {{appName}} vous informera des mises à jour.",
|
||||||
|
"term-cond-h5": "5. Conservation des dossiers et demande de copies papier",
|
||||||
|
"term-cond-p17": "Vous êtes responsable de vous assurer que vous pouvez accéder et conserver le(s) document(s) signé(s) électroniquement. {{appName}} fournit des outils pour télécharger ou imprimer le document signé immédiatement une fois le processus de signature terminé.",
|
||||||
|
"term-cond-p18": "Si vous préférez une copie papier de tout document signé électroniquement, vous pouvez la demander directement à l'expéditeur. L'expéditeur peut facturer des frais raisonnables pour la fourniture de copies papier, sauf interdiction par la loi applicable.",
|
||||||
|
"term-cond-h6": "6. Reconnaissance et accord",
|
||||||
|
"term-cond-p19": "En procédant, Vous :",
|
||||||
|
"term-cond-p20": "Confirmez que vous pouvez accéder et consulter les documents électroniques comme décrit dans cette divulgation.",
|
||||||
|
"term-cond-p21": "Accepter d'utiliser des enregistrements et des signatures électroniques pour la ou les transactions spécifiées avec l'expéditeur.",
|
||||||
|
"term-cond-p22": "Comprenez qu'{{appName}} est une plateforme facilitant la transaction et n'est pas partie à l'accord.",
|
||||||
|
"term-cond-h7": "7. Effet juridique",
|
||||||
|
"term-cond-p23": "Votre signature électronique facilitée grâce à {{appName}}:",
|
||||||
|
"term-cond-p24": "Conforme aux lois applicables sur les signatures électroniques, y compris, mais sans s'y limiter, la loi E-SIGN aux États-Unis, le règlement eIDAS de l'UE et la loi indienne sur les technologies de l'information.",
|
||||||
|
"term-cond-p25": "Est juridiquement contraignant entre vous et l'expéditeur pour le(s) document(s) signé(s).",
|
||||||
|
"term-cond-h8": "8. Rôle de la plateforme et limitation de responsabilité",
|
||||||
|
"term-cond-p26": "{{appName}} sert de plateforme pour faciliter les transactions électroniques. Il n'est pas responsable du contenu, de la validité ou du caractère exécutoire des documents envoyés par l'expéditeur. Tout litige ou problème lié au document ou à sa signature doit être résolu directement entre vous et l'expéditeur. Expéditeur.",
|
||||||
|
"term-cond-h9": "9. Résiliation du Contrat",
|
||||||
|
"term-cond-p27": "Votre consentement s'applique uniquement à la transaction en cours, sauf indication contraire de la part de l'expéditeur. {{appName}} se réserve le droit de mettre fin à l'accès à sa plateforme en cas de violation de ses conditions d'utilisation ou de ses politiques.",
|
||||||
|
"term-cond-p28": "En sélectionnant J'accepte, vous confirmez que vous comprenez et acceptez les termes de cette divulgation d'enregistrement électronique et de signature et consentez à signer électroniquement le(s) document(s) fourni(s) par l'expéditeur via {{appName}}.",
|
||||||
|
"term-cond-p29": "Si vous avez des questions sur cette divulgation, contactez directement l'expéditeur. Pour obtenir une assistance technique avec {{appName}}, visitez ",
|
||||||
|
"term-cond-p30": " ou par email",
|
||||||
|
"js-snippet-msg": "Pour intégrer des modèles {{appName}} dans vos sites Web ou pages de destination HTML, vous pouvez utiliser le code ci-dessous:",
|
||||||
|
"js-snippet-msg-1": "Vous pouvez obtenir le TemplateId à partir de la page Gérer les modèles.",
|
||||||
|
"agrrement-alert": "Pour procéder, vous devez consentir à la divulgation des enregistrements et signatures électroniques.",
|
||||||
|
"webhook-already-exists": "L'URL existe déjà ! Veuillez en essayer un autre.",
|
||||||
|
"webhook-must-be-secure": "L'URL du webhook doit être sécurisée et utiliser https://",
|
||||||
|
"provide-test-token": "Veuillez générer un jeton de test API",
|
||||||
|
"invalid-redirect-url": "Veuillez fournir une URL de redirection valide.",
|
||||||
|
"auto-sign-mssg": "Tout signer automatiquement",
|
||||||
|
"already-exist-signer": "Le signataire sélectionné est déjà présent dans la liste des destinataires",
|
||||||
|
"allow-modify": "Autoriser les modifications",
|
||||||
|
"allow-modify-help": {
|
||||||
|
"p1": "Souhaitez-vous autoriser les modifications du document pendant le processus de signature ?",
|
||||||
|
"p2": "Sélectionner cette option permet aux signataires d'ajouter des éléments supplémentaires, tels que des signatures, des initiales, des tampons ou du texte, par-dessus les widgets existants.",
|
||||||
|
"p3": "Sélectionner cette option empêche les signataires d'apporter des ajouts au document pendant le processus de signature."
|
||||||
|
},
|
||||||
|
"signature-setting": "Paramètres de signature",
|
||||||
|
"change-signer": "Changer de signataire",
|
||||||
|
"signyour-self-button": "Utilisez cette option pour signer le document vous-même sans en ajouter d'autres.",
|
||||||
|
"requestsign-button": "Utilisez cette option pour demander les signatures des autres et de vous-même.",
|
||||||
|
"signyour-self-description": "Utilisez ce formulaire pour signer le document vous-même sans en ajouter d'autres.",
|
||||||
|
"requestsign-description": "Utilisez ce formulaire pour demander les signatures des autres et de vous-même.",
|
||||||
|
"template-form-description": "Utilisez ce formulaire pour créer des modèles réutilisables pour vos documents.",
|
||||||
|
"activation-key": "Clé d'activation",
|
||||||
|
"paid-selfhost-help": "Veuillez contacter {{appName}} pour plus d'informations à l'adresse ",
|
||||||
|
"remaining-credits-selfhost-help": "Utilisez des crédits premium pour la signature de documents via l'API, l'envoi en masse et l'intégration d'{{appName}} sur votre site web. Vous avez {{allowedcredits}} crédits inclus et {{addoncredits}} crédits supplémentaires achetés restants.",
|
||||||
|
"signature-type": "Votre signature",
|
||||||
|
"initial-type": "Vos initiales",
|
||||||
|
"redirect-url": "URL de redirection",
|
||||||
|
"bulk-send": "Envoi groupé",
|
||||||
|
"select-timezone": "Fuseau horaire",
|
||||||
|
"current-time": "Heure actuelle",
|
||||||
|
"email-help": "Vous n'êtes pas autorisé à changer d'adresse e-mail pour des raisons de sécurité. Veuillez créer un autre compte gratuit en utilisant la nouvelle adresse e-mail.",
|
||||||
|
"doc-sent": "Document envoyé avec succès.",
|
||||||
|
"no-signer": "Supprimer",
|
||||||
|
"and": "et",
|
||||||
|
"make-public": "Rendre public",
|
||||||
|
"general": "Général",
|
||||||
|
"request-email": "E-mail de demande",
|
||||||
|
"completion-email": "E-mail de finalisation",
|
||||||
|
"variables-use": "Variables à utiliser",
|
||||||
|
"send-to-sign": "vous a demandé de signer",
|
||||||
|
"use-template": "Utiliser le modèle",
|
||||||
|
"contact-already-exists": "Le contact existe déjà.",
|
||||||
|
"docx-error": "Nous rencontrons actuellement des problèmes avec le traitement des fichiers DOCX. Veuillez télécharger le fichier PDF",
|
||||||
|
"docx-error-contact": "ou contactez-nous à support@opensignlabs.com",
|
||||||
|
"agreement-note": "Remarque : Accepter cela ne signifie pas que vous signez immédiatement le document. Cela vous permet uniquement de consulter le document électroniquement. Vous aurez l'opportunité de le lire entièrement et de décider ensuite si vous souhaitez le signer.",
|
||||||
|
"draft-template-info-p1": "Pour rendre votre modèle public, il doit contenir un seul rôle ou, s'il inclut plusieurs rôles, tous les rôles supplémentaires doivent déjà être attribués aux signataires. Le rôle public non attribué doit rester vide et être placé en première position.",
|
||||||
|
"visit-below-link": "Visitez le lien ci-dessous pour en savoir plus -",
|
||||||
|
"storage-help": "Activer BYOC vous permet de connecter votre propre stockage S3 afin que vos fichiers restent entièrement sous votre contrôle, sans copie externe conservée. Si l'autonomie des données est importante pour vous, envisagez de passer à l'offre Teams pour débloquer cette fonctionnalité.",
|
||||||
|
"daily-quota-reached": "Vous avez atteint votre quota quotidien. Pour obtenir de l'aide, veuillez contacter quotas@opensignlabs.com.",
|
||||||
|
"enabled-signature-type": "Types de signature activés",
|
||||||
|
"enabled-signature-type-help": "Le paramètre 'Types de signature activés' détermine quelles options de signature sont disponibles dans votre organisation. Par exemple, si vous désactivez l'option 'Dessiner', les membres de votre organisation ne la verront pas dans le widget de signature, tandis que les trois autres options resteront accessibles.",
|
||||||
|
"indexing-public-profile": "Autoriser l'indexation du profil public par les moteurs de recherche",
|
||||||
|
"user-created-successfully": "Utilisateur créé avec succès.",
|
||||||
|
"only-15-reminder-allowed": "Vous pouvez définir jusqu'à 15 rappels automatiques. Par exemple, si 'TimeToComplete' est de 15 jours et 'RemindOnceInEvery' est de 1 jour, vous atteindrez la limite maximale de 15 rappels. Ajustez vos paramètres en conséquence.",
|
||||||
|
"rate-your-experience": "Comment s'est passée votre expérience avec {{appName}} ?",
|
||||||
|
"thanks-for-feedback": "Merci pour votre retour 🙏",
|
||||||
|
"share-your-feedback": "Partagez votre avis",
|
||||||
|
"share-your-review": "Partagez votre avis",
|
||||||
|
"date-format": "Format de date",
|
||||||
|
"document-deleted": "Le document a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
|
||||||
|
"save-as-template-?": "Êtes-vous sûr de vouloir enregistrer ce document comme modèle ?",
|
||||||
|
"go-to-manage-templates": "Aller à 'Gérer les modèles'",
|
||||||
|
"template-created": "Modèle créé",
|
||||||
|
"how-would-you-like-to-proceed?": "Comment souhaitez-vous procéder ?",
|
||||||
|
"failed-to-load-refresh-page": "Échec du chargement du document. Veuillez essayer d'actualiser cette page.",
|
||||||
|
"document-has-been-signed": "Le document a été signé avec succès !",
|
||||||
|
"document-has-been-signed-by-you": "Le document a été signé avec succès par vous !",
|
||||||
|
"participant-completed-signing": "Tous les participants ont terminé le processus de signature.",
|
||||||
|
"you-will-receive-email-shortly": "✅ Voilà, c'est fait ! Vous recevrez un e-mail de confirmation sous peu.",
|
||||||
|
"please-provide-templateid": "Veuillez fournir templateid",
|
||||||
|
"this-template-is-not-public": "Ce template n'est pas public",
|
||||||
|
"invalid-templateid": "templateid invalide",
|
||||||
|
"contact-billing-at-opensign": "Pour ajouter plus de places, veuillez contacter OpenSign™ à l'adresse <1>billing@opensignlabs.com</1> pour obtenir de l'aide.",
|
||||||
|
"title-length-alert": "Le titre doit comporter au maximum 250 caractères.",
|
||||||
|
"note-length-alert": "La note doit comporter au maximum 200 caractères.",
|
||||||
|
"description-length-alert": "La description doit comporter au maximum 500 caractères",
|
||||||
|
"fix-&-resend-document": "Corriger et renvoyer le document",
|
||||||
|
"do-you-want-recreate-document?": "Cela créera un brouillon à partir de ce document avec tous les champs intacts. Êtes-vous sûr de vouloir recréer ce document ?",
|
||||||
|
"start-editing": "Commencer l'édition",
|
||||||
|
"unsaved-changes-discard-them?": "Vous avez des modifications non enregistrées. Les supprimer ?",
|
||||||
|
"yes-discard": "Oui, supprimer",
|
||||||
|
"LTV-enabled-signatures": "Signatures avec LTV activée",
|
||||||
|
"BETA": "BETA",
|
||||||
|
"two-factor-authentication": "Authentification à deux facteurs",
|
||||||
|
"2fa-help-text": "L'authentification à deux facteurs ajoute une couche de sécurité supplémentaire à votre compte en exigeant plus qu'un simple mot de passe pour se connecter.",
|
||||||
|
"2fa-help-bullet1": "Renforce la sécurité en demandant à la fois votre mot de passe et un code de vérification.",
|
||||||
|
"2fa-help-bullet2": "Le code de vérification est généré par une application d'authentification sur votre appareil.",
|
||||||
|
"2fa-help-bullet3": "Protège votre compte même si votre mot de passe est compromis.",
|
||||||
|
"setup-2fa": "Configurer 2FA",
|
||||||
|
"setup-2fa-again": "Reconfigurer 2FA",
|
||||||
|
"2fa-setup-intro": "Protégez votre compte avec l'authentification à deux facteurs. Une fois activée, vous devrez saisir un code provenant de votre application d'authentification à chaque connexion.",
|
||||||
|
"scan-qr-code": "Scanner le code QR",
|
||||||
|
"scan-qr-instructions": "Utilisez une application d'authentification comme Google Authenticator, Microsoft Authenticator ou Authy pour scanner ce code QR.",
|
||||||
|
"manual-setup-instructions": "Vous ne pouvez pas scanner le code ? Configurez manuellement votre application d'authentification avec cette clé secrète :",
|
||||||
|
"secret-key": "Clé secrète",
|
||||||
|
"copied-to-clipboard": "Copié dans le presse-papiers",
|
||||||
|
"copy-to-clipboard": "Copier dans le presse-papiers",
|
||||||
|
"recovery-codes": "Codes de récupération",
|
||||||
|
"recovery-codes-instructions": "Enregistrez ces codes de récupération dans un endroit sûr. Si vous perdez l'accès à votre application d'authentification, vous pourrez utiliser l'un de ces codes à usage unique pour vous connecter.",
|
||||||
|
"download-recovery-codes": "Télécharger les codes de récupération",
|
||||||
|
"verification-code": "Code de vérification",
|
||||||
|
"enter-code-from-authenticator-app": "Saisissez le code à 6 chiffres de votre application d'authentification",
|
||||||
|
"verification-code-required": "Le code de vérification est requis",
|
||||||
|
"verification-code-invalid": "Code de vérification invalide. Veuillez réessayer.",
|
||||||
|
"2fa-enabled": "2FA activé",
|
||||||
|
"2fa-enabled-successfully": "Authentification à deux facteurs activée avec succès",
|
||||||
|
"2fa-setup-complete": "Configuration terminée !",
|
||||||
|
"2fa-setup-complete-instructions": "Votre compte est maintenant protégé par l'authentification à deux facteurs. Vous devrez entrer un code de vérification à chaque connexion.",
|
||||||
|
"two-factor-verification": "Vérification en deux étapes",
|
||||||
|
"enter-verification-code-instructions": "Saisissez le code de vérification à 6 chiffres de votre application d'authentification pour continuer.",
|
||||||
|
"recovery-code": "Code de récupération",
|
||||||
|
"enter-recovery-code-help": "Saisissez l'un de vos codes de récupération",
|
||||||
|
"recovery-code-required": "Le code de récupération est requis",
|
||||||
|
"use-verification-code-instead": "Utiliser le code de vérification à la place",
|
||||||
|
"use-recovery-code-instead": "Utiliser le code de récupération à la place",
|
||||||
|
"regenerate-2fa-remove-existing": "Êtes-vous sûr de vouloir régénérer l'authentification à deux facteurs ? Cette action supprimera vos paramètres d'authentification existants.",
|
||||||
|
"use-passkey": "Se connecter avec une clé d'accès",
|
||||||
|
"security-section": "Sécurité",
|
||||||
|
"passkey-authentication": "Authentification par clé d'accès",
|
||||||
|
"passkey-not-supported": "Votre navigateur ou appareil ne prend pas en charge l'authentification par clé d'accès",
|
||||||
|
"passkey-description": "Les clés d'accès offrent une alternative plus sécurisée et résistante au phishing que les mots de passe. Vous pouvez utiliser votre empreinte digitale, reconnaissance faciale ou code PIN de l'appareil pour vous connecter en toute sécurité.",
|
||||||
|
"passkey-tooltip": "Les clés d'accès sont une alternative plus simple et plus sécurisée aux mots de passe. Elles utilisent des données biométriques comme les empreintes digitales ou la reconnaissance faciale déjà stockées sur votre appareil.",
|
||||||
|
"security-section-help": "Gérez les options de sécurité, y compris les clés d'accès et les méthodes d'authentification pour protéger votre compte.",
|
||||||
|
"passkey-register": "Enregistrer une clé d'accès",
|
||||||
|
"passkey-register-another": "Enregistrer une autre clé d'accès",
|
||||||
|
"passkey-registered": "Clé d'accès enregistrée",
|
||||||
|
"passkey-registering": "Enregistrement de la clé d'accès...",
|
||||||
|
"passkey-registered-success": "Clé d'accès enregistrée avec succès !",
|
||||||
|
"passkey-registration-failed": "Échec de l'enregistrement de la clé d'accès",
|
||||||
|
"passkey-auth-failed": "Échec de l'authentification avec la clé d'accès",
|
||||||
|
"passkey-missing-user-info": "Informations utilisateur requises",
|
||||||
|
"passkeys-list": "Vos clés d'accès",
|
||||||
|
"default-passkey": "Votre clé d'accès",
|
||||||
|
"passkey-rename": "Renommer",
|
||||||
|
"passkey-delete": "Supprimer",
|
||||||
|
"passkey-rename-title": "Renommer la clé d'accès",
|
||||||
|
"passkey-delete-title": "Supprimer la clé d'accès",
|
||||||
|
"passkey-delete-confirm": "Êtes-vous sûr de vouloir supprimer la clé d'accès « {{name}} » ?",
|
||||||
|
"passkey-name": "Nom de la clé d'accès",
|
||||||
|
"passkey-name-placeholder": "Entrez un nom descriptif pour cette clé d'accès",
|
||||||
|
"passkey-renamed-success": "Clé d'accès renommée avec succès",
|
||||||
|
"passkey-deleted-success": "Clé d'accès supprimée avec succès",
|
||||||
|
"passkey-rename-failed": "Échec du renommage de la clé d'accès",
|
||||||
|
"passkey-delete-failed": "Échec de la suppression de la clé d'accès",
|
||||||
|
"processing": "Traitement en cours...",
|
||||||
|
"today": "Aujourd'hui",
|
||||||
|
"yesterday": "Hier",
|
||||||
|
"days-ago": "Il y a {{days}} jours",
|
||||||
|
"verify-with-passkey": "Vérifier avec une passkey",
|
||||||
|
"verify-with-otp": "Vérifier avec OTP",
|
||||||
|
"verify-identity": "Vérifier l'identité",
|
||||||
|
"verify-account": "Vérifiez votre identité",
|
||||||
|
"verification": "Vérification",
|
||||||
|
"passkey-verification-failed": "La vérification par passkey a échoué",
|
||||||
|
"security-auth-help": {
|
||||||
|
"p1": "Gérez les paramètres de sécurité de votre compte pour protéger vos données. OpenSign prend en charge des méthodes d'authentification avancées pour renforcer la protection de votre compte.",
|
||||||
|
"2fa-auth-help": "Ajoutez une couche de sécurité supplémentaire en activant 2FA. Cela vous demandera de saisir un code de vérification provenant d’une application d’authentification après votre mot de passe.",
|
||||||
|
"passkey-auth-help": "Utilisez des passkeys pour une connexion sans mot de passe grâce à une vérification biométrique ou basée sur l’appareil, offrant à la fois une sécurité renforcée et une grande commodité."
|
||||||
|
},
|
||||||
|
"signer-already-present": "Signataire déjà présent",
|
||||||
|
"kiosk-sign": "Signature sur kiosque",
|
||||||
|
"dont-have-access-to-template": "Le template a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
|
||||||
|
"kiosk-info": "Le Mode Kiosque vous permet de recueillir des signatures en personne rapidement et efficacement. Idéal pour les salons, événements ou situations où tous les signataires sont physiquement présents. ",
|
||||||
|
"learn-more": "En savoir plus",
|
||||||
|
"finish-mssg": "Êtes-vous sûr de vouloir terminer le document ?",
|
||||||
|
"review": "Revoir",
|
||||||
|
"next-field": "Champ suivant",
|
||||||
|
"required-mssg": "{{leftRequiredWidget}} champs sur {{totalWidget}} restants",
|
||||||
|
"verify-document": "Vérifier le document",
|
||||||
|
"verify-document-signature": "Vérifier la signature du document",
|
||||||
|
"select-pdf-document": "Sélectionner le document PDF",
|
||||||
|
"selected-file": "Fichier sélectionné",
|
||||||
|
"verify-signature": "Vérifier la signature",
|
||||||
|
"verification-status": "État de la vérification",
|
||||||
|
"verification-in-progress": "Vérification en cours...",
|
||||||
|
"verification-results-will-appear-here": "Les résultats de la vérification apparaîtront ici",
|
||||||
|
"please-select-pdf": "Veuillez sélectionner un fichier PDF valide",
|
||||||
|
"please-select-file-to-verify": "Veuillez sélectionner un fichier à vérifier",
|
||||||
|
"no-signature-found": "Aucune signature trouvée dans le document",
|
||||||
|
"error-verifying-pdf": "Erreur lors de la vérification du PDF",
|
||||||
|
"signature-valid-basic": "La signature est valide",
|
||||||
|
"signature-invalid-basic": "La signature est invalide",
|
||||||
|
"all-signatures-verified-convincing": "Document vérifié : Toutes les signatures ont été validées avec succès.",
|
||||||
|
"some-signatures-invalid-basic": "Certaines signatures sont invalides",
|
||||||
|
"no-signatures-processed": "Aucune signature traitée",
|
||||||
|
"unnamed-signature-field": "Champ de signature sans nom",
|
||||||
|
"error-processing-signature": "Erreur lors du traitement de la signature",
|
||||||
|
"signer-info-not-available": "Informations sur le signataire non disponibles",
|
||||||
|
"cert-validity-not-checked": "Validité du certificat non vérifiée",
|
||||||
|
"valid": "Valide",
|
||||||
|
"expired-or-not-yet-valid": "Expiré ou pas encore valide",
|
||||||
|
"valid-from": "Valide du",
|
||||||
|
"to": "au",
|
||||||
|
"signer": "Signataire",
|
||||||
|
"issuer": "Émetteur",
|
||||||
|
"not-available": "Non disponible",
|
||||||
|
"not-performed": "Non effectué",
|
||||||
|
"missing-acrofield-dict": "Dictionnaire Acrofield manquant",
|
||||||
|
"signature-dictionary-not-found-or-invalid": "Dictionnaire de signatures introuvable ou invalide",
|
||||||
|
"missing-or-invalid-byterange": "ByteRange manquant ou invalide",
|
||||||
|
"missing-or-invalid-contents": "Contenu manquant ou invalide",
|
||||||
|
"missing-signature-contents": "Contenu de la signature manquant",
|
||||||
|
"invalid-signature-hex-format": "Format hexadécimal de signature invalide",
|
||||||
|
"unsupported-signature-format-not-signeddata": "Format de signature non pris en charge - pas SignedData",
|
||||||
|
"signer-certificate-not-found": "Certificat du signataire introuvable",
|
||||||
|
"no-certificates-in-signature": "Aucun certificat dans la signature",
|
||||||
|
"no-signer-info-in-pkcs7": "Aucune information sur le signataire dans PKCS#7",
|
||||||
|
"could-not-parse-signer-info": "Impossible d'analyser les informations sur le signataire",
|
||||||
|
"not-calculated": "Non calculé",
|
||||||
|
"not-found-in-signature": "Introuvable dans la signature",
|
||||||
|
"readonly-error": "Le widget en lecture seule {{widgetName}} doit avoir une valeur par défaut ou vous pouvez le rendre facultatif.",
|
||||||
|
"choose-one": "Choisissez-en un",
|
||||||
|
"search-templates": "Rechercher des modèles…",
|
||||||
|
"search-documents": "Rechercher des documents…",
|
||||||
|
"search-contacts": "Rechercher des contacts…",
|
||||||
|
"add-role-alert": "Veuillez ajouter au moins un rôle",
|
||||||
|
"edit-draft": "Modifier le brouillon",
|
||||||
|
"invalid-email-found": "Adresse e-mail invalide trouvée : {{email}}",
|
||||||
|
"duplicate-email-found": "Adresse e-mail en double trouvée : {{email}}",
|
||||||
|
"vertical": "Vertical",
|
||||||
|
"horizontal": "Horizontal",
|
||||||
|
"billing": "Facturation",
|
||||||
|
"console": "Console",
|
||||||
|
"prefill-widget": "Widgets de Préremplissage",
|
||||||
|
"action-prohibited": "Cette action n'est pas autorisée pour votre domaine de messagerie. Veuillez contacter votre administrateur pour obtenir de l'aide.",
|
||||||
|
"must-have-at-least-one-vacant-role": "Vous devez laisser au moins un rôle non attribué avant de définir un template comme 'public'.",
|
||||||
|
"remove-duplicate": "Veuillez supprimer l'option en double",
|
||||||
|
"prefill-bulk-error": "L'envoi en masse n'est pas autorisé lorsque des widgets de pré-remplissage sont ajoutés. Veuillez supprimer les widgets de pré-remplissage pour continuer.",
|
||||||
|
"session-expired-title": "Session expirée",
|
||||||
|
"access-denied": "Accès refusé",
|
||||||
|
"upgrade": "Mettre à jour",
|
||||||
|
"do-not-access-app": "Vous n'avez pas accès à cette application.",
|
||||||
|
"dont-have-access": "Vous n'avez pas accès.",
|
||||||
|
"valid-email-alert": "Veuillez saisir une adresse e-mail valide.",
|
||||||
|
"otp-not-validate": "OTP non valide.",
|
||||||
|
"domain-not-allowed": "Ce domaine n'est pas autorisé",
|
||||||
|
"atleast-one-recipient-alert": "Veuillez ajouter au moins un destinataire !",
|
||||||
|
"incorrect-password-or-decryption-failed": "Mot de passe incorrect ou échec du déchiffrement.",
|
||||||
|
"incorrect-password-for-file": "Mot de passe incorrect pour le fichier : {{file}}",
|
||||||
|
"error-uploading-pdf": "Erreur lors du téléchargement du PDF.",
|
||||||
|
"provide-password": "Veuillez fournir le mot de passe.",
|
||||||
|
"only-pdf-allowed": "Seuls les fichiers PDF sont autorisés.",
|
||||||
|
"invalid-username-password-region": "Nom d'utilisateur/mot de passe ou région invalide.",
|
||||||
|
"pfx-extension-alert": "Veuillez télécharger un fichier avec l'extension .pfx.",
|
||||||
|
"email-already-exist": "L'e-mail existe déjà",
|
||||||
|
"branding": "Image de marque",
|
||||||
|
"branding-help": "Le branding permet le white labelling de votre application",
|
||||||
|
"custom-sub-domain": "Sous-domaine personnalisé",
|
||||||
|
"app-name": "Nom de l'application",
|
||||||
|
"provide-domain-name": "Fournissez votre nom de domaine",
|
||||||
|
"provide-app-name": "Fournissez le nom de votre application",
|
||||||
|
"logo": "Logo",
|
||||||
|
"upload-app-logo": "Téléchargez le logo de votre application",
|
||||||
|
"prefill-unfilled-widget": "Les champs obligatoires suivants ne peuvent pas être vides : {{emptyWidget}}. Veuillez les remplir pour continuer.",
|
||||||
|
"Dashboard": "Tableau de bord",
|
||||||
|
"Analytics": "Analytique",
|
||||||
|
"Templates": "Modèles",
|
||||||
|
"Need your sign": "Besoin de votre signature",
|
||||||
|
"In Progress": "En cours",
|
||||||
|
"Completed": "Complété",
|
||||||
|
"Drafts": "Brouillons",
|
||||||
|
"Declined": "Signature refusé",
|
||||||
|
"Expired": "Expiré",
|
||||||
|
"Contactbook": "Carnet de contacts",
|
||||||
|
"My Signature": "Ma signature",
|
||||||
|
"API Token": "Jeton API",
|
||||||
|
"Webhook": "Webhook",
|
||||||
|
"Preferences": "Préférences",
|
||||||
|
"Teams": "Équipe",
|
||||||
|
"Users": "Utilisateurs",
|
||||||
|
"Drive": "Lecteur",
|
||||||
|
"Branding": "Image de marque",
|
||||||
|
"Mail": "Courrier",
|
||||||
|
"Storage": "Stockage",
|
||||||
|
"Signing certificate": "Certificat de signature",
|
||||||
|
"General": "Général",
|
||||||
|
"Organizations": "Organisations",
|
||||||
|
"OrgAdmins": "OrgAdmins",
|
||||||
|
"Debug Pdf": "Déboguer le PDF",
|
||||||
|
"New Document": "Nouveau document",
|
||||||
|
"subscription": "Abonnement",
|
||||||
|
"Draft document": "Brouillon de document",
|
||||||
|
"Draft template": "Brouillon de modèle",
|
||||||
|
"Public sign": "Signature publique",
|
||||||
|
"Signup": "Inscription",
|
||||||
|
"delete-contact": "Supprimer le contact",
|
||||||
|
"total-records-found": "Nombre total d'enregistrements trouvés : {{count}}",
|
||||||
|
"Invalid-records-found": "Enregistrements invalides trouvés : {{records}}",
|
||||||
|
"previous": "Précédent",
|
||||||
|
"page-n-of-n": "Page {{currentPage}} sur {{totalPages}}",
|
||||||
|
"import": "Importer",
|
||||||
|
"search": "Rechercher",
|
||||||
|
"viewed-on": "Consulté le : {{ViewedOn}}",
|
||||||
|
"signed-on": "Signé le : {{SignedOn}}",
|
||||||
|
"hide": "Masquer",
|
||||||
|
"show-more": "Afficher plus",
|
||||||
|
"browse-or-drag-to-replace-existing-file": "Parcourez ou faites glisser un nouveau fichier pour remplacer l'existant",
|
||||||
|
"optional-details": "Détails facultatifs",
|
||||||
|
"hide-optional-details": "masquer les détails facultatifs",
|
||||||
|
"mail-adapter-subscription-alert": "Veuillez passer au plan Professionnel ou Équipe pour configurer un SMTP personnalisé.",
|
||||||
|
"connect-to-mail": "Se connecter à Gmail",
|
||||||
|
"custom-smtp": "SMTP personnalisé",
|
||||||
|
"default-smtp": "SMTP par défaut de {{appName}}",
|
||||||
|
"host": "Hôte",
|
||||||
|
"port": "Port",
|
||||||
|
"sender-email": "Email de l'expéditeur",
|
||||||
|
"username": "Nom d'utilisateur",
|
||||||
|
"use-default-mail-adapter": "Voulez-vous vraiment utiliser les serveurs de messagerie par défaut de {{appName}} pour envoyer vos demandes de signature ? Nous vous recommandons d'utiliser votre propre serveur Gmail ou SMTP pour une meilleure délivrabilité.",
|
||||||
|
"verification-code-sent-registered-email": "Un code de vérification a été envoyé à votre email enregistré <1>{{useremail}}</1>. Veuillez entrer le code ci-dessous pour confirmer vos paramètres.",
|
||||||
|
"smpt-credentials": "Identifiants SMTP",
|
||||||
|
"delete-account": "Supprimer le compte",
|
||||||
|
"delete-account-que": "Êtes-vous sûr de vouloir supprimer votre compte ?",
|
||||||
|
"delete-account-que-user": "Vous êtes sur le point de supprimer définitivement cet utilisateur et toutes les données associées. Cette action est irréversible.",
|
||||||
|
"user-deleted-successfully": "L'utilisateur et toutes les données associées ont été supprimés avec succès.",
|
||||||
|
"account-deletion-request-sent-via-mail": "Nous vous avons envoyé par e-mail un lien de confirmation. Approuvez la demande pour finaliser la suppression de votre compte.",
|
||||||
|
"type-exact-email-delete": "Saisissez l'adresse e-mail exacte pour activer la suppression",
|
||||||
|
"email-does-not-match": "L'adresse e-mail ne correspond pas.",
|
||||||
|
"please-type-to-confirm": "Veuillez saisir <1>{{userEmail}}</1> pour confirmer:",
|
||||||
|
"email-settings": "Paramètres de messagerie",
|
||||||
|
"email-settings-help": "Pour assurer une meilleure délivrabilité dans la boîte de réception de vos e-mails de demande de signature, vous pouvez connecter votre propre fournisseur de messagerie. Choisissez l’une des options suivantes :",
|
||||||
|
"connect-to-gmail": "Se connecter à Gmail: ",
|
||||||
|
"connect-to-gmail-help": "Utilisez votre compte Gmail pour envoyer des e-mails de demande de signature directement depuis votre propre boîte de réception, améliorant ainsi les taux de délivrabilité et la fiabilité.",
|
||||||
|
"connect-to-smtp": "SMTP personnalisé: ",
|
||||||
|
"connect-to-smtp-help": "Utilisez votre propre serveur SMTP pour envoyer des e-mails via votre domaine. Cette option vous donne un contrôle total sur votre infrastructure de messagerie, améliorant la délivrabilité et la cohérence de la marque.",
|
||||||
|
"connect-to-default": "Si vous le souhaitez, vous pouvez également utiliser les serveurs de messagerie par défaut de {{appName}}, bien que nous recommandions d’utiliser les vôtres pour des résultats optimaux.",
|
||||||
|
"email-settings-redirect-message": "Ce paramètre a été déplacé de la console vers Paramètres du menu principal → Préférences. Cette page sera supprimée dans les prochaines versions.",
|
||||||
|
"go-to-preferences-menu": "Aller au menu Préférences",
|
||||||
|
"document-download-filename-format": "Format du nom de fichier pour le téléchargement du document",
|
||||||
|
"preview": "Aperçu : ",
|
||||||
|
"download-filename-format-help": "Choisissez comment les fichiers PDF téléchargés sont nommés. Votre sélection est enregistrée dans votre profil et utilisée dans toute l'application.",
|
||||||
|
"delete-action-prohibited": "Cette action n'est pas autorisée. Veuillez contacter votre administrateur pour demander la suppression du compte.",
|
||||||
|
"not-verified": "Non vérifié",
|
||||||
|
"verified": "Vérifié",
|
||||||
|
"expires": "Expire",
|
||||||
|
"fix-resend-error": "Vous ne pouvez pas corriger et renvoyer ce document car il contient des widgets préremplis.",
|
||||||
|
"duplicate-template-error": "Vous ne pouvez pas dupliquer ce modèle car il contient des widgets préremplis.",
|
||||||
|
"save-as-template-error": "Ce document ne peut pas être enregistré comme modèle car il contient des widgets préremplis.",
|
||||||
|
"redirecting-you-in": "Redirection dans {{redirectTimeLeft}} s...",
|
||||||
|
"pdf-tools-tour": "Cliquez sur ces boutons pour ajouter, supprimer, réorganiser, faire pivoter et zoomer les pages.",
|
||||||
|
"widgets": "Widgets",
|
||||||
|
"prefill-tour": "Utilisez cette option pour saisir des informations à l'avance avant d'envoyer le document aux destinataires.",
|
||||||
|
"empty-prefill-error": "Les champs obligatoires suivants ne peuvent pas être laissés vides :",
|
||||||
|
"please-fill-out": "Veuillez les remplir pour continuer.",
|
||||||
|
"custom-signing-certificate": "Certificat de signature personnalisé",
|
||||||
|
"signing-certificate-help": "Vous pouvez télécharger votre propre certificat de signature de documents, qui sera utilisé pour signer tous vos documents ainsi que les certificats d’achèvement. Le fichier du certificat doit être un certificat P12 au format PFX.",
|
||||||
|
"certificate-file-p12-in-PFX-format": "Fichier de certificat (certificat p12 au format PFX)",
|
||||||
|
"password-of-pfx-file": "Entrez le mot de passe du fichier pfx",
|
||||||
|
"update": "Mettre à jour",
|
||||||
|
"use-default-certificate": "Utiliser le certificat par défaut de {{appName}}",
|
||||||
|
"upgrade-to-team-plan": "Passer au plan Équipe",
|
||||||
|
"setup-file-storage": "Configurer le stockage de fichiers",
|
||||||
|
"save-and-activate": "Enregistrer et activer",
|
||||||
|
"logging-out-to-apply-settings": "Vous êtes déconnecté pour appliquer les nouveaux paramètres",
|
||||||
|
"reconnect-to-default": "Se reconnecter à {{appName}}",
|
||||||
|
"active-file-adapter": "Adaptateur de fichiers actif",
|
||||||
|
"file-adapter-unique-name": "Nom unique de l'adaptateur de fichiers",
|
||||||
|
"unique-name-for-file-adapter": "Entrez un nom unique pour l'adaptateur de fichiers",
|
||||||
|
"storage-provider": "Fournisseur de stockage",
|
||||||
|
"enter-bucket-name": "Entrez le nom du bucket",
|
||||||
|
"enter-space-name": "Entrez le nom de l'espace",
|
||||||
|
"enter-region-of-bucket": "Entrez la région du bucket",
|
||||||
|
"enter-region-of-space": "Entrez la région de l'espace",
|
||||||
|
"enter-access-key": "Entrez la clé d'accès",
|
||||||
|
"enter-secret-access-key": "Entrez la clé d'accès secrète",
|
||||||
|
"otp-email": "Nous avons envoyé un code de vérification",
|
||||||
|
"save-as-temp-warn": "Remarque : Ce document contient des widgets préremplis, qui seront automatiquement supprimés car ils sont déjà intégrés dans le document de base.",
|
||||||
|
"edit-document": "Modifier le document",
|
||||||
|
"modify": "Modifier",
|
||||||
|
"merge-certificate-to-pdf": "Fusionner le certificat avec le PDF",
|
||||||
|
"merge-cc-to-pdf-help": {
|
||||||
|
"p1": "Cela garantira que le certificat de finalisation est inclus dans le document PDF final. Cependant, veuillez noter qu'une fois fusionné, le certificat ne peut plus être séparé du document principal.",
|
||||||
|
"p2": "Si vous choisissez de ne pas fusionner, le certificat de finalisation sera fourni sous forme de fichier PDF distinct accompagné du document signé."
|
||||||
|
},
|
||||||
|
"enter-name": "Saisir le nom",
|
||||||
|
"enter-email": "Saisir l'e-mail"
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
Name,Email,Phone,Company,JobTitle
|
||||||
|
John Doe,john@example.com,1234567890,abc firm,dev
|
||||||
|
Jane Smith,jane@example.com,9876543210,xyz firm,manager
|
||||||
|
Foo Bar,foo@example.com,5555555555,xyz firm,director
|
||||||
|
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 86 KiB |
@@ -0,0 +1 @@
|
|||||||
|
import "@testing-library/jest-dom";
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
import React, { useState, useEffect, lazy } from "react";
|
|
||||||
import { Routes, Route, BrowserRouter } from "react-router-dom";
|
|
||||||
import { pdfjs } from "react-pdf";
|
|
||||||
import Login from "./pages/Login";
|
|
||||||
import Form from "./pages/Form";
|
|
||||||
import Report from "./pages/Report";
|
|
||||||
import Dashboard from "./pages/Dashboard";
|
|
||||||
import HomeLayout from "./layout/HomeLayout";
|
|
||||||
import PageNotFound from "./pages/PageNotFound";
|
|
||||||
import ValidateRoute from "./primitives/ValidateRoute";
|
|
||||||
import Validate from "./primitives/Validate";
|
|
||||||
import TemplatePlaceholder from "./pages/TemplatePlaceholder";
|
|
||||||
import SignYourSelf from "./pages/SignyourselfPdf";
|
|
||||||
import DraftDocument from "./components/pdf/DraftDocument";
|
|
||||||
import PlaceHolderSign from "./pages/PlaceHolderSign";
|
|
||||||
import PdfRequestFiles from "./pages/PdfRequestFiles";
|
|
||||||
import LazyPage from "./primitives/LazyPage";
|
|
||||||
import { isEnableSubscription } from "./constant/const";
|
|
||||||
import SSOVerify from "./pages/SSOVerify";
|
|
||||||
import Loader from "./primitives/Loader";
|
|
||||||
import TeamList from "./pages/TeamList";
|
|
||||||
import UserList from "./pages/UserList";
|
|
||||||
import { serverUrl_fn } from "./constant/appinfo";
|
|
||||||
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
|
|
||||||
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
|
|
||||||
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
|
|
||||||
const Pgsignup = lazy(() => import("./pages/Pgsignup"));
|
|
||||||
const Subscriptions = lazy(() => import("./pages/PlanSubscriptions"));
|
|
||||||
const ChangePassword = lazy(() => import("./pages/ChangePassword"));
|
|
||||||
const UserProfile = lazy(() => import("./pages/UserProfile"));
|
|
||||||
const Signup = lazy(() => import("./pages/Signup"));
|
|
||||||
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
|
|
||||||
const ManageSign = lazy(() => import("./pages/Managesign"));
|
|
||||||
const GenerateToken = lazy(() => import("./pages/GenerateToken"));
|
|
||||||
const Webhook = lazy(() => import("./pages/Webhook"));
|
|
||||||
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
|
|
||||||
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
|
|
||||||
|
|
||||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
|
|
||||||
const AppLoader = () => {
|
|
||||||
return (
|
|
||||||
<div className="flex justify-center items-center h-[100vh]">
|
|
||||||
<Loader />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
function App() {
|
|
||||||
const [isloading, setIsLoading] = useState(true);
|
|
||||||
useEffect(() => {
|
|
||||||
handleCredentials();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleCredentials = () => {
|
|
||||||
const appId = process.env.REACT_APP_APPID
|
|
||||||
? process.env.REACT_APP_APPID
|
|
||||||
: "opensign";
|
|
||||||
const baseurl = serverUrl_fn();
|
|
||||||
try {
|
|
||||||
localStorage.setItem("baseUrl", `${baseurl}/`);
|
|
||||||
localStorage.setItem("parseAppId", appId);
|
|
||||||
setIsLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("err ", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bg-base-200">
|
|
||||||
{isloading ? (
|
|
||||||
<AppLoader />
|
|
||||||
) : (
|
|
||||||
<BrowserRouter>
|
|
||||||
<Routes>
|
|
||||||
<Route element={<ValidateRoute />}>
|
|
||||||
<Route exact path="/" element={<Login />} />
|
|
||||||
{isEnableSubscription && (
|
|
||||||
<Route path="/signup" element={<LazyPage Page={Signup} />} />
|
|
||||||
)}
|
|
||||||
{!isEnableSubscription && (
|
|
||||||
<>
|
|
||||||
<Route
|
|
||||||
path="/addadmin"
|
|
||||||
element={<LazyPage Page={AddAdmin} />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/upgrade-2.1"
|
|
||||||
element={<LazyPage Page={UpdateExistUserAdmin} />}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Route>
|
|
||||||
<Route element={<Validate />}>
|
|
||||||
<Route
|
|
||||||
path="/load/template/:templateId"
|
|
||||||
element={<TemplatePlaceholder />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path="/load/placeholdersign/:docId"
|
|
||||||
element={<PlaceHolderSign />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path="/load/recipientSignPdf/:docId/:contactBookId"
|
|
||||||
element={<PdfRequestFiles />}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
<Route
|
|
||||||
path="/loadmf/signmicroapp/login/:id/:userMail/:contactBookId/:serverUrl"
|
|
||||||
element={<LazyPage Page={GuestLogin} />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/login/:id/:userMail/:contactBookId/:serverUrl"
|
|
||||||
element={<LazyPage Page={GuestLogin} />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/login/:base64url"
|
|
||||||
element={<LazyPage Page={GuestLogin} />}
|
|
||||||
/>
|
|
||||||
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
|
|
||||||
<Route
|
|
||||||
path="/forgetpassword"
|
|
||||||
element={<LazyPage Page={ForgetPassword} />}
|
|
||||||
/>
|
|
||||||
{isEnableSubscription && (
|
|
||||||
<>
|
|
||||||
<Route
|
|
||||||
path="/pgsignup"
|
|
||||||
element={<LazyPage Page={Pgsignup} />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/subscription"
|
|
||||||
element={<LazyPage Page={Subscriptions} />}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Route element={<HomeLayout />}>
|
|
||||||
<Route
|
|
||||||
path="/changepassword"
|
|
||||||
element={<LazyPage Page={ChangePassword} />}
|
|
||||||
/>
|
|
||||||
<Route path="/form/:id" element={<Form />} />
|
|
||||||
<Route path="/report/:id" element={<Report />} />
|
|
||||||
<Route path="/dashboard/:id" element={<Dashboard />} />
|
|
||||||
<Route
|
|
||||||
path="/profile"
|
|
||||||
element={<LazyPage Page={UserProfile} />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/opensigndrive"
|
|
||||||
element={<LazyPage Page={Opensigndrive} />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/managesign"
|
|
||||||
element={<LazyPage Page={ManageSign} />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/template/:templateId"
|
|
||||||
element={<TemplatePlaceholder />}
|
|
||||||
/>
|
|
||||||
{/* signyouself route with no rowlevel data using docId from url */}
|
|
||||||
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
|
|
||||||
{/* draft document route to handle and navigate route page accordiing to document status */}
|
|
||||||
<Route path="/draftDocument" element={<DraftDocument />} />
|
|
||||||
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
|
|
||||||
<Route
|
|
||||||
path="/placeHolderSign/:docId"
|
|
||||||
element={<PlaceHolderSign />}
|
|
||||||
/>
|
|
||||||
{/* for user signature (need your sign route) with row level data */}
|
|
||||||
<Route path="/pdfRequestFiles" element={<PdfRequestFiles />} />
|
|
||||||
{/* for user signature (need your sign route) with no row level data */}
|
|
||||||
<Route
|
|
||||||
path="/pdfRequestFiles/:docId"
|
|
||||||
element={<PdfRequestFiles />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* recipient signature route with no rowlevel data using docId from url */}
|
|
||||||
<Route
|
|
||||||
path="/recipientSignPdf/:docId/:contactBookId"
|
|
||||||
element={<PdfRequestFiles />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/recipientSignPdf/:docId"
|
|
||||||
element={<PdfRequestFiles />}
|
|
||||||
/>
|
|
||||||
{isEnableSubscription && (
|
|
||||||
<>
|
|
||||||
<Route path="/teams" element={<TeamList />} />
|
|
||||||
<Route
|
|
||||||
path="/generatetoken"
|
|
||||||
element={<LazyPage Page={GenerateToken} />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/webhook"
|
|
||||||
element={<LazyPage Page={Webhook} />}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Route path="/users" element={<UserList />} />
|
|
||||||
</Route>
|
|
||||||
<Route path="/sso" element={<SSOVerify />} />
|
|
||||||
<Route path="*" element={<PageNotFound />} />
|
|
||||||
</Routes>
|
|
||||||
</BrowserRouter>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { lazyWithRetry, hideUpgradeProgress } from "./utils";
|
||||||
|
import { Routes, Route, BrowserRouter } from "react-router";
|
||||||
|
import { pdfjs } from "react-pdf";
|
||||||
|
import Form from "./pages/Form";
|
||||||
|
import Report from "./pages/Report";
|
||||||
|
import Dashboard from "./pages/Dashboard";
|
||||||
|
import HomeLayout from "./layout/HomeLayout";
|
||||||
|
import PageNotFound from "./pages/PageNotFound";
|
||||||
|
import ValidateRoute from "./primitives/ValidateRoute";
|
||||||
|
import Validate from "./primitives/Validate";
|
||||||
|
import TemplatePlaceholder from "./pages/TemplatePlaceholder";
|
||||||
|
import SignYourSelf from "./pages/SignyourselfPdf";
|
||||||
|
import DraftDocument from "./components/pdf/DraftDocument";
|
||||||
|
import PlaceHolderSign from "./pages/PlaceHolderSign";
|
||||||
|
import PdfRequestFiles from "./pages/PdfRequestFiles";
|
||||||
|
import Lazy from "./primitives/LazyPage";
|
||||||
|
import Loader from "./primitives/Loader";
|
||||||
|
import UserList from "./pages/UserList";
|
||||||
|
import { serverUrl_fn } from "./constant/appinfo";
|
||||||
|
import DocSuccessPage from "./pages/DocSuccessPage";
|
||||||
|
import ValidateSession from "./primitives/ValidateSession";
|
||||||
|
import DragProvider from "./components/DragProivder";
|
||||||
|
import Title from "./components/Title";
|
||||||
|
const DebugPdf = lazyWithRetry(() => import("./pages/DebugPdf"));
|
||||||
|
const ForgetPassword = lazyWithRetry(() => import("./pages/ForgetPassword"));
|
||||||
|
const GuestLogin = lazyWithRetry(() => import("./pages/GuestLogin"));
|
||||||
|
const ChangePassword = lazyWithRetry(() => import("./pages/ChangePassword"));
|
||||||
|
const UserProfile = lazyWithRetry(() => import("./pages/UserProfile"));
|
||||||
|
const Opensigndrive = lazyWithRetry(() => import("./pages/Opensigndrive"));
|
||||||
|
const ManageSign = lazyWithRetry(() => import("./pages/Managesign"));
|
||||||
|
const AddAdmin = lazyWithRetry(() => import("./pages/AddAdmin"));
|
||||||
|
const UpdateExistUserAdmin = lazyWithRetry(
|
||||||
|
() => import("./pages/UpdateExistUserAdmin")
|
||||||
|
);
|
||||||
|
const Preferences = lazyWithRetry(() => import("./pages/Preferences"));
|
||||||
|
const Login = lazyWithRetry(() => import("./pages/Login"));
|
||||||
|
const VerifyDocument = lazyWithRetry(() => import("./pages/VerifyDocument"));
|
||||||
|
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
|
||||||
|
const AppLoader = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center h-[100vh]">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
function App() {
|
||||||
|
const [isloading, setIsLoading] = useState(true);
|
||||||
|
useEffect(() => {
|
||||||
|
// initialize creds
|
||||||
|
const id = process.env.REACT_APP_APPID ?? "opensign";
|
||||||
|
localStorage.setItem("parseAppId", id);
|
||||||
|
localStorage.setItem("baseUrl", `${serverUrl_fn()}/`);
|
||||||
|
hideUpgradeProgress();
|
||||||
|
localStorage.removeItem("showUpgradeProgress");
|
||||||
|
setIsLoading(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-base-200">
|
||||||
|
{isloading ? (
|
||||||
|
<AppLoader />
|
||||||
|
) : (
|
||||||
|
<BrowserRouter>
|
||||||
|
<Title />
|
||||||
|
<Routes>
|
||||||
|
<Route element={<ValidateRoute />}>
|
||||||
|
<Route exact path="/" element={<Lazy Page={Login} />} />
|
||||||
|
<Route path="/addadmin" element={<Lazy Page={AddAdmin} />} />
|
||||||
|
<Route
|
||||||
|
path="/upgrade-2.1"
|
||||||
|
element={<Lazy Page={UpdateExistUserAdmin} />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route element={<Validate />}>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path="/load/recipientSignPdf/:docId/:contactBookId"
|
||||||
|
element={<DragProvider Page={PdfRequestFiles} />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route
|
||||||
|
path="/login/:base64url"
|
||||||
|
element={<Lazy Page={GuestLogin} />}
|
||||||
|
/>
|
||||||
|
<Route path="/debugpdf" element={<Lazy Page={DebugPdf} />} />
|
||||||
|
<Route
|
||||||
|
path="/forgetpassword"
|
||||||
|
element={<Lazy Page={ForgetPassword} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
<ValidateSession>
|
||||||
|
<HomeLayout />
|
||||||
|
</ValidateSession>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route path="/users" element={<UserList />} />
|
||||||
|
<Route
|
||||||
|
path="/changepassword"
|
||||||
|
element={<Lazy Page={ChangePassword} />}
|
||||||
|
/>
|
||||||
|
<Route path="/form/:id" element={<Form />} />
|
||||||
|
<Route path="/report/:id" element={<Report />} />
|
||||||
|
<Route path="/dashboard/:id" element={<Dashboard />} />
|
||||||
|
<Route path="/profile" element={<Lazy Page={UserProfile} />} />
|
||||||
|
<Route path="/drive" element={<Lazy Page={Opensigndrive} />} />
|
||||||
|
<Route path="/managesign" element={<Lazy Page={ManageSign} />} />
|
||||||
|
<Route
|
||||||
|
path="/template/:templateId"
|
||||||
|
element={<DragProvider Page={TemplatePlaceholder} />}
|
||||||
|
/>
|
||||||
|
{/* signyouself route with no rowlevel data using docId from url */}
|
||||||
|
<Route
|
||||||
|
path="/signaturePdf/:docId"
|
||||||
|
element={<DragProvider Page={SignYourSelf} />}
|
||||||
|
/>
|
||||||
|
{/* draft document route to handle and navigate route page according to document status */}
|
||||||
|
<Route
|
||||||
|
path="/draftDocument"
|
||||||
|
element={<DragProvider Page={DraftDocument} />}
|
||||||
|
/>
|
||||||
|
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
|
||||||
|
<Route
|
||||||
|
path="/placeHolderSign/:docId"
|
||||||
|
element={<DragProvider Page={PlaceHolderSign} />}
|
||||||
|
/>
|
||||||
|
{/* recipient signature route with no rowlevel data using docId from url */}
|
||||||
|
<Route
|
||||||
|
path="/recipientSignPdf/:docId/:contactBookId"
|
||||||
|
element={<DragProvider Page={PdfRequestFiles} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/recipientSignPdf/:docId"
|
||||||
|
element={<DragProvider Page={PdfRequestFiles} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/verify-document"
|
||||||
|
element={<Lazy Page={VerifyDocument} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/preferences"
|
||||||
|
element={<Lazy Page={Preferences} />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path="/success" element={<DocSuccessPage />} />
|
||||||
|
<Route path="*" element={<PageNotFound />} />
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
Before Width: | Height: | Size: 805 B |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 23 KiB |
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
|
|||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
import Loader from "../primitives/Loader";
|
import Loader from "../primitives/Loader";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { emailRegex } from "../constant/const";
|
||||||
|
|
||||||
const AddSigner = (props) => {
|
const AddSigner = (props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -23,7 +24,7 @@ const AddSigner = (props) => {
|
|||||||
if (savedUserDetails && addYourself) {
|
if (savedUserDetails && addYourself) {
|
||||||
setName(savedUserDetails.name);
|
setName(savedUserDetails.name);
|
||||||
setPhone(savedUserDetails?.phone || "");
|
setPhone(savedUserDetails?.phone || "");
|
||||||
setEmail(savedUserDetails.email);
|
setEmail(savedUserDetails.email?.toLowerCase()?.replace(/\s/g, ""));
|
||||||
}
|
}
|
||||||
}, [addYourself]);
|
}, [addYourself]);
|
||||||
|
|
||||||
@@ -47,6 +48,9 @@ const AddSigner = (props) => {
|
|||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (!emailRegex.test(email)) {
|
||||||
|
alert(t("valid-email-alert"));
|
||||||
|
} else {
|
||||||
setIsLoader(true);
|
setIsLoader(true);
|
||||||
if (localStorage.getItem("TenantId")) {
|
if (localStorage.getItem("TenantId")) {
|
||||||
try {
|
try {
|
||||||
@@ -105,7 +109,8 @@ const AddSigner = (props) => {
|
|||||||
if (props.details) {
|
if (props.details) {
|
||||||
props.details({
|
props.details({
|
||||||
value: parseData[props.valueKey],
|
value: parseData[props.valueKey],
|
||||||
label: parseData[props.displayKey]
|
label: parseData[props.displayKey],
|
||||||
|
email: parseData?.Email
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (props.closePopup) {
|
if (props.closePopup) {
|
||||||
@@ -151,7 +156,8 @@ const AddSigner = (props) => {
|
|||||||
if (props.details) {
|
if (props.details) {
|
||||||
props.details({
|
props.details({
|
||||||
value: parseData[props.valueKey],
|
value: parseData[props.valueKey],
|
||||||
label: parseData[props.displayKey]
|
label: parseData[props.displayKey],
|
||||||
|
email: parseData?.Email
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (props.closePopup) {
|
if (props.closePopup) {
|
||||||
@@ -181,6 +187,7 @@ const AddSigner = (props) => {
|
|||||||
setIsLoader(false);
|
setIsLoader(false);
|
||||||
alert(t("something-went-wrong-mssg"));
|
alert(t("something-went-wrong-mssg"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define a function to handle the "add yourself" checkbox
|
// Define a function to handle the "add yourself" checkbox
|
||||||
@@ -206,6 +213,7 @@ const AddSigner = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="w-full mx-auto p-2 text-base-content">
|
<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 && (
|
{isUserExist && (
|
||||||
<div className="mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<input
|
<input
|
||||||
@@ -236,6 +244,7 @@ const AddSigner = (props) => {
|
|||||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||||
onInput={(e) => e.target.setCustomValidity("")}
|
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"
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
placeholder={t("enter-name")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
@@ -255,6 +264,7 @@ const AddSigner = (props) => {
|
|||||||
onInput={(e) => e.target.setCustomValidity("")}
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
disabled={addYourself}
|
disabled={addYourself}
|
||||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
|
placeholder={t("enter-email")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
@@ -268,6 +278,7 @@ const AddSigner = (props) => {
|
|||||||
onChange={(e) => setPhone(e.target.value)}
|
onChange={(e) => setPhone(e.target.value)}
|
||||||
disabled={addYourself}
|
disabled={addYourself}
|
||||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
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>
|
||||||
<div className="mt-4 flex gap-x-2 justify-start">
|
<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,554 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import Parse from "parse";
|
|
||||||
import Title from "./Title";
|
|
||||||
import Loader from "../primitives/Loader";
|
|
||||||
import { copytoData, fetchSubscriptionInfo } 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 [amount, setAmount] = useState({ quantity: 1, price: 0 });
|
|
||||||
const [planInfo, setPlanInfo] = useState({
|
|
||||||
priceperUser: 0,
|
|
||||||
price: 0,
|
|
||||||
totalAllowedUser: 0,
|
|
||||||
adminId: ""
|
|
||||||
});
|
|
||||||
const [isFormLoader, setIsFormLoader] = useState(false);
|
|
||||||
const [isLoader, setIsLoader] = useState(false);
|
|
||||||
const [teamList, setTeamList] = useState([]);
|
|
||||||
const [allowedUser, setAllowedUser] = useState(0);
|
|
||||||
const [err, setErr] = useState("");
|
|
||||||
const role = ["OrgAdmin", "Editor", "User"];
|
|
||||||
const extUser =
|
|
||||||
localStorage.getItem("Extand_Class") &&
|
|
||||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
|
||||||
useEffect(() => {
|
|
||||||
getTeamList();
|
|
||||||
// eslint-disable-next-line
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getTeamList = async () => {
|
|
||||||
if (isEnableSubscription) {
|
|
||||||
try {
|
|
||||||
setIsLoader(true);
|
|
||||||
const resSub = await fetchSubscriptionInfo();
|
|
||||||
if (!resSub.error) {
|
|
||||||
setPlanInfo((prev) => ({
|
|
||||||
...prev,
|
|
||||||
priceperUser: resSub.price,
|
|
||||||
totalAllowedUser: resSub.totalAllowedUser,
|
|
||||||
adminId: resSub.adminId
|
|
||||||
}));
|
|
||||||
setAmount((prev) => ({ ...prev, price: resSub.price }));
|
|
||||||
const res = await Parse.Cloud.run("allowedusers");
|
|
||||||
if (props.setFormHeader) {
|
|
||||||
if (res > 0) {
|
|
||||||
props.setFormHeader(t("add-user"));
|
|
||||||
} else {
|
|
||||||
props.setFormHeader(
|
|
||||||
resSub.adminId !== extUser?.objectId
|
|
||||||
? "Unauthorized"
|
|
||||||
: t("Add-seats")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setAllowedUser(res);
|
|
||||||
} else {
|
|
||||||
setAllowedUser(0);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setAllowedUser(0);
|
|
||||||
console.log("Err in alloweduser", err);
|
|
||||||
setErr(err.message);
|
|
||||||
} finally {
|
|
||||||
setIsLoader(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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];
|
|
||||||
setIsFormLoader(true);
|
|
||||||
const res = await checkUserExist();
|
|
||||||
if (res) {
|
|
||||||
props.setIsAlert({ type: "danger", msg: t("user-already-exist") });
|
|
||||||
setIsFormLoader(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsFormLoader(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);
|
|
||||||
}
|
|
||||||
setIsFormLoader(false);
|
|
||||||
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("err", err);
|
|
||||||
setIsFormLoader(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
|
|
||||||
};
|
|
||||||
const handlePricePerUser = (e) => {
|
|
||||||
const quantity = e.target.value;
|
|
||||||
const price = e.target?.value > 0 ? planInfo.priceperUser * quantity : 0;
|
|
||||||
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
|
|
||||||
};
|
|
||||||
const handleAddOnSubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsFormLoader(true);
|
|
||||||
try {
|
|
||||||
const resAddon = await Parse.Cloud.run("buyaddonusers", {
|
|
||||||
users: amount.quantity
|
|
||||||
});
|
|
||||||
if (resAddon) {
|
|
||||||
const _resAddon = JSON.parse(JSON.stringify(resAddon));
|
|
||||||
if (_resAddon.status === "success") {
|
|
||||||
setAllowedUser(amount.quantity);
|
|
||||||
setPlanInfo((obj) => ({ ...obj, totalAllowedUser: _resAddon.addon }));
|
|
||||||
}
|
|
||||||
if (props.handleBuyUsers) {
|
|
||||||
props.handleBuyUsers(amount.quantity, _resAddon.addon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Err in buy addon", err);
|
|
||||||
props.setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
|
||||||
} finally {
|
|
||||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 2000);
|
|
||||||
setIsFormLoader(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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>
|
|
||||||
)}
|
|
||||||
{!isLoader ? (
|
|
||||||
<>
|
|
||||||
{err ? (
|
|
||||||
<div className="flex justify-center items-center h-[70px] text-[18px]">
|
|
||||||
{err}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="w-full mx-auto">
|
|
||||||
{!isEnableSubscription || allowedUser > 0 ? (
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
{isEnableSubscription && (
|
|
||||||
<p className="flex justify-center mb-1">
|
|
||||||
{t("remaing-users")}{" "}
|
|
||||||
<span
|
|
||||||
className={`${
|
|
||||||
allowedUser < 2 ? "op-text-accent" : "op-text-primary"
|
|
||||||
} font-medium ml-1`}
|
|
||||||
>
|
|
||||||
{allowedUser} {t("of")} {planInfo.totalAllowedUser}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<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="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"
|
|
||||||
>
|
|
||||||
{t("team")}
|
|
||||||
<span className="text-[red] text-[13px]"> *</span>
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
name="team"
|
|
||||||
value={formdata.team}
|
|
||||||
onChange={(e) => handleChange(e)}
|
|
||||||
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>
|
|
||||||
{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"
|
|
||||||
>
|
|
||||||
{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>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{planInfo.adminId !== extUser?.objectId ? (
|
|
||||||
<div className="mb-3 mt-1 flex justify-center text-center items-center font-medium break-all">
|
|
||||||
{t("unauthorized-modal", {
|
|
||||||
adminEmail: extUser?.TenantId?.EmailAddress
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<form onSubmit={handleAddOnSubmit}>
|
|
||||||
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
|
|
||||||
{t("additional-users")}
|
|
||||||
</p>
|
|
||||||
<div className="mb-3 flex justify-between">
|
|
||||||
<label
|
|
||||||
htmlFor="quantity"
|
|
||||||
className="block text-xs text-gray-700 font-semibold"
|
|
||||||
>
|
|
||||||
{t("Quantity-of-users")}
|
|
||||||
<span className="text-[red] text-[13px]"> *</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
name="quantity"
|
|
||||||
value={amount.quantity}
|
|
||||||
onChange={(e) => handlePricePerUser(e)}
|
|
||||||
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3 flex justify-between">
|
|
||||||
<label className="block text-xs text-gray-700 font-semibold">
|
|
||||||
{t("Price")} (1 * {planInfo.priceperUser})
|
|
||||||
</label>
|
|
||||||
<div className="w-1/4 flex justify-center items-center text-sm">
|
|
||||||
USD {amount.price}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr className="text-base-content mb-3" />
|
|
||||||
<button className="op-btn op-btn-primary w-full">
|
|
||||||
{t("Proceed")}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="w-full h-[200px] inset-0 flex justify-center items-center z-50">
|
|
||||||
<Loader />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddUser;
|
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Parse from "parse";
|
||||||
|
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(t("valid-email-alert"));
|
||||||
|
} 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));
|
||||||
|
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">
|
||||||
|
{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 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"
|
||||||
|
placeholder={t("enter-name")}
|
||||||
|
/>
|
||||||
|
</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"
|
||||||
|
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"
|
||||||
|
placeholder={t("enter-email")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="block text-xs 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 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 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,505 +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";
|
|
||||||
import Parse from "parse";
|
|
||||||
import ModalUi from "../primitives/ModalUi";
|
|
||||||
import { isEnableSubscription } from "../constant/const";
|
|
||||||
import { fetchSubscription } from "../constant/Utils";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
const BulkSendUi = (props) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
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 [isBulkAvailable, setIsBulkAvailable] = useState(false);
|
|
||||||
const quantityList = [500, 1000, 5000, 50000];
|
|
||||||
const [amount, setAmount] = useState({
|
|
||||||
price: (75.0).toFixed(2),
|
|
||||||
quantity: 500,
|
|
||||||
priceperbulksend: 0.15,
|
|
||||||
totalcredits: 0
|
|
||||||
});
|
|
||||||
const [isQuotaReached, setIsQuotaReached] = useState(false);
|
|
||||||
const [isLoader, setIsLoader] = useState(false);
|
|
||||||
const [isFreePlan, setIsFreePlan] = useState(false);
|
|
||||||
const [admin, setAdmin] = useState({
|
|
||||||
objectId: "",
|
|
||||||
email: "",
|
|
||||||
isAdmin: true
|
|
||||||
});
|
|
||||||
const allowedSigners = 50;
|
|
||||||
useEffect(() => {
|
|
||||||
signatureExist();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//function to check atleast one signature field exist
|
|
||||||
const signatureExist = async () => {
|
|
||||||
if (isEnableSubscription) {
|
|
||||||
setIsLoader(true);
|
|
||||||
try {
|
|
||||||
const subscription = await fetchSubscription();
|
|
||||||
const extUser =
|
|
||||||
localStorage.getItem("Extand_Class") &&
|
|
||||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
|
||||||
if (
|
|
||||||
subscription?.adminId &&
|
|
||||||
extUser?.objectId === subscription?.adminId
|
|
||||||
) {
|
|
||||||
setAdmin((obj) => ({
|
|
||||||
...obj,
|
|
||||||
objectId: subscription?.adminId,
|
|
||||||
isAdmin: true
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
setAdmin((obj) => ({
|
|
||||||
...obj,
|
|
||||||
isAdmin: false,
|
|
||||||
email: extUser?.TenantId?.EmailAddress
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
setAdmin((obj) => ({ ...obj, objectId: subscription?.adminId }));
|
|
||||||
if (subscription?.plan === "freeplan") {
|
|
||||||
setIsFreePlan(true);
|
|
||||||
}
|
|
||||||
const resCredits = await Parse.Cloud.run("allowedcredits");
|
|
||||||
if (resCredits) {
|
|
||||||
const allowedcredits = resCredits?.allowedcredits || 0;
|
|
||||||
const addoncredits = resCredits?.addoncredits || 0;
|
|
||||||
const totalcredits = allowedcredits + addoncredits;
|
|
||||||
if (totalcredits > 0) {
|
|
||||||
if (subscription?.plan !== "freeplan") {
|
|
||||||
setIsBulkAvailable(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setAmount((obj) => ({ ...obj, totalcredits: totalcredits }));
|
|
||||||
}
|
|
||||||
const getPlaceholder = props.item?.Placeholders;
|
|
||||||
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
|
||||||
placeholderObj?.placeHolder?.some((holder) =>
|
|
||||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
setIsSignatureExist(checkIsSignatureExistt);
|
|
||||||
setIsLoader(false);
|
|
||||||
} catch (err) {
|
|
||||||
setIsLoader(false);
|
|
||||||
alert(t("something-went-wrong-mssg"));
|
|
||||||
console.log("Err", err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setIsBulkAvailable(true);
|
|
||||||
setAdmin((obj) => ({ ...obj, isAdmin: true }));
|
|
||||||
const getPlaceholder = props.item?.Placeholders;
|
|
||||||
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
|
||||||
placeholderObj?.placeHolder?.some((holder) =>
|
|
||||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
setIsSignatureExist(checkIsSignatureExistt);
|
|
||||||
setIsLoader(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
|
||||||
if (scrollOnNextUpdate && formRef.current) {
|
|
||||||
formRef.current.scrollIntoView({
|
|
||||||
behavior: "smooth",
|
|
||||||
block: "end",
|
|
||||||
inline: "nearest"
|
|
||||||
});
|
|
||||||
setScrollOnNextUpdate(false);
|
|
||||||
}
|
|
||||||
}, [forms, scrollOnNextUpdate]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(() => {
|
|
||||||
if (props?.Placeholders?.length > 0) {
|
|
||||||
let users = [];
|
|
||||||
props?.Placeholders?.forEach((element) => {
|
|
||||||
if (!element.signerObjId) {
|
|
||||||
users = [
|
|
||||||
...users,
|
|
||||||
{
|
|
||||||
fieldId: element.Id,
|
|
||||||
email: "",
|
|
||||||
label: element.Role,
|
|
||||||
signer: {}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
|
|
||||||
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 (isEnableSubscription && forms.length >= amount.totalcredits) {
|
|
||||||
setIsQuotaReached(true);
|
|
||||||
} else {
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleAddOnQuickSubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsSubmit(true);
|
|
||||||
try {
|
|
||||||
const resAddon = await Parse.Cloud.run("buycredits", {
|
|
||||||
credits: amount.quantity
|
|
||||||
});
|
|
||||||
if (resAddon) {
|
|
||||||
const _resAddon = JSON.parse(JSON.stringify(resAddon));
|
|
||||||
if (_resAddon.status === "success") {
|
|
||||||
setIsBulkAvailable(true);
|
|
||||||
handleCloseQuotaReached();
|
|
||||||
setAmount((obj) => ({
|
|
||||||
...obj,
|
|
||||||
quantity: 500,
|
|
||||||
priceperbulksend: 0.15,
|
|
||||||
price: (75.0).toFixed(2),
|
|
||||||
totalcredits: obj?.totalcredits + _resAddon.addon
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Err in buy addon", err);
|
|
||||||
alert(t("something-went-wrong-mssg"));
|
|
||||||
} finally {
|
|
||||||
setIsSubmit(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handlePricePerQuick = async (e) => {
|
|
||||||
const quantity = e.target?.value;
|
|
||||||
const price =
|
|
||||||
quantity > 0
|
|
||||||
? (Math.round(quantity * amount.priceperbulksend * 100) / 100).toFixed(
|
|
||||||
2
|
|
||||||
)
|
|
||||||
: 500 * amount.priceperbulksend;
|
|
||||||
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
|
|
||||||
};
|
|
||||||
const handleCloseQuotaReached = () => {
|
|
||||||
setIsQuotaReached(false);
|
|
||||||
};
|
|
||||||
const handleNavigation = () => {
|
|
||||||
navigate("/subscription");
|
|
||||||
};
|
|
||||||
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>
|
|
||||||
)}
|
|
||||||
{isBulkAvailable ? (
|
|
||||||
<>
|
|
||||||
{props.Placeholders?.length > 0 ? (
|
|
||||||
isSignatureExist ? (
|
|
||||||
<>
|
|
||||||
{props.Placeholders?.some((x) => !x.signerObjId) ? (
|
|
||||||
<div>
|
|
||||||
<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">
|
|
||||||
{isEnableSubscription && (
|
|
||||||
<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-accent focus:outline-none"
|
|
||||||
>
|
|
||||||
<i className="fa-light fa-paper-plane"></i>{" "}
|
|
||||||
<span>{t("send")}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<ModalUi
|
|
||||||
isOpen={isQuotaReached}
|
|
||||||
handleClose={() => handleCloseQuotaReached()}
|
|
||||||
>
|
|
||||||
<div className="p-4 flex justify-center items-center flex-col gap-y-3">
|
|
||||||
<p className="text-center text-base-content">
|
|
||||||
{t("quota-err-quicksend")}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => setIsBulkAvailable(false)}
|
|
||||||
className=" op-btn op-btn-primary w-[200px]"
|
|
||||||
>
|
|
||||||
{t("buy-credits")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ModalUi>
|
|
||||||
</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-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>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{isFreePlan ? (
|
|
||||||
<div className="w-full h-[130px] flex flex-col justify-center items-center text-center p-4">
|
|
||||||
<p className="text-base font-medium mb-2.5">
|
|
||||||
{t("bulk-send-subcription-alert")}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => handleNavigation()}
|
|
||||||
className="op-btn op-btn-primary"
|
|
||||||
>
|
|
||||||
{t("upgrade-now")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{admin?.isAdmin ? (
|
|
||||||
<form onSubmit={handleAddOnQuickSubmit} className="p-3">
|
|
||||||
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
|
|
||||||
{t("additional-credits")}
|
|
||||||
</p>
|
|
||||||
<div className="mb-3 flex justify-between">
|
|
||||||
<label
|
|
||||||
htmlFor="quantity"
|
|
||||||
className="block text-xs text-gray-700 font-semibold"
|
|
||||||
>
|
|
||||||
{t("quantity-of-credits")}
|
|
||||||
<span className="text-[red] text-[13px]">*</span>
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={amount.quantity}
|
|
||||||
onChange={(e) => handlePricePerQuick(e)}
|
|
||||||
name="quantity"
|
|
||||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
{quantityList.length > 0 &&
|
|
||||||
quantityList.map((x) => (
|
|
||||||
<option key={x} value={x}>
|
|
||||||
{x}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3 flex justify-between">
|
|
||||||
<label className="block text-xs text-gray-700 font-semibold">
|
|
||||||
{t("Price")} (1 * {amount.priceperbulksend})
|
|
||||||
</label>
|
|
||||||
<div className="w-1/4 flex justify-center items-center text-sm">
|
|
||||||
USD {amount.price}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr className="text-base-content mb-3" />
|
|
||||||
<button className="op-btn op-btn-primary w-full mt-2">
|
|
||||||
{t("Proceed")}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
) : (
|
|
||||||
<div className="mx-8 mt-4 mb-8 flex justify-center text-center items-center font-medium break-all">
|
|
||||||
{t("unauthorized-modal", { adminEmail: admin?.email })}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BulkSendUi;
|
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
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([]);
|
||||||
|
const [isPrefillExist, setIsPrefillExist] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
signatureExist();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//function to check at least one signature field exist
|
||||||
|
const signatureExist = async () => {
|
||||||
|
const isPrefill = props?.Placeholders.some((x) => x?.Role === "prefill");
|
||||||
|
if (isPrefill) {
|
||||||
|
setIsPrefillExist(isPrefill);
|
||||||
|
}
|
||||||
|
setIsDisableBulkSend(false);
|
||||||
|
const getPlaceholder = props?.Placeholders;
|
||||||
|
const removePrefill = getPlaceholder.filter((x) => x?.Role !== "prefill");
|
||||||
|
const checkIsSignatureExistt = removePrefill?.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>
|
||||||
|
)}
|
||||||
|
{isPrefillExist ? (
|
||||||
|
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||||
|
{t("prefill-bulk-error")}
|
||||||
|
</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,63 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import ModalUi from "../primitives/ModalUi";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const ColumnSelector = ({
|
||||||
|
isOpen,
|
||||||
|
allColumns = [],
|
||||||
|
visibleColumns = [],
|
||||||
|
columnLabels = {},
|
||||||
|
defaultColumns = [],
|
||||||
|
onApply,
|
||||||
|
onClose
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [selected, setSelected] = useState(visibleColumns);
|
||||||
|
const [names, setNames] = useState(columnLabels);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelected(visibleColumns);
|
||||||
|
setNames(columnLabels);
|
||||||
|
}, [visibleColumns, columnLabels]);
|
||||||
|
|
||||||
|
const handleChange = (col) => {
|
||||||
|
setSelected((prev) =>
|
||||||
|
prev.includes(col) ? prev.filter((c) => c !== col) : [...prev, col]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleApply = () => {
|
||||||
|
onApply && onApply(selected, names);
|
||||||
|
onClose && onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalUi isOpen={isOpen} title={t("select-columns")} handleClose={onClose}>
|
||||||
|
<div className="p-[20px] flex flex-col gap-2">
|
||||||
|
{allColumns.map((col, i) => (
|
||||||
|
<div key={col} className="flex justify-between items-center gap-2">
|
||||||
|
<span className="flex justify-center items-center h-full">
|
||||||
|
<input
|
||||||
|
id={col + "_" + i}
|
||||||
|
type="checkbox"
|
||||||
|
checked={selected.includes(col)}
|
||||||
|
onChange={() => handleChange(col)}
|
||||||
|
className="mb-0"
|
||||||
|
/>
|
||||||
|
<span className="whitespace-nowrap ml-1">
|
||||||
|
{t(`report-heading.${col}`, { defaultValue: col })}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="flex justify-start mt-2">
|
||||||
|
<button onClick={handleApply} className="op-btn op-btn-primary">
|
||||||
|
{t("apply")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColumnSelector;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||||
|
import { TouchBackend } from "react-dnd-touch-backend";
|
||||||
|
import {
|
||||||
|
DndProvider,
|
||||||
|
TouchTransition,
|
||||||
|
MouseTransition,
|
||||||
|
Preview
|
||||||
|
} from "react-dnd-multi-backend";
|
||||||
|
import DragElement from "./pdf/DragElement";
|
||||||
|
import LazyPage from "../primitives/LazyPage";
|
||||||
|
import { GuidelinesProvider } from "../context/GuidelinesContext";
|
||||||
|
|
||||||
|
const HTML5toTouch = {
|
||||||
|
backends: [
|
||||||
|
{
|
||||||
|
id: "html5",
|
||||||
|
backend: HTML5Backend,
|
||||||
|
transition: MouseTransition
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "touch",
|
||||||
|
backend: TouchBackend,
|
||||||
|
options: { enableMouseEvents: true },
|
||||||
|
preview: true,
|
||||||
|
transition: TouchTransition
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/* This handling in only for devices which support touch */
|
||||||
|
const generatePreview = (props) => {
|
||||||
|
const { item, style } = props;
|
||||||
|
const newStyle = { ...style };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={newStyle}>
|
||||||
|
<DragElement {...item} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DragProvider({ Page, lazy = false }) {
|
||||||
|
return (
|
||||||
|
<DndProvider options={HTML5toTouch}>
|
||||||
|
<Preview>{generatePreview}</Preview>
|
||||||
|
<GuidelinesProvider>
|
||||||
|
{lazy ? <LazyPage Page={Page} /> : <Page />}
|
||||||
|
</GuidelinesProvider>
|
||||||
|
</DndProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import axios from "axios";
|
|||||||
import { openInNewTab } from "../constant/Utils";
|
import { openInNewTab } from "../constant/Utils";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
|
const appName = "OpenSign™";
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [showButton, setShowButton] = useState(false);
|
const [showButton, setShowButton] = useState(false);
|
||||||
const [version, setVersion] = useState("");
|
const [version, setVersion] = useState("");
|
||||||
@@ -39,7 +40,6 @@ const Footer = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const appName = "OpenSign™";
|
|
||||||
const openUrl = () => {
|
const openUrl = () => {
|
||||||
openInNewTab(
|
openInNewTab(
|
||||||
"https://github.com/OpenSignLabs/OpenSign/releases/tag/" + version
|
"https://github.com/OpenSignLabs/OpenSign/releases/tag/" + version
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import dp from "../assets/images/dp.png";
|
|
||||||
import FullScreenButton from "./FullScreenButton";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import Parse from "parse";
|
|
||||||
import { useWindowSize } from "../hook/useWindowSize";
|
|
||||||
import {
|
|
||||||
checkIsSubscribed,
|
|
||||||
getAppLogo,
|
|
||||||
openInNewTab,
|
|
||||||
saveLanguageInLocal
|
|
||||||
} from "../constant/Utils";
|
|
||||||
import { isEnableSubscription, isStaging } from "../constant/const";
|
|
||||||
import { validplan } from "../json/plansArr";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import ModalUi from "../primitives/ModalUi";
|
|
||||||
import QuotaCard from "../primitives/QuotaCard";
|
|
||||||
|
|
||||||
const Header = ({ showSidebar }) => {
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { width } = useWindowSize();
|
|
||||||
const username = localStorage.getItem("username") || "";
|
|
||||||
const image = localStorage.getItem("profileImg") || dp;
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [isSubscribe, setIsSubscribe] = useState(true);
|
|
||||||
const [isTeam, setIsTeam] = useState({ plan: "", isValid: false });
|
|
||||||
const [applogo, setAppLogo] = useState(
|
|
||||||
localStorage.getItem("appLogo") || " "
|
|
||||||
);
|
|
||||||
const [emailUsed, setEmailUsed] = useState(0);
|
|
||||||
const [isModal, setIsModal] = useState(false);
|
|
||||||
|
|
||||||
const toggleDropdown = () => {
|
|
||||||
setIsOpen(!isOpen);
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
|
||||||
checkSubscription();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
async function checkSubscription() {
|
|
||||||
if (isEnableSubscription) {
|
|
||||||
const subscribe = await checkIsSubscribed();
|
|
||||||
if (subscribe.isValid) {
|
|
||||||
const applogo = await getAppLogo();
|
|
||||||
if (applogo?.logo) {
|
|
||||||
setAppLogo(applogo?.logo);
|
|
||||||
} else {
|
|
||||||
setAppLogo(localStorage.getItem("appLogo") || "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setIsTeam(subscribe);
|
|
||||||
setIsSubscribe(subscribe.isValid);
|
|
||||||
try {
|
|
||||||
const extUser = await Parse.Cloud.run("getUserDetails");
|
|
||||||
const MonthlyFreeEmails = extUser?.get("MonthlyFreeEmails") || 0;
|
|
||||||
setEmailUsed(MonthlyFreeEmails);
|
|
||||||
} catch (err) {
|
|
||||||
console.log("err in while fetching monthlyfreeEmails", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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]);
|
|
||||||
const handleConsoleRedirect = () => {
|
|
||||||
if (isStaging) {
|
|
||||||
window.open(" https://staging-console.opensignlabs.com/");
|
|
||||||
} else {
|
|
||||||
window.open("https://console.opensignlabs.com/");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleNavigation = () => {
|
|
||||||
navigate("/subscription");
|
|
||||||
};
|
|
||||||
const handleMailUsed = () => {
|
|
||||||
setIsModal(!isModal);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{isEnableSubscription && (
|
|
||||||
<div className="shadow py-1 text-center bg-[#CAE4FA] text-[14px] p-1">
|
|
||||||
{t("header-news")} —
|
|
||||||
<span
|
|
||||||
className="cursor-pointer font-medium underline text-blue-800"
|
|
||||||
onClick={() => navigate("/profile")}
|
|
||||||
>
|
|
||||||
{" " + t("header-news-btn") + "."}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="op-navbar bg-base-100 shadow">
|
|
||||||
<div className="flex-none">
|
|
||||||
<button
|
|
||||||
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
|
|
||||||
onClick={showSidebar}
|
|
||||||
>
|
|
||||||
<i className="fa-light fa-bars text-xl text-base-content"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 ml-2">
|
|
||||||
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
|
||||||
<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={() => handleNavigation()}
|
|
||||||
>
|
|
||||||
{t("upgrade-now")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isTeam.isValid && !validplan[isTeam.plan] && (
|
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
{validplan[isTeam.plan] && (
|
|
||||||
<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>
|
|
||||||
{isEnableSubscription && (
|
|
||||||
<li onClick={() => handleConsoleRedirect()}>
|
|
||||||
<span>
|
|
||||||
<i className="fa-light fa-id-card"></i> Console
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
{isEnableSubscription && isTeam?.plan === "freeplan" && (
|
|
||||||
<li className="cursor-pointer" onClick={handleMailUsed}>
|
|
||||||
<span>
|
|
||||||
<i className="fa-light fa-envelope"></i>
|
|
||||||
{emailUsed}/15 {t("sent-this-month")}
|
|
||||||
</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>
|
|
||||||
<ModalUi isOpen={isModal}>
|
|
||||||
<QuotaCard isPaidInfo={true} handlClose={handleMailUsed} />
|
|
||||||
</ModalUi>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Header;
|
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
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";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { toggleSidebar } from "../redux/reducers/sidebarReducer.js";
|
||||||
|
|
||||||
|
const Header = ({ isConsole, setIsLoggingOut }) => {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { width } = useWindowSize();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const username = localStorage.getItem("username") || "";
|
||||||
|
const image = localStorage.getItem("profileImg") || dp;
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [applogo, setAppLogo] = useState("");
|
||||||
|
const [isDarkTheme, setIsDarkTheme] = useState();
|
||||||
|
|
||||||
|
const toggleDropdown = () => {
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
closeSidebar();
|
||||||
|
};
|
||||||
|
const closeSidebar = () => {
|
||||||
|
if (width && width <= 768) {
|
||||||
|
dispatch(toggleSidebar(false));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initializeHead();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
closeSidebar();
|
||||||
|
}, [width]);
|
||||||
|
|
||||||
|
const showSidebar = () => {
|
||||||
|
dispatch(toggleSidebar());
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
async function initializeHead() {
|
||||||
|
const applogo = await getAppLogo();
|
||||||
|
if (applogo?.logo) {
|
||||||
|
setAppLogo(applogo?.logo);
|
||||||
|
} else {
|
||||||
|
const logo = localStorage.getItem("appLogo") || appInfo.applogo;
|
||||||
|
setAppLogo(logo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
setIsLoggingOut(true);
|
||||||
|
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");
|
||||||
|
let favicon = localStorage.getItem("favicon");
|
||||||
|
|
||||||
|
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);
|
||||||
|
localStorage.setItem("favicon", favicon);
|
||||||
|
setIsLoggingOut(false);
|
||||||
|
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 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={handleLogout}>
|
||||||
|
<span>
|
||||||
|
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
|
||||||
|
{t("log-out")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</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,280 +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 () => {
|
|
||||||
try {
|
|
||||||
await Parse.User.logOut();
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Err while logging out", err);
|
|
||||||
}
|
|
||||||
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 = async () => {
|
|
||||||
setIsModal(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);
|
|
||||||
};
|
|
||||||
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 React from "react";
|
||||||
import { Document, Page } from "react-pdf";
|
import { Document, Page } from "react-pdf";
|
||||||
import { Stage, Layer, Rect, Text } from "react-konva";
|
import { Stage, Layer, Rect, Text } from "react-konva";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
const RenderDebugPdf = (props) => {
|
const RenderDebugPdf = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="sticky top-0 p-[10px] z-10 bg-white border-[1px] border-[gray] my-[5px]">
|
<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}
|
onMouseMove={props.handleMouseMoveDiv}
|
||||||
>
|
>
|
||||||
<Document
|
<Document
|
||||||
onLoadError={() => {
|
onLoadError={() => props.setPdfLoadFail(false)}
|
||||||
props.setPdfLoadFail(false);
|
loading={t("loading-doc")}
|
||||||
}}
|
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||||
loading={"Loading Document.."}
|
|
||||||
onLoadSuccess={props.pageDetails}
|
onLoadSuccess={props.pageDetails}
|
||||||
ref={props.pdfRef}
|
ref={props.pdfRef}
|
||||||
file={props.pdfUrl}
|
file={props.pdfUrl}
|
||||||
@@ -6,22 +6,14 @@ function RotateAlert(props) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<ModalUi
|
<ModalUi
|
||||||
isOpen={props.isRotate}
|
isOpen={props.showRotateAlert}
|
||||||
title={t("Rotation-alert")}
|
title={t("Rotation-alert")}
|
||||||
handleClose={() => props.setIsRotate({ status: false, degree: 0 })}
|
handleClose={() => props.setShowRotateAlert({ status: false, degree: 0 })}
|
||||||
>
|
>
|
||||||
{" "}
|
|
||||||
<div className="p-[20px] h-full">
|
<div className="p-[20px] h-full">
|
||||||
<p>{t("rotate-alert-mssg")}</p>
|
<p>{t("rotate-alert-mssg")}</p>
|
||||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<button
|
|
||||||
onClick={() => props.setIsRotate({ status: false, degree: 0 })}
|
|
||||||
type="button"
|
|
||||||
className="op-btn op-btn-ghost shadow-md"
|
|
||||||
>
|
|
||||||
{t("no")}
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => props.handleRemoveWidgets()}
|
onClick={() => props.handleRemoveWidgets()}
|
||||||
type="button"
|
type="button"
|
||||||
@@ -29,6 +21,15 @@ function RotateAlert(props) {
|
|||||||
>
|
>
|
||||||
{t("yes")}
|
{t("yes")}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
props.setShowRotateAlert({ status: false, degree: 0 })
|
||||||
|
}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost text-base-content shadow-md"
|
||||||
|
>
|
||||||
|
{t("no")}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalUi>
|
</ModalUi>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router";
|
||||||
|
|
||||||
const SocialMedia = () => {
|
const SocialMedia = () => {
|
||||||
const { t } = useTranslation();
|
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,19 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Helmet } from "react-helmet";
|
|
||||||
|
|
||||||
function Title({ title, drive }) {
|
|
||||||
return (
|
|
||||||
<Helmet>
|
|
||||||
<title>{drive ? title : `${title} - OpenSign™`}</title>
|
|
||||||
<meta name="description" content={`${title} - OpenSign™`} />
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
href={localStorage.getItem("fev_Icon")}
|
|
||||||
sizes="40x40"
|
|
||||||
/>
|
|
||||||
</Helmet>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Title;
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useLocation, matchPath } from "react-router";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { useManifestUrl } from "../hook/useManifestUrl";
|
||||||
|
|
||||||
|
const TITLE_MAP = {
|
||||||
|
"/": "login",
|
||||||
|
// Homelayout
|
||||||
|
"/dashboard/35KBoSgoAK": "Dashboard",
|
||||||
|
"/form/sHAnZphf69": "Sign Yourself",
|
||||||
|
"/form/8mZzFxbG1z": "Request Signatures",
|
||||||
|
"/form/template": "New Template",
|
||||||
|
"/report/6TeaPr321t": "Templates",
|
||||||
|
"/report/4Hhwbp482K": "Need your sign",
|
||||||
|
"/report/1MwEuxLEkF": "In Progress",
|
||||||
|
"/report/kQUoW4hUXz": "Completed",
|
||||||
|
"/report/ByHuevtCFY": "Drafts",
|
||||||
|
"/report/UPr2Fm5WY3": "Declined",
|
||||||
|
"/report/zNqBHXHsYH": "Expired",
|
||||||
|
"/report/contacts": "Contactbook",
|
||||||
|
"/drive": "Drive",
|
||||||
|
"/managesign": "My Signature",
|
||||||
|
"/preferences": "Preferences",
|
||||||
|
"/users": "Users",
|
||||||
|
"/profile": "profile",
|
||||||
|
"/changepassword": "change-password",
|
||||||
|
"/verify-document": "verify-document",
|
||||||
|
|
||||||
|
"/signaturePdf/:docId": "Sign Yourself",
|
||||||
|
"/placeHolderSign/:docId": "Request Signatures",
|
||||||
|
"/template/:templateId": "New Template",
|
||||||
|
"/recipientSignPdf/:docId": "Request Signatures",
|
||||||
|
"/recipientSignPdf/:docId/:contactBookId": "Request Signatures",
|
||||||
|
"/load/recipientSignPdf/:docId/:contactBookId": "Request Signatures",
|
||||||
|
|
||||||
|
// alone
|
||||||
|
"/debugpdf": "Debug Pdf",
|
||||||
|
"/forgetpassword": "forgot-password",
|
||||||
|
"/success": "success",
|
||||||
|
"/addadmin": "add-admin",
|
||||||
|
"/upgrade-2.1": "add-admin",
|
||||||
|
"/draftDocument": "New Document",
|
||||||
|
"/login/:base64url": "Request Signatures",
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveTitle(pathname, override) {
|
||||||
|
if (override) return override;
|
||||||
|
for (let [pattern, label] of Object.entries(TITLE_MAP)) {
|
||||||
|
if (matchPath({ path: pattern, end: true }, pathname)) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Title() {
|
||||||
|
const { pathname, state } = useLocation();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const appName =
|
||||||
|
"OpenSign™";
|
||||||
|
const logo = useMemo(() => localStorage.getItem("favicon"), []);
|
||||||
|
const prefix = useMemo(
|
||||||
|
() => resolveTitle(pathname, state?.title),
|
||||||
|
[pathname, state?.title]
|
||||||
|
);
|
||||||
|
const title = useMemo(
|
||||||
|
() => (prefix ? `${t(prefix)} - ${appName}` : appName),
|
||||||
|
[t, prefix, appName]
|
||||||
|
);
|
||||||
|
const manifestUrl = useManifestUrl(appName, logo);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Helmet>
|
||||||
|
<title>{title}</title>
|
||||||
|
<meta name="description" content={title} />
|
||||||
|
{logo && <link rel="icon" type="image/png" href={logo} />}
|
||||||
|
<link rel="manifest" href={manifestUrl} />
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router";
|
||||||
import { openInNewTab } from "../../constant/Utils";
|
import { openInNewTab } from "../../constant/Utils";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ const DashboardButton = (props) => {
|
|||||||
: "cursor-default"
|
: "cursor-default"
|
||||||
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
|
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
|
||||||
>
|
>
|
||||||
|
<div className="flex flex-row items-center text-base-content">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
|
||||||
<i
|
<i
|
||||||
@@ -37,8 +38,19 @@ const DashboardButton = (props) => {
|
|||||||
} text-[25px] lg:text-[30px]`}
|
} text-[25px] lg:text-[30px]`}
|
||||||
></i>
|
></i>
|
||||||
</span>
|
</span>
|
||||||
<div className=" text-lg ml-3 text-base-content">
|
</div>
|
||||||
|
<div className="text-lg ml-3">
|
||||||
{t(`sidebar.${props.Label}`)}
|
{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
|
import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router";
|
||||||
import Tooltip from "../../primitives/Tooltip";
|
import Tooltip from "../../primitives/Tooltip";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ const DashboardCard = (props) => {
|
|||||||
skip: 0,
|
skip: 0,
|
||||||
limit: 200
|
limit: 200
|
||||||
};
|
};
|
||||||
const url = `${parseBaseUrl}/functions/getReport`;
|
const url = `${parseBaseUrl}functions/getReport`;
|
||||||
await axios
|
await axios
|
||||||
.post(url, params, {
|
.post(url, params, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -148,7 +148,7 @@ const DashboardCard = (props) => {
|
|||||||
);
|
);
|
||||||
let arr = [];
|
let arr = [];
|
||||||
for (const obj of listData) {
|
for (const obj of listData) {
|
||||||
const isSigner = obj.Signers.some(
|
const isSigner = obj.Signers?.some(
|
||||||
(item) => item.UserId.objectId === currentUser.id
|
(item) => item.UserId.objectId === currentUser.id
|
||||||
);
|
);
|
||||||
if (isSigner) {
|
if (isSigner) {
|
||||||
@@ -168,25 +168,21 @@ const DashboardCard = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setresponse(arr.length);
|
setresponse(arr.length);
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await axios.get(url, { headers: headers }).then((res) => {
|
await axios.get(url, { headers: headers }).then((res) => {
|
||||||
if (res.data.results.length > 0) {
|
if (res?.data?.[props.Data.key]) {
|
||||||
setLoading(false);
|
setresponse(parseInt(res.data[props.Data.key]));
|
||||||
if (props.Data.key !== "count") {
|
} else if (res?.data?.results?.length > 0) {
|
||||||
setresponse(res.data.results[0][props.Data.key]);
|
setresponse(res.data.results.length);
|
||||||
} else {
|
|
||||||
setresponse(res.data[props.Data.key]);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setresponse(0);
|
setresponse(0);
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Problem", e);
|
console.error("Problem", e);
|
||||||
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,7 +342,6 @@ const DashboardCard = (props) => {
|
|||||||
id={props.Label}
|
id={props.Label}
|
||||||
iconColor={"white"}
|
iconColor={"white"}
|
||||||
message={t(`tour-mssg.${props.Label}`)}
|
message={t(`tour-mssg.${props.Label}`)}
|
||||||
// {props?.Data?.tourMessage}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import Parse from "parse";
|
import Parse from "parse";
|
||||||
import ReportTable from "../../primitives/GetReportDisplay";
|
import DocumentsReport from "../../reports/document/DocumentsReport";
|
||||||
import reportJson from "../../json/ReportJson";
|
import reportJson from "../../json/ReportJson";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Loader from "../../primitives/Loader";
|
import Loader from "../../primitives/Loader";
|
||||||
@@ -17,10 +17,16 @@ function DashboardReport(props) {
|
|||||||
const [isMoreDocs, setIsMoreDocs] = useState(true);
|
const [isMoreDocs, setIsMoreDocs] = useState(true);
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const docPerPage = 5;
|
const docPerPage = 5;
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||||
|
const [isSearchResult, setIsSearchResult] = useState(false);
|
||||||
|
const debounceTimer = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setReportName("");
|
setReportName("");
|
||||||
getReportData(props.Record.reportId);
|
setSearchTerm("");
|
||||||
|
setMobileSearchOpen(false);
|
||||||
|
getReportData(props.Record.reportId, 0, 20, "");
|
||||||
|
|
||||||
// Function returned from useEffect is called on unmount
|
// Function returned from useEffect is called on unmount
|
||||||
return () => {
|
return () => {
|
||||||
@@ -36,12 +42,69 @@ function DashboardReport(props) {
|
|||||||
// below useEffect call when isNextRecord state is true and fetch next record
|
// below useEffect call when isNextRecord state is true and fetch next record
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isNextRecord) {
|
if (isNextRecord) {
|
||||||
getReportData(props.Record.reportId, List.length, 200);
|
getReportData(props.Record.reportId, List.length, 20, searchTerm);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [isNextRecord]);
|
}, [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);
|
setIsLoader(true);
|
||||||
const json = reportJson(id);
|
const json = reportJson(id);
|
||||||
if (json) {
|
if (json) {
|
||||||
@@ -56,8 +119,13 @@ function DashboardReport(props) {
|
|||||||
sessiontoken: localStorage.getItem("accesstoken")
|
sessiontoken: localStorage.getItem("accesstoken")
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const params = { reportId: id, skip: skipUserRecord, limit: limit };
|
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
|
||||||
const url = `${localStorage.getItem("baseUrl")}/functions/getReport`;
|
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
|
||||||
|
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||||
|
if (term) {
|
||||||
|
params.searchTerm = term;
|
||||||
|
}
|
||||||
|
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||||
const res = await axios.post(url, params, {
|
const res = await axios.post(url, params, {
|
||||||
headers: headers,
|
headers: headers,
|
||||||
signal: abortController.signal // is used to cancel fetch query
|
signal: abortController.signal // is used to cancel fetch query
|
||||||
@@ -66,7 +134,7 @@ function DashboardReport(props) {
|
|||||||
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
|
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
|
||||||
let arr = [];
|
let arr = [];
|
||||||
for (const obj of listData) {
|
for (const obj of listData) {
|
||||||
const isSigner = obj.Signers.some(
|
const isSigner = obj.Signers?.some(
|
||||||
(item) => item.UserId.objectId === currentUser
|
(item) => item.UserId.objectId === currentUser
|
||||||
);
|
);
|
||||||
if (isSigner) {
|
if (isSigner) {
|
||||||
@@ -94,7 +162,7 @@ function DashboardReport(props) {
|
|||||||
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (res.data.result.length === docPerPage) {
|
if (res.data.result.length >= docPerPage) {
|
||||||
setIsMoreDocs(true);
|
setIsMoreDocs(true);
|
||||||
} else {
|
} else {
|
||||||
setIsMoreDocs(false);
|
setIsMoreDocs(false);
|
||||||
@@ -130,7 +198,7 @@ function DashboardReport(props) {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{reportName ? (
|
{reportName ? (
|
||||||
<ReportTable
|
<DocumentsReport
|
||||||
ReportName={reportName}
|
ReportName={reportName}
|
||||||
List={List}
|
List={List}
|
||||||
setList={setList}
|
setList={setList}
|
||||||
@@ -139,11 +207,17 @@ function DashboardReport(props) {
|
|||||||
setIsNextRecord={setIsNextRecord}
|
setIsNextRecord={setIsNextRecord}
|
||||||
isMoreDocs={isMoreDocs}
|
isMoreDocs={isMoreDocs}
|
||||||
docPerPage={docPerPage}
|
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="flex items-center justify-center h-[100px] w-full bg-white rounded-box">
|
||||||
<div className="text-center">
|
<div className="text-center text-xl text-base-content">
|
||||||
<p className="text-xl text-black">{t("report-not-found")}</p>
|
{t("report-not-found")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { Suspense, lazy } from "react";
|
import React, { Suspense } from "react";
|
||||||
|
import { lazyWithRetry } from "../../utils";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
const DashboardButton = lazy(() => import("./DashboardButton"));
|
const DashboardButton = lazyWithRetry(() => import("./DashboardButton"));
|
||||||
const DashboardCard = lazy(() => import("./DashboardCard"));
|
const DashboardCard = lazyWithRetry(() => import("./DashboardCard"));
|
||||||
const DashboardReport = lazy(() => import("./DashboardReport"));
|
const DashboardReport = lazyWithRetry(() => import("./DashboardReport"));
|
||||||
const buttonList = [
|
const buttonList = [
|
||||||
{
|
{
|
||||||
label: "Sign yourself",
|
label: "Sign yourself",
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import "../../styles/opensigndrive.css";
|
import "../../styles/opensigndrive.css";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import * as ContextMenu from "@radix-ui/react-context-menu";
|
import { ContextMenu } from "radix-ui";
|
||||||
import { saveAs } from "file-saver";
|
import { useNavigate } from "react-router";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import Table from "react-bootstrap/Table";
|
import Table from "react-bootstrap/Table";
|
||||||
import * as HoverCard from "@radix-ui/react-hover-card";
|
import { HoverCard } from "radix-ui";
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
import FolderModal from "../shared/fields/FolderModal";
|
import FolderModal from "../shared/fields/FolderModal";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { handleDownloadPdf, isMobile } from "../../constant/Utils";
|
||||||
|
import Parse from "parse";
|
||||||
|
|
||||||
function DriveBody(props) {
|
function DriveBody(props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -17,7 +18,7 @@ function DriveBody(props) {
|
|||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
const [isOpenMoveModal, setIsOpenMoveModal] = useState(false);
|
const [isOpenMoveModal, setIsOpenMoveModal] = useState(false);
|
||||||
const [selectDoc, setSelectDoc] = useState();
|
const [selectDoc, setSelectDoc] = useState();
|
||||||
const [isDeleteDoc, setIsDeleteDoc] = useState(false);
|
const [isDeleteDoc, setIsDeleteDoc] = useState({});
|
||||||
const contextMenu = [
|
const contextMenu = [
|
||||||
{ type: "Download", icon: "fa-light fa-arrow-down" },
|
{ type: "Download", icon: "fa-light fa-arrow-down" },
|
||||||
{ type: "Rename", icon: "fa-light fa-font" },
|
{ type: "Rename", icon: "fa-light fa-font" },
|
||||||
@@ -100,16 +101,17 @@ function DriveBody(props) {
|
|||||||
|
|
||||||
//function for navigate user to microapp-signature component
|
//function for navigate user to microapp-signature component
|
||||||
const checkPdfStatus = async (data) => {
|
const checkPdfStatus = async (data) => {
|
||||||
const signerExist = data.Signers && data.Signers;
|
const signerExist = data?.Signers;
|
||||||
const isDecline = data.IsDeclined && data.IsDeclined;
|
const isDecline = data?.IsDeclined;
|
||||||
const isPlaceholder = data.Placeholders && data.Placeholders;
|
const isPlaceholder = data?.Placeholders;
|
||||||
const signedUrl = data.SignedUrl;
|
const signedUrl = data?.SignedUrl;
|
||||||
|
const isSignYourself = data?.IsSignyourself;
|
||||||
//checking if document has completed and request signature flow
|
//checking if document has completed and request signature flow
|
||||||
if (data?.IsCompleted && signerExist?.length > 0) {
|
if (data?.IsCompleted && signerExist?.length > 0) {
|
||||||
navigate(`/recipientSignPdf/${data.objectId}`);
|
navigate(`/recipientSignPdf/${data.objectId}`);
|
||||||
}
|
}
|
||||||
//checking if document has completed and signyour-self flow
|
//checking if document has completed and signyour-self flow
|
||||||
else if (!signerExist && !isPlaceholder) {
|
else if ((!signerExist && !isPlaceholder) || isSignYourself) {
|
||||||
navigate(`/signaturePdf/${data.objectId}`);
|
navigate(`/signaturePdf/${data.objectId}`);
|
||||||
}
|
}
|
||||||
//checking if document has declined by someone
|
//checking if document has declined by someone
|
||||||
@@ -136,12 +138,10 @@ function DriveBody(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMenuItemClick = (selectType, data) => {
|
const handleMenuItemClick = async (selectType, data, deleteType) => {
|
||||||
switch (selectType) {
|
switch (selectType) {
|
||||||
case "Download": {
|
case "Download": {
|
||||||
const pdfName = data && data.Name;
|
await handleDownloadPdf([data]);
|
||||||
const pdfUrl = data && data.SignedUrl ? data.SignedUrl : data.URL;
|
|
||||||
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Rename": {
|
case "Rename": {
|
||||||
@@ -150,7 +150,7 @@ function DriveBody(props) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Delete": {
|
case "Delete": {
|
||||||
setIsDeleteDoc(true);
|
setIsDeleteDoc({ status: true, deleteType });
|
||||||
setSelectDoc(data);
|
setSelectDoc(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ function DriveBody(props) {
|
|||||||
};
|
};
|
||||||
//function for delete document
|
//function for delete document
|
||||||
const handleDeleteDocument = async (docData) => {
|
const handleDeleteDocument = async (docData) => {
|
||||||
setIsDeleteDoc(false);
|
setIsDeleteDoc({});
|
||||||
const docId = docData.objectId;
|
const docId = docData.objectId;
|
||||||
const data = {
|
const data = {
|
||||||
IsArchive: true
|
IsArchive: true
|
||||||
@@ -264,18 +264,56 @@ function DriveBody(props) {
|
|||||||
setIsOpenMoveModal(false);
|
setIsOpenMoveModal(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sanitizeFileName = (pdfName) => {
|
|
||||||
// Replace spaces with underscore
|
|
||||||
return pdfName.replace(/ /g, "_");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEnterPress = (e, data) => {
|
const handleEnterPress = (e, data) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
handledRenameDoc(data);
|
handledRenameDoc(data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkFolderEmpty = async (docData) => {
|
||||||
|
let isEmptyFolder = true;
|
||||||
|
const query = new Parse.Query("contracts_Document");
|
||||||
|
query.equalTo("Folder", {
|
||||||
|
__type: "Pointer",
|
||||||
|
className: "contracts_Document",
|
||||||
|
objectId: docData.objectId
|
||||||
|
});
|
||||||
|
query.notEqualTo("IsArchive", true);
|
||||||
|
const res = await query.find();
|
||||||
|
const jsonRes = JSON.parse(JSON.stringify(res));
|
||||||
|
if (jsonRes && jsonRes.length > 0) {
|
||||||
|
isEmptyFolder = false;
|
||||||
|
return isEmptyFolder;
|
||||||
|
} else {
|
||||||
|
return isEmptyFolder;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDeleteFolder = async (docData) => {
|
||||||
|
setIsDeleteDoc({});
|
||||||
|
const isEmptyFolder = await checkFolderEmpty(docData);
|
||||||
|
if (isEmptyFolder) {
|
||||||
|
const docId = docData?.objectId;
|
||||||
|
try {
|
||||||
|
const updateQuery = new Parse.Query("contracts_Document");
|
||||||
|
const updateObj = await updateQuery.get(docId);
|
||||||
|
updateObj.set("IsArchive", true);
|
||||||
|
const res = await updateObj.save();
|
||||||
|
if (res) {
|
||||||
|
const updatedData = props.pdfData.filter((x) => x.objectId !== docId);
|
||||||
|
props.setPdfData(updatedData);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Err ", err);
|
||||||
|
props.setIsAlert({
|
||||||
|
isShow: true,
|
||||||
|
alertMessage: t("something-went-wrong-mssg")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(t("delete-folder-alert-1"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//component to handle type of document and render according to type
|
//component to handle type of document and render according to type
|
||||||
const handleFolderData = (data, ind, listType) => {
|
const handleFolderData = (data, ind, listType) => {
|
||||||
let createddate, status, isDecline, signerExist, isComplete;
|
let createddate, status, isDecline, signerExist, isComplete;
|
||||||
@@ -309,15 +347,18 @@ function DriveBody(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const signersName = () => {
|
const signersName = () => {
|
||||||
const getSignersName = signerExist.map((data) => data.Name);
|
const getSignersName =
|
||||||
const signerName = getSignersName.join(", ");
|
signerExist?.length > 0 && signerExist?.map((data) => data?.Name || "");
|
||||||
|
const signerName =
|
||||||
|
getSignersName?.length > 0 ? getSignersName?.join(", ") : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="text-[12px] font-medium w-[90%] break-words">
|
<span className="text-[12px] font-medium w-[90%] break-words">
|
||||||
{signerName}
|
{signerName && signerName}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return listType === "table" ? (
|
return listType === "table" ? (
|
||||||
data.Type === "Folder" ? (
|
data.Type === "Folder" ? (
|
||||||
<tr onClick={() => handleOnclikFolder(data)}>
|
<tr onClick={() => handleOnclikFolder(data)}>
|
||||||
@@ -325,14 +366,14 @@ function DriveBody(props) {
|
|||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 512 512"
|
||||||
className="w-[26px] h-[26px] fill-current op-text-secondary"
|
className="w-[26px] h-[26px] fill-current"
|
||||||
>
|
>
|
||||||
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
|
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-[12px] font-medium">{data.Name}</span>
|
<span className="text-[12px] font-medium">{data.Name}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>_</td>
|
<td>_</td>
|
||||||
<td>Folder</td>
|
<td>{t("folder")}</td>
|
||||||
<td>_</td>
|
<td>_</td>
|
||||||
<td>_</td>
|
<td>_</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -349,7 +390,7 @@ function DriveBody(props) {
|
|||||||
<span className="text-[12px] font-medium">{data.Name}</span>
|
<span className="text-[12px] font-medium">{data.Name}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{createddate}</td>
|
<td>{createddate}</td>
|
||||||
<td>Pdf</td>
|
<td>{t("pdf")}</td>
|
||||||
<td>{t(`drive-document-status.${status}`)}</td>
|
<td>{t(`drive-document-status.${status}`)}</td>
|
||||||
<td>
|
<td>
|
||||||
<i
|
<i
|
||||||
@@ -364,9 +405,9 @@ function DriveBody(props) {
|
|||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
) : listType === "list" && data.Type === "Folder" ? (
|
) : 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.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 */}
|
{/* folder */}
|
||||||
<div
|
<div
|
||||||
data-tut={props.dataTutSeventh}
|
data-tut={props.dataTutSeventh}
|
||||||
@@ -380,7 +421,7 @@ function DriveBody(props) {
|
|||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 512 512"
|
||||||
className="w-[100px] h-[100px] fill-current op-text-secondary"
|
className="w-[100px] h-[100px] fill-current"
|
||||||
>
|
>
|
||||||
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
|
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -402,11 +443,10 @@ function DriveBody(props) {
|
|||||||
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content 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>
|
</div>
|
||||||
</ContextMenu.Trigger>
|
</ContextMenu.Trigger>
|
||||||
|
|
||||||
<ContextMenu.Portal>
|
<ContextMenu.Portal>
|
||||||
<ContextMenu.Content
|
<ContextMenu.Content
|
||||||
className="ContextMenuContent"
|
className="ContextMenuContent"
|
||||||
@@ -418,7 +458,14 @@ function DriveBody(props) {
|
|||||||
className="ContextMenuItem"
|
className="ContextMenuItem"
|
||||||
>
|
>
|
||||||
<i className="fa-light fa-font mr-[8px]"></i>
|
<i className="fa-light fa-font mr-[8px]"></i>
|
||||||
<span>Rename</span>
|
<span>{t(`context-menu.Rename`)}</span>
|
||||||
|
</ContextMenu.Item>
|
||||||
|
<ContextMenu.Item
|
||||||
|
onClick={() => handleMenuItemClick("Delete", data, data.Type)}
|
||||||
|
className="ContextMenuItem"
|
||||||
|
>
|
||||||
|
<i className="fa-light fa-trash mr-[8px]"></i>
|
||||||
|
<span>{t(`context-menu.Delete`)}</span>
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
</ContextMenu.Portal>
|
</ContextMenu.Portal>
|
||||||
@@ -426,7 +473,7 @@ function DriveBody(props) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<HoverCard.Root
|
<HoverCard.Root
|
||||||
open={rename ? false : undefined}
|
open={rename || isMobile ? false : undefined}
|
||||||
openDelay={0}
|
openDelay={0}
|
||||||
closeDelay={100}
|
closeDelay={100}
|
||||||
>
|
>
|
||||||
@@ -434,7 +481,10 @@ function DriveBody(props) {
|
|||||||
<div>
|
<div>
|
||||||
<ContextMenu.Root>
|
<ContextMenu.Root>
|
||||||
<div className="relative w-[100px] h-[100px] mx-2 my-3">
|
<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 */}
|
{/* pdf */}
|
||||||
<div
|
<div
|
||||||
data-tut={props.dataTutSixth}
|
data-tut={props.dataTutSixth}
|
||||||
@@ -463,14 +513,16 @@ function DriveBody(props) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onBlur={() => handledRenameDoc(data)}
|
onBlur={() => handledRenameDoc(data)}
|
||||||
onKeyDown={(e) => handleEnterPress(e, data)}
|
onKeyDown={(e) => handleEnterPress(e, data, data.Type)}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
defaultValue={renameValue}
|
defaultValue={renameValue}
|
||||||
onChange={(e) => setRenameValue(e.target.value)}
|
onChange={(e) => setRenameValue(e.target.value)}
|
||||||
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
|
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>
|
</div>
|
||||||
</ContextMenu.Trigger>
|
</ContextMenu.Trigger>
|
||||||
@@ -498,7 +550,6 @@ function DriveBody(props) {
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ContextMenu.Portal>
|
<ContextMenu.Portal>
|
||||||
<ContextMenu.Content
|
<ContextMenu.Content
|
||||||
className="ContextMenuContent"
|
className="ContextMenuContent"
|
||||||
@@ -551,7 +602,6 @@ function DriveBody(props) {
|
|||||||
{signersName()}
|
{signersName()}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<HoverCard.Arrow className="HoverCardArrow" />
|
<HoverCard.Arrow className="HoverCardArrow" />
|
||||||
</HoverCard.Content>
|
</HoverCard.Content>
|
||||||
</HoverCard.Portal>
|
</HoverCard.Portal>
|
||||||
@@ -575,21 +625,21 @@ function DriveBody(props) {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{props.pdfData.map((data, ind) => {
|
{props?.pdfData?.map((data, ind) => (
|
||||||
return (
|
|
||||||
<React.Fragment key={ind}>
|
<React.Fragment key={ind}>
|
||||||
{handleFolderData(data, ind, "table")}
|
{handleFolderData(data, ind, "table")}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
))}
|
||||||
})}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
|
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
|
||||||
{props.pdfData.map((data, ind) => {
|
{props?.pdfData?.map((data, ind) => (
|
||||||
return <div key={ind}>{handleFolderData(data, ind, "list")}</div>;
|
<React.Fragment key={ind}>
|
||||||
})}
|
{handleFolderData(data, ind, "list")}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -603,22 +653,33 @@ function DriveBody(props) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ModalUi
|
<ModalUi
|
||||||
isOpen={isDeleteDoc}
|
isOpen={isDeleteDoc.status}
|
||||||
title={t("delete-document")}
|
title={t("delete-document")}
|
||||||
handleClose={() => setIsDeleteDoc(false)}
|
handleClose={() => setIsDeleteDoc({})}
|
||||||
>
|
>
|
||||||
<div className="h-full p-[20px] text-base-content">
|
<div className="h-full p-[20px] text-base-content">
|
||||||
|
{isDeleteDoc.deleteType ? (
|
||||||
|
<p>{t("delete-folder-alert")}</p>
|
||||||
|
) : (
|
||||||
<p>{t("delete-document-alert")}</p>
|
<p>{t("delete-document-alert")}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="h-[1px] w-full bg-[#9f9f9f] my-[15px]"></div>
|
<div className="h-[1px] w-full bg-[#9f9f9f] my-[15px]"></div>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteDocument(selectDoc)}
|
onClick={() => {
|
||||||
|
if (isDeleteDoc.deleteType) {
|
||||||
|
handleDeleteFolder(selectDoc);
|
||||||
|
} else {
|
||||||
|
handleDeleteDocument(selectDoc);
|
||||||
|
}
|
||||||
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
className="op-btn op-btn-primary mr-2"
|
className="op-btn op-btn-primary mr-2"
|
||||||
>
|
>
|
||||||
{t("yes")}
|
{t("yes")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsDeleteDoc(false)}
|
onClick={() => setIsDeleteDoc({})}
|
||||||
type="button"
|
type="button"
|
||||||
className="op-btn op-btn-neutral"
|
className="op-btn op-btn-neutral"
|
||||||
>
|
>
|
||||||
@@ -10,15 +10,15 @@ const AddRoleModal = (props) => {
|
|||||||
isOpen={props.isModalRole}
|
isOpen={props.isModalRole}
|
||||||
handleClose={props.handleCloseRoleModal}
|
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}>
|
<form className="flex flex-col" onSubmit={props.handleAddRole}>
|
||||||
<input
|
<input
|
||||||
value={props.roleName}
|
value={props.roleName}
|
||||||
onChange={(e) => props.setRoleName(e.target.value)}
|
onChange={(e) => props.setRoleName(e.target.value)}
|
||||||
placeholder={
|
placeholder={
|
||||||
props.signersdata.length > 0
|
props.signersdata.length > 0
|
||||||
? "User " + (props.signersdata.length + 1)
|
? "Role " + (props.signersdata.length + 1)
|
||||||
: "User 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"
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs mt-1"
|
||||||
/>
|
/>
|
||||||
@@ -33,7 +33,7 @@ const AddRoleModal = (props) => {
|
|||||||
<button
|
<button
|
||||||
onClick={props.handleCloseRoleModal}
|
onClick={props.handleCloseRoleModal}
|
||||||
type="button"
|
type="button"
|
||||||
className="op-btn op-btn-ghost ml-2"
|
className="op-btn op-btn-ghost text-base-content ml-2"
|
||||||
>
|
>
|
||||||
{t("close")}
|
{t("close")}
|
||||||
</button>
|
</button>
|
||||||
@@ -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 text-base-content"
|
||||||
|
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;
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { useRef, useState } from "react";
|
||||||
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
|
import { EmailBody } from "./EmailBody";
|
||||||
|
import {
|
||||||
|
contractDocument,
|
||||||
|
sendEmailToSigners
|
||||||
|
} from "../../constant/Utils";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Loader from "../../primitives/Loader";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
|
function CustomizeMail(props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const editorRef = useRef();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const copyUrlRef = useRef(null);
|
||||||
|
const [isCustomize, setIsCustomize] = useState(false);
|
||||||
|
const [isLoader, setIsLoader] = useState(false);
|
||||||
|
|
||||||
|
const handleCloseSendmailModal = () => {
|
||||||
|
if (props?.handleClose) {
|
||||||
|
props?.handleClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
props?.setIsMailModal(false);
|
||||||
|
navigate("/report/1MwEuxLEkF");
|
||||||
|
};
|
||||||
|
const handleOnchangeRequest = () => {
|
||||||
|
if (editorRef.current) {
|
||||||
|
const html = editorRef.current.editor.root.innerHTML;
|
||||||
|
props?.setCustomizeMail((prev) => ({
|
||||||
|
...prev,
|
||||||
|
body: html
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleEmailSendToSigners = async () => {
|
||||||
|
setIsLoader(true);
|
||||||
|
const documentData = await contractDocument(props?.documentId);
|
||||||
|
if (documentData && documentData?.length > 0) {
|
||||||
|
props?.setDocumentDetails(documentData[0]);
|
||||||
|
if (
|
||||||
|
documentData?.[0]?.SendinOrder &&
|
||||||
|
documentData?.[0]?.SendinOrder === true
|
||||||
|
) {
|
||||||
|
const ownerEmail = documentData[0].ExtUserPtr.Email;
|
||||||
|
const ownerDetails = documentData[0].Signers.find(
|
||||||
|
(x) => x.Email === ownerEmail
|
||||||
|
);
|
||||||
|
props?.setCurrUserId(ownerDetails?.objectId);
|
||||||
|
}
|
||||||
|
//function is used to send email to signers for sign the document
|
||||||
|
const mailRes = await sendEmailToSigners(
|
||||||
|
documentData,
|
||||||
|
props?.signerList,
|
||||||
|
props?.customizeMail,
|
||||||
|
props?.defaultMail,
|
||||||
|
isCustomize,
|
||||||
|
);
|
||||||
|
props?.setIsMailModal(false);
|
||||||
|
props?.setIsSend(true);
|
||||||
|
setIsLoader(false);
|
||||||
|
if (mailRes?.status === "success") {
|
||||||
|
props?.setMailStatus("success");
|
||||||
|
} else if (mailRes?.status === "quota-reached") {
|
||||||
|
props?.setMailStatus("quotareached");
|
||||||
|
} else if (mailRes?.status === "daily-quota-reached") {
|
||||||
|
props?.setMailStatus("dailyquotareached");
|
||||||
|
} else {
|
||||||
|
props?.setMailStatus("failed");
|
||||||
|
}
|
||||||
|
// setMailStatus(mail_status);
|
||||||
|
} else {
|
||||||
|
alert("something-went-wrong-mssg");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isLoader ? (
|
||||||
|
<div className="absolute w-full h-full flex justify-center items-center bg-black/30 rounded-box z-30">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ModalUi
|
||||||
|
isOpen={props?.isMailModal}
|
||||||
|
title={t("send-mail")}
|
||||||
|
handleClose={() => handleCloseSendmailModal()}
|
||||||
|
>
|
||||||
|
<div className="max-h-96 overflow-y-scroll scroll-hide p-[20px] text-base-content">
|
||||||
|
{!isCustomize && <span>{t("placeholder-alert-3")}</span>}
|
||||||
|
{
|
||||||
|
isCustomize && (
|
||||||
|
<>
|
||||||
|
<EmailBody
|
||||||
|
editorRef={editorRef}
|
||||||
|
requestBody={props?.customizeMail.body}
|
||||||
|
requestSubject={props?.customizeMail.subject}
|
||||||
|
handleOnchangeRequest={handleOnchangeRequest}
|
||||||
|
setCustomizeMail={props?.setCustomizeMail}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="flex justify-end items-center gap-1 mt-2 op-link op-link-primary"
|
||||||
|
onClick={() => {
|
||||||
|
props?.setCustomizeMail(props?.defaultMail);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{t("reset-to-default")}</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div className="flex flex-row items-center gap-2 md:gap-6 mt-2">
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => handleEmailSendToSigners()}
|
||||||
|
className="op-btn op-btn-primary font-[500] text-sm shadow"
|
||||||
|
>
|
||||||
|
{t("send")}
|
||||||
|
</button>
|
||||||
|
{isCustomize && (
|
||||||
|
<button
|
||||||
|
onClick={() => setIsCustomize(false)}
|
||||||
|
className="op-btn op-btn-ghost font-[500] text-sm"
|
||||||
|
>
|
||||||
|
{t("close")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
!isCustomize && (
|
||||||
|
<span
|
||||||
|
className="op-link op-link-accent text-sm"
|
||||||
|
onClick={() => setIsCustomize(!isCustomize)}
|
||||||
|
>
|
||||||
|
{t("cutomize-email")}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center items-center mt-3">
|
||||||
|
<span className="h-[1px] w-[20%] bg-[#ccc]"></span>
|
||||||
|
<span className="ml-[5px] mr-[5px]">{t("or")}</span>
|
||||||
|
<span className="h-[1px] w-[20%] bg-[#ccc]"></span>
|
||||||
|
</div>
|
||||||
|
<div className="my-3">{props?.handleShareList()}</div>
|
||||||
|
<p id="copyUrl" ref={copyUrlRef} className="hidden"></p>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomizeMail;
|
||||||
@@ -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 LoaderWithMsg from "../../primitives/LoaderWithMsg";
|
||||||
import { contractDocument } from "../../constant/Utils";
|
import { contractDocument } from "../../constant/Utils";
|
||||||
import HandleError from "../../primitives/HandleError";
|
import HandleError from "../../primitives/HandleError";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
function useQuery() {
|
function useQuery() {
|
||||||
return new URLSearchParams(useLocation().search);
|
return new URLSearchParams(useLocation().search);
|
||||||
@@ -30,16 +30,20 @@ function DraftDocument() {
|
|||||||
documentData === "Error: Something went wrong!" ||
|
documentData === "Error: Something went wrong!" ||
|
||||||
(documentData.result && documentData.result.error)
|
(documentData.result && documentData.result.error)
|
||||||
) {
|
) {
|
||||||
|
if (documentData?.result?.error?.includes("deleted")) {
|
||||||
setIsLoading({
|
setIsLoading({
|
||||||
isLoader: false,
|
isLoader: false,
|
||||||
message: "Error: Something went wrong!"
|
message: t("document-deleted")
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setIsLoading({
|
setIsLoading({
|
||||||
isLoader: false,
|
isLoader: false,
|
||||||
message: "No data found!"
|
message: t("something-went-wrong-mssg")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setIsLoading({ isLoader: false, message: t("no-data") });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//check document type and render on signyour self and placeholder route
|
//check document type and render on signyour self and placeholder route
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import { widgets } from "../../constant/Utils";
|
||||||
import { getWidgetType, widgets } from "../../constant/Utils";
|
import getWidgetType from "./getWidgetType";
|
||||||
|
|
||||||
function DragElement(item) {
|
function DragElement(item) {
|
||||||
const getWidgets = widgets;
|
const getWidgets = widgets;
|
||||||
@@ -1,42 +1,43 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { isEnableSubscription } from "../../constant/const";
|
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
import { radioButtonWidget } from "../../constant/Utils";
|
import { radioButtonWidget } from "../../constant/Utils";
|
||||||
import Upgrade from "../../primitives/Upgrade";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||||
|
|
||||||
function DropdownWidgetOption(props) {
|
function DropdownWidgetOption(props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [dropdownOptionList, setDropdownOptionList] = useState([
|
const [dropdownOptionList, setDropdownOptionList] = useState([
|
||||||
"option-1",
|
"Option-1",
|
||||||
"option-2"
|
"Option-2"
|
||||||
]);
|
]);
|
||||||
const [minCount, setMinCount] = useState(0);
|
const [minCount, setMinCount] = useState(0);
|
||||||
const [maxCount, setMaxCount] = useState(0);
|
const [maxCount, setMaxCount] = useState(0);
|
||||||
const [dropdownName, setDropdownName] = useState(props.type);
|
const [dropdownName, setDropdownName] = useState();
|
||||||
const [isReadOnly, setIsReadOnly] = useState(false);
|
const [isReadOnly, setIsReadOnly] = useState(false);
|
||||||
const [isHideLabel, setIsHideLabel] = useState(false);
|
const [isHideLabel, setIsHideLabel] = useState(false);
|
||||||
const [status, setStatus] = useState("required");
|
const [status, setStatus] = useState("required");
|
||||||
const [defaultValue, setDefaultValue] = useState("");
|
const [defaultValue, setDefaultValue] = useState("");
|
||||||
const statusArr = ["required", "optional"];
|
|
||||||
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
|
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
|
||||||
|
const [layout, setLayout] = useState("vertical");
|
||||||
|
const statusArr = ["required", "optional"];
|
||||||
|
const layoutArr = ["vertical", "horizontal"];
|
||||||
|
const isPrefillExist = props?.roleName === "prefill";
|
||||||
|
|
||||||
const resetState = () => {
|
const resetState = () => {
|
||||||
setDropdownOptionList(["option-1", "option-2"]);
|
setDropdownOptionList(["Option-1", "Option-2"]);
|
||||||
setDropdownName(props.type);
|
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
|
||||||
setIsReadOnly(false);
|
setIsReadOnly(false);
|
||||||
setIsHideLabel(false);
|
setIsHideLabel(false);
|
||||||
setMinCount(0);
|
setMinCount(0);
|
||||||
setMaxCount(0);
|
setMaxCount(0);
|
||||||
setDefaultCheckbox([]);
|
setDefaultCheckbox([]);
|
||||||
setDefaultValue("");
|
setDefaultValue("");
|
||||||
|
setLayout("vertical");
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
props.currWidgetsDetails?.options?.name &&
|
props.currWidgetsDetails?.options?.name &&
|
||||||
props.currWidgetsDetails?.options?.values
|
props.currWidgetsDetails?.options?.values?.length > 0
|
||||||
) {
|
) {
|
||||||
setDropdownName(props.currWidgetsDetails?.options?.name);
|
setDropdownName(props.currWidgetsDetails?.options?.name);
|
||||||
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
|
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
|
||||||
@@ -51,6 +52,7 @@ function DropdownWidgetOption(props) {
|
|||||||
setStatus(props.currWidgetsDetails?.options?.status || "required");
|
setStatus(props.currWidgetsDetails?.options?.status || "required");
|
||||||
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
|
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
|
||||||
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
|
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
|
||||||
|
setLayout(props.currWidgetsDetails?.options?.layout || "vertical");
|
||||||
} else {
|
} else {
|
||||||
setStatus("required");
|
setStatus("required");
|
||||||
resetState();
|
resetState();
|
||||||
@@ -86,7 +88,7 @@ function DropdownWidgetOption(props) {
|
|||||||
const deleteOption = true;
|
const deleteOption = true;
|
||||||
const addOption = false;
|
const addOption = false;
|
||||||
const getUpdatedOptions = dropdownOptionList.filter(
|
const getUpdatedOptions = dropdownOptionList.filter(
|
||||||
(data, index) => index !== ind
|
(_, index) => index !== ind
|
||||||
);
|
);
|
||||||
setDropdownOptionList(getUpdatedOptions);
|
setDropdownOptionList(getUpdatedOptions);
|
||||||
props.handleSaveWidgetsOptions(
|
props.handleSaveWidgetsOptions(
|
||||||
@@ -101,11 +103,43 @@ function DropdownWidgetOption(props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveOption = () => {
|
const handleSaveOption = () => {
|
||||||
|
if (["checkbox", radioButtonWidget, "dropdown"].includes(props.type)) {
|
||||||
|
const allUnique =
|
||||||
|
new Set(dropdownOptionList).size === dropdownOptionList.length;
|
||||||
|
if (!allUnique) {
|
||||||
|
alert("Please remove duplicate option");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const defaultData =
|
const defaultData =
|
||||||
defaultCheckbox && defaultCheckbox.length > 0
|
defaultCheckbox && defaultCheckbox.length > 0
|
||||||
? defaultCheckbox
|
? defaultCheckbox
|
||||||
: defaultValue;
|
: 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(
|
props.handleSaveWidgetsOptions(
|
||||||
dropdownName,
|
dropdownName,
|
||||||
dropdownOptionList,
|
dropdownOptionList,
|
||||||
@@ -116,28 +150,12 @@ function DropdownWidgetOption(props) {
|
|||||||
null,
|
null,
|
||||||
status,
|
status,
|
||||||
defaultData,
|
defaultData,
|
||||||
isHideLabel
|
isHideLabel,
|
||||||
|
WidgetLayout
|
||||||
);
|
);
|
||||||
// props.setShowDropdown(false);
|
resetState();
|
||||||
setDropdownOptionList(["option-1", "option-2"]);
|
|
||||||
setDropdownName(props.type);
|
|
||||||
// props.setCurrWidgetsDetails({});
|
|
||||||
setIsReadOnly(false);
|
|
||||||
setIsHideLabel(false);
|
|
||||||
setMinCount(0);
|
|
||||||
setMaxCount(0);
|
|
||||||
setDefaultCheckbox([]);
|
|
||||||
setDefaultValue("");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetMinMax = (e) => {
|
|
||||||
const minValue = e.target.value;
|
|
||||||
if (minValue > dropdownOptionList.length) {
|
|
||||||
return "";
|
|
||||||
} else {
|
|
||||||
return minValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDefaultCheck = (index) => {
|
const handleDefaultCheck = (index) => {
|
||||||
const getDefaultCheck = defaultCheckbox.includes(index);
|
const getDefaultCheck = defaultCheckbox.includes(index);
|
||||||
@@ -147,7 +165,6 @@ function DropdownWidgetOption(props) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
|
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
|
||||||
<div className="h-full p-[15px] text-base-content">
|
<div className="h-full p-[15px] text-base-content">
|
||||||
@@ -158,30 +175,30 @@ function DropdownWidgetOption(props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[13px] font-semibold">
|
<label htmlFor="title" className="text-[13px] font-semibold">
|
||||||
{t("name")}
|
{t("name")}
|
||||||
<span className="text-[red] text-[13px]"> *</span>
|
<span className="text-[red] text-[13px]"> *</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="title"
|
||||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||||
onInput={(e) => e.target.setCustomValidity("")}
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
required
|
|
||||||
defaultValue={dropdownName}
|
|
||||||
value={dropdownName}
|
value={dropdownName}
|
||||||
onChange={(e) => setDropdownName(e.target.value)}
|
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"
|
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]">
|
<label className="text-[13px] font-semibold mt-[5px]">
|
||||||
{t("options")}
|
{t("options")}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{dropdownOptionList.map((option, index) => (
|
{dropdownOptionList?.map((option, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex flex-row mb-[5px] items-center"
|
className="flex flex-row mb-[5px] items-center"
|
||||||
>
|
>
|
||||||
{props.type === "checkbox" && !props.isSignYourself && (
|
{props.type === "checkbox" && props.isShowAdvanceFeature && (
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={handleDefaultCheck(index)}
|
checked={handleDefaultCheck(index)}
|
||||||
@@ -220,81 +237,25 @@ function DropdownWidgetOption(props) {
|
|||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<i
|
|
||||||
onClick={handleAddInput}
|
|
||||||
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
|
|
||||||
></i>
|
|
||||||
{isEnableSubscription && (
|
|
||||||
<div>
|
<div>
|
||||||
{props.type === "checkbox" && !props.isSignYourself && (
|
<i
|
||||||
<>
|
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
|
||||||
<label
|
aria-label="Add option"
|
||||||
className={`${
|
onClick={handleAddInput}
|
||||||
!props.isSubscribe ? "text-[gray]" : ""
|
></i>
|
||||||
} 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>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{["dropdown", radioButtonWidget].includes(props.type) && (
|
{["dropdown", radioButtonWidget].includes(props.type) &&
|
||||||
|
!isPrefillExist && (
|
||||||
<>
|
<>
|
||||||
<label className="text-[13px] font-semibold mt-[5px]">
|
<label className="text-[13px] font-semibold mt-[5px]">
|
||||||
{t("default-value")}
|
{t("default-value")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
|
value={defaultValue}
|
||||||
onChange={(e) => setDefaultValue(e.target.value)}
|
onChange={(e) => setDefaultValue(e.target.value)}
|
||||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
name="defaultvalue"
|
name="defaultvalue"
|
||||||
value={defaultValue}
|
|
||||||
placeholder="select default value"
|
|
||||||
>
|
>
|
||||||
<option value="" disabled hidden className="text-[13px]">
|
<option value="" disabled hidden className="text-[13px]">
|
||||||
{t("select")}...
|
{t("select")}...
|
||||||
@@ -309,11 +270,9 @@ function DropdownWidgetOption(props) {
|
|||||||
</select>
|
</select>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{props.type !== "checkbox" && props.type !== radioButtonWidget && (
|
{props.type !== "checkbox" && !isPrefillExist && (
|
||||||
<>
|
|
||||||
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
|
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
|
||||||
{statusArr.map((data, ind) => {
|
{statusArr.map((data, ind) => (
|
||||||
return (
|
|
||||||
<div
|
<div
|
||||||
key={ind}
|
key={ind}
|
||||||
className="flex flex-row gap-[5px] items-center"
|
className="flex flex-row gap-[5px] items-center"
|
||||||
@@ -325,12 +284,12 @@ function DropdownWidgetOption(props) {
|
|||||||
onChange={() => setStatus(data.toLowerCase())}
|
onChange={() => setStatus(data.toLowerCase())}
|
||||||
checked={status.toLowerCase() === data.toLowerCase()}
|
checked={status.toLowerCase() === data.toLowerCase()}
|
||||||
/>
|
/>
|
||||||
<div className="text-[13px] font-500">{data}</div>
|
<div className="text-[13px] font-500 capitalize">
|
||||||
|
{data}
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
))}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center mt-3 mb-3">
|
<div className="flex items-center mt-3 mb-3">
|
||||||
<span>{t("font-size")} :</span>
|
<span>{t("font-size")} :</span>
|
||||||
@@ -339,9 +298,9 @@ function DropdownWidgetOption(props) {
|
|||||||
value={
|
value={
|
||||||
props.fontSize ||
|
props.fontSize ||
|
||||||
props.currWidgetsDetails?.options?.fontSize ||
|
props.currWidgetsDetails?.options?.fontSize ||
|
||||||
"12"
|
12
|
||||||
}
|
}
|
||||||
onChange={(e) => props.setFontSize(e.target.value)}
|
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
|
||||||
>
|
>
|
||||||
{fontsizeArr.map((size, ind) => {
|
{fontsizeArr.map((size, ind) => {
|
||||||
return (
|
return (
|
||||||
@@ -352,7 +311,7 @@ function DropdownWidgetOption(props) {
|
|||||||
})}
|
})}
|
||||||
</select>
|
</select>
|
||||||
<div className="flex flex-row gap-1 items-center ml-4">
|
<div className="flex flex-row gap-1 items-center ml-4">
|
||||||
<span>{t("color")} : </span>
|
<span className="capitalize">{t("color")} : </span>
|
||||||
<select
|
<select
|
||||||
value={
|
value={
|
||||||
props.fontColor ||
|
props.fontColor ||
|
||||||
@@ -381,9 +340,11 @@ function DropdownWidgetOption(props) {
|
|||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{["checkbox", radioButtonWidget].includes(props.type) && (
|
{["checkbox", radioButtonWidget, "dropdown"].includes(
|
||||||
|
props.type
|
||||||
|
) && (
|
||||||
<div className="flex flex-row gap-5 my-2 items-center text-center">
|
<div className="flex flex-row gap-5 my-2 items-center text-center">
|
||||||
{!props.isSignYourself && (
|
{props.isShowAdvanceFeature && !isPrefillExist && (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<input
|
<input
|
||||||
id="isreadonly"
|
id="isreadonly"
|
||||||
@@ -392,11 +353,15 @@ function DropdownWidgetOption(props) {
|
|||||||
className="op-checkbox op-checkbox-sm"
|
className="op-checkbox op-checkbox-sm"
|
||||||
onChange={(e) => setIsReadOnly(e.target.checked)}
|
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")}
|
{t("read-only")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{props.type !== "dropdown" && (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<input
|
<input
|
||||||
id="ishidelabel"
|
id="ishidelabel"
|
||||||
@@ -406,17 +371,48 @@ function DropdownWidgetOption(props) {
|
|||||||
onChange={(e) => setIsHideLabel(e.target.checked)}
|
onChange={(e) => setIsHideLabel(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label className="ml-1 mb-0" htmlFor="ishidelabel">
|
<label
|
||||||
|
className="ml-2 mb-0 capitalize"
|
||||||
|
htmlFor="ishidelabel"
|
||||||
|
>
|
||||||
{t("hide-labels")}
|
{t("hide-labels")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</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
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
props.type === "checkbox" && !props.isSignYourself
|
props.type === "checkbox" && props.isShowAdvanceFeature
|
||||||
? "mb-[15px]"
|
? "mb-[15px]"
|
||||||
: "my-[15px]"
|
: "my-[15px]"
|
||||||
} w-full h-[1px] bg-[#9f9f9f]`}
|
} w-full h-[1px] bg-[#9f9f9f]`}
|
||||||
@@ -432,7 +428,7 @@ function DropdownWidgetOption(props) {
|
|||||||
{props.currWidgetsDetails?.options?.values?.length > 0 && (
|
{props.currWidgetsDetails?.options?.values?.length > 0 && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="op-btn op-btn-ghost ml-1"
|
className="op-btn op-btn-ghost text-base-content ml-1"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.handleClose && props.handleClose();
|
props.handleClose && props.handleClose();
|
||||||
resetState();
|
resetState();
|
||||||
@@ -1,270 +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 { Tooltip } from "react-tooltip";
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false"
|
|
||||||
});
|
|
||||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
|
||||||
useEffect(() => {
|
|
||||||
fetchSubscription();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
const fetchSubscription = async () => {
|
|
||||||
if (isEnableSubscription) {
|
|
||||||
const subscribe = await checkIsSubscribed();
|
|
||||||
setIsSubscribe(subscribe.isValid);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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;
|
|
||||||
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : 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,
|
|
||||||
IsEnableOTP: IsEnableOTP,
|
|
||||||
...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>
|
|
||||||
)}
|
|
||||||
{isEnableSubscription && (
|
|
||||||
<div className="text-xs mt-2">
|
|
||||||
<label className="block">
|
|
||||||
<span className={isSubscribe ? "" : " text-gray-300"}>
|
|
||||||
{t("isenable-otp")}{" "}
|
|
||||||
<a data-tooltip-id="isenableotp-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>{" "}
|
|
||||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
|
||||||
</span>
|
|
||||||
<Tooltip id="isenableotp-tooltip" className="z-50">
|
|
||||||
<div className="max-w-[200px] md:max-w-[450px]">
|
|
||||||
<p className="font-bold">{t("isenable-otp")}</p>
|
|
||||||
<p>{t("isenable-otp-help.p1")}</p>
|
|
||||||
<p className="p-[5px]">
|
|
||||||
<ol className="list-disc">
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">{t("yes")}: </span>
|
|
||||||
<span>{t("isenable-otp-help.p2")}</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">{t("no")}: </span>
|
|
||||||
<span>{t("isenable-otp-help.p3")}</span>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</p>
|
|
||||||
<p>{t("isenable-otp-help.p4")}</p>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
|
||||||
} flex items-center gap-2 ml-2 mb-1 `}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={"true"}
|
|
||||||
className="op-radio op-radio-xs"
|
|
||||||
name="IsEnableOTP"
|
|
||||||
checked={formData.IsEnableOTP === "true"}
|
|
||||||
onChange={handleStrInput}
|
|
||||||
/>
|
|
||||||
<div className="text-center">{t("yes")}</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
|
||||||
} flex items-center gap-2 ml-2 mb-1 `}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
value={"false"}
|
|
||||||
name="IsEnableOTP"
|
|
||||||
className="op-radio op-radio-xs"
|
|
||||||
checked={formData.IsEnableOTP === "false"}
|
|
||||||
onChange={handleStrInput}
|
|
||||||
/>
|
|
||||||
<div className="text-center">{t("no")}</div>
|
|
||||||
</div>
|
|
||||||
</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,542 @@
|
|||||||
|
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,
|
||||||
|
isAddYourSelfCheckbox,
|
||||||
|
}) => {
|
||||||
|
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(t("only-pdf-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);
|
||||||
|
const userId = template?.ExtUserPtr?.UserId?.objectId;
|
||||||
|
SaveFileSize(buffer.length, pdfUrl, tenantId, userId);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
isAddYourSelfCheckbox={isAddYourSelfCheckbox}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs mt-2">
|
||||||
|
<label className="block">{t("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 React from "react";
|
||||||
import { Quill } from "react-quill-new";
|
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
|
// Custom Undo button icon component for Quill editor. You can import it directly
|
||||||
// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not
|
// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not
|
||||||
@@ -54,17 +59,24 @@ Quill.register(Font, true);
|
|||||||
export const module1 = {
|
export const module1 = {
|
||||||
toolbar: {
|
toolbar: {
|
||||||
container: "#toolbar1",
|
container: "#toolbar1",
|
||||||
handlers: {
|
handlers: { undo: undoChange, redo: redoChange }
|
||||||
undo: undoChange,
|
|
||||||
redo: redoChange
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
history: {
|
history: { delay: 500, maxStack: 100, userOnly: true },
|
||||||
delay: 500,
|
// Enable the "<>" button registered above
|
||||||
maxStack: 100,
|
htmlEditButton: {}
|
||||||
userOnly: true
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Modules object for setting up the Quill editor
|
||||||
|
export const module2 = {
|
||||||
|
toolbar: {
|
||||||
|
container: "#toolbar2",
|
||||||
|
handlers: { undo: undoChange, redo: redoChange }
|
||||||
|
},
|
||||||
|
history: { delay: 500, maxStack: 100, userOnly: true },
|
||||||
|
// Enable the "<>" button registered above
|
||||||
|
htmlEditButton: {}
|
||||||
|
};
|
||||||
|
|
||||||
// Formats objects for setting up the Quill editor
|
// Formats objects for setting up the Quill editor
|
||||||
export const formats = [
|
export const formats = [
|
||||||
"header",
|
"header",
|
||||||
@@ -18,7 +18,13 @@ export function EmailBody(props) {
|
|||||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||||
onInput={(e) => e.target.setCustomValidity("")}
|
onInput={(e) => e.target.setCustomValidity("")}
|
||||||
value={props.requestSubject}
|
value={props.requestSubject}
|
||||||
onChange={(e) => props.setRequestSubject(e.target.value)}
|
onChange={(e) => {
|
||||||
|
props?.setRequestSubject(e.target.value);
|
||||||
|
props?.setCustomizeMail((prev) => ({
|
||||||
|
...prev,
|
||||||
|
subject: e.target.value
|
||||||
|
}));
|
||||||
|
}}
|
||||||
placeholder='${senderName} has requested you to sign "${documentName}"'
|
placeholder='${senderName} has requested you to sign "${documentName}"'
|
||||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||||
/>
|
/>
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import axios from "axios";
|
import {
|
||||||
import { handleDownloadPdf, handleToPrint } from "../../constant/Utils";
|
handleToPrint,
|
||||||
import { themeColor, emailRegex } from "../../constant/const";
|
} from "../../constant/Utils";
|
||||||
|
import {
|
||||||
|
emailRegex,
|
||||||
|
} from "../../constant/const";
|
||||||
import Loader from "../../primitives/Loader";
|
import Loader from "../../primitives/Loader";
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Parse from "parse";
|
||||||
|
|
||||||
function EmailComponent({
|
function EmailComponent({
|
||||||
isEmail,
|
isEmail,
|
||||||
pdfUrl,
|
|
||||||
setIsEmail,
|
setIsEmail,
|
||||||
setSuccessEmail,
|
setSuccessEmail,
|
||||||
pdfDetails,
|
pdfDetails,
|
||||||
sender,
|
|
||||||
setIsAlert,
|
setIsAlert,
|
||||||
extUserId,
|
setIsDownloadModal
|
||||||
activeMailAdapter
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [emailList, setEmailList] = useState([]);
|
const [emailList, setEmailList] = useState([]);
|
||||||
@@ -24,57 +25,14 @@ function EmailComponent({
|
|||||||
const [emailErr, setEmailErr] = useState(false);
|
const [emailErr, setEmailErr] = useState(false);
|
||||||
const [isDownloading, setIsDownloading] = useState("");
|
const [isDownloading, setIsDownloading] = useState("");
|
||||||
const isAndroid = /Android/i.test(navigator.userAgent);
|
const isAndroid = /Android/i.test(navigator.userAgent);
|
||||||
|
|
||||||
//function for send email
|
//function for send email
|
||||||
const sendEmail = async () => {
|
const sendEmail = async () => {
|
||||||
const pdfName = pdfDetails[0]?.Name;
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
let sendMail;
|
const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
|
||||||
for (let i = 0; i < emailList.length; i++) {
|
const sendmail = await Parse.Cloud.run("forwarddoc", params);
|
||||||
try {
|
console.log("sendmail ", sendmail);
|
||||||
const imgPng =
|
if (sendmail?.status === "success") {
|
||||||
"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?.data?.result?.status === "success") {
|
|
||||||
setSuccessEmail(true);
|
setSuccessEmail(true);
|
||||||
setIsEmail(false);
|
setIsEmail(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -83,7 +41,8 @@ function EmailComponent({
|
|||||||
setEmailList([]);
|
setEmailList([]);
|
||||||
}, 1500);
|
}, 1500);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setIsEmail(false);
|
setIsEmail(false);
|
||||||
setIsAlert({
|
setIsAlert({
|
||||||
@@ -129,6 +88,11 @@ function EmailComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handleClose = () => {
|
||||||
|
setIsEmail(false);
|
||||||
|
setEmailValue("");
|
||||||
|
setEmailList([]);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* isEmail */}
|
{/* isEmail */}
|
||||||
@@ -148,26 +112,27 @@ function EmailComponent({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-between items-center py-[10px] px-[20px] border-b-[1px] border-base-content">
|
<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")}
|
{t("successfully-signed")}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
{!isAndroid && (
|
{!isAndroid && (
|
||||||
<button
|
<button
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
|
handleToPrint(e, setIsDownloading, pdfDetails)
|
||||||
}
|
}
|
||||||
className="op-btn op-btn-neutral op-btn-sm text-[15px]"
|
className="op-btn op-btn-neutral op-btn-sm text-xs md:text-[15px]"
|
||||||
>
|
>
|
||||||
<i className="fa-light fa-print" aria-hidden="true"></i>
|
<i className="fa-light fa-print" aria-hidden="true"></i>
|
||||||
{t("print")}
|
{t("print")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
|
className="op-btn op-btn-primary op-btn-sm text-xs md:text-[15px] ml-2"
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
|
handleClose();
|
||||||
}
|
setIsDownloadModal(true);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<i className="fa-light fa-download" aria-hidden="true"></i>
|
<i className="fa-light fa-download" aria-hidden="true"></i>
|
||||||
{t("download")}
|
{t("download")}
|
||||||
@@ -179,12 +144,12 @@ function EmailComponent({
|
|||||||
{t("email-mssg")}
|
{t("email-mssg")}
|
||||||
</p>
|
</p>
|
||||||
{emailList.length > 0 ? (
|
{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">
|
<div className="flex flex-row flex-wrap">
|
||||||
{emailList.map((data, ind) => {
|
{emailList.map((data, ind) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<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}
|
key={ind}
|
||||||
>
|
>
|
||||||
<span className="text-base-100 text-[13px]">
|
<span className="text-base-100 text-[13px]">
|
||||||
@@ -204,7 +169,7 @@ function EmailComponent({
|
|||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={emailValue}
|
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}
|
onChange={handleEmailValue}
|
||||||
onKeyDown={handleEnterPress}
|
onKeyDown={handleEnterPress}
|
||||||
onBlur={() => emailValue && handleEnterPress("add")}
|
onBlur={() => emailValue && handleEnterPress("add")}
|
||||||
@@ -221,7 +186,7 @@ function EmailComponent({
|
|||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={emailValue}
|
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}
|
onChange={handleEmailValue}
|
||||||
onKeyDown={handleEnterPress}
|
onKeyDown={handleEnterPress}
|
||||||
placeholder={t("enter-email-plaholder")}
|
placeholder={t("enter-email-plaholder")}
|
||||||
@@ -239,20 +204,7 @@ function EmailComponent({
|
|||||||
{t("email-error-1")}
|
{t("email-error-1")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<button
|
<div className="mt-2">
|
||||||
className={`${
|
|
||||||
emailValue ? "cursor-pointer" : "cursor-default"
|
|
||||||
} op-btn op-btn-primary op-btn-sm m-2 shadow-md`}
|
|
||||||
onClick={() => emailValue && handleEnterPress("add")}
|
|
||||||
>
|
|
||||||
<i className="fa-light fa-plus" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className="bg-[#e3e2e1] mt-[10px] p-[5px] rounded">
|
|
||||||
<span className="font-bold">{t("report-heading.Note")}: </span>
|
|
||||||
<span className="text-[15px]">{t("email-error-2")}</span>
|
|
||||||
</div>
|
|
||||||
<hr className="w-full my-[15px] bg-base-content" />
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="op-btn op-btn-secondary"
|
className="op-btn op-btn-secondary"
|
||||||
@@ -262,16 +214,13 @@ function EmailComponent({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="op-btn op-btn-ghost ml-2"
|
className="op-btn op-btn-ghost text-base-content ml-2"
|
||||||
onClick={() => {
|
onClick={() => handleClose()}
|
||||||
setIsEmail(false);
|
|
||||||
setEmailValue("");
|
|
||||||
setEmailList([]);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{t("close")}
|
{t("close")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</ModalUi>
|
</ModalUi>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const Guidelines = ({ x1, x2, y1, y2 }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Horizontal guidelines */}
|
||||||
|
{/* top guide */}{" "}
|
||||||
|
<div
|
||||||
|
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
|
||||||
|
style={{ top: y1 }}
|
||||||
|
/>
|
||||||
|
{/* bottom guide */}
|
||||||
|
<div
|
||||||
|
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
|
||||||
|
style={{ top: y2 }}
|
||||||
|
/>
|
||||||
|
{/* Vertical guidelines */}
|
||||||
|
{/* left guide */}
|
||||||
|
<div
|
||||||
|
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
|
||||||
|
style={{ left: x1 }}
|
||||||
|
/>
|
||||||
|
{/* right guide */}
|
||||||
|
<div
|
||||||
|
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
|
||||||
|
style={{ left: x2 }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Guidelines;
|
||||||
@@ -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 text-base-content"
|
||||||
|
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 text-base-content"
|
||||||
|
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 text-base-content ml-1"
|
||||||
|
>
|
||||||
|
{t("close")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,545 +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,
|
|
||||||
clickOnZoomIn,
|
|
||||||
clickOnZoomOut,
|
|
||||||
handleRotationFun,
|
|
||||||
isDisableRotate,
|
|
||||||
templateId
|
|
||||||
}) {
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
{isCompleted || 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, 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
|
|
||||||
*/}
|
|
||||||
{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={() => 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-md 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,
|
|
||||||
pdfDetails[0]?.URL,
|
|
||||||
setIsDownloading
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="fa-light fa-arrow-down mr-[3px]"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span className="font-[500]">{t("download")}</span>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
{!isDisableRotate && (
|
|
||||||
<>
|
|
||||||
<DropdownMenu.Item
|
|
||||||
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
|
|
||||||
onClick={() => handleRotationFun(90)}
|
|
||||||
>
|
|
||||||
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
|
||||||
<span className="font-[500]">
|
|
||||||
{t("rotate-right")}
|
|
||||||
</span>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item
|
|
||||||
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
|
|
||||||
onClick={() => handleRotationFun(-90)}
|
|
||||||
>
|
|
||||||
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
|
||||||
<span className="font-[500]">
|
|
||||||
{t("rotate-left")}
|
|
||||||
</span>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DropdownMenu.Item
|
|
||||||
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
|
|
||||||
onClick={() => clickOnZoomIn()}
|
|
||||||
>
|
|
||||||
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
|
|
||||||
<span className="font-[500]">{t("zoom-in")}</span>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item
|
|
||||||
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
|
|
||||||
onClick={() => clickOnZoomOut()}
|
|
||||||
>
|
|
||||||
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
|
|
||||||
<span className="font-[500]">{t("zoom-out")}</span>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
</DropdownMenu.Content>
|
|
||||||
</DropdownMenu.Portal>
|
|
||||||
</DropdownMenu.Root>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isPublicTemplate && (
|
|
||||||
<div
|
|
||||||
data-tut="reactourThird"
|
|
||||||
onClick={() => embedWidgetsData()}
|
|
||||||
className="border-none font-[650] text-[14px] 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={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, 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>
|
|
||||||
{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">
|
|
||||||
{!templateId && (
|
|
||||||
<button
|
|
||||||
onClick={() => window.history.go(-2)}
|
|
||||||
type="button"
|
|
||||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
|
||||||
>
|
|
||||||
{t("back")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{currentSigner && (
|
|
||||||
<>
|
|
||||||
{templateId && (
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
|
||||||
handleDownloadPdf(
|
|
||||||
pdfDetails,
|
|
||||||
pdfUrl,
|
|
||||||
setIsDownloading
|
|
||||||
)
|
|
||||||
}
|
|
||||||
type="button"
|
|
||||||
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
|
|
||||||
>
|
|
||||||
<span className="hidden lg:block">{t("download")}</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<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>
|
|
||||||
{!templateId && (
|
|
||||||
<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, 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={() =>
|
|
||||||
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={() => 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,739 @@
|
|||||||
|
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 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(t("please-select-pdf"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!file.type.includes("pdf")) {
|
||||||
|
alert(t("only-pdf-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(t("incorrect-password-or-decryption-failed"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(t("provide-password"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Err ", err);
|
||||||
|
alert(t("error-uploading-pdf"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(t("error-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);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownloadDoc = async () => {
|
||||||
|
await handleDownloadPdf(
|
||||||
|
props?.pdfDetails,
|
||||||
|
setIsDownloading,
|
||||||
|
props.pdfBase64
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const handleDownloadBtn = async () => {
|
||||||
|
if (
|
||||||
|
props?.isCompleted
|
||||||
|
) {
|
||||||
|
props?.setIsDownloadModal(true);
|
||||||
|
} else {
|
||||||
|
await handleDownloadDoc();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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={() => handleDownloadBtn()}
|
||||||
|
>
|
||||||
|
<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?.handleSaveDoc();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
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={() => handleDownloadDoc()}
|
||||||
|
>
|
||||||
|
<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"></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 text-base-content"></i>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{enabledBackBtn && (
|
||||||
|
<button
|
||||||
|
onClick={() => window.history.go(-2)}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost text-base-content 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?.handleSaveDoc()}
|
||||||
|
>
|
||||||
|
{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={() => handleDownloadBtn()}
|
||||||
|
>
|
||||||
|
<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={() => handleDownloadDoc()}
|
||||||
|
type="button"
|
||||||
|
className="op-btn op-btn-ghost text-base-content 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 text-base-content op-btn-sm mr-[3px]"
|
||||||
|
onClick={() => handleDownloadDoc()}
|
||||||
|
>
|
||||||
|
<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)}
|
||||||
|
onClick={() => handleDownloadBtn()}
|
||||||
|
>
|
||||||
|
<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 text-base-content 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 text-base-content">{t("delete-alert-2")}</p>
|
||||||
|
<p className="pt-3 text-base-content">{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 text-base-content"
|
||||||
|
>
|
||||||
|
{t("no")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
<PageReorderModal
|
||||||
|
isOpen={isReorderModal}
|
||||||
|
handleClose={() => setIsReorderModal(false)}
|
||||||
|
totalPages={props.allPages}
|
||||||
|
onSave={handleReorderSave}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header;
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
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 PdfTools(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) => {
|
||||||
|
props.setIsTour && props.setIsTour(false);
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) {
|
||||||
|
alert(t("please-select-pdf"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!file.type.includes("pdf")) {
|
||||||
|
alert(t("only-pdf-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(t("incorrect-password-or-decryption-failed"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(t("provide-password"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Err ", err);
|
||||||
|
alert(t("error-uploading-pdf"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(t("error-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);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeletePage = () => {
|
||||||
|
setIsDeletePage(true);
|
||||||
|
props.setIsTour && props.setIsTour(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReorderPages = () => {
|
||||||
|
setIsReorderModal(true);
|
||||||
|
props.setIsTour && props.setIsTour(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleZoomIn = () => {
|
||||||
|
props.clickOnZoomIn();
|
||||||
|
props.setIsTour && props.setIsTour(false);
|
||||||
|
};
|
||||||
|
const handleZoomOut = () => {
|
||||||
|
props.clickOnZoomOut();
|
||||||
|
props.setIsTour && props.setIsTour(false);
|
||||||
|
};
|
||||||
|
const handleRotate = () => {
|
||||||
|
props.handleRotationFun(90);
|
||||||
|
props.setIsTour && props.setIsTour(false);
|
||||||
|
};
|
||||||
|
const handleAntiRotate = () => {
|
||||||
|
props.handleRotationFun(-90);
|
||||||
|
props.setIsTour && props.setIsTour(false);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
data-tut="pdftools"
|
||||||
|
className="hidden h-max 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={handleDeletePage}
|
||||||
|
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={handleReorderPages}
|
||||||
|
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={handleZoomIn}
|
||||||
|
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={handleRotate}
|
||||||
|
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={handleAntiRotate}
|
||||||
|
>
|
||||||
|
<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] cursor-pointer"
|
||||||
|
onClick={handleZoomOut}
|
||||||
|
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 text-base-content">{t("delete-alert-2")}</p>
|
||||||
|
<p className="pt-3 text-base-content">{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 text-base-content ml-1"
|
||||||
|
>
|
||||||
|
{t("no")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ModalUi>
|
||||||
|
<PageReorderModal
|
||||||
|
isOpen={isReorderModal}
|
||||||
|
handleClose={() => setIsReorderModal(false)}
|
||||||
|
totalPages={props.allPages}
|
||||||
|
onSave={handleReorderSave}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PdfTools;
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function PdfZoom(props) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[43px] 2xl:mt-[63px]">
|
|
||||||
<span
|
|
||||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 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-[30px]"></i>
|
|
||||||
</span>
|
|
||||||
{!props?.isDisableRotate && (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 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-[30px]"></i>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 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-[30px]"></i>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PdfZoom;
|
|
||||||
@@ -1,985 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import BorderResize from "./BorderResize";
|
|
||||||
import PlaceholderBorder from "./PlaceholderBorder";
|
|
||||||
import { Rnd } from "react-rnd";
|
|
||||||
import {
|
|
||||||
defaultWidthHeight,
|
|
||||||
fontColorArr,
|
|
||||||
fontsizeArr,
|
|
||||||
getContainerScale,
|
|
||||||
handleCopyNextToWidget,
|
|
||||||
isTabAndMobile,
|
|
||||||
onChangeInput,
|
|
||||||
radioButtonWidget,
|
|
||||||
textInputWidget,
|
|
||||||
textWidget
|
|
||||||
} from "../../constant/Utils";
|
|
||||||
import PlaceholderType from "./PlaceholderType";
|
|
||||||
import moment from "moment";
|
|
||||||
import "../../styles/opensigndrive.css";
|
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
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 { t } = useTranslation();
|
|
||||||
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
|
|
||||||
const [isDraggingEnabled, setDraggingEnabled] = useState(true);
|
|
||||||
const [isDateModal, setIsDateModal] = 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 [clickonWidget, setClickonWidget] = 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]);
|
|
||||||
|
|
||||||
//`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.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.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.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,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
props.fontSize || props.pos?.options?.fontSize || "12",
|
|
||||||
props.fontColor || props.pos?.options?.fontColor || "black"
|
|
||||||
);
|
|
||||||
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 && (
|
|
||||||
<i
|
|
||||||
onClick={(e) => {
|
|
||||||
props.setCurrWidgetsDetails(props.pos);
|
|
||||||
setIsDateModal(!isDateModal);
|
|
||||||
e.stopPropagation();
|
|
||||||
setClickonWidget(props.pos);
|
|
||||||
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) => {
|
|
||||||
props.setCurrWidgetsDetails(props.pos);
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsDateModal(!isDateModal);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
zIndex: "99",
|
|
||||||
top: "-18px",
|
|
||||||
right: props.isPlaceholder ? "50px" : "30px",
|
|
||||||
color: "#188ae2",
|
|
||||||
fontSize: "14px"
|
|
||||||
}}
|
|
||||||
className="fa-light fa-gear icon"
|
|
||||||
></i>
|
|
||||||
)}
|
|
||||||
{/* 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>
|
|
||||||
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
|
|
||||||
<div className="h-[100%] p-[20px]">
|
|
||||||
<div className="flex flex-row items-center">
|
|
||||||
<span>{t("format")} : </span>
|
|
||||||
<div className="flex">
|
|
||||||
<select
|
|
||||||
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
|
||||||
defaultValue={""}
|
|
||||||
onChange={(e) => {
|
|
||||||
const selectedIndex = e.target.value;
|
|
||||||
e.stopPropagation();
|
|
||||||
setSelectDate(dateFormat[selectedIndex]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value="" disabled>
|
|
||||||
{t("select-date-format")}
|
|
||||||
</option>
|
|
||||||
{dateFormat.map((data, ind) => {
|
|
||||||
return (
|
|
||||||
<option className="text-[13px]" value={ind} key={ind}>
|
|
||||||
{data?.date ? data?.date : "nodata"}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<span className="text-xs text-gray-400 ml-1">
|
|
||||||
{selectDate.format}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center mt-4 md:mt-2">
|
|
||||||
<span>{t("font-size")} :</span>
|
|
||||||
<select
|
|
||||||
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
|
||||||
value={props.fontSize || clickonWidget.options?.fontSize || "12"}
|
|
||||||
onChange={(e) => props.setFontSize(e.target.value)}
|
|
||||||
>
|
|
||||||
{fontsizeArr.map((size, ind) => {
|
|
||||||
return (
|
|
||||||
<option className="text-[13px]" value={size} key={ind}>
|
|
||||||
{size}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
<div className="flex flex-row gap-1 items-center ml-2 md:ml-4 ">
|
|
||||||
<span>{t("color")}: </span>
|
|
||||||
<select
|
|
||||||
value={
|
|
||||||
props.fontColor || clickonWidget.options?.fontColor || "black"
|
|
||||||
}
|
|
||||||
onChange={(e) => props.setFontColor(e.target.value)}
|
|
||||||
className="ml-[4px] md:ml[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
|
||||||
>
|
|
||||||
{fontColorArr.map((color, ind) => {
|
|
||||||
return (
|
|
||||||
<option value={color} key={ind}>
|
|
||||||
{t(`color-type.${color}`)}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
background:
|
|
||||||
props.fontColor || props.pos.options?.fontColor || "black"
|
|
||||||
}}
|
|
||||||
className="w-5 h-[19px] ml-1"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="op-btn op-btn-primary"
|
|
||||||
onClick={() => {
|
|
||||||
setIsDateModal(false);
|
|
||||||
handleSaveDate(selectDate);
|
|
||||||
props.setFontColor("");
|
|
||||||
props.setFontSize("");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("save")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ModalUi>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,10 +1,6 @@
|
|||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import ModalUi from "../../primitives/ModalUi";
|
import ModalUi from "../../primitives/ModalUi";
|
||||||
import {
|
import { handleCopyNextToWidget, randomId } from "../../constant/Utils";
|
||||||
handleCopyNextToWidget,
|
|
||||||
randomId,
|
|
||||||
textWidget
|
|
||||||
} from "../../constant/Utils";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function PlaceholderCopy(props) {
|
function PlaceholderCopy(props) {
|
||||||
@@ -19,56 +15,89 @@ function PlaceholderCopy(props) {
|
|||||||
//function for get copy placeholder position
|
//function for get copy placeholder position
|
||||||
const getCopyPlaceholderPosition = (
|
const getCopyPlaceholderPosition = (
|
||||||
type,
|
type,
|
||||||
currentPlaceholder,
|
newPlaceholder,
|
||||||
newPageNumber,
|
newPageNumber,
|
||||||
existPlaceholderPosition
|
existPlaceholderPosition
|
||||||
) => {
|
) => {
|
||||||
|
//remove existing page position when used option copy but first or copy but last
|
||||||
const filterPosition =
|
const filterPosition =
|
||||||
existPlaceholderPosition &&
|
existPlaceholderPosition &&
|
||||||
existPlaceholderPosition.filter(
|
existPlaceholderPosition.filter(
|
||||||
(data) => data.xPosition !== currentPlaceholder.xPosition
|
(data) => data.xPosition !== newPlaceholder.xPosition
|
||||||
);
|
);
|
||||||
|
//first option - copy all page but not first
|
||||||
//copy all page placeholder at requested position except first page
|
//copy all page placeholder at requested position except first page
|
||||||
if (newPageNumber === 1 && type === 3) {
|
if (newPageNumber === 1 && type === 3) {
|
||||||
if (filterPosition && filterPosition.length > 0) {
|
//when user choose this option and placed widget on first page then do not remove first page widget
|
||||||
|
//it should be copy all page included first page on same postion
|
||||||
|
if (props.pageNumber === 1) {
|
||||||
|
return {
|
||||||
|
pageNumber: newPageNumber,
|
||||||
|
pos: [...existPlaceholderPosition]
|
||||||
|
};
|
||||||
|
} //else when user copy widget and choose this option and not at first page then copy all page but not at first
|
||||||
|
else if (filterPosition && filterPosition.length > 0) {
|
||||||
return {
|
return {
|
||||||
pageNumber: newPageNumber,
|
pageNumber: newPageNumber,
|
||||||
pos: [...filterPosition]
|
pos: [...filterPosition]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//second option - copy all page but not last
|
||||||
//copy all page placeholder at requested position except last page
|
//copy all page placeholder at requested position except last page
|
||||||
else if (newPageNumber === props.allPages && type === 2) {
|
else if (newPageNumber === props.allPages && type === 2) {
|
||||||
if (existPlaceholderPosition) {
|
if (existPlaceholderPosition) {
|
||||||
|
//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 {
|
return {
|
||||||
pageNumber: newPageNumber,
|
pageNumber: newPageNumber,
|
||||||
pos: [...filterPosition]
|
pos: [...filterPosition]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
//this optin - copy all page with exiting widgets on same page
|
||||||
//copy all page placeholder at requested position with existing placeholder position
|
//copy all page placeholder at requested position with existing placeholder position
|
||||||
else if (existPlaceholderPosition) {
|
else if (existPlaceholderPosition) {
|
||||||
|
if (newPageNumber === props.pageNumber) {
|
||||||
|
return {
|
||||||
|
pageNumber: props.pageNumber,
|
||||||
|
pos: [...existPlaceholderPosition]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
pageNumber: newPageNumber,
|
pageNumber: newPageNumber,
|
||||||
pos: [...filterPosition, currentPlaceholder]
|
pos: [...existPlaceholderPosition, newPlaceholder]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//copy all page placeholder at requested position
|
//copy all page placeholder at requested position
|
||||||
else {
|
else {
|
||||||
|
if (newPageNumber === props.pageNumber) {
|
||||||
|
return {
|
||||||
|
pageNumber: props.pageNumber,
|
||||||
|
pos: [...existPlaceholderPosition]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
pageNumber: newPageNumber,
|
pageNumber: newPageNumber,
|
||||||
pos: [currentPlaceholder]
|
pos: [newPlaceholder]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//function for copy placeholder as per select copy type
|
//function for copy placeholder as per select copy type
|
||||||
const copyPlaceholder = (type) => {
|
const copyPlaceholder = (type) => {
|
||||||
let newPlaceholderPosition = [];
|
let newPlaceholderPosition = [];
|
||||||
let newPageNumber = 1;
|
let newPageNumber = 1;
|
||||||
const signerPosition = props.xyPostion;
|
const signerPosition = props.xyPosition;
|
||||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||||
|
|
||||||
//handle placeholder array and copy for multiple signers placeholder at requested location
|
//handle placeholder array and copy for multiple signers placeholder at requested location
|
||||||
if (signerId) {
|
if (signerId) {
|
||||||
//get current signers data
|
//get current signers data
|
||||||
@@ -90,10 +119,15 @@ function PlaceholderCopy(props) {
|
|||||||
const currentPlaceholder = placeholderPosition[0].pos.find(
|
const currentPlaceholder = placeholderPosition[0].pos.find(
|
||||||
(position) => position.key === props.signKey
|
(position) => position.key === props.signKey
|
||||||
);
|
);
|
||||||
|
|
||||||
for (let i = 0; i < props.allPages; i++) {
|
for (let i = 0; i < props.allPages; i++) {
|
||||||
const newId = randomId();
|
const newId = randomId();
|
||||||
currentPlaceholder.key = newId;
|
const nameId = randomId(2)
|
||||||
|
const widgetName = `${currentPlaceholder?.options?.name}${nameId}`;
|
||||||
|
const newPlaceholder = {
|
||||||
|
...currentPlaceholder,
|
||||||
|
key: newId,
|
||||||
|
options: { ...currentPlaceholder?.options, name: widgetName }
|
||||||
|
};
|
||||||
//get exist placeholder position for particular page
|
//get exist placeholder position for particular page
|
||||||
const existPlaceholder = filterSignerPosition[0].placeHolder.filter(
|
const existPlaceholder = filterSignerPosition[0].placeHolder.filter(
|
||||||
(data) => data.pageNumber === newPageNumber
|
(data) => data.pageNumber === newPageNumber
|
||||||
@@ -104,11 +138,11 @@ function PlaceholderCopy(props) {
|
|||||||
//function for get copy to requested location of placeholder position
|
//function for get copy to requested location of placeholder position
|
||||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||||
type,
|
type,
|
||||||
currentPlaceholder,
|
newPlaceholder,
|
||||||
newPageNumber,
|
newPageNumber,
|
||||||
existPlaceholderPosition
|
existPlaceholderPosition
|
||||||
);
|
);
|
||||||
if (getPlaceholderObj) {
|
if (getPlaceholderObj?.pos) {
|
||||||
newPlaceholderPosition.push(getPlaceholderObj);
|
newPlaceholderPosition.push(getPlaceholderObj);
|
||||||
}
|
}
|
||||||
newPageNumber++;
|
newPageNumber++;
|
||||||
@@ -138,13 +172,13 @@ function PlaceholderCopy(props) {
|
|||||||
|
|
||||||
const signersData = signerPosition;
|
const signersData = signerPosition;
|
||||||
signersData.splice(0, signerPosition.length, ...updatedSignerPlaceholder);
|
signersData.splice(0, signerPosition.length, ...updatedSignerPlaceholder);
|
||||||
props.setXyPostion(signersData);
|
props.setXyPosition(signersData);
|
||||||
}
|
}
|
||||||
//handle signyourself array and copy for single signers placeholder at requested location
|
//handle signyourself array and copy for single signers placeholder at requested location
|
||||||
else {
|
else {
|
||||||
const xyPostion = props.xyPostion;
|
const xyPosition = props.xyPosition;
|
||||||
|
|
||||||
const placeholderPosition = xyPostion.filter(
|
const placeholderPosition = xyPosition.filter(
|
||||||
(data) => data.pageNumber === props.pageNumber
|
(data) => data.pageNumber === props.pageNumber
|
||||||
);
|
);
|
||||||
//get current placeholder position data which user want to copy
|
//get current placeholder position data which user want to copy
|
||||||
@@ -154,18 +188,18 @@ function PlaceholderCopy(props) {
|
|||||||
|
|
||||||
for (let i = 0; i < props.allPages; i++) {
|
for (let i = 0; i < props.allPages; i++) {
|
||||||
//get exist placeholder position for particular page
|
//get exist placeholder position for particular page
|
||||||
const existPlaceholder = xyPostion.filter(
|
const existPlaceholder = xyPosition.filter(
|
||||||
(data) => data.pageNumber === newPageNumber
|
(data) => data.pageNumber === newPageNumber
|
||||||
);
|
);
|
||||||
const existPlaceholderPosition =
|
const existPlaceholderPosition =
|
||||||
existPlaceholder[0] && existPlaceholder[0].pos;
|
existPlaceholder[0] && existPlaceholder[0].pos;
|
||||||
|
|
||||||
const newId = randomId();
|
const newId = randomId();
|
||||||
currentPlaceholder.key = newId;
|
const newPlaceholder = { ...currentPlaceholder, key: newId };
|
||||||
//function for get copy to requested location of placeholder position
|
//function for get copy to requested location of placeholder position
|
||||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||||
type,
|
type,
|
||||||
currentPlaceholder,
|
newPlaceholder,
|
||||||
newPageNumber,
|
newPageNumber,
|
||||||
existPlaceholderPosition
|
existPlaceholderPosition
|
||||||
);
|
);
|
||||||
@@ -175,14 +209,15 @@ function PlaceholderCopy(props) {
|
|||||||
|
|
||||||
newPageNumber++;
|
newPageNumber++;
|
||||||
}
|
}
|
||||||
props.setXyPostion(newPlaceholderPosition);
|
props.setXyPosition(newPlaceholderPosition);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//function for getting selected type placeholder copy
|
//function for getting selected type placeholder copy
|
||||||
const handleApplyCopy = () => {
|
const handleApplyCopy = () => {
|
||||||
|
const newId = randomId();
|
||||||
if (selectCopyType === 4) {
|
if (selectCopyType === 4) {
|
||||||
const signerPosition = props.xyPostion;
|
const signerPosition = props.xyPosition;
|
||||||
let currentXYposition;
|
let currentXYposition;
|
||||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
||||||
if (signerId) {
|
if (signerId) {
|
||||||
@@ -198,29 +233,29 @@ function PlaceholderCopy(props) {
|
|||||||
);
|
);
|
||||||
//function to create new widget next to just widget
|
//function to create new widget next to just widget
|
||||||
handleCopyNextToWidget(
|
handleCopyNextToWidget(
|
||||||
|
newId,
|
||||||
currentXYposition,
|
currentXYposition,
|
||||||
props.widgetType,
|
props.xyPosition,
|
||||||
props.xyPostion,
|
|
||||||
props.pageNumber,
|
props.pageNumber,
|
||||||
props.setXyPostion,
|
props.setXyPosition,
|
||||||
props?.Id
|
props?.Id
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const getIndex = props?.xyPostion.findIndex(
|
const getIndex = props?.xyPosition.findIndex(
|
||||||
(data) => data.pageNumber === props.pageNumber
|
(data) => data.pageNumber === props.pageNumber
|
||||||
);
|
);
|
||||||
const placeholderPosition = props?.xyPostion[getIndex];
|
const placeholderPosition = props?.xyPosition[getIndex];
|
||||||
//get current placeholder position data which user want to copy
|
//get current placeholder position data which user want to copy
|
||||||
currentXYposition = placeholderPosition.pos.find(
|
currentXYposition = placeholderPosition.pos.find(
|
||||||
(pos) => pos.key === props.signKey
|
(pos) => pos.key === props.signKey
|
||||||
);
|
);
|
||||||
//function to create new widget next to just widget
|
//function to create new widget next to just widget
|
||||||
handleCopyNextToWidget(
|
handleCopyNextToWidget(
|
||||||
|
newId,
|
||||||
currentXYposition,
|
currentXYposition,
|
||||||
props.widgetType,
|
props.xyPosition,
|
||||||
props.xyPostion,
|
|
||||||
getIndex,
|
getIndex,
|
||||||
props.setXyPostion
|
props.setXyPosition
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -228,11 +263,6 @@ function PlaceholderCopy(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleUniqueId = () => {
|
const handleUniqueId = () => {
|
||||||
const signerId = props.signerObjId ? props.signerObjId : props.Id;
|
|
||||||
if (signerId && props.widgetType === textWidget) {
|
|
||||||
props.setUniqueId(props?.tempSignerId);
|
|
||||||
props.setTempSignerId("");
|
|
||||||
}
|
|
||||||
props.setIsPageCopy(false);
|
props.setIsPageCopy(false);
|
||||||
setSelectCopyType(1);
|
setSelectCopyType(1);
|
||||||
};
|
};
|
||||||
@@ -260,7 +290,7 @@ function PlaceholderCopy(props) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div className="flex bg-[#9f9f9f] w-full my-[15px]"></div>
|
<div className="flex flex-row bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleApplyCopy();
|
handleApplyCopy();
|
||||||
@@ -274,7 +304,7 @@ function PlaceholderCopy(props) {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="op-btn op-btn-ghost"
|
className="op-btn op-btn-ghost text-base-content ml-2"
|
||||||
onClick={() => handleUniqueId()}
|
onClick={() => handleUniqueId()}
|
||||||
>
|
>
|
||||||
{t("cancel")}
|
{t("cancel")}
|
||||||