Gifting: Premium Gifting (#4472)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
parent
2dc4e9d6cf
commit
34a3b62089
@ -63,6 +63,7 @@ export interface GramJsAppConfig extends LimitsConfig {
|
||||
authorization_autoconfirm_period: number;
|
||||
giveaway_boosts_per_premium: number;
|
||||
giveaway_countries_max: number;
|
||||
boosts_per_sent_gift: number;
|
||||
// Forums
|
||||
topics_pinned_limit: number;
|
||||
// Stories
|
||||
@ -127,6 +128,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
||||
giveawayAddPeersMax: appConfig.giveaway_add_peers_max,
|
||||
giveawayBoostsPerPremium: appConfig.giveaway_boosts_per_premium,
|
||||
giveawayCountriesMax: appConfig.giveaway_countries_max,
|
||||
boostsPerSentGift: appConfig.boosts_per_sent_gift,
|
||||
canDisplayAutoarchiveSetting: appConfig.autoarchive_setting_available,
|
||||
limits: {
|
||||
uploadMaxFileparts: getLimit(appConfig, 'upload_max_fileparts', 'uploadMaxFileparts'),
|
||||
|
||||
@ -189,6 +189,7 @@ export interface ApiAppConfig {
|
||||
giveawayAddPeersMax: number;
|
||||
giveawayBoostsPerPremium: number;
|
||||
giveawayCountriesMax: number;
|
||||
boostsPerSentGift: number;
|
||||
premiumPromoOrder: ApiPremiumSection[];
|
||||
defaultEmojiStatusesStickerSetId: string;
|
||||
maxUniqueReactions: number;
|
||||
|
||||
@ -18,6 +18,7 @@ export { default as DeleteFolderDialog } from '../components/main/DeleteFolderDi
|
||||
export { default as PremiumMainModal } from '../components/main/premium/PremiumMainModal';
|
||||
export { default as GiftPremiumModal } from '../components/main/premium/GiftPremiumModal';
|
||||
export { default as GiveawayModal } from '../components/main/premium/GiveawayModal';
|
||||
export { default as PremiumGiftingModal } from '../components/main/premium/PremiumGiftingModal';
|
||||
export { default as AppendEntityPickerModal } from '../components/main/AppendEntityPickerModal';
|
||||
export { default as PremiumLimitReachedModal } from '../components/main/premium/common/PremiumLimitReachedModal';
|
||||
export { default as StatusPickerMenu } from '../components/left/main/StatusPickerMenu';
|
||||
|
||||
@ -145,7 +145,7 @@ const UserBirthday = ({
|
||||
const canGiftPremium = isToday && !user.isPremium && !user.isSelf && !isPremiumPurchaseBlocked;
|
||||
|
||||
const handleOpenGiftModal = useLastCallback(() => {
|
||||
openGiftPremiumModal({ forUserId: user.id });
|
||||
openGiftPremiumModal({ forUserIds: [user.id] });
|
||||
});
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
}
|
||||
|
||||
.settings-main-menu-premium .PremiumIcon {
|
||||
margin-right: 2rem;
|
||||
margin-right: 1.25rem;
|
||||
}
|
||||
|
||||
.settings-main-menu {
|
||||
|
||||
@ -5,7 +5,10 @@ import { getActions, withGlobal } from '../../../global';
|
||||
import { SettingsScreens } from '../../../types';
|
||||
|
||||
import { FAQ_URL, PRIVACY_URL } from '../../../config';
|
||||
import { selectIsPremiumPurchaseBlocked } from '../../../global/selectors';
|
||||
import {
|
||||
selectIsGiveawayGiftsPurchaseAvailable,
|
||||
selectIsPremiumPurchaseBlocked,
|
||||
} from '../../../global/selectors';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
@ -28,6 +31,7 @@ type StateProps = {
|
||||
sessionCount: number;
|
||||
currentUserId?: string;
|
||||
canBuyPremium?: boolean;
|
||||
isGiveawayAvailable?: boolean;
|
||||
};
|
||||
|
||||
const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
@ -37,12 +41,14 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
currentUserId,
|
||||
sessionCount,
|
||||
canBuyPremium,
|
||||
isGiveawayAvailable,
|
||||
}) => {
|
||||
const {
|
||||
loadProfilePhotos,
|
||||
openPremiumModal,
|
||||
openSupportChat,
|
||||
openUrl,
|
||||
openPremiumGiftingModal,
|
||||
} = getActions();
|
||||
|
||||
const [isSupportDialogOpen, openSupportDialog, closeSupportDialog] = useFlag(false);
|
||||
@ -158,6 +164,16 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
{lang('TelegramPremium')}
|
||||
</ListItem>
|
||||
)}
|
||||
{isGiveawayAvailable && (
|
||||
<ListItem
|
||||
icon="gift"
|
||||
className="settings-main-menu-premium"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openPremiumGiftingModal()}
|
||||
>
|
||||
{lang('GiftPremiumGifting')}
|
||||
</ListItem>
|
||||
)}
|
||||
</div>
|
||||
<div className="settings-main-menu">
|
||||
<ListItem
|
||||
@ -196,11 +212,13 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const { currentUserId } = global;
|
||||
const isGiveawayAvailable = selectIsGiveawayGiftsPurchaseAvailable(global);
|
||||
|
||||
return {
|
||||
sessionCount: global.activeSessions.orderedHashes.length,
|
||||
currentUserId,
|
||||
canBuyPremium: !selectIsPremiumPurchaseBlocked(global),
|
||||
isGiveawayAvailable,
|
||||
};
|
||||
},
|
||||
)(SettingsMain));
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useMemo, useState,
|
||||
memo,
|
||||
useMemo,
|
||||
useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
@ -66,8 +68,7 @@ const AppendEntityPickerModal: FC<OwnProps & StateProps> = ({
|
||||
const lang = useLang();
|
||||
const [isConfirmModalOpen, openConfirmModal, closeConfirmModal] = useFlag();
|
||||
|
||||
const [selectedChannelIds, setSelectedChannelIds] = useState<string[]>([]);
|
||||
const [selectedMemberIds, setSelectedMemberIds] = useState<string[]>([]);
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
const [pendingChannelId, setPendingChannelId] = useState<string | undefined>(undefined);
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
|
||||
@ -127,8 +128,8 @@ const AppendEntityPickerModal: FC<OwnProps & StateProps> = ({
|
||||
return isChannel || isSuperGroup;
|
||||
}),
|
||||
false,
|
||||
selectedChannelIds);
|
||||
}, [channelsIds, lang, searchQuery, selectedChannelIds, isSuperGroup, isChannel]);
|
||||
selectedIds);
|
||||
}, [channelsIds, lang, searchQuery, selectedIds, isSuperGroup, isChannel]);
|
||||
|
||||
const handleCloseButtonClick = useLastCallback(() => {
|
||||
onSubmit([]);
|
||||
@ -136,36 +137,36 @@ const AppendEntityPickerModal: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const handleSendIdList = useLastCallback(() => {
|
||||
onSubmit(entityType === 'members' ? selectedMemberIds : selectedChannelIds);
|
||||
onSubmit(selectedIds);
|
||||
onClose();
|
||||
});
|
||||
|
||||
const confirmPrivateLinkChannelSelection = useLastCallback(() => {
|
||||
if (pendingChannelId) {
|
||||
setSelectedChannelIds((prevIds) => unique([...prevIds, pendingChannelId]));
|
||||
setSelectedIds((prevIds) => unique([...prevIds, pendingChannelId]));
|
||||
}
|
||||
closeConfirmModal();
|
||||
});
|
||||
|
||||
const handleSelectedMembersChange = useLastCallback((newSelectedIds: string[]) => {
|
||||
const handleSelectedMemberIdsChange = useLastCallback((newSelectedIds: string[]) => {
|
||||
if (newSelectedIds.length > selectionLimit) {
|
||||
showNotification({
|
||||
message: lang('BoostingSelectUpToWarningUsers', selectionLimit),
|
||||
});
|
||||
return;
|
||||
}
|
||||
setSelectedMemberIds(newSelectedIds);
|
||||
setSelectedIds(newSelectedIds);
|
||||
});
|
||||
|
||||
const handleSelectedChannelIdsChange = useLastCallback((newSelectedIds: string[]) => {
|
||||
const chatsById = getGlobal().chats.byId;
|
||||
const newlyAddedIds = newSelectedIds.filter((id) => !selectedChannelIds.includes(id));
|
||||
const newlyAddedIds = newSelectedIds.filter((id) => !selectedIds.includes(id));
|
||||
const privateLinkChannelId = newlyAddedIds.find((id) => {
|
||||
const chat = chatsById[id];
|
||||
return chat && !isChatPublic(chat);
|
||||
});
|
||||
|
||||
if (selectedChannelIds?.length >= selectionLimit) {
|
||||
if (selectedIds?.length >= selectionLimit) {
|
||||
showNotification({
|
||||
message: lang('BoostingSelectUpToWarningChannelsPlural', selectionLimit),
|
||||
});
|
||||
@ -176,7 +177,7 @@ const AppendEntityPickerModal: FC<OwnProps & StateProps> = ({
|
||||
setPendingChannelId(privateLinkChannelId);
|
||||
openConfirmModal();
|
||||
} else {
|
||||
setSelectedChannelIds(newSelectedIds);
|
||||
setSelectedIds(newSelectedIds);
|
||||
}
|
||||
});
|
||||
|
||||
@ -218,12 +219,12 @@ const AppendEntityPickerModal: FC<OwnProps & StateProps> = ({
|
||||
<Picker
|
||||
className={styles.picker}
|
||||
itemIds={entityType === 'members' ? displayedMembersIds : displayedChannelIds}
|
||||
selectedIds={entityType === 'members' ? selectedMemberIds : selectedChannelIds}
|
||||
selectedIds={selectedIds}
|
||||
filterValue={searchQuery}
|
||||
filterPlaceholder={lang('Search')}
|
||||
searchInputId="new-members-picker-search"
|
||||
searchInputId={`${entityType}-picker-search`}
|
||||
onSelectedIdsChange={entityType === 'channels'
|
||||
? handleSelectedChannelIdsChange : handleSelectedMembersChange}
|
||||
? handleSelectedChannelIdsChange : handleSelectedMemberIdsChange}
|
||||
onFilterChange={setSearchQuery}
|
||||
isSearchable
|
||||
/>
|
||||
@ -261,7 +262,7 @@ export default memo(withGlobal<OwnProps>((global, { chatId, entityType }): State
|
||||
members = chatFullInfo.members;
|
||||
adminMembersById = chatFullInfo.adminMembersById;
|
||||
}
|
||||
} else if (entityType === 'channels') {
|
||||
} if (entityType === 'channels') {
|
||||
const chat = chatId ? selectChat(global, chatId) : undefined;
|
||||
if (chat) {
|
||||
isChannel = isChatChannel(chat);
|
||||
|
||||
@ -102,6 +102,7 @@ import NewContactModal from './NewContactModal.async';
|
||||
import Notifications from './Notifications.async';
|
||||
import PremiumLimitReachedModal from './premium/common/PremiumLimitReachedModal.async';
|
||||
import GiveawayModal from './premium/GiveawayModal.async';
|
||||
import PremiumGiftingModal from './premium/PremiumGiftingModal.async';
|
||||
import PremiumMainModal from './premium/PremiumMainModal.async';
|
||||
import SafeLinkModal from './SafeLinkModal.async';
|
||||
|
||||
@ -159,6 +160,7 @@ type StateProps = {
|
||||
isReactionPickerOpen: boolean;
|
||||
isAppendModalOpen?: boolean;
|
||||
isGiveawayModalOpen?: boolean;
|
||||
isPremiumGiftingModalOpen?: boolean;
|
||||
isCurrentUserPremium?: boolean;
|
||||
chatlistModal?: TabState['chatlistModal'];
|
||||
boostModal?: TabState['boostModal'];
|
||||
@ -218,6 +220,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
urlAuth,
|
||||
isPremiumModalOpen,
|
||||
isGiveawayModalOpen,
|
||||
isPremiumGiftingModalOpen,
|
||||
isPaymentModalOpen,
|
||||
isReceiptModalOpen,
|
||||
isReactionPickerOpen,
|
||||
@ -603,6 +606,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
<MessageListHistoryHandler />
|
||||
{isPremiumModalOpen && <PremiumMainModal isOpen={isPremiumModalOpen} />}
|
||||
{isGiveawayModalOpen && <GiveawayModal isOpen={isGiveawayModalOpen} />}
|
||||
{isPremiumGiftingModalOpen && <PremiumGiftingModal isOpen={isPremiumGiftingModalOpen} />}
|
||||
<PremiumLimitReachedModal limit={limitReached} />
|
||||
<PaymentModal isOpen={isPaymentModalOpen} onClose={closePaymentModal} />
|
||||
<ReceiptModal isOpen={isReceiptModalOpen} onClose={clearReceipt} />
|
||||
@ -646,6 +650,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
ratingPhoneCall,
|
||||
premiumModal,
|
||||
giveawayModal,
|
||||
giftingModal,
|
||||
isMasterTab,
|
||||
payment,
|
||||
limitReachedModal,
|
||||
@ -712,6 +717,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||
isPremiumModalOpen: premiumModal?.isOpen,
|
||||
isGiveawayModalOpen: giveawayModal?.isOpen,
|
||||
isPremiumGiftingModalOpen: giftingModal?.isOpen,
|
||||
limitReached: limitReachedModal?.limit,
|
||||
isPaymentModalOpen: payment.isPaymentModalOpen,
|
||||
isReceiptModalOpen: Boolean(payment.receipt),
|
||||
|
||||
@ -10,13 +10,16 @@
|
||||
left: 0.5rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin: 0 auto 1.5rem;
|
||||
.avatars {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.headerText {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -36,9 +39,9 @@
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
height: 3rem;
|
||||
background: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
.boostIcon {
|
||||
color: var(--color-primary);
|
||||
vertical-align: middle;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
@ -1,23 +1,28 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useMemo, useState,
|
||||
memo, useEffect, useMemo, useRef,
|
||||
useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiPremiumGiftOption, ApiUser } from '../../../api/types';
|
||||
import type {
|
||||
ApiPremiumGiftCodeOption,
|
||||
} from '../../../api/types';
|
||||
|
||||
import { getUserFirstOrLastName } from '../../../global/helpers';
|
||||
import { BOOST_PER_SENT_GIFT } from '../../../config';
|
||||
import { getUserFullName } from '../../../global/helpers';
|
||||
import {
|
||||
selectTabState,
|
||||
selectUser,
|
||||
selectUserFullInfo,
|
||||
} from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatCurrency } from '../../../util/formatCurrency';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import AvatarList from '../../common/AvatarList';
|
||||
import Icon from '../../common/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import Link from '../../ui/Link';
|
||||
import Modal from '../../ui/Modal';
|
||||
@ -30,55 +35,124 @@ export type OwnProps = {
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
user?: ApiUser;
|
||||
gifts?: ApiPremiumGiftOption[];
|
||||
monthlyCurrency?: string;
|
||||
monthlyAmount?: number;
|
||||
isCompleted?: boolean;
|
||||
gifts?: ApiPremiumGiftCodeOption[] | undefined;
|
||||
forUserIds?: string[];
|
||||
boostPerSentGift?: number;
|
||||
};
|
||||
|
||||
const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
isOpen,
|
||||
user,
|
||||
isCompleted,
|
||||
gifts,
|
||||
boostPerSentGift = BOOST_PER_SENT_GIFT,
|
||||
forUserIds,
|
||||
}) => {
|
||||
const { openPremiumModal, closeGiftPremiumModal, openUrl } = getActions();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const {
|
||||
openPremiumModal, closeGiftPremiumModal, openInvoice, requestConfetti,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const [selectedOption, setSelectedOption] = useState<number | undefined>();
|
||||
const fullMonthlyAmount = useMemo(() => {
|
||||
if (!gifts?.length) {
|
||||
const [selectedMonthOption, setSelectedMonthOption] = useState<number | undefined>();
|
||||
|
||||
const selectedUserQuantity = forUserIds && forUserIds.length * boostPerSentGift;
|
||||
|
||||
useEffect(() => {
|
||||
if (forUserIds?.length) {
|
||||
setSelectedMonthOption(gifts?.[0].months);
|
||||
}
|
||||
}, [gifts, forUserIds]);
|
||||
|
||||
const giftingUserList = useMemo(() => {
|
||||
const usersById = getGlobal().users.byId;
|
||||
return forUserIds?.map((userId) => usersById[userId]).filter(Boolean);
|
||||
}, [forUserIds]);
|
||||
|
||||
const selectedGift = useMemo(() => {
|
||||
return gifts?.find((gift) => gift.months === selectedMonthOption && gift.users === forUserIds?.length);
|
||||
}, [gifts, selectedMonthOption, forUserIds?.length]);
|
||||
|
||||
const filteredGifts = useMemo(() => {
|
||||
return gifts?.filter((gift) => gift.users
|
||||
=== forUserIds?.length);
|
||||
}, [gifts, forUserIds?.length]);
|
||||
|
||||
const fullMonthlyGiftAmount = useMemo(() => {
|
||||
if (!filteredGifts?.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const basicGift = gifts.reduce((acc, gift) => {
|
||||
return gift.months < acc.months ? gift : acc;
|
||||
const basicGift = filteredGifts.reduce((acc, gift) => {
|
||||
return gift.amount < acc.amount ? gift : acc;
|
||||
});
|
||||
|
||||
return Math.floor(basicGift.amount / basicGift.months);
|
||||
}, [gifts]);
|
||||
}, [filteredGifts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && gifts?.length) {
|
||||
setSelectedOption(gifts[0].months);
|
||||
}
|
||||
}, [gifts, isOpen]);
|
||||
|
||||
const selectedGift = useMemo(() => {
|
||||
return gifts?.find((gift) => gift.months === selectedOption);
|
||||
}, [gifts, selectedOption]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
const handleSubmit = useLastCallback(() => {
|
||||
if (!selectedGift) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeGiftPremiumModal();
|
||||
openUrl({ url: selectedGift.botUrl });
|
||||
}, [closeGiftPremiumModal, openUrl, selectedGift]);
|
||||
openInvoice({
|
||||
type: 'giftcode',
|
||||
userIds: forUserIds!,
|
||||
currency: selectedGift!.currency,
|
||||
amount: selectedGift!.amount,
|
||||
option: selectedGift!,
|
||||
});
|
||||
});
|
||||
|
||||
const handlePremiumClick = useCallback(() => {
|
||||
const handlePremiumClick = useLastCallback(() => {
|
||||
openPremiumModal();
|
||||
}, [openPremiumModal]);
|
||||
});
|
||||
|
||||
const showConfetti = useLastCallback(() => {
|
||||
const dialog = dialogRef.current;
|
||||
if (!dialog) return;
|
||||
if (isOpen) {
|
||||
const {
|
||||
top, left, width, height,
|
||||
} = dialog.querySelector('.modal-content')!.getBoundingClientRect();
|
||||
requestConfetti({
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isCompleted) {
|
||||
showConfetti();
|
||||
}
|
||||
}, [isCompleted, showConfetti]);
|
||||
|
||||
const userNameList = useMemo(() => {
|
||||
const usersById = getGlobal().users.byId;
|
||||
return forUserIds?.map((userId) => getUserFullName(usersById[userId])).join(', ');
|
||||
}, [forUserIds]);
|
||||
|
||||
function renderGiftTitle() {
|
||||
if (isCompleted) {
|
||||
return renderText(lang('TelegramPremiumUserGiftedPremiumOutboundDialogTitle',
|
||||
[userNameList, selectedGift?.months]), ['simple_markdown']);
|
||||
}
|
||||
|
||||
return lang('GiftTelegramPremiumTitle');
|
||||
}
|
||||
|
||||
function renderGiftText() {
|
||||
if (isCompleted) {
|
||||
return renderText(lang('TelegramPremiumUserGiftedPremiumOutboundDialogSubtitle', userNameList),
|
||||
['simple_markdown']);
|
||||
}
|
||||
return renderText(lang('GiftPremiumUsersGiveAccessManyZero', userNameList), ['simple_markdown']);
|
||||
}
|
||||
|
||||
function renderPremiumFeaturesLink() {
|
||||
const info = lang('GiftPremiumListFeaturesAndTerms');
|
||||
@ -90,7 +164,7 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<p className={styles.premiumFeatures}>
|
||||
<p className={buildClassName(styles.premiumFeatures, styles.center)}>
|
||||
{parts[1]}
|
||||
<Link isPrimary onClick={handlePremiumClick}>{parts[2]}</Link>
|
||||
{parts[3]}
|
||||
@ -98,8 +172,43 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
function renderBoostsPluralText() {
|
||||
const giftParts = renderText(lang('GiftPremiumWillReceiveBoostsPlural', selectedUserQuantity), ['simple_markdown']);
|
||||
return giftParts.map((part) => {
|
||||
if (typeof part === 'string') {
|
||||
return part.split(/(⚡)/g).map((subpart) => {
|
||||
if (subpart === '⚡') {
|
||||
return <Icon name="boost" className={styles.boostIcon} />;
|
||||
}
|
||||
return subpart;
|
||||
});
|
||||
}
|
||||
return part;
|
||||
});
|
||||
}
|
||||
|
||||
function renderSubscriptionGiftOptions() {
|
||||
return (
|
||||
<div className={styles.subscriptionOptions}>
|
||||
{filteredGifts?.map((gift) => {
|
||||
return (
|
||||
<PremiumSubscriptionOption
|
||||
className={styles.subscriptionOption}
|
||||
key={gift.months}
|
||||
option={gift}
|
||||
fullMonthlyAmount={fullMonthlyGiftAmount}
|
||||
checked={gift.months === selectedMonthOption}
|
||||
onChange={setSelectedMonthOption}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogRef={dialogRef}
|
||||
onClose={closeGiftPremiumModal}
|
||||
isOpen={isOpen}
|
||||
className={styles.modalDialog}
|
||||
@ -116,53 +225,53 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
<Avatar
|
||||
peer={user}
|
||||
size="jumbo"
|
||||
className={styles.avatar}
|
||||
/>
|
||||
<h2 className={styles.headerText}>
|
||||
{lang('GiftTelegramPremiumTitle')}
|
||||
</h2>
|
||||
<p className={styles.description}>
|
||||
{renderText(
|
||||
lang('GiftTelegramPremiumDescription', getUserFirstOrLastName(user)),
|
||||
['emoji', 'simple_markdown'],
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div className={styles.options}>
|
||||
{gifts?.map((gift) => (
|
||||
<PremiumSubscriptionOption
|
||||
key={gift.amount}
|
||||
option={gift}
|
||||
fullMonthlyAmount={fullMonthlyAmount}
|
||||
checked={gift.months === selectedOption}
|
||||
onChange={setSelectedOption}
|
||||
/>
|
||||
))}
|
||||
<div className={styles.avatars}>
|
||||
<AvatarList
|
||||
size="large"
|
||||
peers={giftingUserList}
|
||||
/>
|
||||
</div>
|
||||
<h2 className={buildClassName(styles.headerText, styles.center)}>
|
||||
{renderGiftTitle()}
|
||||
</h2>
|
||||
<p className={buildClassName(styles.description, styles.center)}>
|
||||
{renderGiftText()}
|
||||
</p>
|
||||
{!isCompleted && (
|
||||
<>
|
||||
<p className={styles.description}>
|
||||
{renderText(renderBoostsPluralText(), ['simple_markdown', 'emoji'])}
|
||||
</p>
|
||||
|
||||
<div className={styles.options}>
|
||||
{renderSubscriptionGiftOptions()}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{renderPremiumFeaturesLink()}
|
||||
</div>
|
||||
|
||||
<Button className={styles.button} isShiny disabled={!selectedOption} onClick={handleSubmit}>
|
||||
{lang(
|
||||
'GiftSubscriptionFor',
|
||||
selectedGift && formatCurrency(Number(selectedGift.amount), selectedGift.currency, lang.code),
|
||||
)}
|
||||
</Button>
|
||||
{!isCompleted && (
|
||||
<Button withPremiumGradient className={styles.button} isShiny disabled={!selectedGift} onClick={handleSubmit}>
|
||||
{lang(
|
||||
'GiftSubscriptionFor', selectedGift
|
||||
&& formatCurrency(selectedGift!.amount, selectedGift.currency, lang.code),
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
const { forUserId } = selectTabState(global).giftPremiumModal || {};
|
||||
const user = forUserId ? selectUser(global, forUserId) : undefined;
|
||||
const gifts = user ? selectUserFullInfo(global, user.id)?.premiumGifts : undefined;
|
||||
const {
|
||||
gifts, forUserIds, isCompleted,
|
||||
} = selectTabState(global).giftPremiumModal || {};
|
||||
|
||||
return {
|
||||
user,
|
||||
isCompleted,
|
||||
gifts,
|
||||
boostPerSentGift: global.appConfig?.boostsPerSentGift,
|
||||
forUserIds,
|
||||
};
|
||||
})(GiftPremiumModal));
|
||||
|
||||
@ -396,7 +396,6 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
|
||||
isGiveaway
|
||||
key={gift.months}
|
||||
option={gift}
|
||||
userCount={gift.users}
|
||||
fullMonthlyAmount={fullMonthlyAmount!}
|
||||
checked={gift.months === selectedMonthOption}
|
||||
onChange={setSelectedMonthOption}
|
||||
@ -707,6 +706,7 @@ const GiveawayModal: FC<OwnProps & StateProps> = ({
|
||||
selectionLimit={countrySelectionLimit}
|
||||
/>
|
||||
<AppendEntityPickerModal
|
||||
key={entityType}
|
||||
isOpen={isEntityPickerModalOpen}
|
||||
onClose={closeEntityPickerModal}
|
||||
entityType={entityType}
|
||||
|
||||
18
src/components/main/premium/PremiumGiftingModal.async.tsx
Normal file
18
src/components/main/premium/PremiumGiftingModal.async.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './PremiumGiftingModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const PremiumGiftingModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const PremiumGiftingModal = useModuleLoader(Bundles.Extra, 'PremiumGiftingModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return PremiumGiftingModal ? <PremiumGiftingModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default PremiumGiftingModalAsync;
|
||||
80
src/components/main/premium/PremiumGiftingModal.module.scss
Normal file
80
src/components/main/premium/PremiumGiftingModal.module.scss
Normal file
@ -0,0 +1,80 @@
|
||||
.root :global(.modal-content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.root :global(.modal-dialog) {
|
||||
max-width: 55vh;
|
||||
}
|
||||
|
||||
.root :global(.modal-dialog), .root :global(.modal-content) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.filter {
|
||||
padding: 0.375rem 1rem 0.25rem 0.75rem;
|
||||
margin-bottom: 0.625rem;
|
||||
background-color: var(--color-background);
|
||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
||||
border-bottom: 0.625rem solid var(--color-background-secondary);
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
max-height: 20rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
width: 100%;
|
||||
background: var(--color-background);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.picker {
|
||||
height: 75vh;
|
||||
}
|
||||
|
||||
.avatars {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description,
|
||||
.premiumFeatures {
|
||||
text-align: center;
|
||||
margin: 0 auto 2rem;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
.premiumFeatures {
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.options {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
height: 3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
139
src/components/main/premium/PremiumGiftingModal.tsx
Normal file
139
src/components/main/premium/PremiumGiftingModal.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import { GIVEAWAY_MAX_ADDITIONAL_CHANNELS } from '../../../config';
|
||||
import {
|
||||
filterUsersByName, isUserBot,
|
||||
} from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../../common/Icon';
|
||||
import Picker from '../../common/Picker';
|
||||
import Button from '../../ui/Button';
|
||||
import Modal from '../../ui/Modal';
|
||||
|
||||
import styles from './PremiumGiftingModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen?: boolean;
|
||||
};
|
||||
|
||||
interface StateProps {
|
||||
currentUserId?: string;
|
||||
userSelectionLimit?: number;
|
||||
userIds?: string[];
|
||||
}
|
||||
|
||||
const PremiumGiftingModal: FC<OwnProps & StateProps> = ({
|
||||
isOpen,
|
||||
currentUserId,
|
||||
userSelectionLimit = GIVEAWAY_MAX_ADDITIONAL_CHANNELS,
|
||||
userIds,
|
||||
}) => {
|
||||
const { closePremiumGiftingModal, openGiftPremiumModal, showNotification } = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const [selectedUserIds, setSelectedUserIds] = useState<string[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
|
||||
const displayedUserIds = useMemo(() => {
|
||||
const usersById = getGlobal().users.byId;
|
||||
const filteredContactIds = userIds ? filterUsersByName(userIds, usersById, searchQuery) : [];
|
||||
|
||||
return sortChatIds(unique(filteredContactIds).filter((userId) => {
|
||||
const user = usersById[userId];
|
||||
if (!user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !isUserBot(user) && userId !== currentUserId;
|
||||
}));
|
||||
}, [currentUserId, searchQuery, userIds]);
|
||||
|
||||
const handleSendIdList = useLastCallback(() => {
|
||||
if (selectedUserIds?.length) {
|
||||
openGiftPremiumModal({ forUserIds: selectedUserIds });
|
||||
|
||||
closePremiumGiftingModal();
|
||||
}
|
||||
});
|
||||
|
||||
const handleSelectedUserIdsChange = useLastCallback((newSelectedIds: string[]) => {
|
||||
if (newSelectedIds.length > userSelectionLimit) {
|
||||
showNotification({
|
||||
message: lang('BoostingSelectUpToWarningUsers', userSelectionLimit),
|
||||
});
|
||||
return;
|
||||
}
|
||||
setSelectedUserIds(newSelectedIds);
|
||||
});
|
||||
|
||||
function renderSearchField() {
|
||||
return (
|
||||
<div className={styles.filter} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button
|
||||
round
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => closePremiumGiftingModal()}
|
||||
ariaLabel={lang('Close')}
|
||||
>
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
<h3 className={styles.title}>{lang('GiftTelegramPremiumTitle')}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={styles.root}
|
||||
isOpen={isOpen}
|
||||
onClose={closePremiumGiftingModal}
|
||||
onEnter={handleSendIdList}
|
||||
>
|
||||
<div className={styles.main}>
|
||||
{renderSearchField()}
|
||||
<div className={buildClassName(styles.main, 'custom-scroll')}>
|
||||
<Picker
|
||||
className={styles.picker}
|
||||
itemIds={displayedUserIds}
|
||||
selectedIds={selectedUserIds}
|
||||
filterValue={searchQuery}
|
||||
filterPlaceholder={lang('Search')}
|
||||
searchInputId="users-picker-search"
|
||||
onSelectedIdsChange={handleSelectedUserIdsChange}
|
||||
onFilterChange={setSearchQuery}
|
||||
isSearchable
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.buttons}>
|
||||
<Button withPremiumGradient size="smaller" onClick={handleSendIdList} disabled={!selectedUserIds?.length}>
|
||||
{lang('Continue')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
const { currentUserId } = global;
|
||||
|
||||
return {
|
||||
currentUserId,
|
||||
userIds: global.contactList?.userIds,
|
||||
userSelectionLimit: global.appConfig?.giveawayAddPeersMax,
|
||||
};
|
||||
})(PremiumGiftingModal));
|
||||
@ -174,5 +174,6 @@
|
||||
}
|
||||
|
||||
.subscriptionOption {
|
||||
margin: 0.8125rem;
|
||||
margin: 0.8125rem;
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,6 @@ import styles from './PremiumSubscriptionOption.module.scss';
|
||||
type OwnProps = {
|
||||
option: ApiPremiumGiftOption | ApiPremiumGiftCodeOption;
|
||||
isGiveaway?: boolean;
|
||||
userCount?: number;
|
||||
checked?: boolean;
|
||||
fullMonthlyAmount?: number;
|
||||
className?: string;
|
||||
@ -23,14 +22,16 @@ type OwnProps = {
|
||||
|
||||
const PremiumSubscriptionOption: FC<OwnProps> = ({
|
||||
option, checked, fullMonthlyAmount,
|
||||
onChange, className, isGiveaway, userCount,
|
||||
onChange, className, isGiveaway,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
const {
|
||||
months, amount, currency,
|
||||
} = option;
|
||||
const users = 'users' in option ? option.users : undefined;
|
||||
const perMonth = Math.floor(amount / months);
|
||||
const isUserCountPlural = users ? users > 1 : undefined;
|
||||
|
||||
const discount = useMemo(() => {
|
||||
return fullMonthlyAmount && fullMonthlyAmount > perMonth
|
||||
@ -63,9 +64,9 @@ const PremiumSubscriptionOption: FC<OwnProps> = ({
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.month}>
|
||||
{Boolean(discount) && isGiveaway && (
|
||||
{Boolean(discount) && (
|
||||
<span
|
||||
className={buildClassName(styles.giveawayDiscount, isGiveaway && styles.discount)}
|
||||
className={buildClassName(styles.giveawayDiscount, styles.discount)}
|
||||
title={lang('GiftDiscount')}
|
||||
> −{discount}%
|
||||
</span>
|
||||
@ -73,11 +74,11 @@ const PremiumSubscriptionOption: FC<OwnProps> = ({
|
||||
{lang('Months', months)}
|
||||
</div>
|
||||
<div className={styles.perMonth}>
|
||||
{isGiveaway ? `${formatCurrency(amount, currency, lang.code)} x ${userCount!}`
|
||||
{(isGiveaway || isUserCountPlural) ? `${formatCurrency(amount, currency, lang.code)} x ${users!}`
|
||||
: lang('PricePerMonth', formatCurrency(perMonth, currency, lang.code))}
|
||||
</div>
|
||||
<div className={styles.amount}>
|
||||
{formatCurrency(isGiveaway ? amount * userCount! : amount, currency, lang.code)}
|
||||
{formatCurrency(amount, currency, lang.code)}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@ -313,7 +313,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const handleGiftPremiumClick = useLastCallback(() => {
|
||||
openGiftPremiumModal({ forUserId: chatId });
|
||||
openGiftPremiumModal({ forUserIds: [chatId] });
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
|
||||
@ -656,7 +656,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
providerName = url.startsWith(DONATE_PROVIDER_URL) ? DONATE_PROVIDER : undefined;
|
||||
}
|
||||
|
||||
const chat = inputInvoice && 'chatId' in inputInvoice ? selectChat(global, inputInvoice.chatId) : undefined;
|
||||
const chat = inputInvoice && 'chatId' in inputInvoice ? selectChat(global, inputInvoice.chatId!) : undefined;
|
||||
const isProviderError = Boolean(invoice && (!providerName || !SUPPORTED_PROVIDERS.has(providerName)));
|
||||
const { needCardholderName, needCountry, needZip } = (nativeParams || {});
|
||||
const {
|
||||
|
||||
@ -318,6 +318,7 @@ export const GIVEAWAY_BOOST_PER_PREMIUM = 4;
|
||||
export const GIVEAWAY_MAX_ADDITIONAL_CHANNELS = 10;
|
||||
export const GIVEAWAY_MAX_ADDITIONAL_USERS = 10;
|
||||
export const GIVEAWAY_MAX_ADDITIONAL_COUNTRIES = 10;
|
||||
export const BOOST_PER_SENT_GIFT = 3;
|
||||
|
||||
export const LIGHT_THEME_BG_COLOR = '#99BA92';
|
||||
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
||||
|
||||
@ -422,18 +422,46 @@ addActionHandler('closeGiveawayModal', (global, actions, payload): ActionReturnT
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('openPremiumGiftingModal', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
tabId = getCurrentTabId(),
|
||||
} = payload || {};
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftingModal: {
|
||||
isOpen: true,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('closePremiumGiftingModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
giftingModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftPremiumModal', async (global, actions, payload): Promise<void> => {
|
||||
const { forUserId, tabId = getCurrentTabId() } = payload || {};
|
||||
const {
|
||||
forUserIds, tabId = getCurrentTabId(),
|
||||
} = payload || {};
|
||||
const result = await callApi('fetchPremiumPromo');
|
||||
if (!result) return;
|
||||
|
||||
global = getGlobal();
|
||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||
|
||||
const gifts = await callApi('getPremiumGiftCodeOptions', {});
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftPremiumModal: {
|
||||
isOpen: true,
|
||||
forUserId,
|
||||
forUserIds,
|
||||
gifts,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import type { ActionReturnType } from '../../types';
|
||||
|
||||
import { areDeepEqual } from '../../../util/areDeepEqual';
|
||||
import { formatCurrency } from '../../../util/formatCurrency';
|
||||
import * as langProvider from '../../../util/langProvider';
|
||||
import { IS_PRODUCTION_HOST } from '../../../util/windowEnvironment';
|
||||
import { addActionHandler } from '../../index';
|
||||
import { addActionHandler, setGlobal } from '../../index';
|
||||
import { closeInvoice } from '../../reducers';
|
||||
import { updateTabState } from '../../reducers/tabs';
|
||||
import { selectChatMessage, selectTabState } from '../../selectors';
|
||||
@ -30,6 +31,24 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
}
|
||||
}
|
||||
|
||||
if (inputInvoice && inputInvoice.type === 'giftcode') {
|
||||
if (!inputInvoice.userIds) {
|
||||
return;
|
||||
}
|
||||
const giftModalState = selectTabState(global, tabId).giftPremiumModal;
|
||||
|
||||
if (giftModalState && giftModalState.isOpen
|
||||
&& areDeepEqual(inputInvoice.userIds, giftModalState.forUserIds)) {
|
||||
global = updateTabState(global, {
|
||||
giftPremiumModal: {
|
||||
...giftModalState,
|
||||
isCompleted: true,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
}
|
||||
}
|
||||
|
||||
// On the production host, the payment frame receives a message with the payment event,
|
||||
// after which the payment form closes. In other cases, the payment form must be closed manually.
|
||||
// Closing the invoice will cause the closing of the Payment Modal dialog and then closing the payment.
|
||||
|
||||
@ -641,9 +641,15 @@ export type TabState = {
|
||||
prepaidGiveaway?: ApiPrepaidGiveaway;
|
||||
};
|
||||
|
||||
giftPremiumModal?: {
|
||||
giftingModal?: {
|
||||
isOpen?: boolean;
|
||||
forUserId?: string;
|
||||
};
|
||||
|
||||
giftPremiumModal?: {
|
||||
isCompleted?: boolean;
|
||||
isOpen?: boolean;
|
||||
forUserIds?: string[];
|
||||
gifts?: ApiPremiumGiftCodeOption[];
|
||||
};
|
||||
|
||||
limitReachedModal?: {
|
||||
@ -2966,6 +2972,9 @@ export interface ActionPayloads {
|
||||
} & WithTabId);
|
||||
closeGiveawayModal: WithTabId | undefined;
|
||||
|
||||
openPremiumGiftingModal: WithTabId | undefined;
|
||||
closePremiumGiftingModal: WithTabId | undefined;
|
||||
|
||||
transcribeAudio: {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
@ -2976,7 +2985,9 @@ export interface ActionPayloads {
|
||||
loadPremiumStickers: undefined;
|
||||
|
||||
openGiftPremiumModal: ({
|
||||
forUserId?: string;
|
||||
chatId?: string;
|
||||
forMultipleUsers?: boolean;
|
||||
forUserIds?: string[];
|
||||
} & WithTabId) | undefined;
|
||||
|
||||
closeGiftPremiumModal: WithTabId | undefined;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user