body.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import {Fragment, useState} from 'react';
  2. import {Alert} from 'sentry/components/alert';
  3. import {Button} from 'sentry/components/button';
  4. import ErrorBoundary from 'sentry/components/errorBoundary';
  5. import Footer from 'sentry/components/footer';
  6. import * as Layout from 'sentry/components/layouts/thirds';
  7. import {t, tct} from 'sentry/locale';
  8. import AlertStore from 'sentry/stores/alertStore';
  9. import {Organization} from 'sentry/types';
  10. import useApi from 'sentry/utils/useApi';
  11. import {useRouteContext} from 'sentry/utils/useRouteContext';
  12. import withOrganization from 'sentry/utils/withOrganization';
  13. type OrganizationProps = {
  14. organization: Organization;
  15. };
  16. type BodyProps = {
  17. children?: React.ReactNode;
  18. // Organization can be null in account settings
  19. organization?: Organization;
  20. };
  21. function DeletionInProgress({organization}: OrganizationProps) {
  22. return (
  23. <Layout.Body>
  24. <Layout.Main>
  25. <Alert type="warning" showIcon>
  26. {tct(
  27. 'The [organization] organization is currently in the process of being deleted from Sentry.',
  28. {
  29. organization: <strong>{organization.slug}</strong>,
  30. }
  31. )}
  32. </Alert>
  33. </Layout.Main>
  34. </Layout.Body>
  35. );
  36. }
  37. function DeletionPending({organization}: OrganizationProps) {
  38. const api = useApi();
  39. const [isRestoring, setIsRestoring] = useState(false);
  40. const onRestore = async () => {
  41. setIsRestoring(true);
  42. try {
  43. await api.requestPromise(`/organizations/${organization.slug}/`, {
  44. method: 'PUT',
  45. data: {cancelDeletion: true},
  46. });
  47. window.location.reload();
  48. } catch {
  49. setIsRestoring(false);
  50. AlertStore.addAlert({
  51. message:
  52. 'We were unable to restore this organization. Please try again or contact support.',
  53. type: 'error',
  54. });
  55. }
  56. };
  57. return (
  58. <Layout.Body>
  59. <Layout.Main>
  60. <h3>{t('Deletion Scheduled')}</h3>
  61. <p>
  62. {tct('The [organization] organization is currently scheduled for deletion.', {
  63. organization: <strong>{organization.slug}</strong>,
  64. })}
  65. </p>
  66. {organization.access.includes('org:admin') ? (
  67. <div>
  68. <p>
  69. {t(
  70. 'Would you like to cancel this process and restore the organization back to the original state?'
  71. )}
  72. </p>
  73. <p>
  74. <Button priority="primary" onClick={onRestore} disabled={isRestoring}>
  75. {t('Restore Organization')}
  76. </Button>
  77. </p>
  78. </div>
  79. ) : (
  80. <p>
  81. {t(
  82. 'If this is a mistake, contact an organization owner and ask them to restore this organization.'
  83. )}
  84. </p>
  85. )}
  86. <p>
  87. <small>
  88. {t(
  89. "Note: Restoration is available until the process begins. Once it does, there's no recovering the data that has been removed."
  90. )}
  91. </small>
  92. </p>
  93. </Layout.Main>
  94. </Layout.Body>
  95. );
  96. }
  97. function OrganizationDetailsBody({children, organization}: BodyProps) {
  98. const status = organization?.status?.id;
  99. const routeContext = useRouteContext();
  100. if (organization && status === 'pending_deletion') {
  101. return <DeletionPending organization={organization} />;
  102. }
  103. if (organization && status === 'deletion_in_progress') {
  104. return <DeletionInProgress organization={organization} />;
  105. }
  106. const heartbeatFooter = !!organization?.features.includes(
  107. 'onboarding-heartbeat-footer'
  108. );
  109. const slug = organization?.slug;
  110. const gettingStartedRoutes = [
  111. `/getting-started/${routeContext.params.projectId}/${routeContext.params.platform}/`,
  112. `/${slug}/${routeContext.params.projectId}/getting-started/${routeContext.params.platform}/`,
  113. ];
  114. const onboardingRoutes = [
  115. `/onboarding/welcome/`,
  116. `/onboarding/setup-docs/`,
  117. `/onboarding/select-platform/`,
  118. `/onboarding/${slug}/welcome/`,
  119. `/onboarding/${slug}/setup-docs/`,
  120. `/onboarding/${slug}/select-platform/`,
  121. ];
  122. const showFooter = !heartbeatFooter
  123. ? true
  124. : !gettingStartedRoutes.includes(routeContext.location.pathname) &&
  125. !onboardingRoutes.includes(routeContext.location.pathname);
  126. return (
  127. <Fragment>
  128. <ErrorBoundary>{children}</ErrorBoundary>
  129. {showFooter && <Footer />}
  130. </Fragment>
  131. );
  132. }
  133. export default withOrganization(OrganizationDetailsBody);