add search setting interface
This commit is contained in:
@@ -1,36 +1,18 @@
|
||||
import SectionSearchFile from '@/app/ui/search/section-search-file';
|
||||
import { SupportedFormats } from '@/app/ui/search/supported-formats';
|
||||
import { SearchStats } from '@/app/ui/search/search-stats';
|
||||
import { RecentSearches } from '@/app/ui/search/recent-searches';
|
||||
import { getAllowedFilesExtensions } from '@/app/actions/fileUpload';
|
||||
import SearchTitle from '@/app/ui/search/search-title';
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div className="block-wrapper">
|
||||
<h1 className="page-title">
|
||||
Поиск защищенного контента
|
||||
</h1>
|
||||
<p className="page-description">
|
||||
Загрузите файл для поиска похожего защищенного контента в вашей библиотеке и в интернете
|
||||
</p>
|
||||
</div>
|
||||
<SearchTitle />
|
||||
<div className="search-grid">
|
||||
<SectionSearchFile />
|
||||
<div className="search-sidebar">
|
||||
<SupportedFormats
|
||||
extensionVideo={extensionVideo}
|
||||
extensionAudio={extensionAudio}
|
||||
extensionImage={extensionImage}
|
||||
extensionDocument={extensionDocument}
|
||||
/>
|
||||
<SupportedFormats />
|
||||
<SearchStats />
|
||||
{/* <RecentSearches /> */}
|
||||
</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';
|
||||
|
||||
export interface UseViolationStatistic {
|
||||
total_violations: 2565,
|
||||
new_violations: 2565,
|
||||
in_progress_violations: 0,
|
||||
resolved_violations: 0
|
||||
total_violations: number,
|
||||
new_violations: number,
|
||||
in_progress_violations: number,
|
||||
resolved_violations: number
|
||||
}
|
||||
|
||||
export const useViolationStatistic = () => {
|
||||
|
||||
@@ -736,4 +736,49 @@
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
|
||||
/* 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 (
|
||||
<header className="header">
|
||||
<BurgerMenu />
|
||||
<LocalFileCheck />
|
||||
{/* <LocalFileCheck /> */}
|
||||
<div className="header-action">
|
||||
{/* <LanguageSwitcher /> */}
|
||||
{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 t = useTranslations('Global');
|
||||
|
||||
useEffect(() => {
|
||||
console.log(userSearchData);
|
||||
}, [userSearchData])
|
||||
|
||||
return (
|
||||
<div className="stats-search">
|
||||
<div className="stats-title">
|
||||
Статистика поиска
|
||||
{t('search-stats')}
|
||||
</div>
|
||||
|
||||
<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 { getFileType } from '@/app/lib/getFileType';
|
||||
import { useAllFilesExtensions } from '@/app/hooks/react-query/useAllFilesExtensions';
|
||||
import SearchSettingsBlock from '@/app/ui/search/search-settings-block';
|
||||
interface SelectedFile {
|
||||
file: File;
|
||||
preview: string | undefined;
|
||||
@@ -287,13 +288,16 @@ export default function SectionSearchFile() {
|
||||
return (
|
||||
<div className="upload-section">
|
||||
<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">
|
||||
Наша система анализирует загруженный файл и сравнивает его с вашими защищенными файлами,
|
||||
используя алгоритмы компьютерного зрения и цифровых отпечатков.
|
||||
{t('search-info-description-text')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SearchSettingsBlock />
|
||||
|
||||
<div
|
||||
className={`drag-drop-zone ${isDragging ? 'dragging' : ''}`}
|
||||
onDragOver={handleDragOver}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import { IconImageFile, IconVideoFile, IconAudioFile, IconDocument } from '@/app/ui/icons/icons';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useAllFilesExtensions } from '@/app/hooks/react-query/useAllFilesExtensions';
|
||||
|
||||
interface supportedFormats {
|
||||
extensionVideo: string[]
|
||||
extensionAudio: string[]
|
||||
extensionImage: string[]
|
||||
extensionDocument: string[]
|
||||
}
|
||||
|
||||
export function SupportedFormats({ extensionVideo, extensionAudio, extensionImage, extensionDocument }: supportedFormats) {
|
||||
export function SupportedFormats() {
|
||||
const t = useTranslations('Global');
|
||||
|
||||
const { data: allFilesExtensions, isLoading, isError } = useAllFilesExtensions();
|
||||
|
||||
return (
|
||||
<div className="supported-formats">
|
||||
<div className="formats-title">
|
||||
Поддерживаемые форматы
|
||||
{t('supported-formats')}
|
||||
</div>
|
||||
|
||||
<div className="format-group">
|
||||
@@ -22,7 +20,7 @@ export function SupportedFormats({ extensionVideo, extensionAudio, extensionImag
|
||||
<IconImageFile /> {t('images')}
|
||||
</div>
|
||||
<div className="format-list">
|
||||
{extensionImage.join(', ')}
|
||||
{allFilesExtensions?.images?.join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +29,7 @@ export function SupportedFormats({ extensionVideo, extensionAudio, extensionImag
|
||||
<IconVideoFile /> {t('videos')}
|
||||
</div>
|
||||
<div className="format-list">
|
||||
{extensionVideo.join(', ')}
|
||||
{allFilesExtensions?.videos?.join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +38,7 @@ export function SupportedFormats({ extensionVideo, extensionAudio, extensionImag
|
||||
<IconAudioFile /> {t('audios')}
|
||||
</div>
|
||||
<div className="format-list">
|
||||
{extensionAudio.join(', ')}
|
||||
{allFilesExtensions?.audios?.join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +47,7 @@ export function SupportedFormats({ extensionVideo, extensionAudio, extensionImag
|
||||
<IconDocument /> {t('documents')}
|
||||
</div>
|
||||
<div className="format-list">
|
||||
{extensionDocument.join(', ')}
|
||||
{allFilesExtensions?.documents?.join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -358,7 +358,14 @@
|
||||
"payment-history": "Payment history",
|
||||
"file-verification": "File verification",
|
||||
"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": {
|
||||
"and": "and",
|
||||
|
||||
@@ -358,7 +358,14 @@
|
||||
"payment-history": "История выплат",
|
||||
"file-verification": "Проверка файла",
|
||||
"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": {
|
||||
"and": "и",
|
||||
|
||||
Reference in New Issue
Block a user