Fix analytics data-loss bugs and clean up the admin dashboard; v2.2.4
Publish Container / build (push) Successful in 16m3s

- Self-heal consent desync: if the server's consent cookie and the
  client's localStorage flag fall out of sync, the client now notices
  and stops silently sending events into a void.
- Stop double-counting hard page loads in hit/visitor stats.
- Flush pending disk writes on SIGTERM/SIGINT so redeploys don't drop
  the last few events.
- Log rate-limiter drops once per window (diagnoses proxy misconfig).
- Widen the bot-detection allowlist to avoid flagging real browsers
  (Chrome-iOS, Samsung Browser, etc.) as bots.
- Stop merging distinct visitors who share an IP when a real session
  cookie is already present — only fall back to IP correlation when no
  cookie was sent at all.
- Include analyticsEvents/episodePlays in backup snapshots and restore
  them, so a restore no longer wipes engagement analytics.
- Regroup admin dashboard KPI cards by time scope (range vs all-time)
  and remove a duplicated Top Countries/States block.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5 committed 2026-09-24 14:06:02 -04:00
1 parent 8496e227b3
commit d1a68754a1
9 files changed
+183 -45

No files matched your search

+12 -3
View File
@@ -15,7 +15,7 @@ import { EpisodeAudioPlayer } from './components/EpisodeAudioPlayer'
import { NuggetsHostPortal } from './components/NuggetsHostPortal'
import { BIBLE_BOOKS } from '../shared/bible-books.js'
import './App.css'
import { sendEvent, useScrollDepthTracking, useTimeOnPage, useUTMCapture, useLinkClickTracking } from './analytics'
import { sendEvent, useScrollDepthTracking, useTimeOnPage, useUTMCapture, useLinkClickTracking, checkAnalyticsConsentResponse } from './analytics'
const CONSENT_KEY = 'vbn_analytics_consent_choice'
const HEADLINER_WIDGET_ID = 'WI_cmou3b4q7000701p0o9qmmcfj'
@@ -28,7 +28,16 @@ function ScrollToTop() {
function usePageTracking() {
const location = useLocation()
const isFirstRun = useRef(true)
useEffect(() => {
// The initial hard page load is already counted server-side by the
// hit-counting middleware in server.js (it sees the raw HTML request).
// Only client-side route changes (no new HTTP request) need reporting
// here — otherwise every fresh visit/refresh gets double-counted.
if (isFirstRun.current) {
isFirstRun.current = false
return
}
if (localStorage.getItem(CONSENT_KEY) !== 'accepted') return
const referrer = document.referrer || ''
const controller = new AbortController()
@@ -39,7 +48,7 @@ function usePageTracking() {
body: JSON.stringify({ path: location.pathname, referrer }),
keepalive: true,
signal: controller.signal,
}).catch(() => {}).finally(() => clearTimeout(timeout))
}).then(checkAnalyticsConsentResponse).catch(() => {}).finally(() => clearTimeout(timeout))
}, [location.pathname])
}
@@ -1794,7 +1803,7 @@ function LatestEpisodePlayer({ content }: { content: SiteContent }) {
listenStartRef.current = Date.now()
if (!playTrackedRef.current && title) {
playTrackedRef.current = true
fetch('/api/analytics/play', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) }).catch(() => {})
fetch('/api/analytics/play', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) }).then(checkAnalyticsConsentResponse).catch(() => {})
}
}).catch(() => {})
}