From bd869798be175c1958755ffcb4729df5240a91b9 Mon Sep 17 00:00:00 2001 From: smanylov Date: Sat, 24 Jan 2026 13:19:48 +0700 Subject: [PATCH 001/119] edit small screen layout --- src/app/actions/auth.ts | 1 + src/app/styles/global-styles.scss | 16 ++++ src/app/styles/header.scss | 26 ++++++ src/app/styles/pages-styles.scss | 79 +++++++++++++++++++ src/app/styles/settings.scss | 1 - .../setting-referral-program.tsx | 4 +- 6 files changed, 124 insertions(+), 3 deletions(-) diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index bd6a870..15b4213 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -222,6 +222,7 @@ export async function registration( message_body: { token: string }, message_code: string }; + console.log(parsed); if (parsed.message_desc === 'Operation successful') { await createSession(parsed.message_body.token, email as string); } else { diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index c2f5c4b..b80125b 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -41,6 +41,10 @@ color: v.$white; max-width: calc(100vw - var(--side-bar-width) - 60px); + @media (max-width: 440px) { + max-width: calc(100vw - var(--side-bar-width) - 30px); + } + h1 { font-size: 2rem; font-weight: 700; @@ -73,6 +77,10 @@ display: flex; gap: 28px; align-items: center; + + @media (max-width: 440px) { + flex-direction: column; + } } .header-stat-item { @@ -101,6 +109,10 @@ padding: 30px; background: v.$bg-light; min-height: 100vh; + + @media (max-width: 440px) { + padding: 15px; + } } .main-containter { @@ -121,6 +133,10 @@ &.auto { width: auto; } + + @media (max-width: 440px) { + max-width: calc(100vw - var(--side-bar-width) - 30px); + } } .btn { diff --git a/src/app/styles/header.scss b/src/app/styles/header.scss index e534826..e978741 100644 --- a/src/app/styles/header.scss +++ b/src/app/styles/header.scss @@ -14,11 +14,19 @@ padding: 20px 20px; } + @media (max-width: 440px) { + padding: 10px 10px; + } + .header-action { display: flex; align-items: center; gap: 15px; + @media (max-width: 440px) { + gap: 10px; + } + .icon-btn { width: 40px; height: 40px; @@ -36,6 +44,11 @@ background: v.$bg-hover; transform: translateY(-2px); } + + @media (max-width: 440px) { + width: 30px; + height: 30px; + } } } @@ -68,12 +81,20 @@ @media (max-width: 490px) { padding: 8px; } + + @media (max-width: 440px) { + padding: 8px; + } } .tokens-count { font-weight: 700; font-size: 14px; letter-spacing: 0.5px; + + @media (max-width: 440px) { + font-size: 12px; + } } .tokens-label { @@ -192,6 +213,11 @@ cursor: pointer; transition: all 0.3s; text-transform: uppercase; + + @media (max-width: 440px) { + width: 30px; + height: 30px; + } } .user-dropdown { diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 8adc088..0f9b21e 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -941,6 +941,10 @@ grid-template-columns: auto; } + @media (max-width: 440px) { + max-width: calc(100vw - var(--side-bar-width) - 30px); + } + .stat-card { background: white; padding: 18px; @@ -1008,6 +1012,10 @@ text-align: center; max-width: calc(100vw - var(--side-bar-width) - 60px); + @media (max-width: 440px) { + max-width: calc(100vw - var(--side-bar-width) - 30px); + } + .referral-link-input { display: flex; gap: 15px; @@ -1032,6 +1040,10 @@ align-items: center; justify-content: center; white-space: nowrap; + + @media (max-width: 490px) { + font-size: 10px; + } } .copy-btn { @@ -1276,6 +1288,16 @@ margin-bottom: 30px; border: none; + + @media (max-width: 440px) { + padding: 0; + box-shadow: none; + } + + .settings-form-wrapper { + width: 100%; + } + .form-group { margin-bottom: 20px; @@ -1284,6 +1306,10 @@ margin-bottom: 8px; font-weight: 600; color: v.$text-s; + + @media (max-width: 440px) { + font-size: 12px; + } } input, @@ -1294,6 +1320,11 @@ border-radius: 10px; font-size: 14px; transition: border-color 0.3s; + + @media (max-width: 440px) { + font-size: 12px; + padding: 10px; + } } } @@ -1302,6 +1333,10 @@ align-items: center; gap: 10px; margin-bottom: 15px; + + @media (max-width: 440px) { + font-size: 14px; + } } .btn-primary { @@ -1322,6 +1357,7 @@ grid-template-columns: repeat(5, 1fr); gap: 20px; margin-bottom: 40px; + max-width: calc(100vw - var(--side-bar-width) - 60px); .stat-card.matches { --card-color-1: #3b82f6; @@ -1420,6 +1456,11 @@ @media (max-width: 860px) { grid-template-columns: repeat(2, 1fr); } + + @media (max-width: 440px) { + grid-template-columns: auto; + max-width: calc(100vw - var(--side-bar-width) - 30px); + } } .dashboard-main-grid { @@ -1453,6 +1494,14 @@ max-width: calc(100vw - var(--side-bar-width) - 60px); } } + + @media (max-width: 440px) { + + .left-column, + .right-column { + max-width: calc(100vw - var(--side-bar-width) - 30px); + } + } } .content-section { @@ -1591,6 +1640,8 @@ grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 40px; + width: 100%; + max-width: calc(100vw - var(--side-bar-width) - 60px); .type-card.images { --card-color-1: #ff6b8a; @@ -1705,6 +1756,11 @@ @media (max-width: 1360px) { grid-template-columns: repeat(2, 1fr); } + + @media (max-width: 440px) { + grid-template-columns: auto; + max-width: calc(100vw - var(--side-bar-width) - 30px); + } } .dashboard-limits-section { @@ -1891,6 +1947,12 @@ display: flex; justify-content: space-between; align-items: center; + max-width: calc(100vw - var(--side-bar-width) - 60px); + + @media (max-width: 440px) { + flex-direction: column; + max-width: calc(100vw - var(--side-bar-width) - 30px); + } .last-check-title { font-size: 14px; @@ -2143,6 +2205,10 @@ margin-bottom: 2rem; max-width: calc(100vw - var(--side-bar-width) - 60px); + @media (max-width: 440px) { + max-width: calc(100vw - var(--side-bar-width) - 30px); + } + &-stat-card { background: white; padding: 1.5rem; @@ -2262,6 +2328,10 @@ box-shadow: 0 10px 40px rgba(99, 102, 241, 0.1); max-width: calc(100vw - var(--side-bar-width) - 60px); + @media (max-width: 440px) { + max-width: calc(100vw - var(--side-bar-width) - 30px); + } + .my-cases-header { display: flex; align-items: center; @@ -2419,6 +2489,11 @@ gap: 24px; max-width: calc(100vw - var(--side-bar-width) - 60px); + @media (max-width: 440px) { + max-width: calc(100vw - var(--side-bar-width) - 30px); + flex-direction: column; + } + .check-all-left { flex: 1; position: relative; @@ -2474,6 +2549,10 @@ margin-bottom: 24px; max-width: calc(100vw - var(--side-bar-width) - 60px); + @media (max-width: 440px) { + max-width: calc(100vw - var(--side-bar-width) - 30px); + } + .section-header { display: flex; align-items: center; diff --git a/src/app/styles/settings.scss b/src/app/styles/settings.scss index 4d655ed..e4b4086 100644 --- a/src/app/styles/settings.scss +++ b/src/app/styles/settings.scss @@ -7,7 +7,6 @@ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); border: 1px solid #e2e8f0; transition: all 0.3s ease; - overflow: hidden; } .form-section { diff --git a/src/app/ui/referral-page/setting-referral-program.tsx b/src/app/ui/referral-page/setting-referral-program.tsx index 6c4e1fe..f4fdfb1 100644 --- a/src/app/ui/referral-page/setting-referral-program.tsx +++ b/src/app/ui/referral-page/setting-referral-program.tsx @@ -8,7 +8,7 @@ export default function SettingReferralProgram() {
-
+

πŸ’³ Настройки Π²Ρ‹ΠΏΠ»Π°Ρ‚

@@ -32,7 +32,7 @@ export default function SettingReferralProgram() {
-
+

πŸ”” УвСдомлСния

From 2be8f81a5b221fca27637316c081afb0c53fff6e Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 26 Jan 2026 13:26:21 +0700 Subject: [PATCH 002/119] refactor --- .../[locale]/pages/{marking-photo => marking-images}/page.tsx | 0 src/app/ui/dashboard/protection-overview.tsx | 2 +- src/app/ui/navigation/nav-link-dropdown.tsx | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/app/[locale]/pages/{marking-photo => marking-images}/page.tsx (100%) diff --git a/src/app/[locale]/pages/marking-photo/page.tsx b/src/app/[locale]/pages/marking-images/page.tsx similarity index 100% rename from src/app/[locale]/pages/marking-photo/page.tsx rename to src/app/[locale]/pages/marking-images/page.tsx diff --git a/src/app/ui/dashboard/protection-overview.tsx b/src/app/ui/dashboard/protection-overview.tsx index 93b0d47..52a0c1a 100644 --- a/src/app/ui/dashboard/protection-overview.tsx +++ b/src/app/ui/dashboard/protection-overview.tsx @@ -180,7 +180,7 @@ export default function ProtectionOverview() {
- + {t('photo-marking')} diff --git a/src/app/ui/navigation/nav-link-dropdown.tsx b/src/app/ui/navigation/nav-link-dropdown.tsx index 817db8a..c7dc7a0 100644 --- a/src/app/ui/navigation/nav-link-dropdown.tsx +++ b/src/app/ui/navigation/nav-link-dropdown.tsx @@ -18,7 +18,7 @@ export default function DropDownList() { const links = [ { name: t('photo-marking'), - href: '/pages/marking-photo' + href: '/pages/marking-images' }, { name: t('video-marking'), From 6ef23862e17930254fd07953bc4c7a6ef1354942 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 26 Jan 2026 16:09:39 +0700 Subject: [PATCH 003/119] add protected info --- src/app/components/tanstakTable.tsx | 7 ++-- src/app/styles/pages-styles.scss | 3 +- .../ui/dashboard/new/dashboard-files-info.tsx | 14 ++++---- .../ui/dashboard/new/dashboard-user-stats.tsx | 2 +- .../new/dashborad-limits-section.tsx | 36 ++++++++++++++----- .../marking-page/new/protection-statistic.tsx | 25 ++++++++----- .../new/my-content-stats-overview.tsx | 2 +- src/i18n/messages/en.json | 7 +++- src/i18n/messages/ru.json | 7 +++- 9 files changed, 73 insertions(+), 30 deletions(-) diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index a334407..d104cce 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -31,6 +31,7 @@ type FileItem = { size?: number | undefined; uploadDate?: number; status?: string; + protectStatus: string; _original?: ApiFile; }; @@ -42,6 +43,7 @@ type ApiFile = { fileSize: number; updatedAt: string; status: string; + protectStatus: string; }; type ApiResponse = { @@ -143,6 +145,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { size: item.fileSize, uploadDate: newDate, status: item.status, + protectStatus: item.protectStatus, _original: item }; }); @@ -207,7 +210,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { {cutFileName(row.original.fileName)}
- {cutFileExtension(row.original.fileName)} {row.original?.status} + {cutFileExtension(row.original.fileName)} {t(row.original?.protectStatus)}
@@ -364,7 +367,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { return (
- {t('status')} + {row.original.status}
) diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 0f9b21e..f478e23 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -727,7 +727,8 @@ .table-item-status { padding: 2px 10px; border-radius: 15px; - background-color: v.$b-color-2; + background-color: #dcfce7; + color: #166534; } .actions { diff --git a/src/app/ui/dashboard/new/dashboard-files-info.tsx b/src/app/ui/dashboard/new/dashboard-files-info.tsx index cdee67a..3bb79ab 100644 --- a/src/app/ui/dashboard/new/dashboard-files-info.tsx +++ b/src/app/ui/dashboard/new/dashboard-files-info.tsx @@ -32,8 +32,8 @@ export default function DashboardFilesInfo() { {filesInfo?.images_size ? convertBytes(filesInfo?.images_size) : 0}
- {t('checks')} - {filesInfo?.images_check ? filesInfo?.images_check : 0} + {t('PROTECTED')} + {filesInfo?.protected_image_files_count ? filesInfo?.protected_image_files_count : 0}
{t('violations')} @@ -54,8 +54,8 @@ export default function DashboardFilesInfo() { {filesInfo?.videos_size ? convertBytes(filesInfo?.videos_size) : 0}
- {t('checks')} - {filesInfo?.videos_check ? filesInfo?.videos_check : 0} + {t('PROTECTED')} + {filesInfo?.protected_video_files_count ? filesInfo?.protected_video_files_count : 0}
{t('violations')} @@ -76,8 +76,8 @@ export default function DashboardFilesInfo() { {filesInfo?.audios_size ? convertBytes(filesInfo?.audios_size) : 0}
- {t('checks')} - {filesInfo?.audios_check ? filesInfo?.audios_check : 0} + {t('PROTECTED')} + {filesInfo?.protected_audio_files_count ? filesInfo?.protected_audio_files_count : 0}
{t('violations')} @@ -98,7 +98,7 @@ export default function DashboardFilesInfo() { {filesInfo?.documents_check ? convertBytes(filesInfo?.documents_check) : 0}
- {t('checks')} + {t('PROTECTED')} {filesInfo?.documents_check ? filesInfo?.documents_check : 0}
diff --git a/src/app/ui/dashboard/new/dashboard-user-stats.tsx b/src/app/ui/dashboard/new/dashboard-user-stats.tsx index f02a15f..cfdae78 100644 --- a/src/app/ui/dashboard/new/dashboard-user-stats.tsx +++ b/src/app/ui/dashboard/new/dashboard-user-stats.tsx @@ -23,7 +23,7 @@ export default function DashboardUserStats() {
Π—ΠΠ©Π˜Π©ΠΠΠΠ«Π• ЀАЙЛЫ
-
0
+
{filesInfo?.protected_files_count ? filesInfo?.protected_files_count : 0}
Π€Π°ΠΉΠ»ΠΎΠ² ΠΏΠΎΠ΄ Π·Π°Ρ‰ΠΈΡ‚ΠΎΠΉ
πŸ›‘οΈ
diff --git a/src/app/ui/dashboard/new/dashborad-limits-section.tsx b/src/app/ui/dashboard/new/dashborad-limits-section.tsx index 08465d8..ed7d41d 100644 --- a/src/app/ui/dashboard/new/dashborad-limits-section.tsx +++ b/src/app/ui/dashboard/new/dashborad-limits-section.tsx @@ -1,4 +1,24 @@ +'use client' + +import { useQuery } from '@tanstack/react-query'; +import { getUserFilesInfo } from '@/app/actions/action'; +import { convertBytes } from '@/app/lib/convertBytes'; + export default function DahboardLimitsSection() { + const { + data: filesInfo, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['userFilesInfo'], + queryFn: getUserFilesInfo, + }); + + function getProcents(currentNumber: number, totalNumber: number) { + return Math.round(currentNumber / (totalNumber / 100)); + } + return (

πŸ’° ИспользованиС Π»ΠΈΠΌΠΈΡ‚ΠΎΠ²

@@ -9,10 +29,10 @@ export default function DahboardLimitsSection() { πŸ” ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ
-
520 / 10,000
+
{filesInfo?.all_files_check ? filesInfo?.all_files_check : 0} / 10,000
-
+
5% @@ -25,13 +45,13 @@ export default function DahboardLimitsSection() { πŸ’Ύ Π₯Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅
-
790.2 / 1,024 ΠœΠ‘
+
{filesInfo?.all_files_size ? convertBytes(filesInfo?.all_files_size) : 0} / 1,024 ΠœΠ‘
-
+
- 77% + {getProcents(filesInfo?.all_files_size, 1000000000)}%
@@ -40,13 +60,13 @@ export default function DahboardLimitsSection() { πŸ“ Π€Π°ΠΉΠ»Ρ‹
-
386 / 500
+
{filesInfo?.all_files_quantity ? filesInfo?.all_files_quantity : 0} / 500
-
+
- 77% + {getProcents(filesInfo?.all_files_quantity, 500)}%
diff --git a/src/app/ui/marking-page/new/protection-statistic.tsx b/src/app/ui/marking-page/new/protection-statistic.tsx index e00d297..ee8f722 100644 --- a/src/app/ui/marking-page/new/protection-statistic.tsx +++ b/src/app/ui/marking-page/new/protection-statistic.tsx @@ -8,32 +8,37 @@ import { convertBytes } from '@/app/lib/convertBytes'; type FilesInfo = { totalSize: number; totalCount: number; + totalProtected: number; }; export default function ProtectionStatistic({ fileType }: { fileType: string }) { const t = useTranslations('Global'); - const getFileTypeFields = (fileType: string): { totalSize: string, totalCount: string } => { + const getFileTypeFields = (fileType: string): { totalSize: string, totalCount: string, totalProtected: string } => { switch (fileType) { case 'image': return { totalSize: 'images_size', - totalCount: 'images_quantity' + totalCount: 'images_quantity', + totalProtected: 'protected_image_files_count' }; case 'video': return { totalSize: 'videos_size', - totalCount: 'videos_quantity' + totalCount: 'videos_quantity', + totalProtected: 'protected_video_files_count' }; case 'audio': return { totalSize: 'audios_size', - totalCount: 'audios_quantity' + totalCount: 'audios_quantity', + totalProtected: 'protected_audio_files_count' }; default: return { totalSize: 'all_files_size', - totalCount: 'all_files_quantity' + totalCount: 'all_files_quantity', + totalProtected: 'protected_files_count' }; } }; @@ -53,7 +58,8 @@ export default function ProtectionStatistic({ fileType }: { fileType: string }) if (!data) { return { totalSize: 0, - totalCount: 0 + totalCount: 0, + totalProtected: 0 } } @@ -61,7 +67,8 @@ export default function ProtectionStatistic({ fileType }: { fileType: string }) return { totalSize: data[fields.totalSize as keyof typeof data], - totalCount: data[fields.totalCount as keyof typeof data] + totalCount: data[fields.totalCount as keyof typeof data], + totalProtected: data[fields.totalProtected as keyof typeof data] } } }); @@ -69,7 +76,9 @@ export default function ProtectionStatistic({ fileType }: { fileType: string }) return (
-
0
+
+ {filesInfo?.totalProtected ? filesInfo.totalProtected : 0} +
{t(`${fileType}-protected`)}
diff --git a/src/app/ui/my-content/new/my-content-stats-overview.tsx b/src/app/ui/my-content/new/my-content-stats-overview.tsx index 277567a..52bb1eb 100644 --- a/src/app/ui/my-content/new/my-content-stats-overview.tsx +++ b/src/app/ui/my-content/new/my-content-stats-overview.tsx @@ -33,7 +33,7 @@ export default function MyContentStatsOverview() {
πŸ›‘
+0%
-
0
+
{filesInfo?.protected_files_count}
Π—Π°Ρ‰ΠΈΡ‰Π΅Π½ΠΎ Ρ„Π°ΠΉΠ»ΠΎΠ²
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 9199f79..d5a6b12 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -195,7 +195,12 @@ "new": "new", "in-progress": "In progress", "it-decided": "It's decided", - "API": "API" + "API": "API", + "NOT_PROTECTED": "Not protected", + "PROCESSING": "Processing", + "PROTECTED": "Protected", + "FAILED": "Failed", + "FAILED_SAVE": "Failed save" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 7ce8d55..5b93fbb 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -195,7 +195,12 @@ "new": "НовыС", "in-progress": "Π’ Ρ€Π°Π±ΠΎΡ‚Π΅", "it-decided": "РСшСно", - "API": "API" + "API": "API", + "NOT_PROTECTED": "НС Π·Π°Ρ‰ΠΈΡ‰Π΅Π½ΠΎ", + "PROCESSING": "ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°", + "PROTECTED": "Π—Π°Ρ‰ΠΈΡ‰Π΅Π½ΠΎ", + "FAILED": "Ошибка", + "FAILED_SAVE": "Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π½Π΅ ΡƒΠ΄Π°Π»ΠΎΡΡŒ" }, "Login-register-form": { "and": "ΠΈ", From ab652117c88e42ee0173d1e2502c5028c77fc52e Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 26 Jan 2026 16:10:02 +0700 Subject: [PATCH 004/119] increment version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bc01c80..8f03780 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.23.0", + "version": "0.24.0", "private": true, "scripts": { "dev": "next dev -p 2999", From e9e9f41f2a331d15e5dd0085653a5f2e50c9e185 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 26 Jan 2026 16:38:24 +0700 Subject: [PATCH 005/119] add support id --- src/app/components/tanstakTable.tsx | 38 +++++++++++++++++++++++++++++ src/app/styles/pages-styles.scss | 4 +++ 2 files changed, 42 insertions(+) diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index d104cce..cc0ac74 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -33,6 +33,7 @@ type FileItem = { status?: string; protectStatus: string; _original?: ApiFile; + supportId: number; }; type ApiFile = { @@ -44,6 +45,7 @@ type ApiFile = { updatedAt: string; status: string; protectStatus: string; + supportId: number; }; type ApiResponse = { @@ -146,6 +148,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { uploadDate: newDate, status: item.status, protectStatus: item.protectStatus, + supportId: item.supportId, _original: item }; }); @@ -174,6 +177,41 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { // ΠžΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ const columns = useMemo[]>( () => [ + { + accessorKey: 'id', + header: ({ column }) => ( +
+ + ID + + +
+ ), + cell: ({ row }) => { + return ( +
+ {row.original.supportId ? row.original.supportId: '-'} +
+ ) + }, + }, { accessorKey: 'fileName', header: ({ column }) => ( diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index f478e23..340b296 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -695,6 +695,10 @@ &:last-child { padding-right: 15px; } + + &:has(.table-item-id) { + width: 20px; + } } .table-item { From 1db333a21b374140f8146d33ef90a8d4f3abadda Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 26 Jan 2026 18:42:12 +0700 Subject: [PATCH 006/119] add search page --- src/app/[locale]/pages/search/page.tsx | 22 ++ src/app/components/section-search-file.tsx | 425 +++++++++++++++++++++ src/app/components/upload-section-file.tsx | 1 + src/app/styles/pages-styles.scss | 13 + src/app/ui/navigation/navigation.json | 2 +- 5 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 src/app/[locale]/pages/search/page.tsx create mode 100644 src/app/components/section-search-file.tsx diff --git a/src/app/[locale]/pages/search/page.tsx b/src/app/[locale]/pages/search/page.tsx new file mode 100644 index 0000000..b21aa3b --- /dev/null +++ b/src/app/[locale]/pages/search/page.tsx @@ -0,0 +1,22 @@ +import SectionSearchFile from '@/app/components/section-search-file'; +export default function Page() { + return ( + <> +
+

+ Поиск Π·Π°Ρ‰ΠΈΡ‰Π΅Π½Π½ΠΎΠ³ΠΎ ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚Π° +

+

+ Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚Π΅ Ρ„Π°ΠΉΠ» для поиска ΠΏΠΎΡ…ΠΎΠΆΠ΅Π³ΠΎ Π·Π°Ρ‰ΠΈΡ‰Π΅Π½Π½ΠΎΠ³ΠΎ ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚Π° Π² вашСй Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ΅ ΠΈ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ +

+
+
+ +
+ test +
+
+ + + ) +} \ No newline at end of file diff --git a/src/app/components/section-search-file.tsx b/src/app/components/section-search-file.tsx new file mode 100644 index 0000000..bff82db --- /dev/null +++ b/src/app/components/section-search-file.tsx @@ -0,0 +1,425 @@ +'use client' + +import { useRef, useState, useCallback, ChangeEvent, DragEvent, useEffect, useMemo } from 'react'; +import { IconShieldAdd } from '@/app/ui/icons/icons'; +import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/fileUpload'; +import { useTranslations } from 'next-intl'; +import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker'; +import { useQueryClient } from '@tanstack/react-query'; +interface SelectedFile { + file: File; + preview: string | undefined; + name: string; + size: string; + dimensions?: { + width: number; + height: number; + }; +} + +interface FileUploadInitResponse { + upload_id: string, + file_name: string, + total_chunks: number, + chunk_size: number, + status: string +} + +interface SectionSearchFile { + fileType: string + allowedExtensions: string[] + maxFileSize: number +} + +export default function SectionSearchFile({ fileType, allowedExtensions, maxFileSize }: SectionSearchFile) { + const fileInputRef = useRef(null); + const [isDragging, setIsDragging] = useState(false); + const [selectedFile, setSelectedFile] = useState(null); + const [error, setError] = useState(null); + const [uploadId, setUploadId] = useState(null); + const [isFileUploaded, setIsFileUploaded] = useState(false); + const [uploadProgress, setUploadProgress] = useState(0); + const queryClient = useQueryClient(); + const isCancelledRef = useRef(false); + + 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 handleFileSelect = useCallback(async (file: File | null): Promise => { + if (!file) { + setError(null); + return; + } + + setError(null); + setIsFileUploaded(false); + setUploadProgress(0); + + const validation = validateFile(file); + + if (!validation.isValid) { + setError(validation.errorMessage || t('unknown-validation-error')); + return; + } + + let dimensions = 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(`${t('image-size')}: ${dimensions.width}x${dimensions.height}. ${t('acceptable-range')}: 100x100 - 10000x10000 ${t('pixels')}.`); + return; + } + } catch (err) { + setError(t('error-reading-image')); + return; + } + } + + setSelectedFile({ + file, + name: file.name, + size: `${(file.size / 1024 / 1024).toFixed(2)} MB`, + dimensions, + preview: file.size < 10 * 1024 * 1024 ? URL.createObjectURL(file) : undefined + }); + + console.log('Π€Π°ΠΉΠ» Π²Ρ‹Π±Ρ€Π°Π½:', file.name, file.size); + }, [fileType, t]); + + const handleFileInputChange = (event: ChangeEvent): void => { + const file = event.target.files?.[0] || null; + handleFileSelect(file); + + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + + const handleButtonClick = (): void => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + const handleDragOver = (event: DragEvent): void => { + event.preventDefault(); + setIsDragging(true); + }; + + const handleDragLeave = (): void => { + setIsDragging(false); + }; + + const handleDrop = (event: DragEvent): void => { + event.preventDefault(); + setIsDragging(false); + + const file = event.dataTransfer.files[0]; + handleFileSelect(file); + }; + + const handleClearFile = async (): Promise => { + if (selectedFile && selectedFile.preview) { + URL.revokeObjectURL(selectedFile.preview); + } + + if (uploadId) { + await cancelUpload(uploadId); + } + + isCancelledRef.current = true; + + setIsFileUploaded(false); + setSelectedFile(null); + setError(null); + setUploadProgress(0); + }; + + const handlerFileUpload = useCallback(async (fileInfo: SelectedFile): Promise => { + if (uploadId) return; + + setError(null); + isCancelledRef.current = false; + const file = fileInfo.file; + + try { + const extension = file.name.split('.').pop() || ''; + const initMessageBody = { + file_name: file.name, + file_type: fileType, + extension: extension, + file_size: file.size + }; + + const response = await fileUpload(initMessageBody) as FileUploadInitResponse; + + if (!response?.upload_id) { + throw new Error('Failed to get upload_id'); + } + + const CHUNK_SIZE = response.chunk_size; + const totalChunks = response.total_chunks; + + setUploadId(response.upload_id); + + for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { + if (isCancelledRef.current) { + return; + } + + const start = chunkIndex * CHUNK_SIZE; + const end = Math.min(start + CHUNK_SIZE, file.size); + const chunk = 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); + /* formData.append('findSimilar', '0'); */ + + const chunkResponse = await chunkUpload(formData); + + if (chunkResponse.message_desc !== 'Chunk uploaded successfully') { + throw new Error(`Chunk ${chunkIndex} upload failed`); + } + + setUploadProgress(Math.floor((chunkIndex + 1) / totalChunks * 100)); + } + + const chunkStatus = await checkChunkStatus(response.upload_id); + if (chunkStatus.message_body.missing_chunks === 0) { + setIsFileUploaded(true); + await queryClient.invalidateQueries({ queryKey: ['userFilesData'] }); + await queryClient.invalidateQueries({ queryKey: ['userFilesInfo'] }); + } else { + throw new Error('Not all chunks were uploaded'); + } + + } catch (error) { + if (!isCancelledRef.current) { + setError(t('error-uploading-file')); + console.error('Upload error:', error); + } + } finally { + setUploadId(null); + } + }, [uploadId, fileType, t]); + + useEffect(() => { + // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° закрытия Π²ΠΊΠ»Π°Π΄ΠΊΠΈ + // Π’ΡƒΡ‚ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠΏΡ€ΠΎΠ±ΠΎΠ²Π°Ρ‚ΡŒ ΠΈΠ·ΡƒΡ‡ΠΈΡ‚ΡŒ вопрос с navigator.sendBeacon('', blob) + // ΠΎΠ½ Π½ΡƒΠΆΠ΅Π½ для Ρ‚ΠΎΠ³ΠΎ Ρ‡Ρ‚ΠΎ Π±Ρ‹ ΠΏΠΎΡΠ»Π°Ρ‚ΡŒ запрос Π½Π° Π±Π΅ΠΊ ΠΏΡ€ΠΈ Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΈΠΈ Π²ΠΊΠ»Π°Π΄ΠΊΠΈ, + // Π½ΠΎ Ρƒ Π½Π΅Π³ΠΎ Π΅ΡΡ‚ΡŒ свои особСнности ΠΈ стандартныС запросы Π½Π΅ Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‚. + const handleUnload = () => { + if (uploadId) { + //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 (uploadId) { + window.addEventListener('unload', handleUnload); + } + + return () => { + window.removeEventListener('unload', handleUnload); + }; + }, [uploadId]); + + useNavigationBlocker({ + shouldBlock: !!uploadId, + message: t('have-unsaved-changes'), + onConfirm: async () => { + console.log('User confirmed navigation'); + if (uploadId) { + await cancelUpload(uploadId); + } + }, + onCancel: () => { + console.log('User cancelled navigation'); + } + }); + + return ( +
+

+ {t('upload-for-protection', { fileType: t(fileType.toLocaleLowerCase()) })} +

+ +
+ +

+ {t('drag-the-file-here')} +

+ {/*

+ {t('or')} +

*/} + + {fileType === "image" && ( +

+ {t('image-resolution')}: 100x100 - 10000x10000 {t('pixels')} +

+ )} +

+ {t('file-format')}: {acceptString} +

+

+ {t('file-size')}: {t('to')} {(maxFileSize / 1024 / 1024)} {t('mb')} +

+ + + + + + {error && ( +
+

{error}

+
+ )} + + {selectedFile && ( +
+
+
+

+ + {t('file')}: + {selectedFile.name} +

+

+ + {t('size')}: + {selectedFile.size} +

+

+ {t('cost-of-protection')}: 0 +

+

+ {t('current-balance')}: 0 +

+
+
+ {isFileUploaded && ( +
+ {t('file-successfully-uploaded')} +
+ )} +
+
+ + {(fileType === 'image' && selectedFile.preview) && ( + ΠŸΡ€Π΅Π΄ΠΏΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ изобраТСния setError('НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ ΠΏΡ€Π΅Π²ΡŒΡŽ изобраТСния')} + /> + )} + +
+ + + {uploadProgress !== 0 && ( +
+ {uploadProgress}% +
+ )} +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/app/components/upload-section-file.tsx b/src/app/components/upload-section-file.tsx index f929c2e..752a818 100644 --- a/src/app/components/upload-section-file.tsx +++ b/src/app/components/upload-section-file.tsx @@ -229,6 +229,7 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile formData.append('upload_id', response.upload_id); formData.append('chunk_number', chunkIndex.toString()); formData.append('chunk', chunk); + /* formData.append('findSimilar', '0'); */ const chunkResponse = await chunkUpload(formData); diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 340b296..182d198 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -2759,4 +2759,17 @@ } } } +} + +.search-grid { + display: grid; + grid-template-columns: 1fr 300px; + gap: 24px; + margin-bottom: 24px; + max-width: calc(100vw - var(--side-bar-width) - 60px); + + @media (max-width: 440px) { + grid-template-columns: auto; + max-width: calc(100vw - var(--side-bar-width) - 30px); + } } \ No newline at end of file diff --git a/src/app/ui/navigation/navigation.json b/src/app/ui/navigation/navigation.json index da6ad54..8d2d423 100644 --- a/src/app/ui/navigation/navigation.json +++ b/src/app/ui/navigation/navigation.json @@ -26,7 +26,7 @@ }, { "name": "search", - "href": "/pages/emptypage", + "href": "/pages/search", "img": "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" }, { From 9a6491b76b86341b13471299bb116781fbb7cbb5 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 27 Jan 2026 19:52:01 +0700 Subject: [PATCH 007/119] add file search and some fixes and bugs --- src/app/[locale]/pages/search/page.tsx | 8 +- src/app/actions/fileEntity.ts | 64 +- src/app/components/ActivityTracker.tsx | 24 + src/app/components/section-search-file.tsx | 299 +++++--- src/app/components/tanstakTable.tsx | 20 +- src/app/components/upload-section-file.tsx | 11 +- src/app/styles/global-styles.scss | 12 +- src/app/styles/pages-styles.scss | 663 ++++++++++++++++++ src/app/styles/variable.scss | 2 + src/app/ui/icons/icons.tsx | 6 + .../ui/search/searched-global-files-list.tsx | 37 + .../ui/search/searched-user-files-list.tsx | 184 +++++ src/i18n/messages/en.json | 1 + src/i18n/messages/ru.json | 1 + 14 files changed, 1205 insertions(+), 127 deletions(-) create mode 100644 src/app/ui/search/searched-global-files-list.tsx create mode 100644 src/app/ui/search/searched-user-files-list.tsx diff --git a/src/app/[locale]/pages/search/page.tsx b/src/app/[locale]/pages/search/page.tsx index b21aa3b..0910d6e 100644 --- a/src/app/[locale]/pages/search/page.tsx +++ b/src/app/[locale]/pages/search/page.tsx @@ -11,9 +11,13 @@ export default function Page() {

- +
- test + side
diff --git a/src/app/actions/fileEntity.ts b/src/app/actions/fileEntity.ts index a42edc2..e9607f9 100644 --- a/src/app/actions/fileEntity.ts +++ b/src/app/actions/fileEntity.ts @@ -42,7 +42,7 @@ export async function getUserFilesData(page: number, pageSize: number) { } } -export async function removeUserFile(fileId: string) { +export async function removeUserFile(fileId: string, fullDelete: number) { const token = await getSessionData('token'); try { @@ -54,6 +54,7 @@ export async function removeUserFile(fileId: string) { message_body: { action: 'delete_file', file_id: fileId, + full_delete: fullDelete, token: token } }), @@ -75,6 +76,67 @@ export async function removeUserFile(fileId: string) { throw (`${response.status}`); } + } catch (error) { + return error + } +} + +export async function searchUserFiles(fileId: string) { + const token = await getSessionData('token'); + + try { + const response = await fetch(`${API_BASE_URL}/api/v1/files/${fileId}/similar?auth_token=${token}`, { + method: 'GET' + }); + + 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 searchGlobalFiles(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: 20007, + message_body: { + file_id: fileId, + } + }), + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }); + + 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 } diff --git a/src/app/components/ActivityTracker.tsx b/src/app/components/ActivityTracker.tsx index b5ebad5..039bcff 100644 --- a/src/app/components/ActivityTracker.tsx +++ b/src/app/components/ActivityTracker.tsx @@ -3,12 +3,36 @@ import { useEffect, useRef, useCallback } from 'react'; import { tokenLifeExtension } from '@/app/actions/auth'; +import { usePathname, useSearchParams } from 'next/navigation'; +import { removeUserFile } from '@/app/actions/fileEntity'; export default function ActivityTracker() { const lastRefreshTime = useRef(Date.now()); const activityTimeoutRef = useRef(null); const isRefreshing = useRef(false); + + const pathname = usePathname(); + const searchParams = useSearchParams(); + + useEffect(() => { + const selectedId = document.cookie + .split('; ') + .find(row => row.startsWith('searchedFileId=')) + ?.split('=')[1]; + + const handleCookie = async () => { + if (selectedId) { + await removeUserFile(selectedId, 1); + document.cookie = 'searchedFileId=; path=/; max-age=0'; + } + }; + + if (selectedId) { + handleCookie(); + } + }, [pathname, searchParams]); + // Debounce функция const debounce = useCallback((func: () => void, delay: number) => { if (activityTimeoutRef.current) { diff --git a/src/app/components/section-search-file.tsx b/src/app/components/section-search-file.tsx index bff82db..c0c533b 100644 --- a/src/app/components/section-search-file.tsx +++ b/src/app/components/section-search-file.tsx @@ -1,20 +1,20 @@ 'use client' import { useRef, useState, useCallback, ChangeEvent, DragEvent, useEffect, useMemo } from 'react'; -import { IconShieldAdd } from '@/app/ui/icons/icons'; +import { IconSearch } from '@/app/ui/icons/icons'; import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/fileUpload'; import { useTranslations } from 'next-intl'; import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker'; import { useQueryClient } from '@tanstack/react-query'; +import { searchUserFiles, removeUserFile, searchGlobalFiles } from '@/app/actions/fileEntity'; +import { SearchedUserFilesList } from '@/app/ui/search/searched-user-files-list'; +import { SearchedGlobalFilesList } from '@/app/ui/search/searched-global-files-list'; +import { useRouter } from 'next/navigation'; interface SelectedFile { file: File; preview: string | undefined; name: string; size: string; - dimensions?: { - width: number; - height: number; - }; } interface FileUploadInitResponse { @@ -39,6 +39,10 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile const [uploadId, setUploadId] = useState(null); const [isFileUploaded, setIsFileUploaded] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); + const [fileId, setFileId] = useState(null); + + const [searchedUserFiles, setSearchedUserFiles] = useState([]); + const [searchedGlobalFiles, setSearchedGlobalFiles] = useState([]); const queryClient = useQueryClient(); const isCancelledRef = useRef(false); @@ -74,37 +78,22 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile 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 handleFileSelect = useCallback(async (file: File | null): Promise => { if (!file) { setError(null); return; } + if (fileId) { + await removeUserFile(fileId, 1); + setFileId(null); + } + setError(null); setIsFileUploaded(false); + setSearchedUserFiles([]); + setSearchedGlobalFiles([]); + setUploadProgress(0); const validation = validateFile(file); @@ -114,30 +103,20 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile return; } - let dimensions = 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(`${t('image-size')}: ${dimensions.width}x${dimensions.height}. ${t('acceptable-range')}: 100x100 - 10000x10000 ${t('pixels')}.`); - return; - } - } catch (err) { - setError(t('error-reading-image')); - return; - } - } - setSelectedFile({ file, name: file.name, size: `${(file.size / 1024 / 1024).toFixed(2)} MB`, - dimensions, preview: file.size < 10 * 1024 * 1024 ? URL.createObjectURL(file) : undefined }); - console.log('Π€Π°ΠΉΠ» Π²Ρ‹Π±Ρ€Π°Π½:', file.name, file.size); + handlerFileUpload({ + file, + name: file.name, + size: `${(file.size / 1024 / 1024).toFixed(2)} MB`, + preview: file.size < 10 * 1024 * 1024 ? URL.createObjectURL(file) : undefined + }); + }, [fileType, t]); const handleFileInputChange = (event: ChangeEvent): void => { @@ -221,6 +200,7 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile return; } + const isLastChunk = chunkIndex === totalChunks - 1; const start = chunkIndex * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const chunk = file.slice(start, end); @@ -229,7 +209,7 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile formData.append('upload_id', response.upload_id); formData.append('chunk_number', chunkIndex.toString()); formData.append('chunk', chunk); - /* formData.append('findSimilar', '0'); */ + formData.append('findSimilar', '1'); const chunkResponse = await chunkUpload(formData); @@ -238,6 +218,11 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile } setUploadProgress(Math.floor((chunkIndex + 1) / totalChunks * 100)); + + if (isLastChunk) { + setFileId(chunkResponse.message_body.file_id); + document.cookie = `searchedFileId=${chunkResponse.message_body.file_id}` + } } const chunkStatus = await checkChunkStatus(response.upload_id); @@ -257,7 +242,7 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile } finally { setUploadId(null); } - }, [uploadId, fileType, t]); + }, [uploadId, fileType]); useEffect(() => { // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° закрытия Π²ΠΊΠ»Π°Π΄ΠΊΠΈ @@ -295,11 +280,42 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile } }); + const handlerSearchUserFile = useCallback(async (fileId: string): Promise => { + + try { + let result = await searchUserFiles(fileId); + + if (result.content.length) { + setSearchedUserFiles(result.content); + } + } catch (error) { + + } + + }, [fileId]) + + const handlerSearchGlobalFile = useCallback(async (fileId: string): Promise => { + + try { + let result = await searchGlobalFiles(fileId); + if (result.images.length) { + setSearchedGlobalFiles(result.images); + } + } catch (error) { + + } + + }, [fileId]) + return (
-

- {t('upload-for-protection', { fileType: t(fileType.toLocaleLowerCase()) })} -

+
+
Как Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ поиск?
+
+ Наша систСма Π°Π½Π°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹ΠΉ Ρ„Π°ΠΉΠ» ΠΈ сравниваСт Π΅Π³ΠΎ с вашими Π·Π°Ρ‰ΠΈΡ‰Π΅Π½Π½Ρ‹ΠΌΠΈ Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ, + ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌΡ‹ ΠΊΠΎΠΌΠΏΡŒΡŽΡ‚Π΅Ρ€Π½ΠΎΠ³ΠΎ зрСния ΠΈ Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Ρ… ΠΎΡ‚ΠΏΠ΅Ρ‡Π°Ρ‚ΠΊΠΎΠ². +
+
- +

- {t('drag-the-file-here')} + Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ Ρ„Π°ΠΉΠ» для поиска

- {/*

- {t('or')} -

*/} - - {fileType === "image" && ( -

- {t('image-resolution')}: 100x100 - 10000x10000 {t('pixels')} -

- )} -

- {t('file-format')}: {acceptString} -

-

- {t('file-size')}: {t('to')} {(maxFileSize / 1024 / 1024)} {t('mb')} -

- {t('select-files-to-protect')} + Π’Ρ‹Π±Ρ€Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ» {error && ( @@ -352,9 +353,9 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile {selectedFile && (
-
+

@@ -366,52 +367,10 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile {t('size')}: {selectedFile.size}

-

- {t('cost-of-protection')}: 0 -

-

- {t('current-balance')}: 0 -

-
- {isFileUploaded && ( -
- {t('file-successfully-uploaded')} -
- )} -
-
- - {(fileType === 'image' && selectedFile.preview) && ( - ΠŸΡ€Π΅Π΄ΠΏΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ изобраТСния setError('НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ ΠΏΡ€Π΅Π²ΡŒΡŽ изобраТСния')} - /> - )} - -
- - {uploadProgress !== 0 && (
{uploadProgress}%
@@ -420,6 +379,120 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile
)}
+ + {isFileUploaded && ( + <> +
+ +
+ + {(searchedUserFiles.length !== 0) && ( + + + )} + +
+
+
+ Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹ΠΉ поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ +
+
+ Найти Π³Π΄Π΅ Π΅Ρ‰Ρ‘ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ +
+
+ Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ поиск +
+
+ + +
+
+
+ Поиск ΠΏΠΎ всСму ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Ρƒ +
+
+ Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΏΡ€ΠΎΠ΄Π²ΠΈΠ½ΡƒΡ‚Ρ‹Π΅ Ρ‚Π΅Ρ…Π½ΠΎΠ»ΠΎΠ³ΠΈΠΈ ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎΠ³ΠΎ поиска ΠΏΠΎ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡŽ для поиска ΠΊΠΎΠΏΠΈΠΉ вашСго ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚Π° + Π½Π° сайтах, Π² ΡΠΎΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹Ρ… сСтях ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… источниках. ΠŸΠΎΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΡ‚ΡŒ нСсанкционированноС использованиС. +
+
+ +
+
+
+
+
Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Ρ… поисков сСгодня
+
0 ΠΈΠ· 10
+
+
+
+
+
+
+
+ + + +
+
+
+ Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅... +
+
+ +
+
+
+ НайдСно Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ +
+
+ +
+
+
+ +
+
+

ΠŸΠΎΡ…ΠΎΠΆΠΈΠ΅ изобраТСния Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹

+

Π­Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ·Π½Π°Ρ‡Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎ ΠΈ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π½Π° Π΄Ρ€ΡƒΠ³ΠΈΡ… сайтах.

+
+ +
+ Ошибка глобального поиска: + +
+
+
+ + )}
); } \ No newline at end of file diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index cc0ac74..8045053 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -11,7 +11,7 @@ import { 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 { IconImageFile, IconVideoFile, IconAudioFile, IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconShieldExclamation } 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'; @@ -207,7 +207,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { cell: ({ row }) => { return (
- {row.original.supportId ? row.original.supportId: '-'} + {row.original.supportId ? row.original.supportId : '-'}
) }, @@ -447,7 +447,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { {t('download')} - + */}
@@ -515,7 +515,10 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
+ +
+ + ) + })} + + + + + + ) +} \ No newline at end of file diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index d5a6b12..9d9ddda 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -143,6 +143,7 @@ "pixels": "pixels", "error-processing-file": "Error processing file", "error-uploading-file": "Error uploading file", + "error-duplicate-file": "Such a file already exists", "unknown-validation-error": "Unknown validation error", "unsupported-file-format": "Unsupported file format.", "the-file-is-too-large": "The file is too large", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 5b93fbb..f97c9c2 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -143,6 +143,7 @@ "pixels": "пиксСлСй", "error-processing-file": "Ошибка ΠΏΡ€ΠΈ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ΅ Ρ„Π°ΠΉΠ»Π°", "error-uploading-file": "Ошибка ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ Ρ„Π°ΠΉΠ»Π°", + "error-duplicate-file": "Π’Π°ΠΊΠΎΠΉ Ρ„Π°ΠΉΠ» ΡƒΠΆΠ΅ Π΅ΡΡ‚ΡŒ", "unknown-validation-error": "НСизвСстная ошибка Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ", "unsupported-file-format": "НСподдСрТиваСмый Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ Ρ„Π°ΠΉΠ»Π°.", "the-file-is-too-large": "Π€Π°ΠΉΠ» слишком большой", From f7f53d5af3e0bebbdde7693efd724eb871af4e46 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 28 Jan 2026 11:36:49 +0700 Subject: [PATCH 008/119] image styles for image view modal window --- src/app/styles/pages-styles.scss | 27 +++++++++++++++++++ .../ui/search/searched-user-files-list.tsx | 12 +++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 43a2f34..11d314e 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -3387,6 +3387,33 @@ overflow-y: auto; background: v.$white; + .image-section { + display: flex; + align-items: center; + justify-content: center; + min-height: 400px; + background: #fafafa; + border-radius: 12px; + position: relative; + } + + .image-container { + position: relative; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + + img { + max-width: 100%; + max-height: 100%; + object-fit: contain; + border-radius: 8px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); + } + } + .file-info-card { background: #ffffff; border: 1px solid #e2e8f0; diff --git a/src/app/ui/search/searched-user-files-list.tsx b/src/app/ui/search/searched-user-files-list.tsx index b8f6c26..7a30f53 100644 --- a/src/app/ui/search/searched-user-files-list.tsx +++ b/src/app/ui/search/searched-user-files-list.tsx @@ -54,11 +54,13 @@ export function SearchedUserFilesList({ list }: { list: any }) { className="modal-window-view-file-content" >
-
- img +
+
+ img +
Date: Wed, 28 Jan 2026 12:12:23 +0700 Subject: [PATCH 009/119] refactor --- src/app/components/section-search-file.tsx | 172 ++------------------- src/app/ui/search/file-search-panel.tsx | 158 +++++++++++++++++++ 2 files changed, 167 insertions(+), 163 deletions(-) create mode 100644 src/app/ui/search/file-search-panel.tsx diff --git a/src/app/components/section-search-file.tsx b/src/app/components/section-search-file.tsx index c0c533b..bc149aa 100644 --- a/src/app/components/section-search-file.tsx +++ b/src/app/components/section-search-file.tsx @@ -7,9 +7,7 @@ import { useTranslations } from 'next-intl'; import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker'; import { useQueryClient } from '@tanstack/react-query'; import { searchUserFiles, removeUserFile, searchGlobalFiles } from '@/app/actions/fileEntity'; -import { SearchedUserFilesList } from '@/app/ui/search/searched-user-files-list'; -import { SearchedGlobalFilesList } from '@/app/ui/search/searched-global-files-list'; -import { useRouter } from 'next/navigation'; +import { FileSearchPanel } from '@/app/ui/search/file-search-panel'; interface SelectedFile { file: File; preview: string | undefined; @@ -40,12 +38,11 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile const [isFileUploaded, setIsFileUploaded] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [fileId, setFileId] = useState(null); - - const [searchedUserFiles, setSearchedUserFiles] = useState([]); - const [searchedGlobalFiles, setSearchedGlobalFiles] = useState([]); const queryClient = useQueryClient(); const isCancelledRef = useRef(false); + const childRef = useRef(null); + const t = useTranslations('Global'); const acceptString = useMemo(() => { @@ -91,8 +88,11 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile setError(null); setIsFileUploaded(false); - setSearchedUserFiles([]); - setSearchedGlobalFiles([]); + + if (childRef.current) { + //@ts-ignore + childRef.current.doSomething(); + } setUploadProgress(0); @@ -151,23 +151,6 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile handleFileSelect(file); }; - const handleClearFile = async (): Promise => { - if (selectedFile && selectedFile.preview) { - URL.revokeObjectURL(selectedFile.preview); - } - - if (uploadId) { - await cancelUpload(uploadId); - } - - isCancelledRef.current = true; - - setIsFileUploaded(false); - setSelectedFile(null); - setError(null); - setUploadProgress(0); - }; - const handlerFileUpload = useCallback(async (fileInfo: SelectedFile): Promise => { if (uploadId) return; @@ -280,33 +263,6 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile } }); - const handlerSearchUserFile = useCallback(async (fileId: string): Promise => { - - try { - let result = await searchUserFiles(fileId); - - if (result.content.length) { - setSearchedUserFiles(result.content); - } - } catch (error) { - - } - - }, [fileId]) - - const handlerSearchGlobalFile = useCallback(async (fileId: string): Promise => { - - try { - let result = await searchGlobalFiles(fileId); - if (result.images.length) { - setSearchedGlobalFiles(result.images); - } - } catch (error) { - - } - - }, [fileId]) - return (
@@ -381,117 +337,7 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile
{isFileUploaded && ( - <> -
- -
- - {(searchedUserFiles.length !== 0) && ( - - - )} - -
-
-
- Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹ΠΉ поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ -
-
- Найти Π³Π΄Π΅ Π΅Ρ‰Ρ‘ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ -
-
- Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ поиск -
-
- - -
-
-
- Поиск ΠΏΠΎ всСму ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Ρƒ -
-
- Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΏΡ€ΠΎΠ΄Π²ΠΈΠ½ΡƒΡ‚Ρ‹Π΅ Ρ‚Π΅Ρ…Π½ΠΎΠ»ΠΎΠ³ΠΈΠΈ ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎΠ³ΠΎ поиска ΠΏΠΎ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡŽ для поиска ΠΊΠΎΠΏΠΈΠΉ вашСго ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚Π° - Π½Π° сайтах, Π² ΡΠΎΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹Ρ… сСтях ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… источниках. ΠŸΠΎΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΡ‚ΡŒ нСсанкционированноС использованиС. -
-
- -
-
-
-
-
Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Ρ… поисков сСгодня
-
0 ΠΈΠ· 10
-
-
-
-
-
-
-
- - - -
-
-
- Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅... -
-
- -
-
-
- НайдСно Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ -
-
- -
-
-
- -
-
-

ΠŸΠΎΡ…ΠΎΠΆΠΈΠ΅ изобраТСния Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹

-

Π­Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ·Π½Π°Ρ‡Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎ ΠΈ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π½Π° Π΄Ρ€ΡƒΠ³ΠΈΡ… сайтах.

-
- -
- Ошибка глобального поиска: - -
-
-
- + )}
); diff --git a/src/app/ui/search/file-search-panel.tsx b/src/app/ui/search/file-search-panel.tsx new file mode 100644 index 0000000..fbd190b --- /dev/null +++ b/src/app/ui/search/file-search-panel.tsx @@ -0,0 +1,158 @@ +import { SearchedUserFilesList } from '@/app/ui/search/searched-user-files-list'; +import { SearchedGlobalFilesList } from '@/app/ui/search/searched-global-files-list'; +import { useCallback, useState, useImperativeHandle } from 'react'; +import { searchUserFiles, searchGlobalFiles } from '@/app/actions/fileEntity'; + +export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: any }) { + const [searchedUserFiles, setSearchedUserFiles] = useState([]); + const [searchedGlobalFiles, setSearchedGlobalFiles] = useState([]); + + useImperativeHandle(ref, () => ({ + doSomething: () => { + setSearchedUserFiles([]); + setSearchedGlobalFiles([]) + } + })); + + + const handlerSearchUserFile = useCallback(async (fileId: string): Promise => { + + try { + let result = await searchUserFiles(fileId); + + if (result.content.length) { + setSearchedUserFiles(result.content); + } + } catch (error) { + + } + + }, [fileId]) + + const handlerSearchGlobalFile = useCallback(async (fileId: string): Promise => { + + try { + let result = await searchGlobalFiles(fileId); + if (result.images.length) { + setSearchedGlobalFiles(result.images); + } + } catch (error) { + + } + + }, [fileId]) + + return ( + <> +
+ +
+ + {(searchedUserFiles.length !== 0) && ( + + + )} + +
+
+
+ Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹ΠΉ поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ +
+
+ Найти Π³Π΄Π΅ Π΅Ρ‰Ρ‘ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ +
+
+ Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ поиск +
+
+ + +
+
+
+ Поиск ΠΏΠΎ всСму ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Ρƒ +
+
+ Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΏΡ€ΠΎΠ΄Π²ΠΈΠ½ΡƒΡ‚Ρ‹Π΅ Ρ‚Π΅Ρ…Π½ΠΎΠ»ΠΎΠ³ΠΈΠΈ ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎΠ³ΠΎ поиска ΠΏΠΎ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡŽ для поиска ΠΊΠΎΠΏΠΈΠΉ вашСго ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚Π° + Π½Π° сайтах, Π² ΡΠΎΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹Ρ… сСтях ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… источниках. ΠŸΠΎΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΡ‚ΡŒ нСсанкционированноС использованиС. +
+
+ +
+
+
+
+
Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Ρ… поисков сСгодня
+
0 ΠΈΠ· 10
+
+
+
+
+
+
+
+ + + +
+
+
+ Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅... +
+
+ +
+
+
+ НайдСно Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ +
+
+ +
+
+
+ +
+
+

ΠŸΠΎΡ…ΠΎΠΆΠΈΠ΅ изобраТСния Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹

+

Π­Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ·Π½Π°Ρ‡Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎ ΠΈ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π½Π° Π΄Ρ€ΡƒΠ³ΠΈΡ… сайтах.

+
+ +
+ Ошибка глобального поиска: + +
+
+
+ + ) +} \ No newline at end of file From efae068bacfc5763d28e41424e5b0408871d9349 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 28 Jan 2026 15:32:19 +0700 Subject: [PATCH 010/119] transfer actionTracker --- src/app/[locale]/layout.tsx | 2 -- src/app/[locale]/pages/layout.tsx | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 33d2e07..d684eaf 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -6,7 +6,6 @@ import { routing } from '@/i18n/routing'; import { ToastProvider } from '@/app/providers/ToastProvider'; import "../styles/globals.css"; import "../styles/global-styles.scss"; -import ActivityTracker from '../components/ActivityTracker'; export function generateStaticParams() { return routing.locales.map((locale: any) => ({ locale })); @@ -35,7 +34,6 @@ export default async function RootLayout({ {children} - diff --git a/src/app/[locale]/pages/layout.tsx b/src/app/[locale]/pages/layout.tsx index 23866bb..decb59c 100644 --- a/src/app/[locale]/pages/layout.tsx +++ b/src/app/[locale]/pages/layout.tsx @@ -3,6 +3,7 @@ import HeaderPanel from '@/app/ui/header/headerPanel'; import { getQueryClient } from '@/app/providers/getQueryClient'; import { HydrationBoundary, dehydrate } from '@tanstack/react-query'; import { prefetchLayoutQueries } from '@/app/lib/prefetch-queries'; +import ActivityTracker from '@/app/components/ActivityTracker'; export default async function Layout({ children }: { children: React.ReactNode }) { const queryClient = getQueryClient(); @@ -20,6 +21,7 @@ export default async function Layout({ children }: { children: React.ReactNode }
+
); } \ No newline at end of file From a6a493fe977ad7cfda12b74591978d3340338a43 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 28 Jan 2026 16:30:17 +0700 Subject: [PATCH 011/119] add null result for search files --- src/app/[locale]/pages/search/page.tsx | 2 +- src/app/styles/pages-styles.scss | 28 +++++++++-- src/app/ui/search/file-search-panel.tsx | 46 ++++++++++++++++--- .../ui/search/searched-user-files-list.tsx | 36 ++++++++------- .../search}/section-search-file.tsx | 4 +- 5 files changed, 85 insertions(+), 31 deletions(-) rename src/app/{components => ui/search}/section-search-file.tsx (98%) diff --git a/src/app/[locale]/pages/search/page.tsx b/src/app/[locale]/pages/search/page.tsx index 0910d6e..65f624a 100644 --- a/src/app/[locale]/pages/search/page.tsx +++ b/src/app/[locale]/pages/search/page.tsx @@ -1,4 +1,4 @@ -import SectionSearchFile from '@/app/components/section-search-file'; +import SectionSearchFile from '@/app/ui/search/section-search-file'; export default function Page() { return ( <> diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 11d314e..c10f9c9 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -3125,7 +3125,6 @@ } .global-no-result { - display: none; text-align: center; padding: 60px 20px; background: #f8fafc; @@ -3199,6 +3198,20 @@ } } + .empty-results { + text-align: center; + padding: 60px 20px; + color: #64748b; + + &-hint { + margin-top: 16px; + padding: 12px; + background: #f0f9ff; + border-radius: 8px; + font-size: 14px; + } + } + .results-list { display: flex; flex-direction: column; @@ -3307,9 +3320,8 @@ color: #065f46; } - .badge-high-protection { - background: #ddd6fe; - color: #5b21b6; + .badge-not-protected { + background: v.$b-color-2; } } @@ -3387,6 +3399,14 @@ overflow-y: auto; background: v.$white; + &-wrapper { + display: grid; + grid-template-columns: 1fr 300px; + gap: 20px; + height: 100%; + max-height: 80vh; + } + .image-section { display: flex; align-items: center; diff --git a/src/app/ui/search/file-search-panel.tsx b/src/app/ui/search/file-search-panel.tsx index fbd190b..47dbc0c 100644 --- a/src/app/ui/search/file-search-panel.tsx +++ b/src/app/ui/search/file-search-panel.tsx @@ -5,12 +5,16 @@ import { searchUserFiles, searchGlobalFiles } from '@/app/actions/fileEntity'; export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: any }) { const [searchedUserFiles, setSearchedUserFiles] = useState([]); + const [searchedUserFilesShowNull, setSearchedUserFilesShowNull] = useState(false); const [searchedGlobalFiles, setSearchedGlobalFiles] = useState([]); + const [searchedGlobalFilesShowNull, setSearchedGlobalFilesShowNull] = useState(false); useImperativeHandle(ref, () => ({ - doSomething: () => { + clearList: () => { setSearchedUserFiles([]); - setSearchedGlobalFiles([]) + setSearchedGlobalFiles([]); + setSearchedUserFilesShowNull(false); + setSearchedGlobalFilesShowNull(false); } })); @@ -22,6 +26,8 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a if (result.content.length) { setSearchedUserFiles(result.content); + } else { + setSearchedUserFilesShowNull(true); } } catch (error) { @@ -35,6 +41,8 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a let result = await searchGlobalFiles(fileId); if (result.images.length) { setSearchedGlobalFiles(result.images); + } else { + setSearchedGlobalFilesShowNull(true); } } catch (error) { @@ -62,7 +70,29 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a {(searchedUserFiles.length !== 0) && ( + )} + {searchedUserFilesShowNull && ( +
+
+
Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ поиска
+
+ НайдСно Ρ„Π°ΠΉΠ»ΠΎΠ²: {searchedUserFiles.length} +
+
+
+

ΠŸΠΎΡ…ΠΎΠΆΠΈΡ… Ρ„Π°ΠΉΠ»ΠΎΠ² Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ΠΎ

+

Π’ вашСй Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ΅ Π½Π΅Ρ‚ Ρ„Π°ΠΉΠ»ΠΎΠ², ΠΏΠΎΡ…ΠΎΠΆΠΈΡ… Π½Π° Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹ΠΉ

+
+ Π‘ΠΎΠ²Π΅Ρ‚: Π£Π±Π΅Π΄ΠΈΡ‚Π΅ΡΡŒ, Ρ‡Ρ‚ΠΎ Π²Ρ‹ Π·Π°Π³Ρ€ΡƒΠΆΠ°Π»ΠΈ этот Ρ„Π°ΠΉΠ» Ρ€Π°Π½Π΅Π΅ Ρ‡Π΅Ρ€Π΅Π· страницу "ΠœΠ°Ρ€ΠΊΠΈΡ€ΠΎΠ²ΠΊΠ°" +
+
+ +
)}
-
-
-

ΠŸΠΎΡ…ΠΎΠΆΠΈΠ΅ изобраТСния Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹

-

Π­Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ·Π½Π°Ρ‡Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎ ΠΈ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π½Π° Π΄Ρ€ΡƒΠ³ΠΈΡ… сайтах.

-
+ {searchedGlobalFilesShowNull && ( +
+
+

ΠŸΠΎΡ…ΠΎΠΆΠΈΠ΅ изобраТСния Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹

+

Π­Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ·Π½Π°Ρ‡Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎ ΠΈ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π½Π° Π΄Ρ€ΡƒΠ³ΠΈΡ… сайтах.

+
+ )}
Ошибка глобального поиска: diff --git a/src/app/ui/search/searched-user-files-list.tsx b/src/app/ui/search/searched-user-files-list.tsx index 7a30f53..adf9f24 100644 --- a/src/app/ui/search/searched-user-files-list.tsx +++ b/src/app/ui/search/searched-user-files-list.tsx @@ -3,6 +3,7 @@ import { toast } from 'sonner'; import { useState, ReactNode } from 'react'; import { useTranslations } from 'next-intl'; import ModalWindow from '@/app/components/modalWindow'; +import { IconEye, IconDownload } from '@/app/ui/icons/icons'; export function SearchedUserFilesList({ list }: { list: any }) { const [isFileLoading, setIsFileLoading] = useState(false); @@ -53,7 +54,7 @@ export function SearchedUserFilesList({ list }: { list: any }) {
-
+
{list.map((e: any, index: number) => { + console.log(e); return (
- Preview -
- πŸ“„ -
+ {e.url ? ( + Preview + ) : ( +
+ πŸ“„ +
+ )}
-
{e.originalFileName}
- Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½: - β€’ + Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½: {e.uploadDate ? e.uploadDate : '#'} β€’ Π Π°Π·ΠΌΠ΅Ρ€: {convertBytes(e.fileSize)}
@@ -147,31 +151,29 @@ export function SearchedUserFilesList({ list }: { list: any }) {
- - πŸ›‘οΈ Π—Π°Ρ‰ΠΈΡ‰Π΅Π½ΠΎ + + {e.status} - - πŸ”’ Высокий ΡƒΡ€ΠΎΠ²Π΅Π½ΡŒ Π·Π°Ρ‰ΠΈΡ‚Ρ‹ - -
diff --git a/src/app/components/section-search-file.tsx b/src/app/ui/search/section-search-file.tsx similarity index 98% rename from src/app/components/section-search-file.tsx rename to src/app/ui/search/section-search-file.tsx index bc149aa..aedfbc1 100644 --- a/src/app/components/section-search-file.tsx +++ b/src/app/ui/search/section-search-file.tsx @@ -6,7 +6,7 @@ import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/a import { useTranslations } from 'next-intl'; import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker'; import { useQueryClient } from '@tanstack/react-query'; -import { searchUserFiles, removeUserFile, searchGlobalFiles } from '@/app/actions/fileEntity'; +import { removeUserFile } from '@/app/actions/fileEntity'; import { FileSearchPanel } from '@/app/ui/search/file-search-panel'; interface SelectedFile { file: File; @@ -91,7 +91,7 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile if (childRef.current) { //@ts-ignore - childRef.current.doSomething(); + childRef.current.clearList(); } setUploadProgress(0); From c4ab78b89b4a5bbc4af2945738d25ab046a73744 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 28 Jan 2026 18:53:08 +0700 Subject: [PATCH 012/119] add check modal window --- package.json | 2 +- src/app/actions/action.ts | 14 +- src/app/actions/auth.ts | 22 +-- src/app/actions/definitions.ts | 2 +- src/app/actions/fileEntity.ts | 50 ++++++- src/app/actions/fileUpload.ts | 20 +-- src/app/actions/vkAuth.ts | 4 +- src/app/actions/yandexAuth.ts | 4 +- src/app/components/tanstakTable.tsx | 60 +++++--- src/app/styles/pages-styles.scss | 60 +++++++- .../modal-windows/file-info-modal-window.tsx | 139 ++++++++++++++++++ src/i18n/messages/en.json | 4 +- src/i18n/messages/ru.json | 4 +- 13 files changed, 318 insertions(+), 67 deletions(-) create mode 100644 src/app/ui/modal-windows/file-info-modal-window.tsx diff --git a/package.json b/package.json index 8f03780..f1e863d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.24.0", + "version": "0.25.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/actions/action.ts b/src/app/actions/action.ts index 360af1a..fbe5e68 100644 --- a/src/app/actions/action.ts +++ b/src/app/actions/action.ts @@ -9,7 +9,7 @@ export async function getUserData() { const token = await getSessionData('token'); /* для тСста */ - if (userEmail === "test" && token === "1111") { + if (userEmail === 'test' && token === '1111') { return testUserData; } /* для тСста */ @@ -18,9 +18,9 @@ export async function getUserData() { const response = await fetch(`${API_BASE_URL}/v1/api/user?email=${userEmail}`, { method: 'GET', headers: { - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}`, } }); @@ -42,13 +42,13 @@ export async function getUserFilesInfo() { version: 1, msg_id: 20005, message_body: { - action: "user_files_info", + action: 'user_files_info', token: token } }), headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index 15b4213..6d9c697 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -11,9 +11,9 @@ export async function logout() { await fetch(`${API_BASE_URL}/v1/api/auth/logout`, { method: 'POST', headers: { - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}`, } }); } catch (error) { @@ -36,8 +36,8 @@ export async function authorization( const password = formData.get('password'); /* для тСста */ - if (email === "test" && password === "test") { - await createSession("1111", "test"); + if (email === 'test' && password === 'test') { + await createSession('1111', 'test'); redirect('/pages/dashboard'); } /* для тСста */ @@ -81,8 +81,8 @@ export async function authorization( } }), headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); if (response.ok) { @@ -211,8 +211,8 @@ export async function registration( } }), headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); @@ -302,8 +302,8 @@ export async function tokenLifeExtension() { const response = await fetch(`${API_BASE_URL}/api/v1/data`, { method: 'POST', headers: { - "Content-Type": "application/json", - "Accept": "application/json", + 'Content-Type': 'application/json', + 'Accept': 'application/json', }, body: JSON.stringify({ version: 1, diff --git a/src/app/actions/definitions.ts b/src/app/actions/definitions.ts index cbd260d..c06b06a 100644 --- a/src/app/actions/definitions.ts +++ b/src/app/actions/definitions.ts @@ -1,5 +1,5 @@ import * as z from 'zod' -import { createValidationSchema } from "@/app/lib/validation-config-parser"; +import { createValidationSchema } from '@/app/lib/validation-config-parser'; import validationConfig from '@/app/lib/validation-config.json'; export type FormState = diff --git a/src/app/actions/fileEntity.ts b/src/app/actions/fileEntity.ts index e9607f9..7df3af3 100644 --- a/src/app/actions/fileEntity.ts +++ b/src/app/actions/fileEntity.ts @@ -21,8 +21,8 @@ export async function getUserFilesData(page: number, pageSize: number) { } }), headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); @@ -59,8 +59,8 @@ export async function removeUserFile(fileId: string, fullDelete: number) { } }), headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); @@ -120,8 +120,46 @@ export async function searchGlobalFiles(fileId: string) { } }), headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + 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 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, + token: token + } + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); diff --git a/src/app/actions/fileUpload.ts b/src/app/actions/fileUpload.ts index cc304d0..c3fb85b 100644 --- a/src/app/actions/fileUpload.ts +++ b/src/app/actions/fileUpload.ts @@ -18,7 +18,7 @@ export async function fileUpload(messageBody: initMessageBody) { return; } const message = messageBody; - message.action = "init"; + message.action = 'init'; message.token = token; try { @@ -30,8 +30,8 @@ export async function fileUpload(messageBody: initMessageBody) { message_body: message }), headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); @@ -68,13 +68,13 @@ export async function cancelUpload(udloadId: string) { version: 1, msg_id: 20004, message_body: { - action: "cancel", + action: 'cancel', upload_id: udloadId, } }), headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); @@ -138,8 +138,8 @@ export async function checkChunkStatus(upload_id: string) { } }), headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); @@ -173,8 +173,8 @@ export async function getAllowedFilesExtensions(type: string) { } }), headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); diff --git a/src/app/actions/vkAuth.ts b/src/app/actions/vkAuth.ts index dd88ae4..78b4738 100644 --- a/src/app/actions/vkAuth.ts +++ b/src/app/actions/vkAuth.ts @@ -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}`, { method: 'GET', headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); if (response.ok) { diff --git a/src/app/actions/yandexAuth.ts b/src/app/actions/yandexAuth.ts index f39588f..0623101 100644 --- a/src/app/actions/yandexAuth.ts +++ b/src/app/actions/yandexAuth.ts @@ -15,8 +15,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}`, { method: 'GET', headers: { - "Content-Type": "application/json", - "Accept": "application/json" + 'Content-Type': 'application/json', + 'Accept': 'application/json' } }); if (response.ok) { diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index 8045053..4ca918f 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -15,11 +15,12 @@ import { IconImageFile, IconVideoFile, IconAudioFile, IconEye, IconDoubleArrowRi 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 { getUserFilesData, removeUserFile, viewFileInfo } 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'; +import { FileInfoModalWindow } from '@/app/ui/modal-windows/file-info-modal-window'; type FileType = 'image' | 'video' | 'audio'; @@ -423,30 +424,12 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { cell: ({ row }) => (
- -
-
- {/* */}
+
+ {row.original.protectStatus === 'PROTECTED' && ( + + )} + +
), @@ -467,9 +475,19 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { [] ); - // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ дСйствий - const handleView = (file: FileItem) => { + const handleView = async (file: FileItem) => { console.log(`ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Ρ„Π°ΠΉΠ»Π°: ${file.fileName}`); + console.log(file); + + const fileInfo = await viewFileInfo(file.id); + console.log(fileInfo); + + setOpenWindowChildren(() => { + return ( + + ) + }) + setOpenWindow(true); }; const handleDownload = async (file: FileItem) => { diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index c10f9c9..e806e77 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -769,10 +769,15 @@ } } - .table-action-download { + .table-action-download, + .table-action-delete, + .table-action-view { display: flex; - background-color: #3b72e0; align-items: center; + } + + .table-action-download { + background-color: #3b72e0; &:hover { background-color: #2d56a8; @@ -780,14 +785,20 @@ } .table-action-delete { - display: flex; background-color: #e80a14; - align-items: center; &:hover { background-color: #9f0712; } } + + .table-action-view { + background-color: #2b7fff; + + &:hover { + background-color: #155dfc; + } + } } } } @@ -3482,4 +3493,45 @@ } } } +} + +.file-info-modal-window { + width: 50vw; + min-width: 400px; + + &-close-button { + position: absolute; + top: 15px; + right: 20px; + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: #64748b; + } + + &-section { + margin-bottom: 25px; + } + + &-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; + } + + &-item { + background: v.$bg-light; + padding: 15px; + border-radius: 8px; + } + + &-title { + font-weight: 600; + color: v.$text-p; + } + + &-content { + color: v.$text-s; + } } \ No newline at end of file diff --git a/src/app/ui/modal-windows/file-info-modal-window.tsx b/src/app/ui/modal-windows/file-info-modal-window.tsx new file mode 100644 index 0000000..510d2b4 --- /dev/null +++ b/src/app/ui/modal-windows/file-info-modal-window.tsx @@ -0,0 +1,139 @@ +export function FileInfoModalWindow({ fileInfo, setWindowClose, setWindowChildren }: any) { + + const exampleTest = { + ownerName: 'string', + ownerMail: 'string', + ownerCompany: 'string', + fileName: 'string', + fileSize: 'number', + fileUploadDate: 'number', + fileFormat: 'string', + protectStatus: 'string', + checksCount: 'number' + } + + function closeHandler() { + setWindowChildren(null); + setWindowClose(false); + } + + return ( +
+ +
+

+ Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ +

+ +
+
+ +

+ Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Π²Π»Π°Π΄Π΅Π»ΡŒΡ†Π΅ +

+
+
+
+ Имя +
+
+ {fileInfo.ownerName ? fileInfo.ownerName : '-'} +
+
+
+
+ Email +
+
+ {fileInfo.ownerMail ? fileInfo.ownerMail : '-'} +
+
+
+
+ Компания +
+
+ {fileInfo.ownerCompany ? fileInfo.ownerCompany : '-'} +
+
+
+ +

+ Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ Ρ„Π°ΠΉΠ»Π΅ +

+
+
+
+ Имя Ρ„Π°ΠΉΠ»Π° +
+
+ {fileInfo.fileName ? fileInfo.fileName : '-'} +
+
+
+
+ Π Π°Π·ΠΌΠ΅Ρ€ +
+
+ {fileInfo.fileSize ? fileInfo.fileSize : '-'} +
+
+
+
+ Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½ +
+
+ {fileInfo.fileUploadDate ? fileInfo.fileUploadDate : '-'} +
+
+
+
+ Π€ΠΎΡ€ΠΌΠ°Ρ‚ +
+
+ {fileInfo.fileFormat ? fileInfo.fileFormat : '-'} +
+
+
+ +

+ Π”Π΅Ρ‚Π°Π»ΠΈ Π·Π°Ρ‰ΠΈΡ‚Ρ‹ +

+
+
+
+ Бтатус Π·Π°Ρ‰ΠΈΡ‚Ρ‹ +
+
+ {fileInfo.protectStatus ? fileInfo.protectStatus : '-'} +
+
+
+
+ ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΎΠΊ +
+
+ {fileInfo.checksCount ? fileInfo.checksCount : '-'} +
+
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 9d9ddda..73405c0 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -18,6 +18,7 @@ "violations-few": "Violations", "violations": "Violations", "check": "Check", + "make-check": "Check", "checks": "Checks", "checks-few": "Checks", "documents": "Documents", @@ -201,7 +202,8 @@ "PROCESSING": "Processing", "PROTECTED": "Protected", "FAILED": "Failed", - "FAILED_SAVE": "Failed save" + "FAILED_SAVE": "Failed save", + "view": "View" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index f97c9c2..cd62fbe 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -18,6 +18,7 @@ "violations-few": "ΠΠ°Ρ€ΡƒΡˆΠ΅Π½ΠΈΡ", "violations": "ΠΠ°Ρ€ΡƒΡˆΠ΅Π½ΠΈΠΉ", "check": "ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ°", + "make-check": "ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ", "checks": "ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΎΠΊ", "checks-few": "ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ", "documents": "Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹", @@ -201,7 +202,8 @@ "PROCESSING": "ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°", "PROTECTED": "Π—Π°Ρ‰ΠΈΡ‰Π΅Π½ΠΎ", "FAILED": "Ошибка", - "FAILED_SAVE": "Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π½Π΅ ΡƒΠ΄Π°Π»ΠΎΡΡŒ" + "FAILED_SAVE": "Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π½Π΅ ΡƒΠ΄Π°Π»ΠΎΡΡŒ", + "view": "ΠŸΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ" }, "Login-register-form": { "and": "ΠΈ", From 832c4b0e765415504b1ad5bf2f33c26b8f897f11 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 29 Jan 2026 12:00:29 +0700 Subject: [PATCH 013/119] add info for file info modal window --- package.json | 2 +- src/app/ui/modal-windows/file-info-modal-window.tsx | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f1e863d..f3c2b1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.25.0", + "version": "0.26.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/ui/modal-windows/file-info-modal-window.tsx b/src/app/ui/modal-windows/file-info-modal-window.tsx index 510d2b4..3c35631 100644 --- a/src/app/ui/modal-windows/file-info-modal-window.tsx +++ b/src/app/ui/modal-windows/file-info-modal-window.tsx @@ -17,6 +17,8 @@ export function FileInfoModalWindow({ fileInfo, setWindowClose, setWindowChildre setWindowClose(false); } + console.log(fileInfo); + return (
@@ -56,7 +58,7 @@ export function FileInfoModalWindow({ fileInfo, setWindowClose, setWindowChildre Email
- {fileInfo.ownerMail ? fileInfo.ownerMail : '-'} + {fileInfo.ownerEmail ? fileInfo.ownerEmail : '-'}
@@ -104,7 +106,7 @@ export function FileInfoModalWindow({ fileInfo, setWindowClose, setWindowChildre Π€ΠΎΡ€ΠΌΠ°Ρ‚
- {fileInfo.fileFormat ? fileInfo.fileFormat : '-'} + {fileInfo.fileExtension ? fileInfo.fileExtension : '-'}
@@ -128,7 +130,7 @@ export function FileInfoModalWindow({ fileInfo, setWindowClose, setWindowChildre ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΎΠΊ
- {fileInfo.checksCount ? fileInfo.checksCount : '-'} + {fileInfo.checksCount ? fileInfo.checksCount : '0'}
From 8b68e37b89792bda8771e8ed9433ab8bec30c103 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 29 Jan 2026 14:34:04 +0700 Subject: [PATCH 014/119] btc, btb registration --- src/app/actions/auth.ts | 14 +-- src/app/actions/definitions.ts | 1 - src/app/lib/validation-config-parser.ts | 22 ++++- src/app/lib/validation-config.json | 2 +- src/app/styles/module/login.module.scss | 41 ++++++++- src/app/ui/forms/register-form.tsx | 109 +++++++++++++++++------- 6 files changed, 147 insertions(+), 42 deletions(-) diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index 6d9c697..9a639d6 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -135,7 +135,7 @@ export type FormState = { password: string; confirm_password: string; phone: string; - companyName: string; + companyName?: string; agree: string; }, mailConfirm?: boolean, @@ -146,6 +146,10 @@ export async function registration( state: FormState | undefined, formData: FormData, ): Promise { + const registrationType = formData.get('accoutType') as string || ''; + + console.log(registrationType); + const fullName = formData.get('fullName') as string || ''; const email = formData.get('email') as string || ''; const password = formData.get('password') as string || ''; @@ -163,7 +167,7 @@ export async function registration( password, confirm_password, agree, - companyName + ...(registrationType === 'btb' && { companyName }) }; const validatedFields = SignupFormSchema.safeParse(dataToValidate); @@ -188,8 +192,8 @@ export async function registration( password, confirm_password, phone, - companyName, - agree + agree, + ...(registrationType === 'btb' && { companyName }) }, error: errors }; @@ -207,7 +211,7 @@ export async function registration( email, phone: phone, password, - companyName: companyName, + ...(registrationType === 'btb' && { companyName: companyName }) } }), headers: { diff --git a/src/app/actions/definitions.ts b/src/app/actions/definitions.ts index c06b06a..fec94cf 100644 --- a/src/app/actions/definitions.ts +++ b/src/app/actions/definitions.ts @@ -50,7 +50,6 @@ async function loadConfigFromBackend(): Promise { } export async function getSignupFormSchema() { - console.log('get scheme'); if (cachedSignupSchema) { return cachedSignupSchema; } diff --git a/src/app/lib/validation-config-parser.ts b/src/app/lib/validation-config-parser.ts index 0051173..8baedd7 100644 --- a/src/app/lib/validation-config-parser.ts +++ b/src/app/lib/validation-config-parser.ts @@ -38,6 +38,22 @@ export function createValidationSchema(config: ValidationConfig) { .trim(); } + // fullName + if (config.companyName) { + 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 if (config.email) { let emailSchema = z @@ -147,8 +163,8 @@ export function createValidationSchema(config: ValidationConfig) { schema.password = passwordSchema; } - // companyName (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΠ΅ ΠΏΠΎΠ»Π΅) - if (config.companyName) { + // companyName (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΠ΅ ΠΏΠΎΠ»Π΅), Π½Π°Π΄ΠΎ Π±ΡƒΠ΄Π΅Ρ‚ ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ ΠΏΠΎΡ‚ΠΎΠΌ + /* if (config.companyName) { let companySchema = z.string(); // Если ΠΏΠΎΠ»Π΅ ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎΠ΅, добавляСм .optional().nullable() @@ -214,7 +230,7 @@ export function createValidationSchema(config: ValidationConfig) { companySchema = companySchema.transform((val) => val?.trim() || '') as unknown as z.ZodString; schema.companyName = companySchema; - } + } */ // Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ поля для Ρ„ΠΎΡ€ΠΌΡ‹ рСгистрации schema.confirm_password = z.string(); diff --git a/src/app/lib/validation-config.json b/src/app/lib/validation-config.json index b4620de..816625b 100644 --- a/src/app/lib/validation-config.json +++ b/src/app/lib/validation-config.json @@ -70,7 +70,7 @@ "message": "register-error-company-name-min" }, "allowedSymbols": { - "value": "^[a-zA-ZΠ°-яА-ЯёЁ0-9\\s\\-&.,'\"()]+$", + "value": "^[a-zA-ZΠ°-яА-ЯёЁ0-9\\s\\-&.,'\"()]*$", "message": "register-error-company-name-not-allowed-symbols" } }, diff --git a/src/app/styles/module/login.module.scss b/src/app/styles/module/login.module.scss index 0f62492..8cd4817 100644 --- a/src/app/styles/module/login.module.scss +++ b/src/app/styles/module/login.module.scss @@ -41,8 +41,35 @@ .form-group { margin-bottom: 18px; +} - &-confrim-window { +.form-switcher { + display: flex; + justify-content: center; + gap: 10px; + + &-button { + padding: 3px 15px; + background: linear-gradient(135deg, #6366f1, #8b5cf6); + color: white; + border: none; + border-radius: 10px; + transition: all 0.3s; + opacity: 0.6; + text-transform: uppercase; + cursor: pointer; + + &.active { + opacity: 1; + } + + &:disabled { + opacity: 0.5; + } + + &:hover:not(:disabled) { + transform: translateY(-2px); + } } } @@ -74,6 +101,10 @@ transition: all 0.3s; background: #f9fafb; + &-hidden { + display: none; + } + &[type="password"]::-ms-reveal { display: none; } @@ -140,6 +171,14 @@ transition: all 0.3s; margin-bottom: 18px; margin: 20px 0 0 0; + + &:disabled { + opacity: 0.5; + } + + &:hover:not(:disabled) { + transform: translateY(-2px); + } } .form-group-confrim-window { diff --git a/src/app/ui/forms/register-form.tsx b/src/app/ui/forms/register-form.tsx index ca9c6ce..e62fe5e 100644 --- a/src/app/ui/forms/register-form.tsx +++ b/src/app/ui/forms/register-form.tsx @@ -27,6 +27,7 @@ export default function RegisterForm() { const [formState, setFormState] = useState(undefined); const [showMailConfirmWindow, setShowMailConfirmWindow] = useState(false); const passwordRef = useRef(null); + const [accountType, setAccountType] = useState<'btc' | 'btb'>('btc'); useEffect(() => { setFormState(state); @@ -113,6 +114,24 @@ export default function RegisterForm() { } } + function switchRegistrationTypeHandler(e: React.MouseEvent, type: 'btc' | 'btb') { + e.preventDefault(); + setAccountType(type); + if (formState && type === 'btc') { + setFormState({ + ...formState, + previousState: { + ...formState.previousState, + companyName: '' + }, + error: { + ...formState.error, + companyName: '' + } + }); + } + } + return ( <> {showMailConfirmWindow && ( @@ -163,6 +182,32 @@ export default function RegisterForm() { { formAction(e); }}> +
+ + + +
+
) } \ No newline at end of file diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 73405c0..523ea79 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -203,7 +203,8 @@ "PROTECTED": "Protected", "FAILED": "Failed", "FAILED_SAVE": "Failed save", - "view": "View" + "view": "View", + "are-no-violations": "There are no violations" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index cd62fbe..73c3730 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -203,7 +203,8 @@ "PROTECTED": "Π—Π°Ρ‰ΠΈΡ‰Π΅Π½ΠΎ", "FAILED": "Ошибка", "FAILED_SAVE": "Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π½Π΅ ΡƒΠ΄Π°Π»ΠΎΡΡŒ", - "view": "ΠŸΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ" + "view": "ΠŸΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ", + "are-no-violations": "ΠΠ°Ρ€ΡƒΡˆΠ΅Π½ΠΈΠΉ Π½Π΅Ρ‚Ρƒ" }, "Login-register-form": { "and": "ΠΈ", From 55e0b0723e5a99501ae9be4c22d45f0cb00e1f1b Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 29 Jan 2026 16:53:16 +0700 Subject: [PATCH 018/119] remove language switcher, change file search result --- src/app/ui/header/headerPanel.tsx | 2 +- .../ui/search/searched-user-files-list.tsx | 109 +++++++++--------- 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/src/app/ui/header/headerPanel.tsx b/src/app/ui/header/headerPanel.tsx index 98172be..46940a3 100644 --- a/src/app/ui/header/headerPanel.tsx +++ b/src/app/ui/header/headerPanel.tsx @@ -12,7 +12,7 @@ export default function HeaderPanel() {
- + {/* */} {list.map((e: any, index: number) => { console.log(e); - return ( -
-
-
-
- {e.url ? ( - Preview - ) : ( -
- πŸ“„ + > +
+
+
+ {e.url ? ( + Preview + ) : ( +
+ πŸ“„ +
+ )} +
+
+
+ {e.originalFileName} +
+
+ Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½: {e.uploadDate ? e.uploadDate : '#'} β€’ + Π Π°Π·ΠΌΠ΅Ρ€: {convertBytes(e.fileSize)}
- )} -
-
-
- {e.originalFileName}
-
- Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½: {e.uploadDate ? e.uploadDate : '#'} β€’ - Π Π°Π·ΠΌΠ΅Ρ€: {convertBytes(e.fileSize)} +
+
+ {e.similarityLevel} +
-
-
- {e.similarityLevel} -
+ +
+ + {e.status} + +
+ + +
+ +
- -
- - {e.status} - -
- - -
- - -
-
-
) +
) + } })}
From fc81d471ff1aa3a527c400f75481978a6a038238 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 29 Jan 2026 17:01:00 +0700 Subject: [PATCH 019/119] update accountType --- src/app/actions/auth.ts | 11 ++++++----- src/app/ui/forms/register-form.tsx | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index 9a639d6..0b6439d 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -146,9 +146,9 @@ export async function registration( state: FormState | undefined, formData: FormData, ): Promise { - const registrationType = formData.get('accoutType') as string || ''; + const accountType = formData.get('accountType') as string || ''; - console.log(registrationType); + console.log(accountType); const fullName = formData.get('fullName') as string || ''; const email = formData.get('email') as string || ''; @@ -167,7 +167,7 @@ export async function registration( password, confirm_password, agree, - ...(registrationType === 'btb' && { companyName }) + ...(accountType === 'btb' && { companyName }) }; const validatedFields = SignupFormSchema.safeParse(dataToValidate); @@ -193,7 +193,7 @@ export async function registration( confirm_password, phone, agree, - ...(registrationType === 'btb' && { companyName }) + ...(accountType === 'btb' && { companyName }) }, error: errors }; @@ -211,7 +211,8 @@ export async function registration( email, phone: phone, password, - ...(registrationType === 'btb' && { companyName: companyName }) + accountType, + ...(accountType === 'btb' && { companyName: companyName }) } }), headers: { diff --git a/src/app/ui/forms/register-form.tsx b/src/app/ui/forms/register-form.tsx index e68a4c4..a705888 100644 --- a/src/app/ui/forms/register-form.tsx +++ b/src/app/ui/forms/register-form.tsx @@ -27,7 +27,7 @@ export default function RegisterForm() { const [formState, setFormState] = useState(undefined); const [showMailConfirmWindow, setShowMailConfirmWindow] = useState(false); const passwordRef = useRef(null); - const [accountType, setAccountType] = useState<'btc' | 'btb'>('btc'); + const [accountType, setAccountType] = useState<'b2c' | 'b2b'>('b2c'); useEffect(() => { setFormState(state); @@ -114,10 +114,10 @@ export default function RegisterForm() { } } - function switchRegistrationTypeHandler(e: React.MouseEvent, type: 'btc' | 'btb') { + function switchRegistrationTypeHandler(e: React.MouseEvent, type: 'b2c' | 'b2b') { e.preventDefault(); setAccountType(type); - if (formState && type === 'btc') { + if (formState && type === 'b2c') { setFormState({ ...formState, previousState: { @@ -184,25 +184,25 @@ export default function RegisterForm() { }}>
@@ -239,7 +239,7 @@ export default function RegisterForm() { )}
- {accountType === 'btb' && ( + {accountType === 'b2b' && (
- - {userData.tariffs?.map(((item: { - id: number, - name: string, - price: number, - tokens: number, - maxFilesCount: number, - diskSize: number - }) => { - return ( -
-
-
πŸš€
-

- {item.name} -

-
- {item.price} -
-
Π² мСсяц
-
- {item.tokens} Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΎ
-
-
    -
  • - βœ“ - {item.maxFilesCount} Ρ„Π°ΠΉΠ»ΠΎΠ² Π² мСсяц -
  • -
  • - βœ“ - {convertBytes(item.diskSize)} Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π° -
  • -
  • - βœ“ - Π‘Π°Π·ΠΎΠ²Ρ‹Π΅ ΠΌΠΎΠ΄ΡƒΠ»ΠΈ Π·Π°Ρ‰ΠΈΡ‚Ρ‹ -
  • -
  • - βœ“ - ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° 24/7 -
  • -
  • - βœ— - API доступ -
  • -
-
- ) - }))} +
+
+
πŸš€
+

БВАРВ

+
900β‚½
+
Π² мСсяц
+
100 Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΎ
+
+
    +
  • + βœ“ + 100 Ρ„Π°ΠΉΠ»ΠΎΠ² Π² мСсяц +
  • +
  • + βœ“ + 1 Π“Π‘ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π° +
  • +
  • + βœ“ + Π”ΠΎ 10 ΠœΠ‘ Π½Π° Ρ„Π°ΠΉΠ» +
  • +
  • + βœ“ + Π‘Π°Π·ΠΎΠ²Ρ‹Π΅ ΠΌΠΎΠ΄ΡƒΠ»ΠΈ Π·Π°Ρ‰ΠΈΡ‚Ρ‹ +
  • +
  • + βœ“ + ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° 24/7 +
  • +
  • + βœ— + API доступ +
  • +
+
+
+
+
πŸš€
+

ΠŸΠ Π•ΠœΠ˜Π£Πœ

+
3500β‚½
+
Π² мСсяц
+
100 Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΎ
+
+
    +
  • + βœ“ + 100 Ρ„Π°ΠΉΠ»ΠΎΠ² Π² мСсяц +
  • +
  • + βœ“ + 1 Π“Π‘ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π° +
  • +
  • + βœ“ + Π”ΠΎ 10 ΠœΠ‘ Π½Π° Ρ„Π°ΠΉΠ» +
  • +
  • + βœ“ + Π‘Π°Π·ΠΎΠ²Ρ‹Π΅ ΠΌΠΎΠ΄ΡƒΠ»ΠΈ Π·Π°Ρ‰ΠΈΡ‚Ρ‹ +
  • +
  • + βœ“ + ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° 24/7 +
  • +
  • + βœ— + API доступ +
  • +
+
+
+
+
πŸš€
+

ПРОЀИ

+
9500β‚½
+
Π² мСсяц
+
100 Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΎ
+
+
    +
  • + βœ“ + 100 Ρ„Π°ΠΉΠ»ΠΎΠ² Π² мСсяц +
  • +
  • + βœ“ + 1 Π“Π‘ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π° +
  • +
  • + βœ“ + Π”ΠΎ 10 ΠœΠ‘ Π½Π° Ρ„Π°ΠΉΠ» +
  • +
  • + βœ“ + Π‘Π°Π·ΠΎΠ²Ρ‹Π΅ ΠΌΠΎΠ΄ΡƒΠ»ΠΈ Π·Π°Ρ‰ΠΈΡ‚Ρ‹ +
  • +
  • + βœ“ + ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° 24/7 +
  • +
  • + βœ— + API доступ +
  • +
+
) diff --git a/src/app/ui/payment/user-usage.tsx b/src/app/ui/payment/user-usage.tsx index 4e22e38..0669b1c 100644 --- a/src/app/ui/payment/user-usage.tsx +++ b/src/app/ui/payment/user-usage.tsx @@ -1,47 +1,28 @@ -'use client' - -import { getUserData } from '@/app/actions/action'; -import { useQuery } from '@tanstack/react-query'; -import { useEffect } from 'react'; -import {convertBytes} from '@/app/lib/convertBytes'; - export default function UserUsage() { - const { - data: userData, - isLoading, - isError, - error - } = useQuery({ - queryKey: ['userData'], - queryFn: () => { - return getUserData(); - } - }); - return (

- Π’Π°ΡˆΠ΅ использованиС + πŸ“Š Π’Π°ΡˆΠ΅ использованиС

-
0 / {userData?.tariffInfo?.tariff?.maxFilesCount}
+
0 / 2,000
Π€Π°ΠΉΠ»ΠΎΠ² Π² мСсяц
-
0 / {convertBytes(userData?.tariffInfo?.tariff?.diskSize)}
+
0 / 20 Π“Π‘
ΠžΠ±ΡŠΡ‘ΠΌ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π°
@@ -51,9 +32,7 @@ export default function UserUsage() {
ВокСнов на балансС
-
- {userData?.tariffInfo?.tariff?.name} -
+
ПРОЀИ
Π’Π΅ΠΊΡƒΡ‰ΠΈΠΉ ΠΏΠ»Π°Π½
diff --git a/src/app/ui/settings/personal-data-settings.tsx b/src/app/ui/settings/personal-data-settings.tsx index c8ed0ed..02e6993 100644 --- a/src/app/ui/settings/personal-data-settings.tsx +++ b/src/app/ui/settings/personal-data-settings.tsx @@ -1,9 +1,7 @@ 'use client' import { useTranslations } from 'next-intl'; -import { useActionState, useState, useEffect } from 'react'; -import { getUserData } from '@/app/actions/action'; -import { useQuery } from '@tanstack/react-query'; +import { useActionState, useState } from 'react' export default function PersonalDataSettings() { const [errorMessage, formAction, isPending] = useActionState( @@ -13,23 +11,6 @@ export default function PersonalDataSettings() { undefined ); - const { - data: userData, - isLoading, - isError, - error - } = useQuery({ - queryKey: ['userData'], - queryFn: () => { - return getUserData(); - } - }); - - useEffect(() => { - console.log('show => userData'); - console.log(userData); - }, []) - const [selectedValue, setSelectedValue] = useState('male'); const t = useTranslations('Global'); @@ -45,37 +26,19 @@ export default function PersonalDataSettings() { - + +
+
+ +
- {userData.company && ( -
- - -
- )}
- + {t('email-cant-change')} @@ -84,12 +47,7 @@ export default function PersonalDataSettings() { - +
@@ -46,7 +81,7 @@ export default function SubscriptionSettings() { -
+
*/}
) From 362df2f6097c0416970d6b16ad62f424aaeb15b6 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 4 Feb 2026 12:02:01 +0700 Subject: [PATCH 041/119] version increment --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8f3284..c884217 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.28.0", + "version": "0.29.0", "private": true, "scripts": { "dev": "next dev -p 2999", From 584f0dda2f792292ab51d0a909870027c245473c Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 4 Feb 2026 13:21:32 +0700 Subject: [PATCH 042/119] update errors handler --- src/app/[locale]/pages/search/page.tsx | 3 +- src/app/lib/convertBytes.ts | 4 ++ src/app/styles/pages-styles.scss | 60 +++++++++++++++++++ src/app/styles/payment-container.scss | 1 + src/app/ui/payment/user-usage.tsx | 24 ++++++-- src/app/ui/search/search-stats.tsx | 0 src/app/ui/search/supported-formats.tsx | 46 ++++++++++++++ .../ui/settings/personal-data-settings.tsx | 5 -- src/app/ui/settings/subscription-settings.tsx | 17 +++--- 9 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 src/app/ui/search/search-stats.tsx create mode 100644 src/app/ui/search/supported-formats.tsx diff --git a/src/app/[locale]/pages/search/page.tsx b/src/app/[locale]/pages/search/page.tsx index 65f624a..4cc55ab 100644 --- a/src/app/[locale]/pages/search/page.tsx +++ b/src/app/[locale]/pages/search/page.tsx @@ -1,4 +1,5 @@ import SectionSearchFile from '@/app/ui/search/section-search-file'; +import { SupportedFormats } from '@/app/ui/search/supported-formats'; export default function Page() { return ( <> @@ -17,7 +18,7 @@ export default function Page() { fileType="image" />
- side +
diff --git a/src/app/lib/convertBytes.ts b/src/app/lib/convertBytes.ts index 3516c98..b1886fe 100644 --- a/src/app/lib/convertBytes.ts +++ b/src/app/lib/convertBytes.ts @@ -1,4 +1,8 @@ export function convertBytes(bytes: number) { + if (!bytes) { + return 0; + } + const units = ['B', 'KB', 'MB', 'GB', 'TB']; if (bytes === 0) return '0 B'; diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index b202104..2c639ef 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -3695,4 +3695,64 @@ color: #d97706; white-space: nowrap; } +} + +.supported-formats { + background: v.$white; + border-radius: 12px; + padding: 20px; + box-shadow: 0 4px 6px v.$shadow-1; + + .formats-title { + font-weight: 600; + color: v.$text-p; + ; + margin-bottom: 16px; + display: flex; + align-items: center; + gap: 8px; + } + + .format-group { + margin-bottom: 16px; + } + + .format-type { + font-weight: 500; + color: v.$text-p; + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 8px; + + &.image { + svg { + color: v.$color-image; + } + } + + &.audio { + svg { + color: v.$color-audio; + } + } + + &.video { + svg { + color: v.$color-video; + } + } + + &.document { + svg { + color: v.$color-document; + } + } + } + + .format-list { + color: v.$text-s; + font-size: 14px; + line-height: 1.5; + } } \ No newline at end of file diff --git a/src/app/styles/payment-container.scss b/src/app/styles/payment-container.scss index 58e220c..2837898 100644 --- a/src/app/styles/payment-container.scss +++ b/src/app/styles/payment-container.scss @@ -34,6 +34,7 @@ padding: 16px; text-align: center; border: 1px solid #e2e8f0; + height: 100%; } .stat-value { diff --git a/src/app/ui/payment/user-usage.tsx b/src/app/ui/payment/user-usage.tsx index 4e22e38..24eec36 100644 --- a/src/app/ui/payment/user-usage.tsx +++ b/src/app/ui/payment/user-usage.tsx @@ -3,7 +3,7 @@ import { getUserData } from '@/app/actions/action'; import { useQuery } from '@tanstack/react-query'; import { useEffect } from 'react'; -import {convertBytes} from '@/app/lib/convertBytes'; +import { convertBytes } from '@/app/lib/convertBytes'; export default function UserUsage() { const { @@ -15,6 +15,18 @@ export default function UserUsage() { queryKey: ['userData'], queryFn: () => { return getUserData(); + }, + select: (data) => { + if (data.tariffInfo) { + return { + diskSize: data.tariffInfo?.maxFileOnDisk, + id: data.tariffInfo?.id, + maxFilesCount: data.tariffInfo?.maxFileCounts, + name: data.tariffInfo?.tariffName, + endTariff: data.tariffInfo?.endTariff, + tokens: data.tariffInfo?.tokens + } + } } }); @@ -25,7 +37,7 @@ export default function UserUsage() {
-
0 / {userData?.tariffInfo?.tariff?.maxFilesCount}
+
0 / {userData?.maxFilesCount}
Π€Π°ΠΉΠ»ΠΎΠ² Π² мСсяц
-
0 / {convertBytes(userData?.tariffInfo?.tariff?.diskSize)}
+
0 / {userData?.diskSize ? convertBytes(userData?.diskSize) : 0}
ΠžΠ±ΡŠΡ‘ΠΌ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π°
-
0
+
+ {userData?.tokens ? userData?.tokens : 0} +
ВокСнов на балансС
- {userData?.tariffInfo?.tariff?.name} + {userData?.name ? userData?.name : 'ERROR'}
Π’Π΅ΠΊΡƒΡ‰ΠΈΠΉ ΠΏΠ»Π°Π½
diff --git a/src/app/ui/search/search-stats.tsx b/src/app/ui/search/search-stats.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/app/ui/search/supported-formats.tsx b/src/app/ui/search/supported-formats.tsx new file mode 100644 index 0000000..c73d794 --- /dev/null +++ b/src/app/ui/search/supported-formats.tsx @@ -0,0 +1,46 @@ +import { IconImageFile, IconVideoFile, IconAudioFile, IconDocument } from '@/app/ui/icons/icons'; +export function SupportedFormats() { + return ( +
+
+ ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅ΠΌΡ‹Π΅ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Ρ‹ +
+ +
+
+ Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ +
+
+ JPEG, PNG, GIF, BMP +
+
+ +
+
+ Π’ΠΈΠ΄Π΅ΠΎ +
+
+ MP4, AVI, MOV, WMV +
+
+ +
+
+ Аудио +
+
+ MP3, WAV, FLAC, AAC +
+
+ +
+
+ Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ +
+
+ PDF, DOC, DOCX +
+
+
+ ) +} \ No newline at end of file diff --git a/src/app/ui/settings/personal-data-settings.tsx b/src/app/ui/settings/personal-data-settings.tsx index c8ed0ed..b7cd91b 100644 --- a/src/app/ui/settings/personal-data-settings.tsx +++ b/src/app/ui/settings/personal-data-settings.tsx @@ -25,11 +25,6 @@ export default function PersonalDataSettings() { } }); - useEffect(() => { - console.log('show => userData'); - console.log(userData); - }, []) - const [selectedValue, setSelectedValue] = useState('male'); const t = useTranslations('Global'); diff --git a/src/app/ui/settings/subscription-settings.tsx b/src/app/ui/settings/subscription-settings.tsx index 0ed3c96..88ccb45 100644 --- a/src/app/ui/settings/subscription-settings.tsx +++ b/src/app/ui/settings/subscription-settings.tsx @@ -18,16 +18,12 @@ export default function SubscriptionSettings() { return getUserData(); }, select: (data) => { - if (data.tariffInfo?.tariff) { + if (data.tariffInfo) { return { - diskSize: data.tariffInfo?.tariff?.diskSize, - id: data.tariffInfo?.tariff?.id, - maxFilesCount: data.tariffInfo?.tariff?.maxFilesCount, - maxUsers: data.tariffInfo?.tariff?.maxUsers, - name: data.tariffInfo?.tariff?.name, - price: data.tariffInfo?.tariff?.price, - tokens: data.tariffInfo?.tariff?.tokens, - type: data.tariffInfo?.tariff?.type, + diskSize: data.tariffInfo?.maxFileOnDisk, + id: data.tariffInfo?.id, + maxFilesCount: data.tariffInfo?.maxFileCounts, + name: data.tariffInfo?.tariffName, endTariff: data.tariffInfo?.endTariff } } @@ -35,6 +31,9 @@ export default function SubscriptionSettings() { }); function dateHandler(date: string) { + if (!date) { + return 'error' + } return date.split(' ')[0]; } From 2c26a8cd8590ca75ab1c1820e838d5ed76b089f1 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 4 Feb 2026 14:10:38 +0700 Subject: [PATCH 043/119] add search side panel layout --- src/app/[locale]/pages/search/page.tsx | 4 ++ src/app/styles/pages-styles.scss | 87 +++++++++++++++++++++++++- src/app/ui/search/recent-searches.tsx | 50 +++++++++++++++ src/app/ui/search/search-stats.tsx | 30 +++++++++ 4 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 src/app/ui/search/recent-searches.tsx diff --git a/src/app/[locale]/pages/search/page.tsx b/src/app/[locale]/pages/search/page.tsx index 4cc55ab..83d2b71 100644 --- a/src/app/[locale]/pages/search/page.tsx +++ b/src/app/[locale]/pages/search/page.tsx @@ -1,5 +1,7 @@ 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 { RecentSearches } from '@/app/ui/search/recent-searches'; export default function Page() { return ( <> @@ -19,6 +21,8 @@ export default function Page() { />
+ +
diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 2c639ef..4a38a8c 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -3697,16 +3697,25 @@ } } -.supported-formats { +.search-sidebar { + display: flex; + flex-direction: column; + gap: 20px; +} + +.supported-formats, +.stats-search, +.recent-searches { background: v.$white; border-radius: 12px; padding: 20px; box-shadow: 0 4px 6px v.$shadow-1; - .formats-title { + .formats-title, + .stats-title, + .recent-title { font-weight: 600; color: v.$text-p; - ; margin-bottom: 16px; display: flex; align-items: center; @@ -3755,4 +3764,76 @@ font-size: 14px; line-height: 1.5; } + + .stat-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 0; + border-bottom: 1px solid v.$b-color-2; + + &:last-child { + border-bottom: none; + } + + .stat-label { + color: v.$text-s; + font-size: 14px; + opacity: 0.9; + position: relative; + z-index: 1; + } + + .stat-value { + font-weight: 600; + color: v.$text-p; + font-size: 32px; + margin-bottom: 5px; + position: relative; + z-index: 1; + } + } + + .recent-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 0; + border-bottom: 1px solid v.$b-color-2; + + .recent-type { + color: v.$text-p; + font-size: 14px; + display: flex; + + &.image { + svg { + color: v.$color-image; + } + } + + &.audio { + svg { + color: v.$color-audio; + } + } + + &.video { + svg { + color: v.$color-video; + } + } + + &.document { + svg { + color: v.$color-document; + } + } + } + + .recent-date { + color: v.$text-s; + font-size: 12px; + } + } } \ No newline at end of file diff --git a/src/app/ui/search/recent-searches.tsx b/src/app/ui/search/recent-searches.tsx new file mode 100644 index 0000000..da7e595 --- /dev/null +++ b/src/app/ui/search/recent-searches.tsx @@ -0,0 +1,50 @@ +import { IconImageFile, IconDocument, IconAudioFile, IconVideoFile } from '@/app/ui/icons/icons'; +export function RecentSearches() { + return ( +
+
+ НСдавниС поиски +
+
+ + document +
+
0 поисков
+
+ date +
+
+
+
+ + image +
+
0 поисков
+
+ date +
+
+
+
+ + audio +
+
0 поисков
+
+ date +
+
+
+
+ + video +
+
0 поисков
+
+ date +
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/app/ui/search/search-stats.tsx b/src/app/ui/search/search-stats.tsx index e69de29..9e33d02 100644 --- a/src/app/ui/search/search-stats.tsx +++ b/src/app/ui/search/search-stats.tsx @@ -0,0 +1,30 @@ +export function SearchStats() { + return ( +
+
+ Бтатистика поиска +
+ +
+ Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ + 0 +
+
+ Π’ΠΈΠ΄Π΅ΠΎ + 0 +
+
+ Аудио + 0 +
+
+ Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ + 0 +
+
+ ВсСго поисков + 0 +
+
+ ) +} \ No newline at end of file From 7c757a3c594f4605b31b4744a18a8e333c062eb5 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 4 Feb 2026 14:41:40 +0700 Subject: [PATCH 044/119] add more user tarrif info --- src/app/lib/getProcents.ts | 18 +++++++ src/app/styles/payment-container.scss | 3 +- .../new/dashborad-limits-section.tsx | 7 +-- src/app/ui/header/headerPanel.tsx | 53 ++++++++++++++----- src/app/ui/payment/user-usage.tsx | 19 +++++-- 5 files changed, 75 insertions(+), 25 deletions(-) create mode 100644 src/app/lib/getProcents.ts diff --git a/src/app/lib/getProcents.ts b/src/app/lib/getProcents.ts new file mode 100644 index 0000000..df8145b --- /dev/null +++ b/src/app/lib/getProcents.ts @@ -0,0 +1,18 @@ +export function getProcents( + currentNumber: number, + totalNumber: number, + decimals: number = 0 +): number { + if (!currentNumber && !totalNumber) { + return 0; + } + + if (totalNumber === 0) { + return 0; + } + + const percentage = (currentNumber / totalNumber) * 100; + const factor = Math.pow(10, decimals); + + return Math.round(percentage * factor) / factor; +} \ No newline at end of file diff --git a/src/app/styles/payment-container.scss b/src/app/styles/payment-container.scss index 2837898..5d92c98 100644 --- a/src/app/styles/payment-container.scss +++ b/src/app/styles/payment-container.scss @@ -38,10 +38,11 @@ } .stat-value { - font-size: 1.6rem; + font-size: 1.2rem; font-weight: 700; color: #1e293b; margin-bottom: 4px; + white-space: nowrap; } .stat-label { diff --git a/src/app/ui/dashboard/new/dashborad-limits-section.tsx b/src/app/ui/dashboard/new/dashborad-limits-section.tsx index 7b44a56..6434d0b 100644 --- a/src/app/ui/dashboard/new/dashborad-limits-section.tsx +++ b/src/app/ui/dashboard/new/dashborad-limits-section.tsx @@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query'; import { getUserFilesInfo } from '@/app/actions/action'; import { convertBytes } from '@/app/lib/convertBytes'; +import { getProcents } from '@/app/lib/getProcents'; export default function DahboardLimitsSection() { const { @@ -15,10 +16,6 @@ export default function DahboardLimitsSection() { queryFn: getUserFilesInfo, }); - function getProcents(currentNumber: number, totalNumber: number) { - return Math.round(currentNumber / (totalNumber / 100)); - } - return (

πŸ’° ИспользованиС Π»ΠΈΠΌΠΈΡ‚ΠΎΠ²

@@ -48,7 +45,7 @@ export default function DahboardLimitsSection() {
{filesInfo?.all_files_size ? convertBytes(filesInfo?.all_files_size) : 0} / 1,024 ΠœΠ‘
-
+
{getProcents(filesInfo?.all_files_size, 1000000000)}% diff --git a/src/app/ui/header/headerPanel.tsx b/src/app/ui/header/headerPanel.tsx index 46940a3..aa6702b 100644 --- a/src/app/ui/header/headerPanel.tsx +++ b/src/app/ui/header/headerPanel.tsx @@ -1,32 +1,57 @@ +'use client' + import NotificationsButton from './notificationsButton'; import UserMenuButton from './userMenuButton'; import Link from 'next/link'; import LanguageSwitcher from '@/app/components/LanguageSwitcher'; import { useTranslations } from 'next-intl'; import BurgerMenu from '@/app/ui/navigation/burger-menu'; +import { getUserData } from '@/app/actions/action'; +import { useQuery } from '@tanstack/react-query'; export default function HeaderPanel() { const t = useTranslations("Global"); + const { + data: tokens, + isLoading, + isError, + error + } = useQuery({ + queryKey: ['userData'], + queryFn: () => { + return getUserData(); + }, + select: (data) => { + if (typeof data?.tariffInfo?.tokens === 'number') { + return data?.tariffInfo?.tokens + } else { + return null; + } + + } + }); return (
{/* */} - -
- - - - 800 - - {t('tokens')} - -
- + {tokens && ( + +
+ + + + {tokens} + + {t('tokens')} + +
+ + )}
diff --git a/src/app/ui/payment/user-usage.tsx b/src/app/ui/payment/user-usage.tsx index 24eec36..ca870db 100644 --- a/src/app/ui/payment/user-usage.tsx +++ b/src/app/ui/payment/user-usage.tsx @@ -4,6 +4,7 @@ import { getUserData } from '@/app/actions/action'; import { useQuery } from '@tanstack/react-query'; import { useEffect } from 'react'; import { convertBytes } from '@/app/lib/convertBytes'; +import { getProcents } from '@/app/lib/getProcents'; export default function UserUsage() { const { @@ -24,7 +25,9 @@ export default function UserUsage() { maxFilesCount: data.tariffInfo?.maxFileCounts, name: data.tariffInfo?.tariffName, endTariff: data.tariffInfo?.endTariff, - tokens: data.tariffInfo?.tokens + tokens: data.tariffInfo?.tokens, + currentFileCounts: data.tariffInfo?.currentFileCounts, + currentFileOnDisk: data.tariffInfo?.currentFileOnDisk, } } } @@ -37,23 +40,29 @@ export default function UserUsage() {
-
0 / {userData?.maxFilesCount}
+
+ {userData?.currentFileCounts} / {userData?.maxFilesCount} +
Π€Π°ΠΉΠ»ΠΎΠ² Π² мСсяц
-
0 / {userData?.diskSize ? convertBytes(userData?.diskSize) : 0}
+
+ {userData?.currentFileOnDisk ? convertBytes(userData?.currentFileOnDisk) : 0} / {userData?.diskSize ? convertBytes(userData?.diskSize) : 0} +
ΠžΠ±ΡŠΡ‘ΠΌ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π°
From 5df5e340e578c848f0805b63fa207638f26ae42a Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 4 Feb 2026 15:13:17 +0700 Subject: [PATCH 045/119] refactor --- src/app/[locale]/pages/marking-audio/page.tsx | 2 +- src/app/[locale]/pages/marking-images/page.tsx | 2 +- src/app/[locale]/pages/marking-video/page.tsx | 2 +- .../{upload-section-file.tsx => UploadSectionFile.tsx} | 1 + src/app/components/tanstakTable.tsx | 4 ++-- src/app/ui/dashboard/files-table.tsx | 2 +- src/app/ui/payment/payment-tab-tariffs.tsx | 7 ++++++- src/app/ui/search/searched-user-files-list.tsx | 2 +- 8 files changed, 14 insertions(+), 8 deletions(-) rename src/app/components/{upload-section-file.tsx => UploadSectionFile.tsx} (99%) diff --git a/src/app/[locale]/pages/marking-audio/page.tsx b/src/app/[locale]/pages/marking-audio/page.tsx index 02a3364..ad46029 100644 --- a/src/app/[locale]/pages/marking-audio/page.tsx +++ b/src/app/[locale]/pages/marking-audio/page.tsx @@ -1,7 +1,7 @@ import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import FilesTable from '@/app/ui/dashboard/files-table'; 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 { StackedBarChart } from '@/app/components/StackedBarChart'; import PageTitleColorFrame from '@/app/ui/page-title-color-frame'; diff --git a/src/app/[locale]/pages/marking-images/page.tsx b/src/app/[locale]/pages/marking-images/page.tsx index 963960e..9eba8da 100644 --- a/src/app/[locale]/pages/marking-images/page.tsx +++ b/src/app/[locale]/pages/marking-images/page.tsx @@ -1,5 +1,5 @@ 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 PageTitleColorFrame from '@/app/ui/page-title-color-frame'; import ProtectionStatistic from '@/app/ui/marking-page/new/protection-statistic'; diff --git a/src/app/[locale]/pages/marking-video/page.tsx b/src/app/[locale]/pages/marking-video/page.tsx index 8ee95b9..e2968f3 100644 --- a/src/app/[locale]/pages/marking-video/page.tsx +++ b/src/app/[locale]/pages/marking-video/page.tsx @@ -1,7 +1,7 @@ import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import FilesTable from '@/app/ui/dashboard/files-table'; 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 { StackedBarChart } from '@/app/components/StackedBarChart'; import PageTitleColorFrame from '@/app/ui/page-title-color-frame'; diff --git a/src/app/components/upload-section-file.tsx b/src/app/components/UploadSectionFile.tsx similarity index 99% rename from src/app/components/upload-section-file.tsx rename to src/app/components/UploadSectionFile.tsx index 3918634..af37ccb 100644 --- a/src/app/components/upload-section-file.tsx +++ b/src/app/components/UploadSectionFile.tsx @@ -253,6 +253,7 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile setIsFileUploaded(true); await queryClient.invalidateQueries({ queryKey: ['userFilesData'] }); await queryClient.invalidateQueries({ queryKey: ['userFilesInfo'] }); + await queryClient.invalidateQueries({ queryKey: ['userData'] }); } else { throw new Error('Not all chunks were uploaded'); } diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index 4c3d7ac..b89f9b2 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -13,10 +13,10 @@ import { } from '@tanstack/react-table'; import { IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconShieldExclamation } from '@/app/ui/icons/icons'; import { useTranslations, useLocale } from 'next-intl'; -import DropDownList from '@/app/components/dropDownList'; +import DropDownList from '@/app/components/DropDownList'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { getUserFilesData, removeUserFile, viewFileInfo } from '@/app/actions/fileEntity'; -import ModalWindow from '@/app/components/modalWindow'; +import ModalWindow from '@/app/components/ModalWindow'; import { toast } from 'sonner'; import { pluralize } from '@/app/lib/pluralize'; import { convertBytes } from '@/app/lib/convertBytes'; diff --git a/src/app/ui/dashboard/files-table.tsx b/src/app/ui/dashboard/files-table.tsx index c1c82f6..a1185cf 100644 --- a/src/app/ui/dashboard/files-table.tsx +++ b/src/app/ui/dashboard/files-table.tsx @@ -1,4 +1,4 @@ -import TanstakFilesTable from '@/app/components/tanstakTable'; +import TanstakFilesTable from '@/app/components/TanstakTable'; type FilesTable = { fileType: string diff --git a/src/app/ui/payment/payment-tab-tariffs.tsx b/src/app/ui/payment/payment-tab-tariffs.tsx index aaa9fba..1553d0a 100644 --- a/src/app/ui/payment/payment-tab-tariffs.tsx +++ b/src/app/ui/payment/payment-tab-tariffs.tsx @@ -3,6 +3,7 @@ import { getUserData } from '@/app/actions/action'; import { useQuery } from '@tanstack/react-query'; import { convertBytes } from '@/app/lib/convertBytes'; +import { useTranslations } from 'next-intl'; export default function PaymentTabTariffs() { const { @@ -17,6 +18,8 @@ export default function PaymentTabTariffs() { } }); + const t = useTranslations('Global'); + return (
@@ -46,7 +49,9 @@ export default function PaymentTabTariffs() {
πŸš€

- {item.name} + { + t.has(item.name) ? t(item.name) : item.name + }

{item.price} diff --git a/src/app/ui/search/searched-user-files-list.tsx b/src/app/ui/search/searched-user-files-list.tsx index 5a9b8da..9fb64bb 100644 --- a/src/app/ui/search/searched-user-files-list.tsx +++ b/src/app/ui/search/searched-user-files-list.tsx @@ -2,7 +2,7 @@ import { convertBytes } from '@/app/lib/convertBytes'; import { toast } from 'sonner'; import { useState, ReactNode } from 'react'; import { useTranslations } from 'next-intl'; -import ModalWindow from '@/app/components/modalWindow'; +import ModalWindow from '@/app/components/ModalWindow'; import { IconEye, IconDownload } from '@/app/ui/icons/icons'; export function SearchedUserFilesList({ list }: { list: any }) { From 40fa737208ea3fc00dbaaeeb46b7c4050bede03e Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 4 Feb 2026 15:18:38 +0700 Subject: [PATCH 046/119] change dublicate-file-error text --- src/i18n/messages/en.json | 2 +- src/i18n/messages/ru.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 3bfea82..52561a9 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -144,7 +144,7 @@ "pixels": "pixels", "error-processing-file": "Error processing file", "error-uploading-file": "Error uploading file", - "error-duplicate-file": "Such a file already exists", + "error-duplicate-file": "This file has already been uploaded to the system", "unknown-validation-error": "Unknown validation error", "unsupported-file-format": "Unsupported file format.", "the-file-is-too-large": "The file is too large", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 04dd8f6..fc106bd 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -144,7 +144,7 @@ "pixels": "пиксСлСй", "error-processing-file": "Ошибка ΠΏΡ€ΠΈ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ΅ Ρ„Π°ΠΉΠ»Π°", "error-uploading-file": "Ошибка ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ Ρ„Π°ΠΉΠ»Π°", - "error-duplicate-file": "Π’Π°ΠΊΠΎΠΉ Ρ„Π°ΠΉΠ» ΡƒΠΆΠ΅ Π΅ΡΡ‚ΡŒ", + "error-duplicate-file": "Π’Π°ΠΊΠΎΠΉ Ρ„Π°ΠΉΠ» ΡƒΠΆΠ΅ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½ Π² систСму", "unknown-validation-error": "НСизвСстная ошибка Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ", "unsupported-file-format": "НСподдСрТиваСмый Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ Ρ„Π°ΠΉΠ»Π°.", "the-file-is-too-large": "Π€Π°ΠΉΠ» слишком большой", From e778cf7e3343481aadbddde0ae26b2858b198788 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 4 Feb 2026 16:10:16 +0700 Subject: [PATCH 047/119] update error handler --- src/app/actions/fileUpload.ts | 39 +++++++++++++++---- .../{test-component.tsx => TestComponent.tsx} | 0 src/app/components/UploadSectionFile.tsx | 22 ++++++----- src/app/ui/search/section-search-file.tsx | 10 ++--- src/i18n/messages/en.json | 3 +- src/i18n/messages/ru.json | 3 +- 6 files changed, 53 insertions(+), 24 deletions(-) rename src/app/components/{test-component.tsx => TestComponent.tsx} (100%) diff --git a/src/app/actions/fileUpload.ts b/src/app/actions/fileUpload.ts index c3fb85b..60917c9 100644 --- a/src/app/actions/fileUpload.ts +++ b/src/app/actions/fileUpload.ts @@ -2,6 +2,8 @@ import { API_BASE_URL } from '@/app/actions/definitions'; import { getSessionData } from '@/app/actions/session'; +import { FormState } from '@/app/actions/auth'; +import { unknown } from 'zod'; interface initMessageBody { file_name: string, @@ -101,7 +103,12 @@ 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): Promise { try { const response = await fetch(`${API_BASE_URL}/api/v1/files/chunk`, { method: 'POST', @@ -111,17 +118,35 @@ export async function chunkUpload(formData: FormData) { if (response.ok) { let parsed = await response.json(); - if (parsed.message_desc === 'Upload cancelled successfully') { - return parsed.message_body; + if (parsed.message_code === 0) { + console.log('code 0'); + return { + errorMesage: null, + fileId: parsed.message_body.file_id + } + } else if (parsed.message_desc === 'Duplicate file upload') { + throw '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 { - throw parsed; + throw 'error' } } else { - throw (`${response.status}`); + throw 'error' + } + } catch (error: unknown) { + if (error) { + const typedError = error as any; + + return { + errorMesage: typedError + }; + } else { + return { + errorMesage: 'error' + }; } - } catch (error) { - return error } } diff --git a/src/app/components/test-component.tsx b/src/app/components/TestComponent.tsx similarity index 100% rename from src/app/components/test-component.tsx rename to src/app/components/TestComponent.tsx diff --git a/src/app/components/UploadSectionFile.tsx b/src/app/components/UploadSectionFile.tsx index af37ccb..bbfae50 100644 --- a/src/app/components/UploadSectionFile.tsx +++ b/src/app/components/UploadSectionFile.tsx @@ -234,15 +234,11 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile formData.append('upload_id', response.upload_id); formData.append('chunk_number', chunkIndex.toString()); formData.append('chunk', chunk); - /* formData.append('findSimilar', '0'); */ const chunkResponse = await chunkUpload(formData); - if (chunkResponse.message_desc !== 'Chunk uploaded successfully') { - if (chunkResponse.message_desc === 'Duplicate file upload') { - throw (`duplicate-file`); - } - throw new Error(`Chunk ${chunkIndex} upload failed`); + if (chunkResponse.errorMesage !== null) { + throw chunkResponse.errorMesage; } setUploadProgress(Math.floor((chunkIndex + 1) / totalChunks * 100)); @@ -260,10 +256,16 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile } catch (error) { if (!isCancelledRef.current) { - if (error === 'duplicate-file') { - setErrorHandler(t('error-duplicate-file')); - } else { - setErrorHandler(t('error-uploading-file')); + switch (error) { + case 'duplicate-file': + setErrorHandler(t('error-duplicate-file')); + break; + case 'user-not-have-tokens-for-protect': + setErrorHandler(t('error-user-not-have-tokens-for-protect')); + break; + default: + setErrorHandler(t('error-uploading-file')); + break; } } } finally { diff --git a/src/app/ui/search/section-search-file.tsx b/src/app/ui/search/section-search-file.tsx index 4a7e0f3..e93ea28 100644 --- a/src/app/ui/search/section-search-file.tsx +++ b/src/app/ui/search/section-search-file.tsx @@ -196,15 +196,15 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile const chunkResponse = await chunkUpload(formData); - if (chunkResponse.message_desc !== 'Chunk uploaded successfully') { - throw new Error(`Chunk ${chunkIndex} upload failed`); + if (chunkResponse.errorMesage !== null) { + throw chunkResponse.errorMesage; } setUploadProgress(Math.floor((chunkIndex + 1) / totalChunks * 100)); - if (isLastChunk) { - setFileId(chunkResponse.message_body.file_id); - document.cookie = `searchedFileId=${chunkResponse.message_body.file_id}` + if (isLastChunk && chunkResponse.fileId) { + setFileId(chunkResponse.fileId); + document.cookie = `searchedFileId=${chunkResponse.fileId}` } } diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 52561a9..0342d9f 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -205,7 +205,8 @@ "FAILED_SAVE": "Failed save", "view": "View", "are-no-violations": "There are no violations", - "error-reading-image": "Error reading image" + "error-reading-image": "Error reading image", + "error-user-not-have-tokens-for-protect": "User not have tokens for protect" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index fc106bd..2a74095 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -205,7 +205,8 @@ "FAILED_SAVE": "Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π½Π΅ ΡƒΠ΄Π°Π»ΠΎΡΡŒ", "view": "ΠŸΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ", "are-no-violations": "ΠΠ°Ρ€ΡƒΡˆΠ΅Π½ΠΈΠΉ Π½Π΅Ρ‚", - "error-reading-image": "Ошибка чтСния изобраТСния" + "error-reading-image": "Ошибка чтСния изобраТСния", + "error-user-not-have-tokens-for-protect": "Π£ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π½Π΅Ρ‚ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² для Π·Π°Ρ‰ΠΈΡ‚Ρ‹." }, "Login-register-form": { "and": "ΠΈ", From 97b846deec2afc42d6bc904feca41890d6c2bed1 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 4 Feb 2026 16:14:17 +0700 Subject: [PATCH 048/119] remove extra console.log --- src/app/actions/fileUpload.ts | 1 - src/app/ui/dashboard/new/dashboard-user-files.tsx | 7 ------- src/app/ui/forms/reset-password-form.tsx | 1 - src/app/ui/modal-windows/file-info-modal-window.tsx | 2 -- src/app/ui/search/searched-user-files-list.tsx | 2 -- 5 files changed, 13 deletions(-) diff --git a/src/app/actions/fileUpload.ts b/src/app/actions/fileUpload.ts index 60917c9..cae0beb 100644 --- a/src/app/actions/fileUpload.ts +++ b/src/app/actions/fileUpload.ts @@ -119,7 +119,6 @@ export async function chunkUpload(formData: FormData): Promise { let parsed = await response.json(); if (parsed.message_code === 0) { - console.log('code 0'); return { errorMesage: null, fileId: parsed.message_body.file_id diff --git a/src/app/ui/dashboard/new/dashboard-user-files.tsx b/src/app/ui/dashboard/new/dashboard-user-files.tsx index 34632c7..c124180 100644 --- a/src/app/ui/dashboard/new/dashboard-user-files.tsx +++ b/src/app/ui/dashboard/new/dashboard-user-files.tsx @@ -51,7 +51,6 @@ export default function DashboardUserFiles() { 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(); - console.log(newDate); return { id: item.id, @@ -86,12 +85,6 @@ export default function DashboardUserFiles() { return `${day}.${month}.${year}`; }; - useEffect(() => { - console.log(tableData); - }, [tableData]); - - - return (
diff --git a/src/app/ui/forms/reset-password-form.tsx b/src/app/ui/forms/reset-password-form.tsx index 7417924..aab0e9e 100644 --- a/src/app/ui/forms/reset-password-form.tsx +++ b/src/app/ui/forms/reset-password-form.tsx @@ -22,7 +22,6 @@ export default function ResetPasswordForm() { const t = useTranslations('Login-register-form'); const searchParams = useSearchParams(); const email = searchParams.get('email'); - console.log(email); useEffect(() => { if (state) { diff --git a/src/app/ui/modal-windows/file-info-modal-window.tsx b/src/app/ui/modal-windows/file-info-modal-window.tsx index 877ce62..a1b710d 100644 --- a/src/app/ui/modal-windows/file-info-modal-window.tsx +++ b/src/app/ui/modal-windows/file-info-modal-window.tsx @@ -18,8 +18,6 @@ export function FileInfoModalWindow({ fileInfo, setWindowClose, setWindowChildre setWindowClose(false); } - console.log(fileInfo); - return (
diff --git a/src/app/ui/search/searched-user-files-list.tsx b/src/app/ui/search/searched-user-files-list.tsx index 9fb64bb..d5f35ed 100644 --- a/src/app/ui/search/searched-user-files-list.tsx +++ b/src/app/ui/search/searched-user-files-list.tsx @@ -117,8 +117,6 @@ export function SearchedUserFilesList({ list }: { list: any }) { className="results-list" > {list.map((e: any, index: number) => { - console.log(e); - console.log(e.similarityLevel); if (e.similarityLevel !== 'DIFFERENT') { return (
Date: Wed, 4 Feb 2026 16:15:24 +0700 Subject: [PATCH 049/119] increment version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c884217..5936292 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.29.0", + "version": "0.30.0", "private": true, "scripts": { "dev": "next dev -p 2999", From 81614b028cc0e10e675fbee8306b27607c286f1d Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 4 Feb 2026 17:24:57 +0700 Subject: [PATCH 050/119] add force for dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 93db878..11843e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ WORKDIR /no-copy-frontend COPY . . COPY --from=dependencies /no-copy-frontend/node_modules ./node_modules -RUN npm run build +RUN npm run build --force FROM node:22.20.0 AS runner WORKDIR /no-copy-frontend From be130520d94f2fb992c526bb22efa1fdf17f55d8 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 4 Feb 2026 17:26:33 +0700 Subject: [PATCH 051/119] update dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 11843e8..4329947 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ WORKDIR /no-copy-frontend COPY . . COPY --from=dependencies /no-copy-frontend/node_modules ./node_modules -RUN npm run build --force +RUN rm -rf .next && npm run build --force FROM node:22.20.0 AS runner WORKDIR /no-copy-frontend From a84cd397373685c0047c29fdaa3d23bdfcd1e90b Mon Sep 17 00:00:00 2001 From: vladp Date: Wed, 4 Feb 2026 18:10:48 +0700 Subject: [PATCH 052/119] fix name --- src/app/components/{dropDownList.tsx => DropDownList.tsx} | 0 src/app/components/{modalWindow.tsx => ModalWindow.tsx} | 0 src/app/components/{tanstakTable.tsx => TanstakTable.tsx} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/app/components/{dropDownList.tsx => DropDownList.tsx} (100%) rename src/app/components/{modalWindow.tsx => ModalWindow.tsx} (100%) rename src/app/components/{tanstakTable.tsx => TanstakTable.tsx} (100%) diff --git a/src/app/components/dropDownList.tsx b/src/app/components/DropDownList.tsx similarity index 100% rename from src/app/components/dropDownList.tsx rename to src/app/components/DropDownList.tsx diff --git a/src/app/components/modalWindow.tsx b/src/app/components/ModalWindow.tsx similarity index 100% rename from src/app/components/modalWindow.tsx rename to src/app/components/ModalWindow.tsx diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/TanstakTable.tsx similarity index 100% rename from src/app/components/tanstakTable.tsx rename to src/app/components/TanstakTable.tsx From 1037347e068cfcf02aff5d93135291d307e9f5bc Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 5 Feb 2026 11:55:02 +0700 Subject: [PATCH 053/119] add logs --- src/app/actions/auth.ts | 3 +++ src/app/ui/forms/register-form.tsx | 4 ++-- src/app/ui/settings/personal-data-settings.tsx | 12 +++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index fd1b6e4..eb1e03f 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -170,6 +170,8 @@ export async function registration( const validatedFields = SignupFormSchema.safeParse(dataToValidate); + console.log(`try to register ${accountType}`); + if (!validatedFields.success) { const errors: Record = {} JSON.parse(validatedFields.error.message).forEach((obj: { @@ -227,6 +229,7 @@ export async function registration( }; if (parsed.message_desc === 'Operation successful') { + console.log(`registration - ${accountType}`); await createSession(parsed.message_body.token, email as string); } else { throw parsed; diff --git a/src/app/ui/forms/register-form.tsx b/src/app/ui/forms/register-form.tsx index b818b42..7b57edb 100644 --- a/src/app/ui/forms/register-form.tsx +++ b/src/app/ui/forms/register-form.tsx @@ -203,11 +203,11 @@ export default function RegisterForm() { {t('company')}
diff --git a/src/app/ui/settings/personal-data-settings.tsx b/src/app/ui/settings/personal-data-settings.tsx index b7cd91b..4eb4205 100644 --- a/src/app/ui/settings/personal-data-settings.tsx +++ b/src/app/ui/settings/personal-data-settings.tsx @@ -25,8 +25,6 @@ export default function PersonalDataSettings() { } }); - const [selectedValue, setSelectedValue] = useState('male'); - const t = useTranslations('Global'); return ( @@ -44,10 +42,10 @@ export default function PersonalDataSettings() { type="text" name="full_name" className="form-input" - defaultValue={userData.fullName} + defaultValue={userData?.fullName} />
- {userData.company && ( + {userData?.company && (
)} @@ -68,7 +66,7 @@ export default function PersonalDataSettings() { type="text" name="email" className="form-input" - defaultValue={userData.email} + defaultValue={userData?.email} disabled={true} /> @@ -83,7 +81,7 @@ export default function PersonalDataSettings() { type="phone" name="phone" className="form-input" - defaultValue={userData.phone} + defaultValue={userData?.phone} />
+
+ {formState?.error?.password && ( +

+ { + formState?.error?.password.split('&').map((e, index) => { + return ( + + {t(e)} +
+
+ ) + }) + } +

+ )}
-
-
+ {formState?.error?.server && ( +

+ {t(formState?.error?.server)} +

+ )} + diff --git a/src/app/ui/settings/personal-data-settings.tsx b/src/app/ui/settings/personal-data-settings.tsx index 4eb4205..8e4086c 100644 --- a/src/app/ui/settings/personal-data-settings.tsx +++ b/src/app/ui/settings/personal-data-settings.tsx @@ -25,6 +25,10 @@ export default function PersonalDataSettings() { } }); + useEffect(() => { + console.log(userData) + }, [userData]); + const t = useTranslations('Global'); return ( From a684e1dd94ec858df09f77adf01f566f8fcc6bf6 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 5 Feb 2026 19:50:38 +0700 Subject: [PATCH 063/119] update user registration --- src/app/actions/companyActions.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/actions/companyActions.ts b/src/app/actions/companyActions.ts index 5182eae..c6257da 100644 --- a/src/app/actions/companyActions.ts +++ b/src/app/actions/companyActions.ts @@ -87,8 +87,7 @@ export async function companyUserRegistration( phone: phone, password, accountType: 'b2b', - authToken: token, - companyName: '' + authToken: token } }), headers: { From 4f294e8b90db67e561347c85776e863d09f256ce Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 5 Feb 2026 21:01:19 +0700 Subject: [PATCH 064/119] add search stats --- src/app/actions/fileEntity.ts | 61 ----------------- src/app/actions/searchActions.ts | 90 +++++++++++++++++++++++++ src/app/ui/search/file-search-panel.tsx | 8 ++- src/app/ui/search/search-stats.tsx | 46 +++++++++++-- 4 files changed, 137 insertions(+), 68 deletions(-) create mode 100644 src/app/actions/searchActions.ts diff --git a/src/app/actions/fileEntity.ts b/src/app/actions/fileEntity.ts index 4091863..2dfc5f5 100644 --- a/src/app/actions/fileEntity.ts +++ b/src/app/actions/fileEntity.ts @@ -81,67 +81,6 @@ export async function removeUserFile(fileId: string, fullDelete: number) { } } -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&auth_token=${token}`, { - method: 'GET' - }); - - 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 searchGlobalFiles(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: 20007, - message_body: { - file_id: fileId, - } - }), - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - } - }); - - 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 viewFileInfo(fileId: string) { const token = await getSessionData('token'); diff --git a/src/app/actions/searchActions.ts b/src/app/actions/searchActions.ts new file mode 100644 index 0000000..e4689fc --- /dev/null +++ b/src/app/actions/searchActions.ts @@ -0,0 +1,90 @@ +'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&auth_token=${token}`, { + method: 'GET' + }); + + 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 searchGlobalFiles(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: 20007, + message_body: { + file_id: fileId, + } + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + 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 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; + } + } catch (error) { + console.error('error'); + } +} \ No newline at end of file diff --git a/src/app/ui/search/file-search-panel.tsx b/src/app/ui/search/file-search-panel.tsx index 3b1dfd1..75f0c01 100644 --- a/src/app/ui/search/file-search-panel.tsx +++ b/src/app/ui/search/file-search-panel.tsx @@ -1,8 +1,8 @@ import { SearchedUserFilesList } from '@/app/ui/search/searched-user-files-list'; import { SearchedGlobalFilesList } from '@/app/ui/search/searched-global-files-list'; import { useCallback, useState, useImperativeHandle } from 'react'; -import { searchUserFiles, searchGlobalFiles } from '@/app/actions/fileEntity'; -import { fa } from 'zod/v4/locales'; +import { searchUserFiles, searchGlobalFiles } from '@/app/actions/searchActions'; +import { useQueryClient } from '@tanstack/react-query'; export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: any }) { const [searchedUserFiles, setSearchedUserFiles] = useState([]); @@ -10,6 +10,7 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a const [searchedGlobalFiles, setSearchedGlobalFiles] = useState([]); const [searchedGlobalFilesShowNull, setSearchedGlobalFilesShowNull] = useState(false); const [loading, setLoading] = useState(false); + const queryClient = useQueryClient(); useImperativeHandle(ref, () => ({ clearList: () => { @@ -45,6 +46,7 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a try { let result = await searchGlobalFiles(fileId); + console.log(result); if (result.images.length) { setSearchedGlobalFiles(result.images); } else { @@ -53,6 +55,8 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a } catch (error) { } finally { + await queryClient.invalidateQueries({ queryKey: ['userData'] }); + await queryClient.invalidateQueries({ queryKey: ['userSearchData'] }); setLoading(false); } diff --git a/src/app/ui/search/search-stats.tsx b/src/app/ui/search/search-stats.tsx index 9e33d02..5bb8466 100644 --- a/src/app/ui/search/search-stats.tsx +++ b/src/app/ui/search/search-stats.tsx @@ -1,4 +1,26 @@ +'use client' + +import { useQuery } from '@tanstack/react-query'; +import { getSearchStats } from '@/app/actions/searchActions'; +import { useEffect } from 'react'; + export function SearchStats() { + const { + data: userSearchData, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['userSearchData'], + queryFn: () => { + return getSearchStats(); + }, + }); + + useEffect(() => { + console.log(userSearchData); + }, [userSearchData]); + return (
@@ -7,23 +29,37 @@ export function SearchStats() {
Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ - 0 + + {userSearchData?.image?.count ? userSearchData?.image?.count : 0} +
+
Π’ΠΈΠ΄Π΅ΠΎ - 0 + + {userSearchData?.video?.count ? userSearchData?.video?.count : 0} +
+
Аудио - 0 + + {userSearchData?.audio?.count ? userSearchData?.audi?.count : 0} +
+
Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ - 0 + + {userSearchData?.document?.count ? userSearchData?.document?.count : 0} +
+
ВсСго поисков - 0 + + {userSearchData?.allCountForPeriod ? userSearchData?.allCountForPeriod : 0} +
) From 963b649aacd117877c0596a73d6b20f3fa2d4d42 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 5 Feb 2026 21:05:55 +0700 Subject: [PATCH 065/119] update recent search component --- src/app/ui/search/recent-searches.tsx | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/app/ui/search/recent-searches.tsx b/src/app/ui/search/recent-searches.tsx index da7e595..120fb68 100644 --- a/src/app/ui/search/recent-searches.tsx +++ b/src/app/ui/search/recent-searches.tsx @@ -1,5 +1,22 @@ +'use client' + import { IconImageFile, IconDocument, IconAudioFile, IconVideoFile } from '@/app/ui/icons/icons'; +import { useQuery } from '@tanstack/react-query'; +import { getSearchStats } from '@/app/actions/searchActions'; + export function RecentSearches() { + const { + data: userSearchData, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['userSearchData'], + queryFn: () => { + return getSearchStats(); + }, + }); + return (
@@ -9,7 +26,7 @@ export function RecentSearches() { document
-
0 поисков
+
{userSearchData?.document?.count ? userSearchData?.document?.count : 0} поисков
date
@@ -19,7 +36,7 @@ export function RecentSearches() { image
-
0 поисков
+
{userSearchData?.image?.count ? userSearchData?.image?.count : 0} поисков
date
@@ -29,7 +46,7 @@ export function RecentSearches() { audio
-
0 поисков
+
{userSearchData?.audio?.count ? userSearchData?.audi?.count : 0} поисков
date
@@ -39,7 +56,7 @@ export function RecentSearches() { video
-
0 поисков
+
{userSearchData?.video?.count ? userSearchData?.video?.count : 0} поисков
date
From 6d9710e7cd5f9cfd67bc55774c2bd0b1e4e232bd Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 6 Feb 2026 11:13:13 +0700 Subject: [PATCH 066/119] add loading animation --- package.json | 2 +- src/app/styles/pages-styles.scss | 25 +++++++++++++++++++ src/app/ui/forms/login-form.tsx | 14 +++++++++-- src/app/ui/forms/register-form.tsx | 14 +++++++++-- .../settings/company-users-setting-panel.tsx | 14 +++++++++-- 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 60688d3..dc741c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.31.0", + "version": "0.32.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 4a38a8c..06db882 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -3836,4 +3836,29 @@ font-size: 12px; } } +} + +.loading-animation { + display: block; + text-align: center; + + .global-spinner { + width: 24px; + height: 24px; + margin: 0 auto; + position: relative; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 4px solid #e5e7eb; + border-top: 4px solid #667eea; + border-radius: 50%; + animation: spin 1s linear infinite; + } + } } \ No newline at end of file diff --git a/src/app/ui/forms/login-form.tsx b/src/app/ui/forms/login-form.tsx index 8f25c76..d97c2fa 100644 --- a/src/app/ui/forms/login-form.tsx +++ b/src/app/ui/forms/login-form.tsx @@ -96,8 +96,18 @@ export default function LoginForm() { {t('forgot-password')}?
- ) diff --git a/src/app/ui/forms/register-form.tsx b/src/app/ui/forms/register-form.tsx index 7234cb0..c554719 100644 --- a/src/app/ui/forms/register-form.tsx +++ b/src/app/ui/forms/register-form.tsx @@ -432,8 +432,18 @@ export default function RegisterForm() { {t(formState?.error?.server)}

)} - diff --git a/src/app/ui/settings/company-users-setting-panel.tsx b/src/app/ui/settings/company-users-setting-panel.tsx index 280bf7a..0289a24 100644 --- a/src/app/ui/settings/company-users-setting-panel.tsx +++ b/src/app/ui/settings/company-users-setting-panel.tsx @@ -304,8 +304,18 @@ export function CompanyUsersSettingPanel() {

)} -
From c4de107fba7102b01e71183fa2cefb233615db07 Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 6 Feb 2026 12:37:22 +0700 Subject: [PATCH 067/119] add tanstak table for invite history --- .../ui/referral-page/invitations-table.tsx | 558 +++++++++++++++--- src/i18n/messages/en.json | 3 +- src/i18n/messages/ru.json | 3 +- 3 files changed, 496 insertions(+), 68 deletions(-) diff --git a/src/app/ui/referral-page/invitations-table.tsx b/src/app/ui/referral-page/invitations-table.tsx index c051c40..6fc8d2b 100644 --- a/src/app/ui/referral-page/invitations-table.tsx +++ b/src/app/ui/referral-page/invitations-table.tsx @@ -1,77 +1,503 @@ -import { useTranslations } from 'next-intl'; +'use client' + +import { useState, useMemo, useEffect } from 'react'; +import { + useReactTable, + getCoreRowModel, + getSortedRowModel, + getPaginationRowModel, + getFilteredRowModel, + ColumnDef, + SortingState, + ColumnFiltersState, +} from '@tanstack/react-table'; +import { IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconShieldExclamation } from '@/app/ui/icons/icons'; +import { useTranslations, useLocale } from 'next-intl'; +import DropDownList from '@/app/components/DropDownList'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { toast } from 'sonner'; +import { pluralize } from '@/app/lib/pluralize'; + +type FileItem = { + status?: string; + email?: string; + registrationData: number; +}; + +type ApiFile = { + status?: string; + email?: string; + registrationData: string; +}; + +type ApiResponse = { + files?: ApiFile[]; +}; + + export default function InvitationsTable() { - const t = useTranslations('Global'); + const { + data: testData, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['testData'], + queryFn: () => { + return {} + }, + + select: (data: ApiResponse): FileItem[] => { + if (!data?.files) return [ + { + status: 'string1', + email: 'string1', + registrationData: 1 + }, + { + status: 'string2', + email: 'string2', + registrationData: 2 + }, + { + status: 'string3', + email: 'string3', + registrationData: 3 + } + ]; + + return data.files.map((item: ApiFile) => { + return { + status: 'string', + email: 'string', + registrationData: 40 + }; + }); + }, + }); + + const queryClient = useQueryClient(); + + // Бостояния + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); + const [dateFilter, setDateFilter] = useState('all'); + const [statusFilter, setStatusFilter] = useState('all'); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + + const t = useTranslations("Global"); + const locale = useLocale(); + + // ΠžΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ + const columns = useMemo[]>( + () => [ + { + accessorKey: 'email', + header: ({ column }) => ( +
+ + {t('email')} + + +
+ ), + cell: ({ row }) => ( +
+ {/* {row.original.size !== undefined ? convertBytes(row.original.size) : '-'} */} + {row.original.email} +
+ ), + }, + { + accessorKey: 'status', + header: ({ column }) => ( +
+ + {t('status')} + + +
+ ), + cell: ({ row }) => { + return ( +
+ + {row.original.status} + +
+ ) + }, + }, + { + accessorKey: 'registrationData', + header: ({ column }) => ( +
+ + {t('registration-date')} + + +
+ ), + cell: ({ row }) => { + return ( +
+ {row.original.registrationData} +
+ ) + }, + enableColumnFilter: false, + } + ], + [] + ); + + // Π€ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΡ ΠΏΠΎ Ρ‚ΠΈΠΏΡƒ Ρ„Π°ΠΉΠ»Π° ΠΈ Π΄Π°Ρ‚Π΅ + const filteredData = useMemo(() => { + let result = testData; + if (!result) { + return []; + } + + // Π€ΠΈΠ»ΡŒΡ‚Ρ€ ΠΏΠΎ Ρ‚ΠΈΠΏΡƒ Ρ„Π°ΠΉΠ»Π° + if (statusFilter !== 'all') { + result = result.filter(item => item.status === statusFilter); + } + + // Π€ΠΈΠ»ΡŒΡ‚Ρ€ ΠΏΠΎ Π΄Π°Ρ‚Π΅ + 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.registrationData) { + return item.registrationData >= todayStart + } else { + return 0 + } + }); + break; + case 'week': + const weekAgo = now - sevenDays; + result = result.filter(item => { + if (item.registrationData) { + return item.registrationData >= weekAgo; + } + }); + break; + case 'month': + const monthAgo = now - thirtyDays; + result = result.filter(item => { + if (item.registrationData) { + return item.registrationData >= monthAgo; + } + }); + break; + case 'older': + const monthAgo2 = now - thirtyDays; + result = result.filter(item => { + if (item.registrationData) { + return item.registrationData < monthAgo2; + } + }); + break; + } + } + + return result; + }, [testData, statusFilter, 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 ( -
-

- Π˜ΡΡ‚ΠΎΡ€ΠΈΡ ΠΏΡ€ΠΈΠ³Π»Π°ΡˆΠ΅Π½ΠΈΠΉ -

+
+
+

+ Π˜ΡΡ‚ΠΎΡ€ΠΈΡ ΠΏΡ€ΠΈΠ³Π»Π°ΡˆΠ΅Π½ΠΈΠΉ +

+ {/* Π€ΠΈΠ»ΡŒΡ‚Ρ€Ρ‹ */} +
+
+
+
{t('date-filter')}:
+ { + 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} + > +
  • + {t('all-dates')} +
  • +
  • + {t('today')} +
  • +
  • + {t('for-a-week')} +
  • +
  • + {t('for-a-month')} +
  • +
  • + {t('older-than-a-month')} +
  • +
    +
    +
    -
    -
    - Email -
    -
    - Бтатус -
    -
    - Π—Π°Ρ€Π°Π±ΠΎΡ‚Π°Π½ΠΎ -
    -
    - Π”Π°Ρ‚Π° ΠΏΡ€ΠΈΠ³Π»Π°ΡˆΠ΅Π½ΠΈΡ -
    -
    - РСгистрация +
    +
    {t('items-per-page')}:
    + + {[5, 10, 20, 50, 100].map(pageSize => ( +
  • + {t('show')} {pageSize} +
  • + ))} +
    +
    -
    - email@mail.com -
    -
    - {t('status')} -
    -
    - 0 -
    -
    - 0 -
    -
    - 0 + {/* Π’Π°Π±Π»ΠΈΡ†Π° */} +
    + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + + {table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + ))} + + )) + ) : ( + + + + )} + +
    + {header.isPlaceholder + ? null + : typeof header.column.columnDef.header === 'function' + ? header.column.columnDef.header(header.getContext()) + : header.column.columnDef.header as string} +
    + {typeof cell.column.columnDef.cell === 'function' + ? cell.column.columnDef.cell(cell.getContext()) + : cell.getValue() as string} +
    + {t('no-data-for-selected-filters')} +
    -
    - email@mail.com -
    -
    - {t('status')} -
    -
    - 0 -
    -
    - 0 -
    -
    - 0 -
    + {/* ΠŸΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΡ */} +
    +
    + {/* + {t('page')}{' '} + + {table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1} + + + + | {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {pluralizeFiles(filteredData.length || 0)} + */} +
    -
    - email@mail.com +
    + {table.getCanPreviousPage() && ( + + )} + {table.getCanPreviousPage() && ( + + )} + +
    + {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 ( + + ); + } + return null; + })} +
    + + {table.getCanNextPage() && ( + + )} + {table.getCanNextPage() && ( + + )} +
    -
    - {t('status')} -
    -
    - 0 -
    -
    - 0 -
    -
    - 0 -
    -
    +
    - ) + ); } \ No newline at end of file diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 0342d9f..ec72fb4 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -206,7 +206,8 @@ "view": "View", "are-no-violations": "There are no violations", "error-reading-image": "Error reading image", - "error-user-not-have-tokens-for-protect": "User not have tokens for protect" + "error-user-not-have-tokens-for-protect": "User not have tokens for protect", + "registration-date": "Registration date" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 2a74095..13f981c 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -206,7 +206,8 @@ "view": "ΠŸΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ", "are-no-violations": "ΠΠ°Ρ€ΡƒΡˆΠ΅Π½ΠΈΠΉ Π½Π΅Ρ‚", "error-reading-image": "Ошибка чтСния изобраТСния", - "error-user-not-have-tokens-for-protect": "Π£ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π½Π΅Ρ‚ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² для Π·Π°Ρ‰ΠΈΡ‚Ρ‹." + "error-user-not-have-tokens-for-protect": "Π£ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π½Π΅Ρ‚ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² для Π·Π°Ρ‰ΠΈΡ‚Ρ‹.", + "registration-date": "Π”Π°Ρ‚Π° рСгистрации" }, "Login-register-form": { "and": "ΠΈ", From f6ca797802673926d1f2e07a395dd2bae75857ba Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 6 Feb 2026 13:13:29 +0700 Subject: [PATCH 068/119] add referal code for registration --- src/app/actions/auth.ts | 2 ++ src/app/ui/forms/register-form.tsx | 51 ++++++++++++++++++++++++++++-- src/i18n/messages/en.json | 3 +- src/i18n/messages/ru.json | 3 +- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index 417c54d..5d73ed9 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -137,6 +137,7 @@ export type FormState = { phone: string; companyName?: string; agree: string; + referralCode?: string }, mailConfirm?: boolean, error: Record | null @@ -155,6 +156,7 @@ export async function registration( const phone = formData.get('phone') as string || ''; const companyName = formData.get('companyName') as string || ''; const agree = formData.get('agree') as string || ''; + const referralCode = formData.get('referralCode'); const SignupFormSchema = await getSignupFormSchema(accountType); diff --git a/src/app/ui/forms/register-form.tsx b/src/app/ui/forms/register-form.tsx index c554719..19a49ef 100644 --- a/src/app/ui/forms/register-form.tsx +++ b/src/app/ui/forms/register-form.tsx @@ -8,9 +8,13 @@ import Link from 'next/link'; import { useTranslations } from 'next-intl'; import { IconEye } from '@/app/ui/icons/icons'; import { SignupFormSchema } from '@/app/actions/definitions'; +import { useSearchParams } from 'next/navigation'; import * as z from 'zod' export default function RegisterForm() { + const searchParams = useSearchParams(); + const referralCode = searchParams.get('referralCode'); + const [state, formAction, isPending] = useActionState( registration, undefined, @@ -37,6 +41,10 @@ export default function RegisterForm() { }, [state]) + useEffect(() => { + console.log(referralCode); + }, [referralCode]); + function showPassowrd(target: 'password' | 'confirm-password') { if (target === 'password') { setShowPassword(!showPassword); @@ -77,6 +85,7 @@ export default function RegisterForm() { case 'email': case 'fullName': case 'phone': + case 'referralCode': case 'password': case 'companyName': case 'agree': @@ -87,6 +96,9 @@ export default function RegisterForm() { if (fieldName === 'companyName' && accountType === 'b2c') { return { success: true, data: value }; } + if (fieldName === 'referralCode') { + return { success: true, data: value }; + } return schema.safeParse(value); default: @@ -242,7 +254,7 @@ export default function RegisterForm() { )}
    - {accountType === 'b2b' && ( + {accountType === 'b2b' ? (
    - )} + ) : ( +
    + + { + e.target.value = e.target.value.toLocaleLowerCase(); + onChangeHandler(e) + }} + /> + {formState?.error?.referralCode && ( +

    + { + formState?.error?.referralCode.split('&').map((e, index) => { + return ( + + {t(e)} +
    +
    + ) + }) + } +

    + )} +
    + ) + }
    +
    +
    @@ -18,7 +18,7 @@ export default function IncomeAndPayments() {

    πŸ“ˆ ВсСго Π·Π°Ρ€Π°Π±ΠΎΡ‚Π°Π½ΠΎ

    - 135.00 Π‘ + 0

    Π—Π° всС врСмя участия Π² ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ΅
    @@ -26,7 +26,7 @@ export default function IncomeAndPayments() {

    βœ… Π’Ρ‹ΠΏΠ»Π°Ρ‡Π΅Π½ΠΎ

    - 0.00 Π‘ + 0

    УспСшно Π²Ρ‹ΠΏΠ»Π°Ρ‡Π΅Π½ΠΎ Π½Π° ваши счСта
    @@ -70,8 +70,8 @@ export default function IncomeAndPayments() { ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½ΠΎ
    -
    - 100.00 +
    + 0
    0 @@ -82,39 +82,7 @@ export default function IncomeAndPayments() {
    0
    -
    - 0 -
    - -
    - 100.00 -
    -
    - 0 -
    -
    - 0 -
    -
    - 0 -
    -
    - 0 -
    - -
    - 100.00 -
    -
    - 0 -
    -
    - 0 -
    -
    - 0 -
    -
    +
    0
    diff --git a/src/app/ui/referral-page/commission-levels.tsx b/src/app/ui/referral-page/commission-levels.tsx index 82183b7..d1123ec 100644 --- a/src/app/ui/referral-page/commission-levels.tsx +++ b/src/app/ui/referral-page/commission-levels.tsx @@ -1,4 +1,38 @@ +'use client' + +import { useQuery } from '@tanstack/react-query'; +import { fetchReferralsLevels } from '@/app/actions/referralsActions'; +import { useEffect } from 'react'; + export default function CommissionLevels() { + const { + data: referralsLevels, + isLoading, + isError, + error + } = useQuery({ + queryKey: ['referralsLevels'], + queryFn: () => { + return fetchReferralsLevels(); + }, + select: (data) => { + if (data?.message_body) { + return data?.message_body + } else { + return [] + } + } + }); + + useEffect(() => { + console.log('referralsLevels'); + console.log(referralsLevels); + }, [referralsLevels]) + + if (!referralsLevels?.length) { + return null; + } + return (

    diff --git a/src/app/ui/referral-page/invitations-table.tsx b/src/app/ui/referral-page/invitations-table.tsx index 6fc8d2b..6b6fa7f 100644 --- a/src/app/ui/referral-page/invitations-table.tsx +++ b/src/app/ui/referral-page/invitations-table.tsx @@ -17,38 +17,34 @@ import DropDownList from '@/app/components/DropDownList'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { pluralize } from '@/app/lib/pluralize'; +import { fetchReferralInvitees } from '@/app/actions/referralsActions'; -type FileItem = { +type InviteesType = { status?: string; email?: string; registrationData: number; }; -type ApiFile = { - status?: string; +type InviteType = { + active?: string; email?: string; - registrationData: string; + regDate: number; }; -type ApiResponse = { - files?: ApiFile[]; -}; - - export default function InvitationsTable() { const { - data: testData, + data: referralInvitees, isLoading, isError, error, - } = useQuery({ - queryKey: ['testData'], + } = useQuery({ + queryKey: ['referralInvitees'], queryFn: () => { - return {} + return fetchReferralInvitees(); }, - select: (data: ApiResponse): FileItem[] => { - if (!data?.files) return [ + select: (data: InviteType[]): InviteesType[] => { + if (!data) return [ { status: 'string1', email: 'string1', @@ -66,11 +62,11 @@ export default function InvitationsTable() { } ]; - return data.files.map((item: ApiFile) => { + return data.map((item: InviteType) => { return { - status: 'string', - email: 'string', - registrationData: 40 + status: item.active, + email: item.email, + registrationData: item.regDate }; }); }, @@ -92,7 +88,7 @@ export default function InvitationsTable() { const locale = useLocale(); // ΠžΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ»ΠΎΠ½ΠΎΠΊ - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { accessorKey: 'email', @@ -207,7 +203,7 @@ export default function InvitationsTable() { // Π€ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΡ ΠΏΠΎ Ρ‚ΠΈΠΏΡƒ Ρ„Π°ΠΉΠ»Π° ΠΈ Π΄Π°Ρ‚Π΅ const filteredData = useMemo(() => { - let result = testData; + let result = referralInvitees; if (!result) { return []; } @@ -263,7 +259,7 @@ export default function InvitationsTable() { } return result; - }, [testData, statusFilter, dateFilter]); + }, [referralInvitees, statusFilter, dateFilter]); useEffect(() => { const currentPageRows = table.getRowModel().rows; diff --git a/src/app/ui/referral-page/referral-link-section.tsx b/src/app/ui/referral-page/referral-link-section.tsx index e406f43..adbfbf0 100644 --- a/src/app/ui/referral-page/referral-link-section.tsx +++ b/src/app/ui/referral-page/referral-link-section.tsx @@ -1,18 +1,71 @@ +'use client' + +import { fetchReferralUserStats } from '@/app/actions/referralsActions'; +import { useQuery } from '@tanstack/react-query'; +import { useTranslations } from 'next-intl'; +import { useEffect, useRef, useState } from 'react'; +import { toast } from 'sonner'; + export default function ReferralLinkSection() { + const { + data: referralUserStats, + isLoading, + isError, + error + } = useQuery({ + queryKey: ['referralUserStats'], + queryFn: () => { + return fetchReferralUserStats(); + }, + select: (data) => { + return data; + } + }); + + const t = useTranslations('Global') + const [referralUrl, setReferralUrl] = useState(''); + const refLink = useRef(null); + + useEffect(() => { + if (typeof window !== 'undefined') { + setReferralUrl(`${window.location.origin}/register?ref=${referralUserStats?.referralLink}`); + } + }, [referralUserStats]); + + const copyHandler = () => { + if (refLink.current?.textContent) { + navigator.clipboard.writeText(refLink.current?.textContent); + toast.success(`${t('copied')}: ${refLink.current?.textContent}`) + } + }; + + if (!referralUserStats?.referralLink) { + return null; + } + return (
    -

    πŸ”— Π’Π°ΡˆΠ° Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΡŒΠ½Π°Ρ ссылка

    +

    Π’Π°ΡˆΠ° Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΡŒΠ½Π°Ρ ссылка

    ΠŸΠΎΠ΄Π΅Π»ΠΈΡ‚Π΅ΡΡŒ этой ссылкой с Π΄Ρ€ΡƒΠ·ΡŒΡΠΌΠΈ ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π°ΠΉΡ‚Π΅ комиссию с ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΈΡ… ΠΏΠΎΠΊΡƒΠΏΠΊΠΈ

    - - https://dashboard.no-copy.ru/register.php?ref=REFA0406AC8 + + {referralUrl} - +

    - Код: REFA0406AC8 β€’ - Кликов: 21 β€’ - Комиссия: 15% + Код: {referralUserStats?.referralLink} β€’ + ΠŸΡ€ΠΈΠ³Π»Π°ΡˆΠ΅Π½ΠΈΠΉ: + {referralUserStats?.totalInvitee} +

    ) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index ce849ab..b6aee7a 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -212,7 +212,8 @@ "are-no-violations": "There are no violations", "error-reading-image": "Error reading image", "error-user-not-have-tokens-for-protect": "User not have tokens for protect", - "registration-date": "Registration date" + "registration-date": "Registration date", + "copied": "Copied" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index c35bc9b..bd79f56 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -212,7 +212,8 @@ "are-no-violations": "ΠΠ°Ρ€ΡƒΡˆΠ΅Π½ΠΈΠΉ Π½Π΅Ρ‚", "error-reading-image": "Ошибка чтСния изобраТСния", "error-user-not-have-tokens-for-protect": "Π£ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π½Π΅Ρ‚ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² для Π·Π°Ρ‰ΠΈΡ‚Ρ‹.", - "registration-date": "Π”Π°Ρ‚Π° рСгистрации" + "registration-date": "Π”Π°Ρ‚Π° рСгистрации", + "copied": "Π‘ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π½ΠΎ" }, "Login-register-form": { "and": "ΠΈ", From e36d67be17c9fcff690e942b285a935a865591ba Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 9 Feb 2026 11:33:16 +0700 Subject: [PATCH 073/119] fix translate --- src/i18n/messages/en.json | 2 +- src/i18n/messages/ru.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index b6aee7a..aeb8f1b 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -253,7 +253,7 @@ "register-error-password-min": "Password must be at least 8 characters long.", "register-error-phone-min": "Please enter a phone number.", "register-error-company-name-min": "The company name must contain at least 2 characters.", - "register-error-company-name-max": "The company name must exceed 200 characters.", + "register-error-company-name-max": "The company name must not exceed 200 characters.", "remember-me": "Remember me", "repeat-password": "Repeat password", "request-ended-with-an-error": "The request ended with an error", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index bd79f56..f0c6ad0 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -253,7 +253,7 @@ "register-error-password-min": "Π”Π»ΠΈΠ½Π° пароля Π΄ΠΎΠ»ΠΆΠ½Π° Π±Ρ‹Ρ‚ΡŒ Π½Π΅ ΠΌΠ΅Π½Π΅Π΅ 8 символов.", "register-error-phone-min": "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, Π²Π²Π΅Π΄ΠΈΡ‚Π΅ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€.", "register-error-company-name-min": "НазваниС ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ Π½Π΅ ΠΌΠ΅Π½Π΅Π΅ 2 символов.", - "register-error-company-name-max": "НазваниС ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΠΏΡ€Π΅Π²Ρ‹ΡˆΠ°Ρ‚ΡŒ 200 символов.", + "register-error-company-name-max": "НазваниС ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΠΏΡ€Π΅Π²Ρ‹ΡˆΠ°Ρ‚ΡŒ 200 символов.", "remember-me": "Π—Π°ΠΏΠΎΠΌΠ½ΠΈΡ‚ΡŒ мСня", "repeat-password": "ΠŸΠΎΠ²Ρ‚ΠΎΡ€ΠΈΡ‚Π΅ ΠΏΠ°Ρ€ΠΎΠ»ΡŒ", "request-ended-with-an-error": "Запрос Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»ΡΡ с ошибкой", From e8b9b5e1b99e56f9d28fb8490de346f3f74f207c Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 9 Feb 2026 13:11:25 +0700 Subject: [PATCH 074/119] add referrals levels --- package.json | 2 +- .../[locale]/pages/marking-document/page.tsx | 66 ++++------- src/app/actions/referralsActions.ts | 6 +- src/app/ui/navigation/nav-link-dropdown.tsx | 17 ++- src/app/ui/navigation/nav-links.tsx | 8 +- .../ui/referral-page/commission-levels.tsx | 103 ++++++++++++------ 6 files changed, 115 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index cf8a32c..2e90991 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.34.0", + "version": "0.35.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/[locale]/pages/marking-document/page.tsx b/src/app/[locale]/pages/marking-document/page.tsx index ce05821..dc51914 100644 --- a/src/app/[locale]/pages/marking-document/page.tsx +++ b/src/app/[locale]/pages/marking-document/page.tsx @@ -1,49 +1,29 @@ -import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import FilesTable from '@/app/ui/dashboard/files-table'; -import PageTitle from '@/app/ui/page-title'; import UploadSectionFile from '@/app/components/UploadSectionFile'; import { getAllowedFilesExtensions } from '@/app/actions/fileUpload'; -import { StackedBarChart } from '@/app/components/StackedBarChart'; import PageTitleColorFrame from '@/app/ui/page-title-color-frame'; import ProtectionStatistic from '@/app/ui/marking-page/new/protection-statistic'; - -const data = [ - { - name: 'File A', - checks: 4, - violations: 2 - }, - { - name: 'File B', - checks: 8, - violations: 4 - }, - { - name: 'File C', - checks: 2, - violations: 1 - }, - { - name: 'File D', - checks: 7, - violations: 1 - }, - { - name: 'File E', - checks: 5, - violations: 4 - }, - { - name: 'File F', - checks: 5, - violations: 3 - } -]; +import { fetchReferralUserStats } from '@/app/actions/referralsActions'; +import { getQueryClient } from '@/app/providers/getQueryClient'; export default async function Page() { const FILE_TYPE = "document"; const { file_extension, max_file_size } = await getAllowedFilesExtensions(FILE_TYPE); + const queryClient = getQueryClient(); + + try { + const data = await queryClient.fetchQuery({ + queryKey: ['referralUserStats'], + queryFn: fetchReferralUserStats, + }); + if (data?.referralLink) { + return null; + } + } catch (error) { + console.error('Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ статистики Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΠΎΠ²:', error); + } + return (
    -{/*
    - -
    - -
    -
    */} - +
    - ) + ); } \ No newline at end of file diff --git a/src/app/actions/referralsActions.ts b/src/app/actions/referralsActions.ts index b1bfdc5..5b40846 100644 --- a/src/app/actions/referralsActions.ts +++ b/src/app/actions/referralsActions.ts @@ -27,7 +27,10 @@ export async function fetchReferralsLevels() { let parsed = await response.json(); if (parsed.message_code === 0) { - return parsed.message_body.length ? parsed.message_body : parsed; + return { + referrals: parsed.message_body, + error: null + } } else { throw parsed; } @@ -37,6 +40,7 @@ export async function fetchReferralsLevels() { } catch (error) { return { + referrals: [], error: error } } diff --git a/src/app/ui/navigation/nav-link-dropdown.tsx b/src/app/ui/navigation/nav-link-dropdown.tsx index 6dea497..7c899ad 100644 --- a/src/app/ui/navigation/nav-link-dropdown.tsx +++ b/src/app/ui/navigation/nav-link-dropdown.tsx @@ -5,7 +5,9 @@ import { usePathname } from 'next/navigation'; import { useTranslations } from 'next-intl'; import { useSideMenuStore } from '@/app/stores/sideMenuStore'; -export default function DropDownList() { +export default function DropDownList( + { referralLink }: { referralLink: string | null } +) { const pathname = usePathname().replace('/en/', '/').replace('/ru/', '/'); const [dropDownExpanded, setDropDownExpanded] = useState(false); const t = useTranslations('Global'); @@ -17,19 +19,19 @@ export default function DropDownList() { const links = [ { - name: t('photo-marking'), + name: 'photo-marking', href: '/pages/marking-images' }, { - name: t('video-marking'), + name: 'video-marking', href: '/pages/marking-video' }, { - name: t('audio-marking'), + name: 'audio-marking', href: '/pages/marking-audio' }, { - name: t('document-marking'), + name: 'document-marking', href: '/pages/marking-document' } ]; @@ -54,6 +56,9 @@ export default function DropDownList() {

      {links.map((link) => { + if (referralLink && link.name === 'document-marking') { + return null; + } return ( -

      {link.name}

      +

      {t(link.name)}

      ); })} diff --git a/src/app/ui/navigation/nav-links.tsx b/src/app/ui/navigation/nav-links.tsx index ed61e04..3788d2d 100644 --- a/src/app/ui/navigation/nav-links.tsx +++ b/src/app/ui/navigation/nav-links.tsx @@ -26,7 +26,11 @@ export default function NavLinks() { return fetchReferralUserStats(); }, select: (data) => { - return data?.referralLink; + if (data?.referralLink) { + return data?.referralLink; + } else { + return null; + } } }); @@ -83,7 +87,7 @@ export default function NavLinks() {

      {t('home')}

      - + {links.map((link) => { if (!referralLink && link.name === 'referral-program') { diff --git a/src/app/ui/referral-page/commission-levels.tsx b/src/app/ui/referral-page/commission-levels.tsx index d1123ec..9b5496d 100644 --- a/src/app/ui/referral-page/commission-levels.tsx +++ b/src/app/ui/referral-page/commission-levels.tsx @@ -1,7 +1,7 @@ 'use client' import { useQuery } from '@tanstack/react-query'; -import { fetchReferralsLevels } from '@/app/actions/referralsActions'; +import { fetchReferralsLevels, fetchReferralUserStats } from '@/app/actions/referralsActions'; import { useEffect } from 'react'; export default function CommissionLevels() { @@ -16,23 +16,58 @@ export default function CommissionLevels() { return fetchReferralsLevels(); }, select: (data) => { - if (data?.message_body) { - return data?.message_body + if (data?.referrals) { + return data?.referrals } else { return [] } } }); - useEffect(() => { - console.log('referralsLevels'); - console.log(referralsLevels); - }, [referralsLevels]) + const { + data: activeInvites, + error: activeInvitesError + } = useQuery({ + queryKey: ['referralUserStats'], + queryFn: () => { + return fetchReferralUserStats(); + }, + select: (data) => { + if (data?.activeInvitee) { + return data.activeInvitee + } else { + return 0 + }; + } + }); if (!referralsLevels?.length) { return null; } + function getReferralIcon(referral: string) { + switch (referral) { + case 'bronze': + return 'πŸ₯‰' + case 'silver': + return 'πŸ₯ˆ' + case 'gold': + return 'πŸ₯‡' + case 'platinum': + return 'πŸ’Ž' + default: + return 'πŸ₯‰' + } + } + + function isActiveReferralLevel(activeInvites: number, maxInvites: number, minInvites: number) { + if (activeInvites <= maxInvites && activeInvites >= minInvites) { + return true + } else { + return false + } + } + return (

      @@ -40,32 +75,34 @@ export default function CommissionLevels() { Π£Ρ€ΠΎΠ²Π½ΠΈ комиссии

      -
      -
      πŸ₯‰
      -
      15%
      -
      BRONZE
      -
      0-5 Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΠΎΠ²
      -
      -
      -
      πŸ₯ˆ
      -
      18%
      -
      SILVER
      -
      6-15 Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΠΎΠ²
      -
      - -
      -
      πŸ₯‡
      -
      22%
      -
      GOLD
      -
      16-50 Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΠΎΠ²
      -
      - -
      -
      πŸ’Ž
      -
      25%
      -
      PLATINUM
      -
      50+ Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΠΎΠ²
      -
      + { + referralsLevels.map((item: { + id: string, + share: string, + name: string, + minInvitee: number, + maxInvitee: number + }) => { + return ( +
      +
      + {getReferralIcon(item.id)} +
      +
      {item.share}%
      +
      {item.name}
      +
      { + item.maxInvitee < 100 ? `${item.minInvitee}-${item.maxInvitee}` : `${item.minInvitee}+` + } Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΠΎΠ² +
      +
      + ) + }) + }
      ) From 82d795c1c522c37cf55c3eeebb33a54b55434862 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 9 Feb 2026 13:17:49 +0700 Subject: [PATCH 075/119] hide inactive settings --- src/app/[locale]/pages/settings/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/[locale]/pages/settings/page.tsx b/src/app/[locale]/pages/settings/page.tsx index faa2775..c6cd245 100644 --- a/src/app/[locale]/pages/settings/page.tsx +++ b/src/app/[locale]/pages/settings/page.tsx @@ -17,10 +17,10 @@ export default function Page() {
      - + {/* */} - + {/* */}
      From da99d7e2a87de667bd67b2b2db22f21f56643efc Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 9 Feb 2026 13:33:42 +0700 Subject: [PATCH 076/119] edit error message --- src/i18n/messages/en.json | 4 ++-- src/i18n/messages/ru.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index aeb8f1b..ebf1b59 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -278,8 +278,8 @@ "register-error-company-name-not-allowed-symbols": "The company name must not contain symbols.", "password-too-common": "This password is too common, please choose another.", "register-error-email-min": "Email length must be at least 7 characters.", - "register-error-phone-not-allowed-symbols": "", - "register-error-password-not-allowed-symbols": "Password contains invalid characters.", + "register-error-phone-not-allowed-symbols": "Phone contains invalid characters.", + "register-error-password-not-allowed-symbols": "Password contains invalid characters. Can be used @.!#$%&'*+/=?^_{|}~-", "confirmation-code": "Confirmation code", "enter-confirmation-code": "Enter the confirmation code", "personal": "Personal", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index f0c6ad0..5cf74ea 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -278,8 +278,8 @@ "register-error-company-name-not-allowed-symbols": "НазваниС ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ символов.", "password-too-common": "Π­Ρ‚ΠΎΡ‚ ΠΏΠ°Ρ€ΠΎΠ»ΡŒ слишком простой, поТалуйста, Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ Π΄Ρ€ΡƒΠ³ΠΎΠΉ.", "register-error-email-min": "Π”Π»ΠΈΠ½Π° Email Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½Π° Π±Ρ‹Ρ‚ΡŒ мСньшС 7 символов.", - "register-error-phone-not-allowed-symbols": "", - "register-error-password-not-allowed-symbols": "ΠŸΠ°Ρ€ΠΎΠ»ΡŒ содСрТит нСдопустимыС символы.", + "register-error-phone-not-allowed-symbols": "Π’Π΅Π»Π΅Ρ„ΠΎΠ½ содСрТит нСдопустимыС символы.", + "register-error-password-not-allowed-symbols": "ΠŸΠ°Ρ€ΠΎΠ»ΡŒ содСрТит нСдопустимыС символы. МоТно ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ @.!#$%&'*+/=?^_{|}~-", "confirmation-code": "Код подтвСрТдСния", "enter-confirmation-code": "Π’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΠ΄ подтвСрТдСния", "personal": "Π›ΠΈΡ‡Π½Ρ‹ΠΉ", From b6c5c5991913ece7890e6feeffbadb37b6fd23f6 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 9 Feb 2026 16:07:49 +0700 Subject: [PATCH 077/119] add ref link error, add refill button, update referral invites table --- src/app/actions/auth.ts | 18 +++--- src/app/actions/referralsActions.ts | 56 ++++++++++++++++++- src/app/ui/payment/secure-payments.tsx | 22 ++++++++ .../ui/referral-page/invitations-table.tsx | 35 ++++-------- src/i18n/messages/en.json | 3 +- src/i18n/messages/ru.json | 3 +- 6 files changed, 102 insertions(+), 35 deletions(-) diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index 554712a..e68cfe6 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -214,8 +214,8 @@ export async function registration( phone: phone, password, accountType, - /* referralLink: referralCode, */ - ...(accountType === 'b2b' && { companyName: companyName }) + ...(accountType === 'b2b' && { companyName: companyName }), + ...(referralCode !== '' && { referralLink: referralCode }) } }), headers: { @@ -230,8 +230,8 @@ export async function registration( phone: phone, password, accountType, - /* referralLink: referralCode, */ - ...(accountType === 'b2b' && { companyName: companyName }) + ...(accountType === 'b2b' && { companyName: companyName }), + ...(referralCode !== '' && { referralLink: referralCode }) }) if (response.ok) { @@ -282,9 +282,13 @@ export async function registration( switch (typedError.message_code) { case 1: - errors['server'] = 'email-or-already-registered' - break; - + if (typedError.message_desc === 'Refferal link is not exist') { + errors['server'] = 'referral-link-is-not-exist' + break; + } else { + errors['server'] = 'email-or-already-registered' + break; + } case 2: if (typedError.message_body?.fieldErrors) { typedError.message_body?.fieldErrors?.forEach((obj: { diff --git a/src/app/actions/referralsActions.ts b/src/app/actions/referralsActions.ts index 5b40846..01a6020 100644 --- a/src/app/actions/referralsActions.ts +++ b/src/app/actions/referralsActions.ts @@ -97,7 +97,7 @@ export async function fetchReferralInvitees() { message_body: { action: 'invitees', token: token, - pageSize: 10, + pageSize: 100, pageNumber: 0 } }), @@ -109,6 +109,60 @@ export async function fetchReferralInvitees() { if (response.ok) { let parsed = await response.json(); + console.log({ + action: 'invitees', + token: token, + pageSize: 10, + pageNumber: 0 + }) + console.log(parsed); + + 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', + token: token, + amount: 1, + } + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (response.ok) { + console.log('referralRefill'); + console.log({ + action: 'refill', + token: token, + amount: 1, + }) + let parsed = await response.json(); + console.log(parsed); if (parsed.message_code === 0) { return parsed.message_body; diff --git a/src/app/ui/payment/secure-payments.tsx b/src/app/ui/payment/secure-payments.tsx index d530eaa..d5ff9ff 100644 --- a/src/app/ui/payment/secure-payments.tsx +++ b/src/app/ui/payment/secure-payments.tsx @@ -1,9 +1,31 @@ +'use client' + +import { referralRefill } from '@/app/actions/referralsActions'; +import { toast } from 'sonner'; + export default function SecurePayments() { + return (

      πŸ”’ БСзопасныС ΠΏΠ»Π°Ρ‚Π΅ΠΆΠΈ

      +
      + +
      diff --git a/src/app/ui/referral-page/invitations-table.tsx b/src/app/ui/referral-page/invitations-table.tsx index 6b6fa7f..33f3bbe 100644 --- a/src/app/ui/referral-page/invitations-table.tsx +++ b/src/app/ui/referral-page/invitations-table.tsx @@ -11,24 +11,23 @@ import { SortingState, ColumnFiltersState, } from '@tanstack/react-table'; -import { IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconShieldExclamation } from '@/app/ui/icons/icons'; +import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter } from '@/app/ui/icons/icons'; import { useTranslations, useLocale } from 'next-intl'; import DropDownList from '@/app/components/DropDownList'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { toast } from 'sonner'; import { pluralize } from '@/app/lib/pluralize'; import { fetchReferralInvitees } from '@/app/actions/referralsActions'; type InviteesType = { status?: string; email?: string; - registrationData: number; + registrationData: string; }; type InviteType = { active?: string; email?: string; - regDate: number; + regDate: string; }; export default function InvitationsTable() { @@ -45,28 +44,14 @@ export default function InvitationsTable() { select: (data: InviteType[]): InviteesType[] => { if (!data) return [ - { - status: 'string1', - email: 'string1', - registrationData: 1 - }, - { - status: 'string2', - email: 'string2', - registrationData: 2 - }, - { - status: 'string3', - email: 'string3', - registrationData: 3 - } ]; return data.map((item: InviteType) => { + console.log(item); return { - status: item.active, - email: item.email, - registrationData: item.regDate + status: item?.active ? 'active' : 'not-active', + email: item?.email, + registrationData: item?.regDate }; }); }, @@ -191,7 +176,7 @@ export default function InvitationsTable() { cell: ({ row }) => { return (
      - {row.original.registrationData} + {row.original.registrationData?.split('T')[0]}
      ) }, @@ -214,7 +199,7 @@ export default function InvitationsTable() { } // Π€ΠΈΠ»ΡŒΡ‚Ρ€ ΠΏΠΎ Π΄Π°Ρ‚Π΅ - if (dateFilter !== 'all') { + /* if (dateFilter !== 'all') { const now = Date.now(); const oneDay = 24 * 60 * 60 * 1000; const sevenDays = 7 * oneDay; @@ -256,7 +241,7 @@ export default function InvitationsTable() { }); break; } - } + } */ return result; }, [referralInvitees, statusFilter, dateFilter]); diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index ebf1b59..da92ef3 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -287,6 +287,7 @@ "change-password": "Change Password", "recovery-code": "Recovery code", "referal-code": "Referal code", - "email-invalid": "Invalid email address" + "email-invalid": "Invalid email address", + "referral-link-is-not-exist" : "Referral link is not exist" } } \ No newline at end of file diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 5cf74ea..7b72f06 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -287,6 +287,7 @@ "change-password": "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ ΠΏΠ°Ρ€ΠΎΠ»ΡŒ", "recovery-code": "Код восстановлСния", "referal-code": "Π Π΅Ρ„Π΅Ρ€Π°Π»ΡŒΠ½Ρ‹ΠΉ ΠΊΠΎΠ΄", - "email-invalid": "НСвСрный адрСс элСктронной ΠΏΠΎΡ‡Ρ‚Ρ‹" + "email-invalid": "НСвСрный адрСс элСктронной ΠΏΠΎΡ‡Ρ‚Ρ‹", + "referral-link-is-not-exist" : "ΠžΡ‚ΡΡƒΡ‚ΡΡ‚Π²ΡƒΠ΅Ρ‚ Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΡŒΠ½Π°Ρ ссылка." } } \ No newline at end of file From 1b57d589fb06341955d1ebb155ef240b0b181b91 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 9 Feb 2026 16:44:34 +0700 Subject: [PATCH 078/119] merge global and local search --- src/app/ui/search/file-search-panel.tsx | 168 +++++++++++------------- 1 file changed, 80 insertions(+), 88 deletions(-) diff --git a/src/app/ui/search/file-search-panel.tsx b/src/app/ui/search/file-search-panel.tsx index 75f0c01..6230642 100644 --- a/src/app/ui/search/file-search-panel.tsx +++ b/src/app/ui/search/file-search-panel.tsx @@ -10,6 +10,7 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a const [searchedGlobalFiles, setSearchedGlobalFiles] = useState([]); const [searchedGlobalFilesShowNull, setSearchedGlobalFilesShowNull] = useState(false); const [loading, setLoading] = useState(false); + const [showGlobalSearch, setShowGlobalSearch] = useState(false); const queryClient = useQueryClient(); useImperativeHandle(ref, () => ({ @@ -18,6 +19,7 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a setSearchedGlobalFiles([]); setSearchedUserFilesShowNull(false); setSearchedGlobalFilesShowNull(false); + setShowGlobalSearch(false); } })); @@ -35,6 +37,8 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a } catch (error) { } + setShowGlobalSearch(true); + handlerSearchGlobalFile(fileId) }, [fileId]) @@ -107,97 +111,85 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a
      )} -
      -
      -
      - Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹ΠΉ поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ + {showGlobalSearch && ( +
      +
      +
      + Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹ΠΉ поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ +
      +
      + Найти Π³Π΄Π΅ Π΅Ρ‰Ρ‘ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ +
      +
      + Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ поиск +
      -
      - Найти Π³Π΄Π΅ Π΅Ρ‰Ρ‘ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ -
      -
      - Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ поиск + + +
      +
      +
      + Поиск ΠΏΠΎ всСму ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Ρƒ +
      +
      + Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΏΡ€ΠΎΠ΄Π²ΠΈΠ½ΡƒΡ‚Ρ‹Π΅ Ρ‚Π΅Ρ…Π½ΠΎΠ»ΠΎΠ³ΠΈΠΈ ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎΠ³ΠΎ поиска ΠΏΠΎ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡŽ для поиска ΠΊΠΎΠΏΠΈΠΉ вашСго ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚Π° + Π½Π° сайтах, Π² ΡΠΎΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹Ρ… сСтях ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… источниках. ΠŸΠΎΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΡ‚ΡŒ нСсанкционированноС использованиС. +
      +
      + +
      +
      +
      +
      +
      Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Ρ… поисков сСгодня
      +
      0 ΠΈΠ· 10
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      + Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅... +
      +
      + +
      +
      +
      + НайдСно Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ +
      +
      + +
      +
      +
      + + {searchedGlobalFilesShowNull && ( +
      +
      +

      ΠŸΠΎΡ…ΠΎΠΆΠΈΠ΅ изобраТСния Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹

      +

      Π­Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ·Π½Π°Ρ‡Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎ ΠΈ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π½Π° Π΄Ρ€ΡƒΠ³ΠΈΡ… сайтах.

      +
      + )} + +
      + Ошибка глобального поиска: + +
      - - -
      -
      -
      - Поиск ΠΏΠΎ всСму ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Ρƒ -
      -
      - Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΏΡ€ΠΎΠ΄Π²ΠΈΠ½ΡƒΡ‚Ρ‹Π΅ Ρ‚Π΅Ρ…Π½ΠΎΠ»ΠΎΠ³ΠΈΠΈ ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎΠ³ΠΎ поиска ΠΏΠΎ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡŽ для поиска ΠΊΠΎΠΏΠΈΠΉ вашСго ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚Π° - Π½Π° сайтах, Π² ΡΠΎΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹Ρ… сСтях ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… источниках. ΠŸΠΎΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ±Π½Π°Ρ€ΡƒΠΆΠΈΡ‚ΡŒ нСсанкционированноС использованиС. -
      -
      - -
      -
      -
      -
      -
      Π“Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Ρ… поисков сСгодня
      -
      0 ΠΈΠ· 10
      -
      -
      -
      -
      -
      -
      -
      - - - -
      -
      -
      - Поиск ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅... -
      -
      - -
      -
      -
      - НайдСно Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ -
      -
      - -
      -
      -
      - - {searchedGlobalFilesShowNull && ( -
      -
      -

      ΠŸΠΎΡ…ΠΎΠΆΠΈΠ΅ изобраТСния Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹

      -

      Π­Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ·Π½Π°Ρ‡Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ вашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎ ΠΈ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π½Π° Π΄Ρ€ΡƒΠ³ΠΈΡ… сайтах.

      -
      - )} - -
      - Ошибка глобального поиска: - -
      -
      -
      + )} ) } \ No newline at end of file From 7d6882a00649898b77b3befb46b37beaa2879d9f Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 9 Feb 2026 17:28:03 +0700 Subject: [PATCH 079/119] add new endpoint for tariffs --- src/app/actions/action.ts | 34 ++++++++++++++++++++++ src/app/ui/payment/payment-tab-tariffs.tsx | 9 +++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/app/actions/action.ts b/src/app/actions/action.ts index 73898a3..c5223d6 100644 --- a/src/app/actions/action.ts +++ b/src/app/actions/action.ts @@ -65,4 +65,38 @@ export async function getUserFilesInfo() { } catch (error) { console.error(`error: ${error}`); } +} + +export async function fetchTariffs() { + 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', + user_token: token + } + }), + 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?.tariffs; + } else { + return [] + } + + } + } catch (error) { + console.error('error'); + } } \ No newline at end of file diff --git a/src/app/ui/payment/payment-tab-tariffs.tsx b/src/app/ui/payment/payment-tab-tariffs.tsx index 1553d0a..94faf8b 100644 --- a/src/app/ui/payment/payment-tab-tariffs.tsx +++ b/src/app/ui/payment/payment-tab-tariffs.tsx @@ -1,6 +1,6 @@ 'use client' -import { getUserData } from '@/app/actions/action'; +import { fetchTariffs } from '@/app/actions/action'; import { useQuery } from '@tanstack/react-query'; import { convertBytes } from '@/app/lib/convertBytes'; import { useTranslations } from 'next-intl'; @@ -12,12 +12,13 @@ export default function PaymentTabTariffs() { isError, error } = useQuery({ - queryKey: ['userData'], + queryKey: ['tariffData'], queryFn: () => { - return getUserData(); + return fetchTariffs(); } }); + const t = useTranslations('Global'); return ( @@ -33,7 +34,7 @@ export default function PaymentTabTariffs() {
      - {userData.tariffs?.map(((item: { + {userData?.map(((item: { id: number, name: string, price: number, From 54529fdbb5bbdd0acdbad19ec92360e217928ab6 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 9 Feb 2026 17:35:17 +0700 Subject: [PATCH 080/119] remove console.error --- src/app/[locale]/pages/marking-document/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/[locale]/pages/marking-document/page.tsx b/src/app/[locale]/pages/marking-document/page.tsx index dc51914..2339fe3 100644 --- a/src/app/[locale]/pages/marking-document/page.tsx +++ b/src/app/[locale]/pages/marking-document/page.tsx @@ -21,7 +21,6 @@ export default async function Page() { return null; } } catch (error) { - console.error('Ошибка Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ статистики Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΠΎΠ²:', error); } return ( From 63a241f3b5d9dee496c7edc62b4b95a721ae5fb9 Mon Sep 17 00:00:00 2001 From: smanylov Date: Mon, 9 Feb 2026 18:37:56 +0700 Subject: [PATCH 081/119] change copy function --- .../referral-page/referral-link-section.tsx | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/app/ui/referral-page/referral-link-section.tsx b/src/app/ui/referral-page/referral-link-section.tsx index adbfbf0..3ee6bc2 100644 --- a/src/app/ui/referral-page/referral-link-section.tsx +++ b/src/app/ui/referral-page/referral-link-section.tsx @@ -32,10 +32,51 @@ export default function ReferralLinkSection() { } }, [referralUserStats]); - const copyHandler = () => { - if (refLink.current?.textContent) { - navigator.clipboard.writeText(refLink.current?.textContent); - toast.success(`${t('copied')}: ${refLink.current?.textContent}`) + const copyHandler = async () => { + if (!refLink.current?.textContent) return; + + const textToCopy = refLink.current.textContent; + + if (typeof window === 'undefined' || !navigator.clipboard) { + console.warn('Clipboard API Π½Π΅ доступСн'); + fallbackCopy(textToCopy); + return; + } + + try { + await navigator.clipboard.writeText(textToCopy); + toast.success(`${t('copied')}: ${textToCopy}`); + } catch (err) { + console.error('Ошибка копирования:', err); + + const success = fallbackCopy(textToCopy); + if (success) { + toast.success(`${t('copied')}: ${textToCopy}`); + } else { + toast.error('НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ'); + } + } + }; + + const fallbackCopy = (text: string) => { + try { + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + textArea.style.left = '-999999px'; + textArea.style.top = '-999999px'; + document.body.appendChild(textArea); + + textArea.select(); + textArea.setSelectionRange(0, 99999); + + const successful = document.execCommand('copy'); + document.body.removeChild(textArea); + + return successful; + } catch (err) { + console.error('Fallback copy failed:', err); + return false; } }; From 0898ea9aa0d9a06a2be3b1de3e9b762e46f844b1 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 10 Feb 2026 00:49:07 +0700 Subject: [PATCH 082/119] change referral link error message --- src/i18n/messages/en.json | 2 +- src/i18n/messages/ru.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index da92ef3..b4f1b0d 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -288,6 +288,6 @@ "recovery-code": "Recovery code", "referal-code": "Referal code", "email-invalid": "Invalid email address", - "referral-link-is-not-exist" : "Referral link is not exist" + "referral-link-is-not-exist" : "This referral link does not exist." } } \ No newline at end of file diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 7b72f06..dd3b658 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -288,6 +288,6 @@ "recovery-code": "Код восстановлСния", "referal-code": "Π Π΅Ρ„Π΅Ρ€Π°Π»ΡŒΠ½Ρ‹ΠΉ ΠΊΠΎΠ΄", "email-invalid": "НСвСрный адрСс элСктронной ΠΏΠΎΡ‡Ρ‚Ρ‹", - "referral-link-is-not-exist" : "ΠžΡ‚ΡΡƒΡ‚ΡΡ‚Π²ΡƒΠ΅Ρ‚ Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΡŒΠ½Π°Ρ ссылка." + "referral-link-is-not-exist" : "Π’Π°ΠΊΠΎΠΉ Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΡŒΠ½ΠΎΠΉ ссылки Π½Π΅ сущСствуСт." } } \ No newline at end of file From 40e6172c0b4d18d8848d2120b8ef45b4847bbe61 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 10 Feb 2026 15:40:18 +0700 Subject: [PATCH 083/119] add pagination for test --- src/app/actions/searchActions.ts | 39 ++++++++++- src/app/components/Pagination.tsx | 67 +++++++++++++++++++ src/app/ui/search/file-search-panel.tsx | 31 +++++++-- src/app/ui/search/search-stats.tsx | 4 -- .../ui/search/searched-global-files-list.tsx | 60 +++++++++-------- 5 files changed, 162 insertions(+), 39 deletions(-) create mode 100644 src/app/components/Pagination.tsx diff --git a/src/app/actions/searchActions.ts b/src/app/actions/searchActions.ts index e4689fc..a49ba2f 100644 --- a/src/app/actions/searchActions.ts +++ b/src/app/actions/searchActions.ts @@ -28,8 +28,43 @@ export async function searchUserFiles(fileId: string) { } } -export async function searchGlobalFiles(fileId: string) { +export async function searchGlobalFiles(fileId: string, currentPage: number) { const token = await getSessionData('token'); + console.log('searchGlobalFiles'); + console.log(currentPage); + +/* return { + images: [ + { + url: 'string1', + pageTitle: `string1 from page ${currentPage}`, + height: 100, + width: 100, + host: 'string1', + pageUrl: 'string1' + }, + { + url: 'string1', + pageTitle: `string2 from page ${currentPage}`, + height: 100, + width: 100, + host: 'string1', + pageUrl: 'string1' + }, + { + url: 'string1', + pageTitle: `string3 from page ${currentPage}`, + height: 100, + width: 100, + host: 'string1', + pageUrl: 'string1' + } + ], + page: 0, + pageSize: 0, + totalPages: 10, + totalResults: 100, + } */ try { const response = await fetch(`${API_BASE_URL}/api/v1/data`, { @@ -77,7 +112,7 @@ export async function getSearchStats() { } }); - + if (response.ok) { let parsed = await response.json(); diff --git a/src/app/components/Pagination.tsx b/src/app/components/Pagination.tsx new file mode 100644 index 0000000..85e85bd --- /dev/null +++ b/src/app/components/Pagination.tsx @@ -0,0 +1,67 @@ +'use client' + +export function Pagination({ totalPages, currentPage, fileId, callBack }: any) { + + 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 ( +
      +
      + {/* Кнопка ΠΏΠ΅Ρ€Π²ΠΎΠΉ страницы */} + {visiblePages[0] > 1 && ( + <> + + {visiblePages[0] > 2 && ...} + + )} + + {/* Π’ΠΈΠ΄ΠΈΠΌΡ‹Π΅ страницы */} + {visiblePages.map(page => ( + + ))} + + {/* Кнопка послСднСй страницы */} + {visiblePages[visiblePages.length - 1] < totalPages && ( + <> + {visiblePages[visiblePages.length - 1] < totalPages - 1 && ...} + + + )} +
      +
      + ); +} \ No newline at end of file diff --git a/src/app/ui/search/file-search-panel.tsx b/src/app/ui/search/file-search-panel.tsx index 6230642..9c397ef 100644 --- a/src/app/ui/search/file-search-panel.tsx +++ b/src/app/ui/search/file-search-panel.tsx @@ -3,16 +3,29 @@ import { SearchedGlobalFilesList } from '@/app/ui/search/searched-global-files-l import { useCallback, useState, useImperativeHandle } from 'react'; import { searchUserFiles, searchGlobalFiles } from '@/app/actions/searchActions'; import { useQueryClient } from '@tanstack/react-query'; +import { Pagination } from '@/app/components/Pagination'; + +export interface SearchItem { + url: string, + pageTitle: string, + height: number, + width: number, + host: string, + pageUrl: string +} export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: any }) { const [searchedUserFiles, setSearchedUserFiles] = useState([]); const [searchedUserFilesShowNull, setSearchedUserFilesShowNull] = useState(false); - const [searchedGlobalFiles, setSearchedGlobalFiles] = useState([]); + const [searchedGlobalFiles, setSearchedGlobalFiles] = useState([]); const [searchedGlobalFilesShowNull, setSearchedGlobalFilesShowNull] = useState(false); const [loading, setLoading] = useState(false); const [showGlobalSearch, setShowGlobalSearch] = useState(false); const queryClient = useQueryClient(); + const [searchTotalPages, setSearchTotalPages] = useState(0); + const [searchCurrentPages, setSearchCurrentPages] = useState(1); + useImperativeHandle(ref, () => ({ clearList: () => { setSearchedUserFiles([]); @@ -24,7 +37,7 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a })); - const handlerSearchUserFile = useCallback(async (fileId: string): Promise => { + const handlerSearchUserFile = useCallback(async (fileId: string, page: number): Promise => { try { let result = await searchUserFiles(fileId); @@ -38,21 +51,25 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a } setShowGlobalSearch(true); - handlerSearchGlobalFile(fileId) + handlerSearchGlobalFile(fileId, page) }, [fileId]) - const handlerSearchGlobalFile = useCallback(async (fileId: string): Promise => { + const handlerSearchGlobalFile = useCallback(async (fileId: string, page: number): Promise => { setLoading(true); setSearchedGlobalFilesShowNull(false); setSearchedGlobalFiles([]); try { - let result = await searchGlobalFiles(fileId); + let result = await searchGlobalFiles(fileId, page); + console.log('searchGlobalFiles'); console.log(result); if (result.images.length) { setSearchedGlobalFiles(result.images); + setSearchTotalPages(result.totalPages); + setSearchCurrentPages(page); + } else { setSearchedGlobalFilesShowNull(true); } @@ -76,7 +93,7 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a disabled={fileId ? false : true} onClick={() => { if (fileId) { - handlerSearchUserFile(fileId) + handlerSearchUserFile(fileId, 1) } }} > @@ -187,6 +204,8 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a Ошибка глобального поиска:
      + +
      )} diff --git a/src/app/ui/search/search-stats.tsx b/src/app/ui/search/search-stats.tsx index 5bb8466..9055c11 100644 --- a/src/app/ui/search/search-stats.tsx +++ b/src/app/ui/search/search-stats.tsx @@ -17,10 +17,6 @@ export function SearchStats() { }, }); - useEffect(() => { - console.log(userSearchData); - }, [userSearchData]); - return (
      diff --git a/src/app/ui/search/searched-global-files-list.tsx b/src/app/ui/search/searched-global-files-list.tsx index eeea81c..170c51a 100644 --- a/src/app/ui/search/searched-global-files-list.tsx +++ b/src/app/ui/search/searched-global-files-list.tsx @@ -1,37 +1,43 @@ -export function SearchedGlobalFilesList({ list }: { list: any }) { +import { SearchItem } from './file-search-panel' + +export function SearchedGlobalFilesList({ list }: { list: SearchItem[] }) { + return ( <> - {list.map((item: any, index: number) => { - return ( -
      -
      - {item.pageTitle} -
      -
      -
      - {item.pageTitle} + {list.map( + ( + item: SearchItem, index: number + ) => { + return ( +
      +
      + {item?.pageTitle}
      -
      -
      - {item.height}x{item.width} +
      +
      + {item?.pageTitle}
      -
      - {item.host} +
      +
      + {item?.height}x{item?.width} +
      +
      + {item?.host} +
      +
      + {item?.pageTitle} +
      + + {item?.pageUrl} +
      -
      - {item.pageTitle} -
      - - {item.pageUrl} -
      -
      - ) - })} + ) + })} ) } \ No newline at end of file From 61ed604b3eec26bacd5c1927cacbb6819dd158b2 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 10 Feb 2026 16:24:56 +0700 Subject: [PATCH 084/119] add page for search --- src/app/actions/searchActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/actions/searchActions.ts b/src/app/actions/searchActions.ts index a49ba2f..9b34ed5 100644 --- a/src/app/actions/searchActions.ts +++ b/src/app/actions/searchActions.ts @@ -74,6 +74,7 @@ export async function searchGlobalFiles(fileId: string, currentPage: number) { msg_id: 20007, message_body: { file_id: fileId, + page: currentPage, } }), headers: { From 9a9ec39ff6d63df12a59a193753c258103ade775 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 10 Feb 2026 17:29:36 +0700 Subject: [PATCH 085/119] update search pagination --- src/app/actions/searchActions.ts | 65 ++++++++++++------------ src/app/components/Pagination.tsx | 66 +++++++++++++------------ src/app/components/TanstakTable.tsx | 5 +- src/app/styles/pages-styles.scss | 24 +++++++++ src/app/ui/search/file-search-panel.tsx | 24 ++++++--- 5 files changed, 110 insertions(+), 74 deletions(-) diff --git a/src/app/actions/searchActions.ts b/src/app/actions/searchActions.ts index 9b34ed5..1a4a8fb 100644 --- a/src/app/actions/searchActions.ts +++ b/src/app/actions/searchActions.ts @@ -33,38 +33,39 @@ export async function searchGlobalFiles(fileId: string, currentPage: number) { console.log('searchGlobalFiles'); console.log(currentPage); -/* return { - images: [ - { - url: 'string1', - pageTitle: `string1 from page ${currentPage}`, - height: 100, - width: 100, - host: 'string1', - pageUrl: 'string1' - }, - { - url: 'string1', - pageTitle: `string2 from page ${currentPage}`, - height: 100, - width: 100, - host: 'string1', - pageUrl: 'string1' - }, - { - url: 'string1', - pageTitle: `string3 from page ${currentPage}`, - height: 100, - width: 100, - host: 'string1', - pageUrl: 'string1' - } - ], - page: 0, - pageSize: 0, - totalPages: 10, - totalResults: 100, - } */ + //ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ ΠΊΠΎΠ³Π΄Π° поиск Π±ΡƒΠ΄Π΅Ρ‚ Π½ΠΎΡ€ΠΌΠ°Π»ΡŒΠ½ΠΎ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ + /* return { + images: [ + { + url: 'string1', + pageTitle: `string1 from page ${currentPage}`, + height: 100, + width: 100, + host: 'string1', + pageUrl: 'string1' + }, + { + url: 'string1', + pageTitle: `string2 from page ${currentPage}`, + height: 100, + width: 100, + host: 'string1', + pageUrl: 'string1' + }, + { + url: 'string1', + pageTitle: `string3 from page ${currentPage}`, + height: 100, + width: 100, + host: 'string1', + pageUrl: 'string1' + } + ], + page: 0, + pageSize: 0, + totalPages: 11, + totalResults: 100, + } */ try { const response = await fetch(`${API_BASE_URL}/api/v1/data`, { diff --git a/src/app/components/Pagination.tsx b/src/app/components/Pagination.tsx index 85e85bd..fc2f25c 100644 --- a/src/app/components/Pagination.tsx +++ b/src/app/components/Pagination.tsx @@ -2,6 +2,10 @@ export function Pagination({ totalPages, currentPage, fileId, callBack }: any) { + if (totalPages === 0) { + return null; + } + const getVisiblePages = () => { const pages = []; const maxVisible = 7; @@ -26,42 +30,40 @@ export function Pagination({ totalPages, currentPage, fileId, callBack }: any) { const visiblePages = getVisiblePages(); return ( -
      -
      - {/* Кнопка ΠΏΠ΅Ρ€Π²ΠΎΠΉ страницы */} - {visiblePages[0] > 1 && ( - <> - - {visiblePages[0] > 2 && ...} - - )} - - {/* Π’ΠΈΠ΄ΠΈΠΌΡ‹Π΅ страницы */} - {visiblePages.map(page => ( +
      + {visiblePages[0] > 1 && ( + <> - ))} + {visiblePages[0] > 2 && ...} + + )} - {/* Кнопка послСднСй страницы */} - {visiblePages[visiblePages.length - 1] < totalPages && ( - <> - {visiblePages[visiblePages.length - 1] < totalPages - 1 && ...} - - - )} -
      + {visiblePages.map(page => ( + + ))} + + {visiblePages[visiblePages.length - 1] < totalPages && ( + <> + {visiblePages[visiblePages.length - 1] < totalPages - 1 && ...} + + + )}
      ); } \ No newline at end of file diff --git a/src/app/components/TanstakTable.tsx b/src/app/components/TanstakTable.tsx index b89f9b2..d56103b 100644 --- a/src/app/components/TanstakTable.tsx +++ b/src/app/components/TanstakTable.tsx @@ -116,10 +116,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { 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(); + const newDate = new Date(item.updatedAt).getTime(); return { id: item.id, diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index f671366..ede92dc 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -3839,11 +3839,35 @@ } } +.pagination-wrapper { + display: flex; + gap: 5px; + margin-top: 10px; + + .pagination-button { + border: 2px solid #e2e8f0; + padding: 0.25rem 0.75rem; + border-radius: 10px; + color: v.$p-color; + cursor: pointer; + + &.current-page { + background-color: v.$p-color; + color: v.$white; + } + + &:hover { + background-color: v.$bg-light; + } + } +} + .loading-animation { display: block; text-align: center; .global-spinner { + display: block; width: 24px; height: 24px; margin: 0 auto; diff --git a/src/app/ui/search/file-search-panel.tsx b/src/app/ui/search/file-search-panel.tsx index 9c397ef..e762d0e 100644 --- a/src/app/ui/search/file-search-panel.tsx +++ b/src/app/ui/search/file-search-panel.tsx @@ -20,6 +20,7 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a const [searchedGlobalFiles, setSearchedGlobalFiles] = useState([]); const [searchedGlobalFilesShowNull, setSearchedGlobalFilesShowNull] = useState(false); const [loading, setLoading] = useState(false); + const [paginationLoading, setPaginationLoading] = useState(false); const [showGlobalSearch, setShowGlobalSearch] = useState(false); const queryClient = useQueryClient(); @@ -33,6 +34,8 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a setSearchedUserFilesShowNull(false); setSearchedGlobalFilesShowNull(false); setShowGlobalSearch(false); + setSearchCurrentPages(1); + setSearchTotalPages(0); } })); @@ -55,11 +58,14 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a }, [fileId]) - const handlerSearchGlobalFile = useCallback(async (fileId: string, page: number): Promise => { - setLoading(true); - setSearchedGlobalFilesShowNull(false); - setSearchedGlobalFiles([]); - + const handlerSearchGlobalFile = useCallback(async (fileId: string, page: number, actionFromPagination?: boolean): Promise => { + if (!actionFromPagination) { + setLoading(true); + setSearchedGlobalFilesShowNull(false); + setSearchedGlobalFiles([]); + } else { + setPaginationLoading(true); + } try { let result = await searchGlobalFiles(fileId, page); @@ -79,6 +85,7 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a await queryClient.invalidateQueries({ queryKey: ['userData'] }); await queryClient.invalidateQueries({ queryKey: ['userSearchData'] }); setLoading(false); + setPaginationLoading(false); } }, [fileId]) @@ -185,6 +192,11 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a
      НайдСно Π² ΠΈΠ½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅ + {paginationLoading && ( +
      + +
      + )}
      @@ -205,7 +217,7 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a
      - +
      )} From ffeeff8be31a6e12e1e586c85b179026e793aeb4 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 10 Feb 2026 18:00:05 +0700 Subject: [PATCH 086/119] increment version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2e90991..0a2c7fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.35.0", + "version": "0.36.0", "private": true, "scripts": { "dev": "next dev -p 2999", From 78c606ba9ed22488e1e2324afef1a7d7bada48cc Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 10 Feb 2026 18:07:53 +0700 Subject: [PATCH 087/119] update copy function --- src/app/ui/referral-page/referral-link-section.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/ui/referral-page/referral-link-section.tsx b/src/app/ui/referral-page/referral-link-section.tsx index 3ee6bc2..4da87f7 100644 --- a/src/app/ui/referral-page/referral-link-section.tsx +++ b/src/app/ui/referral-page/referral-link-section.tsx @@ -39,7 +39,13 @@ export default function ReferralLinkSection() { if (typeof window === 'undefined' || !navigator.clipboard) { console.warn('Clipboard API Π½Π΅ доступСн'); - fallbackCopy(textToCopy); + + const success = fallbackCopy(textToCopy); + if (success) { + toast.success(`${t('copied')}: ${textToCopy}`); + } else { + toast.error('НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ'); + } return; } From 062c3e353939a076bc861a63f0aefc92d7d269be Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 10 Feb 2026 21:00:00 +0700 Subject: [PATCH 088/119] fix date for users files --- src/app/ui/dashboard/new/dashboard-user-files.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/app/ui/dashboard/new/dashboard-user-files.tsx b/src/app/ui/dashboard/new/dashboard-user-files.tsx index c124180..09ff25b 100644 --- a/src/app/ui/dashboard/new/dashboard-user-files.tsx +++ b/src/app/ui/dashboard/new/dashboard-user-files.tsx @@ -47,10 +47,7 @@ export default function DashboardUserFiles() { 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(); + const newDate = new Date(item.updatedAt).getTime(); return { id: item.id, From 0ad2b03f2b490248f4ed0a12b6104b20c8004c7e Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 11 Feb 2026 13:57:14 +0700 Subject: [PATCH 089/119] change filter icon for tanstak-table --- src/app/ui/icons/icons.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/app/ui/icons/icons.tsx b/src/app/ui/icons/icons.tsx index 6d6035d..5759458 100644 --- a/src/app/ui/icons/icons.tsx +++ b/src/app/ui/icons/icons.tsx @@ -103,9 +103,24 @@ export function IconArrowDown() { export function IconFilter() { return ( - + + + + + ) } +/* + + + + */ + +{/* + + + + */} export function IconNotification() { return ( From 459035529c4a134064a54512e1c00d5a5e7fd836 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 11 Feb 2026 13:57:32 +0700 Subject: [PATCH 090/119] increment version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0a2c7fb..0a905ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.36.0", + "version": "0.37.0", "private": true, "scripts": { "dev": "next dev -p 2999", From 659d307a121e446ef145554a1db8ef94405ed67a Mon Sep 17 00:00:00 2001 From: vladp Date: Wed, 11 Feb 2026 19:28:16 +0700 Subject: [PATCH 091/119] add build front time --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 4329947..4b0813f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,9 @@ RUN npm install FROM node:22.20.0 AS builder WORKDIR /no-copy-frontend +ARG BUILD_TIME_FRONT +ENV NEXT_PUBLIC_BUILD_TIME_FRONT=$BUILD_TIME_FRONT + COPY . . COPY --from=dependencies /no-copy-frontend/node_modules ./node_modules RUN rm -rf .next && npm run build --force From 6ca112f3e376fa66e28c33b9624ab728e2333533 Mon Sep 17 00:00:00 2001 From: vladp Date: Wed, 11 Feb 2026 19:43:53 +0700 Subject: [PATCH 092/119] add build front time --- Dockerfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4b0813f..4329947 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,6 @@ RUN npm install FROM node:22.20.0 AS builder WORKDIR /no-copy-frontend -ARG BUILD_TIME_FRONT -ENV NEXT_PUBLIC_BUILD_TIME_FRONT=$BUILD_TIME_FRONT - COPY . . COPY --from=dependencies /no-copy-frontend/node_modules ./node_modules RUN rm -rf .next && npm run build --force From 930e811232b9a7df53b6391db82a75f5807c4b82 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 11 Feb 2026 20:11:39 +0700 Subject: [PATCH 093/119] add upload multiple files --- package.json | 2 +- src/app/components/UploadSectionFile.tsx | 579 ++++++++++++++--------- src/i18n/messages/en.json | 9 +- src/i18n/messages/ru.json | 11 +- 4 files changed, 386 insertions(+), 215 deletions(-) diff --git a/package.json b/package.json index 0a905ac..5314cfb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.37.0", + "version": "0.38.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/components/UploadSectionFile.tsx b/src/app/components/UploadSectionFile.tsx index bbfae50..c6cbaae 100644 --- a/src/app/components/UploadSectionFile.tsx +++ b/src/app/components/UploadSectionFile.tsx @@ -6,6 +6,8 @@ import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/a import { useTranslations } from 'next-intl'; import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker'; import { useQueryClient } from '@tanstack/react-query'; +import { resolve } from 'path'; + interface SelectedFile { file: File; preview: string | undefined; @@ -15,32 +17,41 @@ interface SelectedFile { width: number; height: number; }; + uploadId?: string; + progress: number; + status: 'pending' | 'uploading' | 'completed' | 'error' | 'cancelled'; + error?: string; } interface FileUploadInitResponse { - upload_id: string, - file_name: string, - total_chunks: number, - chunk_size: number, - status: string + upload_id: string; + file_name: string; + total_chunks: number; + chunk_size: number; + status: string; } interface UploadSectionFile { - fileType: string - allowedExtensions: string[] - maxFileSize: number + fileType: string; + allowedExtensions: string[]; + maxFileSize: number; + maxParallelUploads?: number; } -export default function UploadSectionFile({ fileType, allowedExtensions, maxFileSize }: UploadSectionFile) { +export default function UploadSectionFile({ + fileType, + allowedExtensions, + maxFileSize, + maxParallelUploads = 3 +}: UploadSectionFile) { const fileInputRef = useRef(null); const [isDragging, setIsDragging] = useState(false); - const [selectedFile, setSelectedFile] = useState(null); + const [selectedFiles, setSelectedFiles] = useState([]); const [error, setError] = useState(null); - const [uploadId, setUploadId] = useState(null); - const [isFileUploaded, setIsFileUploaded] = useState(false); - const [uploadProgress, setUploadProgress] = useState(0); + const [activeUploads, setActiveUploads] = useState>(new Set()); // ID Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ const queryClient = useQueryClient(); - const isCancelledRef = useRef(false); + const isCancelledRef = useRef>(new Map()); // Для отслСТивания ΠΎΡ‚ΠΌΠ΅Π½ ΠΏΠΎ Ρ„Π°ΠΉΠ»Π°ΠΌ + const selectedFilesRef = useRef(selectedFiles); const t = useTranslations('Global'); @@ -74,11 +85,6 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile return { isValid: true }; }; - function setErrorHandler(message: string | null): void { - setError(message); - setUploadProgress(0); - } - const getImageDimensions = (file: File): Promise<{ width: number; height: number }> => { return new Promise((resolve, reject) => { const img = new Image(); @@ -102,63 +108,61 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile }); }; - const handleFileSelect = useCallback(async (file: File | null): Promise => { - if (!file) { - setError(null); - return; - } - + const handleFilesSelect = useCallback(async (files: File[]): Promise => { setError(null); - setIsFileUploaded(false); - setUploadProgress(0); - const validation = validateFile(file); + const newFiles: SelectedFile[] = []; - if (!validation.isValid) { - setError(validation.errorMessage || t('unknown-validation-error')); - return; - } + for (const file of files) { + const validation = validateFile(file); - let dimensions = 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) { - setErrorHandler(`${t('image-size')}: ${dimensions.width}x${dimensions.height}. ${t('acceptable-range')}: 100x100 - 10000x10000 ${t('pixels')}.`); - return; - } - } catch (err) { - setErrorHandler(t('error-reading-image')); - return; + 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; + } + } + + 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' + }); } - setSelectedFile({ - file, - name: file.name, - size: `${(file.size / 1024 / 1024).toFixed(2)} MB`, - dimensions, - preview: file.size < 10 * 1024 * 1024 ? URL.createObjectURL(file) : undefined - }); - - console.log('Π€Π°ΠΉΠ» Π²Ρ‹Π±Ρ€Π°Π½:', file.name, file.size); + setSelectedFiles(prev => [...prev, ...newFiles]); }, [fileType, t]); const handleFileInputChange = (event: ChangeEvent): void => { - const file = event.target.files?.[0] || null; - handleFileSelect(file); + const files = Array.from(event.target.files || []); + handleFilesSelect(files); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; - const handleButtonClick = (): void => { - if (fileInputRef.current) { - fileInputRef.current.click(); - } - }; const handleDragOver = (event: DragEvent): void => { event.preventDefault(); @@ -169,45 +173,73 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile setIsDragging(false); }; + const handleDrop = (event: DragEvent): void => { event.preventDefault(); setIsDragging(false); - const file = event.dataTransfer.files[0]; - handleFileSelect(file); + const files = Array.from(event.dataTransfer.files); + handleFilesSelect(files); }; - const handleClearFile = async (): Promise => { - if (selectedFile && selectedFile.preview) { - URL.revokeObjectURL(selectedFile.preview); + const handleClearFile = async (fileId?: string): Promise => { + 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 { + // ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° всСх Ρ„Π°ΠΉΠ»ΠΎΠ² + 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([]); } - if (uploadId) { - await cancelUpload(uploadId); - } - - isCancelledRef.current = true; - - setIsFileUploaded(false); - setSelectedFile(null); setError(null); - setUploadProgress(0); }; - const handlerFileUpload = useCallback(async (fileInfo: SelectedFile): Promise => { - if (uploadId) return; - - setError(null); - isCancelledRef.current = false; - const file = fileInfo.file; + const uploadSingleFile = async (fileInfo: SelectedFile): Promise => { + const fileId = `${fileInfo.name}-${Date.now()}`; + isCancelledRef.current.set(fileId, false); try { - const extension = file.name.split('.').pop() || ''; + setSelectedFiles(prev => prev.map(f => + f.name === fileInfo.name ? { ...f, status: 'uploading' } : f + )); + + const extension = fileInfo.file.name.split('.').pop() || ''; const initMessageBody = { - file_name: file.name, + file_name: fileInfo.file.name, file_type: fileType, extension: extension, - file_size: file.size + file_size: fileInfo.file.size }; const response = await fileUpload(initMessageBody) as FileUploadInitResponse; @@ -216,19 +248,23 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile 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; - setUploadId(response.upload_id); - for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { - if (isCancelledRef.current) { + if (isCancelledRef.current.get(fileId)) { return; } const start = chunkIndex * CHUNK_SIZE; - const end = Math.min(start + CHUNK_SIZE, file.size); - const chunk = file.slice(start, end); + 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); @@ -241,12 +277,24 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile throw chunkResponse.errorMesage; } - setUploadProgress(Math.floor((chunkIndex + 1) / totalChunks * 100)); + 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) { - setIsFileUploaded(true); + 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'] }); @@ -255,53 +303,103 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile } } catch (error) { - if (!isCancelledRef.current) { - switch (error) { - case 'duplicate-file': - setErrorHandler(t('error-duplicate-file')); - break; - case 'user-not-have-tokens-for-protect': - setErrorHandler(t('error-user-not-have-tokens-for-protect')); - break; - default: - setErrorHandler(t('error-uploading-file')); - break; - } + if (!isCancelledRef.current.get(fileId)) { + const errorMessage = (() => { + switch (error) { + case 'duplicate-file': + return t('error-duplicate-file'); + case 'user-not-have-tokens-for-protect': + return t('error-user-not-have-tokens-for-protect'); + default: + return t('error-uploading-file'); + } + })(); + + setSelectedFiles(prev => prev.map(f => + f.name === fileInfo.name ? { ...f, status: 'error', error: errorMessage } : f + )); } } finally { - setUploadId(null); + /* if (fileInfo.uploadId) { + setActiveUploads(prev => { + const newSet = new Set(prev); + newSet.delete(fileInfo.uploadId!); + return newSet; + }); + } */ } - }, [uploadId, fileType, t]); + }; useEffect(() => { - // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° закрытия Π²ΠΊΠ»Π°Π΄ΠΊΠΈ - // Π’ΡƒΡ‚ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠΏΡ€ΠΎΠ±ΠΎΠ²Π°Ρ‚ΡŒ ΠΈΠ·ΡƒΡ‡ΠΈΡ‚ΡŒ вопрос с navigator.sendBeacon('', blob) - // ΠΎΠ½ Π½ΡƒΠΆΠ΅Π½ для Ρ‚ΠΎΠ³ΠΎ Ρ‡Ρ‚ΠΎ Π±Ρ‹ ΠΏΠΎΡΠ»Π°Ρ‚ΡŒ запрос Π½Π° Π±Π΅ΠΊ ΠΏΡ€ΠΈ Π·Π°ΠΊΡ€Ρ‹Ρ‚ΠΈΠΈ Π²ΠΊΠ»Π°Π΄ΠΊΠΈ, - // Π½ΠΎ Ρƒ Π½Π΅Π³ΠΎ Π΅ΡΡ‚ΡŒ свои особСнности ΠΈ стандартныС запросы Π½Π΅ Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‚. - const handleUnload = () => { - if (uploadId) { - //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'); + selectedFilesRef.current = selectedFiles; + }, [selectedFiles]); + + const handleStartAllUploads = useCallback(async (): Promise => { + const uploadNextBatch = async () => { + const currentPending = selectedFilesRef.current.filter(f => f.status === 'pending'); + const batch = currentPending.slice(0, maxParallelUploads); + + if (batch.length === 0) return; + + console.log('Π’Π΅ΠΊΡƒΡ‰ΠΈΠ΅ pending Ρ„Π°ΠΉΠ»Ρ‹:', currentPending); + console.log('ЗагруТаСмая партия:', batch); + + await Promise.all(batch.map(file => uploadSingleFile(file))); + + if (selectedFilesRef.current.some(f => f.status === 'pending')) { + await uploadNextBatch(); } }; - if (uploadId) { - window.addEventListener('unload', handleUnload); - } - return () => { - window.removeEventListener('unload', handleUnload); - }; - }, [uploadId]); + await uploadNextBatch(); + }, [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: !!uploadId, + shouldBlock: !!activeUploads.size, message: t('have-unsaved-changes'), onConfirm: async () => { console.log('User confirmed navigation'); - if (uploadId) { - await cancelUpload(uploadId); + if (activeUploads.size) { + await handleClearFile(); } }, onCancel: () => { @@ -309,13 +407,16 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile } }); + return ( -
      +

      - {t('upload-for-protection', { fileType: t(fileType.toLocaleLowerCase()) })} + {t('upload-for-protection', { + fileType: t(fileType.toLocaleLowerCase()) + })}

      -
      {t('drag-the-file-here')} - {/*

      - {t('or')} -

      */} - {fileType === "image" && ( -

      - {t('image-resolution')}: 100x100 - 10000x10000 {t('pixels')} -

      - )} -

      + { + fileType === "image" && ( +

      + {t('image-resolution')}: 100x100 - 10000x10000 {t('pixels')} +

      + ) + } +

      {t('file-format')}: {acceptString}

      -

      + < p className="description" > {t('file-size')}: {t('to')} {(maxFileSize / 1024 / 1024)} {t('mb')}

      - - {error && ( -
      -

      {error}

      -
      - )} + { + error && ( +
      +

      {error}

      +
      + ) + } - {selectedFile && ( -
      -
      -
      -

      - - {t('file')}: - {selectedFile.name} -

      -

      - - {t('size')}: - {selectedFile.size} -

      -

      - {t('cost-of-protection')}: 0 -

      -

      - {t('current-balance')}: 0 -

      -
      -
      - {isFileUploaded && ( -
      - {t('file-successfully-uploaded')} + { + selectedFiles.length > 0 && ( +
      +
      +
      +

      + {t('total-files')}: {selectedFiles.length} | + {t('uploading')}: {uploadingCount} | + {t('completed')}: {completedCount} +

      + < div className="w-full bg-gray-200 rounded-full h-2.5 mt-2" > +
      - )} +
      + + < div className="flex gap-2" > + + < button + className="btn btn-primary" + onClick={() => handleClearFile()} + type="button" + > + {t('clear-all')} + +
      +
      + + < div className="space-y-4 max-h-96 overflow-y-auto" > + { + selectedFiles.map((file, index) => ( +
      +
      +
      +

      + {t('file')}: {file.name} +

      + < p className="text-gray-700" > + {t('size')}: {file.size} +

      + < p className="text-gray-700" > + {t('status')}: + + { + file.status === 'completed' ? t('completed') : + file.status === 'uploading' ? t('uploading') : + file.status === 'error' ? t('error') : + t('pending') + } + +

      + { + file.error && ( +

      {file.error}

      + ) + } +
      + < div className="flex items-center" > + + {file.progress} % + + < button + className="btn btn-cancel text-sm py-1 px-3" + onClick={() => handleClearFile(file.uploadId || file.name)} + type="button" + > + {t('remove')} + +
      +
      + + { + (fileType === 'image' && file.preview) && ( + ΠŸΡ€Π΅Π΄ΠΏΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ изобраТСния setError('НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ ΠΏΡ€Π΅Π²ΡŒΡŽ изобраТСния') + } + /> + )} + + { + file.status === 'pending' && ( +
      + +
      + )} +
      + ))}
      - - {(fileType === 'image' && selectedFile.preview) && ( - ΠŸΡ€Π΅Π΄ΠΏΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ изобраТСния setErrorHandler('НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ ΠΏΡ€Π΅Π²ΡŒΡŽ изобраТСния')} - /> - )} - -
      - - - {uploadProgress !== 0 && ( -
      - {uploadProgress}% -
      - )} -
      -
      - )} + )}
      ); diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index b4f1b0d..d72f45c 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -213,7 +213,14 @@ "error-reading-image": "Error reading image", "error-user-not-have-tokens-for-protect": "User not have tokens for protect", "registration-date": "Registration date", - "copied": "Copied" + "copied": "Copied", + "uploading": "uploading", + "clear-all": "clear-all", + "upload-all": "upload-all", + "remove": "remove", + "start-upload": "Start upload", + "completed": "completed", + "pending": "pending" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index dd3b658..f310260 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -213,7 +213,14 @@ "error-reading-image": "Ошибка чтСния изобраТСния", "error-user-not-have-tokens-for-protect": "Π£ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π½Π΅Ρ‚ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² для Π·Π°Ρ‰ΠΈΡ‚Ρ‹.", "registration-date": "Π”Π°Ρ‚Π° рСгистрации", - "copied": "Π‘ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π½ΠΎ" + "copied": "Π‘ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π½ΠΎ", + "uploading": "Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ°", + "clear-all": "ΠΎΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ всё", + "upload-all": "Π·Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ всё", + "remove": "ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ", + "start-upload": "ΠΠ°Ρ‡Π°Ρ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ", + "completed": "Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΎ", + "pending": "Π² ΠΎΠΆΠΈΠ΄Π°Π½ΠΈΠΈ" }, "Login-register-form": { "and": "ΠΈ", @@ -288,6 +295,6 @@ "recovery-code": "Код восстановлСния", "referal-code": "Π Π΅Ρ„Π΅Ρ€Π°Π»ΡŒΠ½Ρ‹ΠΉ ΠΊΠΎΠ΄", "email-invalid": "НСвСрный адрСс элСктронной ΠΏΠΎΡ‡Ρ‚Ρ‹", - "referral-link-is-not-exist" : "Π’Π°ΠΊΠΎΠΉ Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΡŒΠ½ΠΎΠΉ ссылки Π½Π΅ сущСствуСт." + "referral-link-is-not-exist": "Π’Π°ΠΊΠΎΠΉ Ρ€Π΅Ρ„Π΅Ρ€Π°Π»ΡŒΠ½ΠΎΠΉ ссылки Π½Π΅ сущСствуСт." } } \ No newline at end of file From 953f2f22a9a4e584c7e886817111f0e3dd723d06 Mon Sep 17 00:00:00 2001 From: smanylov Date: Wed, 11 Feb 2026 20:47:40 +0700 Subject: [PATCH 094/119] add frontent build date --- public/build-info.json | 3 +++ src/app/lib/prefetch-queries.ts | 2 +- src/app/styles/global-styles.scss | 9 ++++++++ src/app/ui/navigation/nav-links.tsx | 36 +++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 public/build-info.json diff --git a/public/build-info.json b/public/build-info.json new file mode 100644 index 0000000..37d74ed --- /dev/null +++ b/public/build-info.json @@ -0,0 +1,3 @@ +{ + "buildTime": null +} \ No newline at end of file diff --git a/src/app/lib/prefetch-queries.ts b/src/app/lib/prefetch-queries.ts index c9e7059..c384463 100644 --- a/src/app/lib/prefetch-queries.ts +++ b/src/app/lib/prefetch-queries.ts @@ -1,7 +1,7 @@ import { QueryClient } from '@tanstack/react-query'; import { getUserData, getUserFilesInfo } from '@/app/actions/action'; import { getUserFilesData } from '@/app/actions/fileEntity'; -import {fetchReferralUserStats} from '@/app/actions/referralsActions'; +import { fetchReferralUserStats } from '@/app/actions/referralsActions'; export async function prefetchLayoutQueries(queryClient: QueryClient) { await Promise.all([ diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index 0bee0de..75f973b 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -306,6 +306,15 @@ width: var(--side-bar-width); transform: translateX(0); } + + .build-versions { + position: absolute; +/* margin-top: 20px; + margin-left: 5px; */ + bottom: 10px; + left: 10px; + color: #fffc; + } } .sidebar-close-button { diff --git a/src/app/ui/navigation/nav-links.tsx b/src/app/ui/navigation/nav-links.tsx index 3788d2d..6c555d5 100644 --- a/src/app/ui/navigation/nav-links.tsx +++ b/src/app/ui/navigation/nav-links.tsx @@ -13,6 +13,7 @@ import { useRef } from 'react'; import { useClickOutside } from '@/app/hooks/useClickOutside'; import { fetchReferralUserStats } from '@/app/actions/referralsActions'; import { useQuery } from '@tanstack/react-query'; +import { env } from 'process'; export default function NavLinks() { const { @@ -34,6 +35,30 @@ export default function NavLinks() { } }); + const { + data: frontendBuildDate, + } = useQuery({ + queryKey: ['frontendBuildDate'], + queryFn: async () => { + try { + const response = await fetch('/build-info.json'); + if (!response.ok) { + return null; + } + return response.json(); + } catch (error) { + return null; + } + }, + select: (data) => { + if (data?.buildTime) { + return data.buildTime; + } + return null; + }, + retry: false // НС ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΡΡ‚ΡŒ запрос ΠΏΡ€ΠΈ ошибкС + }); + const pathname = usePathname().replace('/en/', '/').replace('/ru/', '/'); const t = useTranslations('Global'); @@ -133,6 +158,17 @@ export default function NavLinks() {

      {t('exit')}

    + +
    + {!frontendBuildDate && ( +
    + front: {frontendBuildDate} +
    + )} + {/*
    + back: +
    */} +
    ); } From be1f310595178e6aaf3825e4b09771aaa1969709 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 11:16:58 +0700 Subject: [PATCH 095/119] add back build time --- src/app/actions/action.ts | 22 ++++++++++++++++++++-- src/app/lib/prefetch-queries.ts | 16 +++++++++++++++- src/app/ui/navigation/nav-links.tsx | 27 ++++++++++++++++++++++----- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/app/actions/action.ts b/src/app/actions/action.ts index c5223d6..e84aa1d 100644 --- a/src/app/actions/action.ts +++ b/src/app/actions/action.ts @@ -2,7 +2,6 @@ import { getSessionData, deleteSession } from '@/app/actions/session'; import { testUserData, API_BASE_URL } from '@/app/actions/definitions'; -import { redirect } from 'next/navigation'; export async function getUserData() { const userEmail = await getSessionData('email'); @@ -99,4 +98,23 @@ export async function fetchTariffs() { } catch (error) { console.error('error'); } -} \ No newline at end of file +} + +export async function getBuildData() { + + try { + const response = await fetch(`${API_BASE_URL}/check/api/build`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + } + }); + + if (response.ok) { + return await response.json(); + } + } catch (error) { + console.error('error'); + } +} diff --git a/src/app/lib/prefetch-queries.ts b/src/app/lib/prefetch-queries.ts index c384463..b597432 100644 --- a/src/app/lib/prefetch-queries.ts +++ b/src/app/lib/prefetch-queries.ts @@ -1,5 +1,5 @@ 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 { fetchReferralUserStats } from '@/app/actions/referralsActions'; @@ -24,5 +24,19 @@ export async function prefetchLayoutQueries(queryClient: QueryClient) { queryKey: ['referralUserStats'], queryFn: () => fetchReferralUserStats() }), + queryClient.prefetchQuery({ + queryKey: ['backendBuildDate'], + queryFn: async () => { + try { + const response = await getBuildData(); + if (response.buildTimeBack) { + return response.buildTimeBack; + } + return null; + } catch (error) { + return null; + } + } + }), ]); } \ No newline at end of file diff --git a/src/app/ui/navigation/nav-links.tsx b/src/app/ui/navigation/nav-links.tsx index 6c555d5..a253fbd 100644 --- a/src/app/ui/navigation/nav-links.tsx +++ b/src/app/ui/navigation/nav-links.tsx @@ -14,6 +14,7 @@ import { useClickOutside } from '@/app/hooks/useClickOutside'; import { fetchReferralUserStats } from '@/app/actions/referralsActions'; import { useQuery } from '@tanstack/react-query'; import { env } from 'process'; +import { getBuildData } from '@/app/actions/action'; export default function NavLinks() { const { @@ -56,7 +57,21 @@ export default function NavLinks() { } return null; }, - retry: false // НС ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΡΡ‚ΡŒ запрос ΠΏΡ€ΠΈ ошибкС + retry: false + }); + + const { + data: backendBuildDate, + } = useQuery({ + queryKey: ['backendBuildDate'], + queryFn: () => getBuildData(), + select: (data) => { + if (data) { + return data; + } + return null; + }, + retry: false }); const pathname = usePathname().replace('/en/', '/').replace('/ru/', '/'); @@ -160,14 +175,16 @@ export default function NavLinks() {
    - {!frontendBuildDate && ( + {frontendBuildDate && (
    front: {frontendBuildDate}
    )} - {/*
    - back: -
    */} + {backendBuildDate && ( +
    + back: {backendBuildDate} +
    + )}
    ); From a52be724c02f5f434e3b59326ba7a9d72bac577b Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 11:17:30 +0700 Subject: [PATCH 096/119] for getBuildData return null while error --- src/app/actions/action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/actions/action.ts b/src/app/actions/action.ts index e84aa1d..d6b2c1e 100644 --- a/src/app/actions/action.ts +++ b/src/app/actions/action.ts @@ -115,6 +115,6 @@ export async function getBuildData() { return await response.json(); } } catch (error) { - console.error('error'); + return null; } } From 726c1dc16ce111cff2fe13fe4f526a047f828c99 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 11:33:52 +0700 Subject: [PATCH 097/119] refactor --- package.json | 2 +- src/app/components/UploadSectionFile.tsx | 19 ++----------------- src/app/styles/global-styles.scss | 2 -- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 5314cfb..634d507 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.38.0", + "version": "0.39.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/components/UploadSectionFile.tsx b/src/app/components/UploadSectionFile.tsx index c6cbaae..ec2f3ff 100644 --- a/src/app/components/UploadSectionFile.tsx +++ b/src/app/components/UploadSectionFile.tsx @@ -6,7 +6,6 @@ import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/a import { useTranslations } from 'next-intl'; import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker'; import { useQueryClient } from '@tanstack/react-query'; -import { resolve } from 'path'; interface SelectedFile { file: File; @@ -128,8 +127,8 @@ export default function UploadSectionFile({ 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) { + 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; @@ -184,7 +183,6 @@ export default function UploadSectionFile({ const handleClearFile = async (fileId?: string): Promise => { if (fileId) { - // ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π° const fileToClear = selectedFiles.find(f => f.uploadId === fileId || f.name === fileId); if (fileToClear?.preview) { URL.revokeObjectURL(fileToClear.preview); @@ -204,14 +202,12 @@ export default function UploadSectionFile({ f.uploadId !== fileId && f.name !== fileId )); } else { - // ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° всСх Ρ„Π°ΠΉΠ»ΠΎΠ² selectedFiles.forEach(file => { if (file.preview) { URL.revokeObjectURL(file.preview); } }); - // ΠžΡ‚ΠΌΠ΅Π½Π° всСх Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… Π·Π°Π³Ρ€ΡƒΠ·ΠΎΠΊ const cancelPromises = Array.from(activeUploads).map(uploadId => cancelUpload(uploadId) ); @@ -319,14 +315,6 @@ export default function UploadSectionFile({ f.name === fileInfo.name ? { ...f, status: 'error', error: errorMessage } : f )); } - } finally { - /* if (fileInfo.uploadId) { - setActiveUploads(prev => { - const newSet = new Set(prev); - newSet.delete(fileInfo.uploadId!); - return newSet; - }); - } */ } }; @@ -341,9 +329,6 @@ export default function UploadSectionFile({ if (batch.length === 0) return; - console.log('Π’Π΅ΠΊΡƒΡ‰ΠΈΠ΅ pending Ρ„Π°ΠΉΠ»Ρ‹:', currentPending); - console.log('ЗагруТаСмая партия:', batch); - await Promise.all(batch.map(file => uploadSingleFile(file))); if (selectedFilesRef.current.some(f => f.status === 'pending')) { diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index 75f973b..4dd2171 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -309,8 +309,6 @@ .build-versions { position: absolute; -/* margin-top: 20px; - margin-left: 5px; */ bottom: 10px; left: 10px; color: #fffc; From 166de1bf15bae602fec7d5dfb20bf29fc2634eac Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 12:38:44 +0700 Subject: [PATCH 098/119] adaptive styles for file uploads --- src/app/components/UploadSectionFile.tsx | 31 +++++++------- src/app/styles/pages-styles.scss | 53 ++++++++++++++++++++++++ src/i18n/messages/en.json | 12 +++--- src/i18n/messages/ru.json | 12 +++--- 4 files changed, 81 insertions(+), 27 deletions(-) diff --git a/src/app/components/UploadSectionFile.tsx b/src/app/components/UploadSectionFile.tsx index ec2f3ff..3ae289a 100644 --- a/src/app/components/UploadSectionFile.tsx +++ b/src/app/components/UploadSectionFile.tsx @@ -456,23 +456,24 @@ export default function UploadSectionFile({ { selectedFiles.length > 0 && (
    -
    +
    -

    - {t('total-files')}: {selectedFiles.length} | - {t('uploading')}: {uploadingCount} | - {t('completed')}: {completedCount} +

    + {t('total-files')}: {selectedFiles.length} + {t('uploading')}: {uploadingCount} + {t('completed')}: {completedCount}

    - < div className="w-full bg-gray-200 rounded-full h-2.5 mt-2" > +
    + > +
    - < div className="flex gap-2" > +
    - < div className="space-y-4 max-h-96 overflow-y-auto" > +
    { selectedFiles.map((file, index) => (
    -
    +

    {t('file')}: {file.name} @@ -527,9 +528,9 @@ export default function UploadSectionFile({ ) }

    - < div className="flex items-center" > +
    < button - className="btn btn-cancel text-sm py-1 px-3" + className="btn btn-cancel" onClick={() => handleClearFile(file.uploadId || file.name)} type="button" > @@ -559,9 +560,9 @@ export default function UploadSectionFile({ { file.status === 'pending' && ( -
    +
    -
    - {item?.height}x{item?.width} -
    + {item?.height & item?.width && ( +
    + {item?.height}x{item?.width} +
    + )}
    {item?.host}
    From 6ccdfc01299fe842df9023d406668f91e3526cd7 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 13:11:14 +0700 Subject: [PATCH 100/119] add reports-info --- src/app/[locale]/pages/reports/page.tsx | 4 +- src/app/ui/reports/reports-info.tsx | 57 +++++++++++++++++++++++++ src/i18n/messages/en.json | 5 ++- src/i18n/messages/ru.json | 5 ++- 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/app/ui/reports/reports-info.tsx diff --git a/src/app/[locale]/pages/reports/page.tsx b/src/app/[locale]/pages/reports/page.tsx index dabb6fd..edf6d9c 100644 --- a/src/app/[locale]/pages/reports/page.tsx +++ b/src/app/[locale]/pages/reports/page.tsx @@ -5,12 +5,14 @@ import AnalyticsCardMonth from '@/app/ui/reports/analytics-card-month'; import AnalyticsCardGeography from '@/app/ui/reports/analytics-carΠ²-geography'; import ActivityByContentType from '@/app/ui/reports/activity-by-content-type'; import PageTitle from '@/app/ui/page-title'; +import ReportsInfo from '@/app/ui/reports/reports-info'; export default function Page() { return (
    - + {/* */} +
    diff --git a/src/app/ui/reports/reports-info.tsx b/src/app/ui/reports/reports-info.tsx new file mode 100644 index 0000000..837a1e2 --- /dev/null +++ b/src/app/ui/reports/reports-info.tsx @@ -0,0 +1,57 @@ +'use client' + +import { useTranslations } from 'next-intl'; +import { useQuery } from '@tanstack/react-query'; +import { getUserFilesInfo } from '@/app/actions/action'; +import {convertBytes} from '@/app/lib/convertBytes'; +import { useEffect } from 'react'; + +export default function ReportsInfo() { + const t = useTranslations("Global"); + + const { + data: filesInfo, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['userFilesInfo'], + queryFn: getUserFilesInfo, + }); + + useEffect(() => { + console.log(filesInfo) + }, [filesInfo]) + + return ( +
    +
    +
    + {t('all-content')} +
    +
    {filesInfo?.all_files_quantity ? filesInfo?.all_files_quantity : 0}
    +
    + +
    +
    + {t('violations-few')} +
    +
    {filesInfo?.all_files_violation ? filesInfo?.all_files_violation : 0}
    +
    + +
    +
    + {t('monitoring')} +
    +
    {filesInfo?.all_files_check ? filesInfo?.all_files_check : 0}
    +
    + +
    +
    + {t('efficiency')} +
    +
    100%
    +
    +
    + ) +} \ No newline at end of file diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 2dd1a7a..cdfce60 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -220,7 +220,10 @@ "remove": "Remove", "start-upload": "Start upload", "completed": "Completed", - "pending": "Pending" + "pending": "Pending", + "all-content": "All content", + "monitoring": "Monitoring", + "efficiency": "Efficiency" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 0aa8ad0..c37ea2e 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -220,7 +220,10 @@ "remove": "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ", "start-upload": "ΠΠ°Ρ‡Π°Ρ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ", "completed": "Π—Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΎ", - "pending": "Π’ ΠΎΠΆΠΈΠ΄Π°Π½ΠΈΠΈ" + "pending": "Π’ ΠΎΠΆΠΈΠ΄Π°Π½ΠΈΠΈ", + "all-content": "Π’Π΅ΡΡŒ ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚", + "monitoring": "ΠœΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³", + "efficiency": "Π­Ρ„Ρ„Π΅ΠΊΡ‚ΠΈΠ²Π½ΠΎΡΡ‚ΡŒ" }, "Login-register-form": { "and": "ΠΈ", From 5f12c58cc8abebb643daf1923a897dbc9661ff43 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 13:12:52 +0700 Subject: [PATCH 101/119] remove console.log --- src/app/ui/reports/reports-info.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app/ui/reports/reports-info.tsx b/src/app/ui/reports/reports-info.tsx index 837a1e2..375a0a1 100644 --- a/src/app/ui/reports/reports-info.tsx +++ b/src/app/ui/reports/reports-info.tsx @@ -19,10 +19,6 @@ export default function ReportsInfo() { queryFn: getUserFilesInfo, }); - useEffect(() => { - console.log(filesInfo) - }, [filesInfo]) - return (
    From 4958d3ca574c5726b5dd4f4f7fd8aaae97ad226f Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 14:01:35 +0700 Subject: [PATCH 102/119] remove procent from MyContentStatsOverview and change icons --- src/app/styles/pages-styles.scss | 4 ++- src/app/ui/icons/icons.tsx | 8 +++++ .../new/my-content-stats-overview.tsx | 35 ++++++++++++------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index a54d600..c7c36c5 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -2362,7 +2362,9 @@ } .stat-icon { - font-size: 36px; + .icon { + transform: scale(1.5); + } } .stat-header { diff --git a/src/app/ui/icons/icons.tsx b/src/app/ui/icons/icons.tsx index 5759458..3f2960c 100644 --- a/src/app/ui/icons/icons.tsx +++ b/src/app/ui/icons/icons.tsx @@ -166,4 +166,12 @@ export function IconSearch() { return ( ) +} + +export function IconDiscet() { + return ( + + + + ) } \ No newline at end of file diff --git a/src/app/ui/my-content/new/my-content-stats-overview.tsx b/src/app/ui/my-content/new/my-content-stats-overview.tsx index 52bb1eb..9047861 100644 --- a/src/app/ui/my-content/new/my-content-stats-overview.tsx +++ b/src/app/ui/my-content/new/my-content-stats-overview.tsx @@ -3,7 +3,8 @@ import { useTranslations } from 'next-intl'; import { useQuery } from '@tanstack/react-query'; import { getUserFilesInfo } from '@/app/actions/action'; -import {convertBytes} from '@/app/lib/convertBytes'; +import { convertBytes } from '@/app/lib/convertBytes'; +import { IconDocument, IconShield, IconSearch, IconDiscet } from '@/app/ui/icons/icons'; export default function MyContentStatsOverview() { const t = useTranslations('Global'); @@ -19,37 +20,45 @@ export default function MyContentStatsOverview() { return (
    -
    +
    -
    πŸ“„
    -
    +0%
    +
    + +
    + {/*
    +0%
    */}
    {filesInfo?.all_files_quantity}
    ВсСго Ρ„Π°ΠΉΠ»ΠΎΠ²
    -
    +
    -
    πŸ›‘
    -
    +0%
    +
    + +
    + {/*
    +0%
    */}
    {filesInfo?.protected_files_count}
    Π—Π°Ρ‰ΠΈΡ‰Π΅Π½ΠΎ Ρ„Π°ΠΉΠ»ΠΎΠ²
    -
    +
    -
    πŸ”
    -
    +0%
    +
    + +
    + {/*
    +0%
    */}
    0
    ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΎΠΊ Π·Π°Ρ‰ΠΈΡ‚Ρ‹
    -
    +
    -
    πŸ’Ύ
    -
    +0%
    +
    + +
    + {/*
    +0%
    */}
    {filesInfo?.all_files_size ? convertBytes(filesInfo?.all_files_size) : 0}
    Π₯Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅
    From 03920524d3c58e31d0fc354ba698c702bf7798dc Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 16:21:10 +0700 Subject: [PATCH 103/119] change download url --- src/app/components/TanstakTable.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/components/TanstakTable.tsx b/src/app/components/TanstakTable.tsx index d56103b..ea9e073 100644 --- a/src/app/components/TanstakTable.tsx +++ b/src/app/components/TanstakTable.tsx @@ -474,7 +474,8 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { setIsFileLoading(true); try { - const response = await fetch(`/api/download/${file.id}`); + /* const response = await fetch(`/api/download/${file.id}`); */ + const response = await fetch(`/api/v1/files/download/${file.id}`); if (!response.ok) { throw new Error(`error: ${response.status}`); From f3e8ef3f6539fcad224aaf364dda7480c04bbd94 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 16:45:29 +0700 Subject: [PATCH 104/119] return download url --- src/app/components/TanstakTable.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/components/TanstakTable.tsx b/src/app/components/TanstakTable.tsx index ea9e073..203ccf3 100644 --- a/src/app/components/TanstakTable.tsx +++ b/src/app/components/TanstakTable.tsx @@ -48,6 +48,7 @@ type ApiFile = { status: string; protectStatus: string; supportId: number; + fileName: string; }; type ApiResponse = { @@ -474,8 +475,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { setIsFileLoading(true); try { - /* const response = await fetch(`/api/download/${file.id}`); */ - const response = await fetch(`/api/v1/files/download/${file.id}`); + const response = await fetch(`/api/download/${file.id}`); if (!response.ok) { throw new Error(`error: ${response.status}`); @@ -485,7 +485,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = file.fileName || `file-${file.id}`; + a.download = file._original?.fileName || file.fileName || `file-${file.id}`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); From a3da6b85303007dc1c4559aad4bba9b2b52059c5 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 18:02:25 +0700 Subject: [PATCH 105/119] modify search function for all files search --- src/app/[locale]/pages/search/page.tsx | 27 +++++++++-- src/app/styles/pages-styles.scss | 1 + src/app/ui/search/file-search-panel.tsx | 14 ++++-- src/app/ui/search/section-search-file.tsx | 57 +++++++++++++++++++---- src/app/ui/search/supported-formats.tsx | 24 ++++++---- 5 files changed, 97 insertions(+), 26 deletions(-) diff --git a/src/app/[locale]/pages/search/page.tsx b/src/app/[locale]/pages/search/page.tsx index 83d2b71..bae66e5 100644 --- a/src/app/[locale]/pages/search/page.tsx +++ b/src/app/[locale]/pages/search/page.tsx @@ -2,7 +2,14 @@ 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 { RecentSearches } from '@/app/ui/search/recent-searches'; -export default function Page() { +import { getAllowedFilesExtensions } from '@/app/actions/fileUpload'; + +export default async function Page() { + const { file_extension: extensionVideo, max_file_size } = await getAllowedFilesExtensions('video'); + const { file_extension: extensionAudio } = await getAllowedFilesExtensions('audio'); + const { file_extension: extensionImage } = await getAllowedFilesExtensions('image'); + const { file_extension: extensionDocument } = await getAllowedFilesExtensions('document'); + return ( <>
    @@ -15,12 +22,22 @@ export default function Page() {
    - +
    diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index c7c36c5..eebb307 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -3819,6 +3819,7 @@ color: v.$text-s; font-size: 14px; line-height: 1.5; + text-transform: uppercase; } .stat-item { diff --git a/src/app/ui/search/file-search-panel.tsx b/src/app/ui/search/file-search-panel.tsx index e762d0e..a21defb 100644 --- a/src/app/ui/search/file-search-panel.tsx +++ b/src/app/ui/search/file-search-panel.tsx @@ -23,6 +23,7 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a const [paginationLoading, setPaginationLoading] = useState(false); const [showGlobalSearch, setShowGlobalSearch] = useState(false); const queryClient = useQueryClient(); + const [errorMessage, setErrorMessage] = useState(null); const [searchTotalPages, setSearchTotalPages] = useState(0); const [searchCurrentPages, setSearchCurrentPages] = useState(1); @@ -41,20 +42,21 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a const handlerSearchUserFile = useCallback(async (fileId: string, page: number): Promise => { + setErrorMessage(null); try { let result = await searchUserFiles(fileId); if (result.content.length) { setSearchedUserFiles(result.content); + setShowGlobalSearch(true); + handlerSearchGlobalFile(fileId, page) } else { setSearchedUserFilesShowNull(true); } } catch (error) { - + setErrorMessage('error'); } - setShowGlobalSearch(true); - handlerSearchGlobalFile(fileId, page) }, [fileId]) @@ -106,6 +108,12 @@ export function FileSearchPanel({ fileId, ref }: { fileId: string | null, ref: a > ΠΠ°Ρ‡Π°Ρ‚ΡŒ поиск + + {errorMessage && ( +
    + Ошибка поиска +
    + )}
    {(searchedUserFiles.length !== 0) && ( diff --git a/src/app/ui/search/section-search-file.tsx b/src/app/ui/search/section-search-file.tsx index e93ea28..b89f212 100644 --- a/src/app/ui/search/section-search-file.tsx +++ b/src/app/ui/search/section-search-file.tsx @@ -24,12 +24,16 @@ interface FileUploadInitResponse { } interface SectionSearchFile { - fileType: string - allowedExtensions: string[] + allowedExtensions: { + images: string[], + videos: string[], + audios: string[], + documents: string[] + } maxFileSize: number } -export default function SectionSearchFile({ fileType, allowedExtensions, maxFileSize }: SectionSearchFile) { +export default function SectionSearchFile({ allowedExtensions, maxFileSize }: SectionSearchFile) { const fileInputRef = useRef(null); const [isDragging, setIsDragging] = useState(false); const [selectedFile, setSelectedFile] = useState(null); @@ -45,17 +49,49 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile const t = useTranslations('Global'); + const allExtensions = useMemo(() => { return [...allowedExtensions.images, ...allowedExtensions.videos, ...allowedExtensions.audios, ...allowedExtensions.documents] }, []); + const acceptString = useMemo(() => { - if (!allowedExtensions || !Array.isArray(allowedExtensions)) { + if (!allExtensions || !Array.isArray(allExtensions)) { return ''; } - return allowedExtensions.map(e => `.${e}`).join(', '); - }, [allowedExtensions]); + return allExtensions.map(e => `.${e}`).join(', '); + }, [allExtensions]); + + + 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'; + } const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => { - if (!allowedExtensions.includes(file.type as string)) { + if (!allExtensions.includes(file.type as string)) { const extension = file.name.split('.').pop()?.toLowerCase(); - if (!extension || !allowedExtensions.includes(extension as string)) { + if (!extension || !allExtensions.includes(extension as string)) { return { isValid: false, errorMessage: t('unsupported-file-format') @@ -117,7 +153,7 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile preview: file.size < 10 * 1024 * 1024 ? URL.createObjectURL(file) : undefined }); - }, [fileType, t]); + }, [t]); const handleFileInputChange = (event: ChangeEvent): void => { const file = event.target.files?.[0] || null; @@ -157,6 +193,7 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile setError(null); isCancelledRef.current = false; const file = fileInfo.file; + const fileType = getFileType(fileInfo.file.name, allowedExtensions); try { const extension = file.name.split('.').pop() || ''; @@ -225,7 +262,7 @@ export default function SectionSearchFile({ fileType, allowedExtensions, maxFile } finally { setUploadId(null); } - }, [uploadId, fileType]); + }, [uploadId, allowedExtensions]); useEffect(() => { // ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° закрытия Π²ΠΊΠ»Π°Π΄ΠΊΠΈ diff --git a/src/app/ui/search/supported-formats.tsx b/src/app/ui/search/supported-formats.tsx index c73d794..ce9b7ae 100644 --- a/src/app/ui/search/supported-formats.tsx +++ b/src/app/ui/search/supported-formats.tsx @@ -1,5 +1,13 @@ import { IconImageFile, IconVideoFile, IconAudioFile, IconDocument } from '@/app/ui/icons/icons'; -export function SupportedFormats() { + +interface supportedFormats { + extensionVideo: string[] + extensionAudio: string[] + extensionImage: string[] + extensionDocument: string[] +} + +export function SupportedFormats({ extensionVideo, extensionAudio, extensionImage, extensionDocument }: supportedFormats) { return (
    @@ -11,34 +19,34 @@ export function SupportedFormats() { Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΡ
    - JPEG, PNG, GIF, BMP + {extensionImage.join(', ')}
    -
    +
    Π’ΠΈΠ΄Π΅ΠΎ
    - MP4, AVI, MOV, WMV + {extensionVideo.join(', ')}
    -
    +
    Аудио
    - MP3, WAV, FLAC, AAC + {extensionAudio.join(', ')}
    -
    +
    Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹
    - PDF, DOC, DOCX + {extensionDocument.join(', ')}
    From 3946e7a0e757205f1e5a6be8a6e31c227924c6bb Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 21:32:06 +0700 Subject: [PATCH 106/119] change npm start script for stand --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 634d507..bc8d714 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "scripts": { "dev": "next dev -p 2999", "build": "next build", - "start": "next start -p 2999", + "start": "next dev -p 2999", + "start-prod": "next start -p 2999", "lint": "eslint" }, "dependencies": { From e36eb4d4228255b76ff8e0e2f49bdc8b9daa32c8 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 12 Feb 2026 21:55:31 +0700 Subject: [PATCH 107/119] return next start -p --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index bc8d714..634d507 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,7 @@ "scripts": { "dev": "next dev -p 2999", "build": "next build", - "start": "next dev -p 2999", - "start-prod": "next start -p 2999", + "start": "next start -p 2999", "lint": "eslint" }, "dependencies": { From d584e68e3a2fb660d9e90885e593a40e93e2248c Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 13 Feb 2026 12:50:58 +0700 Subject: [PATCH 108/119] refactor userFilesInfo query --- package.json | 2 +- src/app/actions/action.ts | 2 +- src/app/hooks/react-query/useUserFilesInfo.ts | 96 +++++++++++++++++++ src/app/lib/getProcents.ts | 4 +- .../ui/dashboard/new/dashboard-files-info.tsx | 46 ++++----- .../ui/dashboard/new/dashboard-user-stats.tsx | 20 +--- .../new/dashborad-limits-section.tsx | 29 ++---- src/app/ui/dashboard/protection-overview.tsx | 81 +++------------- src/app/ui/dashboard/stats-grid.tsx | 28 ++---- .../marking-page/new/protection-statistic.tsx | 73 +++----------- .../ui/marking-page/protection-summary.tsx | 75 ++++----------- .../ui/my-content/my-content-info-block.tsx | 43 ++++----- .../ui/my-content/my-content-pie-chart.tsx | 64 ++----------- .../my-content/new/my-content-page-title.tsx | 18 +--- .../new/my-content-stats-overview.tsx | 19 +--- src/app/ui/reports/reports-info.tsx | 22 ++--- .../ui/violations/violations-statistic.tsx | 42 ++------ 17 files changed, 232 insertions(+), 432 deletions(-) create mode 100644 src/app/hooks/react-query/useUserFilesInfo.ts diff --git a/package.json b/package.json index 634d507..b5a2879 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.39.0", + "version": "0.40.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/actions/action.ts b/src/app/actions/action.ts index d6b2c1e..0241efc 100644 --- a/src/app/actions/action.ts +++ b/src/app/actions/action.ts @@ -62,7 +62,7 @@ export async function getUserFilesInfo() { } } } catch (error) { - console.error(`error: ${error}`); + return null } } diff --git a/src/app/hooks/react-query/useUserFilesInfo.ts b/src/app/hooks/react-query/useUserFilesInfo.ts new file mode 100644 index 0000000..9919a48 --- /dev/null +++ b/src/app/hooks/react-query/useUserFilesInfo.ts @@ -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 => { + 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, + }, + }; + }, + }); +}; \ No newline at end of file diff --git a/src/app/lib/getProcents.ts b/src/app/lib/getProcents.ts index df8145b..eb43f48 100644 --- a/src/app/lib/getProcents.ts +++ b/src/app/lib/getProcents.ts @@ -1,5 +1,5 @@ export function getProcents( - currentNumber: number, + currentNumber: number | undefined, totalNumber: number, decimals: number = 0 ): number { @@ -7,7 +7,7 @@ export function getProcents( return 0; } - if (totalNumber === 0) { + if (totalNumber === 0 || typeof currentNumber === 'undefined') { return 0; } diff --git a/src/app/ui/dashboard/new/dashboard-files-info.tsx b/src/app/ui/dashboard/new/dashboard-files-info.tsx index 3bb79ab..7a7e2ef 100644 --- a/src/app/ui/dashboard/new/dashboard-files-info.tsx +++ b/src/app/ui/dashboard/new/dashboard-files-info.tsx @@ -1,22 +1,12 @@ 'use client' import { useTranslations } from 'next-intl'; -import { useQuery } from '@tanstack/react-query'; -import { getUserFilesInfo } from '@/app/actions/action'; import {convertBytes} from '@/app/lib/convertBytes'; +import {useUserFilesInfo} from '@/app/hooks/react-query/useUserFilesInfo'; export default function DashboardFilesInfo() { const t = useTranslations("Global"); - - const { - data: filesInfo, - isLoading, - isError, - error, - } = useQuery({ - queryKey: ['userFilesInfo'], - queryFn: getUserFilesInfo, - }); + const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); return (
    @@ -24,20 +14,20 @@ export default function DashboardFilesInfo() {
    {t('images')}
    -
    {filesInfo?.images_quantity ? filesInfo?.images_quantity : 0}
    +
    {filesInfo?.images.quantity ? filesInfo?.images.quantity : 0}
    {t('your-image')}
    {t('size')} - {filesInfo?.images_size ? convertBytes(filesInfo?.images_size) : 0} + {filesInfo?.images.size ? convertBytes(filesInfo?.images.size) : 0}
    {t('PROTECTED')} - {filesInfo?.protected_image_files_count ? filesInfo?.protected_image_files_count : 0} + {filesInfo?.images.protected ? filesInfo?.images.protected : 0}
    {t('violations')} - {filesInfo?.images_violations ? filesInfo?.images_violations : 0} + {filesInfo?.images.violation ? filesInfo?.images.violation : 0}
    @@ -46,20 +36,20 @@ export default function DashboardFilesInfo() {
    {t('videos')}
    -
    {filesInfo?.videos_quantity ? filesInfo?.videos_quantity : 0}
    +
    {filesInfo?.videos.quantity ? filesInfo?.videos.quantity : 0}
    {t('your-videos')}
    {t('size')} - {filesInfo?.videos_size ? convertBytes(filesInfo?.videos_size) : 0} + {filesInfo?.videos.size ? convertBytes(filesInfo?.videos.size) : 0}
    {t('PROTECTED')} - {filesInfo?.protected_video_files_count ? filesInfo?.protected_video_files_count : 0} + {filesInfo?.videos.protected ? filesInfo?.videos.protected : 0}
    {t('violations')} - {filesInfo?.videos_violations ? filesInfo?.videos_violations : 0} + {filesInfo?.videos.violation ? filesInfo?.videos.violation : 0}
    @@ -68,20 +58,20 @@ export default function DashboardFilesInfo() {
    {t('audios')}
    -
    {filesInfo?.audios_quantity ? filesInfo?.audios_quantity : 0}
    +
    {filesInfo?.audios.quantity ? filesInfo?.audios.quantity : 0}
    {t('your-audios')}
    {t('size')} - {filesInfo?.audios_size ? convertBytes(filesInfo?.audios_size) : 0} + {filesInfo?.audios.size ? convertBytes(filesInfo?.audios.size) : 0}
    {t('PROTECTED')} - {filesInfo?.protected_audio_files_count ? filesInfo?.protected_audio_files_count : 0} + {filesInfo?.audios.protected ? filesInfo?.audios.protected : 0}
    {t('violations')} - {filesInfo?.audios_violations ? filesInfo?.audios_violations : 0} + {filesInfo?.audios.violation ? filesInfo?.audios.violation : 0}
    @@ -90,20 +80,20 @@ export default function DashboardFilesInfo() {
    {t('documents')}
    -
    {filesInfo?.documents_quantity ? filesInfo?.documents_quantity : 0}
    +
    {filesInfo?.documents.quantity ? filesInfo?.documents.quantity : 0}
    {t('your-documents')}
    {t('size')} - {filesInfo?.documents_check ? convertBytes(filesInfo?.documents_check) : 0} + {filesInfo?.documents.check? convertBytes(filesInfo?.documents.check) : 0}
    {t('PROTECTED')} - {filesInfo?.documents_check ? filesInfo?.documents_check : 0} + {filesInfo?.documents.protected ? filesInfo?.documents.protected : 0}
    {t('violations')} - {filesInfo?.documents_violations ? filesInfo?.documents_violations : 0} + {filesInfo?.documents.violation ? filesInfo?.documents.violation : 0}
    diff --git a/src/app/ui/dashboard/new/dashboard-user-stats.tsx b/src/app/ui/dashboard/new/dashboard-user-stats.tsx index cfdae78..e6640aa 100644 --- a/src/app/ui/dashboard/new/dashboard-user-stats.tsx +++ b/src/app/ui/dashboard/new/dashboard-user-stats.tsx @@ -1,29 +1,19 @@ 'use client' import { useTranslations } from 'next-intl'; -import { useQuery } from '@tanstack/react-query'; -import { getUserFilesInfo } from '@/app/actions/action'; import {convertBytes} from '@/app/lib/convertBytes'; +import {useUserFilesInfo} from '@/app/hooks/react-query/useUserFilesInfo'; export default function DashboardUserStats() { const t = useTranslations("Global"); - - const { - data: filesInfo, - isLoading, - isError, - error, - } = useQuery({ - queryKey: ['userFilesInfo'], - queryFn: getUserFilesInfo, - }); + const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); return (
    Π—ΠΠ©Π˜Π©ΠΠΠΠ«Π• ЀАЙЛЫ
    -
    {filesInfo?.protected_files_count ? filesInfo?.protected_files_count : 0}
    +
    {filesInfo?.total.protected ? filesInfo?.total.protected : 0}
    Π€Π°ΠΉΠ»ΠΎΠ² ΠΏΠΎΠ΄ Π·Π°Ρ‰ΠΈΡ‚ΠΎΠΉ
    πŸ›‘οΈ
    @@ -37,7 +27,7 @@ export default function DashboardUserStats() {
    ΠΠΠ Π£Π¨Π•ΠΠ˜Π―
    -
    {filesInfo?.all_files_violations ? filesInfo?.all_files_violations : 0}
    +
    {filesInfo?.total.violation ? filesInfo?.total.violation : 0}
    Π’Ρ€Π΅Π±ΡƒΡŽΡ‚ внимания
    ⚠️
    @@ -45,7 +35,7 @@ export default function DashboardUserStats() {
    Π₯Π ΠΠΠ˜Π›Π˜Π©Π•
    - {filesInfo?.all_files_size ? convertBytes(filesInfo?.all_files_size) : 0} + {filesInfo?.total.size ? convertBytes(filesInfo?.total.size) : 0}
    Использовано мСста
    πŸ’Ύ
    diff --git a/src/app/ui/dashboard/new/dashborad-limits-section.tsx b/src/app/ui/dashboard/new/dashborad-limits-section.tsx index 6434d0b..f20de55 100644 --- a/src/app/ui/dashboard/new/dashborad-limits-section.tsx +++ b/src/app/ui/dashboard/new/dashborad-limits-section.tsx @@ -1,20 +1,11 @@ 'use client' -import { useQuery } from '@tanstack/react-query'; -import { getUserFilesInfo } from '@/app/actions/action'; import { convertBytes } from '@/app/lib/convertBytes'; import { getProcents } from '@/app/lib/getProcents'; +import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo'; export default function DahboardLimitsSection() { - const { - data: filesInfo, - isLoading, - isError, - error, - } = useQuery({ - queryKey: ['userFilesInfo'], - queryFn: getUserFilesInfo, - }); + const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); return (
    @@ -26,13 +17,13 @@ export default function DahboardLimitsSection() { πŸ” ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ
    -
    {filesInfo?.all_files_check ? filesInfo?.all_files_check : 0} / 10,000
    +
    {filesInfo?.total.check ? filesInfo?.total.check : 0} / 10,000
    - {getProcents(filesInfo?.all_files_check, 10000)}% + {getProcents(filesInfo?.total.check, 10000)}%
    @@ -42,13 +33,13 @@ export default function DahboardLimitsSection() { πŸ’Ύ Π₯Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅
    -
    {filesInfo?.all_files_size ? convertBytes(filesInfo?.all_files_size) : 0} / 1,024 ΠœΠ‘
    +
    {filesInfo?.total.size ? convertBytes(filesInfo?.total.size) : 0} / 1,024 ΠœΠ‘
    -
    +
    - {getProcents(filesInfo?.all_files_size, 1000000000)}% + {getProcents(filesInfo?.total.size, 1000000000)}%
    @@ -57,13 +48,13 @@ export default function DahboardLimitsSection() { πŸ“ Π€Π°ΠΉΠ»Ρ‹
    -
    {filesInfo?.all_files_quantity ? filesInfo?.all_files_quantity : 0} / 500
    +
    {filesInfo?.total.quantity ? filesInfo?.total.quantity : 0} / 500
    -
    +
    - {getProcents(filesInfo?.all_files_quantity, 500)}% + {getProcents(filesInfo?.total.quantity, 500)}%
    diff --git a/src/app/ui/dashboard/protection-overview.tsx b/src/app/ui/dashboard/protection-overview.tsx index 52a0c1a..9477623 100644 --- a/src/app/ui/dashboard/protection-overview.tsx +++ b/src/app/ui/dashboard/protection-overview.tsx @@ -5,23 +5,9 @@ import { PieChartComponent } from '@/app/components/PieChartComponent'; import { useTranslations, useLocale } from 'next-intl'; import Link from 'next/link'; import { useClickOutside } from '@/app/hooks/useClickOutside'; -import { useQuery } from '@tanstack/react-query'; -import { getUserFilesInfo } from '@/app/actions/action'; import { convertBytes } from '@/app/lib/convertBytes'; import { pluralize } from '@/app/lib/pluralize'; - -type FilesInfo = { - totalSize: number; - totalCount: number; - imageQuantity: number; - videoQuantity: number; - audioQuantity: number; - imageSize: number; - videoSize: number; - audioSize: number; - documentSize: number; -}; - +import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo'; export default function ProtectionOverview() { const [isOpen, setIsOpen] = useState(false); @@ -42,68 +28,23 @@ export default function ProtectionOverview() { openDropDownList ); - const { - data: filesInfo, - isLoading, - isError, - error, - } = useQuery<{ - all_files_size: number, - all_files_quantity: number, - images_quantity: number, - videos_quantity: number, - audios_quantity: number, - images_size: number, - videos_size: number, - audios_size: number, - documents_size: number, - }, Error, FilesInfo>({ - queryKey: ['userFilesInfo'], - queryFn: getUserFilesInfo, - select: (data): FilesInfo => { - if (!data) { - return { - totalSize: 0, - totalCount: 0, - imageQuantity: 0, - videoQuantity: 0, - audioQuantity: 0, - imageSize: 0, - videoSize: 0, - audioSize: 0, - documentSize: 0, - } - } - - return { - totalSize: data.all_files_size, - totalCount: data.all_files_quantity, - imageQuantity: data.images_quantity, - videoQuantity: data.videos_quantity, - audioQuantity: data.audios_quantity, - imageSize: data.images_size, - videoSize: data.videos_size, - audioSize: data.audios_size, - documentSize: data.documents_size, - } - }, - }); + const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); useEffect(() => { setFilesCount([ { name: 'images-few', - value: filesInfo?.imageSize ? filesInfo?.imageSize : 0, + value: filesInfo?.images.size ? filesInfo?.images.size : 0, color: '#f08c00' }, { name: 'videos', - value: filesInfo?.videoSize ? filesInfo?.videoSize : 0, + value: filesInfo?.videos.size ? filesInfo?.videos.size : 0, color: '#2f9e44' }, { name: 'audios', - value: filesInfo?.audioSize ? filesInfo?.audioSize : 0, + value: filesInfo?.audios.size ? filesInfo?.audios.size : 0, color: '#1971c2' }, ]) @@ -134,14 +75,14 @@ export default function ProtectionOverview() {
    - {filesInfo?.totalCount ? ( + {filesInfo?.total.quantity ? ( <>
    - {filesInfo?.totalCount ? filesInfo?.totalCount : 0} + {filesInfo?.total.quantity ? filesInfo?.total.quantity : 0} - {pluralizeFiles(filesInfo?.totalCount || 0)} + {pluralizeFiles(filesInfo?.total.quantity || 0)}
    @@ -196,18 +137,18 @@ export default function ProtectionOverview() { )} {isOpen ? (
    - +
    ) : (
    -
    {filesInfo?.totalSize ? convertBytes(filesInfo?.totalSize) : 0} / 0
    +
    {filesInfo?.total.size ? convertBytes(filesInfo?.total.size) : 0} / 0
    {t('disk-space-used')}
    )}
    - {!!filesInfo?.totalCount && ( + {!!filesInfo?.total.quantity && (
    @@ -130,10 +121,10 @@ export default function MyContentInfoBlock() {
    - {filesInfo?.audios_quantity || 0} {pluralizeFiles(filesInfo?.audios_quantity || 0)} + {filesInfo?.audios.quantity || 0} {pluralizeFiles(filesInfo?.audios.quantity || 0)} - {filesInfo?.audios_size ? convertBytes(filesInfo?.audios_size) : 0} + {filesInfo?.audios.size ? convertBytes(filesInfo?.audios.size) : 0}
    @@ -150,10 +141,10 @@ export default function MyContentInfoBlock() {
    - {filesInfo?.documents_quantity || 0} {pluralizeFiles(filesInfo?.documents_quantity || 0)} + {filesInfo?.documents.quantity || 0} {pluralizeFiles(filesInfo?.documents.quantity || 0)} - {filesInfo?.documents_size ? convertBytes(filesInfo?.documents_size) : 0} + {filesInfo?.documents.size ? convertBytes(filesInfo?.documents.size) : 0}
    diff --git a/src/app/ui/my-content/my-content-pie-chart.tsx b/src/app/ui/my-content/my-content-pie-chart.tsx index d6ae2d7..caf060e 100644 --- a/src/app/ui/my-content/my-content-pie-chart.tsx +++ b/src/app/ui/my-content/my-content-pie-chart.tsx @@ -1,10 +1,9 @@ 'use client' import { PieChartComponent } from '@/app/components/PieChartComponent'; -import { useQuery } from '@tanstack/react-query'; -import { getUserFilesInfo } from '@/app/actions/action'; import { useState, useEffect } from 'react'; import { convertBytes } from '@/app/lib/convertBytes'; +import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo'; type FilesInfo = { @@ -28,76 +27,27 @@ export default function MyContentPieChart() { { name: 'documents', value: 0, color: '#a561e6' }, ]); - const { - data: filesInfo, - isLoading, - isError, - error, - } = useQuery<{ - all_files_size: number, - all_files_quantity: number, - images_quantity: number, - videos_quantity: number, - audios_quantity: number, - documents_quantity: number, - images_size: number, - videos_size: number, - audios_size: number, - documents_size: number, - }, Error, FilesInfo>({ - queryKey: ['userFilesInfo'], - queryFn: getUserFilesInfo, - select: (data): FilesInfo => { - if (!data) { - return { - totalSize: 0, - totalCount: 0, - imageQuantity: 0, - videoQuantity: 0, - audioQuantity: 0, - documentQuantity: 0, - imageSize: 0, - videoSize: 0, - audioSize: 0, - documentSize: 0, - } - } - - return { - totalSize: data.all_files_size, - totalCount: data.all_files_quantity, - imageQuantity: data.images_quantity, - videoQuantity: data.videos_quantity, - audioQuantity: data.audios_quantity, - documentQuantity: data.documents_quantity, - imageSize: data.images_size, - videoSize: data.videos_size, - audioSize: data.audios_size, - documentSize: data.documents_size, - } - }, - }); - + const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); useEffect(() => { setFilesCount([ { name: 'images-few', - value: filesInfo?.imageSize ? filesInfo?.imageSize : 0, + value: filesInfo?.images.size ? filesInfo?.images.size : 0, color: '#f08c00' }, { name: 'videos', - value: filesInfo?.videoSize ? filesInfo?.videoSize : 0, + value: filesInfo?.videos.size ? filesInfo?.videos.size : 0, color: '#2f9e44' }, { name: 'audios', - value: filesInfo?.audioSize ? filesInfo?.audioSize : 0, + value: filesInfo?.audios.size ? filesInfo?.audios.size : 0, color: '#1971c2' }, { name: 'documents', - value: filesInfo?.documentSize ? filesInfo?.documentSize : 0, + value: filesInfo?.documents.size ? filesInfo?.documents.size : 0, color: '#a561e6' }, ]) @@ -105,7 +55,7 @@ export default function MyContentPieChart() { return (
    - +
    ) } \ No newline at end of file diff --git a/src/app/ui/my-content/new/my-content-page-title.tsx b/src/app/ui/my-content/new/my-content-page-title.tsx index a619ce8..7f45584 100644 --- a/src/app/ui/my-content/new/my-content-page-title.tsx +++ b/src/app/ui/my-content/new/my-content-page-title.tsx @@ -2,21 +2,13 @@ 'use client' import { useTranslations } from 'next-intl'; -import { useQuery } from '@tanstack/react-query'; -import { getUserFilesInfo } from '@/app/actions/action'; import { convertBytes } from '@/app/lib/convertBytes'; +import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo'; export default function MyContentPageTitleColorFrame({ title, description }: { title: string, description: string }) { const t = useTranslations('Global'); - const { - data: filesInfo, - isLoading, - isError, - error, - } = useQuery({ - queryKey: ['userFilesInfo'], - queryFn: getUserFilesInfo, - }); + + const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); return (
    @@ -31,7 +23,7 @@ export default function MyContentPageTitleColorFrame({ title, description }: { t
    -
    {filesInfo?.all_files_quantity}
    +
    {filesInfo?.total.quantity}
    Π€Π°ΠΉΠ»ΠΎΠ²
    @@ -41,7 +33,7 @@ export default function MyContentPageTitleColorFrame({ title, description }: { t
    -
    {filesInfo?.all_files_size ? convertBytes(filesInfo?.all_files_size) : 0}
    +
    {convertBytes(filesInfo?.total.size ?? 0)}
    Π₯Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅
    diff --git a/src/app/ui/my-content/new/my-content-stats-overview.tsx b/src/app/ui/my-content/new/my-content-stats-overview.tsx index 9047861..deffedf 100644 --- a/src/app/ui/my-content/new/my-content-stats-overview.tsx +++ b/src/app/ui/my-content/new/my-content-stats-overview.tsx @@ -1,22 +1,13 @@ 'use client' import { useTranslations } from 'next-intl'; -import { useQuery } from '@tanstack/react-query'; -import { getUserFilesInfo } from '@/app/actions/action'; import { convertBytes } from '@/app/lib/convertBytes'; import { IconDocument, IconShield, IconSearch, IconDiscet } from '@/app/ui/icons/icons'; +import {useUserFilesInfo} from '@/app/hooks/react-query/useUserFilesInfo'; export default function MyContentStatsOverview() { const t = useTranslations('Global'); - const { - data: filesInfo, - isLoading, - isError, - error, - } = useQuery({ - queryKey: ['userFilesInfo'], - queryFn: getUserFilesInfo, - }); + const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); return (
    @@ -27,7 +18,7 @@ export default function MyContentStatsOverview() {
    {/*
    +0%
    */}
    -
    {filesInfo?.all_files_quantity}
    +
    {filesInfo?.total.quantity}
    ВсСго Ρ„Π°ΠΉΠ»ΠΎΠ²
    @@ -38,7 +29,7 @@ export default function MyContentStatsOverview() {
    {/*
    +0%
    */}
    -
    {filesInfo?.protected_files_count}
    +
    {filesInfo?.total.protected}
    Π—Π°Ρ‰ΠΈΡ‰Π΅Π½ΠΎ Ρ„Π°ΠΉΠ»ΠΎΠ²
    @@ -60,7 +51,7 @@ export default function MyContentStatsOverview() {
    {/*
    +0%
    */}
    -
    {filesInfo?.all_files_size ? convertBytes(filesInfo?.all_files_size) : 0}
    +
    {convertBytes(filesInfo?.total.size ?? 0)}
    Π₯Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅
    diff --git a/src/app/ui/reports/reports-info.tsx b/src/app/ui/reports/reports-info.tsx index 375a0a1..9b96ef6 100644 --- a/src/app/ui/reports/reports-info.tsx +++ b/src/app/ui/reports/reports-info.tsx @@ -1,23 +1,13 @@ 'use client' import { useTranslations } from 'next-intl'; -import { useQuery } from '@tanstack/react-query'; -import { getUserFilesInfo } from '@/app/actions/action'; -import {convertBytes} from '@/app/lib/convertBytes'; -import { useEffect } from 'react'; +import {useUserFilesInfo} from '@/app/hooks/react-query/useUserFilesInfo'; export default function ReportsInfo() { const t = useTranslations("Global"); - const { - data: filesInfo, - isLoading, - isError, - error, - } = useQuery({ - queryKey: ['userFilesInfo'], - queryFn: getUserFilesInfo, - }); + + const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); return (
    @@ -25,21 +15,21 @@ export default function ReportsInfo() {
    {t('all-content')}
    -
    {filesInfo?.all_files_quantity ? filesInfo?.all_files_quantity : 0}
    +
    {filesInfo?.total.quantity ? filesInfo?.total.quantity : 0}
    {t('violations-few')}
    -
    {filesInfo?.all_files_violation ? filesInfo?.all_files_violation : 0}
    +
    {filesInfo?.total.violation ? filesInfo?.total.violation : 0}
    {t('monitoring')}
    -
    {filesInfo?.all_files_check ? filesInfo?.all_files_check : 0}
    +
    {filesInfo?.total.check ? filesInfo?.total.check : 0}
    diff --git a/src/app/ui/violations/violations-statistic.tsx b/src/app/ui/violations/violations-statistic.tsx index b3e2541..e37dc79 100644 --- a/src/app/ui/violations/violations-statistic.tsx +++ b/src/app/ui/violations/violations-statistic.tsx @@ -1,51 +1,25 @@ 'use client' import { useTranslations } from 'next-intl'; -import { useQuery } from '@tanstack/react-query'; -import { getUserFilesInfo } from '@/app/actions/action'; - -type FilesInfo = { - totalSize: number; - totalCount: number; -}; +import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo'; export default function ViolationsStatistic() { const t = useTranslations('Global'); - const { - data: filesInfo, - isLoading, - isError, - error, - } = useQuery<{ - all_files_size: number, - all_files_quantity: number - }, Error, FilesInfo>({ - queryKey: ['userFilesInfo'], - queryFn: getUserFilesInfo, - select: (data): FilesInfo => { - if (!data) { - return { - totalSize: 0, - totalCount: 0 - } - } - - return { - totalSize: data.all_files_size, - totalCount: data.all_files_quantity - } - } - }); + const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); return (
    -
    0
    +
    + {filesInfo?.total.violation} +
    {t(`total-violations`)}
    -
    0
    +
    + {filesInfo?.total.violation} +
    {t(`new`)}
    From edc6e56c69ea5161bc23ad4dd92c2bc82017a722 Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 13 Feb 2026 12:57:45 +0700 Subject: [PATCH 109/119] refactor backendBuildDate --- src/app/lib/prefetch-queries.ts | 5 +---- src/app/ui/navigation/nav-links.tsx | 11 ++++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/app/lib/prefetch-queries.ts b/src/app/lib/prefetch-queries.ts index b597432..71c5d57 100644 --- a/src/app/lib/prefetch-queries.ts +++ b/src/app/lib/prefetch-queries.ts @@ -29,10 +29,7 @@ export async function prefetchLayoutQueries(queryClient: QueryClient) { queryFn: async () => { try { const response = await getBuildData(); - if (response.buildTimeBack) { - return response.buildTimeBack; - } - return null; + return response.buildTimeBack ?? null; } catch (error) { return null; } diff --git a/src/app/ui/navigation/nav-links.tsx b/src/app/ui/navigation/nav-links.tsx index a253fbd..1fd1be0 100644 --- a/src/app/ui/navigation/nav-links.tsx +++ b/src/app/ui/navigation/nav-links.tsx @@ -64,12 +64,13 @@ export default function NavLinks() { data: backendBuildDate, } = useQuery({ queryKey: ['backendBuildDate'], - queryFn: () => getBuildData(), - select: (data) => { - if (data) { - return data; + queryFn: async () => { + try { + const response = await getBuildData(); + return response.buildTimeBack ?? null; + } catch (error) { + return null; } - return null; }, retry: false }); From 1f6301c53138ea21ce28e0919e0ba8fe15e2f442 Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 13 Feb 2026 13:44:03 +0700 Subject: [PATCH 110/119] refactor userData query --- src/app/actions/action.ts | 2 +- src/app/hooks/react-query/useUserDataInfo.ts | 81 +++++++++++++++++++ src/app/hooks/react-query/useUserFilesInfo.ts | 2 +- src/app/lib/getProcents.ts | 4 +- src/app/ui/header/headerPanel.tsx | 28 +------ src/app/ui/header/userMenuButton.tsx | 22 ++--- src/app/ui/payment/user-usage.tsx | 42 ++-------- .../settings/company-users-setting-panel.tsx | 15 +--- .../ui/settings/personal-data-settings.tsx | 13 +-- src/app/ui/settings/subscription-settings.tsx | 42 ++++------ src/app/ui/test.tsx | 17 +--- 11 files changed, 124 insertions(+), 144 deletions(-) create mode 100644 src/app/hooks/react-query/useUserDataInfo.ts diff --git a/src/app/actions/action.ts b/src/app/actions/action.ts index 0241efc..af3ea8b 100644 --- a/src/app/actions/action.ts +++ b/src/app/actions/action.ts @@ -27,7 +27,7 @@ export async function getUserData() { return await response.json(); } } catch (error) { - console.error('error'); + return null } } diff --git a/src/app/hooks/react-query/useUserDataInfo.ts b/src/app/hooks/react-query/useUserDataInfo.ts new file mode 100644 index 0000000..30452c4 --- /dev/null +++ b/src/app/hooks/react-query/useUserDataInfo.ts @@ -0,0 +1,81 @@ +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; +} + +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, + }; + } + + return data; + }, + }); +}; \ No newline at end of file diff --git a/src/app/hooks/react-query/useUserFilesInfo.ts b/src/app/hooks/react-query/useUserFilesInfo.ts index 9919a48..56783eb 100644 --- a/src/app/hooks/react-query/useUserFilesInfo.ts +++ b/src/app/hooks/react-query/useUserFilesInfo.ts @@ -43,7 +43,7 @@ export const useUserFilesInfo = () => { return useQuery({ queryKey: ['userFilesInfo'], queryFn: getUserFilesInfo, - select: (data): FilesInfo => { + select: (data): FilesInfo | null => { if (!data) { return { total: { size: 0, quantity: 0, check: 0, violation: 0, protected: 0 }, diff --git a/src/app/lib/getProcents.ts b/src/app/lib/getProcents.ts index eb43f48..686bb8a 100644 --- a/src/app/lib/getProcents.ts +++ b/src/app/lib/getProcents.ts @@ -1,13 +1,13 @@ export function getProcents( currentNumber: number | undefined, - totalNumber: number, + totalNumber: number | undefined, decimals: number = 0 ): number { if (!currentNumber && !totalNumber) { return 0; } - if (totalNumber === 0 || typeof currentNumber === 'undefined') { + if (totalNumber === 0 || typeof currentNumber === 'undefined' || typeof totalNumber === 'undefined') { return 0; } diff --git a/src/app/ui/header/headerPanel.tsx b/src/app/ui/header/headerPanel.tsx index 6e0e675..0c842a1 100644 --- a/src/app/ui/header/headerPanel.tsx +++ b/src/app/ui/header/headerPanel.tsx @@ -6,37 +6,17 @@ import Link from 'next/link'; import LanguageSwitcher from '@/app/components/LanguageSwitcher'; import { useTranslations } from 'next-intl'; import BurgerMenu from '@/app/ui/navigation/burger-menu'; -import { getUserData } from '@/app/actions/action'; -import { useQuery } from '@tanstack/react-query'; +import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo'; export default function HeaderPanel() { const t = useTranslations("Global"); - const { - data: tokens, - isLoading, - isError, - error - } = useQuery({ - queryKey: ['userData'], - queryFn: () => { - return getUserData(); - }, - select: (data) => { - if (typeof data?.tariffInfo?.tokens === 'number') { - return data?.tariffInfo?.tokens - } else { - return null; - } - - } - }); - + const { data: userData, isLoading, isError, error } = useUserProfile(); return (
    {/* */} - {typeof tokens === 'number' && ( + {typeof userData?.tariffInfo.tokens === 'number' && ( - {tokens} + {userData?.tariffInfo.tokens} {t('tokens')} diff --git a/src/app/ui/header/userMenuButton.tsx b/src/app/ui/header/userMenuButton.tsx index f813a44..35d9ce6 100644 --- a/src/app/ui/header/userMenuButton.tsx +++ b/src/app/ui/header/userMenuButton.tsx @@ -3,27 +3,15 @@ import { useState, useRef, useEffect } from 'react'; import { useClickOutside } from '@/app/hooks/useClickOutside'; import Link from 'next/link'; -import { getUserData } from '@/app/actions/action'; -import { useQuery } from '@tanstack/react-query'; import { logout } from '@/app/actions/auth'; import { useTranslations } from 'next-intl'; +import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo'; export default function UserMenuButton() { const [isOpen, setIsOpen] = useState(false); const menuRef = useRef(null); const t = useTranslations("Global"); - const { - data: userData, - isLoading, - isError, - error - } = useQuery({ - queryKey: ['userData'], - queryFn: () => { - return getUserData(); - } - }) - + const { data: userData, isLoading, isError, error } = useUserProfile(); useClickOutside( menuRef, @@ -50,7 +38,7 @@ export default function UserMenuButton() {
    {userData?.fullName ? userData.fullName[0] : ''}
    @@ -61,10 +49,10 @@ export default function UserMenuButton() {
    - {userData?.fullName ? userData.fullName : ''} + {userData?.fullName ?? ''}
    - {userData?.email ? userData.email : ''} + {userData?.email ?? ''}
    {t('rate')}: diff --git a/src/app/ui/payment/user-usage.tsx b/src/app/ui/payment/user-usage.tsx index ca870db..3fb17b5 100644 --- a/src/app/ui/payment/user-usage.tsx +++ b/src/app/ui/payment/user-usage.tsx @@ -1,37 +1,11 @@ 'use client' -import { getUserData } from '@/app/actions/action'; -import { useQuery } from '@tanstack/react-query'; -import { useEffect } from 'react'; import { convertBytes } from '@/app/lib/convertBytes'; import { getProcents } from '@/app/lib/getProcents'; +import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo'; export default function UserUsage() { - const { - data: userData, - isLoading, - isError, - error - } = useQuery({ - queryKey: ['userData'], - queryFn: () => { - return getUserData(); - }, - select: (data) => { - if (data.tariffInfo) { - return { - diskSize: data.tariffInfo?.maxFileOnDisk, - id: data.tariffInfo?.id, - maxFilesCount: data.tariffInfo?.maxFileCounts, - name: data.tariffInfo?.tariffName, - endTariff: data.tariffInfo?.endTariff, - tokens: data.tariffInfo?.tokens, - currentFileCounts: data.tariffInfo?.currentFileCounts, - currentFileOnDisk: data.tariffInfo?.currentFileOnDisk, - } - } - } - }); + const { data: userData, isLoading, isError, error } = useUserProfile(); return (
    @@ -41,27 +15,27 @@ export default function UserUsage() {
    - {userData?.currentFileCounts} / {userData?.maxFilesCount} + {userData?.tariffInfo.currentFileCounts} / {userData?.tariffInfo.maxFileCounts}
    Π€Π°ΠΉΠ»ΠΎΠ² Π² мСсяц
    - {userData?.currentFileOnDisk ? convertBytes(userData?.currentFileOnDisk) : 0} / {userData?.diskSize ? convertBytes(userData?.diskSize) : 0} + {convertBytes(userData?.tariffInfo.currentFileOnDisk ?? 0)} / {convertBytes(userData?.tariffInfo.maxFileOnDisk ?? 0)}
    ΠžΠ±ΡŠΡ‘ΠΌ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π°
    @@ -69,13 +43,13 @@ export default function UserUsage() {
    - {userData?.tokens ? userData?.tokens : 0} + {userData?.tariffInfo.tokens ?? 0}
    ВокСнов на балансС
    - {userData?.name ? userData?.name : 'ERROR'} + {userData?.tariffInfo.tariffName ? userData?.tariffInfo.tariffName : 'ERROR'}
    Π’Π΅ΠΊΡƒΡ‰ΠΈΠΉ ΠΏΠ»Π°Π½
    diff --git a/src/app/ui/settings/company-users-setting-panel.tsx b/src/app/ui/settings/company-users-setting-panel.tsx index 0289a24..3ac5c0b 100644 --- a/src/app/ui/settings/company-users-setting-panel.tsx +++ b/src/app/ui/settings/company-users-setting-panel.tsx @@ -12,21 +12,12 @@ import * as z from 'zod'; import PhoneInput from '@/app/ui/inputs/phone-input'; import { IconEye } from '@/app/ui/icons/icons'; import { toast } from 'sonner'; +import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo'; export function CompanyUsersSettingPanel() { - const { - data: userData, - isLoading, - isError, - error - } = useQuery({ - queryKey: ['userData'], - queryFn: () => { - return getUserData(); - } - }); + const { data: userData, isLoading, isError, error } = useUserProfile(); - if (userData.permission !== 3) { + if (userData?.permission !== 3) { return null } diff --git a/src/app/ui/settings/personal-data-settings.tsx b/src/app/ui/settings/personal-data-settings.tsx index 8e4086c..37cd0c1 100644 --- a/src/app/ui/settings/personal-data-settings.tsx +++ b/src/app/ui/settings/personal-data-settings.tsx @@ -4,6 +4,7 @@ import { useTranslations } from 'next-intl'; import { useActionState, useState, useEffect } from 'react'; import { getUserData } from '@/app/actions/action'; import { useQuery } from '@tanstack/react-query'; +import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo'; export default function PersonalDataSettings() { const [errorMessage, formAction, isPending] = useActionState( @@ -13,17 +14,7 @@ export default function PersonalDataSettings() { undefined ); - const { - data: userData, - isLoading, - isError, - error - } = useQuery({ - queryKey: ['userData'], - queryFn: () => { - return getUserData(); - } - }); + const { data: userData, isLoading, isError, error } = useUserProfile(); useEffect(() => { console.log(userData) diff --git a/src/app/ui/settings/subscription-settings.tsx b/src/app/ui/settings/subscription-settings.tsx index 88ccb45..8fa51e0 100644 --- a/src/app/ui/settings/subscription-settings.tsx +++ b/src/app/ui/settings/subscription-settings.tsx @@ -4,37 +4,23 @@ import { useTranslations } from 'next-intl'; import { getUserData } from '@/app/actions/action'; import { useQuery } from '@tanstack/react-query'; import { convertBytes } from '@/app/lib/convertBytes'; +import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo'; export default function SubscriptionSettings() { const t = useTranslations('Global'); - const { - data: userData, - isLoading, - isError, - error - } = useQuery({ - queryKey: ['userData'], - queryFn: () => { - return getUserData(); - }, - select: (data) => { - if (data.tariffInfo) { - return { - diskSize: data.tariffInfo?.maxFileOnDisk, - id: data.tariffInfo?.id, - maxFilesCount: data.tariffInfo?.maxFileCounts, - name: data.tariffInfo?.tariffName, - endTariff: data.tariffInfo?.endTariff - } - } - } - }); + const { data: userData, isLoading, isError, error } = useUserProfile(); - function dateHandler(date: string) { + function dateHandler(date: string | undefined) { if (!date) { return 'error' } - return date.split(' ')[0]; + + const newDate = new Date(date); + const day = newDate.getDate().toString().padStart(2, '0'); + const month = (newDate.getMonth() + 1).toString().padStart(2, '0'); + const year = newDate.getFullYear(); + + return `${day}.${month}.${year}`; } return ( @@ -46,18 +32,18 @@ export default function SubscriptionSettings() {
    -

    Подписка {userData?.name}

    -

    πŸ“… Активна Π΄ΠΎ {dateHandler(userData?.endTariff)}

    +

    Подписка {userData?.tariffInfo.tariffName}

    +

    πŸ“… Активна Π΄ΠΎ {dateHandler(userData?.tariffInfo.endTariff)}

    -
    {userData?.maxFilesCount}
    +
    {userData?.tariffInfo.maxFileCounts}

    Ρ„Π°ΠΉΠ»ΠΎΠ² Π² мСсяц

    πŸ’Ύ Π₯Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅
    - {userData?.diskSize ? convertBytes(userData?.diskSize) : 0} + {userData?.tariffInfo.maxFileOnDisk ? convertBytes(userData?.tariffInfo.maxFileOnDisk) : 0}
    πŸ”Œ API доступ
    diff --git a/src/app/ui/test.tsx b/src/app/ui/test.tsx index b1c4047..534facc 100644 --- a/src/app/ui/test.tsx +++ b/src/app/ui/test.tsx @@ -1,20 +1,9 @@ 'use client'; -import { useQuery } from '@tanstack/react-query'; -import { getUserData } from '@/app/actions/action'; +import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo'; export default function HomePage() { - const { - data: userData, - isLoading, - isError, - error, - } = useQuery({ - queryKey: ['userData'], - queryFn: () => { - return getUserData(); - }, - }); + const { data: userData, isLoading, isError, error } = useUserProfile(); if (isLoading) return
    Loading...
    ; if (isError) return
    Error: {error.message}
    ; @@ -23,7 +12,7 @@ export default function HomePage() {

    user email

    - {userData.email} + {userData?.email}
    ); From 7aa29bd398f6c758dd1bdcfbfce706b005826629 Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 13 Feb 2026 14:16:39 +0700 Subject: [PATCH 111/119] add return null when fetch failed --- src/app/actions/action.ts | 10 +++- src/app/actions/searchActions.ts | 7 ++- src/app/actions/session.ts | 11 +++-- .../hooks/react-query/useUserSearchData.ts | 48 +++++++++++++++++++ src/app/ui/header/headerPanel.tsx | 4 +- src/app/ui/payment/user-usage.tsx | 12 ++--- src/app/ui/search/recent-searches.tsx | 17 ++----- src/app/ui/search/search-stats.tsx | 18 ++----- src/app/ui/settings/subscription-settings.tsx | 8 ++-- 9 files changed, 86 insertions(+), 49 deletions(-) create mode 100644 src/app/hooks/react-query/useUserSearchData.ts diff --git a/src/app/actions/action.ts b/src/app/actions/action.ts index af3ea8b..f4c7f13 100644 --- a/src/app/actions/action.ts +++ b/src/app/actions/action.ts @@ -25,6 +25,8 @@ export async function getUserData() { if (response.ok) { return await response.json(); + } else { + return null } } catch (error) { return null @@ -60,6 +62,8 @@ export async function getUserFilesInfo() { } else { throw parsed.message_code; } + } else { + return null } } catch (error) { return null @@ -94,9 +98,11 @@ export async function fetchTariffs() { return [] } + } else { + return null } } catch (error) { - console.error('error'); + return null } } @@ -113,6 +119,8 @@ export async function getBuildData() { if (response.ok) { return await response.json(); + } else { + return null } } catch (error) { return null; diff --git a/src/app/actions/searchActions.ts b/src/app/actions/searchActions.ts index 1a4a8fb..ac481d8 100644 --- a/src/app/actions/searchActions.ts +++ b/src/app/actions/searchActions.ts @@ -114,14 +114,13 @@ export async function getSearchStats() { } }); - - if (response.ok) { let parsed = await response.json(); - return parsed; + } else { + return null; } } catch (error) { - console.error('error'); + return null; } } \ No newline at end of file diff --git a/src/app/actions/session.ts b/src/app/actions/session.ts index 76a2191..dbd8b07 100644 --- a/src/app/actions/session.ts +++ b/src/app/actions/session.ts @@ -97,7 +97,12 @@ export async function getSessionData(type: 'token' | 'email'): Promise { + 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 + }); +}; \ No newline at end of file diff --git a/src/app/ui/header/headerPanel.tsx b/src/app/ui/header/headerPanel.tsx index 0c842a1..bca4a72 100644 --- a/src/app/ui/header/headerPanel.tsx +++ b/src/app/ui/header/headerPanel.tsx @@ -16,7 +16,7 @@ export default function HeaderPanel() {
    {/* */} - {typeof userData?.tariffInfo.tokens === 'number' && ( + {typeof userData?.tariffInfo?.tokens === 'number' && ( - {userData?.tariffInfo.tokens} + {userData?.tariffInfo?.tokens} {t('tokens')} diff --git a/src/app/ui/payment/user-usage.tsx b/src/app/ui/payment/user-usage.tsx index 3fb17b5..2e5a443 100644 --- a/src/app/ui/payment/user-usage.tsx +++ b/src/app/ui/payment/user-usage.tsx @@ -15,27 +15,27 @@ export default function UserUsage() {
    - {userData?.tariffInfo.currentFileCounts} / {userData?.tariffInfo.maxFileCounts} + {userData?.tariffInfo?.currentFileCounts} / {userData?.tariffInfo?.maxFileCounts}
    Π€Π°ΠΉΠ»ΠΎΠ² Π² мСсяц
    - {convertBytes(userData?.tariffInfo.currentFileOnDisk ?? 0)} / {convertBytes(userData?.tariffInfo.maxFileOnDisk ?? 0)} + {convertBytes(userData?.tariffInfo?.currentFileOnDisk ?? 0)} / {convertBytes(userData?.tariffInfo?.maxFileOnDisk ?? 0)}
    ΠžΠ±ΡŠΡ‘ΠΌ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π°
    @@ -43,13 +43,13 @@ export default function UserUsage() {
    - {userData?.tariffInfo.tokens ?? 0} + {userData?.tariffInfo?.tokens ?? 0}
    ВокСнов на балансС
    - {userData?.tariffInfo.tariffName ? userData?.tariffInfo.tariffName : 'ERROR'} + {userData?.tariffInfo?.tariffName ? userData?.tariffInfo?.tariffName : 'ERROR'}
    Π’Π΅ΠΊΡƒΡ‰ΠΈΠΉ ΠΏΠ»Π°Π½
    diff --git a/src/app/ui/search/recent-searches.tsx b/src/app/ui/search/recent-searches.tsx index 120fb68..54e0135 100644 --- a/src/app/ui/search/recent-searches.tsx +++ b/src/app/ui/search/recent-searches.tsx @@ -1,21 +1,10 @@ 'use client' import { IconImageFile, IconDocument, IconAudioFile, IconVideoFile } from '@/app/ui/icons/icons'; -import { useQuery } from '@tanstack/react-query'; -import { getSearchStats } from '@/app/actions/searchActions'; +import { useUserSearchData } from '@/app/hooks/react-query/useUserSearchData'; export function RecentSearches() { - const { - data: userSearchData, - isLoading, - isError, - error, - } = useQuery({ - queryKey: ['userSearchData'], - queryFn: () => { - return getSearchStats(); - }, - }); + const { data: userSearchData, isLoading, isError, error, } = useUserSearchData() return (
    @@ -46,7 +35,7 @@ export function RecentSearches() { audio
    -
    {userSearchData?.audio?.count ? userSearchData?.audi?.count : 0} поисков
    +
    {userSearchData?.audio?.count ? userSearchData?.audio?.count : 0} поисков
    date
    diff --git a/src/app/ui/search/search-stats.tsx b/src/app/ui/search/search-stats.tsx index 9055c11..387ee8d 100644 --- a/src/app/ui/search/search-stats.tsx +++ b/src/app/ui/search/search-stats.tsx @@ -1,21 +1,9 @@ 'use client' -import { useQuery } from '@tanstack/react-query'; -import { getSearchStats } from '@/app/actions/searchActions'; -import { useEffect } from 'react'; +import { useUserSearchData } from '@/app/hooks/react-query/useUserSearchData'; export function SearchStats() { - const { - data: userSearchData, - isLoading, - isError, - error, - } = useQuery({ - queryKey: ['userSearchData'], - queryFn: () => { - return getSearchStats(); - }, - }); + const { data: userSearchData, isLoading, isError, error, } = useUserSearchData() return (
    @@ -40,7 +28,7 @@ export function SearchStats() {
    Аудио - {userSearchData?.audio?.count ? userSearchData?.audi?.count : 0} + {userSearchData?.audio?.count ? userSearchData?.audio?.count : 0}
    diff --git a/src/app/ui/settings/subscription-settings.tsx b/src/app/ui/settings/subscription-settings.tsx index 8fa51e0..beaef02 100644 --- a/src/app/ui/settings/subscription-settings.tsx +++ b/src/app/ui/settings/subscription-settings.tsx @@ -32,18 +32,18 @@ export default function SubscriptionSettings() {
    -

    Подписка {userData?.tariffInfo.tariffName}

    -

    πŸ“… Активна Π΄ΠΎ {dateHandler(userData?.tariffInfo.endTariff)}

    +

    Подписка {userData?.tariffInfo?.tariffName}

    +

    πŸ“… Активна Π΄ΠΎ {dateHandler(userData?.tariffInfo?.endTariff)}

    -
    {userData?.tariffInfo.maxFileCounts}
    +
    {userData?.tariffInfo?.maxFileCounts}

    Ρ„Π°ΠΉΠ»ΠΎΠ² Π² мСсяц

    πŸ’Ύ Π₯Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅
    - {userData?.tariffInfo.maxFileOnDisk ? convertBytes(userData?.tariffInfo.maxFileOnDisk) : 0} + {userData?.tariffInfo?.maxFileOnDisk ? convertBytes(userData?.tariffInfo?.maxFileOnDisk) : 0}
    πŸ”Œ API доступ
    From 1fec3b2584db1d50a5da128d79d485b0afe8b13f Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 13 Feb 2026 19:17:10 +0700 Subject: [PATCH 112/119] add INN registration field --- package-lock.json | 17 +- package.json | 1 + src/app/actions/action.ts | 33 ++++ src/app/actions/auth.ts | 78 +++++---- src/app/lib/validation-config-parser.ts | 29 ++++ src/app/lib/validation-config.json | 14 ++ src/app/ui/forms/register-form.tsx | 217 +++++++++++++++++++----- src/i18n/messages/en.json | 8 +- src/i18n/messages/ru.json | 8 +- 9 files changed, 331 insertions(+), 74 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66e0bfe..ad6333f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "no-copy-frontend", - "version": "0.17.0", + "version": "0.40.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "no-copy-frontend", - "version": "0.17.0", + "version": "0.40.0", "dependencies": { "@react-input/mask": "^2.0.4", "@tailwindcss/postcss": "^4.1.17", @@ -26,6 +26,7 @@ "sonner": "^2.0.7", "swiper": "^12.0.3", "tailwindcss": "^4.1.17", + "use-debounce": "^10.1.0", "validator": "^13.15.26", "zod": "^4.1.13", "zustand": "^5.0.9" @@ -7822,6 +7823,18 @@ "punycode": "^2.1.0" } }, + "node_modules/use-debounce": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.1.0.tgz", + "integrity": "sha512-lu87Za35V3n/MyMoEpD5zJv0k7hCn0p+V/fK2kWD+3k2u3kOCwO593UArbczg1fhfs2rqPEnHpULJ3KmGdDzvg==", + "license": "MIT", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/use-intl": { "version": "4.5.8", "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.5.8.tgz", diff --git a/package.json b/package.json index b5a2879..5a7f0f3 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "sonner": "^2.0.7", "swiper": "^12.0.3", "tailwindcss": "^4.1.17", + "use-debounce": "^10.1.0", "validator": "^13.15.26", "zod": "^4.1.13", "zustand": "^5.0.9" diff --git a/src/app/actions/action.ts b/src/app/actions/action.ts index f4c7f13..71458cc 100644 --- a/src/app/actions/action.ts +++ b/src/app/actions/action.ts @@ -126,3 +126,36 @@ export async function getBuildData() { return null; } } + +export async function fetchINN(inn: string) { + try { + const response = await fetch(`${API_BASE_URL}/api/v1/data`, { + 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 + } +} \ No newline at end of file diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index e68cfe6..d62c0a0 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -3,6 +3,7 @@ import { loginFormSchema, API_BASE_URL, getSignupFormSchema } from '@/app/actions/definitions'; import { createSession, deleteSession, getSessionData, updateSession } from '@/app/actions/session'; import { redirect } from 'next/navigation'; +import { fetchINN } from '@/app/actions/action'; export async function logout() { const token = await getSessionData('token'); @@ -130,15 +131,16 @@ export async function authorization( export type FormState = { previousState: { - fullName: string; - email: string; - password: string; - confirm_password: string; - phone: string; - companyName?: string; - agree: string; - referralCode?: string - }, + fullName?: string | undefined; + email?: string | undefined; + password?: string | undefined; + confirm_password?: string | undefined; + phone?: string | undefined; + companyName?: string | undefined; + agree?: string | undefined; + referralCode?: string | undefined; + inn?: string | undefined; + } | undefined, mailConfirm?: boolean, error: Record | null }; @@ -157,6 +159,7 @@ export async function registration( const companyName = formData.get('companyName') as string || ''; const agree = formData.get('agree') as string || ''; const referralCode = formData.get('referralCode'); + const inn = formData.get('inn') as string || ''; const SignupFormSchema = await getSignupFormSchema(accountType); @@ -167,25 +170,41 @@ export async function registration( password, confirm_password, agree, - ...(accountType === 'b2b' && { companyName }) + ...(accountType === 'b2b' && { companyName, inn }) }; const validatedFields = await SignupFormSchema.safeParse(dataToValidate); console.log(`try to register ${accountType}`); - if (!validatedFields.success) { + 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 = {} - 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; - } - }); + if (validatedFields?.error?.message) { + 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; + } + }); + } + + if (!confirmedCompanyName) { + errors.inn = 'inn-invalid' + } return { previousState: { @@ -195,7 +214,7 @@ export async function registration( confirm_password, phone, agree, - ...(accountType === 'b2b' && { companyName }) + ...(accountType === 'b2b' && { companyName, inn }) }, error: errors }; @@ -214,7 +233,7 @@ export async function registration( phone: phone, password, accountType, - ...(accountType === 'b2b' && { companyName: companyName }), + ...(accountType === 'b2b' && { companyName: companyName, inn: inn }), ...(referralCode !== '' && { referralLink: referralCode }) } }), @@ -230,7 +249,7 @@ export async function registration( phone: phone, password, accountType, - ...(accountType === 'b2b' && { companyName: companyName }), + ...(accountType === 'b2b' && { companyName: companyName, inn: inn }), ...(referralCode !== '' && { referralLink: referralCode }) }) @@ -241,8 +260,6 @@ export async function registration( message_code: number }; - console.log(parsed); - if (parsed.message_code === 0) { console.log(`registration - ${accountType}`); console.log({ @@ -252,10 +269,11 @@ export async function registration( password, accountType, referralLink: 'fds324', - ...(accountType === 'b2b' && { companyName: companyName }) + ...(accountType === 'b2b' && { companyName: companyName, inn: inn }) }); await createSession(parsed.message_body.token, email as string); } else { + console.log(parsed); throw parsed; } } else { @@ -320,7 +338,8 @@ export async function registration( confirm_password, phone, companyName, - agree + agree, + inn }, error: errors }; @@ -334,7 +353,8 @@ export async function registration( confirm_password, phone, companyName, - agree + agree, + inn }, error: errors }; diff --git a/src/app/lib/validation-config-parser.ts b/src/app/lib/validation-config-parser.ts index 886b72f..b964ad5 100644 --- a/src/app/lib/validation-config-parser.ts +++ b/src/app/lib/validation-config-parser.ts @@ -163,6 +163,35 @@ export function createValidationSchema(config: ValidationConfig, accountType?: ' schema.password = passwordSchema; } + // inn + if (config.inn && accountType !== 'b2c') { + let innSchema = z.string(); + + // Минимальная Π΄Π»ΠΈΠ½Π° + if (config.inn.minSize) { + innSchema = innSchema.min(config.inn.minSize.value, { + error: config.inn.minSize.message + }); + } + + // Максимальная Π΄Π»ΠΈΠ½Π° + if (config.inn.maxSize) { + innSchema = innSchema.max(config.inn.maxSize.value, { + error: config.inn.maxSize.message + }); + } + + // ДопустимыС символы + if (config.inn.allowedSymbols) { + innSchema = innSchema.regex( + new RegExp(config.inn.allowedSymbols.value), + { message: config.inn.allowedSymbols.message } + ); + } + + schema.inn = innSchema; + } + // Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ поля для Ρ„ΠΎΡ€ΠΌΡ‹ рСгистрации schema.confirm_password = z.string(); schema.agree = z.string().min(2, { error: 'register-error-agree' }); diff --git a/src/app/lib/validation-config.json b/src/app/lib/validation-config.json index 97abe6c..bd9f6f2 100644 --- a/src/app/lib/validation-config.json +++ b/src/app/lib/validation-config.json @@ -87,5 +87,19 @@ "value": "^[0-9\\+]+$", "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" + } } } \ No newline at end of file diff --git a/src/app/ui/forms/register-form.tsx b/src/app/ui/forms/register-form.tsx index 0321d6f..e5fb574 100644 --- a/src/app/ui/forms/register-form.tsx +++ b/src/app/ui/forms/register-form.tsx @@ -1,15 +1,17 @@ 'use client' import styles from '@/app/styles/module/login.module.scss'; -import { ChangeEvent, useActionState, useEffect, useRef, useState } from 'react'; +import { ChangeEvent, useActionState, useEffect, useMemo, useRef, useState } from 'react'; import { registration, FormState, confirmEmail } from '@/app/actions/auth'; import PhoneInput from '@/app/ui/inputs/phone-input'; import Link from 'next/link'; import { useTranslations } from 'next-intl'; import { IconEye } from '@/app/ui/icons/icons'; -import { SignupFormSchema } from '@/app/actions/definitions'; +import { getSignupFormSchema } from '@/app/actions/definitions'; import { useSearchParams } from 'next/navigation'; import * as z from 'zod' +import { fetchINN } from '@/app/actions/action'; +import { useDebouncedCallback } from 'use-debounce'; export default function RegisterForm() { const searchParams = useSearchParams(); @@ -39,7 +41,40 @@ export default function RegisterForm() { setShowMailConfirmWindow(true); } - }, [state]) + }, [state]); + + const debouncedFetchINN = useDebouncedCallback( + async (value) => { + const response = await fetchINN(value); + + if (response.companyName) { + setFormState({ + ...formState, + previousState: { + ...formState?.previousState, + companyName: response.companyName + }, + error: { + ...formState?.error, + companyName: '' + } + }); + } else { + setFormState({ + ...formState, + previousState: { + companyName: response.companyName + }, + error: { + ...formState?.error, + companyName: '', + inn: 'inn-invalid' + } + }); + } + }, + 500 + ); function showPassowrd(target: 'password' | 'confirm-password') { if (target === 'password') { @@ -49,13 +84,38 @@ export default function RegisterForm() { } } - function validateField( + type SignupFormSchema = Awaited>; + const [signupFormSchema, setSignupFormSchema] = useState(null);; + + useEffect(() => { + let isMounted = true; + + const loadSchema = async () => { + const result = await getSignupFormSchema(accountType); + if (isMounted) { + setSignupFormSchema(result); + } + }; + + loadSchema(); + + return () => { + isMounted = false; + }; + }, [accountType]); + + async function validateField( fieldName: string, value: string, ) { switch (fieldName) { case 'confirm_password': - const baseSchema = SignupFormSchema.shape.confirm_password; + const baseSchema = signupFormSchema?.shape.confirm_password; + + if (!baseSchema) { + return { success: true, data: value }; + } + const baseResult = baseSchema.safeParse(value); if (!baseResult.success) { @@ -85,7 +145,12 @@ export default function RegisterForm() { case 'password': case 'companyName': case 'agree': - const schema = SignupFormSchema.shape[fieldName]; + case 'inn': + const schema = signupFormSchema?.shape[fieldName]; + if (!schema) { + return { success: true, data: value }; + } + if (fieldName === 'phone') { return schema.safeParse(value.replace(/[-\(\)\s]/g, '')); } @@ -102,12 +167,45 @@ export default function RegisterForm() { } } - function onChangeHandler(e: ChangeEvent): void { + async function onChangeHandler(e: ChangeEvent): Promise { const fieldName = e.target.name; if (fieldName !== 'phone') { e.target.value = e.target.value.replace(/([^a-zA-ZΠ°-яёА-ЯЁ0-9@.!#$%&"'*+/=?^_{|}~\-\s])/g, ''); } - const validatedField = validateField(fieldName, e.target.value); + + if (fieldName === 'inn') { + const currentValue = e.target.value; + + const cleanedValue = currentValue.replace(/([^0-9])/g, ''); + + if (cleanedValue.length > 10) { + e.target.value = cleanedValue.slice(0, 10); + debouncedFetchINN(e.target.value); + } else { + e.target.value = cleanedValue; + } + + if (cleanedValue.length === 10) { + debouncedFetchINN(cleanedValue); + } else { + if (formState) { + setFormState({ + ...formState, + previousState: { + ...formState?.previousState, + companyName: '' + }, + error: { + ...formState?.error, + companyName: '', + inn: '' + } + }); + } + } + } + + const validatedField = await validateField(fieldName, e.target.value); if (validatedField.success) { setFormState(prevState => { @@ -128,16 +226,18 @@ export default function RegisterForm() { function switchRegistrationTypeHandler(e: React.MouseEvent, type: 'b2c' | 'b2b') { e.preventDefault(); setAccountType(type); - if (formState) { + if (formState?.previousState) { setFormState({ ...formState, previousState: { ...formState.previousState, - companyName: '' + companyName: '', + inn: '' }, error: { ...formState.error, - companyName: '' + companyName: '', + inn: '' } }); } @@ -251,36 +351,71 @@ export default function RegisterForm() {
    {accountType === 'b2b' ? ( -
    - - - {formState?.error?.companyName && ( -

    - { - formState?.error?.companyName.split('&').map((e, index) => { - return ( - - {t(e)} -
    -
    - ) - }) - } -

    - )} -
    + <> +
    + + + {formState?.error?.inn && ( +

    + { + formState?.error?.inn.split('&').map((e, index) => { + return ( + + {t(e)} +
    +
    + ) + }) + } +

    + )} +
    + +
    + + + {formState?.error?.companyName && ( +

    + { + formState?.error?.companyName.split('&').map((e, index) => { + return ( + + {t(e)} +
    +
    + ) + }) + } +

    + )} +
    + ) : (