Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 82 additions & 19 deletions src/views/InscriptionDetailPage.tsx/CommentCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<CommentCardProps> = ({
comments,
currentUser,
Expand All @@ -199,6 +209,8 @@ const CommentCard: React.FC<CommentCardProps> = ({
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]);
Expand Down Expand Up @@ -490,21 +502,60 @@ const CommentCard: React.FC<CommentCardProps> = ({
};

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);
}
};


Expand Down Expand Up @@ -651,7 +702,7 @@ const CommentCard: React.FC<CommentCardProps> = ({
<button
type="button"
onClick={handleOpenReportModal}
disabled={isDeleting || isUpdating}
disabled={isDeleting || isUpdating || isReporting}
className="text-gray-500 hover:text-red-600 disabled:opacity-50 flex items-center gap-1 cursor-pointer"
>
<TriangleAlert className="w-3 h-3" />
Expand Down Expand Up @@ -709,26 +760,38 @@ const CommentCard: React.FC<CommentCardProps> = ({
>
{REPORT_REASONS.map((reason) => (
<FormControlLabel
key={reason}
value={reason}
key={reason.value}
value={reason.value}
control={<Radio />}
label={reason}
label={reason.label}
/>
))}
</RadioGroup>
</FormControl>
<TextField
margin="dense"
fullWidth
multiline
minRows={3}
label="Additional details (optional)"
placeholder="Share extra context to help moderation."
value={reportDetails}
onChange={(event) => setReportDetails(event.target.value)}
inputProps={{ maxLength: 500 }}
helperText={`${reportDetails.length}/500`}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseReportModal} color="inherit">
<Button onClick={handleCloseReportModal} color="inherit" disabled={isReporting}>
Cancel
</Button>
<Button
onClick={handleReportComment}
color="error"
variant="contained"
disabled={!reportReason}
disabled={!reportReason || isReporting}
>
Report
{isReporting ? "Reporting..." : "Report"}
</Button>
</DialogActions>
</Dialog>
Expand Down
108 changes: 89 additions & 19 deletions src/views/InscriptionDetailPage.tsx/InscriptionDetailPage1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -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<HTMLElement | null>(null);
const [editPostForm, setEditPostForm] = useState<EditPostFormState>({
Expand Down Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -1590,26 +1648,38 @@ const InscriptionDetailsPage: React.FC = () => {
>
{REPORT_REASONS.map((reason) => (
<FormControlLabel
key={reason}
value={reason}
key={reason.value}
value={reason.value}
control={<Radio />}
label={reason}
label={reason.label}
/>
))}
</RadioGroup>
</FormControl>
<TextField
margin="dense"
fullWidth
multiline
minRows={3}
label="Additional details (optional)"
placeholder="Share extra context to help moderation."
value={reportPostDetails}
onChange={(event) => setReportPostDetails(event.target.value)}
inputProps={{ maxLength: 500 }}
helperText={`${reportPostDetails.length}/500`}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseReportPostModal} color="inherit">
<Button onClick={handleCloseReportPostModal} color="inherit" disabled={isReportingPost}>
Cancel
</Button>
<Button
onClick={handleReportPost}
color="error"
variant="contained"
disabled={!reportPostReason}
disabled={!reportPostReason || isReportingPost}
>
Report
{isReportingPost ? "Reporting..." : "Report"}
</Button>
</DialogActions>
</Dialog>
Expand Down Expand Up @@ -1713,7 +1783,7 @@ const InscriptionDetailsPage: React.FC = () => {
</MenuItem>,
]
) : (
<MenuItem onClick={handleOpenReportPostFromMenu} sx={{ color: "#dc2626" }}>
<MenuItem onClick={handleOpenReportPostFromMenu} disabled={isReportingPost} sx={{ color: "#dc2626" }}>
<TriangleAlert className="w-4 h-4 mr-2" />
Report Post
</MenuItem>
Expand Down
Loading