Add comprehensive visitor engagement tracking and scroll-to-top on navigation
Tracks scroll depth (25/50/75/90%), time on page, UTM parameters, outbound link clicks, search queries, audio pause/completion/listen time, and 404s. Logged-in study users are now tied to their visitor record and surfaced in the admin recent visits table. Scroll position resets on every route change. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { sendEvent } from '../analytics'
|
||||
|
||||
interface EpisodeAudioPlayerProps {
|
||||
src: string
|
||||
@@ -36,13 +37,30 @@ export function EpisodeAudioPlayer({ src, title, size = 'full', spotifyUrl }: Ep
|
||||
const [currentTime, setCurrentTime] = useState(0)
|
||||
const [duration, setDuration] = useState(0)
|
||||
const playTrackedRef = useRef(false)
|
||||
const listenStartRef = useRef<number | null>(null)
|
||||
const totalListenSecondsRef = useRef(0)
|
||||
|
||||
function flushListenTime() {
|
||||
if (listenStartRef.current !== null && title) {
|
||||
const seconds = Math.round((Date.now() - listenStartRef.current) / 1000)
|
||||
if (seconds > 1) {
|
||||
totalListenSecondsRef.current += seconds
|
||||
sendEvent('audio_listen_time', { title, seconds })
|
||||
}
|
||||
listenStartRef.current = null
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const audio = audioRef.current
|
||||
if (!audio) return
|
||||
const onTimeUpdate = () => setCurrentTime(audio.currentTime)
|
||||
const onDurationChange = () => setDuration(audio.duration)
|
||||
const onEnded = () => setPlaying(false)
|
||||
const onEnded = () => {
|
||||
setPlaying(false)
|
||||
flushListenTime()
|
||||
if (title) sendEvent('audio_completion', { title })
|
||||
}
|
||||
audio.addEventListener('timeupdate', onTimeUpdate)
|
||||
audio.addEventListener('durationchange', onDurationChange)
|
||||
audio.addEventListener('loadedmetadata', onDurationChange)
|
||||
@@ -52,7 +70,9 @@ export function EpisodeAudioPlayer({ src, title, size = 'full', spotifyUrl }: Ep
|
||||
audio.removeEventListener('durationchange', onDurationChange)
|
||||
audio.removeEventListener('loadedmetadata', onDurationChange)
|
||||
audio.removeEventListener('ended', onEnded)
|
||||
flushListenTime()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [src])
|
||||
|
||||
function togglePlay() {
|
||||
@@ -61,9 +81,12 @@ export function EpisodeAudioPlayer({ src, title, size = 'full', spotifyUrl }: Ep
|
||||
if (playing) {
|
||||
audio.pause()
|
||||
setPlaying(false)
|
||||
flushListenTime()
|
||||
if (title) sendEvent('audio_pause', { title })
|
||||
} else {
|
||||
audio.play().then(() => {
|
||||
setPlaying(true)
|
||||
listenStartRef.current = Date.now()
|
||||
if (!playTrackedRef.current && title) {
|
||||
playTrackedRef.current = true
|
||||
fetch('/api/analytics/play', {
|
||||
|
||||
Reference in New Issue
Block a user