add search setting interface
This commit is contained in:
@@ -1,36 +1,18 @@
|
|||||||
import SectionSearchFile from '@/app/ui/search/section-search-file';
|
import SectionSearchFile from '@/app/ui/search/section-search-file';
|
||||||
import { SupportedFormats } from '@/app/ui/search/supported-formats';
|
import { SupportedFormats } from '@/app/ui/search/supported-formats';
|
||||||
import { SearchStats } from '@/app/ui/search/search-stats';
|
import { SearchStats } from '@/app/ui/search/search-stats';
|
||||||
import { RecentSearches } from '@/app/ui/search/recent-searches';
|
import SearchTitle from '@/app/ui/search/search-title';
|
||||||
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
|
|
||||||
|
|
||||||
export default async function Page() {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="block-wrapper">
|
<SearchTitle />
|
||||||
<h1 className="page-title">
|
|
||||||
Поиск защищенного контента
|
|
||||||
</h1>
|
|
||||||
<p className="page-description">
|
|
||||||
Загрузите файл для поиска похожего защищенного контента в вашей библиотеке и в интернете
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="search-grid">
|
<div className="search-grid">
|
||||||
<SectionSearchFile />
|
<SectionSearchFile />
|
||||||
<div className="search-sidebar">
|
<div className="search-sidebar">
|
||||||
<SupportedFormats
|
<SupportedFormats />
|
||||||
extensionVideo={extensionVideo}
|
|
||||||
extensionAudio={extensionAudio}
|
|
||||||
extensionImage={extensionImage}
|
|
||||||
extensionDocument={extensionDocument}
|
|
||||||
/>
|
|
||||||
<SearchStats />
|
<SearchStats />
|
||||||
{/* <RecentSearches /> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
|
||||||
|
export async function getSearchSettings() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/search/settings`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
if (parsed.message_body) {
|
||||||
|
return parsed.message_body;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putSearchSettings(proxy: boolean, yandex: boolean, google: boolean) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/search/settings`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
engines: {
|
||||||
|
yandex: yandex,
|
||||||
|
google: google
|
||||||
|
},
|
||||||
|
proxyEnabled: proxy
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
if (parsed.message_desc === 'Settings updated') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,10 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { fetchViolationStatistic } from '@/app/actions/violationActions';
|
import { fetchViolationStatistic } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
export interface UseViolationStatistic {
|
export interface UseViolationStatistic {
|
||||||
total_violations: 2565,
|
total_violations: number,
|
||||||
new_violations: 2565,
|
new_violations: number,
|
||||||
in_progress_violations: 0,
|
in_progress_violations: number,
|
||||||
resolved_violations: 0
|
resolved_violations: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useViolationStatistic = () => {
|
export const useViolationStatistic = () => {
|
||||||
|
|||||||
@@ -737,3 +737,48 @@
|
|||||||
/* transform: translateY(-2px); */
|
/* transform: translateY(-2px); */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-settings {
|
||||||
|
&-wrapper {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: #f0f9ff;
|
||||||
|
border: 1px solid #bfdbfe;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 16px;
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-left: 4px;
|
||||||
|
color: v.$red;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: v.$green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* &-action-panel {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
} */
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ export default function HeaderPanel() {
|
|||||||
return (
|
return (
|
||||||
<header className="header">
|
<header className="header">
|
||||||
<BurgerMenu />
|
<BurgerMenu />
|
||||||
<LocalFileCheck />
|
{/* <LocalFileCheck /> */}
|
||||||
<div className="header-action">
|
<div className="header-action">
|
||||||
{/* <LanguageSwitcher /> */}
|
{/* <LanguageSwitcher /> */}
|
||||||
{typeof userData?.tariffInfo?.tokens === 'number' && (
|
{typeof userData?.tariffInfo?.tokens === 'number' && (
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { getSearchSettings, putSearchSettings } from '@/app/actions/SearchSetting';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
interface SearchSettings {
|
||||||
|
engines: {
|
||||||
|
yandex: boolean;
|
||||||
|
google: boolean;
|
||||||
|
};
|
||||||
|
proxyEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SearchSettingsBlock() {
|
||||||
|
|
||||||
|
const { data: searchStats } = useQuery<SearchSettings>({
|
||||||
|
queryKey: ['searchStats'],
|
||||||
|
queryFn: getSearchSettings,
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
async function changeHandler(changetItem: 'proxy' | 'yandex' | 'google') {
|
||||||
|
if (!searchStats) {
|
||||||
|
toast.error(t('error'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
let proxy = searchStats.proxyEnabled;
|
||||||
|
let yandex = searchStats.engines.yandex;
|
||||||
|
let google = searchStats.engines.google;
|
||||||
|
|
||||||
|
switch (changetItem) {
|
||||||
|
case 'yandex':
|
||||||
|
yandex = !yandex;
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'google':
|
||||||
|
google = !google;
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'proxy':
|
||||||
|
proxy = !proxy;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await putSearchSettings(proxy, yandex, google);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
toast.success('Succes');
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['searchStats'] });
|
||||||
|
} else {
|
||||||
|
toast.warning('Not succes');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(t('error'));
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="search-settings-wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`search-settings-header ${isLoading ? 'loading' : ''}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`search-settings-item`}
|
||||||
|
onClick={() => {
|
||||||
|
changeHandler('proxy');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('proxy-enabled')}:
|
||||||
|
<span className={`${searchStats?.proxyEnabled ? 'active' : ''}`} >
|
||||||
|
{searchStats?.proxyEnabled ? t('yes') : t('no')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`search-settings-item`}
|
||||||
|
onClick={() => {
|
||||||
|
changeHandler('yandex');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
yandex:
|
||||||
|
<span
|
||||||
|
className={`${searchStats?.engines?.yandex ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
{searchStats?.engines?.yandex ? t('yes') : t('no')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`search-settings-item`}
|
||||||
|
onClick={() => {
|
||||||
|
changeHandler('google');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
google:
|
||||||
|
<span
|
||||||
|
className={`${searchStats?.engines?.google ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
{searchStats?.engines?.google ? t('yes') : t('no')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{isLoading && (
|
||||||
|
<div
|
||||||
|
className="relative ml-2.5"
|
||||||
|
>
|
||||||
|
<div className="loading-animation">
|
||||||
|
<div className="global-spinner"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -9,14 +9,10 @@ export function SearchStats() {
|
|||||||
const { data: userSearchData, isLoading, isError, error } = useUserSearchData();
|
const { data: userSearchData, isLoading, isError, error } = useUserSearchData();
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(userSearchData);
|
|
||||||
}, [userSearchData])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="stats-search">
|
<div className="stats-search">
|
||||||
<div className="stats-title">
|
<div className="stats-title">
|
||||||
Статистика поиска
|
{t('search-stats')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="stat-item">
|
<div className="stat-item">
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl'
|
||||||
|
|
||||||
|
export default function SearchTitle() {
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="block-wrapper">
|
||||||
|
<h1 className="page-title">
|
||||||
|
{t('search-protected-content')}
|
||||||
|
</h1>
|
||||||
|
<p className="page-description">
|
||||||
|
{t('search-title-description')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import { removeUserFile } from '@/app/actions/fileEntity';
|
|||||||
import { FileSearchPanel } from '@/app/ui/search/file-search-panel';
|
import { FileSearchPanel } from '@/app/ui/search/file-search-panel';
|
||||||
import { getFileType } from '@/app/lib/getFileType';
|
import { getFileType } from '@/app/lib/getFileType';
|
||||||
import { useAllFilesExtensions } from '@/app/hooks/react-query/useAllFilesExtensions';
|
import { useAllFilesExtensions } from '@/app/hooks/react-query/useAllFilesExtensions';
|
||||||
|
import SearchSettingsBlock from '@/app/ui/search/search-settings-block';
|
||||||
interface SelectedFile {
|
interface SelectedFile {
|
||||||
file: File;
|
file: File;
|
||||||
preview: string | undefined;
|
preview: string | undefined;
|
||||||
@@ -287,13 +288,16 @@ export default function SectionSearchFile() {
|
|||||||
return (
|
return (
|
||||||
<div className="upload-section">
|
<div className="upload-section">
|
||||||
<div className="search-info">
|
<div className="search-info">
|
||||||
<div className="search-info-title">Как работает поиск?</div>
|
<div className="search-info-title">
|
||||||
|
{t('how-does-search-work')}
|
||||||
|
</div>
|
||||||
<div className="search-info-text">
|
<div className="search-info-text">
|
||||||
Наша система анализирует загруженный файл и сравнивает его с вашими защищенными файлами,
|
{t('search-info-description-text')}
|
||||||
используя алгоритмы компьютерного зрения и цифровых отпечатков.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SearchSettingsBlock />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`drag-drop-zone ${isDragging ? 'dragging' : ''}`}
|
className={`drag-drop-zone ${isDragging ? 'dragging' : ''}`}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { IconImageFile, IconVideoFile, IconAudioFile, IconDocument } from '@/app/ui/icons/icons';
|
import { IconImageFile, IconVideoFile, IconAudioFile, IconDocument } from '@/app/ui/icons/icons';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useAllFilesExtensions } from '@/app/hooks/react-query/useAllFilesExtensions';
|
||||||
|
|
||||||
interface supportedFormats {
|
export function SupportedFormats() {
|
||||||
extensionVideo: string[]
|
|
||||||
extensionAudio: string[]
|
|
||||||
extensionImage: string[]
|
|
||||||
extensionDocument: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SupportedFormats({ extensionVideo, extensionAudio, extensionImage, extensionDocument }: supportedFormats) {
|
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
|
const { data: allFilesExtensions, isLoading, isError } = useAllFilesExtensions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="supported-formats">
|
<div className="supported-formats">
|
||||||
<div className="formats-title">
|
<div className="formats-title">
|
||||||
Поддерживаемые форматы
|
{t('supported-formats')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="format-group">
|
<div className="format-group">
|
||||||
@@ -22,7 +20,7 @@ export function SupportedFormats({ extensionVideo, extensionAudio, extensionImag
|
|||||||
<IconImageFile /> {t('images')}
|
<IconImageFile /> {t('images')}
|
||||||
</div>
|
</div>
|
||||||
<div className="format-list">
|
<div className="format-list">
|
||||||
{extensionImage.join(', ')}
|
{allFilesExtensions?.images?.join(', ')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -31,7 +29,7 @@ export function SupportedFormats({ extensionVideo, extensionAudio, extensionImag
|
|||||||
<IconVideoFile /> {t('videos')}
|
<IconVideoFile /> {t('videos')}
|
||||||
</div>
|
</div>
|
||||||
<div className="format-list">
|
<div className="format-list">
|
||||||
{extensionVideo.join(', ')}
|
{allFilesExtensions?.videos?.join(', ')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -40,7 +38,7 @@ export function SupportedFormats({ extensionVideo, extensionAudio, extensionImag
|
|||||||
<IconAudioFile /> {t('audios')}
|
<IconAudioFile /> {t('audios')}
|
||||||
</div>
|
</div>
|
||||||
<div className="format-list">
|
<div className="format-list">
|
||||||
{extensionAudio.join(', ')}
|
{allFilesExtensions?.audios?.join(', ')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -49,7 +47,7 @@ export function SupportedFormats({ extensionVideo, extensionAudio, extensionImag
|
|||||||
<IconDocument /> {t('documents')}
|
<IconDocument /> {t('documents')}
|
||||||
</div>
|
</div>
|
||||||
<div className="format-list">
|
<div className="format-list">
|
||||||
{extensionDocument.join(', ')}
|
{allFilesExtensions?.documents?.join(', ')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -358,7 +358,14 @@
|
|||||||
"payment-history": "Payment history",
|
"payment-history": "Payment history",
|
||||||
"file-verification": "File verification",
|
"file-verification": "File verification",
|
||||||
"file-is-protected": "The file is protected.",
|
"file-is-protected": "The file is protected.",
|
||||||
"read-more": "Read more"
|
"read-more": "Read more",
|
||||||
|
"proxy-enabled": "Proxy enabled",
|
||||||
|
"supported-formats": "Supported formats",
|
||||||
|
"search-stats": "Search stats",
|
||||||
|
"search-protected-content": "Search for protected content",
|
||||||
|
"search-title-description": "Upload a file to search for similar protected content in your library and on the web",
|
||||||
|
"how-does-search-work": "How does search work?",
|
||||||
|
"search-info-description-text": "Our system analyzes the uploaded file and compares it with your protected files, using computer vision and digital fingerprinting algorithms."
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "and",
|
"and": "and",
|
||||||
|
|||||||
@@ -358,7 +358,14 @@
|
|||||||
"payment-history": "История выплат",
|
"payment-history": "История выплат",
|
||||||
"file-verification": "Проверка файла",
|
"file-verification": "Проверка файла",
|
||||||
"file-is-protected": "Файл защищен.",
|
"file-is-protected": "Файл защищен.",
|
||||||
"read-more": "Подробнее"
|
"read-more": "Подробнее",
|
||||||
|
"proxy-enabled": "Прокси включен",
|
||||||
|
"supported-formats": "Поддерживаемые форматы",
|
||||||
|
"search-stats": "Статистика поиска",
|
||||||
|
"search-protected-content": "Поиск защищенного контента",
|
||||||
|
"search-title-description": "Загрузите файл для поиска похожего защищенного контента в вашей библиотеке и в интернете",
|
||||||
|
"how-does-search-work": "Как работает поиск?",
|
||||||
|
"search-info-description-text": "Наша система анализирует загруженный файл и сравнивает его с вашими защищенными файлами, используя алгоритмы компьютерного зрения и цифровых отпечатков."
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "и",
|
"and": "и",
|
||||||
|
|||||||
Reference in New Issue
Block a user