New invite user to room dialog (#2460)

* fix 0 displayed in invite with no timestamp

* support displaying invite reason for receiver

* show invite reason as compact message

* remove unused import

* revert: show invite reason as compact message

* remove unused import

* add new invite prompt
This commit is contained in:
Ajay Bura
2025-08-24 18:04:21 +05:30
committed by GitHub
parent c881b59957
commit 13cdcbcdb1
10 changed files with 434 additions and 56 deletions

View File

@@ -81,6 +81,7 @@ type InviteData = {
senderId: string;
senderName: string;
inviteTs?: number;
reason?: string;
isSpace: boolean;
isDirect: boolean;
@@ -102,11 +103,17 @@ const makeInviteData = (mx: MatrixClient, room: Room, useAuthentication: boolean
const member = room.getMember(userId);
const memberEvent = member?.events.member;
const content = memberEvent?.getContent();
const senderId = memberEvent?.getSender();
const senderName = senderId
? getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId
: undefined;
const inviteTs = memberEvent?.getTs() ?? 0;
const inviteTs = memberEvent?.getTs();
const reason =
content && 'reason' in content && typeof content.reason === 'string'
? content.reason
: undefined;
return {
room,
@@ -119,6 +126,7 @@ const makeInviteData = (mx: MatrixClient, room: Room, useAuthentication: boolean
senderId: senderId ?? 'Unknown',
senderName: senderName ?? 'Unknown',
inviteTs,
reason,
isSpace: isSpace(room),
isDirect: direct,
@@ -130,7 +138,8 @@ const hasBadWords = (invite: InviteData): boolean =>
testBadWords(invite.roomName) ||
testBadWords(invite.roomTopic ?? '') ||
testBadWords(invite.senderName) ||
testBadWords(invite.senderId);
testBadWords(invite.senderId) ||
testBadWords(invite.reason || '');
type NavigateHandler = (roomId: string, space: boolean) => void;
@@ -184,7 +193,7 @@ function InviteCard({
variant="SurfaceVariant"
direction="Column"
gap="300"
style={{ padding: `${config.space.S400} ${config.space.S400} ${config.space.S200}` }}
style={{ padding: config.space.S400 }}
>
{(invite.isEncrypted || invite.isDirect || invite.isSpace) && (
<Box gap="200" alignItems="Center">
@@ -298,22 +307,29 @@ function InviteCard({
</Box>
</Box>
</Box>
<Box gap="200" alignItems="Baseline">
<Box grow="Yes">
<Text size="T200" priority="300">
From: <b>{invite.senderId}</b>
</Text>
</Box>
{invite.inviteTs && (
<Box shrink="No">
<Time
size="T200"
ts={invite.inviteTs}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
priority="300"
/>
<Box direction="Column">
<Box gap="200" alignItems="Baseline">
<Box grow="Yes">
<Text size="T200" priority="300">
From: <b>{invite.senderId}</b>
</Text>
</Box>
{typeof invite.inviteTs === 'number' && invite.inviteTs !== 0 && (
<Box shrink="No">
<Time
size="T200"
ts={invite.inviteTs}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
priority="300"
/>
</Box>
)}
</Box>
{invite.reason && (
<Text size="T200" priority="300">
Reason: {invite.reason}
</Text>
)}
</Box>
</SequenceCard>

View File

@@ -82,7 +82,6 @@ import { useRoomsUnread } from '../../../state/hooks/unread';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { markAsRead } from '../../../../client/action/notifications';
import { copyToClipboard } from '../../../utils/dom';
import { openInviteUser } from '../../../../client/action/navigation';
import { stopPropagation } from '../../../utils/keyboard';
import { getMatrixToRoom } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers';
@@ -93,6 +92,7 @@ import { settingsAtom } from '../../../state/settings';
import { useOpenSpaceSettings } from '../../../state/hooks/spaceSettings';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
import { InviteUserPrompt } from '../../../components/invite-user-prompt';
type SpaceMenuProps = {
room: Room;
@@ -111,6 +111,8 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
const canInvite = permissions.action('invite', mx.getSafeUserId());
const openSpaceSettings = useOpenSpaceSettings();
const [invitePrompt, setInvitePrompt] = useState(false);
const allChild = useSpaceChildren(
allRoomsAtom,
room.roomId,
@@ -136,8 +138,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
};
const handleInvite = () => {
openInviteUser(room.roomId);
requestClose();
setInvitePrompt(true);
};
const handleRoomSettings = () => {
@@ -147,6 +148,15 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
return (
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
{invitePrompt && room && (
<InviteUserPrompt
room={room}
requestClose={() => {
setInvitePrompt(false);
requestClose();
}}
/>
)}
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
<MenuItem
onClick={handleMarkAsRead}
@@ -181,6 +191,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
size="300"
after={<Icon size="100" src={Icons.UserPlus} />}
radii="300"
aria-pressed={invitePrompt}
disabled={!canInvite}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>

View File

@@ -57,7 +57,6 @@ import { useSpaceJoinedHierarchy } from '../../../hooks/useSpaceHierarchy';
import { allRoomsAtom } from '../../../state/room-list/roomList';
import { PageNav, PageNavContent, PageNavHeader } from '../../../components/page';
import { usePowerLevels } from '../../../hooks/usePowerLevels';
import { openInviteUser } from '../../../../client/action/navigation';
import { useRecursiveChildScopeFactory, useSpaceChildren } from '../../../state/hooks/roomList';
import { roomToParentsAtom } from '../../../state/room/roomToParents';
import { markAsRead } from '../../../../client/action/notifications';
@@ -84,6 +83,7 @@ import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
import { ContainerColor } from '../../../styles/ContainerColor.css';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { BreakWord } from '../../../styles/Text.css';
import { InviteUserPrompt } from '../../../components/invite-user-prompt';
type SpaceMenuProps = {
room: Room;
@@ -102,6 +102,8 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClo
const openSpaceSettings = useOpenSpaceSettings();
const { navigateRoom } = useRoomNavigate();
const [invitePrompt, setInvitePrompt] = useState(false);
const allChild = useSpaceChildren(
allRoomsAtom,
room.roomId,
@@ -122,8 +124,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClo
};
const handleInvite = () => {
openInviteUser(room.roomId);
requestClose();
setInvitePrompt(true);
};
const handleRoomSettings = () => {
@@ -139,6 +140,15 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClo
return (
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
{invitePrompt && room && (
<InviteUserPrompt
room={room}
requestClose={() => {
setInvitePrompt(false);
requestClose();
}}
/>
)}
<MenuItem
onClick={handleMarkAsRead}
size="300"
@@ -160,6 +170,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClo
size="300"
after={<Icon size="100" src={Icons.UserPlus} />}
radii="300"
aria-pressed={invitePrompt}
disabled={!canInvite}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>