diff --git a/src/components/IncidentReportTracker/IncidentReportCard.tsx b/src/components/IncidentReportTracker/IncidentReportCard.tsx index b07e03da65..829eb9868a 100644 --- a/src/components/IncidentReportTracker/IncidentReportCard.tsx +++ b/src/components/IncidentReportTracker/IncidentReportCard.tsx @@ -17,7 +17,7 @@ /* * This file contains the incident report tracking and management system - * which is used by our IncidentReportTracker widget and our ReportsCenter view. + * which is used by our IncidentReportIndicator widget and our ReportsCenter view. */ import * as React from "react"; diff --git a/src/components/IncidentReportTracker/IncidentReportIndicator.tsx b/src/components/IncidentReportTracker/IncidentReportIndicator.tsx new file mode 100644 index 0000000000..aa02bcb140 --- /dev/null +++ b/src/components/IncidentReportTracker/IncidentReportIndicator.tsx @@ -0,0 +1,131 @@ +/* + * Copyright (C) Online-Go.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import * as React from "react"; +import * as data from "@/lib/data"; +import { useNavigate } from "react-router-dom"; + +import { usePreference } from "@/lib/preferences"; + +import { report_manager } from "@/lib/report_manager"; + +import { useRefresh, useUser } from "@/lib/hooks"; +import * as DynamicHelp from "react-dynamic-help"; +import { IncidentReportList } from "./IncidentReportList"; + +export function IncidentReportIndicator(): JSX.Element | null { + const user = useUser(); + const navigate = useNavigate(); + const [show_incident_list, setShowIncidentList] = React.useState(false); + + const [normal_ct, setNormalCt] = React.useState(0); + const [prefer_hidden] = usePreference("hide-incident-reports"); + const [report_quota] = usePreference("moderator.report-quota"); + const refresh = useRefresh(); + + const { registerTargetItem, triggerFlow, signalUsed } = React.useContext(DynamicHelp.Api); + const { ref: incident_report_indicator, active: incidentReportIndicatorActive } = + registerTargetItem("incident-report-indicator"); + + const { ref: hidden_incident_report_indicator } = registerTargetItem( + "hidden-incident-report-indicator", + ); + + function toggleList() { + if (user.is_moderator || user.moderator_powers) { + signalUsed("incident-report-indicator"); + navigate("/reports-center/"); + } else { + data.set("ui-state.show_incident_list", !show_incident_list); + } + } + + React.useEffect(() => { + if (incidentReportIndicatorActive() && user.moderator_powers) { + if (normal_ct > 0) { + triggerFlow("community-moderator-with-reports-intro"); + } else { + triggerFlow("community-moderator-no-reports-intro"); + } + } + }, [incident_report_indicator, prefer_hidden, normal_ct]); + + React.useEffect(() => { + function updateCt(count: number) { + if ((user.is_moderator || user.moderator_powers > 0) && !!report_quota) { + const handled_today = user.reports_handled_today || 0; + setNormalCt(Math.max(0, Math.min(count, report_quota - handled_today))); + } else { + setNormalCt(count); + } + } + + function updateUser() { + updateCt( + report_manager.getEligibleReports().filter((r) => r.report_type !== "troll").length, + ); + } + + data.watch("user", updateUser); + data.watch("preferences.moderator.report-quota", updateUser); + data.watch("preferences.show-cm-reports", updateUser); + + report_manager.on("active-count", updateCt); + report_manager.on("update", refresh); + + data.watch("ui-state.show_incident_list", setShowIncidentList); + + return () => { + report_manager.off("active-count", updateCt); + report_manager.off("update", refresh); + data.unwatch("user", updateUser); + data.unwatch("preferences.moderator.report-quota", updateUser); + data.unwatch("preferences.show-cm-reports", updateUser); + data.unwatch("ui-state.show_incident_list", setShowIncidentList); + }; + }, []); + + if (!user) { + // Can happen when deleting your account, apparently. + return null; + } + + const reports = report_manager.getEligibleReports(); + const hide_indicator = (reports.length === 0 && !user.is_moderator) || prefer_hidden; + + return ( + <> + {hide_indicator && ( + /* this is a target for a dynamic help popup talking about why this isn't shown, + so we need it rendered while hidden */ +
+ +
+ )} + {!hide_indicator && ( +
+ 0 ? "active" : ""}`} + ref={incident_report_indicator} + /> + 0 ? "active" : ""}`}>{normal_ct} +
+ )} + {!!show_incident_list && } + + ); +} diff --git a/src/components/IncidentReportTracker/IncidentReportTracker.styl b/src/components/IncidentReportTracker/IncidentReportList.styl similarity index 97% rename from src/components/IncidentReportTracker/IncidentReportTracker.styl rename to src/components/IncidentReportTracker/IncidentReportList.styl index 5306ddecd9..c0935ba553 100644 --- a/src/components/IncidentReportTracker/IncidentReportTracker.styl +++ b/src/components/IncidentReportTracker/IncidentReportList.styl @@ -47,7 +47,7 @@ } } -.IncidentReportTracker { +.IncidentReportList { .report-header { display: flex; flex-direction: row; @@ -131,7 +131,7 @@ justify-content: center; } -.IncidentReportList-results { +.IncidentReportList-modal { position: fixed; overflow-y: scroll; padding: 0.5rem; @@ -146,3 +146,7 @@ themed: background-color bg; themed: border-color shade2; } + +.IncidentReportList-plain { + padding: 0.5rem; +} diff --git a/src/components/IncidentReportTracker/IncidentReportList.tsx b/src/components/IncidentReportTracker/IncidentReportList.tsx new file mode 100644 index 0000000000..6e94c6e034 --- /dev/null +++ b/src/components/IncidentReportTracker/IncidentReportList.tsx @@ -0,0 +1,131 @@ +/* + * Copyright (C) Online-Go.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import * as React from "react"; +import * as data from "@/lib/data"; +import { alert } from "@/lib/swal_config"; +import { post } from "@/lib/requests"; + +import { ignore, errorAlerter } from "@/lib/misc"; +import { Report } from "@/lib/report_util"; + +import { IncidentReportCard } from "./IncidentReportCard"; + +// Define a type for the props +type IncidentReportListProps = { + reports: Report[]; + modal?: boolean; +}; + +export function IncidentReportList({ + reports, + modal = true, +}: IncidentReportListProps): JSX.Element | null { + function hideList() { + data.set("ui-state.show_incident_list", false); + } + + const attachActions = (report: Report) => { + // Most of these may no longer be needed, since we're using the new ReportsCenter view. + if (report.state !== "resolved") { + report.unclaim = () => { + post(`moderation/incident/${report.id}`, { id: report.id, action: "unclaim" }) + .then(ignore) + .catch(errorAlerter); + }; + report.good_report = () => { + post(`moderation/incident/${report.id}`, { + id: report.id, + action: "resolve", + was_helpful: true, + }) + .then(ignore) + .catch(errorAlerter); + }; + report.bad_report = () => { + post(`moderation/incident/${report.id}`, { + id: report.id, + action: "resolve", + was_helpful: false, + }) + .then(ignore) + .catch(errorAlerter); + }; + report.steal = () => { + post(`moderation/incident/${report.id}`, { id: report.id, action: "steal" }) + .then((res) => { + if (res.vanished) { + void alert.fire("Report was removed"); + } + }) + .catch(errorAlerter); + }; + report.claim = () => { + post(`moderation/incident/${report.id}`, { id: report.id, action: "claim" }) + .then((res) => { + if (res.vanished) { + void alert.fire("Report was removed"); + } + if (res.already_claimed) { + void alert.fire("Report was removed"); + } + }) + .catch(errorAlerter); + }; + report.cancel = () => { + post(`moderation/incident/${report.id}`, { id: report.id, action: "cancel" }) + .then(ignore) + .catch(errorAlerter); + }; + + report.set_note = () => { + void alert + .fire({ + input: "text", + inputValue: report.moderator_note, + showCancelButton: true, + }) + .then(({ value: txt, isConfirmed }) => { + if (isConfirmed) { + post(`moderation/incident/${report.id}`, { + id: report.id, + action: "note", + note: txt, + }) + .then(ignore) + .catch(errorAlerter); + } + }); + }; + } + }; + + React.useEffect(() => { + reports.forEach(attachActions); + }, [reports]); + + return ( +
+ {modal &&
} +
+ {reports.map((report: Report, index) => ( + + ))} +
+
+ ); +} diff --git a/src/components/IncidentReportTracker/IncidentReportTracker.tsx b/src/components/IncidentReportTracker/IncidentReportTracker.tsx deleted file mode 100644 index cfcde40b47..0000000000 --- a/src/components/IncidentReportTracker/IncidentReportTracker.tsx +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (C) Online-Go.com - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import * as React from "react"; -import * as preferences from "@/lib/preferences"; -import * as data from "@/lib/data"; -import { Link, useNavigate } from "react-router-dom"; -import { alert } from "@/lib/swal_config"; -import { pgettext } from "@/lib/translate"; -import { post } from "@/lib/requests"; -import { usePreference } from "@/lib/preferences"; - -import { ignore, errorAlerter } from "@/lib/misc"; -import { report_manager } from "@/lib/report_manager"; -import { Report } from "@/lib/report_util"; -import { useRefresh, useUser } from "@/lib/hooks"; -import * as DynamicHelp from "react-dynamic-help"; -import { IncidentReportCard } from "./IncidentReportCard"; - -export function IncidentReportTracker(): JSX.Element | null { - const user = useUser(); - const navigate = useNavigate(); - const [show_incident_list, setShowIncidentList] = React.useState(false); - - const [normal_ct, setNormalCt] = React.useState(0); - const [prefer_hidden] = usePreference("hide-incident-reports"); - const [report_quota] = usePreference("moderator.report-quota"); - const refresh = useRefresh(); - - const { registerTargetItem, triggerFlow, signalUsed } = React.useContext(DynamicHelp.Api); - const { ref: incident_report_indicator, active: incidentReportIndicatorActive } = - registerTargetItem("incident-report-indicator"); - - const { ref: hidden_incident_report_indicator } = registerTargetItem( - "hidden-incident-report-indicator", - ); - - function toggleList() { - if (user.is_moderator || user.moderator_powers) { - signalUsed("incident-report-indicator"); - navigate("/reports-center/"); - } else { - data.set("ui-state.show_incident_list", !show_incident_list); - } - } - - React.useEffect(() => { - if (incidentReportIndicatorActive() && user.moderator_powers) { - if (normal_ct > 0) { - triggerFlow("community-moderator-with-reports-intro"); - } else { - triggerFlow("community-moderator-no-reports-intro"); - } - } - }, [incident_report_indicator, prefer_hidden, normal_ct]); - - React.useEffect(() => { - const onReport = (report: Report) => { - if (report.state !== "resolved") { - report.unclaim = () => { - post(`moderation/incident/${report.id}`, { id: report.id, action: "unclaim" }) - .then(ignore) - .catch(errorAlerter); - }; - report.good_report = () => { - post(`moderation/incident/${report.id}`, { - id: report.id, - action: "resolve", - was_helpful: true, - }) - .then(ignore) - .catch(errorAlerter); - }; - report.bad_report = () => { - post(`moderation/incident/${report.id}`, { - id: report.id, - action: "resolve", - was_helpful: false, - }) - .then(ignore) - .catch(errorAlerter); - }; - report.steal = () => { - post(`moderation/incident/${report.id}`, { id: report.id, action: "steal" }) - .then((res) => { - if (res.vanished) { - void alert.fire("Report was removed"); - } - }) - .catch(errorAlerter); - }; - report.claim = () => { - post(`moderation/incident/${report.id}`, { id: report.id, action: "claim" }) - .then((res) => { - if (res.vanished) { - void alert.fire("Report was removed"); - } - if (res.already_claimed) { - void alert.fire("Report was removed"); - } - }) - .catch(errorAlerter); - }; - report.cancel = () => { - post(`moderation/incident/${report.id}`, { id: report.id, action: "cancel" }) - .then(ignore) - .catch(errorAlerter); - }; - - report.set_note = () => { - void alert - .fire({ - input: "text", - inputValue: report.moderator_note, - showCancelButton: true, - }) - .then(({ value: txt, isConfirmed }) => { - if (isConfirmed) { - post(`moderation/incident/${report.id}`, { - id: report.id, - action: "note", - note: txt, - }) - .then(ignore) - .catch(errorAlerter); - } - }); - }; - } - }; - - function updateCt(count: number) { - const user = data.get("user"); - - if ((user.is_moderator || user.moderator_powers > 0) && !!report_quota) { - const handled_today = user.reports_handled_today || 0; - setNormalCt(Math.max(0, Math.min(count, report_quota - handled_today))); - } else { - setNormalCt(count); - } - } - - function updateUser() { - updateCt( - report_manager.getEligibleReports().filter((r) => r.report_type !== "troll").length, - ); - } - - data.watch("user", updateUser); - data.watch("preferences.moderator.report-quota", updateUser); - data.watch("preferences.show-cm-reports", updateUser); - report_manager.on("incident-report", onReport); - report_manager.on("active-count", updateCt); - report_manager.on("update", refresh); - - data.watch("ui-state.show_incident_list", setShowIncidentList); - - return () => { - report_manager.off("incident-report", onReport); - report_manager.off("active-count", updateCt); - report_manager.off("update", refresh); - data.unwatch("user", updateUser); - data.unwatch("preferences.moderator.report-quota", updateUser); - data.unwatch("preferences.show-cm-reports", updateUser); - data.unwatch("ui-state.show_incident_list", setShowIncidentList); - }; - }, []); - - if (!user) { - // Can happen when deleting your account, apparently. - return null; - } - - const reports = report_manager.getEligibleReports(); - const hide_indicator = (reports.length === 0 && !user.is_moderator) || prefer_hidden; - - const filtered_reports = reports.filter( - (report) => - !preferences.get("hide-claimed-reports") || - report.moderator === null || - report.moderator.id === user.id, - ); - - return ( - <> - {hide_indicator && ( - /* this is a target for a dynamic help popup talking about why this isn't shown, - so we need it rendered while hidden */ -
- -
- )} - {!hide_indicator && ( -
- 0 ? "active" : ""}`} - ref={incident_report_indicator} - /> - 0 ? "active" : ""}`}>{normal_ct} -
- )} - {!!show_incident_list && ( -
-
-
- {(user.is_moderator || null) && ( -

- Go to the new Reports Center -

- )} -
- {filtered_reports.length === 0 && user.is_moderator && ( - // note: normal users would see this if they have the last report and cancel it, - // that's why we need to filter for only moderators to see it -
- {pgettext( - "Shown to moderators when there are no active reports", - "No reports left, great job team!", - )} -
- )} - {filtered_reports.map((report: Report, index) => ( - - ))} -
-
- )} - - ); -} diff --git a/src/components/IncidentReportTracker/index.ts b/src/components/IncidentReportTracker/index.ts index 8b0f0e8410..67b8c47771 100644 --- a/src/components/IncidentReportTracker/index.ts +++ b/src/components/IncidentReportTracker/index.ts @@ -15,4 +15,5 @@ * along with this program. If not, see . */ -export * from "./IncidentReportTracker"; +export * from "./IncidentReportList"; +export * from "./IncidentReportIndicator"; diff --git a/src/components/NavBar/NavBar.tsx b/src/components/NavBar/NavBar.tsx index 28cb87a86e..f072e23021 100644 --- a/src/components/NavBar/NavBar.tsx +++ b/src/components/NavBar/NavBar.tsx @@ -27,7 +27,7 @@ import { LineText } from "@/components/misc-ui"; import { createDemoBoard } from "@/components/ChallengeModal"; import { LanguagePicker } from "@/components/LanguagePicker"; import { GobanThemePicker } from "@/components/GobanThemePicker"; -import { IncidentReportTracker } from "@/components/IncidentReportTracker"; +import { IncidentReportIndicator } from "@/components/IncidentReportTracker"; import { KBShortcut } from "@/components/KBShortcut"; import { NotificationList, notification_manager } from "@/components/Notifications"; import { TurnIndicator } from "@/components/TurnIndicator"; @@ -400,7 +400,7 @@ export function NavBar(): JSX.Element { ) : ( <>
- + diff --git a/src/lib/report_manager.tsx b/src/lib/report_manager.tsx index 2630efc53f..1201844b8b 100644 --- a/src/lib/report_manager.tsx +++ b/src/lib/report_manager.tsx @@ -17,7 +17,7 @@ /* * This file contains the incident report tracking and management system - * which is used by our IncidentReportTracker widget and our ReportsCenter view. + * which is used by our IncidentReportIndicator widget and our ReportsCenter view. */ import * as React from "react"; diff --git a/src/views/ReportsCenter/ReportsCenter.tsx b/src/views/ReportsCenter/ReportsCenter.tsx index c09076b373..ef6b7cfe8c 100644 --- a/src/views/ReportsCenter/ReportsCenter.tsx +++ b/src/views/ReportsCenter/ReportsCenter.tsx @@ -30,6 +30,7 @@ import { ReportsCenterSettings } from "./ReportsCenterSettings"; import { ReportsCenterHistory } from "./ReportsCenterHistory"; import { ReportsCenterCMDashboard } from "./ReportsCenterCMDashboard"; import { ReportsCenterCMHistory } from "./ReportsCenterCMHistory"; +import { IncidentReportList } from "@/components/IncidentReportTracker"; interface OtherView { special: string; @@ -56,6 +57,7 @@ const categories: (ReportDescription | OtherView)[] = [ { special: "hr", title: "", show_cm: true }, { special: "history", title: "History", show_cm: true }, { special: "cm", title: "Community Moderation", show_cm: true }, + { special: "my_reports", title: "My Own Reports", show_cm: true }, { special: "settings", title: "Settings", show_cm: false }, ]); @@ -151,6 +153,10 @@ export function ReportsCenter(): JSX.Element | null { community_mod_has_power(user.moderator_powers, category.type)), ); + const my_reports = report_manager + .getEligibleReports() + .filter((report) => report.reporting_user.id === user.id); + return (

@@ -229,6 +235,7 @@ export function ReportsCenter(): JSX.Element | null { case "settings": case "history": case "cm": + case "my_reports": return (
+ ) : category === "my_reports" ? ( + ) : category === "hr" ? null : (