import type {ReactNode} from 'react'; import styled from '@emotion/styled'; import type {MenuItemProps} from 'sentry/components/dropdownMenu'; import {DropdownMenu} from 'sentry/components/dropdownMenu'; import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton'; import UserBadge from 'sentry/components/idBadge/userBadge'; import FullViewport from 'sentry/components/layouts/fullViewport'; import * as Layout from 'sentry/components/layouts/thirds'; import ConfigureReplayCard from 'sentry/components/replays/configureReplayCard'; import DetailsPageBreadcrumbs from 'sentry/components/replays/header/detailsPageBreadcrumbs'; import FeedbackButton from 'sentry/components/replays/header/feedbackButton'; import HeaderPlaceholder from 'sentry/components/replays/header/headerPlaceholder'; import ReplayMetaData from 'sentry/components/replays/header/replayMetaData'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import TimeSince from 'sentry/components/timeSince'; import {IconCalendar, IconDelete, IconEllipsis, IconUpload} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {defined} from 'sentry/utils'; import useDeleteReplay from 'sentry/utils/replays/hooks/useDeleteReplay'; import useShareReplayAtTimestamp from 'sentry/utils/replays/hooks/useShareReplayAtTimestamp'; import type {ReplayError, ReplayRecord} from 'sentry/views/replays/types'; type Props = { children: ReactNode; orgSlug: string; projectSlug: string | null; replayErrors: ReplayError[]; replayRecord: undefined | ReplayRecord; isVideoReplay?: boolean; }; export default function Page({ children, orgSlug, replayRecord, projectSlug, replayErrors, isVideoReplay, }: Props) { const title = replayRecord ? `${replayRecord.user.display_name ?? t('Anonymous User')} — Session Replay — ${orgSlug}` : `Session Replay — ${orgSlug}`; const onShareReplay = useShareReplayAtTimestamp(); const onDeleteReplay = useDeleteReplay({replayId: replayRecord?.id, projectSlug}); const dropdownItems: MenuItemProps[] = [ { key: 'share', label: ( {t('Share')} ), onAction: onShareReplay, }, replayRecord?.id && projectSlug ? { key: 'delete', label: ( {t('Delete')} ), onAction: onDeleteReplay, } : null, ].filter(defined); const header = replayRecord?.is_archived ? (
) : (
{isVideoReplay ? : } , }} size="sm" items={dropdownItems} /> {replayRecord ? ( {replayRecord.user.display_name || t('Anonymous User')} {replayRecord && ( )} } user={{ name: replayRecord.user.display_name || '', email: replayRecord.user.email || '', username: replayRecord.user.username || '', ip_address: replayRecord.user.ip || '', id: replayRecord.user.id || '', }} hideEmail /> ) : ( )}
); return ( {header} {children} ); } const Header = styled(Layout.Header)` gap: ${space(1)}; padding-bottom: ${space(1.5)}; @media (min-width: ${p => p.theme.breakpoints.medium}) { gap: ${space(1)} ${space(3)}; padding: ${space(2)} ${space(2)} ${space(1.5)} ${space(2)}; } `; // TODO(replay); This could make a lot of sense to put inside HeaderActions by default const ButtonActionsWrapper = styled(Layout.HeaderActions)` flex-direction: row; justify-content: flex-end; gap: ${space(1)}; @media (max-width: ${p => p.theme.breakpoints.medium}) { margin-bottom: 0; } `; const ItemSpacer = styled('div')` display: flex; gap: ${space(1)}; align-items: center; `; const Title = styled('h1')` ${p => p.theme.overflowEllipsis}; ${p => p.theme.text.pageTitle}; font-size: ${p => p.theme.fontSizeExtraLarge}; color: ${p => p.theme.headingColor}; margin: 0; line-height: 1.4; `; const TimeContainer = styled('div')` display: flex; gap: ${space(1)}; align-items: center; color: ${p => p.theme.gray300}; font-size: ${p => p.theme.fontSizeMedium}; line-height: 1.4; `; const DisplayHeader = styled('div')` display: flex; flex-direction: column; `;