Compare commits
495
Commits
bf9926f004
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6696cc1027 | ||
|
|
98cb5ab09b | ||
|
|
c501c2bbbf | ||
|
|
a74ea966ad | ||
|
|
0a7d29419f | ||
|
|
a9599d8a1f | ||
|
|
cbc1311e9d | ||
|
|
587d7ceead | ||
|
|
ac8ef8caa2 | ||
|
|
7d0fb265d4 | ||
|
|
743db57bb3 | ||
|
|
d7d2e36573 | ||
|
|
8b157b8e87 | ||
|
|
0935e8cbc7 | ||
|
|
f04ce348ae | ||
|
|
508e0d182c | ||
|
|
dbd5d43d65 | ||
|
|
7d8a725e34 | ||
|
|
491adb4090 | ||
|
|
1a3f5277d9 | ||
|
|
ffcd5fc535 | ||
|
|
65e0c2ed62 | ||
|
|
36b4a3b481 | ||
|
|
e0455b200a | ||
|
|
348a5398d0 | ||
|
|
af87bfc7b6 | ||
|
|
beefe86ffd | ||
|
|
3a7c609588 | ||
|
|
aeeee04166 | ||
|
|
288d6782a4 | ||
|
|
55915af9df | ||
|
|
bfc457470c | ||
|
|
210145f5ca | ||
|
|
d0793a281e | ||
|
|
6994edd216 | ||
|
|
3a0ae93846 | ||
|
|
13a5db6313 | ||
|
|
9c011b6416 | ||
|
|
294e16e1a6 | ||
|
|
71f7120239 | ||
|
|
0e28986dc7 | ||
|
|
51a3ed7d18 | ||
|
|
c15867a41c | ||
|
|
c4e4d775f5 | ||
|
|
cd07502128 | ||
|
|
1ef2cc227a | ||
|
|
7226285746 | ||
|
|
dd93cd302c | ||
|
|
37b7ccb52e | ||
|
|
b8a76bd6a9 | ||
|
|
07e1237f05 | ||
|
|
29546740c7 | ||
|
|
42cc96a28a | ||
|
|
84b07af246 | ||
|
|
8a72dbaf68 | ||
|
|
af6b997ceb | ||
|
|
e97dd392c0 | ||
|
|
ae0526a58a | ||
|
|
d419c1c2cb | ||
|
|
48e03b4237 | ||
|
|
2fad519b7e | ||
|
|
84b73b912f | ||
|
|
a3cbaaa2e7 | ||
|
|
881f3295c0 | ||
|
|
0cc9230672 | ||
|
|
010a8f3a5a | ||
|
|
4952e0daa1 | ||
|
|
455b39bb1e | ||
|
|
34772bd3b4 | ||
|
|
2296044280 | ||
|
|
2d201bfcde | ||
|
|
17931191f5 | ||
|
|
4e8289e976 | ||
|
|
41c56e026f | ||
|
|
1e8bf782ca | ||
|
|
e4eedf464b | ||
|
|
c274cb134d | ||
|
|
c0fe6cce48 | ||
|
|
9e89a37b4d | ||
|
|
a704e410c5 | ||
|
|
d24884f7e6 | ||
|
|
2cada9ba27 | ||
|
|
a7c203d7a4 | ||
|
|
abc07a5e2f | ||
|
|
904c615665 | ||
|
|
e9af59769d | ||
|
|
e933d64790 | ||
|
|
c07fe860e5 | ||
|
|
1b6dd3c40b | ||
|
|
7c82f940fc | ||
|
|
a69636d7e3 | ||
|
|
83f535c2e9 | ||
|
|
47bf0b3e77 | ||
|
|
e09cd3a77c | ||
|
|
5d521358f7 | ||
|
|
cfb2f42e96 | ||
|
|
cbbfb1c6ad | ||
|
|
86263bd235 | ||
|
|
fc9dc836e7 | ||
|
|
a508ede373 | ||
|
|
fd4da24b1d | ||
|
|
2f0acfc07f | ||
|
|
f8cf3618f7 | ||
|
|
0ad1ce98ad | ||
|
|
04c27a9b7f | ||
|
|
12fb7104a1 | ||
|
|
f5548f39da | ||
|
|
b16d0949a4 | ||
|
|
5dfc3573e5 | ||
|
|
7800499c72 | ||
|
|
045eef24c0 | ||
|
|
3a8a1f9c08 | ||
|
|
f55f4cedfe | ||
|
|
85b77aeb91 | ||
|
|
1b4ad64ef6 | ||
|
|
c7939b8923 | ||
|
|
a155a90fba | ||
|
|
31d9307fe1 | ||
|
|
86f56b4735 | ||
|
|
1f44211088 | ||
|
|
fcc1f3bec9 | ||
|
|
cbf30604af | ||
|
|
839dd8cf94 | ||
|
|
365aeded43 | ||
|
|
ad247fb269 | ||
|
|
1e0267c03d | ||
|
|
47a16c0c2b | ||
|
|
6dcf3de29e | ||
|
|
8974d00078 | ||
|
|
d0263e7252 | ||
|
|
0d2cb18745 | ||
|
|
00084d769a | ||
|
|
c1271d9c34 | ||
|
|
4d957ea271 | ||
|
|
9b4fb796f9 | ||
|
|
c4aefdb8b9 | ||
|
|
35b7f82ad9 | ||
|
|
25ad9b0350 | ||
|
|
f22ea522b8 | ||
|
|
9f317d9878 | ||
|
|
aaa46e7d3a | ||
|
|
11692c8537 | ||
|
|
d0901961be | ||
|
|
d8d1eec3ed | ||
|
|
0182616050 | ||
|
|
8da7056cd6 | ||
|
|
50d2497288 | ||
|
|
02b378612b | ||
|
|
90903a67ac | ||
|
|
a551893322 | ||
|
|
ea9d57cc65 | ||
|
|
e141545035 | ||
|
|
62cd597994 | ||
|
|
1eff2d2c0a | ||
|
|
2af7c12cf6 | ||
|
|
3a6034a460 | ||
|
|
9c9bf201c4 | ||
|
|
cb8389bb4a | ||
|
|
77ad126530 | ||
|
|
e152013e2a | ||
|
|
0c3ccb2806 | ||
|
|
eb2615c303 | ||
|
|
29b6120f80 | ||
|
|
90315a112a | ||
|
|
50f45a133b | ||
|
|
c57685065a | ||
|
|
8048c0ff0e | ||
|
|
10c2ca5bc3 | ||
|
|
f688aa210f | ||
|
|
b377c11c41 | ||
|
|
1fe49c24e8 | ||
|
|
27ca7ddf8e | ||
|
|
b6c5278282 | ||
|
|
6cf7bd766d | ||
|
|
e75fcd7bff | ||
|
|
eb14c5cb21 | ||
|
|
0097f768ef | ||
|
|
d6dee3361e | ||
|
|
9b83ba3176 | ||
|
|
2878f546ee | ||
|
|
e71b65d593 | ||
|
|
1d5925e178 | ||
|
|
45c5137565 | ||
|
|
3544b2af81 | ||
|
|
ea003ea73c | ||
|
|
dd483cb628 | ||
|
|
b89e5b4c6b | ||
|
|
a09ce94da1 | ||
|
|
1256334805 | ||
|
|
5ff450c3bc | ||
|
|
8bb6097d90 | ||
|
|
89506c222b | ||
|
|
0887ccde13 | ||
|
|
d68a35903c | ||
|
|
b05661e120 | ||
|
|
d40e3d563b | ||
|
|
f31713a929 | ||
|
|
f4b0a63e23 | ||
|
|
884fb8dc50 | ||
|
|
4abcc9689c | ||
|
|
154e9cc5c1 | ||
|
|
534e18bbd4 | ||
|
|
0df34bdb1c | ||
|
|
11d65410e6 | ||
|
|
f4a0222e0e | ||
|
|
022943d2af | ||
|
|
3a84394847 | ||
|
|
70717f88fb | ||
|
|
95319e51ff | ||
|
|
6a1a3adce5 | ||
|
|
aff0b7a080 | ||
|
|
9128c0e298 | ||
|
|
6453e8402e | ||
|
|
7ae0fecb61 | ||
|
|
c9f230a76f | ||
|
|
17692d6497 | ||
|
|
a9861967ef | ||
|
|
4d5bae0812 | ||
|
|
845a3b479d | ||
|
|
010c39e869 | ||
|
|
e9c2e2f0aa | ||
|
|
cbb01de3a2 | ||
|
|
6ed75bd08c | ||
|
|
3c73dc40dc | ||
|
|
4986455919 | ||
|
|
f4b4f4bafb | ||
|
|
2982741558 | ||
|
|
18e700bb6e | ||
|
|
0955d66931 | ||
|
|
39c3760161 | ||
|
|
b5ec4ccbcd | ||
|
|
c5c4687063 | ||
|
|
b7865acbe3 | ||
|
|
3a3a514825 | ||
|
|
7cdcb3a69a | ||
|
|
d7899d0dd5 | ||
|
|
88a383f85e | ||
|
|
e5ed1e213d | ||
|
|
46ee8ac4a6 | ||
|
|
5ea3823946 | ||
|
|
9d90c81f64 | ||
|
|
38bfc94125 | ||
|
|
261d1300e7 | ||
|
|
e0fde479ad | ||
|
|
1af861aa94 | ||
|
|
ad76b06e8a | ||
|
|
75bab70856 | ||
|
|
f65513b1cb | ||
|
|
ba0b47bec5 | ||
|
|
f2428d7fde | ||
|
|
07eb00c705 | ||
|
|
9907213dd8 | ||
|
|
89e288c591 | ||
|
|
75874ab46f | ||
|
|
333dd5d3c5 | ||
|
|
949038979e | ||
|
|
c61598010c | ||
|
|
f56e6ba6d1 | ||
|
|
13ac3a6543 | ||
|
|
c3de5727fe | ||
|
|
2774b7eab6 | ||
|
|
d54a8bfe60 | ||
|
|
8464616c37 | ||
|
|
be06d7b833 | ||
|
|
f7879485ef | ||
|
|
00c4f4062d | ||
|
|
8c52839c02 | ||
|
|
7a1b45895f | ||
|
|
d36b724d4f | ||
|
|
6e648a522a | ||
|
|
8b56e3da21 | ||
|
|
9fbb74ba60 | ||
|
|
e8f4f9939f | ||
|
|
fa60a79241 | ||
|
|
413daf37d6 | ||
|
|
e24b0b1dc8 | ||
|
|
df81c1e232 | ||
|
|
5bc74d874c | ||
|
|
98f0571d1f | ||
|
|
04ff892e86 | ||
|
|
83f644cb03 | ||
|
|
0c03bb9254 | ||
|
|
1280505297 | ||
|
|
1efdb06d3b | ||
|
|
88c356bdf2 | ||
|
|
060b684e57 | ||
|
|
c739f2e7b1 | ||
|
|
7f85d64167 | ||
|
|
6873daa5de | ||
|
|
1a17683897 | ||
|
|
5a6dcf33fa | ||
|
|
57ceddd9ee | ||
|
|
b79d8b8879 | ||
|
|
6d6d0e102b | ||
|
|
33f5c15e45 | ||
|
|
640f167e4b | ||
|
|
0351c561b5 | ||
|
|
b8c439d2d7 | ||
|
|
a11abdbb56 | ||
|
|
617984e587 | ||
|
|
b1f731fdde | ||
|
|
9899741c00 | ||
|
|
a53c1b4b49 | ||
|
|
3e3a5df34d | ||
|
|
2227a842ae | ||
|
|
3b1509edf0 | ||
|
|
72be27e3c1 | ||
|
|
35f74791c9 | ||
|
|
913a199d95 | ||
|
|
90514e2327 | ||
|
|
3e0f7f077a | ||
|
|
8cea6ccfbb | ||
|
|
c66ee30940 | ||
|
|
60427dbb4f | ||
|
|
a24ccd9cbe | ||
|
|
e547c561e3 | ||
|
|
1b878faf2b | ||
|
|
10c3a2326b | ||
|
|
55acfe7efe | ||
|
|
c4272bf1fe | ||
|
|
2e2d8e897b | ||
|
|
18716cf97f | ||
|
|
1a7bad29d1 | ||
|
|
3b3e18f22d | ||
|
|
d2161fe221 | ||
|
|
4895dcbcc4 | ||
|
|
6990f78f69 | ||
|
|
64d606a791 | ||
|
|
3b72aaa7c2 | ||
|
|
de7199a3df | ||
|
|
c8f2233e24 | ||
|
|
d18071e533 | ||
|
|
bc1d5d5337 | ||
|
|
9da0a095cb | ||
|
|
92bd3fdcdb | ||
|
|
021ca69e6b | ||
|
|
454ec6f865 | ||
|
|
97b24cfadf | ||
|
|
332788a77a | ||
|
|
a33ccf4180 | ||
|
|
1b9b843f1a | ||
|
|
82e27a0ce4 | ||
|
|
a3ac3f6535 | ||
|
|
c1d5f9eebf | ||
|
|
70d6dea264 | ||
|
|
891c3325c9 | ||
|
|
9709b938f2 | ||
|
|
79e3a5b902 | ||
|
|
6248909003 | ||
|
|
c88646b64c | ||
|
|
4f7b464bed | ||
|
|
9d03968bc1 | ||
|
|
9ab78a55c3 | ||
|
|
9f276657c3 | ||
|
|
aeb61936ce | ||
|
|
a3ea25c1e6 | ||
|
|
13d68f93d9 | ||
|
|
81c31547ac | ||
|
|
b5d53e0831 | ||
|
|
22bc8fdcbe | ||
|
|
c7c78e303c | ||
|
|
048e1269ad | ||
|
|
13128d282b | ||
|
|
498424cf27 | ||
|
|
67bf12c11f | ||
|
|
c658721333 | ||
|
|
906b3a554c | ||
|
|
f4a13e2a8e | ||
|
|
1f61ba7edd | ||
|
|
38b262a3f0 | ||
|
|
868ed7ca7e | ||
|
|
7e6760e769 | ||
|
|
62b91f0ce9 | ||
|
|
fad2a525ea | ||
|
|
0c20109254 | ||
|
|
84bf085453 | ||
|
|
57c99bd981 | ||
|
|
bcbe64807b | ||
|
|
198c8b452d | ||
|
|
1fec3b2584 | ||
|
|
7aa29bd398 | ||
|
|
1f6301c531 | ||
|
|
edc6e56c69 | ||
|
|
d584e68e3a | ||
|
|
e36eb4d422 | ||
|
|
3946e7a0e7 | ||
|
|
a3da6b8530 | ||
|
|
f3e8ef3f65 | ||
|
|
03920524d3 | ||
|
|
4958d3ca57 | ||
|
|
5f12c58cc8 | ||
|
|
6ccdfc0129 | ||
|
|
c089de52e7 | ||
|
|
166de1bf15 | ||
|
|
726c1dc16c | ||
|
|
a52be724c0 | ||
|
|
be1f310595 | ||
|
|
953f2f22a9 | ||
|
|
74579fa53e | ||
|
|
930e811232 | ||
|
|
6ca112f3e3 | ||
|
|
659d307a12 | ||
|
|
459035529c | ||
|
|
0ad2b03f2b | ||
|
|
062c3e3539 | ||
|
|
78c606ba9e | ||
|
|
ffeeff8be3 | ||
|
|
9a9ec39ff6 | ||
|
|
61ed604b3e | ||
|
|
40e6172c0b | ||
|
|
0898ea9aa0 | ||
|
|
63a241f3b5 | ||
|
|
54529fdbb5 | ||
|
|
7d6882a006 | ||
|
|
1b57d589fb | ||
|
|
b6c5c59919 | ||
|
|
da99d7e2a8 | ||
|
|
82d795c1c5 | ||
|
|
e8b9b5e1b9 | ||
|
|
e36d67be17 | ||
|
|
0ec7834cc5 | ||
|
|
7572c1ada2 | ||
|
|
256ecbe31f | ||
|
|
037640fc70 | ||
|
|
f6ca797802 | ||
|
|
c4de107fba | ||
|
|
6d9710e7cd | ||
|
|
963b649aac | ||
|
|
4f294e8b90 | ||
|
|
a684e1dd94 | ||
|
|
777555c187 | ||
|
|
8890cfb067 | ||
|
|
6993dd3eb8 | ||
|
|
23317807f6 | ||
|
|
c24a4febc9 | ||
|
|
c7a0131624 | ||
|
|
2f3c3696d6 | ||
|
|
de0a8d0c39 | ||
|
|
8bf5111d7c | ||
|
|
1037347e06 | ||
|
|
a84cd39737 | ||
|
|
be130520d9 | ||
|
|
81614b028c | ||
|
|
c0c89dba77 | ||
|
|
97b846deec | ||
|
|
e778cf7e33 | ||
|
|
40fa737208 | ||
|
|
5df5e340e5 | ||
|
|
7c757a3c59 | ||
|
|
2c26a8cd85 | ||
|
|
584f0dda2f | ||
|
|
362df2f609 | ||
|
|
f99e352693 | ||
|
|
17a7672d4e | ||
|
|
1aedd8bc29 | ||
|
|
3d601cc154 | ||
|
|
e8c57729be | ||
|
|
e0606c5486 | ||
|
|
61e3d08136 | ||
|
|
1e199de7b2 | ||
|
|
2adf66baa2 | ||
|
|
1ee55f503d | ||
|
|
8d10aa77c8 | ||
|
|
ab0b68a7b8 | ||
|
|
7789f19055 | ||
|
|
19e807a7f6 | ||
|
|
b4249c7f38 | ||
|
|
c5baec6d1d | ||
|
|
82c7bb3635 | ||
|
|
c01499f4f1 | ||
|
|
f87be6d2c6 | ||
|
|
5c252cdcb1 | ||
|
|
42100761c1 | ||
|
|
4f6648ead8 | ||
|
|
c65314e7cd | ||
|
|
8b04485533 | ||
|
|
fc81d471ff | ||
|
|
55e0b0723e | ||
|
|
63d380f0f7 | ||
|
|
fc9a7afada | ||
|
|
27d8e75986 | ||
|
|
8b68e37b89 | ||
|
|
832c4b0e76 | ||
|
|
c4ab78b89b | ||
|
|
a6a493fe97 | ||
|
|
efae068bac | ||
|
|
2129fe1727 | ||
|
|
f7f53d5af3 | ||
|
|
9a6491b76b | ||
|
|
1db333a21b | ||
|
|
e9e9f41f2a | ||
|
|
ab652117c8 | ||
|
|
6ef23862e1 | ||
|
|
2be8f81a5b | ||
|
|
bd869798be |
+1
-1
@@ -8,7 +8,7 @@ WORKDIR /no-copy-frontend
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=dependencies /no-copy-frontend/node_modules ./node_modules
|
COPY --from=dependencies /no-copy-frontend/node_modules ./node_modules
|
||||||
RUN npm run build
|
RUN rm -rf .next && npm run build --force
|
||||||
|
|
||||||
FROM node:22.20.0 AS runner
|
FROM node:22.20.0 AS runner
|
||||||
WORKDIR /no-copy-frontend
|
WORKDIR /no-copy-frontend
|
||||||
|
|||||||
@@ -34,3 +34,11 @@ docker network inspect no-copy_app-network
|
|||||||
|
|
||||||
docker volume ls - посмотреть все базы данных
|
docker volume ls - посмотреть все базы данных
|
||||||
docker volume prune - удалить неиспользуемые базы данных
|
docker volume prune - удалить неиспользуемые базы данных
|
||||||
|
|
||||||
|
Юкаса
|
||||||
|
эндпоинт для бека.
|
||||||
|
{URL}/api/payments/webhook/yookassa
|
||||||
|
|
||||||
|
https://dev-workspace.not-copy.com/api/payments/webhook/yookassa
|
||||||
|
|
||||||
|
https://mutely-profound-sandfish.cloudpub.ru/api/payments/webhook/yookassa
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
import createNextIntlPlugin from 'next-intl/plugin';
|
import createNextIntlPlugin from 'next-intl/plugin';
|
||||||
|
const { version } = require('./package.json');
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
experimental: {
|
experimental: {
|
||||||
@@ -7,6 +8,9 @@ const nextConfig: NextConfig = {
|
|||||||
bodySizeLimit: '10mb',
|
bodySizeLimit: '10mb',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
env: {
|
||||||
|
APP_VERSION: version,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const withNextIntl = createNextIntlPlugin();
|
const withNextIntl = createNextIntlPlugin();
|
||||||
|
|||||||
Generated
+1097
-732
File diff suppressed because it is too large
Load Diff
+6
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "no-copy-frontend",
|
"name": "no-copy-frontend",
|
||||||
"version": "0.23.0",
|
"version": "0.122.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 2999",
|
"dev": "next dev -p 2999",
|
||||||
@@ -9,17 +9,20 @@
|
|||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@a2seven/yoo-checkout": "^1.1.4",
|
||||||
"@react-input/mask": "^2.0.4",
|
"@react-input/mask": "^2.0.4",
|
||||||
"@tailwindcss/postcss": "^4.1.17",
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"@tanstack/match-sorter-utils": "^8.19.4",
|
"@tanstack/match-sorter-utils": "^8.19.4",
|
||||||
"@tanstack/react-query": "^5.90.11",
|
"@tanstack/react-query": "^5.90.11",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@vkid/sdk": "^2.6.2",
|
"@vkid/sdk": "^2.6.2",
|
||||||
|
"card-validator": "^10.0.4",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"jose": "^6.1.2",
|
"jose": "^6.1.2",
|
||||||
"next": "^16.0.7",
|
"next": "^16.0.7",
|
||||||
"next-auth": "5.0.0-beta.30",
|
"next-auth": "5.0.0-beta.30",
|
||||||
"next-intl": "^4.5.8",
|
"next-intl": "^4.5.8",
|
||||||
|
"pdfjs-dist": "^5.7.284",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-slick": "^0.31.0",
|
"react-slick": "^0.31.0",
|
||||||
@@ -27,6 +30,7 @@
|
|||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"swiper": "^12.0.3",
|
"swiper": "^12.0.3",
|
||||||
"tailwindcss": "^4.1.17",
|
"tailwindcss": "^4.1.17",
|
||||||
|
"use-debounce": "^10.1.0",
|
||||||
"validator": "^13.15.26",
|
"validator": "^13.15.26",
|
||||||
"zod": "^4.1.13",
|
"zod": "^4.1.13",
|
||||||
"zustand": "^5.0.9"
|
"zustand": "^5.0.9"
|
||||||
@@ -38,6 +42,7 @@
|
|||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.0.3",
|
"eslint-config-next": "16.0.3",
|
||||||
"sass": "^1.94.2",
|
"sass": "^1.94.2",
|
||||||
|
"types-yoomoneycheckoutwidget": "^0.0.2",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"buildTime": null
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
@@ -2,7 +2,6 @@ import { useTranslations } from 'next-intl'
|
|||||||
import LogoIcon from '@/app/ui/logo-icon';
|
import LogoIcon from '@/app/ui/logo-icon';
|
||||||
import styles from '@/app/styles/module/login.module.scss';
|
import styles from '@/app/styles/module/login.module.scss';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import LanguageSwitcher from '@/app/components/LanguageSwitcher';
|
|
||||||
import ForgotPasswordForm from '@/app/ui/forms/forgot-password-form';
|
import ForgotPasswordForm from '@/app/ui/forms/forgot-password-form';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
@@ -10,9 +9,6 @@ export default function Page() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles['login-container-wrapper']}`}>
|
<div className={`${styles['login-container-wrapper']}`}>
|
||||||
<div className={`${styles['login-container-language']}`}>
|
|
||||||
<LanguageSwitcher />
|
|
||||||
</div>
|
|
||||||
<div className={`${styles['login-container']}`}>
|
<div className={`${styles['login-container']}`}>
|
||||||
<div className={`${styles['logo']}`}>
|
<div className={`${styles['logo']}`}>
|
||||||
<LogoIcon />
|
<LogoIcon />
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import Providers from '@/app/providers/getQueryServer'
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { NextIntlClientProvider, hasLocale } from 'next-intl';
|
import { NextIntlClientProvider, hasLocale } from 'next-intl';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { routing } from '@/i18n/routing';
|
import { routing } from '@/i18n/routing';
|
||||||
import { ToastProvider } from '@/app/providers/ToastProvider';
|
import { ToastProvider } from '@/app/providers/ToastProvider';
|
||||||
|
import Providers from '@/app/providers/getQueryServer';
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
import "../styles/global-styles.scss";
|
import "../styles/global-styles.scss";
|
||||||
import ActivityTracker from '../components/ActivityTracker';
|
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return routing.locales.map((locale: any) => ({ locale }));
|
return routing.locales.map((locale: any) => ({ locale }));
|
||||||
@@ -30,12 +31,11 @@ export default async function RootLayout({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={locale}>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<NextIntlClientProvider>
|
<NextIntlClientProvider locale={locale}>
|
||||||
<Providers>
|
<Providers>
|
||||||
{children}
|
{children}
|
||||||
<ActivityTracker/>
|
|
||||||
<ToastProvider />
|
<ToastProvider />
|
||||||
</Providers>
|
</Providers>
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useSearchParams, useRouter } from 'next/navigation';
|
||||||
|
import styles from '@/app/styles/module/login.module.scss'
|
||||||
|
|
||||||
|
export default function YandexCallback() {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const code = searchParams.get('code');
|
||||||
|
const error = searchParams.get('error');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Ошибка авторизации:', error);
|
||||||
|
router.push('/login?error=yandex_denied');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
console.log('получаю код');
|
||||||
|
console.log(code);
|
||||||
|
fetch('/api/auth/yandex/callback', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ code })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
console.log(data);
|
||||||
|
console.log('all ok');
|
||||||
|
/* router.push('/dashboard'); */
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [searchParams, router]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${styles['login-container-wrapper']}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="loading-animation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="global-spinner large"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -7,6 +7,9 @@ import LanguageSwitcher from '@/app/components/LanguageSwitcher';
|
|||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import VKLogin from '@/app/components/VKLogin';
|
import VKLogin from '@/app/components/VKLogin';
|
||||||
import YandexAuthWithScript from '@/app/components/YandexLogin';
|
import YandexAuthWithScript from '@/app/components/YandexLogin';
|
||||||
|
import { getHealt } from '@/app/actions/action';
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
import {YandexLoginButton} from '@/app/components/YandexLoginButton';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Login',
|
title: 'Login',
|
||||||
@@ -15,57 +18,69 @@ export const metadata: Metadata = {
|
|||||||
export default function Page() {
|
export default function Page() {
|
||||||
const t = useTranslations('Login-register-form');
|
const t = useTranslations('Login-register-form');
|
||||||
|
|
||||||
|
async function HealthData() {
|
||||||
|
const data = await getHealt();
|
||||||
|
if (!data) {
|
||||||
|
return <div>Сервер временно не работает</div>
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles['login-container-wrapper']}`}>
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
<div className={`${styles['login-container-language']}`}>
|
<HealthData />
|
||||||
|
<div className={`${styles['login-container-wrapper']}`}>
|
||||||
|
{/* <div className={`${styles['login-container-language']}`}>
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
</div>
|
</div> */}
|
||||||
<div className={`${styles['login-container']}`}>
|
<div className={`${styles['login-container']}`}>
|
||||||
<div className={`${styles['logo']}`}>
|
<div className={`${styles['logo']}`}>
|
||||||
<LogoIcon />
|
<LogoIcon />
|
||||||
<h1>NO COPY</h1>
|
<h1>NO COPY</h1>
|
||||||
<p>
|
<p>
|
||||||
{t('copyright-protection')}
|
{t('copyright-protection')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
<LoginForm />
|
|
||||||
<div className="divider">
|
|
||||||
<span>
|
|
||||||
{t('or-log-in-via')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
|
||||||
<VKLogin />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<YandexAuthWithScript />
|
|
||||||
</div>
|
|
||||||
<div className={`${styles['register-link']}`}>
|
|
||||||
<p>{t('no-account')}?
|
|
||||||
<Link href="register"> {t('register')}</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className={`${styles['features']}`}>
|
|
||||||
<div className={`${styles['feature']}`}>
|
|
||||||
<svg className={`${styles['feature-icon']}`} viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
||||||
</svg>
|
|
||||||
{t('protecting-watermark')}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles['feature']}`}>
|
<LoginForm />
|
||||||
<svg className={`${styles['feature-icon']}`} viewBox="0 0 24 24" fill="currentColor">
|
<div className="divider">
|
||||||
<path d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
<span>
|
||||||
</svg>
|
{t('or-log-in-via')}
|
||||||
{t('monitoring-violations')}
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles['feature']}`}>
|
<div className="mb-4">
|
||||||
<svg className={`${styles['feature-icon']}`} viewBox="0 0 24 24" fill="currentColor">
|
<VKLogin />
|
||||||
<path d="M12 1L3 5v6c0 5.55 3.84 9.74 9 11 5.16-1.26 9-5.45 9-11V5l-9-4z"></path>
|
</div>
|
||||||
</svg>
|
<div>
|
||||||
{t('safe-storage')}
|
<YandexLoginButton />
|
||||||
|
</div>
|
||||||
|
<div className={`${styles['register-link']}`}>
|
||||||
|
<p>{t('no-account')}?
|
||||||
|
<Link href="register"> {t('register')}</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles['features']}`}>
|
||||||
|
<div className={`${styles['feature']}`}>
|
||||||
|
<svg className={`${styles['feature-icon']}`} viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
|
</svg>
|
||||||
|
{t('protecting-watermark')}
|
||||||
|
</div>
|
||||||
|
<div className={`${styles['feature']}`}>
|
||||||
|
<svg className={`${styles['feature-icon']}`} viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||||
|
</svg>
|
||||||
|
{t('monitoring-violations')}
|
||||||
|
</div>
|
||||||
|
<div className={`${styles['feature']}`}>
|
||||||
|
<svg className={`${styles['feature-icon']}`} viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M12 1L3 5v6c0 5.55 3.84 9.74 9 11 5.16-1.26 9-5.45 9-11V5l-9-4z"></path>
|
||||||
|
</svg>
|
||||||
|
{t('safe-storage')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import DashboardFilesInfo from '@/app/ui/dashboard/new/dashboard-files-info';
|
|||||||
import DahboardLimitsSection from '@/app/ui/dashboard/new/dashborad-limits-section';
|
import DahboardLimitsSection from '@/app/ui/dashboard/new/dashborad-limits-section';
|
||||||
import DahboardGeographySection from '@/app/ui/dashboard/new/dashboard-geography-section';
|
import DahboardGeographySection from '@/app/ui/dashboard/new/dashboard-geography-section';
|
||||||
import DashboardLastCheck from '@/app/ui/dashboard/new/dashboard-last-check';
|
import DashboardLastCheck from '@/app/ui/dashboard/new/dashboard-last-check';
|
||||||
import ViolationsCarousel from '@/app/ui/dashboard/new/violations-carousel';
|
import DashboardUserViolations from '@/app/ui/dashboard/new/dashboard-user-violations';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Dashboard',
|
title: 'Dashboard',
|
||||||
@@ -24,18 +24,26 @@ export default function Page() {
|
|||||||
<PageTitle title="statistics" />
|
<PageTitle title="statistics" />
|
||||||
<DashboardUserStats />
|
<DashboardUserStats />
|
||||||
<div className="dashboard-main-grid">
|
<div className="dashboard-main-grid">
|
||||||
|
{/* <DashboardUserFiles />
|
||||||
|
<DashboardUserViolations /> */}
|
||||||
|
|
||||||
<div className="left-column">
|
<div className="left-column">
|
||||||
<DashboardUserFiles />
|
<DashboardUserFiles />
|
||||||
<DashboardPlatformsList />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="right-column">
|
<div className="right-column">
|
||||||
<ViolationsCarousel />
|
<DashboardUserViolations />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<DashboardFilesInfo />
|
<DashboardFilesInfo />
|
||||||
<div className="dashboard-main-grid">
|
<div className="dashboard-main-grid">
|
||||||
<DahboardLimitsSection />
|
|
||||||
<DahboardGeographySection />
|
{/* <DahboardGeographySection /> */}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mb-4"
|
||||||
|
>
|
||||||
|
{/* <DahboardLimitsSection /> */}
|
||||||
</div>
|
</div>
|
||||||
<DashboardLastCheck />
|
<DashboardLastCheck />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import PageTitle from '@/app/ui/page-title';
|
||||||
|
import PopularAskedQuestions from '@/app/ui/faq/popular-asked-questions';
|
||||||
|
import FrequentlyAskedQuestions from '@/app/ui/faq/frequently-asked-questions';
|
||||||
|
import FaqContact from '@/app/ui/faq/faq-contact';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageTitle title="frequently-asked-questions" />
|
||||||
|
<PopularAskedQuestions />
|
||||||
|
<FrequentlyAskedQuestions />
|
||||||
|
<FaqContact />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
// app/files/[id]/page.tsx
|
||||||
|
'use server'
|
||||||
|
|
||||||
|
import FilePageTitle from '@/app/ui/file-page/file-page-title';
|
||||||
|
import FilePageViolationInfo from '@/app/ui/file-page/file-page-violation-info';
|
||||||
|
import ViolationPpageFileStatistic from '@/app/ui/file-page/file-page-file-statistic';
|
||||||
|
import FilePageFileInfo from '@/app/ui/file-page/file-page-file-info';
|
||||||
|
import FilePageActions from '@/app/ui/file-page/file-page-actions';
|
||||||
|
import FilePageViolationsList from '@/app/ui/file-page/violation-table/file-page-violations-list';
|
||||||
|
import FilePageNote from '@/app/ui/file-page/file-page-note';
|
||||||
|
import { viewFileInfo } from '@/app/actions/fileEntity';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import FilePageNotFound from '@/app/ui/file-page/file-page-file-not-found';
|
||||||
|
|
||||||
|
type MonitoringStatus = 'NONE' | 'MONITORING_DAILY' | 'MONITORING_WEEKLY' | 'MONITORING_MONTHLY';
|
||||||
|
type MimeType = 'image' | 'video' | 'audio' | 'document';
|
||||||
|
|
||||||
|
export interface FileDetails {
|
||||||
|
id: string;
|
||||||
|
userId: number;
|
||||||
|
originalFileName: string;
|
||||||
|
storedFileName: string;
|
||||||
|
filePath: string;
|
||||||
|
protectedFilePath: string;
|
||||||
|
fileSize: number;
|
||||||
|
mimeType: MimeType;
|
||||||
|
fileExtension: string;
|
||||||
|
checksum: string;
|
||||||
|
uploadSessionId: string;
|
||||||
|
status: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
formattedSize: string;
|
||||||
|
downloadUrl: string;
|
||||||
|
existsOnDisk: boolean;
|
||||||
|
supportId: number | null;
|
||||||
|
protectStatus: string;
|
||||||
|
ownerName: string;
|
||||||
|
ownerEmail: string;
|
||||||
|
ownerCompany: string | null;
|
||||||
|
fileName: string;
|
||||||
|
fileFormat: string | null;
|
||||||
|
checksCount: number;
|
||||||
|
fileUploadDate: string;
|
||||||
|
monitoring: MonitoringStatus;
|
||||||
|
thumbnailFileUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
searchParams: Promise<{ page?: string, status?: string, violationId?: string, caseType?: 'complaint' | 'claim', startDate: string, endDate: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({
|
||||||
|
params,
|
||||||
|
searchParams
|
||||||
|
}: PageProps) {
|
||||||
|
const { id } = await params;
|
||||||
|
const { page, status, violationId, caseType, startDate, endDate } = await searchParams;
|
||||||
|
const currentPage = Number(page) || 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileInfo: FileDetails = await viewFileInfo(id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="violation-details">
|
||||||
|
<FilePageTitle fileInfo={fileInfo} />
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className="grid grid-cols-[2fr_1fr] gap-3"
|
||||||
|
>
|
||||||
|
<FilePageFileInfo fileInfo={fileInfo} />
|
||||||
|
{/* <FilePageActions /> */}
|
||||||
|
<ViolationPpageFileStatistic id={id} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<FilePageViolationsList
|
||||||
|
currentPage={currentPage}
|
||||||
|
status={status}
|
||||||
|
fileId={id}
|
||||||
|
violationId={violationId}
|
||||||
|
caseType={caseType}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
/>
|
||||||
|
{/* <FilePageNote /> */}
|
||||||
|
{/* <FilePageViolationInfo /> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FilePageNotFound />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import HeaderPanel from '@/app/ui/header/headerPanel';
|
|||||||
import { getQueryClient } from '@/app/providers/getQueryClient';
|
import { getQueryClient } from '@/app/providers/getQueryClient';
|
||||||
import { HydrationBoundary, dehydrate } from '@tanstack/react-query';
|
import { HydrationBoundary, dehydrate } from '@tanstack/react-query';
|
||||||
import { prefetchLayoutQueries } from '@/app/lib/prefetch-queries';
|
import { prefetchLayoutQueries } from '@/app/lib/prefetch-queries';
|
||||||
|
import ActivityTracker from '@/app/components/ActivityTracker';
|
||||||
|
|
||||||
export default async function Layout({ children }: { children: React.ReactNode }) {
|
export default async function Layout({ children }: { children: React.ReactNode }) {
|
||||||
const queryClient = getQueryClient();
|
const queryClient = getQueryClient();
|
||||||
@@ -20,6 +21,7 @@ export default async function Layout({ children }: { children: React.ReactNode }
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</HydrationBoundary>
|
</HydrationBoundary>
|
||||||
|
<ActivityTracker/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
||||||
import FilesTable from '@/app/ui/dashboard/files-table';
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
import PageTitle from '@/app/ui/page-title';
|
import PageTitle from '@/app/ui/page-title';
|
||||||
import UploadSectionFile from '@/app/components/upload-section-file';
|
import UploadSectionFile from '@/app/components/UploadSectionFile';
|
||||||
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
|
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
|
||||||
import { StackedBarChart } from '@/app/components/StackedBarChart';
|
import { StackedBarChart } from '@/app/components/StackedBarChart';
|
||||||
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
|
import UploadSectionFile from '@/app/components/UploadSectionFile';
|
||||||
|
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
|
||||||
|
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
||||||
|
import ProtectionStatistic from '@/app/ui/marking-page/new/protection-statistic';
|
||||||
|
|
||||||
|
export default async function Page() {
|
||||||
|
const FILE_TYPE = "document";
|
||||||
|
const { file_extension, max_file_size } = await getAllowedFilesExtensions(FILE_TYPE);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageTitleColorFrame
|
||||||
|
title="document-protection"
|
||||||
|
description="multi-layered-protection-for-your-document"
|
||||||
|
/>
|
||||||
|
<ProtectionStatistic fileType={FILE_TYPE} />
|
||||||
|
<UploadSectionFile
|
||||||
|
fileType={FILE_TYPE}
|
||||||
|
allowedExtensions={file_extension}
|
||||||
|
maxFileSize={max_file_size}
|
||||||
|
/>
|
||||||
|
<FilesTable fileType={FILE_TYPE} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import FilesTable from '@/app/ui/dashboard/files-table';
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
import UploadSectionFile from '@/app/components/upload-section-file';
|
import UploadSectionFile from '@/app/components/UploadSectionFile';
|
||||||
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
|
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
|
||||||
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
||||||
import ProtectionStatistic from '@/app/ui/marking-page/new/protection-statistic';
|
import ProtectionStatistic from '@/app/ui/marking-page/new/protection-statistic';
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
||||||
import FilesTable from '@/app/ui/dashboard/files-table';
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
import PageTitle from '@/app/ui/page-title';
|
import PageTitle from '@/app/ui/page-title';
|
||||||
import UploadSectionFile from '@/app/components/upload-section-file';
|
import UploadSectionFile from '@/app/components/UploadSectionFile';
|
||||||
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
|
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
|
||||||
import { StackedBarChart } from '@/app/components/StackedBarChart';
|
import { StackedBarChart } from '@/app/components/StackedBarChart';
|
||||||
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import MyContentInfoBlock from '@/app/ui/my-content/my-content-info-block';
|
import MyContentInfoBlock from '@/app/ui/my-content/my-content-info-block';
|
||||||
import FilesTable from '@/app/ui/dashboard/files-table';
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
import MyContentPieChart from '@/app/ui/my-content/my-content-pie-chart';
|
import MyContentPieChart from '@/app/ui/my-content/my-content-pie-chart';
|
||||||
import MyContentPageTitleColorFrame from '@/app/ui/my-content/new/my-content-page-title';
|
|
||||||
import MyContentStatsOverview from '@/app/ui/my-content/new/my-content-stats-overview';
|
import MyContentStatsOverview from '@/app/ui/my-content/new/my-content-stats-overview';
|
||||||
|
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MyContentPageTitleColorFrame title="my-content" description="my-content-description" />
|
<PageTitleColorFrame title="my-content" description="my-content-description"/>
|
||||||
<MyContentStatsOverview />
|
<MyContentStatsOverview />
|
||||||
{/* <div className="split-blocks">
|
{/* <div className="split-blocks">
|
||||||
<MyContentInfoBlock />
|
<MyContentInfoBlock />
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import PageTitle from '@/app/ui/page-title';
|
||||||
|
import { NotificationsList } from '@/app/ui/notifications/notifications-list';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageTitle title='notifications' />
|
||||||
|
|
||||||
|
<NotificationsList />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import UserUsage from '@/app/ui/payment/user-usage';
|
|||||||
import TokensSpendStats from '@/app/ui/payment/tokens-spend-stats';
|
import TokensSpendStats from '@/app/ui/payment/tokens-spend-stats';
|
||||||
import PaymentTabs from '@/app/ui/payment/payment-tabs';
|
import PaymentTabs from '@/app/ui/payment/payment-tabs';
|
||||||
import SecurePayments from '@/app/ui/payment/secure-payments';
|
import SecurePayments from '@/app/ui/payment/secure-payments';
|
||||||
|
import PaymentUserCards from '@/app/ui/payment/payment-user-cards';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'payment',
|
title: 'payment',
|
||||||
@@ -22,6 +23,7 @@ export default function Page() {
|
|||||||
<UserUsage />
|
<UserUsage />
|
||||||
<TokensSpendStats />
|
<TokensSpendStats />
|
||||||
<PaymentTabs />
|
<PaymentTabs />
|
||||||
|
<PaymentUserCards />
|
||||||
<SecurePayments />
|
<SecurePayments />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import PageTitle from '@/app/ui/page-title';
|
|
||||||
import ReferralStats from '@/app/ui/referral-page/referral-stats';
|
import ReferralStats from '@/app/ui/referral-page/referral-stats';
|
||||||
import ReferralLinkSection from '@/app/ui/referral-page/referral-link-section';
|
import ReferralLinkSection from '@/app/ui/referral-page/referral-link-section';
|
||||||
import CommissionLevels from '@/app/ui/referral-page/commission-levels';
|
import CommissionLevels from '@/app/ui/referral-page/commission-levels';
|
||||||
import InvitationsTable from '@/app/ui/referral-page/invitations-table';
|
import InvitationsTable from '@/app/ui/referral-page/table/invitations-table';
|
||||||
import IncomeAndPayments from '@/app/ui/referral-page/Income-and-payments';
|
import IncomeAndPayments from '@/app/ui/referral-page/Income-and-payments';
|
||||||
import SettingReferralProgram from '@/app/ui/referral-page/setting-referral-program';
|
import SettingReferralProgram from '@/app/ui/referral-page/setting-referral-program';
|
||||||
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
||||||
@@ -16,7 +15,7 @@ export default function Page() {
|
|||||||
<Suspense fallback={<>...</>}>
|
<Suspense fallback={<>...</>}>
|
||||||
<PageTitleColorFrame title="refferal-program" description="referral-program-description" />
|
<PageTitleColorFrame title="refferal-program" description="referral-program-description" />
|
||||||
<CommissionLevels />
|
<CommissionLevels />
|
||||||
<ReferralStats />
|
{/* <ReferralStats /> */}
|
||||||
<ReferralLinkSection />
|
<ReferralLinkSection />
|
||||||
<InvitationsTable />
|
<InvitationsTable />
|
||||||
<IncomeAndPayments />
|
<IncomeAndPayments />
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import StatsGrid from '@/app/ui/dashboard/stats-grid';
|
import ViolationsTable from '@/app/ui/violations/violations-table';
|
||||||
import ViolationsTable from '@/app/ui/dashboard/violations-table';
|
|
||||||
import FinalSummary from '@/app/ui/reports/final-summary';
|
import FinalSummary from '@/app/ui/reports/final-summary';
|
||||||
import AnalyticsCardMonth from '@/app/ui/reports/analytics-card-month';
|
import AnalyticsCardMonth from '@/app/ui/reports/analytics-card-month';
|
||||||
import AnalyticsCardGeography from '@/app/ui/reports/analytics-carв-geography';
|
import AnalyticsCardGeography from '@/app/ui/reports/analytics-card-geography';
|
||||||
import ActivityByContentType from '@/app/ui/reports/activity-by-content-type';
|
import ActivityByContentType from '@/app/ui/reports/activity-by-content-type';
|
||||||
import PageTitle from '@/app/ui/page-title';
|
import PageTitle from '@/app/ui/page-title';
|
||||||
|
import ReportsInfo from '@/app/ui/reports/reports-info';
|
||||||
|
import AnalyticsCardDistribution from '@/app/ui/reports/analytics-card-distribution';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageTitle title="reports-and-analytics" />
|
<PageTitle title="reports-and-analytics" />
|
||||||
<StatsGrid />
|
<ReportsInfo />
|
||||||
<ActivityByContentType />
|
<ActivityByContentType />
|
||||||
<div className="analytics-grid">
|
<div className="analytics-grid">
|
||||||
<AnalyticsCardMonth />
|
{/* <AnalyticsCardMonth /> */}
|
||||||
|
<AnalyticsCardDistribution />
|
||||||
<AnalyticsCardGeography />
|
<AnalyticsCardGeography />
|
||||||
</div>
|
</div>
|
||||||
<ViolationsTable />
|
<ViolationsTable />
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import SectionSearchFile from '@/app/ui/search/section-search-file';
|
||||||
|
import { SupportedFormats } from '@/app/ui/search/supported-formats';
|
||||||
|
import { SearchStats } from '@/app/ui/search/search-stats';
|
||||||
|
import SearchTitle from '@/app/ui/search/search-title';
|
||||||
|
|
||||||
|
export default async function Page() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SearchTitle />
|
||||||
|
<div className="search-grid">
|
||||||
|
<SectionSearchFile />
|
||||||
|
<div className="search-sidebar">
|
||||||
|
<SupportedFormats />
|
||||||
|
<SearchStats />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import LastActivitySettings from '@/app/ui/settings/last-activity-settings';
|
|||||||
import DangerZone from '@/app/ui/settings/danger-zone';
|
import DangerZone from '@/app/ui/settings/danger-zone';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import PageTitle from '@/app/ui/page-title';
|
import PageTitle from '@/app/ui/page-title';
|
||||||
|
import { CompanyUsersSettingPanel } from '@/app/ui/settings/company-users-setting-panel';
|
||||||
|
import SettingUserVerification from '@/app/ui/settings/setting-user-verification';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
@@ -16,11 +18,14 @@ export default function Page() {
|
|||||||
<PageTitle title="account-settings" />
|
<PageTitle title="account-settings" />
|
||||||
<div className="settings-grid">
|
<div className="settings-grid">
|
||||||
<PersonalDataSettings />
|
<PersonalDataSettings />
|
||||||
<NotificationsMonitoringSettings />
|
{/* <NotificationsMonitoringSettings /> */}
|
||||||
<SafetySetting />
|
<SafetySetting />
|
||||||
<SubscriptionSettings />
|
<SubscriptionSettings />
|
||||||
<APISettings />
|
{/* <APISettings /> */}
|
||||||
<LastActivitySettings />
|
{/* LastActivitySettings возможно нужно будет вернуть */}
|
||||||
|
{/* <LastActivitySettings /> */}
|
||||||
|
<CompanyUsersSettingPanel />
|
||||||
|
<SettingUserVerification />
|
||||||
</div>
|
</div>
|
||||||
<DangerZone />
|
<DangerZone />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { TrackingStatistic } from '@/app/ui/tracking-page/tracking-statistic';
|
||||||
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
|
import { TrackingHistoryView } from '@/app/ui/tracking-page/tracking-history-view';
|
||||||
|
|
||||||
|
export default async function Page() {
|
||||||
|
const FILE_TYPE = "document";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="tracking-page"
|
||||||
|
>
|
||||||
|
<TrackingStatistic />
|
||||||
|
<FilesTable fileType={FILE_TYPE} showFileLink={true} />
|
||||||
|
<TrackingHistoryView />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
||||||
import DahboardGeographySection from '@/app/ui/dashboard/new/dashboard-geography-section';
|
import ViolationsMyComplaint from '@/app/ui/violations/violations-my-complaint';
|
||||||
import DashboardPlatformsList from '@/app/ui/dashboard/new/dashboard-platforms-list';
|
|
||||||
import ViolationsMyCases from '@/app/ui/violations/violations-my-cases';
|
|
||||||
import ViolationsCheckAllSection from '@/app/ui/violations/violations-check-all-section';
|
import ViolationsCheckAllSection from '@/app/ui/violations/violations-check-all-section';
|
||||||
import ViolationsTable from '@/app/ui/violations/violations-table';
|
import ViolationsTable from '@/app/ui/violations/violations-table';
|
||||||
import ViolationsStatistic from '@/app/ui/violations/violations-statistic';
|
import ViolationsStatistic from '@/app/ui/violations/violations-statistic';
|
||||||
export default function Page() {
|
import AnalyticsCardGeography from '@/app/ui/reports/analytics-card-geography';
|
||||||
const FILE_TYPE = "all";
|
import AnalyticsCardDistribution from '@/app/ui/reports/analytics-card-distribution';
|
||||||
|
import ViolationsMyClaims from '@/app/ui/violations/violations-my-claims';
|
||||||
|
import ViolationsLastCombinedCases from '@/app/ui/violations/violations-last-combined-cases';
|
||||||
|
import { ViolationsMyComplaintsTable } from '@/app/ui/violations/tables/violations-my-complaints-table';
|
||||||
|
import { ViolationsMyClaimsTable } from '@/app/ui/violations/tables/violations-my-claims-table';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageTitleColorFrame
|
<PageTitleColorFrame
|
||||||
@@ -15,12 +18,18 @@ export default function Page() {
|
|||||||
description="monitoring-copyright-infringements-description"
|
description="monitoring-copyright-infringements-description"
|
||||||
/>
|
/>
|
||||||
<ViolationsStatistic />
|
<ViolationsStatistic />
|
||||||
<ViolationsMyCases />
|
|
||||||
<ViolationsCheckAllSection />
|
<ViolationsCheckAllSection />
|
||||||
<ViolationsTable />
|
<ViolationsTable />
|
||||||
<div className="dashboard-main-grid">
|
{/* <ViolationsLastCombinedCases /> */}
|
||||||
<DashboardPlatformsList />
|
{/* <ViolationsMyComplaint />
|
||||||
<DahboardGeographySection />
|
<ViolationsMyClaims /> */}
|
||||||
|
<div className="analytics-grid">
|
||||||
|
<AnalyticsCardDistribution />
|
||||||
|
<AnalyticsCardGeography />
|
||||||
|
</div>
|
||||||
|
<div className="analytics-grid">
|
||||||
|
<ViolationsMyClaimsTable />
|
||||||
|
<ViolationsMyComplaintsTable />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import LogoIcon from '@/app/ui/logo-icon';
|
|||||||
import styles from '@/app/styles/module/login.module.scss'
|
import styles from '@/app/styles/module/login.module.scss'
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import RegisterForm from '@/app/ui/forms/register-form';
|
import RegisterForm from '@/app/ui/forms/register-form';
|
||||||
import LanguageSwitcher from '@/app/components/LanguageSwitcher';
|
|
||||||
import {useTranslations} from 'next-intl';
|
import {useTranslations} from 'next-intl';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
@@ -10,9 +9,6 @@ export default function Page() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles['login-container-wrapper']}`}>
|
<div className={`${styles['login-container-wrapper']}`}>
|
||||||
<div className={`${styles['login-container-language']}`}>
|
|
||||||
<LanguageSwitcher />
|
|
||||||
</div>
|
|
||||||
<div className={`${styles['login-container']}`}>
|
<div className={`${styles['login-container']}`}>
|
||||||
<div className={`${styles['logo']}`}>
|
<div className={`${styles['logo']}`}>
|
||||||
<LogoIcon />
|
<LogoIcon />
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { useTranslations } from 'next-intl'
|
||||||
|
import LogoIcon from '@/app/ui/logo-icon';
|
||||||
|
import styles from '@/app/styles/module/login.module.scss';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import ResetPasswordForm from '@/app/ui/forms/reset-password-form';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const t = useTranslations('Login-register-form');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${styles['login-container-wrapper']}`}>
|
||||||
|
<div className={`${styles['login-container']}`}>
|
||||||
|
<div className={`${styles['logo']}`}>
|
||||||
|
<LogoIcon />
|
||||||
|
<h1>NO COPY</h1>
|
||||||
|
<p>
|
||||||
|
{t('recover-password')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ResetPasswordForm />
|
||||||
|
|
||||||
|
<div className={`${styles['register-link']}`}>
|
||||||
|
<p>{t('already-have-an-account')}? <Link href="login">{t('login')}</Link></p>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles['register-link']}`}>
|
||||||
|
<p>{t('no-account')}?
|
||||||
|
<Link href="register"> {t('register')}</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { PdfProvider } from '@/app/contexts/PdfContext';
|
||||||
|
import { WatchDocProviders } from '@/app/providers/WatchDocProviders';
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<WatchDocProviders>
|
||||||
|
<PdfProvider>
|
||||||
|
{children}
|
||||||
|
</PdfProvider>
|
||||||
|
</WatchDocProviders>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import PdfViewer from '@/app/components/PdfViewer';
|
||||||
|
import { DownloadButton } from '@/app/components/document-viewer/DownloadButton';
|
||||||
|
import { CopyLinkButton } from '@/app/components/document-viewer/CopyLinkButton';
|
||||||
|
import { fetchDocumentInfo } from '@/app/actions/trackingActions';
|
||||||
|
import { LoginUserBage } from '@/app/components/document-viewer/LoginUserBage';
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
searchParams: Promise<{ docid: string; title?: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DocumentInfo {
|
||||||
|
checksCount?: number;
|
||||||
|
checksum?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
downloadUrl?: string;
|
||||||
|
existsOnDisk?: boolean;
|
||||||
|
fileExtension?: string;
|
||||||
|
fileFormat?: string | null;
|
||||||
|
fileName?: string;
|
||||||
|
filePath?: string;
|
||||||
|
fileSize?: number;
|
||||||
|
fileUploadDate?: string;
|
||||||
|
formattedSize?: string;
|
||||||
|
id: string;
|
||||||
|
mimeType?: string;
|
||||||
|
monitoring?: string;
|
||||||
|
originalFileName?: string;
|
||||||
|
ownerCompany?: string | null;
|
||||||
|
ownerEmail?: string;
|
||||||
|
ownerName?: string;
|
||||||
|
permissions?: {
|
||||||
|
DOWNLOAD?: boolean;
|
||||||
|
};
|
||||||
|
protectStatus?: string;
|
||||||
|
protectedFilePath?: string;
|
||||||
|
status?: string;
|
||||||
|
storedFileName?: string;
|
||||||
|
supportId?: number;
|
||||||
|
thumbnailFileUrl?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
uploadSessionId?: string;
|
||||||
|
userId?: number;
|
||||||
|
appealInfos?: any[]; //Не знаю что тут будет
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ searchParams }: PageProps) {
|
||||||
|
const { docid, title } = await searchParams;
|
||||||
|
|
||||||
|
let response: DocumentInfo | null = null;
|
||||||
|
let error: string | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await fetchDocumentInfo(docid);
|
||||||
|
} catch (err) {
|
||||||
|
error = 'Не удалось загрузить информацию о документе';
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentTitle = response?.fileName || 'FileName';
|
||||||
|
|
||||||
|
if (error || !response) {
|
||||||
|
return (
|
||||||
|
<div className="watch-doc-wrapper">
|
||||||
|
<div className="watch-doc-page">
|
||||||
|
<header className="watch-doc-header">
|
||||||
|
<Link href="#" className="header-logo">NoCopy</Link>
|
||||||
|
<LoginUserBage />
|
||||||
|
</header>
|
||||||
|
<div className="error-container">
|
||||||
|
<h2>Ошибка загрузки документа</h2>
|
||||||
|
<p>{error || 'Документ не найден'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="watch-doc-wrapper">
|
||||||
|
<div className="watch-doc-page">
|
||||||
|
<header className="watch-doc-header">
|
||||||
|
<Link href="#" className="header-logo">NoCopy</Link>
|
||||||
|
<LoginUserBage />
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="watch-doc-status">
|
||||||
|
<div className="st-title">Защита подтверждена</div>
|
||||||
|
<div className="st-sub">
|
||||||
|
Документ <strong>{response?.fileName ? response?.fileName : '#'}</strong> зарегистрирован и защищён платформой NoCopy.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="watch-doc-grid">
|
||||||
|
<div
|
||||||
|
className="flex flex-col justify-between"
|
||||||
|
>
|
||||||
|
<div className="watch-doc-card">
|
||||||
|
<div className="card-head">Правообладатель</div>
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="owner-row">
|
||||||
|
<div className="owner-av">
|
||||||
|
{response?.fileName ? response?.fileName[0] : ''}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{response?.ownerName && (
|
||||||
|
<div className="owner-name">{response?.ownerName}</div>
|
||||||
|
)}
|
||||||
|
{response?.ownerCompany && (
|
||||||
|
<div className="owner-name">{response?.ownerCompany}</div>
|
||||||
|
)}
|
||||||
|
{response?.ownerName && (
|
||||||
|
<div className="owner-meta">Зарегистрирован в NoCopy</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="watch-doc-actions">
|
||||||
|
{response?.permissions?.DOWNLOAD && (
|
||||||
|
<DownloadButton fileId={docid} fileName={response?.fileName}/>
|
||||||
|
)}
|
||||||
|
<CopyLinkButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="watch-doc-card">
|
||||||
|
<div className="card-head">Предпросмотр</div>
|
||||||
|
<div className="card-body">
|
||||||
|
<PdfViewer
|
||||||
|
fileId={docid}
|
||||||
|
fileName={documentTitle}
|
||||||
|
showDownload={response.permissions?.DOWNLOAD}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="watch-doc-footer">
|
||||||
|
<Link href="#">NoCopy</Link>
|
||||||
|
<Link href="/terms-of-use">Условия</Link>
|
||||||
|
<Link href="/privacy-policy">Конфиденциальность</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
|
||||||
|
export async function getSearchSettings() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/search/settings`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
if (parsed.message_body) {
|
||||||
|
return parsed.message_body;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putSearchSettings(proxy: boolean, yandex: boolean, google: boolean) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/search/settings`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
engines: {
|
||||||
|
yandex: yandex,
|
||||||
|
google: google
|
||||||
|
},
|
||||||
|
proxyEnabled: proxy
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
if (parsed.message_desc === 'Settings updated') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+165
-12
@@ -1,15 +1,14 @@
|
|||||||
'use server'
|
'use server'
|
||||||
|
|
||||||
import { getSessionData, deleteSession } from '@/app/actions/session';
|
import { getSessionData, deleteSession } from '@/app/actions/session';
|
||||||
import { testUserData, API_BASE_URL } from '@/app/actions/definitions';
|
import { testUserData, API_BASE_URL } from '@/app/actions/definitions';
|
||||||
import { tokenLifeExtension } from '@/app/actions/auth';
|
|
||||||
import { redirect } from 'next/navigation';
|
|
||||||
|
|
||||||
export async function getUserData() {
|
export async function getUserData() {
|
||||||
const userEmail = await getSessionData('email');
|
const userEmail = await getSessionData('email');
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
/* для теста */
|
/* для теста */
|
||||||
if (userEmail === "test" && token === "1111") {
|
if (userEmail === 'test' && token === '1111') {
|
||||||
return testUserData;
|
return testUserData;
|
||||||
}
|
}
|
||||||
/* для теста */
|
/* для теста */
|
||||||
@@ -18,17 +17,19 @@ export async function getUserData() {
|
|||||||
const response = await fetch(`${API_BASE_URL}/v1/api/user?email=${userEmail}`, {
|
const response = await fetch(`${API_BASE_URL}/v1/api/user?email=${userEmail}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json",
|
'Accept': 'application/json',
|
||||||
"Authorization": `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return await response.json();
|
return await response.json();
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('error');
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,13 +43,13 @@ export async function getUserFilesInfo() {
|
|||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 20005,
|
msg_id: 20005,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: "user_files_info",
|
action: 'user_files_info',
|
||||||
token: token
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -61,8 +62,160 @@ export async function getUserFilesInfo() {
|
|||||||
} else {
|
} else {
|
||||||
throw parsed.message_code;
|
throw parsed.message_code;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`error: ${error}`);
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchTariffs(tariffTerm: 'MONTHLY' | 'YEAR') {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30001,
|
||||||
|
message_body: {
|
||||||
|
action: 'get',
|
||||||
|
tariff_term: tariffTerm
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json()
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return parsed.message_body?.tariffs;
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchTokensBundle() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30001,
|
||||||
|
message_body: {
|
||||||
|
action: 'tariffs_by_type',
|
||||||
|
type: 'token'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return parsed.message_body?.token_tariffs;
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBuildData() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/check/api/build`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return await response.json();
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchINN(inn: string) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/auth`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30004,
|
||||||
|
message_body: {
|
||||||
|
inn: inn,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json()
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return parsed.message_body
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getHealt() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/check/api/healt`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return await response.json();
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+360
-89
@@ -3,6 +3,8 @@
|
|||||||
import { loginFormSchema, API_BASE_URL, getSignupFormSchema } from '@/app/actions/definitions';
|
import { loginFormSchema, API_BASE_URL, getSignupFormSchema } from '@/app/actions/definitions';
|
||||||
import { createSession, deleteSession, getSessionData, updateSession } from '@/app/actions/session';
|
import { createSession, deleteSession, getSessionData, updateSession } from '@/app/actions/session';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
import { fetchINN } from '@/app/actions/action';
|
||||||
|
import { headers } from 'next/headers';
|
||||||
|
|
||||||
export async function logout() {
|
export async function logout() {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
@@ -11,9 +13,9 @@ export async function logout() {
|
|||||||
await fetch(`${API_BASE_URL}/v1/api/auth/logout`, {
|
await fetch(`${API_BASE_URL}/v1/api/auth/logout`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json",
|
'Accept': 'application/json',
|
||||||
"Authorization": `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -36,8 +38,8 @@ export async function authorization(
|
|||||||
const password = formData.get('password');
|
const password = formData.get('password');
|
||||||
|
|
||||||
/* для теста */
|
/* для теста */
|
||||||
if (email === "test" && password === "test") {
|
if (email === 'test' && password === 'test') {
|
||||||
await createSession("1111", "test");
|
await createSession('1111', 'test');
|
||||||
redirect('/pages/dashboard');
|
redirect('/pages/dashboard');
|
||||||
}
|
}
|
||||||
/* для теста */
|
/* для теста */
|
||||||
@@ -70,7 +72,7 @@ export async function authorization(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { email, password } = validatedFields.data;
|
const { email, password } = validatedFields.data;
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/auth`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -81,13 +83,14 @@ export async function authorization(
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
if (parsed.message_desc === 'Operation successful') {
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
await createSession(parsed.message_body.token, email);
|
await createSession(parsed.message_body.token, email);
|
||||||
} else {
|
} else {
|
||||||
throw (`${parsed.message_code}`);
|
throw (`${parsed.message_code}`);
|
||||||
@@ -130,14 +133,16 @@ export async function authorization(
|
|||||||
|
|
||||||
export type FormState = {
|
export type FormState = {
|
||||||
previousState: {
|
previousState: {
|
||||||
fullName: string;
|
fullName?: string | undefined;
|
||||||
email: string;
|
email?: string | undefined;
|
||||||
password: string;
|
password?: string | undefined;
|
||||||
confirm_password: string;
|
confirm_password?: string | undefined;
|
||||||
phone: string;
|
phone?: string | undefined;
|
||||||
companyName: string;
|
companyName?: string | undefined;
|
||||||
agree: string;
|
agree?: string | undefined;
|
||||||
},
|
referralCode?: string | undefined;
|
||||||
|
inn?: string | undefined;
|
||||||
|
} | undefined,
|
||||||
mailConfirm?: boolean,
|
mailConfirm?: boolean,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
error: Record<string, string> | null
|
error: Record<string, string> | null
|
||||||
@@ -147,6 +152,11 @@ export async function registration(
|
|||||||
state: FormState | undefined,
|
state: FormState | undefined,
|
||||||
formData: FormData,
|
formData: FormData,
|
||||||
): Promise<FormState> {
|
): Promise<FormState> {
|
||||||
|
const headersList = await headers();
|
||||||
|
const accountType = formData.get('accountType') as 'b2b' | 'b2c';
|
||||||
|
|
||||||
|
/* вернуть в true когда проект выйдет из дева */
|
||||||
|
const mail_verified = false;
|
||||||
const fullName = formData.get('fullName') as string || '';
|
const fullName = formData.get('fullName') as string || '';
|
||||||
const email = formData.get('email') as string || '';
|
const email = formData.get('email') as string || '';
|
||||||
const password = formData.get('password') as string || '';
|
const password = formData.get('password') as string || '';
|
||||||
@@ -154,8 +164,16 @@ export async function registration(
|
|||||||
const phone = formData.get('phone') as string || '';
|
const phone = formData.get('phone') as string || '';
|
||||||
const companyName = formData.get('companyName') as string || '';
|
const companyName = formData.get('companyName') as string || '';
|
||||||
const agree = formData.get('agree') as string || '';
|
const agree = formData.get('agree') as string || '';
|
||||||
|
const referralCode = formData.get('referralCode') as string || '';
|
||||||
|
const inn = formData.get('inn') as string || '';
|
||||||
|
|
||||||
const SignupFormSchema = await getSignupFormSchema();
|
|
||||||
|
const forwardedFor = headersList.get('x-forwarded-for');
|
||||||
|
const realIp = headersList.get('x-real-ip');
|
||||||
|
const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';
|
||||||
|
const userAgent = headersList.get('user-agent') || 'unknown';
|
||||||
|
|
||||||
|
const SignupFormSchema = await getSignupFormSchema(accountType);
|
||||||
|
|
||||||
const dataToValidate = {
|
const dataToValidate = {
|
||||||
fullName,
|
fullName,
|
||||||
@@ -164,23 +182,41 @@ export async function registration(
|
|||||||
password,
|
password,
|
||||||
confirm_password,
|
confirm_password,
|
||||||
agree,
|
agree,
|
||||||
companyName
|
...(accountType === 'b2b' && { companyName, inn })
|
||||||
};
|
};
|
||||||
|
|
||||||
const validatedFields = SignupFormSchema.safeParse(dataToValidate);
|
const validatedFields = await SignupFormSchema.safeParse(dataToValidate);
|
||||||
|
|
||||||
if (!validatedFields.success) {
|
console.log(`try to register ${accountType}`);
|
||||||
|
|
||||||
|
let confirmedCompanyName: boolean = true;
|
||||||
|
if (accountType === 'b2b') {
|
||||||
|
try {
|
||||||
|
const response = await fetchINN(inn);
|
||||||
|
confirmedCompanyName = response?.companyName ? true : false;
|
||||||
|
} catch (error) {
|
||||||
|
confirmedCompanyName = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validatedFields.success || !confirmedCompanyName) {
|
||||||
const errors: Record<string, string> = {}
|
const errors: Record<string, string> = {}
|
||||||
JSON.parse(validatedFields.error.message).forEach((obj: {
|
if (validatedFields?.error?.message) {
|
||||||
message: string,
|
JSON?.parse(validatedFields?.error?.message).forEach((obj: {
|
||||||
path: string
|
message: string,
|
||||||
}) => {
|
path: string
|
||||||
if (errors[obj.path[0]]) {
|
}) => {
|
||||||
errors[obj.path[0]] = `${errors[obj.path[0]]}&${obj.message}`;
|
if (errors[obj.path[0]]) {
|
||||||
} else {
|
errors[obj.path[0]] = `${errors[obj.path[0]]}&${obj.message}`;
|
||||||
errors[obj.path[0]] = obj.message;
|
} else {
|
||||||
}
|
errors[obj.path[0]] = obj.message;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirmedCompanyName) {
|
||||||
|
errors.inn = 'inn-invalid'
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
previousState: {
|
previousState: {
|
||||||
@@ -189,8 +225,9 @@ export async function registration(
|
|||||||
password,
|
password,
|
||||||
confirm_password,
|
confirm_password,
|
||||||
phone,
|
phone,
|
||||||
companyName,
|
agree,
|
||||||
agree
|
...(referralCode !== '' && { referralCode }),
|
||||||
|
...(accountType === 'b2b' && { companyName, inn })
|
||||||
},
|
},
|
||||||
error: errors
|
error: errors
|
||||||
};
|
};
|
||||||
@@ -198,7 +235,7 @@ export async function registration(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { fullName, email, password, phone } = validatedFields.data;
|
const { fullName, email, password, phone } = validatedFields.data;
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/auth`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -208,39 +245,71 @@ export async function registration(
|
|||||||
email,
|
email,
|
||||||
phone: phone,
|
phone: phone,
|
||||||
password,
|
password,
|
||||||
companyName: companyName,
|
accountType,
|
||||||
|
mail_verified: mail_verified,
|
||||||
|
ip: ip,
|
||||||
|
user_agent: userAgent,
|
||||||
|
...(accountType === 'b2b' && { companyName: companyName, inn: inn }),
|
||||||
|
...(referralCode !== '' && { referralLink: referralCode })
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log({
|
||||||
|
fullName: fullName,
|
||||||
|
email,
|
||||||
|
phone: phone,
|
||||||
|
password,
|
||||||
|
accountType,
|
||||||
|
ip: ip,
|
||||||
|
user_agent: userAgent,
|
||||||
|
...(accountType === 'b2b' && { companyName: companyName, inn: inn }),
|
||||||
|
...(referralCode !== '' && { referralLink: referralCode })
|
||||||
|
})
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json() as {
|
let parsed = await response.json() as {
|
||||||
message_desc: string,
|
message_desc: string,
|
||||||
message_body: { token: string, userId: number },
|
message_body: { token: string, userId: number, fieldErrors: string },
|
||||||
message_code: string
|
message_code: number
|
||||||
};
|
};
|
||||||
console.log(parsed);
|
|
||||||
if (parsed.message_desc === 'Operation successful') {
|
|
||||||
|
|
||||||
/* await createSession(parsed.message_body.token, email as string); */
|
if (parsed.message_code === 0) {
|
||||||
return {
|
console.log(`registration - ${accountType}`);
|
||||||
previousState: {
|
console.log({
|
||||||
fullName,
|
fullName: fullName,
|
||||||
email,
|
email,
|
||||||
password,
|
phone: phone,
|
||||||
confirm_password,
|
password,
|
||||||
phone,
|
accountType,
|
||||||
companyName,
|
...(referralCode !== '' && { referralLink: referralCode }),
|
||||||
agree
|
...(accountType === 'b2b' && { companyName: companyName, inn: inn })
|
||||||
},
|
});
|
||||||
mailConfirm: true,
|
|
||||||
userId: parsed.message_body.userId,
|
if (mail_verified) {
|
||||||
error: null
|
return {
|
||||||
};
|
previousState: {
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
confirm_password,
|
||||||
|
phone,
|
||||||
|
agree,
|
||||||
|
...(referralCode !== '' && { referralLink: referralCode }),
|
||||||
|
...(accountType === 'b2b' && { companyName: companyName, inn: inn })
|
||||||
|
},
|
||||||
|
mailConfirm: true,
|
||||||
|
userId: parsed.message_body.userId,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
await createSession(parsed.message_body.token, email as string);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw parsed;
|
throw parsed;
|
||||||
}
|
}
|
||||||
@@ -249,26 +318,37 @@ export async function registration(
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
const errors: Record<string, string> = {};
|
||||||
if (error) {
|
if (error) {
|
||||||
const typedError = error as any;
|
const typedError = error as any;
|
||||||
const errors: Record<string, string> = {};
|
|
||||||
|
|
||||||
switch (typedError.message_code) {
|
switch (typedError.message_code) {
|
||||||
case 1:
|
case 1:
|
||||||
errors['server'] = 'email-or-already-registered'
|
if (typedError.message_desc === 'Refferal link is not exist') {
|
||||||
break;
|
errors['server'] = 'referral-link-is-not-exist'
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
errors['server'] = 'email-or-already-registered'
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 2:
|
case 2:
|
||||||
typedError.message_body.fieldErrors.forEach((obj: {
|
if (typedError.message_body?.fieldErrors) {
|
||||||
code: string,
|
typedError.message_body?.fieldErrors?.forEach((obj: {
|
||||||
field: string
|
code: string,
|
||||||
}) => {
|
field: string
|
||||||
if (errors[obj.field]) {
|
}) => {
|
||||||
errors[obj.field] = `${errors[obj.field]}&${obj.code.replaceAll('.', '-')}`;
|
if (errors[obj.field]) {
|
||||||
} else {
|
errors[obj.field] = `${errors[obj.field]}&${obj.code.replaceAll('.', '-')}`;
|
||||||
errors[obj.field] = obj.code.replaceAll('.', '-');
|
} else {
|
||||||
}
|
errors[obj.field] = obj.code.replaceAll('.', '-');
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (typedError.message_desc === 'Company already exists') {
|
||||||
|
errors['inn'] = 'company-already-registered'
|
||||||
|
} else {
|
||||||
|
errors['server'] = 'unknown-error'
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -284,14 +364,27 @@ export async function registration(
|
|||||||
confirm_password,
|
confirm_password,
|
||||||
phone,
|
phone,
|
||||||
companyName,
|
companyName,
|
||||||
agree
|
agree,
|
||||||
|
inn
|
||||||
},
|
},
|
||||||
mailConfirm: false,
|
mailConfirm: false,
|
||||||
error: errors
|
error: errors
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
return {
|
||||||
|
previousState: {
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
confirm_password,
|
||||||
|
phone,
|
||||||
|
companyName,
|
||||||
|
agree,
|
||||||
|
inn
|
||||||
|
},
|
||||||
|
error: errors
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect('/pages/dashboard');
|
redirect('/pages/dashboard');
|
||||||
@@ -306,15 +399,13 @@ export async function tokenLifeExtension() {
|
|||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json",
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 20008,
|
msg_id: 20008
|
||||||
message_body: {
|
|
||||||
token: token
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -330,13 +421,8 @@ export async function tokenLifeExtension() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function confirmEmail(
|
export async function confirmEmail(userId: number, verifyToken: string) {
|
||||||
state: {error: string} | undefined,
|
|
||||||
formData: FormData
|
|
||||||
) {
|
|
||||||
console.log('confirmEmail');
|
console.log('confirmEmail');
|
||||||
const emailConfirm = formData.get('emailConfirm') as string || '';
|
|
||||||
const userId = formData.get('userId') as string || '';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
@@ -344,14 +430,14 @@ export async function confirmEmail(
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
|
'Authorization': `Bearer ${verifyToken}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 20009,
|
msg_id: 20009,
|
||||||
message_body: {
|
message_body: {
|
||||||
resend: 0,
|
resend: 0,
|
||||||
user_id: userId,
|
user_id: userId
|
||||||
verify_token: emailConfirm
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -362,12 +448,23 @@ export async function confirmEmail(
|
|||||||
|
|
||||||
if (parsed.message_code === 0) {
|
if (parsed.message_code === 0) {
|
||||||
await createSession(parsed.message_body.token, parsed.message_body.email as string);
|
await createSession(parsed.message_body.token, parsed.message_body.email as string);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'entered-code-is-incorrect'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'server-error'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
error: 'error'
|
success: false,
|
||||||
}
|
error: 'network-error'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect('/pages/dashboard');
|
redirect('/pages/dashboard');
|
||||||
@@ -376,7 +473,6 @@ export async function confirmEmail(
|
|||||||
export async function resendConfirmEmail(userId: number) {
|
export async function resendConfirmEmail(userId: number) {
|
||||||
console.log('resendConfirmEmail');
|
console.log('resendConfirmEmail');
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -410,3 +506,178 @@ export async function resendConfirmEmail(userId: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FormStateConfirmPassword = {
|
||||||
|
previousState: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
confirm_password: string;
|
||||||
|
verifyToken: string;
|
||||||
|
},
|
||||||
|
error: Record<string, string> | null
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function resetPassword(
|
||||||
|
state: any | undefined,
|
||||||
|
formData: FormData,
|
||||||
|
) {
|
||||||
|
const email = formData.get('email');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/auth`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20010,
|
||||||
|
message_body: {
|
||||||
|
email: email,
|
||||||
|
action: "resetPassword"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
console.log(parsed);
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw 'error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
error: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
console.log('redirect')
|
||||||
|
redirect(`/reset-password?email=${email}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function confirmPassword(
|
||||||
|
state: FormStateConfirmPassword | undefined,
|
||||||
|
formData: any,
|
||||||
|
): Promise<FormStateConfirmPassword | undefined> {
|
||||||
|
const email = formData.get('email');
|
||||||
|
const password = formData.get('password');
|
||||||
|
const confirm_password = formData.get('confirm_password');
|
||||||
|
const verifyToken = formData.get('verify-token');
|
||||||
|
console.log('confirmPassword');
|
||||||
|
|
||||||
|
const SignupFormSchema = await getSignupFormSchema('resetPassword');
|
||||||
|
|
||||||
|
const dataToValidate = {
|
||||||
|
password,
|
||||||
|
confirm_password
|
||||||
|
};
|
||||||
|
|
||||||
|
const validatedFields = SignupFormSchema.safeParse(dataToValidate);
|
||||||
|
|
||||||
|
if (!validatedFields.success) {
|
||||||
|
const errors: Record<string, string> = {}
|
||||||
|
JSON.parse(validatedFields.error.message).forEach((obj: {
|
||||||
|
message: string,
|
||||||
|
path: string
|
||||||
|
}) => {
|
||||||
|
if (errors[obj.path[0]]) {
|
||||||
|
errors[obj.path[0]] = `${errors[obj.path[0]]}&${obj.message}`;
|
||||||
|
} else {
|
||||||
|
errors[obj.path[0]] = obj.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
confirm_password,
|
||||||
|
verifyToken
|
||||||
|
},
|
||||||
|
error: errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${verifyToken}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20010,
|
||||||
|
message_body: {
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
action: "confirmVerification"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
|
||||||
|
const parsed = await response.json();
|
||||||
|
console.log(parsed);
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
await createSession(parsed.message_body.authToken, email as string);
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errors: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
const typedError = error as any;
|
||||||
|
|
||||||
|
switch (typedError.message_code) {
|
||||||
|
case 1:
|
||||||
|
errors['server'] = 'unknown-error'
|
||||||
|
break
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
if (typedError.message_desc === 'Token not found or time expired') {
|
||||||
|
errors['server'] = 'token-not-found-or-time-expired'
|
||||||
|
} else {
|
||||||
|
errors['server'] = 'unknown-error'
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
errors['server'] = 'request-ended-with-an-error'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
confirm_password,
|
||||||
|
verifyToken
|
||||||
|
},
|
||||||
|
error: errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
confirm_password,
|
||||||
|
verifyToken
|
||||||
|
},
|
||||||
|
error: errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect('/pages/dashboard');
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { API_BASE_URL, getSignupFormSchema } from '@/app/actions/definitions';
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { headers } from 'next/headers';
|
||||||
|
|
||||||
|
export type CompanyUserRegistrationFormState = {
|
||||||
|
previousState: {
|
||||||
|
fullName: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
confirm_password: string;
|
||||||
|
phone: string;
|
||||||
|
},
|
||||||
|
createdUser: string | null
|
||||||
|
error: Record<string, string> | null
|
||||||
|
} | undefined;
|
||||||
|
|
||||||
|
export async function companyUserRegistration(
|
||||||
|
state: CompanyUserRegistrationFormState | undefined,
|
||||||
|
formData: FormData,
|
||||||
|
): Promise<CompanyUserRegistrationFormState> {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
if (!token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headersList = await headers();
|
||||||
|
|
||||||
|
const fullName = formData.get('fullName') as string || '';
|
||||||
|
const email = formData.get('email') as string || '';
|
||||||
|
const password = formData.get('password') as string || '';
|
||||||
|
const confirm_password = formData.get('confirm_password') as string || '';
|
||||||
|
const phone = formData.get('phone') as string || '';
|
||||||
|
|
||||||
|
const SignupFormSchema = await getSignupFormSchema('b2c');
|
||||||
|
|
||||||
|
const forwardedFor = headersList.get('x-forwarded-for');
|
||||||
|
const realIp = headersList.get('x-real-ip');
|
||||||
|
const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';
|
||||||
|
const userAgent = headersList.get('user-agent') || 'unknown';
|
||||||
|
|
||||||
|
const dataToValidate = {
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
phone: phone.replace(/[-\(\)\s]/g, ''),
|
||||||
|
password,
|
||||||
|
confirm_password,
|
||||||
|
agree: 'on'
|
||||||
|
};
|
||||||
|
|
||||||
|
const validatedFields = SignupFormSchema.safeParse(dataToValidate);
|
||||||
|
|
||||||
|
console.log(`try to register company users ${email}`);
|
||||||
|
|
||||||
|
if (!validatedFields.success) {
|
||||||
|
const errors: Record<string, string> = {}
|
||||||
|
JSON.parse(validatedFields.error.message).forEach((obj: {
|
||||||
|
message: string,
|
||||||
|
path: string
|
||||||
|
}) => {
|
||||||
|
if (errors[obj.path[0]]) {
|
||||||
|
errors[obj.path[0]] = `${errors[obj.path[0]]}&${obj.message}`;
|
||||||
|
} else {
|
||||||
|
errors[obj.path[0]] = obj.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('registration error');
|
||||||
|
console.log(errors);
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
confirm_password,
|
||||||
|
phone
|
||||||
|
},
|
||||||
|
createdUser: null,
|
||||||
|
error: errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { fullName, email, password, phone } = validatedFields.data;
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20002,
|
||||||
|
message_body: {
|
||||||
|
fullName: fullName,
|
||||||
|
email,
|
||||||
|
phone: phone,
|
||||||
|
password,
|
||||||
|
accountType: 'b2b',
|
||||||
|
mail_verified: false,
|
||||||
|
ip: ip,
|
||||||
|
user_agent: userAgent,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json() as {
|
||||||
|
message_desc: string,
|
||||||
|
message_body: { token: string },
|
||||||
|
message_code: number
|
||||||
|
};
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
console.log(`registration - ${email}`);
|
||||||
|
console.log({
|
||||||
|
fullName: fullName,
|
||||||
|
email,
|
||||||
|
phone: phone,
|
||||||
|
password,
|
||||||
|
ip: ip,
|
||||||
|
user_agent: userAgent,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
fullName: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
confirm_password: '',
|
||||||
|
phone: ''
|
||||||
|
},
|
||||||
|
createdUser: fullName,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errors: Record<string, string> = {};
|
||||||
|
if (error) {
|
||||||
|
const typedError = error as any;
|
||||||
|
|
||||||
|
switch (typedError.message_code) {
|
||||||
|
case 1:
|
||||||
|
errors['server'] = 'email-or-already-registered'
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
if (typedError.message_body?.fieldErrors) {
|
||||||
|
typedError.message_body?.fieldErrors?.forEach((obj: {
|
||||||
|
code: string,
|
||||||
|
field: string
|
||||||
|
}) => {
|
||||||
|
if (errors[obj.field]) {
|
||||||
|
errors[obj.field] = `${errors[obj.field]}&${obj.code.replaceAll('.', '-')}`;
|
||||||
|
} else {
|
||||||
|
errors[obj.field] = obj.code.replaceAll('.', '-');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
errors['server'] = 'unknown-error'
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
errors['server'] = 'request-ended-with-an-error'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
confirm_password,
|
||||||
|
phone
|
||||||
|
},
|
||||||
|
createdUser: null,
|
||||||
|
error: errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
confirm_password,
|
||||||
|
phone
|
||||||
|
},
|
||||||
|
createdUser: null,
|
||||||
|
error: errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import { createValidationSchema } from "@/app/lib/validation-config-parser";
|
import { createValidationSchema, createValidationSchemaForPassword } from '@/app/lib/validation-config-parser';
|
||||||
import validationConfig from '@/app/lib/validation-config.json';
|
import validationConfig from '@/app/lib/validation-config.json';
|
||||||
|
|
||||||
export type FormState =
|
export type FormState =
|
||||||
@@ -35,30 +35,31 @@ interface ValidationConfig {
|
|||||||
|
|
||||||
const defaultConfig = validationConfig as ValidationConfig;
|
const defaultConfig = validationConfig as ValidationConfig;
|
||||||
|
|
||||||
let cachedSignupSchema: any = null;
|
async function loadConfigFromBackend(accountType?: 'b2b' | 'b2c'): Promise<any> {
|
||||||
let schemaPromise: Promise<any> | null = null;
|
|
||||||
|
|
||||||
async function loadConfigFromBackend(): Promise<any> {
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/validation-config');
|
const response = await fetch('/api/validation-config');
|
||||||
if (!response.ok) throw new Error('Failed to load config');
|
if (!response.ok) throw new Error('Failed to load config');
|
||||||
return await response.json();
|
return await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Using default validation config:', error);
|
//сейчас выполняем это пока бека нету
|
||||||
|
/* console.error('Using default validation config:', error); */
|
||||||
return defaultConfig;
|
return defaultConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSignupFormSchema() {
|
export async function getSignupFormSchema(schemaType?: 'b2b' | 'b2c' | 'resetPassword') {
|
||||||
console.log('get scheme');
|
let schemaPromise: Promise<any> | null = null;
|
||||||
if (cachedSignupSchema) {
|
let cachedSignupSchema: any = null;
|
||||||
return cachedSignupSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!schemaPromise) {
|
if (!schemaPromise) {
|
||||||
schemaPromise = (async () => {
|
schemaPromise = (async () => {
|
||||||
const config = await loadConfigFromBackend();
|
const config = await loadConfigFromBackend();
|
||||||
cachedSignupSchema = createValidationSchema(config);
|
if (schemaType === 'resetPassword') {
|
||||||
|
cachedSignupSchema = createValidationSchemaForPassword(config);
|
||||||
|
} else {
|
||||||
|
cachedSignupSchema = createValidationSchema(config, schemaType);
|
||||||
|
}
|
||||||
|
|
||||||
return cachedSignupSchema;
|
return cachedSignupSchema;
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@@ -66,6 +67,7 @@ export async function getSignupFormSchema() {
|
|||||||
return await schemaPromise;
|
return await schemaPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// для валидации в реально времени на клиентской странице
|
||||||
export const SignupFormSchema = createValidationSchema(defaultConfig);
|
export const SignupFormSchema = createValidationSchema(defaultConfig);
|
||||||
|
|
||||||
|
|
||||||
@@ -81,6 +83,30 @@ export const loginFormSchema = z
|
|||||||
.trim(),
|
.trim(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const createComplaintSchema = z
|
||||||
|
.object({
|
||||||
|
textArea: z
|
||||||
|
.string()
|
||||||
|
.min(1, { error: 'text-area-error-fill-complaint-text' })
|
||||||
|
.max(2000, {error: 'text-area-error-complaint-text-too-long'})
|
||||||
|
.trim(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const createClaimSchema = z
|
||||||
|
.object({
|
||||||
|
textArea: z
|
||||||
|
.string()
|
||||||
|
.min(1, { error: 'fill-text-of-the-case' })
|
||||||
|
.max(2000, {error: 'claim-text-must-not-exceed-2000-characters'})
|
||||||
|
.trim(),
|
||||||
|
caseTitle: z
|
||||||
|
.string()
|
||||||
|
.min(1, { error: 'fill-title-of-the-case' })
|
||||||
|
.max(300, {error: 'claim-title-must-not-exceed-300-characters'})
|
||||||
|
.trim(),
|
||||||
|
})
|
||||||
|
|
||||||
export const localDevelopmentUrl = process.env.DEV_URL ? process.env.DEV_URL : 'http://localhost';
|
export const localDevelopmentUrl = process.env.DEV_URL ? process.env.DEV_URL : 'http://localhost';
|
||||||
|
|
||||||
export const API_BASE_URL = process.env.NODE_ENV === 'development'
|
export const API_BASE_URL = process.env.NODE_ENV === 'development'
|
||||||
@@ -92,3 +118,11 @@ export const testUserData = {
|
|||||||
email: 'test@mail.com',
|
email: 'test@mail.com',
|
||||||
subscriptionType: 'base'
|
subscriptionType: 'base'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const bankCardFormSchema = z
|
||||||
|
.object({
|
||||||
|
bankCard: z
|
||||||
|
.string()
|
||||||
|
.min(19, { error: 'error-bank-card' })
|
||||||
|
.trim()
|
||||||
|
})
|
||||||
+136
-12
@@ -4,7 +4,23 @@ import { getSessionData } from '@/app/actions/session';
|
|||||||
import { API_BASE_URL } from '@/app/actions/definitions';
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
|
||||||
|
|
||||||
export async function getUserFilesData(page: number, pageSize: number) {
|
export async function getUserFilesData({
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
sortBy,
|
||||||
|
sortOrder,
|
||||||
|
type,
|
||||||
|
date,
|
||||||
|
query = ''
|
||||||
|
}: {
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
sortBy?: string;
|
||||||
|
sortOrder?: 'asc' | 'desc' | string;
|
||||||
|
type?: string;
|
||||||
|
date?: string;
|
||||||
|
query?: string;
|
||||||
|
}) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -14,35 +30,41 @@ export async function getUserFilesData(page: number, pageSize: number) {
|
|||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 20005,
|
msg_id: 20005,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'user_files',
|
action: 'search_files',
|
||||||
page: page,
|
page: page,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
token: token
|
query: query,
|
||||||
|
sort_by: sortBy || '', // protectStatus, fileName, fileSize, createdAt, updatedAt, supportId
|
||||||
|
type: type || '', // image, video, document, audio
|
||||||
|
date: date || '', // today, week, month
|
||||||
|
sort_order: sortOrder
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
|
|
||||||
if (parsed.message_code === 0) {
|
if (parsed.message_code === 0) {
|
||||||
return parsed.message_body;
|
return parsed.message_body;
|
||||||
} else {
|
} else {
|
||||||
throw parsed;
|
throw parsed.message_text ? parsed.message_text : 'error';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw (`${response.status}`);
|
throw `${response.status}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeUserFile(fileId: string) {
|
export async function removeUserFile(fileId: string, fullDelete: number) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -54,12 +76,13 @@ export async function removeUserFile(fileId: string) {
|
|||||||
message_body: {
|
message_body: {
|
||||||
action: 'delete_file',
|
action: 'delete_file',
|
||||||
file_id: fileId,
|
file_id: fileId,
|
||||||
token: token
|
full_delete: fullDelete,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,3 +102,104 @@ export async function removeUserFile(fileId: string) {
|
|||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function viewFileInfo(fileId: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20005,
|
||||||
|
message_body: {
|
||||||
|
action: 'file_info',
|
||||||
|
file_id: fileId
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadFile(fileId: string, fileName: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/files/download/${fileId}`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw 'failed-to-download-file';
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayBuffer = await response.arrayBuffer()
|
||||||
|
const base64 = Buffer.from(arrayBuffer).toString('base64')
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: base64,
|
||||||
|
contentType: response.headers.get('content-type') || 'application/octet-stream',
|
||||||
|
fileName: fileName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function FileAnAppeal(fileId: string, appealReason: string, additionalInfo: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20005,
|
||||||
|
message_body: {
|
||||||
|
action: 'submit_appeal',
|
||||||
|
file_id: fileId,
|
||||||
|
appeal_reason: appealReason,
|
||||||
|
additional_info: additionalInfo
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed;
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
}
|
||||||
+119
-22
@@ -9,17 +9,21 @@ interface initMessageBody {
|
|||||||
extension: string,
|
extension: string,
|
||||||
file_size: number | string,
|
file_size: number | string,
|
||||||
action?: string,
|
action?: string,
|
||||||
token?: string
|
token?: string,
|
||||||
|
convertTo?: string | undefined | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fileUpload(messageBody: initMessageBody) {
|
export async function fileUpload(messageBody: initMessageBody, convertTo?: string | null | undefined) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const message = messageBody;
|
const message = messageBody;
|
||||||
message.action = "init";
|
message.action = 'init';
|
||||||
message.token = token;
|
|
||||||
|
if (convertTo && convertTo !== 'reject') {
|
||||||
|
message.convertTo = convertTo;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
@@ -30,8 +34,9 @@ export async function fileUpload(messageBody: initMessageBody) {
|
|||||||
message_body: message
|
message_body: message
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -61,6 +66,8 @@ export async function fileUpload(messageBody: initMessageBody) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function cancelUpload(udloadId: string) {
|
export async function cancelUpload(udloadId: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -68,13 +75,14 @@ export async function cancelUpload(udloadId: string) {
|
|||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 20004,
|
msg_id: 20004,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: "cancel",
|
action: 'cancel',
|
||||||
upload_id: udloadId,
|
upload_id: udloadId,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -101,31 +109,72 @@ export async function cancelUpload(udloadId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function chunkUpload(formData: FormData) {
|
export type ChunkResponse = {
|
||||||
|
errorMesage: string | null
|
||||||
|
fileId?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function chunkUpload(formData: FormData, itPrivate?: boolean): Promise<ChunkResponse> {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/files/chunk`, {
|
const url = itPrivate
|
||||||
|
? `${API_BASE_URL}/api/v1/private/files/chunk`
|
||||||
|
: `${API_BASE_URL}/api/v1/files/chunk`;
|
||||||
|
|
||||||
|
|
||||||
|
/* if (token && itPrivate) {
|
||||||
|
formData.append('token', token);
|
||||||
|
} */
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
/* ...(token && itPrivate && { 'Authorization': `Bearer ${token}` }) возможно нужно будет условно отправлять токен*/
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
|
|
||||||
if (parsed.message_desc === 'Upload cancelled successfully') {
|
if (parsed.message_code === 0) {
|
||||||
return parsed.message_body;
|
return {
|
||||||
|
errorMesage: null,
|
||||||
|
fileId: parsed.message_body.file_id
|
||||||
|
}
|
||||||
|
} else if (parsed.message_desc === 'Duplicate file upload') {
|
||||||
|
throw 'error-duplicate-file'
|
||||||
|
} else if (parsed.message_desc === 'Failed to upload chunk: Failed to upload chunk: User not have tokens for protect') {
|
||||||
|
throw 'user-not-have-tokens-for-protect'
|
||||||
|
} else if (parsed.message_desc === 'Failed to upload chunk: Failed to upload chunk: Failed to upload chunk: .wav file not have RIFF header') {
|
||||||
|
throw 'error-upload-chunk-wav-file-not-have-riff'
|
||||||
} else {
|
} else {
|
||||||
throw parsed;
|
throw 'error-uploading-file'
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw (`${response.status}`);
|
throw 'error-uploading-file'
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error) {
|
||||||
|
const typedError = error as string;
|
||||||
|
|
||||||
|
return {
|
||||||
|
errorMesage: typedError
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
errorMesage: 'error-uploading-file'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
return error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkChunkStatus(upload_id: string) {
|
export async function checkChunkStatus(upload_id: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -138,8 +187,9 @@ export async function checkChunkStatus(upload_id: string) {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -161,6 +211,8 @@ export async function checkChunkStatus(upload_id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllowedFilesExtensions(type: string) {
|
export async function getAllowedFilesExtensions(type: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -173,8 +225,9 @@ export async function getAllowedFilesExtensions(type: string) {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -194,3 +247,47 @@ export async function getAllowedFilesExtensions(type: string) {
|
|||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function checkOperationPrice(action: string, filesCount: number, fileType: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30008,
|
||||||
|
message_body: {
|
||||||
|
action: action,
|
||||||
|
file_type: fileType,
|
||||||
|
count_files: filesCount
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
cost: 0,
|
||||||
|
success: false,
|
||||||
|
tokens_count: 0,
|
||||||
|
error: 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
|
||||||
|
export async function setMonitoring(fileId: string, monitoringType: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30007,
|
||||||
|
message_body: {
|
||||||
|
monitoring_type: monitoringType,
|
||||||
|
file_id: fileId
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
errorMessage: null
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
errorMessage: 'error'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MonitoringStats {
|
||||||
|
total_searches: number;
|
||||||
|
searches_by_status: {
|
||||||
|
processing: number;
|
||||||
|
completed: number;
|
||||||
|
failed: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
recent_searches: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchMonitoringStats(): Promise<MonitoringStats | null> {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30011
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
if (parsed?.message_body?.searches_by_status) {
|
||||||
|
return parsed.message_body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchLastMonitoringCheck() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30011,
|
||||||
|
message_body: {
|
||||||
|
limit: 1
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_body?.searches_by_status) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchMonitoringInfo() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30027
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_body) {
|
||||||
|
return parsed.message_body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
|
||||||
|
export interface NotificationBody {
|
||||||
|
createdAt: string | null,
|
||||||
|
id: number,
|
||||||
|
message: string,
|
||||||
|
status: string | null,
|
||||||
|
type: string,
|
||||||
|
context: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchActiveNotifications() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30015,
|
||||||
|
message_body: {
|
||||||
|
action: 'active'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_body) {
|
||||||
|
return parsed?.message_body
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchAllNotifications(page: number = 0, size: number = 10) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30015,
|
||||||
|
message_body: {
|
||||||
|
action: 'list',
|
||||||
|
page: page,
|
||||||
|
size: size,
|
||||||
|
sortBy: "createdAt",
|
||||||
|
sortDirection: "DESC", //DESC, ASC
|
||||||
|
types: [], //"SEARCH_RESULT", "MONITORING_RESULT"
|
||||||
|
statuses: ["NEW", "READIED"] //"NEW"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return parsed?.message_body
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function notificationsMarkAsRead(ids: number[]) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30015,
|
||||||
|
message_body: {
|
||||||
|
action: 'markRead',
|
||||||
|
notificationIds: ids
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return parsed?.message_body
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function notificationsDelete(ids: number[]) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30015,
|
||||||
|
message_body: {
|
||||||
|
action: 'delete',
|
||||||
|
notificationIds: ids
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
console.log('parsed');
|
||||||
|
console.log(parsed);
|
||||||
|
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return parsed?.message_body
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
|
||||||
|
export async function getPaymentsMethods() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/payments/methods`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deletePaymentsMethods(cardId: string): Promise<boolean | undefined> {
|
||||||
|
if (!cardId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/payments/methods/${cardId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
if (parsed.message === 'Payment method deleted') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL, bankCardFormSchema } from '@/app/actions/definitions';
|
||||||
|
|
||||||
|
export async function fetchReferralsLevels() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30003,
|
||||||
|
message_body: {
|
||||||
|
action: 'levels',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return {
|
||||||
|
referrals: parsed.message_body,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
referrals: [],
|
||||||
|
error: error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchReferralUserStats() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30003,
|
||||||
|
message_body: {
|
||||||
|
action: 'userStats',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
error: error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchReferralInvitees() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30003,
|
||||||
|
message_body: {
|
||||||
|
action: 'invitees',
|
||||||
|
pageSize: 100,
|
||||||
|
pageNumber: 0
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
error: error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function referralRefill() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30003,
|
||||||
|
message_body: {
|
||||||
|
action: 'refill',
|
||||||
|
amount: 2000,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
error: error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PayoutMethods {
|
||||||
|
cardNumber: string,
|
||||||
|
id: number,
|
||||||
|
type: string,
|
||||||
|
displayName: string,
|
||||||
|
maskedDetails: string,
|
||||||
|
default: boolean,
|
||||||
|
verified: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPayoutMethods(): Promise<{
|
||||||
|
methods: PayoutMethods[],
|
||||||
|
error: string | null
|
||||||
|
}> {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/payouts/user/payout-methods`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
methods: parsed,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw 'error';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
methods: [],
|
||||||
|
error: 'error'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserPayments() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/payouts/requests`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
payments: parsed,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw 'error';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
payments: [],
|
||||||
|
error: 'error'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayoutMethodFormState = {
|
||||||
|
previousState: {
|
||||||
|
bankCard?: string | undefined;
|
||||||
|
paymentsType?: 'bank-card' | 'bank-transfer' | 'on-account-balance';
|
||||||
|
} | undefined,
|
||||||
|
success?: boolean,
|
||||||
|
error: Record<string, string | null> | null
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function addPayoutMethod(
|
||||||
|
state: PayoutMethodFormState | undefined,
|
||||||
|
formData: FormData,
|
||||||
|
): Promise<PayoutMethodFormState> {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
console.log('addPayoutMethod');
|
||||||
|
|
||||||
|
const bankCard = formData.get('bankCard') as string || '';
|
||||||
|
const paymentsType = formData.get('paymentsType') as 'bank-card' | 'bank-transfer' | 'on-account-balance';
|
||||||
|
|
||||||
|
const validatedFields = bankCardFormSchema.safeParse({
|
||||||
|
bankCard
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (!validatedFields.success) {
|
||||||
|
const errors: Record<string, string> = {}
|
||||||
|
JSON.parse(validatedFields.error.message).forEach((obj: {
|
||||||
|
message: string,
|
||||||
|
path: string
|
||||||
|
}) => {
|
||||||
|
if (errors[obj.path[0]]) {
|
||||||
|
errors[obj.path[0]] = `${errors[obj.path[0]]} ${obj.message}`;
|
||||||
|
} else {
|
||||||
|
errors[obj.path[0]] = obj.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
bankCard
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
error: errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { bankCard } = validatedFields.data;
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/payouts/payout-method/card`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
"cardNumber": bankCard.replaceAll(' ', '')
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(response);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
console.log(parsed);
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
bankCard,
|
||||||
|
paymentsType
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw 'error-save';
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errors: Record<string, string> = {};
|
||||||
|
errors['server'] = 'error-save'
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
const typedError = error as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
bankCard,
|
||||||
|
paymentsType
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
error: errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayoutRequest = {
|
||||||
|
previousState: {
|
||||||
|
amount?: string | undefined;
|
||||||
|
} | undefined,
|
||||||
|
error: string | null
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createPayoutRequest(
|
||||||
|
state: PayoutRequest | undefined,
|
||||||
|
formData: FormData,
|
||||||
|
): Promise<PayoutRequest> {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
const amount = formData.get('amount') as string || '';
|
||||||
|
const payoutMethodId = formData.get('payoutMethodId') as string || '';
|
||||||
|
|
||||||
|
console.log('createPayoutRequest');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/payouts/create-request`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
'amount': amount,
|
||||||
|
'payoutMethodId': payoutMethodId
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 400) {
|
||||||
|
const responseText = await response.text();
|
||||||
|
throw JSON.parse(responseText)?.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 409) {
|
||||||
|
const responseText = await response.text();
|
||||||
|
if (responseText.includes('Not have money for payout')) {
|
||||||
|
throw 'insufficient-funds-to-receive-payment'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseText.includes('Not found payout method')) {
|
||||||
|
throw 'not-found-payout-method'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
console.log(parsed);
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
amount
|
||||||
|
},
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw 'payment-error';
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
let typedError: string;
|
||||||
|
|
||||||
|
switch (error) {
|
||||||
|
case 'Sum of pay is required':
|
||||||
|
typedError = 'sum-of-pay-is-required';
|
||||||
|
break;
|
||||||
|
case 'Min sum for pay 1000':
|
||||||
|
typedError = 'min-sum-for-payout-1000';
|
||||||
|
break;
|
||||||
|
case 'insufficient-funds-to-receive-payment':
|
||||||
|
typedError = 'insufficient-funds-to-receive-payment';
|
||||||
|
break;
|
||||||
|
case 'not-found-payout-method':
|
||||||
|
typedError = 'not-found-payout-method';
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
typedError = 'payment-error';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
amount
|
||||||
|
},
|
||||||
|
error: typedError
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: {
|
||||||
|
amount
|
||||||
|
},
|
||||||
|
error: 'payment-error'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
|
||||||
|
export async function searchUserFiles(fileId: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/files/${fileId}/similar?similarityLevels=DUPLICATE,SIMILARITY`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
throw parsed.message_desc;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
if (error === 'File for search unsupported') {
|
||||||
|
return {
|
||||||
|
errorMessage: 'file-for-search-unsupported'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
errorMessage: 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchGlobalFiles(fileId: string, currentPage: number) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20007,
|
||||||
|
message_body: {
|
||||||
|
file_id: fileId,
|
||||||
|
page: currentPage,
|
||||||
|
check_similar_first: true,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw 'error';
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
let errorMessage: string = 'error';
|
||||||
|
if (error) {
|
||||||
|
const typedError = error as any;
|
||||||
|
switch (typedError.message_desc) {
|
||||||
|
case 'No results found':
|
||||||
|
errorMessage = 'no-similar-files-title'
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorMessage = 'error'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
images: [],
|
||||||
|
page: 0,
|
||||||
|
pageSize: 0,
|
||||||
|
totalPages: 0,
|
||||||
|
totalResults: 0,
|
||||||
|
error: errorMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSearchStats() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/check/file_stats`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ export async function createSession(token: string, email: string) {
|
|||||||
});
|
});
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
|
|
||||||
cookieStore.set('session', session, {
|
cookieStore.set('session_nc_db', session, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
expires: expiresAt,
|
expires: expiresAt,
|
||||||
@@ -48,7 +48,7 @@ export async function createSession(token: string, email: string) {
|
|||||||
|
|
||||||
export async function updateSession(newToken: string) {
|
export async function updateSession(newToken: string) {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const session = cookieStore.get('session')?.value;
|
const session = cookieStore.get('session_nc_db')?.value;
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return null;
|
return null;
|
||||||
@@ -68,7 +68,7 @@ export async function updateSession(newToken: string) {
|
|||||||
|
|
||||||
const updatedSession = await encrypt(updatedPayload);
|
const updatedSession = await encrypt(updatedPayload);
|
||||||
|
|
||||||
cookieStore.set('session', updatedSession, {
|
cookieStore.set('session_nc_db', updatedSession, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
expires: updatedPayload.expiresAt,
|
expires: updatedPayload.expiresAt,
|
||||||
@@ -79,7 +79,7 @@ export async function updateSession(newToken: string) {
|
|||||||
|
|
||||||
export async function getSessionData(type: 'token' | 'email'): Promise<string | null> {
|
export async function getSessionData(type: 'token' | 'email'): Promise<string | null> {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const sessionCookie = cookieStore.get('session')?.value;
|
const sessionCookie = cookieStore.get('session_nc_db')?.value;
|
||||||
|
|
||||||
if (!sessionCookie) {
|
if (!sessionCookie) {
|
||||||
return null;
|
return null;
|
||||||
@@ -97,7 +97,12 @@ export async function getSessionData(type: 'token' | 'email'): Promise<string |
|
|||||||
|
|
||||||
export async function deleteSession() {
|
export async function deleteSession() {
|
||||||
console.log('delete session')
|
console.log('delete session')
|
||||||
const cookieStore = await cookies()
|
try {
|
||||||
cookieStore.delete('session');
|
const cookieStore = await cookies()
|
||||||
redirect('/login');
|
cookieStore.delete('session_nc_db');
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
redirect('/login');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
import { headers } from 'next/headers';
|
||||||
|
|
||||||
|
export async function requestFileWithTracking(fileId: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
const headersList = await headers();
|
||||||
|
const forwardedFor = headersList.get('x-forwarded-for');
|
||||||
|
const realIp = headersList.get('x-real-ip');
|
||||||
|
const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';
|
||||||
|
const userAgent = headersList.get('user-agent') || 'unknown';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/file/link`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_id: fileId,
|
||||||
|
ip: ip,
|
||||||
|
user_agent: userAgent
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
|
||||||
|
if (contentType?.includes('application/json')) {
|
||||||
|
const data = await response.json();
|
||||||
|
return data.message_body;
|
||||||
|
} else {
|
||||||
|
const blob = await response.blob();
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requestFileAsBuffer(fileId: string): Promise<ArrayBuffer | null> {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
const headersList = await headers();
|
||||||
|
const forwardedFor = headersList.get('x-forwarded-for');
|
||||||
|
const realIp = headersList.get('x-real-ip');
|
||||||
|
const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';
|
||||||
|
const userAgent = headersList.get('user-agent') || 'unknown';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/file/link`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_id: fileId,
|
||||||
|
ip: ip,
|
||||||
|
user_agent: userAgent
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (contentType?.includes('application/json')) {
|
||||||
|
const data = await response.json();
|
||||||
|
const fileUrl = data.message_body;
|
||||||
|
const fileResponse = await fetch(fileUrl);
|
||||||
|
if (!fileResponse.ok) {
|
||||||
|
throw new Error(`Failed to fetch file: ${fileResponse.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await fileResponse.arrayBuffer();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await response.arrayBuffer();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchDocumentInfo(fileId: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
console.log('fetchDocumentInfo');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/files/public/file-info/${fileId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
return parsed;
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function filePermisionChange(fileId: string, permissions: boolean) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20005,
|
||||||
|
message_body: {
|
||||||
|
file_id: fileId,
|
||||||
|
action: 'change_permission',
|
||||||
|
permissions: {
|
||||||
|
DOWNLOAD: permissions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchHistoryView(
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
locale: string,
|
||||||
|
sortDirection?: 'asc' | 'desc',
|
||||||
|
sortBy?: string
|
||||||
|
) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30028,
|
||||||
|
message_body: {
|
||||||
|
action: 'history_view',
|
||||||
|
page: page,
|
||||||
|
size: size,
|
||||||
|
locale: locale,
|
||||||
|
sort_direction: sortDirection,
|
||||||
|
sort_by: sortBy
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchTrackingStats() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30028,
|
||||||
|
message_body: {
|
||||||
|
action: 'view_stats'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadTrackingFile(fileId: string, fileName?: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/files/public-download/${fileId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw 'failed-to-download-file';
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayBuffer = await response.arrayBuffer()
|
||||||
|
const base64 = Buffer.from(arrayBuffer).toString('base64')
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: base64,
|
||||||
|
contentType: response.headers.get('content-type') || 'application/octet-stream',
|
||||||
|
fileName: fileName ? fileName : 'fileName'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,719 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL, createComplaintSchema, createClaimSchema } from '@/app/actions/definitions';
|
||||||
|
|
||||||
|
export async function getViolationSearchStatus() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/global-search/actual-task`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const text = await response.text();
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(text);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: text === 'Task not found' ? 'task-not-found' : text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startGlobalMonitoring(monitoringType: 'monitoring' | 'all_files') {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/global-search/start`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
searchType: monitoringType,
|
||||||
|
...(monitoringType === 'all_files' && { fileIds: [] })
|
||||||
|
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed: {
|
||||||
|
taskId: string,
|
||||||
|
status: string,
|
||||||
|
totalFiles: number
|
||||||
|
} = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.status === 'ACCEPTED') {
|
||||||
|
return parsed
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getViolationFilesArray(props: {
|
||||||
|
page?: number,
|
||||||
|
size?: number,
|
||||||
|
start_date?: string,
|
||||||
|
end_date?: string,
|
||||||
|
file_name?: string
|
||||||
|
}) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
const { page, size, start_date, end_date, file_name } = props;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body: Record<string, any> = {};
|
||||||
|
|
||||||
|
if (page !== undefined) body.page = page;
|
||||||
|
if (size !== undefined) body.size = size;
|
||||||
|
if (start_date !== undefined) body.start_date = start_date;
|
||||||
|
if (end_date !== undefined) body.end_date = end_date;
|
||||||
|
if (file_name !== undefined) body.file_name = file_name;
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/global-search/violation-summary-with-files`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.content.length) {
|
||||||
|
return parsed;
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFileMatches(fileId: string, page: number, status?: string, violationId?: string, startDate?: string, endDate?: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30009,
|
||||||
|
message_body: {
|
||||||
|
file_id: fileId,
|
||||||
|
page: (page > 0 ? page - 1 : 0),
|
||||||
|
size: 5,
|
||||||
|
sort_direction: 'desc',
|
||||||
|
status: status,
|
||||||
|
...(violationId && { action: 'getByViolationId' }),
|
||||||
|
...(violationId && { violation_id: violationId }),
|
||||||
|
...((startDate && endDate) && { start_date: startDate, end_date: endDate })
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_body) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
throw null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchViolationAnalyticStatistic(group: 'domain' | 'tld') {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30009,
|
||||||
|
message_body: {
|
||||||
|
group_by: group,
|
||||||
|
action: "group",
|
||||||
|
sort_direction: 'desc'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_body) {
|
||||||
|
return parsed?.message_body;
|
||||||
|
} else {
|
||||||
|
throw null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchViolationStatistic(id?: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30010,
|
||||||
|
message_body: {
|
||||||
|
...(id && { file_id: id })
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_body) {
|
||||||
|
return parsed?.message_body;
|
||||||
|
} else {
|
||||||
|
throw null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchViolationFinalSummaryStatistic() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30023
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return parsed?.message_body;
|
||||||
|
} else {
|
||||||
|
throw null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface complainBody {
|
||||||
|
previousText: string,
|
||||||
|
violationID: string | null,
|
||||||
|
success: boolean,
|
||||||
|
errorMessage?: Record<string, string> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createComplaint(
|
||||||
|
state: complainBody | undefined,
|
||||||
|
formData: FormData
|
||||||
|
): Promise<complainBody> {
|
||||||
|
const email = await getSessionData('email');
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
const violationId = formData.get('violationId') as string || '';
|
||||||
|
const textArea = formData.get('note') as string || '';
|
||||||
|
|
||||||
|
const validatedFields = createComplaintSchema.safeParse({
|
||||||
|
textArea
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!validatedFields.success) {
|
||||||
|
const errors: Record<string, string> = {}
|
||||||
|
JSON.parse(validatedFields.error.message).forEach((obj: {
|
||||||
|
message: string,
|
||||||
|
path: string
|
||||||
|
}) => {
|
||||||
|
if (errors[obj.path[0]]) {
|
||||||
|
errors[obj.path[0]] = `${errors[obj.path[0]]} ${obj.message}`;
|
||||||
|
} else {
|
||||||
|
errors[obj.path[0]] = obj.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
violationID: violationId,
|
||||||
|
previousText: textArea,
|
||||||
|
success: false,
|
||||||
|
errorMessage: errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30013,
|
||||||
|
message_body: {
|
||||||
|
action: 'create',
|
||||||
|
violation_id: violationId,
|
||||||
|
text: textArea,
|
||||||
|
email: email
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return {
|
||||||
|
violationID: violationId,
|
||||||
|
previousText: textArea,
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errors: Record<string, string> = {}
|
||||||
|
|
||||||
|
if (error && typeof error === 'object' && 'message_code' in error) {
|
||||||
|
const err = error as { message_code: number };
|
||||||
|
if (err.message_code === 2) {
|
||||||
|
errors['server'] = 'the-complaint-has-not-been-registered'
|
||||||
|
|
||||||
|
return {
|
||||||
|
violationID: violationId,
|
||||||
|
previousText: textArea,
|
||||||
|
success: false,
|
||||||
|
errorMessage: errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errors['server'] = 'error-save';
|
||||||
|
|
||||||
|
return {
|
||||||
|
violationID: violationId,
|
||||||
|
previousText: textArea,
|
||||||
|
success: false,
|
||||||
|
errorMessage: errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchComplainInfo(id: number) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30013,
|
||||||
|
message_body: {
|
||||||
|
action: 'get_by_violation',
|
||||||
|
violation_id: id
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return {
|
||||||
|
body: parsed?.message_body
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
errorMessage: 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MatchStatus = 'NEW' | 'SHOWED' | 'LEGAL_IN_WORK' | 'COMPLAINT_IN_WORK' | 'COMPLAINT_AND_LEGAL_IN_WORK' | 'AUTHORIZED_USE' | 'NOT_OWNER_FILE';
|
||||||
|
|
||||||
|
export async function updateMatchStatus(id: number, status: MatchStatus) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30009,
|
||||||
|
message_body: {
|
||||||
|
action: 'updateStatus',
|
||||||
|
violation_id: id,
|
||||||
|
status: status
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_body.status) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchClaimInfo(id: number) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30017,
|
||||||
|
message_body: {
|
||||||
|
action: 'get_all',
|
||||||
|
pageSize: 1,
|
||||||
|
pageNumber: 0,
|
||||||
|
sortBy: "createdAt",
|
||||||
|
sortDir: "desc",
|
||||||
|
violation_id: id
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return {
|
||||||
|
body: parsed?.message_body
|
||||||
|
}
|
||||||
|
} else if (parsed?.message_code !== 0 && parsed?.message_desc === 'Law case not found') {
|
||||||
|
return {
|
||||||
|
body: null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
errorMessage: 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface caseBody {
|
||||||
|
previousText: string,
|
||||||
|
previousCaseTitle: string,
|
||||||
|
previousAmount: string,
|
||||||
|
violationID: string | null,
|
||||||
|
success: boolean,
|
||||||
|
errorMessage?: Record<string, string> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createClaim(
|
||||||
|
state: caseBody | undefined,
|
||||||
|
formData: FormData
|
||||||
|
): Promise<caseBody> {
|
||||||
|
const email = await getSessionData('email');
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
const violationId = formData.get('violationId') as string || '';
|
||||||
|
const textArea = formData.get('note') as string || '';
|
||||||
|
const caseTitle = formData.get('caseTitle') as string || '';
|
||||||
|
const amount = formData.get('amount') as string || '';
|
||||||
|
|
||||||
|
const validatedFields = createClaimSchema.safeParse({
|
||||||
|
textArea,
|
||||||
|
caseTitle
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!validatedFields.success) {
|
||||||
|
const errors: Record<string, string> = {}
|
||||||
|
JSON.parse(validatedFields.error.message).forEach((obj: {
|
||||||
|
message: string,
|
||||||
|
path: string
|
||||||
|
}) => {
|
||||||
|
if (errors[obj.path[0]]) {
|
||||||
|
errors[obj.path[0]] = `${errors[obj.path[0]]} ${obj.message}`;
|
||||||
|
} else {
|
||||||
|
errors[obj.path[0]] = obj.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
violationID: violationId,
|
||||||
|
previousText: textArea,
|
||||||
|
previousCaseTitle: caseTitle,
|
||||||
|
previousAmount: amount,
|
||||||
|
success: false,
|
||||||
|
errorMessage: errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30017,
|
||||||
|
message_body: {
|
||||||
|
action: 'create',
|
||||||
|
violation_id: violationId,
|
||||||
|
description: textArea,
|
||||||
|
email: email,
|
||||||
|
name: caseTitle,
|
||||||
|
priority: 'HIGH',
|
||||||
|
amount: Number(amount)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return {
|
||||||
|
violationID: violationId,
|
||||||
|
previousCaseTitle: caseTitle,
|
||||||
|
previousText: textArea,
|
||||||
|
previousAmount: amount,
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errors: Record<string, string> = {}
|
||||||
|
|
||||||
|
if (error && typeof error === 'object' && 'message_code' in error) {
|
||||||
|
const err = error as { message_code: number };
|
||||||
|
if (err.message_code === 2) {
|
||||||
|
errors['server'] = 'the-complaint-has-not-been-registered'
|
||||||
|
|
||||||
|
return {
|
||||||
|
violationID: violationId,
|
||||||
|
previousText: textArea,
|
||||||
|
previousCaseTitle: caseTitle,
|
||||||
|
previousAmount: amount,
|
||||||
|
success: false,
|
||||||
|
errorMessage: errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errors['server'] = 'error-save';
|
||||||
|
|
||||||
|
return {
|
||||||
|
violationID: violationId,
|
||||||
|
previousText: textArea,
|
||||||
|
previousCaseTitle: caseTitle,
|
||||||
|
previousAmount: amount,
|
||||||
|
success: false,
|
||||||
|
errorMessage: errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComplaintsCaseQueryParams {
|
||||||
|
page?: number,
|
||||||
|
size?: number,
|
||||||
|
sort_by?: string,
|
||||||
|
sort_direction?: 'desc' | 'asc'
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchLastClaims(params: ComplaintsCaseQueryParams) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
const { page, size, sort_by, sort_direction } = params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30017,
|
||||||
|
message_body: {
|
||||||
|
action: "get_all",
|
||||||
|
pageNumber: page,
|
||||||
|
size,
|
||||||
|
sort_by,
|
||||||
|
sort_direction
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
throw null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchLastComplaint(params: ComplaintsCaseQueryParams) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
const { page, size, sort_by, sort_direction } = params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30013,
|
||||||
|
message_body: {
|
||||||
|
action: "get_all",
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
sort_by,
|
||||||
|
sort_direction
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
throw null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,8 +15,8 @@ export async function vkAuthorization(data : any, codeVerifier: string) {
|
|||||||
const response = await fetch(`${API_BASE_URL}/api/v1/vk/authorization/${code}/${state}/${codeVerifier}/${device_id}`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/vk/authorization/${code}/${state}/${codeVerifier}/${device_id}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { API_BASE_URL } from '@/app/actions/definitions';
|
|||||||
import { createSession } from '@/app/actions/session';
|
import { createSession } from '@/app/actions/session';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
|
|
||||||
export async function YandexAuthorization(data: any, codeVerifier: string) {
|
export async function YandexAuthorization(data: any, codeVerifier: string) {
|
||||||
|
|
||||||
console.log(data);
|
console.log(data);
|
||||||
@@ -15,8 +14,8 @@ export async function YandexAuthorization(data: any, codeVerifier: string) {
|
|||||||
const response = await fetch(`${API_BASE_URL}/api/v1/yandex/authorization/${code}/${state}/${codeVerifier}/${device_id}`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/yandex/authorization/${code}/${state}/${codeVerifier}/${device_id}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Accept": "application/json"
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { YooCheckout, ICreatePayment } from '@a2seven/yoo-checkout';
|
||||||
|
import { getSessionData, deleteSession } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
|
||||||
|
const checkout = new YooCheckout({ shopId: '1276731', secretKey: 'test_0Yns_0NHV5GJf6ypJ5HC4NSfnLO8SJkw-1PwrVWsDl4' });
|
||||||
|
|
||||||
|
export async function makePaymentOperation(email: string, tariffId: number, operationUuid: string, operationType: 'TARIFF' | 'TOKEN') {
|
||||||
|
const formData = new FormData();
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
formData.append('email', email);
|
||||||
|
formData.append('tariffId', tariffId.toString());
|
||||||
|
formData.append('operationType', operationType);
|
||||||
|
formData.append('operationUuid', operationUuid);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/payments/create`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
if (parsed.paymentUuid) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
throw 'error'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw 'error'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function yooKasaCreatePayment(amount: number, tariff: number, operationType: 'TARIFF' | 'TOKEN') {
|
||||||
|
const userEmail = await getSessionData('email');
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const random = Math.random().toString(36).substring(2, 10);
|
||||||
|
const idempotenceKey = `${userEmail}-${timestamp}-${amount}-${tariff}-${random}`;
|
||||||
|
|
||||||
|
if (!userEmail) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createPayload: ICreatePayment = {
|
||||||
|
amount: {
|
||||||
|
value: amount.toLocaleString().replace(/[,\s.]/g, ''),
|
||||||
|
currency: 'RUB'
|
||||||
|
},
|
||||||
|
confirmation: {
|
||||||
|
type: 'embedded',
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
userMail: userEmail,
|
||||||
|
tariff: tariff
|
||||||
|
},
|
||||||
|
merchant_customer_id: userEmail,
|
||||||
|
capture: true,
|
||||||
|
save_payment_method: operationType === 'TOKEN' ? false : true,
|
||||||
|
/* ...(operationType === 'TOKEN' && { save_payment_method: false }) */
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payment = await checkout.createPayment(createPayload, idempotenceKey);
|
||||||
|
const response = await makePaymentOperation(userEmail, tariff, payment.id, operationType);
|
||||||
|
|
||||||
|
if (payment && response) {
|
||||||
|
return payment?.confirmation.confirmation_token;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchPaymentsHistory() {
|
||||||
|
const email = await getSessionData('email');
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30005,
|
||||||
|
message_body: {
|
||||||
|
action: 'user_payments',
|
||||||
|
email: email
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return {
|
||||||
|
payments: parsed.message_body.payments,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw parsed.message_code;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
|
||||||
import { getSessionData } from '@/app/actions/session';
|
|
||||||
import { API_BASE_URL } from '@/app/actions/definitions';
|
|
||||||
|
|
||||||
export async function GET(
|
|
||||||
request: NextRequest,
|
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const token = await getSessionData('token');
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'need authorization' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = await params;
|
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/files/download/${id}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
"Authorization": token
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`backend error: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileBlob = await response.blob();
|
|
||||||
const headers = new Headers(response.headers);
|
|
||||||
|
|
||||||
return new NextResponse(fileBlob, {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': headers.get('content-type') || 'application/octet-stream',
|
|
||||||
'Content-Disposition': headers.get('content-disposition') || `attachment; filename="file-${id}"`,
|
|
||||||
'Content-Length': headers.get('content-length') || String(fileBlob.size),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('download error:', error);
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'download file error' },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -48,6 +48,7 @@ export default function DropDownList({ children, value, callBack, translatedValu
|
|||||||
setIsOpen(!isOpen)
|
setIsOpen(!isOpen)
|
||||||
}}
|
}}
|
||||||
className="dropdown-button"
|
className="dropdown-button"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<span className="flex items-center">
|
<span className="flex items-center">
|
||||||
<span className="mr-2">
|
<span className="mr-2">
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import React, { ReactNode, useState, useRef } from 'react';
|
||||||
|
import { useClickOutside } from '@/app/hooks/useClickOutside';
|
||||||
|
import { SelectedFile } from './UploadSectionFile';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
interface DropDownItem {
|
||||||
|
file: SelectedFile;
|
||||||
|
convertTo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropDownListProps {
|
||||||
|
children: ReactNode;
|
||||||
|
value: string | null | undefined;
|
||||||
|
callBack: (value: DropDownItem) => void;
|
||||||
|
translatedValue?: string | null | undefined;
|
||||||
|
selectedFiles: SelectedFile[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LiElementProps extends React.LiHTMLAttributes<HTMLLIElement> {
|
||||||
|
'data-file'?: string;
|
||||||
|
'data-convert-to'?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DropDownListForImageConvert({
|
||||||
|
children,
|
||||||
|
value,
|
||||||
|
callBack,
|
||||||
|
translatedValue,
|
||||||
|
selectedFiles
|
||||||
|
}: DropDownListProps) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
const t = useTranslations('Global')
|
||||||
|
|
||||||
|
useClickOutside(dropdownRef,
|
||||||
|
() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
},
|
||||||
|
isOpen
|
||||||
|
);
|
||||||
|
|
||||||
|
const getDisplayValue = (): string => {
|
||||||
|
if (translatedValue) return translatedValue;
|
||||||
|
return value ? value : t('do-not-convert');
|
||||||
|
};
|
||||||
|
|
||||||
|
const findFileByName = (fileName: string): SelectedFile | undefined => {
|
||||||
|
return selectedFiles.find(file => file.name === fileName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const enhancedChildren = React.Children.map(children, (child, index) => {
|
||||||
|
if (React.isValidElement<LiElementProps>(child)) {
|
||||||
|
const fileName = child.props['data-file'];
|
||||||
|
const convertTo = child.props['data-convert-to'];
|
||||||
|
|
||||||
|
const isCurrent = value === convertTo;
|
||||||
|
|
||||||
|
return React.cloneElement(child, {
|
||||||
|
onClick: () => {
|
||||||
|
if (fileName && convertTo) {
|
||||||
|
const file = findFileByName(fileName);
|
||||||
|
if (file) {
|
||||||
|
callBack({ file, convertTo });
|
||||||
|
} else {
|
||||||
|
console.error(`Файл с именем ${fileName} не найден`);
|
||||||
|
}
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
className: `dropdown-item ${isCurrent ? "current" : ""}`,
|
||||||
|
key: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="dropdown-wrapper" ref={dropdownRef}>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
}}
|
||||||
|
className="dropdown-button"
|
||||||
|
>
|
||||||
|
<span className="flex items-center">
|
||||||
|
<span className="mr-2">
|
||||||
|
{getDisplayValue()}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
className={`w-5 h-5 ml-2 transition-transform ${isOpen ? 'rotate-180' : ''}`}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{isOpen && (
|
||||||
|
<div className="dropdown-list">
|
||||||
|
<ul
|
||||||
|
className=""
|
||||||
|
role="listbox"
|
||||||
|
>
|
||||||
|
{enhancedChildren}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { IconImageFile, IconVideoFile, IconAudioFile, IconDocument } from '@/app/ui/icons/icons';
|
||||||
|
|
||||||
|
export const FileTypeIcon = ({ type }: { type: string }) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'image':
|
||||||
|
return <span className="color-image">
|
||||||
|
<IconImageFile />
|
||||||
|
</span>;
|
||||||
|
case 'video':
|
||||||
|
return <span className="color-video">
|
||||||
|
<IconVideoFile />
|
||||||
|
</span>;
|
||||||
|
case 'audio':
|
||||||
|
return <span className="color-audio">
|
||||||
|
<IconAudioFile />
|
||||||
|
</span>;
|
||||||
|
default:
|
||||||
|
return <span className="color-document">
|
||||||
|
<IconDocument />
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl'
|
||||||
|
|
||||||
|
interface NotificationTextHandlerProps {
|
||||||
|
notificationType: string
|
||||||
|
notificationMessage: string
|
||||||
|
notificationContext: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NotificationTextHandler({
|
||||||
|
notificationType,
|
||||||
|
notificationMessage,
|
||||||
|
notificationContext
|
||||||
|
}: NotificationTextHandlerProps) {
|
||||||
|
const t = useTranslations('Notifications');
|
||||||
|
|
||||||
|
const getNotificationText = () => {
|
||||||
|
if (!notificationType) {
|
||||||
|
return t.has(notificationMessage) ? t(notificationMessage) : notificationMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (notificationType) {
|
||||||
|
case 'USER_VERIFIED':
|
||||||
|
return t('user-verified', {
|
||||||
|
status: notificationContext?.status || t('not-specified'),
|
||||||
|
updateDate: notificationContext?.update_date || t('not-specified-date')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'MONITORING_RESULT':
|
||||||
|
return t('monitoring-result', {
|
||||||
|
message: notificationContext?.message || t('message-missing')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'COMPLAINT_STATUS_CHANGED':
|
||||||
|
return t('complaint-status-changed', {
|
||||||
|
newStatus: notificationContext?.new_status || t('not-specified'),
|
||||||
|
oldStatus: notificationContext?.old_status || t('not-specified'),
|
||||||
|
violation: notificationContext?.violation || t('not-specified'),
|
||||||
|
file: notificationContext?.file || t('not-specified')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'FILE_ADDED_TO_SYSTEM':
|
||||||
|
return t('file-added-to-system', {
|
||||||
|
status: notificationContext?.status || t('not-specified'),
|
||||||
|
fileName: notificationContext?.file_name || t('not-specified-file-name')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'FILE_MODERATION_EVENT':
|
||||||
|
return t('file-moderation-event', {
|
||||||
|
fileName: notificationContext?.file_name || t('not-specified-file-name'),
|
||||||
|
oldStatus: notificationContext?.old_status || t('not-specified'),
|
||||||
|
newStatus: notificationContext?.new_status || t('not-specified'),
|
||||||
|
comment: notificationContext?.comment || t('no-comment')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'PAYMENT_RESULT':
|
||||||
|
return t('payment-result', {
|
||||||
|
status: notificationContext?.status || t('not-specified'),
|
||||||
|
amount: notificationContext?.amount || '0'
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'REFERRAL_REGISTERED':
|
||||||
|
return t('referral-registered', {
|
||||||
|
referralUser: notificationContext?.referral_user || t('unknown-user')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'SEARCH_RESULT':
|
||||||
|
return t('search-result', {
|
||||||
|
startDate: notificationContext?.start_date || t('not-specified-date'),
|
||||||
|
endDate: notificationContext?.end_date || t('not-specified-date'),
|
||||||
|
searchType: notificationContext?.search_type || t('not-specified'),
|
||||||
|
count: notificationContext?.count || '0'
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'SEARCH_OPERATION_FAILED':
|
||||||
|
return t('search-operation-failed', {
|
||||||
|
status: notificationContext?.status || t('not-specified'),
|
||||||
|
errorMessage: notificationContext?.error_message || t('error-not-described'),
|
||||||
|
message: notificationContext?.message || t('message-missing'),
|
||||||
|
reason: notificationContext?.reason || t('reason-not-specified'),
|
||||||
|
nextRun: notificationContext?.next_run || t('not-scheduled')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'TOKEN_NOT_FOUND':
|
||||||
|
return t('token-not-found', {
|
||||||
|
currentTokens: notificationContext?.current_tokens || '0',
|
||||||
|
needTokens: notificationContext?.need_tokens || t('not-specified'),
|
||||||
|
message: notificationContext?.message || t('message-missing'),
|
||||||
|
nextRun: notificationContext?.next_run || t('not-scheduled')
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
return notificationMessage || t('notification-without-text');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{getNotificationText()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
export function Pagination({ totalPages, currentPage, fileId, callBack }: any) {
|
||||||
|
|
||||||
|
if (totalPages === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVisiblePages = () => {
|
||||||
|
const pages = [];
|
||||||
|
const maxVisible = 7;
|
||||||
|
let start = Math.max(1, currentPage - 3);
|
||||||
|
let end = Math.min(totalPages, currentPage + 3);
|
||||||
|
|
||||||
|
if (end - start + 1 < maxVisible) {
|
||||||
|
if (start === 1) {
|
||||||
|
end = Math.min(totalPages, start + maxVisible - 1);
|
||||||
|
} else if (end === totalPages) {
|
||||||
|
start = Math.max(1, end - maxVisible + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
const visiblePages = getVisiblePages();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pagination-wrapper">
|
||||||
|
{visiblePages[0] > 1 && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className={`pagination-button`}
|
||||||
|
onClick={() => callBack(fileId, 1, true)}
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</button>
|
||||||
|
{visiblePages[0] > 2 && <span>...</span>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{visiblePages.map(page => (
|
||||||
|
<button
|
||||||
|
key={page}
|
||||||
|
onClick={() => callBack(fileId, page, true)}
|
||||||
|
className={`pagination-button ${page === currentPage ? 'current-page' : ''}`}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{visiblePages[visiblePages.length - 1] < totalPages && (
|
||||||
|
<>
|
||||||
|
{visiblePages[visiblePages.length - 1] < totalPages - 1 && <span>...</span>}
|
||||||
|
<button
|
||||||
|
className={`pagination-button`}
|
||||||
|
onClick={() => callBack(fileId, totalPages, true)}
|
||||||
|
>
|
||||||
|
{totalPages}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { usePdf } from '@/app/contexts/PdfContext';
|
||||||
|
import { requestFileAsBuffer } from '@/app/actions/trackingActions';
|
||||||
|
|
||||||
|
interface PdfViewerProps {
|
||||||
|
fileId: string;
|
||||||
|
fileName?: string;
|
||||||
|
showDownload?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PdfViewer({ fileId, fileName = 'document.pdf', showDownload }: PdfViewerProps) {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [totalPages, setTotalPages] = useState(0);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [fullscreen, setFullscreen] = useState(false);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const pdfDocumentRef = useRef<any>(null);
|
||||||
|
const { setPdfData } = usePdf();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
|
let objectUrl: string | null = null;
|
||||||
|
|
||||||
|
const loadAndDisplayPdf = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const pdfData = await requestFileAsBuffer(fileId);
|
||||||
|
|
||||||
|
if (!pdfData) {
|
||||||
|
throw new Error('Failed to load document');
|
||||||
|
}
|
||||||
|
|
||||||
|
//это нужно для скачивания так-как иначе pdfData теряется для скачивания
|
||||||
|
if (showDownload) {
|
||||||
|
const pdfDataForDownload = pdfData.slice(0);
|
||||||
|
setPdfData(pdfDataForDownload, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMounted) return;
|
||||||
|
|
||||||
|
const pdfjs = await import('pdfjs-dist');
|
||||||
|
|
||||||
|
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
|
'pdfjs-dist/build/pdf.worker.mjs',
|
||||||
|
import.meta.url
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
const loadingTask = pdfjs.getDocument({
|
||||||
|
data: pdfData,
|
||||||
|
useSystemFonts: true,
|
||||||
|
disableFontFace: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pdfDocument = await loadingTask.promise;
|
||||||
|
|
||||||
|
if (!isMounted) return;
|
||||||
|
|
||||||
|
pdfDocumentRef.current = pdfDocument;
|
||||||
|
setTotalPages(pdfDocument.numPages);
|
||||||
|
setCurrentPage(1);
|
||||||
|
|
||||||
|
await renderPage(pdfDocument, 1);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading PDF:', err);
|
||||||
|
if (isMounted) {
|
||||||
|
setError('Не удалось загрузить документ');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (isMounted) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadAndDisplayPdf();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
if (pdfDocumentRef.current) {
|
||||||
|
pdfDocumentRef.current.destroy();
|
||||||
|
}
|
||||||
|
if (objectUrl) {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [fileId, setPdfData]);
|
||||||
|
|
||||||
|
const renderPage = async (pdfDocument: any, pageNum: number) => {
|
||||||
|
if (!canvasRef.current) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const page = await pdfDocument.getPage(pageNum);
|
||||||
|
|
||||||
|
const container = canvasRef.current.parentElement;
|
||||||
|
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const containerWidth = container.clientWidth || 800;
|
||||||
|
|
||||||
|
// Если ширина контейнера 0, откладываем рендер
|
||||||
|
if (containerWidth === 0) {
|
||||||
|
console.log('Container width is 0, waiting...');
|
||||||
|
setTimeout(() => renderPage(pdfDocument, pageNum), 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalViewport = page.getViewport({ scale: 1 });
|
||||||
|
const scale = containerWidth / originalViewport.width;
|
||||||
|
const viewport = page.getViewport({ scale: Math.min(scale, 1.5) });
|
||||||
|
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
|
||||||
|
const renderContext = {
|
||||||
|
canvasContext: context!,
|
||||||
|
viewport: viewport,
|
||||||
|
};
|
||||||
|
|
||||||
|
await page.render(renderContext).promise;
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error rendering page:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pdfDocumentRef.current || loading) return;
|
||||||
|
|
||||||
|
const checkContainer = () => {
|
||||||
|
const container = canvasRef.current?.parentElement;
|
||||||
|
if (container && container.clientWidth > 0) {
|
||||||
|
renderPage(pdfDocumentRef.current, currentPage);
|
||||||
|
} else {
|
||||||
|
setTimeout(checkContainer, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkContainer();
|
||||||
|
}, [pdfDocumentRef.current, loading]);
|
||||||
|
|
||||||
|
const goToPrevPage = async () => {
|
||||||
|
if (pdfDocumentRef.current && currentPage > 1) {
|
||||||
|
const newPage = currentPage - 1;
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
await renderPage(pdfDocumentRef.current, newPage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToNextPage = async () => {
|
||||||
|
if (pdfDocumentRef.current && currentPage < totalPages) {
|
||||||
|
const newPage = currentPage + 1;
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
await renderPage(pdfDocumentRef.current, newPage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleFullscreen = () => {
|
||||||
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
containerRef.current.requestFullscreen();
|
||||||
|
setFullscreen(true);
|
||||||
|
} else {
|
||||||
|
document.exitFullscreen();
|
||||||
|
setFullscreen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleFullscreenChange = () => {
|
||||||
|
setFullscreen(!!document.fullscreenElement);
|
||||||
|
if (pdfDocumentRef.current && !document.fullscreenElement) {
|
||||||
|
setTimeout(() => renderPage(pdfDocumentRef.current, currentPage), 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
||||||
|
return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
if (pdfDocumentRef.current && !loading) {
|
||||||
|
renderPage(pdfDocumentRef.current, currentPage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, [currentPage, loading]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="prev-wrap">
|
||||||
|
<div className="pdf-loading">Загрузка документа...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="prev-wrap">
|
||||||
|
<div className="pdf-error">{error}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className="pdf-container">
|
||||||
|
<div className="prev-wrap">
|
||||||
|
<canvas ref={canvasRef} className="pdf-canvas" />
|
||||||
|
</div>
|
||||||
|
{totalPages > 0 && (
|
||||||
|
<div className="document-controls">
|
||||||
|
<div className="document-controls__nav">
|
||||||
|
<button
|
||||||
|
className="document-controls__prev"
|
||||||
|
onClick={goToPrevPage}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
← Пред.
|
||||||
|
</button>
|
||||||
|
<span className="document-controls__current-page">
|
||||||
|
{currentPage} / {totalPages}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
className="document-controls__next"
|
||||||
|
onClick={goToNextPage}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
След. →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="document-controls__fullscreen"
|
||||||
|
onClick={toggleFullscreen}
|
||||||
|
>
|
||||||
|
{fullscreen ? 'Выйти' : 'На весь экран'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,681 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useRef, useState, useCallback, ChangeEvent, DragEvent, useEffect, useMemo } from 'react';
|
||||||
|
import { IconShieldAdd } from '@/app/ui/icons/icons';
|
||||||
|
import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus, checkOperationPrice } from '@/app/actions/fileUpload';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import DropDownListForImageConvert from '@/app/components/DropDownListForImageConvert';
|
||||||
|
|
||||||
|
export interface SelectedFile {
|
||||||
|
file: File;
|
||||||
|
preview: string | undefined;
|
||||||
|
name: string;
|
||||||
|
size: string;
|
||||||
|
dimensions?: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
uploadId?: string;
|
||||||
|
progress: number;
|
||||||
|
status: 'pending' | 'uploading' | 'completed' | 'error' | 'cancelled';
|
||||||
|
error?: string;
|
||||||
|
convertTo?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileUploadInitResponse {
|
||||||
|
upload_id: string;
|
||||||
|
file_name: string;
|
||||||
|
total_chunks: number;
|
||||||
|
chunk_size: number;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UploadSectionFile {
|
||||||
|
fileType: string;
|
||||||
|
allowedExtensions: string[];
|
||||||
|
maxFileSize: number;
|
||||||
|
maxParallelUploads?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UploadSectionFile({
|
||||||
|
fileType,
|
||||||
|
allowedExtensions,
|
||||||
|
maxFileSize,
|
||||||
|
maxParallelUploads = 3
|
||||||
|
}: UploadSectionFile) {
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||||
|
const [selectedFiles, setSelectedFiles] = useState<SelectedFile[]>([]);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [activeUploads, setActiveUploads] = useState<Set<string>>(new Set()); // ID активных загрузок
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const isCancelledRef = useRef<Map<string, boolean>>(new Map()); // Для отслеживания отмен по файлам
|
||||||
|
const selectedFilesRef = useRef<SelectedFile[]>(selectedFiles);
|
||||||
|
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
|
const acceptString = useMemo(() => {
|
||||||
|
if (!allowedExtensions || !Array.isArray(allowedExtensions)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return allowedExtensions.map(e => `.${e}`).join(', ');
|
||||||
|
}, [allowedExtensions]);
|
||||||
|
|
||||||
|
const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => {
|
||||||
|
if (!allowedExtensions.includes(file.type as string)) {
|
||||||
|
const extension = file.name.split('.').pop()?.toLowerCase();
|
||||||
|
if (!extension || !allowedExtensions.includes(extension as string)) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: t('unsupported-file-format')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_SIZE = maxFileSize;
|
||||||
|
|
||||||
|
if (file.size > MAX_SIZE) {
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
errorMessage: t('the-file-is-too-large')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isValid: true };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImageDimensions = (file: File): Promise<{ width: number; height: number }> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
const objectUrl = URL.createObjectURL(file);
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
const dimensions = {
|
||||||
|
width: img.width,
|
||||||
|
height: img.height
|
||||||
|
};
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
resolve(dimensions);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = () => {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
reject(new Error(t('error-uploading-file')));
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = objectUrl;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilesSelect = useCallback(async (files: File[]): Promise<void> => {
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const newFiles: SelectedFile[] = [];
|
||||||
|
const selectedFileNames = new Set(selectedFiles.map(f => f.name));
|
||||||
|
const currentFileNames = new Set<string>();
|
||||||
|
const duplicateNames: string[] = [];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (selectedFileNames.has(file.name)) {
|
||||||
|
duplicateNames.push(file.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validation = validateFile(file);
|
||||||
|
|
||||||
|
if (!validation.isValid) {
|
||||||
|
setError(prev => prev ? `${prev}, ${validation.errorMessage}` : validation.errorMessage || t('unknown-validation-error'));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dimensions: {
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
} | undefined = undefined;
|
||||||
|
if (fileType === "image") {
|
||||||
|
try {
|
||||||
|
dimensions = await getImageDimensions(file) as { width: number, height: number };
|
||||||
|
if (dimensions?.width < 100 || dimensions?.height < 100 ||
|
||||||
|
dimensions?.width > 10000 || dimensions?.height > 10000) {
|
||||||
|
setError(prev => prev ? `${prev}, ${t('image-size')}: ${dimensions?.width}x${dimensions?.height}. ${t('acceptable-range')}: 100x100 - 10000x10000 ${t('pixels')}.` :
|
||||||
|
`${t('image-size')}: ${dimensions?.width}x${dimensions?.height}. ${t('acceptable-range')}: 100x100 - 10000x10000 ${t('pixels')}.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(prev => prev ? `${prev}, ${t('error-reading-image')}` : t('error-reading-image'));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFileNames.add(file.name);
|
||||||
|
|
||||||
|
newFiles.push({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
size: `${(file.size / 1024 / 1024).toFixed(2)} MB`,
|
||||||
|
dimensions,
|
||||||
|
preview: file.size < 10 * 1024 * 1024 ? URL.createObjectURL(file) : undefined,
|
||||||
|
progress: 0,
|
||||||
|
status: 'pending'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateNames.length > 0) {
|
||||||
|
const uniqueDuplicates = [...new Set(duplicateNames)];
|
||||||
|
setError(`${t('duplicate-files')}: ${uniqueDuplicates.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedFiles(prev => [...prev, ...newFiles]);
|
||||||
|
}, [fileType, selectedFiles]);
|
||||||
|
|
||||||
|
const handleFileInputChange = (event: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
const files = Array.from(event.target.files || []);
|
||||||
|
handleFilesSelect(files);
|
||||||
|
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleDragOver = (event: DragEvent<HTMLDivElement>): void => {
|
||||||
|
event.preventDefault();
|
||||||
|
setIsDragging(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragLeave = (): void => {
|
||||||
|
setIsDragging(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleDrop = (event: DragEvent<HTMLDivElement>): void => {
|
||||||
|
event.preventDefault();
|
||||||
|
setIsDragging(false);
|
||||||
|
|
||||||
|
const files = Array.from(event.dataTransfer.files);
|
||||||
|
handleFilesSelect(files);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearFile = async (fileId?: string): Promise<void> => {
|
||||||
|
if (fileId) {
|
||||||
|
const fileToClear = selectedFiles.find(f => f.uploadId === fileId || f.name === fileId);
|
||||||
|
if (fileToClear?.preview) {
|
||||||
|
URL.revokeObjectURL(fileToClear.preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileToClear?.uploadId) {
|
||||||
|
await cancelUpload(fileToClear.uploadId);
|
||||||
|
isCancelledRef.current.set(fileToClear.uploadId, true);
|
||||||
|
setActiveUploads(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(fileToClear.uploadId!);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedFiles(prev => prev.filter(f =>
|
||||||
|
f.uploadId !== fileId && f.name !== fileId
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
console.log('clear all')
|
||||||
|
selectedFiles.forEach(file => {
|
||||||
|
if (file.preview) {
|
||||||
|
URL.revokeObjectURL(file.preview);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cancelPromises = Array.from(activeUploads).map(uploadId =>
|
||||||
|
cancelUpload(uploadId)
|
||||||
|
);
|
||||||
|
await Promise.allSettled(cancelPromises);
|
||||||
|
|
||||||
|
isCancelledRef.current.clear();
|
||||||
|
setActiveUploads(new Set());
|
||||||
|
setSelectedFiles([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadSingleFile = async (fileInfo: SelectedFile, uploadType: 'single' | 'multi'): Promise<void> => {
|
||||||
|
const fileId = `${fileInfo.name}-${Date.now()}`;
|
||||||
|
isCancelledRef.current.set(fileId, false);
|
||||||
|
|
||||||
|
if (uploadType === 'single') {
|
||||||
|
setError(null);
|
||||||
|
const priceCheck = await checkOperationPrice('checkTokensForProtect', 1, fileType);
|
||||||
|
|
||||||
|
if (!priceCheck.success) {
|
||||||
|
const errorMessage = `${t('there-are-not-enough-funds-to-protect-file', { count: priceCheck?.tokens_count })}`;
|
||||||
|
setError(errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setSelectedFiles(prev => prev.map(f =>
|
||||||
|
f.name === fileInfo.name ? { ...f, status: 'uploading' } : f
|
||||||
|
));
|
||||||
|
|
||||||
|
const extension = fileInfo.file.name.split('.').pop() || '';
|
||||||
|
const initMessageBody = {
|
||||||
|
file_name: fileInfo.file.name,
|
||||||
|
file_type: fileType,
|
||||||
|
extension: extension,
|
||||||
|
file_size: fileInfo.file.size
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fileUpload(initMessageBody, fileInfo.convertTo) as FileUploadInitResponse;
|
||||||
|
|
||||||
|
if (!response?.upload_id) {
|
||||||
|
throw new Error('Failed to get upload_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedFiles(prev => prev.map(f =>
|
||||||
|
f.name === fileInfo.name ? { ...f, uploadId: response.upload_id } : f
|
||||||
|
));
|
||||||
|
|
||||||
|
setActiveUploads(prev => new Set(prev).add(response.upload_id));
|
||||||
|
|
||||||
|
const CHUNK_SIZE = response.chunk_size;
|
||||||
|
const totalChunks = response.total_chunks;
|
||||||
|
|
||||||
|
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
|
||||||
|
if (isCancelledRef.current.get(fileId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = chunkIndex * CHUNK_SIZE;
|
||||||
|
const end = Math.min(start + CHUNK_SIZE, fileInfo.file.size);
|
||||||
|
const chunk = fileInfo.file.slice(start, end);
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
formData.append('upload_id', response.upload_id);
|
||||||
|
formData.append('chunk_number', chunkIndex.toString());
|
||||||
|
formData.append('chunk', chunk);
|
||||||
|
|
||||||
|
const chunkResponse = await chunkUpload(formData);
|
||||||
|
|
||||||
|
if (chunkResponse.errorMesage !== null) {
|
||||||
|
throw chunkResponse.errorMesage;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress = Math.floor((chunkIndex + 1) / totalChunks * 100);
|
||||||
|
setSelectedFiles(prev => prev.map(f =>
|
||||||
|
f.name === fileInfo.name ? { ...f, progress } : f
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkStatus = await checkChunkStatus(response.upload_id);
|
||||||
|
if (chunkStatus.message_body.missing_chunks === 0) {
|
||||||
|
setSelectedFiles(prev => prev.map(f =>
|
||||||
|
f.name === fileInfo.name ? { ...f, status: 'completed', progress: 100 } : f
|
||||||
|
));
|
||||||
|
|
||||||
|
setActiveUploads(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(response.upload_id);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['userFilesData'] });
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['userFilesInfo'] });
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['userData'] });
|
||||||
|
} else {
|
||||||
|
throw new Error('Not all chunks were uploaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (!isCancelledRef.current.get(fileId)) {
|
||||||
|
const errorMessage = (typeof error === 'string') ? t(error) : t('error-uploading-file');
|
||||||
|
setSelectedFiles(prev => prev.map(f =>
|
||||||
|
f.name === fileInfo.name ? { ...f, status: 'error', error: errorMessage } : f
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
selectedFilesRef.current = selectedFiles;
|
||||||
|
}, [selectedFiles]);
|
||||||
|
|
||||||
|
const handleStartAllUploads = useCallback(async (): Promise<void> => {
|
||||||
|
setError(null);
|
||||||
|
if (selectedFilesRef.current.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const priceCheck = await checkOperationPrice('checkTokensForProtect', selectedFilesRef.current.length, fileType);
|
||||||
|
|
||||||
|
if (priceCheck.success) {
|
||||||
|
const uploadNextBatch = async () => {
|
||||||
|
const currentPending = selectedFilesRef.current.filter(f => f.status === 'pending');
|
||||||
|
const batch = currentPending.slice(0, maxParallelUploads);
|
||||||
|
|
||||||
|
if (batch.length === 0) return;
|
||||||
|
|
||||||
|
await Promise.all(batch.map(file => uploadSingleFile(file, 'multi')));
|
||||||
|
|
||||||
|
if (selectedFilesRef.current.some(f => f.status === 'pending')) {
|
||||||
|
await uploadNextBatch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await uploadNextBatch();
|
||||||
|
} else {
|
||||||
|
const errorMessage = `${t('there-are-not-enough-funds-to-protect-files', { count: priceCheck?.tokens_count })}`;
|
||||||
|
setError(errorMessage);
|
||||||
|
}
|
||||||
|
}, [maxParallelUploads]);
|
||||||
|
|
||||||
|
const getTotalProgress = useMemo(() => {
|
||||||
|
if (selectedFiles.length === 0) return 0;
|
||||||
|
const total = selectedFiles.reduce((sum, file) => sum + file.progress, 0);
|
||||||
|
return Math.floor(total / selectedFiles.length);
|
||||||
|
}, [selectedFiles]);
|
||||||
|
|
||||||
|
const completedCount = useMemo(() =>
|
||||||
|
selectedFiles.filter(f => f.status === 'completed').length,
|
||||||
|
[selectedFiles]
|
||||||
|
);
|
||||||
|
|
||||||
|
const uploadingCount = useMemo(() =>
|
||||||
|
selectedFiles.filter(f => f.status === 'uploading').length,
|
||||||
|
[selectedFiles]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/* useEffect(() => {
|
||||||
|
// Обработка закрытия вкладки
|
||||||
|
// Тут можно попробовать изучить вопрос с navigator.sendBeacon('', blob)
|
||||||
|
// он нужен для того что бы послать запрос на бек при закрытии вкладки,
|
||||||
|
// но у него есть свои особенности и стандартные запросы не работают.
|
||||||
|
const handleUnload = () => {
|
||||||
|
if (activeUploads.size) {
|
||||||
|
//const data = JSON.stringify({ uploadId, reason: 'tab_closed' });
|
||||||
|
//const blob = new Blob([data], { type: 'application/json' });
|
||||||
|
//navigator.sendBeacon('/api/v1/data/cancel', blob);
|
||||||
|
console.log('sendBeacon');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (activeUploads.size) {
|
||||||
|
window.addEventListener('unload', handleUnload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('unload', handleUnload);
|
||||||
|
};
|
||||||
|
}, [activeUploads.size]); */
|
||||||
|
|
||||||
|
useNavigationBlocker({
|
||||||
|
shouldBlock: !!activeUploads.size,
|
||||||
|
message: t('have-unsaved-changes'),
|
||||||
|
onConfirm: async () => {
|
||||||
|
console.log('User confirmed navigation');
|
||||||
|
if (activeUploads.size) {
|
||||||
|
await handleClearFile();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
console.log('User cancelled navigation');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function converButtonHandler(value: {
|
||||||
|
convertTo: string;
|
||||||
|
file: SelectedFile
|
||||||
|
}): void {
|
||||||
|
setSelectedFiles(selectedFiles.map(selectedFile => {
|
||||||
|
if (selectedFile.name === value.file.name) {
|
||||||
|
return {
|
||||||
|
...selectedFile,
|
||||||
|
convertTo: value.convertTo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return selectedFile;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="upload-section" >
|
||||||
|
<h3>
|
||||||
|
{t('upload-for-protection', {
|
||||||
|
fileType: t(fileType.toLocaleLowerCase())
|
||||||
|
})}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
< div
|
||||||
|
className={`drag-drop-zone ${isDragging ? 'dragging' : ''}`}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
>
|
||||||
|
<IconShieldAdd />
|
||||||
|
<h4>
|
||||||
|
{t('drag-the-file-here')}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
{
|
||||||
|
fileType === "image" && (
|
||||||
|
<p className="description" >
|
||||||
|
{t('image-resolution')}: 100x100 - 10000x10000 {t('pixels')}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<p className="description" >
|
||||||
|
{t('file-format')}: {acceptString}
|
||||||
|
</p>
|
||||||
|
< p className="description" >
|
||||||
|
{t('file-size')}: {t('to')} {(maxFileSize / 1024 / 1024)} {t('mb')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
< input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept={acceptString}
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleFileInputChange}
|
||||||
|
aria-label="Выбор файла для защиты"
|
||||||
|
multiple // Добавляем multiple для выбора нескольких файлов
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
type="button"
|
||||||
|
disabled={uploadingCount > maxParallelUploads}
|
||||||
|
>
|
||||||
|
{t('select-files-to-protect')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{
|
||||||
|
error && (
|
||||||
|
<div className="error-message">
|
||||||
|
<p className="error-message-text"> {error} </p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
selectedFiles.length > 0 && (
|
||||||
|
<div className="mt-6" >
|
||||||
|
<div className="selected-file-action-panel" >
|
||||||
|
<div>
|
||||||
|
<p className="selected-file-action-info" >
|
||||||
|
<span>{t('total-files')}: {selectedFiles.length}</span>
|
||||||
|
<span>{t('uploading')}: {uploadingCount}</span>
|
||||||
|
<span>{t('completed')}: {completedCount}</span>
|
||||||
|
</p>
|
||||||
|
<div className="w-full bg-gray-200 rounded-full h-2.5 mt-2">
|
||||||
|
<div
|
||||||
|
className="bg-blue-600 h-2.5 rounded-full transition-all duration-300"
|
||||||
|
style={{ width: `${getTotalProgress}%` }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
className={`btn btn-primary`}
|
||||||
|
onClick={handleStartAllUploads}
|
||||||
|
type="button"
|
||||||
|
disabled={uploadingCount > 0 || selectedFiles.every(f => f.status !== 'pending')}
|
||||||
|
>
|
||||||
|
{t('upload-all')}
|
||||||
|
</button>
|
||||||
|
< button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => handleClearFile()}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{t('clear-all')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="selected-file-wrapper">
|
||||||
|
{
|
||||||
|
selectedFiles.map((file, index) => (
|
||||||
|
<div
|
||||||
|
key={`${file.name}-${index}`}
|
||||||
|
className={`selected-file ${file.status === 'completed' ? 'done' : ''} ${file.status === 'error' ? 'error' : ''}`}
|
||||||
|
>
|
||||||
|
<div className="selected-file-file-info">
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-700" >
|
||||||
|
<span className="font-medium" > {t('file')}: </span> {file.name}
|
||||||
|
</p>
|
||||||
|
< p className="text-gray-700" >
|
||||||
|
<span className="font-medium" > {t('size')}: </span> {file.size}
|
||||||
|
</p>
|
||||||
|
< p className="text-gray-700" >
|
||||||
|
<span className="font-medium" > {t('status')}: </span>
|
||||||
|
<span className={`ml-2 ${file.status === 'completed' ? 'text-green-600' :
|
||||||
|
file.status === 'uploading' ? 'text-blue-600' :
|
||||||
|
file.status === 'error' ? 'text-red-600' :
|
||||||
|
'text-gray-600'
|
||||||
|
}`}>
|
||||||
|
{
|
||||||
|
file.status === 'completed' ? t('completed') :
|
||||||
|
file.status === 'uploading' ? t('uploading') :
|
||||||
|
file.status === 'error' ? t('error') :
|
||||||
|
t('pending')
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
{
|
||||||
|
file.error && (
|
||||||
|
<p className="text-red-600 text-sm mt-1" > {file.error} </p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="selected-file-right-block">
|
||||||
|
{
|
||||||
|
(fileType === 'image' && file.status === 'pending') && (
|
||||||
|
<>
|
||||||
|
<DropDownListForImageConvert
|
||||||
|
value={file.convertTo}
|
||||||
|
translatedValue={file.convertTo === 'reject' ? t('do-not-convert') : file.convertTo}
|
||||||
|
callBack={converButtonHandler}
|
||||||
|
selectedFiles={selectedFiles}
|
||||||
|
>
|
||||||
|
<li data-file={file.name} data-convert-to="reject">
|
||||||
|
{t('do-not-convert')}
|
||||||
|
</li>
|
||||||
|
{
|
||||||
|
allowedExtensions && allowedExtensions.map(extension => {
|
||||||
|
if (extension === 'gif') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={extension} data-file={file.name} data-convert-to={extension}>
|
||||||
|
{t('convert-to')} {extension}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</DropDownListForImageConvert>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
className="flex"
|
||||||
|
>
|
||||||
|
{file.status !== 'error' && (
|
||||||
|
<div className="upload-progress-circle">
|
||||||
|
<svg viewBox="0 0 36 36" className="circular-chart">
|
||||||
|
<path
|
||||||
|
className="circle-bg"
|
||||||
|
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
|
||||||
|
fill="none"
|
||||||
|
stroke="#e5e7eb"
|
||||||
|
strokeWidth="3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="circle"
|
||||||
|
d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
|
||||||
|
fill="none"
|
||||||
|
stroke={file.status === 'completed' ? '#10b981' : file.status === 'uploading' ? '#155dfc' : '#6b7280'}
|
||||||
|
strokeWidth="4"
|
||||||
|
strokeDasharray={`${file.progress}, 100`}
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="circle-percentage">{file.progress}%</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
< button
|
||||||
|
className="btn btn-cancel"
|
||||||
|
onClick={() => handleClearFile(file.uploadId || file.name)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{t('remove')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
(fileType === 'image' && file.preview) && (
|
||||||
|
<div
|
||||||
|
className="image-preview-wrapper"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={file.preview}
|
||||||
|
alt="Предпросмотр загруженного изображения"
|
||||||
|
className="uploaded-image"
|
||||||
|
onError={() => setError('Не удалось загрузить превью изображения')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
file.status === 'pending' && (
|
||||||
|
<div className="mt-2 text-center" >
|
||||||
|
<button
|
||||||
|
className="btn btn-confirm"
|
||||||
|
onClick={() => uploadSingleFile(file, 'single')
|
||||||
|
}
|
||||||
|
type="button"
|
||||||
|
disabled={uploadingCount >= maxParallelUploads}
|
||||||
|
>
|
||||||
|
{t('start-upload')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,10 +14,6 @@ export default function YandexAuthWithScript() {
|
|||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
const [showButton, setShowButton] = useState(true);
|
const [showButton, setShowButton] = useState(true);
|
||||||
|
|
||||||
const handleLogin = (token: string) => {
|
|
||||||
console.log('handleLogin');
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isReady || !buttonRef.current || !window.YaAuthSuggest) return;
|
if (!isReady || !buttonRef.current || !window.YaAuthSuggest) return;
|
||||||
|
|
||||||
@@ -25,31 +21,28 @@ export default function YandexAuthWithScript() {
|
|||||||
{
|
{
|
||||||
client_id: 'b356a7ceac0f4c50a3b434220f86bce0',
|
client_id: 'b356a7ceac0f4c50a3b434220f86bce0',
|
||||||
response_type: 'token',
|
response_type: 'token',
|
||||||
redirect_uri: `http://localhost:2999/ru/login`,
|
redirect_uri: `https://fallibly-desired-moonfish.cloudpub.ru/en/login`,
|
||||||
},
|
},
|
||||||
'http://localhost:2999',
|
'https://fallibly-desired-moonfish.cloudpub.ru',
|
||||||
{
|
{
|
||||||
parentId: buttonRef.current.id,
|
parentId: buttonRef.current.id,
|
||||||
view: 'button',
|
view: 'button',
|
||||||
buttonView: 'main',
|
buttonView: 'main',
|
||||||
buttonTheme: 'light',
|
buttonTheme: 'light',
|
||||||
buttonSize: 'm',
|
buttonSize: 'l',
|
||||||
buttonBorderRadius: 10
|
buttonBorderRadius: 10
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then((result: any) => {
|
.then(function (result: any) {
|
||||||
console.log('then');
|
console.log('you at least try?')
|
||||||
console.log(result);
|
console.log(result);
|
||||||
if (result?.access_token) {
|
return result.handler();
|
||||||
handleLogin(result.access_token);
|
|
||||||
} else {
|
|
||||||
setTimeout(() => {
|
|
||||||
setShowButton(false);
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch((error: any) => {
|
.then(function (data: any) {
|
||||||
console.error('Yandex auth error:', error);
|
console.log('Сообщение с токеном: ', data);
|
||||||
|
})
|
||||||
|
.catch(function (error: any) {
|
||||||
|
console.log('Что-то пошло не так: ', error);
|
||||||
});
|
});
|
||||||
}, [isReady]);
|
}, [isReady]);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export function YandexLoginButton() {
|
||||||
|
const clientId = 'b356a7ceac0f4c50a3b434220f86bce0';
|
||||||
|
const redirectUri = encodeURIComponent('https://fallibly-desired-moonfish.cloudpub.ru/login/callback');
|
||||||
|
|
||||||
|
const authUrl = `https://oauth.yandex.ru/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&force_confirm=yes`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={authUrl}
|
||||||
|
className="yandex-auth-button"
|
||||||
|
>
|
||||||
|
Войти через Яндекс
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
YooMoneyCheckoutWidget,
|
||||||
|
YooMoneyCheckoutWidgetConfig,
|
||||||
|
YooMoneyErrorCallbackResult
|
||||||
|
} from "types-yoomoneycheckoutwidget";
|
||||||
|
|
||||||
|
let isLoading = false;
|
||||||
|
let isInited = false;
|
||||||
|
export default function YooWidget({ config, onComplete, onSuccess, onFail, onModalClose }: {
|
||||||
|
/**
|
||||||
|
* Описание https://yookassa.ru/developers/payment-acceptance/integration-scenarios/widget/reference#initialization-parameters
|
||||||
|
*/
|
||||||
|
config: YooMoneyCheckoutWidgetConfig,
|
||||||
|
/** после успешной оплаты*/
|
||||||
|
onComplete?: () => void,
|
||||||
|
/** после успешной оплаты, только если не подписались на onComplete*/
|
||||||
|
onSuccess?: () => void,
|
||||||
|
/** только если вы отключили обработку неуспешных попыток в виджете: пользователь выбрал любой способ оплаты, но платеж не прошел; закончился срок действия токена.*/
|
||||||
|
onFail?: () => void,
|
||||||
|
/** после закрытия модального окна платежной формы если customization.modal==true */
|
||||||
|
onModalClose?: () => void,
|
||||||
|
}) {
|
||||||
|
const isModal = config?.customization?.modal;
|
||||||
|
let checkout: YooMoneyCheckoutWidget;
|
||||||
|
const destroy = () => {
|
||||||
|
console.log('checkout.destroy');
|
||||||
|
checkout.destroy();
|
||||||
|
isInited = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeWidget = useCallback(
|
||||||
|
() => {
|
||||||
|
isInited = true;
|
||||||
|
isLoading = false;
|
||||||
|
if (!isModal) {
|
||||||
|
const userErrorCallBack = config.error_callback;
|
||||||
|
config.error_callback = (error: YooMoneyErrorCallbackResult) => {
|
||||||
|
userErrorCallBack(error);
|
||||||
|
isInited = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
checkout = new window.YooMoneyCheckoutWidget(config);
|
||||||
|
checkout.on('complete', (result) => {
|
||||||
|
console.log('on complete', result);
|
||||||
|
onComplete && onComplete();
|
||||||
|
destroy();
|
||||||
|
});
|
||||||
|
onSuccess && checkout.on('success', (result) => {
|
||||||
|
console.log('on success', result);
|
||||||
|
onSuccess();
|
||||||
|
destroy();
|
||||||
|
});
|
||||||
|
onFail && checkout.on('fail', (result) => {
|
||||||
|
console.warn('on fail', result);
|
||||||
|
onFail();
|
||||||
|
destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (config.customization?.modal) {
|
||||||
|
checkout.on('modal_close', (result) => {
|
||||||
|
onModalClose && onModalClose();
|
||||||
|
destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
checkout.render(config.customization?.modal ? undefined : 'payment-form');
|
||||||
|
|
||||||
|
}, [config, onSuccess]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && !isInited) {
|
||||||
|
if (!window.YooMoneyCheckoutWidget) {
|
||||||
|
isLoading = true;
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = 'https://yookassa.ru/checkout-widget/v1/checkout-widget.js';
|
||||||
|
script.async = true;
|
||||||
|
script.onload = initializeWidget;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
return () => {
|
||||||
|
document.head.removeChild(script);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
initializeWidget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (!isInited && !isModal) {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isModal ? '' : <div id="payment-form" />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
interface CopyLinkButtonProps {
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CopyLinkButton({ className = 'btn btn-out', children = 'Скопировать ссылку' }: CopyLinkButtonProps) {
|
||||||
|
|
||||||
|
const copyCurrentPageLink = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(window.location.href);
|
||||||
|
toast.success('Ссылка скопирована');
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('Не удалось скопировать ссылку');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={className}
|
||||||
|
onClick={copyCurrentPageLink}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { downloadTrackingFile } from '@/app/actions/trackingActions';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
interface DownloadButtonProps {
|
||||||
|
fileId: string;
|
||||||
|
fileName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DownloadButton({ fileId, fileName }: DownloadButtonProps) {
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const result = await downloadTrackingFile(fileId, fileName);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw 'failed-to-download-file'
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteCharacters = atob(result.data);
|
||||||
|
const byteNumbers = new Array(byteCharacters.length);
|
||||||
|
for (let i = 0; i < byteCharacters.length; i++) {
|
||||||
|
byteNumbers[i] = byteCharacters.charCodeAt(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
|
const blob = new Blob([byteArray], { type: result.contentType });
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = result.fileName;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
toast.success(`${t('file-is-downloading')} - `);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(t('failed-to-download-file'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className="btn btn-out" onClick={handleDownload}>
|
||||||
|
Скачать документ
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
|
||||||
|
|
||||||
|
export function LoginUserBage() {
|
||||||
|
const { data: userData, isLoading, isError, error } = useUserProfile();
|
||||||
|
|
||||||
|
if (!userData?.fullName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link href="#" className="header-user">
|
||||||
|
{userData?.fullName}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { FileItem } from '@/app/components/tanstak-table/TanstakTable';
|
||||||
|
import { useEffect, useState, useRef } from 'react';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
import { setMonitoring } from '@/app/actions/monitoringActions';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
export function MonitoringDropDown({ file }: {
|
||||||
|
file: FileItem
|
||||||
|
}) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [currentMonitoringStatus, setCurrentMonitoringStatus] = useState(file.monitoring);
|
||||||
|
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, width: 0 });
|
||||||
|
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
const t = useTranslations('Monitoring-status');
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
|
||||||
|
const handleClick = (event: MouseEvent) => {
|
||||||
|
if (!dropdownRef?.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buttonRef?.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonRef?.current.contains(event.target as Node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dropdownRef.current.contains(event.target as Node)) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClick);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClick);
|
||||||
|
};
|
||||||
|
}, [dropdownRef, setIsOpen, isOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (file.monitoring) {
|
||||||
|
setCurrentMonitoringStatus(file.monitoring ? file.monitoring : 'NONE');
|
||||||
|
}
|
||||||
|
}, [file.monitoring]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && buttonRef.current) {
|
||||||
|
const rect = buttonRef.current.getBoundingClientRect();
|
||||||
|
setDropdownPosition({
|
||||||
|
top: rect.bottom + window.scrollY,
|
||||||
|
left: rect.left + window.scrollX,
|
||||||
|
width: rect.width
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
async function clickHandler(monitoring: string) {
|
||||||
|
|
||||||
|
const response = await setMonitoring(file.id, monitoring);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['userFilesData'] });
|
||||||
|
setIsOpen(false);
|
||||||
|
toast.success(t('the-file-is monitored', { fileName: file.fileName }));
|
||||||
|
setCurrentMonitoringStatus(monitoring);
|
||||||
|
} else {
|
||||||
|
toast.warning(t('failed-to-establish-monitoring'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="dropdown-wrapper monitoring-dropdown">
|
||||||
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
}}
|
||||||
|
className="dropdown-button"
|
||||||
|
>
|
||||||
|
<span className="flex items-center">
|
||||||
|
<span className="mr-2">
|
||||||
|
{t(currentMonitoringStatus)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
className={`w-5 h-5 ml-2 transition-transform ${isOpen ? 'rotate-180' : ''}`}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{isOpen && createPortal(
|
||||||
|
<div
|
||||||
|
className="dropdown-list-portal"
|
||||||
|
ref={dropdownRef}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: dropdownPosition.top,
|
||||||
|
left: dropdownPosition.left,
|
||||||
|
width: dropdownPosition.width,
|
||||||
|
zIndex: 99999
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ul className="dropdown-list" role="listbox">
|
||||||
|
<li
|
||||||
|
className={`dropdown-item ${currentMonitoringStatus === 'NONE' ? 'current' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
clickHandler('NONE')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('NONE')}
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
className={`dropdown-item ${currentMonitoringStatus === 'MONITORING_DAILY' ? 'current' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
clickHandler('MONITORING_DAILY')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('MONITORING_DAILY')}
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
className={`dropdown-item ${currentMonitoringStatus === 'MONITORING_WEEKLY' ? 'current' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
clickHandler('MONITORING_WEEKLY')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('MONITORING_WEEKLY')}
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
className={`dropdown-item ${currentMonitoringStatus === 'MONITORING_MONTHLY' ? 'current' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
clickHandler('MONITORING_MONTHLY')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('MONITORING_MONTHLY')}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
export function SelectedFilesAction({ filesId, callBack }: { filesId: Set<string>, callBack: (value: Set<string>) => void }) {
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
|
if (filesId.size === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadHandler() {
|
||||||
|
if (filesId.size) {
|
||||||
|
const filesArray = Array.from(filesId);
|
||||||
|
const filesString = filesArray.join(', ');
|
||||||
|
toast.success(`${t('download')}: ${filesString}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearHandler() {
|
||||||
|
callBack(new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="selected-files-wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="selected-files-count"
|
||||||
|
>
|
||||||
|
{t('selected-files')}: {filesId.size}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="selected-files-action"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => {
|
||||||
|
downloadHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('download-all')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => {
|
||||||
|
clearHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('deselect')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,876 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useState, useMemo, useEffect, ReactNode } from 'react';
|
|
||||||
import {
|
|
||||||
useReactTable,
|
|
||||||
getCoreRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
ColumnDef,
|
|
||||||
SortingState,
|
|
||||||
ColumnFiltersState,
|
|
||||||
} from '@tanstack/react-table';
|
|
||||||
import { IconImageFile, IconVideoFile, IconAudioFile, IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconShieldExclamation, IconDelete } from '@/app/ui/icons/icons';
|
|
||||||
import { useTranslations, useLocale } from 'next-intl';
|
|
||||||
import DropDownList from '@/app/components/dropDownList';
|
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
||||||
import { getUserFilesData, removeUserFile } from '@/app/actions/fileEntity';
|
|
||||||
import ModalWindow from '@/app/components/modalWindow';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
import { pluralize } from '@/app/lib/pluralize';
|
|
||||||
import { convertBytes } from '@/app/lib/convertBytes';
|
|
||||||
|
|
||||||
type FileType = 'image' | 'video' | 'audio';
|
|
||||||
|
|
||||||
type FileItem = {
|
|
||||||
id: string;
|
|
||||||
fileName: string;
|
|
||||||
fileType: string;
|
|
||||||
violations?: number | undefined;
|
|
||||||
size?: number | undefined;
|
|
||||||
uploadDate?: number;
|
|
||||||
status?: string;
|
|
||||||
_original?: ApiFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ApiFile = {
|
|
||||||
id: string;
|
|
||||||
originalFileName: string;
|
|
||||||
mimeType: string;
|
|
||||||
violations: number;
|
|
||||||
fileSize: number;
|
|
||||||
updatedAt: string;
|
|
||||||
status: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ApiResponse = {
|
|
||||||
files?: ApiFile[];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Иконки для типов файлов
|
|
||||||
const FileTypeIcon = ({ type }: { type: string }) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'image':
|
|
||||||
return <span className="color-image">
|
|
||||||
<IconImageFile />
|
|
||||||
</span>;
|
|
||||||
case 'video':
|
|
||||||
return <span className="color-video">
|
|
||||||
<IconVideoFile />
|
|
||||||
</span>;
|
|
||||||
case 'audio':
|
|
||||||
return <span className="color-audio">
|
|
||||||
<IconAudioFile />
|
|
||||||
</span>;
|
|
||||||
default:
|
|
||||||
return <span>📄</span>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Форматирование даты из timestamp
|
|
||||||
const formatDate = (timestamp: number) => {
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
const day = date.getDate().toString().padStart(2, '0');
|
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
||||||
const year = date.getFullYear();
|
|
||||||
|
|
||||||
return `${day}.${month}.${year}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDateTime = (timestamp: number) => {
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
const hours = date.getHours().toString().padStart(2, '0');
|
|
||||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
||||||
|
|
||||||
return `${hours}:${minutes}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cutFileName = (fileName: string) => {
|
|
||||||
const MAX_FILE_NAME_LENGTH = 26;
|
|
||||||
const lastDotIndex = fileName.lastIndexOf('.');
|
|
||||||
|
|
||||||
if (lastDotIndex <= 0) {
|
|
||||||
return fileName.length > MAX_FILE_NAME_LENGTH ? fileName.substring(0, MAX_FILE_NAME_LENGTH - 3) + '...' : fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nameWithoutExtension = fileName.substring(0, lastDotIndex);
|
|
||||||
const extension = fileName.substring(lastDotIndex);
|
|
||||||
|
|
||||||
if (fileName.length <= MAX_FILE_NAME_LENGTH) {
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3;
|
|
||||||
|
|
||||||
if (maxNameLength <= 0) {
|
|
||||||
return '...' /* + extension */;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nameWithoutExtension.substring(0, maxNameLength) + '...' /* + extension */;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cutFileExtension = (fileName: string) => {
|
|
||||||
const lastDotIndex = fileName.lastIndexOf('.');
|
|
||||||
const extension = fileName.substring(lastDotIndex + 1);
|
|
||||||
return extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|
||||||
const {
|
|
||||||
data: tableData,
|
|
||||||
isLoading,
|
|
||||||
isError,
|
|
||||||
error,
|
|
||||||
} = useQuery<ApiResponse, Error, FileItem[], ['userFilesData', number, number]>({
|
|
||||||
queryKey: ['userFilesData', 1, 10000],
|
|
||||||
queryFn: () => getUserFilesData(1, 10000),
|
|
||||||
|
|
||||||
select: (data: ApiResponse): FileItem[] => {
|
|
||||||
if (!data?.files) return [];
|
|
||||||
|
|
||||||
return data.files.map((item: ApiFile) => {
|
|
||||||
const [datePart, timePart] = item.updatedAt.split(' ');
|
|
||||||
const [day, month, year] = datePart.split('-').map(Number);
|
|
||||||
const [hours, minutes, seconds] = timePart.split(':').map(Number);
|
|
||||||
const newDate = new Date(year, month - 1, day, hours, minutes, seconds).getTime();
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: item.id,
|
|
||||||
fileName: item.originalFileName,
|
|
||||||
fileType: item.mimeType.toLocaleLowerCase(),
|
|
||||||
violations: item.violations,
|
|
||||||
size: item.fileSize,
|
|
||||||
uploadDate: newDate,
|
|
||||||
status: item.status,
|
|
||||||
_original: item
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
// Состояния
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
|
||||||
const [dateFilter, setDateFilter] = useState<string>('all');
|
|
||||||
const [typeFilter, setTypeFilter] = useState<string>(fileType);
|
|
||||||
const [pagination, setPagination] = useState({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: 10,
|
|
||||||
});
|
|
||||||
const [openWindow, setOpenWindow] = useState<boolean>(false);
|
|
||||||
const [openWindowChildren, setOpenWindowChildren] = useState<ReactNode>(null);
|
|
||||||
|
|
||||||
const [isFileLoading, setIsFileLoading] = useState(false);
|
|
||||||
|
|
||||||
const t = useTranslations("Global");
|
|
||||||
const locale = useLocale();
|
|
||||||
|
|
||||||
// Определение колонок
|
|
||||||
const columns = useMemo<ColumnDef<FileItem>[]>(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
accessorKey: 'fileName',
|
|
||||||
header: ({ column }) => (
|
|
||||||
<div className="column start">
|
|
||||||
<span>
|
|
||||||
{t('file')}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
className="sort-button"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="flex items-center space-x-2 table-item">
|
|
||||||
<FileTypeIcon type={row.original.fileType} />
|
|
||||||
<div>
|
|
||||||
<div className="font-medium w-full truncate" title={row.original.fileName}>
|
|
||||||
<span className="font-semibold">
|
|
||||||
{cutFileName(row.original.fileName)}
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
<span className="table-item-extension">{cutFileExtension(row.original.fileName)}</span> <span className="table-item-protected">{row.original?.status}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
enableColumnFilter: false,
|
|
||||||
},
|
|
||||||
/* {
|
|
||||||
accessorKey: 'violations',
|
|
||||||
header: ({ column }) => (
|
|
||||||
<div className="column">
|
|
||||||
<span>
|
|
||||||
{t('violations')}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
className="sort-button"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
let classCollor = () => {
|
|
||||||
let result = ''
|
|
||||||
if (row.original.violations !== undefined) {
|
|
||||||
result = row.original.violations > 0 ? 'text-red-600' : 'text-green-600'
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className={`text-center font-semibold ${classCollor()}`}>
|
|
||||||
{row.original.violations !== undefined ? row.original.violations : '-'}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}, */
|
|
||||||
{
|
|
||||||
accessorKey: 'size',
|
|
||||||
header: ({ column }) => (
|
|
||||||
<div className="column">
|
|
||||||
<span>
|
|
||||||
{t('size')}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
className="sort-button"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="text-center table-item">
|
|
||||||
{row.original.size !== undefined ? convertBytes(row.original.size) : '-'}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'uploadDate',
|
|
||||||
header: ({ column }) => (
|
|
||||||
<div className="column">
|
|
||||||
<span>
|
|
||||||
{t('upload-date')}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
className="sort-button"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<div className="text-center table-item">
|
|
||||||
{row.original.uploadDate ? (
|
|
||||||
<>
|
|
||||||
{formatDate(row.original.uploadDate)}
|
|
||||||
<br />
|
|
||||||
{formatDateTime(row.original.uploadDate)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div>-</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
enableColumnFilter: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'status',
|
|
||||||
header: ({ column }) => (
|
|
||||||
<div className="column">
|
|
||||||
<span>
|
|
||||||
{t('status')}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
||||||
className="sort-button"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
|
||||||
</span>
|
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
|
||||||
</span>
|
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<div className="text-center font-semibold table-item">
|
|
||||||
<span className="table-item-status">
|
|
||||||
{t('status')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'actions',
|
|
||||||
header: () => {
|
|
||||||
return (
|
|
||||||
<div className="column">
|
|
||||||
{t('actions')}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="actions">
|
|
||||||
<div className="actions-group" style={{ display: "none" }}>
|
|
||||||
<button
|
|
||||||
onClick={() => handleView(row.original)}
|
|
||||||
className="bg-blue-500 hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
<IconEye />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleProtect(row.original)}
|
|
||||||
className="bg-violet-500 hover:bg-violet-600"
|
|
||||||
>
|
|
||||||
<IconShieldExclamation />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="actions-group">
|
|
||||||
<button
|
|
||||||
onClick={() => handleDownload(row.original)}
|
|
||||||
disabled={isFileLoading}
|
|
||||||
className="table-action-download"
|
|
||||||
>
|
|
||||||
<IconFileDownload />
|
|
||||||
<span>
|
|
||||||
{t('download')}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleOpenWindowForRemove(row.original)}
|
|
||||||
className="table-action-delete"
|
|
||||||
>
|
|
||||||
<IconDelete />
|
|
||||||
<span>
|
|
||||||
{t('delete')}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
enableSorting: false,
|
|
||||||
enableColumnFilter: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Обработчики действий
|
|
||||||
const handleView = (file: FileItem) => {
|
|
||||||
console.log(`Просмотр файла: ${file.fileName}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownload = async (file: FileItem) => {
|
|
||||||
setIsFileLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/download/${file.id}`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`error: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = await response.blob();
|
|
||||||
const url = window.URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = file.fileName || `file-${file.id}`;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
document.body.removeChild(a);
|
|
||||||
toast.success(`${t('file-is-downloading')} - ${file.fileName}`);
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(t('failed-to-download-file'))
|
|
||||||
} finally {
|
|
||||||
setIsFileLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleProtect = (file: FileItem) => {
|
|
||||||
console.log(`Щиток: ${file.fileName}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOpenWindowForRemove = async (file: FileItem) => {
|
|
||||||
|
|
||||||
setOpenWindowChildren(() => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="text-2xl">
|
|
||||||
{t('you-sure-you-want-to-delete')}
|
|
||||||
</h3>
|
|
||||||
<div className="mt-2 mb-8 text-center">{file.fileName}</div>
|
|
||||||
<div className="flex justify-center gap-4">
|
|
||||||
<button className="btn-primary btn-modal"
|
|
||||||
onClick={() => {
|
|
||||||
deleteMutation.mutate(file.id)
|
|
||||||
}}
|
|
||||||
disabled={deleteMutation.isPending}
|
|
||||||
>
|
|
||||||
{deleteMutation.isPending ? '...' : t('yes')}
|
|
||||||
</button>
|
|
||||||
<button className="btn-primary btn-modal"
|
|
||||||
onClick={() => {
|
|
||||||
setOpenWindow(false);
|
|
||||||
setOpenWindowChildren(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('no')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
setOpenWindow(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteMutation = useMutation({
|
|
||||||
mutationFn: removeUserFile,
|
|
||||||
|
|
||||||
onMutate: async (fileId: string) => {
|
|
||||||
await queryClient.cancelQueries({ queryKey: ['userFilesData'] });
|
|
||||||
|
|
||||||
queryClient.setQueryData<ApiResponse>(['userFilesData'], (old) => {
|
|
||||||
if (!old?.files) return old;
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
files: old.files.filter(file => file.id !== fileId)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onError: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['userFilesData'] });
|
|
||||||
toast.error(t('error'));
|
|
||||||
},
|
|
||||||
|
|
||||||
onSuccess: (response, fileId) => {
|
|
||||||
if (response === fileId) {
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ['userFilesData'],
|
|
||||||
refetchType: 'active'
|
|
||||||
});
|
|
||||||
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ['userFilesInfo']
|
|
||||||
});
|
|
||||||
|
|
||||||
setOpenWindow(false);
|
|
||||||
setOpenWindowChildren(null);
|
|
||||||
toast.success(t('file-has-been-deleted'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Фильтрация по типу файла и дате
|
|
||||||
const filteredData = useMemo(() => {
|
|
||||||
let result = tableData;
|
|
||||||
if (!result) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Фильтр по типу файла
|
|
||||||
if (typeFilter !== 'all') {
|
|
||||||
result = result.filter(item => item.fileType === typeFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Фильтр по дате
|
|
||||||
if (dateFilter !== 'all') {
|
|
||||||
const now = Date.now();
|
|
||||||
const oneDay = 24 * 60 * 60 * 1000;
|
|
||||||
const sevenDays = 7 * oneDay;
|
|
||||||
const thirtyDays = 30 * oneDay;
|
|
||||||
|
|
||||||
switch (dateFilter) {
|
|
||||||
case 'today':
|
|
||||||
const todayStart = new Date().setHours(0, 0, 0, 0);
|
|
||||||
result = result.filter(item => {
|
|
||||||
if (item.uploadDate) {
|
|
||||||
return item.uploadDate >= todayStart
|
|
||||||
} else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'week':
|
|
||||||
const weekAgo = now - sevenDays;
|
|
||||||
result = result.filter(item => {
|
|
||||||
if (item.uploadDate) {
|
|
||||||
return item.uploadDate >= weekAgo;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'month':
|
|
||||||
const monthAgo = now - thirtyDays;
|
|
||||||
result = result.filter(item => {
|
|
||||||
if (item.uploadDate) {
|
|
||||||
return item.uploadDate >= monthAgo;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'older':
|
|
||||||
const monthAgo2 = now - thirtyDays;
|
|
||||||
result = result.filter(item => {
|
|
||||||
if (item.uploadDate) {
|
|
||||||
return item.uploadDate < monthAgo2;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, [tableData, typeFilter, dateFilter]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const currentPageRows = table.getRowModel().rows;
|
|
||||||
const pageCount = table.getPageCount();
|
|
||||||
|
|
||||||
if (currentPageRows.length === 0 && pagination.pageIndex > 0 && pageCount > 0) {
|
|
||||||
table.setPageIndex(pagination.pageIndex - 1);
|
|
||||||
}
|
|
||||||
}, [filteredData, pagination.pageIndex]);
|
|
||||||
|
|
||||||
// Создание таблицы
|
|
||||||
const table = useReactTable({
|
|
||||||
data: filteredData,
|
|
||||||
columns,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnFilters,
|
|
||||||
pagination
|
|
||||||
},
|
|
||||||
autoResetPageIndex: false,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
initialState: {
|
|
||||||
pagination: {
|
|
||||||
pageSize: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const pluralizeFiles = (number: number) => {
|
|
||||||
const translate = [t('file'), t('files-few'), t('files')];
|
|
||||||
return pluralize(number, translate[0], translate[1], translate[2], locale);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="tanstak-table-wrapper">
|
|
||||||
<h3
|
|
||||||
className="tanstak-table-title"
|
|
||||||
>
|
|
||||||
{(() => {
|
|
||||||
switch (fileType) {
|
|
||||||
case 'image':
|
|
||||||
return t('protected-image')
|
|
||||||
case 'video':
|
|
||||||
return t('protected-video')
|
|
||||||
case 'audio':
|
|
||||||
return t('protected-audio')
|
|
||||||
default:
|
|
||||||
return t('table-description')
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</h3>
|
|
||||||
<ModalWindow children={openWindowChildren} state={openWindow} callBack={setOpenWindow} />
|
|
||||||
{/* Фильтры */}
|
|
||||||
<div className="tanstak-table-filtres" style={{ display: "none" }}>
|
|
||||||
<div className="table-filtres-wrapper">
|
|
||||||
<div className="table-filtres-item">
|
|
||||||
<div className="table-filtres-label">{t('date-filter')}:</div>
|
|
||||||
<DropDownList
|
|
||||||
value={dateFilter}
|
|
||||||
translatedValue={(() => {
|
|
||||||
switch (dateFilter) {
|
|
||||||
case 'all':
|
|
||||||
return t('all-dates')
|
|
||||||
case 'week':
|
|
||||||
return t('for-a-week')
|
|
||||||
case 'month':
|
|
||||||
return t('for-a-month')
|
|
||||||
case 'older':
|
|
||||||
return t('older-than-a-month')
|
|
||||||
default:
|
|
||||||
return t('today')
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
callBack={setDateFilter}
|
|
||||||
>
|
|
||||||
<li value="all">
|
|
||||||
{t('all-dates')}
|
|
||||||
</li>
|
|
||||||
<li value="today">
|
|
||||||
{t('today')}
|
|
||||||
</li>
|
|
||||||
<li value="week">
|
|
||||||
{t('for-a-week')}
|
|
||||||
</li>
|
|
||||||
<li value="month">
|
|
||||||
{t('for-a-month')}
|
|
||||||
</li>
|
|
||||||
<li value="older">
|
|
||||||
{t('older-than-a-month')}
|
|
||||||
</li>
|
|
||||||
</DropDownList>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{fileType === "all" && (
|
|
||||||
<div className="table-filtres-item">
|
|
||||||
<div className="table-filtres-label">{t('type-filter')}:</div>
|
|
||||||
<DropDownList
|
|
||||||
value={typeFilter}
|
|
||||||
translatedValue={
|
|
||||||
(() => {
|
|
||||||
switch (typeFilter) {
|
|
||||||
case 'image':
|
|
||||||
return t('images')
|
|
||||||
case 'video':
|
|
||||||
return t('videos')
|
|
||||||
case 'audio':
|
|
||||||
return t('audios')
|
|
||||||
default:
|
|
||||||
return t('all-types')
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
callBack={setTypeFilter}
|
|
||||||
>
|
|
||||||
<li value="all">
|
|
||||||
{t('all-types')}
|
|
||||||
</li>
|
|
||||||
<li value="image">
|
|
||||||
{t('images-few')}
|
|
||||||
</li>
|
|
||||||
<li value="video">
|
|
||||||
{t('videos')}
|
|
||||||
</li>
|
|
||||||
<li value="audio">
|
|
||||||
{t('audios')}
|
|
||||||
</li>
|
|
||||||
</DropDownList>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="table-filtres-item">
|
|
||||||
<div className="table-filtres-label">{t('items-per-page')}:</div>
|
|
||||||
<DropDownList
|
|
||||||
value={table.getState().pagination.pageSize}
|
|
||||||
//@ts-ignore сделал так потому что для table.setPageSize нужна цифра
|
|
||||||
callBack={table.setPageSize}
|
|
||||||
>
|
|
||||||
{[5, 10, 20, 50, 100].map(pageSize => (
|
|
||||||
<li key={pageSize} value={pageSize}>
|
|
||||||
{t('show')} {pageSize}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</DropDownList>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Таблица */}
|
|
||||||
<div className="tanstak-table-block">
|
|
||||||
<table className="tanstak-table">
|
|
||||||
<thead className="tanstak-table-head">
|
|
||||||
{table.getHeaderGroups().map(headerGroup => (
|
|
||||||
<tr key={headerGroup.id}>
|
|
||||||
{headerGroup.headers.map(header => (
|
|
||||||
<th
|
|
||||||
key={header.id}
|
|
||||||
>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: typeof header.column.columnDef.header === 'function'
|
|
||||||
? header.column.columnDef.header(header.getContext())
|
|
||||||
: header.column.columnDef.header as string}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</thead>
|
|
||||||
<tbody className="tanstak-table-body">
|
|
||||||
{table.getRowModel().rows.length > 0 ? (
|
|
||||||
table.getRowModel().rows.map(row => (
|
|
||||||
<tr key={row.id}>
|
|
||||||
{row.getVisibleCells().map(cell => (
|
|
||||||
<td key={cell.id}>
|
|
||||||
{typeof cell.column.columnDef.cell === 'function'
|
|
||||||
? cell.column.columnDef.cell(cell.getContext())
|
|
||||||
: cell.getValue() as string}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={columns.length} className="text-center">
|
|
||||||
{t('no-data-for-selected-filters')}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Пагинация */}
|
|
||||||
<div className="tanstak-table-pagination">
|
|
||||||
<div className="pagination-info">
|
|
||||||
<span className="pagination-info-pages">
|
|
||||||
{t('page')}{' '}
|
|
||||||
<strong>
|
|
||||||
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
<span className="pagination-info-files">
|
|
||||||
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {pluralizeFiles(filteredData.length || 0)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pagination-controls">
|
|
||||||
{table.getCanPreviousPage() && (
|
|
||||||
<button
|
|
||||||
className="arrow"
|
|
||||||
onClick={() => table.firstPage()}
|
|
||||||
disabled={!table.getCanPreviousPage()}
|
|
||||||
>
|
|
||||||
<IconDoubleArrowLeft />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{table.getCanPreviousPage() && (
|
|
||||||
<button
|
|
||||||
className="arrow"
|
|
||||||
onClick={() => table.previousPage()}
|
|
||||||
disabled={!table.getCanPreviousPage()}
|
|
||||||
>
|
|
||||||
<IconArrowLeft />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="pagination-controls-pages">
|
|
||||||
{Array.from({ length: Math.min(5, table.getPageCount()) }, (_, i) => {
|
|
||||||
const pageIndex = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(
|
|
||||||
table.getPageCount() - 5,
|
|
||||||
table.getState().pagination.pageIndex - 2
|
|
||||||
)
|
|
||||||
) + i;
|
|
||||||
|
|
||||||
if (pageIndex < table.getPageCount()) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={pageIndex}
|
|
||||||
className={`${table.getState().pagination.pageIndex === pageIndex
|
|
||||||
? 'current'
|
|
||||||
: 'other'
|
|
||||||
}`}
|
|
||||||
onClick={() => table.setPageIndex(pageIndex)}
|
|
||||||
>
|
|
||||||
{pageIndex + 1}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{table.getCanNextPage() && (
|
|
||||||
<button
|
|
||||||
className="arrow"
|
|
||||||
onClick={() => table.nextPage()}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
<IconArrowRight />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{table.getCanNextPage() && (
|
|
||||||
<button
|
|
||||||
className="arrow"
|
|
||||||
onClick={() => table.lastPage()}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
<IconDoubleArrowRight />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div >
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createContext, useContext, useRef, ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface PdfContextType {
|
||||||
|
downloadPdf: () => Promise<void>;
|
||||||
|
setPdfData: (data: ArrayBuffer | null, fileName: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PdfContext = createContext<PdfContextType | null>(null);
|
||||||
|
|
||||||
|
export function PdfProvider({ children }: { children: ReactNode }) {
|
||||||
|
const pdfDataRef = useRef<ArrayBuffer | null>(null);
|
||||||
|
const fileNameRef = useRef<string>('');
|
||||||
|
|
||||||
|
const setPdfData = (data: ArrayBuffer | null, fileName: string) => {
|
||||||
|
pdfDataRef.current = data;
|
||||||
|
fileNameRef.current = fileName;
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadPdf = async () => {
|
||||||
|
if (!pdfDataRef.current) {
|
||||||
|
console.log('No PDF data available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blob = new Blob([pdfDataRef.current], { type: 'application/pdf' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = fileNameRef.current || 'document.pdf';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error downloading PDF:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PdfContext.Provider value={{ downloadPdf, setPdfData }}>
|
||||||
|
{children}
|
||||||
|
</PdfContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePdf() {
|
||||||
|
const context = useContext(PdfContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('usePdf must be used within PdfProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchActiveNotifications, NotificationBody } from '@/app/actions/notificationActions';
|
||||||
|
|
||||||
|
export interface UseActiveNotifications {
|
||||||
|
notifications: NotificationBody[],
|
||||||
|
unreadCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useActiveNotifications = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['activeNotifications'],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchActiveNotifications()
|
||||||
|
},
|
||||||
|
select: (data: UseActiveNotifications | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
notifications: [],
|
||||||
|
unreadCount: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false,
|
||||||
|
refetchInterval: 10000
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getAllFilesExtensions } from '@/app/lib/prefetch-queries';
|
||||||
|
|
||||||
|
|
||||||
|
export interface allFilesExtensions {
|
||||||
|
videos: string[];
|
||||||
|
audios: string[];
|
||||||
|
images: string[];
|
||||||
|
documents: string[];
|
||||||
|
maxFileSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_EXTENSIONS: allFilesExtensions = {
|
||||||
|
videos: [],
|
||||||
|
audios: [],
|
||||||
|
images: [],
|
||||||
|
documents: [],
|
||||||
|
maxFileSize: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAllFilesExtensions = () => {
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ['allFilesExtensions'],
|
||||||
|
queryFn: getAllFilesExtensions,
|
||||||
|
retry: false,
|
||||||
|
initialData: EMPTY_EXTENSIONS
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...query,
|
||||||
|
data: query.data ?? EMPTY_EXTENSIONS
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchAllNotifications, NotificationBody } from '@/app/actions/notificationActions';
|
||||||
|
|
||||||
|
export interface UseAllNotifications {
|
||||||
|
notifications: NotificationBody[],
|
||||||
|
currentPage: number,
|
||||||
|
totalElements: number,
|
||||||
|
totalPages: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAllNotifications = (page: number, size: number = 10) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['allNotifications', page, size],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchAllNotifications(page, size)
|
||||||
|
},
|
||||||
|
select: (data: UseAllNotifications | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
notifications: [],
|
||||||
|
currentPage: 0,
|
||||||
|
totalElements: 0,
|
||||||
|
totalPages: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false,
|
||||||
|
refetchInterval: 10000
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getFileMatches } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
|
export interface ViolationFileDetail {
|
||||||
|
id: number;
|
||||||
|
url: string;
|
||||||
|
page_url: string;
|
||||||
|
page_title: string;
|
||||||
|
host: string;
|
||||||
|
status: string;
|
||||||
|
created_date: string;
|
||||||
|
file_id: string;
|
||||||
|
country: string;
|
||||||
|
country_code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ViolationFile {
|
||||||
|
violations: ViolationFileDetail[];
|
||||||
|
total_elements: number;
|
||||||
|
total_pages: number;
|
||||||
|
current_page: number;
|
||||||
|
page_size: number;
|
||||||
|
has_next: boolean;
|
||||||
|
has_previous: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useFileViolations = (id: string, page: number, status?: string, violationId?: string, startDate?: string, endDate?: string,) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['fileViolations', id, page, status, violationId, startDate, endDate],
|
||||||
|
queryFn: () => {
|
||||||
|
return getFileMatches(id, page, status, violationId, startDate, endDate)
|
||||||
|
},
|
||||||
|
select: (data: ViolationFile | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getUserPayments } from '@/app/actions/referralsActions';
|
||||||
|
|
||||||
|
export interface ReferralPayments {
|
||||||
|
amount: number,
|
||||||
|
completedAt: string | null,
|
||||||
|
createdAt: string,
|
||||||
|
id: number,
|
||||||
|
processAt: string | null
|
||||||
|
rejectionReason: string | null,
|
||||||
|
status: string,
|
||||||
|
updatedAt: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserReferralPayments {
|
||||||
|
payments: ReferralPayments[],
|
||||||
|
error: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGetReferralPayments = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['referralPayments'],
|
||||||
|
queryFn: getUserPayments,
|
||||||
|
select: (data): UserReferralPayments => {
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
payments: [],
|
||||||
|
error: 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
payments: data.payments,
|
||||||
|
error: data.error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchHistoryView } from '@/app/actions/trackingActions';
|
||||||
|
|
||||||
|
export interface HistoryViewItem {
|
||||||
|
city: string;
|
||||||
|
country: string;
|
||||||
|
viewer: string;
|
||||||
|
document: string;
|
||||||
|
view_date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseHistoryView {
|
||||||
|
documentsCount: number;
|
||||||
|
history: HistoryViewItem[];
|
||||||
|
pageNumber: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalElements: number;
|
||||||
|
totalPages: number;
|
||||||
|
uniqueCityCount: number;
|
||||||
|
uniqueCountryCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHistoryView = (
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
locale: string,
|
||||||
|
sortDirection?: 'asc' | 'desc',
|
||||||
|
sortBy?: string
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['violationStatistic', page, size, locale, sortDirection, sortBy],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchHistoryView(page, size, locale, sortDirection, sortBy)
|
||||||
|
},
|
||||||
|
select: (data: UseHistoryView | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false,
|
||||||
|
placeholderData: (previousData) => previousData // для оптимистичных обновлений
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchLastClaims, ComplaintsCaseQueryParams } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
|
export interface Case {
|
||||||
|
amount: number;
|
||||||
|
content: null | string;
|
||||||
|
created_at: string;
|
||||||
|
description: string;
|
||||||
|
id: number;
|
||||||
|
lawyer: null | string;
|
||||||
|
name: string;
|
||||||
|
page_number: number;
|
||||||
|
page_size: number;
|
||||||
|
priority: "HIGH" | "MEDIUM" | "LOW";
|
||||||
|
total_elements: number;
|
||||||
|
total_pages: number;
|
||||||
|
type: "ACTIVE" | "INACTIVE" | string;
|
||||||
|
updated_at: string;
|
||||||
|
violation_id: number;
|
||||||
|
file_id: string;
|
||||||
|
}
|
||||||
|
export interface useLastCase {
|
||||||
|
content: Case[],
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
totalElements: number,
|
||||||
|
totalPages: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLastCases = (params: ComplaintsCaseQueryParams) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['lastCases', params.page, params.size, params.sort_by, params.sort_direction],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchLastClaims(params)
|
||||||
|
},
|
||||||
|
select: (data: useLastCase | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchLastComplaint, ComplaintsCaseQueryParams } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
|
export interface Complaint {
|
||||||
|
complaint_text: string;
|
||||||
|
created_at: string;
|
||||||
|
email: string;
|
||||||
|
id: number;
|
||||||
|
not_moderated: boolean;
|
||||||
|
status: "CREATED" | string;
|
||||||
|
updated_at: string;
|
||||||
|
violation_id: number;
|
||||||
|
file_id: string;
|
||||||
|
}
|
||||||
|
export interface useLastComplaint {
|
||||||
|
content: Complaint[],
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
totalElements: number,
|
||||||
|
totalPages: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLastComplaints = (params: ComplaintsCaseQueryParams) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['lastComplaints', params.page, params.size, params.sort_by, params.sort_direction],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchLastComplaint(params)
|
||||||
|
},
|
||||||
|
select: (data: useLastComplaint | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchMonitoringInfo } from '@/app/actions/monitoringActions';
|
||||||
|
|
||||||
|
interface MonitoringCount {
|
||||||
|
day: number;
|
||||||
|
month: number;
|
||||||
|
weekly: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMonitoringCount = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['monitoringCount'],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchMonitoringInfo()
|
||||||
|
},
|
||||||
|
select: (data: MonitoringCount | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchMonitoringStats } from '@/app/actions/monitoringActions';
|
||||||
|
|
||||||
|
interface MonitoringStats {
|
||||||
|
total_searches: number;
|
||||||
|
searches_by_status: {
|
||||||
|
processing: number;
|
||||||
|
completed: number;
|
||||||
|
failed: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
recent_searches: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMonitoringStats = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['monitoringStats'],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchMonitoringStats()
|
||||||
|
},
|
||||||
|
select: (data: MonitoringStats | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getPaymentsMethods } from '@/app/actions/paymentAction';
|
||||||
|
|
||||||
|
export interface PaymentsMethods {
|
||||||
|
cardType: string,
|
||||||
|
expiryMonth: string,
|
||||||
|
expiryYear: string,
|
||||||
|
lastFour: string,
|
||||||
|
paymentMethodId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePaymentsMethods = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['paymentsMethods'],
|
||||||
|
queryFn: () => {
|
||||||
|
return getPaymentsMethods()
|
||||||
|
},
|
||||||
|
select: (data: PaymentsMethods[] | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchTrackingStats } from '@/app/actions/trackingActions';
|
||||||
|
|
||||||
|
export interface useTrackingStats {
|
||||||
|
totalViews: number,
|
||||||
|
uniqueIps: number,
|
||||||
|
uniqueUsers: number,
|
||||||
|
documentsCount: number,
|
||||||
|
uniqueCountryCount: number,
|
||||||
|
uniqueCityCount: number,
|
||||||
|
firstViewDate: string,
|
||||||
|
lastViewDate: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTrackingStats = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['trackingStats'],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchTrackingStats()
|
||||||
|
},
|
||||||
|
select: (data: useTrackingStats | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getUserData } from '@/app/actions/action';
|
||||||
|
|
||||||
|
export interface Tariff {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
price: number;
|
||||||
|
tokens: number;
|
||||||
|
maxFilesCount: number;
|
||||||
|
diskSize: number;
|
||||||
|
maxUsers?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TariffInfo {
|
||||||
|
id: string;
|
||||||
|
status: string;
|
||||||
|
startTariff: string;
|
||||||
|
endTariff: string;
|
||||||
|
tariffId: number | null;
|
||||||
|
tariffName: string | null;
|
||||||
|
tokens: number | null;
|
||||||
|
maxFileOnDisk: number | null;
|
||||||
|
currentFileOnDisk: number;
|
||||||
|
currentFileCounts: number;
|
||||||
|
maxFileCounts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserData {
|
||||||
|
fullName: string;
|
||||||
|
company: string | null;
|
||||||
|
email: string;
|
||||||
|
active: boolean;
|
||||||
|
phone: string;
|
||||||
|
genderType: string | null;
|
||||||
|
birthday: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
subscriptionType: string;
|
||||||
|
tariffs: Tariff[];
|
||||||
|
tariffInfo: TariffInfo;
|
||||||
|
permission: number;
|
||||||
|
verifiedStatus?: 'unknow' | 'NOT_VERIFIED' | 'VERIFICATION_IN_PROGRESS' | 'VERIFIED' | 'VERIFICATION_FAILED';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserProfile = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['userData'],
|
||||||
|
queryFn: getUserData,
|
||||||
|
select: (data): UserData => {
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
fullName: '',
|
||||||
|
company: null,
|
||||||
|
email: '',
|
||||||
|
active: false,
|
||||||
|
phone: '',
|
||||||
|
genderType: '',
|
||||||
|
birthday: null,
|
||||||
|
createdAt: '',
|
||||||
|
subscriptionType: '',
|
||||||
|
tariffs: [],
|
||||||
|
tariffInfo: {
|
||||||
|
id: '',
|
||||||
|
status: '',
|
||||||
|
startTariff: '',
|
||||||
|
endTariff: '',
|
||||||
|
tariffId: 0,
|
||||||
|
tariffName: null,
|
||||||
|
tokens: 0,
|
||||||
|
maxFileOnDisk: null,
|
||||||
|
currentFileOnDisk: 0,
|
||||||
|
currentFileCounts: 0,
|
||||||
|
maxFileCounts: 0,
|
||||||
|
},
|
||||||
|
permission: 0,
|
||||||
|
verifiedStatus: 'unknow'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getUserFilesInfo } from '@/app/actions/action';
|
||||||
|
|
||||||
|
export interface FilesInfo {
|
||||||
|
total: {
|
||||||
|
size: number;
|
||||||
|
quantity: number;
|
||||||
|
check: number;
|
||||||
|
violation: number;
|
||||||
|
protected: number;
|
||||||
|
};
|
||||||
|
images: {
|
||||||
|
quantity: number;
|
||||||
|
size: number;
|
||||||
|
check: number;
|
||||||
|
violation: number;
|
||||||
|
protected: number;
|
||||||
|
};
|
||||||
|
videos: {
|
||||||
|
quantity: number;
|
||||||
|
size: number;
|
||||||
|
check: number;
|
||||||
|
violation: number;
|
||||||
|
protected: number;
|
||||||
|
};
|
||||||
|
audios: {
|
||||||
|
quantity: number;
|
||||||
|
size: number;
|
||||||
|
check: number;
|
||||||
|
violation: number;
|
||||||
|
protected: number;
|
||||||
|
};
|
||||||
|
documents: {
|
||||||
|
quantity: number;
|
||||||
|
size: number;
|
||||||
|
check: number;
|
||||||
|
violation: number;
|
||||||
|
protected: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserFilesInfo = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['userFilesInfo'],
|
||||||
|
queryFn: getUserFilesInfo,
|
||||||
|
select: (data): FilesInfo | null => {
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
total: { size: 0, quantity: 0, check: 0, violation: 0, protected: 0 },
|
||||||
|
images: { quantity: 0, size: 0, check: 0, violation: 0, protected: 0 },
|
||||||
|
videos: { quantity: 0, size: 0, check: 0, violation: 0, protected: 0 },
|
||||||
|
audios: { quantity: 0, size: 0, check: 0, violation: 0, protected: 0 },
|
||||||
|
documents: { quantity: 0, size: 0, check: 0, violation: 0, protected: 0 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: {
|
||||||
|
size: data.all_files_size,
|
||||||
|
quantity: data.all_files_quantity,
|
||||||
|
check: data.all_files_check,
|
||||||
|
violation: data.all_files_violation,
|
||||||
|
protected: data.protected_files_count,
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
quantity: data.images_quantity,
|
||||||
|
size: data.images_size,
|
||||||
|
check: data.images_check,
|
||||||
|
violation: data.images_violations,
|
||||||
|
protected: data.protected_image_files_count,
|
||||||
|
},
|
||||||
|
videos: {
|
||||||
|
quantity: data.videos_quantity,
|
||||||
|
size: data.videos_size,
|
||||||
|
check: data.videos_check,
|
||||||
|
violation: data.videos_violations,
|
||||||
|
protected: data.protected_video_files_count,
|
||||||
|
},
|
||||||
|
audios: {
|
||||||
|
quantity: data.audios_quantity,
|
||||||
|
size: data.audios_size,
|
||||||
|
check: data.audios_check,
|
||||||
|
violation: data.audios_violations,
|
||||||
|
protected: data.protected_audio_files_count,
|
||||||
|
},
|
||||||
|
documents: {
|
||||||
|
quantity: data.document_quantity,
|
||||||
|
size: data.document_size,
|
||||||
|
check: data.document_check,
|
||||||
|
violation: data.document_violations,
|
||||||
|
protected: data.protected_document_files_count,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchPaymentsHistory } from '@/app/actions/yooKassa';
|
||||||
|
|
||||||
|
export interface PaymentsData {
|
||||||
|
amount: number,
|
||||||
|
cancellationReason: string | null,
|
||||||
|
createdAt: string,
|
||||||
|
id: number,
|
||||||
|
operationType: string,
|
||||||
|
paymentUuid: string,
|
||||||
|
status: string,
|
||||||
|
updatedAt: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface UserPaymentsData {
|
||||||
|
payments: PaymentsData[],
|
||||||
|
error: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserPaymentsData = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['userPaymentsData'],
|
||||||
|
queryFn: fetchPaymentsHistory,
|
||||||
|
select: (data): UserPaymentsData => {
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
payments: [],
|
||||||
|
error: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
refetchInterval: 10000,
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getSearchStats } from '@/app/actions/searchActions';
|
||||||
|
|
||||||
|
interface SearchType {
|
||||||
|
count: number,
|
||||||
|
fileType: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserSearchData {
|
||||||
|
allCountForPeriod: number;
|
||||||
|
image: SearchType;
|
||||||
|
video: SearchType;
|
||||||
|
audio: SearchType;
|
||||||
|
document: SearchType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserSearchData = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['userSearchData'],
|
||||||
|
queryFn: getSearchStats,
|
||||||
|
select: (data): UserSearchData | null => {
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
allCountForPeriod: 0,
|
||||||
|
image: {
|
||||||
|
count: 0,
|
||||||
|
fileType: 'image'
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
count: 0,
|
||||||
|
fileType: 'video'
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
count: 0,
|
||||||
|
fileType: 'audio'
|
||||||
|
},
|
||||||
|
document: {
|
||||||
|
count: 0,
|
||||||
|
fileType: 'document'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchViolationAnalyticStatistic } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
|
export interface UseViolationStatistic {
|
||||||
|
[key: string]: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useViolationAnalyticStatistic = (group: 'domain' | 'tld') => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['violationAnalyticStatistic', group],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchViolationAnalyticStatistic(group)
|
||||||
|
},
|
||||||
|
select: (data: UseViolationStatistic | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchViolationFinalSummaryStatistic } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
|
export interface UseViolationFinalSummaryStatistic {
|
||||||
|
complaints_without_case: number;
|
||||||
|
total_complaints: number;
|
||||||
|
total_law_cases: number;
|
||||||
|
total_violations: number;
|
||||||
|
violations_with_complaint: number;
|
||||||
|
violations_without_complaint: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useViolationFinalSummaryStatistic = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['violationFinalSummaryStatistic'],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchViolationFinalSummaryStatistic()
|
||||||
|
},
|
||||||
|
select: (data: UseViolationFinalSummaryStatistic | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchViolationStatistic } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
|
export interface UseViolationStatistic {
|
||||||
|
total_violations: number,
|
||||||
|
new_violations: number,
|
||||||
|
in_progress_violations: number,
|
||||||
|
resolved_violations: number,
|
||||||
|
total_complaints: number,
|
||||||
|
total_law_cases: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useViolationStatistic = (id?: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['violationStatistic', id],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchViolationStatistic(id)
|
||||||
|
},
|
||||||
|
select: (data: UseViolationStatistic | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
export const MOBILE = 'MOBILE'
|
||||||
|
export const TABLET = 'TABLET'
|
||||||
|
export const DESKTOP = 'DESKTOP'
|
||||||
|
|
||||||
|
const getDevice = (width: number) => {
|
||||||
|
if (width < 768) return MOBILE
|
||||||
|
else if (width < 992) return TABLET
|
||||||
|
else return DESKTOP
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useViewport() {
|
||||||
|
const [viewport, setViewport] = useState({
|
||||||
|
width: 0,
|
||||||
|
device: 'DESKTOP'
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setViewport({
|
||||||
|
width: window.innerWidth,
|
||||||
|
device: getDevice(window.innerWidth)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResize();
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return viewport;
|
||||||
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
export function convertBytes(bytes: number) {
|
export function convertBytes(bytes: number) {
|
||||||
|
if (!bytes) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
|
||||||
if (bytes === 0) return '0 B';
|
if (bytes === 0) return '0 B';
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Форматирование даты из timestamp
|
||||||
|
export const formatDate = (timestamp: number | string) => {
|
||||||
|
if (!timestamp) {
|
||||||
|
return '---'
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const day = date.getDate().toString().padStart(2, '0');
|
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
const year = date.getFullYear();
|
||||||
|
|
||||||
|
return `${day}.${month}.${year}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatDateTime = (timestamp: number | string) => {
|
||||||
|
if (!timestamp) {
|
||||||
|
return '---'
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const hours = date.getHours().toString().padStart(2, '0');
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||||
|
|
||||||
|
return `${hours}:${minutes}`;
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
export const getFileType = (fileName: string, allowedExtensions: {
|
||||||
|
images: string[],
|
||||||
|
videos: string[],
|
||||||
|
audios: string[],
|
||||||
|
documents: string[]
|
||||||
|
}): 'image' | 'video' | 'audio' | 'document' | 'unknown' => {
|
||||||
|
|
||||||
|
const lastDotIndex = fileName.lastIndexOf('.');
|
||||||
|
const extension = fileName.substring(lastDotIndex + 1).toLowerCase();
|
||||||
|
|
||||||
|
if (allowedExtensions.images.includes(extension)) {
|
||||||
|
return 'image';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedExtensions.videos.includes(extension)) {
|
||||||
|
return 'video';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedExtensions.audios.includes(extension)) {
|
||||||
|
return 'audio';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedExtensions.documents.includes(extension)) {
|
||||||
|
return 'document';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export function getProcents(
|
||||||
|
currentNumber: number | undefined,
|
||||||
|
totalNumber: number | undefined,
|
||||||
|
decimals: number = 0
|
||||||
|
): number {
|
||||||
|
if (!currentNumber && !totalNumber) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalNumber === 0 || typeof currentNumber === 'undefined' || typeof totalNumber === 'undefined') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const percentage = (currentNumber / totalNumber) * 100;
|
||||||
|
const factor = Math.pow(10, decimals);
|
||||||
|
|
||||||
|
return Math.round(percentage * factor) / factor;
|
||||||
|
}
|
||||||
@@ -1,6 +1,35 @@
|
|||||||
import { QueryClient } from '@tanstack/react-query';
|
import { QueryClient } from '@tanstack/react-query';
|
||||||
import { getUserData, getUserFilesInfo } from '@/app/actions/action';
|
import { getUserData, getUserFilesInfo, getBuildData } from '@/app/actions/action';
|
||||||
import { getUserFilesData } from '@/app/actions/fileEntity';
|
import { getUserFilesData } from '@/app/actions/fileEntity';
|
||||||
|
import { fetchReferralUserStats } from '@/app/actions/referralsActions';
|
||||||
|
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
|
||||||
|
|
||||||
|
export interface allFilesExtensions {
|
||||||
|
videos: string[];
|
||||||
|
audios: string[];
|
||||||
|
images: string[];
|
||||||
|
documents: string[];
|
||||||
|
maxFileSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllFilesExtensions(): Promise<allFilesExtensions> {
|
||||||
|
const [video, audio, image, document] = await Promise.all([
|
||||||
|
getAllowedFilesExtensions('video'),
|
||||||
|
getAllowedFilesExtensions('audio'),
|
||||||
|
getAllowedFilesExtensions('image'),
|
||||||
|
getAllowedFilesExtensions('document')
|
||||||
|
]);
|
||||||
|
|
||||||
|
const result: allFilesExtensions = {
|
||||||
|
videos: video?.file_extension ? video.file_extension : [],
|
||||||
|
audios: audio?.file_extension ? audio.file_extension : [],
|
||||||
|
images: image?.file_extension ? image.file_extension : [],
|
||||||
|
documents: document?.file_extension ? document.file_extension : [],
|
||||||
|
maxFileSize: video?.max_file_size ? video.max_file_size : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export async function prefetchLayoutQueries(queryClient: QueryClient) {
|
export async function prefetchLayoutQueries(queryClient: QueryClient) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -11,7 +40,15 @@ export async function prefetchLayoutQueries(queryClient: QueryClient) {
|
|||||||
queryClient.prefetchQuery({
|
queryClient.prefetchQuery({
|
||||||
queryKey: ['userFilesData'],
|
queryKey: ['userFilesData'],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return getUserFilesData(1, 10000);
|
return getUserFilesData({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
sortBy: '',
|
||||||
|
sortOrder: '',
|
||||||
|
type: '',
|
||||||
|
date: '',
|
||||||
|
query: ''
|
||||||
|
});
|
||||||
},
|
},
|
||||||
staleTime: 30 * 1000
|
staleTime: 30 * 1000
|
||||||
}),
|
}),
|
||||||
@@ -19,5 +56,25 @@ export async function prefetchLayoutQueries(queryClient: QueryClient) {
|
|||||||
queryKey: ['userFilesInfo'],
|
queryKey: ['userFilesInfo'],
|
||||||
queryFn: () => getUserFilesInfo()
|
queryFn: () => getUserFilesInfo()
|
||||||
}),
|
}),
|
||||||
|
queryClient.prefetchQuery({
|
||||||
|
queryKey: ['referralUserStats'],
|
||||||
|
queryFn: () => fetchReferralUserStats()
|
||||||
|
}),
|
||||||
|
queryClient.prefetchQuery({
|
||||||
|
queryKey: ['backendBuildDate'],
|
||||||
|
queryFn: async () => {
|
||||||
|
try {
|
||||||
|
const response = await getBuildData();
|
||||||
|
return response.buildTimeBack ?? null;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
queryClient.prefetchQuery({
|
||||||
|
queryKey: ['allFilesExtensions'],
|
||||||
|
queryFn: () => getAllFilesExtensions(),
|
||||||
|
staleTime: 5 * 60 * 1000
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ interface ValidationConfig {
|
|||||||
|
|
||||||
/* const EMAIL_REGEX = /^([a-z0-9.\-_]+|[а-яё0-9.\-_]+)@(([a-z0-9.\-]+)\.([a-z]{2,})|([a-z0-9.\-]+)\.([а-яё]{2,})|([а-яё0-9.\-]+)\.([a-z]{2,})|([а-яё0-9.\-]+)\.([а-яё]{2,}))$/i; */
|
/* const EMAIL_REGEX = /^([a-z0-9.\-_]+|[а-яё0-9.\-_]+)@(([a-z0-9.\-]+)\.([a-z]{2,})|([a-z0-9.\-]+)\.([а-яё]{2,})|([а-яё0-9.\-]+)\.([a-z]{2,})|([а-яё0-9.\-]+)\.([а-яё]{2,}))$/i; */
|
||||||
|
|
||||||
export function createValidationSchema(config: ValidationConfig) {
|
export function createValidationSchema(config: ValidationConfig, accountType?: 'b2b' | 'b2c') {
|
||||||
const schema: Record<string, any> = {};
|
const schema: Record<string, any> = {};
|
||||||
|
|
||||||
// fullName
|
// fullName
|
||||||
@@ -38,6 +38,22 @@ export function createValidationSchema(config: ValidationConfig) {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fullName
|
||||||
|
if (config.companyName && accountType !== 'b2c') {
|
||||||
|
schema.companyName = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(config.companyName.minSize?.value || 1, {
|
||||||
|
error: config.companyName.minSize?.message || 'register-error-company-name-min'
|
||||||
|
})
|
||||||
|
.max(config.companyName.maxSize?.value || 100, {
|
||||||
|
error: config.companyName.maxSize?.message || 'register-error-company-name-max'
|
||||||
|
})
|
||||||
|
.regex(new RegExp(config.companyName.allowedSymbols?.value || '.*'), {
|
||||||
|
message: config.companyName.allowedSymbols?.message || 'register-error-company-name-not-allowed-symbols'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// email
|
// email
|
||||||
if (config.email) {
|
if (config.email) {
|
||||||
let emailSchema = z
|
let emailSchema = z
|
||||||
@@ -147,73 +163,33 @@ export function createValidationSchema(config: ValidationConfig) {
|
|||||||
schema.password = passwordSchema;
|
schema.password = passwordSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
// companyName (опциональное поле)
|
// inn
|
||||||
if (config.companyName) {
|
if (config.inn && accountType !== 'b2c') {
|
||||||
let companySchema = z.string();
|
let innSchema = z.string();
|
||||||
|
|
||||||
// Если поле опциональное, добавляем .optional().nullable()
|
// Минимальная длина
|
||||||
if (config.companyName.optional?.value) {
|
if (config.inn.minSize) {
|
||||||
companySchema = companySchema
|
innSchema = innSchema.min(config.inn.minSize.value, {
|
||||||
.optional()
|
error: config.inn.minSize.message
|
||||||
.nullable()
|
});
|
||||||
.transform((val) => (val === null || val === undefined ? '' : val))
|
|
||||||
.refine(
|
|
||||||
(value) => {
|
|
||||||
// Для пустого значения пропускаем проверки
|
|
||||||
if (!value || value.trim().length === 0) return true;
|
|
||||||
|
|
||||||
// Проверка минимальной длины
|
|
||||||
const minLength = config.companyName?.minSize?.value || 2;
|
|
||||||
return value.trim().length >= minLength;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
message: config.companyName.minSize?.message || 'register-error-company-name-min'
|
|
||||||
}
|
|
||||||
) as unknown as z.ZodString;
|
|
||||||
} else {
|
|
||||||
// Если поле обязательное
|
|
||||||
if (config.companyName.minSize) {
|
|
||||||
companySchema = companySchema.min(config.companyName.minSize.value, {
|
|
||||||
error: config.companyName.minSize.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Максимальная длина (применяется всегда)
|
// Максимальная длина
|
||||||
if (config.companyName.maxSize) {
|
if (config.inn.maxSize) {
|
||||||
companySchema = companySchema.refine(
|
innSchema = innSchema.max(config.inn.maxSize.value, {
|
||||||
(value) => {
|
error: config.inn.maxSize.message
|
||||||
if (config.companyName?.optional?.value && (!value || value.trim().length === 0)) {
|
});
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const maxLength = config.companyName?.maxSize?.value || 200;
|
|
||||||
return (value || '').trim().length <= maxLength;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
message: config.companyName.maxSize.message
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Допустимые символы
|
// Допустимые символы
|
||||||
if (config.companyName.allowedSymbols?.value) {
|
if (config.inn.allowedSymbols) {
|
||||||
companySchema = companySchema.refine(
|
innSchema = innSchema.regex(
|
||||||
(value) => {
|
new RegExp(config.inn.allowedSymbols.value),
|
||||||
if (config.companyName?.optional?.value && (!value || value.trim().length === 0)) {
|
{ message: config.inn.allowedSymbols.message }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const regex = new RegExp(config.companyName.allowedSymbols!.value);
|
|
||||||
return regex.test(value || '');
|
|
||||||
},
|
|
||||||
{
|
|
||||||
message: config.companyName.allowedSymbols.message
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
companySchema = companySchema.transform((val) => val?.trim() || '') as unknown as z.ZodString;
|
schema.inn = innSchema;
|
||||||
|
|
||||||
schema.companyName = companySchema;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Дополнительные поля для формы регистрации
|
// Дополнительные поля для формы регистрации
|
||||||
@@ -227,3 +203,56 @@ export function createValidationSchema(config: ValidationConfig) {
|
|||||||
path: ['confirm_password'],
|
path: ['confirm_password'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createValidationSchemaForPassword(config: ValidationConfig) {
|
||||||
|
const schema: Record<string, any> = {};
|
||||||
|
|
||||||
|
// password
|
||||||
|
if (config.password) {
|
||||||
|
let passwordSchema = z
|
||||||
|
.string()
|
||||||
|
.min(config.password.minSize?.value || 8, {
|
||||||
|
error: config.password.minSize?.message || 'register-error-password-symbols'
|
||||||
|
})
|
||||||
|
.max(config.password.maxSize?.value || 124, {
|
||||||
|
error: config.password.maxSize?.message || 'register-error-max-password'
|
||||||
|
})
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Проверка допустимых символов
|
||||||
|
if (config.password.allowedSymbols?.value) {
|
||||||
|
passwordSchema = passwordSchema.regex(
|
||||||
|
new RegExp(config.password.allowedSymbols.value),
|
||||||
|
{ message: config.password.allowedSymbols.message }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка наличия цифры
|
||||||
|
if (config.password.requiredDigit?.value) {
|
||||||
|
passwordSchema = passwordSchema.regex(
|
||||||
|
/[0-9]/,
|
||||||
|
{ message: config.password.requiredDigit.message }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка наличия буквы
|
||||||
|
if (config.password.requiredLetter?.value) {
|
||||||
|
passwordSchema = passwordSchema.regex(
|
||||||
|
/[a-zA-Zа-яёА-ЯЁ]/,
|
||||||
|
{ message: config.password.requiredLetter.message }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.password = passwordSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Дополнительные поля для формы регистрации
|
||||||
|
schema.confirm_password = z.string();
|
||||||
|
|
||||||
|
return z
|
||||||
|
.object(schema)
|
||||||
|
.refine((data) => data.password === data.confirm_password, {
|
||||||
|
message: 'repeat-password',
|
||||||
|
path: ['confirm_password'],
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
"message": "register-error-company-name-min"
|
"message": "register-error-company-name-min"
|
||||||
},
|
},
|
||||||
"allowedSymbols": {
|
"allowedSymbols": {
|
||||||
"value": "^[a-zA-Zа-яА-ЯёЁ0-9\\s\\-&.,'\"()]+$",
|
"value": "^[a-zA-Zа-яА-ЯёЁ0-9\\s\\-&.,'\"()]*$",
|
||||||
"message": "register-error-company-name-not-allowed-symbols"
|
"message": "register-error-company-name-not-allowed-symbols"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -80,12 +80,26 @@
|
|||||||
"message": "register-error-phone-max"
|
"message": "register-error-phone-max"
|
||||||
},
|
},
|
||||||
"minSize": {
|
"minSize": {
|
||||||
"value": 2,
|
"value": 12,
|
||||||
"message": "register-error-phone-min"
|
"message": "register-error-phone-min"
|
||||||
},
|
},
|
||||||
"allowedSymbols": {
|
"allowedSymbols": {
|
||||||
"value": "^[0-9\\+]+$",
|
"value": "^[0-9\\+]+$",
|
||||||
"message": "register-error-phone-not-allowed-symbols"
|
"message": "register-error-phone-not-allowed-symbols"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"inn": {
|
||||||
|
"maxSize": {
|
||||||
|
"value": 10,
|
||||||
|
"message": "register-error-inn-max"
|
||||||
|
},
|
||||||
|
"minSize": {
|
||||||
|
"value": 10,
|
||||||
|
"message": "register-error-inn-min"
|
||||||
|
},
|
||||||
|
"allowedSymbols": {
|
||||||
|
"value": "^[0-9]+$",
|
||||||
|
"message": "register-error-inn-not-allowed-symbols"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Providers from './getQueryServer';
|
||||||
|
|
||||||
|
export function WatchDocProviders({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Providers>
|
||||||
|
{children}
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user