Support gift transfer (#5555)
This commit is contained in:
parent
1419d396d3
commit
45fc7aac7c
@ -682,6 +682,13 @@ export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
|
||||
});
|
||||
}
|
||||
|
||||
case 'stargiftTransfer': {
|
||||
return new GramJs.InputInvoiceStarGiftTransfer({
|
||||
stargift: buildInputSavedStarGift(invoice.inputSavedGift),
|
||||
toId: buildInputPeer(invoice.recipient.id, invoice.recipient.accessHash),
|
||||
});
|
||||
}
|
||||
|
||||
case 'giveaway':
|
||||
default: {
|
||||
const purpose = buildInputStorePaymentPurpose(invoice.purpose);
|
||||
|
||||
@ -620,12 +620,12 @@ async function getFullChannelInfo(
|
||||
? exportedInvite.link
|
||||
: undefined;
|
||||
|
||||
const { members, userStatusesById } = (canViewParticipants && await fetchMembers(id, accessHash)) || {};
|
||||
const { members, userStatusesById } = (canViewParticipants && await fetchMembers({ chat })) || {};
|
||||
const { members: kickedMembers, userStatusesById: bannedStatusesById } = (
|
||||
canViewParticipants && adminRights && await fetchMembers(id, accessHash, 'kicked')
|
||||
canViewParticipants && adminRights && await fetchMembers({ chat, memberFilter: 'kicked' })
|
||||
) || {};
|
||||
const { members: adminMembers, userStatusesById: adminStatusesById } = (
|
||||
canViewParticipants && await fetchMembers(id, accessHash, 'admin')
|
||||
canViewParticipants && await fetchMembers({ chat, memberFilter: 'admin' })
|
||||
) || {};
|
||||
const botCommands = botInfo ? buildApiChatBotCommands(botInfo) : undefined;
|
||||
const memberInfoRequest = !chat.isNotJoined && chat.type === 'chatTypeChannel'
|
||||
@ -1285,35 +1285,44 @@ export function toggleSignatures({
|
||||
type ChannelMembersFilter =
|
||||
'kicked'
|
||||
| 'admin'
|
||||
| 'recent';
|
||||
| 'recent'
|
||||
| 'search';
|
||||
|
||||
export async function fetchMembers(
|
||||
chatId: string,
|
||||
accessHash: string,
|
||||
memberFilter: ChannelMembersFilter = 'recent',
|
||||
offset?: number,
|
||||
) {
|
||||
export async function fetchMembers({
|
||||
chat,
|
||||
memberFilter = 'recent',
|
||||
offset,
|
||||
query = '',
|
||||
} : {
|
||||
chat: ApiChat;
|
||||
memberFilter?: ChannelMembersFilter;
|
||||
offset?: number;
|
||||
query?: string;
|
||||
}) {
|
||||
let filter: GramJs.TypeChannelParticipantsFilter;
|
||||
|
||||
switch (memberFilter) {
|
||||
case 'kicked':
|
||||
filter = new GramJs.ChannelParticipantsKicked({ q: '' });
|
||||
filter = new GramJs.ChannelParticipantsKicked({ q: query });
|
||||
break;
|
||||
case 'admin':
|
||||
filter = new GramJs.ChannelParticipantsAdmins();
|
||||
break;
|
||||
case 'search':
|
||||
filter = new GramJs.ChannelParticipantsSearch({ q: query });
|
||||
break;
|
||||
default:
|
||||
filter = new GramJs.ChannelParticipantsRecent();
|
||||
break;
|
||||
}
|
||||
|
||||
const result = await invokeRequest(new GramJs.channels.GetParticipants({
|
||||
channel: buildInputEntity(chatId, accessHash) as GramJs.InputChannel,
|
||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
filter,
|
||||
offset,
|
||||
limit: MEMBERS_LOAD_SLICE,
|
||||
}), {
|
||||
abortControllerChatId: chatId,
|
||||
abortControllerChatId: chat.id,
|
||||
});
|
||||
|
||||
if (!result || result instanceof GramJs.channels.ChannelParticipantsNotModified) {
|
||||
|
||||
@ -694,7 +694,7 @@ export async function fetchStarGiftUpgradePreview({
|
||||
return result.sampleAttributes.map(buildApiStarGiftAttribute).filter(Boolean);
|
||||
}
|
||||
|
||||
export function upgradeGift({
|
||||
export function upgradeStarGift({
|
||||
inputSavedGift,
|
||||
shouldKeepOriginalDetails,
|
||||
}: {
|
||||
@ -709,6 +709,21 @@ export function upgradeGift({
|
||||
});
|
||||
}
|
||||
|
||||
export function transferStarGift({
|
||||
inputSavedGift,
|
||||
toPeer,
|
||||
}: {
|
||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||
toPeer: ApiPeer;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.payments.TransferStarGift({
|
||||
stargift: buildInputSavedStarGift(inputSavedGift),
|
||||
toId: buildInputPeer(toPeer.id, toPeer.accessHash),
|
||||
}), {
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchStarGiftWithdrawalUrl({
|
||||
inputGift,
|
||||
password,
|
||||
|
||||
@ -589,9 +589,16 @@ export type ApiInputInvoiceStarGiftUpgrade = {
|
||||
shouldKeepOriginalDetails?: true;
|
||||
};
|
||||
|
||||
export type ApiInputInvoiceStarGiftTransfer = {
|
||||
type: 'stargiftTransfer';
|
||||
inputSavedGift: ApiInputSavedStarGift;
|
||||
recipientId: string;
|
||||
};
|
||||
|
||||
export type ApiInputInvoice = ApiInputInvoiceMessage | ApiInputInvoiceSlug | ApiInputInvoiceGiveaway
|
||||
| ApiInputInvoiceGiftCode | ApiInputInvoiceStars | ApiInputInvoiceStarsGift | ApiInputInvoiceStarGiftUpgrade
|
||||
| ApiInputInvoiceStarsGiveaway | ApiInputInvoiceStarGift | ApiInputInvoiceChatInviteSubscription;
|
||||
| ApiInputInvoiceGiftCode | ApiInputInvoiceStars | ApiInputInvoiceStarsGift
|
||||
| ApiInputInvoiceStarsGiveaway | ApiInputInvoiceStarGift | ApiInputInvoiceChatInviteSubscription
|
||||
| ApiInputInvoiceStarGiftUpgrade | ApiInputInvoiceStarGiftTransfer;
|
||||
|
||||
/* Used for Invoice request */
|
||||
export type ApiRequestInputInvoiceMessage = {
|
||||
@ -641,6 +648,13 @@ export type ApiRequestInputInvoiceStarGiftUpgrade = {
|
||||
shouldKeepOriginalDetails?: true;
|
||||
};
|
||||
|
||||
export type ApiRequestInputInvoiceStarGiftTransfer = {
|
||||
type: 'stargiftTransfer';
|
||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||
recipient: ApiPeer;
|
||||
};
|
||||
|
||||
export type ApiRequestInputInvoice = ApiRequestInputInvoiceMessage | ApiRequestInputInvoiceSlug
|
||||
| ApiRequestInputInvoiceGiveaway | ApiRequestInputInvoiceStars | ApiRequestInputInvoiceStarsGiveaway
|
||||
| ApiRequestInputInvoiceChatInviteSubscription | ApiRequestInputInvoiceStarGift | ApiRequestInputInvoiceStarGiftUpgrade;
|
||||
| ApiRequestInputInvoiceChatInviteSubscription | ApiRequestInputInvoiceStarGift | ApiRequestInputInvoiceStarGiftUpgrade
|
||||
| ApiRequestInputInvoiceStarGiftTransfer;
|
||||
|
||||
@ -1380,6 +1380,7 @@
|
||||
"GiftHideNameDescription" = "You can hide your name and message from visitors to {receiver}'s profile. {receiver} will still see your name and message.";
|
||||
"GiftHideNameDescriptionChannel" = "You can hide your name and message from all visitors of this channel except its admins.";
|
||||
"GiftSend" = "Send a Gift for {amount}";
|
||||
"GiftUnique" = "{title} #{number}";
|
||||
"GiftInfoSent" = "Sent Gift";
|
||||
"GiftInfoReceived" = "Received Gift";
|
||||
"GiftInfoTitle" = "Gift";
|
||||
@ -1436,7 +1437,15 @@
|
||||
"GiftInfoViewUpgraded" = "View Upgraded Gift";
|
||||
"GiftInfoUpgradeBadge" = "upgrade";
|
||||
"GiftInfoUpgradeForFree" = "Upgrade For Free";
|
||||
"GiftInfoWithdraw" = "Withdraw";
|
||||
"GiftInfoTransfer" = "Transfer";
|
||||
"GiftTransferTitle" = "Transfer";
|
||||
"GiftTransferTON" = "Send via Blockchain";
|
||||
"GiftTransferTONBlocked" = "unlocks in {time}";
|
||||
"GiftTransferConfirmDescription" = "Do you want to transfer ownership of **{gift}** to **{peer}** for **{amount}**?";
|
||||
"GiftTransferConfirmDescriptionFree" = "Do you want to transfer ownership of **{gift}** to **{peer}**?";
|
||||
"GiftTransferConfirmButton" = "Transfer for {amount}";
|
||||
"GiftTransferConfirmButtonFree" = "Transfer";
|
||||
"GiftTransferSuccessMessage" = "You have successfully gifted {gift} to {peer}.";
|
||||
"GiftUpgradeUniqueTitle" = "Unique";
|
||||
"GiftUpgradeUniqueDescription" = "Turn your gift into a unique collectible that you can transfer or auction.";
|
||||
"GiftUpgradeTransferableTitle" = "Transferable";
|
||||
|
||||
@ -9,4 +9,5 @@ export { default as GiftModal } from '../components/modals/gift/GiftModal';
|
||||
export { default as GiftRecipientPicker } from '../components/modals/gift/recipient/GiftRecipientPicker';
|
||||
export { default as GiftInfoModal } from '../components/modals/gift/info/GiftInfoModal';
|
||||
export { default as GiftUpgradeModal } from '../components/modals/gift/upgrade/GiftUpgradeModal';
|
||||
export { default as GiftWithdrawModal } from '../components/modals/gift/fragment/GiftWithdrawModal';
|
||||
export { default as GiftWithdrawModal } from '../components/modals/gift/withdraw/GiftWithdrawModal';
|
||||
export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal';
|
||||
|
||||
@ -7,17 +7,17 @@ import type { ThreadId } from '../../types';
|
||||
|
||||
import { API_CHAT_TYPES } from '../../config';
|
||||
import {
|
||||
filterChatsByName,
|
||||
filterUsersByName,
|
||||
getCanPostInChat,
|
||||
isDeletedUser,
|
||||
} from '../../global/helpers';
|
||||
import { filterChatIdsByType } from '../../global/selectors';
|
||||
import { filterPeersByQuery } from '../../global/helpers/peers';
|
||||
import {
|
||||
filterChatIdsByType, selectChat, selectChatFullInfo, selectUser,
|
||||
} from '../../global/selectors';
|
||||
import { unique } from '../../util/iteratees';
|
||||
import sortChatIds from './helpers/sortChatIds';
|
||||
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
import ChatOrUserPicker from './pickers/ChatOrUserPicker';
|
||||
|
||||
@ -55,7 +55,6 @@ const RecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
onClose,
|
||||
onCloseAnimationEnd,
|
||||
}) => {
|
||||
const lang = useOldLang();
|
||||
const [search, setSearch] = useState('');
|
||||
const ids = useMemo(() => {
|
||||
if (!isOpen) return undefined;
|
||||
@ -67,34 +66,36 @@ const RecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
|
||||
// No need for expensive global updates on users, so we avoid them
|
||||
const global = getGlobal();
|
||||
const usersById = global.users.byId;
|
||||
const chatsById = global.chats.byId;
|
||||
const chatFullInfoById = global.chats.fullInfoById;
|
||||
|
||||
const chatIds = [
|
||||
const peerIds = [
|
||||
...(activeListIds || []),
|
||||
...((search && archivedListIds) || []),
|
||||
].filter((id) => {
|
||||
const chat = chatsById[id];
|
||||
const user = usersById[id];
|
||||
const chat = selectChat(global, id);
|
||||
const user = selectUser(global, id);
|
||||
if (user && isDeletedUser(user)) return false;
|
||||
|
||||
return chat && getCanPostInChat(chat, undefined, undefined, chatFullInfoById[id]);
|
||||
const chatFullInfo = selectChatFullInfo(global, id);
|
||||
|
||||
return chat && chatFullInfo && getCanPostInChat(chat, undefined, undefined, chatFullInfo);
|
||||
});
|
||||
|
||||
const sorted = sortChatIds(
|
||||
unique([
|
||||
...(currentUserId ? [currentUserId] : []),
|
||||
...filterChatsByName(lang, chatIds, chatsById, search, currentUserId),
|
||||
...(contactIds && filter.includes('users') ? filterUsersByName(contactIds, usersById, search) : []),
|
||||
]),
|
||||
filterPeersByQuery({
|
||||
ids: unique([
|
||||
...(currentUserId ? [currentUserId] : []),
|
||||
...peerIds,
|
||||
...(contactIds || []),
|
||||
]),
|
||||
query: search,
|
||||
}),
|
||||
undefined,
|
||||
priorityIds,
|
||||
currentUserId,
|
||||
);
|
||||
|
||||
return filterChatIdsByType(global, sorted, filter);
|
||||
}, [pinnedIds, currentUserId, activeListIds, search, archivedListIds, lang, contactIds, filter, isOpen]);
|
||||
}, [pinnedIds, currentUserId, activeListIds, search, archivedListIds, contactIds, filter, isOpen]);
|
||||
|
||||
const renderingIds = useCurrentOrPrev(ids, true)!;
|
||||
|
||||
|
||||
@ -31,18 +31,18 @@ import PickerItem from './PickerItem';
|
||||
|
||||
import styles from './PickerStyles.module.scss';
|
||||
|
||||
type SingleModeProps = {
|
||||
type SingleModeProps<CategoryType extends string> = {
|
||||
allowMultiple?: false;
|
||||
itemInputType?: 'radio';
|
||||
selectedId?: string;
|
||||
selectedIds?: never; // Help TS to throw an error if this is passed
|
||||
selectedCategory?: CustomPeerType;
|
||||
selectedCategory?: CategoryType;
|
||||
selectedCategories?: never;
|
||||
onSelectedCategoryChange?: (category: CustomPeerType) => void;
|
||||
onSelectedCategoryChange?: (category: CategoryType) => void;
|
||||
onSelectedIdChange?: (id: string) => void;
|
||||
};
|
||||
|
||||
type MultipleModeProps = {
|
||||
type MultipleModeProps<CategoryType extends string> = {
|
||||
allowMultiple: true;
|
||||
itemInputType: 'checkbox';
|
||||
selectedId?: never;
|
||||
@ -50,14 +50,14 @@ type MultipleModeProps = {
|
||||
lockedSelectedIds?: string[];
|
||||
lockedUnselectedIds?: string[];
|
||||
selectedCategory?: never;
|
||||
selectedCategories?: CustomPeerType[];
|
||||
onSelectedCategoriesChange?: (categories: CustomPeerType[]) => void;
|
||||
selectedCategories?: CategoryType[];
|
||||
onSelectedCategoriesChange?: (categories: CategoryType[]) => void;
|
||||
onSelectedIdsChange?: (Ids: string[]) => void;
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
type OwnProps<CategoryType extends string> = {
|
||||
className?: string;
|
||||
categories?: UniqueCustomPeer[];
|
||||
categories?: UniqueCustomPeer<CategoryType>[];
|
||||
itemIds: string[];
|
||||
lockedUnselectedSubtitle?: string;
|
||||
filterValue?: string;
|
||||
@ -73,11 +73,12 @@ type OwnProps = {
|
||||
isViewOnly?: boolean;
|
||||
withStatus?: boolean;
|
||||
withPeerTypes?: boolean;
|
||||
withPeerUsernames?: boolean;
|
||||
withDefaultPadding?: boolean;
|
||||
onFilterChange?: (value: string) => void;
|
||||
onDisabledClick?: (id: string, isSelected: boolean) => void;
|
||||
onLoadMore?: () => void;
|
||||
} & (SingleModeProps | MultipleModeProps);
|
||||
} & (SingleModeProps<CategoryType> | MultipleModeProps<CategoryType>);
|
||||
|
||||
// Focus slows down animation, also it breaks transition layout in Chrome
|
||||
const FOCUS_DELAY_MS = 500;
|
||||
@ -87,7 +88,7 @@ const ALWAYS_FULL_ITEMS_COUNT = 5;
|
||||
|
||||
const ITEM_CLASS_NAME = 'PeerPickerItem';
|
||||
|
||||
const PeerPicker = ({
|
||||
const PeerPicker = <CategoryType extends string = CustomPeerType>({
|
||||
className,
|
||||
categories,
|
||||
itemIds,
|
||||
@ -106,12 +107,13 @@ const PeerPicker = ({
|
||||
itemInputType,
|
||||
withStatus,
|
||||
withPeerTypes,
|
||||
withPeerUsernames,
|
||||
withDefaultPadding,
|
||||
onFilterChange,
|
||||
onDisabledClick,
|
||||
onLoadMore,
|
||||
...optionalProps
|
||||
}: OwnProps) => {
|
||||
}: OwnProps<CategoryType>) => {
|
||||
const lang = useOldLang();
|
||||
|
||||
const allowMultiple = optionalProps.allowMultiple;
|
||||
@ -268,7 +270,16 @@ const PeerPicker = ({
|
||||
|
||||
function getSubtitle() {
|
||||
if (isAlwaysUnselected) return [lockedUnselectedSubtitle];
|
||||
if (withStatus && peer) {
|
||||
if (!peer) return undefined;
|
||||
|
||||
if (withPeerUsernames) {
|
||||
const username = peer.usernames?.[0]?.username;
|
||||
if (username) {
|
||||
return [`@${username}`];
|
||||
}
|
||||
}
|
||||
|
||||
if (withStatus) {
|
||||
if (isApiPeerChat(peer)) {
|
||||
return [getGroupStatus(lang, peer)];
|
||||
}
|
||||
@ -279,10 +290,12 @@ const PeerPicker = ({
|
||||
buildClassName(isUserOnline(peer, userStatus, true) && styles.onlineStatus),
|
||||
];
|
||||
}
|
||||
if (withPeerTypes && peer) {
|
||||
|
||||
if (withPeerTypes) {
|
||||
const langKey = getPeerTypeKey(peer);
|
||||
return langKey && [lang(langKey)];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -316,7 +329,7 @@ const PeerPicker = ({
|
||||
}, [
|
||||
categoriesByType, forceShowSelf, isViewOnly, itemClassName, itemInputType, lang, lockedSelectedIdsSet,
|
||||
lockedUnselectedIdsSet, lockedUnselectedSubtitle, onDisabledClick, selectedCategories, selectedIds,
|
||||
withPeerTypes, withStatus,
|
||||
withPeerTypes, withStatus, withPeerUsernames,
|
||||
]);
|
||||
|
||||
const beforeChildren = useMemo(() => {
|
||||
|
||||
@ -34,11 +34,12 @@
|
||||
.pickerCategoryTitle {
|
||||
color: var(--color-text-secondary);
|
||||
padding-inline: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid var(--color-borders);
|
||||
padding-top: 0.75rem;
|
||||
padding-top: 0.5rem;
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,8 @@ import { getActions, withGlobal } from '../../../global';
|
||||
import type { ApiUser, ApiUserStatus } from '../../../api/types';
|
||||
import { StoryViewerOrigin } from '../../../types';
|
||||
|
||||
import { filterUsersByName, sortUserIds } from '../../../global/helpers';
|
||||
import { sortUserIds } from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
@ -61,7 +62,7 @@ const ContactList: FC<OwnProps & StateProps> = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const filteredIds = filterUsersByName(contactIds, usersById, filter);
|
||||
const filteredIds = filterPeersByQuery({ ids: contactIds, query: filter, type: 'user' });
|
||||
|
||||
return sortUserIds(filteredIds, usersById, userStatusesById);
|
||||
}, [contactIds, filter, usersById, userStatusesById]);
|
||||
|
||||
@ -2,7 +2,8 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import { filterUsersByName, isUserBot } from '../../../global/helpers';
|
||||
import { isUserBot } from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { selectTabState } from '../../../global/selectors';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||
@ -63,7 +64,8 @@ const NewChatStep1: FC<OwnProps & StateProps> = ({
|
||||
const displayedIds = useMemo(() => {
|
||||
// No need for expensive global updates on users, so we avoid them
|
||||
const usersById = getGlobal().users.byId;
|
||||
const foundContactIds = localContactIds ? filterUsersByName(localContactIds, usersById, searchQuery) : [];
|
||||
const foundContactIds = localContactIds
|
||||
? filterPeersByQuery({ ids: localContactIds, query: searchQuery, type: 'user' }) : [];
|
||||
|
||||
return sortChatIds(
|
||||
unique([
|
||||
|
||||
@ -3,12 +3,12 @@ import React, {
|
||||
memo, useCallback, useMemo, useRef,
|
||||
useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import { LoadMoreDirection } from '../../../types';
|
||||
|
||||
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||
import { filterUsersByName } from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { selectTabState } from '../../../global/selectors';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
|
||||
@ -58,8 +58,7 @@ const BotAppResults: FC<OwnProps & StateProps> = ({
|
||||
const recentSet = new Set(recentBotIds);
|
||||
const withoutRecent = foundIds.filter((id) => !recentSet.has(id));
|
||||
|
||||
const usersById = getGlobal().users.byId;
|
||||
return filterUsersByName(withoutRecent, usersById, searchQuery);
|
||||
return filterPeersByQuery({ ids: withoutRecent, query: searchQuery, type: 'user' });
|
||||
}, [foundIds, recentBotIds, searchQuery]);
|
||||
|
||||
const handleChatClick = useLastCallback((id: string) => {
|
||||
|
||||
@ -10,10 +10,9 @@ import { LoadMoreDirection } from '../../../types';
|
||||
|
||||
import { ALL_FOLDER_ID, GLOBAL_SUGGESTED_CHANNELS_ID } from '../../../config';
|
||||
import {
|
||||
filterChatsByName,
|
||||
filterUsersByName,
|
||||
isChatChannel,
|
||||
} from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { selectSimilarChannelIds, selectTabState } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { getOrderedIds } from '../../../util/folderManager';
|
||||
@ -221,7 +220,6 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
// No need for expensive global updates, so we avoid them
|
||||
const usersById = getGlobal().users.byId;
|
||||
const chatsById = getGlobal().chats.byId;
|
||||
|
||||
const orderedChatIds = getOrderedIds(ALL_FOLDER_ID) ?? [];
|
||||
@ -230,7 +228,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
||||
const chat = chatsById[id];
|
||||
return chat && isChatChannel(chat);
|
||||
});
|
||||
const localChatIds = filterChatsByName(oldLang, filteredChatIds, chatsById, searchQuery, currentUserId);
|
||||
const localChatIds = filterPeersByQuery({ ids: filteredChatIds, query: searchQuery, type: 'chat' });
|
||||
|
||||
if (isChannelList) return localChatIds;
|
||||
|
||||
@ -239,9 +237,9 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
||||
...(contactIds || []),
|
||||
];
|
||||
|
||||
const localContactIds = filterUsersByName(
|
||||
contactIdsWithMe, usersById, searchQuery, currentUserId, oldLang('SavedMessages'),
|
||||
);
|
||||
const localContactIds = filterPeersByQuery({
|
||||
ids: contactIdsWithMe, query: searchQuery, type: 'user',
|
||||
});
|
||||
|
||||
const localPeerIds = [
|
||||
...localContactIds,
|
||||
@ -252,7 +250,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
||||
...sortChatIds(localPeerIds, undefined, currentUserId ? [currentUserId] : undefined),
|
||||
...sortChatIds(accountPeerIds || []),
|
||||
]);
|
||||
}, [searchQuery, oldLang, currentUserId, contactIds, accountPeerIds, isChannelList]);
|
||||
}, [searchQuery, currentUserId, contactIds, accountPeerIds, isChannelList]);
|
||||
|
||||
useHorizontalScroll(chatSelectionRef, !localResults.length || isChannelList, true);
|
||||
|
||||
|
||||
@ -7,7 +7,8 @@ import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiUser } from '../../../api/types';
|
||||
|
||||
import { filterUsersByName, getUserFullName } from '../../../global/helpers';
|
||||
import { getUserFullName } from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { selectTabState } from '../../../global/selectors';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
|
||||
@ -57,7 +58,7 @@ const BlockUserModal: FC<OwnProps & StateProps> = ({
|
||||
return contactId !== currentUserId && !blockedIds.includes(contactId);
|
||||
}));
|
||||
|
||||
return filterUsersByName(availableContactIds, usersById, search)
|
||||
return filterPeersByQuery({ ids: availableContactIds, query: search, type: 'user' })
|
||||
.sort((firstId, secondId) => {
|
||||
const firstName = getUserFullName(usersById[firstId]) || '';
|
||||
const secondName = getUserFullName(usersById[secondId]) || '';
|
||||
|
||||
@ -11,8 +11,9 @@ import { SettingsScreens } from '../../../types';
|
||||
|
||||
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||
import {
|
||||
filterChatsByName, isChatChannel, isDeletedUser,
|
||||
isChatChannel, isDeletedUser,
|
||||
} from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import { CUSTOM_PEER_PREMIUM } from '../../../util/objects/customPeer';
|
||||
import { getPrivacyKey } from './helpers/privacy';
|
||||
@ -122,16 +123,16 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
||||
return chatId !== currentUserId && chatId !== SERVICE_NOTIFICATIONS_USER_ID && !isChannel && !isDeleted;
|
||||
});
|
||||
|
||||
const filteredChats = filterChatsByName(oldLang, chatIds, chatsById, searchQuery);
|
||||
const filteredChats = filterPeersByQuery({ ids: chatIds, query: searchQuery });
|
||||
|
||||
// Show only relevant items
|
||||
if (searchQuery) return filteredChats;
|
||||
|
||||
return unique([
|
||||
...selectedContactIds,
|
||||
...filterChatsByName(oldLang, chatIds, chatsById, searchQuery),
|
||||
...chatIds,
|
||||
]);
|
||||
}, [folderAllOrderedIds, folderArchivedOrderedIds, selectedContactIds, oldLang, searchQuery, currentUserId]);
|
||||
}, [folderAllOrderedIds, folderArchivedOrderedIds, selectedContactIds, searchQuery, currentUserId]);
|
||||
|
||||
const handleSelectedCategoriesChange = useCallback((value: CustomPeerType[]) => {
|
||||
setNewSelectedCategoryTypes(value);
|
||||
|
||||
@ -2,12 +2,12 @@ import type { FC } from '../../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useEffect, useMemo, useState,
|
||||
} from '../../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||
import { getActions, withGlobal } from '../../../../global';
|
||||
|
||||
import type { FolderEditDispatch, FoldersState } from '../../../../hooks/reducers/useFoldersReducer';
|
||||
|
||||
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID } from '../../../../config';
|
||||
import { filterChatsByName } from '../../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../../global/helpers/peers';
|
||||
import { selectCurrentLimit } from '../../../../global/selectors/limits';
|
||||
import { unique } from '../../../../util/iteratees';
|
||||
import { CUSTOM_PEER_EXCLUDED_CHAT_TYPES, CUSTOM_PEER_INCLUDED_CHAT_TYPES } from '../../../../util/objects/customPeer';
|
||||
@ -67,14 +67,11 @@ const SettingsFoldersChatFilters: FC<OwnProps & StateProps> = ({
|
||||
}, [isActive]);
|
||||
|
||||
const displayedIds = useMemo(() => {
|
||||
// No need for expensive global updates on chats, so we avoid them
|
||||
const chatsById = getGlobal().chats.byId;
|
||||
|
||||
const chatIds = [...folderAllOrderedIds || [], ...folderArchivedOrderedIds || []];
|
||||
return unique([
|
||||
...filterChatsByName(lang, chatIds, chatsById, chatFilter),
|
||||
...filterPeersByQuery({ ids: chatIds, query: chatFilter, type: 'chat' }),
|
||||
]);
|
||||
}, [folderAllOrderedIds, folderArchivedOrderedIds, lang, chatFilter]);
|
||||
}, [folderAllOrderedIds, folderArchivedOrderedIds, chatFilter]);
|
||||
|
||||
const handleFilterChange = useLastCallback((newFilter: string) => {
|
||||
dispatch({
|
||||
|
||||
@ -4,8 +4,9 @@ import React, {
|
||||
import { getActions, getGlobal } from '../../../global';
|
||||
|
||||
import {
|
||||
filterChatsByName, isChatChannel, isChatPublic, isChatSuperGroup,
|
||||
isChatChannel, isChatPublic, isChatSuperGroup,
|
||||
} from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||
|
||||
@ -61,13 +62,12 @@ const GiveawayChannelPickerModal = ({
|
||||
}, [giveawayChatId]);
|
||||
|
||||
const displayedChannelIds = useMemo(() => {
|
||||
const chatsById = getGlobal().chats.byId;
|
||||
const foundChannelIds = channelIds ? filterChatsByName(lang, channelIds, chatsById, searchQuery) : [];
|
||||
const foundChannelIds = channelIds ? filterPeersByQuery({ ids: channelIds, query: searchQuery, type: 'chat' }) : [];
|
||||
|
||||
return sortChatIds(foundChannelIds,
|
||||
false,
|
||||
selectedIds);
|
||||
}, [channelIds, lang, searchQuery, selectedIds]);
|
||||
}, [channelIds, searchQuery, selectedIds]);
|
||||
|
||||
const handleSelectedChannelIdsChange = useLastCallback((newSelectedIds: string[]) => {
|
||||
const chatsById = getGlobal().chats.byId;
|
||||
|
||||
@ -6,10 +6,10 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
import type { ApiChatMember } from '../../../api/types';
|
||||
|
||||
import {
|
||||
filterUsersByName,
|
||||
isUserBot,
|
||||
sortUserIds,
|
||||
} from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { selectChatFullInfo } from '../../../global/selectors';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||
@ -74,9 +74,10 @@ const GiveawayUserPickerModal = ({
|
||||
|
||||
const displayedMemberIds = useMemo(() => {
|
||||
const usersById = getGlobal().users.byId;
|
||||
const filteredContactIds = memberIds ? filterUsersByName(memberIds, usersById, searchQuery) : [];
|
||||
const filteredUserIds = memberIds
|
||||
? filterPeersByQuery({ ids: memberIds, query: searchQuery, type: 'user' }) : [];
|
||||
|
||||
return sortChatIds(unique(filteredContactIds).filter((userId) => {
|
||||
return sortChatIds(unique(filteredUserIds).filter((userId) => {
|
||||
const user = usersById[userId];
|
||||
if (!user) {
|
||||
return true;
|
||||
|
||||
@ -7,8 +7,9 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||
import {
|
||||
filterUsersByName, isDeletedUser, isUserBot,
|
||||
isDeletedUser, isUserBot,
|
||||
} from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||
|
||||
@ -46,15 +47,17 @@ const StarsGiftingPickerModal: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const displayedUserIds = useMemo(() => {
|
||||
const usersById = getGlobal().users.byId;
|
||||
const combinedIds = [
|
||||
const combinedIds = unique([
|
||||
...(userIds || []),
|
||||
...(activeListIds || []),
|
||||
...(archivedListIds || []),
|
||||
];
|
||||
]);
|
||||
|
||||
const filteredContactIds = filterUsersByName(combinedIds, usersById, searchQuery);
|
||||
const filteredUserIds = filterPeersByQuery({
|
||||
ids: combinedIds, query: searchQuery, type: 'user',
|
||||
});
|
||||
|
||||
return sortChatIds(unique(filteredContactIds).filter((id) => {
|
||||
return sortChatIds(filteredUserIds.filter((id) => {
|
||||
const user = usersById[id];
|
||||
|
||||
if (!user) {
|
||||
|
||||
@ -7,7 +7,8 @@ import type { Signal } from '../../../../util/signals';
|
||||
import { ApiMessageEntityTypes } from '../../../../api/types';
|
||||
|
||||
import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
||||
import { filterUsersByName, getMainUsername, getUserFirstOrLastName } from '../../../../global/helpers';
|
||||
import { getMainUsername, getUserFirstOrLastName } from '../../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../../global/helpers/peers';
|
||||
import focusEditableElement from '../../../../util/focusEditableElement';
|
||||
import { pickTruthy, unique } from '../../../../util/iteratees';
|
||||
import { getCaretPosition, getHtmlBeforeSelection, setCaretPosition } from '../../../../util/selection';
|
||||
@ -82,10 +83,14 @@ export default function useMentionTooltip(
|
||||
}, []);
|
||||
|
||||
const filter = usernameTag.substring(1);
|
||||
const filteredIds = filterUsersByName(unique([
|
||||
...((getWithInlineBots() && topInlineBotIds) || []),
|
||||
...(memberIds || []),
|
||||
]), usersById, filter);
|
||||
const filteredIds = filterPeersByQuery({
|
||||
ids: unique([
|
||||
...((getWithInlineBots() && topInlineBotIds) || []),
|
||||
...(memberIds || []),
|
||||
]),
|
||||
query: filter,
|
||||
type: 'user',
|
||||
});
|
||||
|
||||
setFilteredUsers(Object.values(pickTruthy(usersById, filteredIds)));
|
||||
}, [currentUserId, groupChatMembers, topInlineBotIds, getUsernameTag, getWithInlineBots]);
|
||||
|
||||
@ -15,11 +15,12 @@ import ChatInviteModal from './chatInvite/ChatInviteModal.async';
|
||||
import ChatlistModal from './chatlist/ChatlistModal.async';
|
||||
import CollectibleInfoModal from './collectible/CollectibleInfoModal.async';
|
||||
import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async';
|
||||
import GiftWithdrawModal from './gift/fragment/GiftWithdrawModal.async';
|
||||
import PremiumGiftModal from './gift/GiftModal.async';
|
||||
import GiftInfoModal from './gift/info/GiftInfoModal.async';
|
||||
import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async';
|
||||
import GiftTransferModal from './gift/transfer/GiftTransferModal.async';
|
||||
import GiftUpgradeModal from './gift/upgrade/GiftUpgradeModal.async';
|
||||
import GiftWithdrawModal from './gift/withdraw/GiftWithdrawModal.async';
|
||||
import GiftCodeModal from './giftcode/GiftCodeModal.async';
|
||||
import InviteViaLinkModal from './inviteViaLink/InviteViaLinkModal.async';
|
||||
import LocationAccessModal from './locationAccess/LocationAccessModal.async';
|
||||
@ -69,7 +70,8 @@ type ModalKey = keyof Pick<TabState,
|
||||
'aboutAdsModal' |
|
||||
'giftUpgradeModal' |
|
||||
'monetizationVerificationModal' |
|
||||
'giftWithdrawModal'
|
||||
'giftWithdrawModal' |
|
||||
'giftTransferModal'
|
||||
>;
|
||||
|
||||
type StateProps = {
|
||||
@ -115,6 +117,7 @@ const MODALS: ModalRegistry = {
|
||||
giftUpgradeModal: GiftUpgradeModal,
|
||||
monetizationVerificationModal: VerificationMonetizationModal,
|
||||
giftWithdrawModal: GiftWithdrawModal,
|
||||
giftTransferModal: GiftTransferModal,
|
||||
};
|
||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
||||
|
||||
@ -72,7 +72,7 @@ const GiftInfoModal = ({
|
||||
openGiftUpgradeModal,
|
||||
showNotification,
|
||||
openChatWithDraft,
|
||||
openGiftWithdrawModal,
|
||||
openGiftTransferModal,
|
||||
} = getActions();
|
||||
|
||||
const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag();
|
||||
@ -128,9 +128,9 @@ const GiftInfoModal = ({
|
||||
handleClose();
|
||||
});
|
||||
|
||||
const handleWithdraw = useLastCallback(() => {
|
||||
const handleTransfer = useLastCallback(() => {
|
||||
if (savedGift?.gift.type !== 'starGiftUnique') return;
|
||||
openGiftWithdrawModal({ gift: savedGift });
|
||||
openGiftTransferModal({ gift: savedGift });
|
||||
});
|
||||
|
||||
const handleFocusUpgraded = useLastCallback(() => {
|
||||
@ -298,8 +298,8 @@ const GiftInfoModal = ({
|
||||
{lang('Share')}
|
||||
</MenuItem>
|
||||
{canUpdate && isUniqueGift && (
|
||||
<MenuItem icon="diamond" onClick={handleWithdraw}>
|
||||
{lang('GiftInfoWithdraw')}
|
||||
<MenuItem icon="diamond" onClick={handleTransfer}>
|
||||
{lang('GiftInfoTransfer')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import type { FC } from '../../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useMemo, useState,
|
||||
} from '../../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||
|
||||
import {
|
||||
filterUsersByName, isUserBot,
|
||||
} from '../../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../../global/helpers/peers';
|
||||
import { selectCanGift } from '../../../../global/selectors';
|
||||
import { unique } from '../../../../util/iteratees';
|
||||
import sortChatIds from '../../../common/helpers/sortChatIds';
|
||||
|
||||
@ -24,15 +22,14 @@ export type OwnProps = {
|
||||
|
||||
interface StateProps {
|
||||
currentUserId?: string;
|
||||
userSelectionLimit?: number;
|
||||
userIds?: string[];
|
||||
}
|
||||
|
||||
const GiftRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
const GiftRecipientPicker = ({
|
||||
modal,
|
||||
currentUserId,
|
||||
userIds,
|
||||
}) => {
|
||||
}: OwnProps & StateProps) => {
|
||||
const { closeGiftRecipientPicker, openGiftModal } = getActions();
|
||||
|
||||
const oldLang = useOldLang();
|
||||
@ -41,17 +38,12 @@ const GiftRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
|
||||
const displayedUserIds = useMemo(() => {
|
||||
const usersById = getGlobal().users.byId;
|
||||
const global = getGlobal();
|
||||
const idsWithSelf = userIds ? userIds.concat(currentUserId!) : undefined;
|
||||
const filteredContactIds = idsWithSelf ? filterUsersByName(idsWithSelf, usersById, searchQuery) : [];
|
||||
const filteredPeerIds = idsWithSelf ? filterPeersByQuery({ ids: idsWithSelf, query: searchQuery }) : [];
|
||||
|
||||
return sortChatIds(unique(filteredContactIds).filter((userId) => {
|
||||
const user = usersById[userId];
|
||||
if (!user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !isUserBot(user);
|
||||
return sortChatIds(unique(filteredPeerIds).filter((peerId) => {
|
||||
return selectCanGift(global, peerId);
|
||||
}), undefined, [currentUserId!]);
|
||||
}, [currentUserId, searchQuery, userIds]);
|
||||
|
||||
@ -92,6 +84,5 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
return {
|
||||
currentUserId,
|
||||
userIds: global.contactList?.userIds,
|
||||
userSelectionLimit: global.appConfig?.giveawayAddPeersMax,
|
||||
};
|
||||
})(GiftRecipientPicker));
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../../../lib/teact/teact';
|
||||
import React from '../../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './GiftTransferModal';
|
||||
|
||||
import { Bundles } from '../../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../../hooks/useModuleLoader';
|
||||
|
||||
const GiftTransferModalAsync: FC<OwnProps> = (props) => {
|
||||
const { modal } = props;
|
||||
const GiftTransferModal = useModuleLoader(Bundles.Stars, 'GiftTransferModal', !modal);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return GiftTransferModal ? <GiftTransferModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default GiftTransferModalAsync;
|
||||
@ -0,0 +1,31 @@
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.giftPreview {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
position: relative;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
overflow: hidden;
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 2rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
232
src/components/modals/gift/transfer/GiftTransferModal.tsx
Normal file
232
src/components/modals/gift/transfer/GiftTransferModal.tsx
Normal file
@ -0,0 +1,232 @@
|
||||
import React, {
|
||||
memo, useEffect, useMemo, useState,
|
||||
} from '../../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiStarGiftUnique } from '../../../../api/types';
|
||||
import type { TabState } from '../../../../global/types';
|
||||
import type { UniqueCustomPeer } from '../../../../types';
|
||||
|
||||
import { ALL_FOLDER_ID } from '../../../../config';
|
||||
import { getPeerTitle } from '../../../../global/helpers';
|
||||
import { selectCanGift, selectPeer } from '../../../../global/selectors';
|
||||
import { unique } from '../../../../util/iteratees';
|
||||
import { formatStarsAsIcon, formatStarsAsText } from '../../../../util/localization/format';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
|
||||
import { getGiftAttributes } from '../../../common/helpers/gifts';
|
||||
import { REM } from '../../../common/helpers/mediaDimensions';
|
||||
import sortChatIds from '../../../common/helpers/sortChatIds';
|
||||
|
||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||
import { useFolderManagerForOrderedIds } from '../../../../hooks/useFolderManager';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
import usePeerSearch from '../../../../hooks/usePeerSearch';
|
||||
|
||||
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||
import Avatar from '../../../common/Avatar';
|
||||
import Icon from '../../../common/icons/Icon';
|
||||
import PeerPicker from '../../../common/pickers/PeerPicker';
|
||||
import PickerModal from '../../../common/pickers/PickerModal';
|
||||
import RadialPatternBackground from '../../../common/profile/RadialPatternBackground';
|
||||
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
||||
|
||||
import styles from './GiftTransferModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['giftTransferModal'];
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
contactIds?: string[];
|
||||
currentUserId?: string;
|
||||
};
|
||||
|
||||
type Categories = 'withdraw';
|
||||
|
||||
const AVATAR_SIZE = 4 * REM;
|
||||
const GIFT_STICKER_SIZE = 3 * REM;
|
||||
|
||||
const GiftTransferModal = ({
|
||||
modal, contactIds, currentUserId,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { closeGiftTransferModal, openGiftWithdrawModal, transferGift } = getActions();
|
||||
const isOpen = Boolean(modal);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
|
||||
const renderingModal = useCurrentOrPrev(modal);
|
||||
const uniqueGift = renderingModal?.gift?.gift as ApiStarGiftUnique;
|
||||
const giftAttributes = uniqueGift && getGiftAttributes(uniqueGift);
|
||||
|
||||
const [selectedId, setSelectedId] = useState<string | undefined>();
|
||||
|
||||
const renderingSelectedPeerId = useCurrentOrPrev(selectedId);
|
||||
const renderingSelectedPeer = useMemo(() => {
|
||||
const global = getGlobal();
|
||||
return renderingSelectedPeerId ? selectPeer(global, renderingSelectedPeerId) : undefined;
|
||||
}, [renderingSelectedPeerId]);
|
||||
|
||||
const orderedChatIds = useFolderManagerForOrderedIds(ALL_FOLDER_ID);
|
||||
|
||||
const sortedLocalIds = useMemo(() => {
|
||||
return unique([
|
||||
...(contactIds || []),
|
||||
...(orderedChatIds || []),
|
||||
]);
|
||||
}, [contactIds, orderedChatIds]);
|
||||
|
||||
const { result: foundIds, currentResultsQuery } = usePeerSearch({
|
||||
query: searchQuery,
|
||||
defaultValue: sortedLocalIds,
|
||||
});
|
||||
|
||||
const isLoading = currentResultsQuery !== searchQuery;
|
||||
|
||||
const categories = useMemo(() => {
|
||||
if (currentResultsQuery) return MEMO_EMPTY_ARRAY;
|
||||
|
||||
return [{
|
||||
type: 'withdraw',
|
||||
isCustomPeer: true,
|
||||
avatarIcon: 'toncoin',
|
||||
peerColorId: 5,
|
||||
title: lang('GiftTransferTON'),
|
||||
}] satisfies UniqueCustomPeer<Categories>[];
|
||||
}, [lang, currentResultsQuery]);
|
||||
|
||||
const handleCategoryChange = useLastCallback((category: Categories) => {
|
||||
if (category !== 'withdraw') return;
|
||||
|
||||
openGiftWithdrawModal({
|
||||
gift: renderingModal!.gift,
|
||||
});
|
||||
closeGiftTransferModal();
|
||||
});
|
||||
|
||||
const displayIds = useMemo(() => {
|
||||
if (isLoading) return MEMO_EMPTY_ARRAY;
|
||||
const global = getGlobal();
|
||||
|
||||
return sortChatIds((foundIds || []).filter((peerId) => (
|
||||
peerId !== currentUserId && selectCanGift(global, peerId)
|
||||
)),
|
||||
false);
|
||||
}, [isLoading, foundIds, currentUserId]);
|
||||
|
||||
const closeConfirmModal = useLastCallback(() => {
|
||||
setSelectedId(undefined);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSelectedId(undefined);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleTransfer = useLastCallback(() => {
|
||||
if (!renderingModal?.gift.inputGift) return;
|
||||
transferGift({
|
||||
gift: renderingModal.gift.inputGift,
|
||||
recipientId: renderingSelectedPeerId!,
|
||||
transferStars: renderingModal.gift.transferStars,
|
||||
});
|
||||
|
||||
closeConfirmModal();
|
||||
closeGiftTransferModal();
|
||||
});
|
||||
|
||||
return (
|
||||
<PickerModal
|
||||
isOpen={isOpen}
|
||||
onClose={closeGiftTransferModal}
|
||||
title={lang('GiftTransferTitle')}
|
||||
hasCloseButton
|
||||
shouldAdaptToSearch
|
||||
withFixedHeight
|
||||
ignoreFreeze
|
||||
>
|
||||
<PeerPicker<Categories>
|
||||
itemIds={displayIds}
|
||||
categories={categories}
|
||||
onSelectedCategoryChange={handleCategoryChange}
|
||||
withDefaultPadding
|
||||
withPeerUsernames
|
||||
isSearchable
|
||||
noScrollRestore
|
||||
isLoading={isLoading}
|
||||
filterValue={searchQuery}
|
||||
filterPlaceholder={lang('Search')}
|
||||
onFilterChange={setSearchQuery}
|
||||
onSelectedIdChange={setSelectedId}
|
||||
/>
|
||||
{giftAttributes && (
|
||||
<ConfirmDialog
|
||||
isOpen={Boolean(selectedId)}
|
||||
noDefaultTitle
|
||||
onClose={closeConfirmModal}
|
||||
confirmLabel={renderingModal?.gift.transferStars
|
||||
? lang(
|
||||
'GiftTransferConfirmButton',
|
||||
{ amount: formatStarsAsIcon(lang, renderingModal.gift.transferStars, { asFont: true }) },
|
||||
{ withNodes: true },
|
||||
) : lang('GiftTransferConfirmButtonFree')}
|
||||
confirmHandler={handleTransfer}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.giftPreview}>
|
||||
<RadialPatternBackground
|
||||
className={styles.backdrop}
|
||||
backgroundColors={[giftAttributes.backdrop!.centerColor, giftAttributes.backdrop!.edgeColor]}
|
||||
patternColor={giftAttributes.backdrop?.patternColor}
|
||||
patternIcon={giftAttributes.pattern?.sticker}
|
||||
/>
|
||||
<AnimatedIconFromSticker
|
||||
className={styles.sticker}
|
||||
size={GIFT_STICKER_SIZE}
|
||||
sticker={giftAttributes.model?.sticker}
|
||||
/>
|
||||
</div>
|
||||
<Icon name="next" className={styles.arrow} />
|
||||
<Avatar
|
||||
peer={renderingSelectedPeer}
|
||||
size={AVATAR_SIZE}
|
||||
className={styles.avatar}
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
{renderingModal?.gift.transferStars
|
||||
? lang('GiftTransferConfirmDescription', {
|
||||
gift: lang('GiftUnique', { title: uniqueGift.title, number: uniqueGift.number }),
|
||||
amount: formatStarsAsText(lang, renderingModal.gift.transferStars),
|
||||
peer: getPeerTitle(lang, renderingSelectedPeer!),
|
||||
}, {
|
||||
withNodes: true,
|
||||
withMarkdown: true,
|
||||
})
|
||||
: lang('GiftTransferConfirmDescriptionFree', {
|
||||
gift: lang('GiftUnique', { title: uniqueGift.title, number: uniqueGift.number }),
|
||||
peer: getPeerTitle(lang, renderingSelectedPeer!),
|
||||
}, {
|
||||
withNodes: true,
|
||||
withMarkdown: true,
|
||||
})}
|
||||
</p>
|
||||
</ConfirmDialog>
|
||||
)}
|
||||
</PickerModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const { contactList, currentUserId } = global;
|
||||
|
||||
return {
|
||||
contactIds: contactList?.userIds,
|
||||
currentUserId,
|
||||
};
|
||||
},
|
||||
)(GiftTransferModal));
|
||||
@ -5,11 +5,11 @@ import React, {
|
||||
useMemo,
|
||||
useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import { LoadMoreDirection } from '../../../types';
|
||||
|
||||
import { filterUsersByName } from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { selectTabState } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
@ -57,8 +57,7 @@ const MoreAppsTabContent: FC<OwnProps & StateProps> = ({
|
||||
const filteredFoundIds = useMemo(() => {
|
||||
if (!foundIds) return [];
|
||||
|
||||
const usersById = getGlobal().users.byId;
|
||||
return filterUsersByName(foundIds, usersById, searchQuery);
|
||||
return filterPeersByQuery({ ids: foundIds, query: searchQuery, type: 'user' });
|
||||
}, [foundIds, searchQuery]);
|
||||
|
||||
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||
|
||||
@ -10,8 +10,9 @@ import type {
|
||||
import { NewChatMembersProgress } from '../../types';
|
||||
|
||||
import {
|
||||
filterUsersByName, isChatChannel, isUserBot,
|
||||
isChatChannel, isUserBot,
|
||||
} from '../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../global/helpers/peers';
|
||||
import { selectChat, selectChatFullInfo, selectTabState } from '../../global/selectors';
|
||||
import { unique } from '../../util/iteratees';
|
||||
import sortChatIds from '../common/helpers/sortChatIds';
|
||||
@ -83,14 +84,18 @@ const AddChatMembers: FC<OwnProps & StateProps> = ({
|
||||
const displayedIds = useMemo(() => {
|
||||
// No need for expensive global updates on users, so we avoid them
|
||||
const usersById = getGlobal().users.byId;
|
||||
const filteredContactIds = localContactIds ? filterUsersByName(localContactIds, usersById, searchQuery) : [];
|
||||
|
||||
return sortChatIds(
|
||||
unique([
|
||||
...filteredContactIds,
|
||||
const filteredIds = filterPeersByQuery({
|
||||
ids: unique([
|
||||
...(localContactIds || []),
|
||||
...(localUserIds || []),
|
||||
...(globalUserIds || []),
|
||||
]).filter((userId) => {
|
||||
]),
|
||||
query: searchQuery,
|
||||
type: 'user',
|
||||
});
|
||||
|
||||
return sortChatIds(
|
||||
filteredIds.filter((userId) => {
|
||||
const user = usersById[userId];
|
||||
|
||||
// The user can be added to the chat if the following conditions are met:
|
||||
|
||||
@ -8,9 +8,10 @@ import type { ApiChatMember, ApiUserStatus } from '../../../api/types';
|
||||
import { ManagementScreens, NewChatMembersProgress } from '../../../types';
|
||||
|
||||
import {
|
||||
filterUsersByName, getHasAdminRight, isChatBasicGroup,
|
||||
getHasAdminRight, isChatBasicGroup,
|
||||
isChatChannel, isUserBot, isUserRightBanned, sortUserIds,
|
||||
} from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||
@ -121,7 +122,7 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
|
||||
const shouldUseSearchResults = Boolean(searchQuery);
|
||||
const listedIds = !shouldUseSearchResults
|
||||
? memberIds
|
||||
: (localContactIds ? filterUsersByName(localContactIds, usersById, searchQuery) : []);
|
||||
: (localContactIds ? filterPeersByQuery({ ids: localContactIds, query: searchQuery, type: 'user' }) : []);
|
||||
|
||||
return sortChatIds(
|
||||
unique([
|
||||
|
||||
@ -19,6 +19,7 @@ import useInterval from '../../../hooks/schedulers/useInterval';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||
@ -72,7 +73,8 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
setOpenedInviteInfo,
|
||||
} = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
const lang = useLang();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const [isDeleteRevokeAllDialogOpen, openDeleteRevokeAllDialog, closeDeleteRevokeAllDialog] = useFlag();
|
||||
const [isRevokeDialogOpen, openRevokeDialog, closeRevokeDialog] = useFlag();
|
||||
@ -174,9 +176,9 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
const copyLink = useCallback((link: string) => {
|
||||
copyTextToClipboard(link);
|
||||
showNotification({
|
||||
message: lang('LinkCopied'),
|
||||
message: oldLang('LinkCopied'),
|
||||
});
|
||||
}, [lang, showNotification]);
|
||||
}, [oldLang, showNotification]);
|
||||
|
||||
const prepareUsageText = (invite: ApiExportedInvite) => {
|
||||
const {
|
||||
@ -184,34 +186,34 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
} = invite;
|
||||
let text = '';
|
||||
if (!isRevoked && usageLimit && usage < usageLimit) {
|
||||
text = lang('CanJoin', usageLimit - usage);
|
||||
text = oldLang('CanJoin', usageLimit - usage);
|
||||
} else if (usage) {
|
||||
text = lang('PeopleJoined', usage);
|
||||
text = oldLang('PeopleJoined', usage);
|
||||
} else {
|
||||
text = lang('NoOneJoined');
|
||||
text = oldLang('NoOneJoined');
|
||||
}
|
||||
|
||||
if (isRevoked) {
|
||||
text += ` ${BULLET} ${lang('Revoked')}`;
|
||||
text += ` ${BULLET} ${oldLang('Revoked')}`;
|
||||
return text;
|
||||
}
|
||||
|
||||
if (requested) {
|
||||
text += ` ${BULLET} ${lang('JoinRequests', requested)}`;
|
||||
text += ` ${BULLET} ${oldLang('JoinRequests', requested)}`;
|
||||
}
|
||||
|
||||
if (usageLimit !== undefined && usage === usageLimit) {
|
||||
text += ` ${BULLET} ${lang('LinkLimitReached')}`;
|
||||
text += ` ${BULLET} ${oldLang('LinkLimitReached')}`;
|
||||
} else if (expireDate) {
|
||||
const diff = (expireDate - getServerTime()) * 1000;
|
||||
const diff = expireDate - getServerTime();
|
||||
text += ` ${BULLET} `;
|
||||
if (diff > 0) {
|
||||
text += lang('InviteLink.ExpiresIn', formatCountdown(lang, diff));
|
||||
text += oldLang('InviteLink.ExpiresIn', formatCountdown(lang, diff));
|
||||
} else {
|
||||
text += lang('InviteLink.Expired');
|
||||
text += oldLang('InviteLink.Expired');
|
||||
}
|
||||
} else if (isPermanent) {
|
||||
text += ` ${BULLET} ${lang('Permanent')}`;
|
||||
text += ` ${BULLET} ${oldLang('Permanent')}`;
|
||||
}
|
||||
|
||||
return text;
|
||||
@ -239,14 +241,14 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
const prepareContextActions = (invite: ApiExportedInvite) => {
|
||||
const actions: MenuItemContextAction[] = [];
|
||||
actions.push({
|
||||
title: lang('Copy'),
|
||||
title: oldLang('Copy'),
|
||||
icon: 'copy',
|
||||
handler: () => copyLink(invite.link),
|
||||
});
|
||||
|
||||
if (!invite.isPermanent && !invite.isRevoked) {
|
||||
actions.push({
|
||||
title: lang('Edit'),
|
||||
title: oldLang('Edit'),
|
||||
icon: 'edit',
|
||||
handler: () => editInvite(invite),
|
||||
});
|
||||
@ -254,14 +256,14 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
|
||||
if (!invite.isRevoked) {
|
||||
actions.push({
|
||||
title: lang('RevokeButton'),
|
||||
title: oldLang('RevokeButton'),
|
||||
icon: 'delete',
|
||||
handler: () => askToRevoke(invite),
|
||||
destructive: true,
|
||||
});
|
||||
} else {
|
||||
actions.push({
|
||||
title: lang('DeleteLink'),
|
||||
title: oldLang('DeleteLink'),
|
||||
icon: 'delete',
|
||||
handler: () => askToDelete(invite),
|
||||
destructive: true,
|
||||
@ -279,7 +281,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
size={STICKER_SIZE_INVITES}
|
||||
className="section-icon"
|
||||
/>
|
||||
<p className="section-help">{isChannel ? lang('PrimaryLinkHelpChannel') : lang('PrimaryLinkHelp')}</p>
|
||||
<p className="section-help">{isChannel ? oldLang('PrimaryLinkHelpChannel') : oldLang('PrimaryLinkHelp')}</p>
|
||||
</div>
|
||||
{primaryInviteLink && (
|
||||
<div className="section">
|
||||
@ -288,13 +290,13 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
link={primaryInviteLink}
|
||||
withShare
|
||||
onRevoke={!chat?.usernames ? handlePrimaryRevoke : undefined}
|
||||
title={chat?.usernames ? lang('PublicLink') : lang('lng_create_permanent_link_title')}
|
||||
title={chat?.usernames ? oldLang('PublicLink') : oldLang('lng_create_permanent_link_title')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="section" teactFastList>
|
||||
<Button isText key="create" className="create-link" onClick={handleCreateNewClick}>
|
||||
{lang('CreateNewLink')}
|
||||
{oldLang('CreateNewLink')}
|
||||
</Button>
|
||||
{(!temporalInvites || !temporalInvites.length) && <NothingFound text="No links found" key="nothing" />}
|
||||
{temporalInvites?.map((invite) => (
|
||||
@ -313,18 +315,18 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
</span>
|
||||
</ListItem>
|
||||
))}
|
||||
<p className="section-help hint" key="links-hint">{lang('ManageLinksInfoHelp')}</p>
|
||||
<p className="section-help hint" key="links-hint">{oldLang('ManageLinksInfoHelp')}</p>
|
||||
</div>
|
||||
{revokedExportedInvites && Boolean(revokedExportedInvites.length) && (
|
||||
<div className="section" teactFastList>
|
||||
<p className="section-help" key="title">{lang('RevokedLinks')}</p>
|
||||
<p className="section-help" key="title">{oldLang('RevokedLinks')}</p>
|
||||
<ListItem
|
||||
icon="delete"
|
||||
destructive
|
||||
key="delete"
|
||||
onClick={openDeleteRevokeAllDialog}
|
||||
>
|
||||
<span className="title">{lang('DeleteAllRevokedLinks')}</span>
|
||||
<span className="title">{oldLang('DeleteAllRevokedLinks')}</span>
|
||||
</ListItem>
|
||||
{revokedExportedInvites?.map((invite) => (
|
||||
<ListItem
|
||||
@ -348,28 +350,28 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
<ConfirmDialog
|
||||
isOpen={isDeleteRevokeAllDialogOpen}
|
||||
onClose={closeDeleteRevokeAllDialog}
|
||||
title={lang('DeleteAllRevokedLinks')}
|
||||
text={lang('DeleteAllRevokedLinkHelp')}
|
||||
title={oldLang('DeleteAllRevokedLinks')}
|
||||
text={oldLang('DeleteAllRevokedLinkHelp')}
|
||||
confirmIsDestructive
|
||||
confirmLabel={lang('DeleteAll')}
|
||||
confirmLabel={oldLang('DeleteAll')}
|
||||
confirmHandler={handleDeleteAllRevoked}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
isOpen={isRevokeDialogOpen}
|
||||
onClose={closeRevokeDialog}
|
||||
title={lang('RevokeLink')}
|
||||
text={lang('RevokeAlert')}
|
||||
title={oldLang('RevokeLink')}
|
||||
text={oldLang('RevokeAlert')}
|
||||
confirmIsDestructive
|
||||
confirmLabel={lang('RevokeButton')}
|
||||
confirmLabel={oldLang('RevokeButton')}
|
||||
confirmHandler={handleRevoke}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
isOpen={isDeleteDialogOpen}
|
||||
onClose={closeDeleteDialog}
|
||||
title={lang('DeleteLink')}
|
||||
text={lang('DeleteLinkHelp')}
|
||||
title={oldLang('DeleteLink')}
|
||||
text={oldLang('DeleteLinkHelp')}
|
||||
confirmIsDestructive
|
||||
confirmLabel={lang('Delete')}
|
||||
confirmLabel={oldLang('Delete')}
|
||||
confirmHandler={handleDelete}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -3,11 +3,11 @@ import React, {
|
||||
memo, useCallback,
|
||||
useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChat, ApiChatMember } from '../../../api/types';
|
||||
|
||||
import { filterUsersByName } from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { selectChatFullInfo } from '../../../global/selectors';
|
||||
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
@ -49,10 +49,7 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// No need for expensive global updates on users, so we avoid them
|
||||
const usersById = getGlobal().users.byId;
|
||||
|
||||
return filterUsersByName(availableMemberIds, usersById, search);
|
||||
return filterPeersByQuery({ ids: availableMemberIds, query: search, type: 'user' });
|
||||
}, [chatMembers, currentUserId, search]);
|
||||
|
||||
const handleRemoveUser = useCallback((userId: string) => {
|
||||
|
||||
@ -278,7 +278,6 @@ function StorySettings({
|
||||
id="deny-list"
|
||||
contactListIds={contactListIds}
|
||||
currentUserId={currentUserId}
|
||||
usersById={usersById}
|
||||
selectedIds={selectedBlockedIds}
|
||||
onSelect={handleDenyUserIdsChange}
|
||||
/>
|
||||
@ -291,7 +290,6 @@ function StorySettings({
|
||||
contactListIds={contactListIds}
|
||||
lockedIds={lockedIds}
|
||||
currentUserId={currentUserId}
|
||||
usersById={usersById}
|
||||
selectedIds={privacy?.allowUserIds}
|
||||
onSelect={handleAllowUserIdsChange}
|
||||
/>
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import React, { memo, useMemo, useState } from '../../../lib/teact/teact';
|
||||
|
||||
import type { ApiUser } from '../../../api/types';
|
||||
|
||||
import { filterUsersByName } from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
|
||||
@ -16,7 +14,6 @@ interface OwnProps {
|
||||
currentUserId: string;
|
||||
selectedIds?: string[];
|
||||
lockedIds?: string[];
|
||||
usersById: Record<string, ApiUser>;
|
||||
onSelect: (selectedIds: string[]) => void;
|
||||
}
|
||||
|
||||
@ -24,7 +21,6 @@ function AllowDenyList({
|
||||
id,
|
||||
contactListIds,
|
||||
currentUserId,
|
||||
usersById,
|
||||
selectedIds,
|
||||
lockedIds,
|
||||
onSelect,
|
||||
@ -34,8 +30,8 @@ function AllowDenyList({
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const displayedIds = useMemo(() => {
|
||||
const contactIds = (contactListIds || []).filter((userId) => userId !== currentUserId);
|
||||
return unique(filterUsersByName([...selectedIds || [], ...contactIds], usersById, searchQuery));
|
||||
}, [contactListIds, currentUserId, searchQuery, selectedIds, usersById]);
|
||||
return unique(filterPeersByQuery({ ids: [...selectedIds || [], ...contactIds], query: searchQuery, type: 'user' }));
|
||||
}, [contactListIds, currentUserId, searchQuery, selectedIds]);
|
||||
|
||||
return (
|
||||
<PeerPicker
|
||||
|
||||
@ -5,7 +5,7 @@ import { getActions } from '../../../global';
|
||||
|
||||
import type { ApiUser } from '../../../api/types';
|
||||
|
||||
import { filterUsersByName } from '../../../global/helpers';
|
||||
import { filterPeersByQuery } from '../../../global/helpers/peers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
|
||||
@ -43,8 +43,8 @@ function CloseFriends({
|
||||
|
||||
const displayedIds = useMemo(() => {
|
||||
const contactIds = (contactListIds || []).filter((id) => id !== currentUserId);
|
||||
return unique(filterUsersByName([...closeFriendIds, ...contactIds], usersById, searchQuery));
|
||||
}, [closeFriendIds, contactListIds, currentUserId, searchQuery, usersById]);
|
||||
return unique(filterPeersByQuery({ ids: [...closeFriendIds, ...contactIds], query: searchQuery, type: 'user' }));
|
||||
}, [closeFriendIds, contactListIds, currentUserId, searchQuery]);
|
||||
|
||||
useEffectWithPrevDeps(([prevIsActive]) => {
|
||||
if (!prevIsActive && isActive) {
|
||||
|
||||
@ -14,10 +14,11 @@ import Modal from './Modal';
|
||||
type OwnProps = {
|
||||
isOpen: boolean;
|
||||
title?: string;
|
||||
noDefaultTitle?: boolean;
|
||||
header?: TeactNode;
|
||||
textParts?: TextPart;
|
||||
text?: string;
|
||||
confirmLabel?: string;
|
||||
confirmLabel?: TeactNode;
|
||||
confirmIsDestructive?: boolean;
|
||||
isConfirmDisabled?: boolean;
|
||||
isOnlyConfirm?: boolean;
|
||||
@ -32,6 +33,7 @@ type OwnProps = {
|
||||
const ConfirmDialog: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
title,
|
||||
noDefaultTitle,
|
||||
header,
|
||||
text,
|
||||
textParts,
|
||||
@ -60,7 +62,7 @@ const ConfirmDialog: FC<OwnProps> = ({
|
||||
return (
|
||||
<Modal
|
||||
className={buildClassName('confirm', className)}
|
||||
title={(title || lang('Telegram'))}
|
||||
title={(title || (!noDefaultTitle ? lang('Telegram') : undefined))}
|
||||
header={header}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
|
||||
@ -42,6 +42,7 @@ export type OwnProps = {
|
||||
dialogRef?: React.RefObject<HTMLDivElement>;
|
||||
isLowStackPriority?: boolean;
|
||||
dialogContent?: React.ReactNode;
|
||||
ignoreFreeze?: boolean;
|
||||
onClose: () => void;
|
||||
onCloseAnimationEnd?: () => void;
|
||||
onEnter?: () => void;
|
||||
|
||||
@ -1941,7 +1941,7 @@ addActionHandler('loadMoreMembers', async (global, actions, payload): Promise<vo
|
||||
const offset = selectChatFullInfo(global, chat.id)?.members?.length;
|
||||
if (offset !== undefined && chat.membersCount !== undefined && offset >= chat.membersCount) return;
|
||||
|
||||
const result = await callApi('fetchMembers', chat.id, chat.accessHash!, 'recent', offset);
|
||||
const result = await callApi('fetchMembers', { chat, offset });
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type {
|
||||
ApiInputInvoice, ApiInputInvoiceStarGift, ApiInputInvoiceStarGiftUpgrade, ApiRequestInputInvoice,
|
||||
ApiInputInvoice, ApiInputInvoiceStarGift, ApiRequestInputInvoice,
|
||||
} from '../../../api/types';
|
||||
import type { ApiCredentials } from '../../../components/payment/PaymentModal';
|
||||
import type { RegularLangFnParameters } from '../../../util/localization';
|
||||
@ -977,7 +977,7 @@ addActionHandler('upgradeGift', (global, actions, payload): ActionReturnType =>
|
||||
actions.closeGiftInfoModal({ tabId });
|
||||
|
||||
if (!upgradeStars) {
|
||||
callApi('upgradeGift', {
|
||||
callApi('upgradeStarGift', {
|
||||
inputSavedGift: requestSavedGift,
|
||||
shouldKeepOriginalDetails: shouldKeepOriginalDetails || undefined,
|
||||
});
|
||||
@ -985,7 +985,7 @@ addActionHandler('upgradeGift', (global, actions, payload): ActionReturnType =>
|
||||
return;
|
||||
}
|
||||
|
||||
const invoice: ApiInputInvoiceStarGiftUpgrade = {
|
||||
const invoice: ApiInputInvoice = {
|
||||
type: 'stargiftUpgrade',
|
||||
inputSavedGift: gift,
|
||||
shouldKeepOriginalDetails: shouldKeepOriginalDetails || undefined,
|
||||
@ -994,6 +994,46 @@ addActionHandler('upgradeGift', (global, actions, payload): ActionReturnType =>
|
||||
payInputStarInvoice(global, invoice, upgradeStars, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('transferGift', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
gift, recipientId, transferStars, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const peer = selectChat(global, recipientId);
|
||||
|
||||
const requestSavedGift = getRequestInputSavedStarGift(global, gift);
|
||||
if (!peer || !requestSavedGift) {
|
||||
return;
|
||||
}
|
||||
|
||||
global = updateTabState(global, {
|
||||
isWaitingForStarGiftTransfer: true,
|
||||
}, tabId);
|
||||
|
||||
setGlobal(global);
|
||||
global = getGlobal();
|
||||
|
||||
actions.closeGiftTransferModal({ tabId });
|
||||
actions.closeGiftInfoModal({ tabId });
|
||||
|
||||
if (!transferStars) {
|
||||
callApi('transferStarGift', {
|
||||
inputSavedGift: requestSavedGift,
|
||||
toPeer: peer,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const invoice: ApiInputInvoice = {
|
||||
type: 'stargiftTransfer',
|
||||
inputSavedGift: gift,
|
||||
recipientId,
|
||||
};
|
||||
|
||||
payInputStarInvoice(global, invoice, transferStars, tabId);
|
||||
});
|
||||
|
||||
async function payInputStarInvoice<T extends GlobalState>(
|
||||
global: T, inputInvoice: ApiInputInvoice, price: number,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
|
||||
@ -2,7 +2,8 @@ import type { ActionReturnType } from '../../types';
|
||||
import { PaymentStep } from '../../../types';
|
||||
|
||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||
import { applyLangPackDifference, requestLangPackDifference } from '../../../util/localization';
|
||||
import { applyLangPackDifference, getTranslationFn, requestLangPackDifference } from '../../../util/localization';
|
||||
import { getPeerTitle } from '../../helpers';
|
||||
import { addActionHandler, setGlobal } from '../../index';
|
||||
import {
|
||||
addBlockedUser,
|
||||
@ -21,7 +22,12 @@ import {
|
||||
updateThreadInfos,
|
||||
} from '../../reducers';
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
import { selectPeerStories, selectPeerStory, selectTabState } from '../../selectors';
|
||||
import {
|
||||
selectPeer,
|
||||
selectPeerStories,
|
||||
selectPeerStory,
|
||||
selectTabState,
|
||||
} from '../../selectors';
|
||||
|
||||
addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
switch (update['@type']) {
|
||||
@ -235,7 +241,44 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
isWaitingForStarGiftUpgrade: undefined,
|
||||
}, tabId);
|
||||
}
|
||||
|
||||
if (tabState.isWaitingForStarGiftTransfer) {
|
||||
const chatId = update.message.chatId;
|
||||
const receiver = chatId ? selectPeer(global, chatId) : undefined;
|
||||
if (receiver) {
|
||||
actions.focusMessage({
|
||||
chatId: receiver.id,
|
||||
messageId: update.message.id!,
|
||||
tabId,
|
||||
});
|
||||
|
||||
actions.showNotification({
|
||||
message: {
|
||||
key: 'GiftTransferSuccessMessage',
|
||||
variables: {
|
||||
gift: {
|
||||
key: 'GiftUnique',
|
||||
variables: {
|
||||
title: actionStarGift.gift.title,
|
||||
number: actionStarGift.gift.number,
|
||||
},
|
||||
},
|
||||
peer: getPeerTitle(getTranslationFn(), receiver),
|
||||
},
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
}
|
||||
|
||||
actions.requestConfetti({ withStars: true, tabId });
|
||||
|
||||
global = updateTabState(global, {
|
||||
isWaitingForStarGiftTransfer: undefined,
|
||||
}, tabId);
|
||||
}
|
||||
});
|
||||
|
||||
setGlobal(global);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import type { ActionReturnType } from '../../types';
|
||||
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import * as langProvider from '../../../util/oldLangProvider';
|
||||
import { addTabStateResetterAction } from '../../helpers/meta';
|
||||
import { getPrizeStarsTransactionFromGiveaway, getStarsTransactionFromGift } from '../../helpers/payments';
|
||||
import { addActionHandler } from '../../index';
|
||||
import {
|
||||
@ -63,13 +64,7 @@ addActionHandler('openGiftRecipientPicker', (global, actions, payload): ActionRe
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeGiftRecipientPicker', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
isGiftRecipientPickerOpen: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
addTabStateResetterAction('closeGiftRecipientPicker', 'isGiftRecipientPickerOpen');
|
||||
|
||||
addActionHandler('openStarsGiftingPickerModal', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
@ -83,13 +78,7 @@ addActionHandler('openStarsGiftingPickerModal', (global, actions, payload): Acti
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeStarsGiftingPickerModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
starsGiftingPickerModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
addTabStateResetterAction('closeStarsGiftingPickerModal', 'starsGiftingPickerModal');
|
||||
|
||||
addActionHandler('openPrizeStarsTransactionFromGiveaway', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
@ -148,13 +137,7 @@ addActionHandler('openStarsBalanceModal', (global, actions, payload): ActionRetu
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeStarsBalanceModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
starsBalanceModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
addTabStateResetterAction('closeStarsBalanceModal', 'starsBalanceModal');
|
||||
|
||||
addActionHandler('closeStarsPaymentModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
@ -193,13 +176,7 @@ addActionHandler('openStarsTransactionFromGift', (global, actions, payload): Act
|
||||
return openStarsTransactionModal(global, transaction, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeStarsTransactionModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
starsTransactionModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
addTabStateResetterAction('closeStarsTransactionModal', 'starsTransactionModal');
|
||||
|
||||
addActionHandler('openStarsSubscriptionModal', (global, actions, payload): ActionReturnType => {
|
||||
const { subscription, tabId = getCurrentTabId() } = payload;
|
||||
@ -211,20 +188,9 @@ addActionHandler('openStarsSubscriptionModal', (global, actions, payload): Actio
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeStarsSubscriptionModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
addTabStateResetterAction('closeStarsSubscriptionModal', 'starsSubscriptionModal');
|
||||
|
||||
return updateTabState(global, {
|
||||
starsSubscriptionModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeGiftModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
return updateTabState(global, {
|
||||
giftModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
addTabStateResetterAction('closeGiftModal', 'giftModal');
|
||||
|
||||
addActionHandler('closeStarsGiftModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
@ -287,21 +253,9 @@ addActionHandler('openGiftInfoModal', (global, actions, payload): ActionReturnTy
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeGiftInfoModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
addTabStateResetterAction('closeGiftInfoModal', 'giftInfoModal');
|
||||
|
||||
return updateTabState(global, {
|
||||
giftInfoModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeGiftUpgradeModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
giftUpgradeModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
addTabStateResetterAction('closeGiftUpgradeModal', 'giftUpgradeModal');
|
||||
|
||||
addActionHandler('openGiftWithdrawModal', (global, actions, payload): ActionReturnType => {
|
||||
const { gift, tabId = getCurrentTabId() } = payload || {};
|
||||
@ -313,13 +267,7 @@ addActionHandler('openGiftWithdrawModal', (global, actions, payload): ActionRetu
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeGiftWithdrawModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
giftWithdrawModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
addTabStateResetterAction('closeGiftWithdrawModal', 'giftWithdrawModal');
|
||||
|
||||
addActionHandler('clearGiftWithdrawError', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
@ -334,3 +282,15 @@ addActionHandler('clearGiftWithdrawError', (global, actions, payload): ActionRet
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftTransferModal', (global, actions, payload): ActionReturnType => {
|
||||
const { gift, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
return updateTabState(global, {
|
||||
giftTransferModal: {
|
||||
gift,
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeGiftTransferModal', 'giftTransferModal');
|
||||
|
||||
@ -22,7 +22,6 @@ import {
|
||||
VERIFICATION_CODES_USER_ID,
|
||||
} from '../../config';
|
||||
import { formatDateToString, formatTime } from '../../util/dates/dateFormat';
|
||||
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
||||
import { getGlobal } from '..';
|
||||
import { isSystemBot } from './bots';
|
||||
import { getMainUsername, getUserFirstOrLastName } from './users';
|
||||
@ -395,36 +394,6 @@ export function getMessageSenderName(lang: OldLangFn, chatId: string, sender?: A
|
||||
return getUserFirstOrLastName(sender);
|
||||
}
|
||||
|
||||
export function filterChatsByName(
|
||||
lang: OldLangFn,
|
||||
chatIds: string[],
|
||||
chatsById: Record<string, ApiChat>,
|
||||
query?: string,
|
||||
currentUserId?: string,
|
||||
) {
|
||||
if (!query) {
|
||||
return chatIds;
|
||||
}
|
||||
|
||||
const searchWords = prepareSearchWordsForNeedle(query);
|
||||
|
||||
return chatIds.filter((id) => {
|
||||
const chat = chatsById[id];
|
||||
if (!chat) {
|
||||
return false;
|
||||
}
|
||||
const isSelf = id === currentUserId;
|
||||
|
||||
const translatedTitle = getChatTitle(lang, chat, isSelf);
|
||||
if (isSelf) {
|
||||
// Search both "Saved Messages" and user title
|
||||
return searchWords(translatedTitle) || searchWords(chat.title);
|
||||
}
|
||||
|
||||
return searchWords(translatedTitle) || Boolean(chat.usernames?.find(({ username }) => searchWords(username)));
|
||||
});
|
||||
}
|
||||
|
||||
export function isChatPublic(chat: ApiChat) {
|
||||
return chat.usernames?.some(({ isActive }) => isActive);
|
||||
}
|
||||
|
||||
18
src/global/helpers/meta.ts
Normal file
18
src/global/helpers/meta.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { ActionReturnType, TabState } from '../types';
|
||||
|
||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
import { updateTabState } from '../reducers/tabs';
|
||||
import { addActionHandler, type TabStateActionNames } from '..';
|
||||
|
||||
export function addTabStateResetterAction<ActionName extends TabStateActionNames>(
|
||||
name: ActionName, key: keyof TabState,
|
||||
) {
|
||||
// @ts-ignore
|
||||
addActionHandler(name, (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
[key]: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
}
|
||||
@ -188,6 +188,19 @@ export function getRequestInputInvoice<T extends GlobalState>(
|
||||
};
|
||||
}
|
||||
|
||||
if (inputInvoice.type === 'stargiftTransfer') {
|
||||
const { inputSavedGift, recipientId } = inputInvoice;
|
||||
const savedGift = getRequestInputSavedStarGift(global, inputSavedGift);
|
||||
const peer = selectPeer(global, recipientId);
|
||||
if (!savedGift || !peer) return undefined;
|
||||
|
||||
return {
|
||||
type: 'stargiftTransfer',
|
||||
inputSavedGift: savedGift,
|
||||
recipient: peer,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
import type { ApiChat, ApiPeer, ApiUser } from '../../api/types';
|
||||
|
||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
||||
import { getTranslationFn } from '../../util/localization';
|
||||
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
||||
import { selectChat, selectPeer, selectUser } from '../selectors';
|
||||
import { getGlobal } from '..';
|
||||
import { getChatTitle } from './chats';
|
||||
import { getPeerFullTitle } from './messages';
|
||||
|
||||
export function isApiPeerChat(peer: ApiPeer): peer is ApiChat {
|
||||
return 'title' in peer;
|
||||
@ -10,6 +16,44 @@ export function isApiPeerUser(peer: ApiPeer): peer is ApiUser {
|
||||
return !isApiPeerChat(peer);
|
||||
}
|
||||
|
||||
export function filterPeersByQuery({
|
||||
ids,
|
||||
query,
|
||||
type = 'peer',
|
||||
} : {
|
||||
ids: string[];
|
||||
query: string | undefined;
|
||||
type?: 'chat' | 'user' | 'peer';
|
||||
}) {
|
||||
if (!query) {
|
||||
return ids;
|
||||
}
|
||||
const global = getGlobal();
|
||||
const lang = getTranslationFn();
|
||||
|
||||
const searchWords = prepareSearchWordsForNeedle(query);
|
||||
|
||||
const selectorFn = type === 'chat' ? selectChat : type === 'user' ? selectUser : selectPeer;
|
||||
|
||||
return ids.filter((id) => {
|
||||
const peer = selectorFn(global, id);
|
||||
if (!peer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const localizedTitle = isApiPeerChat(peer)
|
||||
? getChatTitle(lang, peer)
|
||||
: id === global.currentUserId ? lang('SavedMessages') : undefined;
|
||||
const isFoundInLocalized = localizedTitle ? searchWords(localizedTitle) : undefined;
|
||||
|
||||
const name = getPeerFullTitle(lang, peer);
|
||||
|
||||
return isFoundInLocalized
|
||||
|| (name && searchWords(name))
|
||||
|| Boolean(peer.usernames?.find(({ username }) => searchWords(username)));
|
||||
});
|
||||
}
|
||||
|
||||
export function getPeerTypeKey(peer: ApiPeer) {
|
||||
if (isApiPeerChat(peer)) {
|
||||
if (peer.type === 'chatTypeBasicGroup' || peer.type === 'chatTypeSuperGroup') {
|
||||
|
||||
@ -6,7 +6,6 @@ import { formatFullDate, formatTime } from '../../util/dates/dateFormat';
|
||||
import { DAY } from '../../util/dates/units';
|
||||
import { orderBy } from '../../util/iteratees';
|
||||
import { formatPhoneNumber } from '../../util/phoneNumber';
|
||||
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
||||
import { getServerTime, getServerTimeOffset } from '../../util/serverTime';
|
||||
|
||||
export function getUserFirstOrLastName(user?: ApiUser) {
|
||||
@ -239,31 +238,6 @@ export function sortUserIds(
|
||||
}, 'desc');
|
||||
}
|
||||
|
||||
export function filterUsersByName(
|
||||
userIds: string[],
|
||||
usersById: Record<string, ApiUser>,
|
||||
query?: string,
|
||||
currentUserId?: string,
|
||||
savedMessagesLang?: string,
|
||||
) {
|
||||
if (!query) {
|
||||
return userIds;
|
||||
}
|
||||
|
||||
const searchWords = prepareSearchWordsForNeedle(query);
|
||||
|
||||
return userIds.filter((id) => {
|
||||
const user = usersById[id];
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const name = id === currentUserId ? savedMessagesLang : getUserFullName(user);
|
||||
|
||||
return (name && searchWords(name)) || Boolean(user.usernames?.find(({ username }) => searchWords(username)));
|
||||
});
|
||||
}
|
||||
|
||||
export function getMainUsername(userOrChat: ApiPeer) {
|
||||
return userOrChat.usernames?.find((u) => u.isActive)?.username;
|
||||
}
|
||||
|
||||
@ -14,13 +14,18 @@ type ProjectActionTypes =
|
||||
type ProjectActionNames = keyof ProjectActionTypes;
|
||||
|
||||
type Helper<T, E> = Exclude<T, E> extends never ? {} : Exclude<T, E>;
|
||||
|
||||
export type TabStateActionNames = {
|
||||
[ActionName in ProjectActionNames]:
|
||||
'tabId' extends keyof Helper<ProjectActionTypes[ActionName], undefined> ? ActionName : never
|
||||
}[ProjectActionNames];
|
||||
// `Required` actions are called from actions to ensure the `tabId` is always provided if needed.
|
||||
// There are three types of actions:
|
||||
// 1. With tabId, which is made required when calling action from another action handler
|
||||
// 2. Without payload (= undefined), hence made the payload not required
|
||||
// 3. With payload, hence made the payload required
|
||||
export type RequiredGlobalActions = {
|
||||
[ActionName in ProjectActionNames]: 'tabId' extends keyof Helper<ProjectActionTypes[ActionName], undefined> ? ((
|
||||
[ActionName in ProjectActionNames]: ActionName extends TabStateActionNames ? ((
|
||||
payload: ProjectActionTypes[ActionName] & { tabId: number },
|
||||
options?: ActionOptions,
|
||||
) => void) :
|
||||
|
||||
@ -3,9 +3,10 @@ import type { GlobalState, TabArgs } from '../types';
|
||||
|
||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
import { isDeletedUser } from '../helpers';
|
||||
import { selectChat, selectChatFullInfo } from './chats';
|
||||
import { selectTabState } from './tabs';
|
||||
import { selectBot, selectIsPremiumPurchaseBlocked, selectUser } from './users';
|
||||
import { selectBot, selectUser } from './users';
|
||||
|
||||
export function selectPeer<T extends GlobalState>(global: T, peerId: string): ApiPeer | undefined {
|
||||
return selectUser(global, peerId) || selectChat(global, peerId);
|
||||
@ -19,10 +20,11 @@ export function selectCanGift<T extends GlobalState>(global: T, peerId: string)
|
||||
const bot = selectBot(global, peerId);
|
||||
const user = selectUser(global, peerId);
|
||||
|
||||
const areStarGiftsAvailable = selectChatFullInfo(global, peerId)?.areStarGiftsAvailable || user;
|
||||
if (user) {
|
||||
return !bot && peerId !== SERVICE_NOTIFICATIONS_USER_ID && !isDeletedUser(user);
|
||||
}
|
||||
|
||||
return Boolean(!selectIsPremiumPurchaseBlocked(global) && !bot && peerId !== SERVICE_NOTIFICATIONS_USER_ID
|
||||
&& areStarGiftsAvailable);
|
||||
return selectChatFullInfo(global, peerId)?.areStarGiftsAvailable;
|
||||
}
|
||||
|
||||
export function selectPeerSavedGifts<T extends GlobalState>(
|
||||
|
||||
@ -92,7 +92,7 @@ import type { WebApp, WebAppModalStateType, WebAppOutboundEvent } from '../../ty
|
||||
import type { DownloadableMedia } from '../helpers';
|
||||
import type { TabState } from './tabState';
|
||||
|
||||
type WithTabId = { tabId?: number };
|
||||
export type WithTabId = { tabId?: number };
|
||||
|
||||
export interface ActionPayloads {
|
||||
// system
|
||||
@ -2308,6 +2308,7 @@ export interface ActionPayloads {
|
||||
} & WithTabId;
|
||||
closeGiftModal: WithTabId | undefined;
|
||||
sendStarGift: StarGiftInfo & WithTabId;
|
||||
|
||||
openGiftInfoModalFromMessage: {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
@ -2319,6 +2320,7 @@ export interface ActionPayloads {
|
||||
gift: ApiStarGift;
|
||||
}) & WithTabId;
|
||||
closeGiftInfoModal: WithTabId | undefined;
|
||||
|
||||
openGiftUpgradeModal: {
|
||||
giftId: string;
|
||||
peerId?: string;
|
||||
@ -2330,6 +2332,7 @@ export interface ActionPayloads {
|
||||
shouldKeepOriginalDetails?: boolean;
|
||||
upgradeStars?: number;
|
||||
} & WithTabId;
|
||||
|
||||
openGiftWithdrawModal: {
|
||||
gift: ApiSavedStarGift;
|
||||
} & WithTabId;
|
||||
@ -2339,6 +2342,17 @@ export interface ActionPayloads {
|
||||
gift: ApiInputSavedStarGift;
|
||||
password: string;
|
||||
} & WithTabId;
|
||||
|
||||
openGiftTransferModal: {
|
||||
gift: ApiSavedStarGift;
|
||||
} & WithTabId;
|
||||
transferGift: {
|
||||
gift: ApiInputSavedStarGift;
|
||||
transferStars?: number;
|
||||
recipientId: string;
|
||||
} & WithTabId;
|
||||
closeGiftTransferModal: WithTabId | undefined;
|
||||
|
||||
loadPeerSavedGifts: {
|
||||
peerId: string;
|
||||
shouldRefresh?: boolean;
|
||||
|
||||
@ -722,6 +722,10 @@ export type TabState = {
|
||||
gift: ApiSavedStarGift | ApiStarGift;
|
||||
};
|
||||
|
||||
giftTransferModal?: {
|
||||
gift: ApiSavedStarGift;
|
||||
};
|
||||
|
||||
giftUpgradeModal?: {
|
||||
sampleAttributes: ApiStarGiftAttribute[];
|
||||
recipientId?: string;
|
||||
@ -748,4 +752,5 @@ export type TabState = {
|
||||
};
|
||||
|
||||
isWaitingForStarGiftUpgrade?: true;
|
||||
isWaitingForStarGiftTransfer?: true;
|
||||
};
|
||||
|
||||
66
src/hooks/usePeerSearch.ts
Normal file
66
src/hooks/usePeerSearch.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { useState } from '../lib/teact/teact';
|
||||
|
||||
import type { ApiChat } from '../api/types';
|
||||
|
||||
import { callApi } from '../api/gramjs';
|
||||
import useAsync from './useAsync';
|
||||
import useDebouncedMemo from './useDebouncedMemo';
|
||||
import useLastCallback from './useLastCallback';
|
||||
|
||||
const DEBOUNCE_TIMEOUT = 300;
|
||||
|
||||
export async function peerGlobalSearch(query: string) {
|
||||
const searchResult = await callApi('searchChats', { query });
|
||||
if (!searchResult) return undefined;
|
||||
|
||||
const ids = [...searchResult.accountResultIds, ...searchResult.globalResultIds];
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
export function prepareChatMemberSearch(chat: ApiChat) {
|
||||
return async (query: string) => {
|
||||
const searchResult = await callApi('fetchMembers', {
|
||||
chat,
|
||||
memberFilter: 'search',
|
||||
query,
|
||||
});
|
||||
|
||||
return searchResult?.members?.map((member) => member.userId) || [];
|
||||
};
|
||||
}
|
||||
|
||||
export default function usePeerSearch({
|
||||
query,
|
||||
queryFn = peerGlobalSearch,
|
||||
defaultValue,
|
||||
debounceTimeout = DEBOUNCE_TIMEOUT,
|
||||
isDisabled,
|
||||
}: {
|
||||
query: string;
|
||||
queryFn?: (query: string) => Promise<string[] | undefined>;
|
||||
defaultValue?: string[];
|
||||
debounceTimeout?: number;
|
||||
isDisabled?: boolean;
|
||||
}) {
|
||||
const debouncedQuery = useDebouncedMemo(() => query, debounceTimeout, [query]);
|
||||
const [currentResultsQuery, setCurrentResultsQuery] = useState<string>('');
|
||||
const searchQuery = !query ? query : debouncedQuery; // Ignore debounce if query is empty
|
||||
const queryCallback = useLastCallback(queryFn);
|
||||
|
||||
const result = useAsync(async () => {
|
||||
if (!searchQuery || isDisabled) {
|
||||
setCurrentResultsQuery('');
|
||||
return Promise.resolve(defaultValue);
|
||||
}
|
||||
|
||||
const answer = await queryCallback(searchQuery);
|
||||
setCurrentResultsQuery(searchQuery);
|
||||
return answer;
|
||||
}, [searchQuery, defaultValue, queryCallback, isDisabled], defaultValue);
|
||||
|
||||
return {
|
||||
...result,
|
||||
currentResultsQuery,
|
||||
};
|
||||
}
|
||||
28
src/types/language.d.ts
vendored
28
src/types/language.d.ts
vendored
@ -1197,7 +1197,10 @@ export interface LangPair {
|
||||
'GiftInfoViewUpgraded': undefined;
|
||||
'GiftInfoUpgradeBadge': undefined;
|
||||
'GiftInfoUpgradeForFree': undefined;
|
||||
'GiftInfoWithdraw': undefined;
|
||||
'GiftInfoTransfer': undefined;
|
||||
'GiftTransferTitle': undefined;
|
||||
'GiftTransferTON': undefined;
|
||||
'GiftTransferConfirmButtonFree': undefined;
|
||||
'GiftUpgradeUniqueTitle': undefined;
|
||||
'GiftUpgradeUniqueDescription': undefined;
|
||||
'GiftUpgradeTransferableTitle': undefined;
|
||||
@ -1672,6 +1675,10 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
'GiftSend': {
|
||||
'amount': V;
|
||||
};
|
||||
'GiftUnique': {
|
||||
'title': V;
|
||||
'number': V;
|
||||
};
|
||||
'GiftInfoPeerDescriptionFreeUpgradeOut': {
|
||||
'peer': V;
|
||||
};
|
||||
@ -1718,6 +1725,25 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
'date': V;
|
||||
'text': V;
|
||||
};
|
||||
'GiftTransferTONBlocked': {
|
||||
'time': V;
|
||||
};
|
||||
'GiftTransferConfirmDescription': {
|
||||
'gift': V;
|
||||
'peer': V;
|
||||
'amount': V;
|
||||
};
|
||||
'GiftTransferConfirmDescriptionFree': {
|
||||
'gift': V;
|
||||
'peer': V;
|
||||
};
|
||||
'GiftTransferConfirmButton': {
|
||||
'amount': V;
|
||||
};
|
||||
'GiftTransferSuccessMessage': {
|
||||
'gift': V;
|
||||
'peer': V;
|
||||
};
|
||||
'GiftPeerUpgradeText': {
|
||||
'peer': V;
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import type { TimeFormat } from '../../types';
|
||||
import type { LangFn } from '../localization';
|
||||
|
||||
import withCache from '../withCache';
|
||||
import { getDays } from './units';
|
||||
|
||||
const WEEKDAYS_FULL = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
const MONTHS_FULL = [
|
||||
@ -86,22 +87,26 @@ export function formatMonthAndYear(lang: OldLangFn, date: Date, isShort = false)
|
||||
}
|
||||
|
||||
export function formatCountdown(
|
||||
lang: OldLangFn,
|
||||
msLeft: number,
|
||||
lang: LangFn,
|
||||
secondsLeft: number,
|
||||
) {
|
||||
const days = Math.floor(msLeft / MILLISECONDS_IN_DAY);
|
||||
if (msLeft < 0) {
|
||||
const days = getDays(secondsLeft);
|
||||
if (secondsLeft < 0) {
|
||||
return 0;
|
||||
} else if (days < 1) {
|
||||
return formatMediaDuration(msLeft / 1000);
|
||||
return formatMediaDuration(secondsLeft);
|
||||
} else if (days < 7) {
|
||||
return lang('Days', days);
|
||||
const count = days;
|
||||
return lang('Days', { count }, { pluralValue: count });
|
||||
} else if (days < 30) {
|
||||
return lang('Weeks', Math.floor(days / 7));
|
||||
const count = Math.floor(days / 7);
|
||||
return lang('Weeks', { count }, { pluralValue: count });
|
||||
} else if (days < 365) {
|
||||
return lang('Months', Math.floor(days / 30));
|
||||
const count = Math.floor(days / 30);
|
||||
return lang('Months', { count }, { pluralValue: count });
|
||||
} else {
|
||||
return lang('Years', Math.floor(days / 365));
|
||||
const count = Math.floor(days / 365);
|
||||
return lang('Years', { count }, { pluralValue: count });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
import { type FC, type Props, useRef } from '../../lib/teact/teact';
|
||||
|
||||
export default function freezeWhenClosed<T extends FC>(Component: T) {
|
||||
type InjectProps<T extends FC, P extends Props> = FC<Parameters<T>[0] & P>;
|
||||
|
||||
type OwnProps = {
|
||||
ignoreFreeze?: boolean;
|
||||
};
|
||||
|
||||
export default function freezeWhenClosed<T extends FC>(Component: T): InjectProps<T, OwnProps> {
|
||||
function ComponentWrapper(props: Props) {
|
||||
const newProps = useRef(props);
|
||||
|
||||
if (props.ignoreFreeze) return Component(props);
|
||||
|
||||
if (props.isOpen) {
|
||||
newProps.current = props;
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user