import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query"; import { Shield, ShieldAlert, UserCheck, Trash2, Search, AlertTriangle, Ban, KeyRound } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { authClient } from "~/client/lib/auth-client"; import { Button } from "~/client/components/ui/button"; import { cn } from "~/client/lib/utils"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table"; import { Badge } from "~/client/components/ui/badge"; import { Input } from "~/client/components/ui/input"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "~/client/components/ui/dialog"; import { CreateUserDialog } from "./create-user-dialog"; import { getAdminUsersOptions, getUserDeletionImpactOptions, deleteUserAccountMutation, } from "~/client/api-client/@tanstack/react-query.gen"; export function UserManagement({ currentUser }: { currentUser: { id: string } | undefined | null }) { const [search, setSearch] = useState(""); const [userToDelete, setUserToDelete] = useState(null); const [userToBan, setUserToBan] = useState<{ id: string; name: string; isBanned: boolean } | null>(null); const [userToManageAccounts, setUserToManageAccounts] = useState<{ id: string; name: string; accounts: { id: string; providerId: string }[]; }>(); const { data: deletionImpact, isLoading: isLoadingImpact } = useQuery({ ...getUserDeletionImpactOptions({ path: { userId: userToDelete ?? "" } }), enabled: Boolean(userToDelete), }); const { data } = useSuspenseQuery({ ...getAdminUsersOptions() }); const setRoleMutation = useMutation({ mutationFn: async ({ userId, role }: { userId: string; role: "user" | "admin" }) => { const { error } = await authClient.admin.setRole({ userId, role }); if (error) throw error; }, onSuccess: () => { toast.success("User role updated successfully"); }, onError: (error) => { toast.error("Failed to update role", { description: error.message }); }, }); const toggleBanUserMutation = useMutation({ mutationFn: async ({ userId, ban }: { userId: string; ban: boolean }) => { const { error } = ban ? await authClient.admin.banUser({ userId }) : await authClient.admin.unbanUser({ userId }); if (error) throw error; }, onSuccess: () => { toast.success("User ban status updated successfully"); }, onMutate: () => { setUserToBan(null); }, onError: (error) => { toast.error("Failed to update ban status", { description: error.message }); }, }); const deleteUser = useMutation({ mutationFn: async (userId: string) => { const { error } = await authClient.admin.removeUser({ userId }); if (error) throw error; }, onSuccess: () => { toast.success("User deleted successfully"); setUserToDelete(null); }, onError: (error) => { toast.error("Failed to delete user", { description: error.message }); }, }); const deleteAccount = useMutation({ ...deleteUserAccountMutation(), onSuccess: (_data, variables) => { toast.success("Account removed successfully"); setUserToManageAccounts((prev) => { if (!prev) return prev; return { ...prev, accounts: prev.accounts.filter((a) => a.id !== variables.path.accountId), }; }); }, onError: (error) => { toast.error("Failed to remove account", { description: error.message }); }, }); const normalizedSearch = search.toLowerCase(); const filteredUsers = data.users.filter((user) => { const name = user.name?.toLowerCase() ?? ""; const email = user.email.toLowerCase(); return name.includes(normalizedSearch) || email.includes(normalizedSearch); }); return (
setSearch(e.target.value)} />
User Role Status Actions 0 })}> No users found. {filteredUsers.map((user) => { const displayName = user.name ?? user.email; const isCurrentUser = user.id === currentUser?.id; const isBanned = Boolean(user.banned); return (
{displayName} {user.email}
{user.role} Banned Active
); })}
!open && setUserToDelete(null)}> Are you absolutely sure? This action cannot be undone. This will permanently delete the user account and remove their data from our servers.

Important: Data Deletion

The following personal organizations and all their associated resources will be permanently deleted:

{deletionImpact?.organizations.map((org) => (

{org.name}

{org.resources.volumesCount} Volumes {org.resources.repositoriesCount} Repositories {org.resources.backupSchedulesCount} Backups
))}

Analyzing deletion impact...

!open && setUserToBan(null)}> {userToBan?.isBanned ? "Unban" : "Ban"} User Are you sure you want to {userToBan?.isBanned ? "unban" : "ban"} {userToBan?.name}? {userToBan?.isBanned ? " They will regain access to the system." : " They will be immediately signed out and lose access."} !open && setUserToManageAccounts(undefined)}> Manage Accounts Linked authentication accounts for {userToManageAccounts?.name}.

No accounts linked.

{userToManageAccounts?.accounts.map((account) => (
{account.providerId}
))}
); }