newIssueExperienceButton.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import {useCallback} from 'react';
  2. import styled from '@emotion/styled';
  3. import {motion} from 'framer-motion';
  4. import {Button} from 'sentry/components/button';
  5. import DropdownButton from 'sentry/components/dropdownButton';
  6. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  7. import {IconLab} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import {defined} from 'sentry/utils';
  10. import {trackAnalytics} from 'sentry/utils/analytics';
  11. import {useFeedbackForm} from 'sentry/utils/useFeedbackForm';
  12. import useMutateUserOptions from 'sentry/utils/useMutateUserOptions';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import {useUser} from 'sentry/utils/useUser';
  15. import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
  16. export function NewIssueExperienceButton() {
  17. const user = useUser();
  18. const organization = useOrganization();
  19. const hasStreamlinedUIFlag = organization.features.includes('issue-details-streamline');
  20. const hasEnforceStreamlinedUIFlag = organization.features.includes(
  21. 'issue-details-streamline-enforce'
  22. );
  23. const hasStreamlinedUI = useHasStreamlinedUI();
  24. const openForm = useFeedbackForm();
  25. const {mutate} = useMutateUserOptions();
  26. const handleToggle = useCallback(() => {
  27. mutate({['prefersIssueDetailsStreamlinedUI']: !hasStreamlinedUI});
  28. trackAnalytics('issue_details.streamline_ui_toggle', {
  29. isEnabled: !hasStreamlinedUI,
  30. organization: organization,
  31. });
  32. }, [mutate, organization, hasStreamlinedUI]);
  33. // We hide the toggle if the org doesn't have the 'opt-in' flag, or has the 'remove opt-out' flag.
  34. if (!hasStreamlinedUIFlag || hasEnforceStreamlinedUIFlag) {
  35. return null;
  36. }
  37. if (!openForm || !hasStreamlinedUI) {
  38. const label = hasStreamlinedUI
  39. ? t('Switch to the old issue experience')
  40. : t('Switch to the new issue experience');
  41. return (
  42. <ToggleButtonWrapper>
  43. <ToggleButton
  44. enabled={hasStreamlinedUI}
  45. size={hasStreamlinedUI ? 'xs' : 'sm'}
  46. icon={
  47. defined(user?.options?.prefersIssueDetailsStreamlinedUI) ? (
  48. <IconLab isSolid={hasStreamlinedUI} />
  49. ) : (
  50. <motion.div
  51. style={{height: 14}}
  52. animate={{
  53. rotate: [null, 6, -6, 12, -12, 6, -6, 0],
  54. }}
  55. transition={{
  56. duration: 1,
  57. delay: 1,
  58. repeatDelay: 3,
  59. repeat: 3,
  60. }}
  61. >
  62. <IconLab isSolid={hasStreamlinedUI} />
  63. </motion.div>
  64. )
  65. }
  66. title={label}
  67. aria-label={label}
  68. borderless={!hasStreamlinedUI}
  69. onClick={handleToggle}
  70. >
  71. <ToggleBorder />
  72. </ToggleButton>
  73. </ToggleButtonWrapper>
  74. );
  75. }
  76. return (
  77. <DropdownMenu
  78. trigger={triggerProps => (
  79. <StyledDropdownButton
  80. {...triggerProps}
  81. enabled={hasStreamlinedUI}
  82. size={hasStreamlinedUI ? 'xs' : 'sm'}
  83. aria-label={t('Switch issue experience')}
  84. >
  85. {/* Passing icon as child to avoid extra icon margin */}
  86. <IconLab isSolid={hasStreamlinedUI} />
  87. </StyledDropdownButton>
  88. )}
  89. items={[
  90. {
  91. key: 'switch-to-old-ui',
  92. label: t('Switch to the old issue experience'),
  93. onAction: handleToggle,
  94. },
  95. {
  96. key: 'learn-more',
  97. label: t('Learn more about the new UI'),
  98. onAction: () => {
  99. trackAnalytics('issue_details.streamline_ui_learn_more', {
  100. organization,
  101. });
  102. window.open(
  103. 'https://sentry.zendesk.com/hc/en-us/articles/30882241712795',
  104. '_blank'
  105. );
  106. },
  107. },
  108. {
  109. key: 'give-feedback',
  110. label: t('Give feedback on new UI'),
  111. hidden: !openForm,
  112. onAction: () => {
  113. openForm({
  114. messagePlaceholder: t(
  115. 'Excluding bribes, what would make you excited to use the new UI?'
  116. ),
  117. tags: {
  118. ['feedback.source']: 'streamlined_issue_details',
  119. ['feedback.owner']: 'issues',
  120. },
  121. });
  122. },
  123. },
  124. ]}
  125. position="bottom-end"
  126. />
  127. );
  128. }
  129. const StyledDropdownButton = styled(DropdownButton)<{enabled: boolean}>`
  130. color: ${p => (p.enabled ? p.theme.button.primary.background : 'inherit')};
  131. :hover {
  132. color: ${p => (p.enabled ? p.theme.button.primary.background : 'inherit')};
  133. }
  134. `;
  135. const ToggleButtonWrapper = styled('div')`
  136. overflow: hidden;
  137. margin: 0 -1px;
  138. border-radius: 7px;
  139. `;
  140. const ToggleButton = styled(Button)<{enabled: boolean}>`
  141. position: relative;
  142. color: ${p => (p.enabled ? p.theme.button.primary.background : 'inherit')};
  143. :hover {
  144. color: ${p => (p.enabled ? p.theme.button.primary.background : 'inherit')};
  145. }
  146. &:after {
  147. position: absolute;
  148. content: '';
  149. inset: 0;
  150. background: ${p => p.theme.background};
  151. border-radius: ${p => p.theme.borderRadius};
  152. }
  153. span {
  154. z-index: 1;
  155. margin: 0;
  156. }
  157. `;
  158. const ToggleBorder = styled('div')`
  159. @keyframes rotating {
  160. from {
  161. transform: rotate(0deg);
  162. }
  163. to {
  164. transform: rotate(360deg);
  165. }
  166. }
  167. position: absolute;
  168. content: '';
  169. z-index: -1;
  170. width: 46px;
  171. height: 46px;
  172. border-radius: 7px;
  173. background: ${p => p.theme.badge.beta.background};
  174. animation: rotating 10s linear infinite;
  175. `;