@@ -12,6 +12,11 @@ import { ToastContext } from '@/contexts/toast-context';
1212import { ProfileModerationResponse , ModerationEventType , EscalatedItem , EscalatedPostItem } from '@/lib/types/moderation' ;
1313import { BLACKSKY_PDS_URL } from '@/lib/constants/moderation' ;
1414import { getPostUrl } from '@/components/post/utils' ;
15+ import { PostHeader } from '@/components/post/components/post-header' ;
16+ import { PostText } from '@/components/post/components/post-text' ;
17+ import { EmbedRenderer } from '@/components/post/components/embed-renderer' ;
18+ import { AppBskyFeedPost } from '@atproto/api' ;
19+ import { ThreadViewPost } from '@atproto/api/dist/client/types/app/bsky/feed/defs' ;
1520
1621type UserLike = {
1722 did : string ;
@@ -23,11 +28,13 @@ type UserLike = {
2328interface UserModerationModalProps {
2429 user : ProfileViewBasic | EscalatedItem | UserLike | null ;
2530 onClose : ( ) => void ;
31+ onEscalationHandled ?: ( ) => void ;
2632}
2733
2834export const UserModerationModal = ( {
2935 user,
3036 onClose,
37+ onEscalationHandled,
3138} : UserModerationModalProps ) => {
3239 const toastContext = useContext ( ToastContext ) ;
3340
@@ -49,6 +56,10 @@ export const UserModerationModal = ({
4956 const [ moderationData , setModerationData ] = useState < ProfileModerationResponse | null > ( null ) ;
5057 const [ loadingModerationData , setLoadingModerationData ] = useState ( false ) ;
5158
59+ const [ postData , setPostData ] = useState < ThreadViewPost | null > ( null ) ;
60+ const [ loadingPost , setLoadingPost ] = useState ( false ) ;
61+ const [ postError , setPostError ] = useState < string | null > ( null ) ;
62+
5263 const [ banForm , setBanForm ] = useState ( {
5364 showForm : false ,
5465 reason : '' ,
@@ -128,6 +139,31 @@ export const UserModerationModal = ({
128139 console . error ( 'Failed to check mute status:' , error ) ;
129140 setMuteStatus ( { isMuted : false , error : 'Failed to check mute status. Try refreshing.' } ) ;
130141 } ) ;
142+
143+ // Fetch post data for escalated posts
144+ if ( user && 'type' in user && user . type === 'post' && 'postUri' in user ) {
145+ setLoadingPost ( true ) ;
146+ setPostError ( null ) ;
147+ setPostData ( null ) ;
148+ fetch ( `/api/post?uri=${ encodeURIComponent ( ( user as EscalatedPostItem ) . postUri ) } ` )
149+ . then ( ( res ) => {
150+ if ( ! res . ok ) throw new Error ( 'Failed to fetch post' ) ;
151+ return res . json ( ) ;
152+ } )
153+ . then ( ( data ) => {
154+ setPostData ( data ) ;
155+ } )
156+ . catch ( ( error ) => {
157+ console . error ( 'Failed to fetch post:' , error ) ;
158+ setPostError ( error instanceof Error ? error . message : 'Failed to load post' ) ;
159+ } )
160+ . finally ( ( ) => {
161+ setLoadingPost ( false ) ;
162+ } ) ;
163+ } else {
164+ setPostData ( null ) ;
165+ setPostError ( null ) ;
166+ }
131167 }
132168 } , [ user ?. did ] ) ;
133169
@@ -369,8 +405,12 @@ export const UserModerationModal = ({
369405 intent : VisualIntent . Success ,
370406 } ) ;
371407
408+ const actionType = ozoneAction . type ;
372409 resetOzoneAction ( ) ;
373410 await refreshModerationData ( ) ;
411+ if ( actionType === 'acknowledge' || actionType === 'takedown' ) {
412+ onEscalationHandled ?.( ) ;
413+ }
374414 } catch ( error ) {
375415 console . error ( 'Failed to emit moderation event:' , error ) ;
376416 toastContext ?. toast ( {
@@ -433,6 +473,7 @@ export const UserModerationModal = ({
433473
434474 resetOzoneAction ( ) ;
435475 await refreshModerationData ( ) ;
476+ onEscalationHandled ?.( ) ;
436477 } catch ( error ) {
437478 console . error ( 'Failed to acknowledge:' , error ) ;
438479 toastContext ?. toast ( {
@@ -463,6 +504,7 @@ export const UserModerationModal = ({
463504
464505 resetOzoneAction ( ) ;
465506 await refreshModerationData ( ) ;
507+ onEscalationHandled ?.( ) ;
466508 } catch ( error ) {
467509 console . error ( 'Failed to takedown:' , error ) ;
468510 toastContext ?. toast ( {
@@ -524,6 +566,47 @@ export const UserModerationModal = ({
524566 </ div >
525567 </ div >
526568
569+ { /* Inline Post Preview for Escalated Posts */ }
570+ { 'type' in user && user . type === 'post' && (
571+ < div className = 'mt-4 border border-app-border rounded-lg overflow-hidden' >
572+ { loadingPost && (
573+ < div className = 'flex items-center space-x-2 p-4' >
574+ < div className = 'animate-spin h-4 w-4 border-2 border-blue-500 rounded-full border-t-transparent' > </ div >
575+ < span className = 'text-sm text-app-secondary' > Loading post content...</ span >
576+ </ div >
577+ ) }
578+ { postError && (
579+ < div className = 'p-4 bg-yellow-50 border-b border-yellow-200' >
580+ < p className = 'text-sm text-yellow-700' > Could not load post preview: { postError } </ p >
581+ </ div >
582+ ) }
583+ { postData ?. post && (
584+ < div className = 'p-4' >
585+ < PostHeader
586+ author = { postData . post . author }
587+ isAuthorLabeled = { postData . post . author ?. labels ?. some ( ( l : { val : string } ) =>
588+ [ 'porn' , 'sexual' , 'nudity' , 'graphic-media' ] . includes ( l . val )
589+ ) }
590+ postIndexedAt = { postData . post . indexedAt }
591+ />
592+ { AppBskyFeedPost . isRecord ( postData . post . record ) && (
593+ < PostText
594+ text = { postData . post . record . text }
595+ facets = { postData . post . record . facets }
596+ />
597+ ) }
598+ { postData . post . embed && (
599+ < EmbedRenderer
600+ content = { postData . post . embed }
601+ labels = { postData . post . labels }
602+ isSignedIn = { true }
603+ />
604+ ) }
605+ </ div >
606+ ) }
607+ </ div >
608+ ) }
609+
527610 { /* Two Column Layout */ }
528611 < div className = 'flex flex-row gap-6 flex-1 min-h-0 mt-6 overflow-hidden' >
529612 { /* Left Column - Actions */ }
0 commit comments