optInModal.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import {useCallback} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import HeroImg from 'sentry-images/spot/custom-metrics-opt-in-modal-hero.png';
  5. import {type ModalRenderProps, openModal} from 'sentry/actionCreators/modal';
  6. import {Button, type ButtonProps, LinkButton} from 'sentry/components/button';
  7. import {IconClose} from 'sentry/icons/iconClose';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import type {Organization} from 'sentry/types';
  11. import {trackAnalytics} from 'sentry/utils/analytics';
  12. import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import {OrganizationContext} from 'sentry/views/organizationContext';
  15. export function useOptInModal() {
  16. const organization = useOrganization();
  17. const [dismissedModal, setDismissedModal] = useLocalStorageState(
  18. 'sentry:metrics-opt-in-modal-dismissed',
  19. false
  20. );
  21. if (!dismissedModal) {
  22. openMetricsOptInModal(organization, setDismissedModal);
  23. }
  24. }
  25. function openMetricsOptInModal(
  26. organization: Organization,
  27. setDismissedModal: (value: boolean) => void
  28. ) {
  29. return openModal(
  30. deps => (
  31. <OrganizationContext.Provider value={organization}>
  32. <OptInModal
  33. {...deps}
  34. closeModal={() => {
  35. setDismissedModal(true);
  36. deps.closeModal();
  37. }}
  38. />
  39. </OrganizationContext.Provider>
  40. ),
  41. {modalCss, closeEvents: 'none'}
  42. );
  43. }
  44. function OptInModal({closeModal}: ModalRenderProps) {
  45. const organization = useOrganization();
  46. const handleCloseModal = useCallback(
  47. source => {
  48. trackAnalytics('ddm.opt_in_modal_closed', {organization, source});
  49. closeModal();
  50. },
  51. [organization, closeModal]
  52. );
  53. return (
  54. <Content>
  55. <Subheader>{t('Sentry Metrics: Now in Beta')}</Subheader>
  56. <Header>{t('Track and solve what matters')}</Header>
  57. <CloseButton onClick={() => handleCloseModal('close_button')} />
  58. <p>
  59. {t(
  60. 'Create custom metrics to track and visualize the data points you care about over time, like processing time, checkout conversion rate, or user signups, and pinpoint and solve issues faster by using correlated traces.'
  61. )}
  62. </p>
  63. <ListHeader>{t('A few notes:')}</ListHeader>
  64. <List>
  65. <li>{t('This is a beta, so it may be buggy - we recognise the irony.')}</li>
  66. <li>
  67. {t('If we hit any scaling issues, we may need to turn off metrics ingestion.')}
  68. </li>
  69. <li>
  70. {t(
  71. 'We plan to charge for it in the future once it becomes generally available, but it is completely free to use during the beta.'
  72. )}
  73. </li>
  74. </List>
  75. <ButtonGroup>
  76. <LinkButton
  77. external
  78. href="https://help.sentry.io/product-features/other/metrics-beta-faqs/"
  79. onClick={() => {
  80. trackAnalytics('ddm.opt_in_modal_closed', {
  81. organization,
  82. source: 'learn_more',
  83. });
  84. }}
  85. >
  86. {t('Learn more')}
  87. </LinkButton>
  88. <Button onClick={() => handleCloseModal('im_in')} priority="primary">
  89. {t("I'm In")}
  90. </Button>
  91. </ButtonGroup>
  92. <Note>
  93. {t(
  94. 'Metrics is currently supported in the following SDKs, with more coming soon: JavaScript, Node.js, Python, PHP, Laravel, Symfony, Ruby, Rust, Java, React Native, Unity & .NET.'
  95. )}
  96. </Note>
  97. </Content>
  98. );
  99. }
  100. const Content = styled('div')`
  101. background: top no-repeat url('${HeroImg}');
  102. background-size: contain;
  103. margin-inline: -46px;
  104. padding: 170px 46px 32px 46px;
  105. font-size: ${p => p.theme.fontSizeMedium};
  106. border-radius: ${p => p.theme.borderRadius};
  107. p,
  108. ul {
  109. line-height: 1.6rem;
  110. }
  111. `;
  112. const Subheader = styled('h2')`
  113. color: ${p => p.theme.purple300};
  114. font-size: ${p => p.theme.fontSizeSmall};
  115. font-weight: bold;
  116. margin-bottom: ${space(3)};
  117. text-transform: uppercase;
  118. `;
  119. const Header = styled('h1')`
  120. font-size: ${p => p.theme.headerFontSize};
  121. font-weight: bold;
  122. margin: ${space(1.5)} 0;
  123. `;
  124. const ListHeader = styled('div')`
  125. margin: ${space(1)};
  126. `;
  127. const List = styled('ul')`
  128. margin: ${space(1)};
  129. `;
  130. const ButtonGroup = styled('div')`
  131. display: flex;
  132. justify-content: flex-end;
  133. gap: ${space(1)};
  134. `;
  135. const Note = styled('div')`
  136. color: ${p => p.theme.gray300};
  137. font-size: ${p => p.theme.fontSizeExtraSmall};
  138. margin-top: ${space(2)};
  139. line-height: 1rem;
  140. `;
  141. const CloseButton = styled((p: Omit<ButtonProps, 'aria-label'>) => (
  142. <Button
  143. aria-label={t('Close Modal')}
  144. icon={<IconClose legacySize="10px" />}
  145. size="zero"
  146. {...p}
  147. />
  148. ))`
  149. position: absolute;
  150. top: 0;
  151. right: 0;
  152. transform: translate(50%, -50%);
  153. border-radius: 50%;
  154. height: 24px;
  155. width: 24px;
  156. `;
  157. export const modalCss = css`
  158. width: 100%;
  159. max-width: 532px;
  160. [role='document'] {
  161. position: relative;
  162. padding: 0 45px;
  163. box-shadow: none;
  164. }
  165. `;