fix: redesign session review as card layout instead of cramped table

This commit is contained in:
Usman Baig
2026-03-22 13:25:02 +01:00
parent 42b7363cf9
commit 0878bde259

View File

@@ -1369,42 +1369,12 @@ export default function SiteSettingsPage() {
</div> </div>
)} )}
{/* Sessions table */} {/* Session cards */}
<div className="overflow-x-auto"> <div className="space-y-2">
<table className="w-full text-left text-sm">
<thead className="border-b border-neutral-800">
<tr>
<th className="pb-2 pr-2 w-8">
<input
type="checkbox"
onChange={(e) => {
if (e.target.checked) {
const allIds = new Set((sessions?.sessions || []).map(s => s.session_id))
setSelectedSessions(allIds)
} else {
setSelectedSessions(new Set())
}
}}
checked={selectedSessions.size > 0 && selectedSessions.size === (sessions?.sessions || []).length}
className="rounded border-neutral-600 bg-neutral-800 text-brand-orange focus:ring-brand-orange"
/>
</th>
<th className="pb-2 text-neutral-400 font-medium">Session</th>
<th className="pb-2 text-neutral-400 font-medium">Pages</th>
<th className="pb-2 text-neutral-400 font-medium">Duration</th>
<th className="pb-2 text-neutral-400 font-medium">Location</th>
<th className="pb-2 text-neutral-400 font-medium">Browser</th>
<th className="pb-2 text-neutral-400 font-medium">Referrer</th>
<th className="pb-2 text-neutral-400 font-medium">Score</th>
<th className="pb-2 text-neutral-400 font-medium">Action</th>
</tr>
</thead>
<tbody className="divide-y divide-neutral-800">
{(sessions?.sessions || []) {(sessions?.sessions || [])
.filter(s => botView === 'blocked' ? s.bot_filtered : !s.bot_filtered) .filter(s => botView === 'blocked' ? s.bot_filtered : !s.bot_filtered)
.map((session) => ( .map((session) => (
<tr key={session.session_id} className="hover:bg-neutral-800/50"> <div key={session.session_id} className="flex items-center gap-3 p-3 rounded-xl border border-neutral-800 hover:border-neutral-700 transition-colors">
<td className="py-2.5 pr-2">
<input <input
type="checkbox" type="checkbox"
checked={selectedSessions.has(session.session_id)} checked={selectedSessions.has(session.session_id)}
@@ -1414,59 +1384,51 @@ export default function SiteSettingsPage() {
else next.delete(session.session_id) else next.delete(session.session_id)
setSelectedSessions(next) setSelectedSessions(next)
}} }}
className="rounded border-neutral-600 bg-neutral-800 text-brand-orange focus:ring-brand-orange" className="rounded border-neutral-600 bg-neutral-800 text-brand-orange focus:ring-brand-orange shrink-0"
/> />
</td>
<td className="py-2.5"> <div className="flex-1 min-w-0">
<div className="font-mono text-xs text-neutral-300">{session.session_id.slice(0, 12)}...</div> <div className="flex items-center gap-2 mb-1">
<div className="text-xs text-neutral-500 mt-0.5">{session.first_page}</div> <span className="text-sm font-medium text-white truncate">{session.first_page}</span>
</td> <span className={`shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
<td className="py-2.5 text-neutral-300">{session.pageviews}</td>
<td className="py-2.5 text-neutral-300">
{session.duration != null ? `${Math.round(session.duration)}s` : <span className="text-neutral-600">&mdash;</span>}
</td>
<td className="py-2.5 text-neutral-300 text-xs">
{[session.city, session.country].filter(Boolean).join(', ') || '\u2014'}
</td>
<td className="py-2.5 text-neutral-300 text-xs">{session.browser || '\u2014'}</td>
<td className="py-2.5 text-neutral-300 text-xs">{session.referrer || <span className="text-neutral-600">Direct</span>}</td>
<td className="py-2.5">
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
session.suspicion_score >= 5 ? 'bg-red-900/30 text-red-400' : session.suspicion_score >= 5 ? 'bg-red-900/30 text-red-400' :
session.suspicion_score >= 3 ? 'bg-yellow-900/30 text-yellow-400' : session.suspicion_score >= 3 ? 'bg-yellow-900/30 text-yellow-400' :
'bg-neutral-800 text-neutral-400' 'bg-neutral-800 text-neutral-400'
}`}> }`}>
{session.suspicion_score} {session.suspicion_score >= 5 ? 'High risk' : session.suspicion_score >= 3 ? 'Suspicious' : 'Low risk'}
</span> </span>
</td> </div>
<td className="py-2.5"> <div className="flex items-center gap-3 text-xs text-neutral-500">
<span>{session.pageviews} page{session.pageviews !== 1 ? 's' : ''}</span>
<span>{session.duration != null ? `${Math.round(session.duration)}s` : 'No duration'}</span>
<span>{[session.city, session.country].filter(Boolean).join(', ') || 'Unknown location'}</span>
<span>{session.browser || 'Unknown browser'}</span>
<span>{session.referrer || 'Direct'}</span>
</div>
</div>
{botView === 'review' ? ( {botView === 'review' ? (
<button <button
onClick={() => handleBotFilter([session.session_id])} onClick={() => handleBotFilter([session.session_id])}
className="text-xs font-medium text-red-400 hover:text-red-300" className="shrink-0 px-3 py-1.5 text-xs font-medium text-red-400 hover:text-white hover:bg-red-500/20 rounded-lg border border-red-500/20 transition-colors"
> >
Flag Flag as bot
</button> </button>
) : ( ) : (
<button <button
onClick={() => handleBotUnfilter([session.session_id])} onClick={() => handleBotUnfilter([session.session_id])}
className="text-xs font-medium text-green-400 hover:text-green-300" className="shrink-0 px-3 py-1.5 text-xs font-medium text-green-400 hover:text-white hover:bg-green-500/20 rounded-lg border border-green-500/20 transition-colors"
> >
Unblock Unblock
</button> </button>
)} )}
</td> </div>
</tr>
))} ))}
{(sessions?.sessions || []).filter(s => botView === 'blocked' ? s.bot_filtered : !s.bot_filtered).length === 0 && ( {(sessions?.sessions || []).filter(s => botView === 'blocked' ? s.bot_filtered : !s.bot_filtered).length === 0 && (
<tr> <div className="py-12 text-center text-neutral-500 text-sm">
<td colSpan={9} className="py-8 text-center text-neutral-500 text-sm">
{botView === 'blocked' ? 'No blocked sessions' : 'No suspicious sessions found'} {botView === 'blocked' ? 'No blocked sessions' : 'No suspicious sessions found'}
</td> </div>
</tr>
)} )}
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>