2026-06-02 02:03:36 -04:00
|
|
|
/* eslint-disable @next/next/no-img-element -- Local preview data URLs; next/image migration is out of scope. */
|
2025-07-25 23:28:52 -04:00
|
|
|
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>
|
|
|
|
|
);
|
|
|
|
|
}
|