teamDetails.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import {cloneElement, isValidElement, useState} from 'react';
  2. import type {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 useApi from 'sentry/utils/useApi';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. import {useParams} from 'sentry/utils/useParams';
  17. import {useTeams} from 'sentry/utils/useTeams';
  18. type Props = {
  19. children: React.ReactNode;
  20. } & RouteComponentProps<{teamId: string}, {}>;
  21. function TeamDetails({children}: Props) {
  22. const api = useApi();
  23. const params = useParams();
  24. const orgSlug = useOrganization().slug;
  25. const [requesting, setRequesting] = useState(false);
  26. const {teams, initiallyLoaded} = useTeams({slugs: [params.teamId]});
  27. const team = teams.find(({slug}) => slug === params.teamId);
  28. function handleRequestAccess(teamSlug: string) {
  29. setRequesting(true);
  30. joinTeam(
  31. api,
  32. {
  33. orgId: orgSlug,
  34. teamId: teamSlug,
  35. },
  36. {
  37. success: () => {
  38. addSuccessMessage(
  39. tct('You have requested access to [team]', {
  40. team: `#${teamSlug}`,
  41. })
  42. );
  43. setRequesting(false);
  44. },
  45. error: () => {
  46. addErrorMessage(
  47. tct('Unable to request access to [team]', {
  48. team: `#${teamSlug}`,
  49. })
  50. );
  51. setRequesting(false);
  52. },
  53. }
  54. );
  55. }
  56. const routePrefix = `/settings/${orgSlug}/teams/${params.teamId}/`;
  57. const navigationTabs = [
  58. <ListLink key={0} to={`${routePrefix}members/`}>
  59. {t('Members')}
  60. </ListLink>,
  61. <ListLink key={1} to={`${routePrefix}projects/`}>
  62. {t('Projects')}
  63. </ListLink>,
  64. <ListLink key={2} to={`${routePrefix}notifications/`}>
  65. {t('Notifications')}
  66. </ListLink>,
  67. <ListLink key={3} to={`${routePrefix}settings/`}>
  68. {t('Settings')}
  69. </ListLink>,
  70. ];
  71. if (!initiallyLoaded) {
  72. return <LoadingIndicator />;
  73. }
  74. if (!team) {
  75. return (
  76. <Alert type="warning">
  77. <div>{t('This team does not exist, or you do not have access to it.')}</div>
  78. </Alert>
  79. );
  80. }
  81. return (
  82. <div>
  83. <SentryDocumentTitle title={t('Team Details')} orgSlug={orgSlug} />
  84. {team.hasAccess ? (
  85. <div>
  86. <h3>
  87. <IdBadge hideAvatar team={team} avatarSize={36} />
  88. </h3>
  89. <NavTabs underlined>{navigationTabs}</NavTabs>
  90. {isValidElement(children) ? cloneElement<any>(children, {team}) : null}
  91. </div>
  92. ) : (
  93. <Alert type="warning">
  94. <RequestAccessWrapper>
  95. <div>
  96. {tct('You do not have access to the [teamSlug] team.', {
  97. teamSlug: <strong>{`#${team.slug}`}</strong>,
  98. })}
  99. </div>
  100. <Button
  101. disabled={requesting || team.isPending}
  102. size="sm"
  103. onClick={() => handleRequestAccess(team.slug)}
  104. >
  105. {team.isPending ? t('Request Pending') : t('Request Access')}
  106. </Button>
  107. </RequestAccessWrapper>
  108. </Alert>
  109. )}
  110. </div>
  111. );
  112. }
  113. export default TeamDetails;
  114. const RequestAccessWrapper = styled('div')`
  115. display: flex;
  116. justify-content: space-between;
  117. align-items: center;
  118. `;