createProjectsFooter.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import {Fragment} 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 {createProject} from 'sentry/actionCreators/projects';
  12. import Button from 'sentry/components/button';
  13. import {PlatformKey} from 'sentry/data/platformCategories';
  14. import {t, tn} from 'sentry/locale';
  15. import ProjectsStore from 'sentry/stores/projectsStore';
  16. import space from 'sentry/styles/space';
  17. import {Organization} from 'sentry/types';
  18. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  19. import testableTransition from 'sentry/utils/testableTransition';
  20. import useApi from 'sentry/utils/useApi';
  21. import useTeams from 'sentry/utils/useTeams';
  22. import {OnboardingState} from '../types';
  23. import {usePersistedOnboardingState} from '../utils';
  24. import GenericFooter from './genericFooter';
  25. type Props = {
  26. clearPlatforms: () => void;
  27. genSkipOnboardingLink: () => React.ReactNode;
  28. onComplete: () => void;
  29. organization: Organization;
  30. platforms: PlatformKey[];
  31. };
  32. export default function CreateProjectsFooter({
  33. organization,
  34. platforms,
  35. onComplete,
  36. genSkipOnboardingLink,
  37. clearPlatforms,
  38. }: Props) {
  39. const api = useApi();
  40. const {teams} = useTeams();
  41. const [persistedOnboardingState, setPersistedOnboardingState] =
  42. usePersistedOnboardingState();
  43. const createProjects = async () => {
  44. if (!persistedOnboardingState) {
  45. // Do nothing if client state is not loaded yet.
  46. return;
  47. }
  48. try {
  49. addLoadingMessage(t('Creating projects'));
  50. const responses = await Promise.all(
  51. platforms
  52. .filter(platform => !persistedOnboardingState.platformToProjectIdMap[platform])
  53. .map(platform =>
  54. createProject(api, organization.slug, teams[0].slug, platform, platform, {
  55. defaultRules: false,
  56. })
  57. )
  58. );
  59. const nextState: OnboardingState = {
  60. platformToProjectIdMap: persistedOnboardingState.platformToProjectIdMap,
  61. selectedPlatforms: platforms,
  62. state: 'projects_selected',
  63. url: 'setup-docs/',
  64. };
  65. responses.forEach(p => (nextState.platformToProjectIdMap[p.platform] = p.slug));
  66. setPersistedOnboardingState(nextState);
  67. responses.forEach(data => ProjectsStore.onCreateSuccess(data, organization.slug));
  68. trackAdvancedAnalyticsEvent('growth.onboarding_set_up_your_projects', {
  69. platforms: platforms.join(','),
  70. platform_count: platforms.length,
  71. organization,
  72. });
  73. clearIndicators();
  74. setTimeout(onComplete);
  75. } catch (err) {
  76. addErrorMessage(t('Failed to create projects'));
  77. Sentry.captureException(err);
  78. }
  79. };
  80. const renderPlatform = (platform: PlatformKey) => {
  81. platform = platform || 'other';
  82. return <SelectedPlatformIcon key={platform} platform={platform} size={23} />;
  83. };
  84. return (
  85. <GenericFooter>
  86. {genSkipOnboardingLink()}
  87. <SelectionWrapper>
  88. {platforms.length ? (
  89. <Fragment>
  90. <div>{platforms.map(renderPlatform)}</div>
  91. <PlatformSelected>
  92. {tn('%s platform selected', '%s platforms selected', platforms.length)}
  93. <ClearButton priority="link" onClick={clearPlatforms} size="zero">
  94. {t('Clear')}
  95. </ClearButton>
  96. </PlatformSelected>
  97. </Fragment>
  98. ) : null}
  99. </SelectionWrapper>
  100. <ButtonWrapper>
  101. <Button
  102. priority="primary"
  103. onClick={createProjects}
  104. disabled={platforms.length === 0}
  105. data-test-id="platform-select-next"
  106. >
  107. {tn('Create Project', 'Create Projects', platforms.length)}
  108. </Button>
  109. </ButtonWrapper>
  110. </GenericFooter>
  111. );
  112. }
  113. const SelectionWrapper = styled(motion.div)`
  114. display: flex;
  115. flex-direction: column;
  116. justify-content: center;
  117. align-items: center;
  118. @media (max-width: ${p => p.theme.breakpoints.small}) {
  119. display: none;
  120. }
  121. `;
  122. SelectionWrapper.defaultProps = {
  123. transition: testableTransition({
  124. duration: 1.8,
  125. }),
  126. };
  127. const ButtonWrapper = styled(motion.div)`
  128. display: flex;
  129. height: 100%;
  130. align-items: center;
  131. margin-right: ${space(4)};
  132. `;
  133. ButtonWrapper.defaultProps = {
  134. transition: testableTransition({
  135. duration: 1.3,
  136. }),
  137. };
  138. const SelectedPlatformIcon = styled(PlatformIcon)`
  139. margin-right: ${space(1)};
  140. `;
  141. const PlatformSelected = styled('div')`
  142. margin-top: ${space(1)};
  143. `;
  144. const ClearButton = styled(Button)`
  145. margin-left: ${space(2)};
  146. `;