codecovPromotionModal.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import {useEffect} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import type {ModalRenderProps} from 'sentry/actionCreators/modal';
  5. import {Button} from 'sentry/components/button';
  6. import ButtonBar from 'sentry/components/buttonBar';
  7. import HighlightModalContainer from 'sentry/components/highlightModalContainer';
  8. import {IconArrow, IconCodecov} from 'sentry/icons';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import type {Organization} from 'sentry/types/organization';
  12. import PromotionPriceDisplay from 'getsentry/components/promotionPriceDisplay';
  13. import withSubscription from 'getsentry/components/withSubscription';
  14. import type {Subscription} from 'getsentry/types';
  15. import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics';
  16. import {getCodecovJwtLink, useCodecovJwt} from 'getsentry/utils/useCodecovJwt';
  17. interface Props extends ModalRenderProps {
  18. organization: Organization;
  19. subscription: Subscription;
  20. onAccept?: () => void;
  21. }
  22. function CodecovPromotionModal(props: Props) {
  23. const {organization, closeModal, subscription} = props;
  24. const {data: jwtData, isPending} = useCodecovJwt(organization.slug);
  25. const codecovLink = getCodecovJwtLink('sentry-app-subscription-overview', jwtData);
  26. useEffect(() => {
  27. trackGetsentryAnalytics('growth.codecov_promotion_opened', {
  28. organization,
  29. subscription,
  30. });
  31. }, [organization, subscription]);
  32. return (
  33. <HighlightModalContainer topWidth="200px" bottomWidth="150px">
  34. <InnerContent>
  35. <Subheader>
  36. <IconCodecov />
  37. {t('Codecov + Sentry Limited time offer')}
  38. </Subheader>
  39. <AddCodeCoveragerHeader>{t('Try Code Coverage')}</AddCodeCoveragerHeader>
  40. <p>
  41. {t(
  42. 'Find untested code causing errors and avoid similar errors in the future with Sentry and Codecov.*'
  43. )}
  44. </p>
  45. <PromotionPriceComparison>
  46. <PromotionPriceDisplay price={60} title="Current Price" showDecimals={false} />
  47. <OffsetIconArrow direction="right" size="lg" />
  48. <PriceWrapper>
  49. <PromotionPriceDisplay
  50. price={29}
  51. title="Starts At*"
  52. promo
  53. showDecimals={false}
  54. />
  55. <SeatText>{t('Includes 5 seats')}</SeatText>
  56. </PriceWrapper>
  57. </PromotionPriceComparison>
  58. <StyledButtonBar gap={1}>
  59. <Button
  60. size="md"
  61. priority="primary"
  62. disabled={isPending || !codecovLink}
  63. onClick={() => {
  64. if (!codecovLink) {
  65. return;
  66. }
  67. trackGetsentryAnalytics('growth.codecov_promotion_accept', {
  68. organization,
  69. subscription,
  70. });
  71. window.location.assign(codecovLink);
  72. }}
  73. >
  74. {t('Start 14-day Free Trial')}
  75. </Button>
  76. <Button
  77. data-test-id="maybe-later"
  78. priority="default"
  79. onClick={() => {
  80. trackGetsentryAnalytics('growth.codecov_promotion_decline', {
  81. organization,
  82. subscription,
  83. });
  84. closeModal();
  85. }}
  86. >
  87. {t('Maybe Later')}
  88. </Button>
  89. </StyledButtonBar>
  90. <DisclaimerText>
  91. {t(
  92. '* See code coverage in stack traces with the Sentry GitHub Integration available on a Team or Business plan.'
  93. )}
  94. </DisclaimerText>
  95. </InnerContent>
  96. </HighlightModalContainer>
  97. );
  98. }
  99. export default withSubscription(CodecovPromotionModal);
  100. const Subheader = styled('div')`
  101. text-transform: uppercase;
  102. font-weight: bold;
  103. color: ${p => p.theme.purple300};
  104. font-size: ${p => p.theme.fontSizeMedium};
  105. gap: ${space(0.5)};
  106. display: flex;
  107. justify-content: flex-start;
  108. margin-bottom: ${space(1)};
  109. `;
  110. const DisclaimerText = styled('div')`
  111. font-size: ${p => p.theme.fontSizeExtraSmall};
  112. color: ${p => p.theme.gray400};
  113. margin-top: ${space(1)};
  114. `;
  115. const PromotionPriceComparison = styled('div')`
  116. display: flex;
  117. gap: ${space(2)};
  118. `;
  119. const StyledButtonBar = styled(ButtonBar)`
  120. max-width: 150px;
  121. margin-top: ${space(2)};
  122. `;
  123. const InnerContent = styled('div')`
  124. padding: 20px 30px 20px;
  125. font-size: ${p => p.theme.fontSizeLarge};
  126. `;
  127. const PriceWrapper = styled('div')`
  128. display: flex;
  129. flex-direction: column;
  130. `;
  131. const SeatText = styled('div')`
  132. font-size: ${p => p.theme.fontSizeSmall};
  133. color: ${p => p.theme.gray300};
  134. `;
  135. const OffsetIconArrow = styled(IconArrow)`
  136. margin-top: 28px;
  137. `;
  138. const AddCodeCoveragerHeader = styled('h4')`
  139. margin-bottom: 12px;
  140. `;
  141. export const modalCss = css`
  142. width: 100%;
  143. max-width: 540px;
  144. [role='document'] {
  145. position: relative;
  146. }
  147. `;