Gifts: Support gift prepaid upgrade (#6415)

This commit is contained in:
Alexander Zinchuk 2025-11-06 11:36:41 +01:00
parent ef5b34229d
commit f8c61646b1
19 changed files with 237 additions and 28 deletions

View File

@ -158,7 +158,7 @@ export function buildApiStarGiftAttribute(attribute: GramJs.TypeStarGiftAttribut
export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId: string): ApiSavedStarGift { export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId: string): ApiSavedStarGift {
const { const {
gift, date, convertStars, fromId, message, msgId, nameHidden, unsaved, upgradeStars, transferStars, canUpgrade, gift, date, convertStars, fromId, message, msgId, nameHidden, unsaved, upgradeStars, transferStars, canUpgrade,
savedId, canExportAt, pinnedToTop, canResellAt, canTransferAt, savedId, canExportAt, pinnedToTop, canResellAt, canTransferAt, prepaidUpgradeHash,
} = userStarGift; } = userStarGift;
const inputGift: ApiInputSavedStarGift | undefined = savedId && peerId const inputGift: ApiInputSavedStarGift | undefined = savedId && peerId
@ -183,6 +183,7 @@ export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId
canResellAt, canResellAt,
canTransferAt, canTransferAt,
isPinned: pinnedToTop, isPinned: pinnedToTop,
prepaidUpgradeHash,
}; };
} }

View File

@ -390,8 +390,8 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
} }
if (action instanceof GramJs.MessageActionStarGift) { if (action instanceof GramJs.MessageActionStarGift) {
const { const {
nameHidden, saved, converted, upgraded, refunded, canUpgrade, gift, message, convertStars, upgradeMsgId, nameHidden, saved, converted, upgraded, refunded, canUpgrade, prepaidUpgrade, gift, message, convertStars,
upgradeStars, fromId, peer, savedId, upgradeMsgId, giftMsgId, upgradeStars, fromId, peer, savedId, prepaidUpgradeHash,
} = action; } = action;
const starGift = buildApiStarGift(gift); const starGift = buildApiStarGift(gift);
@ -406,20 +406,23 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
isUpgraded: upgraded, isUpgraded: upgraded,
isRefunded: refunded, isRefunded: refunded,
canUpgrade, canUpgrade,
isPrepaidUpgrade: prepaidUpgrade,
gift: starGift, gift: starGift,
message: message && buildApiFormattedText(message), message: message && buildApiFormattedText(message),
starsToConvert: toJSNumber(convertStars), starsToConvert: toJSNumber(convertStars),
upgradeMsgId, upgradeMsgId,
giftMsgId,
alreadyPaidUpgradeStars: toJSNumber(upgradeStars), alreadyPaidUpgradeStars: toJSNumber(upgradeStars),
fromId: fromId && getApiChatIdFromMtpPeer(fromId), fromId: fromId && getApiChatIdFromMtpPeer(fromId),
peerId: peer && getApiChatIdFromMtpPeer(peer), peerId: peer && getApiChatIdFromMtpPeer(peer),
savedId: savedId !== undefined ? buildApiPeerId(savedId, 'user') : undefined, savedId: savedId !== undefined ? buildApiPeerId(savedId, 'user') : undefined,
prepaidUpgradeHash,
}; };
} }
if (action instanceof GramJs.MessageActionStarGiftUnique) { if (action instanceof GramJs.MessageActionStarGiftUnique) {
const { const {
upgrade, transferred, saved, refunded, gift, canExportAt, transferStars, fromId, peer, savedId, upgrade, transferred, saved, refunded, gift, canExportAt, transferStars, fromId, peer, savedId,
resaleAmount, resaleAmount, prepaidUpgrade,
} = action; } = action;
const starGift = buildApiStarGift(gift); const starGift = buildApiStarGift(gift);
@ -432,6 +435,7 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
isTransferred: transferred, isTransferred: transferred,
isSaved: saved, isSaved: saved,
isRefunded: refunded, isRefunded: refunded,
isPrepaidUpgrade: prepaidUpgrade,
gift: starGift, gift: starGift,
canExportAt, canExportAt,
transferStars: toJSNumber(transferStars), transferStars: toJSNumber(transferStars),

View File

@ -553,7 +553,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
const { const {
date, id, peer, amount, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction, date, id, peer, amount, description, photo, title, refund, extendedMedia, failed, msgId, pending, gift, reaction,
subscriptionPeriod, stargift, giveawayPostId, starrefCommissionPermille, stargiftUpgrade, paidMessages, subscriptionPeriod, stargift, giveawayPostId, starrefCommissionPermille, stargiftUpgrade, paidMessages,
stargiftResale, postsSearch, stargiftResale, postsSearch, stargiftPrepaidUpgrade,
} = transaction; } = transaction;
if (photo) { if (photo) {
@ -593,6 +593,7 @@ export function buildApiStarsTransaction(transaction: GramJs.StarsTransaction):
isGiftResale: stargiftResale, isGiftResale: stargiftResale,
paidMessages, paidMessages,
isPostsSearch: postsSearch, isPostsSearch: postsSearch,
isPrepaidUpgrade: stargiftPrepaidUpgrade,
}; };
} }

