onboardingStatus.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import {Component, Fragment} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import OnboardingSidebar from 'app/components/onboardingWizard/sidebar';
  5. import {getMergedTasks} from 'app/components/onboardingWizard/taskConfig';
  6. import ProgressRing, {
  7. RingBackground,
  8. RingBar,
  9. RingText,
  10. } from 'app/components/progressRing';
  11. import {t, tct} from 'app/locale';
  12. import space from 'app/styles/space';
  13. import {OnboardingTaskStatus, Organization} from 'app/types';
  14. import {trackAnalyticsEvent} from 'app/utils/analytics';
  15. import theme, {Theme} from 'app/utils/theme';
  16. import {CommonSidebarProps, SidebarPanelKey} from './types';
  17. type Props = CommonSidebarProps & {
  18. org: Organization;
  19. };
  20. const isDone = (task: OnboardingTaskStatus) =>
  21. task.status === 'complete' || task.status === 'skipped';
  22. const progressTextCss = () => css`
  23. font-size: ${theme.fontSizeMedium};
  24. font-weight: bold;
  25. `;
  26. class OnboardingStatus extends Component<Props> {
  27. handleShowPanel = () => {
  28. const {org, onShowPanel} = this.props;
  29. trackAnalyticsEvent({
  30. eventKey: 'onboarding.wizard_opened',
  31. eventName: 'Onboarding Wizard Opened',
  32. organization_id: org.id,
  33. });
  34. onShowPanel();
  35. };
  36. render() {
  37. const {collapsed, org, currentPanel, orientation, hidePanel} = this.props;
  38. if (!(org.features && org.features.includes('onboarding'))) {
  39. return null;
  40. }
  41. const tasks = getMergedTasks(org);
  42. const allDisplayedTasks = tasks.filter(task => task.display);
  43. const doneTasks = allDisplayedTasks.filter(isDone);
  44. const numberRemaining = allDisplayedTasks.length - doneTasks.length;
  45. const pendingCompletionSeen = doneTasks.some(
  46. task =>
  47. allDisplayedTasks.some(displayedTask => displayedTask.task === task.task) &&
  48. task.status === 'complete' &&
  49. !task.completionSeen
  50. );
  51. const isActive = currentPanel === SidebarPanelKey.OnboardingWizard;
  52. if (doneTasks.length >= allDisplayedTasks.length && !isActive) {
  53. return null;
  54. }
  55. return (
  56. <Fragment>
  57. <Container onClick={this.handleShowPanel} isActive={isActive}>
  58. <ProgressRing
  59. animateText
  60. textCss={progressTextCss}
  61. text={allDisplayedTasks.length - doneTasks.length}
  62. value={(doneTasks.length / allDisplayedTasks.length) * 100}
  63. backgroundColor="rgba(255, 255, 255, 0.15)"
  64. progressEndcaps="round"
  65. size={38}
  66. barWidth={6}
  67. />
  68. {!collapsed && (
  69. <div>
  70. <Heading>{t('Quick Start')}</Heading>
  71. <Remaining>
  72. {tct('[numberRemaining] Remaining tasks', {numberRemaining})}
  73. {pendingCompletionSeen && <PendingSeenIndicator />}
  74. </Remaining>
  75. </div>
  76. )}
  77. </Container>
  78. {isActive && (
  79. <OnboardingSidebar
  80. orientation={orientation}
  81. collapsed={collapsed}
  82. onClose={hidePanel}
  83. />
  84. )}
  85. </Fragment>
  86. );
  87. }
  88. }
  89. const Heading = styled('div')`
  90. transition: color 100ms;
  91. font-size: ${p => p.theme.backgroundSecondary};
  92. color: ${p => p.theme.gray200};
  93. margin-bottom: ${space(0.25)};
  94. `;
  95. const Remaining = styled('div')`
  96. transition: color 100ms;
  97. font-size: ${p => p.theme.fontSizeSmall};
  98. color: ${p => p.theme.gray300};
  99. display: grid;
  100. grid-template-columns: max-content max-content;
  101. grid-gap: ${space(0.75)};
  102. align-items: center;
  103. `;
  104. const PendingSeenIndicator = styled('div')`
  105. background: ${p => p.theme.red300};
  106. border-radius: 50%;
  107. height: 7px;
  108. width: 7px;
  109. `;
  110. const hoverCss = (p: {theme: Theme}) => css`
  111. background: rgba(255, 255, 255, 0.05);
  112. ${RingBackground} {
  113. stroke: rgba(255, 255, 255, 0.3);
  114. }
  115. ${RingBar} {
  116. stroke: ${p.theme.green200};
  117. }
  118. ${RingText} {
  119. color: ${p.theme.white};
  120. }
  121. ${Heading} {
  122. color: ${p.theme.white};
  123. }
  124. ${Remaining} {
  125. color: ${p.theme.gray200};
  126. }
  127. `;
  128. const Container = styled('div')<{isActive: boolean}>`
  129. padding: 9px 19px 9px 16px;
  130. cursor: pointer;
  131. display: grid;
  132. grid-template-columns: max-content 1fr;
  133. grid-gap: ${space(1.5)};
  134. align-items: center;
  135. transition: background 100ms;
  136. ${p => p.isActive && hoverCss(p)};
  137. &:hover {
  138. ${hoverCss};
  139. }
  140. `;
  141. export default OnboardingStatus;