createProjectsFooter.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import {Fragment, useCallback, useContext} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import {motion} from 'framer-motion';
  5. import {PlatformIcon} from 'platformicons';
  6. import {
  7. addErrorMessage,
  8. addLoadingMessage,
  9. clearIndicators,
  10. } from 'sentry/actionCreators/indicator';
  11. import {openModal} from 'sentry/actionCreators/modal';
  12. import {createProject} from 'sentry/actionCreators/projects';
  13. import {Button} from 'sentry/components/button';
  14. import {SUPPORTED_LANGUAGES} from 'sentry/components/onboarding/frameworkSuggestionModal';
  15. import {OnboardingContext} from 'sentry/components/onboarding/onboardingContext';
  16. import {t} from 'sentry/locale';
  17. import ProjectsStore from 'sentry/stores/projectsStore';
  18. import {space} from 'sentry/styles/space';
  19. import {
  20. OnboardingProjectStatus,
  21. OnboardingSelectedSDK,
  22. Organization,
  23. Project,
  24. } from 'sentry/types';
  25. import {trackAnalytics} from 'sentry/utils/analytics';
  26. import testableTransition from 'sentry/utils/testableTransition';
  27. import useApi from 'sentry/utils/useApi';
  28. import useProjects from 'sentry/utils/useProjects';
  29. import useTeams from 'sentry/utils/useTeams';
  30. import GenericFooter from './genericFooter';
  31. type Props = {
  32. clearPlatform: () => void;
  33. genSkipOnboardingLink: () => React.ReactNode;
  34. onComplete: (selectedPlatform: OnboardingSelectedSDK) => void;
  35. organization: Organization;
  36. selectedPlatform?: OnboardingSelectedSDK;
  37. };
  38. export function CreateProjectsFooter({
  39. organization,
  40. selectedPlatform,
  41. onComplete,
  42. genSkipOnboardingLink,
  43. clearPlatform,
  44. }: Props) {
  45. const frameworkSelectionEnabled = !!organization?.features.includes(
  46. 'onboarding-sdk-selection'
  47. );
  48. const api = useApi();
  49. const {teams} = useTeams();
  50. const onboardingContext = useContext(OnboardingContext);
  51. const {projects} = useProjects();
  52. const createPlatformProject = useCallback(
  53. async (selectedFramework?: OnboardingSelectedSDK) => {
  54. if (!selectedPlatform) {
  55. return;
  56. }
  57. let createProjectForPlatform: OnboardingSelectedSDK | undefined = undefined;
  58. if (selectedFramework) {
  59. createProjectForPlatform = projects.find(
  60. p => p.platform === selectedFramework.key
  61. )
  62. ? undefined
  63. : selectedFramework;
  64. } else {
  65. createProjectForPlatform = projects.find(
  66. p => p.platform === onboardingContext.data.selectedSDK?.key
  67. )
  68. ? undefined
  69. : onboardingContext.data.selectedSDK;
  70. }
  71. if (!createProjectForPlatform) {
  72. const platform = selectedFramework ? selectedFramework : selectedPlatform;
  73. trackAnalytics('growth.onboarding_set_up_your_project', {
  74. platform: selectedPlatform.key,
  75. organization,
  76. });
  77. onComplete(platform);
  78. return;
  79. }
  80. try {
  81. addLoadingMessage(t('Creating project'));
  82. const response = (await createProject({
  83. api,
  84. orgSlug: organization.slug,
  85. team: teams[0].slug,
  86. platform: createProjectForPlatform.key,
  87. name: createProjectForPlatform.key,
  88. options: {
  89. defaultRules: true,
  90. },
  91. })) as Project;
  92. ProjectsStore.onCreateSuccess(response, organization.slug);
  93. onboardingContext.setData({
  94. selectedSDK: createProjectForPlatform,
  95. projects: {
  96. ...onboardingContext.data.projects,
  97. [response.slug]: {
  98. slug: response.slug,
  99. status: OnboardingProjectStatus.WAITING,
  100. },
  101. },
  102. });
  103. trackAnalytics('growth.onboarding_set_up_your_project', {
  104. platform: selectedPlatform.key,
  105. organization,
  106. });
  107. clearIndicators();
  108. setTimeout(() => onComplete(createProjectForPlatform!));
  109. } catch (err) {
  110. addErrorMessage(t('Failed to create project'));
  111. Sentry.captureException(err);
  112. }
  113. },
  114. [onboardingContext, selectedPlatform, api, organization, teams, projects, onComplete]
  115. );
  116. const handleProjectCreation = useCallback(async () => {
  117. if (!selectedPlatform) {
  118. return;
  119. }
  120. if (
  121. selectedPlatform.type !== 'language' ||
  122. !Object.values(SUPPORTED_LANGUAGES).includes(
  123. selectedPlatform.language as SUPPORTED_LANGUAGES
  124. )
  125. ) {
  126. createPlatformProject();
  127. return;
  128. }
  129. const {FrameworkSuggestionModal, modalCss} = await import(
  130. 'sentry/components/onboarding/frameworkSuggestionModal'
  131. );
  132. openModal(
  133. deps => (
  134. <FrameworkSuggestionModal
  135. {...deps}
  136. organization={organization}
  137. selectedPlatform={selectedPlatform}
  138. onConfigure={selectedFramework => {
  139. createPlatformProject(selectedFramework);
  140. }}
  141. onSkip={createPlatformProject}
  142. />
  143. ),
  144. {
  145. modalCss,
  146. onClose: () => {
  147. trackAnalytics('onboarding.select_framework_modal_close_button_clicked', {
  148. platform: selectedPlatform.key,
  149. organization,
  150. });
  151. },
  152. }
  153. );
  154. }, [selectedPlatform, createPlatformProject, organization]);
  155. return (
  156. <GenericFooter>
  157. {genSkipOnboardingLink()}
  158. <SelectionWrapper>
  159. {selectedPlatform ? (
  160. <Fragment>
  161. <div>
  162. <SelectedPlatformIcon
  163. platform={selectedPlatform.key ?? 'other'}
  164. size={23}
  165. />
  166. </div>
  167. <PlatformsSelected>
  168. {t('1 platform selected')}
  169. <ClearButton priority="link" onClick={clearPlatform} size="zero">
  170. {t('Clear')}
  171. </ClearButton>
  172. </PlatformsSelected>
  173. </Fragment>
  174. ) : null}
  175. </SelectionWrapper>
  176. <ButtonWrapper>
  177. <Button
  178. priority="primary"
  179. onClick={() =>
  180. frameworkSelectionEnabled ? handleProjectCreation() : createPlatformProject()
  181. }
  182. disabled={!selectedPlatform}
  183. data-test-id="platform-select-next"
  184. >
  185. {t('Create Project')}
  186. </Button>
  187. </ButtonWrapper>
  188. </GenericFooter>
  189. );
  190. }
  191. const SelectionWrapper = styled(motion.div)`
  192. display: flex;
  193. flex-direction: column;
  194. justify-content: center;
  195. align-items: center;
  196. @media (max-width: ${p => p.theme.breakpoints.small}) {
  197. display: none;
  198. }
  199. `;
  200. SelectionWrapper.defaultProps = {
  201. transition: testableTransition({
  202. duration: 1.8,
  203. }),
  204. };
  205. const ButtonWrapper = styled(motion.div)`
  206. display: flex;
  207. height: 100%;
  208. align-items: center;
  209. margin-right: ${space(4)};
  210. margin-left: ${space(4)};
  211. `;
  212. ButtonWrapper.defaultProps = {
  213. transition: testableTransition({
  214. duration: 1.3,
  215. }),
  216. };
  217. const SelectedPlatformIcon = styled(PlatformIcon)`
  218. margin-right: ${space(1)};
  219. `;
  220. const PlatformsSelected = styled('div')`
  221. margin-top: ${space(1)};
  222. `;
  223. const ClearButton = styled(Button)`
  224. margin-left: ${space(2)};
  225. padding: 0;
  226. `;