file upload update

This commit is contained in:
smanylov
2025-12-24 21:36:59 +07:00
parent fe77b4ad36
commit 87b8d68f41
9 changed files with 156 additions and 74 deletions
@@ -1,16 +1,16 @@
import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
import TestSection from '@/app/ui/marking-page/test-section';
import FilesTable from '@/app/ui/dashboard/files-table'; import FilesTable from '@/app/ui/dashboard/files-table';
import {useTranslations} from 'next-intl'; import PageTitle from '@/app/ui/page-title';
const ALLOWED_EXTENSIONS: string[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
const FILE_TYPE = "VIDEO"
const MAX_SIZE = 1048576000;
export default function Page() { export default function Page() {
const t = useTranslations('Global');
return ( return (
<div> <div>
<div className="page-title"> <PageTitle title="audio-protection" />
<h1>{t('audio-protection')}</h1>
</div>
<ProtectionSummary /> <ProtectionSummary />
{/* <TestSection /> */} {/* <TestSection /> */}
<FilesTable /> <FilesTable />
@@ -2,21 +2,22 @@ import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
import PageTitle from '@/app/ui/page-title'; import PageTitle from '@/app/ui/page-title';
import UploadSectionFile from '@/app/components/upload-section-file'; import UploadSectionFile from '@/app/components/upload-section-file';
import FilesTable from '@/app/ui/dashboard/files-table'; import FilesTable from '@/app/ui/dashboard/files-table';
import { FileExtension } from '@/app/actions/definitions';
import { getAllowedFilesExtensions } from '@/app/actions/file-upload'; import { getAllowedFilesExtensions } from '@/app/actions/file-upload';
const ALLOWED_EXTENSIONS: FileExtension[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp']; const ALLOWED_EXTENSIONS: string[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
const FILE_TYPE = "IMAGE" const FILE_TYPE = "IMAGE"
const MAX_SIZE = 1048576000;
export default async function Page() { export default async function Page() {
/* const allowedFilesExtensions = await getAllowedFilesExtensions(FILE_TYPE); /* const allowedFilesExtensions = await getAllowedFilesExtensions(FILE_TYPE);
console.log(allowedFilesExtensions); */ console.log(allowedFilesExtensions);
console.log(allowedFilesExtensions); */
return ( return (
<div> <div>
<PageTitle title="image-protection" /> <PageTitle title="image-protection" />
<ProtectionSummary /> <ProtectionSummary />
<UploadSectionFile fileType={FILE_TYPE} allowedExtensions={ALLOWED_EXTENSIONS} /> <UploadSectionFile fileType={FILE_TYPE} allowedExtensions={ALLOWED_EXTENSIONS} maxFileSize={MAX_SIZE} />
{/* <TestSection /> */} {/* <TestSection /> */}
<FilesTable /> <FilesTable />
{/* <ProtectedFilesTable /> */} {/* <ProtectedFilesTable /> */}
@@ -1,20 +1,20 @@
import ProtectionSummary from '@/app/ui/marking-page/protection-summary'; 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 UploadSectionVideo from '@/app/ui/marking-page/upload-section-video';
import FilesTable from '@/app/ui/dashboard/files-table'; import FilesTable from '@/app/ui/dashboard/files-table';
import {useTranslations} from 'next-intl'; import PageTitle from '@/app/ui/page-title';
import UploadSectionFile from '@/app/components/upload-section-file';
const ALLOWED_EXTENSIONS: string[] = ['mp4', 'avi', 'webm', 'mkv', 'flv', 'wmv', 'mov'];
const FILE_TYPE = "VIDEO"
const MAX_SIZE = 1048576000;
export default function Page() { export default function Page() {
const t = useTranslations('Global');
return ( return (
<div> <div>
<div className="page-title"> <PageTitle title="video-protection" />
<h1>{t('video-protection')}</h1>
</div>
<ProtectionSummary /> <ProtectionSummary />
<UploadSectionVideo /> <UploadSectionFile fileType={FILE_TYPE} allowedExtensions={ALLOWED_EXTENSIONS} maxFileSize={MAX_SIZE} />
{/* <TestSection /> */} {/* <TestSection /> */}
<FilesTable /> <FilesTable />
</div> </div>
-2
View File
@@ -78,8 +78,6 @@ export const loginFormSchema = z
export const localDevelopmentUrl = 'http://localhost'; export const localDevelopmentUrl = 'http://localhost';
export type FileExtension = 'jpg' | 'jpeg' | 'png' | 'gif' | 'bmp';
export const testUserData = { export const testUserData = {
fullName: 'test', fullName: 'test',
email: 'test@mail.com', email: 'test@mail.com',
+121 -49
View File
@@ -4,7 +4,6 @@ import React, { useRef, useState, useCallback, ChangeEvent, DragEvent } from 're
import { IconShieldAdd } from '@/app/ui/icons/icons'; import { IconShieldAdd } from '@/app/ui/icons/icons';
import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/file-upload'; import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/file-upload';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { FileExtension } from '@/app/actions/definitions';
interface SelectedFile { interface SelectedFile {
file: File; file: File;
@@ -27,40 +26,79 @@ interface FileUploadInitResponse {
interface UploadSectionFile { interface UploadSectionFile {
fileType: string fileType: string
allowedExtensions: FileExtension[] allowedExtensions: string[]
maxFileSize: number
} }
/* type SupportedFormat = 'image/jpeg' | 'image/png' | 'image/gif' | 'image/bmp'; */ export default function UploadSectionFile({ fileType, allowedExtensions, maxFileSize }: UploadSectionFile) {
export default function UploadSectionFile({ fileType, allowedExtensions }: UploadSectionFile) {
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [isDragging, setIsDragging] = useState<boolean>(false); const [isDragging, setIsDragging] = useState<boolean>(false);
const [selectedFile, setSelectedFile] = useState<SelectedFile | null>(null); const [selectedFile, setSelectedFile] = useState<SelectedFile | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [uploadId, setUploadId] = useState<string | null>(null); const [uploadId, setUploadId] = useState<string | null>(null);
const [isFileUploaded, setIsFileUploaded] = useState<boolean>(false); const [isFileUploaded, setIsFileUploaded] = useState<boolean>(false);
const [uploadProgress, setUploadProgress] = useState<number>(0);
const t = useTranslations('Global'); const t = useTranslations('Global');
console.log('fileType'); let SUPPORTED_FORMATS: string[] = [];
console.log(allowedExtensions);
let SUPPORTED_FORMATS: string[] = []
if (fileType === "IMAGE") { switch (fileType) {
SUPPORTED_FORMATS = [ case 'IMAGE':
'image/jpeg', SUPPORTED_FORMATS = [
'image/png', 'image/jpeg',
'image/gif', 'image/jpg',
'image/bmp' 'image/png',
]; 'image/gif',
'image/bmp',
'image/webp',
'image/svg+xml',
'image/tiff',
'image/x-icon'
];
break;
case 'VIDEO':
SUPPORTED_FORMATS = [
'video/mp4',
'video/mpeg',
'video/ogg',
'video/webm',
'video/quicktime',
'video/x-msvideo',
'video/x-matroska',
'video/x-flv',
'video/3gpp',
'video/3gpp2'
];
break;
case 'AUDIO':
SUPPORTED_FORMATS = [
'audio/mpeg',
'audio/wav',
'audio/wave',
'audio/x-wav',
'audio/x-pn-wav',
'audio/ogg',
'audio/webm',
'audio/aac',
'audio/mp4',
'audio/x-m4a',
'audio/flac',
'audio/x-flac',
'audio/opus'
];
break;
default:
break;
} }
const ALLOWED_EXTENSIONS: FileExtension[] = allowedExtensions; const ALLOWED_EXTENSIONS: string[] = allowedExtensions;
const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => { const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => {
if (!SUPPORTED_FORMATS.includes(file.type as string)) { if (!SUPPORTED_FORMATS.includes(file.type as string)) {
const extension = file.name.split('.').pop()?.toLowerCase(); const extension = file.name.split('.').pop()?.toLowerCase();
if (!extension || !ALLOWED_EXTENSIONS.includes(extension as FileExtension)) { if (!extension || !ALLOWED_EXTENSIONS.includes(extension as string)) {
return { return {
isValid: false, isValid: false,
errorMessage: 'Неподдерживаемый формат файла.' errorMessage: 'Неподдерживаемый формат файла.'
@@ -68,11 +106,12 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
} }
} }
const MAX_SIZE = 100 * 1024 * 1024; const MAX_SIZE = maxFileSize;
if (file.size > MAX_SIZE) { if (file.size > MAX_SIZE) {
return { return {
isValid: false, isValid: false,
errorMessage: 'Файл слишком большой. Максимальный размер: 100MB' errorMessage: 'Файл слишком большой. Максимальный размер: 1000MB'
}; };
} }
@@ -107,23 +146,28 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
setError(null); setError(null);
return; return;
} }
setUploadProgress(0);
setError(null); setError(null);
const validation = validateFile(file); const validation = validateFile(file);
if (!validation.isValid) { if (!validation.isValid) {
setError(validation.errorMessage || 'Неизвестная ошибка валидации'); setError(validation.errorMessage || 'Неизвестная ошибка валидации');
return; return;
} }
try { try {
const dimensions = await getImageDimensions(file); let dimensions = undefined;
if (dimensions.width < 150 || dimensions.height < 150 || if (fileType === "IMAGE") {
dimensions.width > 8000 || dimensions.height > 8000) { dimensions = await getImageDimensions(file) as { width: number, height: number };
setError(`Размер изображения: ${dimensions.width}x${dimensions.height}. Допустимый диапазон: 150x150 - 4000x4000 пикселей.`); if (dimensions.width < 150 || dimensions.height < 150 ||
return; dimensions.width > 10000 || dimensions.height > 10000) {
setError(`Размер изображения: ${dimensions.width}x${dimensions.height}. Допустимый диапазон: 150x150 - 10000x10000 пикселей.`);
return;
}
} }
const objectUrl = URL.createObjectURL(file); const objectUrl = URL.createObjectURL(file);
@@ -133,14 +177,15 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
preview: objectUrl, preview: objectUrl,
name: file.name, name: file.name,
size: `${(file.size / 1024 / 1024).toFixed(2)} MB`, size: `${(file.size / 1024 / 1024).toFixed(2)} MB`,
dimensions ...(dimensions && { dimensions })
}); });
console.log('Файл обработан:', { console.log('Файл обработан:', {
file: file,
name: file.name, name: file.name,
type: file.type, type: file.type,
size: file.size, size: file.size,
dimensions ...(dimensions && { dimensions })
}); });
} catch (err) { } catch (err) {
@@ -192,13 +237,21 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
setIsFileUploaded(false); setIsFileUploaded(false);
setSelectedFile(null); setSelectedFile(null);
setError(null); setError(null);
setUploadProgress(0);
}; };
const handlerFileUpload = async (file: SelectedFile): Promise<any> => { const handlerFileUpload = async (file: SelectedFile): Promise<void> => {
const extension = file.name.split('.').pop();
if (!extension) {
setError(t('file-has-no-extension'));
return;
}
const initMessageBody = { const initMessageBody = {
file_name: file.name, file_name: file.name,
file_type: fileType, file_type: fileType,
extension: file.name.split('.')[1], extension: extension,
file_size: file.file.size file_size: file.file.size
}; };
@@ -239,26 +292,26 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
start = end; start = end;
chunkIndex++; chunkIndex++;
setUploadProgress(
() => {
return Math.floor(chunkIndex / totalChunks * 100);
}
);
} }
} catch (error) { } catch (error) {
setError('Chunk uploaded error ' + error); setError('Chunk uploaded error ' + error);
console.error('Chunk upload error:', error); console.error('Chunk upload error:', error);
} }
} else {
console.log(response);
setError('file uploaded error');
} }
} }
React.useEffect(() => {
return () => {
if (selectedFile) {
URL.revokeObjectURL(selectedFile.preview);
}
};
}, [selectedFile]);
return ( return (
<div className="upload-section"> <div className="upload-section">
<h3> <h3>
{t('download-for-protection', { fileType: t(fileType.toLocaleLowerCase()) })} {t('upload-for-protection', { fileType: t(fileType.toLocaleLowerCase()) })}
</h3> </h3>
<div <div
@@ -349,14 +402,27 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
</div> </div>
</div> </div>
<img {fileType === 'IMAGE' && (
src={selectedFile.preview} <img
alt="Предпросмотр загруженного изображения" src={selectedFile.preview}
className="uploaded-image" alt="Предпросмотр загруженного изображения"
onError={() => setError('Не удалось загрузить превью изображения')} className="uploaded-image"
/> onError={() => setError('Не удалось загрузить превью изображения')}
/>
)}
<div className="mt-4 flex gap-3 justify-center"> {fileType === 'VIDEO' && (
<video
src={selectedFile.preview}
className="uploaded-image"
controls
muted
onError={() => setError('Не удалось загрузить превью видео')}
onLoadedData={() => console.log('Видео загружено')}
/>
)}
<div className="mt-4 flex gap-3 justify-center relative">
<button <button
className="btn btn-cancel" className="btn btn-cancel"
onClick={handleClearFile} onClick={handleClearFile}
@@ -365,11 +431,12 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
{isFileUploaded ? t('close') : t('cancel')} {isFileUploaded ? t('close') : t('cancel')}
</button> </button>
<button <button
className="btn btn-confirm" className={`btn btn-confirm ${error ? 'disabled' : ''}`}
onClick={() => { onClick={() => {
handlerFileUpload(selectedFile); handlerFileUpload(selectedFile);
}} }}
type="button" type="button"
disabled={error ? true : false}
> >
{t('upload-file')} {t('upload-file')}
</button> </button>
@@ -379,15 +446,20 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
onClick={ onClick={
async () => { async () => {
if (uploadId) { if (uploadId) {
console.log('checkChunkStatus') await checkChunkStatus(uploadId);
const response = await checkChunkStatus(uploadId);
console.log(response);
} }
} }
} }
> >
dev-test-button dev-test-button
</button> </button>
{uploadProgress !== 0 && (
<div
className={`absolute top-5 right-5 font-medium ${isFileUploaded ? "text-green-600" : ""}`}
>
{uploadProgress}%
</div>
)}
</div> </div>
</div> </div>
)} )}
+8
View File
@@ -517,6 +517,14 @@
min-width: 160px; min-width: 160px;
transform: translateY(0px); transform: translateY(0px);
box-shadow: none; box-shadow: none;
&.disabled {
opacity: 0.5;
&:hover {
transform: none;
}
}
} }
.btn-cancel { .btn-cancel {
@@ -1,4 +1,5 @@
'use client'; 'use client';
/* removed */
import { useState, useRef } from 'react'; import { useState, useRef } from 'react';
+4 -3
View File
@@ -17,7 +17,7 @@
"checks": "Checks", "checks": "Checks",
"check": "Checks", "check": "Checks",
"videos": "Videos", "videos": "Videos",
"video": "vides", "video": "videos",
"audios": "Audio", "audios": "Audio",
"audio": "audio", "audio": "audio",
"images": "Images", "images": "Images",
@@ -126,8 +126,9 @@
"or": "or", "or": "or",
"drag-the-file-here": "Drag the file here", "drag-the-file-here": "Drag the file here",
"select-files-to-protect": "Select files to protect", "select-files-to-protect": "Select files to protect",
"download-for-protection": "Download {fileType} for protection", "upload-for-protection": "Upload {fileType} for protection",
"image": "image" "image": "image",
"file-has-no-extension": "File has no extension"
}, },
"Login-register-form": { "Login-register-form": {
"and": "and", "and": "and",
+3 -2
View File
@@ -126,8 +126,9 @@
"or": "или", "or": "или",
"drag-the-file-here": "Перетащите файл сюда", "drag-the-file-here": "Перетащите файл сюда",
"select-files-to-protect": "Выбрать файлы для защиты", "select-files-to-protect": "Выбрать файлы для защиты",
"download-for-protection": "Загрузить {fileType} для защиты", "upload-for-protection": "Загрузить {fileType} для защиты",
"image": "изображений" "image": "изображений",
"file-has-no-extension": "Файл не имеет расширения"
}, },
"Login-register-form": { "Login-register-form": {
"and": "и", "and": "и",