diff --git a/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--dark.png b/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--dark.png index b54d4facb0bfe..e3e93b3dd9678 100644 Binary files a/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--dark.png and b/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--dark.png differ diff --git a/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--light.png b/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--light.png index 332ea24ce3084..d4377362d9270 100644 Binary files a/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--light.png and b/frontend/__snapshots__/exporter-exporter--trends-line-insight-detailed--light.png differ diff --git a/frontend/__snapshots__/lemon-ui-colors--all-three-thousand-color-options--dark.png b/frontend/__snapshots__/lemon-ui-colors--all-three-thousand-color-options--dark.png index cbb31b2fef96d..5b9d724c95a96 100644 Binary files a/frontend/__snapshots__/lemon-ui-colors--all-three-thousand-color-options--dark.png and b/frontend/__snapshots__/lemon-ui-colors--all-three-thousand-color-options--dark.png differ diff --git a/frontend/__snapshots__/lemon-ui-colors--all-three-thousand-color-options--light.png b/frontend/__snapshots__/lemon-ui-colors--all-three-thousand-color-options--light.png index 84d1b755c6ffd..c820ba1044cd8 100644 Binary files a/frontend/__snapshots__/lemon-ui-colors--all-three-thousand-color-options--light.png and b/frontend/__snapshots__/lemon-ui-colors--all-three-thousand-color-options--light.png differ diff --git a/frontend/__snapshots__/replay-watch-next-panel--disabled--dark.png b/frontend/__snapshots__/replay-watch-next-panel--disabled--dark.png new file mode 100644 index 0000000000000..314fc70e79b2c Binary files /dev/null and b/frontend/__snapshots__/replay-watch-next-panel--disabled--dark.png differ diff --git a/frontend/__snapshots__/replay-watch-next-panel--disabled--light.png b/frontend/__snapshots__/replay-watch-next-panel--disabled--light.png new file mode 100644 index 0000000000000..4b77ee8e8e5e3 Binary files /dev/null and b/frontend/__snapshots__/replay-watch-next-panel--disabled--light.png differ diff --git a/frontend/__snapshots__/replay-watch-next-panel--empty--dark.png b/frontend/__snapshots__/replay-watch-next-panel--empty--dark.png new file mode 100644 index 0000000000000..aa1dbc4f6b5b5 Binary files /dev/null and b/frontend/__snapshots__/replay-watch-next-panel--empty--dark.png differ diff --git a/frontend/__snapshots__/replay-watch-next-panel--empty--light.png b/frontend/__snapshots__/replay-watch-next-panel--empty--light.png new file mode 100644 index 0000000000000..458a79009b242 Binary files /dev/null and b/frontend/__snapshots__/replay-watch-next-panel--empty--light.png differ diff --git a/frontend/__snapshots__/replay-watch-next-panel--loading--dark.png b/frontend/__snapshots__/replay-watch-next-panel--loading--dark.png new file mode 100644 index 0000000000000..19ee177f1ef86 Binary files /dev/null and b/frontend/__snapshots__/replay-watch-next-panel--loading--dark.png differ diff --git a/frontend/__snapshots__/replay-watch-next-panel--loading--light.png b/frontend/__snapshots__/replay-watch-next-panel--loading--light.png new file mode 100644 index 0000000000000..90c47df650fca Binary files /dev/null and b/frontend/__snapshots__/replay-watch-next-panel--loading--light.png differ diff --git a/frontend/__snapshots__/replay-watch-next-panel--scores--dark.png b/frontend/__snapshots__/replay-watch-next-panel--scores--dark.png new file mode 100644 index 0000000000000..d415312219653 Binary files /dev/null and b/frontend/__snapshots__/replay-watch-next-panel--scores--dark.png differ diff --git a/frontend/__snapshots__/replay-watch-next-panel--scores--light.png b/frontend/__snapshots__/replay-watch-next-panel--scores--light.png new file mode 100644 index 0000000000000..037153a091079 Binary files /dev/null and b/frontend/__snapshots__/replay-watch-next-panel--scores--light.png differ diff --git a/frontend/src/lib/lemon-ui/colors.stories.tsx b/frontend/src/lib/lemon-ui/colors.stories.tsx index cb3566640b7a7..f2e87c73528af 100644 --- a/frontend/src/lib/lemon-ui/colors.stories.tsx +++ b/frontend/src/lib/lemon-ui/colors.stories.tsx @@ -63,13 +63,123 @@ const preThousand = [ ] const threeThousand = [ + 'primary', + 'danger-highlight', + 'danger-lighter', + 'danger-light', + 'danger', + 'danger-dark', + 'warning-highlight', + 'warning', + 'warning-dark', + 'highlight', + 'success-highlight', + 'success-light', + 'success', + 'success-dark', + 'muted', + 'muted-alt', + 'mark', + 'white', + 'bg-light', + 'side', + 'mid', + 'border', + 'border-light', + 'border-bold', + 'transparent', + 'link', + // Colors of the PostHog logo + 'brand-blue', + 'brand-red', + 'brand-yellow', + 'brand-key', + + // PostHog 3000 + 'text-3000-light', + 'text-secondary-3000-light', + 'muted-3000-light', + 'trace-3000-light', + 'primary-3000-light', + 'primary-highlight-light', + 'primary-3000-hover-light', + 'primary-3000-active-light', + + 'secondary-3000-light', + 'secondary-3000-hover-light', + 'accent-3000-light', + 'bg-3000-light', + 'border-3000-light', + 'border-bold-3000-light', + 'glass-bg-3000-light', + 'glass-border-3000-light', + + 'link-3000-light', + 'primary-3000-frame-bg-light', + 'primary-3000-button-bg-light', + 'primary-3000-button-border-light', + 'primary-3000-button-border-hover-light', + + 'secondary-3000-frame-bg-light', + 'secondary-3000-button-bg-light', + 'secondary-3000-button-border-light', + 'secondary-3000-button-border-hover-light', + + 'danger-3000-frame-bg-light', + 'danger-3000-button-border-light', + 'danger-3000-button-border-hover-light', + + 'shadow-elevation-3000-light', + 'shadow-elevation-3000-dark', + 'text-3000-dark', + 'text-secondary-3000-dark', + 'muted-3000-dark', + 'trace-3000-dark', + 'primary-3000-dark', + 'primary-highlight-dark', + 'primary-3000-hover-dark', + 'primary-3000-active-dark', + 'primary-alt-highlight-light', + + 'secondary-3000-dark', + 'secondary-3000-hover-dark', + 'accent-3000-dark', + 'bg-3000-dark', + 'border-3000-dark', + 'border-bold-3000-dark', + 'glass-bg-3000-dark', + 'glass-border-3000-dark', + 'link-3000-dark', + + 'primary-3000-frame-bg-dark', + 'primary-3000-button-bg-dark', + 'primary-3000-button-border-dark', + 'primary-3000-button-border-hover-dark', + 'primary-alt-highlight-dark', + + 'secondary-3000-frame-bg-dark', + 'secondary-3000-button-bg-dark', + 'secondary-3000-button-border-dark', + 'secondary-3000-button-border-hover-dark', + + 'danger-3000-frame-bg-dark', + 'danger-3000-button-border-dark', + 'danger-3000-button-border-hover-dark', + + // The derived colors + // `--default` is a pre-3000 alias for "default text color" (`--text-3000` now) + 'default', 'text-3000', + 'text-secondary-3000', 'muted-3000', 'primary-3000', 'secondary-3000', 'secondary-3000-hover', 'accent-3000', 'bg-3000', + 'primary-highlight', + 'primary-alt-highlight', + 'primary-alt', ] export function ColorPalette(): JSX.Element { @@ -147,7 +257,7 @@ export function AllThreeThousandColorOptions(): JSX.Element { render: function RenderColor(color) { return (
-
+
) }, @@ -159,7 +269,7 @@ export function AllThreeThousandColorOptions(): JSX.Element { render: function RenderColor(color) { return (
-
+
) }, diff --git a/frontend/src/scenes/project-homepage/ProjectHomePageCompactListItem.tsx b/frontend/src/scenes/project-homepage/ProjectHomePageCompactListItem.tsx index cefd3e82870bc..5f33dee95e1a8 100644 --- a/frontend/src/scenes/project-homepage/ProjectHomePageCompactListItem.tsx +++ b/frontend/src/scenes/project-homepage/ProjectHomePageCompactListItem.tsx @@ -3,7 +3,7 @@ import React from 'react' export type RecentItemRowProps = Pick & { title: string - subtitle: string + subtitle: React.ReactNode prefix?: React.ReactNode suffix?: React.ReactNode } diff --git a/frontend/src/scenes/project-homepage/WatchNextList.stories.tsx b/frontend/src/scenes/project-homepage/WatchNextList.stories.tsx new file mode 100644 index 0000000000000..cd4c01e317244 --- /dev/null +++ b/frontend/src/scenes/project-homepage/WatchNextList.stories.tsx @@ -0,0 +1,78 @@ +import { Meta, StoryFn, StoryObj } from '@storybook/react' +import { WatchNextList, WatchNextListProps } from 'scenes/project-homepage/WatchNextPanel' + +import { SessionRecordingType } from '~/types' + +function asRecording(param: Partial): SessionRecordingType { + return { + id: '0', + person: { + distinct_ids: ['a distinct id'], + properties: {}, + }, + activity_score: 0, + recording_duration: 0, + matching_events: [], + viewed: false, + start_time: '2024-11-01 12:34', + end_time: '2024-11-01 13:34', + snapshot_source: 'web', + ...param, + } +} + +type Story = StoryObj +const meta: Meta = { + title: 'Replay/Watch Next Panel', + decorators: [], + parameters: { + testOptions: { + waitForLoadersToDisappear: false, + }, + }, +} +export default meta + +const Template: StoryFn = ({ ...props }: Partial) => { + return ( + + ) +} + +export const Empty: Story = Template.bind({}) +Empty.args = {} + +export const Disabled: Story = Template.bind({}) +Disabled.args = { + recordingsOptIn: false, +} + +export const Loading: Story = Template.bind({}) +Loading.args = { + loading: true, +} + +export const Scores: Story = Template.bind({}) +Scores.args = { + sessionRecordings: [ + asRecording({ + activity_score: 99, + }), + asRecording({ + activity_score: 75, + }), + asRecording({ + activity_score: 50, + }), + asRecording({ + activity_score: 25, + }), + asRecording({ + activity_score: 5, + }), + ], +} diff --git a/frontend/src/scenes/project-homepage/WatchNextPanel.tsx b/frontend/src/scenes/project-homepage/WatchNextPanel.tsx index 41edce03290bb..e255036cac11f 100644 --- a/frontend/src/scenes/project-homepage/WatchNextPanel.tsx +++ b/frontend/src/scenes/project-homepage/WatchNextPanel.tsx @@ -1,9 +1,11 @@ import './ProjectHomepage.scss' import { IconInfo } from '@posthog/icons' +import clsx from 'clsx' import { useActions, useValues } from 'kea' import { CompactList } from 'lib/components/CompactList/CompactList' import { IconPlayCircle } from 'lib/lemon-ui/icons' +import { LemonSnack } from 'lib/lemon-ui/LemonSnack' import { ProfilePicture } from 'lib/lemon-ui/ProfilePicture' import { Tooltip } from 'lib/lemon-ui/Tooltip' import { humanFriendlyDuration } from 'lib/utils' @@ -25,6 +27,29 @@ interface RecordingRowProps { recording: SessionRecordingType } +type ACTIVITY_DESCRIPTIONS = 'very low' | 'low' | 'medium' | 'high' | 'very high' + +function ActivityScoreLabel({ score }: { score: number | undefined }): JSX.Element { + const n = score ?? 0 + let backgroundColor = 'bg-primary-alt-highlight' + let description: ACTIVITY_DESCRIPTIONS = 'very low' + if (n >= 90) { + backgroundColor = 'bg-success-highlight' + description = 'very high' + } else if (n >= 75) { + backgroundColor = 'bg-success-highlight' + description = 'high' + } else if (n >= 50) { + backgroundColor = 'bg-warning-highlight' + description = 'medium' + } else if (n >= 25) { + backgroundColor = 'bg-warning-highlight' + description = 'low' + } + + return activity: {description} +} + export function RecordingRow({ recording }: RecordingRowProps): JSX.Element { const { openSessionPlayer } = useActions(sessionPlayerModalLogic) const { reportRecordingOpenedFromRecentRecordingList } = useActions(eventUsageLogic) @@ -32,7 +57,7 @@ export function RecordingRow({ recording }: RecordingRowProps): JSX.Element { return ( } prefix={} suffix={
@@ -51,6 +76,47 @@ export function RecordingRow({ recording }: RecordingRowProps): JSX.Element { ) } +export interface WatchNextListProps { + sessionRecordings: SessionRecordingType[] + loading: boolean + recordingsOptIn: boolean | undefined +} + +// separated from the logics so that it can have storybook tests without mocking API calls +export function WatchNextList({ sessionRecordings, loading, recordingsOptIn }: WatchNextListProps): JSX.Element { + return ( + +
+ Watch next + +
+ + } + viewAllURL={urls.replay()} + loading={loading} + emptyMessage={ + recordingsOptIn + ? { + title: 'There are no recordings for this project', + description: 'Make sure you have the javascript snippet setup in your website.', + buttonText: 'Learn more', + buttonTo: 'https://posthog.com/docs/user-guides/recordings', + } + : { + title: 'Recordings are not enabled for this project', + description: 'Once recordings are enabled, recordings will display here.', + buttonText: 'Enable recordings', + buttonTo: urls.settings('project-replay'), + } + } + items={sessionRecordings.slice(0, 5)} + renderRow={(recording: SessionRecordingType, index) => } + /> + ) +} + export function WatchNextPanel(): JSX.Element { const { currentTeam } = useValues(teamLogic) const sessionRecordingsListLogicInstance = sessionRecordingsPlaylistLogic({ @@ -63,38 +129,10 @@ export function WatchNextPanel(): JSX.Element { const { sessionRecordings, sessionRecordingsResponseLoading } = useValues(sessionRecordingsListLogicInstance) return ( - <> - -
- Watch next - -
- - } - viewAllURL={urls.replay()} - loading={sessionRecordingsResponseLoading} - emptyMessage={ - currentTeam?.session_recording_opt_in - ? { - title: 'There are no recordings for this project', - description: 'Make sure you have the javascript snippet setup in your website.', - buttonText: 'Learn more', - buttonTo: 'https://posthog.com/docs/user-guides/recordings', - } - : { - title: 'Recordings are not enabled for this project', - description: 'Once recordings are enabled, recordings will display here.', - buttonText: 'Enable recordings', - buttonTo: urls.settings('project-replay'), - } - } - items={sessionRecordings.slice(0, 5)} - renderRow={(recording: SessionRecordingType, index) => ( - - )} - /> - + ) }