react-icons/io#IoMdCloseCircle TypeScript Examples

The following examples show how to use react-icons/io#IoMdCloseCircle. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: FilterBadge.tsx    From hub with Apache License 2.0 6 votes vote down vote up
FilterBadge = (props: Props) => {
  return (
    <div className={`badge bg-light rounded-pill me-2 mb-2 p-0 ps-2 border ${styles.badgeFilter}`}>
      <div className="d-flex flex-row align-items-center">
        <div className="position-relative">
          {props.type && <small className="fw-normal me-1 text-uppercase">{props.type}:</small>}
          {props.name}
        </div>
        <button
          className={`btn btn-link btn-sm py-0 text-center ${styles.btn}`}
          onClick={props.onClick}
          aria-label={`Remove filter: ${props.type ? `${props.type} -` : ''} ${props.name}`}
        >
          <IoMdCloseCircle className={`position-relative ${styles.btnIcon}`} />
        </button>
      </div>
    </div>
  );
}
Example #2
Source File: UploadedFileInfo.tsx    From nextclade with MIT License 6 votes vote down vote up
function FileStatusIcon({ hasErrors }: { hasErrors: boolean }) {
  const ICON_SIZE = 70

  if (hasErrors) {
    return <IoMdCloseCircle size={ICON_SIZE} color={theme.danger} />
  }

  return <IoMdCheckmarkCircle size={ICON_SIZE} color={theme.success} />
}
Example #3
Source File: UploadedFileInfoCompact.tsx    From nextclade with MIT License 6 votes vote down vote up
function FileStatusIcon({ hasErrors }: { hasErrors: boolean }) {
  const ICON_SIZE = 30

  if (hasErrors) {
    return <IoMdCloseCircle size={ICON_SIZE} color={theme.danger} />
  }

  return <IoMdCheckmarkCircle size={ICON_SIZE} color={theme.success} />
}
Example #4
Source File: Card.tsx    From hub with Apache License 2.0 4 votes vote down vote up
MemberCard = (props: Props) => {
  const { ctx, dispatch } = useContext(AppCtx);
  const [isDeletingMember, setIsDeletingMember] = useState(false);
  const dropdownMenu = useRef(null);
  const [dropdownMenuStatus, setDropdownMenuStatus] = useState<boolean>(false);
  const [modalStatus, setModalStatus] = useState<boolean>(false);

  const closeDropdown = () => {
    setDropdownMenuStatus(false);
  };

  useOutsideClick([dropdownMenu], dropdownMenuStatus, closeDropdown);

  async function deleteMember() {
    try {
      setIsDeletingMember(true);
      await API.deleteOrganizationMember(ctx.prefs.controlPanel.selectedOrg!, props.member.alias);
      setIsDeletingMember(false);
      if (props.member.alias === ctx.user!.alias) {
        dispatch(unselectOrg());
      } else {
        props.onSuccess();
      }
    } catch (err: any) {
      setIsDeletingMember(false);
      if (err.kind !== ErrorKind.Unauthorized) {
        let errorMessage = 'An error occurred removing member from the organization, please try again later.';
        if (err.kind === ErrorKind.Forbidden) {
          errorMessage = 'You do not have permissions to remove members from the organization.';
        }
        alertDispatcher.postAlert({
          type: 'danger',
          message: errorMessage,
        });
      } else {
        props.onAuthError();
      }
    }
  }

  const isUser = props.member.alias === ctx.user!.alias;

  const getFullName = (): string => {
    let fullName = '';
    if (props.member.firstName) {
      fullName += `${props.member.firstName} `;
    }
    if (props.member.lastName) {
      fullName += props.member.lastName;
    }
    return fullName;
  };

  return (
    <div className="col-12 col-xxl-6 py-sm-3 py-2 px-0 px-xxl-3" data-testid="memberCard">
      <div className="card h-100">
        <div className="card-body d-flex flex-column h-100">
          <div className="d-flex flex-row w-100 justify-content-between align-items-start">
            <div
              className={`d-flex align-items-center justify-content-center p-1 overflow-hidden me-2 border border-2 rounded-circle bg-white ${styles.imageWrapper} imageWrapper`}
            >
              <FaUser className={`fs-4 ${styles.image}`} />
            </div>

            <div className="flex-grow-1">
              <div className="d-flex flex-row align-items-start">
                <div className="h5 mb-1">
                  {props.member.firstName || props.member.lastName ? getFullName() : props.member.alias}
                </div>
                {!isUndefined(props.member.confirmed) && !props.member.confirmed && (
                  <div className={classnames('ms-3', { 'me-3': props.membersNumber > 1 })}>
                    <span className="badge bg-warning">Invitation not accepted yet</span>
                  </div>
                )}
              </div>
              <div className="h6 text-muted me-1 fst-italic">{props.member.alias}</div>
            </div>

            {props.membersNumber > 1 && (
              <>
                {modalStatus && (
                  <Modal
                    className="d-inline-block"
                    closeButton={
                      <>
                        <button
                          className="btn btn-sm btn-outline-secondary"
                          onClick={() => setModalStatus(false)}
                          aria-label="Cancel"
                        >
                          <div className="d-flex flex-row align-items-center">
                            <IoMdCloseCircle className="me-2" />
                            <span>Cancel</span>
                          </div>
                        </button>

                        <button
                          className="btn btn-sm btn-danger ms-3"
                          onClick={(e) => {
                            e.preventDefault();
                            deleteMember();
                          }}
                          disabled={isDeletingMember}
                          aria-label={isUser ? 'Leave organization' : 'Remove member'}
                        >
                          <div className="d-flex flex-row align-items-center text-uppercase">
                            {isDeletingMember ? (
                              <>
                                <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                                <span className="ms-2">{isUser ? 'Leaving...' : 'Removing...'}</span>
                              </>
                            ) : (
                              <>
                                {isUser ? (
                                  <FaSignOutAlt className={`me-2 ${styles.btnIcon}`} />
                                ) : (
                                  <FaUserMinus className={`me-2 ${styles.btnIcon}`} />
                                )}
                                <span>{isUser ? 'Leave' : 'Remove'}</span>
                              </>
                            )}
                          </div>
                        </button>
                      </>
                    }
                    header={
                      <div className={`h3 flex-grow-1 m-2 ${styles.title}`}>
                        {isUser ? 'Leave ' : 'Remove from '} organization
                      </div>
                    }
                    onClose={() => setModalStatus(false)}
                    open
                  >
                    <div className="mt-3 mw-100 text-center">
                      <p>
                        {isUser
                          ? 'Are you sure you want to leave this organization?'
                          : 'Are you sure you want to remove this member from this organization?'}
                      </p>
                    </div>
                  </Modal>
                )}

                <div className="ms-auto">
                  <div
                    ref={dropdownMenu}
                    className={classnames('dropdown-menu dropdown-menu-end p-0', styles.dropdownMenu, {
                      show: dropdownMenuStatus,
                    })}
                  >
                    <div className="dropdown-arrow" />

                    {isUser ? (
                      <button
                        className="dropdown-item btn btn-sm rounded-0 text-dark"
                        onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
                          e.preventDefault();
                          closeDropdown();
                          setModalStatus(true);
                        }}
                        aria-label="Open leave organization modal"
                      >
                        <div className="d-flex flex-row align-items-center">
                          <FaSignOutAlt className={`me-2 ${styles.btnIcon}`} />
                          <span>Leave</span>
                        </div>
                      </button>
                    ) : (
                      <ActionBtn
                        className="dropdown-item btn btn-sm rounded-0 text-dark"
                        onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
                          e.preventDefault();
                          closeDropdown();
                          setModalStatus(true);
                        }}
                        action={AuthorizerAction.DeleteOrganizationMember}
                        label="Open leave organization modal"
                      >
                        <>
                          <FaUserMinus className={`me-2 ${styles.btnIcon}`} />
                          <span>Remove</span>
                        </>
                      </ActionBtn>
                    )}
                  </div>

                  <button
                    className={`btn btn-outline-secondary rounded-circle p-0 text-center  ${styles.btnDropdown}`}
                    onClick={() => setDropdownMenuStatus(true)}
                    aria-label="Open menu"
                    aria-expanded={dropdownMenuStatus}
                  >
                    <BsThreeDotsVertical />
                  </button>
                </div>
              </>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}
