index.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import LoadingIndicator from 'sentry/components/loadingIndicator';
  4. import {ThemeAndStyleProvider} from 'sentry/components/themeAndStyleProvider';
  5. import {IconCheckmark} from 'sentry/icons/iconCheckmark';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import type {Organization} from 'sentry/types/organization';
  9. import {trackAnalytics} from 'sentry/utils/analytics';
  10. import useApi from 'sentry/utils/useApi';
  11. type Props = {
  12. hash?: boolean | string;
  13. organizations?: Organization[];
  14. };
  15. function SetupWizard({hash = false, organizations}: Props) {
  16. const api = useApi();
  17. const closeTimeoutRef = useRef<number | undefined>(undefined);
  18. const [finished, setFinished] = useState(false);
  19. // if we have exactly one organization, we can use it for analytics
  20. // otherwise we don't know which org the user is in
  21. const organization = useMemo(
  22. () => (organizations?.length === 1 ? organizations[0] : null),
  23. [organizations]
  24. );
  25. const urlParams = new URLSearchParams(location.search);
  26. const projectPlatform = urlParams.get('project_platform') ?? undefined;
  27. const analyticsParams = useMemo(
  28. () => ({
  29. organization,
  30. project_platform: projectPlatform,
  31. }),
  32. [organization, projectPlatform]
  33. );
  34. useEffect(() => {
  35. return () => {
  36. if (closeTimeoutRef.current) {
  37. window.clearTimeout(closeTimeoutRef.current);
  38. }
  39. };
  40. });
  41. useEffect(() => {
  42. return () => {
  43. window.clearTimeout(closeTimeoutRef.current);
  44. };
  45. });
  46. useEffect(() => {
  47. trackAnalytics('setup_wizard.viewed', analyticsParams);
  48. }, [analyticsParams]);
  49. const checkFinished = useCallback(async () => {
  50. if (finished) {
  51. return;
  52. }
  53. try {
  54. await api.requestPromise(`/wizard/${hash}/`);
  55. } catch {
  56. setFinished(true);
  57. window.clearTimeout(closeTimeoutRef.current);
  58. closeTimeoutRef.current = window.setTimeout(() => window.close(), 10000);
  59. trackAnalytics('setup_wizard.complete', analyticsParams);
  60. }
  61. }, [api, hash, analyticsParams, finished]);
  62. useEffect(() => {
  63. const pollingInterval = window.setInterval(checkFinished, 1000);
  64. return () => window.clearInterval(pollingInterval);
  65. }, [checkFinished]);
  66. return (
  67. <ThemeAndStyleProvider>
  68. {!finished ? (
  69. <LoadingIndicator style={{margin: '2em auto'}}>
  70. <h5>{t('Waiting for wizard to connect')}</h5>
  71. </LoadingIndicator>
  72. ) : (
  73. <SuccessWrapper>
  74. <SuccessCheckmark color="green300" size="xl" isCircled />
  75. <SuccessHeading>
  76. {t('Return to your terminal to complete your setup.')}
  77. </SuccessHeading>
  78. </SuccessWrapper>
  79. )}
  80. </ThemeAndStyleProvider>
  81. );
  82. }
  83. const SuccessCheckmark = styled(IconCheckmark)`
  84. flex-shrink: 0;
  85. `;
  86. const SuccessHeading = styled('h5')`
  87. margin: 0;
  88. `;
  89. const SuccessWrapper = styled('div')`
  90. display: flex;
  91. align-items: center;
  92. gap: ${space(3)};
  93. `;
  94. export default SetupWizard;