From c89948e8fcf97b38482e8da40f281e41b561b6f7 Mon Sep 17 00:00:00 2001 From: Immad Abdul Jabbar Date: Mon, 13 Jul 2026 22:12:31 +0200 Subject: [PATCH 1/5] feat: add accessibility string for profile icon [WPB-21213] --- .../verificationBadges/verificationBadges.tsx | 49 +++++++++++++++++-- .../leftSidebar/userDetails/userDetails.tsx | 27 ++++++++-- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx index e9425d70438..2db99237aba 100644 --- a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx +++ b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx @@ -44,6 +44,7 @@ import {UserState} from 'Repositories/user/userState'; import {MLSStatuses, WireIdentity} from 'src/script/e2eIdentity/e2eIdentityVerification'; import {RootContextValue, useApplicationContext} from 'src/script/page/rootProvider'; import {useKoSubscribableChildren} from 'Util/componentUtil'; +import {translate} from 'Util/localizerUtil'; import {waitFor} from 'Util/waitFor'; type VerificationBadgeContext = 'user' | 'conversation' | 'device'; @@ -98,7 +99,7 @@ const getMLSStatuses = ({identities, user}: {identities?: WireIdentity[]; user?: }); }; -export const UserVerificationBadges = ({ +export const useUserVerificationStatus = ({ user, groupId, isSelfUser, @@ -115,12 +116,52 @@ export const UserVerificationBadges = ({ user, }); - let status: MLSStatuses | undefined = undefined; + let MLSStatus: MLSStatuses | undefined = undefined; if (mlsStatuses && mlsStatuses.length > 0 && mlsStatuses.every(status => status === MLSStatuses.VALID)) { - status = MLSStatuses.VALID; + MLSStatus = MLSStatuses.VALID; + } + + return {MLSStatus, isProteusVerified}; +}; + +export const getUserVerificationBadgeLabel = ({ + MLSStatus, + isProteusVerified, +}: { + MLSStatus?: MLSStatuses; + isProteusVerified?: boolean; +}): string | undefined => { + switch (MLSStatus) { + case MLSStatuses.VALID: + return translate('E2EI.userDevicesVerified'); + case MLSStatuses.EXPIRED: + case MLSStatuses.EXPIRES_SOON: + return translate('E2EI.certificateExpired'); + case MLSStatuses.REVOKED: + return translate('E2EI.certificateRevoked'); + default: + break; } - return ; + if (isProteusVerified) { + return translate('proteusDeviceVerified'); + } + + return undefined; +}; + +export const UserVerificationBadges = ({ + user, + groupId, + isSelfUser, +}: { + user: User; + groupId?: string; + isSelfUser?: boolean; +}) => { + const {MLSStatus, isProteusVerified} = useUserVerificationStatus({user, groupId, isSelfUser}); + + return ; }; export const DeviceVerificationBadges = ({ diff --git a/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx b/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx index e490fe5262a..2b46fc28d07 100644 --- a/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx +++ b/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx @@ -24,10 +24,11 @@ import cx from 'classnames'; import {TabIndex} from '@wireapp/react-ui-kit'; import {Avatar, AVATAR_SIZE} from 'Components/avatar'; -import {UserVerificationBadges} from 'Components/badge'; +import {getUserVerificationBadgeLabel, useUserVerificationStatus, UserVerificationBadges} from 'Components/badge'; import {LegalHoldDot} from 'Components/LegalHoldDot'; import {User} from 'Repositories/entity/User'; import {useApplicationContext} from 'src/script/page/rootProvider'; +import {availabilityTranslationKeys} from 'Util/availabilityStatus'; import {useKoSubscribableChildren} from 'Util/componentUtil'; import * as styles from './userDetails.styles'; @@ -37,18 +38,21 @@ import {AvailabilityContextMenu} from '../../../ui/availabilityContextMenu'; interface AvailabilityStateButtonWrapperProps { children: React.ReactElement; isTeam: boolean; + ariaLabel: string; showAvailabilityContextMenu: (event: MouseEvent) => void; } const AvailabilityStateButtonWrapper = ({ children, isTeam = false, + ariaLabel, showAvailabilityContextMenu, }: AvailabilityStateButtonWrapperProps) => { return isTeam ? ( @@ -69,9 +73,11 @@ const UserDetailsComponent = ({user, isTeam = false, groupId, isSideBarOpen = fa const { name: userName, username: userHandle, + availability, isOnLegalHold, hasPendingLegalHold, - } = useKoSubscribableChildren(user, ['hasPendingLegalHold', 'isOnLegalHold', 'name', 'username']); + } = useKoSubscribableChildren(user, ['availability', 'hasPendingLegalHold', 'isOnLegalHold', 'name', 'username']); + const {MLSStatus, isProteusVerified} = useUserVerificationStatus({user, groupId, isSelfUser: isTeam}); const showLegalHold = isOnLegalHold || hasPendingLegalHold; @@ -84,14 +90,27 @@ const UserDetailsComponent = ({user, isTeam = false, groupId, isSideBarOpen = fa }); }; + const avatarAriaLabel = [ + userName, + userHandle, + isTeam ? translate(availabilityTranslationKeys[availability]) : undefined, + getUserVerificationBadgeLabel({MLSStatus, isProteusVerified}), + ] + .filter(Boolean) + .join(', '); + return (
- + From 471f54bc8294969d7ecba959d4891392852a4b81 Mon Sep 17 00:00:00 2001 From: Immad Abdul Jabbar Date: Wed, 15 Jul 2026 23:18:51 +0200 Subject: [PATCH 2/5] fix: PR comments --- .../verificationBadges.test.tsx | 70 ++++++++++++++++++- .../verificationBadges/verificationBadges.tsx | 62 ++++++++-------- .../leftSidebar/userDetails/userDetails.tsx | 7 +- 3 files changed, 107 insertions(+), 32 deletions(-) diff --git a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx index 197852839df..dc4335d9ba1 100644 --- a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx +++ b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx @@ -26,7 +26,7 @@ import { createRootProviderWrapperForTest, } from 'src/script/page/testSupport/rootContextTestSupport'; -import {VerificationBadges} from './verificationBadges'; +import {VerificationBadges, getUserVerificationBadgeLabel} from './verificationBadges'; import {translateForTest} from 'Util/test/translateForTest'; const rootContextValue = createRootContextValueForTest({translate: translateForTest}); @@ -92,3 +92,71 @@ describe('VerificationBadges', () => { expect(E2EIdentityStatus.getAttribute('data-uie-value')).toEqual(MLSStatuses.EXPIRES_SOON); }); }); + +describe('getUserVerificationBadgeLabel', () => { + it('returns MLS verified label for VALID status', () => { + const label = getUserVerificationBadgeLabel(translateForTest, { + mlsStatus: MLSStatuses.VALID, + isProteusVerified: false, + }); + expect(label).toBe(translateForTest('E2EI.userDevicesVerified')); + }); + + it('returns certificate expired label for EXPIRED status', () => { + const label = getUserVerificationBadgeLabel(translateForTest, { + mlsStatus: MLSStatuses.EXPIRED, + isProteusVerified: false, + }); + expect(label).toBe(translateForTest('E2EI.certificateExpired')); + }); + + it('returns certificate expires soon label for EXPIRES_SOON status', () => { + const label = getUserVerificationBadgeLabel(translateForTest, { + mlsStatus: MLSStatuses.EXPIRES_SOON, + isProteusVerified: false, + }); + expect(label).toBe(translateForTest('E2EI.certificateExpiresSoon')); + }); + + it('returns certificate revoked label for REVOKED status', () => { + const label = getUserVerificationBadgeLabel(translateForTest, { + mlsStatus: MLSStatuses.REVOKED, + isProteusVerified: false, + }); + expect(label).toBe(translateForTest('E2EI.certificateRevoked')); + }); + + it('returns Proteus device verified label when isProteusVerified is true', () => { + const label = getUserVerificationBadgeLabel(translateForTest, { + mlsStatus: undefined, + isProteusVerified: true, + }); + expect(label).toBe(translateForTest('proteusDeviceVerified')); + }); + + it('returns composed labels when both MLS and Proteus are verified', () => { + const label = getUserVerificationBadgeLabel(translateForTest, { + mlsStatus: MLSStatuses.VALID, + isProteusVerified: true, + }); + expect(label).toBe( + `${translateForTest('E2EI.userDevicesVerified')}, ${translateForTest('proteusDeviceVerified')}`, + ); + }); + + it('returns undefined when neither MLS nor Proteus is verified', () => { + const label = getUserVerificationBadgeLabel(translateForTest, { + mlsStatus: undefined, + isProteusVerified: false, + }); + expect(label).toBeUndefined(); + }); + + it('handles certificate not downloaded status', () => { + const label = getUserVerificationBadgeLabel(translateForTest, { + mlsStatus: MLSStatuses.NOT_ACTIVATED, + isProteusVerified: false, + }); + expect(label).toBe(translateForTest('E2EI.certificateNotDownloaded')); + }); +}); diff --git a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx index 2db99237aba..c16f3952add 100644 --- a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx +++ b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx @@ -22,6 +22,7 @@ import {CSSProperties, useEffect, useMemo, useRef, useState} from 'react'; import {CSSObject} from '@emotion/react'; import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team'; import {stringifyQualifiedId} from '@wireapp/core/lib/util/qualifiedIdUtil'; +import {match} from 'ts-pattern'; import {container} from 'tsyringe'; import { @@ -44,7 +45,6 @@ import {UserState} from 'Repositories/user/userState'; import {MLSStatuses, WireIdentity} from 'src/script/e2eIdentity/e2eIdentityVerification'; import {RootContextValue, useApplicationContext} from 'src/script/page/rootProvider'; import {useKoSubscribableChildren} from 'Util/componentUtil'; -import {translate} from 'Util/localizerUtil'; import {waitFor} from 'Util/waitFor'; type VerificationBadgeContext = 'user' | 'conversation' | 'device'; @@ -116,38 +116,44 @@ export const useUserVerificationStatus = ({ user, }); - let MLSStatus: MLSStatuses | undefined = undefined; - if (mlsStatuses && mlsStatuses.length > 0 && mlsStatuses.every(status => status === MLSStatuses.VALID)) { - MLSStatus = MLSStatuses.VALID; - } + const mlsStatus = + mlsStatuses && mlsStatuses.length > 0 && mlsStatuses.every(status => status === MLSStatuses.VALID) + ? MLSStatuses.VALID + : undefined; - return {MLSStatus, isProteusVerified}; + return {mlsStatus, isProteusVerified}; }; -export const getUserVerificationBadgeLabel = ({ - MLSStatus, - isProteusVerified, -}: { - MLSStatus?: MLSStatuses; - isProteusVerified?: boolean; -}): string | undefined => { - switch (MLSStatus) { - case MLSStatuses.VALID: - return translate('E2EI.userDevicesVerified'); - case MLSStatuses.EXPIRED: - case MLSStatuses.EXPIRES_SOON: - return translate('E2EI.certificateExpired'); - case MLSStatuses.REVOKED: - return translate('E2EI.certificateRevoked'); - default: - break; - } +export const getUserVerificationBadgeLabel = ( + translate: RootContextValue['translate'], + {mlsStatus, isProteusVerified}: {mlsStatus?: MLSStatuses; isProteusVerified?: boolean}, +): string | undefined => { + const labels: string[] = []; + + match(mlsStatus) + .with(MLSStatuses.VALID, () => { + labels.push(translate('E2EI.userDevicesVerified')); + }) + .with(MLSStatuses.EXPIRED, () => { + labels.push(translate('E2EI.certificateExpired')); + }) + .with(MLSStatuses.EXPIRES_SOON, () => { + labels.push(translate('E2EI.certificateExpiresSoon')); + }) + .with(MLSStatuses.REVOKED, () => { + labels.push(translate('E2EI.certificateRevoked')); + }) + .with(MLSStatuses.NOT_ACTIVATED, () => { + labels.push(translate('E2EI.certificateNotDownloaded')); + }) + .with(undefined, () => {}) + .exhaustive(); if (isProteusVerified) { - return translate('proteusDeviceVerified'); + labels.push(translate('proteusDeviceVerified')); } - return undefined; + return labels.length > 0 ? labels.join(', ') : undefined; }; export const UserVerificationBadges = ({ @@ -159,9 +165,9 @@ export const UserVerificationBadges = ({ groupId?: string; isSelfUser?: boolean; }) => { - const {MLSStatus, isProteusVerified} = useUserVerificationStatus({user, groupId, isSelfUser}); + const {mlsStatus, isProteusVerified} = useUserVerificationStatus({user, groupId, isSelfUser}); - return ; + return ; }; export const DeviceVerificationBadges = ({ diff --git a/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx b/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx index 2b46fc28d07..7dc8cf19d29 100644 --- a/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx +++ b/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx @@ -53,6 +53,7 @@ const AvailabilityStateButtonWrapper = ({ onClick={event => showAvailabilityContextMenu(event.nativeEvent)} className="button-reset-default user-details-avatar" aria-label={ariaLabel} + aria-haspopup="menu" > {children} @@ -77,7 +78,7 @@ const UserDetailsComponent = ({user, isTeam = false, groupId, isSideBarOpen = fa isOnLegalHold, hasPendingLegalHold, } = useKoSubscribableChildren(user, ['availability', 'hasPendingLegalHold', 'isOnLegalHold', 'name', 'username']); - const {MLSStatus, isProteusVerified} = useUserVerificationStatus({user, groupId, isSelfUser: isTeam}); + const verificationStatus = useUserVerificationStatus({user, groupId, isSelfUser: isTeam}); const showLegalHold = isOnLegalHold || hasPendingLegalHold; @@ -94,9 +95,9 @@ const UserDetailsComponent = ({user, isTeam = false, groupId, isSideBarOpen = fa userName, userHandle, isTeam ? translate(availabilityTranslationKeys[availability]) : undefined, - getUserVerificationBadgeLabel({MLSStatus, isProteusVerified}), + getUserVerificationBadgeLabel(translate, verificationStatus), ] - .filter(Boolean) + .filter((label): label is string => label !== undefined && label.length > 0) .join(', '); return ( From b640a191c20f67bb91d885ef1ee70f7395160c98 Mon Sep 17 00:00:00 2001 From: Immad Abdul Jabbar Date: Wed, 15 Jul 2026 23:28:29 +0200 Subject: [PATCH 3/5] fix: PR comments --- apps/webapp/src/i18n/en-US.json | 3 ++- .../verificationBadges/verificationBadges.test.tsx | 8 +++----- .../components/verificationBadges/verificationBadges.tsx | 2 +- .../script/page/leftSidebar/userDetails/userDetails.tsx | 3 ++- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/webapp/src/i18n/en-US.json b/apps/webapp/src/i18n/en-US.json index a31fe29da77..61477cca83a 100644 --- a/apps/webapp/src/i18n/en-US.json +++ b/apps/webapp/src/i18n/en-US.json @@ -58,6 +58,7 @@ "E2EI.certificateExpired": "End-to-end identity certificate expired", "E2EI.certificateExpiresSoon": "End-to-end identity certificate expires soon", "E2EI.certificateNotDownloaded": "End-to-end identity certificate not downloaded", + "E2EI.certificateNotActivated": "End-to-end identity certificate not activated", "E2EI.certificateRevoked": "End-to-end identity certificate revoked", "E2EI.certificateTitle": "End-to-end identity certificate", "E2EI.conversationVerified": "Conversation verified (End-to-end identity)", @@ -2233,4 +2234,4 @@ "wireMacos": "{brandName} for macOS", "wireWindows": "{brandName} for Windows", "wire_for_web": "{brandName} for Web" -} +} \ No newline at end of file diff --git a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx index dc4335d9ba1..c77e33053be 100644 --- a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx +++ b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx @@ -139,9 +139,7 @@ describe('getUserVerificationBadgeLabel', () => { mlsStatus: MLSStatuses.VALID, isProteusVerified: true, }); - expect(label).toBe( - `${translateForTest('E2EI.userDevicesVerified')}, ${translateForTest('proteusDeviceVerified')}`, - ); + expect(label).toBe(`${translateForTest('E2EI.userDevicesVerified')}, ${translateForTest('proteusDeviceVerified')}`); }); it('returns undefined when neither MLS nor Proteus is verified', () => { @@ -152,11 +150,11 @@ describe('getUserVerificationBadgeLabel', () => { expect(label).toBeUndefined(); }); - it('handles certificate not downloaded status', () => { + it('handles not activated status', () => { const label = getUserVerificationBadgeLabel(translateForTest, { mlsStatus: MLSStatuses.NOT_ACTIVATED, isProteusVerified: false, }); - expect(label).toBe(translateForTest('E2EI.certificateNotDownloaded')); + expect(label).toBe(translateForTest('E2EI.certificateNotActivated')); }); }); diff --git a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx index c16f3952add..b8bd4d3af72 100644 --- a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx +++ b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx @@ -144,7 +144,7 @@ export const getUserVerificationBadgeLabel = ( labels.push(translate('E2EI.certificateRevoked')); }) .with(MLSStatuses.NOT_ACTIVATED, () => { - labels.push(translate('E2EI.certificateNotDownloaded')); + labels.push(translate('E2EI.certificateNotActivated')); }) .with(undefined, () => {}) .exhaustive(); diff --git a/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx b/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx index 7dc8cf19d29..a833c80c572 100644 --- a/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx +++ b/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx @@ -19,6 +19,7 @@ import {memo} from 'react'; +import is from '@sindresorhus/is'; import cx from 'classnames'; import {TabIndex} from '@wireapp/react-ui-kit'; @@ -97,7 +98,7 @@ const UserDetailsComponent = ({user, isTeam = false, groupId, isSideBarOpen = fa isTeam ? translate(availabilityTranslationKeys[availability]) : undefined, getUserVerificationBadgeLabel(translate, verificationStatus), ] - .filter((label): label is string => label !== undefined && label.length > 0) + .filter((label): label is string => is.nonEmptyString(label)) .join(', '); return ( From a8c60b946c50ee3727d7de4e0172c1786b77636e Mon Sep 17 00:00:00 2001 From: Immad Abdul Jabbar Date: Wed, 22 Jul 2026 23:05:40 +0200 Subject: [PATCH 4/5] fix: lint --- apps/webapp/src/i18n/en-US.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/webapp/src/i18n/en-US.json b/apps/webapp/src/i18n/en-US.json index 61477cca83a..f8336c1a566 100644 --- a/apps/webapp/src/i18n/en-US.json +++ b/apps/webapp/src/i18n/en-US.json @@ -57,8 +57,8 @@ "E2EI.certificateDetails": "Certificate details (PEM format)", "E2EI.certificateExpired": "End-to-end identity certificate expired", "E2EI.certificateExpiresSoon": "End-to-end identity certificate expires soon", - "E2EI.certificateNotDownloaded": "End-to-end identity certificate not downloaded", "E2EI.certificateNotActivated": "End-to-end identity certificate not activated", + "E2EI.certificateNotDownloaded": "End-to-end identity certificate not downloaded", "E2EI.certificateRevoked": "End-to-end identity certificate revoked", "E2EI.certificateTitle": "End-to-end identity certificate", "E2EI.conversationVerified": "Conversation verified (End-to-end identity)", @@ -2234,4 +2234,4 @@ "wireMacos": "{brandName} for macOS", "wireWindows": "{brandName} for Windows", "wire_for_web": "{brandName} for Web" -} \ No newline at end of file +} From bc8a288ac10e88e01dd0ad8b8610bc67cbc9ef41 Mon Sep 17 00:00:00 2001 From: Immad Abdul Jabbar Date: Thu, 3 Sep 2026 19:43:19 +0200 Subject: [PATCH 5/5] fix: PR comments --- .../verificationBadges.test.tsx | 41 ++-------------- .../verificationBadges/verificationBadges.tsx | 47 +++++++++---------- .../leftSidebar/userDetails/userDetails.tsx | 34 ++++++++++++-- 3 files changed, 57 insertions(+), 65 deletions(-) diff --git a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx index c77e33053be..ac6d8e8aab0 100644 --- a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx +++ b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.test.tsx @@ -18,6 +18,7 @@ */ import {render} from '@testing-library/react'; +import {Maybe} from 'true-myth'; import {withTheme} from 'src/script/auth/util/test/testUtil'; import {MLSStatuses} from 'src/script/e2eIdentity'; @@ -96,39 +97,15 @@ describe('VerificationBadges', () => { describe('getUserVerificationBadgeLabel', () => { it('returns MLS verified label for VALID status', () => { const label = getUserVerificationBadgeLabel(translateForTest, { - mlsStatus: MLSStatuses.VALID, + mlsStatus: Maybe.just(MLSStatuses.VALID), isProteusVerified: false, }); expect(label).toBe(translateForTest('E2EI.userDevicesVerified')); }); - it('returns certificate expired label for EXPIRED status', () => { - const label = getUserVerificationBadgeLabel(translateForTest, { - mlsStatus: MLSStatuses.EXPIRED, - isProteusVerified: false, - }); - expect(label).toBe(translateForTest('E2EI.certificateExpired')); - }); - - it('returns certificate expires soon label for EXPIRES_SOON status', () => { - const label = getUserVerificationBadgeLabel(translateForTest, { - mlsStatus: MLSStatuses.EXPIRES_SOON, - isProteusVerified: false, - }); - expect(label).toBe(translateForTest('E2EI.certificateExpiresSoon')); - }); - - it('returns certificate revoked label for REVOKED status', () => { - const label = getUserVerificationBadgeLabel(translateForTest, { - mlsStatus: MLSStatuses.REVOKED, - isProteusVerified: false, - }); - expect(label).toBe(translateForTest('E2EI.certificateRevoked')); - }); - it('returns Proteus device verified label when isProteusVerified is true', () => { const label = getUserVerificationBadgeLabel(translateForTest, { - mlsStatus: undefined, + mlsStatus: Maybe.nothing(), isProteusVerified: true, }); expect(label).toBe(translateForTest('proteusDeviceVerified')); @@ -136,7 +113,7 @@ describe('getUserVerificationBadgeLabel', () => { it('returns composed labels when both MLS and Proteus are verified', () => { const label = getUserVerificationBadgeLabel(translateForTest, { - mlsStatus: MLSStatuses.VALID, + mlsStatus: Maybe.just(MLSStatuses.VALID), isProteusVerified: true, }); expect(label).toBe(`${translateForTest('E2EI.userDevicesVerified')}, ${translateForTest('proteusDeviceVerified')}`); @@ -144,17 +121,9 @@ describe('getUserVerificationBadgeLabel', () => { it('returns undefined when neither MLS nor Proteus is verified', () => { const label = getUserVerificationBadgeLabel(translateForTest, { - mlsStatus: undefined, + mlsStatus: Maybe.nothing(), isProteusVerified: false, }); expect(label).toBeUndefined(); }); - - it('handles not activated status', () => { - const label = getUserVerificationBadgeLabel(translateForTest, { - mlsStatus: MLSStatuses.NOT_ACTIVATED, - isProteusVerified: false, - }); - expect(label).toBe(translateForTest('E2EI.certificateNotActivated')); - }); }); diff --git a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx index b8bd4d3af72..12274bbe54c 100644 --- a/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx +++ b/apps/webapp/src/script/components/badge/components/verificationBadges/verificationBadges.tsx @@ -22,8 +22,8 @@ import {CSSProperties, useEffect, useMemo, useRef, useState} from 'react'; import {CSSObject} from '@emotion/react'; import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team'; import {stringifyQualifiedId} from '@wireapp/core/lib/util/qualifiedIdUtil'; -import {match} from 'ts-pattern'; import {container} from 'tsyringe'; +import {Maybe} from 'true-myth'; import { TabIndex, @@ -116,38 +116,22 @@ export const useUserVerificationStatus = ({ user, }); - const mlsStatus = + const mlsStatus = Maybe.of( mlsStatuses && mlsStatuses.length > 0 && mlsStatuses.every(status => status === MLSStatuses.VALID) ? MLSStatuses.VALID - : undefined; + : undefined, + ); return {mlsStatus, isProteusVerified}; }; export const getUserVerificationBadgeLabel = ( translate: RootContextValue['translate'], - {mlsStatus, isProteusVerified}: {mlsStatus?: MLSStatuses; isProteusVerified?: boolean}, + {mlsStatus, isProteusVerified}: {mlsStatus: Maybe; isProteusVerified: boolean}, ): string | undefined => { const labels: string[] = []; - match(mlsStatus) - .with(MLSStatuses.VALID, () => { - labels.push(translate('E2EI.userDevicesVerified')); - }) - .with(MLSStatuses.EXPIRED, () => { - labels.push(translate('E2EI.certificateExpired')); - }) - .with(MLSStatuses.EXPIRES_SOON, () => { - labels.push(translate('E2EI.certificateExpiresSoon')); - }) - .with(MLSStatuses.REVOKED, () => { - labels.push(translate('E2EI.certificateRevoked')); - }) - .with(MLSStatuses.NOT_ACTIVATED, () => { - labels.push(translate('E2EI.certificateNotActivated')); - }) - .with(undefined, () => {}) - .exhaustive(); + mlsStatus.map(() => labels.push(translate('E2EI.userDevicesVerified'))); if (isProteusVerified) { labels.push(translate('proteusDeviceVerified')); @@ -156,6 +140,22 @@ export const getUserVerificationBadgeLabel = ( return labels.length > 0 ? labels.join(', ') : undefined; }; +export const UserVerificationBadgesContent = ({ + mlsStatus, + isProteusVerified, +}: { + mlsStatus: Maybe; + isProteusVerified: boolean; +}) => { + return ( + + ); +}; + export const UserVerificationBadges = ({ user, groupId, @@ -166,8 +166,7 @@ export const UserVerificationBadges = ({ isSelfUser?: boolean; }) => { const {mlsStatus, isProteusVerified} = useUserVerificationStatus({user, groupId, isSelfUser}); - - return ; + return ; }; export const DeviceVerificationBadges = ({ diff --git a/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx b/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx index a833c80c572..1b1be37e6bc 100644 --- a/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx +++ b/apps/webapp/src/script/page/leftSidebar/userDetails/userDetails.tsx @@ -21,15 +21,19 @@ import {memo} from 'react'; import is from '@sindresorhus/is'; import cx from 'classnames'; +import {Availability} from '@wireapp/protocol-messaging'; import {TabIndex} from '@wireapp/react-ui-kit'; import {Avatar, AVATAR_SIZE} from 'Components/avatar'; -import {getUserVerificationBadgeLabel, useUserVerificationStatus, UserVerificationBadges} from 'Components/badge'; +import { + getUserVerificationBadgeLabel, + useUserVerificationStatus, + UserVerificationBadgesContent, +} from 'Components/badge'; import {LegalHoldDot} from 'Components/LegalHoldDot'; import {User} from 'Repositories/entity/User'; import {useApplicationContext} from 'src/script/page/rootProvider'; -import {availabilityTranslationKeys} from 'Util/availabilityStatus'; import {useKoSubscribableChildren} from 'Util/componentUtil'; import * as styles from './userDetails.styles'; @@ -43,6 +47,22 @@ interface AvailabilityStateButtonWrapperProps { showAvailabilityContextMenu: (event: MouseEvent) => void; } +const isAvailabilityType = (value: unknown): value is Availability.Type => + Object.values(Availability.Type).some(availabilityType => availabilityType === value); + +const getAvailabilityTranslationKey = (availability: Availability.Type) => { + switch (availability) { + case Availability.Type.AVAILABLE: + return 'availability.available'; + case Availability.Type.BUSY: + return 'availability.busy'; + case Availability.Type.AWAY: + return 'availability.away'; + case Availability.Type.NONE: + return 'availability.none'; + } +}; + const AvailabilityStateButtonWrapper = ({ children, isTeam = false, @@ -95,7 +115,7 @@ const UserDetailsComponent = ({user, isTeam = false, groupId, isSideBarOpen = fa const avatarAriaLabel = [ userName, userHandle, - isTeam ? translate(availabilityTranslationKeys[availability]) : undefined, + isTeam && isAvailabilityType(availability) ? translate(getAvailabilityTranslationKey(availability)) : undefined, getUserVerificationBadgeLabel(translate, verificationStatus), ] .filter((label): label is string => is.nonEmptyString(label)) @@ -120,13 +140,17 @@ const UserDetailsComponent = ({user, isTeam = false, groupId, isSideBarOpen = fa {isTeam ? ( <>
- - +
{showLegalHold && (