diff --git a/src/bundles/extra.ts b/src/bundles/extra.ts index ce3d4c92e..b5ff94f9e 100644 --- a/src/bundles/extra.ts +++ b/src/bundles/extra.ts @@ -84,5 +84,5 @@ export { default as Management } from '../components/right/management/Management export { default as PaymentModal } from '../components/payment/PaymentModal'; export { default as ReceiptModal } from '../components/payment/ReceiptModal'; -export { default as InviteViaLinkModal } from '../components/main/InviteViaLinkModal'; +export { default as InviteViaLinkModal } from '../components/modals/inviteViaLink/InviteViaLinkModal'; export { default as OneTimeMediaModal } from '../components/modals/oneTimeMedia/OneTimeMediaModal'; diff --git a/src/components/calls/group/GroupCallTopPane.scss b/src/components/calls/group/GroupCallTopPane.scss index f1036dadc..974b4533c 100644 --- a/src/components/calls/group/GroupCallTopPane.scss +++ b/src/components/calls/group/GroupCallTopPane.scss @@ -48,26 +48,10 @@ } } - .avatars { - display: flex; - flex-direction: row; - align-items: center; - - .Avatar { - margin: 0 0 0 -0.75rem; - font-size: 0.75rem; - - &:first-child { - width: 2rem !important; - height: 2rem !important; - } - - &:not(:first-child) { - width: 2.25rem !important; - height: 2.25rem !important; - border: 0.125rem solid var(--color-background); - } - } + .avatars .Avatar { + width: 2.25rem !important; + height: 2.25rem !important; + margin-inline-end: unset !important; } .join { diff --git a/src/components/calls/group/GroupCallTopPane.tsx b/src/components/calls/group/GroupCallTopPane.tsx index 78565aec5..57a2a5eb1 100644 --- a/src/components/calls/group/GroupCallTopPane.tsx +++ b/src/components/calls/group/GroupCallTopPane.tsx @@ -14,7 +14,7 @@ import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; import useLang from '../../../hooks/useLang'; import useShowTransition from '../../../hooks/useShowTransition'; -import Avatar from '../../common/Avatar'; +import AvatarList from '../../common/AvatarList'; import Button from '../../ui/Button'; import './GroupCallTopPane.scss'; @@ -110,14 +110,9 @@ const GroupCallTopPane: FC = ({ {lang('VoipGroupVoiceChat')} {lang('Participants', renderingParticipantCount ?? 0, 'i')} -
- {renderingFetchedParticipants?.map((peer) => ( - - ))} -
+ {Boolean(renderingFetchedParticipants?.length) && ( + + )} diff --git a/src/components/common/AvatarList.module.scss b/src/components/common/AvatarList.module.scss index 29a7e9882..76e061bea 100644 --- a/src/components/common/AvatarList.module.scss +++ b/src/components/common/AvatarList.module.scss @@ -1,5 +1,6 @@ .root { display: flex; + position: relative; --spacing: calc(var(--size) * 0.4); --spacing-gap: calc(var(--size) * 0.04); --size: 0px; @@ -58,3 +59,21 @@ .root[dir="rtl"] .avatar { --offset: calc(100% + var(--half-size) - var(--spacing)); } + +.badge { + position: absolute; + bottom: -1px; + right: -1px; + + background-color: var(--color-primary); + color: var(--color-white); + + border: 1px solid var(--color-background); + border-radius: 1rem; + + font-size: 0.75rem; + line-height: 1rem; + font-weight: 500; + + padding: 0rem 0.25rem; +} diff --git a/src/components/common/AvatarList.tsx b/src/components/common/AvatarList.tsx index e8db4206b..7580a55ab 100644 --- a/src/components/common/AvatarList.tsx +++ b/src/components/common/AvatarList.tsx @@ -1,5 +1,5 @@ import type { FC } from '../../lib/teact/teact'; -import React, { memo } from '../../lib/teact/teact'; +import React, { memo, useMemo } from '../../lib/teact/teact'; import type { ApiPeer } from '../../api/types'; import type { AvatarSize } from './Avatar'; @@ -12,25 +12,41 @@ import Avatar from './Avatar'; import styles from './AvatarList.module.scss'; +const DEFAULT_LIMIT = 3; + type OwnProps = { size: AvatarSize; peers?: ApiPeer[]; className?: string; + limit?: number; + badgeText?: string; }; const AvatarList: FC = ({ peers, size, className, + limit = DEFAULT_LIMIT, + badgeText, }) => { const lang = useLang(); + const renderingBadgeText = useMemo(() => { + if (badgeText) return badgeText; + if (!peers?.length || peers.length <= limit) return undefined; + return `+${peers.length - limit}`; + }, [badgeText, peers, limit]); return (
- {peers?.map((peer) => )} + {peers?.slice(0, limit).map((peer) => )} + {renderingBadgeText && ( +
+ {renderingBadgeText} +
+ )}
); }; diff --git a/src/components/common/Picker.tsx b/src/components/common/Picker.tsx index 407af9a64..aadeeb863 100644 --- a/src/components/common/Picker.tsx +++ b/src/components/common/Picker.tsx @@ -9,7 +9,6 @@ import { requestMeasure } from '../../lib/fasterdom/fasterdom'; import { isUserId } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; import { buildCollectionByKey } from '../../util/iteratees'; -import { MEMO_EMPTY_ARRAY } from '../../util/memo'; import useInfiniteScroll from '../../hooks/useInfiniteScroll'; import useLang from '../../hooks/useLang'; @@ -30,6 +29,9 @@ type OwnProps = { className?: string; itemIds: string[]; selectedIds: string[]; + lockedSelectedIds?: string[]; + lockedUnselectedIds?: string[]; + lockedUnselectedSubtitle?: string; filterValue?: string; filterPlaceholder?: string; notFoundText?: string; @@ -38,12 +40,11 @@ type OwnProps = { noScrollRestore?: boolean; isSearchable?: boolean; isRoundCheckbox?: boolean; - lockedIds?: string[]; forceShowSelf?: boolean; isViewOnly?: boolean; onSelectedIdsChange?: (ids: string[]) => void; onFilterChange?: (value: string) => void; - onDisabledClick?: (id: string) => void; + onDisabledClick?: (id: string, isSelected: boolean) => void; onLoadMore?: () => void; isCountryList?: boolean; countryList?: ApiCountry[]; @@ -67,7 +68,9 @@ const Picker: FC = ({ noScrollRestore, isSearchable, isRoundCheckbox, - lockedIds, + lockedSelectedIds, + lockedUnselectedIds, + lockedUnselectedSubtitle, forceShowSelf, isViewOnly, onSelectedIdsChange, @@ -90,32 +93,39 @@ const Picker: FC = ({ }, FOCUS_DELAY_MS); }, [isSearchable]); - const [lockedSelectedIds, unlockedSelectedIds] = useMemo(() => { - if (!lockedIds?.length) return [MEMO_EMPTY_ARRAY, selectedIds]; - const unlockedIds = selectedIds.filter((id) => !lockedIds.includes(id)); - return [lockedIds, unlockedIds]; - }, [selectedIds, lockedIds]); + const lockedSelectedIdsSet = useMemo(() => new Set(lockedSelectedIds), [lockedSelectedIds]); + const lockedUnselectedIdsSet = useMemo(() => new Set(lockedUnselectedIds), [lockedUnselectedIds]); - const lockedIdsSet = useMemo(() => new Set(lockedIds), [lockedIds]); + const unlockedSelectedIds = useMemo(() => { + return selectedIds.filter((id) => !lockedSelectedIdsSet.has(id)); + }, [lockedSelectedIdsSet, selectedIds]); const sortedItemIds = useMemo(() => { - const lockedBucket: string[] = []; + const lockedSelectedBucket: string[] = []; const unlockedBucket: string[] = []; + const lockedUnselectableBucket: string[] = []; itemIds.forEach((id) => { - if (lockedIdsSet.has(id)) { - lockedBucket.push(id); + if (lockedSelectedIdsSet.has(id)) { + lockedSelectedBucket.push(id); + } else if (lockedUnselectedIdsSet.has(id)) { + lockedUnselectableBucket.push(id); } else { unlockedBucket.push(id); } }); - return lockedBucket.concat(unlockedBucket); - }, [itemIds, lockedIdsSet]); + return lockedSelectedBucket.concat(unlockedBucket).concat(lockedUnselectableBucket); + }, [itemIds, lockedSelectedIdsSet, lockedUnselectedIdsSet]); const handleItemClick = useLastCallback((id: string) => { - if (lockedIdsSet.has(id)) { - onDisabledClick?.(id); + if (lockedSelectedIdsSet.has(id)) { + onDisabledClick?.(id, true); + return; + } + + if (lockedUnselectedIdsSet.has(id)) { + onDisabledClick?.(id, false); return; } @@ -144,13 +154,20 @@ const Picker: FC = ({ }, [countryList]); const renderChatInfo = (id: string) => { + const isUnselectable = lockedUnselectedIdsSet.has(id); if (isCountryList && countriesByIso) { const country = countriesByIso[id]; return
{country.defaultName}
; } else if (isUserId(id)) { - return ; + return ( + + ); } else { - return ; + return ; } }; @@ -158,7 +175,7 @@ const Picker: FC = ({
{isSearchable && (
- {lockedSelectedIds.map((id, i) => ( + {lockedSelectedIds?.map((id, i) => ( = ({ = ({ noScrollRestore={noScrollRestore} > {viewportIds.map((id) => { + const shouldRenderLockIcon = lockedUnselectedIdsSet.has(id); + const isLocked = lockedSelectedIdsSet.has(id) || shouldRenderLockIcon; const renderCheckbox = () => { - return isViewOnly ? undefined : ( + return (isViewOnly || shouldRenderLockIcon) ? undefined : ( @@ -210,9 +229,10 @@ const Picker: FC = ({ handleItemClick(id)} ripple diff --git a/src/components/left/settings/folders/SettingsShareChatlist.tsx b/src/components/left/settings/folders/SettingsShareChatlist.tsx index c544ee6e3..a0ac72a2c 100644 --- a/src/components/left/settings/folders/SettingsShareChatlist.tsx +++ b/src/components/left/settings/folders/SettingsShareChatlist.tsx @@ -170,7 +170,7 @@ const SettingsShareChatlist: FC = ({
= ({ - chatId, userIds, -}) => { - const { sendInviteMessages, closeInviteViaLinkModal } = getActions(); - - const lang = useLang(); - const [selectedMemberIds, setSelectedMemberIds] = useState([]); - - useEffect(() => { - if (userIds) { - setSelectedMemberIds(userIds); - } - }, [userIds]); - - const handleClose = useLastCallback(() => closeInviteViaLinkModal()); - const handleSkip = useLastCallback(() => closeInviteViaLinkModal()); - - const handleSendInviteLink = useCallback(() => { - sendInviteMessages({ chatId: chatId!, userIds: selectedMemberIds! }); - closeInviteViaLinkModal(); - }, [selectedMemberIds, chatId]); - - const userNames = useMemo(() => { - const usersById = getGlobal().users.byId; - return userIds?.map((userId) => getUserFullName(usersById[userId])).join(', '); - }, [userIds]); - - const canSendInviteLink = useMemo(() => { - if (!chatId) { - return false; - } - const chat = selectChat(getGlobal(), chatId); - return Boolean(chat?.isCreator || chat?.adminRights?.inviteUsers); - }, [chatId]); - - const contentText = useMemo(() => { - const langKey = canSendInviteLink - ? 'SendInviteLink.TextAvailableSingleUser' - : 'SendInviteLink.TextUnavailableSingleUser'; - return renderText(lang(langKey, userNames), ['simple_markdown']); - }, [userNames, lang, canSendInviteLink]); - - return ( - -

- {contentText} -

- -
- {canSendInviteLink && ( - - )} - {canSendInviteLink && ( - - )} - {!canSendInviteLink && ( - - )} -
-
- ); -}; - -export default memo(InviteViaLinkModal); diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index ae83b08c5..3fd1557dd 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -79,6 +79,7 @@ import AttachBotInstallModal from '../modals/attachBotInstall/AttachBotInstallMo import BoostModal from '../modals/boost/BoostModal.async'; import ChatlistModal from '../modals/chatlist/ChatlistModal.async'; import GiftCodeModal from '../modals/giftcode/GiftCodeModal.async'; +import InviteViaLinkModal from '../modals/inviteViaLink/InviteViaLinkModal.async'; import MapModal from '../modals/map/MapModal.async'; import OneTimeMediaModal from '../modals/oneTimeMedia/OneTimeMediaModal.async'; import UrlAuthModal from '../modals/urlAuth/UrlAuthModal.async'; @@ -97,7 +98,6 @@ import DraftRecipientPicker from './DraftRecipientPicker.async'; import ForwardRecipientPicker from './ForwardRecipientPicker.async'; import GameModal from './GameModal'; import HistoryCalendar from './HistoryCalendar.async'; -import InviteViaLinkModal from './InviteViaLinkModal.async'; import NewContactModal from './NewContactModal.async'; import Notifications from './Notifications.async'; import PremiumLimitReachedModal from './premium/common/PremiumLimitReachedModal.async'; @@ -606,7 +606,7 @@ const Main: FC = ({ - +
); }; diff --git a/src/components/modals/chatlist/ChatlistAlready.tsx b/src/components/modals/chatlist/ChatlistAlready.tsx index 37804101b..3a50df18e 100644 --- a/src/components/modals/chatlist/ChatlistAlready.tsx +++ b/src/components/modals/chatlist/ChatlistAlready.tsx @@ -86,7 +86,7 @@ const ChatlistAlready: FC = ({ invite, folder }) => {
diff --git a/src/components/modals/chatlist/ChatlistNew.tsx b/src/components/modals/chatlist/ChatlistNew.tsx index 6991b8904..610bc7a9c 100644 --- a/src/components/modals/chatlist/ChatlistNew.tsx +++ b/src/components/modals/chatlist/ChatlistNew.tsx @@ -71,7 +71,7 @@ const ChatlistNew: FC = ({ invite }) => { diff --git a/src/components/main/InviteViaLinkModal.async.tsx b/src/components/modals/inviteViaLink/InviteViaLinkModal.async.tsx similarity index 56% rename from src/components/main/InviteViaLinkModal.async.tsx rename to src/components/modals/inviteViaLink/InviteViaLinkModal.async.tsx index e14526b9a..a26fcf32b 100644 --- a/src/components/main/InviteViaLinkModal.async.tsx +++ b/src/components/modals/inviteViaLink/InviteViaLinkModal.async.tsx @@ -1,15 +1,15 @@ -import type { FC } from '../../lib/teact/teact'; -import React from '../../lib/teact/teact'; +import type { FC } from '../../../lib/teact/teact'; +import React from '../../../lib/teact/teact'; import type { OwnProps } from './InviteViaLinkModal'; -import { Bundles } from '../../util/moduleLoader'; +import { Bundles } from '../../../util/moduleLoader'; -import useModuleLoader from '../../hooks/useModuleLoader'; +import useModuleLoader from '../../../hooks/useModuleLoader'; const InviteViaLinkModalAsync: FC = (props) => { - const { userIds, chatId } = props; - const InviteViaLinkModal = useModuleLoader(Bundles.Extra, 'InviteViaLinkModal', !(userIds && chatId)); + const { chatId } = props; + const InviteViaLinkModal = useModuleLoader(Bundles.Extra, 'InviteViaLinkModal', !chatId); // eslint-disable-next-line react/jsx-props-no-spreading return InviteViaLinkModal ? : undefined; diff --git a/src/components/modals/inviteViaLink/InviteViaLinkModal.module.scss b/src/components/modals/inviteViaLink/InviteViaLinkModal.module.scss new file mode 100644 index 000000000..9b163ecf6 --- /dev/null +++ b/src/components/modals/inviteViaLink/InviteViaLinkModal.module.scss @@ -0,0 +1,38 @@ +.content { + display: flex; + flex-direction: column; + align-items: center; +} + +.closeButton { + position: absolute; + top: 0.5rem; + left: 0.5rem; + z-index: 1; +} + +.contentText { + color: var(--color-text-secondary); + text-align: center !important; + text-wrap: pretty; +} + +.title { + font-size: 1.25rem; + margin-top: 1rem; + margin-bottom: 0; +} + +.separator { + width: 100%; + margin-top: 1rem; +} + +.userPicker { + flex-shrink: 0; + width: 100%; +} + +.sendInvites, .avatarList { + margin-top: 1rem; +} diff --git a/src/components/modals/inviteViaLink/InviteViaLinkModal.tsx b/src/components/modals/inviteViaLink/InviteViaLinkModal.tsx new file mode 100644 index 000000000..46f631638 --- /dev/null +++ b/src/components/modals/inviteViaLink/InviteViaLinkModal.tsx @@ -0,0 +1,206 @@ +import type { FC } from '../../../lib/teact/teact'; +import React, { + memo, useCallback, + useEffect, + useMemo, useState, +} from '../../../lib/teact/teact'; +import { getActions, getGlobal, withGlobal } from '../../../global'; + +import type { ApiChat, ApiMissingInvitedUser } from '../../../api/types'; + +import { getUserFullName } from '../../../global/helpers'; +import { selectChat } from '../../../global/selectors'; +import { partition } from '../../../util/iteratees'; +import renderText from '../../common/helpers/renderText'; + +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; + +import AvatarList from '../../common/AvatarList'; +import Picker from '../../common/Picker'; +import Button from '../../ui/Button'; +import Modal from '../../ui/Modal'; +import Separator from '../../ui/Separator'; + +import styles from './InviteViaLinkModal.module.scss'; + +export type OwnProps = { + chatId?: string; + missingUsers?: ApiMissingInvitedUser[]; +}; + +type StateProps = { + chat?: ApiChat; +}; + +const InviteViaLinkModal: FC = ({ + missingUsers, + chat, +}) => { + const { sendInviteMessages, closeInviteViaLinkModal, openPremiumModal } = getActions(); + + const lang = useLang(); + const [selectedMemberIds, setSelectedMemberIds] = useState([]); + + const userIds = useMemo(() => missingUsers?.map((user) => user.id), [missingUsers]); + const [unselectableIds, selectableIds] = useMemo(() => { + if (!missingUsers?.length) return [[], []]; + const [requirePremiumIds, regularIds] = partition(missingUsers, (user) => user.isRequiringPremiumToMessage); + return [requirePremiumIds.map((user) => user.id), regularIds.map((user) => user.id)]; + }, [missingUsers]); + + const invitableWithPremiumIds = useMemo(() => { + return missingUsers?.filter((user) => user.isRequiringPremiumToInvite || user.isRequiringPremiumToMessage) + .map((user) => user.id); + }, [missingUsers]); + + const isEveryPremiumBlocksPm = useMemo(() => { + if (!missingUsers) return undefined; + return !missingUsers.some((user) => user.isRequiringPremiumToInvite && !user.isRequiringPremiumToMessage); + }, [missingUsers]); + + const topListPeers = useMemo(() => { + const users = getGlobal().users.byId; + return invitableWithPremiumIds?.map((id) => users[id]); + }, [invitableWithPremiumIds]); + + useEffect(() => { + setSelectedMemberIds(selectableIds); + }, [selectableIds]); + + const handleClose = useLastCallback(() => closeInviteViaLinkModal()); + + const handleSendInviteLink = useCallback(() => { + sendInviteMessages({ chatId: chat!.id, userIds: selectedMemberIds! }); + closeInviteViaLinkModal(); + }, [selectedMemberIds, chat]); + + const handleOpenPremiumModal = useCallback(() => { + openPremiumModal(); + }, []); + + const canSendInviteLink = useMemo(() => { + if (!chat) return undefined; + return Boolean(chat?.isCreator || chat?.adminRights?.inviteUsers); + }, [chat]); + + const inviteSectionText = useMemo(() => { + return canSendInviteLink + ? lang(missingUsers?.length === 1 ? 'InviteBlockedOneMessage' : 'InviteBlockedManyMessage') + : lang('InviteRestrictedUsers2', missingUsers?.length); + }, [canSendInviteLink, lang, missingUsers?.length]); + + const premiumSectionText = useMemo(() => { + if (!invitableWithPremiumIds?.length || !topListPeers?.length) return undefined; + const prefix = isEveryPremiumBlocksPm ? 'InviteMessagePremiumBlocked' : 'InvitePremiumBlocked'; + let langKey = `${prefix}One`; + let params = [getUserFullName(topListPeers[0])]; + if (invitableWithPremiumIds.length === 2) { + langKey = `${prefix}Two`; + params = [getUserFullName(topListPeers[0]), getUserFullName(topListPeers[1])]; + } else if (invitableWithPremiumIds.length === 3) { + langKey = `${prefix}Three`; + params = [getUserFullName(topListPeers[0]), getUserFullName(topListPeers[1]), getUserFullName(topListPeers[2])]; + } else if (invitableWithPremiumIds.length > 3) { + langKey = `${prefix}Many`; + params = [ + getUserFullName(topListPeers[0]), + getUserFullName(topListPeers[1]), + (invitableWithPremiumIds!.length - 2).toString(), + ]; + } + + return lang(langKey, params, undefined, topListPeers.length); + }, [invitableWithPremiumIds, isEveryPremiumBlocksPm, lang, topListPeers]); + + if (!userIds) return undefined; + + const hasPremiumSection = Boolean(topListPeers?.length); + const hasSelectableSection = Boolean(selectableIds?.length); + + return ( + + + {premiumSectionText && ( + <> + +

+ {canSendInviteLink ? lang('InvitePremiumBlockedTitle') : lang('ChannelInviteViaLinkRestricted')} +

+

+ {renderText(premiumSectionText, ['simple_markdown'])} +

+ + + )} + {hasPremiumSection && hasSelectableSection && ( + + {lang('InvitePremiumBlockedOr')} + + )} + {hasSelectableSection && ( + <> +

{lang('InviteBlockedTitle')}

+

+ {inviteSectionText} +

+ + {canSendInviteLink && ( + + )} + + )} +
+ ); +}; + +export default memo(withGlobal( + (global, { chatId }): StateProps => { + const chat = chatId ? selectChat(global, chatId) : undefined; + + return { + chat, + }; + }, +)(InviteViaLinkModal)); diff --git a/src/components/story/privacy/AllowDenyList.tsx b/src/components/story/privacy/AllowDenyList.tsx index da3f62d6f..8660068d0 100644 --- a/src/components/story/privacy/AllowDenyList.tsx +++ b/src/components/story/privacy/AllowDenyList.tsx @@ -42,7 +42,7 @@ function AllowDenyList({ key={id} itemIds={displayedIds} selectedIds={selectedIds ?? MEMO_EMPTY_ARRAY} - lockedIds={lockedIds} + lockedSelectedIds={lockedIds} filterValue={searchQuery} filterPlaceholder={lang('Search')} searchInputId={`${id}-picker-search`} diff --git a/src/global/actions/api/chats.ts b/src/global/actions/api/chats.ts index b9dad48b1..26c6679ce 100644 --- a/src/global/actions/api/chats.ts +++ b/src/global/actions/api/chats.ts @@ -1,6 +1,6 @@ import type { ApiChat, ApiChatFolder, ApiChatlistExportedInvite, - ApiChatMember, ApiError, ApiUser, + ApiChatMember, ApiError, ApiMissingInvitedUser, ApiUser, } from '../../../api/types'; import type { RequiredGlobalActions } from '../../index'; import type { @@ -59,7 +59,6 @@ import { addSimilarChannels, addUsers, addUserStatuses, - addUsersToRestrictedInviteList, deleteChatMessages, deleteTopic, leaveChat, @@ -80,6 +79,7 @@ import { updateChatsLastMessageId, updateListedTopicIds, updateManagementProgress, + updateMissingInvitedUsers, updatePeerFullInfo, updateThread, updateThreadInfo, @@ -692,11 +692,11 @@ addActionHandler('createChannel', async (global, actions, payload): Promise id); + missingInvitedUsers = result?.missingUsers; } catch (error) { global = getGlobal(); @@ -732,9 +732,9 @@ addActionHandler('createChannel', async (global, actions, payload): Promise id); - if (restrictedUserIds) { + if (missingUsers) { global = getGlobal(); - global = addUsersToRestrictedInviteList(global, restrictedUserIds, chatId, tabId); + global = updateMissingInvitedUsers(global, chatId, missingUsers, tabId); setGlobal(global); } @@ -915,10 +912,6 @@ addActionHandler('createGroupChat', async (global, actions, payload): Promise id), createdChatId!, tabId); - setGlobal(global); } } }); @@ -1926,7 +1919,7 @@ addActionHandler('loadMoreMembers', async (global, actions, payload): Promise => { const { chatId, memberIds, tabId = getCurrentTabId() } = payload; const chat = selectChat(global, chatId); - const users = (memberIds as string[]).map((userId) => selectUser(global, userId)).filter(Boolean); + const users = memberIds.map((userId) => selectUser(global, userId)).filter(Boolean); if (!chat || !users.length) { return; @@ -1934,10 +1927,9 @@ addActionHandler('addChatMembers', async (global, actions, payload): Promise user.id); - if (restrictedUserIds) { + if (missingUsers) { global = getGlobal(); - global = addUsersToRestrictedInviteList(global, restrictedUserIds, chat.id, tabId); + global = updateMissingInvitedUsers(global, chatId, missingUsers, tabId); setGlobal(global); } actions.setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Closed, tabId }); diff --git a/src/global/reducers/users.ts b/src/global/reducers/users.ts index 0079977c1..60bd739b0 100644 --- a/src/global/reducers/users.ts +++ b/src/global/reducers/users.ts @@ -1,9 +1,11 @@ -import type { ApiUser, ApiUserFullInfo, ApiUserStatus } from '../../api/types'; +import type { + ApiMissingInvitedUser, ApiUser, ApiUserFullInfo, ApiUserStatus, +} from '../../api/types'; import type { GlobalState, TabArgs, TabState } from '../types'; import { areDeepEqual } from '../../util/areDeepEqual'; import { getCurrentTabId } from '../../util/establishMultitabRole'; -import { omit, pick, unique } from '../../util/iteratees'; +import { omit, pick } from '../../util/iteratees'; import { MEMO_EMPTY_ARRAY } from '../../util/memo'; import { selectTabState } from '../selectors'; import { updateChat } from './chats'; @@ -264,17 +266,21 @@ export function closeNewContactDialog( }, tabId); } -export function addUsersToRestrictedInviteList( +export function updateMissingInvitedUsers( global: T, - userIds: string[], chatId: string, + missingUsers: ApiMissingInvitedUser[], ...[tabId = getCurrentTabId()]: TabArgs ): T { - const { inviteViaLinkModal } = selectTabState(global, tabId); + if (!missingUsers.length) { + return updateTabState(global, { + inviteViaLinkModal: undefined, + }, tabId); + } + return updateTabState(global, { inviteViaLinkModal: { - ...inviteViaLinkModal, - restrictedUserIds: unique([...inviteViaLinkModal?.restrictedUserIds ?? [], ...userIds]), + missingUsers, chatId, }, }, tabId); diff --git a/src/global/types.ts b/src/global/types.ts index ed22b5cd4..893dcf260 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -33,6 +33,7 @@ import type { ApiKeyboardButton, ApiMessage, ApiMessageEntity, + ApiMissingInvitedUser, ApiMyBoost, ApiNewPoll, ApiNotification, @@ -702,7 +703,7 @@ export type TabState = { }; inviteViaLinkModal?: { - restrictedUserIds: string[]; + missingUsers: ApiMissingInvitedUser[]; chatId: string; }; diff --git a/src/util/iteratees.ts b/src/util/iteratees.ts index b3ec7bd56..50df26fc5 100644 --- a/src/util/iteratees.ts +++ b/src/util/iteratees.ts @@ -163,7 +163,7 @@ export function split(array: T[], chunkSize: number) { } export function partition( - array: T[], filter: (value: T, index: number, array: T[]) => boolean, + array: T[], filter: (value: T, index: number, array: T[]) => boolean | undefined, ): [T[], T[]] { const pass: T[] = []; const fail: T[] = [];