function UserListing()

in frontend/src/containers/UserListing.tsx [18:237]


function UserListing() {
  const history = useHistory<LocationState>();
  const { users, reloadUsers, setUsers } = useUsers();
  const [filter, setFilter] = useState("");
  const [selected, setSelected] = useState<Array<User>>([]);
  const [isOpenResendInviteModal, setIsOpenResendInviteModal] = useState(false);
  const [isOpenRemoveUsersModal, setIsOpenRemoveUsersModal] = useState(false);

  const { t } = useTranslation();

  const addUsers = () => {
    history.push("/admin/users/add");
  };

  const changeRole = () => {
    history.push("/admin/users/changerole", {
      emails: selected.map((s) => s.email).join(", "),
      usernames: selected.map((s) => s.userId),
    });
  };

  const removeUsers = async () => {
    if (selected.length) {
      const usernames = selected.map((user) => user.userId);
      try {
        await BackendService.removeUsers(usernames);
        history.replace("/admin/users", {
          alert: {
            type: "success",
            message: `${t("SuccessfullyRemoved")}${selected.length} ${t(
              selected.length === 1 ? "GlobalUser" : "GlobalUsers"
            )}.`,
          },
        });

        // Reload users but also update the UI optimistically because
        // the backend sometimes returns the same list of users including
        // the deleted one due to eventual consistency.
        await reloadUsers();
        setUsers(users.filter((user) => !usernames.includes(user.userId)));
      } catch (err) {
        await reloadUsers();
        history.replace("/admin/users", {
          alert: {
            type: "error",
            message: `${t("UserListingRemoveUserFail")}`,
          },
        });
      } finally {
        setIsOpenRemoveUsersModal(false);
      }
    }
  };

  const onSearch = useCallback((query) => {
    setFilter(query);
  }, []);

  const onSelect = useCallback((selectedUsers: Array<User>) => {
    setSelected(selectedUsers);
  }, []);

  const closeRemoveUsersModal = () => {
    setIsOpenRemoveUsersModal(false);
  };

  const closeResendInviteModal = () => {
    setIsOpenResendInviteModal(false);
  };

  const onResendInvite = () => {
    setIsOpenResendInviteModal(true);
  };

  const onRemoveUsers = () => {
    setIsOpenRemoveUsersModal(true);
  };

  const resendInvite = async () => {
    closeResendInviteModal();

    if (selected.length) {
      await BackendService.resendInvite(selected.map((s) => s.email));

      history.replace("/admin/users", {
        alert: {
          type: "success",
          message:
            selected.length === 1
              ? `${selected.length} ${t("UserListingResentInvites")}`
              : `${selected.length} ${t("UserListingResentInvitesPlural")}`,
        },
      });

      await reloadUsers();
    }
  };

  const resendInviteEmailDisabled = () => {
    return (
      selected.length === 0 ||
      selected.some((s) => s.userStatus !== "FORCE_CHANGE_PASSWORD")
    );
  };

  return (
    <>
      <AlertContainer />
      <h1 className="margin-top-1">{t("ManageUsers")}</h1>
      <Modal
        isOpen={isOpenResendInviteModal}
        closeModal={closeResendInviteModal}
        title={t("UserListingModalTitleResendInvites")}
        message={t("UserListingModalTitleResendInvitesMessage", {
          count: selected.length,
        })}
        buttonType={t("GlobalResend")}
        buttonAction={resendInvite}
      />
      <Modal
        isOpen={isOpenRemoveUsersModal}
        closeModal={closeRemoveUsersModal}
        title={t("UserListingActionsRemoveUsers")}
        message={t("UserListingActionsRemoveUsersMessage", {
          count: selected.length,
        })}
        buttonType={t("GlobalDelete")}
        buttonAction={removeUsers}
      />
      <p>
        {t("UserListingDescription")}{" "}
        <Link target="_blank" to={"/admin/userstatus"} external>
          {t("UserListingLink")}
        </Link>
      </p>
      <div className="grid-row margin-y-3">
        <div className="tablet:grid-col-4 text-left padding-top-1px">
          <ul className="usa-button-group">
            <li className="usa-button-group__item">
              <span>
                <Search
                  id={t("GlobalSearch")}
                  onSubmit={onSearch}
                  size="small"
                />
              </span>
            </li>
          </ul>
        </div>
        <div className="tablet:grid-col-8 text-right">
          <span>
            <DropdownMenu
              buttonText={t("UserListingDropdownMenuActions")}
              variant="outline"
            >
              <MenuItem onSelect={changeRole} disabled={selected.length === 0}>
                {t("UserListingDropdownChangeRole")}
              </MenuItem>

              <MenuItem
                onSelect={onResendInvite}
                disabled={resendInviteEmailDisabled()}
                title={t("UserListingDropdownResendInviteText")}
              >
                {t("UserListingActionsResendInvites")}
              </MenuItem>
              <MenuItem
                onSelect={onRemoveUsers}
                disabled={selected.length === 0}
              >
                {t("UserListingActionsRemoveUsers")}
              </MenuItem>
            </DropdownMenu>
          </span>
          <span>
            <Button variant="base" onClick={addUsers}>
              {t("UserListingAddUsers")}
            </Button>
          </span>
        </div>
      </div>
      <Table
        selection="multiple"
        screenReaderField="userId"
        filterQuery={filter}
        initialSortByField="userId"
        onSelection={onSelect}
        initialSortAscending
        width="100%"
        columns={useMemo(
          () => [
            {
              Header: t("UserListingUsername"),
              accessor: "userId",
            },
            {
              Header: t("UserListingEmail"),
              accessor: "email",
            },
            {
              Header: t("UserListingRole"),
              accessor: (row: User) => t(row.roles[0]),
            },
            {
              Header: t("UserListingStatus"),
              accessor: (row: User) =>
                t(
                  `UserStatuses.${UtilsService.getTranslationUserStatusValue(
                    row.userStatus
                  )}`
                ),
            },
          ],
          [t]
        )}
        rows={users}
      />
    </>
  );
}