From d2b3d37724a999d4cc78c46157593267e29d184e Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:18:00 +0200 Subject: [feature/frontend] Reports frontend v2 (#3022) * use apiutil + paging in admin processor+handlers * we're making it happen * fix little whoopsie * styling for report list * don't youuuu forget about meee don't don't don't don't * last bits * sanitize content before showing in report statuses * update report docs --- .../settings/views/admin/emoji/local/detail.tsx | 6 +- .../settings/views/admin/emoji/local/new-emoji.tsx | 6 +- .../admin/http-header-permissions/overview.tsx | 2 +- .../views/moderation/accounts/detail/index.tsx | 4 +- .../views/moderation/accounts/detail/util.tsx | 43 --- .../views/moderation/accounts/search/index.tsx | 2 +- .../settings/views/moderation/reports/detail.tsx | 335 +++++++++++---------- .../settings/views/moderation/reports/overview.tsx | 97 ------ .../settings/views/moderation/reports/search.tsx | 252 ++++++++++++++++ web/source/settings/views/moderation/router.tsx | 5 +- web/source/settings/views/user/profile.tsx | 2 +- 11 files changed, 434 insertions(+), 320 deletions(-) delete mode 100644 web/source/settings/views/moderation/accounts/detail/util.tsx delete mode 100644 web/source/settings/views/moderation/reports/overview.tsx create mode 100644 web/source/settings/views/moderation/reports/search.tsx (limited to 'web/source/settings/views') diff --git a/web/source/settings/views/admin/emoji/local/detail.tsx b/web/source/settings/views/admin/emoji/local/detail.tsx index 2913b6c17..4126bbedc 100644 --- a/web/source/settings/views/admin/emoji/local/detail.tsx +++ b/web/source/settings/views/admin/emoji/local/detail.tsx @@ -22,7 +22,7 @@ import { Redirect, useParams } from "wouter"; import { useComboBoxInput, useFileInput, useValue } from "../../../../lib/form"; import useFormSubmit from "../../../../lib/form/submit"; import { useBaseUrl } from "../../../../lib/navigation/util"; -import FakeToot from "../../../../components/fake-toot"; +import { FakeStatus } from "../../../../components/status"; import FormWithData from "../../../../lib/form/form-with-data"; import Loading from "../../../../components/loading"; import { FileInput } from "../../../../components/form/inputs"; @@ -124,14 +124,14 @@ function EmojiDetailForm({ data: emoji }) { disabled={!form.image.value} /> - + Look at this new custom emoji {emoji.shortcode} isn't it cool? - + {result.error && } {deleteResult.error && } diff --git a/web/source/settings/views/admin/emoji/local/new-emoji.tsx b/web/source/settings/views/admin/emoji/local/new-emoji.tsx index 20f45f372..f2f5a56b1 100644 --- a/web/source/settings/views/admin/emoji/local/new-emoji.tsx +++ b/web/source/settings/views/admin/emoji/local/new-emoji.tsx @@ -23,7 +23,7 @@ import useShortcode from "./use-shortcode"; import useFormSubmit from "../../../../lib/form/submit"; import { TextInput, FileInput } from "../../../../components/form/inputs"; import { CategorySelect } from '../category-select'; -import FakeToot from "../../../../components/fake-toot"; +import { FakeStatus } from "../../../../components/status"; import MutationButton from "../../../../components/form/mutation-button"; import { useAddEmojiMutation } from "../../../../lib/query/admin/custom-emoji"; import { useInstanceV1Query } from "../../../../lib/query/gts-api"; @@ -103,9 +103,9 @@ export default function NewEmojiForm() {

Add new custom emoji

- + Look at this new custom emoji {emojiOrShortcode} isn't it cool? - +
{ // When clicking on a header perm, // go to the detail view for perm. diff --git a/web/source/settings/views/moderation/accounts/detail/index.tsx b/web/source/settings/views/moderation/accounts/detail/index.tsx index 830a894cb..958a3121b 100644 --- a/web/source/settings/views/moderation/accounts/detail/index.tsx +++ b/web/source/settings/views/moderation/accounts/detail/index.tsx @@ -21,13 +21,13 @@ import React from "react"; import { useGetAccountQuery } from "../../../../lib/query/admin"; import FormWithData from "../../../../lib/form/form-with-data"; -import FakeProfile from "../../../../components/fake-profile"; +import FakeProfile from "../../../../components/profile"; import { AdminAccount } from "../../../../lib/types/account"; import { AccountActions } from "./actions"; import { useParams } from "wouter"; import { useBaseUrl } from "../../../../lib/navigation/util"; import BackButton from "../../../../components/back-button"; -import { UseOurInstanceAccount, yesOrNo } from "./util"; +import { UseOurInstanceAccount, yesOrNo } from "../../../../lib/util"; export default function AccountDetail() { const params: { accountID: string } = useParams(); diff --git a/web/source/settings/views/moderation/accounts/detail/util.tsx b/web/source/settings/views/moderation/accounts/detail/util.tsx deleted file mode 100644 index b82d44a6e..000000000 --- a/web/source/settings/views/moderation/accounts/detail/util.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - 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 { useMemo } from "react"; - -import { AdminAccount } from "../../../../lib/types/account"; -import { store } from "../../../../redux/store"; - -export function yesOrNo(b: boolean): string { - return b ? "yes" : "no"; -} - -export function UseOurInstanceAccount(account: AdminAccount): boolean { - // Pull our own URL out of storage so we can - // tell if account is our instance account. - const ourDomain = useMemo(() => { - const instanceUrlStr = store.getState().oauth.instanceUrl; - if (!instanceUrlStr) { - return ""; - } - - const instanceUrl = new URL(instanceUrlStr); - return instanceUrl.host; - }, []); - - return !account.domain && account.username == ourDomain; -} diff --git a/web/source/settings/views/moderation/accounts/search/index.tsx b/web/source/settings/views/moderation/accounts/search/index.tsx index 16e89ce43..f37e22a66 100644 --- a/web/source/settings/views/moderation/accounts/search/index.tsx +++ b/web/source/settings/views/moderation/accounts/search/index.tsx @@ -83,7 +83,7 @@ export function AccountSearchForm() { } // Location to return to when user clicks "back" on the account detail view. - const backLocation = location + (urlQueryParams ? `?${urlQueryParams}` : ""); + const backLocation = location + (urlQueryParams.size > 0 ? `?${urlQueryParams}` : ""); // Function to map an item to a list entry. function itemToEntry(account: AdminAccount): ReactNode { diff --git a/web/source/settings/views/moderation/reports/detail.tsx b/web/source/settings/views/moderation/reports/detail.tsx index ad8d69a47..7d6e542fb 100644 --- a/web/source/settings/views/moderation/reports/detail.tsx +++ b/web/source/settings/views/moderation/reports/detail.tsx @@ -17,8 +17,8 @@ along with this program. If not, see . */ -import React, { useState } from "react"; -import { useParams } from "wouter"; +import React from "react"; +import { useLocation, useParams } from "wouter"; import FormWithData from "../../../lib/form/form-with-data"; import BackButton from "../../../components/back-button"; import { useValue, useTextInput } from "../../../lib/form"; @@ -28,84 +28,172 @@ import MutationButton from "../../../components/form/mutation-button"; import Username from "../../../components/username"; import { useGetReportQuery, useResolveReportMutation } from "../../../lib/query/admin/reports"; import { useBaseUrl } from "../../../lib/navigation/util"; +import { AdminReport } from "../../../lib/types/report"; +import { yesOrNo } from "../../../lib/util"; +import { Status } from "../../../components/status"; export default function ReportDetail({ }) { + const params: { reportId: string } = useParams(); const baseUrl = useBaseUrl(); - const params = useParams(); + const backLocation: String = history.state?.backLocation ?? `~${baseUrl}`; return ( -
-

Report Details

+
+

Report Details

); } -function ReportDetailForm({ data: report }) { +function ReportDetailForm({ data: report }: { data: AdminReport }) { + const [ location ] = useLocation(); + const baseUrl = useBaseUrl(); + + return ( + <> + + + { report.action_taken + && + } + + { report.statuses && + + } + + { !report.action_taken && + + } + + ); +} + +interface ReportSectionProps { + report: AdminReport; + baseUrl: string; + location: string; +} + +function ReportBasicInfo({ report, baseUrl, location }: ReportSectionProps) { const from = report.account; const target = report.target_account; + const comment = report.comment; + const status = report.action_taken ? "Resolved" : "Unresolved"; + const created = new Date(report.created_at).toLocaleString(); return ( -
-
- - <> reported - +
+
+
Reported account
+
+ +
+
+ +
+
Reported by
+
+ +
- {report.action_taken && -
-

Resolved by @{report.action_taken_by_account.account.acct}

- at {new Date(report.action_taken_at).toLocaleString()} -
- Comment: {report.action_taken_comment} -
- } +
+
Status
+
+ { report.action_taken + ? <>{status} + : {status} + } +
+
-
-

Report info:

-
- Created: - {new Date(report.created_at).toLocaleString()} +
+
Reason
+
+ { comment.length > 0 + ? <>{comment} + : none provided + } +
+
- Forwarded: {report.forwarded ? "Yes" : "No"} - Category: {report.category} +
+
Created
+
+ +
+
- Reason: - {report.comment.length > 0 - ?

{report.comment}

- : none provided - } +
+
Category
+
{ report.category }
+
-
+
+
Forwarded
+
{ yesOrNo(report.forwarded) }
+
+ ); +} - {!report.action_taken && } - - { - report.statuses.length > 0 && -
-

Reported toots ({report.statuses.length}):

-
- {report.statuses.map((status) => ( - - ))} -
+function ReportHistory({ report, baseUrl, location }: ReportSectionProps) { + const handled_by = report.action_taken_by_account; + if (!handled_by) { + throw "report handled by action_taken_by_account undefined"; + } + + const handled = report.action_taken_at ? new Date(report.action_taken_at).toLocaleString() : "never"; + + return ( + <> +

Moderation History

+
+
+
Handled by
+
+ +
- } -
+ +
+
Handled
+
+ +
+
+ +
+
Comment
+
{ report.action_taken_comment ?? "none"}
+
+ + ); } @@ -118,13 +206,18 @@ function ReportActionForm({ report }) { const [submit, result] = useFormSubmit(form, useResolveReportMutation(), { changedOnly: false }); return ( - -

Resolving this report

-

+ +

Resolve this report

+ <> An optional comment can be included while resolving this report. - Useful for providing an explanation about what action was taken (if any) before the report was marked as resolved.
- This will be visible to the user that created the report! -

+ This is useful for providing an explanation about what action was + taken (if any) before the report was marked as resolved. +
+ + Any comment made here will be visible + to the user that created the report! + +