"use client" import { useState, useRef, DragEvent } from "react" import { useRouter } from "next/navigation" interface PhotoData { file: File | null url: string answerName: string points: string penaltyEnabled: boolean penaltyPoints: string maxAttempts: string preview: string | null } export default function UploadPage() { const router = useRouter() const fileInputRef = useRef(null) const [photos, setPhotos] = useState([]) const [error, setError] = useState("") const [success, setSuccess] = useState("") const [loading, setLoading] = useState(false) const [isDragging, setIsDragging] = useState(false) const handleDragOver = (e: DragEvent) => { e.preventDefault() e.stopPropagation() setIsDragging(true) } const handleDragLeave = (e: DragEvent) => { e.preventDefault() e.stopPropagation() setIsDragging(false) } const handleDrop = (e: DragEvent) => { e.preventDefault() e.stopPropagation() setIsDragging(false) const files = Array.from(e.dataTransfer.files).filter((file) => file.type.startsWith("image/") ) if (files.length === 0) { setError("Please drop image files") return } handleFiles(files) } const handleFileInputChange = (e: React.ChangeEvent) => { const files = Array.from(e.target.files || []) if (files.length > 0) { handleFiles(files) } } const handleFiles = (files: File[]) => { const newPhotos: PhotoData[] = [] files.forEach((file) => { if (file.size > 10 * 1024 * 1024) { setError(`File ${file.name} is too large (max 10MB)`) return } const reader = new FileReader() reader.onloadend = () => { const photoData: PhotoData = { file, url: "", answerName: "", points: "1", penaltyEnabled: false, penaltyPoints: "0", maxAttempts: "", preview: reader.result as string, } newPhotos.push(photoData) // Update state when all files are processed if (newPhotos.length === files.length) { setPhotos((prev) => [...prev, ...newPhotos]) setError("") } } reader.readAsDataURL(file) }) } const updatePhoto = (index: number, updates: Partial) => { setPhotos((prev) => prev.map((photo, i) => (i === index ? { ...photo, ...updates } : photo)) ) } const removePhoto = (index: number) => { setPhotos((prev) => prev.filter((_, i) => i !== index)) } const addUrlPhoto = () => { setPhotos((prev) => [ ...prev, { file: null, url: "", answerName: "", points: "1", penaltyEnabled: false, penaltyPoints: "0", maxAttempts: "", preview: null, }, ]) } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError("") setSuccess("") // Validate all photos have required fields for (let i = 0; i < photos.length; i++) { const photo = photos[i] if (!photo.answerName.trim()) { setError(`Photo ${i + 1}: Answer name is required`) return } if (!photo.file && !photo.url) { setError(`Photo ${i + 1}: Please provide a file or URL`) return } } if (photos.length === 0) { setError("Please add at least one photo") return } setLoading(true) try { const formData = new FormData() // Add all photos data with indexed keys to preserve order photos.forEach((photo, index) => { if (photo.file) { formData.append(`photo_${index}_file`, photo.file) } else if (photo.url) { formData.append(`photo_${index}_url`, photo.url) } formData.append(`photo_${index}_answerName`, photo.answerName.trim()) formData.append(`photo_${index}_points`, photo.points) // Auto-enable penalty if penaltyPoints has a value > 0 const penaltyPointsValue = parseInt(photo.penaltyPoints || "0", 10) formData.append(`photo_${index}_penaltyEnabled`, penaltyPointsValue > 0 ? "true" : "false") formData.append(`photo_${index}_penaltyPoints`, photo.penaltyPoints || "0") formData.append(`photo_${index}_maxAttempts`, photo.maxAttempts || "") }) formData.append("count", photos.length.toString()) const response = await fetch("/api/photos/upload-multiple", { method: "POST", body: formData, }) const data = await response.json() if (!response.ok) { setError(data.error || "Failed to upload photos") } else { setSuccess( `Successfully uploaded ${data.photos.length} photo(s)! Emails sent to all users.` ) setPhotos([]) if (fileInputRef.current) { fileInputRef.current.value = "" } setTimeout(() => { router.push("/photos") }, 2000) } } catch { setError("An error occurred. Please try again.") } finally { setLoading(false) } } return (

Upload Photos

{/* File Upload Section */}
{/* Drag and Drop Area */}

or drag and drop multiple images

PNG, JPG, GIF up to 10MB each

{/* Add URL Photo Button */}
{/* Photos List */} {photos.length > 0 && (

Photos ({photos.length})

{photos.map((photo, index) => (

Photo {index + 1}

{/* Preview */} {photo.preview && (
{/* eslint-disable-next-line @next/next/no-img-element */} {`Preview
)} {/* URL Input (if no file) */} {!photo.file && (
updatePhoto(index, { url: e.target.value }) } placeholder="https://example.com/photo.jpg" className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white text-gray-900 focus:outline-none focus:ring-purple-500 focus:border-purple-500" />
)} {/* Answer Name */}
updatePhoto(index, { answerName: e.target.value }) } placeholder="Enter the correct answer" className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white text-gray-900 focus:outline-none focus:ring-purple-500 focus:border-purple-500" />
{/* Points */}
updatePhoto(index, { points: e.target.value }) } placeholder="1" className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white text-gray-900 focus:outline-none focus:ring-purple-500 focus:border-purple-500" />
{/* Penalty Points */}
updatePhoto(index, { penaltyPoints: e.target.value }) } placeholder="0" className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white text-gray-900 focus:outline-none focus:ring-purple-500 focus:border-purple-500" />

Leave empty or set to 0 to disable point deduction. If set to a value greater than 0, users will lose these points for each incorrect guess.

{/* Max Attempts */}
updatePhoto(index, { maxAttempts: e.target.value }) } placeholder="Unlimited (leave empty or 0)" className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-white text-gray-900 focus:outline-none focus:ring-purple-500 focus:border-purple-500" />

Maximum number of guesses allowed per user. Leave empty or 0 for unlimited attempts.

))}
)} {error && (

{error}

)} {success && (

{success}

)}
) }