welcome.tsx 5.9 KB

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