onDemandSettings.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import {useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  4. import type {ResponseMeta} from 'sentry/api';
  5. import ExternalLink from 'sentry/components/links/externalLink';
  6. import Panel from 'sentry/components/panels/panel';
  7. import PanelHeader from 'sentry/components/panels/panelHeader';
  8. import QuestionTooltip from 'sentry/components/questionTooltip';
  9. import {t, tct} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import type {Organization} from 'sentry/types/organization';
  12. import useApi from 'sentry/utils/useApi';
  13. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  14. import {PlanTier, type Subscription} from 'getsentry/types/index';
  15. import {isTrialPlan} from 'getsentry/utils/billing';
  16. import OnDemandBudgets from 'getsentry/views/onDemandBudgets';
  17. import {EditOnDemandButton} from 'getsentry/views/onDemandBudgets/editOnDemandButton';
  18. import {hasOnDemandBudgetsFeature} from 'getsentry/views/onDemandBudgets/utils';
  19. import OnDemandSummary from 'getsentry/views/subscriptionPage/onDemandSummary';
  20. interface OnDemandSettingsProps {
  21. organization: Organization;
  22. subscription: Subscription;
  23. }
  24. export function OnDemandSettings({subscription, organization}: OnDemandSettingsProps) {
  25. const api = useApi();
  26. const [onDemandError, setOnDemandError] = useState<undefined | Error | string>(
  27. undefined
  28. );
  29. const displayOnDemandConfig =
  30. !subscription.isFree &&
  31. !isTrialPlan(subscription.plan) &&
  32. subscription.supportsOnDemand;
  33. if (!displayOnDemandConfig) {
  34. return null;
  35. }
  36. function saveOnDemand(onDemandMaxSpend: string | number) {
  37. api.request(`/customers/${subscription.slug}/`, {
  38. method: 'PUT',
  39. data: {onDemandMaxSpend},
  40. error: (err: ResponseMeta) => {
  41. setOnDemandError(
  42. err.responseJSON?.onDemandMaxSpend ||
  43. err.responseJSON?.detail ||
  44. t('There as an unknown error saving your changes.')
  45. );
  46. addErrorMessage(t('An error occurred.'));
  47. },
  48. success: data => {
  49. SubscriptionStore.set(data.slug, data);
  50. addSuccessMessage(
  51. t(
  52. '%s max spend updated',
  53. subscription.planTier === PlanTier.AM3 ? 'pay-as-you-go' : 'On-Demand'
  54. )
  55. );
  56. },
  57. });
  58. }
  59. const onDemandEnabled = subscription.planDetails.allowOnDemand;
  60. // VC partner accounts don't require a payment source (i.e. credit card) since they make all payments via VC
  61. const isVCPartner = subscription.partner?.partnership?.id === 'VC';
  62. const hasPaymentSource = !!subscription.paymentSource || isVCPartner;
  63. const hasOndemandBudgets =
  64. hasOnDemandBudgetsFeature(organization, subscription) &&
  65. Boolean(subscription.onDemandBudgets);
  66. return (
  67. <Panel>
  68. <PanelHeader
  69. // Displays the edit button when user has budgets enabled
  70. hasButtons={
  71. (hasOnDemandBudgetsFeature(organization, subscription) &&
  72. !!subscription.paymentSource) ||
  73. subscription.planTier === PlanTier.AM3
  74. }
  75. >
  76. <PanelTitleWrapper>
  77. {subscription.planTier === PlanTier.AM3
  78. ? t('Pay-as-you-go Budget')
  79. : t('On-Demand Budgets')}{' '}
  80. <QuestionTooltip
  81. size="sm"
  82. title={tct(
  83. `[budgetType] allows you to pay for additional data beyond your subscription's
  84. reserved quotas. [budgetType] is billed monthly at the end of each usage period. [link:Learn more]`,
  85. {
  86. budgetType:
  87. subscription.planTier === PlanTier.AM3
  88. ? t('Pay-as-you-go')
  89. : t('On-Demand'),
  90. link: (
  91. <ExternalLink
  92. href={
  93. subscription.planTier === PlanTier.AM3
  94. ? `https://docs.sentry.io/pricing/#pricing-how-it-works`
  95. : `https://docs.sentry.io/pricing/legacy-pricing/#on-demand-volume`
  96. }
  97. />
  98. ),
  99. }
  100. )}
  101. isHoverable
  102. />
  103. </PanelTitleWrapper>
  104. {subscription.onDemandBudgets?.enabled && (
  105. <EditOnDemandButton organization={organization} subscription={subscription} />
  106. )}
  107. </PanelHeader>
  108. {/* AM3 doesn't have on-demand-budgets, but we want them to see the newer ui */}
  109. {hasOndemandBudgets || subscription.planTier === PlanTier.AM3 ? (
  110. <OnDemandBudgets
  111. onDemandEnabled={onDemandEnabled}
  112. organization={organization}
  113. hasPaymentSource={hasPaymentSource}
  114. subscription={subscription}
  115. />
  116. ) : (
  117. <OnDemandSummary
  118. enabled={onDemandEnabled}
  119. error={onDemandError}
  120. value={subscription.onDemandMaxSpend}
  121. pricePerEvent={subscription.planDetails.onDemandEventPrice}
  122. hasPaymentSource={hasPaymentSource}
  123. onSave={value => saveOnDemand(value)}
  124. subscription={subscription}
  125. withPanel={false}
  126. withHeader={false}
  127. showSave
  128. />
  129. )}
  130. </Panel>
  131. );
  132. }
  133. const PanelTitleWrapper = styled('div')`
  134. display: flex;
  135. align-items: center;
  136. gap: ${space(1)};
  137. `;