welcome.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import {Fragment, useCallback, useContext, useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {MotionProps} from 'framer-motion';
  4. import {motion} from 'framer-motion';
  5. import OnboardingInstall from 'sentry-images/spot/onboarding-install.svg';
  6. import {Button} from 'sentry/components/button';
  7. import Link from 'sentry/components/links/link';
  8. import {OnboardingContext} from 'sentry/components/onboarding/onboardingContext';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import {trackAnalytics} from 'sentry/utils/analytics';
  12. import testableTransition from 'sentry/utils/testableTransition';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import FallingError from 'sentry/views/onboarding/components/fallingError';
  15. import WelcomeBackground from 'sentry/views/onboarding/components/welcomeBackground';
  16. import {useOnboardingSidebar} from 'sentry/views/onboarding/useOnboardingSidebar';
  17. import type {StepProps} from './types';
  18. const fadeAway: MotionProps = {
  19. variants: {
  20. initial: {opacity: 0},
  21. animate: {opacity: 1, filter: 'blur(0px)'},
  22. exit: {opacity: 0, filter: 'blur(1px)'},
  23. },
  24. transition: testableTransition({duration: 0.8}),
  25. };
  26. type TextWrapperProps = {
  27. cta: React.ReactNode;
  28. src: string;
  29. subText: React.ReactNode;
  30. title: React.ReactNode;
  31. };
  32. function InnerAction({title, subText, cta, src}: TextWrapperProps) {
  33. return (
  34. <Fragment>
  35. <ActionImage src={src} />
  36. <TextWrapper>
  37. <ActionTitle>{title}</ActionTitle>
  38. <SubText>{subText}</SubText>
  39. </TextWrapper>
  40. <ButtonWrapper>{cta}</ButtonWrapper>
  41. </Fragment>
  42. );
  43. }
  44. function TargetedOnboardingWelcome(props: StepProps) {
  45. const organization = useOrganization();
  46. const onboardingContext = useContext(OnboardingContext);
  47. const {activateSidebar} = useOnboardingSidebar();
  48. const source = 'targeted_onboarding';
  49. useEffect(() => {
  50. trackAnalytics('growth.onboarding_start_onboarding', {
  51. organization,
  52. source,
  53. });
  54. if (onboardingContext.data.selectedSDK) {
  55. // At this point the selectedSDK shall be undefined but just in case, cleaning this up here too
  56. onboardingContext.setData({...onboardingContext.data, selectedSDK: undefined});
  57. }
  58. }, [organization, onboardingContext]);
  59. const handleComplete = useCallback(() => {
  60. trackAnalytics('growth.onboarding_clicked_instrument_app', {
  61. organization,
  62. source,
  63. });
  64. props.onComplete();
  65. }, [organization, source, props]);
  66. const handleSkipOnboarding = useCallback(() => {
  67. trackAnalytics('growth.onboarding_clicked_skip', {
  68. organization,
  69. source,
  70. });
  71. activateSidebar();
  72. }, [organization, source, activateSidebar]);
  73. return (
  74. <FallingError>
  75. {({fallingError, fallCount, isFalling}) => (
  76. <Wrapper>
  77. <WelcomeBackground />
  78. <motion.h1 {...fadeAway} style={{marginBottom: space(0.5)}}>
  79. {t('Welcome to Sentry')}
  80. </motion.h1>
  81. <SubHeaderText style={{marginBottom: space(4)}} {...fadeAway}>
  82. {t(
  83. 'Your code is probably broken. Maybe not. Find out for sure. Get started below.'
  84. )}
  85. </SubHeaderText>
  86. <ActionItem {...fadeAway}>
  87. <InnerAction
  88. title={t('Install Sentry')}
  89. subText={t(
  90. 'Select your languages or frameworks and install the SDKs to start tracking issues'
  91. )}
  92. src={OnboardingInstall}
  93. cta={
  94. <Fragment>
  95. <ButtonWithFill onClick={handleComplete} priority="primary">
  96. {t('Start')}
  97. </ButtonWithFill>
  98. {(fallCount === 0 || isFalling) && (
  99. <PositionedFallingError>{fallingError}</PositionedFallingError>
  100. )}
  101. </Fragment>
  102. }
  103. />
  104. </ActionItem>
  105. <motion.p style={{margin: 0}} {...fadeAway}>
  106. {t("Gee, I've used Sentry before.")}
  107. <br />
  108. <Link
  109. onClick={handleSkipOnboarding}
  110. to={`/organizations/${organization.slug}/issues/?referrer=onboarding-welcome-skip`}
  111. >
  112. {t('Skip onboarding.')}
  113. </Link>
  114. </motion.p>
  115. </Wrapper>
  116. )}
  117. </FallingError>
  118. );
  119. }
  120. export default TargetedOnboardingWelcome;
  121. const PositionedFallingError = styled('span')`
  122. display: block;
  123. position: absolute;
  124. right: 0px;
  125. top: 30px;
  126. `;
  127. const Wrapper = styled(motion.div)`
  128. position: relative;
  129. margin-top: auto;
  130. margin-bottom: auto;
  131. max-width: 400px;
  132. display: flex;
  133. flex-direction: column;
  134. align-items: center;
  135. text-align: center;
  136. margin-left: auto;
  137. margin-right: auto;
  138. h1 {
  139. font-size: 42px;
  140. }
  141. `;
  142. const ActionItem = styled(motion.div)`
  143. min-height: 120px;
  144. border-radius: ${space(0.5)};
  145. padding: ${space(2)};
  146. margin-bottom: ${space(2)};
  147. justify-content: space-around;
  148. border: 1px solid ${p => p.theme.gray200};
  149. @media (min-width: ${p => p.theme.breakpoints.small}) {
  150. display: grid;
  151. grid-template-columns: 125px auto 125px;
  152. width: 680px;
  153. align-items: center;
  154. }
  155. @media (max-width: ${p => p.theme.breakpoints.small}) {
  156. display: flex;
  157. flex-direction: column;
  158. }
  159. `;
  160. const TextWrapper = styled('div')`
  161. text-align: left;
  162. margin: auto ${space(3)};
  163. min-height: 70px;
  164. @media (max-width: ${p => p.theme.breakpoints.small}) {
  165. text-align: center;
  166. margin: ${space(1)} ${space(1)};
  167. margin-top: ${space(3)};
  168. }
  169. `;
  170. const ActionTitle = styled('h5')`
  171. font-weight: ${p => p.theme.fontWeightBold};
  172. margin: 0 0 ${space(0.5)};
  173. color: ${p => p.theme.gray400};
  174. `;
  175. const SubText = styled('span')`
  176. font-weight: ${p => p.theme.fontWeightNormal};
  177. color: ${p => p.theme.gray400};
  178. `;
  179. const SubHeaderText = styled(motion.h6)`
  180. color: ${p => p.theme.gray300};
  181. `;
  182. const ButtonWrapper = styled('div')`
  183. margin: ${space(1)};
  184. position: relative;
  185. `;
  186. const ActionImage = styled('img')`
  187. height: 100px;
  188. `;
  189. const ButtonWithFill = styled(Button)`
  190. width: 100%;
  191. position: relative;
  192. z-index: 1;
  193. `;