11'use client' ;
22
3+ import type { SessionEvent } from '@databuddy/shared' ;
34import { FileTextIcon , SparkleIcon } from '@phosphor-icons/react' ;
45import { Badge } from '@/components/ui/badge' ;
56import {
@@ -9,20 +10,110 @@ import {
910 getEventIconAndColor ,
1011} from './session-utils' ;
1112
12- interface SessionEvent {
13- event_id ?: string ;
14- time : string ;
15- event_name : string ;
16- path ?: string ;
17- error_message ?: string ;
18- error_type ?: string ;
19- properties ?: Record < string , unknown > ;
20- }
21-
2213interface SessionEventTimelineProps {
2314 events : SessionEvent [ ] ;
2415}
2516
17+ function EventProperties ( {
18+ properties,
19+ } : {
20+ properties : Record < string , unknown > ;
21+ } ) {
22+ return (
23+ < div className = "mt-3 rounded-lg border-2 border-accent/20 bg-accent/10 p-3" >
24+ < div className = "mb-2 flex items-center gap-2" >
25+ < SparkleIcon className = "h-4 w-4 text-accent-foreground" />
26+ < span className = "font-semibold text-accent-foreground text-sm" >
27+ Event Properties
28+ </ span >
29+ </ div >
30+ < div className = "space-y-2" >
31+ { Object . entries ( properties ) . map ( ( [ key , value ] ) => (
32+ < div
33+ className = "flex items-center gap-3 rounded border border-accent/20 bg-card/60 p-2"
34+ key = { key }
35+ >
36+ < span className = "min-w-0 truncate font-mono font-semibold text-accent-foreground text-xs" >
37+ { key }
38+ </ span >
39+ < span className = "rounded bg-accent/20 px-2 py-1 font-medium text-accent-foreground text-xs" >
40+ { formatPropertyValue ( value ) }
41+ </ span >
42+ </ div >
43+ ) ) }
44+ </ div >
45+ </ div >
46+ ) ;
47+ }
48+
49+ // Helper component for individual event
50+ function EventItem ( {
51+ event,
52+ eventIndex,
53+ } : {
54+ event : SessionEvent ;
55+ eventIndex : number ;
56+ } ) {
57+ const hasProperties = Boolean (
58+ event . properties && Object . keys ( event . properties ) . length > 0
59+ ) ;
60+ const { icon, color, bgColor, borderColor, badgeColor } =
61+ getEventIconAndColor ( event . event_name , hasProperties ) ;
62+ const displayPath = getDisplayPath ( event . path || '' ) ;
63+ const fullPath = cleanUrl ( event . path || '' ) ;
64+
65+ const eventTitle = event . event_name ;
66+ const titleColor = hasProperties
67+ ? 'text-accent-foreground'
68+ : 'text-foreground' ;
69+
70+ return (
71+ < div
72+ className = { `group flex items-start gap-3 rounded-lg border-2 p-4 ${ bgColor } ${ borderColor } ${ hasProperties ? 'shadow-sm' : '' } ` }
73+ key = { event . event_id || eventIndex }
74+ >
75+ < div
76+ className = { `flex h-8 w-8 items-center justify-center rounded-full border-2 bg-card font-bold text-xs ${ color } flex-shrink-0 shadow-sm` }
77+ >
78+ { eventIndex + 1 }
79+ </ div >
80+ < div className = "flex min-w-0 flex-1 items-start gap-3" >
81+ < div className = { `${ color } mt-1 flex-shrink-0` } > { icon } </ div >
82+ < div className = "min-w-0 flex-1" >
83+ < div className = "mb-2 flex items-start justify-between gap-2" >
84+ < div className = "flex min-w-0 flex-wrap items-center gap-2" >
85+ < span className = { `font-semibold text-sm ${ titleColor } ` } >
86+ { eventTitle }
87+ </ span >
88+ { displayPath && (
89+ < Badge
90+ className = "font-mono text-xs"
91+ title = { fullPath }
92+ variant = "secondary"
93+ >
94+ { displayPath }
95+ </ Badge >
96+ ) }
97+ { hasProperties && (
98+ < Badge className = { `font-medium text-xs ${ badgeColor } ` } >
99+ Custom Event
100+ </ Badge >
101+ ) }
102+ </ div >
103+ < div className = "flex-shrink-0 whitespace-nowrap font-medium text-muted-foreground text-xs" >
104+ { new Date ( event . time ) . toLocaleTimeString ( ) }
105+ </ div >
106+ </ div >
107+
108+ { hasProperties && event . properties && (
109+ < EventProperties properties = { event . properties } />
110+ ) }
111+ </ div >
112+ </ div >
113+ </ div >
114+ ) ;
115+ }
116+
26117export function SessionEventTimeline ( { events } : SessionEventTimelineProps ) {
27118 if ( ! events ?. length ) {
28119 return (
@@ -35,106 +126,13 @@ export function SessionEventTimeline({ events }: SessionEventTimelineProps) {
35126
36127 return (
37128 < div className = "max-h-96 space-y-3 overflow-y-auto" >
38- { events . map ( ( event , eventIndex ) => {
39- const hasProperties = Boolean (
40- event . properties && Object . keys ( event . properties ) . length > 0
41- ) ;
42- const { icon, color, bgColor, borderColor, badgeColor } =
43- getEventIconAndColor (
44- event . event_name ,
45- Boolean ( event . error_message ) ,
46- hasProperties
47- ) ;
48- const displayPath = getDisplayPath ( event . path || '' ) ;
49- const fullPath = cleanUrl ( event . path || '' ) ;
50-
51- return (
52- < div
53- className = { `group flex items-start gap-3 rounded-lg border-2 p-4 ${ bgColor } ${ borderColor } ${ hasProperties ? 'shadow-sm' : '' } ` }
54- key = { event . event_id || eventIndex }
55- >
56- < div
57- className = { `flex h-8 w-8 items-center justify-center rounded-full border-2 bg-card font-bold text-xs ${ color } flex-shrink-0 shadow-sm` }
58- >
59- { eventIndex + 1 }
60- </ div >
61- < div className = "flex min-w-0 flex-1 items-start gap-3" >
62- < div className = { `${ color } mt-1 flex-shrink-0` } > { icon } </ div >
63- < div className = "min-w-0 flex-1" >
64- < div className = "mb-2 flex items-start justify-between gap-2" >
65- < div className = "flex min-w-0 flex-wrap items-center gap-2" >
66- < span
67- className = { `font-semibold text-sm ${
68- event . error_message
69- ? 'text-destructive'
70- : hasProperties
71- ? 'text-accent-foreground'
72- : 'text-foreground'
73- } `}
74- >
75- { event . error_message ? 'Error' : event . event_name }
76- </ span >
77- { displayPath && (
78- < Badge
79- className = "font-mono text-xs"
80- title = { fullPath }
81- variant = "secondary"
82- >
83- { displayPath }
84- </ Badge >
85- ) }
86- { hasProperties && (
87- < Badge className = { `font-medium text-xs ${ badgeColor } ` } >
88- Custom Event
89- </ Badge >
90- ) }
91- </ div >
92- < div className = "flex-shrink-0 whitespace-nowrap font-medium text-muted-foreground text-xs" >
93- { new Date ( event . time ) . toLocaleTimeString ( ) }
94- </ div >
95- </ div >
96-
97- { hasProperties && (
98- < div className = "mt-3 rounded-lg border-2 border-accent/20 bg-accent/10 p-3" >
99- < div className = "mb-2 flex items-center gap-2" >
100- < SparkleIcon className = "h-4 w-4 text-accent-foreground" />
101- < span className = "font-semibold text-accent-foreground text-sm" >
102- Event Properties
103- </ span >
104- </ div >
105- < div className = "space-y-2" >
106- { Object . entries ( event . properties || { } ) . map (
107- ( [ key , value ] ) => (
108- < div
109- className = "flex items-center gap-3 rounded border border-accent/20 bg-card/60 p-2"
110- key = { key }
111- >
112- < span className = "min-w-0 truncate font-mono font-semibold text-accent-foreground text-xs" >
113- { key }
114- </ span >
115- < span className = "rounded bg-accent/20 px-2 py-1 font-medium text-accent-foreground text-xs" >
116- { formatPropertyValue ( value ) }
117- </ span >
118- </ div >
119- )
120- ) }
121- </ div >
122- </ div >
123- ) }
124-
125- { event . error_message && (
126- < div className = "mt-3 rounded-lg border-2 border-destructive/20 bg-destructive/10 p-3" >
127- < div className = "text-destructive text-sm" >
128- < span className = "font-semibold" > { event . error_type } :</ span > { ' ' }
129- { event . error_message }
130- </ div >
131- </ div >
132- ) }
133- </ div >
134- </ div >
135- </ div >
136- ) ;
137- } ) }
129+ { events . map ( ( event , eventIndex ) => (
130+ < EventItem
131+ event = { event }
132+ eventIndex = { eventIndex }
133+ key = { event . event_id || eventIndex }
134+ />
135+ ) ) }
138136 </ div >
139137 ) ;
140138}
0 commit comments