deckhearth/components/UploadImageModal.js
varutasu 309cfa238a
fix(lint): clear ESLint baseline in components/ (#61)
Resolve react-hooks purity, immutability, refs, and set-state-in-effect
violations without behavior changes; align img usage with pages/ disable
pattern for external URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 01:03:36 -05:00

199 lines
No EOL
7.3 KiB
JavaScript

/* eslint-disable @next/next/no-img-element -- Local preview data URLs; next/image migration is out of scope. */
import { useState } from 'react';
export default function UploadImageModal({ isOpen, onClose, onUpload, currentImage }) {
const [uploadMethod, setUploadMethod] = useState('url'); // 'url' or 'file'
const [imageUrl, setImageUrl] = useState(currentImage || '');
const [dragActive, setDragActive] = useState(false);
const [uploading, setUploading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
if (!imageUrl.trim()) return;
setUploading(true);
try {
await onUpload(imageUrl);
onClose();
} catch (error) {
console.error('Upload failed:', error);
} finally {
setUploading(false);
}
};
const handleDrag = (e) => {
e.preventDefault();
e.stopPropagation();
if (e.type === "dragenter" || e.type === "dragover") {
setDragActive(true);
} else if (e.type === "dragleave") {
setDragActive(false);
}
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
handleFileUpload(e.dataTransfer.files[0]);
}
};
const handleFileUpload = (file) => {
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => {
setImageUrl(e.target.result);
setUploadMethod('file');
};
reader.readAsDataURL(file);
}
};
const handleFileInput = (e) => {
if (e.target.files && e.target.files[0]) {
handleFileUpload(e.target.files[0]);
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-xl shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto">
<div className="p-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold text-gray-900">Upload Hero Image</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 transition-colors"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<form onSubmit={handleSubmit}>
{/* Upload Method Tabs */}
<div className="flex space-x-1 mb-6 bg-gray-100 rounded-lg p-1">
<button
type="button"
onClick={() => setUploadMethod('url')}
className={`flex-1 py-2 px-3 rounded-md text-sm font-medium transition-colors ${
uploadMethod === 'url'
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Image URL
</button>
<button
type="button"
onClick={() => setUploadMethod('file')}
className={`flex-1 py-2 px-3 rounded-md text-sm font-medium transition-colors ${
uploadMethod === 'file'
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Upload File
</button>
</div>
{uploadMethod === 'url' ? (
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Image URL
</label>
<input
type="url"
value={imageUrl}
onChange={(e) => setImageUrl(e.target.value)}
placeholder="https://example.com/image.jpg"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
</div>
) : (
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Upload Image
</label>
<div
className={`border-2 border-dashed rounded-lg p-6 text-center transition-colors ${
dragActive
? 'border-purple-500 bg-purple-50'
: 'border-gray-300 hover:border-gray-400'
}`}
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={handleDrop}
>
<svg className="w-12 h-12 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<p className="text-gray-600 mb-2">Drag and drop an image here, or</p>
<label className="inline-block px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 cursor-pointer transition-colors">
Choose File
<input
type="file"
accept="image/*"
onChange={handleFileInput}
className="hidden"
/>
</label>
<p className="text-xs text-gray-500 mt-2">PNG, JPG, GIF up to 10MB</p>
</div>
</div>
)}
{/* Preview */}
{imageUrl && (
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Preview
</label>
<div className="relative rounded-lg overflow-hidden bg-gray-100">
<img
src={imageUrl}
alt="Preview"
className="w-full h-32 object-cover"
onError={(e) => {
e.target.src = 'https://via.placeholder.com/400x128/f3f4f6/6b7280?text=Invalid+Image';
}}
/>
</div>
</div>
)}
{/* Actions */}
<div className="flex space-x-3">
<button
type="button"
onClick={onClose}
className="flex-1 py-2 px-4 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={!imageUrl.trim() || uploading}
className={`flex-1 py-2 px-4 rounded-lg font-medium transition-colors ${
!imageUrl.trim() || uploading
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-purple-600 text-white hover:bg-purple-700'
}`}
>
{uploading ? 'Uploading...' : 'Upload Image'}
</button>
</div>
</form>
</div>
</div>
</div>
);
}