1
0
Fork 0
panel/components/pages/inspector/ReportActions.tsx

331 lines
11 KiB
TypeScript

"use client";
import { Report } from "revolt-api";
import { Textarea } from "../../ui/textarea";
import { Button } from "../../ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../../ui/dropdown-menu";
import { useState } from "react";
import { useToast } from "../../ui/use-toast";
import {
assignReportToCase,
rejectReport,
reopenReport,
resolveReport,
sendAlert,
updateReportNotes,
} from "@/lib/actions";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "../../ui/alert-dialog";
import { ReportCard } from "../../cards/ReportCard";
import { Popover } from "@radix-ui/react-popover";
import { PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { CaseDocument, ReportDocument, fetchOpenCases } from "@/lib/db";
import { CaseCard } from "@/components/cards/CaseCard";
const template: Record<string, (ref: string) => string> = {
resolved: (ref) =>
`Your report (${ref}) has been actioned and appropriate action has been taken.`,
invalid: (ref) => `Your report (${ref}) has been marked as invalid.`,
false: (ref) =>
`Your report (${ref}) has been marked as false or spam. False reports may lead to additional action against your account.`,
duplicate: (ref) => `Your report (${ref}) has been marked as a duplicate.`,
"not enough evidence": (ref) =>
`Your report (${ref}) has not been actioned at this time due to a lack of supporting evidence, if you have additional information to support your report, please either report individual relevant messages or send an email to contact@revolt.chat.`,
clarify: (ref) =>
`Your report (${ref}) needs clarification, please provide additional information.`,
acknowledged: (ref) =>
`Your report (${ref}) has been acknowledged, we will be monitoring the situation.`,
default: (ref) =>
`Report (${ref})\n\nNo template found for rejection reason, please specify.`,
};
export function ReportActions({
report,
reference,
}: {
report: ReportDocument;
reference: string;
}) {
const { toast } = useToast();
const [reportDraft, setDraft] = useState(report);
const [availableCases, setAvailableCases] = useState<CaseDocument[]>([]);
function rejectHandler(reason: string) {
return async () => {
try {
const $set = await rejectReport(report._id, reason);
setDraft((report) => ({ ...report, ...$set }));
toast({
title: "Rejected report",
});
} catch (err) {
toast({
title: "Failed to reject report",
description: String(err),
variant: "destructive",
});
}
};
}
return (
<>
<ReportCard report={reportDraft} />
<Textarea
rows={8}
placeholder="Enter notes here... (save on unfocus)"
className="!min-h-0 !h-[76px]"
defaultValue={report.notes}
onBlur={async (e) => {
const notes = e.currentTarget.value;
if (notes === reportDraft.notes ?? "") return;
try {
await updateReportNotes(report._id, notes);
setDraft((report) => ({ ...report, notes }));
toast({
title: "Updated report notes",
});
} catch (err) {
toast({
title: "Failed to update report notes",
description: String(err),
variant: "destructive",
});
}
}}
/>
{reportDraft.case_id ? (
<Button
variant="destructive"
onClick={async () => {
try {
const $set = await assignReportToCase(report._id);
setDraft((report) => ({ ...report, ...$set }));
toast({
title: "Removed report from case",
});
} catch (err) {
toast({
title: "Failed to resolve report",
description: String(err),
variant: "destructive",
});
}
}}
>
Remove from case
</Button>
) : (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
onClick={() => {
fetchOpenCases().then(setAvailableCases);
}}
>
Add to case
</Button>
</PopoverTrigger>
<PopoverContent className="w-80">
<div className="grid gap-4">
<div className="space-y-2">
<h4 className="font-medium leading-none">Open Cases</h4>
{availableCases.map((entry) => (
<a
key={entry._id}
onClick={async () => {
try {
const $set = await assignReportToCase(
report._id,
entry._id
);
setDraft((report) => ({ ...report, ...$set }));
toast({
title: "Assigned report to case",
});
} catch (err) {
toast({
title: "Failed to resolve report",
description: String(err),
variant: "destructive",
});
}
}}
>
<CaseCard entry={entry} />
</a>
))}
</div>
</div>
</PopoverContent>
</Popover>
)}
<div className="flex gap-2">
{reportDraft.status === "Created" ? (
<>
<Button
className="flex-1 bg-green-400 hover:bg-green-300"
onClick={async () => {
try {
const $set = await resolveReport(report._id);
setDraft((report) => ({ ...report, ...$set }));
toast({
title: "Resolved report",
});
} catch (err) {
toast({
title: "Failed to resolve report",
description: String(err),
variant: "destructive",
});
}
}}
>
Resolve
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="flex-1 bg-red-400 hover:bg-red-300">
Reject
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onClick={() => {
const reason = prompt("Enter a custom reason:");
// TODO: modal
reason && rejectHandler(reason)();
}}
>
Custom Reason
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>Presets</DropdownMenuLabel>
<DropdownMenuItem onClick={rejectHandler("invalid")}>
Invalid
</DropdownMenuItem>
<DropdownMenuItem onClick={rejectHandler("false")}>
False or Spam
</DropdownMenuItem>
<DropdownMenuItem onClick={rejectHandler("duplicate")}>
Duplicate
</DropdownMenuItem>
<DropdownMenuItem
onClick={rejectHandler("not enough evidence")}
>
Not Enough Evidence
</DropdownMenuItem>
<DropdownMenuItem onClick={rejectHandler("clarify")}>
Request Clarification
</DropdownMenuItem>
<DropdownMenuItem onClick={rejectHandler("acknowledged")}>
Acknowledge Only
</DropdownMenuItem>
<DropdownMenuItem onClick={rejectHandler("ignore")}>
Ignore
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
) : (
<>
<Button
className="flex-1"
onClick={async () => {
try {
const $set = await reopenReport(report._id);
setDraft((report) => ({ ...report, ...$set }));
toast({
title: "Opened report again",
});
} catch (err) {
toast({
title: "Failed to re-open report",
description: String(err),
variant: "destructive",
});
}
}}
>
Re-open
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button className="flex-1">Send resolution notification</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Send Notification</AlertDialogTitle>
<AlertDialogDescription className="flex flex-col gap-2">
<span>
This will send a message from the Platform Moderation
account.
</span>
<Textarea
id="notification-content"
defaultValue={(
template[
reportDraft.status === "Rejected"
? reportDraft.rejection_reason
: "resolved"
] ?? template["default"]
)(reference)}
/>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
const msg = (
document.getElementById(
"notification-content"
) as HTMLTextAreaElement
).value;
if (!msg) return;
sendAlert(report.author_id, msg)
.then(() => toast({ title: "Sent Alert" }))
.catch((err) =>
toast({
title: "Failed to send alert!",
description: String(err),
variant: "destructive",
})
);
}}
>
Send
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)}
</div>
</>
);
}