platform.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import {Component} from 'react';
  2. import {motion} from 'framer-motion';
  3. import {addErrorMessage} from 'app/actionCreators/indicator';
  4. import {createProject} from 'app/actionCreators/projects';
  5. import ProjectActions from 'app/actions/projectActions';
  6. import {Client} from 'app/api';
  7. import Button from 'app/components/button';
  8. import ExternalLink from 'app/components/links/externalLink';
  9. import PlatformPicker from 'app/components/platformPicker';
  10. import {PlatformKey} from 'app/data/platformCategories';
  11. import {t, tct} from 'app/locale';
  12. import {Team} from 'app/types';
  13. import trackAdvancedAnalyticsEvent from 'app/utils/analytics/trackAdvancedAnalyticsEvent';
  14. import withApi from 'app/utils/withApi';
  15. import withTeams from 'app/utils/withTeams';
  16. import StepHeading from './components/stepHeading';
  17. import {StepProps} from './types';
  18. type Props = StepProps & {
  19. api: Client;
  20. teams: Team[];
  21. };
  22. type State = {
  23. /**
  24. * This will be flipped to true immediately before creating the first
  25. * project. We use state here to avoid the intermittent prop value where
  26. * the project is created but the store hasn't propagated its value to the
  27. * component yet, leaving a brief period where the button will flash
  28. * between labels / disabled states.
  29. */
  30. firstProjectCreated: boolean;
  31. /**
  32. * `progressing` indicates that we are moving to the next step. Again, this
  33. * is kept as state to avoid intermittent states causing flickering of the
  34. * button.
  35. */
  36. progressing: boolean;
  37. };
  38. class OnboardingPlatform extends Component<Props, State> {
  39. state: State = {
  40. firstProjectCreated: false,
  41. progressing: false,
  42. };
  43. componentDidMount() {
  44. trackAdvancedAnalyticsEvent('growth.onboarding_load_choose_platform', {
  45. organization: this.props.organization ?? null,
  46. });
  47. }
  48. componentDidUpdate(prevProps: Props) {
  49. if (prevProps.active && !this.props.active) {
  50. // eslint-disable-next-line react/no-did-update-set-state
  51. this.setState({progressing: false});
  52. }
  53. }
  54. get hasFirstProject() {
  55. return this.props.project || this.state.firstProjectCreated;
  56. }
  57. get continueButtonLabel() {
  58. if (this.state.progressing) {
  59. return t('Creating Project...');
  60. }
  61. if (!this.hasFirstProject) {
  62. return t('Create Project');
  63. }
  64. if (!this.props.active) {
  65. return t('Project Created');
  66. }
  67. return t('Set Up Your Project');
  68. }
  69. async createFirstProject(platform: PlatformKey) {
  70. const {api, orgId, teams} = this.props;
  71. if (this.hasFirstProject) {
  72. return;
  73. }
  74. if (teams.length < 1) {
  75. return;
  76. }
  77. this.setState({firstProjectCreated: true});
  78. try {
  79. const data = await createProject(api, orgId, teams[0].slug, orgId, platform, {
  80. defaultRules: false,
  81. });
  82. ProjectActions.createSuccess(data);
  83. } catch (error) {
  84. addErrorMessage(t('Failed to create project'));
  85. throw error;
  86. }
  87. }
  88. handleSetPlatform = (platform: PlatformKey | null) => this.props.onUpdate({platform});
  89. handleContinue = async () => {
  90. this.setState({progressing: true});
  91. const {platform} = this.props;
  92. if (platform === null) {
  93. return;
  94. }
  95. trackAdvancedAnalyticsEvent('growth.onboarding_set_up_your_project', {
  96. platform,
  97. organization: this.props.organization ?? null,
  98. });
  99. // Create their first project if they don't already have one. This is a
  100. // no-op if they already have a project.
  101. await this.createFirstProject(platform);
  102. this.props.onComplete({});
  103. };
  104. render() {
  105. const {active, project, platform} = this.props;
  106. const selectedPlatform = platform || (project && project.platform);
  107. const continueDisabled = this.state.progressing || (this.hasFirstProject && !active);
  108. return (
  109. <div>
  110. <StepHeading step={1}>Choose your project’s platform</StepHeading>
  111. <motion.div
  112. variants={{
  113. initial: {y: 30, opacity: 0},
  114. animate: {y: 0, opacity: 1},
  115. exit: {opacity: 0},
  116. }}
  117. >
  118. <p>
  119. {tct(
  120. `Variety is the spice of application monitoring. Sentry SDKs integrate
  121. with most languages and platforms your developer heart desires.
  122. [link:View the full list].`,
  123. {link: <ExternalLink href="https://docs.sentry.io/platforms/" />}
  124. )}
  125. </p>
  126. <PlatformPicker
  127. noAutoFilter
  128. platform={selectedPlatform}
  129. setPlatform={this.handleSetPlatform}
  130. source="Onboarding"
  131. organization={this.props.organization}
  132. />
  133. <p>
  134. {tct(
  135. `Don't see your platform-of-choice? Fear not. Select
  136. [otherPlatformLink:other platform] when using a [communityClient:community client].
  137. Need help? Learn more in [docs:our docs].`,
  138. {
  139. otherPlatformLink: (
  140. <Button
  141. priority="link"
  142. onClick={() => this.handleSetPlatform('other')}
  143. />
  144. ),
  145. communityClient: (
  146. <ExternalLink href="https://docs.sentry.io/platforms/#community-supported" />
  147. ),
  148. docs: <ExternalLink href="https://docs.sentry.io/platforms/" />,
  149. }
  150. )}
  151. </p>
  152. {selectedPlatform && (
  153. <Button
  154. data-test-id="platform-select-next"
  155. priority="primary"
  156. disabled={continueDisabled}
  157. onClick={this.handleContinue}
  158. >
  159. {this.continueButtonLabel}
  160. </Button>
  161. )}
  162. </motion.div>
  163. </div>
  164. );
  165. }
  166. }
  167. export default withApi(withTeams(OnboardingPlatform));