From 54184821dbbf7406cc4ede3b32ba187d1e20f93b Mon Sep 17 00:00:00 2001 From: myusername Date: Wed, 20 May 2026 12:25:59 +0530 Subject: [PATCH] feat: Report Post and Comment functionality integrated with frontend --- .../InscriptionDetailPage.tsx/CommentCard.tsx | 101 +++++++++++++--- .../InscriptionDetailPage1.tsx | 108 +++++++++++++++--- 2 files changed, 171 insertions(+), 38 deletions(-) diff --git a/src/views/InscriptionDetailPage.tsx/CommentCard.tsx b/src/views/InscriptionDetailPage.tsx/CommentCard.tsx index 698fd26..ca04548 100644 --- a/src/views/InscriptionDetailPage.tsx/CommentCard.tsx +++ b/src/views/InscriptionDetailPage.tsx/CommentCard.tsx @@ -15,6 +15,8 @@ import { TextField, Tooltip, } from "@mui/material"; +import { authStore } from "@/store/authStore"; +import { apiClient } from "@/utils/http/clients/backendApiClientGeneral"; import { coreBackendClient } from "@/utils/http/clients/coreBackend.client"; interface CommentCardProps { @@ -169,15 +171,23 @@ const extractModerationReason = (message: string): string | null => { return "Invalid input: Contains inappropriate language."; }; -const REPORT_REASONS = [ - "Bullying or harassment", - "Hate symbols or hate speech", - "Inappropriate language", - "Spam or misleading", - "Violence or dangerous organizations", - "Selling or promoting restricted items", +type ReportReasonOption = { + label: string; + value: string; +}; + +const REPORT_REASONS: ReportReasonOption[] = [ + { label: "Misinformation", value: "MISINFORMATION" }, + { label: "Bullying or harassment", value: "HARASSMENT" }, + { label: "Hate symbols or hate speech", value: "HATE_SPEECH" }, + { label: "Spam or misleading", value: "SPAM" }, + { label: "Explicit content", value: "EXPLICIT_CONTENT" }, + { label: "Other", value: "OTHER" }, ]; +const getReportReasonLabel = (reasonValue: string): string => + REPORT_REASONS.find((reasonOption) => reasonOption.value === reasonValue)?.label ?? reasonValue; + const CommentCard: React.FC = ({ comments, currentUser, @@ -199,6 +209,8 @@ const CommentCard: React.FC = ({ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isReportModalOpen, setIsReportModalOpen] = useState(false); const [reportReason, setReportReason] = useState(""); + const [reportDetails, setReportDetails] = useState(""); + const [isReporting, setIsReporting] = useState(false); const commentId = useMemo(() => toComparableId(comments._id ?? comments.id), [comments._id, comments.id]); const currentUserId = useMemo(() => toComparableId(currentUser?._id), [currentUser?._id]); @@ -490,21 +502,60 @@ const CommentCard: React.FC = ({ }; const handleOpenReportModal = () => { - if (isAuthor || isDeleting || isUpdating) return; + if (isAuthor || isDeleting || isUpdating || isReporting) return; setReportReason(""); + setReportDetails(""); setIsReportModalOpen(true); }; const handleCloseReportModal = () => { + if (isReporting) return; setIsReportModalOpen(false); setReportReason(""); + setReportDetails(""); }; - const handleReportComment = () => { + const handleReportComment = async () => { if (!reportReason) return; - setIsReportModalOpen(false); - setReportReason(""); - onActionSuccess?.("Comment reported successfully."); + if (!commentId) { + onActionError?.("Comment id missing. Unable to report."); + return; + } + + const trimmedDetails = reportDetails.trim(); + const reasonLabel = getReportReasonLabel(reportReason); + + setIsReporting(true); + try { + const accessToken = authStore.getToken(); + const response = await apiClient.post("/report", { + targetType: "COMMENT", + targetId: commentId, + reason: reportReason, + details: trimmedDetails || reasonLabel, + }, { + headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined, + }); + + const body = response.data; + const payload = extractApiPayload(body); + const ok = resolveApiOk(body, payload); + + if (!ok) { + throw new Error(resolveApiMessage(body, payload, `Request failed with status ${response.status}`)); + } + + const successMessage = resolveApiMessage(body, payload, "Report submitted for AI moderation."); + setIsReportModalOpen(false); + setReportReason(""); + setReportDetails(""); + onActionSuccess?.(successMessage); + } catch (error) { + const message = resolveRequestErrorMessage(error, "Failed to report comment."); + onActionError?.(message); + } finally { + setIsReporting(false); + } }; @@ -651,7 +702,7 @@ const CommentCard: React.FC = ({ diff --git a/src/views/InscriptionDetailPage.tsx/InscriptionDetailPage1.tsx b/src/views/InscriptionDetailPage.tsx/InscriptionDetailPage1.tsx index 50d98df..2744e79 100644 --- a/src/views/InscriptionDetailPage.tsx/InscriptionDetailPage1.tsx +++ b/src/views/InscriptionDetailPage.tsx/InscriptionDetailPage1.tsx @@ -35,6 +35,8 @@ import RatingModal1 from './RatingModal1'; import cdacRoundLogo from '@/assets/cdacroundlogo.png'; import type { User } from '@/types'; import ShareModal from '@/components/ShareModal/ShareModal'; +import { authStore } from '@/store/authStore'; +import { apiClient } from '@/utils/http/clients/backendApiClientGeneral'; import { coreBackendClient } from '@/utils/http/clients/coreBackend.client'; import { detectAIClient } from '@/utils/http/clients/detectAIClient'; import mockDiscoveryPosts from '@/Db/feeds'; @@ -46,14 +48,22 @@ import { MoreVert } from '@mui/icons-material'; import AppImage from "@/components/AppImage"; const USE_FALLBACK = false; -const REPORT_REASONS = [ - "Bullying or harassment", - "Hate symbols or hate speech", - "Inappropriate language", - "Spam or misleading", - "Violence or dangerous organizations", - "Selling or promoting restricted items", +type ReportReasonOption = { + label: string; + value: string; +}; + +const REPORT_REASONS: ReportReasonOption[] = [ + { label: "Misinformation", value: "MISINFORMATION" }, + { label: "Bullying or harassment", value: "HARASSMENT" }, + { label: "Hate symbols or hate speech", value: "HATE_SPEECH" }, + { label: "Spam or misleading", value: "SPAM" }, + { label: "Explicit content", value: "EXPLICIT_CONTENT" }, + { label: "Other", value: "OTHER" }, ]; + +const getReportReasonLabel = (reasonValue: string): string => + REPORT_REASONS.find((reasonOption) => reasonOption.value === reasonValue)?.label ?? reasonValue; const DESCRIPTION_PREVIEW_CHAR_LIMIT = 320; export interface Comment { @@ -404,6 +414,8 @@ const InscriptionDetailsPage: React.FC = () => { const [isUpdatingPost, setIsUpdatingPost] = useState(false); const [isReportPostModalOpen, setIsReportPostModalOpen] = useState(false); const [reportPostReason, setReportPostReason] = useState(""); + const [reportPostDetails, setReportPostDetails] = useState(""); + const [isReportingPost, setIsReportingPost] = useState(false); const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); const [postActionAnchorEl, setPostActionAnchorEl] = useState(null); const [editPostForm, setEditPostForm] = useState({ @@ -1004,23 +1016,69 @@ const InscriptionDetailsPage: React.FC = () => { const handleOpenReportPostFromMenu = () => { handleClosePostActionMenu(); - if (!isAuthenticated || isPostAuthor) { + if (!isAuthenticated || isPostAuthor || isReportingPost) { return; } setReportPostReason(""); + setReportPostDetails(""); setIsReportPostModalOpen(true); }; const handleCloseReportPostModal = () => { + if (isReportingPost) return; setIsReportPostModalOpen(false); setReportPostReason(""); + setReportPostDetails(""); }; - const handleReportPost = () => { + const handleReportPost = async () => { if (!reportPostReason) return; - setIsReportPostModalOpen(false); - setReportPostReason(""); - handlePostSuccess("Post reported successfully."); + + if (!isAuthenticated) { + handlePostError("Your session has expired. Please log in again."); + return; + } + + const resolvedPostId = normalizeEntityId(postToRender?._id) || normalizeEntityId(postId); + if (!resolvedPostId) { + handlePostError("Post id missing. Unable to report."); + return; + } + + const trimmedDetails = reportPostDetails.trim(); + const reasonLabel = getReportReasonLabel(reportPostReason); + + setIsReportingPost(true); + try { + const accessToken = authStore.getToken(); + const response = await apiClient.post("/report", { + targetType: "POST", + targetId: resolvedPostId, + reason: reportPostReason, + details: trimmedDetails || reasonLabel, + }, { + headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined, + }); + + const body = response?.data; + const payload = unwrapApiData(body); + const success = resolveApiSuccess(body, payload); + + if (!success) { + throw new Error(resolveApiMessage(body, payload, "Failed to report post.")); + } + + const successMessage = resolveApiMessage(body, payload, "Report submitted for AI moderation."); + setIsReportPostModalOpen(false); + setReportPostReason(""); + setReportPostDetails(""); + handlePostSuccess(successMessage); + } catch (error) { + const message = resolveRequestErrorMessage(error, "Failed to report post."); + handlePostError(message); + } finally { + setIsReportingPost(false); + } }; const handleCloseEditPostModal = () => { @@ -1590,26 +1648,38 @@ const InscriptionDetailsPage: React.FC = () => { > {REPORT_REASONS.map((reason) => ( } - label={reason} + label={reason.label} /> ))} + setReportPostDetails(event.target.value)} + inputProps={{ maxLength: 500 }} + helperText={`${reportPostDetails.length}/500`} + /> - @@ -1713,7 +1783,7 @@ const InscriptionDetailsPage: React.FC = () => { , ] ) : ( - + Report Post