feedbackListPage.tsx 6.2 KB

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