Example #5
Source File: Card.tsx    From hub with Apache License 2.0 4 votes vote down vote up
OrganizationCard = (props: Props) => {
  const { ctx, dispatch } = useContext(AppCtx);
  const [isLeaving, setIsLeaving] = useState(false);
  const [isAccepting, setIsAccepting] = useState(false);
  const dropdownMenu = useRef(null);
  const [dropdownMenuStatus, setDropdownMenuStatus] = useState<boolean>(false);
  const [leaveModalStatus, setLeaveModalStatus] = useState<boolean>(false);

  const isMember =
    !isUndefined(props.organization.confirmed) && !isNull(props.organization.confirmed) && props.organization.confirmed;

  const closeDropdown = () => {
    setDropdownMenuStatus(false);
  };

  useOutsideClick([dropdownMenu], dropdownMenuStatus, closeDropdown);

  async function leaveOrganization() {
    try {
      setIsLeaving(true);
      await API.deleteOrganizationMember(props.organization.name, ctx.user!.alias);
      setIsLeaving(false);
      closeDropdown();
      props.onSuccess();
      if (
        !isUndefined(ctx.prefs.controlPanel.selectedOrg) &&
        ctx.prefs.controlPanel.selectedOrg === props.organization.name
      ) {
        dispatch(unselectOrg());
      }
    } catch (err: any) {
      setIsLeaving(false);
      if (err.kind !== ErrorKind.Unauthorized) {
        closeDropdown();
        alertDispatcher.postAlert({
          type: 'danger',
          message: 'An error occurred leaving the organization, please try again later.',
        });
      } else {
        props.onAuthError();
      }
    }
  }

  async function confirmOrganizationMembership() {
    setIsAccepting(true);
    try {
      await API.confirmOrganizationMembership(props.organization.name);
      setIsAccepting(false);
      props.onSuccess();
    } catch {
      setIsAccepting(false);
    }
  }

  const hasDropdownContent =
    !isUndefined(props.organization) &&
    (!props.organization.confirmed ||
      (props.organization.confirmed &&
        isMember &&
        props.organization.membersCount &&
        props.organization.membersCount > 1));

  return (
    <div className="col-12 col-xxl-6 py-sm-3 py-2 px-0 px-xxl-3" data-testid="organizationCard">
      <div className="card h-100">
        <div className="card-body d-flex flex-column h-100">
          <div className="d-flex flex-row w-100 justify-content-between align-items-start">
            <div className="d-flex flex-row align-items-center w-100">
              <div
                className={`d-flex align-items-center justify-content-center overflow-hidden p-1 me-2 position-relative border border-3 bg-white rounded-circle ${styles.imageWrapper} imageWrapper`}
              >
                {!isUndefined(props.organization.logoImageId) ? (
                  <Image
                    alt={props.organization.displayName || props.organization.name}
                    imageId={props.organization.logoImageId}
                    className={`fs-4 ${styles.image}`}
                    placeholderIcon={<MdBusiness />}
                  />
                ) : (
                  <MdBusiness className={styles.image} />
                )}
              </div>

              <div className="text-truncate">
                <div className={`h5 mb-0 text-truncate ${styles.title}`}>
                  {props.organization.displayName || props.organization.name}
                </div>
              </div>

              {!isMember && (
                <div className="ms-3">
                  <span className="badge bg-warning">Invitation not accepted yet</span>
                </div>
              )}

              <div className="ms-auto">
                <div
                  ref={dropdownMenu}
                  className={classnames('dropdown-menu dropdown-menu-end p-0', styles.dropdownMenu, {
                    show: dropdownMenuStatus,
                  })}
                >
                  <div className={`dropdown-arrow ${styles.arrow}`} />

                  {props.organization.confirmed ? (
                    <>
                      {isMember && props.organization.membersCount && props.organization.membersCount > 1 && (
                        <button
                          className="dropdown-item btn btn-sm rounded-0 text-dark"
                          onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
                            e.preventDefault();
                            closeDropdown();
                            setLeaveModalStatus(true);
                          }}
                          aria-label="Open modal"
                        >
                          <div className="d-flex flex-row align-items-center">
                            <FaSignOutAlt className={`me-2 ${styles.btnIcon}`} />
                            <span>Leave</span>
                          </div>
                        </button>
                      )}
                    </>
                  ) : (
                    <div>
                      <button
                        className="dropdown-item btn btn-sm rounded-0 text-dark"
                        onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
                          e.preventDefault();
                          confirmOrganizationMembership();
                          closeDropdown();
                        }}
                        disabled={isAccepting}
                        aria-label="Confirm membership"
                      >
                        <div className="d-flex flex-row align-items-center">
                          {isAccepting ? (
                            <>
                              <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                              <span className="ms-2">Accepting invitation...</span>
                            </>
                          ) : (
                            <>
                              <FaEnvelopeOpenText className={`me-2 ${styles.btnIcon}`} />
                              <span>Accept invitation</span>
                            </>
                          )}
                        </div>
                      </button>
                    </div>
                  )}
                </div>

                {hasDropdownContent && (
                  <button
                    className={`ms-3 mb-2 btn btn-outline-secondary rounded-circle p-0 text-center  ${styles.btnDropdown}`}
                    onClick={() => setDropdownMenuStatus(true)}
                    aria-label="Open menu"
                    aria-expanded={dropdownMenuStatus}
                  >
                    <BsThreeDotsVertical />
                  </button>
                )}
              </div>
            </div>

            {leaveModalStatus && (
              <Modal
                className={`d-inline-block ${styles.modal}`}
                closeButton={
                  <>
                    <button
                      className="btn btn-sm btn-outline-secondary text-uppercase"
                      onClick={() => setLeaveModalStatus(false)}
                      aria-label="Close modal"
                    >
                      <div className="d-flex flex-row align-items-center">
                        <IoMdCloseCircle className="me-2" />
                        <span>Cancel</span>
                      </div>
                    </button>

                    <button
                      className="btn btn-sm btn-danger ms-3"
                      onClick={(e) => {
                        e.preventDefault();
                        leaveOrganization();
                      }}
                      disabled={isLeaving}
                      aria-label="Leave organization"
                    >
                      <div className="d-flex flex-row align-items-center text-uppercase">
                        {isLeaving ? (
                          <>
                            <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                            <span className="ms-2">Leaving...</span>
                          </>
                        ) : (
                          <>
                            <FaSignOutAlt className={`me-2 ${styles.btnIcon}`} />
                            <span>Leave</span>
                          </>
                        )}
                      </div>
                    </button>
                  </>
                }
                header={<div className={`h3 m-2 flex-grow-1 ${styles.title}`}>Leave organization</div>}
                onClose={() => setLeaveModalStatus(false)}
                open
              >
                <div className="mt-3 mw-100 text-center">
                  <p>Are you sure you want to leave this organization?</p>
                </div>
              </Modal>
            )}
          </div>

          {props.organization.homeUrl && (
            <div className="mt-3 text-truncate">
              <small className="text-muted text-uppercase me-1">Homepage: </small>
              <ExternalLink
                href={props.organization.homeUrl}
                className={`text-reset ${styles.link}`}
                label={`Open link ${props.organization.homeUrl}`}
              >
                {props.organization.homeUrl}
              </ExternalLink>
            </div>
          )}

          {props.organization.description && (
            <div className="mt-2">
              <p className="mb-0">{props.organization.description}</p>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}
Example #6
Source File: DeletionModal.tsx    From hub with Apache License 2.0 4 votes vote down vote up
DeletionModal = (props: Props) => {
  const [isDeleting, setIsDeleting] = useState(false);
  const [isValidInput, setIsValidInput] = useState<boolean>(false);

  const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
    setIsValidInput(e.target.value === props.repository.name);
  };

  async function deleteRepository() {
    try {
      setIsDeleting(true);
      await API.deleteRepository(props.repository.name, props.organizationName);
      setIsDeleting(false);
      props.onSuccess();
    } catch (err: any) {
      setIsDeleting(false);
      if (err.kind === ErrorKind.Unauthorized) {
        props.onAuthError();
      } else {
        let errorMessage = 'An error occurred deleting the repository, please try again later.';
        if (!isUndefined(props.organizationName) && err.kind === ErrorKind.Forbidden) {
          errorMessage = 'You do not have permissions to delete the repository from the organization.';
        }
        alertDispatcher.postAlert({
          type: 'danger',
          message: errorMessage,
        });
      }
    }
  }

  return (
    <Modal
      className="d-inline-block"
      closeButton={
        <>
          <button
            className="btn btn-sm btn-outline-secondary text-uppercase"
            onClick={() => props.setDeletionModalStatus(false)}
            aria-label="Cancel"
          >
            <div className="d-flex flex-row align-items-center">
              <IoMdCloseCircle className="me-2" />
              <span>Cancel</span>
            </div>
          </button>

          <button
            className="btn btn-sm btn-danger ms-3"
            onClick={(e) => {
              e.preventDefault();
              deleteRepository();
            }}
            disabled={isDeleting || !isValidInput}
            aria-label="Delete repository"
          >
            <div className="d-flex flex-row align-items-center text-uppercase">
              {isDeleting ? (
                <>
                  <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                  <span className="ms-2">Deleting...</span>
                </>
              ) : (
                <>
                  <FaTrashAlt className="me-2" />
                  <span>Delete</span>
                </>
              )}
            </div>
          </button>
        </>
      }
      header={<div className={`h3 m-2 flex-grow-1 ${styles.title}`}>Delete repository</div>}
      onClose={() => props.setDeletionModalStatus(false)}
      open
    >
      <div className="mw-100">
        <div className="alert alert-warning my-4">
          <span className="fw-bold text-uppercase">Important:</span> Please read this carefully.
        </div>

        <p>If you delete this repository all packages belonging to it will be deleted.</p>

        <p>
          All information related to your repository or packages will be permanently deleted as well. This includes
          packages' stars, users subscriptions to packages, webhooks, events and notifications.
        </p>

        <p>
          <span className="fw-bold">This operation cannot be undone</span>.
        </p>

        <p>
          Please type <span className="fw-bold">{props.repository.name}</span> to confirm:
        </p>

        <InputField type="text" name="repoName" autoComplete="off" value="" onChange={onInputChange} />
      </div>
    </Modal>
  );
}
Example #7
Source File: DeleteOrg.tsx    From hub with Apache License 2.0 4 votes vote down vote up
DeleteOrganization = (props: Props) => {
  const { dispatch } = useContext(AppCtx);
  const [openStatus, setOpenStatus] = useState<boolean>(false);
  const [isDeleting, setIsDeleting] = useState(false);
  const [isValidInput, setIsValidInput] = useState<boolean>(false);

  const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
    setIsValidInput(e.target.value === props.organization.name);
  };

  async function deleteOrganization() {
    try {
      setIsDeleting(true);
      await API.deleteOrganization(props.organization.name);
      dispatch(unselectOrg());
      window.scrollTo(0, 0); // Scroll to top when org is deleted
      setIsDeleting(false);
    } catch (err: any) {
      setIsDeleting(false);
      if (err.kind === ErrorKind.Unauthorized) {
        props.onAuthError();
      } else {
        let errorMessage = 'An error occurred deleting the organization, please try again later.';
        if (err.kind === ErrorKind.Forbidden) {
          errorMessage = 'You do not have permissions to delete the organization.';
        }
        alertDispatcher.postAlert({
          type: 'danger',
          message: errorMessage,
        });
      }
    }
  }

  return (
    <>
      <div className="mt-4 mt-md-5">
        <p className="mb-4">
          Deleting your organization will also delete all the content that belongs to it. Please be certain as{' '}
          <span className="fw-bold">this operation cannot be undone</span>.
        </p>

        <ActionBtn
          className="btn btn-sm btn-danger"
          onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
            e.preventDefault();
            setOpenStatus(true);
          }}
          action={AuthorizerAction.DeleteOrganization}
          label="Open delete organization modal"
        >
          <div className="d-flex flex-row align-items-center text-uppercase">
            <FaTrashAlt className="me-2" />
            <div>Delete organization</div>
          </div>
        </ActionBtn>
      </div>

      <Modal
        className="d-inline-block"
        closeButton={
          <>
            <button
              className="btn btn-sm btn-outline-secondary text-uppercase"
              onClick={() => setOpenStatus(false)}
              aria-label="Close"
            >
              <div className="d-flex flex-row align-items-center">
                <IoMdCloseCircle className="me-2" />
                <span>Cancel</span>
              </div>
            </button>

            <button
              className="btn btn-sm btn-danger ms-3"
              onClick={(e) => {
                e.preventDefault();
                deleteOrganization();
              }}
              disabled={isDeleting || !isValidInput}
              aria-label="Delete organization"
            >
              <div className="d-flex flex-row align-items-center text-uppercase">
                {isDeleting ? (
                  <>
                    <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                    <span className="ms-2">Deleting...</span>
                  </>
                ) : (
                  <>
                    <FaTrashAlt className="me-2" />
                    <span>Delete</span>
                  </>
                )}
              </div>
            </button>
          </>
        }
        header={<div className={`h3 m-2 flex-grow-1 ${styles.title}`}>Delete organization</div>}
        onClose={() => setOpenStatus(false)}
        open={openStatus}
      >
        <div className="mw-100">
          <div className="alert alert-warning my-4">
            <span className="fw-bold text-uppercase">Important:</span> Please read this carefully.
          </div>

          <p>
            If you delete this organization all repositories belonging to it will be deleted. Please consider
            transferring them to another organization or your personal account.
          </p>
          <p>
            All information related to the repositories will be permanently deleted as well. This includes packages,
            stars, users subscriptions, webhooks, events and notifications. Some of this information was created by
            users and will be lost.
          </p>

          <p>
            <span className="fw-bold">This operation cannot be undone</span>.
          </p>

          <p data-testid="confirmationText">
            Please type <span className="fw-bold">{props.organization.name}</span> to confirm:
          </p>

          <InputField type="text" name="orgName" autoComplete="off" value="" onChange={onInputChange} />
        </div>
      </Modal>
    </>
  );
}
Example #8
Source File: Card.tsx    From hub with Apache License 2.0 4 votes vote down vote up
APIKeyCard = (props: Props) => {
  const [isDeleting, setIsDeleting] = useState(false);
  const [dropdownMenuStatus, setDropdownMenuStatus] = useState<boolean>(false);
  const dropdownMenu = useRef(null);
  const [deletionModalStatus, setDeletionModalStatus] = useState<boolean>(false);

  const closeDropdown = () => {
    setDropdownMenuStatus(false);
  };

  useOutsideClick([dropdownMenu], dropdownMenuStatus, closeDropdown);

  async function deleteAPIKey() {
    try {
      setIsDeleting(true);
      await API.deleteAPIKey(props.apiKey.apiKeyId!);
      setIsDeleting(false);
      props.onSuccess();
    } catch (err: any) {
      setIsDeleting(false);
      if (err.kind === ErrorKind.Unauthorized) {
        props.onAuthError();
      } else {
        alertDispatcher.postAlert({
          type: 'danger',
          message: 'An error occurred deleting the API key, please try again later.',
        });
      }
    }
  }

  return (
    <div className="col-12 col-xxl-6 py-sm-3 py-2 px-0 px-xxl-3" data-testid="APIKeyCard">
      <div className="card h-100">
        <div className="card-body d-flex flex-column h-100">
          <div className="d-flex flex-row w-100 justify-content-between">
            <div className={`h5 mb-1 me-2 text-break ${styles.titleCard}`}>{props.apiKey.name}</div>
            {deletionModalStatus && (
              <Modal
                className={`d-inline-block ${styles.modal}`}
                closeButton={
                  <>
                    <button
                      className="btn btn-sm btn-outline-secondary text-uppercase"
                      onClick={() => setDeletionModalStatus(false)}
                      aria-label="Cancel"
                    >
                      <div className="d-flex flex-row align-items-center">
                        <IoMdCloseCircle className="me-2" />
                        <span>Cancel</span>
                      </div>
                    </button>

                    <button
                      className="btn btn-sm btn-danger ms-3"
                      onClick={(e) => {
                        e.preventDefault();
                        closeDropdown();
                        deleteAPIKey();
                      }}
                      disabled={isDeleting}
                      aria-label="Delete API key"
                    >
                      <div className="d-flex flex-row align-items-center text-uppercase">
                        {isDeleting ? (
                          <>
                            <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                            <span className="ms-2">Deleting...</span>
                          </>
                        ) : (
                          <>
                            <FaTrashAlt className={`me-2 ${styles.btnDeleteIcon}`} />
                            <span>Delete</span>
                          </>
                        )}
                      </div>
                    </button>
                  </>
                }
                header={<div className={`h3 m-2 flex-grow-1 ${styles.title}`}>Delete API key</div>}
                onClose={() => setDeletionModalStatus(false)}
                open
              >
                <div className="mt-3 mw-100 text-center">
                  <p>Are you sure you want to remove this API key?</p>
                </div>
              </Modal>
            )}

            <div className="ms-auto">
              <div
                ref={dropdownMenu}
                className={classnames('dropdown-menu dropdown-menu-end p-0', styles.dropdownMenu, {
                  show: dropdownMenuStatus,
                })}
              >
                <div className={`dropdown-arrow ${styles.arrow}`} />

                <button
                  className="dropdown-item btn btn-sm rounded-0 text-dark"
                  onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
                    e.preventDefault();
                    closeDropdown();
                    props.setModalStatus({
                      open: true,
                      apiKey: props.apiKey,
                    });
                  }}
                  aria-label="Open API key modal"
                >
                  <div className="d-flex flex-row align-items-center">
                    <FaPencilAlt className={`me-2 ${styles.btnIcon}`} />
                    <span>Edit</span>
                  </div>
                </button>

                <button
                  className="dropdown-item btn btn-sm rounded-0 text-dark"
                  onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
                    e.preventDefault();
                    closeDropdown();
                    setDeletionModalStatus(true);
                  }}
                  aria-label="Open deletion modal"
                >
                  <div className="d-flex flex-row align-items-center">
                    <FaTrashAlt className={`me-2 ${styles.btnIcon}`} />
                    <span>Delete</span>
                  </div>
                </button>
              </div>

              <button
                className={`btn btn-outline-secondary rounded-circle p-0 text-center  ${styles.btnDropdown}`}
                onClick={() => setDropdownMenuStatus(true)}
                aria-label="Open menu"
                aria-expanded={dropdownMenuStatus}
              >
                <BsThreeDotsVertical />
              </button>
            </div>
          </div>

          <div className="mt-2 d-flex flex-row align-items-baseline">
            <div className="text-truncate">
              <small className="text-muted text-uppercase me-1">API-KEY-ID: </small>
              <small>{props.apiKey.apiKeyId}</small>
            </div>
            <div className={`ms-1 ${styles.copyBtn}`}>
              <div className={`position-absolute ${styles.copyBtnWrapper}`}>
                <ButtonCopyToClipboard
                  text={props.apiKey.apiKeyId!}
                  className="btn-link border-0 text-dark fw-bold"
                  label="Copy API key ID to clipboard"
                />
              </div>
            </div>
          </div>
          <div className="text-truncate">
            <small className="text-muted text-uppercase me-1">Created at: </small>
            <small>{moment.unix(props.apiKey.createdAt!).format('YYYY/MM/DD HH:mm:ss (Z)')}</small>
          </div>
        </div>
      </div>
    </div>
  );
}
Example #9
Source File: DeleteAccount.tsx    From hub with Apache License 2.0 4 votes vote down vote up
DeleteAccount = (props: Props) => {
  const { ctx } = useContext(AppCtx);
  const [openStatus, setOpenStatus] = useState<boolean>(false);
  const [isDeleting, setIsDeleting] = useState(false);
  const [deleteSuccess, setDeleteSuccess] = useState<boolean>(false);
  const [isValidInput, setIsValidInput] = useState<boolean>(false);

  const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
    setIsValidInput(e.target.value === ctx.user!.alias);
  };

  const onClose = () => {
    setOpenStatus(false);
    setDeleteSuccess(false);
    setIsValidInput(false);
  };

  async function registerDeleteUserCode() {
    try {
      setIsDeleting(true);
      await API.registerDeleteUserCode();
      setIsDeleting(false);
      setDeleteSuccess(true);
    } catch (err: any) {
      setIsDeleting(false);
      if (err.kind === ErrorKind.Unauthorized) {
        props.onAuthError();
      } else {
        alertDispatcher.postAlert({
          type: 'danger',
          message: 'An error occurred deleting your account, please try again later.',
        });
      }
    }
  }

  return (
    <>
      <div className="mt-4 mt-md-5">
        <p className="mb-4">
          Deleting your account will also delete all the content that belongs to it (repositories, subscriptions,
          webhooks, etc), as well as all organizations where you are the only member.
        </p>

        <button
          className="btn btn-sm btn-danger"
          onClick={() => setOpenStatus(true)}
          aria-label="Open deletion account modal"
        >
          <div className="d-flex flex-row align-items-center text-uppercase">
            <FaTrashAlt className="me-2" />
            <div>Delete account</div>
          </div>
        </button>
      </div>

      <Modal
        className="d-inline-block"
        modalClassName={styles.modal}
        closeButton={
          deleteSuccess ? undefined : (
            <>
              <button
                className="btn btn-sm btn-outline-secondary text-uppercase"
                onClick={() => setOpenStatus(false)}
                aria-label="Close"
              >
                <div className="d-flex flex-row align-items-center">
                  <IoMdCloseCircle className="me-2" />
                  <span>Cancel</span>
                </div>
              </button>

              <button
                className="btn btn-sm btn-danger ms-3"
                onClick={(e) => {
                  e.preventDefault();
                  registerDeleteUserCode();
                }}
                disabled={isDeleting || !isValidInput}
                aria-label="Delete account"
              >
                <div className="d-flex flex-row align-items-center text-uppercase">
                  {isDeleting ? (
                    <>
                      <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                      <span className="ms-2">Deleting...</span>
                    </>
                  ) : (
                    <>
                      <FaTrashAlt className="me-2" />
                      <span>Delete account</span>
                    </>
                  )}
                </div>
              </button>
            </>
          )
        }
        header={<div className={`h3 m-2 flex-grow-1 ${styles.title}`}>Delete account</div>}
        onClose={onClose}
        open={openStatus}
      >
        <div className="mw-100 h-100">
          {deleteSuccess ? (
            <div className="d-flex h-100 w-100 align-items-center justify-content-center">
              <div className="alert" role="alert" aria-live="assertive" aria-atomic="true">
                <div className="d-flex flex-sm-column flex-md-row align-items-center">
                  <div className="me-3">
                    <CgUserRemove className="h1 text-dark mb-3 mb-md-0" />
                  </div>
                  <h4 className="alert-heading mb-0">We've just sent you a confirmation email</h4>
                </div>
                <hr />
                <p>
                  Please click on the link that has just been sent to your email account to delete your account and
                  complete the process.
                </p>
                <p className="mb-0">
                  Please note that the link <span className="fw-bold">is only valid for 15 minutes</span>. If you
                  haven't clicked the link by then you'll need to start the process from the beginning.
                </p>
              </div>
            </div>
          ) : (
            <>
              <div className="alert alert-warning my-4">
                <span className="fw-bold text-uppercase">Important:</span> Please read this carefully.
              </div>

              <p>
                If you delete your account all repositories belonging to it will be deleted. Please consider
                transferring them to another user.
              </p>
              <p>
                All information related to the repositories will be permanently deleted as well. This includes packages,
                stars, users subscriptions, webhooks, events and notifications. Some of this information was created by
                users and will be lost. In addition to that, all organizations where you are the only member and all
                content belonging to those organizations will be removed as well.
              </p>

              <p>
                <span className="fw-bold">This operation cannot be undone</span>.
              </p>

              <p data-testid="confirmationText">
                Please type <span className="fw-bold">{ctx.user!.alias}</span> to confirm:
              </p>

              <div className="pb-3">
                <InputField type="text" name="alias" autoComplete="off" value="" onChange={onInputChange} />
              </div>
            </>
          )}
        </div>
      </Modal>
    </>
  );
}
Example #10
Source File: DisableModal.tsx    From hub with Apache License 2.0 4 votes vote down vote up
DisableTwoFactorAuthenticationModal = (props: Props) => {
  const [openStatus, setOpenStatus] = useState<boolean>(false);
  const [isProcessing, setIsProcessing] = useState<boolean>(false);
  const [passcode, setPasscode] = useState<string>('');
  const [apiError, setApiError] = useState<null | string>(null);

  const onPasscodeChange = (e: ChangeEvent<HTMLInputElement>) => {
    setPasscode(e.target.value);
    if (!isNull(apiError)) {
      setApiError(null);
    }
  };

  async function disableTFA() {
    try {
      setIsProcessing(true);
      await API.disableTFA(passcode);
      setApiError(null);
      props.onChange();
      setOpenStatus(false);
    } catch (err: any) {
      setIsProcessing(false);
      if (err.kind !== ErrorKind.Unauthorized) {
        setApiError('An error occurred turning off two-factor authentication, please try again later.');
      } else {
        props.onAuthError();
      }
    }
  }

  return (
    <>
      <button
        className="btn btn-danger btn-sm"
        onClick={() => setOpenStatus(true)}
        aria-label="Open disable two-factor authentication modal"
      >
        <div className="d-flex flex-row align-items-center">
          <FaUnlock className="me-2" />
          <span>Disable two-factor authentication</span>
        </div>
      </button>

      <Modal
        modalClassName={styles.modal}
        header={<div className={`h3 m-2 flex-grow-1 text-truncate ${styles.title}`}>Disable 2FA</div>}
        open={openStatus}
        onClose={() => setOpenStatus(false)}
        closeButton={
          <>
            <button
              className="btn btn-sm btn-outline-secondary text-uppercase"
              onClick={() => setOpenStatus(false)}
              aria-label="Cancel"
            >
              <div className="d-flex flex-row align-items-center">
                <IoMdCloseCircle className="me-2" />
                <span>Cancel</span>
              </div>
            </button>

            <button
              className="btn btn-sm btn-danger ms-3"
              onClick={(e) => {
                e.preventDefault();
                disableTFA();
              }}
              disabled={passcode === '' || isProcessing}
              aria-label="Disable two-factor authentication"
            >
              <div className="d-flex flex-row align-items-center text-uppercase">
                {isProcessing ? (
                  <>
                    <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                    <span className="ms-2">Disabling...</span>
                  </>
                ) : (
                  <>
                    <FaUnlock className="me-2" />
                    <span>Disable</span>
                  </>
                )}
              </div>
            </button>
          </>
        }
        error={apiError}
        cleanError={() => setApiError(null)}
      >
        <div className="mw-100">
          <div className={`mb-4 ${styles.label}`}>
            To disable two-factor authentication for your account please enter one of the codes from the 2FA
            authentication app or one of your recovery codes.
          </div>

          <InputField
            type="text"
            name="passcode"
            autoComplete="off"
            value={passcode}
            onChange={onPasscodeChange}
            invalidText={{
              default: 'This field is required',
            }}
            validateOnBlur
            required
          />
        </div>
      </Modal>
    </>
  );
}
Example #11
Source File: EnableModal.tsx    From hub with Apache License 2.0 4 votes vote down vote up
EnableTwoFactorAuthenticationModal = (props: Props) => {
  const [openStatus, setOpenStatus] = useState<boolean>(false);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [isProcessing, setIsProcessing] = useState<boolean>(false);
  const [setUp, setSetUp] = useState<TwoFactorAuth | null | undefined>();
  const [activeStep, setActiveStep] = useState<number>(1);
  const [passcode, setPasscode] = useState<string>('');
  const [apiError, setApiError] = useState<null | string>(null);

  const onPasscodeChange = (e: ChangeEvent<HTMLInputElement>) => {
    setPasscode(e.target.value);
    if (!isNull(apiError)) {
      setApiError(null);
    }
  };

  const onClose = () => {
    setOpenStatus(false);
    if (activeStep === 3) {
      props.onChange();
    }
    setSetUp(undefined);
    setApiError(null);
    setPasscode('');
    setActiveStep(1);
  };

  async function setUpTFA() {
    try {
      setIsLoading(true);
      setSetUp(await API.setUpTFA());
      setOpenStatus(true);
      setIsLoading(false);
    } catch (err: any) {
      setIsLoading(false);
      setSetUp(null);
      if (err.kind !== ErrorKind.Unauthorized) {
        let error = compoundErrorMessage(err, 'An error occurred turning on two-factor authentication');
        alertDispatcher.postAlert({
          type: 'danger',
          message: error,
        });
      } else {
        props.onAuthError();
      }
    }
  }

  async function enableTFA() {
    try {
      setIsProcessing(true);
      await API.enableTFA(passcode);
      setApiError(null);
      setActiveStep(3);
    } catch (err: any) {
      setIsProcessing(false);
      if (err.kind !== ErrorKind.Unauthorized) {
        let error = compoundErrorMessage(err, 'An error occurred turning on two-factor authentication');
        setApiError(error);
      } else {
        props.onAuthError();
      }
    }
  }

  return (
    <>
      <button className="btn btn-success btn-sm" onClick={setUpTFA} disabled={isLoading} aria-label="Open modal">
        <div className="d-flex flex-row align-items-center">
          {isLoading ? (
            <>
              <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
              <span className="ms-2">Enabling two-factor authentication...</span>
            </>
          ) : (
            <>
              <FaLock className="me-2" />
              <span>Enable two-factor authentication</span>
            </>
          )}
        </div>
      </button>

      <Modal
        modalClassName={styles.modal}
        header={<div className={`h3 m-2 flex-grow-1 text-truncate ${styles.title}`}>Setup 2FA</div>}
        open={openStatus}
        onClose={onClose}
        closeButton={
          <>
            <button
              className="btn btn-sm btn-outline-secondary text-uppercase"
              onClick={onClose}
              aria-label={activeStep === 3 ? 'Close' : 'Cancel'}
            >
              <div className="d-flex flex-row align-items-center">
                <IoMdCloseCircle className="me-2" />
                <span>{activeStep === 3 ? 'Close' : 'Cancel'}</span>
              </div>
            </button>

            {(() => {
              switch (activeStep) {
                case 1:
                  return (
                    <button
                      className="btn btn-sm btn-outline-secondary ms-3"
                      onClick={(e) => {
                        e.preventDefault();
                        setActiveStep(2);
                      }}
                      aria-label="Open next step"
                    >
                      <div className="d-flex flex-row align-items-center text-uppercase">
                        <MdNavigateNext className="me-2" />
                        <span>Next</span>
                      </div>
                    </button>
                  );
                case 2:
                  return (
                    <button
                      className="btn btn-sm btn-success ms-3"
                      onClick={(e) => {
                        e.preventDefault();
                        enableTFA();
                      }}
                      disabled={passcode === '' || isProcessing}
                      aria-label="Enable two-factor authentication"
                    >
                      <div className="d-flex flex-row align-items-center text-uppercase">
                        {isProcessing ? (
                          <>
                            <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                            <span className="ms-2">Enabling...</span>
                          </>
                        ) : (
                          <>
                            <FaLock className="me-2" />
                            <span>Enable</span>
                          </>
                        )}
                      </div>
                    </button>
                  );
                default:
                  return null;
              }
            })()}
          </>
        }
        error={apiError}
        cleanError={() => setApiError(null)}
      >
        <div className="mw-100 h-100 position-relative">
          {setUp && (
            <>
              {(() => {
                switch (activeStep) {
                  case 1:
                    return (
                      <>
                        <div className="h4">Recovery codes</div>
                        <div className={`mt-3 mb-4 ${styles.label}`}>
                          These codes can be used if you lose access to your 2FA credentials. Each of the codes can only
                          be used once.{' '}
                          <span className="fw-bold">Please treat them as passwords and store them safely</span>.
                        </div>
                        <div className={`border rounded position-relative p-2 p-sm-4 ${styles.codesWrapper}`}>
                          <BlockCodeButtons
                            filename="artifacthub-recovery-codes.txt"
                            content={setUp.recoveryCodes.join('\n')}
                            hiddenCopyBtn
                          />

                          <div className="d-flex flex-column align-items-center overflow-auto">
                            {setUp.recoveryCodes.map((code: string) => (
                              <div className={`font-monospace ${styles.code}`} key={`code_${code}`}>
                                {code}
                              </div>
                            ))}
                          </div>
                        </div>

                        <div className="mt-4 alert alert-warning">
                          We strongly recommend you to download and print your recovery codes before proceeding with the
                          2FA setup.
                        </div>
                      </>
                    );
                  case 2:
                    return (
                      <>
                        <div className="h4">Authentication app</div>
                        <div className={`mt-3 mb-4 ${styles.label}`}>
                          Please scan the image below with your 2FA authentication app. If you can't scan it, you can
                          use{' '}
                          <ButtonCopyToClipboard
                            text={setUp.secret}
                            wrapperClassName="d-inline-block"
                            icon={<></>}
                            visibleBtnText
                            contentBtn="this text code"
                            className={`btn-link text-reset p-0 text-secondary fw-bold ${styles.copyBtn}`}
                            label="Copy 2FA code to clipboard"
                          />
                          to set it up manually.
                        </div>

                        <div className="text-center mb-4">
                          <div className="border rounded d-inline-block p-1 my-1">
                            <img className={styles.qrCode} src={setUp.qrCode} alt="QR code" />
                          </div>
                        </div>

                        <div className={`mb-4 ${styles.label}`}>
                          Please enter one of the codes from the 2FA authentication app to confirm your account has been
                          setup successfully before completing the process.
                        </div>

                        <InputField
                          className={styles.inputWrapper}
                          type="text"
                          name="passcode"
                          autoComplete="off"
                          value={passcode}
                          onChange={onPasscodeChange}
                          invalidText={{
                            default: 'This field is required',
                          }}
                          validateOnBlur
                          required
                        />
                      </>
                    );
                  case 3:
                    return (
                      <div className="d-flex flex-column h-100 w-100 px-3 align-items-center justify-content-center text-center position-relative">
                        {isNull(apiError) && (
                          <>
                            <MdDone className="display-4 text-success mb-4" />
                            Two-factor authentication has been successfully enabled. We recommend you to sign out and
                            back in to your account.
                          </>
                        )}
                      </div>
                    );
                  default:
                    return null;
                }
              })()}
            </>
          )}
        </div>
      </Modal>
    </>
  );
}
Example #12
Source File: Card.tsx    From hub with Apache License 2.0 4 votes vote down vote up
WebhookCard = (props: Props) => {
  const { ctx } = useContext(AppCtx);
  const [isDeleting, setIsDeleting] = useState<boolean>(false);
  const [dropdownMenuStatus, setDropdownMenuStatus] = useState<boolean>(false);
  const dropdownMenu = useRef(null);
  const [deletionModalStatus, setDeletionModalStatus] = useState<boolean>(false);

  const closeDropdown = () => {
    setDropdownMenuStatus(false);
  };

  useOutsideClick([dropdownMenu], dropdownMenuStatus, closeDropdown);

  async function deleteWebhook() {
    try {
      setIsDeleting(true);
      await API.deleteWebhook(props.webhook.webhookId!, ctx.prefs.controlPanel.selectedOrg);
      setIsDeleting(false);
      props.onDeletion();
    } catch (err: any) {
      setIsDeleting(false);
      if (err.kind === ErrorKind.Unauthorized) {
        props.onAuthError();
      } else {
        alertDispatcher.postAlert({
          type: 'danger',
          message: 'An error occurred deleting the webhook, please try again later.',
        });
      }
    }
  }

  return (
    <div className="col-12 col-xxl-6 py-sm-3 py-2 px-0 px-xxl-3" role="listitem">
      <div className={`card cardWithHover w-100 h-100 mw-100 bg-white ${styles.card}`}>
        <div className="card-body position-relative">
          <div className="d-flex flex-row">
            <div className="h5 card-title mb-3 me-3 lh-1 text-break">
              <div className="d-flex flex-row align-items-start">
                <div>{props.webhook.name}</div>
                {props.webhook.active ? (
                  <span
                    className={`ms-3 mt-1 fw-bold badge rounded-pill border border-success text-success text-uppercase ${styles.badge}`}
                  >
                    Active
                  </span>
                ) : (
                  <span
                    className={`ms-3 mt-1 fw-bold badge rounded-pill border border-dark text-dark text-uppercase ${styles.badge} ${styles.inactiveBadge}`}
                  >
                    Inactive
                  </span>
                )}
              </div>
            </div>

            {deletionModalStatus && (
              <Modal
                className={`d-inline-block ${styles.modal}`}
                closeButton={
                  <>
                    <button
                      className="btn btn-sm btn-outline-secondary text-uppercase"
                      onClick={() => setDeletionModalStatus(false)}
                      aria-label="Close deletion modal"
                    >
                      <div className="d-flex flex-row align-items-center">
                        <IoMdCloseCircle className="me-2" />
                        <span>Cancel</span>
                      </div>
                    </button>

                    <button
                      className="btn btn-sm btn-danger ms-3"
                      onClick={(e) => {
                        e.preventDefault();
                        deleteWebhook();
                      }}
                      disabled={isDeleting}
                      aria-label="Delete webhook"
                    >
                      <div className="d-flex flex-row align-items-center text-uppercase">
                        {isDeleting ? (
                          <>
                            <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" />
                            <span className="ms-2">Deleting...</span>
                          </>
                        ) : (
                          <>
                            <FaTrashAlt className={`me-2 ${styles.btnDeleteIcon}`} />
                            <span>Delete</span>
                          </>
                        )}
                      </div>
                    </button>
                  </>
                }
                header={<div className={`h3 m-2 flex-grow-1 ${styles.title}`}>Delete webhook</div>}
                onClose={() => setDeletionModalStatus(false)}
                open
              >
                <div className="mt-3 mw-100 text-center">
                  <p>Are you sure you want to delete this webhook?</p>
                </div>
              </Modal>
            )}

            <div className="ms-auto">
              <div
                ref={dropdownMenu}
                className={classnames('dropdown-menu dropdown-menu-end p-0', styles.dropdownMenu, {
                  show: dropdownMenuStatus,
                })}
              >
                <div className={`dropdown-arrow ${styles.arrow}`} />

                <button
                  className="dropdown-item btn btn-sm rounded-0 text-dark"
                  onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
                    e.preventDefault();
                    closeDropdown();
                    props.onEdition();
                  }}
                  aria-label="Edit webhook"
                >
                  <div className="d-flex flex-row align-items-center">
                    <FaPencilAlt className={`me-2 ${styles.btnIcon}`} />
                    <span>Edit</span>
                  </div>
                </button>

                <button
                  className="dropdown-item btn btn-sm rounded-0 text-dark"
                  onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
                    e.preventDefault();
                    closeDropdown();
                    setDeletionModalStatus(true);
                  }}
                  aria-label="Open deletion webhook modal"
                >
                  <div className="d-flex flex-row align-items-center">
                    <FaTrashAlt className={`me-2 ${styles.btnIcon}`} />
                    <span>Delete</span>
                  </div>
                </button>
              </div>

              <button
                className={`btn btn-outline-secondary rounded-circle p-0 text-center  ${styles.btnDropdown}`}
                onClick={() => setDropdownMenuStatus(true)}
                aria-label="Open menu"
                aria-expanded={dropdownMenuStatus}
              >
                <BsThreeDotsVertical />
              </button>
            </div>
          </div>

          <div className="d-flex flex-column">
            <div className="card-subtitle d-flex flex-column mw-100 mt-1">
              <p className="card-text">{props.webhook.description}</p>
            </div>

            <div className="text-truncate">
              <small className="text-muted text-uppercase me-2">Url:</small>
              <small>{props.webhook.url}</small>
            </div>

            <div className="d-flex flex-row justify-content-between align-items-baseline">
              {props.webhook.lastNotifications && (
                <div className="d-none d-md-inline mt-2">
                  <LastNotificationsModal notifications={props.webhook.lastNotifications} />
                </div>
              )}

              {(isUndefined(props.webhook.packages) || props.webhook.packages.length === 0) && (
                <div className="ms-auto mt-2">
                  <ElementWithTooltip
                    element={
                      <span
                        className={`d-flex flex-row align-items-center badge bg-warning rounded-pill ${styles.badgeNoPackages}`}
                      >
                        <TiWarningOutline />
                        <span className="ms-1">No packages</span>
                      </span>
                    }
                    tooltipMessage="This webhook is not associated to any packages."
                    active
                    visibleTooltip
                  />
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}