From af767879e356f6615c5e0889a1b4511e4ace682f Mon Sep 17 00:00:00 2001 From: Yanis Date: Fri, 1 May 2026 12:10:23 +0200 Subject: [PATCH] feat(admin/users): bannedReason textarea on user detail page Adds a Textarea below the ban Switch that lets the admin write the reason shown to the mobile user in the BannedModal. The reason is persisted on blur via PATCH /api/users/[id] (existing route), and only rendered when the user is currently banned to keep the UI tight. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../(admin)/users/[id]/user-detail-client.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/vineye-admin/app/(admin)/users/[id]/user-detail-client.tsx b/vineye-admin/app/(admin)/users/[id]/user-detail-client.tsx index f30472e..a4eaf8a 100644 --- a/vineye-admin/app/(admin)/users/[id]/user-detail-client.tsx +++ b/vineye-admin/app/(admin)/users/[id]/user-detail-client.tsx @@ -1,11 +1,14 @@ "use client"; +import { useState } from "react"; import { useRouter } from "next/navigation"; import { ArrowLeft, ScanLine, Trophy, Zap, Calendar } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Switch } from "@/components/ui/switch"; +import { Textarea } from "@/components/ui/textarea"; +import { Label } from "@/components/ui/label"; import { Select, SelectContent, @@ -45,6 +48,7 @@ const SEVERITY_STYLES: Record = { export default function UserDetailClient({ user }: UserDetailProps) { const router = useRouter(); + const [reasonDraft, setReasonDraft] = useState(user.bannedReason ?? ""); async function handleUpdate(data: Record) { try { @@ -61,6 +65,13 @@ export default function UserDetailClient({ user }: UserDetailProps) { } } + async function handleReasonBlur() { + const trimmed = reasonDraft.trim(); + const current = user.bannedReason ?? ""; + if (trimmed === current) return; + await handleUpdate({ bannedReason: trimmed.length > 0 ? trimmed : null }); + } + const STAT_ITEMS = [ { label: "Scans", value: user._count.scans, icon: ScanLine, color: "text-vine" }, { label: "XP", value: user.xp, icon: Zap, color: "text-gold" }, @@ -164,6 +175,26 @@ export default function UserDetailClient({ user }: UserDetailProps) { onCheckedChange={(banned) => handleUpdate({ banned })} /> + {user.banned && ( +
+ +