teamMembersRow.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import styled from '@emotion/styled';
  2. import {Button} from 'sentry/components/button';
  3. import IdBadge from 'sentry/components/idBadge';
  4. import {PanelItem} from 'sentry/components/panels';
  5. import RoleSelectControl from 'sentry/components/roleSelectControl';
  6. import {IconSubtract} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import {Member, Organization, TeamMember, User} from 'sentry/types';
  10. import {
  11. hasOrgRoleOverwrite,
  12. RoleOverwriteIcon,
  13. } from 'sentry/views/settings/organizationTeams/roleOverwriteWarning';
  14. const TeamMembersRow = (props: {
  15. hasWriteAccess: boolean;
  16. member: TeamMember;
  17. organization: Organization;
  18. removeMember: (member: Member) => void;
  19. updateMemberRole: (member: Member, newRole: string) => void;
  20. user: User;
  21. }) => {
  22. const {organization, member, user, hasWriteAccess, removeMember, updateMemberRole} =
  23. props;
  24. return (
  25. <TeamRolesPanelItem key={member.id}>
  26. <div>
  27. <IdBadge avatarSize={36} member={member} useLink orgId={organization.slug} />
  28. </div>
  29. <div>
  30. <TeamRoleSelect
  31. hasWriteAccess={hasWriteAccess}
  32. updateMemberRole={updateMemberRole}
  33. organization={organization}
  34. member={member}
  35. />
  36. </div>
  37. <div>
  38. <RemoveButton
  39. hasWriteAccess={hasWriteAccess}
  40. onClick={() => removeMember(member)}
  41. member={member}
  42. user={user}
  43. />
  44. </div>
  45. </TeamRolesPanelItem>
  46. );
  47. };
  48. const TeamRoleSelect = (props: {
  49. hasWriteAccess: boolean;
  50. member: TeamMember;
  51. organization: Organization;
  52. updateMemberRole: (member: TeamMember, newRole: string) => void;
  53. }) => {
  54. const {hasWriteAccess, organization, member, updateMemberRole} = props;
  55. const {orgRoleList, teamRoleList, features} = organization;
  56. if (!features.includes('team-roles')) {
  57. return null;
  58. }
  59. const {orgRole: orgRoleId} = member;
  60. const orgRole = orgRoleList.find(r => r.id === orgRoleId);
  61. const teamRoleId = member.teamRole || orgRole?.minimumTeamRole;
  62. const teamRole = teamRoleList.find(r => r.id === teamRoleId) || teamRoleList[0];
  63. if (
  64. !hasWriteAccess ||
  65. hasOrgRoleOverwrite({orgRole: orgRoleId, orgRoleList, teamRoleList})
  66. ) {
  67. return (
  68. <RoleName>
  69. {teamRole.name}
  70. <IconWrapper>
  71. <RoleOverwriteIcon
  72. orgRole={orgRoleId}
  73. orgRoleList={orgRoleList}
  74. teamRoleList={teamRoleList}
  75. />
  76. </IconWrapper>
  77. </RoleName>
  78. );
  79. }
  80. return (
  81. <RoleSelectWrapper>
  82. <RoleSelectControl
  83. roles={teamRoleList}
  84. value={teamRole.id}
  85. onChange={option => updateMemberRole(member, option.value)}
  86. disableUnallowed
  87. />
  88. </RoleSelectWrapper>
  89. );
  90. };
  91. const RemoveButton = (props: {
  92. hasWriteAccess: boolean;
  93. member: TeamMember;
  94. onClick: () => void;
  95. user: User;
  96. }) => {
  97. const {member, user, hasWriteAccess, onClick} = props;
  98. const isSelf = member.email === user.email;
  99. const canRemoveMember = hasWriteAccess || isSelf;
  100. if (!canRemoveMember) {
  101. return null;
  102. }
  103. if (member.flags['idp:provisioned']) {
  104. return (
  105. <Button
  106. size="xs"
  107. disabled
  108. icon={<IconSubtract size="xs" isCircled />}
  109. onClick={onClick}
  110. aria-label={t('Remove')}
  111. title={t(
  112. "Membership to this team is managed through your organization's identity provider."
  113. )}
  114. >
  115. {t('Remove')}
  116. </Button>
  117. );
  118. }
  119. return (
  120. <Button
  121. size="xs"
  122. disabled={!canRemoveMember}
  123. icon={<IconSubtract size="xs" isCircled />}
  124. onClick={onClick}
  125. aria-label={t('Remove')}
  126. >
  127. {t('Remove')}
  128. </Button>
  129. );
  130. };
  131. const RoleName = styled('div')`
  132. display: flex;
  133. align-items: center;
  134. `;
  135. const IconWrapper = styled('div')`
  136. height: ${space(2)};
  137. margin-left: ${space(1)};
  138. `;
  139. const RoleSelectWrapper = styled('div')`
  140. display: flex;
  141. flex-direction: row;
  142. align-items: center;
  143. > div:first-child {
  144. flex-grow: 1;
  145. }
  146. `;
  147. const TeamRolesPanelItem = styled(PanelItem)`
  148. display: grid;
  149. grid-template-columns: minmax(120px, 4fr) minmax(120px, 2fr) minmax(100px, 1fr);
  150. gap: ${space(2)};
  151. align-items: center;
  152. > div:last-child {
  153. margin-left: auto;
  154. }
  155. `;
  156. export default TeamMembersRow;