firstEventIndicator.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import styled from '@emotion/styled';
  2. import {AnimatePresence, HTMLMotionProps, motion, Variants} from 'framer-motion';
  3. import Button from 'sentry/components/button';
  4. import {IconCheckmark} from 'sentry/icons';
  5. import {t} from 'sentry/locale';
  6. import pulsingIndicatorStyles from 'sentry/styles/pulsingIndicator';
  7. import space from 'sentry/styles/space';
  8. import {Group} from 'sentry/types';
  9. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  10. import EventWaiter, {EventWaiterProps} from 'sentry/utils/eventWaiter';
  11. import testableTransition from 'sentry/utils/testableTransition';
  12. type RenderProps = {
  13. firstEventButton: React.ReactNode;
  14. indicator: React.ReactNode;
  15. };
  16. interface FirstEventIndicatorProps extends Omit<EventWaiterProps, 'children' | 'api'> {
  17. children: (props: RenderProps) => React.ReactNode;
  18. }
  19. const FirstEventIndicator = ({children, ...props}: FirstEventIndicatorProps) => (
  20. <EventWaiter {...props}>
  21. {({firstIssue}) =>
  22. children({
  23. indicator: <Indicator firstIssue={firstIssue} {...props} />,
  24. firstEventButton: (
  25. <Button
  26. title={t("You'll need to send your first error to continue")}
  27. tooltipProps={{disabled: !!firstIssue}}
  28. disabled={!firstIssue}
  29. priority="primary"
  30. onClick={() =>
  31. trackAdvancedAnalyticsEvent('growth.onboarding_take_to_error', {
  32. organization: props.organization,
  33. })
  34. }
  35. to={`/organizations/${props.organization.slug}/issues/${
  36. firstIssue !== true && firstIssue !== null ? `${firstIssue.id}/` : ''
  37. }`}
  38. >
  39. {t('Take me to my error')}
  40. </Button>
  41. ),
  42. })
  43. }
  44. </EventWaiter>
  45. );
  46. interface IndicatorProps extends Omit<EventWaiterProps, 'children' | 'api'> {
  47. firstIssue: Group | null | true;
  48. }
  49. const Indicator = ({firstIssue}: IndicatorProps) => (
  50. <Container>
  51. <AnimatePresence>
  52. {!firstIssue ? <Waiting key="waiting" /> : <Success key="received" />}
  53. </AnimatePresence>
  54. </Container>
  55. );
  56. const Container = styled('div')`
  57. display: grid;
  58. grid-template-columns: 1fr;
  59. justify-content: right;
  60. `;
  61. const StatusWrapper = styled(motion.div)`
  62. display: grid;
  63. grid-template-columns: 1fr max-content;
  64. gap: ${space(1)};
  65. align-items: center;
  66. font-size: ${p => p.theme.fontSizeMedium};
  67. /* Keep the wrapper in the parent grids first cell for transitions */
  68. grid-column: 1;
  69. grid-row: 1;
  70. `;
  71. StatusWrapper.defaultProps = {
  72. initial: 'initial',
  73. animate: 'animate',
  74. exit: 'exit',
  75. variants: {
  76. initial: {opacity: 0, y: -10},
  77. animate: {
  78. opacity: 1,
  79. y: 0,
  80. transition: testableTransition({when: 'beforeChildren', staggerChildren: 0.35}),
  81. },
  82. exit: {opacity: 0, y: 10},
  83. },
  84. };
  85. const Waiting = (props: HTMLMotionProps<'div'>) => (
  86. <StatusWrapper {...props}>
  87. <AnimatedText>{t('Waiting to receive first event to continue')}</AnimatedText>
  88. <WaitingIndicator />
  89. </StatusWrapper>
  90. );
  91. const Success = (props: HTMLMotionProps<'div'>) => (
  92. <StatusWrapper {...props}>
  93. <AnimatedText>{t('Event was received!')}</AnimatedText>
  94. <ReceivedIndicator />
  95. </StatusWrapper>
  96. );
  97. const indicatorAnimation: Variants = {
  98. initial: {opacity: 0, y: -10},
  99. animate: {opacity: 1, y: 0},
  100. exit: {opacity: 0, y: 10},
  101. };
  102. const AnimatedText = styled(motion.div)``;
  103. AnimatedText.defaultProps = {
  104. variants: indicatorAnimation,
  105. transition: testableTransition(),
  106. };
  107. const WaitingIndicator = styled(motion.div)`
  108. margin: 0 6px;
  109. ${pulsingIndicatorStyles};
  110. `;
  111. WaitingIndicator.defaultProps = {
  112. variants: indicatorAnimation,
  113. transition: testableTransition(),
  114. };
  115. const ReceivedIndicator = styled(IconCheckmark)`
  116. color: #fff;
  117. background: ${p => p.theme.green300};
  118. border-radius: 50%;
  119. padding: 3px;
  120. margin: 0 ${space(0.25)};
  121. `;
  122. ReceivedIndicator.defaultProps = {
  123. size: 'sm',
  124. };
  125. export {Indicator};
  126. export default FirstEventIndicator;