changeBalanceAction.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import {Fragment, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {addSuccessMessage} from 'sentry/actionCreators/indicator';
  4. import type {ModalRenderProps} from 'sentry/actionCreators/modal';
  5. import {openModal} from 'sentry/actionCreators/modal';
  6. import Form from 'sentry/components/deprecatedforms/form';
  7. import NumberField from 'sentry/components/deprecatedforms/numberField';
  8. import InputField from 'sentry/components/forms/fields/inputField';
  9. import TextField from 'sentry/components/forms/fields/textField';
  10. import {space} from 'sentry/styles/space';
  11. import useApi from 'sentry/utils/useApi';
  12. import type {Subscription} from 'getsentry/types';
  13. import {formatBalance} from 'getsentry/utils/billing';
  14. type Props = {
  15. onSuccess: () => void;
  16. orgId: string;
  17. subscription: Subscription;
  18. };
  19. type ModalProps = Props & ModalRenderProps;
  20. function ChangeBalanceModal({
  21. orgId,
  22. onSuccess,
  23. subscription,
  24. closeModal,
  25. Header,
  26. Body,
  27. }: ModalProps) {
  28. const [ticketUrl, setTicketUrl] = useState('');
  29. const [notes, setNotes] = useState('');
  30. const api = useApi();
  31. function coerceValue(value: number) {
  32. if (isNaN(value)) {
  33. return undefined;
  34. }
  35. return value * 100;
  36. }
  37. async function onSubmit(data: any, _onSubmitSuccess: unknown, onSubmitError: any) {
  38. const creditAmount = coerceValue(data.creditAmount);
  39. if (!creditAmount) {
  40. return;
  41. }
  42. try {
  43. await api.requestPromise(`/_admin/customers/${orgId}/balance-changes/`, {
  44. method: 'POST',
  45. data: {ticketUrl, notes, creditAmount},
  46. });
  47. addSuccessMessage('Customer balance updated');
  48. onSuccess();
  49. closeModal();
  50. } catch (err) {
  51. onSubmitError({
  52. responseJSON: err.responseJSON,
  53. });
  54. }
  55. }
  56. return (
  57. <Fragment>
  58. <Header>Add or Remove Credit</Header>
  59. <Body>
  60. <p data-test-id="balance">
  61. <span>
  62. <CurrentBalance>Current Balance: </CurrentBalance>
  63. {formatBalance(subscription.accountBalance)}
  64. </span>
  65. </p>
  66. <Form
  67. onSubmit={onSubmit}
  68. onCancel={closeModal}
  69. submitLabel="Submit"
  70. cancelLabel="Cancel"
  71. footerClass="modal-footer"
  72. >
  73. <NumberField
  74. label="Credit Amount"
  75. name="creditAmount"
  76. help="Add or remove credit, in dollars"
  77. />
  78. <AuditFields>
  79. <InputField
  80. data-test-id="url-field"
  81. name="ticket-url"
  82. type="url"
  83. label="TicketUrl"
  84. inline={false}
  85. stacked
  86. flexibleControlStateSize
  87. onChange={(ticketUrlInput: any) => setTicketUrl(ticketUrlInput)}
  88. />
  89. <TextField
  90. data-test-id="notes-field"
  91. name="notes"
  92. label="Notes"
  93. inline={false}
  94. stacked
  95. flexibleControlStateSize
  96. maxLength={500}
  97. onChange={(notesInput: any) => setNotes(notesInput)}
  98. />
  99. </AuditFields>
  100. </Form>
  101. </Body>
  102. </Fragment>
  103. );
  104. }
  105. type Options = Pick<Props, 'orgId' | 'subscription' | 'onSuccess'>;
  106. const triggerChangeBalanceModal = (opts: Options) =>
  107. openModal(deps => <ChangeBalanceModal {...deps} {...opts} />);
  108. const CurrentBalance = styled('span')`
  109. font-weight: bold;
  110. `;
  111. const AuditFields = styled('div')`
  112. margin-top: ${space(2)};
  113. `;
  114. export default triggerChangeBalanceModal;