teamMembersRow.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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. return (
  104. <Button
  105. size="xs"
  106. disabled={!canRemoveMember}
  107. icon={<IconSubtract size="xs" isCircled />}
  108. onClick={onClick}
  109. aria-label={t('Remove')}
  110. >
  111. {t('Remove')}
  112. </Button>
  113. );
  114. };
  115. const RoleName = styled('div')`
  116. display: flex;
  117. align-items: center;
  118. `;
  119. const IconWrapper = styled('div')`
  120. height: ${space(2)};
  121. margin-left: ${space(1)};
  122. `;
  123. const RoleSelectWrapper = styled('div')`
  124. display: flex;
  125. flex-direction: row;
  126. align-items: center;
  127. > div:first-child {
  128. flex-grow: 1;
  129. }
  130. `;
  131. const TeamRolesPanelItem = styled(PanelItem)`
  132. display: grid;
  133. grid-template-columns: minmax(120px, 4fr) minmax(120px, 2fr) minmax(100px, 1fr);
  134. gap: ${space(2)};
  135. align-items: center;
  136. > div:last-child {
  137. margin-left: auto;
  138. }
  139. `;
  140. export default TeamMembersRow;