33import { ArrowLeftIcon , SpinnerIcon , UserIcon } from '@phosphor-icons/react' ;
44import dayjs from 'dayjs' ;
55import { useParams , useRouter } from 'next/navigation' ;
6+ import { FaviconImage } from '@/components/analytics/favicon-image' ;
67import { BrowserIcon , CountryFlag , OSIcon } from '@/components/icon' ;
78import { Badge } from '@/components/ui/badge' ;
89import { Button } from '@/components/ui/button' ;
@@ -21,6 +22,45 @@ export default function UserDetailPage() {
2122 dateRange
2223 ) ;
2324
25+ function getReferrerInfo ( referrer ?: string ) {
26+ if ( ! referrer || referrer === 'direct' ) {
27+ return { name : 'Direct' , domain : null } as {
28+ name : string ;
29+ domain : string | null ;
30+ } ;
31+ }
32+ try {
33+ const url = new URL (
34+ referrer . startsWith ( 'http' ) ? referrer : `https://${ referrer } `
35+ ) ;
36+ const domain = url . hostname . replace ( 'www.' , '' ) ;
37+ return { name : domain , domain } as {
38+ name : string ;
39+ domain : string | null ;
40+ } ;
41+ } catch {
42+ return { name : referrer , domain : null } as {
43+ name : string ;
44+ domain : string | null ;
45+ } ;
46+ }
47+ }
48+
49+ const totalEvents =
50+ userProfile ?. sessions ?. reduce (
51+ ( acc : number , s : any ) =>
52+ acc + ( Array . isArray ( s . events ) ? s . events . length : 0 ) ,
53+ 0
54+ ) || 0 ;
55+ const totalPages =
56+ userProfile ?. sessions ?. reduce (
57+ ( acc : number , s : any ) => acc + ( Number ( s . page_views ) || 0 ) ,
58+ 0
59+ ) || 0 ;
60+ const avgPagesPerSession = userProfile ?. total_sessions
61+ ? totalPages / userProfile . total_sessions
62+ : 0 ;
63+
2464 const handleBack = ( ) => {
2565 router . push ( `/websites/${ websiteId } /users` ) ;
2666 } ;
@@ -187,7 +227,7 @@ export default function UserDetailPage() {
187227 < div className = "flex min-h-0 flex-1 flex-col overflow-hidden" >
188228 < div className = "grid grid-cols-1 gap-0 lg:grid-cols-3" >
189229 { /* User Overview */ }
190- < div className = "border-b bg-background lg:border-r lg:border-b-0" >
230+ < div className = "border-b bg-background lg:sticky lg:top-[89px] lg:h-[calc(100vh-89px)] lg:overflow-auto lg: border-r lg:border-b-0" >
191231 < div className = "px-4 py-6" >
192232 < h2 className = "mb-4 font-semibold text-foreground text-lg" >
193233 User Overview
@@ -244,6 +284,20 @@ export default function UserDetailPage() {
244284 Pageviews
245285 </ div >
246286 </ div >
287+ < div className = "rounded-lg border bg-muted/20 p-4 text-center" >
288+ < div className = "font-bold text-2xl text-foreground" >
289+ { totalEvents }
290+ </ div >
291+ < div className = "text-muted-foreground text-sm" > Events</ div >
292+ </ div >
293+ < div className = "rounded-lg border bg-muted/20 p-4 text-center" >
294+ < div className = "font-bold text-2xl text-foreground" >
295+ { avgPagesPerSession . toFixed ( 1 ) }
296+ </ div >
297+ < div className = "text-muted-foreground text-sm" >
298+ Pages / Session
299+ </ div >
300+ </ div >
247301 </ div >
248302
249303 { /* Visit Info */ }
@@ -347,6 +401,28 @@ export default function UserDetailPage() {
347401 </ div >
348402 </ div >
349403
404+ { /* Referrer chip */ }
405+ { ( session . referrer || session . referrer_parsed ) && (
406+ < div className = "mb-3 inline-flex items-center gap-2 rounded border bg-background px-2 py-1 text-xs" >
407+ { ( ( ) => {
408+ const info = getReferrerInfo ( session . referrer ) ;
409+ return info . domain ? (
410+ < FaviconImage
411+ className = "flex-shrink-0"
412+ domain = { info . domain }
413+ size = { 14 }
414+ />
415+ ) : null ;
416+ } ) ( ) }
417+ < span className = "text-muted-foreground" >
418+ Referrer:
419+ </ span >
420+ < span className = "font-medium" >
421+ { getReferrerInfo ( session . referrer ) . name }
422+ </ span >
423+ </ div >
424+ ) }
425+
350426 { /* Event Timeline */ }
351427 { session . events && session . events . length > 0 && (
352428 < div >
0 commit comments