View File

@ -781,6 +781,13 @@ export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
}); });
} }
case 'stargiftPrepaidUpgrade': {
return new GramJs.InputInvoiceStarGiftPrepaidUpgrade({
peer: buildInputPeer(invoice.peer.id, invoice.peer.accessHash),
hash: invoice.hash,
});
}
case 'giveaway': case 'giveaway':
default: { default: {
const purpose = buildInputStorePaymentPurpose(invoice.purpose); const purpose = buildInputStorePaymentPurpose(invoice.purpose);

View File

@ -242,14 +242,17 @@ export interface ApiMessageActionStarGift extends ActionMediaType {
isUpgraded?: true; isUpgraded?: true;
isRefunded?: true; isRefunded?: true;
canUpgrade?: true; canUpgrade?: true;
isPrepaidUpgrade?: true;
gift: ApiStarGiftRegular; gift: ApiStarGiftRegular;
message?: ApiFormattedText; message?: ApiFormattedText;
starsToConvert?: number; starsToConvert?: number;
upgradeMsgId?: number; upgradeMsgId?: number;
giftMsgId?: number;
alreadyPaidUpgradeStars?: number; alreadyPaidUpgradeStars?: number;
fromId?: string; fromId?: string;
peerId?: string; peerId?: string;
savedId?: string; savedId?: string;
prepaidUpgradeHash?: string;
} }
export interface ApiMessageActionStarGiftUnique extends ActionMediaType { export interface ApiMessageActionStarGiftUnique extends ActionMediaType {
@ -258,6 +261,7 @@ export interface ApiMessageActionStarGiftUnique extends ActionMediaType {
isTransferred?: true; isTransferred?: true;
isSaved?: true; isSaved?: true;
isRefunded?: true; isRefunded?: true;
isPrepaidUpgrade?: true;
gift: ApiStarGiftUnique; gift: ApiStarGiftUnique;
canExportAt?: number; canExportAt?: number;
transferStars?: number; transferStars?: number;

View File

@ -399,10 +399,17 @@ export type ApiInputInvoiceStarGiftTransfer = {
recipientId: string; recipientId: string;
}; };
export type ApiInputInvoiceStarGiftPrepaidUpgrade = {
type: 'stargiftPrepaidUpgrade';
peerId: string;
hash: string;
};
export type ApiInputInvoice = ApiInputInvoiceMessage | ApiInputInvoiceSlug | ApiInputInvoiceGiveaway export type ApiInputInvoice = ApiInputInvoiceMessage | ApiInputInvoiceSlug | ApiInputInvoiceGiveaway
| ApiInputInvoiceGiftCode | ApiInputInvoicePremiumGiftStars | ApiInputInvoiceStars | ApiInputInvoiceStarsGift | ApiInputInvoiceGiftCode | ApiInputInvoicePremiumGiftStars | ApiInputInvoiceStars | ApiInputInvoiceStarsGift
| ApiInputInvoiceStarsGiveaway | ApiInputInvoiceStarGift | ApiInputInvoiceChatInviteSubscription | ApiInputInvoiceStarsGiveaway | ApiInputInvoiceStarGift | ApiInputInvoiceChatInviteSubscription
| ApiInputInvoiceStarGiftUpgrade | ApiInputInvoiceStarGiftTransfer | ApiInputInvoiceStarGiftResale; | ApiInputInvoiceStarGiftUpgrade | ApiInputInvoiceStarGiftTransfer | ApiInputInvoiceStarGiftResale
| ApiInputInvoiceStarGiftPrepaidUpgrade;
/* Used for Invoice request */ /* Used for Invoice request */
export type ApiRequestInputInvoiceMessage = { export type ApiRequestInputInvoiceMessage = {
@ -472,11 +479,18 @@ export type ApiRequestInputInvoiceStarGiftTransfer = {
recipient: ApiPeer; recipient: ApiPeer;
}; };
export type ApiRequestInputInvoiceStarGiftPrepaidUpgrade = {
type: 'stargiftPrepaidUpgrade';
peer: ApiPeer;
hash: string;
};
export type ApiRequestInputInvoice = ApiRequestInputInvoiceMessage | ApiRequestInputInvoiceSlug export type ApiRequestInputInvoice = ApiRequestInputInvoiceMessage | ApiRequestInputInvoiceSlug
| ApiRequestInputInvoiceGiveaway | ApiRequestInputInvoiceStars | ApiRequestInputInvoiceStarsGiveaway | ApiRequestInputInvoiceGiveaway | ApiRequestInputInvoiceStars | ApiRequestInputInvoiceStarsGiveaway
| ApiRequestInputInvoiceChatInviteSubscription | ApiRequestInputInvoiceStarGift | ApiRequestInputInvoiceChatInviteSubscription | ApiRequestInputInvoiceStarGift
| ApiRequestInputInvoiceStarGiftUpgrade | ApiRequestInputInvoiceStarGiftTransfer | ApiRequestInputInvoiceStarGiftUpgrade | ApiRequestInputInvoiceStarGiftTransfer
| ApiRequestInputInvoicePremiumGiftStars | ApiRequestInputInvoiceStarGiftResale; | ApiRequestInputInvoicePremiumGiftStars | ApiRequestInputInvoiceStarGiftResale
| ApiRequestInputInvoiceStarGiftPrepaidUpgrade;
export interface ApiUniqueStarGiftValueInfo { export interface ApiUniqueStarGiftValueInfo {
isLastSaleOnFragment?: true; isLastSaleOnFragment?: true;

View File

@ -107,6 +107,7 @@ export interface ApiSavedStarGift {
canTransferAt?: number; canTransferAt?: number;
canResellAt?: number; canResellAt?: number;
isPinned?: boolean; isPinned?: boolean;
prepaidUpgradeHash?: string;
isConverted?: boolean; // Local field, used for Action Message isConverted?: boolean; // Local field, used for Action Message
upgradeMsgId?: number; // Local field, used for Action Message upgradeMsgId?: number; // Local field, used for Action Message
localTag?: number; // Local field, used for key in list localTag?: number; // Local field, used for key in list
@ -244,6 +245,7 @@ export interface ApiStarsTransaction {
isGiftResale?: true; isGiftResale?: true;
paidMessages?: number; paidMessages?: number;
isPostsSearch?: true; isPostsSearch?: true;
isPrepaidUpgrade?: true;
} }
export interface ApiStarsSubscription { export interface ApiStarsSubscription {

View File

@ -1569,8 +1569,14 @@
"GiftUpgradeTextOwn" = "Turn your gift into a unique collectible that you can transfer or auction."; "GiftUpgradeTextOwn" = "Turn your gift into a unique collectible that you can transfer or auction.";
"GiftUpgradeKeepDetails" = "Keep sender's name and comment"; "GiftUpgradeKeepDetails" = "Keep sender's name and comment";
"GiftUpgradeButton" = "Upgrade for {amount}"; "GiftUpgradeButton" = "Upgrade for {amount}";
"GiftPayForUpgradeButton" = "Pay {amount} for Upgrade";
"GiftUpgradedTitle" = "This gift is now a collectible"; "GiftUpgradedTitle" = "This gift is now a collectible";
"GiftUpgradedDescription" = "You have received a unique number, model, backdrop and symbol for your gift."; "GiftUpgradedDescription" = "You have received a unique number, model, backdrop and symbol for your gift.";
"GiftPeerUpgradeUniqueDescription" = "{user} will get a unique number, model, backdrop, and symbol for the gift.";
"GiftPeerUpgradeTransferableDescription" = "{user} will be able to send the gift to anyone on Telegram.";
"GiftPeerUpgradeTradeableDescription" = "{user} will be able to auction the gift on third-party NFT marketplaces.";
"GiftUpgradeSentTitle" = "Gift Upgrade sent!";
"GiftUpgradeSentMessage" = "{user} can now upgrade this gift for free.";
"GiftMakeUnique" = "Make unique for {stars}"; "GiftMakeUnique" = "Make unique for {stars}";
"GiftMakeUniqueAcc" = "Make unique"; "GiftMakeUniqueAcc" = "Make unique";
"GiftMakeUniqueDescription" = "Enable this to let {user} turn your gift into a unique collectible. {link}"; "GiftMakeUniqueDescription" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
@ -1872,6 +1878,8 @@
"ActionStarGiftReceivedAnonymous" = "Someone sent you a gift for {cost}"; "ActionStarGiftReceivedAnonymous" = "Someone sent you a gift for {cost}";
"ActionStarGiftSentChannel" = "{user} sent a gift to {channel} for {cost}"; "ActionStarGiftSentChannel" = "{user} sent a gift to {channel} for {cost}";
"ActionStarGiftSentChannelYou" = "You sent a gift to {channel} for {cost}"; "ActionStarGiftSentChannelYou" = "You sent a gift to {channel} for {cost}";
"ActionStarGiftPrepaidUpgradeYou" = "You sent an upgrade worth {cost} for {peer}'s gift";
"ActionStarGiftPrepaidUpgrade" = "{peer} sent an upgrade worth {cost} for your gift";
"ActionStarGiftSelfBought" = "You bought a gift for {cost}"; "ActionStarGiftSelfBought" = "You bought a gift for {cost}";
"ActionStarGiftTo" = "Gift to {peer}"; "ActionStarGiftTo" = "Gift to {peer}";
"ActionStarGiftFrom" = "Gift from {peer}"; "ActionStarGiftFrom" = "Gift from {peer}";
@ -2293,6 +2301,10 @@
"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";
"GiftAnUpgradeButton" = "Gift an Upgrade";
"GiftPrepaidUpgradeTransactionTitle" = "Gift Upgrade";
"ActionStarGiftPrepaidUpgraded" = "{user} turned the gift into a unique collectible";
"ActionStarGiftPrepaidUpgradedYou" = "You turned the gift into a unique collectible";
"UserNoteTitle" = "Notes"; "UserNoteTitle" = "Notes";
"UserNoteHint" = "only visible to you"; "UserNoteHint" = "only visible to you";
"EditUserNoteHint" = "Notes are only visible to you."; "EditUserNoteHint" = "Notes are only visible to you.";

View File

@ -568,7 +568,7 @@ const ActionMessageText = ({
case 'starGift': { case 'starGift': {
const { const {
gift, alreadyPaidUpgradeStars, peerId, savedId, fromId, gift, alreadyPaidUpgradeStars, peerId, savedId, fromId, isPrepaidUpgrade,
} = action; } = action;
const isToChannel = Boolean(peerId && savedId); const isToChannel = Boolean(peerId && savedId);
@ -576,9 +576,25 @@ const ActionMessageText = ({
const fromTitle = (fromPeer && getPeerTitle(lang, fromPeer)) || userFallbackText; const fromTitle = (fromPeer && getPeerTitle(lang, fromPeer)) || userFallbackText;
const fromLink = renderPeerLink(fromPeer?.id, fromTitle, asPreview); const fromLink = renderPeerLink(fromPeer?.id, fromTitle, asPreview);
const toPeer = peerId ? selectPeer(global, peerId) : undefined;
const toTitle = (toPeer && getPeerTitle(lang, toPeer))
|| (isToChannel ? channelFallbackText : userFallbackText);
const toLink = renderPeerLink(toPeer?.id, toTitle, asPreview);
const starsAmount = gift.stars + (alreadyPaidUpgradeStars || 0); const starsAmount = gift.stars + (alreadyPaidUpgradeStars || 0);
const cost = renderStrong(formatStarsAsText(lang, starsAmount)); const cost = renderStrong(formatStarsAsText(lang, starsAmount));
if (isPrepaidUpgrade && gift.upgradeStars) {
const upgradeCost = renderStrong(formatStarsAsText(lang, gift.upgradeStars));
return translateWithYou(
lang, 'ActionStarGiftPrepaidUpgrade', isOutgoing, {
peer: isOutgoing ? toLink : senderLink,
cost: upgradeCost,
},
);
}
if (isToChannel) { if (isToChannel) {
const channelPeer = selectPeer(global, peerId!); const channelPeer = selectPeer(global, peerId!);
const isYou = fromPeer?.id === currentUserId; const isYou = fromPeer?.id === currentUserId;
@ -607,7 +623,7 @@ const ActionMessageText = ({
case 'starGiftUnique': { case 'starGiftUnique': {
const { const {
isTransferred, isUpgrade, savedId, peerId, fromId, resaleAmount, gift, transferStars, isTransferred, isUpgrade, savedId, peerId, fromId, resaleAmount, gift, transferStars, isPrepaidUpgrade,
} = action; } = action;
const isToChannel = Boolean(peerId && savedId); const isToChannel = Boolean(peerId && savedId);
@ -616,6 +632,18 @@ const ActionMessageText = ({
const fromTitle = (fromPeer && getPeerTitle(lang, fromPeer)) || userFallbackText; const fromTitle = (fromPeer && getPeerTitle(lang, fromPeer)) || userFallbackText;
const fromLink = renderPeerLink(fromPeer?.id, fromTitle, asPreview); const fromLink = renderPeerLink(fromPeer?.id, fromTitle, asPreview);
const toPeer = peerId ? selectPeer(global, peerId) : undefined;
const toTitle = (toPeer && getPeerTitle(lang, toPeer))
|| (isToChannel ? channelFallbackText : userFallbackText);
const toLink = renderPeerLink(toPeer?.id, toTitle, asPreview);
if (isPrepaidUpgrade) {
if (isOutgoing) {
return lang('ActionStarGiftPrepaidUpgradedYou');
}
return lang('ActionStarGiftPrepaidUpgraded', { user: toLink }, { withNodes: true });
}
if (resaleAmount && !transferStars) { if (resaleAmount && !transferStars) {
const amountText = resaleAmount.currency === TON_CURRENCY_CODE const amountText = resaleAmount.currency === TON_CURRENCY_CODE
? formatTonAsText(lang, convertTonFromNanos(resaleAmount.amount)) ? formatTonAsText(lang, convertTonFromNanos(resaleAmount.amount))

View File

@ -93,7 +93,7 @@
} }
.subtitle { .subtitle {
max-width: 75%; max-width: 85%;
font-size: 0.875rem; font-size: 0.875rem;
text-align: center; text-align: center;

View File

@ -282,7 +282,8 @@ const GiftInfoModal = ({
const handleOpenUpgradeModal = useLastCallback(() => { const handleOpenUpgradeModal = useLastCallback(() => {
if (!savedGift) return; if (!savedGift) return;
openGiftUpgradeModal({ giftId: savedGift.gift.id, gift: savedGift }); const giftOwnerId = renderingTargetPeer?.id;
openGiftUpgradeModal({ giftId: savedGift.gift.id, gift: savedGift, peerId: giftOwnerId });
}); });
const handleBuyGift = useLastCallback(() => { const handleBuyGift = useLastCallback(() => {
@ -376,6 +377,15 @@ const GiftInfoModal = ({
); );
} }
if (savedGift?.prepaidUpgradeHash) {
return (
<Button isShiny onClick={handleOpenUpgradeModal}>
{lang('GiftAnUpgradeButton')}
<Icon name="arrow-down-circle" className={styles.upgradeIcon} />
</Button>
);
}
return ( return (
<Button onClick={handleClose}> <Button onClick={handleClose}>
{lang('OK')} {lang('OK')}

View File

@ -49,13 +49,15 @@ type Attributes = {
const PREVIEW_UPDATE_INTERVAL = 3000; const PREVIEW_UPDATE_INTERVAL = 3000;
const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => { const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
const { closeGiftUpgradeModal, upgradeGift } = getActions(); const { closeGiftUpgradeModal, closeGiftInfoModal, upgradeGift, upgradePrepaidGift } = getActions();
const isOpen = Boolean(modal); const isOpen = Boolean(modal);
const renderingModal = useCurrentOrPrev(modal); const renderingModal = useCurrentOrPrev(modal);
const renderingRecipient = useCurrentOrPrev(recipient); const renderingRecipient = useCurrentOrPrev(recipient);
const [shouldKeepOriginalDetails, setShouldKeepOriginalDetails] = useState(false); const [shouldKeepOriginalDetails, setShouldKeepOriginalDetails] = useState(false);
const isPrepaid = Boolean(renderingModal?.gift?.prepaidUpgradeHash);
const [previewAttributes, setPreviewAttributes] = useState<Attributes | undefined>(); const [previewAttributes, setPreviewAttributes] = useState<Attributes | undefined>();
const lang = useLang(); const lang = useLang();
@ -64,12 +66,31 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
const handleUpgrade = useLastCallback(() => { const handleUpgrade = useLastCallback(() => {
const gift = renderingModal?.gift; const gift = renderingModal?.gift;
if (!gift) return;
const regularGift = gift.gift.type === 'starGift' ? gift.gift : undefined;
if (isPrepaid && gift.prepaidUpgradeHash && renderingRecipient) {
const upgradeStars = regularGift?.upgradeStars;
if (!upgradeStars) return;
upgradePrepaidGift({
peerId: renderingRecipient.id,
hash: gift.prepaidUpgradeHash,
stars: upgradeStars,
});
handleClose();
closeGiftInfoModal();
return;
}
if (!gift?.inputGift) return; if (!gift?.inputGift) return;
upgradeGift({ upgradeGift({
gift: gift.inputGift, gift: gift.inputGift,
shouldKeepOriginalDetails, shouldKeepOriginalDetails,
upgradeStars: !gift.alreadyPaidUpgradeStars ? (gift.gift as ApiStarGiftRegular).upgradeStars : undefined, upgradeStars: !gift.alreadyPaidUpgradeStars ? regularGift?.upgradeStars : undefined,
}); });
handleClose(); handleClose();
}); });
@ -109,16 +130,27 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
const gift = renderingModal?.gift; const gift = renderingModal?.gift;
const listItemData = [ const userName = renderingRecipient ? getPeerTitle(lang, renderingRecipient) : lang('ActionFallbackUser');
const listItemData = (renderingRecipient ? [
['diamond', lang('GiftUpgradeUniqueTitle'), lang('GiftPeerUpgradeUniqueDescription', { user: userName })],
['trade', lang('GiftUpgradeTransferableTitle'),
lang('GiftPeerUpgradeTransferableDescription', { user: userName })],
['auction', lang('GiftUpgradeTradeableTitle'), lang('GiftPeerUpgradeTradeableDescription', { user: userName })],
] : [
['diamond', lang('GiftUpgradeUniqueTitle'), lang('GiftUpgradeUniqueDescription')], ['diamond', lang('GiftUpgradeUniqueTitle'), lang('GiftUpgradeUniqueDescription')],
['trade', lang('GiftUpgradeTransferableTitle'), lang('GiftUpgradeTransferableDescription')], ['trade', lang('GiftUpgradeTransferableTitle'), lang('GiftUpgradeTransferableDescription')],
['auction', lang('GiftUpgradeTradeableTitle'), lang('GiftUpgradeTradeableDescription')], ['auction', lang('GiftUpgradeTradeableTitle'), lang('GiftUpgradeTradeableDescription')],
] satisfies TableAboutData; ]) satisfies TableAboutData;
const subtitle = renderingRecipient const subtitle = renderingRecipient
? lang('GiftPeerUpgradeText', { peer: getPeerTitle(lang, renderingRecipient) }) ? lang('GiftPeerUpgradeText', { peer: getPeerTitle(lang, renderingRecipient) })
: lang('GiftUpgradeTextOwn'); : lang('GiftUpgradeTextOwn');
const formattedPrice = gift
? formatStarsAsIcon(lang, (gift.gift as ApiStarGiftRegular).upgradeStars!, { asFont: true })
: undefined;
const header = ( const header = (
<UniqueGiftHeader <UniqueGiftHeader
modelAttribute={previewAttributes.model} modelAttribute={previewAttributes.model}
@ -138,17 +170,19 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
)} )}
{gift && ( {gift && (
<> <>
<Checkbox {!isPrepaid && (
label={lang('GiftUpgradeKeepDetails')} <Checkbox
onCheck={setShouldKeepOriginalDetails} label={lang('GiftUpgradeKeepDetails')}
checked={shouldKeepOriginalDetails} onCheck={setShouldKeepOriginalDetails}
/> checked={shouldKeepOriginalDetails}
/>
)}
<Button className={styles.footerButton} isShiny onClick={handleUpgrade}> <Button className={styles.footerButton} isShiny onClick={handleUpgrade}>
{gift.alreadyPaidUpgradeStars {gift.alreadyPaidUpgradeStars
? lang('GeneralConfirm') ? lang('GeneralConfirm')
: lang('GiftUpgradeButton', { : isPrepaid
amount: formatStarsAsIcon(lang, (gift.gift as ApiStarGiftRegular).upgradeStars!, { asFont: true }), ? lang('GiftPayForUpgradeButton', { amount: formattedPrice }, { withNodes: true })
}, { withNodes: true })} : lang('GiftUpgradeButton', { amount: formattedPrice }, { withNodes: true })}
</Button> </Button>
</> </>
)} )}
@ -160,7 +194,9 @@ const GiftUpgradeModal = ({ modal, recipient }: OwnProps & StateProps) => {
header, header,
footer, footer,
}; };
}, [previewAttributes, isOpen, lang, renderingRecipient, renderingModal?.gift, shouldKeepOriginalDetails]); }, [previewAttributes, isOpen, lang,
renderingRecipient, renderingModal?.gift,
shouldKeepOriginalDetails, isPrepaid]);
return ( return (
<TableAboutModal <TableAboutModal

View File

@ -29,6 +29,10 @@ export function getTransactionTitle(oldLang: OldLangFn, lang: LangFn, transactio
return lang('PostsSearchTransaction'); return lang('PostsSearchTransaction');
} }
if (transaction.isPrepaidUpgrade) {
return lang('GiftPrepaidUpgradeTransactionTitle');
}
if (transaction.starRefCommision) { if (transaction.starRefCommision) {
return oldLang('StarTransactionCommission', formatPercent(transaction.starRefCommision)); return oldLang('StarTransactionCommission', formatPercent(transaction.starRefCommision));
} }

View File

@ -1117,6 +1117,18 @@ addActionHandler('transferGift', (global, actions, payload): ActionReturnType =>
payInputStarInvoice(global, invoice, transferStars, tabId); payInputStarInvoice(global, invoice, transferStars, tabId);
}); });
addActionHandler('upgradePrepaidGift', (global, actions, payload): ActionReturnType => {
const { peerId, hash, stars, tabId = getCurrentTabId() } = payload;
const invoice: ApiInputInvoice = {
type: 'stargiftPrepaidUpgrade',
peerId,
hash,
};
payInputStarInvoice(global, invoice, stars, tabId);
});
async function payInputStarInvoice<T extends GlobalState>( async function payInputStarInvoice<T extends GlobalState>(
global: T, inputInvoice: ApiInputInvoice, price: number, global: T, inputInvoice: ApiInputInvoice, price: number,
...[tabId = getCurrentTabId()]: TabArgs<T> ...[tabId = getCurrentTabId()]: TabArgs<T>

View File

@ -2,10 +2,11 @@ import type { ActionReturnType } from '../../types';
import { formatCurrencyAsString } from '../../../util/formatCurrency'; import { formatCurrencyAsString } from '../../../util/formatCurrency';
import * as langProvider from '../../../util/oldLangProvider'; import * as langProvider from '../../../util/oldLangProvider';
import { getPeerTitle } from '../../helpers/peers';
import { addActionHandler, setGlobal } from '../../index'; import { addActionHandler, setGlobal } from '../../index';
import { updateStarsBalance } from '../../reducers'; import { updateStarsBalance } from '../../reducers';
import { updateTabState } from '../../reducers/tabs'; import { updateTabState } from '../../reducers/tabs';
import { selectTabState } from '../../selectors'; import { selectPeer, selectTabState } from '../../selectors';
addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
switch (update['@type']) { switch (update['@type']) {
@ -175,6 +176,24 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
actions.reloadPeerSavedGifts({ peerId: global.currentUserId }); actions.reloadPeerSavedGifts({ peerId: global.currentUserId });
} }
if (inputInvoice?.type === 'stargiftPrepaidUpgrade') {
actions.reloadPeerSavedGifts({ peerId: inputInvoice.peerId });
const lang = langProvider.getTranslationFn();
const peer = selectPeer(global, inputInvoice.peerId);
const peerTitle = peer ? getPeerTitle(lang, peer) : undefined;
actions.showNotification({
icon: 'gift',
title: { key: 'GiftUpgradeSentTitle' },
message: {
key: 'GiftUpgradeSentMessage',
variables: { user: peerTitle },
},
tabId,
});
}
break; break;
} }

View File

@ -233,12 +233,19 @@ addActionHandler('openGiftInfoModalFromMessage', async (global, actions, payload
const starGift = action.type === 'starGift' ? action : undefined; const starGift = action.type === 'starGift' ? action : undefined;
const uniqueGift = action.type === 'starGiftUnique' ? action : undefined; const uniqueGift = action.type === 'starGiftUnique' ? action : undefined;
const giftMsgId = starGift?.giftMsgId;
const giftReceiverId = action.peerId || (message.isOutgoing ? message.chatId : global.currentUserId!); const giftReceiverId = action.peerId || (message.isOutgoing ? message.chatId : global.currentUserId!);
const inputGift: ApiInputSavedStarGift = action.savedId const inputGift: ApiInputSavedStarGift = (() => {
? { type: 'chat', chatId, savedId: action.savedId } if (giftMsgId) {
: { type: 'user', messageId }; return { type: 'user', messageId: giftMsgId };
}
if (action.savedId) {
return { type: 'chat', chatId, savedId: action.savedId };
}
return { type: 'user', messageId };
})();
const fromId = action.fromId || (message.isOutgoing ? global.currentUserId! : message.chatId); const fromId = action.fromId || (message.isOutgoing ? global.currentUserId! : message.chatId);
@ -259,6 +266,7 @@ addActionHandler('openGiftInfoModalFromMessage', async (global, actions, payload
canExportAt: uniqueGift?.canExportAt, canExportAt: uniqueGift?.canExportAt,
savedId: action.savedId, savedId: action.savedId,
transferStars: uniqueGift?.transferStars, transferStars: uniqueGift?.transferStars,
prepaidUpgradeHash: starGift?.prepaidUpgradeHash,
}; };
actions.openGiftInfoModal({ peerId: giftReceiverId, gift, tabId }); actions.openGiftInfoModal({ peerId: giftReceiverId, gift, tabId });

View File

@ -235,6 +235,18 @@ export function getRequestInputInvoice<T extends GlobalState>(
}; };
} }
if (inputInvoice.type === 'stargiftPrepaidUpgrade') {
const { peerId, hash } = inputInvoice;
const peer = selectPeer(global, peerId);
if (!peer) return undefined;
return {
type: 'stargiftPrepaidUpgrade',
peer,
hash,
};
}
return undefined; return undefined;
} }

View File

@ -2629,6 +2629,11 @@ export interface ActionPayloads {
shouldKeepOriginalDetails?: boolean; shouldKeepOriginalDetails?: boolean;
upgradeStars?: number; upgradeStars?: number;
} & WithTabId; } & WithTabId;
upgradePrepaidGift: {
peerId: string;
hash: string;
stars: number;
} & WithTabId;
openGiftWithdrawModal: { openGiftWithdrawModal: {
gift: ApiSavedStarGift; gift: ApiSavedStarGift;

View File

@ -1303,6 +1303,7 @@ export interface LangPair {
'GiftUpgradeKeepDetails': undefined; 'GiftUpgradeKeepDetails': undefined;
'GiftUpgradedTitle': undefined; 'GiftUpgradedTitle': undefined;
'GiftUpgradedDescription': undefined; 'GiftUpgradedDescription': undefined;
'GiftUpgradeSentTitle': undefined;
'GiftMakeUniqueAcc': undefined; 'GiftMakeUniqueAcc': undefined;
'GiftMakeUniqueLink': undefined; 'GiftMakeUniqueLink': undefined;
'GiftWithdrawTitle': undefined; 'GiftWithdrawTitle': undefined;
@ -1716,6 +1717,9 @@ export interface LangPair {
'ConfirmBuyGiftForTonDescription': undefined; 'ConfirmBuyGiftForTonDescription': undefined;
'TitleGiftLocked': undefined; 'TitleGiftLocked': undefined;
'QuickPreview': undefined; 'QuickPreview': undefined;
'GiftAnUpgradeButton': undefined;
'GiftPrepaidUpgradeTransactionTitle': undefined;
'ActionStarGiftPrepaidUpgradedYou': undefined;
'UserNoteTitle': undefined; 'UserNoteTitle': undefined;
'UserNoteHint': undefined; 'UserNoteHint': undefined;
'EditUserNoteHint': undefined; 'EditUserNoteHint': undefined;
@ -2193,6 +2197,21 @@ export interface LangPairWithVariables<V = LangVariable> {
'GiftUpgradeButton': { 'GiftUpgradeButton': {
'amount': V; 'amount': V;
}; };
'GiftPayForUpgradeButton': {
'amount': V;
};
'GiftPeerUpgradeUniqueDescription': {
'user': V;
};
'GiftPeerUpgradeTransferableDescription': {
'user': V;
};
'GiftPeerUpgradeTradeableDescription': {
'user': V;
};
'GiftUpgradeSentMessage': {
'user': V;
};
'GiftMakeUnique': { 'GiftMakeUnique': {
'stars': V; 'stars': V;
}; };
@ -2486,6 +2505,14 @@ export interface LangPairWithVariables<V = LangVariable> {
'channel': V; 'channel': V;
'cost': V; 'cost': V;
}; };
'ActionStarGiftPrepaidUpgradeYou': {
'cost': V;
'peer': V;
};
'ActionStarGiftPrepaidUpgrade': {
'peer': V;
'cost': V;
};
'ActionStarGiftSelfBought': { 'ActionStarGiftSelfBought': {
'cost': V; 'cost': V;
}; };
@ -2945,6 +2972,9 @@ export interface LangPairWithVariables<V = LangVariable> {
'GiftLockedMessage': { 'GiftLockedMessage': {
'relativeDate': V; 'relativeDate': V;
}; };
'ActionStarGiftPrepaidUpgraded': {
'user': V;
};
} }
export interface LangPairPlural { export interface LangPairPlural {