feat: extend verification badge to user profile and settings members list
CI / Build & Quality Checks (push) Successful in 10m22s
CI / Build & Quality Checks (push) Successful in 10m22s
Extract MemberVerificationBadge into a shared component and render it in: - UserRoomProfile: shield badge beside the display name on the profile card - common-settings Members: badge next to each member in the room/space settings members page (accessible from the lobby) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Icon, Icons, Text, Tooltip, TooltipProvider } from 'folds';
|
||||||
|
import { useUserVerifiedStatus } from '../hooks/useUserVerifiedStatus';
|
||||||
|
|
||||||
|
type MemberVerificationBadgeProps = {
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function MemberVerificationBadge({ userId }: MemberVerificationBadgeProps) {
|
||||||
|
const vs = useUserVerifiedStatus(userId);
|
||||||
|
if (vs === 'unknown') return null;
|
||||||
|
const color =
|
||||||
|
vs === 'verified' ? 'var(--tc-positive-normal, #5effc4)' : 'var(--tc-warning-normal, #ffcc55)';
|
||||||
|
const label = vs === 'verified' ? 'Identity verified' : 'Not verified';
|
||||||
|
return (
|
||||||
|
<TooltipProvider
|
||||||
|
position="Top"
|
||||||
|
tooltip={
|
||||||
|
<Tooltip>
|
||||||
|
<Text size="T200">{label}</Text>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(ref) => (
|
||||||
|
<span
|
||||||
|
ref={ref}
|
||||||
|
title={label}
|
||||||
|
style={{ display: 'inline-flex', alignItems: 'center', flexShrink: 0 }}
|
||||||
|
>
|
||||||
|
<Icon size="100" src={Icons.ShieldUser} style={{ color }} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import { usePowerLevels } from '../../hooks/usePowerLevels';
|
|||||||
import { useRoom } from '../../hooks/useRoom';
|
import { useRoom } from '../../hooks/useRoom';
|
||||||
import { useUserPresence } from '../../hooks/useUserPresence';
|
import { useUserPresence } from '../../hooks/useUserPresence';
|
||||||
import { IgnoredUserAlert, MutualRoomsChip, OptionsChip, ServerChip, ShareChip } from './UserChips';
|
import { IgnoredUserAlert, MutualRoomsChip, OptionsChip, ServerChip, ShareChip } from './UserChips';
|
||||||
|
import { useCrossSigningActive } from '../../hooks/useCrossSigning';
|
||||||
|
import { MemberVerificationBadge } from '../MemberVerificationBadge';
|
||||||
import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
||||||
import { PowerChip } from './PowerChip';
|
import { PowerChip } from './PowerChip';
|
||||||
import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration';
|
import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration';
|
||||||
@@ -28,6 +30,7 @@ type UserRoomProfileProps = {
|
|||||||
};
|
};
|
||||||
export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const crossSigningActive = useCrossSigningActive();
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const closeUserRoomProfile = useCloseUserRoomProfile();
|
const closeUserRoomProfile = useCloseUserRoomProfile();
|
||||||
@@ -35,6 +38,7 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
|||||||
const ignored = ignoredUsers.includes(userId);
|
const ignored = ignoredUsers.includes(userId);
|
||||||
|
|
||||||
const room = useRoom();
|
const room = useRoom();
|
||||||
|
const showEncryption = room.hasEncryptionStateEvent() && crossSigningActive;
|
||||||
const powerLevels = usePowerLevels(room);
|
const powerLevels = usePowerLevels(room);
|
||||||
const creators = useRoomCreators(room);
|
const creators = useRoomCreators(room);
|
||||||
|
|
||||||
@@ -76,8 +80,9 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
|||||||
/>
|
/>
|
||||||
<Box direction="Column" gap="500" style={{ padding: config.space.S400 }}>
|
<Box direction="Column" gap="500" style={{ padding: config.space.S400 }}>
|
||||||
<Box direction="Column" gap="400">
|
<Box direction="Column" gap="400">
|
||||||
<Box gap="400" alignItems="Start">
|
<Box gap="400" alignItems="Center">
|
||||||
<UserHeroName displayName={displayName} userId={userId} />
|
<UserHeroName displayName={displayName} userId={userId} />
|
||||||
|
{showEncryption && <MemberVerificationBadge userId={userId} />}
|
||||||
{userId !== myUserId && (
|
{userId !== myUserId && (
|
||||||
<Box shrink="No">
|
<Box shrink="No">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ import { useSpaceOptionally } from '../../../hooks/useSpace';
|
|||||||
import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../../hooks/useMemberPowerTag';
|
import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../../hooks/useMemberPowerTag';
|
||||||
import { useRoomCreators } from '../../../hooks/useRoomCreators';
|
import { useRoomCreators } from '../../../hooks/useRoomCreators';
|
||||||
import { getMouseEventCords } from '../../../utils/dom';
|
import { getMouseEventCords } from '../../../utils/dom';
|
||||||
|
import { useCrossSigningActive } from '../../../hooks/useCrossSigning';
|
||||||
|
import { MemberVerificationBadge } from '../../../components/MemberVerificationBadge';
|
||||||
|
|
||||||
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
|
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
@@ -79,6 +81,8 @@ export function Members({ requestClose }: MembersProps) {
|
|||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
const room = useRoom();
|
const room = useRoom();
|
||||||
const members = useRoomMembers(mx, room.roomId);
|
const members = useRoomMembers(mx, room.roomId);
|
||||||
|
const crossSigningActive = useCrossSigningActive();
|
||||||
|
const showEncryption = room.hasEncryptionStateEvent() && crossSigningActive;
|
||||||
const fetchingMembers = members.length < room.getJoinedMemberCount();
|
const fetchingMembers = members.length < room.getJoinedMemberCount();
|
||||||
const openProfile = useOpenUserRoomProfile();
|
const openProfile = useOpenUserRoomProfile();
|
||||||
const profileUser = useUserRoomProfileState();
|
const profileUser = useUserRoomProfileState();
|
||||||
@@ -328,11 +332,16 @@ export function Members({ requestClose }: MembersProps) {
|
|||||||
member={tagOrMember}
|
member={tagOrMember}
|
||||||
useAuthentication={useAuthentication}
|
useAuthentication={useAuthentication}
|
||||||
after={
|
after={
|
||||||
server && (
|
<>
|
||||||
<Box as="span" shrink="No" alignSelf="End">
|
{showEncryption && (
|
||||||
<ServerBadge server={server} fill="None" />
|
<MemberVerificationBadge userId={tagOrMember.userId} />
|
||||||
</Box>
|
)}
|
||||||
)
|
{server && (
|
||||||
|
<Box as="span" shrink="No" alignSelf="End">
|
||||||
|
<ServerBadge server={server} fill="None" />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ import { ContainerColor } from '../../styles/ContainerColor.css';
|
|||||||
import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
|
import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
|
||||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||||
import { useCrossSigningActive } from '../../hooks/useCrossSigning';
|
import { useCrossSigningActive } from '../../hooks/useCrossSigning';
|
||||||
import { useUserVerifiedStatus } from '../../hooks/useUserVerifiedStatus';
|
import { MemberVerificationBadge } from '../../components/MemberVerificationBadge';
|
||||||
|
|
||||||
type MemberDrawerHeaderProps = {
|
type MemberDrawerHeaderProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
@@ -115,34 +115,6 @@ type MemberItemProps = {
|
|||||||
showEncryption?: boolean;
|
showEncryption?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function MemberVerificationBadge({ userId }: { userId: string }) {
|
|
||||||
const vs = useUserVerifiedStatus(userId);
|
|
||||||
if (vs === 'unknown') return null;
|
|
||||||
const color =
|
|
||||||
vs === 'verified' ? 'var(--tc-positive-normal, #5effc4)' : 'var(--tc-warning-normal, #ffcc55)';
|
|
||||||
const label = vs === 'verified' ? 'Identity verified' : 'Not verified';
|
|
||||||
return (
|
|
||||||
<TooltipProvider
|
|
||||||
position="Top"
|
|
||||||
tooltip={
|
|
||||||
<Tooltip>
|
|
||||||
<Text size="T200">{label}</Text>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{(ref) => (
|
|
||||||
<span
|
|
||||||
ref={ref}
|
|
||||||
title={label}
|
|
||||||
style={{ display: 'inline-flex', alignItems: 'center', flexShrink: 0 }}
|
|
||||||
>
|
|
||||||
<Icon size="50" src={Icons.ShieldUser} style={{ color }} />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MemberItem({
|
function MemberItem({
|
||||||
mx,
|
mx,
|
||||||
useAuthentication,
|
useAuthentication,
|
||||||
|
|||||||
Reference in New Issue
Block a user