feat: add user management panel for superusers
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user