pokedex-web / frontend /src /components /UploadZone.js
gpimentel's picture
feat: implement anomaly detection ensemble, feedback system, and upgraded model configuration
d56d352
Raw
History Blame Contribute Delete
5.12 kB
'use client';
import { useState, useRef } from 'react';
import styles from './UploadZone.module.css';
const resizeImage = (file, maxSize = 512) => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
let { width, height } = img;
if (width > height) {
if (width > maxSize) {
height = Math.round((height * maxSize) / width);
width = maxSize;
}
} else {
if (height > maxSize) {
width = Math.round((width * maxSize) / height);
height = maxSize;
}
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob((blob) => {
resolve({ blob, preview: URL.createObjectURL(blob) });
}, 'image/png');
};
img.src = URL.createObjectURL(file);
});
};
export default function UploadZone({ status, selectedImage, onImageSelect, onSubmit, errorMessage }) {
const [isDragging, setIsDragging] = useState(false);
const fileInputRef = useRef(null);
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
};
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
if (!e.currentTarget.contains(e.relatedTarget)) {
setIsDragging(false);
}
};
const processFile = async (file) => {
if (!file) return;
const allowed = ['image/png', 'image/jpeg', 'image/webp', 'image/gif'];
if (!allowed.includes(file.type)) {
onImageSelect(null, "Invalid file type. Use PNG, JPEG, WebP, or GIF.");
return;
}
if (file.size > 10 * 1024 * 1024) {
onImageSelect(null, "File is too large. Max 10MB allowed.");
return;
}
try {
const resized = await resizeImage(file, 512);
onImageSelect(resized.blob, null, resized.preview);
} catch (err) {
onImageSelect(null, "Failed to process image.");
}
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
processFile(e.dataTransfer.files[0]);
} else if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
const item = e.dataTransfer.items[0];
if (item.kind === 'file') {
const file = item.getAsFile();
processFile(file);
}
}
if (e.dataTransfer.clearData) {
e.dataTransfer.clearData();
}
};
const handleFileChange = (e) => {
if (e.target.files && e.target.files.length > 0) {
processFile(e.target.files[0]);
}
};
if (status === 'loading' || status === 'success') {
return null;
}
return (
<div className={styles.uploadContainer}>
<div
className={`glass-panel ${styles.dropZone} ${isDragging ? styles.dragging : ''} ${errorMessage ? styles.errorShake : ''}`}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => !selectedImage && fileInputRef.current?.click()}
>
<input
type="file"
ref={fileInputRef}
hidden
accept="image/png, image/jpeg, image/webp, image/gif"
onChange={handleFileChange}
/>
{selectedImage ? (
<div className={styles.previewContainer}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={selectedImage.preview} alt="Preview" className={styles.previewImage} />
<div className={styles.actions}>
<button
className={styles.resetBtn}
onClick={(e) => { e.stopPropagation(); onImageSelect(null, null, null); }}
>
✕ Try another image
</button>
</div>
</div>
) : (
<div className={styles.promptContent}>
<div className={styles.icon}>
<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
</div>
<h3>Drop image here</h3>
<p className={styles.hint}>or click to upload</p>
</div>
)}
</div>
{errorMessage && (
<div className={styles.errorMessage}>{errorMessage}</div>
)}
{selectedImage && (
<button
className={`btn glow-primary ${styles.submitBtn}`}
onClick={onSubmit}
>
Identify Pokémon!
</button>
)}
</div>
);
}