modalSamePrice.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import {useCallback} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import * as Sentry from '@sentry/react';
  5. import HeroImg from 'getsentry-images/features/replay-modal-hero.jpg';
  6. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  7. import type {ModalRenderProps} from 'sentry/actionCreators/modal';
  8. import {closeModal} from 'sentry/actionCreators/modal';
  9. import {Button} from 'sentry/components/button';
  10. import ExternalLink from 'sentry/components/links/externalLink';
  11. import {t, tct} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import type {Organization} from 'sentry/types/organization';
  14. import useApi from 'sentry/utils/useApi';
  15. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  16. import type {Plan, Subscription} from 'getsentry/types';
  17. import type {AM2UpdateSurfaces} from 'getsentry/utils/trackGetsentryAnalytics';
  18. import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics';
  19. import type {Reservations} from './types';
  20. import useLogUpgradeNowViewed from './useLogUpgradeNowViewed';
  21. import {redirectToManage} from './utils';
  22. type Props = ModalRenderProps & {
  23. organization: Organization;
  24. plan: Plan;
  25. reservations: Reservations;
  26. subscription: Subscription;
  27. surface: AM2UpdateSurfaces;
  28. onComplete?: () => void;
  29. };
  30. function UpgradeNowModal({
  31. onComplete,
  32. organization,
  33. plan,
  34. reservations,
  35. subscription,
  36. surface,
  37. }: Props) {
  38. useLogUpgradeNowViewed({organization, subscription, surface, hasPriceChange: false});
  39. const api = useApi();
  40. const onUpdatePlan = useCallback(async () => {
  41. try {
  42. await api.requestPromise(`/customers/${organization.slug}/subscription/`, {
  43. method: 'PUT',
  44. data: {
  45. ...reservations,
  46. plan: plan.id,
  47. referrer: 'replay-am2-update-modal',
  48. },
  49. });
  50. SubscriptionStore.loadData(organization.slug, () => {
  51. if (onComplete) {
  52. onComplete();
  53. }
  54. closeModal();
  55. addSuccessMessage(t('Subscription Updated!'));
  56. trackGetsentryAnalytics('upgrade_now.modal.update_now', {
  57. organization,
  58. planTier: subscription.planTier,
  59. canSelfServe: subscription.canSelfServe,
  60. channel: subscription.channel,
  61. has_billing_scope: organization.access?.includes('org:billing'),
  62. surface,
  63. has_price_change: false,
  64. });
  65. });
  66. } catch (err) {
  67. Sentry.captureException(err);
  68. redirectToManage(organization);
  69. addErrorMessage(
  70. t(
  71. 'Oops! Unable to update Subscription automatically. Click through to update manually.'
  72. )
  73. );
  74. }
  75. }, [api, organization, subscription, plan, reservations, onComplete, surface]);
  76. return (
  77. <UpsellContent>
  78. <Subheader>{t('Enable Session Replays Now')}</Subheader>
  79. <Header>{t('Get to the root cause of an error faster')}</Header>
  80. <p>
  81. {t(
  82. 'Enable video-like reproduction of your user sessions so you can see what happened before, during and after an error or performance issue occured.'
  83. )}
  84. </p>
  85. <CTAPanel>
  86. <div>
  87. <CTAPrimary>{t('500 replays')}</CTAPrimary>
  88. <CTASecondary>{t('at no additional cost')}</CTASecondary>
  89. </div>
  90. <Button priority="primary" onClick={onUpdatePlan}>
  91. {t('Enable Now')}
  92. </Button>
  93. </CTAPanel>
  94. <Note>
  95. {tct(
  96. 'Enabling Session Replay also unlocks [perfAtScale:Performance at Scale] and [profiling:Profiling] at no additional charge. Your existing features will remain unchanged.',
  97. {
  98. perfAtScale: (
  99. <ExternalLink href="https://docs.sentry.io/product/performance/performance-at-scale/" />
  100. ),
  101. profiling: <ExternalLink href="https://docs.sentry.io/product/profiling/" />,
  102. }
  103. )}
  104. </Note>
  105. </UpsellContent>
  106. );
  107. }
  108. const UpsellContent = styled('div')`
  109. background: top no-repeat url('${HeroImg}');
  110. background-size: contain;
  111. background-position-y: -20px;
  112. padding-top: 190px;
  113. margin-inline: -45px;
  114. padding-inline: 45px;
  115. font-size: ${p => p.theme.fontSizeLarge};
  116. `;
  117. const Subheader = styled('h2')`
  118. color: ${p => p.theme.purple300};
  119. font-size: ${p => p.theme.fontSizeSmall};
  120. font-weight: bold;
  121. margin-bottom: ${space(1.5)};
  122. text-transform: uppercase;
  123. `;
  124. const Header = styled('h1')`
  125. font-size: ${p => p.theme.headerFontSize};
  126. font-weight: bold;
  127. margin: ${space(1.5)} 0;
  128. `;
  129. const CTAPanel = styled('div')`
  130. background: ${p => p.theme.backgroundSecondary};
  131. border-radius: ${p => p.theme.borderRadius};
  132. display: flex;
  133. justify-content: space-between;
  134. padding: ${space(2)};
  135. margin-block: ${space(2)};
  136. `;
  137. const CTAPrimary = styled('div')`
  138. font-size: ${p => p.theme.fontSizeLarge};
  139. font-weight: bold;
  140. `;
  141. const CTASecondary = styled('div')`
  142. font-size: ${p => p.theme.fontSizeMedium};
  143. `;
  144. const Note = styled('p')`
  145. text-align: center;
  146. color: ${p => p.theme.gray300};
  147. font-size: ${p => p.theme.fontSizeExtraSmall};
  148. margin-block: ${space(4)};
  149. `;
  150. export const modalCss = css`
  151. width: 100%;
  152. max-width: 532px;
  153. [role='document'] {
  154. position: relative;
  155. padding: 0 45px;
  156. overflow: hidden;
  157. }
  158. `;
  159. export default UpgradeNowModal;