Add report room dialog button/dialog. (#29513)

* Add report room dialog button/dialog.

* Update copy

* fixup tests / lint

* Fix title in test.

* update snapshot

* Add unit tests for dialog

* lint
This commit is contained in:
Will Hunt
2025-03-21 17:08:37 +00:00
committed by GitHub
parent b54122884c
commit e1970df704
11 changed files with 556 additions and 123 deletions

View File

@@ -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<IProps> = function ({ roomId, onFinished }) {
const [error, setErr] = useState<string>();
const [busy, setBusy] = useState(false);
const [sent, setSent] = useState(false);
const [reason, setReason] = useState("");
const client = MatrixClientPeg.safeGet();
const onReasonChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>((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 = <p dangerouslySetInnerHTML={{ __html: html }} />;
}
return (
<BaseDialog
className="mx_ReportRoomDialog"
onFinished={() => onFinished(sent)}
title={_t("report_room|title")}
contentId="mx_ReportEventDialog"
>
{sent && <p>{_t("report_room|sent")}</p>}
{!sent && (
<Root id="mx_ReportEventDialog" onSubmit={onSubmit}>
<p>{_t("report_room|description")}</p>
{adminMessage}
<Field name="reason">
<Label htmlFor="mx_ReportRoomDialog_reason">{_t("room_settings|permissions|ban_reason")}</Label>
<textarea
id="mx_ReportRoomDialog_reason"
placeholder={_t("report_room|reason_placeholder")}
rows={5}
onChange={onReasonChange}
value={reason}
disabled={busy}
/>
{error ? <ErrorMessage>{error}</ErrorMessage> : null}
</Field>
{busy ? <InlineSpinner /> : null}
<DialogButtons
primaryButton={_t("action|send_report")}
onPrimaryButtonClick={onSubmit}
focus={true}
onCancel={onCancel}
disabled={busy}
/>
</Root>
)}
</BaseDialog>
);
};

View File

@@ -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
@@ -77,6 +77,7 @@ import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
import { ReportRoomDialog } from "../dialogs/ReportRoomDialog.tsx";
interface IProps {
room: Room;
@@ -231,6 +232,11 @@ const RoomSummaryCard: React.FC<IProps> = ({
room_id: room.roomId,
});
};
const onReportRoomClick = (): void => {
Modal.createDialog(ReportRoomDialog, {
roomId: room.roomId,
});
};
const isRoomEncrypted = useIsEncrypted(cli, room);
const roomContext = useScopedRoomContext("e2eStatus", "timelineRenderingType");
@@ -439,14 +445,21 @@ const RoomSummaryCard: React.FC<IProps> = ({
<MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={onRoomSettingsClick} />
<Separator />
<MenuItem
className="mx_RoomSummaryCard_leave"
Icon={LeaveIcon}
kind="critical"
label={_t("action|leave_room")}
onSelect={onLeaveRoomClick}
/>
<div className="mx_RoomSummaryCard_bottomOptions">
<MenuItem
className="mx_RoomSummaryCard_leave"
Icon={LeaveIcon}
kind="critical"
label={_t("action|leave_room")}
onSelect={onLeaveRoomClick}
/>
<MenuItem
Icon={ErrorIcon}
kind="critical"
label={_t("action|report_room")}
onSelect={onReportRoomClick}
/>
</div>
</div>
</BaseCard>
);