diff --git a/playwright/e2e/right-panel/right-panel.spec.ts b/playwright/e2e/right-panel/right-panel.spec.ts index cc03963801..44b051c987 100644 --- a/playwright/e2e/right-panel/right-panel.spec.ts +++ b/playwright/e2e/right-panel/right-panel.spec.ts @@ -1,5 +1,5 @@ /* -Copyright 2024 New Vector Ltd. +Copyright 2024, 2025 New Vector Ltd. Copyright 2022 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial @@ -10,6 +10,7 @@ import { type Locator, type Page } from "@playwright/test"; import { test, expect } from "../../element-web-test"; import { checkRoomSummaryCard, viewRoomSummaryByName } from "./utils"; +import { isDendrite } from "../../plugins/homeserver/dendrite"; const ROOM_NAME = "Test room"; const ROOM_NAME_LONG = @@ -133,6 +134,17 @@ test.describe("RightPanel", () => { await page.getByLabel("Room info").nth(1).click(); await checkRoomSummaryCard(page, ROOM_NAME); }); + test.describe("room reporting", () => { + test.skip(isDendrite, "Dendrite does not implement room reporting"); + test("should handle reporting a room", async ({ page, app }) => { + await viewRoomSummaryByName(page, app, ROOM_NAME); + await page.getByRole("menuitem", { name: "Report room" }).click(); + const dialog = await page.getByRole("dialog", { name: "Report Room" }); + await dialog.getByLabel("reason").fill("This room should be reported"); + await dialog.getByRole("button", { name: "Send report" }).click(); + await expect(page.getByText("Your report was sent.")).toBeVisible(); + }); + }); }); test.describe("in spaces", () => { diff --git a/playwright/snapshots/right-panel/right-panel.spec.ts/with-leave-room-linux.png b/playwright/snapshots/right-panel/right-panel.spec.ts/with-leave-room-linux.png index a2bc63aaf4..63a49dc751 100644 Binary files a/playwright/snapshots/right-panel/right-panel.spec.ts/with-leave-room-linux.png and b/playwright/snapshots/right-panel/right-panel.spec.ts/with-leave-room-linux.png differ diff --git a/res/css/_components.pcss b/res/css/_components.pcss index bd927c9843..9df9dcb8cb 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -152,6 +152,7 @@ @import "./views/dialogs/_ModalWidgetDialog.pcss"; @import "./views/dialogs/_PollCreateDialog.pcss"; @import "./views/dialogs/_RegistrationEmailPromptDialog.pcss"; +@import "./views/dialogs/_ReportRoomDialog.pcss"; @import "./views/dialogs/_RoomSettingsDialog.pcss"; @import "./views/dialogs/_RoomSettingsDialogBridges.pcss"; @import "./views/dialogs/_RoomUpgradeDialog.pcss"; diff --git a/res/css/views/dialogs/_ReportRoomDialog.pcss b/res/css/views/dialogs/_ReportRoomDialog.pcss new file mode 100644 index 0000000000..7323ac65c9 --- /dev/null +++ b/res/css/views/dialogs/_ReportRoomDialog.pcss @@ -0,0 +1,16 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +.mx_ReportRoomDialog { + textarea { + font: var(--cpd-font-body-md-regular); + border: 1px solid var(--cpd-color-border-interactive-primary); + background: var(--cpd-color-bg-canvas-default); + border-radius: 0.5rem; + padding: var(--cpd-space-3x) var(--cpd-space-4x); + } +} diff --git a/res/css/views/right_panel/_RoomSummaryCard.pcss b/res/css/views/right_panel/_RoomSummaryCard.pcss index eb3b14579f..3c1c07677c 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.pcss +++ b/res/css/views/right_panel/_RoomSummaryCard.pcss @@ -1,5 +1,5 @@ /* -Copyright 2024 New Vector Ltd. +Copyright 2024, 2025 New Vector Ltd. Copyright 2020 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial @@ -101,6 +101,6 @@ Please see LICENSE files in the repository root for full details. margin: $spacing-12 0 $spacing-4; } -.mx_RoomSummaryCard_leave { +.mx_RoomSummaryCard_bottomOptions { margin: 0 0 var(--cpd-space-8x); } diff --git a/src/components/views/dialogs/ReportRoomDialog.tsx b/src/components/views/dialogs/ReportRoomDialog.tsx new file mode 100644 index 0000000000..ff4e96ea97 --- /dev/null +++ b/src/components/views/dialogs/ReportRoomDialog.tsx @@ -0,0 +1,95 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import React, { type ChangeEventHandler, useCallback, useState } from "react"; +import { Root, Field, Label, InlineSpinner, ErrorMessage } from "@vector-im/compound-web"; + +import { _t } from "../../../languageHandler"; +import SdkConfig from "../../../SdkConfig"; +import Markdown from "../../../Markdown"; +import BaseDialog from "./BaseDialog"; +import DialogButtons from "../elements/DialogButtons"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; + +interface IProps { + roomId: string; + onFinished(complete: boolean): void; +} + +/* + * A dialog for reporting a room. + */ + +export const ReportRoomDialog: React.FC = function ({ roomId, onFinished }) { + const [error, setErr] = useState(); + const [busy, setBusy] = useState(false); + const [sent, setSent] = useState(false); + const [reason, setReason] = useState(""); + const client = MatrixClientPeg.safeGet(); + + const onReasonChange = useCallback>((e) => setReason(e.target.value), []); + const onCancel = useCallback(() => onFinished(sent), [sent, onFinished]); + const onSubmit = useCallback(async () => { + setBusy(true); + try { + await client.reportRoom(roomId, reason); + setSent(true); + } catch (ex) { + if (ex instanceof Error) { + setErr(ex.message); + } else { + setErr("Unknown error"); + } + } finally { + setBusy(false); + } + }, [roomId, reason, client]); + + const adminMessageMD = SdkConfig.getObject("report_event")?.get("admin_message_md", "adminMessageMD"); + let adminMessage: JSX.Element | undefined; + if (adminMessageMD) { + const html = new Markdown(adminMessageMD).toHTML({ externalLinks: true }); + adminMessage =

; + } + + return ( + onFinished(sent)} + title={_t("report_room|title")} + contentId="mx_ReportEventDialog" + > + {sent &&

{_t("report_room|sent")}

} + {!sent && ( + +

{_t("report_room|description")}

+ {adminMessage} + + +