add string pixels
This commit is contained in:
@@ -340,7 +340,7 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
|
|||||||
|
|
||||||
{fileType === "image" && (
|
{fileType === "image" && (
|
||||||
<p className="description">
|
<p className="description">
|
||||||
{t('image-resolution')}: 100x100 - 10000x10000
|
{t('image-resolution')}: 100x100 - 10000x10000 {t('pixels')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p className="description">
|
<p className="description">
|
||||||
|
|||||||
@@ -1,278 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import React, { useRef, useState, useCallback, ChangeEvent, DragEvent } from 'react';
|
|
||||||
import {IconShieldAdd} from '@/app/ui/icons/icons';
|
|
||||||
|
|
||||||
interface SelectedFile {
|
|
||||||
file: File;
|
|
||||||
preview: string;
|
|
||||||
name: string;
|
|
||||||
size: string;
|
|
||||||
dimensions?: {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type SupportedFormat = 'image/jpeg' | 'image/png' | 'image/gif' | 'image/bmp';
|
|
||||||
type FileExtension = 'jpg' | 'jpeg' | 'png' | 'gif' | 'bmp';
|
|
||||||
|
|
||||||
export default function UploadSectionImage() {
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
|
||||||
const [selectedFile, setSelectedFile] = useState<SelectedFile | null>(null);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const SUPPORTED_FORMATS: SupportedFormat[] = [
|
|
||||||
'image/jpeg',
|
|
||||||
'image/png',
|
|
||||||
'image/gif',
|
|
||||||
'image/bmp'
|
|
||||||
];
|
|
||||||
|
|
||||||
const ALLOWED_EXTENSIONS: FileExtension[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
|
|
||||||
|
|
||||||
|
|
||||||
const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => {
|
|
||||||
if (!SUPPORTED_FORMATS.includes(file.type as SupportedFormat)) {
|
|
||||||
const extension = file.name.split('.').pop()?.toLowerCase();
|
|
||||||
if (!extension || !ALLOWED_EXTENSIONS.includes(extension as FileExtension)) {
|
|
||||||
return {
|
|
||||||
isValid: false,
|
|
||||||
errorMessage: 'Неподдерживаемый формат файла.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAX_SIZE = 10 * 1024 * 1024;
|
|
||||||
if (file.size > MAX_SIZE) {
|
|
||||||
return {
|
|
||||||
isValid: false,
|
|
||||||
errorMessage: 'Файл слишком большой. Максимальный размер: 10MB'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { isValid: true };
|
|
||||||
};
|
|
||||||
|
|
||||||
const getImageDimensions = (file: File): Promise<{ width: number; height: number }> => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const img = new Image();
|
|
||||||
const objectUrl = URL.createObjectURL(file);
|
|
||||||
|
|
||||||
img.onload = () => {
|
|
||||||
const dimensions = {
|
|
||||||
width: img.width,
|
|
||||||
height: img.height
|
|
||||||
};
|
|
||||||
URL.revokeObjectURL(objectUrl);
|
|
||||||
resolve(dimensions);
|
|
||||||
};
|
|
||||||
|
|
||||||
img.onerror = () => {
|
|
||||||
URL.revokeObjectURL(objectUrl);
|
|
||||||
reject(new Error('Не удалось загрузить изображение'));
|
|
||||||
};
|
|
||||||
|
|
||||||
img.src = objectUrl;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFileSelect = useCallback(async (file: File | null): Promise<void> => {
|
|
||||||
if (!file) {
|
|
||||||
setError(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
const validation = validateFile(file);
|
|
||||||
if (!validation.isValid) {
|
|
||||||
setError(validation.errorMessage || 'Неизвестная ошибка валидации');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const dimensions = await getImageDimensions(file);
|
|
||||||
|
|
||||||
if (dimensions.width < 150 || dimensions.height < 150 ||
|
|
||||||
dimensions.width > 4000 || dimensions.height > 4000) {
|
|
||||||
setError(`Размер изображения: ${dimensions.width}x${dimensions.height}. Допустимый диапазон: 150x150 - 4000x4000 пикселей.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const objectUrl = URL.createObjectURL(file);
|
|
||||||
|
|
||||||
setSelectedFile({
|
|
||||||
file,
|
|
||||||
preview: objectUrl,
|
|
||||||
name: file.name,
|
|
||||||
size: `${(file.size / 1024 / 1024).toFixed(2)} MB`,
|
|
||||||
dimensions
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Файл обработан:', {
|
|
||||||
name: file.name,
|
|
||||||
type: file.type,
|
|
||||||
size: file.size,
|
|
||||||
dimensions
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : 'Ошибка при обработке файла');
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleFileInputChange = (event: ChangeEvent<HTMLInputElement>): void => {
|
|
||||||
const file = event.target.files?.[0] || null;
|
|
||||||
handleFileSelect(file);
|
|
||||||
|
|
||||||
if (fileInputRef.current) {
|
|
||||||
fileInputRef.current.value = '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleButtonClick = (): void => {
|
|
||||||
if (fileInputRef.current) {
|
|
||||||
fileInputRef.current.click();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDragOver = (event: DragEvent<HTMLDivElement>): void => {
|
|
||||||
event.preventDefault();
|
|
||||||
setIsDragging(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDragLeave = (): void => {
|
|
||||||
setIsDragging(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDrop = (event: DragEvent<HTMLDivElement>): void => {
|
|
||||||
event.preventDefault();
|
|
||||||
setIsDragging(false);
|
|
||||||
|
|
||||||
const file = event.dataTransfer.files[0];
|
|
||||||
handleFileSelect(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClearFile = (): void => {
|
|
||||||
if (selectedFile) {
|
|
||||||
URL.revokeObjectURL(selectedFile.preview);
|
|
||||||
}
|
|
||||||
setSelectedFile(null);
|
|
||||||
setError(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (selectedFile) {
|
|
||||||
URL.revokeObjectURL(selectedFile.preview);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [selectedFile]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="upload-section">
|
|
||||||
<h3>Загрузить изображение для защиты</h3>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`drag-drop-zone ${isDragging ? 'dragging' : ''}`}
|
|
||||||
onDragOver={handleDragOver}
|
|
||||||
onDragLeave={handleDragLeave}
|
|
||||||
onDrop={handleDrop}
|
|
||||||
style={{
|
|
||||||
border: isDragging ? '2px dashed #3b82f6' : '2px dashed #d1d5db',
|
|
||||||
backgroundColor: isDragging ? '#eff6ff' : '#f9fafb',
|
|
||||||
padding: '2rem',
|
|
||||||
borderRadius: '.75rem',
|
|
||||||
textAlign: 'center',
|
|
||||||
transition: 'all .2s ease-in-out'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconShieldAdd />
|
|
||||||
<h4>Перетащите изображения сюда</h4>
|
|
||||||
<p className="mb-2.5 text-[#6b7280]">или</p>
|
|
||||||
<input
|
|
||||||
ref={fileInputRef}
|
|
||||||
type="file"
|
|
||||||
accept=".jpg,.jpeg,.png,.gif,.bmp"
|
|
||||||
style={{ display: 'none' }}
|
|
||||||
onChange={handleFileInputChange}
|
|
||||||
aria-label="Выбор файла для защиты"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={handleButtonClick}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Выбрать файлы для защиты
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<p className="description">
|
|
||||||
Разрешение изображение: 100x100 - 10000x10000 пикселей
|
|
||||||
</p>
|
|
||||||
<p className="description">
|
|
||||||
Размер файла: до 20 МБ
|
|
||||||
</p>
|
|
||||||
<p className="description">
|
|
||||||
Формат файла: JPG, PNG, GIF, BMP (WEBP НЕ поддерживается)
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg">
|
|
||||||
<p className="text-red-600 font-medium">❌ {error}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedFile && (
|
|
||||||
<div
|
|
||||||
className="selected-file"
|
|
||||||
>
|
|
||||||
<div className="selected-file-file-info">
|
|
||||||
<p className="text-gray-700">
|
|
||||||
<span className="font-medium">Файл:</span> {selectedFile.name}
|
|
||||||
</p>
|
|
||||||
<p className="text-gray-700">
|
|
||||||
<span className="font-medium">Размер:</span> {selectedFile.size}
|
|
||||||
</p>
|
|
||||||
<p className="text-gray-700">
|
|
||||||
<span className="font-medium">Стоимость защиты:</span> 0
|
|
||||||
</p>
|
|
||||||
<p className="text-gray-700">
|
|
||||||
<span className="font-medium">Текущий баланс:</span> 0
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<img
|
|
||||||
src={selectedFile.preview}
|
|
||||||
alt="Предпросмотр загруженного изображения"
|
|
||||||
className="uploaded-image"
|
|
||||||
onError={() => setError('Не удалось загрузить превью изображения')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mt-4 flex gap-3 justify-center">
|
|
||||||
<button
|
|
||||||
className="btn btn-cancel"
|
|
||||||
onClick={handleClearFile}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Отмена
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-confirm"
|
|
||||||
onClick={() => {
|
|
||||||
console.log('Обработка файла:', selectedFile.file);
|
|
||||||
console.log('Файл отправлен на защиту!');
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Подтвердить защиту
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user