index.tsx 4.2 KB

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