trialStarter.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import {Component} from 'react';
  2. import {fetchOrganizationDetails} from 'sentry/actionCreators/organization';
  3. import type {Client} from 'sentry/api';
  4. import type {Organization} from 'sentry/types/organization';
  5. import withApi from 'sentry/utils/withApi';
  6. import withSubscription from 'getsentry/components/withSubscription';
  7. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  8. import type {Subscription} from 'getsentry/types';
  9. import trackMarketingEvent from 'getsentry/utils/trackMarketingEvent';
  10. type ChildProps = {
  11. startTrial: () => Promise<void>;
  12. subscription: Subscription;
  13. trialFailed: boolean;
  14. trialStarted: boolean;
  15. trialStarting: boolean;
  16. };
  17. type Props = {
  18. api: Client;
  19. children: (args: ChildProps) => React.ReactNode;
  20. organization: Organization;
  21. source: string;
  22. subscription: Subscription;
  23. onTrialFailed?: (err: Error) => void;
  24. // Can't use default prop typings because of HoC wrappers.
  25. onTrialStarted?: () => void;
  26. requestData?: Record<string, unknown>;
  27. };
  28. type State = {
  29. trialFailed: boolean;
  30. trialStarted: boolean;
  31. trialStarting: boolean;
  32. };
  33. class TrialStarter extends Component<Props, State> {
  34. static defaultProps = {
  35. onTrialFailed: () => {},
  36. onTrialStarted: () => {},
  37. };
  38. state: State = {
  39. trialStarting: false,
  40. trialStarted: false,
  41. trialFailed: false,
  42. };
  43. handleStartTrial = async () => {
  44. const {organization, source, onTrialStarted, onTrialFailed, requestData} = this.props;
  45. this.setState({trialStarting: true});
  46. let data: any;
  47. let url: any;
  48. if (requestData) {
  49. data = {referrer: source, ...requestData};
  50. url = `/customers/${organization.slug}/product-trial/`;
  51. } else {
  52. data = {trial: true, referrer: source};
  53. url = `/customers/${organization.slug}/`;
  54. }
  55. try {
  56. await this.props.api.requestPromise(url, {
  57. method: 'PUT',
  58. data,
  59. });
  60. } catch (err) {
  61. onTrialFailed?.(err);
  62. this.setState({trialStarting: false, trialFailed: true});
  63. return;
  64. }
  65. this.setState({trialStarting: false, trialStarted: true});
  66. trackMarketingEvent('Start Trial');
  67. onTrialStarted?.();
  68. // Refresh organization and subscription state
  69. SubscriptionStore.loadData(organization.slug, null, {markStartedTrial: true});
  70. fetchOrganizationDetails(this.props.api, organization.slug);
  71. // we showed the "new" icon for the upsell that wasn't the actual dashboard
  72. // we should clear this so folks can see "new" for the actual dashboard
  73. localStorage.removeItem('sidebar-new-seen:customizable-dashboards');
  74. };
  75. render() {
  76. const {trialStarted, trialStarting, trialFailed} = this.state;
  77. const {subscription, children} = this.props;
  78. return children({
  79. startTrial: this.handleStartTrial,
  80. trialStarting,
  81. trialStarted,
  82. trialFailed,
  83. subscription,
  84. });
  85. }
  86. }
  87. // We enable the persistInFlight on the withApi wrapper to ensure that we don't
  88. // cancel the in-flight requests to reload the organization details after the
  89. // trial has been started. Otherwise if this component is unmounted as a result
  90. // of starting the trial.
  91. export default withSubscription(withApi(TrialStarter, {persistInFlight: true}));