Files
euchre_camp/src/components/EditTournamentForm.tsx
T
david 5763534e26 fix: check response.ok before parsing JSON in fetch calls
Fix JSON parsing errors when server returns non-JSON responses:
- Check response.ok before calling response.json()
- Add fallback error messages using status text
- Apply fix to all fetch calls across 12 components

This prevents 'JSON.parse: unexpected character' errors when
server returns HTML error pages or other non-JSON responses.
2026-04-26 16:44:47 -07:00

261 lines
8.8 KiB
TypeScript

"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import Link from "next/link"
import type { Event } from "@prisma/client"
interface EditTournamentFormProps {
tournament: Event
}
export default function EditTournamentForm({ tournament }: EditTournamentFormProps) {
const router = useRouter()
const [formData, setFormData] = useState({
name: tournament.name,
description: tournament.description || "",
eventDate: tournament.eventDate ? tournament.eventDate.toISOString().split('T')[0] : "",
eventType: tournament.eventType,
format: tournament.format,
status: tournament.status,
maxParticipants: tournament.maxParticipants?.toString() || "",
targetScore: tournament.targetScore?.toString() || "",
allowTies: tournament.allowTies || false,
})
const [error, setError] = useState("")
const [success, setSuccess] = useState("")
const [isLoading, setIsLoading] = useState(false)
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value, type } = e.target
const checked = (e.target as HTMLInputElement).checked
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value,
}))
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError("")
setSuccess("")
setIsLoading(true)
try {
const response = await fetch(`/api/tournaments/${tournament.id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
...formData,
maxParticipants: formData.maxParticipants ? parseInt(formData.maxParticipants) : null,
eventDate: formData.eventDate ? new Date(formData.eventDate).toISOString() : null,
targetScore: formData.targetScore ? parseInt(formData.targetScore) : null,
allowTies: formData.allowTies,
}),
})
if (!response.ok) {
try {
const data = await response.json()
setError(data.error || "Failed to update tournament")
} catch {
setError(`Failed to update tournament: ${response.status} ${response.statusText}`)
}
setIsLoading(false)
return
}
setSuccess("Tournament updated successfully!")
setIsLoading(false)
// Redirect to tournament detail after 2 seconds
setTimeout(() => {
router.push(`/admin/tournaments/${tournament.id}`)
}, 2000)
} catch {
setError("An error occurred. Please try again.")
setIsLoading(false)
}
}
return (
<form onSubmit={handleSubmit} className="space-y-6">
{error && (
<div className="rounded-md bg-red-50 p-4">
<div className="text-sm text-red-700">{error}</div>
</div>
)}
{success && (
<div className="rounded-md bg-green-50 p-4">
<div className="text-sm text-green-700">{success}</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
Tournament Name
</label>
<input
type="text"
id="name"
name="name"
required
value={formData.name}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500 sm:text-sm"
/>
</div>
<div>
<label htmlFor="eventDate" className="block text-sm font-medium text-gray-700">
Date
</label>
<input
type="date"
id="eventDate"
name="eventDate"
value={formData.eventDate}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500 sm:text-sm"
/>
</div>
</div>
<div>
<label htmlFor="description" className="block text-sm font-medium text-gray-700">
Description
</label>
<textarea
id="description"
name="description"
rows={3}
value={formData.description}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500 sm:text-sm"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label htmlFor="eventType" className="block text-sm font-medium text-gray-700">
Event Type
</label>
<select
id="eventType"
name="eventType"
value={formData.eventType}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500 sm:text-sm"
>
<option value="tournament">Tournament</option>
<option value="league">League</option>
<option value="casual">Casual Play</option>
</select>
</div>
<div>
<label htmlFor="format" className="block text-sm font-medium text-gray-700">
Format
</label>
<select
id="format"
name="format"
value={formData.format}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500 sm:text-sm"
>
<option value="round_robin">Round Robin</option>
<option value="single_elimination">Single Elimination</option>
<option value="double_elimination">Double Elimination</option>
<option value="swiss">Swiss</option>
</select>
</div>
<div>
<label htmlFor="status" className="block text-sm font-medium text-gray-700">
Status
</label>
<select
id="status"
name="status"
value={formData.status}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500 sm:text-sm"
>
<option value="planned">Planned</option>
<option value="registration">Registration Open</option>
<option value="in_progress">In Progress</option>
<option value="completed">Completed</option>
<option value="cancelled">Cancelled</option>
</select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label htmlFor="maxParticipants" className="block text-sm font-medium text-gray-700">
Max Participants (optional)
</label>
<input
type="number"
id="maxParticipants"
name="maxParticipants"
min="1"
value={formData.maxParticipants}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500 sm:text-sm"
/>
</div>
<div>
<label htmlFor="targetScore" className="block text-sm font-medium text-gray-700">
Target Score (optional)
</label>
<input
type="number"
id="targetScore"
name="targetScore"
min="1"
value={formData.targetScore}
onChange={handleChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500 sm:text-sm"
placeholder="Default: 5"
/>
</div>
</div>
<div>
<label className="flex items-center">
<input
type="checkbox"
name="allowTies"
checked={formData.allowTies}
onChange={handleChange}
className="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded"
/>
<span className="ml-2 text-sm text-gray-700">Allow Ties</span>
</label>
</div>
<div className="flex justify-end space-x-4">
<Link
href={`/admin/tournaments/${tournament.id}`}
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
>
Cancel
</Link>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700 disabled:opacity-50"
>
{isLoading ? "Saving..." : "Save Changes"}
</button>
</div>
</form>
)
}