noProjectMessage.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import React from 'react';
  2. import styled from '@emotion/styled';
  3. /* TODO: replace with I/O when finished */
  4. import img from 'sentry-images/spot/hair-on-fire.svg';
  5. import Button from 'app/components/button';
  6. import ButtonBar from 'app/components/buttonBar';
  7. import PageHeading from 'app/components/pageHeading';
  8. import {t} from 'app/locale';
  9. import ConfigStore from 'app/stores/configStore';
  10. import space from 'app/styles/space';
  11. import {LightWeightOrganization, Organization, Project} from 'app/types';
  12. type Props = {
  13. organization: LightWeightOrganization | Organization;
  14. projects?: Project[];
  15. loadingProjects?: boolean;
  16. superuserNeedsToBeProjectMember?: boolean;
  17. };
  18. export default class NoProjectMessage extends React.Component<Props> {
  19. render() {
  20. const {
  21. children,
  22. organization,
  23. projects,
  24. loadingProjects,
  25. superuserNeedsToBeProjectMember,
  26. } = this.props;
  27. const orgId = organization.slug;
  28. const canCreateProject = organization.access.includes('project:write');
  29. const canJoinTeam = organization.access.includes('team:read');
  30. let orgHasProjects: boolean;
  31. let hasProjectAccess: boolean;
  32. if ('projects' in organization) {
  33. const {isSuperuser} = ConfigStore.get('user');
  34. orgHasProjects = organization.projects.length > 0;
  35. hasProjectAccess =
  36. isSuperuser && !superuserNeedsToBeProjectMember
  37. ? organization.projects.some(p => p.hasAccess)
  38. : organization.projects.some(p => p.isMember && p.hasAccess);
  39. } else {
  40. hasProjectAccess = projects ? projects.length > 0 : false;
  41. orgHasProjects = hasProjectAccess;
  42. }
  43. if (hasProjectAccess || loadingProjects) {
  44. return children;
  45. }
  46. // If the organization has some projects, but the user doesn't have access to
  47. // those projects, the primary action is to Join a Team. Otherwise the primary
  48. // action is to create a project.
  49. const joinTeamAction = (
  50. <Button
  51. title={canJoinTeam ? undefined : t('You do not have permission to join a team.')}
  52. disabled={!canJoinTeam}
  53. priority={orgHasProjects ? 'primary' : 'default'}
  54. to={`/settings/${orgId}/teams/`}
  55. >
  56. {t('Join a Team')}
  57. </Button>
  58. );
  59. const createProjectAction = (
  60. <Button
  61. title={
  62. canCreateProject
  63. ? undefined
  64. : t('You do not have permission to create a project.')
  65. }
  66. disabled={!canCreateProject}
  67. priority={orgHasProjects ? 'default' : 'primary'}
  68. to={`/organizations/${orgId}/projects/new/`}
  69. >
  70. {t('Create project')}
  71. </Button>
  72. );
  73. return (
  74. <Wrapper>
  75. <HeightWrapper>
  76. <img src={img} height={350} alt="Nothing to see" />
  77. <Content>
  78. <StyledPageHeading>{t('Remain Calm')}</StyledPageHeading>
  79. <HelpMessage>
  80. {t('You need at least one project to use this view')}
  81. </HelpMessage>
  82. <Actions gap={1}>
  83. {!orgHasProjects ? (
  84. createProjectAction
  85. ) : (
  86. <React.Fragment>
  87. {joinTeamAction}
  88. {createProjectAction}
  89. </React.Fragment>
  90. )}
  91. </Actions>
  92. </Content>
  93. </HeightWrapper>
  94. </Wrapper>
  95. );
  96. }
  97. }
  98. const StyledPageHeading = styled(PageHeading)`
  99. font-size: 28px;
  100. margin-bottom: ${space(1.5)};
  101. `;
  102. const HelpMessage = styled('div')`
  103. margin-bottom: ${space(2)};
  104. `;
  105. const Flex = styled('div')`
  106. display: flex;
  107. `;
  108. const Wrapper = styled(Flex)`
  109. flex: 1;
  110. align-items: center;
  111. justify-content: center;
  112. `;
  113. const HeightWrapper = styled(Flex)`
  114. height: 350px;
  115. `;
  116. const Content = styled(Flex)`
  117. flex-direction: column;
  118. justify-content: center;
  119. margin-left: 40px;
  120. `;
  121. const Actions = styled(ButtonBar)`
  122. width: fit-content;
  123. `;