From 7213e40b965f0aa0d0d7853f43b20c7a45bf19b6 Mon Sep 17 00:00:00 2001 From: smanylov Date: Thu, 15 Jan 2026 12:27:39 +0700 Subject: [PATCH] add my content layout --- src/app/[locale]/pages/my-content/page.tsx | 8 +- src/app/components/StackedBarChart.tsx | 7 +- src/app/components/tanstakTable.tsx | 5 +- src/app/lib/pluralize.ts | 10 +-- src/app/styles/global-styles.scss | 37 ++++++++- src/app/styles/pages-styles.scss | 81 +++++++++++++++++++ src/app/ui/dashboard/protection-overview.tsx | 9 ++- .../ui/marking-page/protection-summary.tsx | 10 ++- .../ui/my-content/my-content-info-block.tsx | 5 +- .../my-content/new/my-content-page-title.tsx | 50 ++++++++++++ .../new/my-content-stats-overview.tsx | 59 ++++++++++++++ src/i18n/messages/en.json | 3 +- src/i18n/messages/ru.json | 3 +- 13 files changed, 257 insertions(+), 30 deletions(-) create mode 100644 src/app/ui/my-content/new/my-content-page-title.tsx create mode 100644 src/app/ui/my-content/new/my-content-stats-overview.tsx diff --git a/src/app/[locale]/pages/my-content/page.tsx b/src/app/[locale]/pages/my-content/page.tsx index fbbab98..d7393ba 100644 --- a/src/app/[locale]/pages/my-content/page.tsx +++ b/src/app/[locale]/pages/my-content/page.tsx @@ -1,14 +1,18 @@ import MyContentInfoBlock from '@/app/ui/my-content/my-content-info-block'; import FilesTable from '@/app/ui/dashboard/files-table'; import MyContentPieChart from '@/app/ui/my-content/my-content-pie-chart'; +import MyContentPageTitleColorFrame from '@/app/ui/my-content/new/my-content-page-title'; +import MyContentStatsOverview from '@/app/ui/my-content/new/my-content-stats-overview'; export default function Page() { return ( <> -
+ + +{/*
-
+
*/} ) diff --git a/src/app/components/StackedBarChart.tsx b/src/app/components/StackedBarChart.tsx index 208f6a0..d72a634 100644 --- a/src/app/components/StackedBarChart.tsx +++ b/src/app/components/StackedBarChart.tsx @@ -1,6 +1,6 @@ 'use client' -import { useTranslations } from 'next-intl'; +import { useTranslations, useLocale } from 'next-intl'; import { useMemo } from 'react'; import { BarChart, Bar, Cell, XAxis, Tooltip } from 'recharts'; import { pluralize } from '@/app/lib/pluralize'; @@ -24,6 +24,7 @@ interface BarShapeProps { } export const StackedBarChart = ({ data }: Files) => { + const locale = useLocale(); const t = useTranslations('Global'); const CHECKS_COLOR = '#6366f1'; @@ -154,12 +155,12 @@ export const StackedBarChart = ({ data }: Files) => { const pluralizeCheks = (number: number) => { const translate = [t('check'), t('checks-few'), t('checks')]; - return pluralize(number, translate[0], translate[1], translate[2]); + return pluralize(number, translate[0], translate[1], translate[2], locale); }; const pluralizeViolations = (number: number) => { const translate = [t('violation'), t('violations-few'), t('violations')]; - return pluralize(number, translate[0], translate[1], translate[2]); + return pluralize(number, translate[0], translate[1], translate[2], locale); }; return ( diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index 9abc0c7..6cc116f 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -12,7 +12,7 @@ import { ColumnFiltersState, } from '@tanstack/react-table'; import { IconImageFile, IconVideoFile, IconAudioFile, IconEye, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconShieldExclamation, IconDelete } from '@/app/ui/icons/icons'; -import { useTranslations } from 'next-intl'; +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'; @@ -156,6 +156,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { const [isFileLoading, setIsFileLoading] = useState(false); const t = useTranslations("Global"); + const locale = useLocale(); // Определение колонок const columns = useMemo[]>( @@ -606,7 +607,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) { const pluralizeFiles = (number: number) => { const translate = [t('file'), t('files-few'), t('files')]; - return pluralize(number, translate[0], translate[1], translate[2]); + return pluralize(number, translate[0], translate[1], translate[2], locale); }; return ( diff --git a/src/app/lib/pluralize.ts b/src/app/lib/pluralize.ts index 4efe7fd..12a5131 100644 --- a/src/app/lib/pluralize.ts +++ b/src/app/lib/pluralize.ts @@ -1,14 +1,6 @@ 'use client' -export const pluralize = (number: number, one: string, few: string, many: string) => { - const getCurrentLang = () => { - if (typeof window !== 'undefined') { - return document?.documentElement?.getAttribute('lang') || 'ru'; - } - return 'ru'; // значение по умолчанию для сервера - } - const currentLang = getCurrentLang(); - +export const pluralize = (number: number, one: string, few: string, many: string, currentLang: string) => { if (currentLang === 'ru') { const n = Math.abs(number) % 100; const n1 = n % 10; diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index 8e8c23a..c25c771 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -38,9 +38,9 @@ border-radius: 16px; margin-bottom: 2rem; box-shadow: 0 10px 40px rgba(102, 126, 234, 0.2); + color: v.$white; h1 { - color: v.$white; font-size: 2rem; font-weight: 700; margin: 0 0 0.5rem 0; @@ -50,10 +50,43 @@ } p { - color: rgba(255, 255, 255, 0.9); font-size: 1rem; margin: 0; } + + .header-content { + display: flex; + justify-content: space-between; + align-items: center; + gap: 40px; + + .header-main { + flex: 1; + } + + .header-stats { + display: flex; + gap: 28px; + align-items: center; + } + + .header-stat-item { + text-align: center; + } + + .header-stat-value { + font-size: 28px; + font-weight: 700; + margin-bottom: 4px; + } + + .header-stat-label { + font-size: 13px; + opacity: 0.85; + text-transform: uppercase; + letter-spacing: 0.5px; + } + } } .main-containter-wrapper { diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index aefb378..b917c06 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -1856,4 +1856,85 @@ color: #6b7280; font-weight: 500; } +} + +.my-content-stats-overview { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 20px; + margin-bottom: 30px; + + @media (max-width: 1400px) { + grid-template-columns: repeat(2, 1fr); + } + + .stat-card.files { + --card-color-1: #ff6b8a; + --card-color-2: #ff8da1; + } + + .stat-card.protected { + --card-color-1: #8b5cf6; + --card-color-2: #a78bfa; + } + + .stat-card.checks { + --card-color-1: #10b981; + --card-color-2: #34d399; + } + + .stat-card.storage { + --card-color-1: #f59e0b; + --card-color-2: #fbbf24; + } + + .stat-card { + background: linear-gradient(135deg, var(--card-color-1), var(--card-color-2)); + border-radius: 20px; + padding: 32px 28px; + color: white; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); + transition: all 0.4s ease; + cursor: pointer; + position: relative; + overflow: hidden; + + &:hover { + transform: translateY(-8px); + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); + } + } + + .stat-icon { + font-size: 36px; + } + + .stat-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + position: relative; + z-index: 1; + } + + .stat-value { + font-size: 32px; + font-weight: 700; + color: white; + margin-bottom: 6px; + position: relative; + z-index: 1; + } + + .stat-label { + font-size: 13px; + color: white; + opacity: 0.9; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; + position: relative; + z-index: 1; + } } \ No newline at end of file diff --git a/src/app/ui/dashboard/protection-overview.tsx b/src/app/ui/dashboard/protection-overview.tsx index da7b402..6441268 100644 --- a/src/app/ui/dashboard/protection-overview.tsx +++ b/src/app/ui/dashboard/protection-overview.tsx @@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from 'react'; import { PieChartComponent } from '@/app/components/PieChartComponent'; -import { useTranslations } from 'next-intl'; +import {useTranslations, useLocale} from 'next-intl'; import Link from 'next/link'; import { useClickOutside } from '@/app/hooks/useClickOutside'; import { useQuery } from '@tanstack/react-query'; @@ -30,6 +30,7 @@ export default function ProtectionOverview() { { name: 'videos', value: 0, color: '#2f9e44' }, { name: 'audios', value: 0, color: '#1971c2' }, ]); + const locale = useLocale(); const t = useTranslations('Global'); const [openDropDownList, setOpenDropDownList] = useState(false); const dropDownList = useRef(null); @@ -106,17 +107,17 @@ export default function ProtectionOverview() { const pluralizeFiles = (number: number) => { const translate = [t('file'), t('files-few'), t('files')]; - return pluralize(number, translate[0], translate[1], translate[2]); + return pluralize(number, translate[0], translate[1], translate[2], locale); }; const pluralizeCheks = (number: number) => { const translate = [t('check'), t('checks-few'), t('checks')]; - return pluralize(number, translate[0], translate[1], translate[2]); + return pluralize(number, translate[0], translate[1], translate[2], locale); }; const pluralizeViolations = (number: number) => { const translate = [t('violation'), t('violations-few'), t('violations')]; - return pluralize(number, translate[0], translate[1], translate[2]); + return pluralize(number, translate[0], translate[1], translate[2], locale); }; const CHECKS = 6; diff --git a/src/app/ui/marking-page/protection-summary.tsx b/src/app/ui/marking-page/protection-summary.tsx index de90622..8e2d41b 100644 --- a/src/app/ui/marking-page/protection-summary.tsx +++ b/src/app/ui/marking-page/protection-summary.tsx @@ -1,6 +1,6 @@ 'use client' -import { useTranslations } from 'next-intl'; +import {useTranslations, useLocale} from 'next-intl'; import { useQuery } from '@tanstack/react-query'; import { getUserFilesInfo } from '@/app/actions/action'; import { convertBytes } from '@/app/lib/convertBytes'; @@ -72,19 +72,21 @@ export default function ProtectionSummary({ fileType }: { fileType: string }) { } }); + const locale = useLocale(); + const pluralizeFilesName = (number: number) => { const translate = [t(`${fileType}`), t(`${fileType}s-few`), t(`${fileType}s`)]; - return pluralize(number, translate[0], translate[1], translate[2]); + return pluralize(number, translate[0], translate[1], translate[2], locale); }; const pluralizeCheks = (number: number) => { const translate = [t('check'), t('checks-few'), t('checks')]; - return pluralize(number, translate[0], translate[1], translate[2]); + return pluralize(number, translate[0], translate[1], translate[2], locale); }; const pluralizeViolations = (number: number) => { const translate = [t('violation'), t('violations-few'), t('violations')]; - return pluralize(number, translate[0], translate[1], translate[2]); + return pluralize(number, translate[0], translate[1], translate[2], locale); }; return ( diff --git a/src/app/ui/my-content/my-content-info-block.tsx b/src/app/ui/my-content/my-content-info-block.tsx index 28f2a24..8820ae1 100644 --- a/src/app/ui/my-content/my-content-info-block.tsx +++ b/src/app/ui/my-content/my-content-info-block.tsx @@ -3,11 +3,12 @@ import { IconDocument, IconImageFile, IconVideoFile, IconAudioFile } from '@/app/ui/icons/icons'; import { useQuery } from '@tanstack/react-query'; import { getUserFilesInfo } from '@/app/actions/action'; -import { useTranslations } from 'next-intl'; +import {useTranslations, useLocale} from 'next-intl'; import { convertBytes } from '@/app/lib/convertBytes'; import { pluralize } from '@/app/lib/pluralize'; export default function MyContentInfoBlock() { + const locale = useLocale(); const t = useTranslations('Global'); const { @@ -70,7 +71,7 @@ export default function MyContentInfoBlock() { const pluralizeFiles = (number: number) => { const translate = [t('file'), t('files-few'), t('files')]; - return pluralize(number, translate[0], translate[1], translate[2]); + return pluralize(number, translate[0], translate[1], translate[2], locale); }; return ( 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 new file mode 100644 index 0000000..fba6b62 --- /dev/null +++ b/src/app/ui/my-content/new/my-content-page-title.tsx @@ -0,0 +1,50 @@ +'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'; + +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, + }); + + return ( +
+
+
+

+ {t(title)} +

+

+ {t(description)} +

+
+
+
+
{filesInfo?.all_files_quantity}
+
Файлов
+
+
+
+
0
+
Защищено
+
+
+
+
{filesInfo.all_files_size ? convertBytes(filesInfo.all_files_size) : 0}
+
Хранилище
+
+
+
+
+ ) +} \ 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 new file mode 100644 index 0000000..97a0c70 --- /dev/null +++ b/src/app/ui/my-content/new/my-content-stats-overview.tsx @@ -0,0 +1,59 @@ +'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'; + +export default function MyContentStatsOverview() { + const t = useTranslations('Global'); + const { + data: filesInfo, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['userFilesInfo'], + queryFn: getUserFilesInfo, + }); + + return ( +
+
+
+
📄
+
+0%
+
+
{filesInfo?.all_files_quantity}
+
Всего файлов
+
+ +
+
+
🛡
+
+0%
+
+
0
+
Защищено файлов
+
+ +
+
+
🔍
+
+0%
+
+
0
+
Проверок защиты
+
+ +
+
+
💾
+
+0%
+
+
{filesInfo.all_files_size ? convertBytes(filesInfo.all_files_size) : 0}
+
Хранилище
+
+
+ ) +} \ No newline at end of file diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 6125f2f..8565b16 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -169,7 +169,8 @@ "your-documents": "Your documents", "multi-layered-protection-for-your-images": "Multi-layered image content protection with invisible watermarks", "multi-layered-protection-for-your-audios": "Multi-layered audio content protection with invisible watermarks", - "multi-layered-protection-for-your-videos": "Multi-layered video content protection with invisible watermarks" + "multi-layered-protection-for-your-videos": "Multi-layered video content protection with invisible watermarks", + "my-content-description": "Managing protected files and copyright control" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 4f071a5..a4e0c94 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -169,7 +169,8 @@ "your-documents": "Ваши документы", "multi-layered-protection-for-your-images": "Многослойная защита изображений с невидимыми водяными знаками", "multi-layered-protection-for-your-audios": "Многослойная защита аудиоконтента с невидимыми водяными знаками", - "multi-layered-protection-for-your-videos": "Многослойная защита видеоконтента с невидимыми водяными знаками" + "multi-layered-protection-for-your-videos": "Многослойная защита видеоконтента с невидимыми водяными знаками", + "my-content-description": "Управление защищенными файлами и контролем авторских прав" }, "Login-register-form": { "and": "и",