recurringCredits.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import moment from 'moment-timezone';
  4. import Panel from 'sentry/components/panels/panel';
  5. import QuestionTooltip from 'sentry/components/questionTooltip';
  6. import {t, tct} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import type {DataCategory} from 'sentry/types/core';
  9. import {useRecurringCredits} from 'getsentry/hooks/useRecurringCredits';
  10. import type {Plan, RecurringCredit} from 'getsentry/types';
  11. import {CreditType} from 'getsentry/types';
  12. import {formatReservedWithUnits} from 'getsentry/utils/billing';
  13. import {getCreditDataCategory, getPlanCategoryName} from 'getsentry/utils/dataCategory';
  14. import {displayPrice} from 'getsentry/views/amCheckout/utils';
  15. import {AlertStripedTable, PanelBodyWithTable} from './styles';
  16. const isExpired = (date: moment.MomentInput) => {
  17. return moment(date).utc().startOf('day') < moment().utc().startOf('day');
  18. };
  19. const getActiveDiscounts = (recurringCredits: RecurringCredit[]) =>
  20. recurringCredits.filter(
  21. credit =>
  22. (credit.type === CreditType.DISCOUNT || credit.type === CreditType.PERCENT) &&
  23. credit.totalAmountRemaining > 0 &&
  24. !isExpired(credit.periodEnd)
  25. );
  26. type Props = {
  27. displayType: 'data' | 'discount';
  28. planDetails: Plan;
  29. };
  30. function RecurringCredits({displayType, planDetails}: Props) {
  31. const {recurringCredits, isLoading} = useRecurringCredits();
  32. if (isLoading) {
  33. return null;
  34. }
  35. const displayDiscounts = displayType === 'discount';
  36. const getCredits = () => {
  37. if (displayDiscounts) {
  38. return getActiveDiscounts(recurringCredits);
  39. }
  40. return recurringCredits.filter(
  41. credit => getCreditDataCategory(credit) && !isExpired(credit.periodEnd)
  42. );
  43. };
  44. const credits = getCredits();
  45. if (!credits.length) {
  46. return null;
  47. }
  48. const getTooltipTitle = (credit: RecurringCredit) => {
  49. return credit.type === CreditType.DISCOUNT || credit.type === CreditType.PERCENT
  50. ? tct('[amount] per month or [annualAmount] remaining towards an annual plan.', {
  51. amount: displayPrice({cents: credit.amount}),
  52. annualAmount: displayPrice({
  53. cents: Math.min(credit.amount * 12, credit.totalAmountRemaining),
  54. }),
  55. })
  56. : undefined;
  57. };
  58. const getAmount = (credit: RecurringCredit, category: DataCategory | CreditType) => {
  59. if (credit.type === CreditType.DISCOUNT || credit.type === CreditType.PERCENT) {
  60. return (
  61. <Fragment>
  62. {tct('[amount]/mo', {
  63. amount: displayPrice({cents: credit.amount}),
  64. })}
  65. <StyledQuestionTooltip title={getTooltipTitle(credit)} size="xs" />
  66. </Fragment>
  67. );
  68. }
  69. return `+${formatReservedWithUnits(credit.amount, category, {
  70. isAbbreviated: true,
  71. useUnitScaling: true,
  72. })}/mo`;
  73. };
  74. return (
  75. <Panel data-test-id="recurring-credits-panel">
  76. <StyledPanelBody withPadding>
  77. <div>
  78. <h4>{t('Recurring Credits')}</h4>
  79. <SubText>{t('A summary of your active recurring credits.')}</SubText>
  80. </div>
  81. <div>
  82. <AlertStripedTable>
  83. <thead>
  84. <tr>
  85. <th>{t('Type')}</th>
  86. <th>{t('Amount')}</th>
  87. <th>{t('Ends on')}</th>
  88. </tr>
  89. </thead>
  90. <tbody>
  91. {credits.map((credit, index) => {
  92. const category =
  93. credit.type === CreditType.DISCOUNT ||
  94. credit.type === CreditType.PERCENT
  95. ? credit.type
  96. : getCreditDataCategory(credit)!;
  97. return (
  98. <tr key={index}>
  99. <Title>
  100. {getPlanCategoryName({
  101. plan: planDetails,
  102. category,
  103. capitalize: false,
  104. })}
  105. </Title>
  106. <td data-test-id="amount">
  107. <span>{getAmount(credit, category)}</span>
  108. </td>
  109. <td data-test-id="end-date">
  110. {moment(credit.periodEnd).format('ll')}
  111. </td>
  112. </tr>
  113. );
  114. })}
  115. </tbody>
  116. </AlertStripedTable>
  117. </div>
  118. </StyledPanelBody>
  119. </Panel>
  120. );
  121. }
  122. export default RecurringCredits;
  123. const StyledPanelBody = styled(PanelBodyWithTable)`
  124. h4 {
  125. margin-bottom: ${space(1.5)};
  126. }
  127. `;
  128. const SubText = styled('p')`
  129. font-size: ${p => p.theme.fontSizeMedium};
  130. color: ${p => p.theme.subText};
  131. `;
  132. const Title = styled('td')`
  133. text-transform: capitalize;
  134. `;
  135. const StyledQuestionTooltip = styled(QuestionTooltip)`
  136. margin-left: ${space(0.5)};
  137. `;