diff --git a/app/panel/inspect/message/[id]/page.tsx b/app/panel/inspect/message/[id]/page.tsx
index 4b2456c..b9b8702 100644
--- a/app/panel/inspect/message/[id]/page.tsx
+++ b/app/panel/inspect/message/[id]/page.tsx
@@ -28,9 +28,11 @@ export default async function Message({
-
-
-
+ {author && (
+
+
+
+ )}
diff --git a/app/panel/reports/[id]/page.tsx b/app/panel/reports/[id]/page.tsx
index b0d9b65..ce9cc9c 100644
--- a/app/panel/reports/[id]/page.tsx
+++ b/app/panel/reports/[id]/page.tsx
@@ -41,8 +41,18 @@ export default async function Reports({ params }: { params: { id: string } }) {
return (
Viewing Report
-
-
+
diff --git a/components/cards/CompactMessage.tsx b/components/cards/CompactMessage.tsx
index a70452e..1bfe71e 100644
--- a/components/cards/CompactMessage.tsx
+++ b/components/cards/CompactMessage.tsx
@@ -68,9 +68,9 @@ export function CompactMessage({
)}
- {(message.attachments || message.embeds) && (
+ {message.attachments?.length || message.embeds?.length ? (
- )}{" "}
+ ) : null}{" "}
{message.content ?? No text.}
@@ -80,9 +80,7 @@ export function CompactMessage({
{user?.avatar && (
-
+
)}{" "}
{user?.username}#{user?.discriminator}
diff --git a/components/cards/ReportCard.tsx b/components/cards/ReportCard.tsx
index a14fc52..668716d 100644
--- a/components/cards/ReportCard.tsx
+++ b/components/cards/ReportCard.tsx
@@ -16,17 +16,28 @@ export function ReportCard({ report }: { report: Report }) {
report.status !== "Created" ? "text-gray-500" : ""
}`}
>
- {report.content.report_reason.includes("Illegal") && (
-
- Urgent
+ {report.status === "Resolved" ? (
+ Resolved
+ ) : report.status === "Rejected" ? (
+
+ Closed for {report.rejection_reason}
+ ) : (
+ report.content.report_reason.includes("Illegal") && (
+
+ Urgent
+
+ )
)}{" "}
{report.additional_context || "No reason specified"}
{report._id.toString().substring(20, 26)} ·{" "}
{report.content.report_reason} · {report.content.type} ·{" "}
- {dayjs(decodeTime(report._id)).fromNow()}
+ {dayjs(decodeTime(report._id)).fromNow()}{" "}
+ {report.status !== "Created" && report.closed_at && (
+ <>· Closed {dayjs(report.closed_at).fromNow()}>
+ )}
diff --git a/components/inspector/RecentMessages.tsx b/components/inspector/RecentMessages.tsx
index 3d44ebd..18b05bc 100644
--- a/components/inspector/RecentMessages.tsx
+++ b/components/inspector/RecentMessages.tsx
@@ -17,7 +17,7 @@ export async function RecentMessages({
query: Filter;
users?: boolean | User[];
}) {
- const recentMessages = await fetchMessages(query);
+ const recentMessages = (await fetchMessages(query)).reverse();
const userList = (
users === true
diff --git a/components/inspector/RelevantModerationNotices.tsx b/components/inspector/RelevantModerationNotices.tsx
index f97fab2..73f7ba3 100644
--- a/components/inspector/RelevantModerationNotices.tsx
+++ b/components/inspector/RelevantModerationNotices.tsx
@@ -68,7 +68,9 @@ export function RelevantModerationNotices({
{strikesDraft.map((strike) => (
{strike.reason}
- {dayjs(decodeTime(strike._id)).fromNow()}
+
+ {dayjs(decodeTime(strike._id))?.fromNow()}
+
))}
diff --git a/components/inspector/ReportActions.tsx b/components/inspector/ReportActions.tsx
index 6a93d81..6123d74 100644
--- a/components/inspector/ReportActions.tsx
+++ b/components/inspector/ReportActions.tsx
@@ -11,16 +11,77 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
-import { useRef, useState } from "react";
+import { useState } from "react";
import { useToast } from "../ui/use-toast";
-import { updateReportNotes } from "@/lib/actions";
+import {
+ 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";
-export function ReportActions({ report }: { report: Report }) {
+const template: Record 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: Report;
+ reference: string;
+}) {
const { toast } = useToast();
const [reportDraft, setDraft] = useState(report);
+ 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 (
<>
+
+
- {report.status === "Created" ? (
+ {reportDraft.status === "Created" ? (
<>
-
- Custom Reason
+ {
+ const reason = prompt("Enter a custom reason:");
+ // TODO: modal
+ reason && rejectHandler(reason)();
+ }}
+ >
+ Custom Reason
+
Presets
- Invalid
- False or Spam
- Duplicate
- Not Enough Evidence
- Request Clarification
- Acknowledge Only
- Ignore
+
+ Invalid
+
+
+ False or Spam
+
+
+ Duplicate
+
+
+ Not Enough Evidence
+
+
+ Request Clarification
+
+
+ Acknowledge Only
+
+
+ Ignore
+
>
) : (
<>
-
Re-open
-
Send resolution notification
+
{
+ 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
+
+
+
+
+ Send resolution notification
+
+
+
+ Send Notification
+
+
+ This will send a message from the Platform Moderation
+ account.
+
+
+
+
+
+ Cancel
+ {
+ 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
+
+
+
+
>
)}
diff --git a/components/inspector/UserActions.tsx b/components/inspector/UserActions.tsx
index 7dde812..f4040a8 100644
--- a/components/inspector/UserActions.tsx
+++ b/components/inspector/UserActions.tsx
@@ -22,6 +22,7 @@ import {
import { Input } from "../ui/input";
import {
banUser,
+ closeReportsByUser,
sendAlert,
suspendUser,
unsuspendUser,
@@ -249,17 +250,10 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
More Options
-
+
- {
- throw "Cancel immediate propagation.";
- }}
- disabled={userInaccessible}
- >
- Send Alert
-
+ Send Alert
@@ -302,6 +296,42 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
+
+
+ Close Open Reports
+
+
+
+ Close Open Reports
+
+
+ This will close all reports still open by this user.
+
+
+
+
+ Cancel
+
+ closeReportsByUser(user._id)
+ .then((reports) =>
+ toast({ title: `Closed ${reports} Reports` })
+ )
+ .catch((err) =>
+ toast({
+ title: "Failed to close reports!",
+ description: String(err),
+ variant: "destructive",
+ })
+ )
+ }
+ >
+ Continue
+
+
+
+
+
{/*
Clear ({counts.pending}) Friend Requests
diff --git a/lib/actions.ts b/lib/actions.ts
index f04b233..82b1eeb 100644
--- a/lib/actions.ts
+++ b/lib/actions.ts
@@ -85,6 +85,80 @@ export async function updateReportNotes(reportId: string, notes: string) {
);
}
+export async function resolveReport(reportId: string) {
+ const $set = {
+ status: "Resolved",
+ closed_at: new Date().toISOString(),
+ } as Report;
+
+ await mongo().db("revolt").collection("safety_reports").updateOne(
+ { _id: reportId },
+ {
+ $set,
+ }
+ );
+
+ return $set;
+}
+
+export async function rejectReport(reportId: string, reason: string) {
+ const $set = {
+ status: "Rejected",
+ rejection_reason: reason,
+ closed_at: new Date().toISOString(),
+ } as Report;
+
+ await mongo().db("revolt").collection("safety_reports").updateOne(
+ { _id: reportId },
+ {
+ $set,
+ }
+ );
+
+ return $set;
+}
+
+export async function reopenReport(reportId: string) {
+ const $set = {
+ status: "Created",
+ } as Report;
+
+ await mongo()
+ .db("revolt")
+ .collection("safety_reports")
+ .updateOne(
+ { _id: reportId },
+ {
+ $set,
+ $unset: {
+ closed_at: 1,
+ } as never,
+ }
+ );
+
+ return $set;
+}
+
+export async function closeReportsByUser(userId: string) {
+ return await mongo()
+ .db("revolt")
+ .collection("safety_reports")
+ .updateMany(
+ {
+ status: "Created",
+ author_id: userId,
+ },
+ {
+ $set: {
+ status: "Rejected",
+ rejection_reason: "bulk close",
+ closed_at: new Date().toISOString(),
+ },
+ }
+ )
+ .then((res) => res.modifiedCount);
+}
+
export async function disableAccount(userId: string) {
await mongo()
.db("revolt")