1- import { ChevronDownIcon , Paperclip , Reply } from 'lucide-react' ;
1+ import { useMutation } from '@tanstack/react-query' ;
2+ import { ChevronDownIcon , Paperclip , Reply , ShieldAlert } from 'lucide-react' ;
23import { useEffect , useRef , useState } from 'react' ;
34import { ToggleQuotesButton } from '@/components/board-card/toggle-quotes-button' ;
45import { Attachment } from '@/components/editor/attachment' ;
56import { Avatar , AvatarFallback , AvatarImage } from '@/components/ui/avatar' ;
7+ import { Badge } from '@/components/ui/badge' ;
68import { Button } from '@/components/ui/button' ;
79import { Card } from '@/components/ui/card' ;
810import { Popover , PopoverContent , PopoverTrigger } from '@/components/ui/popover' ;
911import { useEmailIframe } from '@/hooks/use-email-iframe' ;
12+ import { useRouteContext } from '@/hooks/use-route-context' ;
1013import type { BoardMember } from '@/query-helpers/board' ;
1114import type { EmailMessage } from '@/query-helpers/board-card' ;
1215import { sanitizedDisplayHtml } from '@/utils/email' ;
@@ -18,10 +21,12 @@ const EmailMessageBody = ({
1821 emailMessage,
1922 boardId,
2023 boardCardId,
24+ setBlockedTrackers,
2125} : {
2226 emailMessage : EmailMessage ;
2327 boardId : string ;
2428 boardCardId : string ;
29+ setBlockedTrackers : ( blockedTrackers : string [ ] ) => void ;
2530} ) => {
2631 const [ displayMainHtml , setDisplayMainHtml ] = useState ( '' ) ;
2732 const [ blockquotesExpanded , setBlockquotesExpanded ] = useState ( false ) ;
@@ -31,26 +36,38 @@ const EmailMessageBody = ({
3136
3237 const { mainHtml, mainText, quotedHtml, quotedText, styles } = emailMessage ;
3338
39+ const { trpc } = useRouteContext ( ) ;
40+ const reportSuspiciousTrackerMutation = useMutation ( trpc . report . suspiciousTracker . mutationOptions ( ) ) ;
41+
42+ // biome-ignore lint/correctness/useExhaustiveDependencies: ignore mutation to avoid re-running
3443 useEffect ( ( ) => {
3544 if ( mainHtml || quotedHtml ) {
36- const displayMain = sanitizedDisplayHtml ( {
37- bodyHtml : mainHtml || '' ,
45+ const mainResult = sanitizedDisplayHtml ( {
46+ html : mainHtml || '' ,
3847 gmailAttachments : emailMessage . gmailAttachments ,
3948 boardId,
4049 boardCardId,
4150 } ) ;
4251
43- const displayQuoted = sanitizedDisplayHtml ( {
44- bodyHtml : quotedHtml || '' ,
52+ const quotedResult = sanitizedDisplayHtml ( {
53+ html : quotedHtml || '' ,
4554 gmailAttachments : emailMessage . gmailAttachments ,
4655 boardId,
4756 boardCardId,
4857 } ) ;
4958
50- setDisplayMainHtml ( displayMain ) ;
51- setDisplayQuotedHtml ( displayQuoted ) ;
59+ const allBlockedTrackers = [ ...mainResult . blockedTrackers , ...quotedResult . blockedTrackers ] ;
60+ const allSuspiciousTrackers = [ ...mainResult . suspiciousTrackers , ...quotedResult . suspiciousTrackers ] ;
61+
62+ setDisplayMainHtml ( mainResult . displayHtml ) ;
63+ setDisplayQuotedHtml ( quotedResult . displayHtml ) ;
64+ setBlockedTrackers ( allBlockedTrackers ) ;
65+
66+ if ( allSuspiciousTrackers . length > 0 && ! reportSuspiciousTrackerMutation . isPending ) {
67+ reportSuspiciousTrackerMutation . mutate ( { urls : allSuspiciousTrackers } ) ;
68+ }
5269 }
53- } , [ mainHtml , quotedHtml , emailMessage . gmailAttachments , boardId , boardCardId ] ) ;
70+ } , [ mainHtml , quotedHtml , emailMessage . gmailAttachments , boardId , boardCardId , setBlockedTrackers ] ) ;
5471
5572 useEmailIframe ( bodyIframeRef , { html : displayMainHtml , styles : styles ?? '' } ) ;
5673 useEmailIframe ( backquotesIframeRef , { html : displayQuotedHtml , styles : styles ?? '' , enabled : blockquotesExpanded } ) ;
@@ -117,7 +134,7 @@ export const EmailMessageCard = ({
117134 boardMembers : BoardMember [ ] ;
118135 onReply ?: ( ) => void ;
119136} ) => {
120- const [ detailsOpen , setDetailsOpen ] = useState ( false ) ;
137+ const [ blockedTrackers , setBlockedTrackers ] = useState < string [ ] > ( [ ] ) ;
121138
122139 const participants = [
123140 emailMessage . from ,
@@ -180,13 +197,39 @@ export const EmailMessageCard = ({
180197 < div className = "text-xs text-muted-foreground" > { `<${ firstParticipant . email } >` } </ div >
181198 </ div >
182199 ) }
183- < div className = "text-xs text-muted-foreground flex-shrink-0" >
200+ < div className = "text-xs text-muted-foreground flex-shrink-0 flex gap-2" >
201+ { blockedTrackers . length > 0 && (
202+ < Popover >
203+ < PopoverTrigger asChild >
204+ < button type = "button" className = "focus:outline-none" >
205+ < Badge
206+ variant = "outline"
207+ size = "sm"
208+ className = "gap-1 cursor-pointer hover:bg-accent text-muted-foreground"
209+ >
210+ < ShieldAlert className = "size-3" />
211+ { blockedTrackers . length } blocked
212+ </ Badge >
213+ </ button >
214+ </ PopoverTrigger >
215+ < PopoverContent align = "end" className = "w-64" >
216+ < div className = "flex flex-col gap-2" >
217+ < div className = "font-medium text-muted-foreground text-xs" > Blocked trackers</ div >
218+ < ul className = "list-disc list-inside text-xs" >
219+ { blockedTrackers . map ( ( tracker ) => (
220+ < li key = { tracker } > { tracker } </ li >
221+ ) ) }
222+ </ ul >
223+ </ div >
224+ </ PopoverContent >
225+ </ Popover >
226+ ) }
184227 { formattedShortTime ( new Date ( emailMessage . externalCreatedAt ) ) }
185228 </ div >
186229 </ div >
187230 < div className = "flex items-center gap-1 min-w-0" >
188231 < div className = "text-xs text-muted-foreground truncate" > { shortAddresses || 'To' } </ div >
189- < Popover open = { detailsOpen } onOpenChange = { setDetailsOpen } >
232+ < Popover >
190233 < PopoverTrigger asChild >
191234 < Button variant = "ghost" size = "icon" className = "size-4.5 flex-shrink-0 rounded-full" >
192235 < ChevronDownIcon className = "size-4 text-muted-foreground pt-[2px]" />
@@ -244,7 +287,12 @@ export const EmailMessageCard = ({
244287 </ div >
245288 </ div >
246289 </ div >
247- < EmailMessageBody emailMessage = { emailMessage } boardId = { boardId } boardCardId = { boardCardId } />
290+ < EmailMessageBody
291+ emailMessage = { emailMessage }
292+ boardId = { boardId }
293+ boardCardId = { boardCardId }
294+ setBlockedTrackers = { setBlockedTrackers }
295+ />
248296 { emailMessage . gmailAttachments . length > 0 && (
249297 < div className = "flex flex-col gap-2 mt-4 pt-4 border-t" >
250298 < div className = "flex items-center gap-1.5" >
0 commit comments