11'use client' ;
22
33import { useState , useEffect } from 'react' ;
4+ import { usePathname , useRouter , useSearchParams } from 'next/navigation' ;
45import { useQuery , useMutation , useQueryClient } from '@tanstack/react-query' ;
56import { useTRPC } from '@/lib/trpc/utils' ;
67import { toast } from 'sonner' ;
78import { Card , CardContent , CardDescription , CardHeader , CardTitle } from '@/components/ui/card' ;
89import { Button } from '@/components/ui/button' ;
910import { Textarea } from '@/components/ui/textarea' ;
1011import { Badge } from '@/components/ui/badge' ;
12+ import { Checkbox } from '@/components/ui/checkbox' ;
13+ import { Label } from '@/components/ui/label' ;
1114import { Tabs , TabsContent , TabsList , TabsTrigger } from '@/components/ui/tabs' ;
1215import {
1316 Table ,
@@ -194,23 +197,26 @@ function StatsTab() {
194197
195198function SuspiciousTab ( ) {
196199 const trpc = useTRPC ( ) ;
200+ const [ hideLegitimateProviders , setHideLegitimateProviders ] = useState ( true ) ;
197201
198- const { data, isLoading } = useQuery ( trpc . admin . blacklistDomains . suspicious . queryOptions ( ) ) ;
202+ const { data, isLoading } = useQuery (
203+ trpc . admin . blacklistDomains . suspicious . queryOptions ( { hideLegitimateProviders } )
204+ ) ;
199205
200206 const domains = data ?. domains ?? [ ] ;
201207 const blacklistedCount = domains . filter ( d => d . isBlacklisted ) . length ;
202208
203209 return (
204210 < Card >
205211 < CardHeader >
206- < div className = "flex items-center justify-between" >
212+ < div className = "flex items-start justify-between gap-4 " >
207213 < div >
208214 < CardTitle > Suspicious Domains</ CardTitle >
209215 < CardDescription >
210216 Top 100 registrable domains by blocked account count, then total account count. Only
211- shows domains where at least 30% of accounts have been blocked, to filter out
212- legitimate high-volume providers . Use this to spot domains that are accumulating abuse
213- but aren't yet blacklisted.
217+ shows domains where at least 30% of accounts have been blocked when the provider-noise
218+ filter is on . Use this to spot domains that are accumulating abuse but aren't yet
219+ blacklisted.
214220 </ CardDescription >
215221 </ div >
216222 { data && (
@@ -226,7 +232,17 @@ function SuspiciousTab() {
226232 ) }
227233 </ div >
228234 </ CardHeader >
229- < CardContent >
235+ < CardContent className = "flex flex-col gap-4" >
236+ < div className = "flex items-center gap-2" >
237+ < Checkbox
238+ id = "hide-legitimate-providers"
239+ checked = { hideLegitimateProviders }
240+ onCheckedChange = { checked => setHideLegitimateProviders ( checked === true ) }
241+ />
242+ < Label htmlFor = "hide-legitimate-providers" className = "text-sm font-normal" >
243+ Hide legitimate high-volume providers
244+ </ Label >
245+ </ div >
230246 { isLoading ? (
231247 < div className = "text-muted-foreground py-8 text-center text-sm" > Loading…</ div >
232248 ) : domains . length === 0 ? (
@@ -311,11 +327,36 @@ function formatTimestamp(value: string | null): string {
311327const tabTriggerClass =
312328 'text-muted-foreground hover:text-foreground data-[state=active]:border-foreground data-[state=active]:text-foreground rounded-none border-b-2 border-transparent px-0 py-3 text-sm font-medium transition-colors data-[state=active]:border-0 data-[state=active]:border-b-2 data-[state=active]:bg-transparent data-[state=active]:shadow-none' ;
313329
330+ const blacklistedDomainsTabs = [ 'edit' , 'stats' , 'suspicious' ] as const ;
331+ type BlacklistedDomainsTab = ( typeof blacklistedDomainsTabs ) [ number ] ;
332+
333+ function isBlacklistedDomainsTab ( value : string ) : value is BlacklistedDomainsTab {
334+ return blacklistedDomainsTabs . some ( tab => tab === value ) ;
335+ }
336+
337+ function getTabFromSearchParam ( value : string | null ) : BlacklistedDomainsTab {
338+ if ( value && isBlacklistedDomainsTab ( value ) ) {
339+ return value ;
340+ }
341+
342+ return 'edit' ;
343+ }
344+
314345export function BlacklistedDomains ( ) {
315- const [ activeTab , setActiveTab ] = useState ( 'edit' ) ;
346+ const pathname = usePathname ( ) ;
347+ const router = useRouter ( ) ;
348+ const searchParams = useSearchParams ( ) ;
349+ const activeTab = getTabFromSearchParam ( searchParams . get ( 'tab' ) ) ;
350+
351+ function handleTabChange ( tab : string ) {
352+ const nextTab = getTabFromSearchParam ( tab ) ;
353+ const params = new URLSearchParams ( searchParams ) ;
354+ params . set ( 'tab' , nextTab ) ;
355+ router . replace ( `${ pathname } ?${ params . toString ( ) } ` , { scroll : false } ) ;
356+ }
316357
317358 return (
318- < Tabs value = { activeTab } onValueChange = { setActiveTab } >
359+ < Tabs value = { activeTab } onValueChange = { handleTabChange } >
319360 < TabsList className = "h-auto w-full justify-start gap-6 rounded-none border-b bg-transparent p-0" >
320361 < TabsTrigger value = "edit" className = { tabTriggerClass } >
321362 Edit
0 commit comments