allTeamsRow.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import * as React from 'react';
  2. import {Link} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator';
  5. import {joinTeam, leaveTeam} from 'app/actionCreators/teams';
  6. import {Client} from 'app/api';
  7. import Button from 'app/components/button';
  8. import IdBadge from 'app/components/idBadge';
  9. import {PanelItem} from 'app/components/panels';
  10. import {t, tct, tn} from 'app/locale';
  11. import space from 'app/styles/space';
  12. import {Organization, Team} from 'app/types';
  13. import withApi from 'app/utils/withApi';
  14. type Props = {
  15. api: Client;
  16. urlPrefix: string;
  17. organization: Organization;
  18. team: Team;
  19. openMembership: boolean;
  20. };
  21. type State = {
  22. loading: boolean;
  23. error: boolean;
  24. };
  25. class AllTeamsRow extends React.Component<Props, State> {
  26. state: State = {
  27. loading: false,
  28. error: false,
  29. };
  30. handleRequestAccess = async () => {
  31. const {team} = this.props;
  32. try {
  33. this.joinTeam({
  34. successMessage: tct('You have requested access to [team]', {
  35. team: `#${team.slug}`,
  36. }),
  37. errorMessage: tct('Unable to request access to [team]', {
  38. team: `#${team.slug}`,
  39. }),
  40. });
  41. // TODO: Ideally we would update team so that `isPending` is true
  42. } catch (_err) {
  43. // No need to do anything
  44. }
  45. };
  46. handleJoinTeam = () => {
  47. const {team} = this.props;
  48. this.joinTeam({
  49. successMessage: tct('You have joined [team]', {
  50. team: `#${team.slug}`,
  51. }),
  52. errorMessage: tct('Unable to join [team]', {
  53. team: `#${team.slug}`,
  54. }),
  55. });
  56. };
  57. joinTeam = ({
  58. successMessage,
  59. errorMessage,
  60. }: {
  61. successMessage: React.ReactNode;
  62. errorMessage: React.ReactNode;
  63. }) => {
  64. const {api, organization, team} = this.props;
  65. this.setState({
  66. loading: true,
  67. });
  68. return new Promise<void>((resolve, reject) =>
  69. joinTeam(
  70. api,
  71. {
  72. orgId: organization.slug,
  73. teamId: team.slug,
  74. },
  75. {
  76. success: () => {
  77. this.setState({
  78. loading: false,
  79. error: false,
  80. });
  81. addSuccessMessage(successMessage);
  82. resolve();
  83. },
  84. error: () => {
  85. this.setState({
  86. loading: false,
  87. error: true,
  88. });
  89. addErrorMessage(errorMessage);
  90. reject(new Error('Unable to join team'));
  91. },
  92. }
  93. )
  94. );
  95. };
  96. handleLeaveTeam = () => {
  97. const {api, organization, team} = this.props;
  98. this.setState({
  99. loading: true,
  100. });
  101. leaveTeam(
  102. api,
  103. {
  104. orgId: organization.slug,
  105. teamId: team.slug,
  106. },
  107. {
  108. success: () => {
  109. this.setState({
  110. loading: false,
  111. error: false,
  112. });
  113. addSuccessMessage(
  114. tct('You have left [team]', {
  115. team: `#${team.slug}`,
  116. })
  117. );
  118. },
  119. error: () => {
  120. this.setState({
  121. loading: false,
  122. error: true,
  123. });
  124. addErrorMessage(
  125. tct('Unable to leave [team]', {
  126. team: `#${team.slug}`,
  127. })
  128. );
  129. },
  130. }
  131. );
  132. };
  133. render() {
  134. const {team, urlPrefix, openMembership} = this.props;
  135. const display = (
  136. <IdBadge
  137. team={team}
  138. avatarSize={36}
  139. description={tn('%s Member', '%s Members', team.memberCount)}
  140. />
  141. );
  142. // You can only view team details if you have access to team -- this should account
  143. // for your role + org open membership
  144. const canViewTeam = team.hasAccess;
  145. return (
  146. <TeamPanelItem>
  147. <TeamNameWrapper>
  148. {canViewTeam ? (
  149. <TeamLink to={`${urlPrefix}teams/${team.slug}/`}>{display}</TeamLink>
  150. ) : (
  151. display
  152. )}
  153. </TeamNameWrapper>
  154. <Spacer>
  155. {this.state.loading ? (
  156. <Button size="small" disabled>
  157. ...
  158. </Button>
  159. ) : team.isMember ? (
  160. <Button size="small" onClick={this.handleLeaveTeam}>
  161. {t('Leave Team')}
  162. </Button>
  163. ) : team.isPending ? (
  164. <Button size="small" disabled>
  165. {t('Request Pending')}
  166. </Button>
  167. ) : openMembership ? (
  168. <Button size="small" onClick={this.handleJoinTeam}>
  169. {t('Join Team')}
  170. </Button>
  171. ) : (
  172. <Button size="small" onClick={this.handleRequestAccess}>
  173. {t('Request Access')}
  174. </Button>
  175. )}
  176. </Spacer>
  177. </TeamPanelItem>
  178. );
  179. }
  180. }
  181. const TeamLink = styled(Link)`
  182. display: inline-block;
  183. &.focus-visible {
  184. margin: -${space(1)};
  185. padding: ${space(1)};
  186. background: #f2eff5;
  187. border-radius: 3px;
  188. outline: none;
  189. }
  190. `;
  191. export {AllTeamsRow};
  192. export default withApi(AllTeamsRow);
  193. const TeamPanelItem = styled(PanelItem)`
  194. padding: 0;
  195. align-items: center;
  196. `;
  197. const Spacer = styled('div')`
  198. padding: ${space(2)};
  199. `;
  200. const TeamNameWrapper = styled(Spacer)`
  201. flex: 1;
  202. `;