platform.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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/advancedAnalytics';
  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(
  45. 'growth.onboarding_load_choose_platform',
  46. {},
  47. this.props.organization ?? null,
  48. {sendMarketing: true}
  49. );
  50. }
  51. componentDidUpdate(prevProps: Props) {
  52. if (prevProps.active && !this.props.active) {
  53. // eslint-disable-next-line react/no-did-update-set-state
  54. this.setState({progressing: false});
  55. }
  56. }
  57. get hasFirstProject() {
  58. return this.props.project || this.state.firstProjectCreated;
  59. }
  60. get continueButtonLabel() {
  61. if (this.state.progressing) {
  62. return t('Creating Project...');
  63. }
  64. if (!this.hasFirstProject) {
  65. return t('Create Project');
  66. }
  67. if (!this.props.active) {
  68. return t('Project Created');
  69. }
  70. return t('Set Up Your Project');
  71. }
  72. async createFirstProject(platform: PlatformKey) {
  73. const {api, orgId, teams} = this.props;
  74. if (this.hasFirstProject) {
  75. return;
  76. }
  77. if (teams.length < 1) {
  78. return;
  79. }
  80. this.setState({firstProjectCreated: true});
  81. try {
  82. const data = await createProject(api, orgId, teams[0].slug, orgId, platform, {
  83. defaultRules: false,
  84. });
  85. ProjectActions.createSuccess(data);
  86. } catch (error) {
  87. addErrorMessage(t('Failed to create project'));
  88. throw error;
  89. }
  90. }
  91. handleSetPlatform = (platform: PlatformKey | null) => this.props.onUpdate({platform});
  92. handleContinue = async () => {
  93. this.setState({progressing: true});
  94. const {platform} = this.props;
  95. if (platform === null) {
  96. return;
  97. }
  98. trackAdvancedAnalyticsEvent(
  99. 'growth.onboarding_set_up_your_project',
  100. {platform},
  101. this.props.organization ?? null,
  102. {sendMarketing: true}
  103. );
  104. // Create their first project if they don't already have one. This is a
  105. // no-op if they already have a project.
  106. await this.createFirstProject(platform);
  107. this.props.onComplete({});
  108. };
  109. render() {
  110. const {active, project, platform} = this.props;
  111. const selectedPlatform = platform || (project && project.platform);
  112. const continueDisabled = this.state.progressing || (this.hasFirstProject && !active);
  113. return (
  114. <div>
  115. <StepHeading step={1}>Choose your project’s platform</StepHeading>
  116. <motion.div
  117. variants={{
  118. initial: {y: 30, opacity: 0},
  119. animate: {y: 0, opacity: 1},
  120. exit: {opacity: 0},
  121. }}
  122. >
  123. <p>
  124. {tct(
  125. `Variety is the spice of application monitoring. Sentry SDKs integrate
  126. with most languages and platforms your developer heart desires.
  127. [link:View the full list].`,
  128. {link: <ExternalLink href="https://docs.sentry.io/platforms/" />}
  129. )}
  130. </p>
  131. <PlatformPicker
  132. noAutoFilter
  133. platform={selectedPlatform}
  134. setPlatform={this.handleSetPlatform}
  135. source="Onboarding"
  136. organization={this.props.organization}
  137. />
  138. <p>
  139. {tct(
  140. `Don't see your platform-of-choice? Fear not. Select
  141. [otherPlatformLink:other platform] when using a [communityClient:community client].
  142. Need help? Learn more in [docs:our docs].`,
  143. {
  144. otherPlatformLink: (
  145. <Button
  146. priority="link"
  147. onClick={() => this.handleSetPlatform('other')}
  148. />
  149. ),
  150. communityClient: (
  151. <ExternalLink href="https://docs.sentry.io/platforms/#community-supported" />
  152. ),
  153. docs: <ExternalLink href="https://docs.sentry.io/platforms/" />,
  154. }
  155. )}
  156. </p>
  157. {selectedPlatform && (
  158. <Button
  159. data-test-id="platform-select-next"
  160. priority="primary"
  161. disabled={continueDisabled}
  162. onClick={this.handleContinue}
  163. >
  164. {this.continueButtonLabel}
  165. </Button>
  166. )}
  167. </motion.div>
  168. </div>
  169. );
  170. }
  171. }
  172. export default withApi(withTeams(OnboardingPlatform));