fix: comprehensive P0 quality pass — audit findings resolved
- ReportRoomModal: use mx.reportRoom() SDK method, fix undefined CSS vars
(--mx-surface/border → folds color tokens), add role/aria-modal/aria-labelledby,
accessible select/input labels, per-error-code messages, auto-close on success
- About.tsx: clickable matrix_id + email_address links (Text as="a"), AbortController
cleanup, runtime JSON type guard, loading state, role display for all role values,
remove classList theming hack, use mx.getHomeserverUrl()
- RoomViewHeader: useLocalRoomName for header title, useReportRoomSupported gate,
hide Invite/Settings/Report for server notice rooms, isCreator guard on Report,
FocusTrap returnFocusOnDeactivate on topic overlay, Server Notice chip tooltip
- RoomInput: replace raw <div> with folds <Box> for server notice read-only message
- EditHistoryModal: isRawEditEvent type guard, handle next_batch truncation,
getVersionBody handles formatted_body (strips HTML for text display),
role/aria-modal/aria-labelledby accessibility, guard for undefined eventId,
use config.space tokens (remove var(--mx-spacing-*) strings)
- RoomNavItem: remove duplicate getExistingContent (use exported getLocalRoomNamesContent),
maxLength={255} on rename input, fix FocusTrap nesting (renameDialog state moved to
RoomNavItem_, RenameRoomDialog rendered outside menu, menu closes before dialog opens),
pencil icon opacity via config.opacity.P300
- useRoomMeta: export getLocalRoomNamesContent for reuse
- RoomIntro: useLocalRoomName, formatted topic viewer with Overlay/FocusTrap/RoomTopicViewer
- CallRoomName: useLocalRoomName for consistent rename display in call overlay
- General.tsx: fix #980000/#FF6B00 hardcoded hex → color tokens/CSS vars, URL Preview
capitalization, improved encrypted preview warning text + Warning chip, add
description to plain urlPreview setting
- sanitize.ts: fix hex color regex to support 3/4/6/8 digit hex (CSS4 #RGBA, #RRGGBBAA)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { FormEventHandler, useCallback, useState } from 'react';
|
||||
import React, { FormEventHandler, useCallback, useEffect, useState } from 'react';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import {
|
||||
Box,
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
color,
|
||||
Spinner,
|
||||
} from 'folds';
|
||||
import { Method } from 'matrix-js-sdk';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
@@ -42,17 +41,20 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
const [reportState, submitReport] = useAsyncCallback(
|
||||
useCallback(
|
||||
async (reason: string) => {
|
||||
await mx.http.authedRequest(
|
||||
Method.Post,
|
||||
`/rooms/${encodeURIComponent(roomId)}/report`,
|
||||
undefined,
|
||||
{ reason },
|
||||
);
|
||||
await mx.reportRoom(roomId, reason);
|
||||
},
|
||||
[mx, roomId],
|
||||
),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (reportState.status === AsyncStatus.Success) {
|
||||
const timer = setTimeout(onClose, 1500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
return undefined;
|
||||
}, [reportState.status, onClose]);
|
||||
|
||||
const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
|
||||
evt.preventDefault();
|
||||
if (reportState.status === AsyncStatus.Loading || reportState.status === AsyncStatus.Success) {
|
||||
@@ -61,12 +63,20 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
const target = evt.target as HTMLFormElement;
|
||||
const reasonInput = target.elements.namedItem('reasonInput') as HTMLInputElement | null;
|
||||
const reasonText = reasonInput?.value.trim() ?? '';
|
||||
const fullReason = reasonText
|
||||
? `[${CATEGORY_LABELS[category]}] ${reasonText}`
|
||||
: `[${CATEGORY_LABELS[category]}]`;
|
||||
const fullReason = `[${CATEGORY_LABELS[category]}] ${reasonText}`;
|
||||
submitReport(fullReason);
|
||||
};
|
||||
|
||||
const errcode = (reportState.status === AsyncStatus.Error
|
||||
? (reportState.error as { errcode?: string })?.errcode
|
||||
: undefined);
|
||||
const errorMsg =
|
||||
errcode === 'M_LIMIT_EXCEEDED'
|
||||
? 'You are being rate limited. Please wait before reporting again.'
|
||||
: errcode === 'M_FORBIDDEN'
|
||||
? 'You cannot report this room.'
|
||||
: 'Failed to submit report. Please try again.';
|
||||
|
||||
return (
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
<OverlayCenter>
|
||||
@@ -80,12 +90,15 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
>
|
||||
<Box
|
||||
as="form"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="report-room-dialog-title"
|
||||
onSubmit={handleSubmit}
|
||||
direction="Column"
|
||||
style={{
|
||||
background: 'var(--mx-surface)',
|
||||
background: color.Surface.Container,
|
||||
borderRadius: config.radii.R400,
|
||||
boxShadow: '0 8px 32px rgba(0,0,0,0.55)',
|
||||
boxShadow: color.Other.Shadow,
|
||||
width: '100%',
|
||||
maxWidth: 420,
|
||||
overflow: 'hidden',
|
||||
@@ -100,7 +113,7 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
size="500"
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text size="H4">Report Room</Text>
|
||||
<Text id="report-room-dialog-title" size="H4">Report Room</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={onClose} radii="300" aria-label="Close">
|
||||
<Icon src={Icons.Cross} />
|
||||
@@ -113,9 +126,11 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
</Text>
|
||||
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Category</Text>
|
||||
<Text as="label" htmlFor="report-category" size="L400">Category</Text>
|
||||
<Box
|
||||
as="select"
|
||||
id="report-category"
|
||||
aria-label="Report category"
|
||||
value={category}
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
setCategory(e.target.value as ReportCategory)
|
||||
@@ -123,9 +138,9 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
style={{
|
||||
padding: `${config.space.S200} ${config.space.S300}`,
|
||||
borderRadius: config.radii.R300,
|
||||
border: '1px solid var(--mx-border)',
|
||||
background: 'var(--mx-bg-surface)',
|
||||
color: 'var(--mx-c-surface-on)',
|
||||
border: `1px solid ${color.Surface.ContainerLine}`,
|
||||
background: color.Surface.Container,
|
||||
color: color.Surface.OnContainer,
|
||||
fontSize: 'inherit',
|
||||
fontFamily: 'inherit',
|
||||
width: '100%',
|
||||
@@ -140,11 +155,17 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
||||
</Box>
|
||||
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Reason</Text>
|
||||
<Input name="reasonInput" variant="Background" required />
|
||||
<Text as="label" htmlFor="report-reason-input" size="L400">Reason</Text>
|
||||
<Input
|
||||
id="report-reason-input"
|
||||
name="reasonInput"
|
||||
aria-label="Reason for report"
|
||||
variant="Background"
|
||||
required
|
||||
/>
|
||||
{reportState.status === AsyncStatus.Error && (
|
||||
<Text style={{ color: color.Critical.Main }} size="T300">
|
||||
Failed to submit report. Please try again.
|
||||
{errorMsg}
|
||||
</Text>
|
||||
)}
|
||||
{reportState.status === AsyncStatus.Success && (
|
||||
|
||||
Reference in New Issue
Block a user