1- ' use client' ;
1+ " use client" ;
22
33import {
44 ArrowSquareOutIcon ,
@@ -12,128 +12,128 @@ import dayjs from 'dayjs';
1212import { FaviconImage } from '@/components/analytics/favicon-image' ;
1313import { Badge } from '@/components/ui/badge' ;
1414import {
15- Collapsible ,
16- CollapsibleContent ,
17- CollapsibleTrigger ,
18- } from ' @/components/ui/collapsible' ;
15+ Collapsible ,
16+ CollapsibleContent ,
17+ CollapsibleTrigger ,
18+ } from " @/components/ui/collapsible" ;
1919
2020type ProfileData = {
21- visitor_id : string ;
22- first_visit : string ;
23- last_visit : string ;
24- total_sessions : number ;
25- total_pageviews : number ;
26- total_duration : number ;
27- total_duration_formatted : string ;
28- device : string ;
29- browser : string ;
30- os : string ;
31- country : string ;
32- region : string ;
33- sessions : Array < {
34- session_id : string ;
35- session_name : string ;
36- first_visit : string ;
37- last_visit : string ;
38- duration : number ;
39- duration_formatted : string ;
40- page_views : number ;
41- unique_pages : number ;
42- device : string ;
43- browser : string ;
44- os : string ;
45- country : string ;
46- region : string ;
47- referrer : string ;
48- events : Array < {
49- event_id : string ;
50- time : string ;
51- event_name : string ;
52- path : string ;
53- error_message ?: string ;
54- error_type ?: string ;
55- properties : Record < string , any > ;
56- } > ;
57- } > ;
21+ visitor_id : string ;
22+ first_visit : string ;
23+ last_visit : string ;
24+ total_sessions : number ;
25+ total_pageviews : number ;
26+ total_duration : number ;
27+ total_duration_formatted : string ;
28+ device : string ;
29+ browser : string ;
30+ os : string ;
31+ country : string ;
32+ region : string ;
33+ sessions : Array < {
34+ session_id : string ;
35+ session_name : string ;
36+ first_visit : string ;
37+ last_visit : string ;
38+ duration : number ;
39+ duration_formatted : string ;
40+ page_views : number ;
41+ unique_pages : number ;
42+ device : string ;
43+ browser : string ;
44+ os : string ;
45+ country : string ;
46+ region : string ;
47+ referrer : string ;
48+ events : Array < {
49+ event_id : string ;
50+ time : string ;
51+ event_name : string ;
52+ path : string ;
53+ error_message ?: string ;
54+ error_type ?: string ;
55+ properties : Record < string , any > ;
56+ } > ;
57+ } > ;
5858} ;
5959
6060import {
61- formatDuration ,
62- getBrowserIconComponent ,
63- getCountryFlag ,
64- getDeviceIcon ,
65- getOSIconComponent ,
66- } from ' ./profile-utils' ;
61+ formatDuration ,
62+ getBrowserIconComponent ,
63+ getCountryFlag ,
64+ getDeviceIcon ,
65+ getOSIconComponent ,
66+ } from " ./profile-utils" ;
6767
6868interface ProfileRowProps {
69- profile : ProfileData ;
70- index : number ;
71- isExpanded : boolean ;
72- onToggle : ( ) => void ;
69+ profile : ProfileData ;
70+ index : number ;
71+ isExpanded : boolean ;
72+ onToggle : ( ) => void ;
7373}
7474
7575function getReferrerDisplayInfo ( session : any ) {
76- // Check if we have parsed referrer info
77- if ( session . referrer_parsed ) {
78- return {
79- name :
80- session . referrer_parsed . name ||
81- session . referrer_parsed . domain ||
82- ' Unknown' ,
83- domain : session . referrer_parsed . domain ,
84- type : session . referrer_parsed . type ,
85- } ;
86- }
76+ // Check if we have parsed referrer info
77+ if ( session . referrer_parsed ) {
78+ return {
79+ name :
80+ session . referrer_parsed . name ||
81+ session . referrer_parsed . domain ||
82+ " Unknown" ,
83+ domain : session . referrer_parsed . domain ,
84+ type : session . referrer_parsed . type ,
85+ } ;
86+ }
8787
88- // Fallback to raw referrer
89- if (
90- session . referrer &&
91- session . referrer !== ' direct' &&
92- session . referrer !== ''
93- ) {
94- try {
95- const url = new URL (
96- session . referrer . startsWith ( ' http' )
97- ? session . referrer
98- : `https://${ session . referrer } `
99- ) ;
100- return {
101- name : url . hostname . replace ( ' www.' , '' ) ,
102- domain : url . hostname ,
103- type : ' referrer' ,
104- } ;
105- } catch {
106- return {
107- name : session . referrer ,
108- domain : null ,
109- type : ' referrer' ,
110- } ;
111- }
112- }
88+ // Fallback to raw referrer
89+ if (
90+ session . referrer &&
91+ session . referrer !== " direct" &&
92+ session . referrer !== ""
93+ ) {
94+ try {
95+ const url = new URL (
96+ session . referrer . startsWith ( " http" )
97+ ? session . referrer
98+ : `https://${ session . referrer } ` ,
99+ ) ;
100+ return {
101+ name : url . hostname . replace ( " www." , "" ) ,
102+ domain : url . hostname ,
103+ type : " referrer" ,
104+ } ;
105+ } catch {
106+ return {
107+ name : session . referrer ,
108+ domain : null ,
109+ type : " referrer" ,
110+ } ;
111+ }
112+ }
113113
114- return {
115- name : ' Direct' ,
116- domain : null ,
117- type : ' direct' ,
118- } ;
114+ return {
115+ name : " Direct" ,
116+ domain : null ,
117+ type : " direct" ,
118+ } ;
119119}
120120
121121export function ProfileRow ( {
122- profile,
123- index,
124- isExpanded,
125- onToggle,
122+ profile,
123+ index,
124+ isExpanded,
125+ onToggle,
126126} : ProfileRowProps ) {
127- const avgSessionDuration =
128- profile . total_sessions > 0
129- ? ( profile . total_duration || 0 ) / profile . total_sessions
130- : 0 ;
127+ const avgSessionDuration =
128+ profile . total_sessions > 0
129+ ? ( profile . total_duration || 0 ) / profile . total_sessions
130+ : 0 ;
131131
132- // Get the most recent session's referrer for the main profile display
133- const latestSession = profile . sessions ?. [ 0 ] ;
134- const profileReferrerInfo = latestSession
135- ? getReferrerDisplayInfo ( latestSession )
136- : null ;
132+ // Get the most recent session's referrer for the main profile display
133+ const latestSession = profile . sessions ?. [ 0 ] ;
134+ const profileReferrerInfo = latestSession
135+ ? getReferrerDisplayInfo ( latestSession )
136+ : null ;
137137
138138 return (
139139 < Collapsible onOpenChange = { onToggle } open = { isExpanded } >
@@ -158,17 +158,17 @@ export function ProfileRow({
158158 { getOSIconComponent ( profile . os ) }
159159 </ div >
160160
161- { /* Profile Info */ }
162- < div className = "min-w-0 flex-1" >
163- < div className = "truncate font-semibold text-base text-foreground" >
164- Visitor { profile . visitor_id . substring ( 0 , 8 ) } ...
165- </ div >
166- < div className = "flex items-center gap-2 text-muted-foreground text-sm" >
167- < span > { profile . browser } </ span >
168- < span className = "text-muted-foreground/60" > •</ span >
169- < span > { profile . country || ' Unknown' } </ span >
170- </ div >
171- </ div >
161+ { /* Profile Info */ }
162+ < div className = "min-w-0 flex-1" >
163+ < div className = "truncate font-semibold text-base text-foreground" >
164+ Visitor { profile . visitor_id . substring ( 0 , 8 ) } ...
165+ </ div >
166+ < div className = "flex items-center gap-2 text-muted-foreground text-sm" >
167+ < span > { profile . browser } </ span >
168+ < span className = "text-muted-foreground/60" > •</ span >
169+ < span > { profile . country || " Unknown" } </ span >
170+ </ div >
171+ </ div >
172172
173173 { /* Latest Referrer Info */ }
174174 { profileReferrerInfo && (
@@ -204,41 +204,41 @@ export function ProfileRow({
204204 </ span >
205205 </ div >
206206
207- { /* Page Views */ }
208- < div className = "hidden min-w-[60px] flex-col items-center gap-1 sm:flex" >
209- < div className = "flex items-center gap-1 text-muted-foreground text-xs" >
210- < EyeIcon className = "h-3 w-3" />
211- < span > Pages</ span >
212- </ div >
213- < span className = "font-semibold text-foreground text-sm" >
214- { profile . total_pageviews }
215- </ span >
216- </ div >
207+ { /* Page Views */ }
208+ < div className = "hidden min-w-[60px] flex-col items-center gap-1 sm:flex" >
209+ < div className = "flex items-center gap-1 text-muted-foreground text-xs" >
210+ < EyeIcon className = "h-3 w-3" />
211+ < span > Pages</ span >
212+ </ div >
213+ < span className = "font-semibold text-foreground text-sm" >
214+ { profile . total_pageviews }
215+ </ span >
216+ </ div >
217217
218- { /* Avg Duration */ }
219- < div className = "hidden min-w-[60px] flex-col items-center gap-1 lg:flex" >
220- < div className = "flex items-center gap-1 text-muted-foreground text-xs" >
221- < ClockIcon className = "h-3 w-3" />
222- < span > Avg Time</ span >
223- </ div >
224- < span className = "font-semibold text-foreground text-sm" >
225- { formatDuration ( avgSessionDuration ) }
226- </ span >
227- </ div >
218+ { /* Avg Duration */ }
219+ < div className = "hidden min-w-[60px] flex-col items-center gap-1 lg:flex" >
220+ < div className = "flex items-center gap-1 text-muted-foreground text-xs" >
221+ < ClockIcon className = "h-3 w-3" />
222+ < span > Avg Time</ span >
223+ </ div >
224+ < span className = "font-semibold text-foreground text-sm" >
225+ { formatDuration ( avgSessionDuration ) }
226+ </ span >
227+ </ div >
228228
229- { /* Visitor Type Badge */ }
230- < div className = "flex min-w-[70px] flex-col items-center gap-1" >
231- < div className = "text-muted-foreground text-xs" > Type</ div >
232- < Badge
233- className = "px-2 py-1 font-semibold text-xs"
234- variant = { profile . total_sessions > 1 ? ' default' : ' secondary' }
235- >
236- { profile . total_sessions > 1 ? ' Return' : ' New' }
237- </ Badge >
238- </ div >
239- </ div >
240- </ div >
241- </ CollapsibleTrigger >
229+ { /* Visitor Type Badge */ }
230+ < div className = "flex min-w-[70px] flex-col items-center gap-1" >
231+ < div className = "text-muted-foreground text-xs" > Type</ div >
232+ < Badge
233+ className = "px-2 py-1 font-semibold text-xs"
234+ variant = { profile . total_sessions > 1 ? " default" : " secondary" }
235+ >
236+ { profile . total_sessions > 1 ? " Return" : " New" }
237+ </ Badge >
238+ </ div >
239+ </ div >
240+ </ div >
241+ </ CollapsibleTrigger >
242242
243243 < CollapsibleContent >
244244 < div className = "border-t bg-muted/20 px-4 pb-4" >
0 commit comments