welcome.tsx 6.2 KB

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