feat: add user management panel for superusers

This commit is contained in:
2026-03-31 10:24:35 -07:00
parent 0f2228b9eb
commit 2ed86dafe9
8 changed files with 981 additions and 0 deletions
@@ -0,0 +1,158 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import Link from "next/link"
import type { User, Player } from "@prisma/client"
interface EditUserFormProps {
user: User & { player?: Player | null };
availablePlayers: Player[];
}
export default function EditUserForm({ user, availablePlayers }: EditUserFormProps) {
const router = useRouter()
const [formData, setFormData] = useState({
email: user.email,
name: user.name || "",
role: user.role,
playerId: user.playerId?.toString() || "",
})
const [error, setError] = useState("")
const [success, setSuccess] = useState("")
const [loading, setLoading] = useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError("")
setSuccess("")
setLoading(true)
try {
const response = await fetch(`/api/admin/users/${user.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || "Failed to update user")
}
setSuccess("User updated successfully!")
setTimeout(() => {
router.push("/admin/users")
}, 1500)
} catch (err) {
if (err instanceof Error) {
setError(err.message)
} else {
setError("An unknown error occurred")
}
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit} className="space-y-6">
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
{error}
</div>
)}
{success && (
<div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded">
{success}
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email <span className="text-red-500">*</span>
</label>
<input
type="email"
id="email"
required
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
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="name" className="block text-sm font-medium text-gray-700">
Name
</label>
<input
type="text"
id="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
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="role" className="block text-sm font-medium text-gray-700">
Role <span className="text-red-500">*</span>
</label>
<select
id="role"
required
value={formData.role}
onChange={(e) => setFormData({ ...formData, role: e.target.value })}
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="player">Player</option>
<option value="tournament_admin">Tournament Admin</option>
<option value="club_admin">Club Admin</option>
<option value="site_admin">Site Admin</option>
</select>
</div>
<div className="md:col-span-2">
<label htmlFor="playerId" className="block text-sm font-medium text-gray-700">
Associate with Player
</label>
<select
id="playerId"
value={formData.playerId}
onChange={(e) => setFormData({ ...formData, playerId: e.target.value })}
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="">-- No player association --</option>
{user.player && (
<option value={user.player.id}>{user.player.name} (current)</option>
)}
{availablePlayers.map((player) => (
<option key={player.id} value={player.id}>
{player.name}
</option>
))}
</select>
</div>
</div>
<div className="flex justify-end space-x-3">
<Link
href="/admin/users"
className="px-4 py-2 text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors"
>
Cancel
</Link>
<button
type="submit"
disabled={loading}
className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors disabled:opacity-50"
>
{loading ? "Saving..." : "Save Changes"}
</button>
</div>
</form>
)
}