index.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import {useLayoutEffect, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import NotFound from 'sentry/components/errors/notFound';
  4. import {BorderlessEventEntries} from 'sentry/components/events/eventEntries';
  5. import Footer from 'sentry/components/footer';
  6. import Link from 'sentry/components/links/link';
  7. import LoadingError from 'sentry/components/loadingError';
  8. import LoadingIndicator from 'sentry/components/loadingIndicator';
  9. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  10. import {t} from 'sentry/locale';
  11. import {space} from 'sentry/styles/space';
  12. import type {Group} from 'sentry/types/group';
  13. import type {RouteComponentProps} from 'sentry/types/legacyReactRouter';
  14. import {useApiQuery} from 'sentry/utils/queryClient';
  15. import {OrganizationContext} from '../organizationContext';
  16. import SharedGroupHeader from './sharedGroupHeader';
  17. type Props = RouteComponentProps<{shareId: string; orgId?: string}, {}>;
  18. function SharedGroupDetails({params}: Props) {
  19. useLayoutEffect(() => {
  20. document.body.classList.add('shared-group');
  21. return () => {
  22. document.body.classList.remove('shared-group');
  23. };
  24. }, []);
  25. const orgSlug = useMemo(() => {
  26. if (params.orgId) {
  27. return params.orgId;
  28. }
  29. const {customerDomain} = window.__initialData || {};
  30. if (customerDomain?.subdomain) {
  31. return customerDomain.subdomain;
  32. }
  33. return null;
  34. }, [params.orgId]);
  35. const {shareId} = params;
  36. const {
  37. data: group,
  38. isPending,
  39. isError,
  40. refetch,
  41. } = useApiQuery<Group>(
  42. [
  43. orgSlug
  44. ? `/organizations/${orgSlug}/shared/issues/${shareId}/`
  45. : `/shared/issues/${shareId}/`,
  46. ],
  47. {
  48. staleTime: 0,
  49. }
  50. );
  51. if (isPending) {
  52. return <LoadingIndicator />;
  53. }
  54. if (!group) {
  55. return <NotFound />;
  56. }
  57. if (isError) {
  58. return <LoadingError onRetry={refetch} />;
  59. }
  60. // project.organization is not a real organization, it's just the slug and name
  61. // Add the features array to avoid errors when using OrganizationContext
  62. const org = {...group.project.organization, features: []};
  63. return (
  64. <SentryDocumentTitle noSuffix title={group?.title ?? 'Sentry'}>
  65. <OrganizationContext.Provider value={org}>
  66. <div className="app">
  67. <div className="pattern-bg" />
  68. <div className="container">
  69. <div className="box box-modal">
  70. <div className="box-header">
  71. <Link className="logo" to="/">
  72. <span className="icon-sentry-logo-full" />
  73. </Link>
  74. {group.permalink && (
  75. <Link className="details" to={group.permalink}>
  76. {t('Details')}
  77. </Link>
  78. )}
  79. </div>
  80. <div className="box-content">
  81. <SharedGroupHeader group={group} />
  82. <Container className="group-overview event-details-container">
  83. <BorderlessEventEntries
  84. organization={org}
  85. group={group}
  86. event={group.latestEvent}
  87. project={group.project}
  88. isShare
  89. />
  90. </Container>
  91. <Footer />
  92. </div>
  93. </div>
  94. </div>
  95. </div>
  96. </OrganizationContext.Provider>
  97. </SentryDocumentTitle>
  98. );
  99. }
  100. const Container = styled('div')`
  101. padding: ${space(4)};
  102. `;
  103. export default SharedGroupDetails;