import {Fragment, PureComponent} from 'react'; import styled from '@emotion/styled'; import UserAvatar from 'sentry/components/avatar/userAvatar'; import {Button} from 'sentry/components/button'; import Confirm from 'sentry/components/confirm'; import HookOrDefault from 'sentry/components/hookOrDefault'; import Link from 'sentry/components/links/link'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import PanelItem from 'sentry/components/panels/panelItem'; import {IconCheckmark, IconClose, IconFlag, IconMail, IconSubtract} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Member, Organization} from 'sentry/types/organization'; import type {AvatarUser} from 'sentry/types/user'; import isMemberDisabledFromLimit from 'sentry/utils/isMemberDisabledFromLimit'; import {capitalize} from 'sentry/utils/string/capitalize'; type Props = { canAddMembers: boolean; canRemoveMembers: boolean; currentUser: AvatarUser; member: Member; memberCanLeave: boolean; onLeave: (member: Member) => void; onRemove: (member: Member) => void; onSendInvite: (member: Member) => void; organization: Organization; requireLink: boolean; status: '' | 'loading' | 'success' | 'error' | null; }; type State = { busy: boolean; }; const DisabledMemberTooltip = HookOrDefault({ hookName: 'component:disabled-member-tooltip', defaultComponent: ({children}) => {children}, }); export default class OrganizationMemberRow extends PureComponent { state: State = { busy: false, }; handleRemove = () => { const {onRemove} = this.props; if (typeof onRemove !== 'function') { return; } this.setState({busy: true}); onRemove(this.props.member); }; handleLeave = () => { const {onLeave} = this.props; if (typeof onLeave !== 'function') { return; } this.setState({busy: true}); onLeave(this.props.member); }; handleSendInvite = () => { const {onSendInvite, member} = this.props; if (typeof onSendInvite !== 'function') { return; } onSendInvite(member); }; renderMemberRole() { const {member} = this.props; const {roleName, pending, expired} = member; if (isMemberDisabledFromLimit(member)) { return {t('Deactivated')}; } if (pending) { return ( {expired ? t('Expired Invite') : tct('Invited [roleName]', {roleName})} ); } return {capitalize(member.orgRole)}; } render() { const { member, organization, status, requireLink, memberCanLeave, currentUser, canRemoveMembers, canAddMembers, } = this.props; const {id, flags, email, name, pending, user} = member; // if member is not the only owner, they can leave const isIdpProvisioned = flags['idp:provisioned']; const isPartnershipUser = flags['partnership:restricted']; const needsSso = !flags['sso:linked'] && requireLink; const isCurrentUser = currentUser.email === email; const showRemoveButton = !isCurrentUser; const showLeaveButton = isCurrentUser; const canRemoveMember = canRemoveMembers && !isCurrentUser && !isIdpProvisioned && !isPartnershipUser; // member has a `user` property if they are registered with sentry // i.e. has accepted an invite to join org const has2fa = user?.has2fa; const detailsUrl = `/settings/${organization.slug}/members/${id}/`; const isInviteSuccessful = status === 'success'; const isInviting = status === 'loading'; const showResendButton = pending || needsSso; return (
{name}
{email}
{this.renderMemberRole()}
{showResendButton ? ( {isInviting && ( )} {isInviteSuccessful && {t('Sent!')}} {!isInviting && !isInviteSuccessful && ( )} ) : ( {has2fa ? ( ) : ( )} {has2fa ? t('2FA Enabled') : t('2FA Not Enabled')} )}
{showRemoveButton || showLeaveButton ? ( {showRemoveButton && canRemoveMember && ( )} {showRemoveButton && !canRemoveMember && ( )} {showLeaveButton && memberCanLeave && ( )} {showLeaveButton && !memberCanLeave && ( )} ) : null}
); } } const StyledPanelItem = styled(PanelItem)` display: grid; grid-template-columns: minmax(150px, 4fr) minmax(90px, 2fr) minmax(120px, 2fr) minmax( 100px, 1fr ); gap: ${space(2)}; align-items: center; `; // Force action button at the end to align to right const RightColumn = styled('div')` display: flex; justify-content: flex-end; `; const Section = styled('div')` display: inline-grid; grid-template-columns: max-content auto; gap: ${space(1)}; align-items: center; `; const MemberHeading = styled(Section)``; const MemberDescription = styled(Link)` overflow: hidden; `; const UserName = styled('div')` display: block; overflow: hidden; font-size: ${p => p.theme.fontSizeMedium}; text-overflow: ellipsis; `; const Email = styled('div')` color: ${p => p.theme.subText}; font-size: ${p => p.theme.fontSizeSmall}; overflow: hidden; text-overflow: ellipsis; `; const InvitedRole = styled(Section)``; const LoadingContainer = styled('div')` margin-top: 0; margin-bottom: ${space(1.5)}; `; const AuthStatus = styled(Section)``;