Compare commits
935 Commits
without-bu
...
main
Author | SHA1 | Date | |
---|---|---|---|
be993e2206 | |||
48ae1359ac | |||
7686c9e326 | |||
2d087ae113 | |||
43839c5879 | |||
effc4cbea9 | |||
89b55d4d3e | |||
25deb4a0f4 | |||
5ad3a810ef | |||
5ff3d7ae16 | |||
8604daf6a4 | |||
a7c78e57e7 | |||
a8d6db9f2d | |||
c396752ce6 | |||
929e6047cf | |||
6465652f82 | |||
9bb69f1ab2 | |||
ac7c001738 | |||
eb5f8168c8 | |||
918b6bf145 | |||
61c0357adc | |||
5ea6d6638a | |||
87d4c9d53d | |||
98bf92466b | |||
595f4b041b | |||
2efdd0b422 | |||
a83214acd4 | |||
f0a977031d | |||
f5a4bdc36f | |||
8255fe6908 | |||
2540dd8079 | |||
ff2569dea8 | |||
dd2a96f948 | |||
7f024bcf78 | |||
cc6d78935c | |||
14bfc6750f | |||
65db81af2d | |||
fbb6025512 | |||
1e8a50077b | |||
e7121cb06a | |||
c2d79c04cc | |||
6273e62e66 | |||
0e0376686d | |||
350b28cb6b | |||
ab1e221ada | |||
262337272f | |||
e2e391325b | |||
aa917fe6ba | |||
c6cb130d5b | |||
4eda624080 | |||
22b61bed44 | |||
1b32719ef7 | |||
b9154061ec | |||
93af4a0707 | |||
39b0e04293 | |||
1e657569a4 | |||
62762c955c | |||
9336508260 | |||
cf1fce88cb | |||
459553056d | |||
8e6f756f78 | |||
f4ea6b15a7 | |||
fff9560f9c | |||
0cdc7797f8 | |||
5bba710b91 | |||
277a8c3076 | |||
de8f66a837 | |||
62a9237925 | |||
b3537381b7 | |||
2f843b4c96 | |||
3d35ef033a | |||
99e394390c | |||
32e153518c | |||
ff57344ee7 | |||
1ec8f34238 | |||
28a1ee950e | |||
f5cf23fdef | |||
05ffc45c18 | |||
75c7fb55e7 | |||
fa1a907222 | |||
390caf646e | |||
cca1877021 | |||
1d708a9e5c | |||
9783ea9033 | |||
4e07c648e9 | |||
dae45927c1 | |||
14c70d33ed | |||
a1f0e2b7dc | |||
1914c20e67 | |||
5b473c9971 | |||
5144d1eabe | |||
19c1332ad9 | |||
1378433baf | |||
4210c752fc | |||
cdaa9db369 | |||
9b1410abf8 | |||
607c057660 | |||
fd02cb59bb | |||
6bb0e59979 | |||
61a9d8e913 | |||
d5f7e89c5f | |||
5b9228d17f | |||
4e413aaa14 | |||
00a640f2ab | |||
58b45885b6 | |||
dd6b419005 | |||
1bbd41f813 | |||
6151aee1a2 | |||
ab05007b85 | |||
6f3f4719d9 | |||
ed963b33ff | |||
ca2da99b15 | |||
71e4a253a0 | |||
6db1e0c2bc | |||
6c3469205a | |||
deecd2b0b6 | |||
b862e1cabe | |||
09058ae499 | |||
f731197fdd | |||
6df10d1f03 | |||
0cd932ec45 | |||
72ce4ca597 | |||
686cf6b4c1 | |||
03afb130b6 | |||
fbea252656 | |||
518c4b340d | |||
9c78e245c3 | |||
ba32cce126 | |||
8f3facf08a | |||
06e4bed325 | |||
8ad1054436 | |||
f914eb7b31 | |||
2dfd25dc9f | |||
2b87ec6973 | |||
f43ef82c14 | |||
ee4120d0bc | |||
b3bee6e951 | |||
9566243962 | |||
ee01b19354 | |||
6881589a28 | |||
fabe34ecad | |||
b5c0ecf509 | |||
14965f0f39 | |||
ba3518b7e5 | |||
92fc18b543 | |||
2b692028f1 | |||
8f401fe936 | |||
cb11510744 | |||
3e10a41504 | |||
63bfe53e6c | |||
22f8e80d16 | |||
19b80694cb | |||
b9f9c2da34 | |||
f301308c74 | |||
![]() |
f6a49fdfcb | ||
136e68415c | |||
7d0ad493c3 | |||
c5a7cc213f | |||
601fdaf161 | |||
3f58d052a8 | |||
e14ecb51dd | |||
40147884a5 | |||
77a32d793b | |||
977629f5cb | |||
437ab0c22c | |||
ac0ad5a49a | |||
a01d45ed12 | |||
89f1917a7c | |||
9577eeb678 | |||
41359f09e1 | |||
f3a64398d3 | |||
4eb9a72219 | |||
bde2b4e8d4 | |||
29175c3b17 | |||
a4699983d5 | |||
c2ca5e527e | |||
1d1fb02ca7 | |||
6945cf857f | |||
b4e6bbc556 | |||
5a029c01a9 | |||
fbd5783061 | |||
9bee2da343 | |||
4856297998 | |||
3a224b897e | |||
d1266d67ef | |||
924c488af3 | |||
39ae777a63 | |||
6554e248b2 | |||
9cebb17a07 | |||
b32f771390 | |||
e29e41d8b8 | |||
0c7a54d4ba | |||
8a6efc36ed | |||
7766c2d3a5 | |||
cf3b60e19f | |||
4fa98f644f | |||
![]() |
5652ed2070 | ||
6a7063fe0c | |||
ce82a0d961 | |||
d717322f87 | |||
8dc2f750ae | |||
c9d7da58f4 | |||
ec2b957450 | |||
c3313742f2 | |||
6a0f561c91 | |||
69e49e8a81 | |||
da355e2e73 | |||
ad8538666d | |||
bb4775e18e | |||
e23de71f0d | |||
5138b9ee01 | |||
7d7bc941fa | |||
4d1074559b | |||
d10df12b7e | |||
100882bbda | |||
0cfe0825ea | |||
0344b6ea93 | |||
02c8d36bc8 | |||
34d542d40c | |||
2041a01293 | |||
e210480920 | |||
95e2084de3 | |||
09e46d10c8 | |||
e5be8426fa | |||
4529839144 | |||
ee788bd8ed | |||
64acb5ee65 | |||
421dfae1ec | |||
5edad99ff6 | |||
f02867174d | |||
3fa473b9be | |||
abe303bce6 | |||
8a19d41dc4 | |||
ab021ab719 | |||
15379f92f7 | |||
a634d827b7 | |||
607e671d9d | |||
59687dd566 | |||
![]() |
71f913e078 | ||
4a0b7ba3cf | |||
![]() |
753ae6c4a1 | ||
66aea3c6d0 | |||
72fbb34434 | |||
![]() |
def873967d | ||
5974125cbe | |||
91677d836a | |||
6b90ca793d | |||
b4451fbfbe | |||
f62fd84b09 | |||
![]() |
1794ce032a | ||
bae93b1bf1 | |||
![]() |
55ebc8a768 | ||
![]() |
ce39371fe5 | ||
![]() |
fdef102baf | ||
8e503629b1 | |||
![]() |
e473dd051c | ||
![]() |
7a64bb4372 | ||
![]() |
fc66d44e49 | ||
![]() |
2bad64bf0a | ||
![]() |
67a5d3c63f | ||
![]() |
c81e54b06f | ||
![]() |
f8a1a21444 | ||
![]() |
7e006b759f | ||
![]() |
25a1b28147 | ||
![]() |
72a9a6e343 | ||
![]() |
45a8b44c3f | ||
![]() |
6c3d460062 | ||
96282c2c02 | |||
![]() |
b41251966a | ||
b3dc7adeb7 | |||
092cc21f38 | |||
fd2b556606 | |||
9e4c94d09e | |||
dcb254e8e4 | |||
2cb1aff63a | |||
4354d54346 | |||
179be13488 | |||
80dd693ea9 | |||
f516897dad | |||
96b9d19ac5 | |||
d9fbedcf77 | |||
5c9b0ecc0a | |||
![]() |
4ab3d9c7ec | ||
8dbdcd5385 | |||
6ae2b22d50 | |||
853d106630 | |||
![]() |
98c0c4474a | ||
![]() |
cc4c74329a | ||
![]() |
4332bc0fe5 | ||
![]() |
8afeaadfcb | ||
5fd2534216 | |||
66b63eb3c8 | |||
0df1417dda | |||
0d6602d1a3 | |||
![]() |
c979280d45 | ||
![]() |
b9a285b1bd | ||
![]() |
46227405c1 | ||
![]() |
018e8268af | ||
![]() |
28d2331417 | ||
c76a4f7c5d | |||
![]() |
19e7dab62c | ||
3aa9856566 | |||
e7a09457ca | |||
![]() |
ea4c985cf0 | ||
40c029da34 | |||
7d435281ee | |||
7e84a3006b | |||
25f055cb8e | |||
![]() |
4782800476 | ||
![]() |
c8f33e8e69 | ||
a51c4d3b1e | |||
![]() |
84c69d4ddb | ||
2659496d49 | |||
b4ea040946 | |||
d20a1df067 | |||
ca132a1575 | |||
c9b27cbef9 | |||
efe9467a88 | |||
58dcba5ac8 | |||
dd250bade4 | |||
1e804edfcd | |||
![]() |
61a3214fbd | ||
![]() |
a6c1b4b93a | ||
e67a9c9b8f | |||
03cbf1d963 | |||
d5f5378f5e | |||
![]() |
b3444ba020 | ||
dfc5d648f9 | |||
![]() |
a44e981225 | ||
![]() |
1baf550c07 | ||
![]() |
33bdc416e6 | ||
![]() |
14627fbc07 | ||
![]() |
a19efe1e99 | ||
![]() |
f71127d4d9 | ||
![]() |
ea90092c9f | ||
![]() |
f90e256be5 | ||
![]() |
0edf02ea6e | ||
![]() |
b4b282a820 | ||
![]() |
643c62b32a | ||
![]() |
59d4678afd | ||
![]() |
52ff0a4e6c | ||
![]() |
50e2141c82 | ||
![]() |
d503d83fce | ||
5bc8bb3d9b | |||
eb3c8b960a | |||
![]() |
313e4626d3 | ||
![]() |
8d9c8e4f14 | ||
![]() |
f8f22e1397 | ||
![]() |
a66a4764cf | ||
![]() |
73fa046cc8 | ||
![]() |
9e21fc491d | ||
![]() |
85b825d546 | ||
![]() |
e1ba407cc8 | ||
![]() |
55c414d7bd | ||
![]() |
e40666f0b1 | ||
![]() |
addee744ef | ||
700a3da36c | |||
![]() |
b650ce09af | ||
c49099b52e | |||
83a93574c0 | |||
![]() |
bd3564dd0c | ||
![]() |
c3d39cde78 | ||
![]() |
bb4225021f | ||
![]() |
b486b2baa4 | ||
![]() |
679a7b6034 | ||
![]() |
a92f04a3b4 | ||
![]() |
3899860109 | ||
![]() |
92e447cf21 | ||
![]() |
7d10cc61a9 | ||
![]() |
a29297890e | ||
![]() |
9131826058 | ||
![]() |
bcb8b6a988 | ||
![]() |
02be15c8ea | ||
![]() |
2f3798dc9a | ||
b6e4695dcf | |||
53cb150a66 | |||
27ba8db053 | |||
![]() |
2dab3aebe5 | ||
![]() |
4e4565813a | ||
![]() |
01f1e50c4a | ||
![]() |
17a697893d | ||
73e8a03a22 | |||
![]() |
7dc71b91e1 | ||
![]() |
a23181e7db | ||
![]() |
5c5890cfb6 | ||
45d9406352 | |||
b11d465c50 | |||
700ab8dffc | |||
c8d18fc625 | |||
a472b34a2c | |||
5cc0240e54 | |||
113c4df0e1 | |||
dc76d62de7 | |||
![]() |
bc5d6b781f | ||
![]() |
686c9316ce | ||
516fd5649d | |||
c46c68a6a1 | |||
9693572f5e | |||
![]() |
129748a4d8 | ||
a32870fd1a | |||
7c93e99a6c | |||
e73b92601c | |||
61c0eaabd9 | |||
9d93f0f159 | |||
1a8fb106bc | |||
![]() |
8adedcb34b | ||
ce284ee0d4 | |||
![]() |
f26afb2061 | ||
![]() |
b2b5f2213d | ||
![]() |
aafb505d2b | ||
1b57e93cec | |||
f041498d05 | |||
d687722a03 | |||
813d9f17ae | |||
f7b295551f | |||
![]() |
4de864a06a | ||
![]() |
c30966a1ba | ||
![]() |
92eafcfaa3 | ||
43b412ec74 | |||
9a7581cd37 | |||
8caf1ca91a | |||
97d9f85517 | |||
![]() |
5191282b33 | ||
![]() |
56e2f34c57 | ||
440d964af8 | |||
f40fb26ba9 | |||
dc0ef0a928 | |||
559f668112 | |||
669a731fbe | |||
![]() |
1668cbe36b | ||
4e457e9294 | |||
![]() |
1db9e54458 | ||
![]() |
38ab273404 | ||
![]() |
a1f0df3a69 | ||
![]() |
8d455f08cc | ||
09bab17d66 | |||
246a090d10 | |||
![]() |
7dc2dd1ec1 | ||
![]() |
93574151bb | ||
![]() |
012169e12b | ||
![]() |
129b7c0c49 | ||
223b8f4856 | |||
fdf344fd3d | |||
2481d57cec | |||
b129058f3d | |||
f1ed8f608e | |||
6ef0157afb | |||
4efadbc628 | |||
f1f9d13a86 | |||
5194a26395 | |||
![]() |
0362770311 | ||
ced2e8cc80 | |||
![]() |
ffba0c8d04 | ||
![]() |
a398ddf872 | ||
![]() |
3751614acf | ||
![]() |
7bd832495b | ||
![]() |
f30deceb39 | ||
![]() |
66ee298e96 | ||
![]() |
d7df648023 | ||
![]() |
af01e68047 | ||
44ee0c1226 | |||
![]() |
4aa5f4abf0 | ||
5ae83de84f | |||
![]() |
b0d7bf0305 | ||
![]() |
2b675e08e0 | ||
![]() |
62a5b29bea | ||
9a473c5ff7 | |||
5f93f453f5 | |||
![]() |
b66cdcaee9 | ||
![]() |
4e9d02efcf | ||
![]() |
264dc15173 | ||
![]() |
6a5d46b63b | ||
![]() |
b03cbef05a | ||
9bbc1f6d4c | |||
1e2b320f79 | |||
a6a6bda089 | |||
4bb0fcce58 | |||
![]() |
39ea24310c | ||
![]() |
24c3b45a3a | ||
![]() |
d970759e2f | ||
![]() |
784a884002 | ||
![]() |
7ff6d4d16e | ||
![]() |
f0f08c4f7c | ||
ff4cc5f567 | |||
04b2b6c091 | |||
e615b395a0 | |||
de9107932f | |||
ff93d649db | |||
01f586d445 | |||
38f1c9b390 | |||
230bf79513 | |||
f849bb84a1 | |||
06a462edfc | |||
![]() |
a30534ba61 | ||
![]() |
ac0ca414ec | ||
![]() |
d4d252ce71 | ||
7dc217db78 | |||
098fc1aeee | |||
![]() |
d5eacb0227 | ||
![]() |
d970049b37 | ||
![]() |
dc2a09dd83 | ||
![]() |
bd0fd2a1fa | ||
![]() |
28cd507004 | ||
![]() |
f7bca1ae82 | ||
![]() |
dce30ad61e | ||
![]() |
64e701a036 | ||
![]() |
d686290df5 | ||
![]() |
3f7bcc011f | ||
![]() |
17a0fd8bc5 | ||
![]() |
dff10de2d7 | ||
![]() |
94134bcb46 | ||
![]() |
137fa317da | ||
![]() |
5434d0cafc | ||
![]() |
6b907e48fe | ||
0277ee6520 | |||
![]() |
3001b87bdf | ||
93822697ae | |||
4ed6f07a15 | |||
86203e1a31 | |||
![]() |
fcf4ce6a4f | ||
0758d97297 | |||
![]() |
5e19805ae6 | ||
![]() |
85357b5973 | ||
45cbd55898 | |||
![]() |
f08bd29ab8 | ||
4ff60c5402 | |||
e5d1630df3 | |||
5146d2fa3d | |||
62169bd9b7 | |||
![]() |
a40b9ee0c8 | ||
b9a5a4e806 | |||
804dcb6679 | |||
![]() |
0e04ad4c8a | ||
05478ef88b | |||
![]() |
d15d5fe262 | ||
![]() |
588efe0cbf | ||
b404f8039e | |||
961207ee4a | |||
ee73f131d5 | |||
511738aca2 | |||
c5f196c1eb | |||
e9faf37bdd | |||
9f6734456f | |||
5519140fc8 | |||
5cf68a9002 | |||
![]() |
6d14dbdd50 | ||
![]() |
5ed07c758f | ||
02a73096e8 | |||
39f49233f0 | |||
4992737e9e | |||
9f277b047c | |||
![]() |
f0f5b1fe96 | ||
20d7fbdc4e | |||
af4c68304a | |||
![]() |
ea2e138f38 | ||
0e25cd7636 | |||
466959cda2 | |||
![]() |
bb5b9ad25a | ||
![]() |
81fae9ad4c | ||
![]() |
5793830b2d | ||
43237113a0 | |||
![]() |
54e5f40780 | ||
![]() |
8c76b4b801 | ||
![]() |
7d6f69396b | ||
![]() |
69a1acddc7 | ||
![]() |
3c4f80056f | ||
![]() |
5321d45adc | ||
![]() |
121eb4036a | ||
![]() |
07c9bd06cc | ||
![]() |
d07acf57fd | ||
![]() |
4cd7ae14a6 | ||
![]() |
31f103c3ab | ||
d80c7d6c71 | |||
f188bcee2d | |||
fb223056b9 | |||
34f7493376 | |||
![]() |
843877c570 | ||
4deecc2f66 | |||
![]() |
e77e83d5d4 | ||
60c88764af | |||
859987445b | |||
3578603293 | |||
fa588d77a4 | |||
03db893403 | |||
7e14ebdeeb | |||
6033cf6084 | |||
0f7e134411 | |||
5098b682e9 | |||
ec8ce9c46d | |||
3149250dae | |||
ada81a6360 | |||
4b87ca20fa | |||
![]() |
dc102ea323 | ||
![]() |
9ddf522eed | ||
![]() |
2c167faa29 | ||
![]() |
591914fdb9 | ||
![]() |
b0c15ff98f | ||
a331d14445 | |||
668c921972 | |||
fb0647060c | |||
![]() |
53aa64e9ea | ||
e841fd798e | |||
5384455f85 | |||
f1640a0bd1 | |||
ffda341e4b | |||
c5eeec3a08 | |||
1ea33d4e0d | |||
3beaeedf4b | |||
4fcd7babe5 | |||
![]() |
76b06f12ea | ||
![]() |
6f081b0b85 | ||
![]() |
dbd271a20a | ||
![]() |
9df4e9f0d2 | ||
![]() |
8a4cd00ec8 | ||
![]() |
47d199ab0b | ||
![]() |
257b027bac | ||
![]() |
724e7bca32 | ||
![]() |
6c18b4cd99 | ||
![]() |
a8e80d88e1 | ||
![]() |
e8a3ca6e84 | ||
18272be5ad | |||
fe39bc1b32 | |||
1b93727d61 | |||
![]() |
af70d8b2c6 | ||
![]() |
d2e1e7c00a | ||
93ddfb8570 | |||
![]() |
dc78c2d490 | ||
![]() |
41655b2dcb | ||
![]() |
2c5c53ba57 | ||
dd58065255 | |||
b0b6352cc0 | |||
a6987adc0a | |||
30e97416fa | |||
292cd0e5fa | |||
e41dc330bd | |||
![]() |
af39e627da | ||
0831da8403 | |||
c87e190706 | |||
![]() |
3d7a518208 | ||
a980fe5fe4 | |||
![]() |
4dbf8e7594 | ||
![]() |
68e47f26e0 | ||
![]() |
00dde35812 | ||
aec10b2745 | |||
![]() |
484e5336b5 | ||
![]() |
cfcd5bbb8e | ||
![]() |
3269a0a883 | ||
![]() |
aea8df01b7 | ||
![]() |
6a326d1fa8 | ||
![]() |
8dc047e6bb | ||
![]() |
319d321a6a | ||
![]() |
c303348e2e | ||
![]() |
16f36cf5d9 | ||
![]() |
31a8b613bb | ||
![]() |
35a84f301f | ||
![]() |
71fb420b94 | ||
![]() |
e5c1285f90 | ||
7f11b79aac | |||
0744aeb7c9 | |||
bfb7876f3b | |||
![]() |
881822abbd | ||
![]() |
8709350151 | ||
![]() |
71853901a6 | ||
1a9c26ccf7 | |||
![]() |
a9c912378b | ||
248ffe406b | |||
5f42a86e1f | |||
6fb44e9a14 | |||
4c7d3b0351 | |||
![]() |
c4ad3de036 | ||
![]() |
26e41b6e2a | ||
![]() |
75a3042e3b | ||
![]() |
0eceb3a727 | ||
![]() |
aff2120637 | ||
![]() |
1579ea1dd4 | ||
fbbfab380d | |||
da35de466f | |||
815397c466 | |||
![]() |
9f280de2fe | ||
![]() |
6f684cc191 | ||
![]() |
e157651e6a | ||
![]() |
55aaf04f12 | ||
![]() |
ce2f5b98ee | ||
5c9c270f31 | |||
![]() |
e227ebbf25 | ||
![]() |
c2950872b5 | ||
![]() |
312170a560 | ||
86da639c01 | |||
185a9dc6fb | |||
6c4b929c68 | |||
923c64feab | |||
![]() |
dc62a16576 | ||
![]() |
e968e5c7ee | ||
![]() |
d6c8532280 | ||
60e5cbd63b | |||
7681000440 | |||
76b3daab25 | |||
![]() |
5c8e4c22a8 | ||
![]() |
d20a5d6435 | ||
![]() |
55c1a75d9f | ||
![]() |
5654c6d502 | ||
![]() |
7291d08d6a | ||
755ec24ff0 | |||
![]() |
36a4049621 | ||
9797f44d05 | |||
![]() |
c531b16131 | ||
![]() |
cbda27a7e1 | ||
![]() |
a58af46dac | ||
![]() |
d211a32403 | ||
85c99344f1 | |||
edac95e644 | |||
![]() |
312f366501 | ||
![]() |
c721d55246 | ||
![]() |
d74b9159a8 | ||
![]() |
bf1d150954 | ||
![]() |
9a4f4e8a66 | ||
![]() |
4f68ddfad5 | ||
![]() |
3f82b7d97a | ||
![]() |
69e76397f5 | ||
![]() |
4e915f3e39 | ||
![]() |
d20c249796 | ||
![]() |
7a91c0f953 | ||
![]() |
0c7ed71d3d | ||
![]() |
4a48bef6d0 | ||
![]() |
dfa690685a | ||
6a3036eb4a | |||
d0b96ca8a0 | |||
9839f8b9de | |||
4b36fa5aeb | |||
![]() |
8cf57b5308 | ||
342c75b1c4 | |||
![]() |
604c448c69 | ||
![]() |
04ede22e9d | ||
![]() |
3a21410b97 | ||
5e4a6053d3 | |||
156d34d843 | |||
5df9a1ea21 | |||
![]() |
85181d0f5f | ||
d892bf30b3 | |||
e9dcbd931b | |||
0221e61843 | |||
57752e8eb3 | |||
25348fd8a7 | |||
cd995e2075 | |||
d6ae91967d | |||
00d34fd191 | |||
![]() |
f7b73ef11e | ||
942adbd489 | |||
915f178ac3 | |||
44ed5d203b | |||
c12320d60f | |||
![]() |
1811b3fd2e | ||
e25c3eb821 | |||
![]() |
1e2472fe61 | ||
![]() |
5737ed9ed5 | ||
c5af81174e | |||
![]() |
d6a029c6c0 | ||
![]() |
06d8ce02d8 | ||
![]() |
635e485740 | ||
c536bf2514 | |||
![]() |
017311721d | ||
2aeb2099d2 | |||
![]() |
47a818eddb | ||
3a68df687e | |||
![]() |
56b4580762 | ||
0096c27edb | |||
![]() |
2e869560c8 | ||
![]() |
b0faac78cf | ||
ab4767de07 | |||
6b35f8e9a3 | |||
4a72644d92 | |||
![]() |
7cbc1e1930 | ||
![]() |
a2759ae546 | ||
![]() |
f60ca0c37e | ||
b5d9a1f411 | |||
d5ad04cf3a | |||
![]() |
a6d8435d5e | ||
c0e7396455 | |||
![]() |
5074c37370 | ||
![]() |
6a84f5b33d | ||
5bbbfab6ca | |||
![]() |
622a587d25 | ||
e121f99dc5 | |||
6302afa071 | |||
5489fa6955 | |||
![]() |
7c898d7e83 | ||
![]() |
088589b583 | ||
![]() |
6964ea5c04 | ||
32a7104cee | |||
cf6e2c8cb1 | |||
![]() |
7e65a8a112 | ||
015cf35353 | |||
f67cff7db5 | |||
![]() |
698ca3a051 | ||
9c251cc81d | |||
e191955adb | |||
b9b027cd42 | |||
![]() |
d0297753f0 | ||
75f4571864 | |||
0c989614c1 | |||
f788c7e467 | |||
![]() |
6eebb8491f | ||
1390adc40f | |||
b50b664d03 | |||
0c2894b5fb | |||
![]() |
ccee8eb5e3 | ||
7dc9cbe8d2 | |||
bd2c55dd5e | |||
![]() |
3de2c035c6 | ||
6efdee13ad | |||
7e0ee868c2 | |||
868bcf02a8 | |||
90dc7fef08 | |||
![]() |
fa2c307bc6 | ||
![]() |
b8d7ad23a7 | ||
![]() |
14a9756738 | ||
![]() |
e910c708c6 | ||
627ead0be9 | |||
331727da85 | |||
![]() |
dfb6fad6ae | ||
7895373495 | |||
7d7907dee8 | |||
![]() |
29786ad816 | ||
![]() |
568547689a | ||
![]() |
237e693678 | ||
![]() |
d88fad5cb8 | ||
![]() |
cc743f5915 | ||
1a30e2a875 | |||
![]() |
3fbe3c4cf0 | ||
![]() |
d51d7ae44d | ||
![]() |
38c40d1dfe | ||
![]() |
c73027e2d3 | ||
5570f3a2f1 | |||
9f9cbb1e6a | |||
![]() |
737cc61cf7 | ||
125caf4851 | |||
29b5779b8e | |||
1dd6ee8534 | |||
f7a00ad181 | |||
730e21eec6 | |||
23b50cb9b7 | |||
![]() |
e64adc026b | ||
![]() |
d2dbedc0be | ||
8e78fdc72e | |||
b63fb76af2 | |||
3339b35fc6 | |||
2c64a8e9d1 | |||
b5dce75489 | |||
724d8b7388 | |||
54eb5603cc | |||
c4db4a80af | |||
68ac5dfc50 | |||
dcc2bc3cb7 | |||
4418e14338 | |||
201b16b270 | |||
6a84f9c7fe | |||
05851caa9f | |||
44ffba41de | |||
c293060068 | |||
ba69863940 | |||
![]() |
a013612251 | ||
a070f9e8c3 | |||
![]() |
ee09655fbf | ||
![]() |
1ec03d04d7 | ||
a978fe7c1e | |||
99c1e1a387 | |||
4d17b0deec | |||
![]() |
de774ecc6b | ||
![]() |
ef50ddf8ac | ||
![]() |
3acee01801 | ||
9c8fc78212 | |||
![]() |
571736906c | ||
017a4b26f1 | |||
![]() |
deb3a2c672 | ||
0f687b5643 | |||
![]() |
6b31dcfd4d | ||
![]() |
23905636d9 | ||
![]() |
95ae8c6a83 | ||
![]() |
5323914582 | ||
![]() |
7cb046b245 | ||
e9ccc51af5 | |||
3365a6f39f | |||
![]() |
75e672934c | ||
![]() |
9b0454eab5 | ||
![]() |
043246d2f2 | ||
7b913c59b3 | |||
80a370959a | |||
244918fabc | |||
![]() |
ac2d91d320 | ||
![]() |
3889c06be1 | ||
d90f9770f9 | |||
![]() |
dc4edf9be7 | ||
![]() |
c15a82cda8 | ||
![]() |
7439ff5a62 | ||
989baca61a | |||
![]() |
7382d8eaa9 | ||
![]() |
17f1a813bf | ||
![]() |
772c64a33a | ||
b59112ab35 | |||
5816490e11 | |||
028a40b86e | |||
56202996f6 | |||
![]() |
16115af63a | ||
![]() |
b2c0c87d2d | ||
1096b16ae3 | |||
![]() |
2780cbe3c4 | ||
![]() |
33e279a443 | ||
![]() |
b3a2e3be94 | ||
![]() |
3454e63334 | ||
![]() |
40f8698e90 | ||
![]() |
9233444c11 | ||
028b2329db | |||
0f5b60c38a | |||
75464d0d49 | |||
d6f566889c | |||
c5e209e426 | |||
26656fe3c7 | |||
![]() |
b8eaa5208c | ||
04a195ab0e | |||
![]() |
e76c90cb78 | ||
![]() |
fbde202ce5 | ||
![]() |
28bfd30fdb | ||
![]() |
605667ad0e | ||
![]() |
36ba2dfb61 | ||
![]() |
9155f1b8ed | ||
![]() |
d565a50a66 | ||
![]() |
c673b5be9f | ||
![]() |
a2296190b6 | ||
![]() |
667a0cd069 | ||
77fd0b9a1a | |||
![]() |
5543c9980a | ||
![]() |
588e21ef1e | ||
d191c18fe2 | |||
caeb88cc93 | |||
![]() |
b8b5bc5d2e |
2
CHANGELOG.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
1.0.1 Страница заявок корректно отображает мультиответ
|
||||||
|
1.0.0 Добавлены фичи "мультиответ", "перенос строки в своём ответе", "свой ответ", "плейсхолдер своего ответа"
|
2
.env
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
TSC_COMPILE_ON_ERROR=true
|
||||||
|
ESLINT_NO_DEV_ERRORS=true
|
1
.env.development.local
Normal file
@ -0,0 +1 @@
|
|||||||
|
REACT_APP_DOMAIN="https://squiz.pena.digital"
|
1
.env.production.local
Normal file
@ -0,0 +1 @@
|
|||||||
|
REACT_APP_DOMAIN=""
|
26
.gitea/workflows/deployProd.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: Deploy
|
||||||
|
run-name: ${{ gitea.actor }} build image and push to container registry
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CreateImage:
|
||||||
|
runs-on: [skeris]
|
||||||
|
uses: https://gitea.pena/PenaDevops/actions.git/.gitea/workflows/build-image.yml@v1.1.6-p
|
||||||
|
with:
|
||||||
|
runner: skeris
|
||||||
|
secrets:
|
||||||
|
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||||
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
DeployService:
|
||||||
|
runs-on: [frontprod]
|
||||||
|
needs: CreateImage
|
||||||
|
uses: https://gitea.pena/PenaDevops/actions.git/.gitea/workflows/deploy.yml@v1.1.4-p7
|
||||||
|
with:
|
||||||
|
runner: hubprod
|
||||||
|
actionid: ${{ gitea.run_id }}
|
||||||
|
|
||||||
|
|
26
.gitea/workflows/deployStaging.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: Deploy
|
||||||
|
run-name: ${{ gitea.actor }} build image and push to container registry
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'staging'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CreateImage:
|
||||||
|
runs-on: [skeris]
|
||||||
|
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/build-image.yml@v1.1.6-p
|
||||||
|
with:
|
||||||
|
runner: skeris
|
||||||
|
secrets:
|
||||||
|
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||||
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
DeployService:
|
||||||
|
runs-on: [frontstaging]
|
||||||
|
needs: CreateImage
|
||||||
|
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/deploy.yml@v1.1.4-p7
|
||||||
|
with:
|
||||||
|
runner: frontstaging
|
||||||
|
actionid: ${{ gitea.run_id }}
|
||||||
|
|
||||||
|
|
14
.gitea/workflows/lint.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
name: Lint
|
||||||
|
run-name: ${{ gitea.actor }} produce linting
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Lint:
|
||||||
|
runs-on: [hubstaging]
|
||||||
|
uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/lint.yml@v1.1.0
|
||||||
|
with:
|
||||||
|
runner: hubstaging
|
2
.gitignore
vendored
@ -14,9 +14,7 @@
|
|||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
.env.local
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
include:
|
|
||||||
- project: "devops/pena-continuous-integration"
|
|
||||||
file: "/templates/docker/build-template.gitlab-ci.yml"
|
|
||||||
- project: "devops/pena-continuous-integration"
|
|
||||||
file: "/templates/docker/clean-template.gitlab-ci.yml"
|
|
||||||
- project: "devops/pena-continuous-integration"
|
|
||||||
file: "/templates/docker/deploy-template.gitlab-ci.yml"
|
|
||||||
stages:
|
|
||||||
- clean
|
|
||||||
- build
|
|
||||||
- deploy
|
|
||||||
|
|
||||||
clear-old-images:
|
|
||||||
extends: .clean_template
|
|
||||||
variables:
|
|
||||||
STAGING_BRANCH: "main"
|
|
||||||
PRODUCTION_BRANCH: "main"
|
|
||||||
image:
|
|
||||||
name: docker/compose:1.28.0
|
|
||||||
entrypoint: [""]
|
|
||||||
before_script:
|
|
||||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
|
||||||
- docker images
|
|
||||||
script:
|
|
||||||
- docker system prune -af
|
|
||||||
build-app:
|
|
||||||
extends: .build_template
|
|
||||||
variables:
|
|
||||||
DOCKER_BUILD_PATH: "./Dockerfile"
|
|
||||||
STAGING_BRANCH: "main"
|
|
||||||
PRODUCTION_BRANCH: "main"
|
|
||||||
|
|
||||||
deploy-to-staging:
|
|
||||||
extends: .deploy_template
|
|
||||||
variables:
|
|
||||||
DEPLOY_TO: "staging"
|
|
||||||
BRANCH: "main"
|
|
||||||
|
|
1
.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
@frontend:registry=http://gitea.pena/api/packages/skeris/npm/
|
4
.vs/VSWorkspaceState.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"ExpandedNodes": [""],
|
||||||
|
"PreviewInSolutionExplorer": false
|
||||||
|
}
|
BIN
.vs/slnx.sqlite
Normal file
BIN
.vs/squiz/v15/.suo
Normal file
5
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"godrix.svgr-preview"
|
||||||
|
]
|
||||||
|
}
|
1
.yarnrc
@ -1 +0,0 @@
|
|||||||
"@frontend:registry" "https://penahub.gitlab.yandexcloud.net/api/v4/packages/npm/"
|
|
13
Containerfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
FROM gitea.pena/penadevops/container-images/node:main as build
|
||||||
|
|
||||||
|
WORKDIR /usr/app
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
|
||||||
|
RUN npm install --force && yarn cache clean
|
||||||
|
RUN psstat.sh "npm run build"
|
||||||
|
|
||||||
|
FROM gitea.pena/penadevops/container-images/nginx:main as result
|
||||||
|
WORKDIR /usr/share/nginx/html
|
||||||
|
COPY --from=build /usr/app/build/ /usr/share/nginx/html
|
||||||
|
COPY hub.conf /etc/nginx/conf.d/default.conf
|
14
Dockerfile
@ -1,14 +0,0 @@
|
|||||||
FROM node:20.10-alpine3.18 as build
|
|
||||||
|
|
||||||
RUN apk update && rm -rf /var/cache/apk/*
|
|
||||||
WORKDIR /usr/app
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN yarn install --ignore-scripts --non-interactive --frozen-lockfile && yarn cache clean
|
|
||||||
RUN yarn build
|
|
||||||
|
|
||||||
|
|
||||||
FROM nginx:latest as result
|
|
||||||
WORKDIR /usr/share/nginx/html
|
|
||||||
COPY --from=build /usr/app/build/ /usr/share/nginx/html
|
|
||||||
COPY hub.conf /etc/nginx/conf.d/default.conf
|
|
577
api-docs.html
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>QUIZ Service API Documentation</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-color: #7E2AEA;
|
||||||
|
--text-color: #333;
|
||||||
|
--bg-color: #fff;
|
||||||
|
--border-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-color);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
padding: 2rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.get { background: #61affe; }
|
||||||
|
.post { background: #49cc90; }
|
||||||
|
.put { background: #fca130; }
|
||||||
|
.delete { background: #f93e3e; }
|
||||||
|
.patch { background: #50e3c2; }
|
||||||
|
|
||||||
|
.schema {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: white;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-decoration: none;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #f1f1f1;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.response {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 4px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.components {
|
||||||
|
margin: 2rem 0;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security {
|
||||||
|
background: #fff3cd;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parameter {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parameter.required {
|
||||||
|
border-left: 4px solid #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enum-values {
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="container">
|
||||||
|
<h1>QUIZ Service API Documentation</h1>
|
||||||
|
<p>Version 1.0.0</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav class="nav">
|
||||||
|
<div class="container">
|
||||||
|
<a href="#components">Components</a>
|
||||||
|
<a href="#quiz">Quiz Endpoints</a>
|
||||||
|
<a href="#question">Question Endpoints</a>
|
||||||
|
<a href="#results">Results Endpoints</a>
|
||||||
|
<a href="#statistics">Statistics Endpoints</a>
|
||||||
|
<a href="#account">Account Endpoints</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<section id="components">
|
||||||
|
<h2>Components</h2>
|
||||||
|
|
||||||
|
<div class="components">
|
||||||
|
<h3>Quiz Model</h3>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"id": integer, // Id of created quiz
|
||||||
|
"qid": string, // string id for customers
|
||||||
|
"deleted": boolean, // true if quiz deleted
|
||||||
|
"archived": boolean, // true if quiz archived
|
||||||
|
"fingerprinting": boolean, // set true for save deviceId
|
||||||
|
"repeatable": boolean, // set true for allow user to repeat quiz
|
||||||
|
"note_prevented": boolean, // set true for save statistic of incomplete quiz passing
|
||||||
|
"mail_notifications": boolean, // set true for mail notification for each quiz passing
|
||||||
|
"unique_answers": boolean, // set true for save statistics only for unique quiz passing
|
||||||
|
"name": string, // name of quiz. max 280 length
|
||||||
|
"description": string, // description of quiz
|
||||||
|
"config": string, // config of quiz. serialized json for rules of quiz flow
|
||||||
|
"status": string, // status of quiz. allow only '', 'draft', 'template', 'stop', 'start'
|
||||||
|
"limit": integer, // limit is count of max quiz passing
|
||||||
|
"due_to": integer, // last time when quiz is valid. timestamp in seconds
|
||||||
|
"time_of_passing": integer, // seconds to pass quiz
|
||||||
|
"pausable": boolean, // true if it is allowed for pause quiz
|
||||||
|
"version": integer, // version of quiz
|
||||||
|
"version_comment": string, // version comment to version of quiz
|
||||||
|
"parent_ids": integer[], // array of previous versions of quiz
|
||||||
|
"created_at": string, // time of creating
|
||||||
|
"updated_at": string, // time of last updating
|
||||||
|
"question_cnt": integer, // count of questions
|
||||||
|
"passed_count": integer, // count passings
|
||||||
|
"average_time": integer, // average time of passing
|
||||||
|
"super": boolean, // set true if squiz realize group functionality
|
||||||
|
"group_id": integer // group of new quiz
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="components">
|
||||||
|
<h3>Question Model</h3>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"id": integer, // Id of created question
|
||||||
|
"quiz_id": integer, // relation to quiz
|
||||||
|
"title": string, // title of question. max 512 length
|
||||||
|
"description": string, // description of question
|
||||||
|
"type": string, // status of question. allow only text, select, file, variant, images, varimg, emoji, date, number, page, rating
|
||||||
|
"required": boolean, // user must pass this question
|
||||||
|
"deleted": boolean, // true if question is deleted
|
||||||
|
"page": integer, // page if question
|
||||||
|
"content": string, // serialized json of created question
|
||||||
|
"version": integer, // version of quiz
|
||||||
|
"parent_ids": integer[], // array of previous versions of quiz
|
||||||
|
"created_at": string, // time of creating
|
||||||
|
"updated_at": string // time of last updating
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="components">
|
||||||
|
<h3>Answer Model</h3>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"Id": integer, // id ответа
|
||||||
|
"Content": string, // контент ответа
|
||||||
|
"QuestionId": integer, // id вопроса к которому ответ
|
||||||
|
"QuizId": integer, // id опроса к которому ответ
|
||||||
|
"Fingerprint": string, // fingerprint
|
||||||
|
"Session": string, // сессия
|
||||||
|
"Result": boolean, // true or false?
|
||||||
|
"CreatedAt": string, // таймшап когда ответ создан
|
||||||
|
"New": boolean, // новый ответ?
|
||||||
|
"Deleted": boolean // удален?
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="components">
|
||||||
|
<h3>LeadTarget Model</h3>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"ID": integer, // primary key
|
||||||
|
"AccountID": string, // account identifier
|
||||||
|
"Type": string, // type of target (mail, telegram, whatsapp)
|
||||||
|
"QuizID": integer, // ID of the quiz
|
||||||
|
"Target": string, // target address
|
||||||
|
"InviteLink": string, // invitation link
|
||||||
|
"Deleted": boolean, // is deleted
|
||||||
|
"CreatedAt": string // creation timestamp
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="components">
|
||||||
|
<h3>TgAccount Model</h3>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"ID": integer, // primary key
|
||||||
|
"ApiID": integer, // Telegram API ID
|
||||||
|
"ApiHash": string, // Telegram API Hash
|
||||||
|
"PhoneNumber": string, // phone number
|
||||||
|
"Password": string, // account password
|
||||||
|
"Status": string, // account status (active, inactive, ban)
|
||||||
|
"Deleted": boolean, // is deleted
|
||||||
|
"CreatedAt": string // creation timestamp
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="quiz">
|
||||||
|
<h2>Quiz Endpoints</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<h3>Create Quiz</h3>
|
||||||
|
<span class="method post">POST</span>
|
||||||
|
<code>/quiz/create</code>
|
||||||
|
<p>Create a new quiz with specified parameters.</p>
|
||||||
|
|
||||||
|
<div class="security">
|
||||||
|
<h4>Security</h4>
|
||||||
|
<p>This endpoint requires authentication.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Request Body:</h4>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"fingerprinting": boolean, // set true for save deviceId
|
||||||
|
"repeatable": boolean, // set true for allow user to repeat quiz
|
||||||
|
"note_prevented": boolean, // set true for save statistic of incomplete quiz passing
|
||||||
|
"mail_notifications": boolean, // set true for mail notification for each quiz passing
|
||||||
|
"unique_answers": boolean, // set true for save statistics only for unique quiz passing
|
||||||
|
"name": string, // name of quiz. max 280 length
|
||||||
|
"description": string, // description of quiz
|
||||||
|
"config": string, // config of quiz. serialized json for rules of quiz flow
|
||||||
|
"status": string, // status of quiz. allow only '', 'draft', 'template', 'stop', 'start'
|
||||||
|
"limit": integer, // limit is count of max quiz passing
|
||||||
|
"due_to": integer, // last time when quiz is valid. timestamp in seconds
|
||||||
|
"time_of_passing": integer, // seconds to pass quiz
|
||||||
|
"pausable": boolean, // true if it is allowed for pause quiz
|
||||||
|
"question_cnt": integer, // count of questions
|
||||||
|
"super": boolean, // set true if squiz realize group functionality
|
||||||
|
"group_id": integer // group of new quiz
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Responses:</h4>
|
||||||
|
<div class="response">
|
||||||
|
<h5>201 Created</h5>
|
||||||
|
<p>Quiz successfully created. Returns the created quiz object.</p>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"id": integer,
|
||||||
|
"qid": string,
|
||||||
|
"name": string,
|
||||||
|
"description": string,
|
||||||
|
// ... other quiz properties
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="response">
|
||||||
|
<h5>422 Unprocessable Entity</h5>
|
||||||
|
<p>Name field should have less than 280 characters.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="response">
|
||||||
|
<h5>406 Not Acceptable</h5>
|
||||||
|
<p>Status on creating must be only draft, template, stop, start or due to time must be lesser than now.</p>
|
||||||
|
<div class="enum-values">
|
||||||
|
Allowed status values: '', 'draft', 'template', 'stop', 'start'
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="response">
|
||||||
|
<h5>409 Conflict</h5>
|
||||||
|
<p>You can pause quiz only if it has deadline for passing.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="response">
|
||||||
|
<h5>500 Internal Server Error</h5>
|
||||||
|
<p>If you get any content string send it to developer.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<h3>Get Quiz List</h3>
|
||||||
|
<span class="method post">POST</span>
|
||||||
|
<code>/quiz/getList</code>
|
||||||
|
<p>Get paginated list of quizzes with filtering options.</p>
|
||||||
|
|
||||||
|
<h4>Request Body:</h4>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"limit": integer,
|
||||||
|
"offset": integer,
|
||||||
|
"from": integer,
|
||||||
|
"to": integer,
|
||||||
|
"search": string,
|
||||||
|
"status": string,
|
||||||
|
"deleted": boolean,
|
||||||
|
"archived": boolean,
|
||||||
|
"super": boolean,
|
||||||
|
"group_id": integer
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Responses:</h4>
|
||||||
|
<div class="response">
|
||||||
|
<h5>200 OK</h5>
|
||||||
|
<p>Returns list of quizzes with total count.</p>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"count": integer,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": integer,
|
||||||
|
"qid": string,
|
||||||
|
// ... other quiz properties
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="response">
|
||||||
|
<h5>406 Not Acceptable</h5>
|
||||||
|
<p>Inappropriate status, allowed only '', 'stop', 'start', 'draft', 'template', 'timeout', 'offlimit'.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="response">
|
||||||
|
<h5>500 Internal Server Error</h5>
|
||||||
|
<p>If you get any content string send it to developer.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add more quiz endpoints -->
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="question">
|
||||||
|
<h2>Question Endpoints</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<h3>Create Question</h3>
|
||||||
|
<span class="method post">POST</span>
|
||||||
|
<code>/question/create</code>
|
||||||
|
<p>Create a new question for a quiz.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add more question endpoints -->
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="results">
|
||||||
|
<h2>Results Endpoints</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<h3>Get Quiz Results</h3>
|
||||||
|
<span class="method post">POST</span>
|
||||||
|
<code>/results/getResults/{quizId}</code>
|
||||||
|
<p>Get list of quiz results with pagination.</p>
|
||||||
|
|
||||||
|
<h4>Path Parameters:</h4>
|
||||||
|
<div class="parameter">
|
||||||
|
<code>quizId</code> - ID of the quiz to get results for
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Request Body:</h4>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"to": integer, // таймштамп времени, до которого выбирать статистику. если 0 или не передано - этого ограничения нет
|
||||||
|
"from": integer, // таймштамп времени, после которого выбирать статистику. если 0 или не передано - этого ограничения нет
|
||||||
|
"new": boolean, // флаг, по которому вернутся только новые результаты, ещё не просмотренные пользователем
|
||||||
|
"page": integer, // номер страницы для пагинации
|
||||||
|
"limit": integer // размер страницы для пагинации
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Responses:</h4>
|
||||||
|
<div class="response">
|
||||||
|
<h5>200 OK</h5>
|
||||||
|
<p>Returns paginated list of results.</p>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"total_count": integer, // общее количество элементов удволетворяющее фильтру
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"content": string, // содержимое ответа
|
||||||
|
"id": integer, // айдишник ответа
|
||||||
|
"new": boolean, // статус, был ли просмотрен ответ
|
||||||
|
"created_at": string // время создания этого результата
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<h3>Export Results</h3>
|
||||||
|
<span class="method post">POST</span>
|
||||||
|
<code>/results/{quizID}/export</code>
|
||||||
|
<p>Export quiz results to CSV format.</p>
|
||||||
|
|
||||||
|
<h4>Path Parameters:</h4>
|
||||||
|
<div class="parameter required">
|
||||||
|
<code>quizID</code> - ID of the quiz to export results from
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Request Body:</h4>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"to": string, // Дата окончания диапазона времени для экспорта результатов
|
||||||
|
"from": string, // Дата начала временного диапазона для экспорта результатов
|
||||||
|
"new": boolean // Экспортировать ли только новые результаты?
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Responses:</h4>
|
||||||
|
<div class="response">
|
||||||
|
<h5>200 OK</h5>
|
||||||
|
<p>Returns CSV file with quiz results.</p>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>Content-Type: text/csv</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="telegram">
|
||||||
|
<h2>Telegram Endpoints</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<h3>Create Telegram Account</h3>
|
||||||
|
<span class="method post">POST</span>
|
||||||
|
<code>/telegram/create</code>
|
||||||
|
<p>Authorize server in Telegram account.</p>
|
||||||
|
|
||||||
|
<h4>Request Body:</h4>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"ApiID": integer, // Telegram API ID
|
||||||
|
"ApiHash": string, // Telegram API Hash
|
||||||
|
"PhoneNumber": string, // Phone number
|
||||||
|
"Password": string // Account password
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Responses:</h4>
|
||||||
|
<div class="response">
|
||||||
|
<h5>200 OK</h5>
|
||||||
|
<p>Returns signature for code verification.</p>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"signature": string // Session identifier for code verification
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="response">
|
||||||
|
<h5>409 Conflict</h5>
|
||||||
|
<p>Account already exists and is active.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="audience">
|
||||||
|
<h2>Audience Endpoints</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<h3>Create Quiz Audience</h3>
|
||||||
|
<span class="method post">POST</span>
|
||||||
|
<code>/quiz/{quizID}/auditory</code>
|
||||||
|
<p>Create audience for a quiz.</p>
|
||||||
|
|
||||||
|
<h4>Path Parameters:</h4>
|
||||||
|
<div class="parameter required">
|
||||||
|
<code>quizID</code> - ID of the quiz
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Responses:</h4>
|
||||||
|
<div class="response">
|
||||||
|
<h5>200 OK</h5>
|
||||||
|
<p>Returns ID of created audience.</p>
|
||||||
|
<div class="schema">
|
||||||
|
<pre><code>{
|
||||||
|
"id": integer // ID of created auditory
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="statistics">
|
||||||
|
<h2>Statistics Endpoints</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<h3>Get Question Statistics</h3>
|
||||||
|
<span class="method post">POST</span>
|
||||||
|
<code>/statistic/{quizID}/questions</code>
|
||||||
|
<p>Get statistics for specific questions in a quiz.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add more statistics endpoints -->
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="account">
|
||||||
|
<h2>Account Endpoints</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<h3>Add Lead Target</h3>
|
||||||
|
<span class="method post">POST</span>
|
||||||
|
<code>/account/leadtarget</code>
|
||||||
|
<p>Add a target destination for lead notifications.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add more account endpoints -->
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="container">
|
||||||
|
<p>© 2024 QUIZ Service API Documentation</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,17 +1,14 @@
|
|||||||
const CracoAlias = require("craco-alias");
|
const CracoAlias = require("craco-alias");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
{
|
||||||
plugin: CracoAlias,
|
plugin: CracoAlias,
|
||||||
options: {
|
options: {
|
||||||
source: "tsconfig",
|
source: "tsconfig",
|
||||||
// baseUrl SHOULD be specified
|
baseUrl: "./src",
|
||||||
// plugin does not take it from tsconfig
|
tsConfigPath: "./tsconfig.extend.json",
|
||||||
baseUrl: "./src",
|
},
|
||||||
// tsConfigPath should point to the file where "baseUrl" and "paths" are specified
|
},
|
||||||
tsConfigPath: "./tsconfig.extend.json"
|
],
|
||||||
}
|
};
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
@ -3,8 +3,23 @@ import { defineConfig } from "cypress";
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
e2e: {
|
e2e: {
|
||||||
baseUrl: 'http://localhost:3000',
|
baseUrl: 'http://localhost:3000',
|
||||||
viewportWidth: 1440,
|
viewportWidth: 1280,
|
||||||
viewportHeight: 900,
|
viewportHeight: 720,
|
||||||
supportFile: false,
|
video: true,
|
||||||
|
screenshotOnRunFailure: true,
|
||||||
|
supportFile: 'cypress/support/e2e.ts',
|
||||||
|
defaultCommandTimeout: 10000,
|
||||||
|
pageLoadTimeout: 30000,
|
||||||
|
requestTimeout: 10000,
|
||||||
|
responseTimeout: 30000,
|
||||||
|
setupNodeEvents(on, config) {
|
||||||
|
// implement node event listeners here
|
||||||
|
},
|
||||||
|
},
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
75
cypress/e2e/personalization.cy.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
describe('Personalization Flow', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Логинимся перед каждым тестом
|
||||||
|
cy.login();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should complete personalization flow and open link in new tab', () => {
|
||||||
|
// Ищем нужный квиз и нажимаем редактировать
|
||||||
|
cy.contains('Сочетание перестановки и размещения')
|
||||||
|
.parent()
|
||||||
|
.parent()
|
||||||
|
.contains('Редактировать')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Переходим на вкладку персонализации
|
||||||
|
cy.contains('Персонализация').click();
|
||||||
|
|
||||||
|
// Ждем загрузки данных
|
||||||
|
cy.get('.MuiSkeleton-root', { timeout: 30000 }).should('not.exist');
|
||||||
|
cy.wait(6000);
|
||||||
|
|
||||||
|
// Удаляем все существующие ссылки
|
||||||
|
cy.get('body').then(($body) => {
|
||||||
|
if ($body.find('.delete_aud').length > 0) {
|
||||||
|
// Пока есть кнопки удаления - удаляем ссылки
|
||||||
|
const deleteLinks = () => {
|
||||||
|
// Находим первую кнопку удаления и кликаем по ней
|
||||||
|
cy.get('.delete_aud').first().click();
|
||||||
|
// Подтверждаем удаление
|
||||||
|
cy.get('#delete_OK').click();
|
||||||
|
// Проверяем, остались ли еще кнопки удаления
|
||||||
|
cy.get('body').then(($body) => {
|
||||||
|
if ($body.find('.delete_aud').length > 0) {
|
||||||
|
deleteLinks();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
deleteLinks();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Выбираем пол (М)
|
||||||
|
cy.contains('М').click();
|
||||||
|
|
||||||
|
// Генерируем случайный возраст от 1 до 99
|
||||||
|
const randomAge = Math.floor(Math.random() * 99) + 1;
|
||||||
|
|
||||||
|
// Вводим возраст
|
||||||
|
cy.get('input[placeholder="Введите возраст"]')
|
||||||
|
.type(randomAge.toString())
|
||||||
|
.should('have.value', randomAge.toString());
|
||||||
|
|
||||||
|
// Нажимаем кнопку Ок
|
||||||
|
cy.contains('Ок').click();
|
||||||
|
|
||||||
|
// Ждем появления ссылки и получаем её текст
|
||||||
|
cy.get('.link', { timeout: 30000 })
|
||||||
|
.should('be.visible')
|
||||||
|
.invoke('text')
|
||||||
|
.then((text) => {
|
||||||
|
// Исправляем домен в ссылке
|
||||||
|
const url = new URL(text);
|
||||||
|
url.hostname = 's.hbpn.link';
|
||||||
|
const correctUrl = url.toString();
|
||||||
|
|
||||||
|
// Переходим на страницу по исправленной ссылке
|
||||||
|
cy.visit(correctUrl);
|
||||||
|
|
||||||
|
// Проверяем содержимое страницы
|
||||||
|
cy.contains('Сочетание перестановки и размещения').should('exist');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -61,8 +61,6 @@ describe("Тестирование полей главной страницы",
|
|||||||
|
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
|
|
||||||
// cy.get('[data-cy="create-question"]').click();
|
|
||||||
|
|
||||||
cy.get(`[data-cy="select-questiontype-images"]`).click();
|
cy.get(`[data-cy="select-questiontype-images"]`).click();
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="checkbox-optional-question"] > .PrivateSwitchBase-input',
|
'[data-cy="checkbox-optional-question"] > .PrivateSwitchBase-input',
|
||||||
@ -115,8 +113,8 @@ describe("Тестирование полей главной страницы",
|
|||||||
cy.visit(linkText);
|
cy.visit(linkText);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.origin("https://hbpn.link", () => {
|
cy.origin("https://s.hbpn.link", () => {
|
||||||
// <команды, направленные на https://hbpn.link, идут здесь>
|
// <команды, направленные на https://s.hbpn.link, идут здесь>
|
||||||
cy.contains("p", "Заголовок заголовка").should("exist");
|
cy.contains("p", "Заголовок заголовка").should("exist");
|
||||||
cy.wait(100);
|
cy.wait(100);
|
||||||
cy.contains("p", "У нас тут какой-то текст").should("exist");
|
cy.contains("p", "У нас тут какой-то текст").should("exist");
|
||||||
|
@ -22,7 +22,6 @@ describe("Тестирование полей вопросов", () => {
|
|||||||
cy.get('[data-cy="setup-questions"]').click();
|
cy.get('[data-cy="setup-questions"]').click();
|
||||||
|
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
// cy.get('[data-cy="create-question"]').click();
|
|
||||||
|
|
||||||
cy.get(`[data-cy="select-questiontype-images"]`).click();
|
cy.get(`[data-cy="select-questiontype-images"]`).click();
|
||||||
|
|
||||||
|
141
cypress/e2e/quizResultsPage.cy.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import "cypress-file-upload";
|
||||||
|
|
||||||
|
describe("Тест на появление страницы Результатов", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit("http://localhost:3000");
|
||||||
|
cy.wait(1000);
|
||||||
|
cy.contains("Регистрация / Войти").click();
|
||||||
|
const login = "valid_user@exammple.com";
|
||||||
|
const password = "valid_password";
|
||||||
|
|
||||||
|
cy.get("#email").type(login);
|
||||||
|
cy.get("#password").type(password);
|
||||||
|
cy.get('button[type="submit"]').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Тест ", () => {
|
||||||
|
//создаём вопрос и выбираем стартовую страницу
|
||||||
|
cy.get('[data-cy="create-quiz"]').click();
|
||||||
|
cy.wait(1000);
|
||||||
|
cy.get('button[data-cy="create-quiz-card"]').eq(0).click();
|
||||||
|
cy.wait(1000);
|
||||||
|
cy.get('button[data-cy="select-quiz-layout-standard"]').click();
|
||||||
|
cy.get('input[type="checkbox"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="setup-questions"]').click();
|
||||||
|
|
||||||
|
cy.wait(500);
|
||||||
|
|
||||||
|
cy.get(`[data-cy="select-questiontype-images"]`).click();
|
||||||
|
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="checkbox-optional-question"] > .PrivateSwitchBase-input',
|
||||||
|
).click();
|
||||||
|
|
||||||
|
cy.get("#questionTitle").type("Вопрос");
|
||||||
|
|
||||||
|
cy.get('[data-cy="quiz-variant-question-answer"]')
|
||||||
|
.eq(0)
|
||||||
|
.type("1")
|
||||||
|
.type("{enter}");
|
||||||
|
cy.get('[data-cy="quiz-variant-question-answer"]')
|
||||||
|
.eq(1)
|
||||||
|
.should("have.value", "")
|
||||||
|
.type("2");
|
||||||
|
|
||||||
|
cy.get('[data-cy="next-step"]').click();
|
||||||
|
|
||||||
|
//проверяем все поля на пустоту страницы результата
|
||||||
|
|
||||||
|
cy.get(`[data-cy="expand-question"]`).eq(0).click();
|
||||||
|
cy.wait(200);
|
||||||
|
cy.get("#after-the-contact-form").click();
|
||||||
|
|
||||||
|
cy.get("#heading-result").should("have.value", "");
|
||||||
|
cy.get("#headline-is-bolder").should("have.value", "");
|
||||||
|
cy.get("#heading-description").should("have.value", "");
|
||||||
|
|
||||||
|
cy.get(`[data-cy="add-button"]`).click();
|
||||||
|
cy.wait(200);
|
||||||
|
cy.get("#button-text-result").should("have.value", "");
|
||||||
|
cy.get("#link-page-result").should("have.value", "");
|
||||||
|
|
||||||
|
//перемещаемся на странице тестового просмотра
|
||||||
|
|
||||||
|
cy.visit("http://localhost:3000/view");
|
||||||
|
|
||||||
|
cy.get(".MuiFormControlLabel-label").contains("1").click();
|
||||||
|
cy.get("#buttonFurther").click();
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
cy.get("#name-input").type("Name");
|
||||||
|
cy.get("#email-input").type("valid_user@exammple.com");
|
||||||
|
cy.get("#phone-input").type("+2682585");
|
||||||
|
|
||||||
|
cy.get('input[type="checkbox"]').click();
|
||||||
|
cy.wait(200);
|
||||||
|
cy.get("#get-results").click();
|
||||||
|
|
||||||
|
//проверяем что страница результатов не отобразилась
|
||||||
|
|
||||||
|
cy.wait(2000);
|
||||||
|
cy.get("#find-out-more-button").should("not.exist");
|
||||||
|
|
||||||
|
//поселе чего заполняем все поля в результате
|
||||||
|
|
||||||
|
cy.visit("http://localhost:3000/edit");
|
||||||
|
|
||||||
|
cy.get("#heading-result").type("Результат");
|
||||||
|
cy.get("#headline-is-bolder").type("Заголовок пожирнее");
|
||||||
|
cy.get("#heading-description").type("Описание обычного заголовка");
|
||||||
|
cy.get(`[data-cy="add-button"]`).click();
|
||||||
|
cy.wait(200);
|
||||||
|
cy.get("#button-text-result").type("Узнать подробнее");
|
||||||
|
|
||||||
|
//переходим на страницу тестового просмотра
|
||||||
|
|
||||||
|
cy.visit("http://localhost:3000/view");
|
||||||
|
|
||||||
|
cy.get(".MuiFormControlLabel-label").contains("1").click();
|
||||||
|
cy.get("#buttonFurther").click();
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
cy.get("#name-input").type("Name");
|
||||||
|
cy.get("#email-input").type("valid_user@exammple.com");
|
||||||
|
cy.get("#phone-input").type("+2682585");
|
||||||
|
|
||||||
|
cy.get('input[type="checkbox"]').click();
|
||||||
|
cy.wait(200);
|
||||||
|
cy.get("#get-results").click();
|
||||||
|
cy.wait(2000);
|
||||||
|
cy.contains("p", "Заголовок пожирнее").should("exist");
|
||||||
|
|
||||||
|
//Переключаем показывания результата до формы контактов
|
||||||
|
cy.visit("http://localhost:3000/edit");
|
||||||
|
|
||||||
|
cy.get(`[data-cy="expand-question"]`).eq(0).click();
|
||||||
|
cy.wait(200);
|
||||||
|
cy.get("#before-contact-form").click();
|
||||||
|
cy.wait(2000);
|
||||||
|
|
||||||
|
cy.visit("http://localhost:3000/view");
|
||||||
|
cy.get(".MuiFormControlLabel-label").contains("1").click();
|
||||||
|
cy.get("#buttonFurther").click();
|
||||||
|
cy.wait(2000);
|
||||||
|
cy.contains("p", "Заголовок пожирнее").should("exist");
|
||||||
|
|
||||||
|
//тоже самое тольок теперь очищаем поля формы результатов
|
||||||
|
cy.visit("http://localhost:3000/edit");
|
||||||
|
cy.get("#heading-result").clear();
|
||||||
|
cy.get("#headline-is-bolder").clear();
|
||||||
|
cy.get("#heading-description").clear();
|
||||||
|
cy.get("#button-text-result").clear();
|
||||||
|
|
||||||
|
cy.visit("http://localhost:3000/view");
|
||||||
|
|
||||||
|
cy.get(".MuiFormControlLabel-label").contains("1").click();
|
||||||
|
cy.get("#buttonFurther").click();
|
||||||
|
cy.wait(2000);
|
||||||
|
cy.get("#find-out-more-button").should("not.exist");
|
||||||
|
});
|
||||||
|
});
|
28
cypress/support/commands.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Cypress {
|
||||||
|
interface Chainable {
|
||||||
|
login(): Chainable<void>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('login', () => {
|
||||||
|
// Пробуем перейти на страницу входа
|
||||||
|
cy.visit('/signin', {
|
||||||
|
timeout: 10000, // Увеличиваем таймаут до 10 секунд
|
||||||
|
failOnStatusCode: false // Не падаем при ошибках статуса
|
||||||
|
});
|
||||||
|
|
||||||
|
// Проверяем, что мы на странице входа
|
||||||
|
cy.url().should('include', '/signin');
|
||||||
|
|
||||||
|
// Вводим данные для входа
|
||||||
|
cy.get('#email', { timeout: 10000 }).should('be.visible').type('test@test.ru');
|
||||||
|
cy.get('#password', { timeout: 10000 }).should('be.visible').type('testtest');
|
||||||
|
cy.get('button[type="submit"]', { timeout: 10000 }).should('be.visible').click();
|
||||||
|
|
||||||
|
// Ждем успешного входа
|
||||||
|
cy.url().should('not.include', '/signin', { timeout: 10000 });
|
||||||
|
});
|
13
cypress/support/e2e.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands';
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Cypress {
|
||||||
|
interface Chainable {
|
||||||
|
login(): Chainable<void>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
deployments/main/docker-compose.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
services:
|
||||||
|
squiz:
|
||||||
|
container_name: squiz
|
||||||
|
restart: unless-stopped
|
||||||
|
image: gitea.pena/squiz/frontpanel/main:$GITHUB_RUN_NUMBER
|
||||||
|
hostname: squiz
|
||||||
|
tty: true
|
@ -2,12 +2,6 @@ services:
|
|||||||
squiz:
|
squiz:
|
||||||
container_name: squiz
|
container_name: squiz
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
image: gitea.pena/squiz/frontpanel/staging:$GITHUB_RUN_NUMBER
|
||||||
networks:
|
|
||||||
- marketplace_penahub_frontend
|
|
||||||
hostname: squiz
|
hostname: squiz
|
||||||
tty: true
|
tty: true
|
||||||
networks:
|
|
||||||
marketplace_penahub_frontend:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
|
15
jest.config.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module.exports = {
|
||||||
|
transformIgnorePatterns: [
|
||||||
|
'/node_modules/(?!(@frontend/kitui|@frontend/squzanswerer)/)'
|
||||||
|
],
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^@/(.*)$': '<rootDir>/src/$1',
|
||||||
|
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
|
||||||
|
'^@assets/(.*)$': '<rootDir>/src/assets/$1'
|
||||||
|
},
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.(ts|tsx)$': 'ts-jest'
|
||||||
|
}
|
||||||
|
};
|
20860
package-lock.json
generated
Normal file
42
package.json
@ -6,9 +6,11 @@
|
|||||||
"@craco/craco": "^7.0.0",
|
"@craco/craco": "^7.0.0",
|
||||||
"@emotion/react": "^11.10.5",
|
"@emotion/react": "^11.10.5",
|
||||||
"@emotion/styled": "^11.10.5",
|
"@emotion/styled": "^11.10.5",
|
||||||
"@frontend/kitui": "^1.0.55",
|
"@frontend/kitui": "^1.0.108",
|
||||||
|
"@frontend/squzanswerer": "^1.0.57",
|
||||||
"@mui/icons-material": "^5.10.14",
|
"@mui/icons-material": "^5.10.14",
|
||||||
"@mui/material": "^5.10.14",
|
"@mui/material": "^5.10.14",
|
||||||
|
"@mui/x-charts": "^6.19.5",
|
||||||
"@mui/x-date-pickers": "^6.16.1",
|
"@mui/x-date-pickers": "^6.16.1",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^13.0.0",
|
"@testing-library/react": "^13.0.0",
|
||||||
@ -17,25 +19,27 @@
|
|||||||
"@types/file-saver": "^2.0.5",
|
"@types/file-saver": "^2.0.5",
|
||||||
"@types/jest": "^27.0.1",
|
"@types/jest": "^27.0.1",
|
||||||
"@types/node": "^16.7.13",
|
"@types/node": "^16.7.13",
|
||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.2.55",
|
||||||
"@types/react-dnd": "^3.0.2",
|
"@types/react-dnd": "^3.0.2",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.2.19",
|
||||||
|
"@types/react-slick": "^0.23.13",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"cypress-file-upload": "^5.0.8",
|
"country-flag-emoji-polyfill": "^0.1.8",
|
||||||
"cytoscape": "^3.26.0",
|
"cytoscape": "^3.26.0",
|
||||||
"cytoscape-popper": "^2.0.0",
|
"cytoscape-popper": "^2.0.0",
|
||||||
"date-fns": "^3.0.6",
|
"date-fns": "^3.0.6",
|
||||||
"dayjs": "^1.11.10",
|
"emoji-mart": "^5.6.0",
|
||||||
"emoji-mart": "^5.5.2",
|
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"formik": "^2.4.5",
|
"formik": "^2.4.5",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"immer": "^10.0.3",
|
"immer": "^10.0.3",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
|
"moment": "^2.30.1",
|
||||||
"nanoid": "^5.0.3",
|
"nanoid": "^5.0.3",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
"react-colorful": "^5.6.1",
|
||||||
"react-cytoscapejs": "^2.0.0",
|
"react-cytoscapejs": "^2.0.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",
|
||||||
@ -44,10 +48,15 @@
|
|||||||
"react-error-boundary": "^4.0.11",
|
"react-error-boundary": "^4.0.11",
|
||||||
"react-image-crop": "^10.1.5",
|
"react-image-crop": "^10.1.5",
|
||||||
"react-image-file-resizer": "^0.4.8",
|
"react-image-file-resizer": "^0.4.8",
|
||||||
|
"react-lazily": "^0.9.2",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"react-router-dom": "^6.6.2",
|
"react-router-dom": "^6.6.2",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"react-slick": "^0.29.0",
|
||||||
|
"slick-carousel": "^1.8.1",
|
||||||
|
"swiper": "^11.1.4",
|
||||||
"swr": "^2.2.4",
|
"swr": "^2.2.4",
|
||||||
|
"transliteration": "^2.3.5",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"use-debounce": "^9.0.4",
|
"use-debounce": "^9.0.4",
|
||||||
"web-vitals": "^2.1.0",
|
"web-vitals": "^2.1.0",
|
||||||
@ -59,9 +68,10 @@
|
|||||||
"build": "craco build",
|
"build": "craco build",
|
||||||
"test": "craco test",
|
"test": "craco test",
|
||||||
"eject": "craco eject",
|
"eject": "craco eject",
|
||||||
|
"code:format": "prettier --write --ignore-unknown",
|
||||||
|
"prepare": "husky install",
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
"code:format": "prettier ./src --write --ignore-unknown",
|
"cypress:run": "cypress run"
|
||||||
"prepare": "husky install"
|
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@ -76,18 +86,30 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@emoji-mart/data": "^1.1.2",
|
"@emoji-mart/data": "^1.2.1",
|
||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
"@types/cytoscape-popper": "^2.0.4",
|
"@types/cytoscape-popper": "^2.0.4",
|
||||||
"@types/react-beautiful-dnd": "^13.1.4",
|
"@types/react-beautiful-dnd": "^13.1.4",
|
||||||
"@types/react-cytoscapejs": "^1.2.4",
|
"@types/react-cytoscapejs": "^1.2.4",
|
||||||
"craco-alias": "^3.0.1",
|
"craco-alias": "^3.0.1",
|
||||||
"cypress": "^13.6.1",
|
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^15.2.0",
|
"lint-staged": "^15.2.0",
|
||||||
"prettier": "^3.1.1"
|
"prettier": "^3.1.1"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"**/*": "yarn code:format"
|
"**/*": "yarn code:format"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"singleAttributePerLine": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
prettierrc
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"semi": true,
|
|
||||||
"trailingComma": "es5",
|
|
||||||
"singleQuote": false,
|
|
||||||
"printWidth": 120,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": false,
|
|
||||||
"endOfLine": "auto",
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"arrowParens": "always",
|
|
||||||
"jsxSingleQuote": false
|
|
||||||
}
|
|
Before Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 7.7 KiB |
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<browserconfig>
|
|
||||||
<msapplication>
|
|
||||||
<tile>
|
|
||||||
<square150x150logo src="/mstile-150x150.png"/>
|
|
||||||
<TileColor>#da532c</TileColor>
|
|
||||||
</tile>
|
|
||||||
</msapplication>
|
|
||||||
</browserconfig>
|
|
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.5 KiB |
BIN
public/favicon.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
71
public/favicon.svg
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="43.585045"
|
||||||
|
height="39.566273"
|
||||||
|
viewBox="0 0 63.268614 57.700814"
|
||||||
|
fill="none"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
sodipodi:docname="favicon.svg"
|
||||||
|
inkscape:export-filename="favicon.png"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview8"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showguides="true" />
|
||||||
|
<g
|
||||||
|
clip-path="url(#clip0_316_1239)"
|
||||||
|
id="g8"
|
||||||
|
transform="translate(-1.6513393,-3.2563442)">
|
||||||
|
<g
|
||||||
|
id="g9"
|
||||||
|
transform="translate(0.27803262,-0.00871564)">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M 25.9138,3.31956 C 18.594,2.47167 13.5439,10.3345 8.84663,16.0182 4.72431,21.0062 1.6549,26.6402 1.29838,33.1042 0.919075,39.9813 2.16658,47.1435 6.85174,52.1873 11.6777,57.3827 18.9068,60.6653 25.9138,59.604 32.3391,58.6308 35.1822,51.5749 39.9658,47.1716 45.16,42.3905 54.837,40.1668 54.7027,33.1042 54.5683,26.0308 44.3552,24.6463 39.441,19.5621 34.3509,14.2959 33.1853,4.16185 25.9138,3.31956 Z"
|
||||||
|
fill="#7e2aea"
|
||||||
|
id="path1" />
|
||||||
|
<circle
|
||||||
|
cx="44.125999"
|
||||||
|
cy="56.918098"
|
||||||
|
r="4.0390601"
|
||||||
|
fill="#7e2aea"
|
||||||
|
id="circle1" />
|
||||||
|
<circle
|
||||||
|
cx="40.086498"
|
||||||
|
cy="12.1038"
|
||||||
|
r="1.53869"
|
||||||
|
fill="#7e2aea"
|
||||||
|
id="circle2" />
|
||||||
|
<path
|
||||||
|
d="m 64.699,31.4509 c -0.4487,-4.3618 -2.5007,-8.4017 -5.7585,-11.3366 -3.2577,-2.9349 -7.4891,-4.5558 -11.8739,-4.5485 -0.6225,3e-4 -1.2446,0.0329 -1.8638,0.0976 -4.3599,0.4578 -8.3958,2.5137 -11.3293,5.7713 -2.9336,3.2577 -4.557,7.4861 -4.5571,11.8699 v 0 25.3412 h 7.6024 v -10.77 c 2.9724,2.0679 6.5079,3.1735 10.1288,3.1676 0.6226,-2e-4 1.2447,-0.0327 1.8639,-0.0975 2.3167,-0.2435 4.5629,-0.9409 6.6101,-2.0525 2.0472,-1.1116 3.8555,-2.6155 5.3215,-4.4259 1.466,-1.8104 2.5611,-3.8918 3.2227,-6.1254 0.6616,-2.2336 0.8767,-4.5757 0.6332,-6.8924 z m -9.764,8.2359 c -0.8351,1.0374 -1.8677,1.8988 -3.038,2.5343 -1.1704,0.6355 -2.4552,1.0325 -3.78,1.168 -0.3553,0.0369 -0.7122,0.0555 -1.0694,0.0558 -2.2991,-0.0021 -4.5293,-0.7858 -6.3243,-2.2224 -1.7951,-1.4366 -3.0484,-3.4408 -3.5544,-5.6836 -0.5059,-2.2428 -0.2343,-4.5909 0.7702,-6.6591 1.0045,-2.0681 2.6822,-3.7333 4.7578,-4.7222 2.0756,-0.9889 4.4257,-1.2429 6.6647,-0.7202 2.2389,0.5228 4.2336,1.7911 5.6567,3.5969 1.4231,1.8058 2.19,4.0417 2.1749,6.3408 -0.0151,2.2991 -0.8114,4.5248 -2.2582,6.3117 z"
|
||||||
|
fill="#000000"
|
||||||
|
id="path2" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs
|
||||||
|
id="defs8">
|
||||||
|
<clipPath
|
||||||
|
id="clip0_316_1239">
|
||||||
|
<rect
|
||||||
|
width="179.509"
|
||||||
|
height="69.487198"
|
||||||
|
fill="#ffffff"
|
||||||
|
id="rect8"
|
||||||
|
x="0"
|
||||||
|
y="0" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/favicon192.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
public/favicon512.png
Normal file
After Width: | Height: | Size: 20 KiB |
@ -2,19 +2,192 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
|
||||||
|
<title>Pena Quiz</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Веб-сервис с инструментами для повышения эффективности маркетологов."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="keywords"
|
||||||
|
content=" Экосистема маркетинговых инструментов,
|
||||||
|
Инструменты для социальных исследований,
|
||||||
|
Малый бизнес,
|
||||||
|
Маркетинговые инструменты,
|
||||||
|
Социальные исследования,
|
||||||
|
Бизнес-инструменты,
|
||||||
|
Исследование рынка,
|
||||||
|
Аналитика бизнеса,
|
||||||
|
Онлайн-маркетинг,
|
||||||
|
Исследования рынка,
|
||||||
|
Продвижение бизнеса,
|
||||||
|
Реклама и маркетинг,
|
||||||
|
Управление проектами,
|
||||||
|
Автоматизация процессов,
|
||||||
|
Оптимизация бюджета,
|
||||||
|
Планирование стратегии,
|
||||||
|
Оценка эффективности,
|
||||||
|
Анализ данных,
|
||||||
|
Улучшение результатов,
|
||||||
|
Увеличение прибыли,
|
||||||
|
Повышение конкурентоспособности "
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" sizes="any" />
|
||||||
|
<!-- 32×32 -->
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" type="image/svg+xml" />
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.png" />
|
||||||
|
<!-- 180×180 -->
|
||||||
|
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<meta http-equiv="Pragma" content="no-cache" />
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<title>Pena Quiz</title>
|
<!-- Yandex.Metrika counter -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
(function (m, e, t, r, i, k, a) {
|
||||||
|
m[i] =
|
||||||
|
m[i] ||
|
||||||
|
function () {
|
||||||
|
(m[i].a = m[i].a || []).push(arguments);
|
||||||
|
};
|
||||||
|
m[i].l = 1 * new Date();
|
||||||
|
for (var j = 0; j < document.scripts.length; j++) {
|
||||||
|
if (document.scripts[j].src === r) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(k = e.createElement(t)),
|
||||||
|
(a = e.getElementsByTagName(t)[0]),
|
||||||
|
(k.async = 1),
|
||||||
|
(k.src = r),
|
||||||
|
a.parentNode.insertBefore(k, a);
|
||||||
|
})(
|
||||||
|
window,
|
||||||
|
document,
|
||||||
|
"script",
|
||||||
|
"https://mc.yandex.ru/metrika/tag.js",
|
||||||
|
"ym",
|
||||||
|
);
|
||||||
|
|
||||||
|
const domain = location.hostname;
|
||||||
|
if (domain === "quiz.pena.digital") {
|
||||||
|
ym(96979576, "init", {
|
||||||
|
clickmap: true,
|
||||||
|
trackLinks: true,
|
||||||
|
accurateTrackBounce: true,
|
||||||
|
webvisor: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// <!-- Top.Mail.Ru counter -->
|
||||||
|
var _tmr = window._tmr || (window._tmr = []);
|
||||||
|
_tmr.push({
|
||||||
|
id: "3513005",
|
||||||
|
type: "pageView",
|
||||||
|
start: new Date().getTime(),
|
||||||
|
});
|
||||||
|
(function (d, w, id) {
|
||||||
|
if (d.getElementById(id)) return;
|
||||||
|
var ts = d.createElement("script");
|
||||||
|
ts.type = "text/javascript";
|
||||||
|
ts.async = true;
|
||||||
|
ts.id = id;
|
||||||
|
ts.src = "https://top-fwz1.mail.ru/js/code.js";
|
||||||
|
var f = function () {
|
||||||
|
var s = d.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(ts, s);
|
||||||
|
};
|
||||||
|
if (w.opera == "[object Opera]") {
|
||||||
|
d.addEventListener("DOMContentLoaded", f, false);
|
||||||
|
} else {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
})(document, window, "tmr-code");
|
||||||
|
// <!-- /Top.Mail.Ru counter -->
|
||||||
|
}
|
||||||
|
if (domain === "squiz.pena.digital") {
|
||||||
|
ym(96979625, "init", {
|
||||||
|
clickmap: true,
|
||||||
|
trackLinks: true,
|
||||||
|
accurateTrackBounce: true,
|
||||||
|
webvisor: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (domain === "penaquiz.online" || domain === "penaquiz.ru") {
|
||||||
|
ym(97241101, "init", {
|
||||||
|
clickmap: true,
|
||||||
|
trackLinks: true,
|
||||||
|
accurateTrackBounce: true,
|
||||||
|
webvisor: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<noscript>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src="https://mc.yandex.ru/watch/96979576"
|
||||||
|
style="position: absolute; left: -9999px"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src="https://mc.yandex.ru/watch/96979625"
|
||||||
|
style="position: absolute; left: -9999px"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src="https://mc.yandex.ru/watch/97241101"
|
||||||
|
style="position: absolute; left: -9999px"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src="https://top-fwz1.mail.ru/counter?id=3513005;js=na"
|
||||||
|
style="position: absolute; left: -9999px"
|
||||||
|
alt="Top.Mail.Ru"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
|
<!-- /Yandex.Metrika counter -->
|
||||||
|
<script>
|
||||||
|
let params = new URLSearchParams(document.location.search);
|
||||||
|
console.log(params.get("debug"))
|
||||||
|
if (params.get("debug")) {
|
||||||
|
console.log(
|
||||||
|
"mhgfhdhfjhffhfhjfghjgf"
|
||||||
|
)
|
||||||
|
let scriptTag = document.createElement('script');
|
||||||
|
scriptTag.setAttribute('src', "https://markknol.github.io/console-log-viewer/console-log-viewer.js");
|
||||||
|
scriptTag.setAttribute('async', '');
|
||||||
|
document.getElementsByTagName("head")[0].append(scriptTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
//Ждём время отображения сайта и оповещаем при выходе за рамки лимита.
|
||||||
|
const timeout = 25;
|
||||||
|
window.LoadingObserver = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (window.LoadingObserver) fetch({
|
||||||
|
url: "https://squiz.pena.digital/heruvym/v1.0.0/create",
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ Title: "Error", Message: `Реакт приложение не отобразилось за ${timeout} секунд`, system: true })
|
||||||
|
});
|
||||||
|
}, timeout * 1000)
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
23
public/manifest.json
Executable file → Normal file
@ -1,23 +1,10 @@
|
|||||||
{
|
{
|
||||||
"short_name": "React App",
|
"short_name": "PenaHub",
|
||||||
"name": "Create React App Sample",
|
"name": "Pena Hub",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{ "src": "/favicon192.png", "type": "image/png", "sizes": "192x192" },
|
||||||
"src": "favicon.ico",
|
{ "src": "/favicon512.png", "type": "image/png", "sizes": "512x512" }
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
],
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "android-chrome-192x192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "android-chrome-512x512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
|
Before Width: | Height: | Size: 5.9 KiB |
@ -1,48 +0,0 @@
|
|||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
|
||||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="576.000000pt" height="576.000000pt" viewBox="0 0 576.000000 576.000000"
|
|
||||||
preserveAspectRatio="xMidYMid meet">
|
|
||||||
<metadata>
|
|
||||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
|
||||||
</metadata>
|
|
||||||
<g transform="translate(0.000000,576.000000) scale(0.100000,-0.100000)"
|
|
||||||
fill="#000000" stroke="none">
|
|
||||||
<path d="M1980 5475 c-206 -46 -387 -146 -578 -320 -83 -75 -199 -195 -272
|
|
||||||
-281 -48 -56 -136 -162 -155 -186 -39 -50 -211 -264 -215 -268 -4 -4 -155
|
|
||||||
-192 -188 -235 -19 -25 -131 -185 -164 -235 -46 -70 -131 -221 -161 -285 -37
|
|
||||||
-77 -75 -163 -85 -190 -6 -16 -17 -43 -23 -60 -7 -16 -13 -32 -14 -35 -2 -11
|
|
||||||
-27 -86 -32 -100 -3 -8 -8 -24 -10 -34 -2 -10 -12 -53 -23 -95 -10 -42 -21
|
|
||||||
-94 -25 -116 -3 -23 -8 -52 -10 -65 -28 -137 -27 -633 0 -798 3 -15 7 -43 10
|
|
||||||
-62 3 -19 8 -53 11 -75 7 -44 62 -265 76 -305 3 -8 16 -44 28 -80 107 -313
|
|
||||||
338 -619 641 -850 71 -55 243 -170 253 -170 3 0 28 -14 56 -30 28 -17 57 -30
|
|
||||||
65 -30 8 0 15 -4 15 -10 0 -5 9 -10 20 -10 11 0 20 -4 20 -9 0 -5 10 -11 23
|
|
||||||
-14 12 -3 65 -22 117 -42 136 -53 311 -95 465 -112 98 -11 321 -5 411 11 94
|
|
||||||
16 212 54 270 87 20 11 36 14 39 9 7 -12 684 -14 686 -2 1 4 2 169 2 368 l2
|
|
||||||
361 32 40 c87 106 95 114 117 107 12 -4 23 -10 26 -14 6 -8 147 -68 230 -98
|
|
||||||
323 -115 754 -107 1080 20 102 40 294 138 310 159 3 3 15 12 28 18 25 13 25
|
|
||||||
14 118 90 120 97 289 289 335 380 8 14 16 28 19 31 25 25 117 225 148 320 25
|
|
||||||
78 49 173 57 220 2 19 9 62 14 95 9 58 7 350 -3 407 -2 16 -7 47 -11 70 -21
|
|
||||||
133 -75 291 -151 443 -114 230 -275 416 -509 590 -84 62 -342 193 -359 182 -1
|
|
||||||
-1 -9 3 -17 10 -8 6 -41 19 -74 28 -185 51 -307 68 -485 67 -64 0 -131 -3
|
|
||||||
-150 -5 -19 -3 -57 -9 -85 -12 -39 -6 -225 -51 -280 -69 -43 -13 -187 -78
|
|
||||||
-231 -102 l-48 -27 -22 26 c-46 54 -154 244 -312 547 -94 181 -107 205 -114
|
|
||||||
208 -5 2 -8 8 -8 13 0 10 -79 140 -90 149 -4 3 -11 12 -16 21 -35 62 -167 197
|
|
||||||
-241 247 -72 48 -156 87 -208 98 -11 2 -31 7 -45 10 -55 14 -246 13 -310 -1z
|
|
||||||
m2295 -1799 c49 -5 125 -22 155 -34 8 -3 17 -6 20 -7 40 -14 157 -69 184 -87
|
|
||||||
202 -132 334 -305 395 -518 12 -41 26 -86 30 -100 8 -28 7 -276 -2 -325 -31
|
|
||||||
-171 -136 -372 -254 -488 -118 -116 -282 -209 -428 -243 -117 -27 -345 -37
|
|
||||||
-345 -15 0 5 44 35 98 66 96 58 322 203 332 214 3 3 28 24 55 45 75 59 176
|
|
||||||
165 220 231 40 61 88 174 100 235 9 46 10 187 2 225 -16 76 -20 90 -39 134
|
|
||||||
-42 94 -123 196 -223 281 -36 31 -184 134 -205 143 -8 4 -28 16 -45 26 -32 21
|
|
||||||
-153 88 -275 154 -41 22 -77 42 -79 44 -9 7 6 12 54 18 28 4 51 8 52 9 4 3
|
|
||||||
152 -3 198 -8z"/>
|
|
||||||
<path d="M3470 4818 c-28 -10 -80 -62 -81 -81 0 -7 -3 -24 -5 -39 -7 -40 19
|
|
||||||
-91 59 -122 28 -22 45 -27 79 -25 80 5 129 51 135 128 5 52 -18 98 -61 126
|
|
||||||
-30 20 -91 26 -126 13z"/>
|
|
||||||
<path d="M3759 967 c-149 -63 -230 -177 -232 -327 -2 -96 2 -114 36 -180 138
|
|
||||||
-265 520 -262 650 4 34 69 48 162 34 226 -28 127 -97 216 -207 267 -81 38
|
|
||||||
-204 43 -281 10z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.9 KiB |
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "",
|
|
||||||
"short_name": "",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/android-chrome-192x192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/android-chrome-512x512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff",
|
|
||||||
"display": "standalone"
|
|
||||||
}
|
|
302
src/App.tsx
@ -1,104 +1,43 @@
|
|||||||
|
import { clearAuthToken, getMessageFromFetchError, UserAccount, useUserFetcher } from "@frontend/kitui";
|
||||||
|
import type { OriginalUserAccount } from "@root/user";
|
||||||
|
import { clearUserData, setCustomerAccount, setUser, setUserAccount, useUserStore } from "@root/user";
|
||||||
import ContactFormModal from "@ui_kit/ContactForm";
|
import ContactFormModal from "@ui_kit/ContactForm";
|
||||||
import ImageCrop from "@ui_kit/Modal/ImageCrop";
|
import FloatingSupportChat from "@ui_kit/FloatingSupportChat";
|
||||||
import dayjs from "dayjs";
|
import PrivateRoute from "@ui_kit/PrivateRoute";
|
||||||
import "dayjs/locale/ru";
|
import { useAfterPay } from "@utils/hooks/useAutoPay";
|
||||||
|
import { useUserAccountFetcher } from "@utils/hooks/useUserAccountFetcher";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import type { SuspenseProps } from "react";
|
||||||
|
import { lazy, Suspense } from "react";
|
||||||
|
import { lazily } from "react-lazily";
|
||||||
|
import { Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useAmoAccount } from "./api/integration";
|
||||||
|
import ListPageDummy from "./components/Dummys/pageDummys/listPageDummy";
|
||||||
|
import "./index.css";
|
||||||
|
import OutdatedLink from "./pages/auth/OutdatedLink";
|
||||||
|
import RecoverPassword from "./pages/auth/RecoverPassword";
|
||||||
|
import { Restore } from "./pages/auth/Restore";
|
||||||
import SigninDialog from "./pages/auth/Signin";
|
import SigninDialog from "./pages/auth/Signin";
|
||||||
import SignupDialog from "./pages/auth/Signup";
|
import SignupDialog from "./pages/auth/Signup";
|
||||||
import { ViewPage } from "./pages/ViewPublicationPage";
|
import { InfoPrivilege } from "./pages/InfoPrivilege";
|
||||||
import { DesignPage } from "./pages/DesignPage/DesignPage";
|
import AmoTokenExpiredDialog from "./pages/IntegrationsPage/IntegrationsModal/Amo/AmoTokenExpiredDialog";
|
||||||
import {
|
|
||||||
Route,
|
|
||||||
Routes,
|
|
||||||
useLocation,
|
|
||||||
useNavigate,
|
|
||||||
Navigate,
|
|
||||||
} from "react-router-dom";
|
|
||||||
import "./index.css";
|
|
||||||
import ContactFormPage from "./pages/ContactFormPage/ContactFormPage";
|
|
||||||
import InstallQuiz from "./pages/InstallQuiz/InstallQuiz";
|
|
||||||
import Landing from "./pages/Landing/Landing";
|
import Landing from "./pages/Landing/Landing";
|
||||||
import QuestionsPage from "./pages/Questions/QuestionsPage";
|
|
||||||
import { Result } from "./pages/ResultPage/Result";
|
|
||||||
import { ResultSettings } from "./pages/ResultPage/ResultSettings";
|
|
||||||
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
|
|
||||||
import Main from "./pages/main";
|
import Main from "./pages/main";
|
||||||
import EditPage from "./pages/startPage/EditPage";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { Tariffs } from "./pages/Tariffs/Tariffs";
|
|
||||||
import {
|
|
||||||
clearAuthToken,
|
|
||||||
getMessageFromFetchError,
|
|
||||||
useUserFetcher,
|
|
||||||
UserAccount,
|
|
||||||
makeRequest,
|
|
||||||
devlog,
|
|
||||||
createUserAccount,
|
|
||||||
} from "@frontend/kitui";
|
|
||||||
import {
|
|
||||||
clearUserData,
|
|
||||||
setUser,
|
|
||||||
setUserAccount,
|
|
||||||
useUserStore,
|
|
||||||
} from "@root/user";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import PrivateRoute from "@ui_kit/PrivateRoute";
|
|
||||||
|
|
||||||
import { Restore } from "./pages/startPage/Restore";
|
const MyQuizzesFull = lazy(() => import("./pages/createQuize/MyQuizzesFull"));
|
||||||
|
const QuizGallery = lazy(() => import("./pages/createQuize/QuizGallery"));
|
||||||
import { isAxiosError } from "axios";
|
const ViewPage = lazy(() => import("./pages/ViewPublicationPage"));
|
||||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
const Analytics = lazy(() => import("./pages/Analytics/Analytics"));
|
||||||
export function useUserAccountFetcher({
|
const EditPage = lazy(() => import("./pages/startPage/EditPage"));
|
||||||
onError,
|
const { Tariffs } = lazily(() => import("./pages/Tariffs/Tariffs"));
|
||||||
onNewUserAccount,
|
const { DesignPage } = lazily(() => import("./pages/DesignPage/DesignPage"));
|
||||||
url,
|
const { IntegrationsPage } = lazily(() => import("./pages/IntegrationsPage/IntegrationsPage"));
|
||||||
userId,
|
const { QuizAnswersPage } = lazily(() => import("./pages/QuizAnswersPage/QuizAnswersPage"));
|
||||||
}: {
|
const ChatImageNewWindow = lazy(() => import("@ui_kit/FloatingSupportChat/ChatImageNewWindow"));
|
||||||
url: string;
|
const PersonalizationAI = lazy(() => import("./pages/PersonalizationAI/PersonalizationAI"));
|
||||||
userId: string | null;
|
let params = new URLSearchParams(document.location.search);
|
||||||
onNewUserAccount: (response: UserAccount) => void;
|
const isTest = Boolean(params.get("test"))
|
||||||
onError?: (error: any) => void;
|
|
||||||
}) {
|
|
||||||
const onNewUserAccountRef = useRef(onNewUserAccount);
|
|
||||||
const onErrorRef = useRef(onError);
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
onNewUserAccountRef.current = onNewUserAccount;
|
|
||||||
onErrorRef.current = onError;
|
|
||||||
}, [onError, onNewUserAccount]);
|
|
||||||
useEffect(() => {
|
|
||||||
if (!userId) return;
|
|
||||||
const controller = new AbortController();
|
|
||||||
makeRequest<never, UserAccount>({
|
|
||||||
url,
|
|
||||||
contentType: true,
|
|
||||||
method: "GET",
|
|
||||||
useToken: true,
|
|
||||||
withCredentials: false,
|
|
||||||
signal: controller.signal,
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
devlog("User account", result);
|
|
||||||
onNewUserAccountRef.current(result);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
devlog("Error fetching user account", error);
|
|
||||||
if (isAxiosError(error) && error.response?.status === 404) {
|
|
||||||
createUserAccount(controller.signal, url.replace("get", "create"))
|
|
||||||
.then((result) => {
|
|
||||||
devlog("Created user account", result);
|
|
||||||
onNewUserAccountRef.current(result);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
devlog("Error creating user account", error);
|
|
||||||
onErrorRef.current?.(error);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
onErrorRef.current?.(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return () => controller.abort();
|
|
||||||
}, [url, userId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
dayjs.locale("ru");
|
|
||||||
|
|
||||||
const routeslink = [
|
const routeslink = [
|
||||||
{
|
{
|
||||||
@ -115,15 +54,34 @@ const routeslink = [
|
|||||||
sidebar: true,
|
sidebar: true,
|
||||||
footer: true,
|
footer: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/integrations",
|
||||||
|
page: IntegrationsPage,
|
||||||
|
header: true,
|
||||||
|
sidebar: true,
|
||||||
|
footer: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/personalization-ai",
|
||||||
|
page: PersonalizationAI,
|
||||||
|
header: true,
|
||||||
|
sidebar: true,
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
const LazyLoading = ({ children, fallback }: SuspenseProps) => (
|
||||||
|
<Suspense fallback={fallback ?? <></>}>{children}</Suspense>
|
||||||
|
);
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
window.LoadingObserver = false;
|
||||||
const userId = useUserStore((state) => state.userId);
|
const userId = useUserStore((state) => state.userId);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { data: amoAccount } = useAmoAccount();
|
||||||
|
|
||||||
useUserFetcher({
|
useUserFetcher({
|
||||||
url: `https://hub.pena.digital/user/${userId}`,
|
url: `${process.env.REACT_APP_DOMAIN}/user/${userId}`,
|
||||||
userId,
|
userId,
|
||||||
onNewUser: setUser,
|
onNewUser: setUser,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@ -136,8 +94,23 @@ export default function App() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useUserAccountFetcher({
|
useUserAccountFetcher<UserAccount>({
|
||||||
url: "https://squiz.pena.digital/squiz/account/get",
|
url: `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1/account`,
|
||||||
|
userId,
|
||||||
|
onNewUserAccount: setCustomerAccount,
|
||||||
|
onError: (error) => {
|
||||||
|
const errorMessage = getMessageFromFetchError(error);
|
||||||
|
if (errorMessage) {
|
||||||
|
enqueueSnackbar(errorMessage);
|
||||||
|
clearUserData();
|
||||||
|
clearAuthToken();
|
||||||
|
navigate("/signin");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useUserAccountFetcher<OriginalUserAccount>({
|
||||||
|
url: `${process.env.REACT_APP_DOMAIN}/squiz/account/get`,
|
||||||
userId,
|
userId,
|
||||||
onNewUserAccount: setUserAccount,
|
onNewUserAccount: setUserAccount,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@ -151,6 +124,8 @@ export default function App() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useAfterPay();
|
||||||
|
|
||||||
if (location.state?.redirectTo)
|
if (location.state?.redirectTo)
|
||||||
return (
|
return (
|
||||||
<Navigate
|
<Navigate
|
||||||
@ -162,53 +137,148 @@ export default function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{amoAccount && <AmoTokenExpiredDialog isAmoTokenExpired={amoAccount.stale} />}
|
||||||
|
|
||||||
<ContactFormModal />
|
<ContactFormModal />
|
||||||
|
{!isTest && <FloatingSupportChat />}
|
||||||
{location.state?.backgroundLocation && (
|
{location.state?.backgroundLocation && (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/signin" element={<SigninDialog />} />
|
<Route
|
||||||
<Route path="/signup" element={<SignupDialog />} />
|
path="/signin"
|
||||||
<Route path="/restore" element={<Restore />} />
|
element={<SigninDialog />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/signup"
|
||||||
|
element={<SignupDialog />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/recover"
|
||||||
|
element={<Restore />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/changepwd"
|
||||||
|
element={<RecoverPassword />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/changepwd/expired"
|
||||||
|
element={<OutdatedLink />}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
)}
|
)}
|
||||||
<Routes location={location.state?.backgroundLocation || location}>
|
<Routes location={location.state?.backgroundLocation || location}>
|
||||||
<Route path="/" element={<Landing />} />
|
<Route
|
||||||
{/* <Route
|
path="/"
|
||||||
|
element={<Landing />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
path="/signin"
|
path="/signin"
|
||||||
element={
|
element={
|
||||||
<Navigate to="/" replace state={{ redirectTo: "/signin" }} />
|
<Navigate
|
||||||
|
to="/"
|
||||||
|
replace
|
||||||
|
state={{ redirectTo: "/signin" }}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/signup"
|
path="/signup"
|
||||||
element={
|
element={
|
||||||
<Navigate to="/" replace state={{ redirectTo: "/signup" }} />
|
<Navigate
|
||||||
|
to="/"
|
||||||
|
replace
|
||||||
|
state={{ redirectTo: "/signup" }}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/restore"
|
path="/recover"
|
||||||
element={
|
element={
|
||||||
<Navigate to="/" replace state={{ redirectTo: "/restore" }} />
|
<Navigate
|
||||||
|
to="/"
|
||||||
|
replace
|
||||||
|
state={{ redirectTo: "/recover" }}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/list" element={<MyQuizzesFull />} />
|
<Route
|
||||||
<Route path={"/view"} element={<ViewPage />} />
|
path="/changepwd"
|
||||||
<Route path={"/tariffs"} element={<Tariffs />} />
|
element={
|
||||||
|
<Navigate
|
||||||
|
to="/"
|
||||||
|
replace
|
||||||
|
state={{
|
||||||
|
redirectTo: window.location.pathname + window.location.search,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/changepwd/expired"
|
||||||
|
element={
|
||||||
|
<Navigate
|
||||||
|
to="/"
|
||||||
|
replace
|
||||||
|
state={{ redirectTo: "/changepwd/expired" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/gallery"
|
||||||
|
element={<LazyLoading children={<QuizGallery />} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/list"
|
||||||
|
element={
|
||||||
|
<LazyLoading
|
||||||
|
children={<MyQuizzesFull />}
|
||||||
|
fallback={<ListPageDummy />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={"/view/:quizId"}
|
||||||
|
element={<LazyLoading children={<ViewPage />} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={"/tariffs"}
|
||||||
|
element={<LazyLoading children={<Tariffs />} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={"/analytics"}
|
||||||
|
element={<LazyLoading children={<Analytics />} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={"/results/:quizId"}
|
||||||
|
element={<LazyLoading children={<QuizAnswersPage />} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={"/qaz"}
|
||||||
|
element={<LazyLoading children={<InfoPrivilege />} />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={"/image/:srcImage"}
|
||||||
|
element={<ChatImageNewWindow />}
|
||||||
|
/>
|
||||||
<Route element={<PrivateRoute />}>
|
<Route element={<PrivateRoute />}>
|
||||||
{routeslink.map((e, i) => (
|
{routeslink.map((e, i) => (
|
||||||
<Route
|
<Route
|
||||||
key={i}
|
key={i}
|
||||||
path={e.path}
|
path={e.path}
|
||||||
element={
|
element={
|
||||||
<Main
|
<LazyLoading
|
||||||
Page={e.page}
|
children={
|
||||||
header={e.header}
|
<Main
|
||||||
sidebar={e.sidebar}
|
Page={e.page}
|
||||||
footer={e.footer}
|
header={e.header}
|
||||||
|
sidebar={e.sidebar}
|
||||||
|
footer={e.footer}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Route> */}
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
108
src/api/auditory.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
|
||||||
|
|
||||||
|
// Types
|
||||||
|
export interface AuditoryItem {
|
||||||
|
id: number;
|
||||||
|
quiz_id: number;
|
||||||
|
sex: number; // 0 - женский, 1 - мужской, 2 - оба
|
||||||
|
age: string;
|
||||||
|
deleted: boolean;
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditoryResponse {
|
||||||
|
ID: number;
|
||||||
|
quiz_id: number;
|
||||||
|
sex: number;
|
||||||
|
age: string;
|
||||||
|
deleted: boolean;
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request Types
|
||||||
|
export interface AuditoryGetRequest {
|
||||||
|
quizId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditoryDeleteRequest {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditoryAddRequest {
|
||||||
|
sex: number;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameters
|
||||||
|
export interface AuditoryGetParams {
|
||||||
|
quizId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditoryDeleteParams {
|
||||||
|
quizId: number;
|
||||||
|
auditoryId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuditoryAddParams {
|
||||||
|
quizId: number;
|
||||||
|
body: AuditoryAddRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API calls
|
||||||
|
export const auditoryGet = async ({ quizId }: AuditoryGetParams): Promise<[AuditoryItem[] | null, string?]> => {
|
||||||
|
if (!quizId) {
|
||||||
|
return [null, "Quiz ID is required"];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await makeRequest<AuditoryGetRequest, AuditoryItem[]>({
|
||||||
|
url: `${API_URL}/quiz/${quizId}/auditory`,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось получить аудиторию. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const auditoryDelete = async ({ quizId, auditoryId }: AuditoryDeleteParams): Promise<[AuditoryResponse | null, string?]> => {
|
||||||
|
if (!quizId || !auditoryId) {
|
||||||
|
return [null, "Quiz ID and Auditory ID are required"];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await makeRequest<AuditoryDeleteRequest, AuditoryResponse>({
|
||||||
|
url: `${API_URL}/quiz/${quizId}/auditory/${auditoryId}`,
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось удалить аудиторию. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const auditoryAdd = async ({ quizId, body }: AuditoryAddParams): Promise<[AuditoryResponse | null, string?]> => {
|
||||||
|
if (!quizId) {
|
||||||
|
return [null, "Quiz ID is required"];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await makeRequest<AuditoryAddRequest, AuditoryResponse>({
|
||||||
|
url: `${API_URL}/quiz/${quizId}/auditory`,
|
||||||
|
body,
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось добавить аудиторию. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
@ -1,4 +1,6 @@
|
|||||||
import { makeRequest } from "@frontend/kitui";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
LoginRequest,
|
LoginRequest,
|
||||||
@ -6,24 +8,24 @@ import type {
|
|||||||
RegisterRequest,
|
RegisterRequest,
|
||||||
RegisterResponse,
|
RegisterResponse,
|
||||||
} from "@frontend/kitui";
|
} from "@frontend/kitui";
|
||||||
import { parseAxiosError } from "../utils/parse-error";
|
|
||||||
|
|
||||||
const apiUrl =
|
type RecoverResponse = {
|
||||||
process.env.NODE_ENV === "production"
|
message: string;
|
||||||
? "/auth"
|
};
|
||||||
: "https://squiz.pena.digital/auth";
|
|
||||||
|
|
||||||
export async function register(
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/auth`;
|
||||||
|
|
||||||
|
export const register = async (
|
||||||
login: string,
|
login: string,
|
||||||
password: string,
|
password: string,
|
||||||
phoneNumber: string,
|
phoneNumber: string,
|
||||||
): Promise<[RegisterResponse | null, string?]> {
|
): Promise<[RegisterResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const registerResponse = await makeRequest<
|
const registerResponse = await makeRequest<
|
||||||
RegisterRequest,
|
RegisterRequest,
|
||||||
RegisterResponse
|
RegisterResponse
|
||||||
>({
|
>({
|
||||||
url: apiUrl + "/register",
|
url: `${API_URL}/register`,
|
||||||
body: { login, password, phoneNumber },
|
body: { login, password, phoneNumber },
|
||||||
useToken: false,
|
useToken: false,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
@ -32,18 +34,19 @@ export async function register(
|
|||||||
return [registerResponse];
|
return [registerResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError);
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
return [null, `Не удалось зарегестрировать аккаунт. ${error}`];
|
return [null, `Не удалось зарегестрировать аккаунт. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function login(
|
export const login = async (
|
||||||
login: string,
|
login: string,
|
||||||
password: string,
|
password: string,
|
||||||
): Promise<[LoginResponse | null, string?]> {
|
): Promise<[LoginResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const loginResponse = await makeRequest<LoginRequest, LoginResponse>({
|
const loginResponse = await makeRequest<LoginRequest, LoginResponse>({
|
||||||
url: apiUrl + "/login",
|
url: `${API_URL}/login`,
|
||||||
body: { login, password },
|
body: { login, password },
|
||||||
useToken: false,
|
useToken: false,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
@ -55,13 +58,13 @@ export async function login(
|
|||||||
|
|
||||||
return [null, `Не удалось войти. ${error}`];
|
return [null, `Не удалось войти. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export async function logout(): Promise<[unknown, string?]> {
|
export const logout = async (): Promise<[void | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const logoutResponse = await makeRequest<never, void>({
|
const logoutResponse = await makeRequest<never, void>({
|
||||||
url: apiUrl + "/logout",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: `${API_URL}/logout`,
|
||||||
useToken: true,
|
useToken: true,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
@ -72,4 +75,30 @@ export async function logout(): Promise<[unknown, string?]> {
|
|||||||
|
|
||||||
return [null, `Не удалось выйти. ${error}`];
|
return [null, `Не удалось выйти. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const recover = async (
|
||||||
|
email: string,
|
||||||
|
): Promise<[RecoverResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("email", email);
|
||||||
|
formData.append(
|
||||||
|
"RedirectionURL",
|
||||||
|
`${process.env.REACT_APP_DOMAIN}/changepwd`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const recoverResponse = await makeRequest<FormData, RecoverResponse>({
|
||||||
|
url: `${process.env.REACT_APP_DOMAIN}/codeword/recover`,
|
||||||
|
body: formData,
|
||||||
|
useToken: false,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [recoverResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось восстановить пароль. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
63
src/api/cart.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
import type { UserAccount } from "@frontend/kitui";
|
||||||
|
|
||||||
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1/cart`;
|
||||||
|
|
||||||
|
const payCart = async (): Promise<[UserAccount | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const payCartResponse = await makeRequest<never, UserAccount>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/pay`,
|
||||||
|
useToken: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [payCartResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const error = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось оплатить товар из корзины. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addCartItem = async (
|
||||||
|
id: string,
|
||||||
|
): Promise<[UserAccount | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const addedItem = await makeRequest<never, UserAccount>({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `${API_URL}?id=${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [addedItem];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось добавить товар в корзину. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteCartItem = async (
|
||||||
|
id: string,
|
||||||
|
): Promise<[UserAccount | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const deletedItem = await makeRequest<never, UserAccount>({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `${API_URL}?id=${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [deletedItem];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось удалить товар из корзины. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cartApi = {
|
||||||
|
pay: payCart,
|
||||||
|
add: addCartItem,
|
||||||
|
delete: deleteCartItem,
|
||||||
|
};
|
@ -1,19 +1,31 @@
|
|||||||
import axios from "axios";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
const domen =
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
window.location.hostname === "localhost"
|
|
||||||
? "squiz.pena.digital"
|
|
||||||
: window.location.hostname;
|
|
||||||
|
|
||||||
export function sendContactFormRequest(body: {
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/feedback`;
|
||||||
|
|
||||||
|
type SendContactFormBody = {
|
||||||
contact: string;
|
contact: string;
|
||||||
whoami: string;
|
whoami: string;
|
||||||
}) {
|
};
|
||||||
return axios(`https://${domen}/feedback/callme`, {
|
|
||||||
method: "POST",
|
export const sendContactFormRequest = async (
|
||||||
headers: {
|
body: SendContactFormBody,
|
||||||
"Content-Type": "application/json",
|
): Promise<[unknown | null, string?, number?]> => {
|
||||||
},
|
try {
|
||||||
data: body,
|
const sendContactFormResponse = await makeRequest<
|
||||||
});
|
SendContactFormBody,
|
||||||
}
|
unknown
|
||||||
|
>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/callme`,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [sendContactFormResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error, status] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось отправить контакты. ${error}`, status];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
24
src/api/discounts.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { makeRequest } from "@frontend/kitui";
|
||||||
|
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
import type { Discount } from "@model/discounts";
|
||||||
|
|
||||||
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/price/discount`;
|
||||||
|
|
||||||
|
export const getDiscounts = async (
|
||||||
|
userId: string,
|
||||||
|
): Promise<[Discount[] | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const { Discounts } = await makeRequest<never, { Discounts: Discount[] }>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/user/${userId}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [Discounts];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить скидки. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
380
src/api/integration.ts
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
import { QuestionKeys } from "@/pages/IntegrationsPage/IntegrationsModal/Amo/types";
|
||||||
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
import { useToken } from "@frontend/kitui";
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
export type PaginationRequest = {
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/amocrm`;
|
||||||
|
|
||||||
|
// получение информации об аккаунте
|
||||||
|
|
||||||
|
export type AccountResponse = {
|
||||||
|
id: number;
|
||||||
|
accountID: string;
|
||||||
|
amoID: number;
|
||||||
|
name: string;
|
||||||
|
deleted: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
subdomain: string;
|
||||||
|
country: string;
|
||||||
|
driveURL: string;
|
||||||
|
stale: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAccount = async (): Promise<[AccountResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const response = await makeRequest<void, AccountResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/account`,
|
||||||
|
useToken: true,
|
||||||
|
});
|
||||||
|
return [response];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, ""];
|
||||||
|
// return [null, `Не удалось получить информацию об аккаунте. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useAmoAccount() {
|
||||||
|
const token = useToken();
|
||||||
|
|
||||||
|
return useSWR(token ? "amoAccount" : null, () =>
|
||||||
|
makeRequest<void, AccountResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/account`,
|
||||||
|
useToken: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// подключить Amo
|
||||||
|
|
||||||
|
export const connectAmo = async (): Promise<[string | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const response = await makeRequest<void, { link: string }>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/account`,
|
||||||
|
useToken: true,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
return [response.link];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось подключить аккаунт. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// получение токена
|
||||||
|
|
||||||
|
export type TokenPair = {
|
||||||
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTokens = async (): Promise<[TokenPair | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const response = await makeRequest<void, TokenPair>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/webhook/create`,
|
||||||
|
useToken: true,
|
||||||
|
});
|
||||||
|
return [response];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Failed to get tokens. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//получение списка тегов
|
||||||
|
|
||||||
|
export type Tag = {
|
||||||
|
ID: number;
|
||||||
|
AmoID: number;
|
||||||
|
AccountID: number;
|
||||||
|
Entity: string;
|
||||||
|
Name: string;
|
||||||
|
Color: string;
|
||||||
|
Deleted: boolean;
|
||||||
|
CreatedAt: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TagsResponse = {
|
||||||
|
count: number;
|
||||||
|
items: Tag[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTags = async ({ page, size }: PaginationRequest): Promise<[TagsResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const tagsResponse = await makeRequest<PaginationRequest, TagsResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/tags?page=${page}&size=${size}`,
|
||||||
|
});
|
||||||
|
return [tagsResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось получить список тегов. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//получение списка пользователей
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
id: number;
|
||||||
|
amoID: number;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
role: number;
|
||||||
|
group: number;
|
||||||
|
deleted: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
amoUserID: number;
|
||||||
|
|
||||||
|
// Subdomain: string;
|
||||||
|
// AccountID: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UsersResponse = {
|
||||||
|
count: number;
|
||||||
|
items: User[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUsers = async ({ page, size }: PaginationRequest): Promise<[UsersResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const usersResponse = await makeRequest<PaginationRequest, UsersResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/users?page=${page}&size=${size}`,
|
||||||
|
});
|
||||||
|
return [usersResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось получить список пользователей. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//получение списка шагов
|
||||||
|
|
||||||
|
export type Step = {
|
||||||
|
ID: number;
|
||||||
|
AmoID: number;
|
||||||
|
PipelineID: number;
|
||||||
|
AccountID: number;
|
||||||
|
Name: string;
|
||||||
|
Color: string;
|
||||||
|
Deleted: boolean;
|
||||||
|
CreatedAt: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StepsResponse = {
|
||||||
|
count: number;
|
||||||
|
items: Step[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSteps = async ({
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
pipelineId,
|
||||||
|
}: PaginationRequest & { pipelineId: number }): Promise<[StepsResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const stepsResponse = await makeRequest<PaginationRequest & { pipelineId: number }, StepsResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/steps?page=${page}&size=${size}&pipelineID=${pipelineId}`,
|
||||||
|
});
|
||||||
|
return [stepsResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось получить список шагов. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//получение списка воронок
|
||||||
|
|
||||||
|
export type Pipeline = {
|
||||||
|
ID: number;
|
||||||
|
AmoID: number;
|
||||||
|
AccountID: number;
|
||||||
|
Name: string;
|
||||||
|
IsArchive: boolean;
|
||||||
|
Deleted: boolean;
|
||||||
|
CreatedAt: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PipelinesResponse = {
|
||||||
|
count: number;
|
||||||
|
items: Pipeline[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPipelines = async ({ page, size }: PaginationRequest): Promise<[PipelinesResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const pipelinesResponse = await makeRequest<PaginationRequest, PipelinesResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/pipelines?page=${page}&size=${size}`,
|
||||||
|
});
|
||||||
|
return [pipelinesResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось получить список воронок. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//получение настроек интеграции
|
||||||
|
export type QuestionID = Record<string, number>;
|
||||||
|
|
||||||
|
export type IntegrationRules = {
|
||||||
|
PipelineID: number;
|
||||||
|
StepID: number;
|
||||||
|
PerformerID?: number;
|
||||||
|
FieldsRule: FieldsRule;
|
||||||
|
TagsToAdd: {
|
||||||
|
Lead: number[] | null;
|
||||||
|
Contact: number[] | null;
|
||||||
|
Company: number[] | null;
|
||||||
|
Customer: number[] | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type FieldsRule = Record<Partial<QuestionKeys>, null | [{ QuestionID: QuestionID }]>;
|
||||||
|
|
||||||
|
export const getIntegrationRules = async (quizID: string): Promise<[IntegrationRules | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const settingsResponse = await makeRequest<void, IntegrationRules>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/rules/${quizID}`,
|
||||||
|
});
|
||||||
|
return [settingsResponse || null];
|
||||||
|
} catch (nativeError) {
|
||||||
|
if (nativeError.response.status === 404) return [null, "first"];
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось получить настройки интеграции. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//обновление настроек интеграции
|
||||||
|
|
||||||
|
export type IntegrationRulesUpdate = {
|
||||||
|
PerformerID: number;
|
||||||
|
PipelineID: number;
|
||||||
|
StepID: number;
|
||||||
|
Utms: number[];
|
||||||
|
FieldsRule: {
|
||||||
|
Lead: { QuestionID: number }[];
|
||||||
|
Contact: { ContactRuleMap: string }[];
|
||||||
|
Company: { QuestionID: number }[];
|
||||||
|
Customer: { QuestionID: number }[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setIntegrationRules = async (
|
||||||
|
quizID: string,
|
||||||
|
settings: IntegrationRulesUpdate
|
||||||
|
): Promise<[string | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const updateResponse = await makeRequest<IntegrationRulesUpdate, string>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/rules/${quizID}`,
|
||||||
|
body: settings,
|
||||||
|
});
|
||||||
|
return [updateResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Failed to update integration settings. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const updateIntegrationRules = async (
|
||||||
|
quizID: string,
|
||||||
|
settings: IntegrationRulesUpdate
|
||||||
|
): Promise<[string | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const updateResponse = await makeRequest<IntegrationRulesUpdate, string>({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `${API_URL}/rules/${quizID}`,
|
||||||
|
body: settings,
|
||||||
|
});
|
||||||
|
return [updateResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Failed to update integration settings. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Получение кастомных полей
|
||||||
|
|
||||||
|
export type CustomField = {
|
||||||
|
ID: number;
|
||||||
|
AmoID: number;
|
||||||
|
Code: string;
|
||||||
|
AccountID: number;
|
||||||
|
Name: string;
|
||||||
|
EntityType: string;
|
||||||
|
Type: string;
|
||||||
|
Deleted: boolean;
|
||||||
|
CreatedAt: number;
|
||||||
|
};
|
||||||
|
export type Field = {
|
||||||
|
ID: number;
|
||||||
|
AmoID: number;
|
||||||
|
Code: string;
|
||||||
|
AccountID: number;
|
||||||
|
Name: string;
|
||||||
|
Entity: string;
|
||||||
|
Type: string;
|
||||||
|
Deleted: boolean;
|
||||||
|
CreatedAt: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CustomFieldsResponse = {
|
||||||
|
count: number;
|
||||||
|
items: CustomField[];
|
||||||
|
};
|
||||||
|
export type FieldsResponse = {
|
||||||
|
count: number;
|
||||||
|
items: Field[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCustomFields = async (
|
||||||
|
pagination: PaginationRequest
|
||||||
|
): Promise<[CustomFieldsResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const customFieldsResponse = await makeRequest<PaginationRequest, CustomFieldsResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/fields?page=${pagination.page}&size=${pagination.size}`,
|
||||||
|
});
|
||||||
|
return [customFieldsResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось получить список кастомных полей. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Отвязать аккаунт амо от публикации
|
||||||
|
|
||||||
|
export const removeAmoAccount = async (): Promise<[void | null, string?]> => {
|
||||||
|
try {
|
||||||
|
await makeRequest<void>({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `${API_URL}/account`,
|
||||||
|
});
|
||||||
|
return [null, ""];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось отвязать аккаунт. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getFields = async (pagination: PaginationRequest): Promise<[FieldsResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const fieldsResponse = await makeRequest<PaginationRequest, FieldsResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/fields?page=${pagination.page}&size=${pagination.size}`,
|
||||||
|
});
|
||||||
|
return [fieldsResponse, ""];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Не удалось получить список полей. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
55
src/api/makeRequest.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import * as KIT from "@frontend/kitui";
|
||||||
|
import { Method, ResponseType, AxiosError } from "axios";
|
||||||
|
import { redirect } from "react-router-dom";
|
||||||
|
import { clearAuthToken } from "@frontend/kitui";
|
||||||
|
|
||||||
|
import { cleanAuthTicketData } from "@root/ticket";
|
||||||
|
import { clearUserData } from "@root/user";
|
||||||
|
import { clearQuizData } from "@root/quizes/store";
|
||||||
|
|
||||||
|
import type { AxiosResponse } from "axios";
|
||||||
|
import { selectSendingMethod } from "@/ui_kit/FloatingSupportChat/utils";
|
||||||
|
|
||||||
|
interface MakeRequest {
|
||||||
|
method?: Method | undefined;
|
||||||
|
url: string;
|
||||||
|
body?: unknown;
|
||||||
|
useToken?: boolean | undefined;
|
||||||
|
contentType?: boolean | undefined;
|
||||||
|
responseType?: ResponseType | undefined;
|
||||||
|
signal?: AbortSignal | undefined;
|
||||||
|
withCredentials?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtendedAxiosResponse = AxiosResponse & { message: string };
|
||||||
|
|
||||||
|
export const makeRequest = async <TRequest = unknown, TResponse = unknown>(
|
||||||
|
data: MakeRequest,
|
||||||
|
): Promise<TResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await KIT.makeRequest<unknown, TResponse>(data);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (nativeError) {
|
||||||
|
const error = nativeError as AxiosError;
|
||||||
|
|
||||||
|
// if (window.location.hostname !== 'localhost') selectSendingMethod({
|
||||||
|
// messageField: `status: ${error.response?.status}. Message ${(error.response?.data as ExtendedAxiosResponse)?.message}`,
|
||||||
|
// isSnackbar: false,
|
||||||
|
// systemError: true
|
||||||
|
// });
|
||||||
|
if (
|
||||||
|
error.response?.status === 400 &&
|
||||||
|
(error.response?.data as ExtendedAxiosResponse)?.message ===
|
||||||
|
"refreshToken is empty"
|
||||||
|
) {
|
||||||
|
cleanAuthTicketData();
|
||||||
|
clearAuthToken();
|
||||||
|
clearUserData();
|
||||||
|
clearQuizData();
|
||||||
|
redirect("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw nativeError;
|
||||||
|
}
|
||||||
|
};
|
30
src/api/promocode.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
type ActivatePromocodeRequest = { codeword: string } | { fastLink: string };
|
||||||
|
type ActivatePromocodeResponse = { greetings: string };
|
||||||
|
|
||||||
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/codeword/promocode`;
|
||||||
|
|
||||||
|
export const activatePromocode = async (
|
||||||
|
promocode: string,
|
||||||
|
): Promise<[string | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const response = await makeRequest<
|
||||||
|
ActivatePromocodeRequest,
|
||||||
|
ActivatePromocodeResponse
|
||||||
|
>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/activate`,
|
||||||
|
body: { codeword: promocode },
|
||||||
|
contentType: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [response.greetings];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Ошибка при активации промокода. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
@ -1,85 +1,155 @@
|
|||||||
import { makeRequest } from "@frontend/kitui";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import { CreateQuestionRequest } from "model/question/create";
|
|
||||||
import { RawQuestion } from "model/question/question";
|
import { replaceSpacesToEmptyLines } from "@utils/replaceSpacesToEmptyLines";
|
||||||
import {
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
import type { CreateQuestionRequest } from "model/question/create";
|
||||||
|
import type { RawQuestion } from "model/question/question";
|
||||||
|
import type {
|
||||||
GetQuestionListRequest,
|
GetQuestionListRequest,
|
||||||
GetQuestionListResponse,
|
GetQuestionListResponse,
|
||||||
} from "@model/question/getList";
|
} from "@model/question/getList";
|
||||||
import {
|
import type {
|
||||||
EditQuestionRequest,
|
EditQuestionRequest,
|
||||||
EditQuestionResponse,
|
EditQuestionResponse,
|
||||||
} from "@model/question/edit";
|
} from "@model/question/edit";
|
||||||
import {
|
import type {
|
||||||
DeleteQuestionRequest,
|
DeleteQuestionRequest,
|
||||||
DeleteQuestionResponse,
|
DeleteQuestionResponse,
|
||||||
} from "@model/question/delete";
|
} from "@model/question/delete";
|
||||||
import {
|
import type {
|
||||||
CopyQuestionRequest,
|
CopyQuestionRequest,
|
||||||
CopyQuestionResponse,
|
CopyQuestionResponse,
|
||||||
} from "@model/question/copy";
|
} from "@model/question/copy";
|
||||||
import { replaceSpacesToEmptyLines } from "../utils/replaceSpacesToEmptyLines";
|
|
||||||
|
|
||||||
const baseUrl =
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
|
||||||
process.env.NODE_ENV === "production"
|
|
||||||
? "/squiz"
|
|
||||||
: "https://squiz.pena.digital/squiz";
|
|
||||||
|
|
||||||
function createQuestion(body: CreateQuestionRequest) {
|
export const createQuestion = async (
|
||||||
return makeRequest<CreateQuestionRequest, RawQuestion>({
|
body: CreateQuestionRequest,
|
||||||
url: `${baseUrl}/question/create`,
|
): Promise<[RawQuestion | null, string?]> => {
|
||||||
body,
|
try {
|
||||||
method: "POST",
|
const createdQuestion = await makeRequest<
|
||||||
});
|
CreateQuestionRequest,
|
||||||
}
|
RawQuestion
|
||||||
|
>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/question/create`,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
async function getQuestionList(body?: Partial<GetQuestionListRequest>) {
|
return [createdQuestion];
|
||||||
if (!body?.quiz_id) return null;
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
const response = await makeRequest<
|
return [null, `Не удалось создать вопрос. ${error}`];
|
||||||
GetQuestionListRequest,
|
}
|
||||||
GetQuestionListResponse
|
};
|
||||||
>({
|
|
||||||
url: `${baseUrl}/question/getList`,
|
|
||||||
body: { ...defaultGetQuestionListBody, ...body },
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
const clearArrayFromEmptySpaceBlaBlaValue = response.items?.map(
|
|
||||||
(question) => {
|
|
||||||
let data = question;
|
|
||||||
for (let key in question) {
|
|
||||||
if (question[key] === " ") data[key] = "";
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return replaceSpacesToEmptyLines(clearArrayFromEmptySpaceBlaBlaValue);
|
const getQuestionList = async (
|
||||||
}
|
body?: Partial<GetQuestionListRequest>,
|
||||||
|
): Promise<[RawQuestion[] | null, string?]> => {
|
||||||
|
try {
|
||||||
|
if (!body?.quiz_id) return [null, "Квиз не найден"];
|
||||||
|
|
||||||
function editQuestion(body: EditQuestionRequest, signal?: AbortSignal) {
|
const response = await makeRequest<
|
||||||
return makeRequest<EditQuestionRequest, EditQuestionResponse>({
|
GetQuestionListRequest,
|
||||||
url: `${baseUrl}/question/edit`,
|
GetQuestionListResponse
|
||||||
body,
|
>({
|
||||||
method: "PATCH",
|
method: "POST",
|
||||||
signal,
|
url: `${API_URL}/question/getList`,
|
||||||
});
|
body: { ...defaultGetQuestionListBody, ...body },
|
||||||
}
|
});
|
||||||
|
|
||||||
function copyQuestion(questionId: number, quizId: number) {
|
const clearArrayFromEmptySpaceBlaBlaValue = response.items?.map(
|
||||||
return makeRequest<CopyQuestionRequest, CopyQuestionResponse>({
|
(question) => {
|
||||||
url: `${baseUrl}/question/copy`,
|
let data = question;
|
||||||
body: { id: questionId, quiz_id: quizId },
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteQuestion(id: number) {
|
for (let key in question) {
|
||||||
return makeRequest<DeleteQuestionRequest, DeleteQuestionResponse>({
|
if (question[key as keyof RawQuestion] === " ") {
|
||||||
url: `${baseUrl}/question/delete`,
|
//@ts-ignore
|
||||||
body: { id },
|
data[key] = "";
|
||||||
method: "DELETE",
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
replaceSpacesToEmptyLines(clearArrayFromEmptySpaceBlaBlaValue) ?? null,
|
||||||
|
];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить список вопросов. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const editQuestion = async (
|
||||||
|
body: EditQuestionRequest,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<[EditQuestionResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const editedQuestion = await makeRequest<
|
||||||
|
EditQuestionRequest,
|
||||||
|
EditQuestionResponse
|
||||||
|
>({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `${API_URL}/question/edit`,
|
||||||
|
body,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [editedQuestion];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось изменить вопрос. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copyQuestion = async (
|
||||||
|
questionId: number,
|
||||||
|
quizId: number,
|
||||||
|
): Promise<[CopyQuestionResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const copiedQuestion = await makeRequest<
|
||||||
|
CopyQuestionRequest,
|
||||||
|
CopyQuestionResponse
|
||||||
|
>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/question/copy`,
|
||||||
|
body: { id: questionId, quiz_id: quizId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [copiedQuestion];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось скопировать вопрос. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteQuestion = async (
|
||||||
|
id: number,
|
||||||
|
): Promise<[DeleteQuestionResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const deletedQuestion = await makeRequest<
|
||||||
|
DeleteQuestionRequest,
|
||||||
|
DeleteQuestionResponse
|
||||||
|
>({
|
||||||
|
url: `${API_URL}/question/delete`,
|
||||||
|
body: { id },
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [deletedQuestion];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось удалить вопрос. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const questionApi = {
|
export const questionApi = {
|
||||||
create: createQuestion,
|
create: createQuestion,
|
||||||
|
253
src/api/quiz.ts
@ -1,85 +1,197 @@
|
|||||||
import { makeRequest } from "@frontend/kitui";
|
import { makeRequest } from "@api/makeRequest";
|
||||||
import { defaultQuizConfig } from "@model/quizSettings";
|
import { defaultQuizConfig } from "@model/quizSettings";
|
||||||
import { CopyQuizRequest, CopyQuizResponse } from "model/quiz/copy";
|
|
||||||
import { CreateQuizRequest } from "model/quiz/create";
|
|
||||||
import { DeleteQuizRequest, DeleteQuizResponse } from "model/quiz/delete";
|
|
||||||
import { EditQuizRequest, EditQuizResponse } from "model/quiz/edit";
|
|
||||||
import { GetQuizRequest, GetQuizResponse } from "model/quiz/get";
|
|
||||||
import { GetQuizListRequest, GetQuizListResponse } from "model/quiz/getList";
|
|
||||||
import { RawQuiz } from "model/quiz/quiz";
|
|
||||||
|
|
||||||
const baseUrl =
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
process.env.NODE_ENV === "production"
|
|
||||||
? "/squiz"
|
|
||||||
: "https://squiz.pena.digital/squiz";
|
|
||||||
const imagesUrl =
|
|
||||||
process.env.NODE_ENV === "production"
|
|
||||||
? "/squizstorer"
|
|
||||||
: "https://squiz.pena.digital/squizstorer";
|
|
||||||
|
|
||||||
function createQuiz(body?: Partial<CreateQuizRequest>) {
|
import type { RawQuiz } from "model/quiz/quiz";
|
||||||
return makeRequest<CreateQuizRequest, RawQuiz>({
|
import type { CopyQuizRequest, CopyQuizResponse } from "model/quiz/copy";
|
||||||
url: `${baseUrl}/quiz/create`,
|
import type { CreateQuizRequest } from "model/quiz/create";
|
||||||
body: { ...defaultCreateQuizBody, ...body },
|
import type { DeleteQuizRequest, DeleteQuizResponse } from "model/quiz/delete";
|
||||||
method: "POST",
|
import type { EditQuizRequest, EditQuizResponse } from "model/quiz/edit";
|
||||||
});
|
import type { GetQuizRequest, GetQuizResponse } from "model/quiz/get";
|
||||||
}
|
import { transliterate } from 'transliteration';
|
||||||
|
import type {
|
||||||
|
GetQuizListRequest,
|
||||||
|
GetQuizListResponse,
|
||||||
|
} from "model/quiz/getList";
|
||||||
|
|
||||||
async function getQuizList(body?: Partial<GetQuizListRequest>) {
|
type AddedQuizImagesResponse = {
|
||||||
const response = await makeRequest<GetQuizListRequest, GetQuizListResponse>({
|
[key: string]: string;
|
||||||
url: `${baseUrl}/quiz/getList`,
|
};
|
||||||
body: { ...defaultGetQuizListBody, ...body },
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.items;
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
|
||||||
}
|
const IMAGES_URL = `${process.env.REACT_APP_DOMAIN}/squizstorer/v1.0.0`;
|
||||||
|
|
||||||
function getQuiz(body?: Partial<GetQuizRequest>) {
|
export const createQuiz = async (
|
||||||
return makeRequest<GetQuizRequest, GetQuizResponse>({
|
body?: Partial<CreateQuizRequest>,
|
||||||
url: `${baseUrl}/quiz/get`,
|
): Promise<[RawQuiz | null, string?]> => {
|
||||||
body: { ...defaultGetQuizBody, ...body },
|
try {
|
||||||
method: "GET",
|
const createdQuiz = await makeRequest<CreateQuizRequest, RawQuiz>({
|
||||||
});
|
method: "POST",
|
||||||
}
|
url: `${API_URL}/quiz/create`,
|
||||||
|
body: { ...defaultCreateQuizBody, ...body },
|
||||||
|
});
|
||||||
|
|
||||||
async function editQuiz(body: EditQuizRequest, signal?: AbortSignal) {
|
return [createdQuiz];
|
||||||
return makeRequest<EditQuizRequest, EditQuizResponse>({
|
} catch (nativeError) {
|
||||||
url: `${baseUrl}/quiz/edit`,
|
const [error] = parseAxiosError(nativeError);
|
||||||
body,
|
|
||||||
method: "PATCH",
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyQuiz(id: number) {
|
return [null, `Не удалось создать квиз. ${error}`];
|
||||||
return makeRequest<CopyQuizRequest, CopyQuizResponse>({
|
}
|
||||||
url: `${baseUrl}/quiz/copy`,
|
};
|
||||||
body: { id },
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteQuiz(id: number) {
|
export const getQuizList = async (
|
||||||
return makeRequest<DeleteQuizRequest, DeleteQuizResponse>({
|
body?: Partial<CreateQuizRequest>,
|
||||||
url: `${baseUrl}/quiz/delete`,
|
): Promise<[RawQuiz[] | null, string?]> => {
|
||||||
body: { id },
|
try {
|
||||||
method: "DELETE",
|
const { items } = await makeRequest<
|
||||||
});
|
GetQuizListRequest,
|
||||||
}
|
GetQuizListResponse
|
||||||
|
>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/quiz/getList`,
|
||||||
|
body: { ...defaultGetQuizListBody, ...body },
|
||||||
|
});
|
||||||
|
|
||||||
function addQuizImages(quizId: number, image: Blob) {
|
return [items];
|
||||||
const formData = new FormData();
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
formData.append("quiz", quizId.toString());
|
return [null, `Не удалось получить список квизов. ${error}`];
|
||||||
formData.append("image", image);
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return makeRequest<FormData, { [key: string]: string }>({
|
export const getQuiz = async (
|
||||||
url: `${imagesUrl}/quiz/putImages`,
|
body?: Partial<GetQuizRequest>,
|
||||||
body: formData,
|
): Promise<[GetQuizResponse | null, string?]> => {
|
||||||
method: "PUT",
|
try {
|
||||||
});
|
const quiz = await makeRequest<GetQuizRequest, GetQuizResponse>({
|
||||||
}
|
method: "GET",
|
||||||
|
url: `${API_URL}/quiz/get`,
|
||||||
|
body: { ...defaultGetQuizBody, ...body },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [quiz];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить квиз. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const editQuiz = async (
|
||||||
|
body: EditQuizRequest,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<[EditQuizResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const editedQuiz = await makeRequest<EditQuizRequest, EditQuizResponse>({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `${API_URL}/quiz/edit`,
|
||||||
|
body,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [editedQuiz];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось изменить квиз. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copyQuiz = async (
|
||||||
|
id: number,
|
||||||
|
): Promise<[EditQuizResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const copiedQuiz = await makeRequest<CopyQuizRequest, CopyQuizResponse>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/quiz/copy`,
|
||||||
|
body: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [copiedQuiz];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось скопировать квиз. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteQuiz = async (
|
||||||
|
id: number,
|
||||||
|
): Promise<[DeleteQuizResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const deletedQuiz = await makeRequest<
|
||||||
|
DeleteQuizRequest,
|
||||||
|
DeleteQuizResponse
|
||||||
|
>({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `${API_URL}/quiz/delete`,
|
||||||
|
body: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [deletedQuiz];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось удалить квиз. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addQuizImages = async (
|
||||||
|
quizId: number,
|
||||||
|
image: Blob,
|
||||||
|
): Promise<[AddedQuizImagesResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
const name = image?.name ? transliterate(image?.name.replace(/\s/g, '_')) : "blob"
|
||||||
|
|
||||||
|
//Замена всех побелов на _
|
||||||
|
const renamedImage = new File([image], name)
|
||||||
|
|
||||||
|
|
||||||
|
formData.append("quiz", quizId.toString());
|
||||||
|
formData.append("image", renamedImage);
|
||||||
|
|
||||||
|
const addedQuizImages = await makeRequest<
|
||||||
|
FormData,
|
||||||
|
AddedQuizImagesResponse
|
||||||
|
>({
|
||||||
|
url: `${IMAGES_URL}/quiz/putImages`,
|
||||||
|
body: formData,
|
||||||
|
method: "PUT",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [addedQuizImages];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось добавить изображение. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copyQuizTemplate = async (
|
||||||
|
qid: string,
|
||||||
|
): Promise<[number | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const { id } = await makeRequest<{ Qid: string }, { id: number }>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/quiz/template`,
|
||||||
|
body: { Qid: qid },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return [null, `Не удалось скопировать шаблон квиза.`];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [id];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось скопировать шаблон квиза. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const quizApi = {
|
export const quizApi = {
|
||||||
create: createQuiz,
|
create: createQuiz,
|
||||||
@ -89,6 +201,7 @@ export const quizApi = {
|
|||||||
copy: copyQuiz,
|
copy: copyQuiz,
|
||||||
delete: deleteQuiz,
|
delete: deleteQuiz,
|
||||||
addImages: addQuizImages,
|
addImages: addQuizImages,
|
||||||
|
copyTemplate: copyQuizTemplate,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultCreateQuizBody: CreateQuizRequest = {
|
const defaultCreateQuizBody: CreateQuizRequest = {
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
import { makeRequest } from "@frontend/kitui";
|
|
||||||
import { CreateQuestionRequest } from "model/question/create";
|
|
||||||
import { RawQuestion } from "model/question/question";
|
|
||||||
import {
|
|
||||||
GetQuestionListRequest,
|
|
||||||
GetQuestionListResponse,
|
|
||||||
} from "@model/question/getList";
|
|
||||||
import {
|
|
||||||
EditQuestionRequest,
|
|
||||||
EditQuestionResponse,
|
|
||||||
} from "@model/question/edit";
|
|
||||||
import {
|
|
||||||
DeleteQuestionRequest,
|
|
||||||
DeleteQuestionResponse,
|
|
||||||
} from "@model/question/delete";
|
|
||||||
import {
|
|
||||||
CopyQuestionRequest,
|
|
||||||
CopyQuestionResponse,
|
|
||||||
} from "@model/question/copy";
|
|
||||||
|
|
||||||
const baseUrl =
|
|
||||||
process.env.NODE_ENV === "production"
|
|
||||||
? "/squiz"
|
|
||||||
: "https://squiz.pena.digital";
|
|
||||||
|
|
||||||
function get(quizId: string) {
|
|
||||||
return makeRequest<any>({
|
|
||||||
url: `${baseUrl}/question/copy`,
|
|
||||||
body: { id: questionId, quiz_id: quizId },
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function quizRelase(quizId: string, status: "start" | "stop") {
|
|
||||||
return makeRequest<any>({
|
|
||||||
url: `https://squiz.pena.digital/answer/quiz/get`,
|
|
||||||
body: {
|
|
||||||
quiz_id: quizId,
|
|
||||||
limit: 100,
|
|
||||||
page: 0,
|
|
||||||
need_config: true,
|
|
||||||
},
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const relaseApi = {
|
|
||||||
relase: quizRelase,
|
|
||||||
get: quizRelase,
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultGetQuestionListBody: GetQuestionListRequest = {
|
|
||||||
limit: 100,
|
|
||||||
offset: 0,
|
|
||||||
type: "",
|
|
||||||
};
|
|
149
src/api/result.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
import type { RawResult } from "@model/result/result";
|
||||||
|
|
||||||
|
interface IResultListBody {
|
||||||
|
to: number;
|
||||||
|
from: string;
|
||||||
|
new: boolean;
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAnswerResult {
|
||||||
|
Browser: string;
|
||||||
|
CreatedAt: string;
|
||||||
|
Deleted: boolean;
|
||||||
|
Device: string;
|
||||||
|
DeviceType: string;
|
||||||
|
Email: string;
|
||||||
|
Fingerprint: string;
|
||||||
|
IP: string;
|
||||||
|
Id: number;
|
||||||
|
OS: string;
|
||||||
|
QuizId: number;
|
||||||
|
Result: boolean;
|
||||||
|
Session: string;
|
||||||
|
Start: boolean;
|
||||||
|
content: string;
|
||||||
|
new: boolean;
|
||||||
|
question_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResultFilter = {
|
||||||
|
from?: string;
|
||||||
|
new?: boolean;
|
||||||
|
to?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ObsolescenceRequest = {
|
||||||
|
answers: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
|
||||||
|
|
||||||
|
const getResultList = async (
|
||||||
|
quizId: number,
|
||||||
|
page: number,
|
||||||
|
body: ResultFilter,
|
||||||
|
): Promise<[RawResult | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const resultList = await makeRequest<IResultListBody, RawResult>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/results/getResults/${quizId}`,
|
||||||
|
body: { page: page, limit: 10, ...body },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [resultList];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить результат. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteResult = async (
|
||||||
|
resultId: number,
|
||||||
|
): Promise<[string | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const deletedResult = await makeRequest<void, string>({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `${API_URL}/results/delete/${resultId}`,
|
||||||
|
body: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
return [deletedResult];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось удалить результат. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const obsolescenceResult = async (
|
||||||
|
idResultArray: number[],
|
||||||
|
): Promise<[null, string?]> => {
|
||||||
|
try {
|
||||||
|
const obsolescencedResult = await makeRequest<ObsolescenceRequest, null>({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `${API_URL}/result/seen`,
|
||||||
|
body: { answers: idResultArray },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [obsolescencedResult];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось изменить результат. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAnswerResultList = async (
|
||||||
|
resultId: number,
|
||||||
|
): Promise<[IAnswerResult[] | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const answerResultList = await makeRequest<never, IAnswerResult[]>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/result/${resultId}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [answerResultList];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить список результатов. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const AnswerResultListEx = async (
|
||||||
|
quizId: number,
|
||||||
|
body: ResultFilter,
|
||||||
|
): Promise<[Blob | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const answerResultListEx = await makeRequest<ResultFilter, Blob>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/results/${quizId}/export`,
|
||||||
|
body,
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [answerResultListEx];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [
|
||||||
|
null,
|
||||||
|
`Не удалось получить список устаревших результатов. ${error}`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resultApi = {
|
||||||
|
getList: getResultList,
|
||||||
|
delete: deleteResult,
|
||||||
|
getAnswerList: getAnswerResultList,
|
||||||
|
export: AnswerResultListEx,
|
||||||
|
obsolescence: obsolescenceResult,
|
||||||
|
};
|
114
src/api/statistic.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
export type DevicesResponse = {
|
||||||
|
Device: Record<string, number>;
|
||||||
|
OS: Record<string, number>;
|
||||||
|
Browser: Record<string, number>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GeneralResponse = {
|
||||||
|
Open: Record<string, number>;
|
||||||
|
Result: Record<string, number>;
|
||||||
|
AvTime: Record<string, number>;
|
||||||
|
Conversion: Record<string, number>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type QuestionsResponse = {
|
||||||
|
Funnel: number[];
|
||||||
|
FunnelData: number[];
|
||||||
|
Results: Record<string, number>;
|
||||||
|
Questions: Record<string, Record<string, number>>;
|
||||||
|
};
|
||||||
|
export type GraphicsResponse = unknown;
|
||||||
|
|
||||||
|
type TRequest = {
|
||||||
|
to: number;
|
||||||
|
from: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/statistic`;
|
||||||
|
|
||||||
|
export const getDevices = async (
|
||||||
|
quizId: string,
|
||||||
|
to: number,
|
||||||
|
from: number,
|
||||||
|
): Promise<[DevicesResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const devicesResponse = await makeRequest<TRequest, DevicesResponse>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/${quizId}/devices`,
|
||||||
|
withCredentials: true,
|
||||||
|
body: { to, from },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [devicesResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить статистику о девайсах. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGeneral = async (
|
||||||
|
quizId: string,
|
||||||
|
to: number,
|
||||||
|
from: number,
|
||||||
|
): Promise<[GeneralResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const generalResponse = await makeRequest<TRequest, GeneralResponse>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/${quizId}/general`,
|
||||||
|
withCredentials: true,
|
||||||
|
body: { to, from },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [generalResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить ключевые метрики. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getQuestions = async (
|
||||||
|
quizId: string,
|
||||||
|
to: number,
|
||||||
|
from: number,
|
||||||
|
): Promise<[QuestionsResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const questionsResponse = await makeRequest<TRequest, QuestionsResponse>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/${quizId}/questions`,
|
||||||
|
withCredentials: true,
|
||||||
|
body: { to, from },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [questionsResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить статистику по результатам. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGraphics = async (
|
||||||
|
quizId: string,
|
||||||
|
to: number,
|
||||||
|
from: number,
|
||||||
|
): Promise<[GraphicsResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const questionsResponse = await makeRequest<TRequest, QuestionsResponse>({
|
||||||
|
method: "get",
|
||||||
|
url: `${API_URL}s/${quizId}/pipelines?from=${from}&to=${to}`,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [questionsResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить статистику. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
45
src/api/tariff.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
import type { GetTariffsResponse } from "@frontend/kitui";
|
||||||
|
|
||||||
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/strator/tariff`;
|
||||||
|
|
||||||
|
export const getTariffs = async (
|
||||||
|
page: number = 1,
|
||||||
|
): Promise<[GetTariffsResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const tariffs = await makeRequest<never, GetTariffsResponse>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${API_URL}/getList?page=${page}&limit=100`,
|
||||||
|
});
|
||||||
|
return [tariffs];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
return [null, `Ошибка при получении списка тарифов. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
|
||||||
|
const apiUrl = process.env.REACT_APP_DOMAIN + "/requestquiz";
|
||||||
|
|
||||||
|
export async function sendContactFormRequest(body: {
|
||||||
|
contact: string;
|
||||||
|
whoami: string;
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
const a = await axios(apiUrl + "/callme", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
});
|
||||||
|
return [a];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Ошибка при отправке запроса. ${error}`];
|
||||||
|
}
|
||||||
|
}
|
9
src/api/tariffs.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { makeRequest } from '@utils/makeRequest';
|
||||||
|
import type { GetTariffsResponse } from '@/model/tariff';
|
||||||
|
|
||||||
|
export const getTariffs = async (): Promise<[GetTariffsResponse | null, string?]> => {
|
||||||
|
return makeRequest<GetTariffsResponse>({
|
||||||
|
url: `${process.env.REACT_APP_DOMAIN}/tariffs`,
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
};
|
102
src/api/ticket.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { createTicket as createTicketRequest } from "@frontend/kitui";
|
||||||
|
|
||||||
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
SendTicketMessageRequest,
|
||||||
|
CreateTicketResponse,
|
||||||
|
} from "@frontend/kitui";
|
||||||
|
|
||||||
|
type SendFileResponse = {
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_URL = `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0`;
|
||||||
|
|
||||||
|
export const sendTicketMessage = async (
|
||||||
|
ticketId: string,
|
||||||
|
message: string,
|
||||||
|
systemError: boolean
|
||||||
|
): Promise<[null, string?]> => {
|
||||||
|
try {
|
||||||
|
const sendTicketMessageResponse = await makeRequest<
|
||||||
|
SendTicketMessageRequest,
|
||||||
|
null
|
||||||
|
>({
|
||||||
|
url: `${API_URL}/send`,
|
||||||
|
method: "POST",
|
||||||
|
useToken: true,
|
||||||
|
body: { ticket: ticketId, message: message, lang: "ru", files: [], system: systemError },
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return [sendTicketMessageResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось отправить сообщение. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shownMessage = async (id: string): Promise<[null, string?]> => {
|
||||||
|
try {
|
||||||
|
const shownMessageResponse = await makeRequest<{ id: string }, null>({
|
||||||
|
url: `${API_URL}/shown`,
|
||||||
|
method: "POST",
|
||||||
|
useToken: true,
|
||||||
|
body: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [shownMessageResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось прочесть сообщение. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendFile = async (
|
||||||
|
ticketId: string,
|
||||||
|
file: File,
|
||||||
|
): Promise<[SendFileResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const body = new FormData();
|
||||||
|
|
||||||
|
body.append(file.name, file);
|
||||||
|
body.append("ticket", ticketId);
|
||||||
|
|
||||||
|
const sendResponse = await makeRequest<FormData, SendFileResponse>({
|
||||||
|
method: "POST",
|
||||||
|
url: `${API_URL}/sendFiles`,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [sendResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось отправить файл. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createTicket = async (
|
||||||
|
message: string,
|
||||||
|
useToken: boolean,
|
||||||
|
systemError: boolean
|
||||||
|
): Promise<[CreateTicketResponse | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const createdTicket = await createTicketRequest({
|
||||||
|
url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/create`,
|
||||||
|
body: { Title: "Unauth title", Message: message, system: systemError },
|
||||||
|
useToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [createdTicket];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось создать тикет. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
66
src/api/user.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { makeRequest } from "@api/makeRequest";
|
||||||
|
|
||||||
|
import { parseAxiosError } from "@utils/parse-error";
|
||||||
|
|
||||||
|
import type { UserAccount } from "@frontend/kitui";
|
||||||
|
import type { OriginalUserAccount } from "@root/user";
|
||||||
|
|
||||||
|
type RecoverUserRequest = {
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUser = async (): Promise<[UserAccount | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const user = await makeRequest<never, UserAccount>({
|
||||||
|
method: "GET",
|
||||||
|
url: `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1/account`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [user];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить пользователя. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAccount = async (): Promise<
|
||||||
|
[OriginalUserAccount | null, string?]
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
const account = await makeRequest<never, OriginalUserAccount>({
|
||||||
|
url: `${process.env.REACT_APP_DOMAIN}/squiz/account/get`,
|
||||||
|
contentType: true,
|
||||||
|
method: "GET",
|
||||||
|
useToken: true,
|
||||||
|
withCredentials: false,
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [account];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось получить данные аккаунта. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const recoverUser = async (
|
||||||
|
password: string,
|
||||||
|
): Promise<[unknown | null, string?]> => {
|
||||||
|
try {
|
||||||
|
const recoverResponse = await makeRequest<RecoverUserRequest, unknown>({
|
||||||
|
url: `${process.env.REACT_APP_DOMAIN}/user`,
|
||||||
|
method: "PATCH",
|
||||||
|
body: { password },
|
||||||
|
});
|
||||||
|
|
||||||
|
return [recoverResponse];
|
||||||
|
} catch (nativeError) {
|
||||||
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
|
return [null, `Не удалось восстановить пароль. ${error}`];
|
||||||
|
}
|
||||||
|
};
|
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB |
@ -141,7 +141,6 @@ export default function Notebook({ color }: Props) {
|
|||||||
</g>
|
</g>
|
||||||
<mask
|
<mask
|
||||||
id="mask0_3_590"
|
id="mask0_3_590"
|
||||||
// style="mask-type:alpha"
|
|
||||||
maskUnits="userSpaceOnUse"
|
maskUnits="userSpaceOnUse"
|
||||||
x="26"
|
x="26"
|
||||||
y="10"
|
y="10"
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 1.2 KiB |
52
src/assets/icons/AccountSetting.tsx
Normal file
5
src/assets/icons/AiPersonalizationIcon.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19 15L19.6668 16.2577C20.1354 17.1416 20.8584 17.8646 21.7423 18.3332L23 19L21.7423 19.6668C20.8584 20.1354 20.1354 20.8584 19.6668 21.7423L19 23L18.3332 21.7423C17.8646 20.8584 17.1416 20.1354 16.2577 19.6668L15 19L16.2577 18.3332C17.1416 17.8646 17.8646 17.1416 18.3332 16.2577L19 15Z" stroke="#7E2AEA" stroke-width="1.5" stroke-linejoin="round"/>
|
||||||
|
<path d="M20 11V7C20 4.23858 17.7614 2 15 2H7C4.23858 2 2 4.23858 2 7V15C2 17.7614 4.23858 20 7 20H11" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M7.5 14.5V9.25C7.5 8.78587 7.68437 8.34075 8.01256 8.01256C8.34075 7.68437 8.78587 7.5 9.25 7.5C9.71413 7.5 10.1592 7.68437 10.4874 8.01256C10.8156 8.34075 11 8.78587 11 9.25V14.5M7.5 11.875H11M14.5 7.5V14.5" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 930 B |
12
src/assets/icons/AiPersonalizationIcon.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { SvgIcon, SvgIconProps } from "@mui/material";
|
||||||
|
|
||||||
|
const AiPersonalizationIcon = (props: SvgIconProps) => (
|
||||||
|
<SvgIcon {...props} viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M19 15L19.6668 16.2577C20.1354 17.1416 20.8584 17.8646 21.7423 18.3332L23 19L21.7423 19.6668C20.8584 20.1354 20.1354 20.8584 19.6668 21.7423L19 23L18.3332 21.7423C17.8646 20.8584 17.1416 20.1354 16.2577 19.6668L15 19L16.2577 18.3332C17.1416 17.8646 17.8646 17.1416 18.3332 16.2577L19 15Z" stroke="#7E2AEA" strokeWidth="1.5" strokeLinejoin="round"/>
|
||||||
|
<path d="M20 11V7C20 4.23858 17.7614 2 15 2H7C4.23858 2 2 4.23858 2 7V15C2 17.7614 4.23858 20 7 20H11" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round"/>
|
||||||
|
<path d="M7.5 14.5V9.25C7.5 8.78587 7.68437 8.34075 8.01256 8.01256C8.34075 7.68437 8.78587 7.5 9.25 7.5C9.71413 7.5 10.1592 7.68437 10.4874 8.01256C10.8156 8.34075 11 8.78587 11 9.25V14.5M7.5 11.875H11M14.5 7.5V14.5" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AiPersonalizationIcon;
|
26
src/assets/icons/AmoTrash.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
color?: string;
|
||||||
|
height?: string;
|
||||||
|
width?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AmoTrash({ color, height, width }: Props) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19.4994 6H4.5" stroke="#FC2012" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5.5 18.7492V8.74609H18.5V18.7492C18.5 20.1299 17.3807 21.2492 16 21.2492H8C6.61929 21.2492 5.5 20.1299 5.5 18.7492Z" fill="#FC2012" stroke="#F02B2B"/>
|
||||||
|
<path d="M15.75 6V4.5C15.75 4.10218 15.592 3.72064 15.3107 3.43934C15.0294 3.15804 14.6478 3 14.25 3H9.75C9.35218 3 8.97064 3.15804 8.68934 3.43934C8.40804 3.72064 8.25 4.10218 8.25 4.5V6" stroke="#FC2012" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
BIN
src/assets/icons/Amologo.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
16
src/assets/icons/Analytics/doubleCheck.svg
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<svg
|
||||||
|
width="30"
|
||||||
|
height="30"
|
||||||
|
viewBox="0 0 30 30"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect width="30" height="30" rx="6" fill="#FEDFD0" />
|
||||||
|
<path
|
||||||
|
d="M18.7891 11.2006L14.526 15.4943M10.3154 15.2084L14.526 19.2993L22.7891 10.7006M7.21053 15.4144L11 19.0962"
|
||||||
|
stroke="#FC712F"
|
||||||
|
stroke-width="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 394 B |
3
src/assets/icons/Analytics/leftArrow.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 1.5L1 6L6 10.5" stroke="white" stroke-width="1.5" strokeLinecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 211 B |
23
src/assets/icons/Analytics/next.svg
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<svg
|
||||||
|
width="30"
|
||||||
|
height="30"
|
||||||
|
viewBox="0 0 30 30"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect width="30" height="30" rx="6" fill="#FEDFD0" />
|
||||||
|
<path
|
||||||
|
d="M11.5 10.5L15.5 15L11.5 19.5"
|
||||||
|
stroke="#FC712F"
|
||||||
|
stroke-width="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15.5 10.5L19.5 15L15.5 19.5"
|
||||||
|
stroke="#FC712F"
|
||||||
|
stroke-width="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 466 B |
3
src/assets/icons/Analytics/open.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="14" height="7" viewBox="0 0 14 7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1 1L7 6L13 1" stroke="#7E2AEA" stroke-width="1.5" strokeLinecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 209 B |
4
src/assets/icons/Analytics/reset.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17.8955 10.1257H22.7705V5.25073" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M19.3174 19.3172C18.0677 20.5679 16.4752 21.4198 14.7413 21.7652C13.0074 22.1107 11.21 21.9341 9.57649 21.2579C7.94295 20.5816 6.54667 19.4361 5.56428 17.9662C4.5819 16.4962 4.05754 14.768 4.05754 13C4.05754 11.232 4.5819 9.50376 5.56428 8.03384C6.54667 6.56391 7.94295 5.41838 9.57649 4.74213C11.21 4.06589 13.0074 3.88932 14.7413 4.23477C16.4752 4.58022 18.0677 5.43216 19.3174 6.68282L22.7705 10.1258" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 730 B |
3
src/assets/icons/Analytics/rightArrow.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1 1.5L6 6L1 10.5" stroke="white" stroke-width="1.5" strokeLinecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 211 B |
@ -1,43 +1,35 @@
|
|||||||
import { Box, useTheme } from "@mui/material";
|
import { Box, SxProps, Theme } from "@mui/material";
|
||||||
|
|
||||||
export default function ChartIcon() {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
|
export default function ChartLineUp(sx: SxProps<Theme>) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box sx={sx}>
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M21 19.5H3V4.5"
|
d="M21 19.5H3V4.5"
|
||||||
stroke={theme.palette.brightPurple.main}
|
stroke="#7E2AEA"
|
||||||
strokeWidth="1.5"
|
stroke-width="1.5"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M19.5 6L12 13.5L9 10.5L3 16.5"
|
d="M19.5 6L12 13.5L9 10.5L3 16.5"
|
||||||
stroke={theme.palette.brightPurple.main}
|
stroke="#7E2AEA"
|
||||||
strokeWidth="1.5"
|
stroke-width="1.5"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M19.5 9.75V6H15.75"
|
d="M19.5 9.75V6H15.75"
|
||||||
stroke={theme.palette.brightPurple.main}
|
stroke="#7E2AEA"
|
||||||
strokeWidth="1.5"
|
stroke-width="1.5"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -3,11 +3,13 @@ import { Box, useTheme } from "@mui/material";
|
|||||||
interface CheckboxIconProps {
|
interface CheckboxIconProps {
|
||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
color?: string;
|
color?: string;
|
||||||
|
isRounded?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CheckboxIcon({
|
export default function CheckboxIcon({
|
||||||
checked = false,
|
checked = false,
|
||||||
color = "#7E2AEA",
|
color = "#7E2AEA",
|
||||||
|
isRounded,
|
||||||
}: CheckboxIconProps) {
|
}: CheckboxIconProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@ -16,7 +18,7 @@ export default function CheckboxIcon({
|
|||||||
sx={{
|
sx={{
|
||||||
height: "24px",
|
height: "24px",
|
||||||
width: "24px",
|
width: "24px",
|
||||||
borderRadius: "6px",
|
borderRadius: isRounded ? "50%" : "6px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
14
src/assets/icons/ClockWiseIcon.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const ClockWiseIcon = () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10.4115 22.35L11.1615 22.35L11.1615 20.85L10.4115 20.85L10.4115 22.35ZM19.2553 12L19.2553 12.75L20.7553 12.75L20.7553 12L19.2553 12ZM20.0053 13.6L19.4748 14.1302L20.0053 14.661L20.5358 14.1302L20.0053 13.6ZM10.4115 20.85C5.52885 20.85 1.56777 16.887 1.56777 12L0.0677734 12C0.0677731 17.7146 4.6995 22.35 10.4115 22.35L10.4115 20.85ZM1.56777 12C1.56777 7.11295 5.52885 3.15 10.4115 3.15L10.4115 1.65C4.6995 1.65 0.0677736 6.28545 0.0677734 12L1.56777 12ZM10.4115 3.15C15.2942 3.15 19.2553 7.11295 19.2553 12L20.7553 12C20.7553 6.28545 16.1235 1.65 10.4115 1.65L10.4115 3.15ZM22.6726 9.86985L19.4748 13.0698L20.5358 14.1302L23.7336 10.9302L22.6726 9.86985ZM20.5358 13.0698L17.3377 9.86983L16.2767 10.9302L19.4748 14.1302L20.5358 13.0698Z"
|
||||||
|
fill="#7E2AEA"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
@ -8,8 +8,6 @@ export default function AddressIcon({ color }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
// height: "38px",
|
|
||||||
// width: "45px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
@ -8,8 +8,6 @@ export default function EmailIcon({ color }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
// height: "38px",
|
|
||||||
// width: "45px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
@ -8,8 +8,6 @@ export default function NameIcon({ color }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
// height: "38px",
|
|
||||||
// width: "45px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
@ -8,8 +8,6 @@ export default function PhoneIcon({ color }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
// height: "38px",
|
|
||||||
// width: "45px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
@ -8,8 +8,6 @@ export default function TextIcon({ color }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
// height: "38px",
|
|
||||||
// width: "45px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
@ -8,8 +8,6 @@ export default function SupplementIcon({ color }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
// height: "38px",
|
|
||||||
// width: "45px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
@ -4,9 +4,15 @@ interface Props {
|
|||||||
color?: string;
|
color?: string;
|
||||||
bgcolor?: string;
|
bgcolor?: string;
|
||||||
marL?: string;
|
marL?: string;
|
||||||
|
width?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CopyIcon({ color, bgcolor, marL }: Props) {
|
export default function CopyIcon({
|
||||||
|
color,
|
||||||
|
bgcolor,
|
||||||
|
marL,
|
||||||
|
width = "36px",
|
||||||
|
}: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -14,8 +20,8 @@ export default function CopyIcon({ color, bgcolor, marL }: Props) {
|
|||||||
sx={{
|
sx={{
|
||||||
bgcolor,
|
bgcolor,
|
||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
height: "36px",
|
height: width,
|
||||||
width: "36px",
|
width: width,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
36
src/assets/icons/EditPencil.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
color: string;
|
||||||
|
height: string;
|
||||||
|
width: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EditPencil({ color, height, width }: Props) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="19"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 19 18"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M10.5137 0.80552C11.5869 -0.269234 13.3274 -0.269892 14.4013 0.804049L16.8932 3.2959C17.958 4.36068 17.969 6.08444 16.918 7.16281L7.68486 16.6361C6.97933 17.3599 6.01167 17.7681 5.00124 17.7681L2.24909 17.768C0.969844 17.7679 -0.0517699 16.7015 0.00203171 15.4224L0.120186 12.6134C0.159684 11.6744 0.549963 10.7844 1.2138 10.1195L10.5137 0.80552ZM13.3415 1.86551C12.8533 1.37736 12.0622 1.37766 11.5744 1.86618L9.9113 3.53178L14.1911 7.81157L15.8446 6.11505C16.3224 5.62488 16.3173 4.84136 15.8333 4.35737L13.3415 1.86551ZM2.27446 11.1802L8.85145 4.59325L13.144 8.88585L6.61148 15.5883C6.18816 16.0226 5.60756 16.2675 5.0013 16.2675L2.24916 16.2674C1.82274 16.2674 1.4822 15.9119 1.50014 15.4856L1.61829 12.6765C1.64199 12.1131 1.87616 11.5791 2.27446 11.1802ZM17.5148 17.6948C17.9289 17.6948 18.2645 17.3589 18.2645 16.9445C18.2645 16.5301 17.9289 16.1942 17.5148 16.1942H11.3931C10.9791 16.1942 10.6434 16.5301 10.6434 16.9445C10.6434 17.3589 10.9791 17.6948 11.3931 17.6948H17.5148Z"
|
||||||
|
fill="#7E2AEA"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -6,6 +6,7 @@ type InfoProps = {
|
|||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
onClick?: any;
|
onClick?: any;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
color?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Info({
|
export default function Info({
|
||||||
@ -14,6 +15,7 @@ export default function Info({
|
|||||||
sx,
|
sx,
|
||||||
onClick,
|
onClick,
|
||||||
className,
|
className,
|
||||||
|
color = "#7e2aea",
|
||||||
}: InfoProps) {
|
}: InfoProps) {
|
||||||
return (
|
return (
|
||||||
<IconButton sx={sx} className={className} onClick={onClick}>
|
<IconButton sx={sx} className={className} onClick={onClick}>
|
||||||
@ -26,21 +28,21 @@ export default function Info({
|
|||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M10 19C14.9706 19 19 14.9706 19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19Z"
|
d="M10 19C14.9706 19 19 14.9706 19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19Z"
|
||||||
stroke="#7E2AEA"
|
stroke={color}
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M9.25 9.25H10V14.5H10.75"
|
d="M9.25 9.25H10V14.5H10.75"
|
||||||
stroke="#7E2AEA"
|
stroke={color}
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M9.8125 7C10.4338 7 10.9375 6.49632 10.9375 5.875C10.9375 5.25368 10.4338 4.75 9.8125 4.75C9.19118 4.75 8.6875 5.25368 8.6875 5.875C8.6875 6.49632 9.19118 7 9.8125 7Z"
|
d="M9.8125 7C10.4338 7 10.9375 6.49632 10.9375 5.875C10.9375 5.25368 10.4338 4.75 9.8125 4.75C9.19118 4.75 8.6875 5.25368 8.6875 5.875C8.6875 6.49632 9.19118 7 9.8125 7Z"
|
||||||
fill="#7E2AEA"
|
fill={color}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -3,10 +3,11 @@ import { Box, useTheme } from "@mui/material";
|
|||||||
import type { SxProps } from "@mui/material";
|
import type { SxProps } from "@mui/material";
|
||||||
|
|
||||||
type InfoIconProps = {
|
type InfoIconProps = {
|
||||||
|
color?: string;
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function InfoIcon({ sx }: InfoIconProps) {
|
export default function InfoIcon({ sx, color = "#7e2aea" }: InfoIconProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -29,21 +30,21 @@ export default function InfoIcon({ sx }: InfoIconProps) {
|
|||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z"
|
d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z"
|
||||||
stroke={theme.palette.brightPurple.main}
|
stroke={color}
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M11.25 11.25H12V16.5H12.75"
|
d="M11.25 11.25H12V16.5H12.75"
|
||||||
stroke={theme.palette.brightPurple.main}
|
stroke={color}
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M11.8125 9C12.4338 9 12.9375 8.49632 12.9375 7.875C12.9375 7.25368 12.4338 6.75 11.8125 6.75C11.1912 6.75 10.6875 7.25368 10.6875 7.875C10.6875 8.49632 11.1912 9 11.8125 9Z"
|
d="M11.8125 9C12.4338 9 12.9375 8.49632 12.9375 7.875C12.9375 7.25368 12.4338 6.75 11.8125 6.75C11.1912 6.75 10.6875 7.25368 10.6875 7.875C10.6875 8.49632 11.1912 9 11.8125 9Z"
|
||||||
fill={theme.palette.brightPurple.main}
|
fill={color}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -12,23 +12,23 @@ export const LinkSimple: FC<SVGProps<SVGSVGElement>> = (props) => (
|
|||||||
<path
|
<path
|
||||||
d="M8.82031 15.1781L15.1766 8.8125"
|
d="M8.82031 15.1781L15.1766 8.8125"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-width="1.5"
|
strokeWidth="1.5"
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M13.5949 16.7719L10.9418 19.425C10.5238 19.843 10.0276 20.1745 9.48151 20.4007C8.93541 20.6269 8.35009 20.7434 7.75899 20.7434C6.5652 20.7434 5.42031 20.2691 4.57618 19.425C3.73204 18.5809 3.25781 17.436 3.25781 16.2422C3.25781 15.0484 3.73204 13.9035 4.57618 13.0594L7.2293 10.4062"
|
d="M13.5949 16.7719L10.9418 19.425C10.5238 19.843 10.0276 20.1745 9.48151 20.4007C8.93541 20.6269 8.35009 20.7434 7.75899 20.7434C6.5652 20.7434 5.42031 20.2691 4.57618 19.425C3.73204 18.5809 3.25781 17.436 3.25781 16.2422C3.25781 15.0484 3.73204 13.9035 4.57618 13.0594L7.2293 10.4062"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-width="1.5"
|
strokeWidth="1.5"
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M16.7719 13.5937L19.425 10.9406C20.2691 10.0965 20.7434 8.95159 20.7434 7.7578C20.7434 6.56401 20.2691 5.41912 19.425 4.57499C18.5809 3.73085 17.436 3.25662 16.2422 3.25662C15.0484 3.25662 13.9035 3.73085 13.0594 4.57499L10.4062 7.22811"
|
d="M16.7719 13.5937L19.425 10.9406C20.2691 10.0965 20.7434 8.95159 20.7434 7.7578C20.7434 6.56401 20.2691 5.41912 19.425 4.57499C18.5809 3.73085 17.436 3.25662 16.2422 3.25662C15.0484 3.25662 13.9035 3.73085 13.0594 4.57499L10.4062 7.22811"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-width="1.5"
|
strokeWidth="1.5"
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
22
src/assets/icons/NotebookWithPencil.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { Box, SxProps, Theme } from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
export default function NotebookWithPencil({ sx }: Props) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "24px",
|
||||||
|
...sx
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.88766 6.5124H15.3537M5.88766 10.4075H12.0172M5.88766 14.398H9.18792M18.988 4.41492V3.69678C18.988 3.14449 18.5403 2.69678 17.988 2.69678H3.19922C2.64693 2.69678 2.19922 3.14449 2.19922 3.69678V19.7038C2.19922 20.5407 3.16577 21.0074 3.8211 20.4869L6.25047 18.5577C6.42733 18.4173 6.64652 18.3408 6.87236 18.3408H17.988C18.5403 18.3408 18.988 17.8931 18.988 17.3408V15.2147" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" />
|
||||||
|
<path d="M20.383 7.29034L20.9133 6.76001L20.383 6.22968L19.8527 6.76001L20.383 7.29034ZM15.9919 11.6815L15.4615 11.1511L14.9312 11.6815L15.4615 12.2118L15.9919 11.6815ZM16.7185 12.4081L16.1882 12.9384L16.7185 13.4688L17.2489 12.9384L16.7185 12.4081ZM21.1096 8.01698L21.64 8.54731L22.1703 8.01698L21.64 7.48665L21.1096 8.01698ZM14.4001 13.2732L14.9304 12.7429L14.4001 12.2126L13.8698 12.7429L14.4001 13.2732ZM14.0817 13.5917L13.5514 13.0613C13.5011 13.1115 13.4583 13.1686 13.4242 13.2309L14.0817 13.5917ZM13.1982 15.2018L12.5407 14.841C12.3803 15.1333 12.4321 15.4964 12.6678 15.7321C12.9036 15.9679 13.2667 16.0197 13.559 15.8593L13.1982 15.2018ZM14.8083 14.3183L15.1691 14.9758C15.2314 14.9417 15.2885 14.8988 15.3387 14.8486L14.8083 14.3183ZM15.1267 13.9999L15.6571 14.5302L16.1874 13.9999L15.6571 13.4696L15.1267 13.9999ZM21.9075 5.76585L21.3772 5.23552L20.8468 5.76585L21.3772 6.29618L21.9075 5.76585ZM22.2812 5.39209L22.8116 4.86176C22.5187 4.56887 22.0438 4.56887 21.7509 4.86176L22.2812 5.39209ZM23.0079 6.11874L23.5382 6.64907C23.8311 6.35617 23.8311 5.8813 23.5382 5.58841L23.0079 6.11874ZM22.6341 6.4925L22.1038 7.02283L22.6341 7.55316L23.1645 7.02283L22.6341 6.4925ZM19.8527 6.76001L15.4615 11.1511L16.5222 12.2118L20.9133 7.82067L19.8527 6.76001ZM15.4615 12.2118L16.1882 12.9384L17.2489 11.8778L16.5222 11.1511L15.4615 12.2118ZM17.2489 12.9384L21.64 8.54731L20.5793 7.48665L16.1882 11.8778L17.2489 12.9384ZM21.64 7.48665L20.9133 6.76001L19.8527 7.82067L20.5793 8.54731L21.64 7.48665ZM13.8698 12.7429L13.5514 13.0613L14.612 14.122L14.9304 13.8036L13.8698 12.7429ZM13.4242 13.2309L12.5407 14.841L13.8557 15.5626L14.7392 13.9524L13.4242 13.2309ZM13.559 15.8593L15.1691 14.9758L14.4475 13.6608L12.8374 14.5443L13.559 15.8593ZM15.3387 14.8486L15.6571 14.5302L14.5964 13.4696L14.278 13.788L15.3387 14.8486ZM15.6571 13.4696L14.9304 12.7429L13.8698 13.8036L14.5964 14.5302L15.6571 13.4696ZM22.4378 6.29618L22.8116 5.92242L21.7509 4.86176L21.3772 5.23552L22.4378 6.29618ZM21.7509 5.92242L22.4776 6.64907L23.5382 5.58841L22.8116 4.86176L21.7509 5.92242ZM22.4776 5.58841L22.1038 5.96217L23.1645 7.02283L23.5382 6.64907L22.4776 5.58841ZM23.1645 5.96217L22.4378 5.23552L21.3772 6.29618L22.1038 7.02283L23.1645 5.96217Z" fill="#7E2AEA" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
import { Box } from "@mui/material";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
color: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NumberThree({ color }: Props) {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
height: "30px",
|
|
||||||
width: "30px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="25"
|
|
||||||
viewBox="0 0 24 25"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M12 21.1875C16.9706 21.1875 21 17.1581 21 12.1875C21 7.21694 16.9706 3.1875 12 3.1875C7.02944 3.1875 3 7.21694 3 12.1875C3 17.1581 7.02944 21.1875 12 21.1875Z"
|
|
||||||
stroke={color}
|
|
||||||
strokeWidth="1.5"
|
|
||||||
strokeMiterlimit="10"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M9.75 8.0625H14.25L11.625 11.8125C12.0567 11.8125 12.4817 11.919 12.8624 12.1225C13.243 12.326 13.5677 12.6203 13.8075 12.9792C14.0473 13.3381 14.1949 13.7507 14.2372 14.1803C14.2795 14.6099 14.2152 15.0433 14.05 15.4421C13.8848 15.8409 13.6238 16.1928 13.2901 16.4666C12.9564 16.7405 12.5603 16.9278 12.137 17.0121C11.7136 17.0963 11.276 17.0748 10.8629 16.9495C10.4498 16.8242 10.074 16.599 9.76875 16.2937"
|
|
||||||
stroke={color}
|
|
||||||
strokeWidth="1.5"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|