feedbackListPage.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import {Fragment} from 'react';
  2. import type {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import ErrorBoundary from 'sentry/components/errorBoundary';
  5. import FeedbackFilters from 'sentry/components/feedback/feedbackFilters';
  6. import FeedbackItemLoader from 'sentry/components/feedback/feedbackItem/feedbackItemLoader';
  7. import FeedbackWidgetBanner from 'sentry/components/feedback/feedbackOnboarding/feedbackWidgetBanner';
  8. import FeedbackSearch from 'sentry/components/feedback/feedbackSearch';
  9. import FeedbackSetupPanel from 'sentry/components/feedback/feedbackSetupPanel';
  10. import FeedbackWhatsNewBanner from 'sentry/components/feedback/feedbackWhatsNewBanner';
  11. import FeedbackList from 'sentry/components/feedback/list/feedbackList';
  12. import OldFeedbackButton from 'sentry/components/feedback/oldFeedbackButton';
  13. import useCurrentFeedbackId from 'sentry/components/feedback/useCurrentFeedbackId';
  14. import useHaveSelectedProjectsSetupFeedback, {
  15. useHaveSelectedProjectsSetupNewFeedback,
  16. } from 'sentry/components/feedback/useFeedbackOnboarding';
  17. import {FeedbackQueryKeys} from 'sentry/components/feedback/useFeedbackQueryKeys';
  18. import FullViewport from 'sentry/components/layouts/fullViewport';
  19. import * as Layout from 'sentry/components/layouts/thirds';
  20. import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
  21. import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
  22. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  23. import {feedbackWidgetPlatforms} from 'sentry/data/platformCategories';
  24. import {t} from 'sentry/locale';
  25. import {space} from 'sentry/styles/space';
  26. import useOrganization from 'sentry/utils/useOrganization';
  27. import usePageFilters from 'sentry/utils/usePageFilters';
  28. import useProjects from 'sentry/utils/useProjects';
  29. import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
  30. interface Props extends RouteComponentProps<{}, {}, {}> {}
  31. export default function FeedbackListPage({}: Props) {
  32. const organization = useOrganization();
  33. const {hasSetupOneFeedback} = useHaveSelectedProjectsSetupFeedback();
  34. const {hasSetupNewFeedback} = useHaveSelectedProjectsSetupNewFeedback();
  35. const showWhatsNewBanner = hasSetupOneFeedback && !hasSetupNewFeedback;
  36. const hasNewOnboarding = organization.features.includes('user-feedback-onboarding');
  37. const feedbackSlug = useCurrentFeedbackId();
  38. const hasSlug = Boolean(feedbackSlug);
  39. const pageFilters = usePageFilters();
  40. const projects = useProjects();
  41. const selectedProjects = projects.projects.filter(p =>
  42. pageFilters.selection.projects.includes(Number(p.id))
  43. );
  44. // one selected project is widget eligible
  45. const oneIsWidgetEligible = selectedProjects.some(p =>
  46. feedbackWidgetPlatforms.includes(p.platform!)
  47. );
  48. const showWidgetBanner = showWhatsNewBanner && oneIsWidgetEligible && hasNewOnboarding;
  49. return (
  50. <SentryDocumentTitle title={t('User Feedback')} orgSlug={organization.slug}>
  51. <FullViewport>
  52. <FeedbackQueryKeys organization={organization}>
  53. <Layout.Header>
  54. <Layout.HeaderContent>
  55. <Layout.Title>
  56. {t('User Feedback')}
  57. <PageHeadingQuestionTooltip
  58. title={t(
  59. 'The User Feedback Widget allows users to submit feedback quickly and easily any time they encounter something that isn’t working as expected.'
  60. )}
  61. docsUrl="https://docs.sentry.io/product/user-feedback/"
  62. />
  63. </Layout.Title>
  64. </Layout.HeaderContent>
  65. <Layout.HeaderActions>
  66. <OldFeedbackButton />
  67. </Layout.HeaderActions>
  68. </Layout.Header>
  69. <PageFiltersContainer>
  70. <ErrorBoundary>
  71. <LayoutGrid data-banner={showWhatsNewBanner}>
  72. {showWidgetBanner ? (
  73. <FeedbackWidgetBanner style={{gridArea: 'banner'}} />
  74. ) : showWhatsNewBanner ? (
  75. <FeedbackWhatsNewBanner style={{gridArea: 'banner'}} />
  76. ) : null}
  77. <FeedbackFilters style={{gridArea: 'filters'}} />
  78. {hasSetupOneFeedback || hasSlug ? (
  79. <Fragment>
  80. <Container style={{gridArea: 'list'}}>
  81. <FeedbackList />
  82. </Container>
  83. <FeedbackSearch style={{gridArea: 'search'}} />
  84. <Container style={{gridArea: 'details'}}>
  85. <FeedbackItemLoader />
  86. </Container>
  87. </Fragment>
  88. ) : (
  89. <SetupContainer>
  90. <FeedbackSetupPanel />
  91. </SetupContainer>
  92. )}
  93. </LayoutGrid>
  94. </ErrorBoundary>
  95. </PageFiltersContainer>
  96. </FeedbackQueryKeys>
  97. </FullViewport>
  98. </SentryDocumentTitle>
  99. );
  100. }
  101. const LayoutGrid = styled('div')`
  102. background: ${p => p.theme.background};
  103. overflow: hidden;
  104. flex-grow: 1;
  105. display: grid;
  106. gap: ${space(2)};
  107. place-items: stretch;
  108. grid-template-rows: max-content 1fr;
  109. grid-template-areas:
  110. 'filters search'
  111. 'list details';
  112. &[data-banner='true'] {
  113. grid-template-rows: max-content max-content 1fr;
  114. grid-template-areas:
  115. 'banner banner'
  116. 'filters search'
  117. 'list details';
  118. }
  119. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  120. padding: ${space(2)};
  121. grid-template-columns: 1fr;
  122. grid-template-areas:
  123. 'filters'
  124. 'search'
  125. 'list'
  126. 'details';
  127. &[data-banner='true'] {
  128. grid-template-areas:
  129. 'banner'
  130. 'filters'
  131. 'search'
  132. 'list'
  133. 'details';
  134. }
  135. }
  136. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  137. padding: ${space(2)};
  138. grid-template-columns: minmax(1fr, 195px) 1fr;
  139. }
  140. @media (min-width: ${p => p.theme.breakpoints.large}) {
  141. padding: ${space(2)} ${space(4)} ${space(2)} ${space(4)};
  142. grid-template-columns: 390px 1fr;
  143. }
  144. @media (min-width: ${p => p.theme.breakpoints.large}) {
  145. grid-template-columns: minmax(390px, 1fr) 2fr;
  146. }
  147. `;
  148. const Container = styled(FluidHeight)`
  149. border: 1px solid ${p => p.theme.border};
  150. border-radius: ${p => p.theme.borderRadius};
  151. `;
  152. const SetupContainer = styled('div')`
  153. overflow: hidden;
  154. grid-column: 1 / -1;
  155. `;