diff --git a/package-lock.json b/package-lock.json index fac1abe..60c4472 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "no-copy-frontend", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "no-copy-frontend", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { "@tailwindcss/postcss": "^4.1.17", "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-query": "^5.90.11", "@tanstack/react-table": "^8.21.3", + "@vkid/sdk": "^2.6.2", "clsx": "^2.1.1", "jose": "^6.1.2", "next": "^16.0.7", @@ -2953,6 +2954,15 @@ "win32" ] }, + "node_modules/@vkid/sdk": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@vkid/sdk/-/sdk-2.6.2.tgz", + "integrity": "sha512-TCaGh6BvQNY8Atru0KJXdGuwm70y9WS2xBOLNripHjYMEcft0BMIiLKUvJ7pAeJOtk/15MvPaUIEmBS2Vt0NBg==", + "license": "MIT", + "dependencies": { + "crypto-js": "^4.1.1" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3491,6 +3501,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", diff --git a/package.json b/package.json index 3a1787f..9f7fce6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.1.1", + "version": "0.4.0", "private": true, "scripts": { "dev": "next dev -p 2999", @@ -13,6 +13,7 @@ "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-query": "^5.90.11", "@tanstack/react-table": "^8.21.3", + "@vkid/sdk": "^2.6.2", "clsx": "^2.1.1", "jose": "^6.1.2", "next": "^16.0.7", diff --git a/src/app/[locale]/login/page.tsx b/src/app/[locale]/login/page.tsx index 22194bf..f6999f6 100644 --- a/src/app/[locale]/login/page.tsx +++ b/src/app/[locale]/login/page.tsx @@ -2,9 +2,10 @@ import { Metadata } from 'next'; import styles from '@/app/styles/login.module.scss' import LogoIcon from '@/app/ui/logo-icon'; import Link from 'next/link'; -import LoginForm from '@/app/ui/login-form'; +import LoginForm from '@/app/ui/forms/login-form'; import LanguageSwitcher from '@/app/components/LanguageSwitcher'; import { useTranslations } from 'next-intl'; +import VKLogin from '@/app/components/VKLogin'; export const metadata: Metadata = { title: 'Login', @@ -32,6 +33,9 @@ export default function Page() { {t('or-log-in-via')} +
+ +

