Skip to content

Commit 8347c55

Browse files
committed
add report post
1 parent 758b24f commit 8347c55

File tree

4 files changed

+163
-13
lines changed

4 files changed

+163
-13
lines changed

frontend/app/components/BlogPostComments.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export default function BlogPostComments({ postId, comments = [], reportReasons
144144
if (!selectedReason || !reportingCommentId) return;
145145

146146
const formData = new FormData();
147-
formData.append('_action', 'report');
147+
formData.append('_action', 'reportComment');
148148
formData.append('commentId', reportingCommentId.toString());
149149
formData.append('reason', selectedReason);
150150
formData.append('description', reportDescription.trim());

frontend/app/components/BlogPostDetail.tsx

Lines changed: 127 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import * as React from "react";
2-
import { useNavigate, Link } from "react-router";
2+
import { useNavigate, Link, useFetcher, useActionData } from "react-router";
33
import {
44
Favorite as FavoriteIcon,
55
Edit as EditIcon,
66
Share as ShareIcon,
7-
Delete as DeleteIcon
7+
Delete as DeleteIcon,
8+
Flag as FlagIcon
89
} from '@mui/icons-material';
910
import ReactMarkdown from "react-markdown";
1011
import {
@@ -14,7 +15,17 @@ import {
1415
Chip,
1516
IconButton,
1617
Container,
17-
Paper
18+
Paper,
19+
Dialog,
20+
DialogTitle,
21+
DialogContent,
22+
Select,
23+
MenuItem,
24+
FormControl,
25+
InputLabel,
26+
TextField,
27+
Button,
28+
Tooltip
1829
} from '@mui/material';
1930

2031
import UserContext, { type UserContextType } from "../contexts/UserContext";
@@ -40,17 +51,29 @@ export interface BlogPost {
4051
description?: string;
4152
}
4253

54+
interface ReportReason {
55+
id: number;
56+
name: string;
57+
}
58+
4359
interface BlogPostDetailProps {
4460
post: BlogPost;
4561
comments?: any[];
46-
reportReasons?: any[];
62+
reportReasons?: ReportReason[];
4763
}
4864

4965
export default function BlogPostDetail({ post, comments = [], reportReasons = [] }: BlogPostDetailProps) {
5066
const navigate = useNavigate();
67+
const fetcher = useFetcher();
68+
const actionData = useActionData() as { success?: boolean; error?: string; report?: any } | undefined;
5169
const { user, isAuthenticated } = React.useContext<UserContextType>(UserContext);
5270
const [likesCount, setLikesCount] = React.useState(post.likes_count);
5371
const [isLiked, setIsLiked] = React.useState(post.is_liked);
72+
73+
// Report dialog state
74+
const [reportDialogOpen, setReportDialogOpen] = React.useState(false);
75+
const [selectedReason, setSelectedReason] = React.useState('');
76+
const [reportDescription, setReportDescription] = React.useState('');
5477

5578
const handleShare = async () => {
5679
const url = window.location.href;
@@ -110,6 +133,39 @@ export default function BlogPostDetail({ post, comments = [], reportReasons = []
110133
}
111134
};
112135

136+
const handleOpenReportDialog = () => {
137+
setReportDialogOpen(true);
138+
};
139+
140+
const handleCloseReportDialog = () => {
141+
setReportDialogOpen(false);
142+
setSelectedReason('');
143+
setReportDescription('');
144+
};
145+
146+
const handleSubmitReport = () => {
147+
if (!selectedReason) return;
148+
149+
const formData = new FormData();
150+
formData.append('_action', 'reportPost');
151+
formData.append('reason', selectedReason);
152+
formData.append('description', reportDescription.trim());
153+
154+
fetcher.submit(formData, { method: 'post' });
155+
handleCloseReportDialog();
156+
};
157+
158+
// Handle action responses
159+
React.useEffect(() => {
160+
if (actionData?.error) {
161+
console.error('🔍 DEBUG: Action error:', actionData.error);
162+
// You can add toast notification here
163+
} else if (actionData?.success && actionData?.report) {
164+
console.log('🔍 DEBUG: Post reported successfully:', actionData);
165+
// You can add success toast notification here
166+
}
167+
}, [actionData]);
168+
113169
return (
114170
<Container maxWidth="md">
115171
<Paper elevation={0} sx={{ p: 3, my: 3 }}>
@@ -237,18 +293,37 @@ export default function BlogPostDetail({ post, comments = [], reportReasons = []
237293
>
238294
<ShareIcon />
239295
</IconButton>
296+
297+
{/* Report button - always show when authenticated */}
298+
{isAuthenticated && (
299+
<Tooltip title={user?.id === post.author?.id ? "You can't report your own post" : "Report post"}>
300+
<span>
301+
<IconButton
302+
onClick={user?.id === post.author?.id ? undefined : handleOpenReportDialog}
303+
disabled={user?.id === post.author?.id}
304+
aria-label="Report post"
305+
sx={user?.id === post.author?.id ? {
306+
color: 'action.disabled',
307+
cursor: 'not-allowed'
308+
} : { color: 'warning.main' }}
309+
>
310+
<FlagIcon />
311+
</IconButton>
312+
</span>
313+
</Tooltip>
314+
)}
240315
</Box>
241316

242317
{isAuthenticated && user?.id === post.author?.id && (
243318
<Box sx={{ display: 'flex', gap: 1 }}>
244-
<IconButton
319+
<IconButton
245320
onClick={handleEdit}
246321
color="primary"
247322
aria-label="Edit post"
248323
>
249324
<EditIcon />
250325
</IconButton>
251-
<IconButton
326+
<IconButton
252327
onClick={handleDelete}
253328
color="error"
254329
aria-label="Delete post"
@@ -260,11 +335,53 @@ export default function BlogPostDetail({ post, comments = [], reportReasons = []
260335
</Box>
261336
</Paper>
262337

263-
<BlogPostComments
264-
postId={post.id}
265-
comments={comments}
266-
reportReasons={reportReasons}
338+
<BlogPostComments
339+
postId={post.id}
340+
comments={comments}
341+
reportReasons={reportReasons}
267342
/>
343+
344+
{/* Report Dialog */}
345+
<Dialog open={reportDialogOpen} onClose={handleCloseReportDialog}>
346+
<DialogTitle>Report Post</DialogTitle>
347+
<DialogContent>
348+
<FormControl fullWidth sx={{ mt: 2 }}>
349+
<InputLabel>Reason</InputLabel>
350+
<Select
351+
value={selectedReason}
352+
onChange={(e) => setSelectedReason(e.target.value)}
353+
label="Reason"
354+
>
355+
{reportReasons.map((reason) => (
356+
<MenuItem key={reason.id} value={reason.id}>
357+
{reason.name}
358+
</MenuItem>
359+
))}
360+
</Select>
361+
</FormControl>
362+
<TextField
363+
fullWidth
364+
multiline
365+
rows={3}
366+
label="Additional Details (Optional)"
367+
value={reportDescription}
368+
onChange={(e) => setReportDescription(e.target.value)}
369+
sx={{ mt: 2 }}
370+
/>
371+
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'flex-end' }}>
372+
<Button onClick={handleCloseReportDialog} sx={{ mr: 1 }}>
373+
Cancel
374+
</Button>
375+
<Button
376+
variant="contained"
377+
onClick={handleSubmitReport}
378+
disabled={!selectedReason}
379+
>
380+
Submit Report
381+
</Button>
382+
</Box>
383+
</DialogContent>
384+
</Dialog>
268385
</Container>
269386
);
270387
}

frontend/app/routes/_app.blog.posts.$postId.$slug.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
createComment,
1111
updateComment,
1212
deleteComment,
13-
reportComment
13+
reportComment,
14+
reportPost
1415
} from "../utils/server-fetch";
1516
import type { LoaderData } from "../routes/_app";
1617

@@ -128,7 +129,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
128129
return { success: true, deleted: commentId };
129130
}
130131

131-
case 'report': {
132+
case 'reportComment': {
132133
const commentId = formData.get('commentId') as string;
133134
const reason = formData.get('reason') as string;
134135
const description = formData.get('description') as string;
@@ -145,6 +146,22 @@ export async function action({ request, params }: ActionFunctionArgs) {
145146
return { success: true, report };
146147
}
147148

149+
case 'reportPost': {
150+
const reason = formData.get('reason') as string;
151+
const description = formData.get('description') as string;
152+
153+
if (!reason) {
154+
return { error: 'Reason is required' };
155+
}
156+
157+
console.log('🔍 DEBUG: Reporting post:', postId, 'Reason:', reason);
158+
159+
const report = await reportPost(postId, reason, description || '', request);
160+
161+
console.log('🔍 DEBUG: Post reported successfully:', report);
162+
return { success: true, report };
163+
}
164+
148165
default:
149166
console.log('🔍 DEBUG: Unknown action type:', actionType);
150167
return { error: 'Unknown action type' };

frontend/app/utils/server-fetch.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,22 @@ export async function reportComment(commentId: string, reason: string, descripti
110110
});
111111
}
112112

113+
export async function reportPost(postId: string, reason: string, description: string, request: Request) {
114+
return fetchFromApi('/moderation/reports/', {
115+
method: 'POST',
116+
headers: {
117+
'Content-Type': 'application/json',
118+
Cookie: request.headers.get('Cookie') || '',
119+
'X-CSRFToken': extractCSRFToken(request),
120+
},
121+
body: JSON.stringify({
122+
post_id: postId,
123+
reason,
124+
description: description || '',
125+
}),
126+
});
127+
}
128+
113129
// Helper function to extract CSRF token from request
114130
function extractCSRFToken(request: Request): string {
115131
// Try header first

0 commit comments

Comments
 (0)