Gift Modal: Support My gifts category (#6316)

This commit is contained in:
Alexander Zinchuk 2025-10-11 19:07:20 +02:00
parent 104fbb1d54
commit aacd4a8429
24 changed files with 419 additions and 175 deletions

View File

@ -1452,6 +1452,7 @@
"StarsGiftHeader" = "Send a Gift"; "StarsGiftHeader" = "Send a Gift";
"StarsGiftHeaderSelf" = "Buy a Gift"; "StarsGiftHeaderSelf" = "Buy a Gift";
"StarGiftDescription" = "Give {user} gifts that can be kept on the profile or converted to Stars."; "StarGiftDescription" = "Give {user} gifts that can be kept on the profile or converted to Stars.";
"StarGiftDescriptionCollectibles" = "Collectible gifts are unique digital items you exchange or sell.";
"StarGiftDescriptionSelf" = "Buy yourself a gift to add to your profile or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others."; "StarGiftDescriptionSelf" = "Buy yourself a gift to add to your profile or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others.";
"StarGiftDescriptionChannel" = "Select gift to show appreciation to **{peer}**."; "StarGiftDescriptionChannel" = "Select gift to show appreciation to **{peer}**.";
"GiftLimited" = "limited"; "GiftLimited" = "limited";
@ -1583,8 +1584,6 @@
"StarsAmountText_one" = "{amount} Star"; "StarsAmountText_one" = "{amount} Star";
"StarsAmountText_other" = "{amount} Stars"; "StarsAmountText_other" = "{amount} Stars";
"AllGiftsCategory" = "All gifts"; "AllGiftsCategory" = "All gifts";
"LimitedGiftsCategory" = "Limited";
"StockGiftsCategory" = "In Stock";
"PremiumGiftDescription" = "Premium"; "PremiumGiftDescription" = "Premium";
"SendPaidReaction" = "Send ⭐️{amount}"; "SendPaidReaction" = "Send ⭐️{amount}";
"StarsPay" = "Confirm and Pay {amount}"; "StarsPay" = "Confirm and Pay {amount}";
@ -2070,7 +2069,8 @@
"ComposerTitleForwardFrom" = "From: **{users}**"; "ComposerTitleForwardFrom" = "From: **{users}**";
"ContextMenuItemMention" = "Mention"; "ContextMenuItemMention" = "Mention";
"GiftRibbonResale" = "resale"; "GiftRibbonResale" = "resale";
"GiftCategoryResale" = "Resale"; "GiftCategoryCollectibles" = "Collectibles";
"GiftCategoryMyGifts" = "My Gifts";
"HeaderDescriptionResaleGifts_one" = "{count} for resale"; "HeaderDescriptionResaleGifts_one" = "{count} for resale";
"HeaderDescriptionResaleGifts_other" = "{count} for resale"; "HeaderDescriptionResaleGifts_other" = "{count} for resale";
"GiftSortByPrice" = "Sort by Price"; "GiftSortByPrice" = "Sort by Price";
@ -2285,6 +2285,7 @@
"GiftValueForSaleOnFragment" = "for sale on Fragment"; "GiftValueForSaleOnFragment" = "for sale on Fragment";
"GiftValueForSaleOnTelegram" = "for sale on Telegram"; "GiftValueForSaleOnTelegram" = "for sale on Telegram";
"EmbeddedMessageNoCaption" = "Caption removed"; "EmbeddedMessageNoCaption" = "Caption removed";
"ConfirmBuyGiftForTonDescription" = "The seller only accepts TON as payment.";
"TitleGiftLocked" = "Gift Locked"; "TitleGiftLocked" = "Gift Locked";
"GiftLockedMessage" = "This gift is currently only available to earlier Telegram users. It will unlock for your account in about **{relativeDate}**."; "GiftLockedMessage" = "This gift is currently only available to earlier Telegram users. It will unlock for your account in about **{relativeDate}**.";
"QuickPreview" = "Quick Preview"; "QuickPreview" = "Quick Preview";

View File

@ -15,5 +15,6 @@ export { default as GiftUpgradeModal } from '../components/modals/gift/upgrade/G
export { default as GiftStatusInfoModal } from '../components/modals/gift/status/GiftStatusInfoModal'; export { default as GiftStatusInfoModal } from '../components/modals/gift/status/GiftStatusInfoModal';
export { default as GiftWithdrawModal } from '../components/modals/gift/withdraw/GiftWithdrawModal'; export { default as GiftWithdrawModal } from '../components/modals/gift/withdraw/GiftWithdrawModal';
export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal'; export { default as GiftTransferModal } from '../components/modals/gift/transfer/GiftTransferModal';
export { default as GiftTransferConfirmModal } from '../components/modals/gift/transfer/GiftTransferConfirmModal';
export { default as ChatRefundModal } from '../components/modals/stars/chatRefund/ChatRefundModal'; export { default as ChatRefundModal } from '../components/modals/stars/chatRefund/ChatRefundModal';
export { default as PriceConfirmModal } from '../components/modals/priceConfirm/PriceConfirmModal'; export { default as PriceConfirmModal } from '../components/modals/priceConfirm/PriceConfirmModal';

View File

@ -217,6 +217,7 @@ const Main = ({
loadPremiumGifts, loadPremiumGifts,
loadTonGifts, loadTonGifts,
loadStarGifts, loadStarGifts,
loadMyCollectibleGifts,
loadDefaultTopicIcons, loadDefaultTopicIcons,
loadAddedStickers, loadAddedStickers,
loadFavoriteStickers, loadFavoriteStickers,
@ -332,6 +333,7 @@ const Main = ({
loadPremiumGifts(); loadPremiumGifts();
loadTonGifts(); loadTonGifts();
loadStarGifts(); loadStarGifts();
loadMyCollectibleGifts();
loadAvailableEffects(); loadAvailableEffects();
loadBirthdayNumbersStickers(); loadBirthdayNumbersStickers();
loadRestrictedEmojiStickers(); loadRestrictedEmojiStickers();

View File

@ -25,6 +25,7 @@ import GiftLockedModal from './gift/locked/GiftLockedModal.async';
import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async'; import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async';
import GiftResalePriceComposerModal from './gift/resale/GiftResalePriceComposerModal.async'; import GiftResalePriceComposerModal from './gift/resale/GiftResalePriceComposerModal.async';
import GiftStatusInfoModal from './gift/status/GiftStatusInfoModal.async'; import GiftStatusInfoModal from './gift/status/GiftStatusInfoModal.async';
import GiftTransferConfirmModal from './gift/transfer/GiftTransferConfirmModal.async';
import GiftTransferModal from './gift/transfer/GiftTransferModal.async'; import GiftTransferModal from './gift/transfer/GiftTransferModal.async';
import GiftUpgradeModal from './gift/upgrade/GiftUpgradeModal.async'; import GiftUpgradeModal from './gift/upgrade/GiftUpgradeModal.async';
import GiftInfoValueModal from './gift/value/GiftInfoValueModal.async'; import GiftInfoValueModal from './gift/value/GiftInfoValueModal.async';
@ -95,6 +96,7 @@ type ModalKey = keyof Pick<TabState,
'sharePreparedMessageModal' | 'sharePreparedMessageModal' |
'giftStatusInfoModal' | 'giftStatusInfoModal' |
'giftTransferModal' | 'giftTransferModal' |
'giftTransferConfirmModal' |
'chatRefundModal' | 'chatRefundModal' |
'priceConfirmModal' | 'priceConfirmModal' |
'isFrozenAccountModalOpen' | 'isFrozenAccountModalOpen' |
@ -156,6 +158,7 @@ const MODALS: ModalRegistry = {
preparedMessageModal: PreparedMessageModal, preparedMessageModal: PreparedMessageModal,
sharePreparedMessageModal: SharePreparedMessageModal, sharePreparedMessageModal: SharePreparedMessageModal,
giftTransferModal: GiftTransferModal, giftTransferModal: GiftTransferModal,
giftTransferConfirmModal: GiftTransferConfirmModal,
chatRefundModal: ChatRefundModal, chatRefundModal: ChatRefundModal,
priceConfirmModal: PriceConfirmModal, priceConfirmModal: PriceConfirmModal,
isFrozenAccountModalOpen: FrozenAccountModal, isFrozenAccountModalOpen: FrozenAccountModal,

View File

@ -11,6 +11,7 @@
justify-content: center; justify-content: center;
min-width: 0; min-width: 0;
margin-inline: 0.125rem;
padding: 0.625rem; padding: 0.625rem;
padding-top: 0; padding-top: 0;
border-radius: 0.625rem; border-radius: 0.625rem;
@ -69,6 +70,10 @@
line-height: 1; line-height: 1;
} }
.transferBadge {
font-size: 0.8125rem !important;
}
.star { .star {
margin-inline-start: 0 !important; margin-inline-start: 0 !important;
margin-inline-end: 0.125rem; margin-inline-end: 0.125rem;

View File

@ -29,6 +29,7 @@ export type OwnProps = {
observeIntersection?: ObserveFn; observeIntersection?: ObserveFn;
onClick: (gift: ApiStarGift, target: 'original' | 'resell') => void; onClick: (gift: ApiStarGift, target: 'original' | 'resell') => void;
isResale?: boolean; isResale?: boolean;
withTransferBadge?: boolean;
}; };
type StateProps = { type StateProps = {
@ -38,7 +39,7 @@ type StateProps = {
const GIFT_STICKER_SIZE = 90; const GIFT_STICKER_SIZE = 90;
function GiftItemStar({ function GiftItemStar({
gift, observeIntersection, onClick, isResale, isCurrentUserPremium, gift, observeIntersection, onClick, isResale, isCurrentUserPremium, withTransferBadge,
}: OwnProps & StateProps) { }: OwnProps & StateProps) {
const { openGiftInfoModal, openPremiumModal, showNotification, checkCanSendGift } = getActions(); const { openGiftInfoModal, openPremiumModal, showNotification, checkCanSendGift } = getActions();
@ -72,7 +73,7 @@ function GiftItemStar({
: getPriceAmount(uniqueGift?.resellPrice); : getPriceAmount(uniqueGift?.resellPrice);
const priceCurrency = priceInfo?.currency || STARS_CURRENCY_CODE; const priceCurrency = priceInfo?.currency || STARS_CURRENCY_CODE;
const resellMinStars = regularGift?.resellMinStars; const resellMinStars = regularGift?.resellMinStars;
const priceInStarsAsString = !isGiftUnique && isResale && resellMinStars const formattedPrice = !isGiftUnique && isResale && resellMinStars
? lang.number(resellMinStars) + '+' : priceInfo?.amount || 0; ? lang.number(resellMinStars) + '+' : priceInfo?.amount || 0;
const isLimited = !isGiftUnique && Boolean(regularGift?.isLimited); const isLimited = !isGiftUnique && Boolean(regularGift?.isLimited);
const isSoldOut = !isGiftUnique && Boolean(regularGift?.isSoldOut); const isSoldOut = !isGiftUnique && Boolean(regularGift?.isSoldOut);
@ -175,6 +176,24 @@ function GiftItemStar({
setIsVisible(visible); setIsVisible(visible);
}); });
const badgeContent = useMemo(() => {
if (withTransferBadge) {
return lang('GiftTransferTitle');
}
if (priceCurrency === TON_CURRENCY_CODE) {
return formatTonAsIcon(lang, formattedPrice || 0, {
shouldConvertFromNanos: true,
className: styles.star,
});
}
return formatStarsAsIcon(lang, formattedPrice || 0, {
asFont: true,
className: styles.star,
});
}, [withTransferBadge, priceCurrency, formattedPrice, lang]);
return ( return (
<div <div
ref={ref} ref={ref}
@ -212,17 +231,18 @@ function GiftItemStar({
</div> </div>
<Button <Button
className={styles.buy} className={buildClassName(
styles.buy,
withTransferBadge && styles.transferBadge,
)}
nonInteractive nonInteractive
size="tiny" size="tiny"
color={isGiftUnique ? 'bluredStarsBadge' : 'stars'} color={isGiftUnique ? 'bluredStarsBadge' : 'stars'}
withSparkleEffect={isVisible} withSparkleEffect={isVisible && !withTransferBadge}
pill pill
fluid fluid
> >
{priceCurrency === TON_CURRENCY_CODE {badgeContent}
? formatTonAsIcon(lang, priceInStarsAsString || 0, { shouldConvertFromNanos: true, className: styles.star })
: formatStarsAsIcon(lang, priceInStarsAsString || 0, { asFont: true, className: styles.star })}
</Button> </Button>
{giftRibbon} {giftRibbon}
{isLocked && <Icon name="lock-badge" className={styles.lockIcon} />} {isLocked && <Icon name="lock-badge" className={styles.lockIcon} />}

View File

@ -81,7 +81,7 @@
.starGiftsContainer, .starGiftsContainer,
.premiumGiftsGallery { .premiumGiftsGallery {
display: flex; display: flex;
gap: 0.625rem; gap: 0.5rem;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@ -9,6 +9,7 @@ import type {
ApiDisallowedGifts, ApiDisallowedGifts,
ApiPeer, ApiPeer,
ApiPremiumGiftCodeOption, ApiPremiumGiftCodeOption,
ApiSavedStarGift,
ApiStarGift, ApiStarGift,
ApiStarGiftRegular, ApiStarGiftRegular,
ApiStarsAmount, ApiStarsAmount,
@ -34,6 +35,7 @@ import Avatar from '../../common/Avatar';
import InteractiveSparkles from '../../common/InteractiveSparkles'; import InteractiveSparkles from '../../common/InteractiveSparkles';
import SafeLink from '../../common/SafeLink'; import SafeLink from '../../common/SafeLink';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import InfiniteScroll from '../../ui/InfiniteScroll';
import Modal from '../../ui/Modal'; import Modal from '../../ui/Modal';
import Transition from '../../ui/Transition'; import Transition from '../../ui/Transition';
import BalanceBlock from '../stars/BalanceBlock'; import BalanceBlock from '../stars/BalanceBlock';
@ -56,6 +58,8 @@ type StateProps = {
boostPerSentGift?: number; boostPerSentGift?: number;
starGiftsById?: Record<string, ApiStarGiftRegular>; starGiftsById?: Record<string, ApiStarGiftRegular>;
starGiftIdsByCategory?: Record<StarGiftCategory, string[]>; starGiftIdsByCategory?: Record<StarGiftCategory, string[]>;
myCollectibleGiftsById?: Record<string, ApiSavedStarGift>;
myCollectibleGiftIds?: string[];
starBalance?: ApiStarsAmount; starBalance?: ApiStarsAmount;
peer?: ApiPeer; peer?: ApiPeer;
isSelf?: boolean; isSelf?: boolean;
@ -77,6 +81,8 @@ const GiftModal: FC<OwnProps & StateProps> = ({
modal, modal,
starGiftsById, starGiftsById,
starGiftIdsByCategory, starGiftIdsByCategory,
myCollectibleGiftsById,
myCollectibleGiftIds,
starBalance, starBalance,
peer, peer,
isSelf, isSelf,
@ -87,7 +93,14 @@ const GiftModal: FC<OwnProps & StateProps> = ({
tabId, tabId,
}) => { }) => {
const { const {
closeGiftModal, openGiftInfoModal, resetResaleGifts, loadResaleGifts, openGiftInMarket, closeResaleGiftsMarket, closeGiftModal,
openGiftInfoModal,
resetResaleGifts,
loadResaleGifts,
openGiftInMarket,
closeResaleGiftsMarket,
loadMyCollectibleGifts,
openGiftTransferConfirmModal,
} = getActions(); } = getActions();
const dialogRef = useRef<HTMLDivElement>(); const dialogRef = useRef<HTMLDivElement>();
const transitionRef = useRef<HTMLDivElement>(); const transitionRef = useRef<HTMLDivElement>();
@ -122,6 +135,7 @@ const GiftModal: FC<OwnProps & StateProps> = ({
const areUnlimitedStarGiftsDisallowed = !isSelf && disallowedGifts?.shouldDisallowUnlimitedStarGifts; const areUnlimitedStarGiftsDisallowed = !isSelf && disallowedGifts?.shouldDisallowUnlimitedStarGifts;
const areLimitedStarGiftsDisallowed = !isSelf && disallowedGifts?.shouldDisallowLimitedStarGifts; const areLimitedStarGiftsDisallowed = !isSelf && disallowedGifts?.shouldDisallowLimitedStarGifts;
const areUniqueStarGiftsDisallowed = !isSelf && disallowedGifts?.shouldDisallowUniqueStarGifts;
const oldLang = useOldLang(); const oldLang = useOldLang();
const lang = useLang(); const lang = useLang();
@ -206,19 +220,29 @@ const GiftModal: FC<OwnProps & StateProps> = ({
), ),
}, { withNodes: true }); }, { withNodes: true });
const starGiftDescription = chat const starGiftDescription = useMemo(() => {
? lang('StarGiftDescriptionChannel', { peer: getPeerTitle(lang, chat) }, { if (chat) {
withNodes: true, return lang('StarGiftDescriptionChannel', { peer: getPeerTitle(lang, chat) }, {
withMarkdown: true, withNodes: true,
}) withMarkdown: true,
: isSelf });
? lang('StarGiftDescriptionSelf', undefined, { }
if (isSelf) {
return lang('StarGiftDescriptionSelf', undefined, {
withNodes: true, withNodes: true,
renderTextFilters: ['br'], renderTextFilters: ['br'],
}) });
: lang('StarGiftDescription', { }
user: getUserFullName(user)!,
}, { withNodes: true, withMarkdown: true }); if (selectedCategory === 'resale') {
return lang('StarGiftDescriptionCollectibles');
}
return lang('StarGiftDescription', {
user: getUserFullName(user)!,
}, { withNodes: true, withMarkdown: true });
}, [chat, isSelf, selectedCategory, user, lang]);
function renderGiftPremiumHeader() { function renderGiftPremiumHeader() {
return ( return (
@ -268,22 +292,62 @@ const GiftModal: FC<OwnProps & StateProps> = ({
setIsGiftScreenHeaderForStarGifts('id' in gift); setIsGiftScreenHeaderForStarGifts('id' in gift);
}); });
const handleMyGiftClick = useLastCallback((gift: ApiStarGift) => {
if (gift.type === 'starGift' || !myCollectibleGiftsById || !peer?.id) return;
const savedGift = myCollectibleGiftsById[gift.id];
openGiftTransferConfirmModal({
gift: savedGift,
recipientId: peer.id,
});
});
const handleLoadMore = useLastCallback(() => {
if (selectedCategory === 'myCollectibles') {
loadMyCollectibleGifts();
}
});
function renderStarGifts() { function renderStarGifts() {
if (selectedCategory === 'myCollectibles') {
return (
<InfiniteScroll
className={styles.starGiftsContainer}
items={myCollectibleGiftIds}
onLoadMore={handleLoadMore}
scrollContainerClosest={`.${styles.main}`}
itemSelector=".starGiftItem"
>
{myCollectibleGiftsById && myCollectibleGiftIds?.map((giftId) => {
const savedGift = myCollectibleGiftsById[giftId];
if (!savedGift) return undefined;
return (
<GiftItemStar
key={giftId}
gift={savedGift.gift}
observeIntersection={observeIntersection}
onClick={handleMyGiftClick}
withTransferBadge
/>
);
})}
</InfiniteScroll>
);
}
const filteredGiftIds = starGiftIdsByCategory?.[selectedCategory]?.filter((giftId) => { const filteredGiftIds = starGiftIdsByCategory?.[selectedCategory]?.filter((giftId) => {
const gift = starGiftsById?.[giftId]; const gift = starGiftsById?.[giftId];
if (!gift) return false; if (!gift) return false;
const { isLimited, isSoldOut, upgradeStars } = gift; const { isLimited, availabilityResale } = gift;
if (areUnlimitedStarGiftsDisallowed && !areLimitedStarGiftsDisallowed) {
return isLimited; if (areLimitedStarGiftsDisallowed && isLimited) {
} return !areUniqueStarGiftsDisallowed ? availabilityResale : false;
if (areLimitedStarGiftsDisallowed && !areUnlimitedStarGiftsDisallowed) {
return !isLimited && !isSoldOut;
}
if (areUnlimitedStarGiftsDisallowed && areLimitedStarGiftsDisallowed) {
return Boolean(isLimited && Boolean(upgradeStars));
} }
if (areUnlimitedStarGiftsDisallowed && !isLimited) return false;
return true; return true;
}); });
@ -291,8 +355,9 @@ const GiftModal: FC<OwnProps & StateProps> = ({
<div className={styles.starGiftsContainer}> <div className={styles.starGiftsContainer}>
{starGiftsById && filteredGiftIds?.flatMap((giftId) => { {starGiftsById && filteredGiftIds?.flatMap((giftId) => {
const gift = starGiftsById[giftId]; const gift = starGiftsById[giftId];
const shouldShowResale = selectedCategory !== 'stock' && Boolean(gift.availabilityResale); const shouldShowResale = Boolean(gift.availabilityResale) && !areUniqueStarGiftsDisallowed;
const shouldDuplicateAsResale = selectedCategory !== 'resale' && shouldShowResale && !gift.isSoldOut; const shouldDuplicateAsResale = selectedCategory !== 'resale'
&& shouldShowResale && !gift.isSoldOut && !areLimitedStarGiftsDisallowed;
const elements = [ const elements = [
<GiftItemStar <GiftItemStar
@ -399,7 +464,9 @@ const GiftModal: FC<OwnProps & StateProps> = ({
{renderStarGiftsHeader()} {renderStarGiftsHeader()}
{renderStarGiftsDescription()} {renderStarGiftsDescription()}
<StarGiftCategoryList <StarGiftCategoryList
areLimitedStarGiftsDisallowed={areLimitedStarGiftsDisallowed} areCollectibleStarGiftsDisallowed={areUniqueStarGiftsDisallowed}
isSelf={isSelf}
hasCollectible={Boolean(myCollectibleGiftIds?.length)}
onCategoryChanged={onCategoryChanged} onCategoryChanged={onCategoryChanged}
/> />
<Transition <Transition
@ -535,6 +602,8 @@ export default memo(withGlobal<OwnProps>((global, { modal }): Complete<StateProp
boostPerSentGift: global.appConfig.boostsPerSentGift, boostPerSentGift: global.appConfig.boostsPerSentGift,
starGiftsById: starGifts?.byId, starGiftsById: starGifts?.byId,
starGiftIdsByCategory: starGifts?.idsByCategory, starGiftIdsByCategory: starGifts?.idsByCategory,
myCollectibleGiftsById: global.myCollectibleGifts?.byId,
myCollectibleGiftIds: global.myCollectibleGifts?.ids,
starBalance: stars?.balance, starBalance: stars?.balance,
peer, peer,
isSelf, isSelf,
@ -548,8 +617,6 @@ export default memo(withGlobal<OwnProps>((global, { modal }): Complete<StateProp
function getCategoryKey(category: StarGiftCategory) { function getCategoryKey(category: StarGiftCategory) {
if (category === 'all') return 0; if (category === 'all') return 0;
if (category === 'limited') return 1; if (category === 'myCollectibles') return 1;
if (category === 'resale') return 2; return 2;
if (category === 'stock') return 3;
return category + 3;
} }

View File

@ -10,7 +10,7 @@
flex-wrap: nowrap; flex-wrap: nowrap;
gap: 0.0625rem; gap: 0.0625rem;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
// Prevent first item from being always partially obscured // Prevent first item from being always partially obscured
margin-left: -0.5rem; margin-left: -0.5rem;

View File

@ -1,5 +1,5 @@
import { import {
memo, useMemo, useRef, useState, memo, useRef, useState,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { withGlobal } from '../../../global'; import { withGlobal } from '../../../global';
@ -10,12 +10,12 @@ import buildClassName from '../../../util/buildClassName';
import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import StarIcon from '../../common/icons/StarIcon';
import styles from './StarGiftCategoryList.module.scss'; import styles from './StarGiftCategoryList.module.scss';
type OwnProps = { type OwnProps = {
areLimitedStarGiftsDisallowed?: boolean; areCollectibleStarGiftsDisallowed?: boolean;
isSelf?: boolean;
hasCollectible?: boolean;
onCategoryChanged: (category: StarGiftCategory) => void; onCategoryChanged: (category: StarGiftCategory) => void;
}; };
@ -26,16 +26,13 @@ type StateProps = {
const StarGiftCategoryList = ({ const StarGiftCategoryList = ({
idsByCategory, idsByCategory,
onCategoryChanged, onCategoryChanged,
areLimitedStarGiftsDisallowed, areCollectibleStarGiftsDisallowed,
isSelf,
hasCollectible,
}: StateProps & OwnProps) => { }: StateProps & OwnProps) => {
const ref = useRef<HTMLDivElement>(); const ref = useRef<HTMLDivElement>();
const lang = useLang(); const lang = useLang();
const starCategories: number[] | undefined = useMemo(() => idsByCategory && Object.keys(idsByCategory)
.filter((category) => category !== 'all' && category !== 'limited')
.map(Number)
.sort((a, b) => a - b),
[idsByCategory]);
const hasResale = idsByCategory && idsByCategory['resale'].length > 0; const hasResale = idsByCategory && idsByCategory['resale'].length > 0;
@ -50,9 +47,8 @@ const StarGiftCategoryList = ({
function renderCategoryName(category: StarGiftCategory) { function renderCategoryName(category: StarGiftCategory) {
if (category === 'all') return lang('AllGiftsCategory'); if (category === 'all') return lang('AllGiftsCategory');
if (category === 'stock') return lang('StockGiftsCategory'); if (category === 'myCollectibles') return lang('GiftCategoryMyGifts');
if (category === 'limited') return lang('LimitedGiftsCategory'); if (category === 'resale') return lang('GiftCategoryCollectibles');
if (category === 'resale') return lang('GiftCategoryResale');
return category; return category;
} }
@ -65,13 +61,6 @@ const StarGiftCategoryList = ({
)} )}
onClick={() => handleItemClick(category)} onClick={() => handleItemClick(category)}
> >
{Number.isInteger(category) && (
<StarIcon
className={styles.star}
type="gold"
size="middle"
/>
)}
{renderCategoryName(category)} {renderCategoryName(category)}
</div> </div>
); );
@ -82,10 +71,8 @@ const StarGiftCategoryList = ({
return ( return (
<div ref={ref} className={buildClassName(styles.list, 'no-scrollbar')}> <div ref={ref} className={buildClassName(styles.list, 'no-scrollbar')}>
{renderCategoryItem('all')} {renderCategoryItem('all')}
{!areLimitedStarGiftsDisallowed && renderCategoryItem('limited')} {!areCollectibleStarGiftsDisallowed && !isSelf && hasCollectible && renderCategoryItem('myCollectibles')}
{!areLimitedStarGiftsDisallowed && hasResale && renderCategoryItem('resale')} {!areCollectibleStarGiftsDisallowed && hasResale && renderCategoryItem('resale')}
{renderCategoryItem('stock')}
{starCategories?.map(renderCategoryItem)}
</div> </div>
); );
}; };

View File

@ -19,6 +19,17 @@
font-size: 1.25rem; font-size: 1.25rem;
} }
.descriptionConfirm {
padding-bottom: 0.75rem;
color: var(--color-text-secondary);
}
.titleConfirm {
margin-bottom: 0.75rem;
font-size: 1.25rem;
font-weight: var(--font-weight-medium);
}
.header { .header {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -938,10 +938,19 @@ const GiftInfoModal = ({
confirmHandler={handleConfirmBuyGift} confirmHandler={handleConfirmBuyGift}
> >
{uniqueGift.resaleTonOnly
&& (
<div className={styles.descriptionConfirm}>
{lang('ConfirmBuyGiftForTonDescription')}
</div>
)}
<GiftTransferPreview <GiftTransferPreview
peer={recipientPeer || currentUser} peer={recipientPeer || currentUser}
gift={uniqueGift} gift={uniqueGift}
/> />
<div className={styles.titleConfirm}>
{lang('TitleConfirmPayment')}
</div>
{!recipientPeer {!recipientPeer
&& ( && (
<p> <p>

View File

@ -0,0 +1,20 @@
import type { FC } from '../../../../lib/teact/teact';
import type { OwnProps } from './GiftTransferConfirmModal';
import { Bundles } from '../../../../util/moduleLoader';
import useModuleLoader from '../../../../hooks/useModuleLoader';
const GiftTransferConfirmModalAsync: FC<OwnProps> = (props) => {
const { modal } = props;
const GiftTransferConfirmModal = useModuleLoader(
Bundles.Stars,
'GiftTransferConfirmModal',
!modal,
);
return GiftTransferConfirmModal ? <GiftTransferConfirmModal {...props} /> : undefined;
};
export default GiftTransferConfirmModalAsync;

View File

@ -0,0 +1,108 @@
import { memo } from '../../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../../global';
import type { ApiPeer } from '../../../../api/types';
import type { TabState } from '../../../../global/types';
import { getPeerTitle } from '../../../../global/helpers/peers';
import { selectPeer } from '../../../../global/selectors';
import { formatStarsAsIcon, formatStarsAsText } from '../../../../util/localization/format';
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
import useLang from '../../../../hooks/useLang';
import useLastCallback from '../../../../hooks/useLastCallback';
import GiftTransferPreview from '../../../common/gift/GiftTransferPreview';
import ConfirmDialog from '../../../ui/ConfirmDialog';
export type OwnProps = {
modal: TabState['giftTransferConfirmModal'];
};
type StateProps = {
selectedPeer?: ApiPeer;
};
const GiftTransferConfirmModal = ({ modal, selectedPeer }: OwnProps & StateProps) => {
const {
closeGiftTransferConfirmModal, transferGift, openChat, closeGiftModal, closeGiftTransferModal,
} = getActions();
const lang = useLang();
const isOpen = Boolean(modal);
const renderingModal = useCurrentOrPrev(modal);
const renderingSelectedPeer = useCurrentOrPrev(selectedPeer);
const handleConfirm = useLastCallback(() => {
if (!renderingModal?.gift.inputGift || !renderingModal.recipientId) return;
transferGift({
gift: renderingModal.gift.inputGift,
recipientId: renderingModal.recipientId,
transferStars: renderingModal.gift.transferStars,
});
closeGiftTransferConfirmModal();
openChat({ id: renderingModal.recipientId });
closeGiftModal();
closeGiftTransferModal();
});
if (!renderingModal) return undefined;
const { gift } = renderingModal;
const uniqueGift = gift.gift.type === 'starGiftUnique' ? gift.gift : undefined;
if (!uniqueGift) return undefined;
return (
<ConfirmDialog
isOpen={isOpen}
noDefaultTitle
onClose={closeGiftTransferConfirmModal}
confirmLabel={gift.transferStars
? lang(
'GiftTransferConfirmButton',
{ amount: formatStarsAsIcon(lang, gift.transferStars, { asFont: true }) },
{ withNodes: true },
) : lang('GiftTransferConfirmButtonFree')}
confirmHandler={handleConfirm}
>
{renderingSelectedPeer && (
<GiftTransferPreview
peer={renderingSelectedPeer}
gift={uniqueGift}
/>
)}
<p>
{gift.transferStars
? lang('GiftTransferConfirmDescription', {
gift: lang('GiftUnique', { title: uniqueGift.title, number: uniqueGift.number }),
amount: formatStarsAsText(lang, 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>
);
};
export default memo(
withGlobal<OwnProps>((global, { modal }): Complete<StateProps> => {
const selectedPeer = modal?.recipientId ? selectPeer(global, modal.recipientId) : undefined;
return {
selectedPeer,
};
})(GiftTransferConfirmModal),
);

View File

@ -1,19 +1,15 @@
import { import {
memo, useEffect, useMemo, useState, memo, useMemo, useState,
} from '../../../../lib/teact/teact'; } from '../../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../../global'; import { getActions, getGlobal, withGlobal } from '../../../../global';
import type { ApiStarGiftUnique } from '../../../../api/types';
import type { TabState } from '../../../../global/types'; import type { TabState } from '../../../../global/types';
import type { UniqueCustomPeer } from '../../../../types'; import type { UniqueCustomPeer } from '../../../../types';
import { ALL_FOLDER_ID } from '../../../../config'; import { ALL_FOLDER_ID } from '../../../../config';
import { getPeerTitle } from '../../../../global/helpers/peers'; import { selectCanGift } from '../../../../global/selectors';
import { selectCanGift, selectPeer } from '../../../../global/selectors';
import { unique } from '../../../../util/iteratees'; import { unique } from '../../../../util/iteratees';
import { formatStarsAsIcon, formatStarsAsText } from '../../../../util/localization/format';
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
import { getGiftAttributes } from '../../../common/helpers/gifts';
import sortChatIds from '../../../common/helpers/sortChatIds'; import sortChatIds from '../../../common/helpers/sortChatIds';
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev'; import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
@ -22,10 +18,8 @@ import useLang from '../../../../hooks/useLang';
import useLastCallback from '../../../../hooks/useLastCallback'; import useLastCallback from '../../../../hooks/useLastCallback';
import usePeerSearch from '../../../../hooks/usePeerSearch'; import usePeerSearch from '../../../../hooks/usePeerSearch';
import GiftTransferPreview from '../../../common/gift/GiftTransferPreview';
import PeerPicker from '../../../common/pickers/PeerPicker'; import PeerPicker from '../../../common/pickers/PeerPicker';
import PickerModal from '../../../common/pickers/PickerModal'; import PickerModal from '../../../common/pickers/PickerModal';
import ConfirmDialog from '../../../ui/ConfirmDialog';
export type OwnProps = { export type OwnProps = {
modal: TabState['giftTransferModal']; modal: TabState['giftTransferModal'];
@ -41,7 +35,11 @@ type Categories = 'withdraw';
const GiftTransferModal = ({ const GiftTransferModal = ({
modal, contactIds, currentUserId, modal, contactIds, currentUserId,
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { closeGiftTransferModal, openGiftWithdrawModal, transferGift } = getActions(); const {
closeGiftTransferModal,
openGiftWithdrawModal,
openGiftTransferConfirmModal,
} = getActions();
const isOpen = Boolean(modal); const isOpen = Boolean(modal);
const lang = useLang(); const lang = useLang();
@ -49,16 +47,6 @@ const GiftTransferModal = ({
const [searchQuery, setSearchQuery] = useState<string>(''); const [searchQuery, setSearchQuery] = useState<string>('');
const renderingModal = useCurrentOrPrev(modal); 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 orderedChatIds = useFolderManagerForOrderedIds(ALL_FOLDER_ID);
@ -107,26 +95,13 @@ const GiftTransferModal = ({
false); false);
}, [isLoading, foundIds, currentUserId]); }, [isLoading, foundIds, currentUserId]);
const closeConfirmModal = useLastCallback(() => { const handlePeerSelect = useLastCallback((peerId: string) => {
setSelectedId(undefined); if (!renderingModal?.gift) return;
});
useEffect(() => { openGiftTransferConfirmModal({
if (!isOpen) { gift: renderingModal.gift,
setSelectedId(undefined); recipientId: peerId,
}
}, [isOpen]);
const handleTransfer = useLastCallback(() => {
if (!renderingModal?.gift.inputGift) return;
transferGift({
gift: renderingModal.gift.inputGift,
recipientId: renderingSelectedPeerId!,
transferStars: renderingModal.gift.transferStars,
}); });
closeConfirmModal();
closeGiftTransferModal();
}); });
return ( return (
@ -151,47 +126,8 @@ const GiftTransferModal = ({
filterValue={searchQuery} filterValue={searchQuery}
filterPlaceholder={lang('Search')} filterPlaceholder={lang('Search')}
onFilterChange={setSearchQuery} onFilterChange={setSearchQuery}
onSelectedIdChange={setSelectedId} onSelectedIdChange={handlePeerSelect}
/> />
{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}
>
{renderingSelectedPeer && (
<GiftTransferPreview
peer={renderingSelectedPeer}
gift={uniqueGift}
/>
)}
<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> </PickerModal>
); );
}; };

View File

@ -1,5 +1,4 @@
import type { ApiSavedStarGift, ApiStarGiftUnique } from '../../../api/types'; import type { ApiSavedStarGift, ApiStarGiftUnique } from '../../../api/types';
import type { StarGiftCategory } from '../../../types';
import type { ActionReturnType } from '../../types'; import type { ActionReturnType } from '../../types';
import { import {
@ -8,7 +7,7 @@ import {
TON_CURRENCY_CODE, TON_CURRENCY_CODE,
} from '../../../config'; } from '../../../config';
import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByCallback, buildCollectionByKey } from '../../../util/iteratees';
import { callApi } from '../../../api/gramjs'; import { callApi } from '../../../api/gramjs';
import { RESALE_GIFTS_LIMIT } from '../../../limits'; import { RESALE_GIFTS_LIMIT } from '../../../limits';
import { areInputSavedGiftsEqual, getRequestInputSavedStarGift } from '../../helpers/payments'; import { areInputSavedGiftsEqual, getRequestInputSavedStarGift } from '../../helpers/payments';
@ -136,49 +135,83 @@ addActionHandler('loadStarGifts', async (global): Promise<void> => {
const byId = buildCollectionByKey(result.gifts, 'id'); const byId = buildCollectionByKey(result.gifts, 'id');
const idsByCategoryName: Record<StarGiftCategory, string[]> = {
all: [],
stock: [],
limited: [],
resale: [],
};
const allStarGiftIds = Object.keys(byId); const allStarGiftIds = Object.keys(byId);
const allStarGifts = Object.values(byId); const allStarGifts = Object.values(byId);
const limitedStarGiftIds = allStarGifts.map((gift) => (gift.isLimited ? gift.id : undefined))
.filter(Boolean);
const stockedStarGiftIds = allStarGifts.map((gift) => (
gift.availabilityRemains || !gift.availabilityTotal ? gift.id : undefined
)).filter(Boolean);
const resaleStarGiftIds = allStarGifts.map((gift) => (gift.availabilityResale ? gift.id : undefined)) const resaleStarGiftIds = allStarGifts.map((gift) => (gift.availabilityResale ? gift.id : undefined))
.filter(Boolean); .filter(Boolean);
idsByCategoryName.all = allStarGiftIds;
idsByCategoryName.limited = limitedStarGiftIds;
idsByCategoryName.stock = stockedStarGiftIds;
idsByCategoryName.resale = resaleStarGiftIds;
allStarGifts.forEach((gift) => {
const starsCategory = gift.stars;
if (!idsByCategoryName[starsCategory]) {
idsByCategoryName[starsCategory] = [];
}
idsByCategoryName[starsCategory].push(gift.id);
});
global = { global = {
...global, ...global,
starGifts: { starGifts: {
byId, byId,
idsByCategory: idsByCategoryName, idsByCategory: {
all: allStarGiftIds,
resale: resaleStarGiftIds,
myCollectibles: [],
},
}, },
}; };
setGlobal(global); setGlobal(global);
}); });
addActionHandler('loadMyCollectibleGifts', async (global, actions, payload): Promise<void> => {
const { shouldRefresh } = payload || {};
const currentUserId = global.currentUserId;
if (!currentUserId) return;
const currentMyCollectibleGifts = global.myCollectibleGifts;
const localNextOffset = currentMyCollectibleGifts?.nextOffset;
if (currentMyCollectibleGifts && !localNextOffset && !shouldRefresh) return;
const peer = selectPeer(global, currentUserId);
if (!peer) return;
const result = await callApi('fetchSavedStarGifts', {
peer,
offset: !shouldRefresh ? localNextOffset : undefined,
filter: {
sortType: 'byDate',
shouldIncludeUnique: true,
shouldIncludeUnlimited: false,
shouldIncludeUpgradable: false,
shouldIncludeLimited: false,
shouldIncludeDisplayed: true,
shouldIncludeHidden: true,
},
});
if (!result) return;
global = getGlobal();
const gifts = result.gifts;
const byId = buildCollectionByCallback(gifts, (savedGift) => (
[savedGift.gift.id, savedGift]
));
const ids = gifts.map((gift) => gift.gift.id);
global = {
...global,
myCollectibleGifts: {
byId: {
...!shouldRefresh && (global.myCollectibleGifts?.byId || {}),
...byId,
},
ids: [
...!shouldRefresh ? (global.myCollectibleGifts?.ids || []) : [],
...ids,
],
nextOffset: result.nextOffset,
},
};
setGlobal(global);
});
addActionHandler('updateResaleGiftsFilter', (global, actions, payload): ActionReturnType => { addActionHandler('updateResaleGiftsFilter', (global, actions, payload): ActionReturnType => {
const { const {
filter, tabId = getCurrentTabId(), filter, tabId = getCurrentTabId(),
@ -338,6 +371,9 @@ addActionHandler('reloadPeerSavedGifts', (global, actions, payload): ActionRetur
actions.loadPeerSavedGifts({ peerId, shouldRefresh: true, tabId: tabState.id }); actions.loadPeerSavedGifts({ peerId, shouldRefresh: true, tabId: tabState.id });
} }
}); });
if (peerId === global.currentUserId) {
actions.loadMyCollectibleGifts({ shouldRefresh: true });
}
}); });
addActionHandler('loadStarsSubscriptions', async (global): Promise<void> => { addActionHandler('loadStarsSubscriptions', async (global): Promise<void> => {

View File

@ -298,6 +298,8 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
global = updateTabState(global, { global = updateTabState(global, {
isWaitingForStarGiftTransfer: undefined, isWaitingForStarGiftTransfer: undefined,
}, tabId); }, tabId);
actions.reloadPeerSavedGifts({ peerId: global.currentUserId! });
} }
}); });

View File

@ -146,6 +146,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
}, },
tabId, tabId,
}); });
actions.reloadPeerSavedGifts({ peerId: starGiftModalState.forPeerId });
actions.requestConfetti({ withStars: true, tabId }); actions.requestConfetti({ withStars: true, tabId });
actions.closeGiftModal({ tabId }); actions.closeGiftModal({ tabId });
} }

View File

@ -434,6 +434,21 @@ addActionHandler('openGiftTransferModal', (global, actions, payload): ActionRetu
addTabStateResetterAction('closeGiftTransferModal', 'giftTransferModal'); addTabStateResetterAction('closeGiftTransferModal', 'giftTransferModal');
addActionHandler('openGiftTransferConfirmModal', (global, actions, payload): ActionReturnType => {
const {
gift, recipientId, tabId = getCurrentTabId(),
} = payload;
return updateTabState(global, {
giftTransferConfirmModal: {
gift,
recipientId,
},
}, tabId);
});
addTabStateResetterAction('closeGiftTransferConfirmModal', 'giftTransferConfirmModal');
addActionHandler('updateSelectedGiftCollection', (global, actions, payload): ActionReturnType => { addActionHandler('updateSelectedGiftCollection', (global, actions, payload): ActionReturnType => {
const { peerId, collectionId, tabId = getCurrentTabId() } = payload; const { peerId, collectionId, tabId = getCurrentTabId() } = payload;
const tabState = selectTabState(global, tabId); const tabState = selectTabState(global, tabId);

View File

@ -2544,6 +2544,9 @@ export interface ActionPayloads {
loadPremiumGifts: undefined; loadPremiumGifts: undefined;
loadTonGifts: undefined; loadTonGifts: undefined;
loadStarGifts: undefined; loadStarGifts: undefined;
loadMyCollectibleGifts: {
shouldRefresh?: true;
} | undefined;
updateResaleGiftsFilter: { updateResaleGiftsFilter: {
filter: ResaleGiftsFilterOptions; filter: ResaleGiftsFilterOptions;
} & WithTabId; } & WithTabId;
@ -2643,6 +2646,11 @@ export interface ActionPayloads {
recipientId: string; recipientId: string;
} & WithTabId; } & WithTabId;
closeGiftTransferModal: WithTabId | undefined; closeGiftTransferModal: WithTabId | undefined;
openGiftTransferConfirmModal: {
gift: ApiSavedStarGift;
recipientId: string;
} & WithTabId;
closeGiftTransferConfirmModal: WithTabId | undefined;
updateSelectedGiftCollection: { updateSelectedGiftCollection: {
peerId: string; peerId: string;
collectionId: number; collectionId: number;

View File

@ -28,6 +28,7 @@ import type {
ApiReaction, ApiReaction,
ApiReactionKey, ApiReactionKey,
ApiSavedReactionTag, ApiSavedReactionTag,
ApiSavedStarGift,
ApiSession, ApiSession,
ApiSponsoredMessage, ApiSponsoredMessage,
ApiStarGiftCollection, ApiStarGiftCollection,
@ -311,6 +312,11 @@ export type GlobalState = {
byId: Record<string, ApiStarGiftRegular>; byId: Record<string, ApiStarGiftRegular>;
idsByCategory: Record<StarGiftCategory, string[]>; idsByCategory: Record<StarGiftCategory, string[]>;
}; };
myCollectibleGifts?: {
byId: Record<string, ApiSavedStarGift>;
ids: string[];
nextOffset?: string;
};
starGiftCollections?: { starGiftCollections?: {
byPeerId: Record<string, ApiStarGiftCollection[]>; byPeerId: Record<string, ApiStarGiftCollection[]>;
}; };

View File

@ -849,6 +849,11 @@ export type TabState = {
gift: ApiSavedStarGift; gift: ApiSavedStarGift;
}; };
giftTransferConfirmModal?: {
gift: ApiSavedStarGift;
recipientId: string;
};
giftUpgradeModal?: { giftUpgradeModal?: {
sampleAttributes: ApiStarGiftAttribute[]; sampleAttributes: ApiStarGiftAttribute[];
recipientId?: string; recipientId?: string;

View File

@ -669,7 +669,7 @@ export type WebPageMediaSize = 'large' | 'small';
export type AttachmentCompression = 'compress' | 'original'; export type AttachmentCompression = 'compress' | 'original';
export type StarGiftCategory = number | 'all' | 'limited' | 'stock' | 'resale'; export type StarGiftCategory = 'all' | 'myCollectibles' | 'resale';
export type CallSound = ( export type CallSound = (
'join' | 'allowTalk' | 'leave' | 'connecting' | 'incoming' | 'end' | 'connect' | 'busy' | 'ringing' 'join' | 'allowTalk' | 'leave' | 'connecting' | 'incoming' | 'end' | 'connect' | 'busy' | 'ringing'

View File

@ -1232,6 +1232,7 @@ export interface LangPair {
'GiftPremiumDescriptionLink': undefined; 'GiftPremiumDescriptionLink': undefined;
'StarsGiftHeader': undefined; 'StarsGiftHeader': undefined;
'StarsGiftHeaderSelf': undefined; 'StarsGiftHeaderSelf': undefined;
'StarGiftDescriptionCollectibles': undefined;
'StarGiftDescriptionSelf': undefined; 'StarGiftDescriptionSelf': undefined;
'GiftLimited': undefined; 'GiftLimited': undefined;
'GiftSoldOut': undefined; 'GiftSoldOut': undefined;
@ -1304,8 +1305,6 @@ export interface LangPair {
'GiftWithdrawTitle': undefined; 'GiftWithdrawTitle': undefined;
'GiftWithdrawSubmit': undefined; 'GiftWithdrawSubmit': undefined;
'AllGiftsCategory': undefined; 'AllGiftsCategory': undefined;
'LimitedGiftsCategory': undefined;
'StockGiftsCategory': undefined;
'PremiumGiftDescription': undefined; 'PremiumGiftDescription': undefined;
'StarsReactionLinkText': undefined; 'StarsReactionLinkText': undefined;
'StarsReactionLink': undefined; 'StarsReactionLink': undefined;
@ -1578,7 +1577,8 @@ export interface LangPair {
'StarGiftPurchaseTransaction': undefined; 'StarGiftPurchaseTransaction': undefined;
'ContextMenuItemMention': undefined; 'ContextMenuItemMention': undefined;
'GiftRibbonResale': undefined; 'GiftRibbonResale': undefined;
'GiftCategoryResale': undefined; 'GiftCategoryCollectibles': undefined;
'GiftCategoryMyGifts': undefined;
'GiftSortByPrice': undefined; 'GiftSortByPrice': undefined;
'GiftSortByNumber': undefined; 'GiftSortByNumber': undefined;
'ContextMenuItemSelectAll': undefined; 'ContextMenuItemSelectAll': undefined;
@ -1709,6 +1709,7 @@ export interface LangPair {
'GiftValueForSaleOnFragment': undefined; 'GiftValueForSaleOnFragment': undefined;
'GiftValueForSaleOnTelegram': undefined; 'GiftValueForSaleOnTelegram': undefined;
'EmbeddedMessageNoCaption': undefined; 'EmbeddedMessageNoCaption': undefined;
'ConfirmBuyGiftForTonDescription': undefined;
'TitleGiftLocked': undefined; 'TitleGiftLocked': undefined;
'QuickPreview': undefined; 'QuickPreview': undefined;
} }