Add new verification section to user profile (#29200)
* Create new verification section * Remove old code and use new VerificationSection * Add styling and translation * Fix tests * Remove dead code * Fix broken test * Remove imports * Remove console.log * Update snapshots * Fix broken tests * Fix lint * Make badge expand with content * Remove unused code
This commit is contained in:
@@ -25,7 +25,8 @@ import {
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { type UserVerificationStatus, type VerificationRequest, CryptoEvent } from "matrix-js-sdk/src/crypto-api";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Heading, MenuItem, Text, Tooltip } from "@vector-im/compound-web";
|
||||
import { Badge, Button, Heading, InlineSpinner, MenuItem, Text, Tooltip } from "@vector-im/compound-web";
|
||||
import VerifiedIcon from "@vector-im/compound-design-tokens/assets/web/icons/verified";
|
||||
import ChatIcon from "@vector-im/compound-design-tokens/assets/web/icons/chat";
|
||||
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
|
||||
import ShareIcon from "@vector-im/compound-design-tokens/assets/web/icons/share";
|
||||
@@ -42,21 +43,19 @@ import dis from "../../../dispatcher/dispatcher";
|
||||
import Modal from "../../../Modal";
|
||||
import { _t, UserFriendlyError } from "../../../languageHandler";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
|
||||
import { type ButtonEvent } from "../elements/AccessibleButton";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import MultiInviter from "../../../utils/MultiInviter";
|
||||
import E2EIcon from "../rooms/E2EIcon";
|
||||
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
|
||||
import { textualPowerLevel } from "../../../Roles";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import EncryptionPanel from "./EncryptionPanel";
|
||||
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
|
||||
import { verifyDevice, verifyUser } from "../../../verification";
|
||||
import { verifyUser } from "../../../verification";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
|
||||
import BaseCard from "./BaseCard";
|
||||
import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||
import ImageView from "../elements/ImageView";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import PowerSelector from "../elements/PowerSelector";
|
||||
@@ -81,7 +80,6 @@ import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { DirectoryMember, startDmOnFirstMessage } from "../../../utils/direct-messages";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
import { asyncSome } from "../../../utils/arrays";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import CopyableText from "../elements/CopyableText";
|
||||
import { useUserTimezone } from "../../../hooks/useUserTimezone";
|
||||
@@ -107,32 +105,6 @@ export const disambiguateDevices = (devices: IDevice[]): void => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getE2EStatus = async (
|
||||
cli: MatrixClient,
|
||||
userId: string,
|
||||
devices: IDevice[],
|
||||
): Promise<E2EStatus | undefined> => {
|
||||
const crypto = cli.getCrypto();
|
||||
if (!crypto) return undefined;
|
||||
const isMe = userId === cli.getUserId();
|
||||
const userTrust = await crypto.getUserVerificationStatus(userId);
|
||||
if (!userTrust.isCrossSigningVerified()) {
|
||||
return userTrust.wasCrossSigningVerified() ? E2EStatus.Warning : E2EStatus.Normal;
|
||||
}
|
||||
|
||||
const anyDeviceUnverified = await asyncSome(devices, async (device) => {
|
||||
const { deviceId } = device;
|
||||
// For your own devices, we use the stricter check of cross-signing
|
||||
// verification to encourage everyone to trust their own devices via
|
||||
// cross-signing so that other users can then safely trust you.
|
||||
// For other people's devices, the more general verified check that
|
||||
// includes locally verified devices can be used.
|
||||
const deviceTrust = await crypto.getDeviceVerificationStatus(userId, deviceId);
|
||||
return isMe ? !deviceTrust?.crossSigningVerified : !deviceTrust?.isVerified();
|
||||
});
|
||||
return anyDeviceUnverified ? E2EStatus.Warning : E2EStatus.Verified;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the member to a DirectoryMember and starts a DM with them.
|
||||
*/
|
||||
@@ -146,251 +118,6 @@ async function openDmForUser(matrixClient: MatrixClient, user: Member): Promise<
|
||||
await startDmOnFirstMessage(matrixClient, [startDmUser]);
|
||||
}
|
||||
|
||||
type SetUpdating = (updating: boolean) => void;
|
||||
|
||||
function useHasCrossSigningKeys(
|
||||
cli: MatrixClient,
|
||||
member: User,
|
||||
canVerify: boolean,
|
||||
setUpdating: SetUpdating,
|
||||
): boolean | undefined {
|
||||
return useAsyncMemo(async () => {
|
||||
if (!canVerify) {
|
||||
return undefined;
|
||||
}
|
||||
setUpdating(true);
|
||||
try {
|
||||
return await cli.getCrypto()?.userHasCrossSigningKeys(member.userId, true);
|
||||
} finally {
|
||||
setUpdating(false);
|
||||
}
|
||||
}, [cli, member, canVerify]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display one device and the related actions
|
||||
* @param userId current user id
|
||||
* @param device device to display
|
||||
* @param isUserVerified false when the user is not verified
|
||||
* @constructor
|
||||
*/
|
||||
export function DeviceItem({
|
||||
userId,
|
||||
device,
|
||||
isUserVerified,
|
||||
}: {
|
||||
userId: string;
|
||||
device: IDevice;
|
||||
isUserVerified: boolean;
|
||||
}): JSX.Element {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const isMe = userId === cli.getUserId();
|
||||
|
||||
/** is the device verified? */
|
||||
const isVerified = useAsyncMemo(async () => {
|
||||
const deviceTrust = await cli.getCrypto()?.getDeviceVerificationStatus(userId, device.deviceId);
|
||||
if (!deviceTrust) return false;
|
||||
|
||||
// For your own devices, we use the stricter check of cross-signing
|
||||
// verification to encourage everyone to trust their own devices via
|
||||
// cross-signing so that other users can then safely trust you.
|
||||
// For other people's devices, the more general verified check that
|
||||
// includes locally verified devices can be used.
|
||||
return isMe ? deviceTrust.crossSigningVerified : deviceTrust.isVerified();
|
||||
}, [cli, userId, device]);
|
||||
|
||||
const classes = classNames("mx_UserInfo_device", {
|
||||
mx_UserInfo_device_verified: isVerified,
|
||||
mx_UserInfo_device_unverified: !isVerified,
|
||||
});
|
||||
const iconClasses = classNames("mx_E2EIcon", {
|
||||
mx_E2EIcon_normal: !isUserVerified,
|
||||
mx_E2EIcon_verified: isVerified,
|
||||
mx_E2EIcon_warning: isUserVerified && !isVerified,
|
||||
});
|
||||
|
||||
const onDeviceClick = (): void => {
|
||||
const user = cli.getUser(userId);
|
||||
if (user) {
|
||||
verifyDevice(cli, user, device);
|
||||
}
|
||||
};
|
||||
|
||||
let deviceName;
|
||||
if (!device.displayName?.trim()) {
|
||||
deviceName = device.deviceId;
|
||||
} else {
|
||||
deviceName = device.ambiguous ? device.displayName + " (" + device.deviceId + ")" : device.displayName;
|
||||
}
|
||||
|
||||
let trustedLabel: string | undefined;
|
||||
if (isUserVerified) trustedLabel = isVerified ? _t("common|trusted") : _t("common|not_trusted");
|
||||
|
||||
if (isVerified === undefined) {
|
||||
// we're still deciding if the device is verified
|
||||
return <div className={classes} title={device.deviceId} />;
|
||||
} else if (isVerified) {
|
||||
return (
|
||||
<div className={classes} title={device.deviceId}>
|
||||
<div className={iconClasses} />
|
||||
<div className="mx_UserInfo_device_name">{deviceName}</div>
|
||||
<div className="mx_UserInfo_device_trusted">{trustedLabel}</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<AccessibleButton
|
||||
className={classes}
|
||||
title={device.deviceId}
|
||||
aria-label={deviceName}
|
||||
onClick={onDeviceClick}
|
||||
>
|
||||
<div className={iconClasses} />
|
||||
<div className="mx_UserInfo_device_name">{deviceName}</div>
|
||||
<div className="mx_UserInfo_device_trusted">{trustedLabel}</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a list of devices
|
||||
* @param devices devices to display
|
||||
* @param userId current user id
|
||||
* @param loading displays a spinner instead of the device section
|
||||
* @param isUserVerified is false when
|
||||
* - the user is not verified, or
|
||||
* - `MatrixClient.getCrypto.getUserVerificationStatus` async call is in progress (in which case `loading` will also be `true`)
|
||||
* @constructor
|
||||
*/
|
||||
function DevicesSection({
|
||||
devices,
|
||||
userId,
|
||||
loading,
|
||||
isUserVerified,
|
||||
}: {
|
||||
devices: IDevice[];
|
||||
userId: string;
|
||||
loading: boolean;
|
||||
isUserVerified: boolean;
|
||||
}): JSX.Element {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const [isExpanded, setExpanded] = useState(false);
|
||||
|
||||
const deviceTrusts = useAsyncMemo(() => {
|
||||
const cryptoApi = cli.getCrypto();
|
||||
if (!cryptoApi) return Promise.resolve(undefined);
|
||||
return Promise.all(devices.map((d) => cryptoApi.getDeviceVerificationStatus(userId, d.deviceId)));
|
||||
}, [cli, userId, devices]);
|
||||
|
||||
if (loading || deviceTrusts === undefined) {
|
||||
// still loading
|
||||
return <Spinner />;
|
||||
}
|
||||
const isMe = userId === cli.getUserId();
|
||||
|
||||
let expandSectionDevices: IDevice[] = [];
|
||||
const unverifiedDevices: IDevice[] = [];
|
||||
|
||||
let expandCountCaption;
|
||||
let expandHideCaption;
|
||||
let expandIconClasses = "mx_E2EIcon";
|
||||
|
||||
const dehydratedDeviceIds: string[] = [];
|
||||
for (const device of devices) {
|
||||
if (device.dehydrated) {
|
||||
dehydratedDeviceIds.push(device.deviceId);
|
||||
}
|
||||
}
|
||||
// If the user has exactly one device marked as dehydrated, we consider
|
||||
// that as the dehydrated device, and hide it as a normal device (but
|
||||
// indicate that the user is using a dehydrated device). If the user has
|
||||
// more than one, that is anomalous, and we show all the devices so that
|
||||
// nothing is hidden.
|
||||
const dehydratedDeviceId: string | undefined = dehydratedDeviceIds.length == 1 ? dehydratedDeviceIds[0] : undefined;
|
||||
let dehydratedDeviceInExpandSection = false;
|
||||
|
||||
if (isUserVerified) {
|
||||
for (let i = 0; i < devices.length; ++i) {
|
||||
const device = devices[i];
|
||||
const deviceTrust = deviceTrusts[i];
|
||||
// For your own devices, we use the stricter check of cross-signing
|
||||
// verification to encourage everyone to trust their own devices via
|
||||
// cross-signing so that other users can then safely trust you.
|
||||
// For other people's devices, the more general verified check that
|
||||
// includes locally verified devices can be used.
|
||||
const isVerified = deviceTrust && (isMe ? deviceTrust.crossSigningVerified : deviceTrust.isVerified());
|
||||
|
||||
if (isVerified) {
|
||||
// don't show dehydrated device as a normal device, if it's
|
||||
// verified
|
||||
if (device.deviceId === dehydratedDeviceId) {
|
||||
dehydratedDeviceInExpandSection = true;
|
||||
} else {
|
||||
expandSectionDevices.push(device);
|
||||
}
|
||||
} else {
|
||||
unverifiedDevices.push(device);
|
||||
}
|
||||
}
|
||||
expandCountCaption = _t("user_info|count_of_verified_sessions", { count: expandSectionDevices.length });
|
||||
expandHideCaption = _t("user_info|hide_verified_sessions");
|
||||
expandIconClasses += " mx_E2EIcon_verified";
|
||||
} else {
|
||||
if (dehydratedDeviceId) {
|
||||
devices = devices.filter((device) => device.deviceId !== dehydratedDeviceId);
|
||||
dehydratedDeviceInExpandSection = true;
|
||||
}
|
||||
expandSectionDevices = devices;
|
||||
expandCountCaption = _t("user_info|count_of_sessions", { count: devices.length });
|
||||
expandHideCaption = _t("user_info|hide_sessions");
|
||||
expandIconClasses += " mx_E2EIcon_normal";
|
||||
}
|
||||
|
||||
let expandButton;
|
||||
if (expandSectionDevices.length) {
|
||||
if (isExpanded) {
|
||||
expandButton = (
|
||||
<AccessibleButton kind="link" className="mx_UserInfo_expand" onClick={() => setExpanded(false)}>
|
||||
<div>{expandHideCaption}</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
} else {
|
||||
expandButton = (
|
||||
<AccessibleButton kind="link" className="mx_UserInfo_expand" onClick={() => setExpanded(true)}>
|
||||
<div className={expandIconClasses} />
|
||||
<div>{expandCountCaption}</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let deviceList = unverifiedDevices.map((device, i) => {
|
||||
return <DeviceItem key={i} userId={userId} device={device} isUserVerified={isUserVerified} />;
|
||||
});
|
||||
if (isExpanded) {
|
||||
const keyStart = unverifiedDevices.length;
|
||||
deviceList = deviceList.concat(
|
||||
expandSectionDevices.map((device, i) => {
|
||||
return (
|
||||
<DeviceItem key={i + keyStart} userId={userId} device={device} isUserVerified={isUserVerified} />
|
||||
);
|
||||
}),
|
||||
);
|
||||
if (dehydratedDeviceInExpandSection) {
|
||||
deviceList.push(<div>{_t("user_info|dehydrated_device_enabled")}</div>);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_UserInfo_devices">
|
||||
<div>{deviceList}</div>
|
||||
<div>{expandButton}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const MessageButton = ({ member }: { member: Member }): JSX.Element => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const [busy, setBusy] = useState(false);
|
||||
@@ -1400,12 +1127,84 @@ export const useDevices = (userId: string): IDevice[] | undefined | null => {
|
||||
return devices;
|
||||
};
|
||||
|
||||
function useHasCrossSigningKeys(cli: MatrixClient, member: User, canVerify: boolean): boolean | undefined {
|
||||
return useAsyncMemo(async () => {
|
||||
if (!canVerify) return undefined;
|
||||
return await cli.getCrypto()?.userHasCrossSigningKeys(member.userId, true);
|
||||
}, [cli, member, canVerify]);
|
||||
}
|
||||
|
||||
const VerificationSection: React.FC<{
|
||||
member: User | RoomMember;
|
||||
devices: IDevice[];
|
||||
}> = ({ member, devices }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
let content;
|
||||
const homeserverSupportsCrossSigning = useHomeserverSupportsCrossSigning(cli);
|
||||
|
||||
const userTrust = useAsyncMemo<UserVerificationStatus | undefined>(
|
||||
async () => cli.getCrypto()?.getUserVerificationStatus(member.userId),
|
||||
[member.userId],
|
||||
// the user verification status is not initialized
|
||||
undefined,
|
||||
);
|
||||
const hasUserVerificationStatus = Boolean(userTrust);
|
||||
const isUserVerified = Boolean(userTrust?.isVerified());
|
||||
const isMe = member.userId === cli.getUserId();
|
||||
const canVerify =
|
||||
hasUserVerificationStatus &&
|
||||
homeserverSupportsCrossSigning &&
|
||||
!isUserVerified &&
|
||||
!isMe &&
|
||||
devices &&
|
||||
devices.length > 0;
|
||||
|
||||
const hasCrossSigningKeys = useHasCrossSigningKeys(cli, member as User, canVerify);
|
||||
|
||||
if (isUserVerified) {
|
||||
content = (
|
||||
<Badge kind="green" className="mx_UserInfo_verified_badge">
|
||||
<VerifiedIcon className="mx_UserInfo_verified_icon" height="16px" width="16px" />
|
||||
<Text size="sm" weight="medium" className="mx_UserInfo_verified_label">
|
||||
{_t("common|verified")}
|
||||
</Text>
|
||||
</Badge>
|
||||
);
|
||||
} else if (hasCrossSigningKeys === undefined) {
|
||||
// We are still fetching the cross-signing keys for the user, show spinner.
|
||||
content = <InlineSpinner size={24} />;
|
||||
} else if (canVerify && hasCrossSigningKeys) {
|
||||
content = (
|
||||
<div className="mx_UserInfo_container_verifyButton">
|
||||
<Button
|
||||
className="mx_UserInfo_verify_button"
|
||||
kind="tertiary"
|
||||
size="sm"
|
||||
onClick={() => verifyUser(cli, member as User)}
|
||||
>
|
||||
{_t("user_info|verify_button")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Text className="mx_UserInfo_verification_unavailable" size="sm">
|
||||
({_t("user_info|verification_unavailable")})
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex justify="center" align="center" className="mx_UserInfo_verification">
|
||||
{content}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const BasicUserInfo: React.FC<{
|
||||
room: Room;
|
||||
member: User | RoomMember;
|
||||
devices: IDevice[];
|
||||
isRoomEncrypted: boolean;
|
||||
}> = ({ room, member, devices, isRoomEncrypted }) => {
|
||||
}> = ({ room, member }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const powerLevels = useRoomPowerLevels(cli, room);
|
||||
@@ -1503,111 +1302,10 @@ const BasicUserInfo: React.FC<{
|
||||
spinner = <Spinner />;
|
||||
}
|
||||
|
||||
// only display the devices list if our client supports E2E
|
||||
const cryptoEnabled = Boolean(cli.getCrypto());
|
||||
|
||||
let text;
|
||||
if (!isRoomEncrypted) {
|
||||
if (!cryptoEnabled) {
|
||||
text = _t("encryption|unsupported");
|
||||
} else if (room && !room.isSpaceRoom()) {
|
||||
text = _t("user_info|room_unencrypted");
|
||||
}
|
||||
} else if (!room.isSpaceRoom()) {
|
||||
text = _t("user_info|room_encrypted");
|
||||
}
|
||||
|
||||
let verifyButton;
|
||||
const homeserverSupportsCrossSigning = useHomeserverSupportsCrossSigning(cli);
|
||||
|
||||
const userTrust = useAsyncMemo<UserVerificationStatus | undefined>(
|
||||
async () => cli.getCrypto()?.getUserVerificationStatus(member.userId),
|
||||
[member.userId],
|
||||
// the user verification status is not initialized
|
||||
undefined,
|
||||
);
|
||||
const hasUserVerificationStatus = Boolean(userTrust);
|
||||
const isUserVerified = Boolean(userTrust?.isVerified());
|
||||
const isMe = member.userId === cli.getUserId();
|
||||
const canVerify =
|
||||
hasUserVerificationStatus &&
|
||||
homeserverSupportsCrossSigning &&
|
||||
!isUserVerified &&
|
||||
!isMe &&
|
||||
devices &&
|
||||
devices.length > 0;
|
||||
|
||||
const setUpdating: SetUpdating = (updating) => {
|
||||
setPendingUpdateCount((count) => count + (updating ? 1 : -1));
|
||||
};
|
||||
const hasCrossSigningKeys = useHasCrossSigningKeys(cli, member as User, canVerify, setUpdating);
|
||||
|
||||
// Display the spinner only when
|
||||
// - the devices are not populated yet, or
|
||||
// - the crypto is available and we don't have the user verification status yet
|
||||
const showDeviceListSpinner = (cryptoEnabled && !hasUserVerificationStatus) || devices === undefined;
|
||||
if (canVerify) {
|
||||
if (hasCrossSigningKeys !== undefined) {
|
||||
// Note: mx_UserInfo_verifyButton is for the end-to-end tests
|
||||
verifyButton = (
|
||||
<div className="mx_UserInfo_container_verifyButton">
|
||||
<AccessibleButton
|
||||
kind="link"
|
||||
className="mx_UserInfo_field mx_UserInfo_verifyButton"
|
||||
onClick={() => verifyUser(cli, member as User)}
|
||||
>
|
||||
{_t("action|verify")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
} else if (!showDeviceListSpinner) {
|
||||
// HACK: only show a spinner if the device section spinner is not shown,
|
||||
// to avoid showing a double spinner
|
||||
// We should ask for a design that includes all the different loading states here
|
||||
verifyButton = <Spinner />;
|
||||
}
|
||||
}
|
||||
|
||||
let editDevices;
|
||||
if (member.userId == cli.getUserId()) {
|
||||
editDevices = (
|
||||
<div>
|
||||
<AccessibleButton
|
||||
kind="link"
|
||||
className="mx_UserInfo_field"
|
||||
onClick={() => {
|
||||
dis.dispatch({
|
||||
action: Action.ViewUserDeviceSettings,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{_t("user_info|edit_own_devices")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const securitySection = (
|
||||
<Container>
|
||||
<h2>{_t("common|security")}</h2>
|
||||
<p>{text}</p>
|
||||
{verifyButton}
|
||||
{cryptoEnabled && (
|
||||
<DevicesSection
|
||||
loading={showDeviceListSpinner}
|
||||
devices={devices}
|
||||
userId={member.userId}
|
||||
isUserVerified={isUserVerified}
|
||||
/>
|
||||
)}
|
||||
{editDevices}
|
||||
</Container>
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{securitySection}
|
||||
|
||||
<UserOptionsSection
|
||||
canInvite={roomPermissions.canInvite}
|
||||
member={member as RoomMember}
|
||||
@@ -1615,15 +1313,12 @@ const BasicUserInfo: React.FC<{
|
||||
>
|
||||
{memberDetails}
|
||||
</UserOptionsSection>
|
||||
|
||||
{adminToolsContainer}
|
||||
|
||||
{!isMe && (
|
||||
<Container>
|
||||
<IgnoreToggleButton member={member} />
|
||||
</Container>
|
||||
)}
|
||||
|
||||
{spinner}
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -1633,9 +1328,10 @@ export type Member = User | RoomMember;
|
||||
|
||||
export const UserInfoHeader: React.FC<{
|
||||
member: Member;
|
||||
e2eStatus?: E2EStatus;
|
||||
devices: IDevice[];
|
||||
roomId?: string;
|
||||
}> = ({ member, e2eStatus, roomId }) => {
|
||||
hideVerificationSection?: boolean;
|
||||
}> = ({ member, devices, roomId, hideVerificationSection }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const onMemberAvatarClick = useCallback(() => {
|
||||
@@ -1686,7 +1382,6 @@ export const UserInfoHeader: React.FC<{
|
||||
|
||||
const timezoneInfo = useUserTimezone(cli, member.userId);
|
||||
|
||||
const e2eIcon = e2eStatus ? <E2EIcon size={18} status={e2eStatus} isUser={true} /> : null;
|
||||
const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier?.(member.userId, {
|
||||
roomId,
|
||||
withDisplayName: true,
|
||||
@@ -1715,7 +1410,6 @@ export const UserInfoHeader: React.FC<{
|
||||
<Heading size="sm" weight="semibold" as="h1" dir="auto">
|
||||
<Flex className="mx_UserInfo_profile_name" direction="row-reverse" align="center">
|
||||
{displayName}
|
||||
{e2eIcon}
|
||||
</Flex>
|
||||
</Heading>
|
||||
{presenceLabel}
|
||||
@@ -1734,6 +1428,7 @@ export const UserInfoHeader: React.FC<{
|
||||
</CopyableText>
|
||||
</Text>
|
||||
</Flex>
|
||||
{!hideVerificationSection && <VerificationSection member={member} devices={devices} />}
|
||||
</Container>
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -1757,13 +1452,6 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
||||
const isRoomEncrypted = useIsEncrypted(cli, room);
|
||||
const devices = useDevices(user.userId) ?? [];
|
||||
|
||||
const e2eStatus = useAsyncMemo(async () => {
|
||||
if (!isRoomEncrypted || !devices) {
|
||||
return undefined;
|
||||
}
|
||||
return await getE2EStatus(cli, user.userId, devices);
|
||||
}, [cli, isRoomEncrypted, user.userId, devices]);
|
||||
|
||||
const classes = ["mx_UserInfo"];
|
||||
|
||||
let cardState: IRightPanelCardState = {};
|
||||
@@ -1779,14 +1467,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
||||
let content: JSX.Element | undefined;
|
||||
switch (phase) {
|
||||
case RightPanelPhases.MemberInfo:
|
||||
content = (
|
||||
<BasicUserInfo
|
||||
room={room as Room}
|
||||
member={member as User}
|
||||
devices={devices}
|
||||
isRoomEncrypted={Boolean(isRoomEncrypted)}
|
||||
/>
|
||||
);
|
||||
content = <BasicUserInfo room={room as Room} member={member as User} />;
|
||||
break;
|
||||
case RightPanelPhases.EncryptionPanel:
|
||||
classes.push("mx_UserInfo_smallAvatar");
|
||||
@@ -1811,7 +1492,12 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
||||
|
||||
const header = (
|
||||
<>
|
||||
<UserInfoHeader member={member} e2eStatus={e2eStatus} roomId={room?.roomId} />
|
||||
<UserInfoHeader
|
||||
hideVerificationSection={phase === RightPanelPhases.EncryptionPanel}
|
||||
member={member}
|
||||
devices={devices}
|
||||
roomId={room?.roomId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user