From b3ccb584314d28829b69213a55de4d6e01437550 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Wed, 25 Mar 2026 17:48:39 +0100 Subject: [PATCH] feat(settings): add session review to unified bot & spam tab --- .../settings/unified/tabs/SiteBotSpamTab.tsx | 150 ++++++++++++++++-- 1 file changed, 141 insertions(+), 9 deletions(-) diff --git a/components/settings/unified/tabs/SiteBotSpamTab.tsx b/components/settings/unified/tabs/SiteBotSpamTab.tsx index 98a452d..7055e73 100644 --- a/components/settings/unified/tabs/SiteBotSpamTab.tsx +++ b/components/settings/unified/tabs/SiteBotSpamTab.tsx @@ -1,17 +1,26 @@ 'use client' import { useState, useEffect } from 'react' -import { Button, Toggle, toast, Spinner } from '@ciphera-net/ui' +import { Button, Toggle, toast, Spinner, getDateRange } from '@ciphera-net/ui' import { ShieldCheck } from '@phosphor-icons/react' -import { useSite, useBotFilterStats } from '@/lib/swr/dashboard' +import { useSite, useBotFilterStats, useSessions } from '@/lib/swr/dashboard' import { updateSite } from '@/lib/api/sites' +import { botFilterSessions, botUnfilterSessions } from '@/lib/api/bot-filter' export default function SiteBotSpamTab({ siteId }: { siteId: string }) { const { data: site, mutate } = useSite(siteId) - const { data: botStats } = useBotFilterStats(siteId) + const { data: botStats, mutate: mutateBotStats } = useBotFilterStats(siteId) const [filterBots, setFilterBots] = useState(false) const [saving, setSaving] = useState(false) + const [botView, setBotView] = useState<'review' | 'blocked'>('review') + const [suspiciousOnly, setSuspiciousOnly] = useState(true) + const [selectedSessions, setSelectedSessions] = useState>(new Set()) + const [botDateRange] = useState(() => getDateRange(7)) + + const { data: sessionsData, mutate: mutateSessions } = useSessions(siteId, botDateRange.start, botDateRange.end, botView === 'review' ? suspiciousOnly : false) + const sessions = sessionsData?.sessions + useEffect(() => { if (site) setFilterBots(site.filter_bots ?? false) }, [site]) @@ -29,6 +38,30 @@ export default function SiteBotSpamTab({ siteId }: { siteId: string }) { } } + const handleBotFilter = async (sessionIds: string[]) => { + try { + await botFilterSessions(siteId, sessionIds) + toast.success(`${sessionIds.length} session(s) flagged as bot`) + setSelectedSessions(new Set()) + mutateSessions() + mutateBotStats() + } catch { + toast.error('Failed to flag sessions') + } + } + + const handleBotUnfilter = async (sessionIds: string[]) => { + try { + await botUnfilterSessions(siteId, sessionIds) + toast.success(`${sessionIds.length} session(s) unblocked`) + setSelectedSessions(new Set()) + mutateSessions() + mutateBotStats() + } catch { + toast.error('Failed to unblock sessions') + } + } + if (!site) return
return ( @@ -68,12 +101,111 @@ export default function SiteBotSpamTab({ siteId }: { siteId: string }) { )} -

- For detailed session review and manual blocking, use the full{' '} - - site settings page - . -

+ {/* Session Review */} +
+
+

Session Review

+ {/* Review/Blocked toggle */} +
+ + +
+
+ + {/* Suspicious only filter (review mode only) */} + {botView === 'review' && ( + + )} + + {/* Bulk actions bar */} + {selectedSessions.size > 0 && ( +
+ {selectedSessions.size} selected + {botView === 'review' ? ( + + ) : ( + + )} + +
+ )} + + {/* Session cards */} +
+ {(sessions || []) + .filter(s => botView === 'blocked' ? s.bot_filtered : !s.bot_filtered) + .map(session => ( +
+ { + const next = new Set(selectedSessions) + e.target.checked ? next.add(session.session_id) : next.delete(session.session_id) + setSelectedSessions(next) + }} + className="w-4 h-4 shrink-0 cursor-pointer" + style={{ accentColor: '#FD5E0F' }} + /> +
+
+ {session.first_page || '/'} + {session.suspicion_score != null && ( + = 5 ? 'bg-red-900/30 text-red-400' : + session.suspicion_score >= 3 ? 'bg-yellow-900/30 text-yellow-400' : + 'bg-neutral-800 text-neutral-400' + }`}> + {session.suspicion_score >= 5 ? 'High risk' : session.suspicion_score >= 3 ? 'Suspicious' : 'Low risk'} + + )} +
+
+ {session.pageviews} page(s) + {session.duration ? `${Math.round(session.duration)}s` : 'No duration'} + {[session.city, session.country].filter(Boolean).join(', ') || 'Unknown location'} + {session.browser || 'Unknown browser'} + {session.referrer || 'Direct'} +
+
+ +
+ ))} + {(!sessions || sessions.filter(s => botView === 'blocked' ? s.bot_filtered : !s.bot_filtered).length === 0) && ( +

+ {botView === 'blocked' ? 'No blocked sessions' : 'No suspicious sessions found'} +

+ )} +
+