add new marking-content layout

This commit is contained in:
smanylov
2026-01-14 15:01:30 +07:00
parent f790a95e71
commit ede6ad179c
11 changed files with 217 additions and 15 deletions
@@ -4,6 +4,8 @@ import PageTitle from '@/app/ui/page-title';
import UploadSectionFile from '@/app/components/upload-section-file';
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 = [
{
@@ -44,9 +46,13 @@ export default async function Page() {
return (
<div>
<PageTitle title="audio-protection" />
<PageTitleColorFrame
title="audio-protection"
description="multi-layered-protection-for-your-audios"
/>
<ProtectionStatistic fileType={FILE_TYPE} />
<div className="split-blocks">
<ProtectionSummary fileType={FILE_TYPE}/>
<ProtectionSummary fileType={FILE_TYPE} />
<div className="protection-overview">
<StackedBarChart data={data} />
</div>
+11 -5
View File
@@ -3,8 +3,10 @@ import FilesTable from '@/app/ui/dashboard/files-table';
import PageTitle from '@/app/ui/page-title';
import UploadSectionFile from '@/app/components/upload-section-file';
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
import {StackedBarChart} from '@/app/components/StackedBarChart';
import {TestComponent} from '@/app/components/test-component';
import { StackedBarChart } from '@/app/components/StackedBarChart';
import { TestComponent } from '@/app/components/test-component';
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
import ProtectionStatistic from '@/app/ui/marking-page/new/protection-statistic';
const data = [
{
@@ -40,10 +42,14 @@ export default async function Page() {
return (
<div>
<PageTitle title="image-protection" />
<TestComponent/>
<PageTitleColorFrame
title="image-protection"
description="multi-layered-protection-for-your-images"
/>
<TestComponent />
<ProtectionStatistic fileType={FILE_TYPE}/>
<div className="split-blocks">
<ProtectionSummary fileType={FILE_TYPE}/>
<ProtectionSummary fileType={FILE_TYPE} />
<div className="protection-overview">
<StackedBarChart data={data} />
</div>
@@ -4,6 +4,8 @@ import PageTitle from '@/app/ui/page-title';
import UploadSectionFile from '@/app/components/upload-section-file';
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 = [
{
@@ -44,9 +46,13 @@ export default async function Page() {
return (
<div>
<PageTitle title="video-protection" />
<PageTitleColorFrame
title="video-protection"
description="multi-layered-protection-for-your-videos"
/>
<ProtectionStatistic fileType={FILE_TYPE} />
<div className="split-blocks">
<ProtectionSummary fileType={FILE_TYPE}/>
<ProtectionSummary fileType={FILE_TYPE} />
<div className="protection-overview">
<StackedBarChart data={data} />
</div>
+7 -1
View File
@@ -18,6 +18,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { getUserFilesData, removeUserFile } from '@/app/actions/fileEntity';
import ModalWindow from '@/app/components/modalWindow';
import { toast } from 'sonner';
import { pluralize } from '@/app/lib/pluralize';
type FileType = 'image' | 'video' | 'audio';
@@ -603,6 +604,11 @@ 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 (
<div className="tanstak-table-wrapper">
<ModalWindow children={openWindowChildren} state={openWindow} callBack={setOpenWindow}></ModalWindow>
@@ -755,7 +761,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
</strong>
</span>
<span className="pagination-info-files">
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {t('files')}
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {pluralizeFiles(filteredData.length || 0)}
</span>
</div>
+9 -1
View File
@@ -1,5 +1,13 @@
'use client'
export const pluralize = (number: number, one: string, few: string, many: string) => {
const currentLang = document.documentElement.getAttribute('lang') || 'ru';
const getCurrentLang = () => {
if (typeof window !== 'undefined') {
return document?.documentElement?.getAttribute('lang') || 'ru';
}
return 'ru'; // значение по умолчанию для сервера
}
const currentLang = getCurrentLang();
if (currentLang === 'ru') {
const n = Math.abs(number) % 100;
+28
View File
@@ -32,6 +32,30 @@
}
}
.page-title-color-frame {
background: linear-gradient(135deg, v.$p-color 0%, v.$s-color 100%);
padding: 2rem;
border-radius: 16px;
margin-bottom: 2rem;
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.2);
h1 {
color: v.$white;
font-size: 2rem;
font-weight: 700;
margin: 0 0 0.5rem 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
p {
color: rgba(255, 255, 255, 0.9);
font-size: 1rem;
margin: 0;
}
}
.main-containter-wrapper {
margin-left: var(--side-bar-width);
;
@@ -400,3 +424,7 @@
}
}
}
.yaPreloadingSuggestBlockContainer {
height: 52px !important;
}
+31 -2
View File
@@ -1825,6 +1825,35 @@
}
}
.yaPreloadingSuggestBlockContainer {
height: 52px !important;
.protection-statistic {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
&-stat-card {
background: white;
padding: 1.5rem;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07);
border: 2px solid #f3f4f6;
text-align: center;
transition: all 0.3s;
}
&-stat-number {
font-size: 2rem;
font-weight: 700;
/* background: linear-gradient(135deg, #667eea, #764ba2);
background-clip: text;
-webkit-text-fill-color: transparent; */
color: v.$p-color;
margin-bottom: 0.5rem;
}
&-stat-label {
font-size: 0.9rem;
color: #6b7280;
font-weight: 500;
}
}
@@ -0,0 +1,91 @@
'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';
type FilesInfo = {
totalSize: number;
totalCount: number;
};
export default function ProtectionStatistic({ fileType }: { fileType: string }) {
const t = useTranslations('Global');
const getFileTypeFields = (fileType: string): { totalSize: string, totalCount: string } => {
switch (fileType) {
case 'image':
return {
totalSize: 'images_size',
totalCount: 'images_quantity'
};
case 'video':
return {
totalSize: 'videos_size',
totalCount: 'videos_quantity'
};
case 'audio':
return {
totalSize: 'audios_size',
totalCount: 'audios_quantity'
};
default:
return {
totalSize: 'all_files_size',
totalCount: 'all_files_quantity'
};
}
};
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
}
}
const fields = getFileTypeFields(fileType);
return {
totalSize: data[fields.totalSize as keyof typeof data],
totalCount: data[fields.totalCount as keyof typeof data]
}
}
});
return (
<div className="protection-statistic">
<div className="protection-statistic-stat-card">
<div className="protection-statistic-stat-number">0</div>
<div className="protection-statistic-stat-label">Видео защищено</div>
</div>
<div className="protection-statistic-stat-card">
<div className="protection-statistic-stat-number">0</div>
<div className="protection-statistic-stat-label">Видео проверялось</div>
</div>
<div className="protection-statistic-stat-card">
<div className="protection-statistic-stat-number">
{filesInfo?.totalSize ? convertBytes(filesInfo?.totalSize) : 0}
</div>
<div className="protection-statistic-stat-label">Занимают видео</div>
</div>
<div className="protection-statistic-stat-card">
<div className="protection-statistic-stat-number">0</div>
<div className="protection-statistic-stat-label">Найдено нарушений</div>
</div>
</div>
)
}
+16
View File
@@ -0,0 +1,16 @@
import { useTranslations } from 'next-intl';
export default function PageTitleColorFrame({ title, description }: { title: string, description: string }) {
const t = useTranslations('Global');
return (
<div className="page-title-color-frame">
<h1>
{t(title)}
</h1>
<p>
{t(description)}
</p>
</div>
)
}
+4 -1
View File
@@ -166,7 +166,10 @@
"your-image": "Your images",
"your-videos": "Your videos",
"your-audios": "Your audios",
"your-documents": "Your documents"
"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"
},
"Login-register-form": {
"and": "and",
+4 -1
View File
@@ -166,7 +166,10 @@
"your-image": "Ваши изображения",
"your-videos": "Ваш видео",
"your-audios": "Ваш аудио",
"your-documents": "Ваши документы"
"your-documents": "Ваши документы",
"multi-layered-protection-for-your-images": "Многослойная защита изображений с невидимыми водяными знаками",
"multi-layered-protection-for-your-audios": "Многослойная защита аудиоконтента с невидимыми водяными знаками",
"multi-layered-protection-for-your-videos": "Многослойная защита видеоконтента с невидимыми водяными знаками"
},
"Login-register-form": {
"and": "и",