index.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import ButtonBar from 'sentry/components/buttonBar';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import {ThemeAndStyleProvider} from 'sentry/components/themeAndStyleProvider';
  7. import {t} from 'sentry/locale';
  8. import {Organization} from 'sentry/types';
  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. const platformDocsMapping = {
  16. 'javascript-nextjs':
  17. 'https://docs.sentry.io/platforms/javascript/guides/nextjs/#verify',
  18. 'react-native': 'https://docs.sentry.io/platforms/react-native/#verify',
  19. cordova: 'https://docs.sentry.io/platforms/javascript/guides/cordova/#verify',
  20. 'javascript-electron':
  21. 'https://docs.sentry.io/platforms/javascript/guides/electron/#verify',
  22. };
  23. function SetupWizard({hash = false, organizations}: Props) {
  24. const api = useApi();
  25. const closeTimeoutRef = useRef<number | undefined>(undefined);
  26. const [finished, setFinished] = useState(false);
  27. // if we have exactly one organization, we can use it for analytics
  28. // otherwise we don't know which org the user is in
  29. const organization = useMemo(
  30. () => (organizations?.length === 1 ? organizations[0] : null),
  31. [organizations]
  32. );
  33. const urlParams = new URLSearchParams(location.search);
  34. const projectPlatform = urlParams.get('project_platform') ?? undefined;
  35. const analyticsParams = useMemo(
  36. () => ({
  37. organization,
  38. project_platform: projectPlatform,
  39. }),
  40. [organization, projectPlatform]
  41. );
  42. // outside of route context
  43. const docsLink = useMemo(() => {
  44. return platformDocsMapping[projectPlatform || ''] || 'https://docs.sentry.io/';
  45. }, [projectPlatform]);
  46. useEffect(() => {
  47. return () => {
  48. if (closeTimeoutRef.current) {
  49. window.clearTimeout(closeTimeoutRef.current);
  50. }
  51. };
  52. });
  53. useEffect(() => {
  54. return () => {
  55. window.clearTimeout(closeTimeoutRef.current);
  56. };
  57. });
  58. useEffect(() => {
  59. trackAnalytics('setup_wizard.viewed', analyticsParams);
  60. }, [analyticsParams]);
  61. const checkFinished = useCallback(async () => {
  62. if (finished) {
  63. return;
  64. }
  65. try {
  66. await api.requestPromise(`/wizard/${hash}/`);
  67. } catch {
  68. setFinished(true);
  69. window.clearTimeout(closeTimeoutRef.current);
  70. closeTimeoutRef.current = window.setTimeout(() => window.close(), 10000);
  71. trackAnalytics('setup_wizard.complete', analyticsParams);
  72. }
  73. }, [api, hash, analyticsParams, finished]);
  74. useEffect(() => {
  75. const pollingInterval = window.setInterval(checkFinished, 1000);
  76. return () => window.clearInterval(pollingInterval);
  77. }, [checkFinished]);
  78. return (
  79. <ThemeAndStyleProvider>
  80. <div className="container">
  81. {!finished ? (
  82. <LoadingIndicator style={{margin: '2em auto'}}>
  83. <div className="row">
  84. <h5>{t('Waiting for wizard to connect')}</h5>
  85. </div>
  86. </LoadingIndicator>
  87. ) : (
  88. <div className="row">
  89. <h5>{t('Return to your terminal to complete your setup')}</h5>
  90. <MinWidthButtonBar gap={1}>
  91. <Button
  92. priority="primary"
  93. to="/"
  94. onClick={() =>
  95. trackAnalytics('setup_wizard.clicked_viewed_issues', analyticsParams)
  96. }
  97. >
  98. {t('View Issues')}
  99. </Button>
  100. <Button
  101. href={docsLink}
  102. external
  103. onClick={() =>
  104. trackAnalytics('setup_wizard.clicked_viewed_docs', analyticsParams)
  105. }
  106. >
  107. {t('See Docs')}
  108. </Button>
  109. </MinWidthButtonBar>
  110. </div>
  111. )}
  112. </div>
  113. </ThemeAndStyleProvider>
  114. );
  115. }
  116. const MinWidthButtonBar = styled(ButtonBar)`
  117. width: min-content;
  118. margin-top: 20px;
  119. `;
  120. export default SetupWizard;