"use client"; import { useState, useCallback, useEffect, useRef } from "react"; import { useRouter } from "next/navigation"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { MemberList } from "./member-list"; import { TransferOwnershipModal } from "./transfer-ownership-modal"; import { GROUP_NAME_MAX_LENGTH } from "@/lib/constants"; interface GroupData { group: { id: string; name: string; invite_code: string; created_by: string; created_at: string; }; role: "admin" | "member"; } interface Member { user_id: string; role: "admin" | "member"; users: { display_name: string; avatar_color: string | null } | null; } interface MembersResponse { members: Member[]; currentUserRole: "admin" | "member"; } export function SettingsPanel({ groupId }: { groupId: string }) { const queryClient = useQueryClient(); const router = useRouter(); const [newName, setNewName] = useState(""); const [copied, setCopied] = useState(false); const [transferTarget, setTransferTarget] = useState<{ userId: string; displayName: string; } | null>(null); const [deleteState, setDeleteState] = useState<"idle" | "armed" | "choosing">("idle"); const disarmTimer = useRef | null>(null); const arm = useCallback(() => { setDeleteState("armed"); if (disarmTimer.current) clearTimeout(disarmTimer.current); disarmTimer.current = setTimeout(() => setDeleteState("idle"), 4000); }, []); useEffect(() => { return () => { if (disarmTimer.current) clearTimeout(disarmTimer.current); }; }, []); const { data, isLoading, error } = useQuery({ queryKey: ["groups", groupId], queryFn: async () => { const res = await fetch(`/api/groups/${groupId}`); if (!res.ok) throw new Error("Failed to fetch group"); return res.json(); }, staleTime: 30_000, }); const rename = useMutation({ mutationFn: async (name: string) => { const res = await fetch(`/api/groups/${groupId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), }); if (!res.ok) { const body = await res.json(); throw new Error(body.error || "Failed to rename"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["groups", groupId] }); setNewName(""); }, }); const regenerateCode = useMutation({ mutationFn: async () => { const res = await fetch(`/api/groups/${groupId}/invite`, { method: "POST", }); if (!res.ok) { const body = await res.json(); throw new Error(body.error || "Failed to regenerate code"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["groups", groupId] }); }, }); const deleteGroup = useMutation({ mutationFn: async () => { const res = await fetch(`/api/groups/${groupId}`, { method: "DELETE" }); if (!res.ok) { const body = await res.json(); throw new Error(body.error || "Failed to delete group"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["groups"] }); router.push("/"); }, }); const leaveGroup = useMutation({ mutationFn: async () => { const res = await fetch(`/api/groups/${groupId}/leave`, { method: "POST", }); if (!res.ok) { const body = await res.json(); throw new Error(body.error || "Failed to leave group"); } return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["groups"] }); router.push("/"); }, }); const transferAndLeave = useMutation({ mutationFn: async (newAdminId: string) => { const tRes = await fetch(`/api/groups/${groupId}/transfer`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ new_admin_id: newAdminId }), }); if (!tRes.ok) { const body = await tRes.json(); throw new Error(body.error || "Failed to transfer ownership"); } const lRes = await fetch(`/api/groups/${groupId}/leave`, { method: "POST" }); if (!lRes.ok) { const body = await lRes.json(); throw new Error(body.error || "Failed to leave group"); } return lRes.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["groups"] }); router.push("/"); }, }); const membersQuery = useQuery({ queryKey: ["groups", groupId, "members"], queryFn: async () => { const res = await fetch(`/api/groups/${groupId}/members`); if (!res.ok) throw new Error("Failed to fetch members"); return res.json(); }, staleTime: 30_000, }); const memberCount = membersQuery.data?.members.length ?? 0; const successors = (membersQuery.data?.members ?? []).filter((m) => m.role !== "admin"); const isSoloAdmin = memberCount <= 1; const inviteCode = data?.group.invite_code; const handleCopyCode = useCallback(async () => { if (!inviteCode) return; try { await navigator.clipboard.writeText(inviteCode); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { // Fallback: select text for manual copy } }, [inviteCode]); const handleTransferRequest = useCallback((userId: string, displayName: string) => { setTransferTarget({ userId, displayName }); }, []); if (isLoading) return

Loading...

; if (error) return

Failed to load group

; if (!data) return null; const isAdmin = data.role === "admin"; return (
{/* Rename (admin only) */} {isAdmin && (

Rename List

{ e.preventDefault(); const trimmed = newName.trim(); if (trimmed) rename.mutate(trimmed); }} className="flex flex-col gap-2 sm:flex-row" > setNewName(e.target.value)} maxLength={GROUP_NAME_MAX_LENGTH} placeholder={data.group.name} className="flex-1 rounded-md border border-gray-300 bg-transparent px-3 py-2 text-base focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-700" />
{rename.error &&

{rename.error.message}

}
{isAdmin && ( )}
{regenerateCode.error && (

{regenerateCode.error.message}

)}
)} {!isAdmin && (
)} {/* Members */} {/* Leave / Delete */}
{isAdmin ? (
{deleteState !== "choosing" && ( )} {deleteState === "choosing" && (

Choose a new admin. You will leave the list once ownership transfers.

{membersQuery.isLoading ? (

Loading members...

) : successors.length === 0 ? (

No other members available.

) : (
    {successors.map((m) => (
  • ))}
)} {transferAndLeave.error && (

{transferAndLeave.error.message}

)}
)} {deleteGroup.error && (

{deleteGroup.error.message}

)}
) : (
{leaveGroup.error && (

{leaveGroup.error.message}

)}
)}
{/* Transfer modal */} {transferTarget && ( setTransferTarget(null)} /> )}
); }