teamDetails.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import {cloneElement, isValidElement, useState} from 'react';
  2. import {browserHistory, RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  5. import {joinTeam} from 'sentry/actionCreators/teams';
  6. import Alert from 'sentry/components/alert';
  7. import Button from 'sentry/components/button';
  8. import IdBadge from 'sentry/components/idBadge';
  9. import ListLink from 'sentry/components/links/listLink';
  10. import LoadingIndicator from 'sentry/components/loadingIndicator';
  11. import NavTabs from 'sentry/components/navTabs';
  12. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  13. import {t, tct} from 'sentry/locale';
  14. import TeamStore from 'sentry/stores/teamStore';
  15. import {Team} from 'sentry/types';
  16. import recreateRoute from 'sentry/utils/recreateRoute';
  17. import useApi from 'sentry/utils/useApi';
  18. import useTeams from 'sentry/utils/useTeams';
  19. type Props = {
  20. children: React.ReactNode;
  21. } & RouteComponentProps<{orgId: string; teamId: string}, {}>;
  22. function TeamDetails({children, ...props}: Props) {
  23. const api = useApi();
  24. const [currentTeam, setCurrentTeam] = useState(
  25. TeamStore.getBySlug(props.params.teamId)
  26. );
  27. const [requesting, setRequesting] = useState(false);
  28. function handleRequestAccess(team: Team) {
  29. if (!team) {
  30. return;
  31. }
  32. setRequesting(true);
  33. joinTeam(
  34. api,
  35. {
  36. orgId: props.params.orgId,
  37. teamId: team.slug,
  38. },
  39. {
  40. success: () => {
  41. addSuccessMessage(
  42. tct('You have requested access to [team]', {
  43. team: `#${team.slug}`,
  44. })
  45. );
  46. setRequesting(false);
  47. },
  48. error: () => {
  49. addErrorMessage(
  50. tct('Unable to request access to [team]', {
  51. team: `#${team.slug}`,
  52. })
  53. );
  54. setRequesting(false);
  55. },
  56. }
  57. );
  58. }
  59. function onTeamChange(data: Team) {
  60. if (currentTeam !== data) {
  61. const orgId = props.params.orgId;
  62. browserHistory.replace(`/organizations/${orgId}/teams/${data.slug}/settings/`);
  63. } else {
  64. setCurrentTeam({...currentTeam, ...data});
  65. }
  66. }
  67. // `/organizations/${orgId}/teams/${teamId}`;
  68. const routePrefix = recreateRoute('', {
  69. routes: props.routes,
  70. params: props.params,
  71. stepBack: -1,
  72. });
  73. const navigationTabs = [
  74. <ListLink key={0} to={`${routePrefix}members/`}>
  75. {t('Members')}
  76. </ListLink>,
  77. <ListLink key={1} to={`${routePrefix}projects/`}>
  78. {t('Projects')}
  79. </ListLink>,
  80. <ListLink key={2} to={`${routePrefix}notifications/`}>
  81. {t('Notifications')}
  82. </ListLink>,
  83. <ListLink key={3} to={`${routePrefix}settings/`}>
  84. {t('Settings')}
  85. </ListLink>,
  86. ];
  87. const {teams, initiallyLoaded} = useTeams({slugs: [props.params.teamId]});
  88. return (
  89. <div>
  90. {initiallyLoaded ? (
  91. teams.length ? (
  92. teams.map((team, i) => {
  93. if (!team || !team.hasAccess) {
  94. return (
  95. <Alert type="warning">
  96. {team ? (
  97. <RequestAccessWrapper>
  98. {tct('You do not have access to the [teamSlug] team.', {
  99. teamSlug: <strong>{`#${team.slug}`}</strong>,
  100. })}
  101. <Button
  102. disabled={requesting || team.isPending}
  103. size="sm"
  104. onClick={() => handleRequestAccess(team)}
  105. >
  106. {team.isPending ? t('Request Pending') : t('Request Access')}
  107. </Button>
  108. </RequestAccessWrapper>
  109. ) : (
  110. <div>{t('You do not have access to this team.')}</div>
  111. )}
  112. </Alert>
  113. );
  114. }
  115. return (
  116. <div key={i}>
  117. <SentryDocumentTitle
  118. title={t('Team Details')}
  119. orgSlug={props.params.orgId}
  120. />
  121. <h3>
  122. <IdBadge hideAvatar team={team} avatarSize={36} />
  123. </h3>
  124. <NavTabs underlined>{navigationTabs}</NavTabs>
  125. {isValidElement(children) &&
  126. cloneElement(children, {
  127. team,
  128. onTeamChange: () => onTeamChange(team),
  129. })}
  130. </div>
  131. );
  132. })
  133. ) : (
  134. <Alert type="warning">
  135. <div>{t('You do not have access to this team.')}</div>
  136. </Alert>
  137. )
  138. ) : (
  139. <LoadingIndicator />
  140. )}
  141. </div>
  142. );
  143. }
  144. export default TeamDetails;
  145. const RequestAccessWrapper = styled('div')`
  146. display: flex;
  147. justify-content: space-between;
  148. align-items: center;
  149. `;