"use client"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { useDebounce } from "@calcom/lib/hooks/useDebounce"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import { Avatar } from "@calcom/ui/components/avatar"; import { Badge } from "@calcom/ui/components/badge"; import { Button } from "@calcom/ui/components/button"; import { ConfirmationDialogContent, Dialog } from "@calcom/ui/components/dialog"; import { TextField } from "@calcom/ui/components/form"; import { DropdownActions, Table } from "@calcom/ui/components/table"; import { showToast } from "@calcom/ui/components/toast"; import { keepPreviousData } from "@tanstack/react-query"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; const { Cell, ColumnTitle, Header, Row } = Table; const FETCH_LIMIT = 25; export function UsersTable() { const { t } = useLocale(); const tableContainerRef = useRef(null); const utils = trpc.useUtils(); const [searchTerm, setSearchTerm] = useState(""); const debouncedSearchTerm = useDebounce(searchTerm, 500); const router = useRouter(); const [userToDelete, setUserToDelete] = useState(null); const deleteMutation = trpc.viewer.users.delete.useMutation({ onSuccess: async () => { showToast(t("user_has_been_deleted"), "success"); utils.viewer.admin.listPaginated.setInfiniteData({ limit: FETCH_LIMIT }, (cachedData) => { if (!cachedData) { return { pages: [], pageParams: [], }; } return { ...cachedData, pages: cachedData.pages.map((page) => ({ ...page, rows: page.rows.filter((row) => row.id !== userToDelete), })), }; }); }, onError: (err) => { console.error(err.message); showToast(t("error_deleting_user"), "error"); }, onSettled: () => { setUserToDelete(null); }, }); const { data, fetchNextPage, isFetching } = trpc.viewer.admin.listPaginated.useInfiniteQuery( { limit: FETCH_LIMIT, searchTerm: debouncedSearchTerm, }, { getNextPageParam: (lastPage) => lastPage.nextCursor, placeholderData: keepPreviousData, refetchOnWindowFocus: false, } ); const sendPasswordResetEmail = trpc.viewer.admin.sendPasswordReset.useMutation({ onSuccess: () => { showToast(t("password_reset_email_sent"), "success"); }, }); const removeTwoFactor = trpc.viewer.admin.removeTwoFactor.useMutation({ onSuccess: () => { showToast(t("two_factor_removed"), "success"); }, }); const lockUserAccount = trpc.viewer.admin.lockUserAccount.useMutation({ onSuccess: ({ locked }) => { showToast(locked ? t("user_locked") : t("user_unlocked"), "success"); utils.viewer.admin.listPaginated.invalidate(); }, }); const flatData = useMemo(() => data?.pages?.flatMap((page) => page.rows) ?? [], [data]); const totalRowCount = data?.pages?.[0]?.meta?.totalRowCount ?? 0; const totalFetched = flatData.length; const fetchMoreOnBottomReached = useCallback( (containerRefElement?: HTMLDivElement | null) => { if (containerRefElement) { const { scrollHeight, scrollTop, clientHeight } = containerRefElement; if (scrollHeight - scrollTop - clientHeight < 300 && !isFetching && totalFetched < totalRowCount) { fetchNextPage(); } } }, [fetchNextPage, isFetching, totalFetched, totalRowCount] ); useEffect(() => { fetchMoreOnBottomReached(tableContainerRef.current); }, [fetchMoreOnBottomReached]); return (
setSearchTerm(e.target.value)} />
fetchMoreOnBottomReached()} style={{ height: "calc(100vh - 30vh)", overflow: "auto", }}>
{t("user")} {t("timezone")} {t("role")} {t("edit")}
{flatData.map((user) => (
{user.name} /{user.username} {user.locked && {t("locked")}}
{user.email}
{user.timeZone} {user.role.toLowerCase()}
sendPasswordResetEmail.mutate({ userId: user.id }), icon: "lock" as const, }, { id: "lock-user", label: user.locked ? t("unlock_user_account") : t("lock_user_account"), onClick: () => lockUserAccount.mutate({ userId: user.id, locked: !user.locked }), icon: "lock" as const, }, { id: "remove-2fa", label: t("remove_2fa"), color: "destructive" as const, onClick: () => removeTwoFactor.mutate({ userId: user.id }), icon: "shield" as const, }, { id: "delete", label: t("delete"), color: "destructive" as const, onClick: () => setUserToDelete(user.id), icon: "trash" as const, }, ]} />
))}
setUserToDelete(null)} onConfirm={() => { if (!userToDelete) return; deleteMutation.mutate({ userId: userToDelete }); }} />
); } function DeleteUserDialog({ user, onConfirm, onClose, }: { user: number | null; onConfirm: () => void; onClose: () => void; }) { const { t } = useLocale(); return ( (open ? undefined : onClose())}>

{t("delete_user_confirmation")}

); }