From 9b4b9cb31c29fbdf78bcf2cc46e81f07fdd7bb03 Mon Sep 17 00:00:00 2001 From: Paul Anuschek Date: Thu, 19 Feb 2026 11:06:10 +0100 Subject: [PATCH 1/8] added Note component --- .../GroupCreation/GroupCreationModal.tsx | 40 ++++++++++++----- .../src/script/components/Note/Note.styles.ts | 43 ++++++++++++++++++ .../src/script/components/Note/Note.tsx | 45 +++++++++++++++++++ 3 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 apps/webapp/src/script/components/Note/Note.styles.ts create mode 100644 apps/webapp/src/script/components/Note/Note.tsx diff --git a/apps/webapp/src/script/components/Modals/GroupCreation/GroupCreationModal.tsx b/apps/webapp/src/script/components/Modals/GroupCreation/GroupCreationModal.tsx index 667016d06c7..1d5ce7d8471 100644 --- a/apps/webapp/src/script/components/Modals/GroupCreation/GroupCreationModal.tsx +++ b/apps/webapp/src/script/components/Modals/GroupCreation/GroupCreationModal.tsx @@ -27,12 +27,13 @@ import {amplify} from 'amplify'; import cx from 'classnames'; import {container} from 'tsyringe'; -import {Button, ButtonVariant, Option, Select} from '@wireapp/react-ui-kit'; +import {Button, ButtonVariant, Option, Select, Text} from '@wireapp/react-ui-kit'; import {WebAppEvents} from '@wireapp/webapp-events'; import {FadingScrollbar} from 'Components/FadingScrollbar'; import * as Icon from 'Components/Icon'; import {ModalComponent} from 'Components/Modals/ModalComponent'; +import {Note} from 'Components/Note/Note'; import {SearchInput} from 'Components/SearchInput'; import {TextInput} from 'Components/TextInput'; import {InfoToggle} from 'Components/toggle/InfoToggle'; @@ -79,11 +80,13 @@ const GroupCreationModal = ({ isMLSEnabled: isMLSEnabledForTeam, isProtocolToggleEnabledForUser, isCellsEnabled: isCellsEnabledForTeam, + isAppsEnabled: isAppsEnabledForTeam, } = useKoSubscribableChildren(teamState, [ 'isTeam', 'isMLSEnabled', 'isProtocolToggleEnabledForUser', 'isCellsEnabled', + 'isAppsEnabled', ]); const {self: selfUser} = useKoSubscribableChildren(userState, ['self']); @@ -154,7 +157,13 @@ const GroupCreationModal = ({ const isGuestAndServicesRoom = accessState === ACCESS_STATE.TEAM.GUESTS_SERVICES; const isGuestRoom = accessState === ACCESS_STATE.TEAM.GUEST_ROOM; const isGuestEnabled = isGuestRoom || isGuestAndServicesRoom; - const isServicesEnabled = isServicesRoom || isGuestAndServicesRoom; + + const isAppsFeatureAvailable = + isTeam && + ((selectedProtocol.value == CONVERSATION_PROTOCOL.PROTEUS && teamState?.hasWhitelistedServices()) || + (selectedProtocol.value == CONVERSATION_PROTOCOL.MLS && isAppsEnabledForTeam)); + + const isServicesEnabled = isAppsFeatureAvailable && (isServicesRoom || isGuestAndServicesRoom); const {setCurrentTab: setCurrentSidebarTab} = useSidebarStore(); @@ -504,17 +513,24 @@ const GroupCreationModal = ({ name={t('guestOptionsTitle')} info={t('guestRoomToggleInfo')} /> - {selectedProtocol.value !== CONVERSATION_PROTOCOL.MLS && ( - + + + {!isAppsFeatureAvailable && ( + + + To improve your workflow with apps, your team needs configuration. Please contact your team admin. + + )} + {areReadReceiptsEnabled && ( = ({title, children}) => { + return ( +
+
+ + {title} +
+ +
+
{children}
+
+
+ ); +}; + +export {Note}; From 3c7190231796c3ecfaead95ed96bb2673d21ef59 Mon Sep 17 00:00:00 2001 From: Paul Anuschek Date: Tue, 24 Feb 2026 09:25:57 +0100 Subject: [PATCH 2/8] added apps disabled banner --- apps/webapp/src/i18n/en-US.json | 2 + .../CreateConversationSteps/Preference.tsx | 40 ++++++++++++------- .../GroupCreation/GroupCreationModal.tsx | 12 ++---- .../AppsDisabledNote/AppsDisabledNote.tsx | 33 +++++++++++++++ .../src/script/components/Note/Note.styles.ts | 16 +++++--- 5 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 apps/webapp/src/script/components/Note/AppsDisabledNote/AppsDisabledNote.tsx diff --git a/apps/webapp/src/i18n/en-US.json b/apps/webapp/src/i18n/en-US.json index f9319f97ba3..7efda501999 100644 --- a/apps/webapp/src/i18n/en-US.json +++ b/apps/webapp/src/i18n/en-US.json @@ -1843,6 +1843,8 @@ "selfNotSupportMLSMsgPart1": "You can't communicate with {selfUserName}, as your device doesn't support the suitable protocol.", "selfNotSupportMLSMsgPart2": "to call, and send messages and files.", "selfProfileImageAlt": "Your profile picture", + "servicesNotEnabledNoteTitle": "Your team doesn't use apps yet", + "servicesNotEnabledBody": "To improve your workflow with apps, your team needs configuration. Please contact your team admin.", "servicesOptionsTitle": "Apps", "servicesRoomToggleInfo": "Open this conversation to apps.", "servicesRoomToggleInfoExtended": "Open this conversation to apps. You can always change it later.", diff --git a/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx b/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx index b480d986535..187132bdc66 100644 --- a/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx +++ b/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx @@ -20,6 +20,7 @@ import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team'; import {container} from 'tsyringe'; +import {AppsDisabledNote} from 'Components/Note/AppsDisabledNote/AppsDisabledNote'; import {InfoToggle} from 'Components/toggle/InfoToggle'; import {TeamState} from 'Repositories/team/TeamState'; import {Config} from 'src/script/Config'; @@ -27,7 +28,6 @@ import {useKoSubscribableChildren} from 'Util/ComponentUtil'; import {t} from 'Util/LocalizerUtil'; import {useCreateConversationModal} from '../hooks/useCreateConversationModal'; -import {ConversationType} from '../types'; export const Preference = () => { const { @@ -44,9 +44,16 @@ export const Preference = () => { const teamState = container.resolve(TeamState); - const {isCellsEnabled: isCellsEnabledForTeam, isMLSEnabled} = useKoSubscribableChildren(teamState, [ + const { + isCellsEnabled: isCellsEnabledForTeam, + isMLSEnabled, + isAppsEnabled, + hasWhitelistedServices, + } = useKoSubscribableChildren(teamState, [ 'isCellsEnabled', 'isMLSEnabled', + 'isAppsEnabled', + 'hasWhitelistedServices', ]); const isCellsEnabledForEnvironment = Config.getConfig().FEATURE.ENABLE_CELLS; const isCellsOptionEnabled = isCellsEnabledForEnvironment && isCellsEnabledForTeam; @@ -55,7 +62,11 @@ export const Preference = () => { ? teamState.teamFeatures()?.mls?.config.defaultProtocol : CONVERSATION_PROTOCOL.PROTEUS; - // Read receipts are temorarily disabled for MLS groups and channels until it is supported + const isAppsFeatureAvailable = + (defaultProtocol === CONVERSATION_PROTOCOL.MLS && isAppsEnabled) || + (defaultProtocol === CONVERSATION_PROTOCOL.PROTEUS && hasWhitelistedServices); + + // Read receipts are temporarily disabled for MLS groups and channels until it is supported const areReadReceiptsEnabled = defaultProtocol !== CONVERSATION_PROTOCOL.MLS; return ( @@ -70,17 +81,18 @@ export const Preference = () => { dataUieName="read-receipts" /> - {conversationType === ConversationType.Group && ( - - )} + + + {!isAppsFeatureAvailable && } + {areReadReceiptsEnabled && ( - {!isAppsFeatureAvailable && ( - - - To improve your workflow with apps, your team needs configuration. Please contact your team admin. - - - )} + {!isAppsFeatureAvailable && } {areReadReceiptsEnabled && ( { + return ( + + {t('servicesNotEnabledBody')} + + ); +}; + +export {AppsDisabledNote}; diff --git a/apps/webapp/src/script/components/Note/Note.styles.ts b/apps/webapp/src/script/components/Note/Note.styles.ts index 748f141f2be..eea7cf35f26 100644 --- a/apps/webapp/src/script/components/Note/Note.styles.ts +++ b/apps/webapp/src/script/components/Note/Note.styles.ts @@ -20,11 +20,17 @@ import {CSSObject} from '@emotion/react'; export const ContainerStyle: CSSObject = { - gap: '12px', - padding: '16px 20px', - backgroundColor: '#e8f0fe', - border: '1px solid #1967d2', - borderRadius: '8px', + display: 'flex', + flexDirection: 'column', + gap: '0.3rem', + padding: '0.75rem', + background: 'var(--accent-color-50)', + '.theme-dark &': { + background: 'var(--accent-color-800)', + boxShadow: 'none', + }, + border: '1px solid var(--accent-color-500)', + borderRadius: '0.5rem', color: '#000', lineHeight: '1.5', }; From 2840a6113e22e5af052c47bb6f45084cec7511bf Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 24 Feb 2026 13:45:26 +0100 Subject: [PATCH 3/8] fixed apps not enabled note formatting --- .../CreateConversationSteps/Preference.tsx | 8 +++++--- apps/webapp/src/script/components/Note/Note.styles.ts | 2 +- apps/webapp/src/script/components/toggle/InfoToggle.tsx | 5 ++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx b/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx index 187132bdc66..1f4292411ce 100644 --- a/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx +++ b/apps/webapp/src/script/components/Modals/CreateConversation/CreateConversationSteps/Preference.tsx @@ -20,6 +20,7 @@ import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team'; import {container} from 'tsyringe'; +import {ConversationType} from 'Components/Modals/CreateConversation/types'; import {AppsDisabledNote} from 'Components/Note/AppsDisabledNote/AppsDisabledNote'; import {InfoToggle} from 'Components/toggle/InfoToggle'; import {TeamState} from 'Repositories/team/TeamState'; @@ -64,7 +65,9 @@ export const Preference = () => { const isAppsFeatureAvailable = (defaultProtocol === CONVERSATION_PROTOCOL.MLS && isAppsEnabled) || - (defaultProtocol === CONVERSATION_PROTOCOL.PROTEUS && hasWhitelistedServices); + (defaultProtocol === CONVERSATION_PROTOCOL.PROTEUS && + hasWhitelistedServices && + conversationType !== ConversationType.Channel); // Read receipts are temporarily disabled for MLS groups and channels until it is supported const areReadReceiptsEnabled = defaultProtocol !== CONVERSATION_PROTOCOL.MLS; @@ -89,10 +92,9 @@ export const Preference = () => { isDisabled={!isAppsFeatureAvailable} name={t('servicesOptionsTitle')} isChecked={isServicesEnabled && isAppsFeatureAvailable} + label={!isAppsFeatureAvailable && } /> - {!isAppsFeatureAvailable && } - {areReadReceiptsEnabled && ( void; + label?: React.ReactNode; } const InfoToggle = ({ @@ -39,6 +40,7 @@ const InfoToggle = ({ isDisabled, name, setIsChecked, + label, }: InfoToggleProps) => { const dataUieNameInfoText = `status-info-toggle-${dataUieName}`; const dataUieNameLabelText = `do-toggle-${dataUieName}`; @@ -77,6 +79,7 @@ const InfoToggle = ({ + {label} ); }; From 490b58e7b2bbd1a047636038017b665a774c0885 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 25 Feb 2026 11:13:46 +0100 Subject: [PATCH 4/8] fixed i18n.d.ts types --- apps/webapp/src/types/i18n.d.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/webapp/src/types/i18n.d.ts b/apps/webapp/src/types/i18n.d.ts index 25a858c12dd..da3ececd86d 100644 --- a/apps/webapp/src/types/i18n.d.ts +++ b/apps/webapp/src/types/i18n.d.ts @@ -717,13 +717,6 @@ declare module 'I18n/en-US.json' { 'conversationFileUploadOverlayDescription': `Drag & drop to add files`; 'conversationFileUploadOverlayTitle': `Upload files`; 'conversationFileVideoPreviewLabel': `Video file preview for: {src}`; - 'conversationFilterDrafts': `Drafts`; - 'conversationFilterMentions': `Mentions`; - 'conversationFilterNone': `No filter`; - 'conversationFilterPings': `Pings`; - 'conversationFilterReplies': `Replies`; - 'conversationFilterTooltip': `Filter conversations`; - 'conversationFilterUnread': `Unread`; 'conversationFoldersEmptyText': `Add your conversations to folders to stay organized.`; 'conversationFoldersEmptyTextLearnMore': `Learn more`; 'conversationFooterArchive': `Archive`; @@ -1798,6 +1791,7 @@ declare module 'I18n/en-US.json' { 'searchCreateGroup': `Create group`; 'searchCreateGuestRoom': `Create guest room`; 'searchDirectConversations': `Search 1:1 conversations`; + 'searchDraftsConversations': `Search in drafts`; 'searchFavoriteConversations': `Search favorites`; 'searchFederatedDomainNotAvailable': `The federated domain is currently not available.`; 'searchFederatedDomainNotAvailableLearnMore': `Learn more`; @@ -1816,6 +1810,7 @@ declare module 'I18n/en-US.json' { 'searchManageServices': `Manage Apps`; 'searchManageServicesNoResults': `Manage apps`; 'searchMemberInvite': `Invite people to join the team`; + 'searchMentionsConversations': `Search in mentions`; 'searchNoContactsOnWire': `You have no contacts on {brandName}.\nTry finding people by\nname or username.`; 'searchNoMatchesPartner': `No results`; 'searchNoServicesManager': `Apps are helpers that can improve your workflow.`; @@ -1826,6 +1821,8 @@ declare module 'I18n/en-US.json' { 'searchPeople': `People`; 'searchPeopleOnlyPlaceholder': `Search people`; 'searchPeoplePlaceholder': `Search for people and conversations`; + 'searchPingsConversations': `Search in pings`; + 'searchRepliesConversations': `Search in replies`; 'searchServiceConfirmButton': `Open Conversation`; 'searchServicePlaceholder': `Search by name`; 'searchServices': `Apps`; @@ -1835,6 +1832,7 @@ declare module 'I18n/en-US.json' { 'searchTrySearch': `Find people by\nname or username`; 'searchTrySearchFederation': `Find people in Wire by name or\n@username\n\nFind people from another domain\nby @username@domainname`; 'searchTrySearchLearnMore': `Learn more`; + 'searchUnreadConversations': `Search in unread`; 'selectAccountTypeHeading': `How will you use Wire?`; 'selectPersonalAccountTypeOptionButtonText': `Create Personal Account`; 'selectPersonalAccountTypeOptionDescription': `Chat with friends and family.`; @@ -1849,6 +1847,8 @@ declare module 'I18n/en-US.json' { 'selfNotSupportMLSMsgPart1': `You can\'t communicate with {selfUserName}, as your device doesn\'t support the suitable protocol.`; 'selfNotSupportMLSMsgPart2': `to call, and send messages and files.`; 'selfProfileImageAlt': `Your profile picture`; + 'servicesNotEnabledNoteTitle': `Your team doesn\'t use apps yet`; + 'servicesNotEnabledBody': `To improve your workflow with apps, your team needs configuration. Please contact your team admin.`; 'servicesOptionsTitle': `Apps`; 'servicesRoomToggleInfo': `Open this conversation to apps.`; 'servicesRoomToggleInfoExtended': `Open this conversation to apps. You can always change it later.`; @@ -1885,6 +1885,8 @@ declare module 'I18n/en-US.json' { 'success.openWebAppText': `Open Wire for web`; 'success.subheader': `What do you want to do next?`; 'systemMessageLearnMore': `Learn more`; + 'tabsFilterHeader': `Show filters`; + 'tabsFilterTooltip': `Customize visible tabs`; 'takeoverButtonChoose': `Choose your own`; 'takeoverButtonKeep': `Keep this one`; 'takeoverLink': `Learn more`; From 2075ba2e531e3ac7e0adab8565019e9d00a13f06 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 26 Feb 2026 08:55:12 +0100 Subject: [PATCH 5/8] reintroduced missing i18n.d.ts types --- apps/webapp/src/i18n/en-US.json | 7 +++++++ apps/webapp/src/types/i18n.d.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/apps/webapp/src/i18n/en-US.json b/apps/webapp/src/i18n/en-US.json index 7efda501999..3b020706739 100644 --- a/apps/webapp/src/i18n/en-US.json +++ b/apps/webapp/src/i18n/en-US.json @@ -713,6 +713,13 @@ "conversationFileUploadOverlayDescription": "Drag & drop to add files", "conversationFileUploadOverlayTitle": "Upload files", "conversationFileVideoPreviewLabel": "Video file preview for: {src}", + "conversationFilterDrafts": "Drafts", + "conversationFilterMentions": "Mentions", + "conversationFilterNone": "No filter", + "conversationFilterPings": "Pings", + "conversationFilterReplies": "Replies", + "conversationFilterTooltip": "Filter conversations", + "conversationFilterUnread": "Unread", "conversationFoldersEmptyText": "Add your conversations to folders to stay organized.", "conversationFoldersEmptyTextLearnMore": "Learn more", "conversationFooterArchive": "Archive", diff --git a/apps/webapp/src/types/i18n.d.ts b/apps/webapp/src/types/i18n.d.ts index da3ececd86d..b6b94385073 100644 --- a/apps/webapp/src/types/i18n.d.ts +++ b/apps/webapp/src/types/i18n.d.ts @@ -717,6 +717,13 @@ declare module 'I18n/en-US.json' { 'conversationFileUploadOverlayDescription': `Drag & drop to add files`; 'conversationFileUploadOverlayTitle': `Upload files`; 'conversationFileVideoPreviewLabel': `Video file preview for: {src}`; + 'conversationFilterDrafts': `Drafts`; + 'conversationFilterMentions': `Mentions`; + 'conversationFilterNone': `No filter`; + 'conversationFilterPings': `Pings`; + 'conversationFilterReplies': `Replies`; + 'conversationFilterTooltip': `Filter conversations`; + 'conversationFilterUnread': `Unread`; 'conversationFoldersEmptyText': `Add your conversations to folders to stay organized.`; 'conversationFoldersEmptyTextLearnMore': `Learn more`; 'conversationFooterArchive': `Archive`; From 6819545784a32beda9f428e0e3afa2c4eb3fc4c3 Mon Sep 17 00:00:00 2001 From: Paul <44203546+panuschek@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:34:22 +0100 Subject: [PATCH 6/8] Update apps/webapp/src/script/components/Note/AppsDisabledNote/AppsDisabledNote.tsx Co-authored-by: Zafar Saeed Khan --- .../components/Note/AppsDisabledNote/AppsDisabledNote.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webapp/src/script/components/Note/AppsDisabledNote/AppsDisabledNote.tsx b/apps/webapp/src/script/components/Note/AppsDisabledNote/AppsDisabledNote.tsx index 4b102e8a847..973859fbf1a 100644 --- a/apps/webapp/src/script/components/Note/AppsDisabledNote/AppsDisabledNote.tsx +++ b/apps/webapp/src/script/components/Note/AppsDisabledNote/AppsDisabledNote.tsx @@ -22,7 +22,7 @@ import React from 'react'; import {Note} from 'Components/Note/Note'; import {t} from 'Util/LocalizerUtil'; -const AppsDisabledNote: React.FC = () => { +const AppsDisabledNote = () => { return ( {t('servicesNotEnabledBody')} From d39247e2541b85c4a57b7499eb916a5bbad59602 Mon Sep 17 00:00:00 2001 From: Paul <44203546+panuschek@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:34:31 +0100 Subject: [PATCH 7/8] Update apps/webapp/src/script/components/Note/Note.tsx Co-authored-by: Zafar Saeed Khan --- apps/webapp/src/script/components/Note/Note.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webapp/src/script/components/Note/Note.tsx b/apps/webapp/src/script/components/Note/Note.tsx index fda7d77960e..d3add20369a 100644 --- a/apps/webapp/src/script/components/Note/Note.tsx +++ b/apps/webapp/src/script/components/Note/Note.tsx @@ -27,7 +27,7 @@ interface NoteProps { children?: ReactNode; } -const Note: React.FC = ({title, children}) => { +const Note = ({title, children}: NoteProps) => { return (
From b2fc7f72c6a5d72cd67982e3e531717d1451f95a Mon Sep 17 00:00:00 2001 From: Paul Date: Sun, 1 Mar 2026 08:11:42 +0100 Subject: [PATCH 8/8] add search for aps --- .../AppsDisabledNote/AppsDisabledNote.tsx | 2 - .../src/script/components/Note/Note.tsx | 2 +- .../UserSearchableList/UserSearchableList.tsx | 19 +- .../AddParticipants/AddParticipants.tsx | 230 +++++++++--------- .../ConversationDetailsOptions.tsx | 5 +- .../script/repositories/entity/User/User.ts | 3 +- .../script/repositories/user/UserMapper.ts | 3 + libraries/api-client/src/user/User.ts | 7 + .../ConversationService.ts | 3 + 9 files changed, 146 insertions(+), 128 deletions(-) diff --git a/apps/webapp/src/script/components/Note/AppsDisabledNote/AppsDisabledNote.tsx b/apps/webapp/src/script/components/Note/AppsDisabledNote/AppsDisabledNote.tsx index 973859fbf1a..017e404bc5a 100644 --- a/apps/webapp/src/script/components/Note/AppsDisabledNote/AppsDisabledNote.tsx +++ b/apps/webapp/src/script/components/Note/AppsDisabledNote/AppsDisabledNote.tsx @@ -17,8 +17,6 @@ * */ -import React from 'react'; - import {Note} from 'Components/Note/Note'; import {t} from 'Util/LocalizerUtil'; diff --git a/apps/webapp/src/script/components/Note/Note.tsx b/apps/webapp/src/script/components/Note/Note.tsx index d3add20369a..de11257df14 100644 --- a/apps/webapp/src/script/components/Note/Note.tsx +++ b/apps/webapp/src/script/components/Note/Note.tsx @@ -17,7 +17,7 @@ * */ -import React, {ReactNode} from 'react'; +import {ReactNode} from 'react'; import {InfoIcon} from 'Components/Icon'; import {ContainerStyle, ContentStyle, HeaderStyle} from 'Components/Note/Note.styles'; diff --git a/apps/webapp/src/script/components/UserSearchableList/UserSearchableList.tsx b/apps/webapp/src/script/components/UserSearchableList/UserSearchableList.tsx index 9c365987473..db24a0a1f90 100644 --- a/apps/webapp/src/script/components/UserSearchableList/UserSearchableList.tsx +++ b/apps/webapp/src/script/components/UserSearchableList/UserSearchableList.tsx @@ -19,7 +19,7 @@ import React, {useEffect, useState} from 'react'; -import {QualifiedId} from '@wireapp/api-client/lib/user'; +import {QualifiedId, UserType} from '@wireapp/api-client/lib/user'; import {container} from 'tsyringe'; import {useDebouncedCallback} from 'use-debounce'; @@ -53,6 +53,7 @@ export type UserListProps = React.ComponentProps & { /** will do an extra request to the server when user types in (otherwise will only lookup given local users) */ allowRemoteSearch?: boolean; filterRemoteTeamUsers?: boolean; + userType: UserType; }; export const UserSearchableList = ({ @@ -66,6 +67,7 @@ export const UserSearchableList = ({ selfUser, users, teamState = container.resolve(TeamState), + userType, ...props }: UserListProps) => { const {searchRepository, teamRepository, selfFirst, ...userListProps} = props; @@ -74,7 +76,9 @@ export const UserSearchableList = ({ const [filteredUsers, setFilteredUsers] = useState([]); const [remoteTeamMembers, setRemoteTeamMembers] = useState([]); - const filteredSelectedUsers = selectedUsers ? searchRepository.searchUserInSet(filter, selectedUsers) : undefined; + const filteredSelectedUsers = selectedUsers + ? searchRepository.searchUserInSet(filter, selectedUsers).filter(u => u.type === UserType.REGULAR) + : undefined; const selfInTeam = teamState.isInTeam(selfUser); @@ -85,7 +89,7 @@ export const UserSearchableList = ({ const fetchMembersFromBackend = useDebouncedCallback(async (query: string, ignoreMembers: User[]) => { const resultUsers = await searchRepository.searchByName(query, selfUser.teamId); const selfTeamId = selfUser.teamId; - const foundMembers = resultUsers.filter(user => user.teamId === selfTeamId); + const foundMembers = resultUsers.filter(user => user.teamId === selfTeamId && user.type === userType); const ignoreIds = ignoreMembers.map(member => member.id); const uniqueMembers = foundMembers.filter(member => !ignoreIds.includes(member.id)); @@ -108,10 +112,11 @@ export const UserSearchableList = ({ .searchUserInSet(filter, users) .filter( user => - user.isMe || - conversationState.hasConversationWith(user) || - teamRepository.isSelfConnectedTo(user.id) || - user.username() === normalizedQuery, + (user.isMe || + conversationState.hasConversationWith(user) || + teamRepository.isSelfConnectedTo(user.id) || + user.username() === normalizedQuery) && + user.type === userType, ); if (normalizedQuery !== '' && selfInTeam && allowRemoteSearch) { diff --git a/apps/webapp/src/script/page/RightSidebar/AddParticipants/AddParticipants.tsx b/apps/webapp/src/script/page/RightSidebar/AddParticipants/AddParticipants.tsx index e89b1e97e3c..8d9b7758bba 100644 --- a/apps/webapp/src/script/page/RightSidebar/AddParticipants/AddParticipants.tsx +++ b/apps/webapp/src/script/page/RightSidebar/AddParticipants/AddParticipants.tsx @@ -19,15 +19,13 @@ import {FC, useMemo, useState} from 'react'; -import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team'; +import {UserType} from '@wireapp/api-client/lib/user'; import cx from 'classnames'; -import {TabIndex, Button, ButtonVariant} from '@wireapp/react-ui-kit'; +import {Button, TabIndex} from '@wireapp/react-ui-kit'; import {FadingScrollbar} from 'Components/FadingScrollbar'; -import * as Icon from 'Components/Icon'; import {SearchInput} from 'Components/SearchInput'; -import {ServiceList} from 'Components/ServiceList/ServiceList'; import {UserSearchableList} from 'Components/UserSearchableList'; import {ConversationRepository} from 'Repositories/conversation/ConversationRepository'; import {Conversation} from 'Repositories/entity/Conversation'; @@ -118,10 +116,15 @@ const AddParticipants: FC = ({ const contacts = useMemo(() => { if (isTeam) { const isTeamOrServices = isTeamOnly || isServicesRoom; - return isTeamOrServices ? teamMembers.sort(sortUsersByPriority) : teamUsers; + // isTeamOnly is true if guests are not allowed in this conversation + // If guests are allowed teamUsers are loaded as they contain team members AND all users the logged in user is connected with + if (currentState === PARTICIPANTS_STATE.ADD_PEOPLE) { + return isTeamOrServices ? teamMembers.sort(sortUsersByPriority) : teamUsers; + } + return teamMembers.sort(sortUsersByPriority).filter(u => u.type === UserType.APP); } return connectedUsers; - }, [connectedUsers, isServicesRoom, isTeam, isTeamOnly, teamMembers, teamUsers]); + }, [connectedUsers, isServicesRoom, isTeam, isTeamOnly, teamMembers, teamUsers, currentState]); const enabledAddAction = selectedContacts.length > ENABLE_ADD_ACTIONS_LENGTH; @@ -134,14 +137,7 @@ const AddParticipants: FC = ({ const isService = !!firstUserEntity?.isService; const allowIntegrations = isGroupOrChannel || isService; - return ( - isTeam && - allowIntegrations && - inTeam && - !isTeamOnly && - isServicesEnabled && - activeConversation.protocol !== CONVERSATION_PROTOCOL.MLS - ); + return isTeam && allowIntegrations && inTeam && !isTeamOnly && isServicesEnabled; }, [ firstUserEntity?.isService, inTeam, @@ -155,19 +151,24 @@ const AddParticipants: FC = ({ const manageServicesUrl = getManageServicesUrl('client_landing'); const isSearching = searchInput.length > ENABLE_IS_SEARCHING_LENGTH; - const onAddPeople = () => setCurrentState(PARTICIPANTS_STATE.ADD_PEOPLE); - - const searchServices = async (value: string) => { - await integrationRepository.searchForServices(value); - setIsInitialServiceSearch(false); + const onAddPeople = () => { + setSelectedContacts([]); + setCurrentState(PARTICIPANTS_STATE.ADD_PEOPLE); }; const onAddServices = async () => { + setSelectedContacts([]); setCurrentState(PARTICIPANTS_STATE.ADD_SERVICE); await searchServices(searchInput); }; + const searchServices = async (value: string) => { + await integrationRepository.searchForServices(value); + + setIsInitialServiceSearch(false); + }; + const openManageServices = () => { if (manageServicesUrl) { safeWindowOpen(manageServicesUrl); @@ -179,6 +180,8 @@ const AddParticipants: FC = ({ const addUsers = async () => { const userEntities = selectedContacts.slice(); + userEntities.forEach(ue => console.log(`userEntity ${JSON.stringify(ue)}`)); + await conversationRepository.addUsers(activeConversation, userEntities); }; @@ -254,104 +257,101 @@ const AddParticipants: FC = ({ )} - {isAddPeopleState && ( - - )} - - {isAddServiceState && ( - <> - {!!services.length && ( - <> - {canManageServices() && !!manageServicesUrl && ( -
    -
  • - handleKeyDown({ - event, - callback: openManageServices, - keys: [KEY.ENTER, KEY.SPACE], - }) - } - data-uie-name="go-manage-services" - > -
    - -
    - -
    {t('addParticipantsManageServices')}
    -
  • -
- )} - - - - )} - - {!services.length && !isInitialServiceSearch && ( -
- - - {canManageServices() && !!manageServicesUrl && ( - <> -
- {t('addParticipantsNoServicesManager')} -
- - - - )} - - {!canManageServices() && ( -
- {t('addParticipantsNoServicesMember')} -
- )} -
- )} - - )} + + + {/*{isAddServiceState && (*/} + {/* <>*/} + {/* {!!services.length && (*/} + {/* <>*/} + {/* {canManageServices() && !!manageServicesUrl && (*/} + {/*
    */} + {/* */} + {/* handleKeyDown({*/} + {/* event,*/} + {/* callback: openManageServices,*/} + {/* keys: [KEY.ENTER, KEY.SPACE],*/} + {/* })*/} + {/* }*/} + {/* data-uie-name="go-manage-services"*/} + {/* >*/} + {/*
    */} + {/* */} + {/*
    */} + + {/*
    {t('addParticipantsManageServices')}
    */} + {/* */} + {/*
*/} + {/* )}*/} + + {/* */} + {/* */} + {/* )}*/} + + {/* {!services.length && !isInitialServiceSearch && (*/} + {/*
*/} + {/* */} + + {/* {canManageServices() && !!manageServicesUrl && (*/} + {/* <>*/} + {/*
*/} + {/* {t('addParticipantsNoServicesManager')}*/} + {/*
*/} + + {/* */} + {/* handleKeyDown({*/} + {/* event,*/} + {/* callback: openManageServices,*/} + {/* keys: [KEY.ENTER, KEY.SPACE],*/} + {/* })*/} + {/* }*/} + {/* data-uie-name="go-enable-services"*/} + {/* style={{marginTop: '1em'}}*/} + {/* >*/} + {/* {t('addParticipantsManageServicesNoResults')}*/} + {/* */} + {/* */} + {/* )}*/} + + {/* {!canManageServices() && (*/} + {/*
*/} + {/* {t('addParticipantsNoServicesMember')}*/} + {/*
*/} + {/* )}*/} + {/*
*/} + {/* )}*/} + {/* */} + {/*)}*/}
- {isAddPeopleState && ( -
- -
- )} +
+ +
); diff --git a/apps/webapp/src/script/page/RightSidebar/ConversationDetails/components/ConversationDetailsOptions/ConversationDetailsOptions.tsx b/apps/webapp/src/script/page/RightSidebar/ConversationDetails/components/ConversationDetailsOptions/ConversationDetailsOptions.tsx index d9882526793..3ce6ed47339 100644 --- a/apps/webapp/src/script/page/RightSidebar/ConversationDetails/components/ConversationDetailsOptions/ConversationDetailsOptions.tsx +++ b/apps/webapp/src/script/page/RightSidebar/ConversationDetails/components/ConversationDetailsOptions/ConversationDetailsOptions.tsx @@ -29,7 +29,7 @@ import {PanelActions} from 'Components/panel/PanelActions'; import {ReceiptModeToggle} from 'Components/toggle/ReceiptModeToggle'; import {ConversationRepository} from 'Repositories/conversation/ConversationRepository'; import {ConversationRoleRepository} from 'Repositories/conversation/ConversationRoleRepository'; -import {isGroupMLSConversation, isMLSConversation} from 'Repositories/conversation/ConversationSelectors'; +import {isGroupMLSConversation} from 'Repositories/conversation/ConversationSelectors'; import {Conversation} from 'Repositories/entity/Conversation'; import {User} from 'Repositories/entity/User'; import {TeamState} from 'Repositories/team/TeamState'; @@ -126,7 +126,8 @@ const ConversationDetailsOptions = ({ const showOptionGuests = isActiveGroupParticipant && isTeamConversation; const showOptionNotificationsGroup = isMutable && isGroupOrChannel; const showOptionTimedMessages = isActiveGroupParticipant && isSelfDeletingMessagesEnabled; - const showOptionServices = isActiveGroupParticipant && isTeamConversation && !isMLSConversation(activeConversation); + //TODO: Actually check for apps and services availability + const showOptionServices = isActiveGroupParticipant && isTeamConversation; // && !isMLSConversation(activeConversation); const showOptionNotifications1To1 = isMutable && !isGroupOrChannel; const showOptionReadReceipts = isTeamConversation && !isGroupMLSConversation(activeConversation); const showChannelOptions = isChannel && isChannelsEnabled; diff --git a/apps/webapp/src/script/repositories/entity/User/User.ts b/apps/webapp/src/script/repositories/entity/User/User.ts index cded53951ce..c69ba7d3412 100644 --- a/apps/webapp/src/script/repositories/entity/User/User.ts +++ b/apps/webapp/src/script/repositories/entity/User/User.ts @@ -19,7 +19,7 @@ import {ConnectionStatus} from '@wireapp/api-client/lib/connection/'; import {CONVERSATION_PROTOCOL} from '@wireapp/api-client/lib/team'; -import {QualifiedId} from '@wireapp/api-client/lib/user'; +import {QualifiedId, UserType} from '@wireapp/api-client/lib/user'; import {amplify} from 'amplify'; import ko from 'knockout'; @@ -104,6 +104,7 @@ export class User { public domain: string; public readonly isBlockedLegalHold: ko.PureComputed; public readonly supportedProtocols: ko.Observable; + public type: UserType | undefined; public static get ACCENT_COLOR() { return { diff --git a/apps/webapp/src/script/repositories/user/UserMapper.ts b/apps/webapp/src/script/repositories/user/UserMapper.ts index 399ebba7fb6..f1e78b704f7 100644 --- a/apps/webapp/src/script/repositories/user/UserMapper.ts +++ b/apps/webapp/src/script/repositories/user/UserMapper.ts @@ -108,6 +108,7 @@ export class UserMapper { service, team: teamId, supported_protocols: supportedProtocols, + type, } = userData; if (accentId) { @@ -181,6 +182,8 @@ export class UserMapper { userEntity.isDeleted = true; } + userEntity.type = type; + return userEntity; } } diff --git a/libraries/api-client/src/user/User.ts b/libraries/api-client/src/user/User.ts index 1516d193ae5..68cff186166 100644 --- a/libraries/api-client/src/user/User.ts +++ b/libraries/api-client/src/user/User.ts @@ -26,6 +26,12 @@ import {Picture} from '../self/'; import {CONVERSATION_PROTOCOL} from '../team'; import {UserAsset} from '../user/'; +export enum UserType { + REGULAR = 'regular', + APP = 'app', + BOT = 'bot', +} + export interface User { accent_id?: AccentColor.AccentColorID; assets?: UserAsset[]; @@ -42,4 +48,5 @@ export interface User { team?: string; searchable?: boolean; supported_protocols?: CONVERSATION_PROTOCOL[]; + type: UserType; } diff --git a/libraries/core/src/conversation/ConversationService/ConversationService.ts b/libraries/core/src/conversation/ConversationService/ConversationService.ts index 546f835a896..032e886b08e 100644 --- a/libraries/core/src/conversation/ConversationService/ConversationService.ts +++ b/libraries/core/src/conversation/ConversationService/ConversationService.ts @@ -444,6 +444,9 @@ export class ConversationService extends TypedEventEmitter { groupId, conversationId, }: Required & {shouldRetry?: boolean}): Promise { + console.log( + `addUsersToMLSConversation(qualifiedUsers=${JSON.stringify(qualifiedUsers)}, groupId=${groupId}, conversationId=${conversationId})`, + ); return this.MLSRecoveryOrchestrator.execute({ context: {operationName: OperationName.addUsers, qualifiedConversationId: conversationId, groupId}, callBack: () => this.performAddUsersToMLSConversationAPI({qualifiedUsers, groupId, conversationId}),