Skip to content

Commit cb657d6

Browse files
authored
Update report room dialog to match designs (#29669)
* Rework for designs * Update report room position * lint * Improve test coverage
1 parent 1f9db9f commit cb657d6

File tree

11 files changed

+196
-102
lines changed

11 files changed

+196
-102
lines changed

playwright/e2e/right-panel/right-panel.spec.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,30 @@ test.describe("RightPanel", () => {
136136
});
137137
test.describe("room reporting", () => {
138138
test.skip(isDendrite, "Dendrite does not implement room reporting");
139-
test("should handle reporting a room", async ({ page, app }) => {
139+
test("should handle reporting a room", { tag: "@screenshot" }, async ({ page, app }) => {
140140
await viewRoomSummaryByName(page, app, ROOM_NAME);
141+
141142
await page.getByRole("menuitem", { name: "Report room" }).click();
142143
const dialog = await page.getByRole("dialog", { name: "Report Room" });
143144
await dialog.getByLabel("reason").fill("This room should be reported");
145+
await expect(dialog).toMatchScreenshot("room-report-dialog.png");
144146
await dialog.getByRole("button", { name: "Send report" }).click();
145-
await expect(page.getByText("Your report was sent.")).toBeVisible();
147+
148+
// Dialog should have gone
149+
await expect(page.locator(".mx_Dialog")).toHaveCount(0);
150+
});
151+
test("should handle reporting a room and leaving the room", async ({ page, app }) => {
152+
await viewRoomSummaryByName(page, app, ROOM_NAME);
153+
154+
await page.getByRole("menuitem", { name: "Report room" }).click();
155+
const dialog = await page.getByRole("dialog", { name: "Report room" });
156+
await dialog.getByRole("switch", { name: "Leave room" }).click();
157+
await dialog.getByLabel("reason").fill("This room should be reported");
158+
await dialog.getByRole("button", { name: "Send report" }).click();
159+
await page.getByRole("dialog", { name: "Leave room" }).getByRole("button", { name: "Leave" }).click();
160+
161+
// Dialog should have gone
162+
await expect(page.locator(".mx_Dialog")).toHaveCount(0);
146163
});
147164
});
148165
});
Loading
Loading

res/css/views/dialogs/_ReportRoomDialog.pcss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@ Please see LICENSE files in the repository root for full details.
1313
border-radius: 0.5rem;
1414
padding: var(--cpd-space-3x) var(--cpd-space-4x);
1515
}
16+
17+
label {
18+
color: var(--cpd-color-text-primary);
19+
font-weight: var(--cpd-font-weight-semibold);
20+
}
1621
}

src/components/views/dialogs/ReportRoomDialog.tsx

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@ Please see LICENSE files in the repository root for full details.
66
*/
77

88
import React, { type JSX, type ChangeEventHandler, useCallback, useState } from "react";
9-
import { Root, Field, Label, InlineSpinner, ErrorMessage } from "@vector-im/compound-web";
9+
import { Root, Field, Label, InlineSpinner, ErrorMessage, HelpMessage } from "@vector-im/compound-web";
1010

1111
import { _t } from "../../../languageHandler";
1212
import SdkConfig from "../../../SdkConfig";
1313
import Markdown from "../../../Markdown";
1414
import BaseDialog from "./BaseDialog";
1515
import DialogButtons from "../elements/DialogButtons";
1616
import { MatrixClientPeg } from "../../../MatrixClientPeg";
17+
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
1718

1819
interface IProps {
1920
roomId: string;
20-
onFinished(complete: boolean): void;
21+
onFinished(leave: boolean): void;
2122
}
2223