{t('no-account')}? {t('register')} diff --git a/src/app/[locale]/pages/marking-audio/page.tsx b/src/app/[locale]/pages/marking-audio/page.tsx index 8fcfa94..64a16b7 100644 --- a/src/app/[locale]/pages/marking-audio/page.tsx +++ b/src/app/[locale]/pages/marking-audio/page.tsx @@ -1,7 +1,6 @@ -import ProtectedFilesTable from '@/app/ui/marking-page/protected-files-table'; import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import TestSection from '@/app/ui/marking-page/test-section'; -import { IconAudioFile } from '@/app/ui/icons/icons'; +import FilesTable from '@/app/ui/dashboard/files-table'; export default function Page() { return ( @@ -10,8 +9,8 @@ export default function Page() {

Защита аудио

- - + {/* */} + ) } \ No newline at end of file diff --git a/src/app/[locale]/pages/marking-photo/page.tsx b/src/app/[locale]/pages/marking-photo/page.tsx index 9d2b8cf..1c1b711 100644 --- a/src/app/[locale]/pages/marking-photo/page.tsx +++ b/src/app/[locale]/pages/marking-photo/page.tsx @@ -2,18 +2,23 @@ import ProtectedFilesTable from '@/app/ui/marking-page/protected-files-table'; import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import TestSection from '@/app/ui/marking-page/test-section'; import UploadSectionImage from '@/app/ui/marking-page/upload-section-image'; -import { IconImageFile } from '@/app/ui/icons/icons'; +import { useTranslations } from 'next-intl'; +import FilesTable from '@/app/ui/dashboard/files-table'; export default function Page() { + const t = useTranslations('Global'); return (
-

Защита изображений

+

+ {t('image-protection')} +

- - + {/* */} + + {/* */}
) } \ No newline at end of file diff --git a/src/app/[locale]/pages/marking-video/page.tsx b/src/app/[locale]/pages/marking-video/page.tsx index 53fcd68..3f5c730 100644 --- a/src/app/[locale]/pages/marking-video/page.tsx +++ b/src/app/[locale]/pages/marking-video/page.tsx @@ -1,7 +1,7 @@ -import ProtectedFilesTable from '@/app/ui/marking-page/protected-files-table'; import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import TestSection from '@/app/ui/marking-page/test-section'; import UploadSectionVideo from '@/app/ui/marking-page/upload-section-video'; +import FilesTable from '@/app/ui/dashboard/files-table'; export default function Page() { @@ -12,8 +12,8 @@ export default function Page() { - - + {/* */} + ) } \ No newline at end of file diff --git a/src/app/[locale]/pages/settings/page.tsx b/src/app/[locale]/pages/settings/page.tsx index 2143810..fa7f71b 100644 --- a/src/app/[locale]/pages/settings/page.tsx +++ b/src/app/[locale]/pages/settings/page.tsx @@ -5,20 +5,25 @@ import SubscriptionSettings from '@/app/ui/settings/subscription-settings'; import APISettings from '@/app/ui/settings/api-settings'; import LastActivitySettings from '@/app/ui/settings/last-activity-settings'; import DangerZone from '@/app/ui/settings/danger-zone'; +import { useTranslations } from 'next-intl'; export default function Page() { + const t = useTranslations('Global'); + return ( <> -

⚙️ Настройки аккаунта

+

+ {t('account-settings')} +

- - - - - + + + + +
- + ) } \ No newline at end of file diff --git a/src/app/[locale]/register/page.tsx b/src/app/[locale]/register/page.tsx index 763202a..fa360a5 100644 --- a/src/app/[locale]/register/page.tsx +++ b/src/app/[locale]/register/page.tsx @@ -1,7 +1,7 @@ import LogoIcon from '@/app/ui/logo-icon'; import styles from '@/app/styles/login.module.scss' import Link from 'next/link'; -import RegisterForm from '@/app/ui/register-form'; +import RegisterForm from '@/app/ui/forms/register-form'; import LanguageSwitcher from '@/app/components/LanguageSwitcher'; import {useTranslations} from 'next-intl'; diff --git a/src/app/actions/definitions.ts b/src/app/actions/definitions.ts index d5f03a2..4b68c1c 100644 --- a/src/app/actions/definitions.ts +++ b/src/app/actions/definitions.ts @@ -16,20 +16,28 @@ export type FormAction = ( formData: FormData ) => Promise +const emailRegex = /^([a-zA-Z0-9.\-_]+|[а-яА-ЯёЁ0-9.\-_]+)@(([a-zA-Z0-9.\-]+)\.([a-zA-Z]{2,})|([a-zA-Z0-9.\-]+)\.([а-яА-ЯёЁ]{2,})|([а-яА-ЯёЁ0-9.\-]+)\.([a-zA-Z]{2,})|([а-яА-ЯёЁ0-9.\-]+)\.([а-яА-ЯёЁ]{2,}))$/i; + export const SignupFormSchema = z .object({ full_name: z .string() .min(3, { error: 'register-error-name' }) .trim(), - email: z.email({ error: 'register-error-valid-email' }).trim(), + email: z + .string() + .trim() + .refine( + (value) => emailRegex.test(value), + { message: 'register-error-valid-email' } + ), phone: z .string() .min(12, { error: 'register-error-phone' }), password: z .string() .min(8, { error: 'register-error-password-symbols' }) - .regex(/[a-zA-Zа-яёА-ЯЁ]/, { error: 'register-error-password-one-letter'}) + .regex(/[a-zA-Zа-яёА-ЯЁ]/, { error: 'register-error-password-one-letter' }) .regex(/[0-9]/, { error: 'register-error-password-one-digit' }) .trim(), confirm_password: z diff --git a/src/app/components/PieChartComponent.tsx b/src/app/components/PieChartComponent.tsx index e6431a4..801a5e3 100644 --- a/src/app/components/PieChartComponent.tsx +++ b/src/app/components/PieChartComponent.tsx @@ -16,13 +16,13 @@ export const PieChartComponent = ({ data }: { > - + 0 {t('out-of')} - + 0 { + const t = useTranslations('Global'); + + const strokeColor = "#dfdede24"; + const strokeWidth = 1; + + const renderBarWithPartialBorder = (props: BarShapeProps) => { + const { x, y, width, height, fill } = props; + const borderRadius = 10; + + return ( + <> + {/* Основной прямоугольник с закругленными углами */} + + + {/* Левая граница (центральная часть, без закруглений) */} + + + {/* Правая граница (центральная часть, без закруглений) */} + + + {/* Нижняя граница (прямая часть между закруглениями) */} + + + {/* Верхняя граница (прямая часть между закруглениями) */} + + + ); + }; + + const renderBarWithTopBorder = (props: BarShapeProps) => { + const { x, y, width, height, fill } = props; + const borderRadius = 10; + + if (height <= 0 || width <= 0) return null; + + return ( + <> + {/* Основной прямоугольник с закругленными углами */} + + + {/* Левая граница (центральная часть, без закруглений) */} + + + {/* Правая граница (центральная часть, без закруглений) */} + + + {/* Нижняя граница (прямая часть между закруглениями) */} + + + {/* Верхняя граница (прямая часть между закруглениями) */} + + + ); + }; + + return ( + + {/* */} + + + {data.map((_entry, index) => ( + + ))} + + + {data.map((_entry, index) => ( + + ))} + + + ); +}; \ No newline at end of file diff --git a/src/app/components/VKLogin.tsx b/src/app/components/VKLogin.tsx new file mode 100644 index 0000000..9bd07ca --- /dev/null +++ b/src/app/components/VKLogin.tsx @@ -0,0 +1,48 @@ +'use client' + +import { useEffect, useRef } from 'react'; +import * as VKID from '@vkid/sdk'; + +export default function VKLogin() { + + const vkButton = useRef(null) + + function clickHandler() { + alert('Кнопка пока не подключена к бизнес аккаунту!'); +/* VKID.Auth.login() + .then(vkidOnSuccess) + .catch(console.error); */ + } + +/* function vkidOnSuccess(data: any) { + console.log('vkidOnSuccess'); + console.log(data); + } */ + +/* useEffect(() => { + VKID.Config.init({ + app: 54389559, + redirectUrl: 'http://localhost', + responseMode: VKID.ConfigResponseMode.Callback, + source: VKID.ConfigSource.LOWCODE, + scope: 'email' + }); + }, []); */ + + return ( + + ) +} \ No newline at end of file diff --git a/src/app/components/dropDownList.tsx b/src/app/components/dropDownList.tsx new file mode 100644 index 0000000..098c212 --- /dev/null +++ b/src/app/components/dropDownList.tsx @@ -0,0 +1,79 @@ +import React, { ReactNode, useState, useRef } from 'react'; +import { useClickOutside } from '@/app/hooks/useClickOutside'; + +interface DropDownListProps { + children: ReactNode; + value: string | number; + callBack: (value: string) => void; +} + +interface ChildProps { + value?: string; + onClick?: () => void; + className?: string; + children?: React.ReactNode; +} + +export default function DropDownList({ children, value, callBack }: DropDownListProps) { + const [isOpen, setIsOpen] = useState(false) + const dropdownRef = useRef(null) + useClickOutside(dropdownRef, () => { + setIsOpen(false); + }); + + const enhancedChildren = React.Children.map(children, (child, index) => { + if (React.isValidElement(child)) { + const childValue = child.props.value || undefined; + + return React.cloneElement(child, { + onClick: () => { + callBack(childValue || ""); + setIsOpen(false); + }, + className: `flex items-center w-full px-4 py-2 text-sm text-left hover:bg-blue-100 cursor-pointer select-none`, + key: index + }); + } + return child; + }); + + return ( +
+ + {isOpen && ( +
+
    + {enhancedChildren} +
+
+ )} +
+ ) +} \ No newline at end of file diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx index 70f265a..bff4a59 100644 --- a/src/app/components/tanstakTable.tsx +++ b/src/app/components/tanstakTable.tsx @@ -10,292 +10,542 @@ import { ColumnDef, SortingState, ColumnFiltersState, - FilterFn, } from '@tanstack/react-table'; -import { rankItem } from '@tanstack/match-sorter-utils'; +import { IconImageFile, IconVideoFile, IconAudioFile, IconEye, IconDownload, IconShield, DoubleArrowRight, ArrowRight, DoubleArrowLeft, ArrowLeft, ArrowUp, ArrowDown, Filter } from '@/app/ui/icons/icons'; +import { useTranslations } from 'next-intl'; +import DropDownList from '@/app/components/dropDownList'; // Типы данных -type Person = { +type FileType = 'image' | 'video' | 'audio'; +type FileItem = { id: number; - name: string; - age: number; - email: string; - department: string; - salary: number; + fileName: string; + fileType: FileType; + violations: number; + checks: number; + lastCheck: number; // timestamp в миллисекундах }; -// Кастомная функция фильтрации -const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { - const itemRank = rankItem(row.getValue(columnId), value); - addMeta({ itemRank }); - return itemRank.passed; -}; - -// Определяем тип для фильтров -declare module '@tanstack/react-table' { - interface FilterFns { - fuzzy: FilterFn; +// Иконки для типов файлов +const FileTypeIcon = ({ type }: { type: FileType }) => { + switch (type) { + case 'image': + return + + ; + case 'video': + return + + ; + case 'audio': + return + + ; + default: + return 📄; } -} +}; -export default function DataTable() { - // Данные - const [data] = useState([ - { id: 1, name: 'Иван Петров', age: 28, email: 'ivan@example.com', department: 'IT', salary: 80000 }, - { id: 2, name: 'Анна Сидорова', age: 32, email: 'anna@example.com', department: 'HR', salary: 65000 }, - { id: 3, name: 'Сергей Иванов', age: 45, email: 'sergey@example.com', department: 'Финансы', salary: 95000 }, - { id: 4, name: 'Мария Кузнецова', age: 26, email: 'maria@example.com', department: 'Маркетинг', salary: 60000 }, - { id: 5, name: 'Алексей Смирнов', age: 38, email: 'alex@example.com', department: 'IT', salary: 85000 }, - { id: 6, name: 'Ольга Васнецова', age: 29, email: 'olga@example.com', department: 'HR', salary: 62000 }, - { id: 7, name: 'Дмитрий Орлов', age: 41, email: 'dmitry@example.com', department: 'Финансы', salary: 92000 }, - { id: 8, name: 'Екатерина Новикова', age: 35, email: 'ekaterina@example.com', department: 'Маркетинг', salary: 70000 }, +// Форматирование даты из timestamp +const formatDate = (timestamp: number) => { + const date = new Date(timestamp); + const day = date.getDate().toString().padStart(2, '0'); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const year = date.getFullYear(); + return `${day}.${month}.${year}`; +}; + +// Компонент таблицы +export default function TanstakFilesTable() { + // Данные с timestamp вместо Date объектов + const [data] = useState([ + { id: 1, fileName: 'image1.jpg', fileType: 'image', violations: 5, checks: 12, lastCheck: 1705267200000 }, // 15.01.2024 + { id: 2, fileName: 'presentation.mp4', fileType: 'video', violations: 2, checks: 8, lastCheck: 1705180800000 }, // 14.01.2024 + { id: 3, fileName: 'song.mp3', fileType: 'audio', violations: 0, checks: 5, lastCheck: 1705094400000 }, // 13.01.2024 + { id: 4, fileName: 'photo1.jpg', fileType: 'image', violations: 3, checks: 10, lastCheck: 1705008000000 }, // 12.01.2024 + { id: 5, fileName: 'movie.mp4', fileType: 'video', violations: 1, checks: 7, lastCheck: 1704921600000 }, // 11.01.2024 + { id: 6, fileName: 'podcast.mp3', fileType: 'audio', violations: 4, checks: 9, lastCheck: 1704835200000 }, // 10.01.2024 + { id: 7, fileName: 'screenshot.png', fileType: 'image', violations: 0, checks: 3, lastCheck: 1704748800000 }, // 09.01.2024 + { id: 8, fileName: 'tutorial.mp4', fileType: 'video', violations: 2, checks: 6, lastCheck: 1704662400000 }, // 08.01.2024 + { id: 9, fileName: 'music.mp3', fileType: 'audio', violations: 1, checks: 4, lastCheck: 1704576000000 }, // 07.01.2024 + { id: 10, fileName: 'image2.jpg', fileType: 'image', violations: 5, checks: 11, lastCheck: 1704489600000 }, // 06.01.2024 + { id: 11, fileName: 'film.mov', fileType: 'video', violations: 0, checks: 2, lastCheck: 1704403200000 }, // 05.01.2024 + { id: 12, fileName: 'sound.wav', fileType: 'audio', violations: 3, checks: 8, lastCheck: 1704316800000 }, // 04.01.2024 + { id: 13, fileName: 'picture.png', fileType: 'image', violations: 2, checks: 7, lastCheck: 1704230400000 }, // 03.01.2024 + { id: 14, fileName: 'video2.mp4', fileType: 'video', violations: 1, checks: 5, lastCheck: 1704144000000 }, // 02.01.2024 + { id: 15, fileName: 'audio2.mp3', fileType: 'audio', violations: 0, checks: 3, lastCheck: 1704057600000 }, // 01.01.2024 ]); // Состояния const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); - const [globalFilter, setGlobalFilter] = useState(''); - const [rowSelection, setRowSelection] = useState({}); + const [dateFilter, setDateFilter] = useState('all'); + const [typeFilter, setTypeFilter] = useState('all'); + + const t = useTranslations("Global"); // Определение колонок - const columns = useMemo[]>( + const columns = useMemo[]>( () => [ { - id: 'select', - header: ({ table }) => ( - + accessorKey: 'fileName', + header: ({ column }) => ( +
+ + {t('file')} + + +
), cell: ({ row }) => ( - +
+ +
+
{row.original.fileName}
+
+
), - enableSorting: false, enableColumnFilter: false, }, { - accessorKey: 'id', - header: 'ID', - cell: (info) => info.getValue(), + accessorKey: 'violations', + header: ({ column }) => ( +
+ + {t('violations')} + + +
+ ), + cell: ({ row }) => ( +
0 ? 'text-red-600' : 'text-green-600'}`}> + {row.original.violations} +
+ ), }, { - accessorKey: 'name', - header: 'Имя', - cell: (info) => info.getValue(), - filterFn: fuzzyFilter, + accessorKey: 'checks', + header: ({ column }) => ( +
+ + {t('checks')} + + +
+ ), + cell: ({ row }) => ( +
+ {row.original.checks} +
+ ), }, { - accessorKey: 'age', - header: 'Возраст', - cell: (info) => info.getValue(), + accessorKey: 'lastCheck', + header: ({ column }) => ( +
+ + {t('last-check')} + + +
+ ), + cell: ({ row }) => ( +
+ {formatDate(row.original.lastCheck)} +
+ ), + enableColumnFilter: false, }, { - accessorKey: 'email', - header: 'Email', - cell: (info) => info.getValue(), - }, - { - accessorKey: 'department', - header: 'Отдел', - cell: (info) => info.getValue(), - }, - { - accessorKey: 'salary', - header: 'Зарплата', - cell: (info) => `$${Number(info.getValue()).toLocaleString()}`, + id: 'actions', + header: () => { + return ( +
+ {t('actions')} +
+ ) + }, + cell: ({ row }) => ( +
+ + + +
+ ), + enableSorting: false, + enableColumnFilter: false, }, ], [] ); + // Обработчики действий + const handleView = (file: FileItem) => { + console.log(`Просмотр файла: ${file.fileName}`); + }; + + const handleDownload = (file: FileItem) => { + console.log(`Скачать: ${file.fileName}`); + }; + + const handleProtect = (file: FileItem) => { + console.log(`Щиток: ${file.fileName}`); + }; + + // Фильтрация по типу файла и дате + const filteredData = useMemo(() => { + let result = data; + + // Фильтр по типу файла + if (typeFilter !== 'all') { + result = result.filter(item => item.fileType === typeFilter); + } + + // Фильтр по дате + if (dateFilter !== 'all') { + const now = Date.now(); + const oneDay = 24 * 60 * 60 * 1000; + const sevenDays = 7 * oneDay; + const thirtyDays = 30 * oneDay; + + switch (dateFilter) { + case 'today': + const todayStart = new Date().setHours(0, 0, 0, 0); + result = result.filter(item => item.lastCheck >= todayStart); + break; + case 'week': + const weekAgo = now - sevenDays; + result = result.filter(item => item.lastCheck >= weekAgo); + break; + case 'month': + const monthAgo = now - thirtyDays; + result = result.filter(item => item.lastCheck >= monthAgo); + break; + case 'older': + const monthAgo2 = now - thirtyDays; + result = result.filter(item => item.lastCheck < monthAgo2); + break; + } + } + + return result; + }, [data, typeFilter, dateFilter]); + // Создание таблицы const table = useReactTable({ - data, + data: filteredData, columns, - filterFns: { - fuzzy: fuzzyFilter, - }, state: { sorting, columnFilters, - globalFilter, - rowSelection, }, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, - onGlobalFilterChange: setGlobalFilter, - onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), getFilteredRowModel: getFilteredRowModel(), - globalFilterFn: fuzzyFilter, // Используем саму функцию, а не строку + initialState: { + pagination: { + pageSize: 10, + }, + }, }); 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')} +
  • +
    +
    - {/* Глобальный поиск */} -
    - setGlobalFilter(e.target.value)} - className="px-4 py-2 border rounded-md w-full max-w-md" - /> -
    +
    +
    {t('type-filter')}:
    + { + switch (typeFilter) { + case 'image': + return t('images') + case 'video': + return t('videos') + case 'audio': + return t('audios') + default: + return t('all-types') + } + })()} + callBack={setTypeFilter} + > +
  • + {t('all-types')} +
  • +
  • + {t('images')} +
  • +
  • + {t('videos')} +
  • +
  • + {t('audios')} +
  • +
    +
    +
    - {/* Фильтры по колонкам */} -
    - {table.getAllColumns() - .filter(column => column.getCanFilter() && column.id !== 'select') - .map(column => ( -
    - - {column.id === 'department' ? ( - - ) : column.id === 'age' ? ( - column.setFilterValue(e.target.value ? parseInt(e.target.value) : undefined)} - className="px-3 py-1 border rounded w-32" - /> - ) : ( - column.setFilterValue(e.target.value)} - className="px-3 py-1 border rounded" - /> - )} -
    - ))} +
    +
    {t('items-per-page')}:
    + + {[5, 10, 20, 50, 100].map(pageSize => ( +
  • + {t('show')} {pageSize} +
  • + ))} +
    +
    {/* Таблица */} -
    - - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - + )} + +
    -
    +
    +
    + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + - ))} - - ))} - - - {table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - + ))} + + + {table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + ))} + + )) + ) : ( + + - ))} - - ))} - -
    {header.isPlaceholder ? null - : typeof header.column.columnDef.header === 'string' - ? header.column.columnDef.header - : header.column.id} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} - -
    - {typeof cell.column.columnDef.cell === 'function' - ? cell.column.columnDef.cell(cell.getContext()) - : cell.getValue() as string} + : 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')}
    +
    +
    {/* Пагинация */} -
    +
    +
    + + {t('page')}{' '} + + {table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount()} + + + + | {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {t('files')} + +
    +
    + +
    + {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.getState().pagination.pageIndex + 1} из {table.getPageCount()} - - - - - Всего строк: {data.length} | Отфильтровано: {table.getFilteredRowModel().rows.length} - -
    ); diff --git a/src/app/styles/VKLogin.scss b/src/app/styles/VKLogin.scss new file mode 100644 index 0000000..18dd26e --- /dev/null +++ b/src/app/styles/VKLogin.scss @@ -0,0 +1,52 @@ +.VkIdWebSdk__button_reset { + border: none; + margin: 0; + padding: 0; + width: auto; + overflow: visible; + background: transparent; + color: inherit; + font: inherit; + line-height: normal; + -webkit-font-smoothing: inherit; + -moz-osx-font-smoothing: inherit; +} + +.VkIdWebSdk__button { + background: #0077ff; + cursor: pointer; + transition: all .1s ease-out; +} + +.VkIdWebSdk__button:hover { + opacity: 0.8; +} + +.VkIdWebSdk__button:active { + opacity: .7; + transform: scale(.97); +} + +.VkIdWebSdk__button { + border-radius: 8px; + width: 100%; + min-height: 44px; +} + +.VkIdWebSdk__button_container { + display: flex; + align-items: center; + padding: 12px 10px; +} + +.VkIdWebSdk__button_icon+.VkIdWebSdk__button_text { + margin-left: -28px; +} + +.VkIdWebSdk__button_text { + display: flex; + font-family: -apple-system, system-ui, "Helvetica Neue", Roboto, sans-serif; + flex: 1; + justify-content: center; + color: #ffffff; +} \ No newline at end of file diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index 4bd1313..856c77a 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -1,10 +1,10 @@ @use './header.scss'; -@use './dashboard.scss'; +@use './pages-styles.scss'; @use './settings.scss'; @use './payment-container.scss'; @use './reports.scss'; -@use './marking-pages.scss'; @use './privacy-and-terms-pages.scss'; +@use './VKLogin.scss'; .btn { padding: 12px 24px; @@ -92,4 +92,37 @@ margin: 0 auto; padding: 0 24px; } +} + +.show-password-button { + cursor: pointer; + position: absolute; + top: 0px; + right: 6px; + + svg { + width: 16px; + color: #686060; + transition: color 0.2s ease; + } + + &.show { + svg { + color: black; + } + } +} + +@media (max-width: 1300px) { + .tanstak-file-table { + max-width: 800px; + margin: 0 auto; + } +} + +@media (max-width: 1200px) { + .tanstak-file-table { + max-width: 500px; + margin: 0 auto; + } } \ No newline at end of file diff --git a/src/app/styles/login.module.scss b/src/app/styles/login.module.scss index e3f652f..e8c24d2 100644 --- a/src/app/styles/login.module.scss +++ b/src/app/styles/login.module.scss @@ -188,4 +188,8 @@ font-size: 13px; line-height: 1.4; } +} + +.password-wrapper { + position: relative; } \ No newline at end of file diff --git a/src/app/styles/marking-pages.scss b/src/app/styles/marking-pages.scss deleted file mode 100644 index 988b7f1..0000000 --- a/src/app/styles/marking-pages.scss +++ /dev/null @@ -1,284 +0,0 @@ -.protection-summary { - background: linear-gradient(135deg, #e8f5e8, #c8e6c9); - border: 2px solid #4caf50; - border-radius: 15px; - padding: 20px; - margin-bottom: 25px; - text-align: center; - - h3 { - color: #2e7d32; - margin-bottom: 15px; - font-size: 18px; - } - - .protection-stats { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 15px; - margin-top: 15px; - } - - .stat-card { - background: rgba(255, 255, 255, 0.8); - padding: 15px; - border-radius: 10px; - text-align: center; - } - - .stat-number { - font-size: 24px; - font-weight: bold; - color: #2e7d32; - } - - .stat-label { - font-size: 12px; - color: #666; - margin-top: 5px; - } -} - -.upload-section { - background: white; - border-radius: 20px; - padding: 30px; - margin-bottom: 30px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - - h3 { - margin-bottom: 20px; - color: #111827; - font-size: 20px; - } - - .drag-drop-zone { - border: 3px dashed #6366f1; - border-radius: 15px; - padding: 40px 20px; - text-align: center; - background: linear-gradient(135deg, #f0f9ff, #e0f2fe); - margin-bottom: 20px; - transition: all 0.3s ease; - - h4 { - color: #6366f1; - margin-bottom: 10px; - } - - .description { - color: #6b7280; - margin-bottom: 20px; - } - - .btn-primary { - background: linear-gradient(45deg, #6566f1, #01579b); - margin-bottom: 20px; - } - } - - .selected-file { - border-radius: 15px; - background: #fff; - padding: 10px; - - &-file-info { - text-align: left; - } - - .uploaded-image { - border-radius: 0.25rem; - max-height: calc(4px * 40); - margin-inline: auto; - margin-bottom: 20px; - border: 1px solid #d1d5db; - } - - .btn-confirm { - background: linear-gradient(45deg, #6566f1, #01579b); - color: white; - border: none; - padding: 5px 15px; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - font-size: 16px; - transition: 0.3s; - min-width: 160px; - transform: translateY(0px); - box-shadow: none; - } - - .btn-cancel { - background: #fff; - color: rgb(99, 102, 241); - border: 2px solid rgb(99, 102, 241); - padding: 5px 15px; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - font-size: 16px; - transition: 0.3s; - min-width: 120px; - } - - .btn:hover { - transform: translateY(-2px); - box-shadow: 0 8px 25px #6366f14d; - } - } -} - -.test-section { - background: linear-gradient(135deg, #fff3e0, #ffe0b2); - border-radius: 20px; - padding: 30px; - margin-bottom: 30px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - - h3 { - margin-bottom: 20px; - color: #e65100; - font-size: 20px; - } - - .test-section-content { - border: 2px dashed #ff9800; - border-radius: 10px; - padding: 20px; - text-align: center; - background: #fff8e1; - - h4 { - font-size: 32px; - margin-bottom: 10px; - } - - .btn { - background: linear-gradient(45deg, #ff9800, #f57c00); - } - - p { - margin-top: 15px; - font-size: 12px; - color: #e65100; - } - } -} - -.protected-files-table { - background: white; - border-radius: 15px; - overflow: hidden; - margin-top: 30px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - - h3 { - padding: 20px 20px 0; - color: #111827; - font-size: 18px; - margin-bottom: 20px; - } - - table { - width: 100%; - border-collapse: collapse; - } - - th { - background: linear-gradient(135deg, #6366f1, #8b5cf6); - color: white; - padding: 15px; - text-align: left; - font-weight: 600; - font-size: 14px; - } - - td { - padding: 15px; - border-bottom: 1px solid #f3f4f6; - vertical-align: middle; - } - - .files-table { - &-file { - display: flex; - align-items: center; - } - - &-icon { - width: 40px; - height: 40px; - background: #f0f9ff; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - margin-right: 12px; - border: 2px solid #6366f1; - color: #6366f1; - } - - &-title { - font-weight: 500; - color: #111827; - font-size: 14px; - } - - &-protect { - font-size: 11px; - color: #6366f1; - font-weight: 600; - } - - &-size { - color: #6b7280; - font-size: 14px; - } - - &-protect { - display: flex; - align-items: center; - } - - &-bar { - width: 30px; - height: 6px; - background: #e5e7eb; - border-radius: 3px; - margin-right: 8px; - } - - &-fill { - width: 100%; - height: 100%; - background: #6366f1; - border-radius: 3px; - } - - &-date { - color: #6b7280; - font-size: 13px; - } - } - - .btn-secondary { - background: #6366f1; - color: white; - padding: 5px 16px 5px 10px; - } - - .icon { - height: 18px; - } - - .real-protection-badge { - background: linear-gradient(135deg, #6366f1, #8b5cf6); - color: white; - padding: 2px 6px; - border-radius: 4px; - font-size: 9px; - font-weight: bold; - margin-left: 5px; - } -} \ No newline at end of file diff --git a/src/app/styles/dashboard.scss b/src/app/styles/pages-styles.scss similarity index 51% rename from src/app/styles/dashboard.scss rename to src/app/styles/pages-styles.scss index 369a144..62d4ba4 100644 --- a/src/app/styles/dashboard.scss +++ b/src/app/styles/pages-styles.scss @@ -118,6 +118,24 @@ font-size: 14px; opacity: 0.9; } + + &-total-info { + position: absolute; + top: 10; + left: 10; + background-color: #1f293718; + padding: 10px; + border-radius: 10; + font-size: 15px; + } + + .stacked-bar-chart { + border-radius: 10; + width: 100%; + padding: 0 20px; + display: flex; + justify-content: center; + } } &-switch { @@ -128,13 +146,6 @@ } .stats-wrapper { - border-radius: 20px; - overflow: hidden; - background: white; - box-shadow: 0 4px 20px #00000014; - margin-bottom: 30px; - padding: 20px; - h3 { font-size: 20px; margin-bottom: 10px; @@ -172,13 +183,15 @@ &.stats-header { background: #f9fafb; border-left: none; - font-size: 18px; + font-size: 16px; + font-weight: 500; } &.first-column { - font-size: 18px; + font-size: 16px; justify-content: start; padding-left: 20px; + font-weight: 500; .icon { color: var(--card-color-1); @@ -186,9 +199,6 @@ } } - &.last-column { - } - &.last-row { border-bottom: 1px solid var(--boder-collor); } @@ -348,3 +358,297 @@ } } } + +.protection-summary { + background: linear-gradient(135deg, #e8f5e8, #c8e6c9); + border: 2px solid #4caf50; + border-radius: 15px; + padding: 20px; + margin-bottom: 25px; + text-align: center; + + h3 { + color: #2e7d32; + margin-bottom: 15px; + font-size: 18px; + } + + .protection-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 15px; + margin-top: 15px; + } + + .stat-card { + background: rgba(255, 255, 255, 0.8); + padding: 15px; + border-radius: 10px; + text-align: center; + } + + .stat-number { + font-size: 24px; + font-weight: bold; + color: #2e7d32; + } + + .stat-label { + font-size: 12px; + color: #666; + margin-top: 5px; + } +} + +.upload-section { + background: white; + border-radius: 20px; + padding: 30px; + margin-bottom: 30px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + + h3 { + margin-bottom: 20px; + color: #111827; + font-size: 20px; + } + + .drag-drop-zone { + border: 3px dashed #6366f1; + border-radius: 15px; + padding: 40px 20px; + text-align: center; + background: linear-gradient(135deg, #f0f9ff, #e0f2fe); + margin-bottom: 20px; + transition: all 0.3s ease; + + .icon { + width: 70px; + height: 70px; + margin: 0 auto; + color: #6366f1; + } + + h4 { + color: #6366f1; + margin-bottom: 5px; + font-size: 22px; + } + + .description { + color: #6b7280; + margin-bottom: 5px; + text-align: start; + } + + .btn-primary { + background: linear-gradient(45deg, #6566f1, #5a5ce9); + margin-bottom: 40px; + } + } + + .selected-file { + border-radius: 15px; + background: #fff; + padding: 10px; + + &-file-info { + text-align: left; + } + + .uploaded-image { + border-radius: 0.25rem; + max-height: calc(4px * 40); + margin-inline: auto; + margin-bottom: 20px; + border: 1px solid #d1d5db; + } + + .btn-confirm { + background: linear-gradient(45deg, #6566f1, #01579b); + color: white; + border: none; + padding: 5px 15px; + border-radius: 12px; + cursor: pointer; + font-weight: 600; + font-size: 16px; + transition: 0.3s; + min-width: 160px; + transform: translateY(0px); + box-shadow: none; + } + + .btn-cancel { + background: #fff; + color: rgb(99, 102, 241); + border: 2px solid rgb(99, 102, 241); + padding: 5px 15px; + border-radius: 12px; + cursor: pointer; + font-weight: 600; + font-size: 16px; + transition: 0.3s; + min-width: 120px; + } + + .btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px #6366f14d; + } + } +} + +.test-section { + background: linear-gradient(135deg, #fff3e0, #ffe0b2); + border-radius: 20px; + padding: 30px; + margin-bottom: 30px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + + h3 { + margin-bottom: 20px; + color: #e65100; + font-size: 20px; + } + + .test-section-content { + border: 2px dashed #ff9800; + border-radius: 10px; + padding: 20px; + text-align: center; + background: #fff8e1; + + h4 { + font-size: 32px; + margin-bottom: 10px; + } + + .btn { + background: linear-gradient(45deg, #ff9800, #f57c00); + } + + p { + margin-top: 15px; + font-size: 12px; + color: #e65100; + } + } +} + +.protected-files-table { + background: white; + border-radius: 15px; + overflow: hidden; + margin-top: 30px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + + h3 { + padding: 20px 20px 0; + color: #111827; + font-size: 18px; + margin-bottom: 20px; + } + + table { + width: 100%; + border-collapse: collapse; + } + + th { + background: linear-gradient(180deg, #6366f1, #8b5cf6); + color: white; + padding: 15px; + text-align: left; + font-weight: 600; + font-size: 14px; + } + + td { + padding: 15px; + border-bottom: 1px solid #f3f4f6; + vertical-align: middle; + } + + .files-table { + &-file { + display: flex; + align-items: center; + } + + &-icon { + width: 40px; + height: 40px; + background: #f0f9ff; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 12px; + border: 2px solid #6366f1; + color: #6366f1; + } + + &-title { + font-weight: 500; + color: #111827; + font-size: 14px; + } + + &-protect { + font-size: 11px; + color: #6366f1; + font-weight: 600; + } + + &-size { + color: #6b7280; + font-size: 14px; + } + + &-protect { + display: flex; + align-items: center; + } + + &-bar { + width: 30px; + height: 6px; + background: #e5e7eb; + border-radius: 3px; + margin-right: 8px; + } + + &-fill { + width: 100%; + height: 100%; + background: #6366f1; + border-radius: 3px; + } + + &-date { + color: #6b7280; + font-size: 13px; + } + } + + .btn-secondary { + background: #6366f1; + color: white; + padding: 5px 16px 5px 10px; + } + + .icon { + height: 18px; + } + + .real-protection-badge { + background: linear-gradient(135deg, #6366f1, #8b5cf6); + color: white; + padding: 2px 6px; + border-radius: 4px; + font-size: 9px; + font-weight: bold; + margin-left: 5px; + } +} \ No newline at end of file diff --git a/src/app/styles/ui.module.scss b/src/app/styles/ui.module.scss index 1bab389..f25dc83 100644 --- a/src/app/styles/ui.module.scss +++ b/src/app/styles/ui.module.scss @@ -2,7 +2,6 @@ width: 280px; background: linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%); padding: 30px 0; - border-radius: 0 30px 30px 0; position: fixed; height: 100vh; overflow-y: auto; diff --git a/src/app/ui/dashboard/files-table.tsx b/src/app/ui/dashboard/files-table.tsx index 6c96b70..e0b6de3 100644 --- a/src/app/ui/dashboard/files-table.tsx +++ b/src/app/ui/dashboard/files-table.tsx @@ -1,8 +1,8 @@ -import DataTable from '@/app/components/tanstakTable'; +import TanstakFilesTable from '@/app/components/tanstakTable'; export default function FilesTable() { return ( -
    - +
    +
    ) } \ No newline at end of file diff --git a/src/app/ui/dashboard/stats-grid.tsx b/src/app/ui/dashboard/stats-grid.tsx index 13ab89f..e6514ea 100644 --- a/src/app/ui/dashboard/stats-grid.tsx +++ b/src/app/ui/dashboard/stats-grid.tsx @@ -4,7 +4,7 @@ export default function StatsGrid() { const t = useTranslations("Global"); return ( -
    +

    {t('your-content')}

    diff --git a/src/app/ui/login-form.tsx b/src/app/ui/forms/login-form.tsx similarity index 67% rename from src/app/ui/login-form.tsx rename to src/app/ui/forms/login-form.tsx index 896d600..b6ca382 100644 --- a/src/app/ui/login-form.tsx +++ b/src/app/ui/forms/login-form.tsx @@ -1,9 +1,10 @@ 'use client' import styles from '@/app/styles/login.module.scss' -import { useActionState } from 'react'; +import { useActionState, useState } from 'react'; import { authorization } from '@/app/actions/auth'; import { useTranslations } from 'next-intl'; +import { IconEye } from '@/app/ui/icons/icons'; export default function LoginForm() { const [state, formAction, isPending] = useActionState( @@ -12,6 +13,20 @@ export default function LoginForm() { ); const t = useTranslations('Login-register-form'); + const [showPassword, setShowPassword] = useState(false); + + function handleMouseDown() { + setShowPassword(true); + } + + function handleMouseUp() { + setShowPassword(false); + } + + function handleMouseOut() { + setShowPassword(false); + } + return (
    @@ -36,13 +51,24 @@ export default function LoginForm() { - +
    + + +
    {state?.error?.password && (

    {t(state?.error?.password)} diff --git a/src/app/ui/register-form.tsx b/src/app/ui/forms/register-form.tsx similarity index 63% rename from src/app/ui/register-form.tsx rename to src/app/ui/forms/register-form.tsx index 06778b2..e527dfe 100644 --- a/src/app/ui/register-form.tsx +++ b/src/app/ui/forms/register-form.tsx @@ -1,11 +1,12 @@ 'use client' import styles from '@/app/styles/login.module.scss'; -import { useActionState } from 'react'; +import { useActionState, useState } from 'react'; import { registration } 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'; export default function RegisterForm() { const [state, formAction, isPending] = useActionState( @@ -14,6 +15,34 @@ export default function RegisterForm() { ); const t = useTranslations('Login-register-form'); + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); + + function handleMouseDown(target: 'password' | 'confirm-password') { + if (target === 'password') { + setShowPassword(true); + } else { + setShowConfirmPassword(true); + } + } + + function handleMouseUp(target: 'password' | 'confirm-password') { + if (target === 'password') { + setShowPassword(false); + } else { + setShowConfirmPassword(false); + } + + } + + function handleMouseOut(target: 'password' | 'confirm-password') { + if (target === 'password') { + setShowPassword(false); + } else { + setShowConfirmPassword(false); + } + } + return ( { formAction(e); @@ -89,13 +118,32 @@ export default function RegisterForm() { - +

    + + +
    {state?.error?.password && (

    { @@ -110,14 +158,31 @@ export default function RegisterForm() { - +

    + + +
    {state?.error?.confirm_password && (

    {t(state?.error?.confirm_password)} diff --git a/src/app/ui/header/notificationsButton.tsx b/src/app/ui/header/notificationsButton.tsx index 1a4906a..66e7ead 100644 --- a/src/app/ui/header/notificationsButton.tsx +++ b/src/app/ui/header/notificationsButton.tsx @@ -65,7 +65,7 @@ export default function NotificationsButton() {

    -
    🪙 Токены добавлены
    +
    Токены добавлены
    На ваш баланс добавлено 100 токенов. Теперь вы можете защитить ещё больше файлов!
    4 дн назад
    diff --git a/src/app/ui/icons/icons.tsx b/src/app/ui/icons/icons.tsx index d9b324d..8840b9c 100644 --- a/src/app/ui/icons/icons.tsx +++ b/src/app/ui/icons/icons.tsx @@ -26,7 +26,17 @@ export function IconAudioFile() { export function IconShield() { return ( - + + + + ) +} + +export function IconShieldAdd() { + return ( + + + ) } @@ -36,4 +46,55 @@ export function IconDownload() { ) +} + +export function IconEye() { + return ( + + + + ) +} + +export function DoubleArrowRight() { + return ( + + ) +} + +export function DoubleArrowLeft() { + return ( + + ) +} + +export function ArrowRight() { + return ( + + + ) +} + +export function ArrowLeft() { + return ( + + ) +} + +export function ArrowUp() { + return ( + + ) +} + +export function ArrowDown() { + return ( + + ) +} + +export function Filter() { + return ( + + ) } \ No newline at end of file diff --git a/src/app/ui/inputs/phone-input.tsx b/src/app/ui/inputs/phone-input.tsx index 9c0faf0..61c3a6a 100644 --- a/src/app/ui/inputs/phone-input.tsx +++ b/src/app/ui/inputs/phone-input.tsx @@ -22,8 +22,8 @@ export default function PhoneInput({ phoneState }: PhoneInputProps) { } const limitedDigits = digitsOnly.slice(0, 11); - const countryCode = limitedDigits.startsWith('8') ? '8' : '7'; - const phoneNumber = limitedDigits.startsWith('8') || limitedDigits.startsWith('7') + const countryCode = 7; + const phoneNumber = limitedDigits.startsWith('7') ? limitedDigits.slice(1) : limitedDigits; diff --git a/src/app/ui/marking-page/protected-files-table.tsx b/src/app/ui/marking-page/protected-files-table.tsx index 987854f..e968f0a 100644 --- a/src/app/ui/marking-page/protected-files-table.tsx +++ b/src/app/ui/marking-page/protected-files-table.tsx @@ -1,3 +1,5 @@ +/* removed */ + import { IconShield, IconDownload } from '@/app/ui/icons/icons'; export default function ProtectedFilesTable() { return ( diff --git a/src/app/ui/marking-page/protection-summary.tsx b/src/app/ui/marking-page/protection-summary.tsx index 30a560b..2981eec 100644 --- a/src/app/ui/marking-page/protection-summary.tsx +++ b/src/app/ui/marking-page/protection-summary.tsx @@ -1,21 +1,128 @@ +'use client' + +import { useState } from 'react'; +import { StackedBarChart } from '@/app/components/StackedBarChart'; +import { useTranslations } from 'next-intl'; + +const data = [ + { + name: 'File A', + checks: 4000, + violations: 2000 + }, + { + name: 'File B', + checks: 3000, + violations: 1400 + }, + { + name: 'File C', + checks: 2000, + violations: 1000 + }, + { + name: 'File D', + checks: 2780, + violations: 1000 + }, + { + name: 'File E', + checks: 5090, + violations: 4800 + }, + { + name: 'File F', + checks: 5390, + violations: 3800 + }, + { + name: 'File H', + checks: 4000, + violations: 1500 + } +]; + export default function ProtectionSummary() { + const [isOpen, setIsOpen] = useState(false); + const t = useTranslations('Global'); + return ( -
    -

    Статистика защиты

    -
    -
    -
    0
    -
    Всего файлов
    +
    +

    {t('protecting-your-content')}

    +

    {t('current-status-of')}

    +
    +
    +
    0
    +
    {t('total-files')}
    -
    -
    0
    -
    Общий размер
    -
    -
    -
    0
    -
    Проверки/нарушения
    + +
    + {isOpen ? ( + <> +
    0
    +
    + {t('size')} +
    + + ) : + <> +
    +
    0
    +
    {t('check')}
    +
    +
    +
    0
    +
    {t('violations')}
    +
    + + }
    + {isOpen ? ( +
    +
    + +
    +
    +
    + {t('check')}: 777 +
    +
    + {t('violations')}: 777 +
    +
    +
    + ) : ( +
    +
    0
    +
    + {t('size')} +
    +
    + )}
    +
    ) } \ No newline at end of file diff --git a/src/app/ui/marking-page/upload-section-image.tsx b/src/app/ui/marking-page/upload-section-image.tsx index 1cf0bf0..fba6005 100644 --- a/src/app/ui/marking-page/upload-section-image.tsx +++ b/src/app/ui/marking-page/upload-section-image.tsx @@ -1,6 +1,7 @@ 'use client' import React, { useRef, useState, useCallback, ChangeEvent, DragEvent } from 'react'; +import {IconShieldAdd} from '@/app/ui/icons/icons'; interface SelectedFile { file: File; @@ -188,11 +189,9 @@ export default function UploadSectionImage() { transition: 'all .2s ease-in-out' }} > +

    Перетащите изображения сюда

    -

    - Размер: 150x150 - 4000x4000 пикселей, форматы: JPG, PNG, GIF, BMP (WEBP НЕ поддерживается) -

    - +

    или

    +

    + Разрешение изображение: 100x100 - 10000x10000 пикселей +

    +

    + Размер файла: до 20 МБ +

    +

    + Формат файла: JPG, PNG, GIF, BMP (WEBP НЕ поддерживается) +

    + {error && (

    ❌ {error}

    diff --git a/src/app/ui/nav-link-dropdown.tsx b/src/app/ui/nav-link-dropdown.tsx index 53008cc..70cc2e5 100644 --- a/src/app/ui/nav-link-dropdown.tsx +++ b/src/app/ui/nav-link-dropdown.tsx @@ -6,7 +6,9 @@ import { usePathname } from 'next/navigation'; import { useTranslations } from 'next-intl'; export default function DropDownList() { - const pathname = usePathname(); + const pathname = usePathname().replace('/en/', '/').replace('/ru/', '/'); + console.log('pathname'); + console.log(pathname); const [dropDownExpanded, setDropDownExpanded] = useState(false); const t = useTranslations('Global'); diff --git a/src/app/ui/nav-links.tsx b/src/app/ui/nav-links.tsx index 4d7aebe..09163e3 100644 --- a/src/app/ui/nav-links.tsx +++ b/src/app/ui/nav-links.tsx @@ -10,7 +10,7 @@ import { logout } from '@/app/actions/auth'; import { useTranslations } from 'next-intl'; export default function NavLinks() { - const pathname = usePathname(); + const pathname = usePathname().replace('/en/', '/').replace('/ru/', '/'); const t = useTranslations('Global'); const links = [ diff --git a/src/app/ui/settings/personal-data-settings.tsx b/src/app/ui/settings/personal-data-settings.tsx index fb659c4..61128e8 100644 --- a/src/app/ui/settings/personal-data-settings.tsx +++ b/src/app/ui/settings/personal-data-settings.tsx @@ -1,5 +1,6 @@ 'use client' +import { useTranslations } from 'next-intl'; import { useActionState, useState } from 'react' export default function PersonalDataSettings() { @@ -12,58 +13,82 @@ export default function PersonalDataSettings() { const [selectedValue, setSelectedValue] = useState('male'); + const t = useTranslations('Global'); + return (
    -

    👤 Персональные данные

    +

    + {t('personal-data')} +

    - +
    - +
    - + - Email нельзя изменить. Обратитесь в поддержку. + {t('email-cant-change')}
    - +
    - +
    - + - Используется для отправки поздравления + {t('used-to-send-congratulations')}
    - + - Заполняется автоматически исходя из даты рождения + {t('automatically-filled-in-based-on-date-of-birth')}
    - +
    diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 721a1f8..66a94c1 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -4,9 +4,12 @@ "description": "This is a demo application with i18n support" }, "Global": { + "actions": "Actions", + "all-types": "All types", "statistics": "Statistics", "your-content": "Your content", - "fail": "Fail", + "file": "File", + "files": "Files", "content-type": "Content type", "quantity": "Quantity", "size": "Size", @@ -37,7 +40,36 @@ "current-status-of": "Current status of security and activity of the monitoring system", "total-files": "Total files", "disk-space-used": "Disk space used", - "out-of": "out of" + "out-of": "out of", + "last-check": "Last check", + "all-dates": "All dates", + "today": "Today", + "for-a-week": "For a week", + "for-a-month": "For a month", + "older-than-a-month": "Older than a month", + "show": "Show", + "no-data-for-selected-filters": "No data for selected filters", + "page": "Page", + "shown": "Shown", + "date-filter": "Date filter", + "type-filter": "Type filter", + "items-per-page": "Items per page", + "email": "Email", + "image-protection": "Защита изображений", + "account-settings": "Account settings", + "personal-data": "Personal data", + "full-name": "Full name", + "company": "Company", + "email-cant-change": "Email address cannot be changed. Please contact support.", + "phone": "Phone", + "gender": "Gender", + "birthday": "Birthday", + "age": "Age", + "male": "Male", + "female": "Female", + "used-to-send-congratulations": "Used to send congratulations", + "automatically-filled-in-based-on-date-of-birth": "Automatically filled in based on date of birth", + "save-changes": "Save changes" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 5ac29b0..2ddde03 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -4,9 +4,12 @@ "description": "Это демонстрационное приложение с поддержкой i18n" }, "Global": { + "actions": "Действия", + "all-types": "Все типы", "statistics": "Статистика", "your-content": "Ваш контент", - "fail": "Фаил", + "file": "Фаил", + "files": "Фаил", "content-type": "Тип контента", "quantity": "Количество", "size": "Размер", @@ -37,7 +40,36 @@ "current-status-of": "Текущий статус защищенности и активности системы мониторинга", "total-files": "Всего файлов", "disk-space-used": "Использовано места на диске", - "out-of": "из" + "out-of": "из", + "last-check": "Последняя проверка", + "all-dates": "Все даты", + "today": "Сегодня", + "for-a-week": "За неделю", + "for-a-month": "За месяц", + "older-than-a-month": "Старше месяца", + "show": "Показать", + "no-data-for-selected-filters": "Нет данных по выбранным фильтрам", + "page": "Страница", + "shown": "Показано", + "date-filter": "Фильтр по дате", + "type-filter": "Фильтр по типу", + "items-per-page": "Записей на странице", + "email": "Почта", + "image-protection": "Защита изображений", + "account-settings": "Настройки аккаунта", + "personal-data": "Персональные данные", + "full-name": "Полное имя", + "company": "Компания", + "email-cant-change": "Email нельзя изменить. Обратитесь в поддержку.", + "phone": "Телефон", + "gender": "Пол", + "birthday": "День рождения", + "age": "Возраст", + "male": "Мужской", + "female": "Женский", + "used-to-send-congratulations": "Используется для отправки поздравления", + "automatically-filled-in-based-on-date-of-birth": "Заполняется автоматически исходя из даты рождения", + "save-changes": "Сохранить изменения" }, "Login-register-form": { "and": "и",