Distinguish an unverifiable device from a unverified device

This commit is contained in:
Half-Shot 2026-04-13 11:16:54 +01:00
parent e30adf4eb3
commit 981afd0947
8 changed files with 59 additions and 19 deletions

View File

@ -43,6 +43,7 @@ Please see LICENSE files in the repository root for full details.
--background-color: $e2e-warning-color-light;
}
&.Unverifiable,
&.Inactive {
--icon-color: $secondary-content;
--background-color: $panels;

View File

@ -57,6 +57,10 @@ Please see LICENSE files in the repository root for full details.
--v-icon-color: $e2e-verified-color;
}
&.unverifiable {
--v-icon-color: $secondary-content;
}
&.unverified {
--v-icon-color: $e2e-warning-color;
}

View File

@ -52,11 +52,22 @@ const getInactiveMetadata = (device: ExtendedDevice): { id: string; value: React
const DeviceMetaDatum: React.FC<{ value: string | React.ReactNode; id: string }> = ({ value, id }) =>
value ? <span data-testid={`device-metadata-${id}`}>{value}</span> : null;
function getVerifiedTextStatus(isVerified: boolean | null): string {
switch (isVerified) {
case true:
return _t("common|verified");
case false:
return _t("common|unverified");
case null:
return _t("common|unverifiable");
}
}
export const DeviceMetaData: React.FC<Props> = ({ device }) => {
const inactive = getInactiveMetadata(device);
const lastActivity =
device.last_seen_ts && `${_t("settings|sessions|last_activity")} ${formatLastActivity(device.last_seen_ts)}`;
const verificationStatus = device.isVerified ? _t("common|verified") : _t("common|unverified");
const verificationStatus = getVerifiedTextStatus(device.isVerified);
// if device is inactive, don't display last activity or verificationStatus
const metadata = inactive
? [inactive, { id: "lastSeenIp", value: device.last_seen_ip }]

View File

@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import classNames from "classnames";
import React from "react";
import { ShieldIcon, ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { ShieldIcon, ErrorSolidIcon, InfoSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Icon as InactiveIcon } from "../../../../../res/img/element-icons/settings/inactive.svg";
import { DeviceSecurityVariation } from "./types";
@ -24,7 +24,7 @@ const VariationIcon: Record<DeviceSecurityVariation, React.FC<React.SVGProps<SVG
[DeviceSecurityVariation.Inactive]: InactiveIcon,
[DeviceSecurityVariation.Verified]: ShieldIcon,
[DeviceSecurityVariation.Unverified]: ErrorSolidIcon,
[DeviceSecurityVariation.Unverifiable]: ErrorSolidIcon,
[DeviceSecurityVariation.Unverifiable]: InfoSolidIcon,
};
const DeviceSecurityIcon: React.FC<{ variation: DeviceSecurityVariation }> = ({ variation }) => {

View File

@ -15,6 +15,7 @@ import {
MobileIcon,
WebBrowserIcon,
DevicesIcon,
InfoSolidIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t, _td } from "../../../../languageHandler";
@ -22,7 +23,7 @@ import { type ExtendedDevice } from "./types";
import { DeviceType } from "../../../../utils/device/parseUserAgent";
interface Props {
isVerified?: ExtendedDevice["isVerified"];
isVerified: ExtendedDevice["isVerified"];
isSelected?: boolean;
deviceType?: DeviceType;
}
@ -43,6 +44,38 @@ const deviceTypeLabel: Record<DeviceType, TranslationKey> = {
export const DeviceTypeIcon: React.FC<Props> = ({ isVerified, isSelected, deviceType }) => {
const Icon = deviceTypeIcon[deviceType!] || deviceTypeIcon[DeviceType.Unknown];
const label = _t(deviceTypeLabel[deviceType!] || deviceTypeLabel[DeviceType.Unknown]);
let statusIcon;
switch (isVerified) {
case true:
statusIcon = (
<ShieldIcon
className={classNames("mx_DeviceTypeIcon_verificationIcon", "verified")}
role="img"
aria-label={_t("common|verified")}
/>
);
break;
case false:
statusIcon = (
<ErrorSolidIcon
className={classNames("mx_DeviceTypeIcon_verificationIcon", "unverified")}
role="img"
aria-label={_t("common|unverified")}
/>
);
break;
default:
statusIcon = (
<InfoSolidIcon
className={classNames("mx_DeviceTypeIcon_verificationIcon", "unverifiable")}
role="img"
aria-label={_t("common|unverifiable")}
/>
);
break;
}
return (
<div
className={classNames("mx_DeviceTypeIcon", {
@ -52,19 +85,7 @@ export const DeviceTypeIcon: React.FC<Props> = ({ isVerified, isSelected, device
<div className="mx_DeviceTypeIcon_deviceIconWrapper">
<Icon className="mx_DeviceTypeIcon_deviceIcon" role="img" aria-label={label} />
</div>
{isVerified ? (
<ShieldIcon
className={classNames("mx_DeviceTypeIcon_verificationIcon", "verified")}
role="img"
aria-label={_t("common|verified")}
/>
) : (
<ErrorSolidIcon
className={classNames("mx_DeviceTypeIcon_verificationIcon", "unverified")}
role="img"
aria-label={_t("common|unverified")}
/>
)}
{statusIcon}
</div>
);
};

View File

@ -45,7 +45,7 @@ const getCardProps = (
}
if (device.isVerified === null) {
return {
variation: DeviceSecurityVariation.Unverified,
variation: DeviceSecurityVariation.Unverifiable,
heading: _t("settings|sessions|unverified_session"),
description: (
<>

View File

@ -17,6 +17,7 @@ export const INACTIVE_DEVICE_AGE_DAYS = INACTIVE_DEVICE_AGE_MS / MS_DAY;
export type FilterVariation =
| DeviceSecurityVariation.Verified
| DeviceSecurityVariation.Inactive
| DeviceSecurityVariation.Unverifiable
| DeviceSecurityVariation.Unverified;
export const isDeviceInactive: DeviceFilterCondition = (device) =>
@ -24,7 +25,8 @@ export const isDeviceInactive: DeviceFilterCondition = (device) =>
const filters: Record<FilterVariation, DeviceFilterCondition> = {
[DeviceSecurityVariation.Verified]: (device) => !!device.isVerified,
[DeviceSecurityVariation.Unverified]: (device) => !device.isVerified,
[DeviceSecurityVariation.Unverified]: (device) => device.isVerified === false,
[DeviceSecurityVariation.Unverifiable]: (device) => device.isVerified === null,
[DeviceSecurityVariation.Inactive]: isDeviceInactive,
};

View File

@ -578,6 +578,7 @@
"unmute": "Unmute",
"unnamed_room": "Unnamed Room",
"unnamed_space": "Unnamed Space",
"unverifiable": "Unverifiable",
"unverified": "Unverified",
"updating": "Updating...",
"user": "User",