2324
/*
@@ -27,27 +28,26 @@ interface IProps {
2728
export const ReportRoomDialog: React.FC<IProps> = function ({ roomId, onFinished }) {
2829
const [error, setErr] = useState<string>();
2930
const [busy, setBusy] = useState(false);
30-
const [sent, setSent] = useState(false);
3131
const [reason, setReason] = useState("");
32+
const [leaveRoom, setLeaveRoom] = useState(false);
3233
const client = MatrixClientPeg.safeGet();
3334

3435
const onReasonChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>((e) => setReason(e.target.value), []);
35-
const onCancel = useCallback(() => onFinished(sent), [sent, onFinished]);
36+
const onCancel = useCallback(() => onFinished(false), [onFinished]);
3637
const onSubmit = useCallback(async () => {
3738
setBusy(true);
3839
try {
3940
await client.reportRoom(roomId, reason);
40-
setSent(true);
41+
onFinished(leaveRoom);
4142
} catch (ex) {
43+
setBusy(false);
4244
if (ex instanceof Error) {
4345
setErr(ex.message);
4446
} else {
4547
setErr("Unknown error");
4648
}
47-
} finally {
48-
setBusy(false);
4949
}
50-
}, [roomId, reason, client]);
50+
}, [roomId, reason, client, leaveRoom, onFinished]);
5151

5252
const adminMessageMD = SdkConfig.getObject("report_event")?.get("admin_message_md", "adminMessageMD");
5353
let adminMessage: JSX.Element | undefined;
@@ -59,37 +59,39 @@ export const ReportRoomDialog: React.FC<IProps> = function ({ roomId, onFinished
5959
return (
6060
<BaseDialog
6161
className="mx_ReportRoomDialog"
62-
onFinished={() => onFinished(sent)}
63-
title={_t("report_room|title")}
62+
onFinished={onCancel}
63+
title={_t("action|report_room")}
6464
contentId="mx_ReportEventDialog"
6565
>
66-
{sent && <p>{_t("report_room|sent")}</p>}
67-
{!sent && (
68-
<Root id="mx_ReportEventDialog" onSubmit={onSubmit}>
69-
<p>{_t("report_room|description")}</p>
70-
{adminMessage}
71-
<Field name="reason">
72-
<Label htmlFor="mx_ReportRoomDialog_reason">{_t("room_settings|permissions|ban_reason")}</Label>
73-
<textarea
74-
id="mx_ReportRoomDialog_reason"
75-
placeholder={_t("report_room|reason_placeholder")}
76-
rows={5}
77-
onChange={onReasonChange}
78-
value={reason}
79-
disabled={busy}
80-
/>
81-
{error ? <ErrorMessage>{error}</ErrorMessage> : null}
82-
</Field>
83-
{busy ? <InlineSpinner /> : null}
84-
<DialogButtons
85-
primaryButton={_t("action|send_report")}
86-
onPrimaryButtonClick={onSubmit}
87-
focus={true}
88-
onCancel={onCancel}
66+
<Root id="mx_ReportEventDialog" onSubmit={onSubmit}>
67+
<Field name="reason">
68+
<Label htmlFor="mx_ReportRoomDialog_reason">{_t("report_room|reason_label")}</Label>
69+
<textarea
70+
id="mx_ReportRoomDialog_reason"
71+
rows={5}
72+
onChange={onReasonChange}
73+
value={reason}
8974
disabled={busy}
9075
/>
91-
</Root>
92-
)}
76+
{error ? <ErrorMessage>{error}</ErrorMessage> : null}
77+
<HelpMessage>{_t("report_room|description")}</HelpMessage>
78+
</Field>
79+
{adminMessage}
80+
{busy ? <InlineSpinner /> : null}
81+
<LabelledToggleSwitch
82+
label={_t("room_list|more_options|leave_room")}
83+
value={leaveRoom}
84+
onChange={setLeaveRoom}
85+
/>
86+
<DialogButtons
87+
primaryButton={_t("action|send_report")}
88+
onPrimaryButtonClick={onSubmit}
89+
focus={true}
90+
onCancel={onCancel}
91+
primaryButtonClass="danger"
92+
primaryDisabled={busy || !reason}
93+
/>
94+
</Root>
9395
</BaseDialog>
9496
);
9597
};

src/components/views/right_panel/RoomSummaryCard.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,16 @@ const RoomSummaryCard: React.FC<IProps> = ({
233233
room_id: room.roomId,
234234
});
235235
};
236-
const onReportRoomClick = (): void => {
237-
Modal.createDialog(ReportRoomDialog, {
236+
const onReportRoomClick = async (): Promise<void> => {
237+
const [leave] = await Modal.createDialog(ReportRoomDialog, {
238238
roomId: room.roomId,
239-
});
239+
}).finished;
240+
if (leave) {
241+
defaultDispatcher.dispatch({
242+
action: "leave_room",
243+
room_id: room.roomId,
244+
});
245+
}
240246
};
241247

242248
const isRoomEncrypted = useIsEncrypted(cli, room);
@@ -447,19 +453,19 @@ const RoomSummaryCard: React.FC<IProps> = ({
447453

448454
<Separator />
449455
<div className="mx_RoomSummaryCard_bottomOptions">
456+
<MenuItem
457+
Icon={ErrorIcon}
458+
kind="critical"
459+
label={_t("action|report_room")}
460+
onSelect={onReportRoomClick}
461+
/>
450462
<MenuItem
451463
className="mx_RoomSummaryCard_leave"
452464
Icon={LeaveIcon}
453465
kind="critical"
454466
label={_t("action|leave_room")}
455467
onSelect={onLeaveRoomClick}
456468
/>
457-
<MenuItem
458-
Icon={ErrorIcon}
459-
kind="critical"
460-
label={_t("action|report_room")}
461-
onSelect={onReportRoomClick}
462-
/>
463469
</div>
464470
</div>
465471
</BaseCard>

src/i18n/strings/en_EN.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,10 +1827,8 @@
18271827
"toxic_behaviour": "Toxic Behaviour"
18281828
},
18291829
"report_room": {
1830-
"description": "Report this room to your homeserver admin. This will send the room's unique ID, but if messages are encrypted, the administrator won't be able to read them or view shared files.",
1831-
"reason_placeholder": " Reason for reporting...",
1832-
"sent": "Your report was sent.",
1833-
"title": "Report Room"
1830+
"description": "Report this room to your account provider. If the messages are encrypted, your admin will not be able to read them.",
1831+
"reason_label": "Describe the reason"
18341832
},
18351833
"restore_key_backup_dialog": {
18361834
"count_of_decryption_failures": "Failed to decrypt %(failedCount)s sessions!",

test/unit-tests/components/views/dialogs/ReportRoomDialog-test.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import SdkConfig from "../../../../../src/SdkConfig";
1414
import { stubClient } from "../../../../test-utils";
1515

1616
const ROOM_ID = "!foo:bar";
17+
const REASON = "This room is bad!";
1718

1819
describe("ReportRoomDialog", () => {
1920
const onFinished: jest.Mock<any, any> = jest.fn();
@@ -48,19 +49,24 @@ This doesn't actually go **anywhere**.`,
4849
expect(container).toMatchSnapshot();
4950
});
5051

51-
it("can submit a report", async () => {
52-
const REASON = "This room is bad!";
53-
const { getByLabelText, getByText, getByRole } = render(
54-
<ReportRoomDialog roomId={ROOM_ID} onFinished={onFinished} />,
55-
);
52+
it("can submit a report without leaving the room", async () => {
53+
const { getByLabelText, getByRole } = render(<ReportRoomDialog roomId={ROOM_ID} onFinished={onFinished} />);
5654

57-
await userEvent.type(getByLabelText("Reason"), REASON);
55+
await userEvent.type(getByLabelText("Describe the reason"), REASON);
5856
await userEvent.click(getByRole("button", { name: "Send report" }));
5957

6058
expect(reportRoom).toHaveBeenCalledWith(ROOM_ID, REASON);
61-
expect(getByText("Your report was sent.")).toBeInTheDocument();
59+
expect(onFinished).toHaveBeenCalledWith(false);
60+
});
61+
62+
it("can submit a report and leave the room", async () => {
63+
const { getByLabelText, getByRole } = render(<ReportRoomDialog roomId={ROOM_ID} onFinished={onFinished} />);
6264

63-
await userEvent.click(getByRole("button", { name: "Close dialog" }));
65+
await userEvent.type(getByLabelText("Describe the reason"), REASON);
66+
await userEvent.click(getByRole("switch", { name: "Leave room" }));
67+
await userEvent.click(getByRole("button", { name: "Send report" }));
68+
69+
expect(reportRoom).toHaveBeenCalledWith(ROOM_ID, REASON);
6470
expect(onFinished).toHaveBeenCalledWith(true);
6571
});
6672
});

test/unit-tests/components/views/dialogs/__snapshots__/ReportRoomDialog-test.tsx.snap

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,33 @@ exports[`ReportRoomDialog displays admin message 1`] = `
2121
class="mx_Heading_h3 mx_Dialog_title"
2222
id="mx_BaseDialog_title"
2323
>
24-
Report Room
24+
Report room
2525
</h1>
2626
</div>
2727
<form
2828
class="_root_19upo_16"
2929
id="mx_ReportEventDialog"
3030
>
31-
<p>
32-
Report this room to your homeserver admin. This will send the room's unique ID, but if messages are encrypted, the administrator won't be able to read them or view shared files.
33-
</p>
31+
<div
32+
class="_field_19upo_26"
33+
>
34+
<label
35+
class="_label_19upo_59"
36+
for="mx_ReportRoomDialog_reason"
37+
>
38+
Describe the reason
39+
</label>
40+
<textarea
41+
id="mx_ReportRoomDialog_reason"
42+
rows="5"
43+
/>
44+
<span
45+
class="_message_19upo_85 _help-message_19upo_91"
46+
id="radix-:r7:"
47+
>
48+
Report this room to your account provider. If the messages are encrypted, your admin will not be able to read them.
49+
</span>
50+
</div>
3451
<p>
3552
<h1>
3653
You should know
@@ -48,19 +65,29 @@ exports[`ReportRoomDialog displays admin message 1`] = `
4865
4966
</p>
5067
<div
51-
class="_field_19upo_26"
68+
class="mx_SettingsFlag"
5269
>
53-
<label
54-
class="_label_19upo_59"
55-
for="mx_ReportRoomDialog_reason"
70+
<span
71+
class="mx_SettingsFlag_label"
5672
>
57-
Reason
58-
</label>
59-
<textarea
60-
id="mx_ReportRoomDialog_reason"
61-
placeholder=" Reason for reporting..."
62-
rows="5"
63-
/>
73+
<div
74+
id="mx_LabelledToggleSwitch_undefined"
75+
>
76+
Leave room
77+
</div>
78+
</span>
79+
<div
80+
aria-checked="false"
81+
aria-disabled="false"
82+
aria-labelledby="mx_LabelledToggleSwitch_undefined"
83+
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
84+
role="switch"
85+
tabindex="0"
86+
>
87+
<div
88+
class="mx_ToggleSwitch_ball"
89+
/>
90+
</div>
6491
</div>
6592
<div
6693
class="mx_Dialog_buttons"
@@ -75,8 +102,9 @@ exports[`ReportRoomDialog displays admin message 1`] = `
75102
Cancel
76103
</button>
77104
<button
78-
class="mx_Dialog_primary"
105+
class="mx_Dialog_primary danger"
79106
data-testid="dialog-primary-button"
107+
disabled=""
80108
type="button"
81109
>
82110
Send report

0 commit comments

Comments
 (0)