diff --git a/src/app/actions/fileUpload.ts b/src/app/actions/fileUpload.ts index fcfa5d3..e5b25d9 100644 --- a/src/app/actions/fileUpload.ts +++ b/src/app/actions/fileUpload.ts @@ -12,7 +12,7 @@ interface initMessageBody { file_size: number | string, action?: string, token?: string, - convertTo? : string | undefined | null + convertTo?: string | undefined | null } export async function fileUpload(messageBody: initMessageBody, convertTo?: string | null | undefined) { @@ -228,4 +228,49 @@ export async function getAllowedFilesExtensions(type: string) { } catch (error) { return error } +} + +export async function checkOperationPrice(action: string, filesCount: number, fileType: string) { + const token = await getSessionData('token'); + + try { + const response = await fetch(`${API_BASE_URL}/api/v1/data`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30008, + message_body: { + auth_token: token, + action: action, + file_type: fileType, + count_files: filesCount + } + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (response.ok) { + let parsed = await response.json(); + console.log(parsed); + + if (parsed.message_code === 0) { + return parsed.message_body; + } else { + throw parsed; + } + } else { + throw (`${response.status}`); + } + + } catch (error) { + return { + cost: 0, + success: false, + tokens_count: 0, + error: 'error' + } + } } \ No newline at end of file diff --git a/src/app/components/UploadSectionFile.tsx b/src/app/components/UploadSectionFile.tsx index e558f27..f3ab5e8 100644 --- a/src/app/components/UploadSectionFile.tsx +++ b/src/app/components/UploadSectionFile.tsx @@ -2,7 +2,7 @@ import { useRef, useState, useCallback, ChangeEvent, DragEvent, useEffect, useMemo } from 'react'; import { IconShieldAdd } from '@/app/ui/icons/icons'; -import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/fileUpload'; +import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus, checkOperationPrice } from '@/app/actions/fileUpload'; import { useTranslations } from 'next-intl'; import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker'; import { useQueryClient } from '@tanstack/react-query'; @@ -113,8 +113,16 @@ export default function UploadSectionFile({ setError(null); const newFiles: SelectedFile[] = []; + const selectedFileNames = new Set(selectedFiles.map(f => f.name)); + const currentFileNames = new Set(); + const duplicateNames: string[] = []; for (const file of files) { + if (selectedFileNames.has(file.name)) { + duplicateNames.push(file.name); + continue; + } + const validation = validateFile(file); if (!validation.isValid) { @@ -141,6 +149,8 @@ export default function UploadSectionFile({ } } + currentFileNames.add(file.name); + newFiles.push({ file, name: file.name, @@ -152,8 +162,13 @@ export default function UploadSectionFile({ }); } + if (duplicateNames.length > 0) { + const uniqueDuplicates = [...new Set(duplicateNames)]; + setError(`${t('duplicate-files')}: ${uniqueDuplicates.join(', ')}`); + } + setSelectedFiles(prev => [...prev, ...newFiles]); - }, [fileType, t]); + }, [fileType, selectedFiles]); const handleFileInputChange = (event: ChangeEvent): void => { const files = Array.from(event.target.files || []); @@ -204,6 +219,7 @@ export default function UploadSectionFile({ f.uploadId !== fileId && f.name !== fileId )); } else { + console.log('clear all') selectedFiles.forEach(file => { if (file.preview) { URL.revokeObjectURL(file.preview); @@ -223,10 +239,21 @@ export default function UploadSectionFile({ setError(null); }; - const uploadSingleFile = async (fileInfo: SelectedFile): Promise => { + const uploadSingleFile = async (fileInfo: SelectedFile, uploadType: 'single' | 'multi'): Promise => { const fileId = `${fileInfo.name}-${Date.now()}`; isCancelledRef.current.set(fileId, false); + if (uploadType === 'single') { + setError(null); + const priceCheck = await checkOperationPrice('checkTokensForProtect', 1, fileType); + + if (!priceCheck.success) { + const errorMessage = `${t('there-are-not-enough-funds-to-protect-file', { count: priceCheck?.tokens_count })}`; + setError(errorMessage); + return; + } + } + try { setSelectedFiles(prev => prev.map(f => f.name === fileInfo.name ? { ...f, status: 'uploading' } : f @@ -315,20 +342,31 @@ export default function UploadSectionFile({ }, [selectedFiles]); const handleStartAllUploads = useCallback(async (): Promise => { - const uploadNextBatch = async () => { - const currentPending = selectedFilesRef.current.filter(f => f.status === 'pending'); - const batch = currentPending.slice(0, maxParallelUploads); + setError(null); + if (selectedFilesRef.current.length === 0) { + return; + } + const priceCheck = await checkOperationPrice('checkTokensForProtect', selectedFilesRef.current.length, fileType); - if (batch.length === 0) return; + if (priceCheck.success) { + const uploadNextBatch = async () => { + const currentPending = selectedFilesRef.current.filter(f => f.status === 'pending'); + const batch = currentPending.slice(0, maxParallelUploads); - await Promise.all(batch.map(file => uploadSingleFile(file))); + if (batch.length === 0) return; - if (selectedFilesRef.current.some(f => f.status === 'pending')) { - await uploadNextBatch(); - } - }; + await Promise.all(batch.map(file => uploadSingleFile(file, 'multi'))); - await uploadNextBatch(); + if (selectedFilesRef.current.some(f => f.status === 'pending')) { + await uploadNextBatch(); + } + }; + + await uploadNextBatch(); + } else { + const errorMessage = `${t('there-are-not-enough-funds-to-protect-files', { count: priceCheck?.tokens_count })}`; + setError(errorMessage); + } }, [maxParallelUploads]); const getTotalProgress = useMemo(() => { @@ -399,7 +437,6 @@ export default function UploadSectionFile({ })); } - return (

@@ -607,7 +644,7 @@ export default function UploadSectionFile({