diff --git a/src/App.css b/src/App.css index 74e36c8..d083707 100644 --- a/src/App.css +++ b/src/App.css @@ -7886,6 +7886,130 @@ color: var(--brand-gold); } +/* ── Visitor cards ── */ +.admin-visitor-cards { + display: flex; + flex-direction: column; + gap: 0.4rem; + margin-top: 0.5rem; +} + +.admin-visitor-card { + border: 1px solid rgba(201, 168, 76, 0.12); + border-radius: 0.5rem; + background: #0d0d0a; + overflow: hidden; + transition: border-color 0.15s; +} + +.admin-visitor-card--expanded { + border-color: rgba(201, 168, 76, 0.35); +} + +.admin-visitor-card__main { + display: flex; + align-items: center; + gap: 1rem; + width: 100%; + padding: 0.65rem 1rem; + background: none; + border: none; + cursor: pointer; + text-align: left; + color: inherit; +} + +.admin-visitor-card__main:hover { + background: rgba(201, 168, 76, 0.04); +} + +.admin-visitor-card__left { + display: flex; + flex-direction: column; + gap: 0.15rem; + flex: 1; + min-width: 0; +} + +.admin-visitor-card__name { + font-size: 0.88rem; + font-weight: 600; + color: var(--brand-warm-white); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.admin-visitor-card__path { + font-size: 0.78rem; + color: var(--brand-gold); + opacity: 0.8; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: monospace; +} + +.admin-visitor-card__right { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.2rem; + flex-shrink: 0; +} + +.admin-visitor-card__meta { + font-size: 0.76rem; + color: #8a7f5a; + white-space: nowrap; +} + +.admin-visitor-card__tags { + display: flex; + align-items: center; + gap: 0.4rem; + flex-wrap: wrap; + justify-content: flex-end; +} + +.admin-visitor-card__time { + font-size: 0.72rem; + color: #666; +} + +.admin-visitor-card__chevron { + font-size: 0.65rem; + color: #555; + flex-shrink: 0; + margin-left: 0.25rem; +} + +.admin-visitor-tag--user { + background: #1a1a3a; + color: #8888dd; + border: 1px solid #2a2a5a; +} + +.admin-visitor-card__detail { + padding: 0.75rem 1rem 1rem; + border-top: 1px solid rgba(201, 168, 76, 0.1); + background: #080807; +} + +.admin-visitor-card__detail-meta { + display: flex; + flex-wrap: wrap; + gap: 0.5rem 1.5rem; + margin-bottom: 0.75rem; + font-size: 0.78rem; + color: #8a7f5a; +} + +.admin-visitor-card__detail-meta strong { + color: #b0a48c; + font-weight: 600; +} + /* ── Podcast hub tabs ── */ .admin-tabs--podcast { margin-bottom: 2rem; diff --git a/src/components/AnalyticsPanel.tsx b/src/components/AnalyticsPanel.tsx index 4bb027e..782de5c 100644 --- a/src/components/AnalyticsPanel.tsx +++ b/src/components/AnalyticsPanel.tsx @@ -450,76 +450,68 @@ export function AnalyticsPanel({ {stats.visitors.recentVisits.length === 0 ? (

No visitor records yet.

) : ( -
- - - - - - - - - - - - - - - - {stats.visitors.recentVisits.map(row => { - const rowKey = `${row.visitorId}-${row.at}` - const isExpanded = expandedVisitor === rowKey - return ( - <> - setExpandedVisitor(isExpanded ? null : rowKey)} - title="Click to view page history" - > - - - - - - - - - - - {isExpanded && (row.pageHistory ?? []).length > 0 && ( - - - +
+ {stats.visitors.recentVisits.map(row => { + const rowKey = `${row.visitorId}-${row.at}` + const isExpanded = expandedVisitor === rowKey + const history = row.pageHistory ?? [] + const location = [row.city !== 'Unknown' && row.city, row.state !== 'Unknown' && row.state, row.country !== 'Unknown' && row.country].filter(Boolean).join(', ') || 'Unknown location' + return ( +
+ + + {isExpanded && ( +
+
+ IP {maskIp(row.ip)} + {row.studyUser && Username {row.studyUser.username}} + {row.referrer && Referrer {row.referrer}} + Location {[row.city, row.county, row.state, row.country].filter(v => v && v !== 'Unknown').join(', ') || '—'} +
+ {history.length > 0 ? ( + <> +

Page history ({history.length} pages)

+
    + {[...history].reverse().map((entry, i) => ( +
  1. + {formatDate(entry.at)} + {entry.path} + {entry.referrer && from {entry.referrer}} +
  2. + ))} +
+ + ) : ( +

No page history yet — recorded after consent is given.

)} - {isExpanded && (row.pageHistory ?? []).length === 0 && ( -
- - - )} - - ) - })} - -
TimeIPUserCountryPathReferrerDeviceReturningVisit #
{formatDate(row.at)}{maskIp(row.ip)}{row.studyUser ? (row.studyUser.displayName || row.studyUser.username) : '—'}{row.country || '—'}{row.path}{row.referrer || '—'}{row.device || '—'} - - {row.returningVisitor ? 'Returning' : 'New'} - - {row.visitCount} {isExpanded ? '▲' : '▼'}
-
-

Full page history for this visitor ({(row.pageHistory ?? []).length} pages):

-
    - {[...(row.pageHistory ?? [])].reverse().map((entry, i) => ( -
  1. - {formatDate(entry.at)} - {entry.path} - {entry.referrer && from {entry.referrer}} -
  2. - ))} -
-
-

No page history yet — history is built from SPA navigations after consent.

+
+ )} + + ) + })} )}