dataConsentBanner.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import {useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import dataConsentImage from 'sentry-images/spot/add-integration-provider.svg';
  4. import bannerStars from 'sentry-images/spot/ai-suggestion-banner-stars.svg';
  5. import {usePrompt} from 'sentry/actionCreators/prompts';
  6. import {Button} from 'sentry/components/button';
  7. import {IconClose} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import {defined} from 'sentry/utils';
  11. import getOrganizationAge from 'sentry/utils/getOrganizationAge';
  12. import useOrganization from 'sentry/utils/useOrganization';
  13. import useProjects from 'sentry/utils/useProjects';
  14. import {openDataConsentModal} from 'getsentry/actionCreators/modal';
  15. import withSubscription from 'getsentry/components/withSubscription';
  16. import type {Subscription} from 'getsentry/types';
  17. const titles = {
  18. issues: t('Help make Sentry less noisy and more precise'),
  19. alerts: t('Help make Sentry alerts less noisy and more actionable'),
  20. grouping: t('Help Sentry improve grouping quality'),
  21. };
  22. function DataConsentBanner({
  23. source,
  24. subscription,
  25. }: {
  26. source: string;
  27. subscription: Subscription;
  28. }) {
  29. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  30. const bannerTitle = titles[source];
  31. const organization = useOrganization();
  32. const {projects} = useProjects();
  33. const hasBillingAccess = organization.access.includes('org:billing');
  34. const hasOrgSentFirstEvent = useMemo(() => {
  35. return projects.map(project => project.firstEvent).some(Boolean);
  36. }, [projects]);
  37. const {isLoading, isError, isPromptDismissed, dismissPrompt} = usePrompt({
  38. feature: 'data_consent_banner',
  39. organization,
  40. });
  41. const organizationAge = getOrganizationAge(organization);
  42. const hideDataConsentBanner =
  43. isLoading ||
  44. isError ||
  45. isPromptDismissed ||
  46. organization.aggregatedDataConsent ||
  47. !hasBillingAccess ||
  48. (defined(subscription.msaUpdatedForDataConsent)
  49. ? !subscription.msaUpdatedForDataConsent
  50. : false) ||
  51. organizationAge < 60 ||
  52. organizationAge > 120 ||
  53. !hasOrgSentFirstEvent;
  54. if (hideDataConsentBanner) {
  55. return null;
  56. }
  57. return (
  58. <DataConsentBannerWrapper>
  59. <div>
  60. <DataConsentBannerTitle>{bannerTitle}</DataConsentBannerTitle>
  61. <Button
  62. analyticsEventKey="data_consent_banner.learn_more"
  63. analyticsEventName="Data Consent Banner: Learn More"
  64. analyticsParams={{source}}
  65. size="sm"
  66. onClick={() => openDataConsentModal()}
  67. >
  68. {t('Learn More')}
  69. </Button>
  70. </div>
  71. <DismissButton
  72. analyticsEventKey="data_consent_banner.dismissed"
  73. analyticsEventName="Data Consent Banner: Dismissed"
  74. analyticsParams={{source}}
  75. size="zero"
  76. borderless
  77. icon={<IconClose size="xs" />}
  78. aria-label={t('Dismiss')}
  79. onClick={() => dismissPrompt()}
  80. />
  81. <StarContainer>
  82. <LeftStars src={bannerStars} />
  83. </StarContainer>
  84. <IllustrationContainer>
  85. <RightStars src={bannerStars} />
  86. <Sentaur src={dataConsentImage} />
  87. </IllustrationContainer>
  88. </DataConsentBannerWrapper>
  89. );
  90. }
  91. export default withSubscription(DataConsentBanner, {noLoader: true});
  92. const DataConsentBannerWrapper = styled('div')`
  93. position: relative;
  94. border: 1px solid ${p => p.theme.border};
  95. border-radius: ${p => p.theme.borderRadius};
  96. padding: ${space(2)};
  97. margin-bottom: ${space(2)};
  98. grid-column: 1 / -1;
  99. background: linear-gradient(
  100. 90deg,
  101. ${p => p.theme.backgroundSecondary}00 0%,
  102. ${p => p.theme.backgroundSecondary}FF 70%,
  103. ${p => p.theme.backgroundSecondary}FF 100%
  104. );
  105. `;
  106. const DataConsentBannerTitle = styled('div')`
  107. font-size: ${p => p.theme.fontSizeExtraLarge};
  108. margin-bottom: ${space(1)};
  109. font-weight: 600;
  110. `;
  111. const StarContainer = styled('div')`
  112. display: none;
  113. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  114. display: block;
  115. position: absolute;
  116. bottom: 0;
  117. right: 0;
  118. top: 0;
  119. width: 600px;
  120. overflow: hidden;
  121. border-radius: 0 ${p => p.theme.borderRadius} ${p => p.theme.borderRadius} 0;
  122. }
  123. `;
  124. const IllustrationContainer = styled('div')`
  125. display: none;
  126. @media (min-width: ${p => p.theme.breakpoints.large}) {
  127. display: block;
  128. position: absolute;
  129. bottom: 0;
  130. right: 0;
  131. top: 0;
  132. width: 600px;
  133. overflow: hidden;
  134. border-radius: 0 ${p => p.theme.borderRadius} ${p => p.theme.borderRadius} 0;
  135. }
  136. `;
  137. const Sentaur = styled('img')`
  138. height: 90px;
  139. position: absolute;
  140. bottom: -3px;
  141. right: 300px;
  142. z-index: 1;
  143. pointer-events: none;
  144. `;
  145. const RightStars = styled('img')`
  146. pointer-events: none;
  147. position: absolute;
  148. right: -120px;
  149. bottom: 10px;
  150. height: 110px;
  151. `;
  152. const LeftStars = styled('img')`
  153. pointer-events: none;
  154. position: absolute;
  155. right: 230px;
  156. bottom: 30px;
  157. height: 100px;
  158. `;
  159. const DismissButton = styled(Button)`
  160. position: absolute;
  161. top: ${space(1)};
  162. right: ${space(1)};
  163. color: ${p => p.theme.subText};
  164. z-index: 1;
  165. `;