Invite via Link: Display users that require Premium (#4461)
This commit is contained in:
parent
8d942e62bb
commit
0631a9111d
@ -84,5 +84,5 @@ export { default as Management } from '../components/right/management/Management
|
|||||||
|
|
||||||
export { default as PaymentModal } from '../components/payment/PaymentModal';
|
export { default as PaymentModal } from '../components/payment/PaymentModal';
|
||||||
export { default as ReceiptModal } from '../components/payment/ReceiptModal';
|
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';
|
export { default as OneTimeMediaModal } from '../components/modals/oneTimeMedia/OneTimeMediaModal';
|
||||||
|
|||||||
@ -48,26 +48,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatars {
|
.avatars .Avatar {
|
||||||
display: flex;
|
width: 2.25rem !important;
|
||||||
flex-direction: row;
|
height: 2.25rem !important;
|
||||||
align-items: center;
|
margin-inline-end: unset !important;
|
||||||
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.join {
|
.join {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
|||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
|
|
||||||
import Avatar from '../../common/Avatar';
|
import AvatarList from '../../common/AvatarList';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
import './GroupCallTopPane.scss';
|
import './GroupCallTopPane.scss';
|
||||||
@ -110,14 +110,9 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
|
|||||||
<span className="title">{lang('VoipGroupVoiceChat')}</span>
|
<span className="title">{lang('VoipGroupVoiceChat')}</span>
|
||||||
<span className="participants">{lang('Participants', renderingParticipantCount ?? 0, 'i')}</span>
|
<span className="participants">{lang('Participants', renderingParticipantCount ?? 0, 'i')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="avatars">
|
{Boolean(renderingFetchedParticipants?.length) && (
|
||||||
{renderingFetchedParticipants?.map((peer) => (
|
<AvatarList size="small" peers={renderingFetchedParticipants} className="avatars" />
|
||||||
<Avatar
|
)}
|
||||||
key={peer.id}
|
|
||||||
peer={peer}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<Button round className="join">
|
<Button round className="join">
|
||||||
{lang('VoipChatJoin')}
|
{lang('VoipChatJoin')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
.root {
|
.root {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
--spacing: calc(var(--size) * 0.4);
|
--spacing: calc(var(--size) * 0.4);
|
||||||
--spacing-gap: calc(var(--size) * 0.04);
|
--spacing-gap: calc(var(--size) * 0.04);
|
||||||
--size: 0px;
|
--size: 0px;
|
||||||
@ -58,3 +59,21 @@
|
|||||||
.root[dir="rtl"] .avatar {
|
.root[dir="rtl"] .avatar {
|
||||||
--offset: calc(100% + var(--half-size) - var(--spacing));
|
--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;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
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 { ApiPeer } from '../../api/types';
|
||||||
import type { AvatarSize } from './Avatar';
|
import type { AvatarSize } from './Avatar';
|
||||||
@ -12,25 +12,41 @@ import Avatar from './Avatar';
|
|||||||
|
|
||||||
import styles from './AvatarList.module.scss';
|
import styles from './AvatarList.module.scss';
|
||||||
|
|
||||||
|
const DEFAULT_LIMIT = 3;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
size: AvatarSize;
|
size: AvatarSize;
|
||||||
peers?: ApiPeer[];
|
peers?: ApiPeer[];
|
||||||
className?: string;
|
className?: string;
|
||||||
|
limit?: number;
|
||||||
|
badgeText?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AvatarList: FC<OwnProps> = ({
|
const AvatarList: FC<OwnProps> = ({
|
||||||
peers,
|
peers,
|
||||||
size,
|
size,
|
||||||
className,
|
className,
|
||||||
|
limit = DEFAULT_LIMIT,
|
||||||
|
badgeText,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName(className, styles.root, styles[`size-${size}`])}
|
className={buildClassName(className, styles.root, styles[`size-${size}`])}
|
||||||
dir={lang.isRtl ? 'rtl' : 'ltr'}
|
dir={lang.isRtl ? 'rtl' : 'ltr'}
|
||||||
>
|
>
|
||||||
{peers?.map((peer) => <Avatar size={size} peer={peer} className={styles.avatar} />)}
|
{peers?.slice(0, limit).map((peer) => <Avatar size={size} peer={peer} className={styles.avatar} />)}
|
||||||
|
{renderingBadgeText && (
|
||||||
|
<div className={styles.badge}>
|
||||||
|
{renderingBadgeText}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
|||||||
import { isUserId } from '../../global/helpers';
|
import { isUserId } from '../../global/helpers';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { buildCollectionByKey } from '../../util/iteratees';
|
import { buildCollectionByKey } from '../../util/iteratees';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
|
||||||
|
|
||||||
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -30,6 +29,9 @@ type OwnProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
itemIds: string[];
|
itemIds: string[];
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
|
lockedSelectedIds?: string[];
|
||||||
|
lockedUnselectedIds?: string[];
|
||||||
|
lockedUnselectedSubtitle?: string;
|
||||||
filterValue?: string;
|
filterValue?: string;
|
||||||
filterPlaceholder?: string;
|
filterPlaceholder?: string;
|
||||||
notFoundText?: string;
|
notFoundText?: string;
|
||||||
@ -38,12 +40,11 @@ type OwnProps = {
|
|||||||
noScrollRestore?: boolean;
|
noScrollRestore?: boolean;
|
||||||
isSearchable?: boolean;
|
isSearchable?: boolean;
|
||||||
isRoundCheckbox?: boolean;
|
isRoundCheckbox?: boolean;
|
||||||
lockedIds?: string[];
|
|
||||||
forceShowSelf?: boolean;
|
forceShowSelf?: boolean;
|
||||||
isViewOnly?: boolean;
|
isViewOnly?: boolean;
|
||||||
onSelectedIdsChange?: (ids: string[]) => void;
|
onSelectedIdsChange?: (ids: string[]) => void;
|
||||||
onFilterChange?: (value: string) => void;
|
onFilterChange?: (value: string) => void;
|
||||||
onDisabledClick?: (id: string) => void;
|
onDisabledClick?: (id: string, isSelected: boolean) => void;
|
||||||
onLoadMore?: () => void;
|
onLoadMore?: () => void;
|
||||||
isCountryList?: boolean;
|
isCountryList?: boolean;
|
||||||
countryList?: ApiCountry[];
|
countryList?: ApiCountry[];
|
||||||
@ -67,7 +68,9 @@ const Picker: FC<OwnProps> = ({
|
|||||||
noScrollRestore,
|
noScrollRestore,
|
||||||
isSearchable,
|
isSearchable,
|
||||||
isRoundCheckbox,
|
isRoundCheckbox,
|
||||||
lockedIds,
|
lockedSelectedIds,
|
||||||
|
lockedUnselectedIds,
|
||||||
|
lockedUnselectedSubtitle,
|
||||||
forceShowSelf,
|
forceShowSelf,
|
||||||
isViewOnly,
|
isViewOnly,
|
||||||
onSelectedIdsChange,
|
onSelectedIdsChange,
|
||||||
@ -90,32 +93,39 @@ const Picker: FC<OwnProps> = ({
|
|||||||
}, FOCUS_DELAY_MS);
|
}, FOCUS_DELAY_MS);
|
||||||
}, [isSearchable]);
|
}, [isSearchable]);
|
||||||
|
|
||||||
const [lockedSelectedIds, unlockedSelectedIds] = useMemo(() => {
|
const lockedSelectedIdsSet = useMemo(() => new Set(lockedSelectedIds), [lockedSelectedIds]);
|
||||||
if (!lockedIds?.length) return [MEMO_EMPTY_ARRAY, selectedIds];
|
const lockedUnselectedIdsSet = useMemo(() => new Set(lockedUnselectedIds), [lockedUnselectedIds]);
|
||||||
const unlockedIds = selectedIds.filter((id) => !lockedIds.includes(id));
|
|
||||||
return [lockedIds, unlockedIds];
|
|
||||||
}, [selectedIds, lockedIds]);
|
|
||||||
|
|
||||||
const lockedIdsSet = useMemo(() => new Set(lockedIds), [lockedIds]);
|
const unlockedSelectedIds = useMemo(() => {
|
||||||
|
return selectedIds.filter((id) => !lockedSelectedIdsSet.has(id));
|
||||||
|
}, [lockedSelectedIdsSet, selectedIds]);
|
||||||
|
|
||||||
const sortedItemIds = useMemo(() => {
|
const sortedItemIds = useMemo(() => {
|
||||||
const lockedBucket: string[] = [];
|
const lockedSelectedBucket: string[] = [];
|
||||||
const unlockedBucket: string[] = [];
|
const unlockedBucket: string[] = [];
|
||||||
|
const lockedUnselectableBucket: string[] = [];
|
||||||
|
|
||||||
itemIds.forEach((id) => {
|
itemIds.forEach((id) => {
|
||||||
if (lockedIdsSet.has(id)) {
|
if (lockedSelectedIdsSet.has(id)) {
|
||||||
lockedBucket.push(id);
|
lockedSelectedBucket.push(id);
|
||||||
|
} else if (lockedUnselectedIdsSet.has(id)) {
|
||||||
|
lockedUnselectableBucket.push(id);
|
||||||
} else {
|
} else {
|
||||||
unlockedBucket.push(id);
|
unlockedBucket.push(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return lockedBucket.concat(unlockedBucket);
|
return lockedSelectedBucket.concat(unlockedBucket).concat(lockedUnselectableBucket);
|
||||||
}, [itemIds, lockedIdsSet]);
|
}, [itemIds, lockedSelectedIdsSet, lockedUnselectedIdsSet]);
|
||||||
|
|
||||||
const handleItemClick = useLastCallback((id: string) => {
|
const handleItemClick = useLastCallback((id: string) => {
|
||||||
if (lockedIdsSet.has(id)) {
|
if (lockedSelectedIdsSet.has(id)) {
|
||||||
onDisabledClick?.(id);
|
onDisabledClick?.(id, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lockedUnselectedIdsSet.has(id)) {
|
||||||
|
onDisabledClick?.(id, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,13 +154,20 @@ const Picker: FC<OwnProps> = ({
|
|||||||
}, [countryList]);
|
}, [countryList]);
|
||||||
|
|
||||||
const renderChatInfo = (id: string) => {
|
const renderChatInfo = (id: string) => {
|
||||||
|
const isUnselectable = lockedUnselectedIdsSet.has(id);
|
||||||
if (isCountryList && countriesByIso) {
|
if (isCountryList && countriesByIso) {
|
||||||
const country = countriesByIso[id];
|
const country = countriesByIso[id];
|
||||||
return <div>{country.defaultName}</div>;
|
return <div>{country.defaultName}</div>;
|
||||||
} else if (isUserId(id)) {
|
} else if (isUserId(id)) {
|
||||||
return <PrivateChatInfo forceShowSelf={forceShowSelf} userId={id} />;
|
return (
|
||||||
|
<PrivateChatInfo
|
||||||
|
forceShowSelf={forceShowSelf}
|
||||||
|
userId={id}
|
||||||
|
status={isUnselectable ? lockedUnselectedSubtitle : undefined}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return <GroupChatInfo chatId={id} />;
|
return <GroupChatInfo chatId={id} status={isUnselectable ? lockedUnselectedSubtitle : undefined} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -158,7 +175,7 @@ const Picker: FC<OwnProps> = ({
|
|||||||
<div className={buildClassName('Picker', className)}>
|
<div className={buildClassName('Picker', className)}>
|
||||||
{isSearchable && (
|
{isSearchable && (
|
||||||
<div className="picker-header custom-scroll" dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className="picker-header custom-scroll" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
{lockedSelectedIds.map((id, i) => (
|
{lockedSelectedIds?.map((id, i) => (
|
||||||
<PickerSelectedItem
|
<PickerSelectedItem
|
||||||
peerId={id}
|
peerId={id}
|
||||||
isMinimized={shouldMinimize && i < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT}
|
isMinimized={shouldMinimize && i < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT}
|
||||||
@ -171,7 +188,7 @@ const Picker: FC<OwnProps> = ({
|
|||||||
<PickerSelectedItem
|
<PickerSelectedItem
|
||||||
peerId={id}
|
peerId={id}
|
||||||
isMinimized={
|
isMinimized={
|
||||||
shouldMinimize && i + lockedSelectedIds.length < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT
|
shouldMinimize && i + (lockedSelectedIds?.length || 0) < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT
|
||||||
}
|
}
|
||||||
canClose
|
canClose
|
||||||
onClick={handleItemClick}
|
onClick={handleItemClick}
|
||||||
@ -196,11 +213,13 @@ const Picker: FC<OwnProps> = ({
|
|||||||
noScrollRestore={noScrollRestore}
|
noScrollRestore={noScrollRestore}
|
||||||
>
|
>
|
||||||
{viewportIds.map((id) => {
|
{viewportIds.map((id) => {
|
||||||
|
const shouldRenderLockIcon = lockedUnselectedIdsSet.has(id);
|
||||||
|
const isLocked = lockedSelectedIdsSet.has(id) || shouldRenderLockIcon;
|
||||||
const renderCheckbox = () => {
|
const renderCheckbox = () => {
|
||||||
return isViewOnly ? undefined : (
|
return (isViewOnly || shouldRenderLockIcon) ? undefined : (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label=""
|
label=""
|
||||||
disabled={lockedIdsSet.has(id)}
|
disabled={isLocked}
|
||||||
checked={selectedIds.includes(id)}
|
checked={selectedIds.includes(id)}
|
||||||
round={isRoundCheckbox}
|
round={isRoundCheckbox}
|
||||||
/>
|
/>
|
||||||
@ -210,9 +229,10 @@ const Picker: FC<OwnProps> = ({
|
|||||||
<ListItem
|
<ListItem
|
||||||
key={id}
|
key={id}
|
||||||
className={buildClassName('chat-item-clickable picker-list-item', isRoundCheckbox && 'chat-item')}
|
className={buildClassName('chat-item-clickable picker-list-item', isRoundCheckbox && 'chat-item')}
|
||||||
disabled={lockedIdsSet.has(id)}
|
disabled={isLocked}
|
||||||
inactive={isViewOnly}
|
inactive={isViewOnly}
|
||||||
allowDisabledClick={Boolean(onDisabledClick)}
|
allowDisabledClick={Boolean(onDisabledClick)}
|
||||||
|
secondaryIcon={shouldRenderLockIcon ? 'lock-badge' : undefined}
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
onClick={() => handleItemClick(id)}
|
onClick={() => handleItemClick(id)}
|
||||||
ripple
|
ripple
|
||||||
|
|||||||
@ -170,7 +170,7 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="settings-item settings-item-chatlist">
|
<div className="settings-item settings-item-chatlist">
|
||||||
<Picker
|
<Picker
|
||||||
itemIds={itemIds}
|
itemIds={itemIds}
|
||||||
lockedIds={lockedIds}
|
lockedSelectedIds={lockedIds}
|
||||||
onSelectedIdsChange={handleSelectedIdsChange}
|
onSelectedIdsChange={handleSelectedIdsChange}
|
||||||
selectedIds={selectedIds}
|
selectedIds={selectedIds}
|
||||||
onDisabledClick={handleClickDisabled}
|
onDisabledClick={handleClickDisabled}
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
.contentText {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.userPicker {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
|
||||||
import React, {
|
|
||||||
memo, useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo, useState,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
import { getActions, getGlobal } from '../../global';
|
|
||||||
|
|
||||||
import { getUserFullName } from '../../global/helpers';
|
|
||||||
import { selectChat } from '../../global/selectors';
|
|
||||||
import renderText from '../common/helpers/renderText';
|
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
|
||||||
|
|
||||||
import Picker from '../common/Picker';
|
|
||||||
import Button from '../ui/Button';
|
|
||||||
import Modal from '../ui/Modal';
|
|
||||||
|
|
||||||
import styles from './InviteViaLinkModal.module.scss';
|
|
||||||
|
|
||||||
export type OwnProps = {
|
|
||||||
chatId?: string;
|
|
||||||
userIds?: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const InviteViaLinkModal: FC<OwnProps> = ({
|
|
||||||
chatId, userIds,
|
|
||||||
}) => {
|
|
||||||
const { sendInviteMessages, closeInviteViaLinkModal } = getActions();
|
|
||||||
|
|
||||||
const lang = useLang();
|
|
||||||
const [selectedMemberIds, setSelectedMemberIds] = useState<string[]>([]);
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Modal
|
|
||||||
isOpen={Boolean(userIds && chatId)}
|
|
||||||
title={canSendInviteLink ? lang('SendInviteLink.InviteTitle') : lang('SendInviteLink.LinkUnavailableTitle')}
|
|
||||||
onClose={handleClose}
|
|
||||||
isSlim
|
|
||||||
>
|
|
||||||
<p className={styles.contentText}>
|
|
||||||
{contentText}
|
|
||||||
</p>
|
|
||||||
<Picker
|
|
||||||
className={styles.userPicker}
|
|
||||||
itemIds={userIds!}
|
|
||||||
selectedIds={selectedMemberIds}
|
|
||||||
onSelectedIdsChange={setSelectedMemberIds}
|
|
||||||
isViewOnly={!canSendInviteLink}
|
|
||||||
isRoundCheckbox
|
|
||||||
/>
|
|
||||||
<div className="dialog-buttons">
|
|
||||||
{canSendInviteLink && (
|
|
||||||
<Button
|
|
||||||
className="confirm-dialog-button"
|
|
||||||
isText
|
|
||||||
onClick={handleSendInviteLink}
|
|
||||||
disabled={!selectedMemberIds.length}
|
|
||||||
>
|
|
||||||
{lang('SendInviteLink.ActionInvite')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{canSendInviteLink && (
|
|
||||||
<Button
|
|
||||||
className="confirm-dialog-button"
|
|
||||||
isText
|
|
||||||
onClick={handleSkip}
|
|
||||||
>
|
|
||||||
{lang('SendInviteLink.ActionSkip')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!canSendInviteLink && (
|
|
||||||
<Button
|
|
||||||
className="confirm-dialog-button"
|
|
||||||
isText
|
|
||||||
onClick={handleClose}
|
|
||||||
>
|
|
||||||
{lang('SendInviteLink.ActionClose')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(InviteViaLinkModal);
|
|
||||||
@ -79,6 +79,7 @@ import AttachBotInstallModal from '../modals/attachBotInstall/AttachBotInstallMo
|
|||||||
import BoostModal from '../modals/boost/BoostModal.async';
|
import BoostModal from '../modals/boost/BoostModal.async';
|
||||||
import ChatlistModal from '../modals/chatlist/ChatlistModal.async';
|
import ChatlistModal from '../modals/chatlist/ChatlistModal.async';
|
||||||
import GiftCodeModal from '../modals/giftcode/GiftCodeModal.async';
|
import GiftCodeModal from '../modals/giftcode/GiftCodeModal.async';
|
||||||
|
import InviteViaLinkModal from '../modals/inviteViaLink/InviteViaLinkModal.async';
|
||||||
import MapModal from '../modals/map/MapModal.async';
|
import MapModal from '../modals/map/MapModal.async';
|
||||||
import OneTimeMediaModal from '../modals/oneTimeMedia/OneTimeMediaModal.async';
|
import OneTimeMediaModal from '../modals/oneTimeMedia/OneTimeMediaModal.async';
|
||||||
import UrlAuthModal from '../modals/urlAuth/UrlAuthModal.async';
|
import UrlAuthModal from '../modals/urlAuth/UrlAuthModal.async';
|
||||||
@ -97,7 +98,6 @@ import DraftRecipientPicker from './DraftRecipientPicker.async';
|
|||||||
import ForwardRecipientPicker from './ForwardRecipientPicker.async';
|
import ForwardRecipientPicker from './ForwardRecipientPicker.async';
|
||||||
import GameModal from './GameModal';
|
import GameModal from './GameModal';
|
||||||
import HistoryCalendar from './HistoryCalendar.async';
|
import HistoryCalendar from './HistoryCalendar.async';
|
||||||
import InviteViaLinkModal from './InviteViaLinkModal.async';
|
|
||||||
import NewContactModal from './NewContactModal.async';
|
import NewContactModal from './NewContactModal.async';
|
||||||
import Notifications from './Notifications.async';
|
import Notifications from './Notifications.async';
|
||||||
import PremiumLimitReachedModal from './premium/common/PremiumLimitReachedModal.async';
|
import PremiumLimitReachedModal from './premium/common/PremiumLimitReachedModal.async';
|
||||||
@ -606,7 +606,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
<ReceiptModal isOpen={isReceiptModalOpen} onClose={clearReceipt} />
|
<ReceiptModal isOpen={isReceiptModalOpen} onClose={clearReceipt} />
|
||||||
<DeleteFolderDialog folder={deleteFolderDialog} />
|
<DeleteFolderDialog folder={deleteFolderDialog} />
|
||||||
<ReactionPicker isOpen={isReactionPickerOpen} />
|
<ReactionPicker isOpen={isReactionPickerOpen} />
|
||||||
<InviteViaLinkModal userIds={inviteViaLinkModal?.restrictedUserIds} chatId={inviteViaLinkModal?.chatId} />
|
<InviteViaLinkModal missingUsers={inviteViaLinkModal?.missingUsers} chatId={inviteViaLinkModal?.chatId} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -86,7 +86,7 @@ const ChatlistAlready: FC<OwnProps> = ({ invite, folder }) => {
|
|||||||
</div>
|
</div>
|
||||||
<Picker
|
<Picker
|
||||||
itemIds={invite.alreadyPeerIds}
|
itemIds={invite.alreadyPeerIds}
|
||||||
lockedIds={invite.alreadyPeerIds}
|
lockedSelectedIds={invite.alreadyPeerIds}
|
||||||
selectedIds={invite.alreadyPeerIds}
|
selectedIds={invite.alreadyPeerIds}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -71,7 +71,7 @@ const ChatlistNew: FC<OwnProps> = ({ invite }) => {
|
|||||||
</div>
|
</div>
|
||||||
<Picker
|
<Picker
|
||||||
itemIds={invite.peerIds}
|
itemIds={invite.peerIds}
|
||||||
lockedIds={joinedIds}
|
lockedSelectedIds={joinedIds}
|
||||||
onSelectedIdsChange={setSelectedPeerIds}
|
onSelectedIdsChange={setSelectedPeerIds}
|
||||||
selectedIds={selectedPeerIds}
|
selectedIds={selectedPeerIds}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React from '../../lib/teact/teact';
|
import React from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { OwnProps } from './InviteViaLinkModal';
|
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<OwnProps> = (props) => {
|
const InviteViaLinkModalAsync: FC<OwnProps> = (props) => {
|
||||||
const { userIds, chatId } = props;
|
const { chatId } = props;
|
||||||
const InviteViaLinkModal = useModuleLoader(Bundles.Extra, 'InviteViaLinkModal', !(userIds && chatId));
|
const InviteViaLinkModal = useModuleLoader(Bundles.Extra, 'InviteViaLinkModal', !chatId);
|
||||||
|
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
return InviteViaLinkModal ? <InviteViaLinkModal {...props} /> : undefined;
|
return InviteViaLinkModal ? <InviteViaLinkModal {...props} /> : undefined;
|
||||||
@ -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;
|
||||||
|
}
|
||||||
206
src/components/modals/inviteViaLink/InviteViaLinkModal.tsx
Normal file
206
src/components/modals/inviteViaLink/InviteViaLinkModal.tsx
Normal file
@ -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<OwnProps & StateProps> = ({
|
||||||
|
missingUsers,
|
||||||
|
chat,
|
||||||
|
}) => {
|
||||||
|
const { sendInviteMessages, closeInviteViaLinkModal, openPremiumModal } = getActions();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
const [selectedMemberIds, setSelectedMemberIds] = useState<string[]>([]);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Modal
|
||||||
|
isOpen={Boolean(userIds && chat)}
|
||||||
|
contentClassName={styles.content}
|
||||||
|
onClose={handleClose}
|
||||||
|
isSlim
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
color="translucent"
|
||||||
|
size="smaller"
|
||||||
|
className={styles.closeButton}
|
||||||
|
ariaLabel={lang('Close')}
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
<i className="icon icon-close" />
|
||||||
|
</Button>
|
||||||
|
{premiumSectionText && (
|
||||||
|
<>
|
||||||
|
<AvatarList
|
||||||
|
className={styles.avatarList}
|
||||||
|
peers={topListPeers}
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
<h3 className={styles.title}>
|
||||||
|
{canSendInviteLink ? lang('InvitePremiumBlockedTitle') : lang('ChannelInviteViaLinkRestricted')}
|
||||||
|
</h3>
|
||||||
|
<p className={styles.contentText}>
|
||||||
|
{renderText(premiumSectionText, ['simple_markdown'])}
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
withPremiumGradient
|
||||||
|
isShiny
|
||||||
|
size="smaller"
|
||||||
|
onClick={handleOpenPremiumModal}
|
||||||
|
>
|
||||||
|
{lang('InvitePremiumBlockedSubscribe')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{hasPremiumSection && hasSelectableSection && (
|
||||||
|
<Separator className={styles.separator}>
|
||||||
|
{lang('InvitePremiumBlockedOr')}
|
||||||
|
</Separator>
|
||||||
|
)}
|
||||||
|
{hasSelectableSection && (
|
||||||
|
<>
|
||||||
|
<h3 className={styles.title}>{lang('InviteBlockedTitle')}</h3>
|
||||||
|
<p className={styles.contentText}>
|
||||||
|
{inviteSectionText}
|
||||||
|
</p>
|
||||||
|
<Picker
|
||||||
|
className={styles.userPicker}
|
||||||
|
itemIds={userIds!}
|
||||||
|
selectedIds={selectedMemberIds}
|
||||||
|
lockedUnselectedIds={unselectableIds}
|
||||||
|
lockedUnselectedSubtitle={lang('InvitePremiumBlockedUser')}
|
||||||
|
onSelectedIdsChange={setSelectedMemberIds}
|
||||||
|
isViewOnly={!canSendInviteLink}
|
||||||
|
isRoundCheckbox
|
||||||
|
/>
|
||||||
|
{canSendInviteLink && (
|
||||||
|
<Button
|
||||||
|
className={styles.sendInvites}
|
||||||
|
size="smaller"
|
||||||
|
onClick={handleSendInviteLink}
|
||||||
|
disabled={!selectedMemberIds.length}
|
||||||
|
>
|
||||||
|
{lang('SendInviteLink.ActionInvite')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { chatId }): StateProps => {
|
||||||
|
const chat = chatId ? selectChat(global, chatId) : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
chat,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(InviteViaLinkModal));
|
||||||
@ -42,7 +42,7 @@ function AllowDenyList({
|
|||||||
key={id}
|
key={id}
|
||||||
itemIds={displayedIds}
|
itemIds={displayedIds}
|
||||||
selectedIds={selectedIds ?? MEMO_EMPTY_ARRAY}
|
selectedIds={selectedIds ?? MEMO_EMPTY_ARRAY}
|
||||||
lockedIds={lockedIds}
|
lockedSelectedIds={lockedIds}
|
||||||
filterValue={searchQuery}
|
filterValue={searchQuery}
|
||||||
filterPlaceholder={lang('Search')}
|
filterPlaceholder={lang('Search')}
|
||||||
searchInputId={`${id}-picker-search`}
|
searchInputId={`${id}-picker-search`}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiChatFolder, ApiChatlistExportedInvite,
|
ApiChat, ApiChatFolder, ApiChatlistExportedInvite,
|
||||||
ApiChatMember, ApiError, ApiUser,
|
ApiChatMember, ApiError, ApiMissingInvitedUser, ApiUser,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type {
|
import type {
|
||||||
@ -59,7 +59,6 @@ import {
|
|||||||
addSimilarChannels,
|
addSimilarChannels,
|
||||||
addUsers,
|
addUsers,
|
||||||
addUserStatuses,
|
addUserStatuses,
|
||||||
addUsersToRestrictedInviteList,
|
|
||||||
deleteChatMessages,
|
deleteChatMessages,
|
||||||
deleteTopic,
|
deleteTopic,
|
||||||
leaveChat,
|
leaveChat,
|
||||||
@ -80,6 +79,7 @@ import {
|
|||||||
updateChatsLastMessageId,
|
updateChatsLastMessageId,
|
||||||
updateListedTopicIds,
|
updateListedTopicIds,
|
||||||
updateManagementProgress,
|
updateManagementProgress,
|
||||||
|
updateMissingInvitedUsers,
|
||||||
updatePeerFullInfo,
|
updatePeerFullInfo,
|
||||||
updateThread,
|
updateThread,
|
||||||
updateThreadInfo,
|
updateThreadInfo,
|
||||||
@ -692,11 +692,11 @@ addActionHandler('createChannel', async (global, actions, payload): Promise<void
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
let createdChannel: ApiChat | undefined;
|
let createdChannel: ApiChat | undefined;
|
||||||
let restrictedUserIds: string[] | undefined;
|
let missingInvitedUsers: ApiMissingInvitedUser[] | undefined;
|
||||||
try {
|
try {
|
||||||
const result = await callApi('createChannel', { title, about, users });
|
const result = await callApi('createChannel', { title, about, users });
|
||||||
createdChannel = result?.channel;
|
createdChannel = result?.channel;
|
||||||
restrictedUserIds = result?.missingUsers?.map(({ id }) => id);
|
missingInvitedUsers = result?.missingUsers;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
@ -732,9 +732,9 @@ addActionHandler('createChannel', async (global, actions, payload): Promise<void
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
actions.openChat({ id: channelId, shouldReplaceHistory: true, tabId });
|
actions.openChat({ id: channelId, shouldReplaceHistory: true, tabId });
|
||||||
|
|
||||||
if (restrictedUserIds) {
|
if (missingInvitedUsers) {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = addUsersToRestrictedInviteList(global, restrictedUserIds, channelId, tabId);
|
global = updateMissingInvitedUsers(global, channelId, missingInvitedUsers, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -862,7 +862,6 @@ addActionHandler('createGroupChat', async (global, actions, payload): Promise<vo
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
let createdChatId: string | undefined;
|
|
||||||
try {
|
try {
|
||||||
const { chat: createdChat, missingUsers } = await callApi('createGroupChat', {
|
const { chat: createdChat, missingUsers } = await callApi('createGroupChat', {
|
||||||
title,
|
title,
|
||||||
@ -874,7 +873,6 @@ addActionHandler('createGroupChat', async (global, actions, payload): Promise<vo
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { id: chatId } = createdChat;
|
const { id: chatId } = createdChat;
|
||||||
createdChatId = chatId;
|
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = updateChat(global, chatId, createdChat);
|
global = updateChat(global, chatId, createdChat);
|
||||||
@ -891,10 +889,9 @@ addActionHandler('createGroupChat', async (global, actions, payload): Promise<vo
|
|||||||
tabId,
|
tabId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const restrictedUserIds = missingUsers?.map(({ id }) => id);
|
if (missingUsers) {
|
||||||
if (restrictedUserIds) {
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = addUsersToRestrictedInviteList(global, restrictedUserIds, chatId, tabId);
|
global = updateMissingInvitedUsers(global, chatId, missingUsers, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -915,10 +912,6 @@ addActionHandler('createGroupChat', async (global, actions, payload): Promise<vo
|
|||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
} else if ((err as ApiError).message === 'USER_PRIVACY_RESTRICTED') {
|
|
||||||
global = getGlobal();
|
|
||||||
global = addUsersToRestrictedInviteList(global, users.map(({ id }) => id), createdChatId!, tabId);
|
|
||||||
setGlobal(global);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1926,7 +1919,7 @@ addActionHandler('loadMoreMembers', async (global, actions, payload): Promise<vo
|
|||||||
addActionHandler('addChatMembers', async (global, actions, payload): Promise<void> => {
|
addActionHandler('addChatMembers', async (global, actions, payload): Promise<void> => {
|
||||||
const { chatId, memberIds, tabId = getCurrentTabId() } = payload;
|
const { chatId, memberIds, tabId = getCurrentTabId() } = payload;
|
||||||
const chat = selectChat(global, chatId);
|
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) {
|
if (!chat || !users.length) {
|
||||||
return;
|
return;
|
||||||
@ -1934,10 +1927,9 @@ addActionHandler('addChatMembers', async (global, actions, payload): Promise<voi
|
|||||||
|
|
||||||
actions.setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Loading, tabId });
|
actions.setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Loading, tabId });
|
||||||
const missingUsers = await callApi('addChatMembers', chat, users);
|
const missingUsers = await callApi('addChatMembers', chat, users);
|
||||||
const restrictedUserIds = missingUsers?.map((user) => user.id);
|
if (missingUsers) {
|
||||||
if (restrictedUserIds) {
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = addUsersToRestrictedInviteList(global, restrictedUserIds, chat.id, tabId);
|
global = updateMissingInvitedUsers(global, chatId, missingUsers, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}
|
}
|
||||||
actions.setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Closed, tabId });
|
actions.setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Closed, tabId });
|
||||||
|
|||||||
@ -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 type { GlobalState, TabArgs, TabState } from '../types';
|
||||||
|
|
||||||
import { areDeepEqual } from '../../util/areDeepEqual';
|
import { areDeepEqual } from '../../util/areDeepEqual';
|
||||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
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 { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||||
import { selectTabState } from '../selectors';
|
import { selectTabState } from '../selectors';
|
||||||
import { updateChat } from './chats';
|
import { updateChat } from './chats';
|
||||||
@ -264,17 +266,21 @@ export function closeNewContactDialog<T extends GlobalState>(
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addUsersToRestrictedInviteList<T extends GlobalState>(
|
export function updateMissingInvitedUsers<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
userIds: string[],
|
|
||||||
chatId: string,
|
chatId: string,
|
||||||
|
missingUsers: ApiMissingInvitedUser[],
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): T {
|
): T {
|
||||||
const { inviteViaLinkModal } = selectTabState(global, tabId);
|
if (!missingUsers.length) {
|
||||||
|
return updateTabState(global, {
|
||||||
|
inviteViaLinkModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
}
|
||||||
|
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
inviteViaLinkModal: {
|
inviteViaLinkModal: {
|
||||||
...inviteViaLinkModal,
|
missingUsers,
|
||||||
restrictedUserIds: unique([...inviteViaLinkModal?.restrictedUserIds ?? [], ...userIds]),
|
|
||||||
chatId,
|
chatId,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import type {
|
|||||||
ApiKeyboardButton,
|
ApiKeyboardButton,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
|
ApiMissingInvitedUser,
|
||||||
ApiMyBoost,
|
ApiMyBoost,
|
||||||
ApiNewPoll,
|
ApiNewPoll,
|
||||||
ApiNotification,
|
ApiNotification,
|
||||||
@ -702,7 +703,7 @@ export type TabState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
inviteViaLinkModal?: {
|
inviteViaLinkModal?: {
|
||||||
restrictedUserIds: string[];
|
missingUsers: ApiMissingInvitedUser[];
|
||||||
chatId: string;
|
chatId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -163,7 +163,7 @@ export function split<T extends any>(array: T[], chunkSize: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function partition<T extends unknown>(
|
export function partition<T extends unknown>(
|
||||||
array: T[], filter: (value: T, index: number, array: T[]) => boolean,
|
array: T[], filter: (value: T, index: number, array: T[]) => boolean | undefined,
|
||||||
): [T[], T[]] {
|
): [T[], T[]] {
|
||||||
const pass: T[] = [];
|
const pass: T[] = [];
|
||||||
const fail: T[] = [];
|
const fail: T[] = [];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user